rename.ts 8.6 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 { isPromiseCanceledError, onUnexpectedExternalError, illegalArgument } from 'vs/base/common/errors';
J
Johannes Rieken 已提交
10
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
A
Alex Dima 已提交
11
import Severity from 'vs/base/common/severity';
J
Johannes Rieken 已提交
12
import { TPromise } from 'vs/base/common/winjs.base';
13
import { IFileService } from 'vs/platform/files/common/files';
J
Johannes Rieken 已提交
14 15 16 17 18
import { RawContextKey, IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IMessageService } from 'vs/platform/message/common/message';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { editorAction, ServicesAccessor, EditorAction, EditorCommand, CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions';
import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions';
19
import { ICommonCodeEditor, IEditorContribution, IReadOnlyModel } from 'vs/editor/common/editorCommon';
20
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
J
Johannes Rieken 已提交
21 22
import { BulkEdit, createBulkEdit } from 'vs/editor/common/services/bulkEdit';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
A
Alex Dima 已提交
23
import RenameInputField from './renameInputField';
24
import { ITextModelService } from 'vs/editor/common/services/resolverService';
25
import { optional } from 'vs/platform/instantiation/common/instantiation';
26
import { IThemeService } from 'vs/platform/theme/common/themeService';
J
Johannes Rieken 已提交
27 28 29
import { sequence, asWinJsPromise } from 'vs/base/common/async';
import { WorkspaceEdit, RenameProviderRegistry } from 'vs/editor/common/modes';
import { Position } from 'vs/editor/common/core/position';
30
import { alert } from 'vs/base/browser/ui/aria/aria';
31
import { Range } from 'vs/editor/common/core/range';
32

J
Johannes Rieken 已提交
33 34 35 36 37 38 39 40

export function rename(model: IReadOnlyModel, position: Position, newName: string): TPromise<WorkspaceEdit> {

	const supports = RenameProviderRegistry.ordered(model);
	const rejects: string[] = [];
	let hasResult = false;

	const factory = supports.map(support => {
41
		return (): TPromise<WorkspaceEdit> => {
J
Johannes Rieken 已提交
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
			if (!hasResult) {
				return asWinJsPromise((token) => {
					return support.provideRenameEdits(model, position, newName, token);
				}).then(result => {
					if (!result) {
						// ignore
					} else if (!result.rejectReason) {
						hasResult = true;
						return result;
					} else {
						rejects.push(result.rejectReason);
					}
					return undefined;
				}, err => {
					onUnexpectedExternalError(err);
					return TPromise.wrapError<WorkspaceEdit>('provider failed');
				});
			}
			return undefined;
		};
	});

	return sequence(factory).then((values): WorkspaceEdit => {
		let result = values[0];
		if (rejects.length > 0) {
			return {
				edits: undefined,
				rejectReason: rejects.join('\n')
			};
		} else if (!result) {
			return {
				edits: undefined,
				rejectReason: nls.localize('no result', "No result.")
			};
		} else {
			return result;
		}
	});
}

E
Erich Gamma 已提交
82

83 84
// ---  register actions and commands

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

87
@editorContribution
A
Alex Dima 已提交
88
class RenameController implements IEditorContribution {
89

A
Alex Dima 已提交
90
	private static ID = 'editor.contrib.renameController';
E
Erich Gamma 已提交
91

J
Johannes Rieken 已提交
92
	public static get(editor: ICommonCodeEditor): RenameController {
A
Alex Dima 已提交
93
		return editor.getContribution<RenameController>(RenameController.ID);
A
Alex Dima 已提交
94
	}
E
Erich Gamma 已提交
95 96

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

A
Alex Dima 已提交
99
	constructor(
J
Johannes Rieken 已提交
100
		private editor: ICodeEditor,
J
Johannes Rieken 已提交
101
		@IMessageService private _messageService: IMessageService,
102
		@ITextModelService private _textModelResolverService: ITextModelService,
J
Johannes Rieken 已提交
103
		@IProgressService private _progressService: IProgressService,
104
		@IContextKeyService contextKeyService: IContextKeyService,
B
Benjamin Pasero 已提交
105
		@IThemeService themeService: IThemeService,
106
		@optional(IFileService) private _fileService: IFileService
J
Johannes Rieken 已提交
107
	) {
B
Benjamin Pasero 已提交
108
		this._renameInputField = new RenameInputField(editor, themeService);
109
		this._renameInputVisible = CONTEXT_RENAME_INPUT_VISIBLE.bindTo(contextKeyService);
E
Erich Gamma 已提交
110 111
	}

A
Alex Dima 已提交
112 113
	public dispose(): void {
		this._renameInputField.dispose();
114 115
	}

A
Alex Dima 已提交
116 117
	public getId(): string {
		return RenameController.ID;
118 119
	}

A
Alex Dima 已提交
120
	public run(): TPromise<void> {
E
Erich Gamma 已提交
121

122
		const selection = this.editor.getSelection(),
E
Erich Gamma 已提交
123 124 125
			word = this.editor.getModel().getWordAtPosition(selection.getStartPosition());

		if (!word) {
M
Matt Bierner 已提交
126
			return undefined;
E
Erich Gamma 已提交
127 128
		}

129
		let lineNumber = selection.startLineNumber,
E
Erich Gamma 已提交
130 131
			selectionStart = 0,
			selectionEnd = word.word.length,
A
Alex Dima 已提交
132 133 134 135 136 137 138 139
			wordRange: Range;

		wordRange = new Range(
			lineNumber,
			word.startColumn,
			lineNumber,
			word.endColumn
		);
E
Erich Gamma 已提交
140 141 142 143 144 145 146 147 148

		if (!selection.isEmpty() && selection.startLineNumber === selection.endLineNumber) {
			selectionStart = Math.max(0, selection.startColumn - word.startColumn);
			selectionEnd = Math.min(word.endColumn, selection.endColumn) - word.startColumn;
		}

		this._renameInputVisible.set(true);
		return this._renameInputField.getInput(wordRange, word.word, selectionStart, selectionEnd).then(newName => {
			this._renameInputVisible.reset();
J
Johannes Rieken 已提交
149 150 151
			this.editor.focus();

			const renameOperation = this._prepareRename(newName).then(edit => {
E
Erich Gamma 已提交
152 153 154 155 156

				return edit.finish().then(selection => {
					if (selection) {
						this.editor.setSelection(selection);
					}
157 158
					// alert
					alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", word.word, newName, edit.ariaMessage()));
E
Erich Gamma 已提交
159 160 161 162 163
				});

			}, err => {
				if (typeof err === 'string') {
					this._messageService.show(Severity.Info, err);
M
Matt Bierner 已提交
164
					return undefined;
E
Erich Gamma 已提交
165
				} else {
J
Johannes Rieken 已提交
166
					this._messageService.show(Severity.Error, nls.localize('rename.failed', "Sorry, rename failed to execute."));
E
Erich Gamma 已提交
167 168 169
					return TPromise.wrapError(err);
				}
			});
J
Johannes Rieken 已提交
170 171 172 173

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

E
Erich Gamma 已提交
174 175 176 177
		}, err => {
			this._renameInputVisible.reset();
			this.editor.focus();

A
Alex Dima 已提交
178
			if (!isPromiseCanceledError(err)) {
E
Erich Gamma 已提交
179 180
				return TPromise.wrapError(err);
			}
M
Matt Bierner 已提交
181
			return undefined;
E
Erich Gamma 已提交
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
		});
	}

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

	public cancelRenameInput(): void {
		this._renameInputField.cancelInput();
	}

	private _prepareRename(newName: string): TPromise<BulkEdit> {

		// start recording of file changes so that we can figure out if a file that
		// is to be renamed conflicts with another (concurrent) modification
197
		let edit = createBulkEdit(this._textModelResolverService, <ICodeEditor>this.editor, this._fileService);
E
Erich Gamma 已提交
198

J
Johannes Rieken 已提交
199 200
		return rename(this.editor.getModel(), this.editor.getPosition(), newName).then(result => {
			if (result.rejectReason) {
201
				return TPromise.wrapError<BulkEdit>(result.rejectReason);
E
Erich Gamma 已提交
202 203 204 205 206 207
			}
			edit.add(result.edits);
			return edit;
		});
	}
}
J
Johannes Rieken 已提交
208

A
Alex Dima 已提交
209 210
// ---- action implementation

A
Alex Dima 已提交
211
@editorAction
A
Alex Dima 已提交
212
export class RenameAction extends EditorAction {
J
Johannes Rieken 已提交
213

A
Alex Dima 已提交
214
	constructor() {
215 216 217 218
		super({
			id: 'editor.action.rename',
			label: nls.localize('rename.label', "Rename Symbol"),
			alias: 'Rename Symbol',
219
			precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasRenameProvider),
220
			kbOpts: {
221
				kbExpr: EditorContextKeys.textFocus,
222 223 224 225
				primary: KeyCode.F2
			},
			menuOpts: {
				group: '1_modification',
A
Alex Dima 已提交
226
				order: 1.1
227 228
			}
		});
J
Johannes Rieken 已提交
229
	}
A
Alex Dima 已提交
230

J
Johannes Rieken 已提交
231
	public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): TPromise<void> {
232 233 234 235
		let controller = RenameController.get(editor);
		if (controller) {
			return controller.run();
		}
M
Matt Bierner 已提交
236
		return undefined;
A
Alex Dima 已提交
237 238 239
	}
}

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

242
CommonEditorRegistry.registerEditorCommand(new RenameCommand({
243 244 245 246 247
	id: 'acceptRenameInput',
	precondition: CONTEXT_RENAME_INPUT_VISIBLE,
	handler: x => x.acceptRenameInput(),
	kbOpts: {
		weight: CommonEditorRegistry.commandWeight(99),
248
		kbExpr: EditorContextKeys.focus,
A
Alex Dima 已提交
249 250
		primary: KeyCode.Enter
	}
251
}));
A
Alex Dima 已提交
252

253
CommonEditorRegistry.registerEditorCommand(new RenameCommand({
254 255 256 257 258
	id: 'cancelRenameInput',
	precondition: CONTEXT_RENAME_INPUT_VISIBLE,
	handler: x => x.cancelRenameInput(),
	kbOpts: {
		weight: CommonEditorRegistry.commandWeight(99),
259
		kbExpr: EditorContextKeys.focus,
A
Alex Dima 已提交
260 261 262
		primary: KeyCode.Escape,
		secondary: [KeyMod.Shift | KeyCode.Escape]
	}
263
}));
J
Johannes Rieken 已提交
264 265 266 267 268 269 270 271 272 273

// ---- api bridge command

CommonEditorRegistry.registerDefaultLanguageCommand('_executeDocumentRenameProvider', function (model, position, args) {
	let { newName } = args;
	if (typeof newName !== 'string') {
		throw illegalArgument('newName');
	}
	return rename(model, position, newName);
});