Skip to content
This repository has been archived by the owner on Nov 5, 2021. It is now read-only.

Adds a CodeAction provider to support fixits #40

Merged
merged 6 commits into from
Oct 2, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 75 additions & 4 deletions src/languageFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,16 @@ export class DiagnostcsAdapter extends Adapter {
return null;
}
const promises: Promise<ts.Diagnostic[]>[] = [];
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()));
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also means that JS files should get suggestions too

return Promise.all(promises);
}).then(diagnostics => {
if (!diagnostics || !monaco.editor.getModel(resource)) {
Expand All @@ -191,14 +194,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),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously we'd only show only errors, now it can be of many types

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 ------
Expand Down Expand Up @@ -632,6 +645,64 @@ 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.CodeActionList> {
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?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one I'm still not sure of, it doesn't feel critical - but if anyone has ideas on what the right way to pass in the formatting options, I'd love to know them

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);
})
}).then(result => {
return {
actions: result,
dispose: () => {}
};
});
}


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;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is my best-guess implementation, leaving most of the work to the edits which come with the original TS code action ideas

}
}
// --- rename ----

export class RenameAdapter extends Adapter implements monaco.languages.RenameProvider {
Expand Down Expand Up @@ -675,4 +746,4 @@ export class RenameAdapter extends Adapter implements monaco.languages.RenamePro

return { edits };
}
}
}
1 change: 1 addition & 0 deletions src/monaco.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ declare module monaco.languages.typescript {
export interface DiagnosticsOptions {
noSemanticValidation?: boolean;
noSyntaxValidation?: boolean;
noSuggestionDiagnostics ?: boolean;
}

export interface LanguageServiceDefaults {
Expand Down
1 change: 1 addition & 0 deletions src/tsMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ 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));
monaco.languages.registerRenameProvider(modeId, new languageFeatures.RenameAdapter(worker));
new languageFeatures.DiagnostcsAdapter(defaults, modeId, worker);

Expand Down
11 changes: 11 additions & 0 deletions src/tsWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ export class TypeScriptWorker implements ts.LanguageServiceHost {
return Promise.resolve(diagnostics);
}

getSuggestionDiagnostics(fileName: string): Promise<ts.DiagnosticWithLocation[]> {
const diagnostics = this._languageService.getSuggestionDiagnostics(fileName);
TypeScriptWorker.clearFiles(diagnostics);
return Promise.resolve(diagnostics);
}

getCompilerOptionsDiagnostics(fileName: string): Promise<ts.Diagnostic[]> {
const diagnostics = this._languageService.getCompilerOptionsDiagnostics();
TypeScriptWorker.clearFiles(diagnostics);
Expand Down Expand Up @@ -208,6 +214,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<ReadonlyArray<ts.CodeFixAction>> {
const preferences = {}
return Promise.resolve(this._languageService.getCodeFixesAtPosition(fileName, start, end, errorCodes, formatOptions, preferences));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think its worth exposing all of the TS code fix options to devs, but I'm open to changing that?

it looks like this:

interface UserPreferences {
        readonly disableSuggestions?: boolean;
        readonly quotePreference?: "auto" | "double" | "single";
        readonly includeCompletionsForModuleExports?: boolean;
        readonly includeCompletionsWithInsertText?: boolean;
        readonly importModuleSpecifierPreference?: "relative" | "non-relative";
        /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */
        readonly importModuleSpecifierEnding?: "minimal" | "index" | "js";
        readonly allowTextChangesInNewFiles?: boolean;
        readonly providePrefixAndSuffixTextForRename?: boolean;
}

}

updateExtraLibs(extraLibs: IExtraLibs) {
this._extraLibs = extraLibs;
}
Expand Down
5 changes: 3 additions & 2 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,11 @@ <h2>Monaco Editor TypeScript test page</h2>
'var game = new Conway.GameOfLife();',

].join('\n'),
language: 'typescript'
language: 'typescript',
lightbulb: { enabled: true }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took 30m guessing, then 5m with a debugger on the plane to figure this one out! 🍡 💡

});
});
</script>

</body>
</html>
</html>