quickFixCommands.ts 10.1 KB
Newer Older
J
Johannes Rieken 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
M
Matt Bierner 已提交
8 9 10 11 12
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, EditorCommand, ServicesAccessor, registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { BulkEdit } from 'vs/editor/browser/services/bulkEdit';
13
import { IEditorContribution } from 'vs/editor/common/editorCommon';
14
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
15 16
import { CodeAction } from 'vs/editor/common/modes';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
17
import { MessageController } from 'vs/editor/contrib/message/messageController';
M
Matt Bierner 已提交
18 19 20 21 22 23 24 25 26 27 28 29
import * as nls from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IFileService } from 'vs/platform/files/common/files';
import { optional } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IMarkerService } from 'vs/platform/markers/common/markers';
import { CodeActionAutoApply, CodeActionFilter, CodeActionKind } from './codeActionTrigger';
import { LightBulbWidget } from './lightBulbWidget';
import { QuickFixComputeEvent, QuickFixModel } from './quickFixModel';
import { QuickFixContextMenu } from './quickFixWidget';
J
Johannes Rieken 已提交
30 31 32

export class QuickFixController implements IEditorContribution {

33
	private static readonly ID = 'editor.contrib.quickFixController';
J
Johannes Rieken 已提交
34

35
	public static get(editor: ICodeEditor): QuickFixController {
J
Johannes Rieken 已提交
36 37 38 39 40
		return editor.getContribution<QuickFixController>(QuickFixController.ID);
	}

	private _editor: ICodeEditor;
	private _model: QuickFixModel;
41 42
	private _quickFixContextMenu: QuickFixContextMenu;
	private _lightBulbWidget: LightBulbWidget;
J
Johannes Rieken 已提交
43 44
	private _disposables: IDisposable[] = [];

45
	constructor(editor: ICodeEditor,
J
Johannes Rieken 已提交
46
		@IMarkerService markerService: IMarkerService,
47
		@IContextKeyService contextKeyService: IContextKeyService,
48
		@ICommandService private readonly _commandService: ICommandService,
49
		@IContextMenuService contextMenuService: IContextMenuService,
50 51
		@IKeybindingService private readonly _keybindingService: IKeybindingService,
		@ITextModelService private readonly _textModelService: ITextModelService,
52
		@optional(IFileService) private _fileService: IFileService
J
Johannes Rieken 已提交
53 54 55
	) {
		this._editor = editor;
		this._model = new QuickFixModel(this._editor, markerService);
56
		this._quickFixContextMenu = new QuickFixContextMenu(editor, contextMenuService, action => this._onApplyCodeAction(action));
57 58 59
		this._lightBulbWidget = new LightBulbWidget(editor);

		this._updateLightBulbTitle();
J
Johannes Rieken 已提交
60 61

		this._disposables.push(
M
Matt Bierner 已提交
62
			this._quickFixContextMenu.onDidExecuteCodeAction(_ => this._model.trigger({ type: 'auto', filter: {} })),
63 64 65
			this._lightBulbWidget.onClick(this._handleLightBulbSelect, this),
			this._model.onDidChangeFixes(e => this._onQuickFixEvent(e)),
			this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitle, this)
J
Johannes Rieken 已提交
66 67 68 69 70 71 72 73 74
		);
	}

	public dispose(): void {
		this._model.dispose();
		dispose(this._disposables);
	}

	private _onQuickFixEvent(e: QuickFixComputeEvent): void {
M
Matt Bierner 已提交
75
		if (e && e.trigger.filter && e.trigger.filter.kind) {
M
Matt Bierner 已提交
76 77 78 79 80 81 82 83 84 85 86
			// Triggered for specific scope
			// Apply if we only have one action or requested autoApply, otherwise show menu
			e.fixes.then(fixes => {
				if (e.trigger.autoApply === CodeActionAutoApply.First || (e.trigger.autoApply === CodeActionAutoApply.IfSingle && fixes.length === 1)) {
					this._onApplyCodeAction(fixes[0]);
				} else {
					this._quickFixContextMenu.show(e.fixes, e.position);
				}
			});
			return;
		}
87

M
Matt Bierner 已提交
88 89
		if (e && e.trigger.type === 'manual') {
			this._quickFixContextMenu.show(e.fixes, e.position);
J
Johannes Rieken 已提交
90 91 92 93
		} else if (e && e.fixes) {
			// auto magically triggered
			// * update an existing list of code actions
			// * manage light bulb
94 95
			if (this._quickFixContextMenu.isVisible) {
				this._quickFixContextMenu.show(e.fixes, e.position);
J
Johannes Rieken 已提交
96
			} else {
97
				this._lightBulbWidget.model = e;
J
Johannes Rieken 已提交
98 99
			}
		} else {
100
			this._lightBulbWidget.hide();
J
Johannes Rieken 已提交
101 102 103 104 105 106 107 108
		}
	}

	public getId(): string {
		return QuickFixController.ID;
	}

	private _handleLightBulbSelect(coords: { x: number, y: number }): void {
109
		this._quickFixContextMenu.show(this._lightBulbWidget.model.fixes, coords);
J
Johannes Rieken 已提交
110 111
	}

M
Matt Bierner 已提交
112 113
	public triggerFromEditorSelection(filter?: CodeActionFilter, autoApply?: CodeActionAutoApply): TPromise<CodeAction[] | undefined> {
		return this._model.trigger({ type: 'manual', filter, autoApply });
J
Johannes Rieken 已提交
114 115 116 117 118 119 120 121 122 123
	}

	private _updateLightBulbTitle(): void {
		const kb = this._keybindingService.lookupKeybinding(QuickFixAction.Id);
		let title: string;
		if (kb) {
			title = nls.localize('quickFixWithKb', "Show Fixes ({0})", kb.getLabel());
		} else {
			title = nls.localize('quickFix', "Show Fixes");
		}
124
		this._lightBulbWidget.title = title;
J
Johannes Rieken 已提交
125
	}
126 127

	private async _onApplyCodeAction(action: CodeAction): TPromise<void> {
128
		if (action.edit) {
129
			await BulkEdit.perform(action.edit.edits, this._textModelService, this._fileService, this._editor);
130 131 132 133 134 135
		}

		if (action.command) {
			await this._commandService.executeCommand(action.command.id, ...action.command.arguments);
		}
	}
J
Johannes Rieken 已提交
136 137
}

138 139 140
function showCodeActionsForEditorSelection(
	editor: ICodeEditor,
	notAvailableMessage: string,
M
Matt Bierner 已提交
141
	filter?: CodeActionFilter,
142 143 144 145 146 147 148 149
	autoApply?: CodeActionAutoApply
) {
	const controller = QuickFixController.get(editor);
	if (!controller) {
		return;
	}

	const pos = editor.getPosition();
M
Matt Bierner 已提交
150
	controller.triggerFromEditorSelection(filter, autoApply).then(codeActions => {
151 152 153 154 155 156
		if (!codeActions || !codeActions.length) {
			MessageController.get(editor).showMessage(notAvailableMessage, pos);
		}
	});
}

J
Johannes Rieken 已提交
157 158
export class QuickFixAction extends EditorAction {

159
	static readonly Id = 'editor.action.quickFix';
J
Johannes Rieken 已提交
160 161 162 163

	constructor() {
		super({
			id: QuickFixAction.Id,
164
			label: nls.localize('quickfix.trigger.label', "Quick Fix..."),
J
Johannes Rieken 已提交
165
			alias: 'Quick Fix',
166
			precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider),
J
Johannes Rieken 已提交
167
			kbOpts: {
168
				kbExpr: EditorContextKeys.editorTextFocus,
J
Johannes Rieken 已提交
169 170 171 172 173
				primary: KeyMod.CtrlCmd | KeyCode.US_DOT
			}
		});
	}

174
	public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
175
		return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"));
J
Johannes Rieken 已提交
176 177
	}
}
178

M
Matt Bierner 已提交
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227

class CodeActionCommandArgs {
	public static fromUser(arg: any): CodeActionCommandArgs {
		if (!arg || typeof arg !== 'object') {
			return new CodeActionCommandArgs(CodeActionKind.Empty, CodeActionAutoApply.IfSingle);
		}
		return new CodeActionCommandArgs(
			CodeActionCommandArgs.getKindFromUser(arg),
			CodeActionCommandArgs.getApplyFromUser(arg));
	}

	private static getApplyFromUser(arg: any) {
		switch (typeof arg.apply === 'string' ? arg.apply.toLowerCase() : '') {
			case 'first':
				return CodeActionAutoApply.First;

			case 'never':
				return CodeActionAutoApply.Never;

			case 'ifsingle':
			default:
				return CodeActionAutoApply.IfSingle;
		}
	}

	private static getKindFromUser(arg: any) {
		return typeof arg.kind === 'string'
			? new CodeActionKind(arg.kind)
			: CodeActionKind.Empty;
	}

	private constructor(
		public readonly kind: CodeActionKind,
		public readonly apply: CodeActionAutoApply
	) { }
}

export class CodeActionCommand extends EditorCommand {

	static readonly Id = 'editor.action.codeAction';

	constructor() {
		super({
			id: CodeActionCommand.Id,
			precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider)
		});
	}

	public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, userArg: any) {
228
		const args = CodeActionCommandArgs.fromUser(userArg);
M
Matt Bierner 已提交
229
		return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"), { kind: args.kind, includeSourceActions: true }, args.apply);
M
Matt Bierner 已提交
230 231 232
	}
}

M
Matt Bierner 已提交
233 234 235 236 237 238 239 240

export class RefactorAction extends EditorAction {

	static readonly Id = 'editor.action.refactor';

	constructor() {
		super({
			id: RefactorAction.Id,
241
			label: nls.localize('refactor.label', "Refactor..."),
M
Matt Bierner 已提交
242
			alias: 'Refactor',
M
Matt Bierner 已提交
243 244
			precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider),
			kbOpts: {
245
				kbExpr: EditorContextKeys.editorTextFocus,
M
Matt Bierner 已提交
246
				primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_R
247 248 249 250
			},
			menuOpts: {
				group: '1_modification',
				order: 2
M
Matt Bierner 已提交
251
			}
M
Matt Bierner 已提交
252 253 254 255
		});
	}

	public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
256 257
		return showCodeActionsForEditorSelection(editor,
			nls.localize('editor.action.refactor.noneMessage', "No refactorings available"),
M
Matt Bierner 已提交
258
			{ kind: CodeActionKind.Refactor },
259
			CodeActionAutoApply.Never);
M
Matt Bierner 已提交
260 261 262 263
	}
}


M
Matt Bierner 已提交
264 265 266 267 268 269 270
export class SourceAction extends EditorAction {

	static readonly Id = 'editor.action.sourceAction';

	constructor() {
		super({
			id: SourceAction.Id,
271
			label: nls.localize('source.label', "Source Action..."),
M
Matt Bierner 已提交
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
			alias: 'Source Action',
			precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider),
			menuOpts: {
				group: '1_modification',
				order: 2.1
			}
		});
	}

	public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
		return showCodeActionsForEditorSelection(editor,
			nls.localize('editor.action.source.noneMessage', "No source actions available"),
			{ kind: CodeActionKind.Source, includeSourceActions: true },
			CodeActionAutoApply.Never);
	}
}

289
registerEditorContribution(QuickFixController);
290
registerEditorAction(QuickFixAction);
M
Matt Bierner 已提交
291
registerEditorAction(RefactorAction);
M
Matt Bierner 已提交
292
registerEditorAction(SourceAction);
M
Matt Bierner 已提交
293
registerEditorCommand(new CodeActionCommand());