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';
A
Alex Dima 已提交
19
import { 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
import { alert } from 'vs/base/browser/ui/aria/aria';
A
Alex Dima 已提交
30
import { Range } from "vs/editor/common/core/range";
31

J
Johannes Rieken 已提交
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 80

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 已提交
81

82 83
// ---  register actions and commands

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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