mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-11-05 08:28:32 +00:00
Reveal the error location after on failed verify.
Closes #608 Closes #229 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
@@ -0,0 +1,656 @@
|
||||
import {
|
||||
Command,
|
||||
CommandRegistry,
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
Emitter,
|
||||
MaybePromise,
|
||||
nls,
|
||||
notEmpty,
|
||||
} from '@theia/core';
|
||||
import { ApplicationShell, FrontendApplication } from '@theia/core/lib/browser';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
Location,
|
||||
Range,
|
||||
} from '@theia/core/shared/vscode-languageserver-protocol';
|
||||
import {
|
||||
EditorWidget,
|
||||
TextDocumentChangeEvent,
|
||||
} from '@theia/editor/lib/browser';
|
||||
import {
|
||||
EditorDecoration,
|
||||
TrackedRangeStickiness,
|
||||
} from '@theia/editor/lib/browser/decorations/editor-decoration';
|
||||
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
|
||||
import * as monaco from '@theia/monaco-editor-core';
|
||||
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
|
||||
import { MonacoToProtocolConverter } from '@theia/monaco/lib/browser/monaco-to-protocol-converter';
|
||||
import { ProtocolToMonacoConverter } from '@theia/monaco/lib/browser/protocol-to-monaco-converter';
|
||||
import { CoreError } from '../../common/protocol/core-service';
|
||||
import {
|
||||
ArduinoPreferences,
|
||||
ErrorRevealStrategy,
|
||||
} from '../arduino-preferences';
|
||||
import { InoSelector } from '../ino-selectors';
|
||||
import { fullRange } from '../utils/monaco';
|
||||
import { Contribution } from './contribution';
|
||||
import { CoreErrorHandler } from './core-error-handler';
|
||||
|
||||
interface ErrorDecoration {
|
||||
/**
|
||||
* This is the unique ID of the decoration given by `monaco`.
|
||||
*/
|
||||
readonly id: string;
|
||||
/**
|
||||
* The resource this decoration belongs to.
|
||||
*/
|
||||
readonly uri: string;
|
||||
}
|
||||
namespace ErrorDecoration {
|
||||
export function rangeOf(
|
||||
{ id, uri }: ErrorDecoration,
|
||||
editorProvider: (uri: string) => Promise<MonacoEditor | undefined>
|
||||
): Promise<monaco.Range | undefined>;
|
||||
export function rangeOf(
|
||||
{ id, uri }: ErrorDecoration,
|
||||
editorProvider: MonacoEditor
|
||||
): monaco.Range | undefined;
|
||||
export function rangeOf(
|
||||
{ id, uri }: ErrorDecoration,
|
||||
editorProvider:
|
||||
| ((uri: string) => Promise<MonacoEditor | undefined>)
|
||||
| MonacoEditor
|
||||
): MaybePromise<monaco.Range | undefined> {
|
||||
if (editorProvider instanceof MonacoEditor) {
|
||||
const control = editorProvider.getControl();
|
||||
const model = control.getModel();
|
||||
if (model) {
|
||||
return control
|
||||
.getDecorationsInRange(fullRange(model))
|
||||
?.find(({ id: candidateId }) => id === candidateId)?.range;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
return editorProvider(uri).then((editor) => {
|
||||
if (editor) {
|
||||
return rangeOf({ id, uri }, editor);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
// export async function rangeOf(
|
||||
// { id, uri }: ErrorDecoration,
|
||||
// editorProvider:
|
||||
// | ((uri: string) => Promise<MonacoEditor | undefined>)
|
||||
// | MonacoEditor
|
||||
// ): Promise<monaco.Range | undefined> {
|
||||
// const editor =
|
||||
// editorProvider instanceof MonacoEditor
|
||||
// ? editorProvider
|
||||
// : await editorProvider(uri);
|
||||
// if (editor) {
|
||||
// const control = editor.getControl();
|
||||
// const model = control.getModel();
|
||||
// if (model) {
|
||||
// return control
|
||||
// .getDecorationsInRange(fullRange(model))
|
||||
// ?.find(({ id: candidateId }) => id === candidateId)?.range;
|
||||
// }
|
||||
// }
|
||||
// return undefined;
|
||||
// }
|
||||
export function sameAs(
|
||||
left: ErrorDecoration,
|
||||
right: ErrorDecoration
|
||||
): boolean {
|
||||
return left.id === right.id && left.uri === right.uri;
|
||||
}
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class CompilerErrors
|
||||
extends Contribution
|
||||
implements monaco.languages.CodeLensProvider
|
||||
{
|
||||
@inject(EditorManager)
|
||||
private readonly editorManager: EditorManager;
|
||||
|
||||
@inject(ProtocolToMonacoConverter)
|
||||
private readonly p2m: ProtocolToMonacoConverter;
|
||||
|
||||
@inject(MonacoToProtocolConverter)
|
||||
private readonly mp2: MonacoToProtocolConverter;
|
||||
|
||||
@inject(CoreErrorHandler)
|
||||
private readonly coreErrorHandler: CoreErrorHandler;
|
||||
|
||||
@inject(ArduinoPreferences)
|
||||
private readonly preferences: ArduinoPreferences;
|
||||
|
||||
private readonly errors: ErrorDecoration[] = [];
|
||||
private readonly onDidChangeEmitter = new monaco.Emitter<this>();
|
||||
private readonly currentErrorDidChangEmitter = new Emitter<ErrorDecoration>();
|
||||
private readonly onCurrentErrorDidChange =
|
||||
this.currentErrorDidChangEmitter.event;
|
||||
private readonly toDisposeOnCompilerErrorDidChange =
|
||||
new DisposableCollection();
|
||||
private shell: ApplicationShell | undefined;
|
||||
private revealStrategy = ErrorRevealStrategy.Default;
|
||||
private currentError: ErrorDecoration | undefined;
|
||||
private get currentErrorIndex(): number {
|
||||
const current = this.currentError;
|
||||
if (!current) {
|
||||
return -1;
|
||||
}
|
||||
return this.errors.findIndex((error) =>
|
||||
ErrorDecoration.sameAs(error, current)
|
||||
);
|
||||
}
|
||||
|
||||
override onStart(app: FrontendApplication): void {
|
||||
this.shell = app.shell;
|
||||
monaco.languages.registerCodeLensProvider(InoSelector, this);
|
||||
this.coreErrorHandler.onCompilerErrorsDidChange((errors) =>
|
||||
this.filter(errors).then(this.handleCompilerErrorsDidChange.bind(this))
|
||||
);
|
||||
this.onCurrentErrorDidChange(async (error) => {
|
||||
const range = await ErrorDecoration.rangeOf(error, (uri) =>
|
||||
this.monacoEditor(uri)
|
||||
);
|
||||
if (!range) {
|
||||
console.warn(
|
||||
'compiler-errors',
|
||||
`Could not find range of decoration: ${error.id}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const editor = await this.revealLocationInEditor({
|
||||
uri: error.uri,
|
||||
range: this.mp2.asRange(range),
|
||||
});
|
||||
if (!editor) {
|
||||
console.warn(
|
||||
'compiler-errors',
|
||||
`Failed to mark error ${error.id} as the current one.`
|
||||
);
|
||||
}
|
||||
});
|
||||
this.preferences.ready.then(() => {
|
||||
this.preferences.onPreferenceChanged(({ preferenceName, newValue }) => {
|
||||
if (preferenceName === 'arduino.compile.revealRange') {
|
||||
this.revealStrategy = ErrorRevealStrategy.is(newValue)
|
||||
? newValue
|
||||
: ErrorRevealStrategy.Default;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
override registerCommands(registry: CommandRegistry): void {
|
||||
registry.registerCommand(CompilerErrors.Commands.NEXT_ERROR, {
|
||||
execute: () => {
|
||||
const index = this.currentErrorIndex;
|
||||
if (index < 0) {
|
||||
console.warn(
|
||||
'compiler-errors',
|
||||
`Could not advance to next error. Unknown current error.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const nextError =
|
||||
this.errors[index === this.errors.length - 1 ? 0 : index + 1];
|
||||
this.markAsCurrentError(nextError);
|
||||
},
|
||||
isEnabled: () => !!this.currentError && this.errors.length > 1,
|
||||
});
|
||||
registry.registerCommand(CompilerErrors.Commands.PREVIOUS_ERROR, {
|
||||
execute: () => {
|
||||
const index = this.currentErrorIndex;
|
||||
if (index < 0) {
|
||||
console.warn(
|
||||
'compiler-errors',
|
||||
`Could not advance to previous error. Unknown current error.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const previousError =
|
||||
this.errors[index === 0 ? this.errors.length - 1 : index - 1];
|
||||
this.markAsCurrentError(previousError);
|
||||
},
|
||||
isEnabled: () => !!this.currentError && this.errors.length > 1,
|
||||
});
|
||||
}
|
||||
|
||||
get onDidChange(): monaco.IEvent<this> {
|
||||
return this.onDidChangeEmitter.event;
|
||||
}
|
||||
|
||||
async provideCodeLenses(
|
||||
model: monaco.editor.ITextModel,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_token: monaco.CancellationToken
|
||||
): Promise<monaco.languages.CodeLensList> {
|
||||
const lenses: monaco.languages.CodeLens[] = [];
|
||||
if (
|
||||
this.currentError &&
|
||||
this.currentError.uri === model.uri.toString() &&
|
||||
this.errors.length > 1
|
||||
) {
|
||||
const range = await ErrorDecoration.rangeOf(this.currentError, (uri) =>
|
||||
this.monacoEditor(uri)
|
||||
);
|
||||
if (range) {
|
||||
lenses.push(
|
||||
{
|
||||
range,
|
||||
command: {
|
||||
id: CompilerErrors.Commands.PREVIOUS_ERROR.id,
|
||||
title: nls.localize(
|
||||
'arduino/editor/previousError',
|
||||
'Previous Error'
|
||||
),
|
||||
arguments: [this.currentError],
|
||||
},
|
||||
},
|
||||
{
|
||||
range,
|
||||
command: {
|
||||
id: CompilerErrors.Commands.NEXT_ERROR.id,
|
||||
title: nls.localize('arduino/editor/nextError', 'Next Error'),
|
||||
arguments: [this.currentError],
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
return {
|
||||
lenses,
|
||||
dispose: () => {
|
||||
/* NOOP */
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private async handleCompilerErrorsDidChange(
|
||||
errors: CoreError.Compiler[]
|
||||
): Promise<void> {
|
||||
this.toDisposeOnCompilerErrorDidChange.dispose();
|
||||
const compilerErrorsPerResource = this.groupByResource(
|
||||
await this.filter(errors)
|
||||
);
|
||||
const decorations = await this.decorateEditors(compilerErrorsPerResource);
|
||||
this.errors.push(...decorations.errors);
|
||||
this.toDisposeOnCompilerErrorDidChange.pushAll([
|
||||
Disposable.create(() => (this.errors.length = 0)),
|
||||
Disposable.create(() => this.onDidChangeEmitter.fire(this)),
|
||||
...(await Promise.all([
|
||||
decorations.dispose,
|
||||
this.trackEditors(
|
||||
compilerErrorsPerResource,
|
||||
(editor) =>
|
||||
editor.editor.onSelectionChanged((selection) =>
|
||||
this.handleSelectionChange(editor, selection)
|
||||
),
|
||||
(editor) =>
|
||||
editor.onDidDispose(() =>
|
||||
this.handleEditorDidDispose(editor.editor.uri.toString())
|
||||
),
|
||||
(editor) =>
|
||||
editor.editor.onDocumentContentChanged((event) =>
|
||||
this.handleDocumentContentChange(editor, event)
|
||||
)
|
||||
),
|
||||
])),
|
||||
]);
|
||||
const currentError = this.errors[0];
|
||||
if (currentError) {
|
||||
await this.markAsCurrentError(currentError);
|
||||
}
|
||||
}
|
||||
|
||||
private async filter(
|
||||
errors: CoreError.Compiler[]
|
||||
): Promise<CoreError.Compiler[]> {
|
||||
if (!errors.length) {
|
||||
return [];
|
||||
}
|
||||
await this.preferences.ready;
|
||||
if (this.preferences['arduino.compile.experimental']) {
|
||||
return errors;
|
||||
}
|
||||
// Always shows maximum one error; hence the code lens navigation is unavailable.
|
||||
return [errors[0]];
|
||||
}
|
||||
|
||||
private async decorateEditors(
|
||||
errors: Map<string, CoreError.Compiler[]>
|
||||
): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> {
|
||||
const composite = await Promise.all(
|
||||
[...errors.entries()].map(([uri, errors]) =>
|
||||
this.decorateEditor(uri, errors)
|
||||
)
|
||||
);
|
||||
return {
|
||||
dispose: new DisposableCollection(
|
||||
...composite.map(({ dispose }) => dispose)
|
||||
),
|
||||
errors: composite.reduce(
|
||||
(acc, { errors }) => acc.concat(errors),
|
||||
[] as ErrorDecoration[]
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
private async decorateEditor(
|
||||
uri: string,
|
||||
errors: CoreError.Compiler[]
|
||||
): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> {
|
||||
const editor = await this.editorManager.getByUri(new URI(uri));
|
||||
if (!editor) {
|
||||
return { dispose: Disposable.NULL, errors: [] };
|
||||
}
|
||||
const oldDecorations = editor.editor.deltaDecorations({
|
||||
oldDecorations: [],
|
||||
newDecorations: errors.map((error) =>
|
||||
this.compilerErrorDecoration(error.location.range)
|
||||
),
|
||||
});
|
||||
return {
|
||||
dispose: Disposable.create(() => {
|
||||
if (editor) {
|
||||
editor.editor.deltaDecorations({
|
||||
oldDecorations,
|
||||
newDecorations: [],
|
||||
});
|
||||
}
|
||||
}),
|
||||
errors: oldDecorations.map((id) => ({ id, uri })),
|
||||
};
|
||||
}
|
||||
|
||||
private compilerErrorDecoration(range: Range): EditorDecoration {
|
||||
return {
|
||||
range,
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
className: 'compiler-error',
|
||||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the selection in all editors that have an error. If the editor selection overlaps one of the compiler error's range, mark as current error.
|
||||
*/
|
||||
private handleSelectionChange(editor: EditorWidget, selection: Range): void {
|
||||
const monacoEditor = this.monacoEditor(editor);
|
||||
if (!monacoEditor) {
|
||||
return;
|
||||
}
|
||||
const uri = monacoEditor.uri.toString();
|
||||
const monacoSelection = this.p2m.asRange(selection);
|
||||
console.log(
|
||||
'compiler-errors',
|
||||
`Handling selection change in editor ${uri}. New (monaco) selection: ${monacoSelection.toJSON()}`
|
||||
);
|
||||
const calculatePriority = (
|
||||
candidateErrorRange: monaco.Range,
|
||||
currentSelection: monaco.Range
|
||||
) => {
|
||||
console.trace(
|
||||
'compiler-errors',
|
||||
`Candidate error range: ${candidateErrorRange.toJSON()}`
|
||||
);
|
||||
console.trace(
|
||||
'compiler-errors',
|
||||
`Current selection range: ${currentSelection.toJSON()}`
|
||||
);
|
||||
if (candidateErrorRange.intersectRanges(currentSelection)) {
|
||||
console.trace('Intersects.');
|
||||
return { score: 2 };
|
||||
}
|
||||
if (
|
||||
candidateErrorRange.startLineNumber <=
|
||||
currentSelection.startLineNumber &&
|
||||
candidateErrorRange.endLineNumber >= currentSelection.endLineNumber
|
||||
) {
|
||||
console.trace('Same line.');
|
||||
return { score: 1 };
|
||||
}
|
||||
|
||||
console.trace('No match');
|
||||
return undefined;
|
||||
};
|
||||
const error = this.errors
|
||||
.filter((error) => error.uri === uri)
|
||||
.map((error) => ({
|
||||
error,
|
||||
range: ErrorDecoration.rangeOf(error, monacoEditor),
|
||||
}))
|
||||
.map(({ error, range }) => {
|
||||
if (range) {
|
||||
const priority = calculatePriority(range, monacoSelection);
|
||||
if (priority) {
|
||||
return { ...priority, error };
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
.filter(notEmpty)
|
||||
.sort((left, right) => right.score - left.score) // highest first
|
||||
.map(({ error }) => error)
|
||||
.shift();
|
||||
if (error) {
|
||||
this.markAsCurrentError(error);
|
||||
} else {
|
||||
console.info(
|
||||
'compiler-errors',
|
||||
`New (monaco) selection ${monacoSelection.toJSON()} does not intersect any error locations. Skipping.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This code does not deal with resource deletion, but tracks editor dispose events. It does not matter what was the cause of the editor disposal.
|
||||
* If editor closes, delete the decorators.
|
||||
*/
|
||||
private handleEditorDidDispose(uri: string): void {
|
||||
let i = this.errors.length;
|
||||
// `splice` re-indexes the array. It's better to "iterate and modify" from the last element.
|
||||
while (i--) {
|
||||
const error = this.errors[i];
|
||||
if (error.uri === uri) {
|
||||
this.errors.splice(i, 1);
|
||||
}
|
||||
}
|
||||
this.onDidChangeEmitter.fire(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a document change "destroys" the range of the decoration, the decoration must be removed.
|
||||
*/
|
||||
private handleDocumentContentChange(
|
||||
editor: EditorWidget,
|
||||
event: TextDocumentChangeEvent
|
||||
): void {
|
||||
const monacoEditor = this.monacoEditor(editor);
|
||||
if (!monacoEditor) {
|
||||
return;
|
||||
}
|
||||
// A decoration location can be "destroyed", hence should be deleted when:
|
||||
// - deleting range (start != end AND text is empty)
|
||||
// - inserting text into range (start != end AND text is not empty)
|
||||
// Filter unrelated delta changes to spare the CPU.
|
||||
const relevantChanges = event.contentChanges.filter(
|
||||
({ range: { start, end } }) =>
|
||||
start.line !== end.line || start.character !== end.character
|
||||
);
|
||||
if (!relevantChanges.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resolvedMarkers = this.errors
|
||||
.filter((error) => error.uri === event.document.uri)
|
||||
.map((error, index) => {
|
||||
const range = ErrorDecoration.rangeOf(error, monacoEditor);
|
||||
if (range) {
|
||||
return { error, range, index };
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
.filter(notEmpty);
|
||||
|
||||
const decorationIdsToRemove = relevantChanges
|
||||
.map(({ range }) => this.p2m.asRange(range))
|
||||
.map((changeRange) =>
|
||||
resolvedMarkers.filter(({ range: decorationRange }) =>
|
||||
changeRange.containsRange(decorationRange)
|
||||
)
|
||||
)
|
||||
.reduce((acc, curr) => acc.concat(curr), [])
|
||||
.map(({ error, index }) => {
|
||||
this.errors.splice(index, 1);
|
||||
return error.id;
|
||||
});
|
||||
if (!decorationIdsToRemove.length) {
|
||||
return;
|
||||
}
|
||||
monacoEditor.getControl().deltaDecorations(decorationIdsToRemove, []);
|
||||
this.onDidChangeEmitter.fire(this);
|
||||
}
|
||||
|
||||
private async trackEditors(
|
||||
errors: Map<string, CoreError.Compiler[]>,
|
||||
...track: ((editor: EditorWidget) => Disposable)[]
|
||||
): Promise<Disposable> {
|
||||
return new DisposableCollection(
|
||||
...(await Promise.all(
|
||||
Array.from(errors.keys()).map(async (uri) => {
|
||||
const editor = await this.editorManager.getByUri(new URI(uri));
|
||||
if (!editor) {
|
||||
return Disposable.NULL;
|
||||
}
|
||||
return new DisposableCollection(...track.map((t) => t(editor)));
|
||||
})
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
private async markAsCurrentError(error: ErrorDecoration): Promise<void> {
|
||||
const index = this.errors.findIndex((candidate) =>
|
||||
ErrorDecoration.sameAs(candidate, error)
|
||||
);
|
||||
if (index < 0) {
|
||||
console.warn(
|
||||
'compiler-errors',
|
||||
`Failed to mark error ${
|
||||
error.id
|
||||
} as the current one. Error is unknown. Known errors are: ${this.errors.map(
|
||||
({ id }) => id
|
||||
)}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const newError = this.errors[index];
|
||||
if (
|
||||
!this.currentError ||
|
||||
!ErrorDecoration.sameAs(this.currentError, newError)
|
||||
) {
|
||||
this.currentError = this.errors[index];
|
||||
console.log(
|
||||
'compiler-errors',
|
||||
`Current error changed to ${this.currentError.id}`
|
||||
);
|
||||
this.currentErrorDidChangEmitter.fire(this.currentError);
|
||||
this.onDidChangeEmitter.fire(this);
|
||||
}
|
||||
}
|
||||
|
||||
// The double editor activation logic is required: https://github.com/eclipse-theia/theia/issues/11284
|
||||
private async revealLocationInEditor(
|
||||
location: Location
|
||||
): Promise<EditorWidget | undefined> {
|
||||
const { uri, range } = location;
|
||||
const editor = await this.editorManager.getByUri(new URI(uri), {
|
||||
mode: 'activate',
|
||||
});
|
||||
if (editor && this.shell) {
|
||||
// to avoid flickering, reveal the range here and not with `getByUri`, because it uses `at: 'center'` for the reveal option.
|
||||
// TODO: check the community reaction whether it is better to set the focus at the error marker. it might cause flickering even if errors are close to each other
|
||||
editor.editor.revealRange(range, { at: this.revealStrategy });
|
||||
const activeWidget = await this.shell.activateWidget(editor.id);
|
||||
if (!activeWidget) {
|
||||
console.warn(
|
||||
'compiler-errors',
|
||||
`editor widget activation has failed. editor widget ${editor.id} expected to be the active one.`
|
||||
);
|
||||
return editor;
|
||||
}
|
||||
if (editor !== activeWidget) {
|
||||
console.warn(
|
||||
'compiler-errors',
|
||||
`active widget was not the same as previously activated editor. editor widget ID ${editor.id}, active widget ID: ${activeWidget.id}`
|
||||
);
|
||||
}
|
||||
return editor;
|
||||
}
|
||||
console.warn(
|
||||
'compiler-errors',
|
||||
`could not found editor widget for URI: ${uri}`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private groupByResource(
|
||||
errors: CoreError.Compiler[]
|
||||
): Map<string, CoreError.Compiler[]> {
|
||||
return errors.reduce((acc, curr) => {
|
||||
const {
|
||||
location: { uri },
|
||||
} = curr;
|
||||
let errors = acc.get(uri);
|
||||
if (!errors) {
|
||||
errors = [];
|
||||
acc.set(uri, errors);
|
||||
}
|
||||
errors.push(curr);
|
||||
return acc;
|
||||
}, new Map<string, CoreError.Compiler[]>());
|
||||
}
|
||||
|
||||
private monacoEditor(widget: EditorWidget): MonacoEditor | undefined;
|
||||
private monacoEditor(uri: string): Promise<MonacoEditor | undefined>;
|
||||
private monacoEditor(
|
||||
uriOrWidget: string | EditorWidget
|
||||
): MaybePromise<MonacoEditor | undefined> {
|
||||
if (uriOrWidget instanceof EditorWidget) {
|
||||
const editor = uriOrWidget.editor;
|
||||
if (editor instanceof MonacoEditor) {
|
||||
return editor;
|
||||
}
|
||||
return undefined;
|
||||
} else {
|
||||
return this.editorManager
|
||||
.getByUri(new URI(uriOrWidget))
|
||||
.then((editor) => {
|
||||
if (editor) {
|
||||
return this.monacoEditor(editor);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
export namespace CompilerErrors {
|
||||
export namespace Commands {
|
||||
export const NEXT_ERROR: Command = {
|
||||
id: 'arduino-editor-next-error',
|
||||
};
|
||||
export const PREVIOUS_ERROR: Command = {
|
||||
id: 'arduino-editor-previous-error',
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user