瀏覽代碼

Adds a CodeAction provider to support fixits (#40)

Adds a CodeAction provider to support fixits
Alexandru Dima 5 年之前
父節點
當前提交
c050127710
共有 5 個文件被更改,包括 84 次插入8 次删除
  1. 70 5
      src/languageFeatures.ts
  2. 1 0
      src/monaco.d.ts
  3. 1 0
      src/tsMode.ts
  4. 11 0
      src/tsWorker.ts
  5. 1 3
      test/index.html

+ 70 - 5
src/languageFeatures.ts

@@ -163,13 +163,16 @@ export class DiagnosticsAdapter 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()));
+			}
 			return Promise.all(promises);
 		}).then(diagnostics => {
 			if (!diagnostics || !monaco.editor.getModel(resource)) {
@@ -191,14 +194,24 @@ export class DiagnosticsAdapter 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 ------
@@ -356,7 +369,7 @@ export class SignatureHelpAdapter extends Adapter implements monaco.languages.Si
 
 			return {
 				value: ret,
-				dispose() {}
+				dispose() { }
 			};
 		});
 	}
@@ -632,6 +645,58 @@ 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 });
+
+			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);
+
+		}).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,
+			kind: "quickfix"
+		};
+
+		return action;
+	}
+}
 // --- rename ----
 
 export class RenameAdapter extends Adapter implements monaco.languages.RenameProvider {
@@ -675,4 +740,4 @@ export class RenameAdapter extends Adapter implements monaco.languages.RenamePro
 
 		return { edits };
 	}
-}
+}

+ 1 - 0
src/monaco.d.ts

@@ -128,6 +128,7 @@ declare module monaco.languages.typescript {
     export interface DiagnosticsOptions {
         noSemanticValidation?: boolean;
         noSyntaxValidation?: boolean;
+        noSuggestionDiagnostics?: boolean;
     }
 
     export interface LanguageServiceDefaults {

+ 1 - 0
src/tsMode.ts

@@ -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.DiagnosticsAdapter(defaults, modeId, worker);
 

+ 11 - 0
src/tsWorker.ts

@@ -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);
@@ -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));
+	}
+
 	updateExtraLibs(extraLibs: IExtraLibs) {
 		this._extraLibs = extraLibs;
 	}

+ 1 - 3
test/index.html

@@ -160,10 +160,8 @@
 				'}',
 				'',
 				'var game = new Conway.GameOfLife();',
-
-			].join('\n')
+			].join('\n');
 	}
-
 	require([
 		'vs/basic-languages/monaco.contribution',
 		'vs/language/typescript/monaco.contribution'