rename.ts 10.2 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import * as nls from 'vs/nls';
J
Johannes Rieken 已提交
7
import { illegalArgument, onUnexpectedError } from 'vs/base/common/errors';
J
Johannes Rieken 已提交
8 9 10 11
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { TPromise } from 'vs/base/common/winjs.base';
import { RawContextKey, IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IProgressService } from 'vs/platform/progress/common/progress';
12
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
13
import { IEditorContribution } from 'vs/editor/common/editorCommon';
A
Alex Dima 已提交
14
import { ITextModel } from 'vs/editor/common/model';
15
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
J
Johannes Rieken 已提交
16
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
A
Alex Dima 已提交
17
import RenameInputField from './renameInputField';
18
import { IThemeService } from 'vs/platform/theme/common/themeService';
19
import { WorkspaceEdit, RenameProviderRegistry, RenameProvider, RenameLocation, Rejection } from 'vs/editor/common/modes';
J
Johannes Rieken 已提交
20
import { Position, IPosition } from 'vs/editor/common/core/position';
21
import { alert } from 'vs/base/browser/ui/aria/aria';
22
import { Range } from 'vs/editor/common/core/range';
23
import { MessageController } from 'vs/editor/contrib/message/messageController';
24
import { EditorState, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState';
25
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
26
import { INotificationService } from 'vs/platform/notification/common/notification';
B
Benjamin Pasero 已提交
27
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
28
import { URI } from 'vs/base/common/uri';
J
Johannes Rieken 已提交
29
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
30
import { CancellationToken } from 'vs/base/common/cancellation';
31

J
Johannes Rieken 已提交
32
class RenameSkeleton {
J
Johannes Rieken 已提交
33

J
Johannes Rieken 已提交
34
	private _provider: RenameProvider[];
J
Johannes Rieken 已提交
35

J
Johannes Rieken 已提交
36 37 38 39 40 41 42 43 44 45 46
	constructor(
		readonly model: ITextModel,
		readonly position: Position
	) {
		this._provider = RenameProviderRegistry.ordered(model);
	}

	hasProvider() {
		return this._provider.length > 0;
	}

47
	async resolveRenameLocation(token: CancellationToken): Promise<RenameLocation & Rejection> {
J
Johannes Rieken 已提交
48 49

		let [provider] = this._provider;
50
		let res: RenameLocation & Rejection;
J
Johannes Rieken 已提交
51

J
Johannes Rieken 已提交
52
		if (provider.resolveRenameLocation) {
53
			res = await provider.resolveRenameLocation(this.model, this.position, token);
J
Johannes Rieken 已提交
54 55
		}

56
		if (!res) {
J
Johannes Rieken 已提交
57 58
			let word = this.model.getWordAtPosition(this.position);
			if (word) {
59 60 61 62
				res = {
					range: new Range(this.position.lineNumber, word.startColumn, this.position.lineNumber, word.endColumn),
					text: word.word
				};
J
Johannes Rieken 已提交
63
			}
J
Johannes Rieken 已提交
64
		}
J
Johannes Rieken 已提交
65

66
		return res;
J
Johannes Rieken 已提交
67 68
	}

69
	async provideRenameEdits(newName: string, i: number = 0, rejects: string[] = [], token: CancellationToken): Promise<WorkspaceEdit & Rejection> {
70 71 72

		if (i >= this._provider.length) {
			return {
J
Johannes Rieken 已提交
73
				edits: undefined,
74
				rejectReason: rejects.join('\n')
J
Johannes Rieken 已提交
75 76
			};
		}
77 78

		let provider = this._provider[i];
79
		let result = await provider.provideRenameEdits(this.model, this.position, newName, token);
80
		if (!result) {
81
			return this.provideRenameEdits(newName, i + 1, rejects.concat(nls.localize('no result', "No result.")), token);
82
		} else if (result.rejectReason) {
83
			return this.provideRenameEdits(newName, i + 1, rejects.concat(result.rejectReason), token);
84
		}
J
Johannes Rieken 已提交
85 86
		return result;
	}
J
Johannes Rieken 已提交
87 88
}

89
export async function rename(model: ITextModel, position: Position, newName: string): Promise<WorkspaceEdit & Rejection> {
90
	return new RenameSkeleton(model, position).provideRenameEdits(newName, undefined, undefined, CancellationToken.None);
91
}
E
Erich Gamma 已提交
92

93 94
// ---  register actions and commands

A
Alex Dima 已提交
95
const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey<boolean>('renameInputVisible', false);
96

A
Alex Dima 已提交
97
class RenameController implements IEditorContribution {
98

99
	private static readonly ID = 'editor.contrib.renameController';
E
Erich Gamma 已提交
100

101
	public static get(editor: ICodeEditor): RenameController {
A
Alex Dima 已提交
102
		return editor.getContribution<RenameController>(RenameController.ID);
A
Alex Dima 已提交
103
	}
E
Erich Gamma 已提交
104 105

	private _renameInputField: RenameInputField;
A
Alex Dima 已提交
106
	private _renameInputVisible: IContextKey<boolean>;
E
Erich Gamma 已提交
107

A
Alex Dima 已提交
108
	constructor(
J
Johannes Rieken 已提交
109
		private editor: ICodeEditor,
110
		@INotificationService private readonly _notificationService: INotificationService,
111
		@IBulkEditService private readonly _bulkEditService: IBulkEditService,
112
		@IProgressService private readonly _progressService: IProgressService,
113
		@IContextKeyService contextKeyService: IContextKeyService,
B
Benjamin Pasero 已提交
114
		@IThemeService themeService: IThemeService,
J
Johannes Rieken 已提交
115
	) {
B
Benjamin Pasero 已提交
116
		this._renameInputField = new RenameInputField(editor, themeService);
117
		this._renameInputVisible = CONTEXT_RENAME_INPUT_VISIBLE.bindTo(contextKeyService);
E
Erich Gamma 已提交
118 119
	}

A
Alex Dima 已提交
120 121
	public dispose(): void {
		this._renameInputField.dispose();
122 123
	}

A
Alex Dima 已提交
124 125
	public getId(): string {
		return RenameController.ID;
126 127
	}

128
	public async run(token: CancellationToken): Promise<void> {
129

130 131
		const position = this.editor.getPosition();
		const skeleton = new RenameSkeleton(this.editor.getModel(), position);
E
Erich Gamma 已提交
132

J
Johannes Rieken 已提交
133 134 135 136
		if (!skeleton.hasProvider()) {
			return undefined;
		}

137
		let loc: RenameLocation & Rejection;
138
		try {
139
			loc = await skeleton.resolveRenameLocation(token);
140
		} catch (e) {
141
			MessageController.get(this.editor).showMessage(e || nls.localize('resolveRenameLocationFailed', "An unknown error occurred while resolving rename location"), position);
J
Johannes Rieken 已提交
142
			return undefined;
E
Erich Gamma 已提交
143
		}
144

145
		if (!loc) {
146 147 148
			return undefined;
		}

149 150 151 152 153
		if (loc.rejectReason) {
			MessageController.get(this.editor).showMessage(loc.rejectReason, position);
			return undefined;
		}

J
Johannes Rieken 已提交
154 155
		let selection = this.editor.getSelection();
		let selectionStart = 0;
156
		let selectionEnd = loc.text.length;
157

158 159 160
		if (!Range.isEmpty(selection) && !Range.spansMultipleLines(selection) && Range.containsRange(loc.range, selection)) {
			selectionStart = Math.max(0, selection.startColumn - loc.range.startColumn);
			selectionEnd = Math.min(loc.range.endColumn, selection.endColumn) - loc.range.startColumn;
E
Erich Gamma 已提交
161 162 163
		}

		this._renameInputVisible.set(true);
164
		return this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd).then(newNameOrFocusFlag => {
E
Erich Gamma 已提交
165
			this._renameInputVisible.reset();
J
Johannes Rieken 已提交
166 167 168 169 170 171 172 173

			if (typeof newNameOrFocusFlag === 'boolean') {
				if (newNameOrFocusFlag) {
					this.editor.focus();
				}
				return undefined;
			}

J
Johannes Rieken 已提交
174 175
			this.editor.focus();

176
			const state = new EditorState(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection | CodeEditorStateFlag.Scroll);
177

178
			const renameOperation = Promise.resolve(skeleton.provideRenameEdits(newNameOrFocusFlag, 0, [], token).then(result => {
179
				if (result.rejectReason) {
180 181 182
					if (state.validate(this.editor)) {
						MessageController.get(this.editor).showMessage(result.rejectReason, this.editor.getPosition());
					} else {
183
						this._notificationService.info(result.rejectReason);
184
					}
185 186
					return undefined;
				}
E
Erich Gamma 已提交
187

188
				return this._bulkEditService.apply(result, { editor: this.editor }).then(result => {
189
					// alert
190 191 192
					if (result.ariaSummary) {
						alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc.text, newNameOrFocusFlag, result.ariaSummary));
					}
E
Erich Gamma 已提交
193 194 195
				});

			}, err => {
196
				this._notificationService.error(nls.localize('rename.failed', "Rename failed to execute."));
197
				return Promise.reject(err);
198
			}));
J
Johannes Rieken 已提交
199 200 201 202

			this._progressService.showWhile(renameOperation, 250);
			return renameOperation;

E
Erich Gamma 已提交
203 204
		}, err => {
			this._renameInputVisible.reset();
205
			return Promise.reject(err);
E
Erich Gamma 已提交
206 207 208 209 210 211 212 213
		});
	}

	public acceptRenameInput(): void {
		this._renameInputField.acceptInput();
	}

	public cancelRenameInput(): void {
J
Johannes Rieken 已提交
214
		this._renameInputField.cancelInput(true);
E
Erich Gamma 已提交
215 216
	}
}
J
Johannes Rieken 已提交
217

A
Alex Dima 已提交
218 219
// ---- action implementation

A
Alex Dima 已提交
220
export class RenameAction extends EditorAction {
J
Johannes Rieken 已提交
221

A
Alex Dima 已提交
222
	constructor() {
223 224 225 226
		super({
			id: 'editor.action.rename',
			label: nls.localize('rename.label', "Rename Symbol"),
			alias: 'Rename Symbol',
227
			precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasRenameProvider),
228
			kbOpts: {
229
				kbExpr: EditorContextKeys.editorTextFocus,
A
Alex Dima 已提交
230
				primary: KeyCode.F2,
231
				weight: KeybindingWeight.EditorContrib
232 233 234
			},
			menuOpts: {
				group: '1_modification',
A
Alex Dima 已提交
235
				order: 1.1
236 237
			}
		});
J
Johannes Rieken 已提交
238
	}
A
Alex Dima 已提交
239

J
Johannes Rieken 已提交
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
	runCommand(accessor: ServicesAccessor, args: [URI, IPosition]): void | TPromise<void> {
		const editorService = accessor.get(ICodeEditorService);
		const [uri, pos] = args || [undefined, undefined];

		if (URI.isUri(uri) && Position.isIPosition(pos)) {
			return editorService.openCodeEditor({ resource: uri }, editorService.getActiveCodeEditor()).then(editor => {
				editor.setPosition(pos);
				editor.invokeWithinContext(accessor => {
					this.reportTelemetry(accessor, editor);
					return this.run(accessor, editor);
				});
			}, onUnexpectedError);
		}

		return super.runCommand(accessor, args);
	}

	run(accessor: ServicesAccessor, editor: ICodeEditor): TPromise<void> {
258 259
		let controller = RenameController.get(editor);
		if (controller) {
260
			return TPromise.wrap(controller.run(CancellationToken.None));
261
		}
M
Matt Bierner 已提交
262
		return undefined;
A
Alex Dima 已提交
263 264 265
	}
}

266
registerEditorContribution(RenameController);
267
registerEditorAction(RenameAction);
268

269
const RenameCommand = EditorCommand.bindToContribution<RenameController>(RenameController.get);
A
Alex Dima 已提交
270

271
registerEditorCommand(new RenameCommand({
272 273 274 275
	id: 'acceptRenameInput',
	precondition: CONTEXT_RENAME_INPUT_VISIBLE,
	handler: x => x.acceptRenameInput(),
	kbOpts: {
276
		weight: KeybindingWeight.EditorContrib + 99,
277
		kbExpr: EditorContextKeys.focus,
A
Alex Dima 已提交
278 279
		primary: KeyCode.Enter
	}
280
}));
A
Alex Dima 已提交
281

282
registerEditorCommand(new RenameCommand({
283 284 285 286
	id: 'cancelRenameInput',
	precondition: CONTEXT_RENAME_INPUT_VISIBLE,
	handler: x => x.cancelRenameInput(),
	kbOpts: {
287
		weight: KeybindingWeight.EditorContrib + 99,
288
		kbExpr: EditorContextKeys.focus,
A
Alex Dima 已提交
289 290 291
		primary: KeyCode.Escape,
		secondary: [KeyMod.Shift | KeyCode.Escape]
	}
292
}));
J
Johannes Rieken 已提交
293 294 295

// ---- api bridge command

296
registerDefaultLanguageCommand('_executeDocumentRenameProvider', function (model, position, args) {
J
Johannes Rieken 已提交
297 298 299 300 301 302
	let { newName } = args;
	if (typeof newName !== 'string') {
		throw illegalArgument('newName');
	}
	return rename(model, position, newName);
});