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';
J
Johannes Rieken 已提交
19
import { IRange, ICommonCodeEditor, EditorContextKeys, ModeContextKeys, IEditorContribution, IReadOnlyModel } from 'vs/editor/common/editorCommon';
J
Johannes Rieken 已提交
20 21
import { BulkEdit, createBulkEdit } from 'vs/editor/common/services/bulkEdit';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
A
Alex Dima 已提交
22
import RenameInputField from './renameInputField';
23
import { ITextModelResolverService } from 'vs/editor/common/services/resolverService';
24
import { optional } from 'vs/platform/instantiation/common/instantiation';
B
Benjamin Pasero 已提交
25
import { IThemeService } from "vs/platform/theme/common/themeService";
J
Johannes Rieken 已提交
26 27 28
import { sequence, asWinJsPromise } from 'vs/base/common/async';
import { WorkspaceEdit, RenameProviderRegistry } from 'vs/editor/common/modes';
import { Position } from 'vs/editor/common/core/position';
29 30
import { alert } from 'vs/base/browser/ui/aria/aria';

J
Johannes Rieken 已提交
31 32 33 34 35 36 37 38 39 40 41 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

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 => {
		return () => {
			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 已提交
80

81 82
// ---  register actions and commands

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

85
@editorContribution
A
Alex Dima 已提交
86
class RenameController implements IEditorContribution {
87

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

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

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

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

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

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

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

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

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

127
		let lineNumber = selection.startLineNumber,
E
Erich Gamma 已提交
128 129
			selectionStart = 0,
			selectionEnd = word.word.length,
A
Alex Dima 已提交
130
			wordRange: IRange;
E
Erich Gamma 已提交
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146

		wordRange = {
			startLineNumber: lineNumber,
			startColumn: word.startColumn,
			endLineNumber: lineNumber,
			endColumn: word.endColumn
		};

		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 已提交
147 148 149
			this.editor.focus();

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

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

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

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

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

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

	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
195
		let edit = createBulkEdit(this._textModelResolverService, <ICodeEditor>this.editor, this._fileService);
E
Erich Gamma 已提交
196

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

A
Alex Dima 已提交
207 208
// ---- action implementation

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

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

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

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

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

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

// ---- 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);
});