From b8063f957f89a57b57457e0bb38f8c779f712cca Mon Sep 17 00:00:00 2001 From: Orta Therox Date: Thu, 29 Aug 2019 13:16:22 -0400 Subject: [PATCH 1/3] Adds a CodeAction provider to support fixits --- src/languageFeatures.ts | 73 +++++++++++++++++++++++++++++++++++++++-- src/monaco.d.ts | 1 + src/tsMode.ts | 2 ++ src/tsWorker.ts | 11 +++++++ test/index.html | 5 +-- 5 files changed, 87 insertions(+), 5 deletions(-) diff --git a/src/languageFeatures.ts b/src/languageFeatures.ts index cc76509c..6f9f1205 100644 --- a/src/languageFeatures.ts +++ b/src/languageFeatures.ts @@ -160,13 +160,16 @@ export class DiagnostcsAdapter extends Adapter { return null; } const promises: Promise[] = []; - const { noSyntaxValidation, noSemanticValidation } = this._defaults.getDiagnosticsOptions(); + const { noSyntaxValidation, noSemanticValidation, noSuggestionDiagnostics } = this._defaults.getDiagnosticsOptions(); if (!noSyntaxValidation) { promises.push(worker.getSyntacticDiagnostics(resource.toString())); } if (!noSemanticValidation) { promises.push(worker.getSemanticDiagnostics(resource.toString())); } + if (!noSuggestionDiagnostics) { + promises.push(worker.getSuggestionDiagnostics(resource.toString())); + } return Promise.all(promises); }).then(diagnostics => { if (!diagnostics || !monaco.editor.getModel(resource)) { @@ -188,14 +191,24 @@ export class DiagnostcsAdapter extends Adapter { const { lineNumber: endLineNumber, column: endColumn } = this._offsetToPosition(resource, diag.start + diag.length); return { - severity: monaco.MarkerSeverity.Error, + severity: this._tsDiagnosticCategoryToMarkerSeverity(diag.category), startLineNumber, startColumn, endLineNumber, endColumn, - message: flattenDiagnosticMessageText(diag.messageText, '\n') + message: flattenDiagnosticMessageText(diag.messageText, '\n'), + code: diag.code.toString() }; } + + private _tsDiagnosticCategoryToMarkerSeverity(category: ts.DiagnosticCategory): monaco.MarkerSeverity { + switch(category) { + case ts.DiagnosticCategory.Error: return monaco.MarkerSeverity.Error + case ts.DiagnosticCategory.Message: return monaco.MarkerSeverity.Info + case ts.DiagnosticCategory.Warning: return monaco.MarkerSeverity.Warning + case ts.DiagnosticCategory.Suggestion: return monaco.MarkerSeverity.Hint + } + } } // --- suggest ------ @@ -626,3 +639,57 @@ export class FormatOnTypeAdapter extends FormatHelper implements monaco.language }); } } + +// --- code actions ------ + +export class CodeActionAdaptor extends FormatHelper implements monaco.languages.CodeActionProvider { + + public provideCodeActions(model: monaco.editor.ITextModel, range: Range, context: monaco.languages.CodeActionContext, token: CancellationToken): Promise<(monaco.languages.Command | monaco.languages.CodeAction)[]> { + const resource = model.uri; + + return this._worker(resource).then(worker => { + const start = this._positionToOffset(resource, { lineNumber: range.startLineNumber, column: range.startColumn }); + const end = this._positionToOffset(resource, { lineNumber: range.endLineNumber, column: range.endColumn }); + + // TODO: where to get the current formatting options from? + const formatOptions = FormatHelper._convertOptions({insertSpaces: true, tabSize: 2}); + const errorCodes = context.markers.filter(m => m.code).map(m => m.code).map(Number); + + return worker.getCodeFixesAtPosition(resource.toString(), start, end, errorCodes, formatOptions); + + }).then(codeFixes => { + + return codeFixes.filter(fix => { + // Removes any 'make a new file'-type code fix + return fix.changes.filter(change => change.isNewFile).length === 0; + }).map(fix => { + return this._tsCodeFixActionToMonacoCodeAction(model, context, fix); + }) + }); + } + + + private _tsCodeFixActionToMonacoCodeAction(model: monaco.editor.ITextModel, context: monaco.languages.CodeActionContext, codeFix: ts.CodeFixAction): monaco.languages.CodeAction { + const edits: monaco.languages.ResourceTextEdit[] = codeFix.changes.map(edit => ({ + resource: model.uri, + edits: edit.textChanges.map(tc => ({ + range: this._textSpanToRange(model.uri, tc.span), + text: tc.newText + })) + })); + + const action: monaco.languages.CodeAction = { + title: codeFix.description, + edit: { edits: edits }, + diagnostics: context.markers, + command: { + id: codeFix.fixName, + title: codeFix.description, + tooltip: codeFix.description + }, + kind: codeFix.fixName + }; + + return action; + } +} diff --git a/src/monaco.d.ts b/src/monaco.d.ts index 59fc8e1c..620f4b02 100644 --- a/src/monaco.d.ts +++ b/src/monaco.d.ts @@ -125,6 +125,7 @@ declare module monaco.languages.typescript { export interface DiagnosticsOptions { noSemanticValidation?: boolean; noSyntaxValidation?: boolean; + noSuggestionDiagnostics ?: boolean; } export interface LanguageServiceDefaults { diff --git a/src/tsMode.ts b/src/tsMode.ts index ce9caeca..e474e6fc 100644 --- a/src/tsMode.ts +++ b/src/tsMode.ts @@ -64,6 +64,8 @@ function setupMode(defaults: LanguageServiceDefaultsImpl, modeId: string): (firs monaco.languages.registerDocumentSymbolProvider(modeId, new languageFeatures.OutlineAdapter(worker)); monaco.languages.registerDocumentRangeFormattingEditProvider(modeId, new languageFeatures.FormatAdapter(worker)); monaco.languages.registerOnTypeFormattingEditProvider(modeId, new languageFeatures.FormatOnTypeAdapter(worker)); + monaco.languages.registerCodeActionProvider(modeId, new languageFeatures.CodeActionAdaptor(worker)); + new languageFeatures.DiagnostcsAdapter(defaults, modeId, worker); return worker; diff --git a/src/tsWorker.ts b/src/tsWorker.ts index fd2b280a..a5b0089d 100644 --- a/src/tsWorker.ts +++ b/src/tsWorker.ts @@ -146,6 +146,12 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { return Promise.resolve(diagnostics); } + getSuggestionDiagnostics(fileName: string): Promise { + const diagnostics = this._languageService.getSuggestionDiagnostics(fileName); + TypeScriptWorker.clearFiles(diagnostics); + return Promise.resolve(diagnostics); + } + getCompilerOptionsDiagnostics(fileName: string): Promise { const diagnostics = this._languageService.getCompilerOptionsDiagnostics(); TypeScriptWorker.clearFiles(diagnostics); @@ -200,6 +206,11 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { return Promise.resolve(this._languageService.getEmitOutput(fileName)); } + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes:number[], formatOptions: ts.FormatCodeOptions): Promise> { + const preferences = {} + return Promise.resolve(this._languageService.getCodeFixesAtPosition(fileName, start, end, errorCodes, formatOptions, preferences)); + } + updateExtraLibs(extraLibs: IExtraLibs) { this._extraLibs = extraLibs; } diff --git a/test/index.html b/test/index.html index deb28551..579d5ff4 100644 --- a/test/index.html +++ b/test/index.html @@ -165,10 +165,11 @@ 'var game = new Conway.GameOfLife();', ].join('\n'), - language: 'typescript' + language: 'typescript', + lightbulb: { enabled: true } }); }); - \ No newline at end of file + From 0dd1066fc4d00b3c6543df6f13ac3f866d8bf95c Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 19 Sep 2019 09:43:26 +0200 Subject: [PATCH 2/3] formatting options & source formatting --- src/languageFeatures.ts | 27 +++++++++++++-------------- src/monaco.d.ts | 2 +- src/tsWorker.ts | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/languageFeatures.ts b/src/languageFeatures.ts index ea7fd7c7..113048e2 100644 --- a/src/languageFeatures.ts +++ b/src/languageFeatures.ts @@ -204,8 +204,8 @@ export class DiagnostcsAdapter extends Adapter { }; } - private _tsDiagnosticCategoryToMarkerSeverity(category: ts.DiagnosticCategory): monaco.MarkerSeverity { - switch(category) { + private _tsDiagnosticCategoryToMarkerSeverity(category: ts.DiagnosticCategory): monaco.MarkerSeverity { + switch (category) { case ts.DiagnosticCategory.Error: return monaco.MarkerSeverity.Error case ts.DiagnosticCategory.Message: return monaco.MarkerSeverity.Info case ts.DiagnosticCategory.Warning: return monaco.MarkerSeverity.Warning @@ -369,7 +369,7 @@ export class SignatureHelpAdapter extends Adapter implements monaco.languages.Si return { value: ret, - dispose() {} + dispose() { } }; }); } @@ -656,8 +656,7 @@ export class CodeActionAdaptor extends FormatHelper implements monaco.languages. const start = this._positionToOffset(resource, { lineNumber: range.startLineNumber, column: range.startColumn }); const end = this._positionToOffset(resource, { lineNumber: range.endLineNumber, column: range.endColumn }); - // TODO: where to get the current formatting options from? - const formatOptions = FormatHelper._convertOptions({insertSpaces: true, tabSize: 2}); + const formatOptions = FormatHelper._convertOptions(model.getOptions()); const errorCodes = context.markers.filter(m => m.code).map(m => m.code).map(Number); return worker.getCodeFixesAtPosition(resource.toString(), start, end, errorCodes, formatOptions); @@ -665,15 +664,15 @@ export class CodeActionAdaptor extends FormatHelper implements monaco.languages. }).then(codeFixes => { return codeFixes.filter(fix => { - // Removes any 'make a new file'-type code fix - return fix.changes.filter(change => change.isNewFile).length === 0; + // Removes any 'make a new file'-type code fix + return fix.changes.filter(change => change.isNewFile).length === 0; }).map(fix => { return this._tsCodeFixActionToMonacoCodeAction(model, context, fix); }) }).then(result => { return { actions: result, - dispose: () => {} + dispose: () => { } }; }); } @@ -681,12 +680,12 @@ export class CodeActionAdaptor extends FormatHelper implements monaco.languages. private _tsCodeFixActionToMonacoCodeAction(model: monaco.editor.ITextModel, context: monaco.languages.CodeActionContext, codeFix: ts.CodeFixAction): monaco.languages.CodeAction { const edits: monaco.languages.ResourceTextEdit[] = codeFix.changes.map(edit => ({ - resource: model.uri, - edits: edit.textChanges.map(tc => ({ - range: this._textSpanToRange(model.uri, tc.span), - text: tc.newText - })) - })); + resource: model.uri, + edits: edit.textChanges.map(tc => ({ + range: this._textSpanToRange(model.uri, tc.span), + text: tc.newText + })) + })); const action: monaco.languages.CodeAction = { title: codeFix.description, diff --git a/src/monaco.d.ts b/src/monaco.d.ts index 9ae51073..8aa58804 100644 --- a/src/monaco.d.ts +++ b/src/monaco.d.ts @@ -128,7 +128,7 @@ declare module monaco.languages.typescript { export interface DiagnosticsOptions { noSemanticValidation?: boolean; noSyntaxValidation?: boolean; - noSuggestionDiagnostics ?: boolean; + noSuggestionDiagnostics?: boolean; } export interface LanguageServiceDefaults { diff --git a/src/tsWorker.ts b/src/tsWorker.ts index 1bd21116..06787de2 100644 --- a/src/tsWorker.ts +++ b/src/tsWorker.ts @@ -214,7 +214,7 @@ export class TypeScriptWorker implements ts.LanguageServiceHost { return Promise.resolve(this._languageService.getEmitOutput(fileName)); } - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes:number[], formatOptions: ts.FormatCodeOptions): Promise> { + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: ts.FormatCodeOptions): Promise> { const preferences = {} return Promise.resolve(this._languageService.getCodeFixesAtPosition(fileName, start, end, errorCodes, formatOptions, preferences)); } From c6ad8230be75ce790fb3c6f980ffcd0768266aff Mon Sep 17 00:00:00 2001 From: Orta Therox Date: Tue, 1 Oct 2019 09:15:27 -0400 Subject: [PATCH 3/3] Use the code deltas from TS only, and use the right kind name to trigger the popover --- src/languageFeatures.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/languageFeatures.ts b/src/languageFeatures.ts index 113048e2..21b3087f 100644 --- a/src/languageFeatures.ts +++ b/src/languageFeatures.ts @@ -691,12 +691,7 @@ export class CodeActionAdaptor extends FormatHelper implements monaco.languages. title: codeFix.description, edit: { edits: edits }, diagnostics: context.markers, - command: { - id: codeFix.fixName, - title: codeFix.description, - tooltip: codeFix.description - }, - kind: codeFix.fixName + kind: "quickfix" }; return action;