rename.ts 9.9 KB
Newer Older
E
Erich Gamma 已提交
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';

8
import * as nls from 'vs/nls';
J
Johannes Rieken 已提交
9
import { illegalArgument, onUnexpectedError } from 'vs/base/common/errors';
J
Johannes Rieken 已提交
10 11 12 13
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';
14
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
15
import { IEditorContribution } from 'vs/editor/common/editorCommon';
A
Alex Dima 已提交
16
import { ITextModel } from 'vs/editor/common/model';
17
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
J
Johannes Rieken 已提交
18
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
A
Alex Dima 已提交
19
import RenameInputField from './renameInputField';
20
import { IThemeService } from 'vs/platform/theme/common/themeService';
J
Johannes Rieken 已提交
21
import { asWinJsPromise } from 'vs/base/common/async';
22
import { WorkspaceEdit, RenameProviderRegistry, RenameProvider, RenameLocation } from 'vs/editor/common/modes';
J
Johannes Rieken 已提交
23
import { Position, IPosition } from 'vs/editor/common/core/position';
24
import { alert } from 'vs/base/browser/ui/aria/aria';
25
import { Range } from 'vs/editor/common/core/range';
26
import { MessageController } from 'vs/editor/contrib/message/messageController';
27
import { EditorState, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState';
28
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
29
import { INotificationService } from 'vs/platform/notification/common/notification';
B
Benjamin Pasero 已提交
30
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
J
Johannes Rieken 已提交
31 32
import URI from 'vs/base/common/uri';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
33

J
Johannes Rieken 已提交
34
class RenameSkeleton {
J
Johannes Rieken 已提交
35

J
Johannes Rieken 已提交
36
	private _provider: RenameProvider[];
J
Johannes Rieken 已提交
37

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

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

49
	async resolveRenameLocation(): TPromise<RenameLocation> {
J
Johannes Rieken 已提交
50 51

		let [provider] = this._provider;
52
		let res: RenameLocation;
J
Johannes Rieken 已提交
53

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

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

68
		return res;
J
Johannes Rieken 已提交
69 70
	}

71
	async provideRenameEdits(newName: string, i: number = 0, rejects: string[] = [], position: Position = this.position): TPromise<WorkspaceEdit> {
72 73 74

		if (i >= this._provider.length) {
			return {
J
Johannes Rieken 已提交
75
				edits: undefined,
76
				rejectReason: rejects.join('\n')
J
Johannes Rieken 已提交
77 78
			};
		}
79 80 81 82 83 84 85 86

		let provider = this._provider[i];
		let result = await asWinJsPromise((token) => provider.provideRenameEdits(this.model, this.position, newName, token));
		if (!result) {
			return this.provideRenameEdits(newName, i + 1, rejects.concat(nls.localize('no result', "No result.")));
		} else if (result.rejectReason) {
			return this.provideRenameEdits(newName, i + 1, rejects.concat(result.rejectReason));
		}
J
Johannes Rieken 已提交
87 88
		return result;
	}
J
Johannes Rieken 已提交
89 90
}

91 92
export async function rename(model: ITextModel, position: Position, newName: string): TPromise<WorkspaceEdit> {
	return new RenameSkeleton(model, position).provideRenameEdits(newName);
93
}
E
Erich Gamma 已提交
94

95 96
// ---  register actions and commands

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

A
Alex Dima 已提交
99
class RenameController implements IEditorContribution {
100

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

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

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

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

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

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

130 131
	public async run(): TPromise<void> {

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

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

139
		let loc: RenameLocation;
140
		try {
141
			loc = await skeleton.resolveRenameLocation();
142 143
		} catch (e) {
			MessageController.get(this.editor).showMessage(e, position);
J
Johannes Rieken 已提交
144
			return undefined;
E
Erich Gamma 已提交
145
		}
146

147
		if (!loc) {
148 149 150
			return undefined;
		}

J
Johannes Rieken 已提交
151 152
		let selection = this.editor.getSelection();
		let selectionStart = 0;
153
		let selectionEnd = loc.text.length;
154

155 156 157
		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 已提交
158 159 160
		}

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

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

J
Johannes Rieken 已提交
171 172
			this.editor.focus();

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

175
			const renameOperation = skeleton.provideRenameEdits(newNameOrFocusFlag, 0, [], Range.lift(loc.range).getStartPosition()).then(result => {
176
				if (result.rejectReason) {
177 178 179
					if (state.validate(this.editor)) {
						MessageController.get(this.editor).showMessage(result.rejectReason, this.editor.getPosition());
					} else {
180
						this._notificationService.info(result.rejectReason);
181
					}
182 183
					return undefined;
				}
E
Erich Gamma 已提交
184

185 186 187
				return this._bulkEditService.apply(result, { editor: this.editor }).then(result => {
					if (result.selection) {
						this.editor.setSelection(result.selection);
E
Erich Gamma 已提交
188
					}
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 TPromise.wrapError(err);
E
Erich Gamma 已提交
198
			});
J
Johannes Rieken 已提交
199 200 201 202

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

E
Erich Gamma 已提交
203 204
		}, err => {
			this._renameInputVisible.reset();
J
Johannes Rieken 已提交
205
			return TPromise.wrapError(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,
230 231 232 233
				primary: KeyCode.F2
			},
			menuOpts: {
				group: '1_modification',
A
Alex Dima 已提交
234
				order: 1.1
235 236
			}
		});
J
Johannes Rieken 已提交
237
	}
A
Alex Dima 已提交
238

J
Johannes Rieken 已提交
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
	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> {
257 258 259 260
		let controller = RenameController.get(editor);
		if (controller) {
			return controller.run();
		}
M
Matt Bierner 已提交
261
		return undefined;
A
Alex Dima 已提交
262 263 264
	}
}

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

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

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

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

// ---- api bridge command

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