import { CompositeDisposible } from "../../../common/events"; import { cast, server } from "../../../socket/socketClient"; import { Types } from "../../../socket/socketContract"; import * as utils from "../../../common/utils"; import * as state from "../../state/state"; import * as commands from "../../commands/commands"; require('./quickFix.css'); type Editor = monaco.editor.ICodeEditor; const quickFixClassName = 'monaco-quickfix'; const quickFixDecorationOptions: monaco.editor.IModelDecorationOptions = { glyphMarginClassName: quickFixClassName, isWholeLine: true, hoverMessage: 'QuickFixes available. Click to select.' }; export function setup(editor: Editor): { dispose: () => void } { // if (cm) return { dispose: () => null }; // DEBUG : while the feature isn't complete used to disable it let disposed = false; let lastWidget: monaco.editor.IContentWidget | null = null; // The key quick fix get logic const refreshQuickFixes = () => { if (disposed) return; // Clear any previous attempt editor._lastQuickFixInformation = null; if (lastWidget) { editor.removeContentWidget(lastWidget); lastWidget = null; } // If not active project return if (!state.inActiveProjectFilePath(editor.filePath)) { return; } const indentSize = editor.getModel().getOptions().tabSize; const pos = editor.getPosition(); const position = editor.getModel().getOffsetAt(pos); // query the server with live analysis server.getQuickFixes({ indentSize, filePath: editor.filePath, position }).then(res => { // If no longer relevant abort and wait for a new call. const newPos = editor.getPosition(); if (!newPos.equals(pos)) return; /** Only add the decoration if there are some fixes available */ if (res.fixes.length) { editor._lastQuickFixInformation = { res, position }; // Setup the marker. Note: Must be done outside `getDomNode` to make it idempotent var marker = document.createElement("div"); marker.className = quickFixClassName; marker.title = `Quick fixes available`; marker.innerHTML = "💡"; marker.onclick = () => { runQuickFixSelector(editor); } lastWidget = { allowEditorOverflow: false, getId: () => 'quickfix', getDomNode: () => marker, getPosition: () => { return { position: { lineNumber: pos.lineNumber, column: editor.getModel().getLineContent(pos.lineNumber).length + 1 }, preference: [ monaco.editor.ContentWidgetPositionPreference.EXACT, ] } } }; editor.addContentWidget(lastWidget); } }); }; const refreshQuickFixesDebounced = utils.debounce(refreshQuickFixes, 1000); const disposible = new CompositeDisposible(); disposible.add(editor.onDidFocusEditor(refreshQuickFixesDebounced)); disposible.add(editor.onDidChangeModelContent(refreshQuickFixesDebounced)); disposible.add(editor.onDidChangeCursorPosition(refreshQuickFixesDebounced)); disposible.add(cast.activeProjectConfigDetailsUpdated.on(() => { refreshQuickFixesDebounced(); })); disposible.add({ dispose() { disposed = true; } }) return disposible; } /** * We add the quickfix information to the editor to allow easy invocation from an action */ declare global { namespace monaco { namespace editor { export interface ICommonCodeEditor { _lastQuickFixInformation?: { res: Types.GetQuickFixesResponse, position: number } } } } } import CommonEditorRegistry = monaco.CommonEditorRegistry; import ICommonCodeEditor = monaco.ICommonCodeEditor; import TPromise = monaco.Promise; import EditorAction = monaco.EditorAction; import KeyMod = monaco.KeyMod; import KeyCode = monaco.KeyCode; import ServicesAccessor = monaco.ServicesAccessor; import IActionOptions = monaco.IActionOptions; import EditorContextKeys = monaco.EditorContextKeys; import * as selectListView from "../../selectListView"; import * as ui from "../../ui"; import * as uix from "../../uix"; import * as React from "react"; class QuickFixAction extends EditorAction { static ID = 'editor.action.quickfix'; constructor() { super({ id: QuickFixAction.ID, label: 'TypeScript Quick Fix', alias: 'TypeScript Quick Fix', precondition: EditorContextKeys.Focus, kbOpts: { kbExpr: EditorContextKeys.TextFocus, primary: KeyMod.Alt | KeyCode.Enter } }); } public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void | TPromise { runQuickFixSelector(editor); } } function runQuickFixSelector(editor: ICommonCodeEditor) { const cm = editor; if (!cm._lastQuickFixInformation) { ui.notifyInfoNormalDisappear('No active quick fixes for last editor position'); return; } const fixes = cm._lastQuickFixInformation.res.fixes; selectListView.selectListView.show({ header: '💡 Quick Fixes', data: fixes, render: (fix, highlighted) => { return
{highlighted}
; }, textify: (fix) => fix.display, onSelect: (fix) => { /** * For each ts code fix its expected to request formatting as well * https://github.com/Microsoft/TypeScript/issues/12249 * So add them to the refactorings * The backend can't do it as the file hasn't been edited yet. Have to request from frontend :-/ * * But its probably not worth too much trouble for now so ignoring. **/ // tsCodeFix.changes.forEach(change => { // change.textChanges.forEach(tc => { // /** The end depends on the old text vs. the new text. But always greater than start (good enough) */ // let end = tc.span.start + tc.newText.length - tc.span.length; // if (end < tc.span.start) end = tc.span.start; // let start = tc.span.start; // let tsresult = formatting.formatDocumentRangeUsingPos(project, change.fileName, start, end, // /** // * This is not 100% correct as the changed file can be different. // * But the likelyhood of the *other* project file having different formatting requirements is very low // **/ // query.editorOptions // ); // console.log(tsresult, change.fileName, tc.span, end); // tsresult.forEach(formatting => { // refactorings.push({ // filePath: change.fileName, // newText: formatting.newText, // span: formatting.span // }); // }) // }); // }); server.applyQuickFix({ key: fix.key, indentSize: cm.getModel().getOptions().tabSize, additionalData: null, filePath: cm.filePath, position: cm._lastQuickFixInformation.position, }).then((res) => { // apply refactorings // console.log('Apply refactorings:', res.refactorings); // DEBUG uix.API.applyRefactorings(res.refactorings); }); return ''; } }); } CommonEditorRegistry.registerEditorAction(new QuickFixAction());