From b8063f957f89a57b57457e0bb38f8c779f712cca Mon Sep 17 00:00:00 2001 From: Orta Therox Date: Thu, 29 Aug 2019 13:16:22 -0400 Subject: [PATCH] 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 +