mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-15 05:09:29 +00:00
* Notify the LS about the new `build_path` after verify. Closes #714 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
@@ -251,11 +251,14 @@ export class ArduinoFrontendContribution
|
||||
);
|
||||
});
|
||||
|
||||
const start = async ({ selectedBoard }: BoardsConfig.Config) => {
|
||||
const start = async (
|
||||
{ selectedBoard }: BoardsConfig.Config,
|
||||
forceStart = false
|
||||
) => {
|
||||
if (selectedBoard) {
|
||||
const { name, fqbn } = selectedBoard;
|
||||
if (fqbn) {
|
||||
this.startLanguageServer(fqbn, name);
|
||||
this.startLanguageServer(fqbn, name, forceStart);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -270,7 +273,8 @@ export class ArduinoFrontendContribution
|
||||
if (event.newValue !== event.oldValue) {
|
||||
switch (event.preferenceName) {
|
||||
case 'arduino.language.log':
|
||||
start(this.boardsServiceClientImpl.boardsConfig);
|
||||
case 'arduino.language.realTimeDiagnostics':
|
||||
start(this.boardsServiceClientImpl.boardsConfig, true);
|
||||
break;
|
||||
case 'arduino.window.zoomLevel':
|
||||
if (typeof event.newValue === 'number') {
|
||||
@@ -318,7 +322,8 @@ export class ArduinoFrontendContribution
|
||||
protected languageServerStartMutex = new Mutex();
|
||||
protected async startLanguageServer(
|
||||
fqbn: string,
|
||||
name: string | undefined
|
||||
name: string | undefined,
|
||||
forceStart = false
|
||||
): Promise<void> {
|
||||
const port = await this.daemon.tryGetPort();
|
||||
if (!port) {
|
||||
@@ -352,12 +357,15 @@ export class ArduinoFrontendContribution
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (fqbn === this.languageServerFqbn) {
|
||||
if (!forceStart && fqbn === this.languageServerFqbn) {
|
||||
// NOOP
|
||||
return;
|
||||
}
|
||||
this.logger.info(`Starting language server: ${fqbn}`);
|
||||
const log = this.arduinoPreferences.get('arduino.language.log');
|
||||
const realTimeDiagnostics = this.arduinoPreferences.get(
|
||||
'arduino.language.realTimeDiagnostics'
|
||||
);
|
||||
let currentSketchPath: string | undefined = undefined;
|
||||
if (log) {
|
||||
const currentSketch = await this.sketchServiceClient.currentSketch();
|
||||
@@ -388,6 +396,7 @@ export class ArduinoFrontendContribution
|
||||
clangdPath,
|
||||
log: currentSketchPath ? currentSketchPath : log,
|
||||
cliDaemonInstance: '1',
|
||||
realTimeDiagnostics,
|
||||
board: {
|
||||
fqbn,
|
||||
name: name ? `"${name}"` : undefined,
|
||||
|
||||
@@ -51,6 +51,14 @@ export const ArduinoConfigSchema: PreferenceSchema = {
|
||||
),
|
||||
default: false,
|
||||
},
|
||||
'arduino.language.realTimeDiagnostics': {
|
||||
type: 'boolean',
|
||||
description: nls.localize(
|
||||
'arduino/preferences/language.realTimeDiagnostics',
|
||||
"If true, the language server provides real-time diagnostics when typing in the editor. It's false by default."
|
||||
),
|
||||
default: false,
|
||||
},
|
||||
'arduino.compile.verbose': {
|
||||
type: 'boolean',
|
||||
description: nls.localize(
|
||||
@@ -238,6 +246,7 @@ export const ArduinoConfigSchema: PreferenceSchema = {
|
||||
|
||||
export interface ArduinoConfiguration {
|
||||
'arduino.language.log': boolean;
|
||||
'arduino.language.realTimeDiagnostics': boolean;
|
||||
'arduino.compile.verbose': boolean;
|
||||
'arduino.compile.experimental': boolean;
|
||||
'arduino.compile.revealRange': ErrorRevealStrategy;
|
||||
|
||||
@@ -275,7 +275,7 @@ export class CompilerErrors
|
||||
}
|
||||
|
||||
private async handleCompilerErrorsDidChange(
|
||||
errors: CoreError.Compiler[]
|
||||
errors: CoreError.ErrorLocation[]
|
||||
): Promise<void> {
|
||||
this.toDisposeOnCompilerErrorDidChange.dispose();
|
||||
const compilerErrorsPerResource = this.groupByResource(
|
||||
@@ -312,8 +312,8 @@ export class CompilerErrors
|
||||
}
|
||||
|
||||
private async filter(
|
||||
errors: CoreError.Compiler[]
|
||||
): Promise<CoreError.Compiler[]> {
|
||||
errors: CoreError.ErrorLocation[]
|
||||
): Promise<CoreError.ErrorLocation[]> {
|
||||
if (!errors.length) {
|
||||
return [];
|
||||
}
|
||||
@@ -326,7 +326,7 @@ export class CompilerErrors
|
||||
}
|
||||
|
||||
private async decorateEditors(
|
||||
errors: Map<string, CoreError.Compiler[]>
|
||||
errors: Map<string, CoreError.ErrorLocation[]>
|
||||
): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> {
|
||||
const composite = await Promise.all(
|
||||
[...errors.entries()].map(([uri, errors]) =>
|
||||
@@ -346,7 +346,7 @@ export class CompilerErrors
|
||||
|
||||
private async decorateEditor(
|
||||
uri: string,
|
||||
errors: CoreError.Compiler[]
|
||||
errors: CoreError.ErrorLocation[]
|
||||
): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> {
|
||||
const editor = await this.editorManager.getByUri(new URI(uri));
|
||||
if (!editor) {
|
||||
@@ -523,7 +523,7 @@ export class CompilerErrors
|
||||
}
|
||||
|
||||
private async trackEditors(
|
||||
errors: Map<string, CoreError.Compiler[]>,
|
||||
errors: Map<string, CoreError.ErrorLocation[]>,
|
||||
...track: ((editor: EditorWidget) => Disposable)[]
|
||||
): Promise<Disposable> {
|
||||
return new DisposableCollection(
|
||||
@@ -605,8 +605,8 @@ export class CompilerErrors
|
||||
}
|
||||
|
||||
private groupByResource(
|
||||
errors: CoreError.Compiler[]
|
||||
): Map<string, CoreError.Compiler[]> {
|
||||
errors: CoreError.ErrorLocation[]
|
||||
): Map<string, CoreError.ErrorLocation[]> {
|
||||
return errors.reduce((acc, curr) => {
|
||||
const {
|
||||
location: { uri },
|
||||
@@ -618,7 +618,7 @@ export class CompilerErrors
|
||||
}
|
||||
errors.push(curr);
|
||||
return acc;
|
||||
}, new Map<string, CoreError.Compiler[]>());
|
||||
}, new Map<string, CoreError.ErrorLocation[]>());
|
||||
}
|
||||
|
||||
private monacoEditor(widget: EditorWidget): MonacoEditor | undefined;
|
||||
|
||||
@@ -4,29 +4,29 @@ import { CoreError } from '../../common/protocol/core-service';
|
||||
|
||||
@injectable()
|
||||
export class CoreErrorHandler {
|
||||
private readonly compilerErrors: CoreError.Compiler[] = [];
|
||||
private readonly errors: CoreError.ErrorLocation[] = [];
|
||||
private readonly compilerErrorsDidChangeEmitter = new Emitter<
|
||||
CoreError.Compiler[]
|
||||
CoreError.ErrorLocation[]
|
||||
>();
|
||||
|
||||
tryHandle(error: unknown): void {
|
||||
if (CoreError.is(error)) {
|
||||
this.compilerErrors.length = 0;
|
||||
this.compilerErrors.push(...error.data.filter(CoreError.Compiler.is));
|
||||
this.errors.length = 0;
|
||||
this.errors.push(...error.data);
|
||||
this.fireCompilerErrorsDidChange();
|
||||
}
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.compilerErrors.length = 0;
|
||||
this.errors.length = 0;
|
||||
this.fireCompilerErrorsDidChange();
|
||||
}
|
||||
|
||||
get onCompilerErrorsDidChange(): Event<CoreError.Compiler[]> {
|
||||
get onCompilerErrorsDidChange(): Event<CoreError.ErrorLocation[]> {
|
||||
return this.compilerErrorsDidChangeEmitter.event;
|
||||
}
|
||||
|
||||
private fireCompilerErrorsDidChange(): void {
|
||||
this.compilerErrorsDidChangeEmitter.fire(this.compilerErrors.slice());
|
||||
this.compilerErrorsDidChangeEmitter.fire(this.errors.slice());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import type {
|
||||
BoardUserField,
|
||||
Port,
|
||||
} from '../../common/protocol/boards-service';
|
||||
import type { ErrorInfo as CliErrorInfo } from '../../node/cli-error-parser';
|
||||
import type { Programmer } from './boards-service';
|
||||
import type { Sketch } from './sketches-service';
|
||||
|
||||
@@ -17,16 +16,10 @@ export const CompilerWarningLiterals = [
|
||||
] as const;
|
||||
export type CompilerWarnings = typeof CompilerWarningLiterals[number];
|
||||
export namespace CoreError {
|
||||
export type ErrorInfo = CliErrorInfo;
|
||||
export interface Compiler extends ErrorInfo {
|
||||
export interface ErrorLocation {
|
||||
readonly message: string;
|
||||
readonly location: Location;
|
||||
}
|
||||
export namespace Compiler {
|
||||
export function is(error: ErrorInfo): error is Compiler {
|
||||
const { message, location } = error;
|
||||
return !!message && !!location;
|
||||
}
|
||||
readonly details?: string;
|
||||
}
|
||||
export const Codes = {
|
||||
Verify: 4001,
|
||||
@@ -42,7 +35,7 @@ export namespace CoreError {
|
||||
export const BurnBootloaderFailed = create(Codes.BurnBootloader);
|
||||
export function is(
|
||||
error: unknown
|
||||
): error is ApplicationError<number, ErrorInfo[]> {
|
||||
): error is ApplicationError<number, ErrorLocation[]> {
|
||||
return (
|
||||
error instanceof Error &&
|
||||
ApplicationError.is(error) &&
|
||||
@@ -51,10 +44,10 @@ export namespace CoreError {
|
||||
}
|
||||
function create(
|
||||
code: number
|
||||
): ApplicationError.Constructor<number, ErrorInfo[]> {
|
||||
): ApplicationError.Constructor<number, ErrorLocation[]> {
|
||||
return ApplicationError.declare(
|
||||
code,
|
||||
(message: string, data: ErrorInfo[]) => {
|
||||
(message: string, data: ErrorLocation[]) => {
|
||||
return {
|
||||
data,
|
||||
message,
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
import { notEmpty } from '@theia/core';
|
||||
import { notEmpty } from '@theia/core/lib/common/objects';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
import { FileUri } from '@theia/core/lib/node/file-uri';
|
||||
import {
|
||||
Location,
|
||||
Range,
|
||||
Position,
|
||||
} from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import { Sketch } from '../common/protocol';
|
||||
import type { CoreError } from '../common/protocol';
|
||||
import { Sketch } from '../common/protocol/sketches-service';
|
||||
|
||||
export interface ErrorInfo {
|
||||
readonly message?: string;
|
||||
readonly location?: Location;
|
||||
readonly details?: string;
|
||||
}
|
||||
export interface ErrorSource {
|
||||
readonly content: string | ReadonlyArray<Uint8Array>;
|
||||
readonly sketch?: Sketch;
|
||||
}
|
||||
|
||||
export function tryParseError(source: ErrorSource): ErrorInfo[] {
|
||||
export function tryParseError(source: ErrorSource): CoreError.ErrorLocation[] {
|
||||
const { content, sketch } = source;
|
||||
const err =
|
||||
typeof content === 'string'
|
||||
@@ -28,7 +23,7 @@ export function tryParseError(source: ErrorSource): ErrorInfo[] {
|
||||
return tryParse(err)
|
||||
.map(remapErrorMessages)
|
||||
.filter(isLocationInSketch(sketch))
|
||||
.map(errorInfo());
|
||||
.map(toErrorInfo);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
@@ -50,9 +45,7 @@ namespace ParseResult {
|
||||
}
|
||||
}
|
||||
|
||||
function isLocationInSketch(
|
||||
sketch: Sketch
|
||||
): (value: ParseResult, index: number, array: ParseResult[]) => unknown {
|
||||
function isLocationInSketch(sketch: Sketch): (result: ParseResult) => boolean {
|
||||
return (result) => {
|
||||
const uri = FileUri.create(result.path).toString();
|
||||
if (!Sketch.isInSketch(uri, sketch)) {
|
||||
@@ -65,15 +58,21 @@ function isLocationInSketch(
|
||||
};
|
||||
}
|
||||
|
||||
function errorInfo(): (value: ParseResult) => ErrorInfo {
|
||||
return ({ error, message, path, line, column }) => ({
|
||||
function toErrorInfo({
|
||||
error,
|
||||
message,
|
||||
path,
|
||||
line,
|
||||
column,
|
||||
}: ParseResult): CoreError.ErrorLocation {
|
||||
return {
|
||||
message: error,
|
||||
details: message,
|
||||
location: {
|
||||
uri: FileUri.create(path).toString(),
|
||||
range: range(line, column),
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function range(line: number, column?: number): Range {
|
||||
|
||||
@@ -26,7 +26,7 @@ import { ResponseService } from '../common/protocol/response-service';
|
||||
import { Board, OutputMessage, Port, Status } from '../common/protocol';
|
||||
import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
|
||||
import { Port as GrpcPort } from './cli-protocol/cc/arduino/cli/commands/v1/port_pb';
|
||||
import { ApplicationError, Disposable, nls } from '@theia/core';
|
||||
import { ApplicationError, CommandService, Disposable, nls } from '@theia/core';
|
||||
import { MonitorManager } from './monitor-manager';
|
||||
import { AutoFlushingBuffer } from './utils/buffers';
|
||||
import { tryParseError } from './cli-error-parser';
|
||||
@@ -42,6 +42,9 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
@inject(MonitorManager)
|
||||
private readonly monitorManager: MonitorManager;
|
||||
|
||||
@inject(CommandService)
|
||||
private readonly commandService: CommandService;
|
||||
|
||||
async compile(
|
||||
options: CoreService.Compile.Options & {
|
||||
exportBinaries?: boolean;
|
||||
@@ -50,7 +53,19 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
): Promise<void> {
|
||||
const coreClient = await this.coreClient;
|
||||
const { client, instance } = coreClient;
|
||||
const handler = this.createOnDataHandler();
|
||||
let buildPath: string | undefined = undefined;
|
||||
const handler = this.createOnDataHandler<CompileResponse>((response) => {
|
||||
const currentBuildPath = response.getBuildPath();
|
||||
if (!buildPath && currentBuildPath) {
|
||||
buildPath = currentBuildPath;
|
||||
} else {
|
||||
if (!!currentBuildPath && currentBuildPath !== buildPath) {
|
||||
throw new Error(
|
||||
`The CLI has already provided a build path: <${buildPath}>, and there is a new build path value: <${currentBuildPath}>.`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
const request = this.compileRequest(options, instance);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
client
|
||||
@@ -84,7 +99,36 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
}
|
||||
})
|
||||
.on('end', resolve);
|
||||
}).finally(() => handler.dispose());
|
||||
}).finally(() => {
|
||||
handler.dispose();
|
||||
if (!buildPath) {
|
||||
console.error(
|
||||
`Have not received the build path from the CLI while running the compilation.`
|
||||
);
|
||||
} else {
|
||||
this.fireBuildDidComplete(FileUri.create(buildPath).toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// This executes on the frontend, the VS Code extension receives it, and sends an `ino/buildDidComplete` notification to the language server.
|
||||
private fireBuildDidComplete(buildOutputUri: string): void {
|
||||
const params = {
|
||||
buildOutputUri,
|
||||
};
|
||||
console.info(
|
||||
`Executing 'arduino.languageserver.notifyBuildDidComplete' with ${JSON.stringify(
|
||||
params
|
||||
)}`
|
||||
);
|
||||
this.commandService
|
||||
.executeCommand('arduino.languageserver.notifyBuildDidComplete', params)
|
||||
.catch((err) =>
|
||||
console.error(
|
||||
`Unexpected error when firing event on build did complete. ${buildOutputUri}`,
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private compileRequest(
|
||||
@@ -124,8 +168,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
options,
|
||||
() => new UploadRequest(),
|
||||
(client, req) => client.upload(req),
|
||||
(message: string, info: CoreError.ErrorInfo[]) =>
|
||||
CoreError.UploadFailed(message, info),
|
||||
(message: string, locations: CoreError.ErrorLocation[]) =>
|
||||
CoreError.UploadFailed(message, locations),
|
||||
'upload'
|
||||
);
|
||||
}
|
||||
@@ -137,8 +181,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
options,
|
||||
() => new UploadUsingProgrammerRequest(),
|
||||
(client, req) => client.uploadUsingProgrammer(req),
|
||||
(message: string, info: CoreError.ErrorInfo[]) =>
|
||||
CoreError.UploadUsingProgrammerFailed(message, info),
|
||||
(message: string, locations: CoreError.ErrorLocation[]) =>
|
||||
CoreError.UploadUsingProgrammerFailed(message, locations),
|
||||
'upload using programmer'
|
||||
);
|
||||
}
|
||||
@@ -152,8 +196,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
) => ClientReadableStream<UploadResponse | UploadUsingProgrammerResponse>,
|
||||
errorHandler: (
|
||||
message: string,
|
||||
info: CoreError.ErrorInfo[]
|
||||
) => ApplicationError<number, CoreError.ErrorInfo[]>,
|
||||
locations: CoreError.ErrorLocation[]
|
||||
) => ApplicationError<number, CoreError.ErrorLocation[]>,
|
||||
task: string
|
||||
): Promise<void> {
|
||||
await this.compile(Object.assign(options, { exportBinaries: false }));
|
||||
@@ -285,7 +329,9 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
return request;
|
||||
}
|
||||
|
||||
private createOnDataHandler<R extends StreamingResponse>(): Disposable & {
|
||||
private createOnDataHandler<R extends StreamingResponse>(
|
||||
onResponse?: (response: R) => void
|
||||
): Disposable & {
|
||||
stderr: Buffer[];
|
||||
onData: (response: R) => void;
|
||||
} {
|
||||
@@ -297,10 +343,14 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
||||
}
|
||||
});
|
||||
});
|
||||
const onData = StreamingResponse.createOnDataHandler(stderr, (out, err) => {
|
||||
buffer.addChunk(out);
|
||||
buffer.addChunk(err, OutputMessage.Severity.Error);
|
||||
});
|
||||
const onData = StreamingResponse.createOnDataHandler(
|
||||
stderr,
|
||||
(out, err) => {
|
||||
buffer.addChunk(out);
|
||||
buffer.addChunk(err, OutputMessage.Severity.Error);
|
||||
},
|
||||
onResponse
|
||||
);
|
||||
return {
|
||||
dispose: () => buffer.dispose(),
|
||||
stderr,
|
||||
@@ -369,13 +419,17 @@ namespace StreamingResponse {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function createOnDataHandler<R extends StreamingResponse>(
|
||||
stderr: Uint8Array[],
|
||||
onData: (out: Uint8Array, err: Uint8Array) => void
|
||||
onData: (out: Uint8Array, err: Uint8Array) => void,
|
||||
onResponse?: (response: R) => void
|
||||
): (response: R) => void {
|
||||
return (response: R) => {
|
||||
const out = response.getOutStream_asU8();
|
||||
const err = response.getErrStream_asU8();
|
||||
stderr.push(err);
|
||||
onData(out, err);
|
||||
if (onResponse) {
|
||||
onResponse(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user