diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 5c09c49af21..46e81803edc 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -929,9 +929,14 @@ export interface WorkspaceEdit { rejectReason?: string; // TODO@joh, move to rename } +export interface RenameLocation { + range: IRange; + text: string; +} + export interface RenameProvider { provideRenameEdits(model: model.ITextModel, position: Position, newName: string, token: CancellationToken): WorkspaceEdit | Thenable; - resolveRenameLocation?(model: model.ITextModel, position: Position, token: CancellationToken): IRange | Thenable; + resolveRenameLocation?(model: model.ITextModel, position: Position, token: CancellationToken): RenameLocation | Thenable; } diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 8c1b35360ea..803d4ac0b8c 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -23,10 +23,10 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { optional } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { asWinJsPromise } from 'vs/base/common/async'; -import { WorkspaceEdit, RenameProviderRegistry, RenameProvider } from 'vs/editor/common/modes'; +import { WorkspaceEdit, RenameProviderRegistry, RenameProvider, RenameLocation } from 'vs/editor/common/modes'; import { Position } from 'vs/editor/common/core/position'; import { alert } from 'vs/base/browser/ui/aria/aria'; -import { Range, IRange } from 'vs/editor/common/core/range'; +import { Range } from 'vs/editor/common/core/range'; import { MessageController } from 'vs/editor/contrib/message/messageController'; import { EditorState, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -47,23 +47,26 @@ class RenameSkeleton { return this._provider.length > 0; } - async resolveRenameLocation(): TPromise { + async resolveRenameLocation(): TPromise { let [provider] = this._provider; - let range: IRange; + let res: RenameLocation; if (provider.resolveRenameLocation) { - range = await asWinJsPromise(token => provider.resolveRenameLocation(this.model, this.position, token)); + res = await asWinJsPromise(token => provider.resolveRenameLocation(this.model, this.position, token)); } - if (!range) { + if (!res) { let word = this.model.getWordAtPosition(this.position); if (word) { - range = new Range(this.position.lineNumber, word.startColumn, this.position.lineNumber, word.endColumn); + res = { + range: new Range(this.position.lineNumber, word.startColumn, this.position.lineNumber, word.endColumn), + text: word.word + }; } } - return range; + return res; } async provideRenameEdits(newName: string, i: number = 0, rejects: string[] = [], position: Position = this.position): TPromise { @@ -131,30 +134,29 @@ class RenameController implements IEditorContribution { const position = this.editor.getPosition(); const skeleton = new RenameSkeleton(this.editor.getModel(), position); - let range: IRange; + let loc: RenameLocation; try { - range = await skeleton.resolveRenameLocation(); + loc = await skeleton.resolveRenameLocation(); } catch (e) { MessageController.get(this.editor).showMessage(e, position); return undefined; } - if (!range) { + if (!loc) { return undefined; } - let text = this.editor.getModel().getValueInRange(range); let selection = this.editor.getSelection(); let selectionStart = 0; - let selectionEnd = text.length; + let selectionEnd = loc.text.length; - if (!selection.isEmpty() && selection.startLineNumber === selection.endLineNumber) { - selectionStart = Math.max(0, selection.startColumn - range.startColumn); - selectionEnd = Math.min(range.endColumn, selection.endColumn) - range.startColumn; + 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; } this._renameInputVisible.set(true); - return this._renameInputField.getInput(Range.lift(range), text, selectionStart, selectionEnd).then(newNameOrFocusFlag => { + return this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd).then(newNameOrFocusFlag => { this._renameInputVisible.reset(); if (typeof newNameOrFocusFlag === 'boolean') { @@ -169,7 +171,7 @@ class RenameController implements IEditorContribution { const edit = new BulkEdit(this.editor, null, this._textModelResolverService, this._fileService); const state = new EditorState(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection | CodeEditorStateFlag.Scroll); - const renameOperation = skeleton.provideRenameEdits(newNameOrFocusFlag, 0, [], Range.lift(range).getStartPosition()).then(result => { + const renameOperation = skeleton.provideRenameEdits(newNameOrFocusFlag, 0, [], Range.lift(loc.range).getStartPosition()).then(result => { if (result.rejectReason) { if (state.validate(this.editor)) { MessageController.get(this.editor).showMessage(result.rejectReason, this.editor.getPosition()); @@ -185,7 +187,7 @@ class RenameController implements IEditorContribution { this.editor.setSelection(selection); } // alert - alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", text, newNameOrFocusFlag, edit.ariaMessage())); + alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", loc.text, newNameOrFocusFlag, edit.ariaMessage())); }); }, err => { diff --git a/src/vs/editor/contrib/rename/renameInputField.ts b/src/vs/editor/contrib/rename/renameInputField.ts index 4eb6d89148d..e7f6c7df33a 100644 --- a/src/vs/editor/contrib/rename/renameInputField.ts +++ b/src/vs/editor/contrib/rename/renameInputField.ts @@ -9,7 +9,7 @@ import 'vs/css!./renameInputField'; import { localize } from 'vs/nls'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; -import { Range } from 'vs/editor/common/core/range'; +import { Range, IRange } from 'vs/editor/common/core/range'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { inputBackground, inputBorder, inputForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; @@ -123,7 +123,7 @@ export default class RenameInputField implements IContentWidget, IDisposable { } } - public getInput(where: Range, value: string, selectionStart: number, selectionEnd: number): TPromise { + public getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number): TPromise { this._position = new Position(where.startLineNumber, where.startColumn); this._inputField.value = value; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index b6a73b5120e..d7164a77c2e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5016,9 +5016,14 @@ declare namespace monaco.languages { rejectReason?: string; } + export interface RenameLocation { + range: IRange; + text: string; + } + export interface RenameProvider { provideRenameEdits(model: editor.ITextModel, position: Position, newName: string, token: CancellationToken): WorkspaceEdit | Thenable; - resolveRenameLocation?(model: editor.ITextModel, position: Position, token: CancellationToken): IRange | Thenable; + resolveRenameLocation?(model: editor.ITextModel, position: Position, token: CancellationToken): RenameLocation | Thenable; } export interface Command { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 1a9db25a0f5..01ee496996d 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -503,7 +503,7 @@ declare module 'vscode' { * @param token A cancellation token. * @return The range of the identifier that is to be renamed. The lack of a result can signaled by returning `undefined` or `null`. */ - resolveRenameLocation?(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + resolveRenameLocation?(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; } diff --git a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts index d9057794eab..edebb1a5bd2 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts @@ -13,7 +13,7 @@ import { WorkspaceSymbolProviderRegistry, IWorkspaceSymbolProvider } from 'vs/wo import { wireCancellationToken } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Position as EditorPosition } from 'vs/editor/common/core/position'; -import { Range as EditorRange, IRange } from 'vs/editor/common/core/range'; +import { Range as EditorRange } from 'vs/editor/common/core/range'; import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, SymbolInformationDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter } from '../node/extHost.protocol'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; @@ -258,7 +258,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return wireCancellationToken(token, this._proxy.$provideRenameEdits(handle, model.uri, position, newName)).then(reviveWorkspaceEditDto); }, resolveRenameLocation: supportResolveLocation - ? (model: ITextModel, position: EditorPosition, token: CancellationToken): Thenable => wireCancellationToken(token, this._proxy.$resolveRenameLocation(handle, model.uri, position)) + ? (model: ITextModel, position: EditorPosition, token: CancellationToken): Thenable => wireCancellationToken(token, this._proxy.$resolveRenameLocation(handle, model.uri, position)) : undefined }); } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 4d160267c77..2301dee43bc 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -710,7 +710,7 @@ export interface ExtHostLanguageFeaturesShape { $resolveWorkspaceSymbol(handle: number, symbol: SymbolInformationDto): TPromise; $releaseWorkspaceSymbols(handle: number, id: number): void; $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string): TPromise; - $resolveRenameLocation(handle: number, resource: UriComponents, position: IPosition): TPromise; + $resolveRenameLocation(handle: number, resource: UriComponents, position: IPosition): TPromise; $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.SuggestContext): TPromise; $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, suggestion: modes.ISuggestion): TPromise; $releaseCompletionItems(handle: number, id: number): void; diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index 0d4825c77bd..2c5b7c4eb7d 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -22,6 +22,7 @@ import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; +import { isObject } from 'vs/base/common/types'; // --- adapter @@ -506,7 +507,7 @@ class RenameAdapter { }); } - resolveRenameLocation(resource: URI, position: IPosition): TPromise { + resolveRenameLocation(resource: URI, position: IPosition): TPromise { if (typeof this._provider.resolveRenameLocation !== 'function') { return TPromise.as(undefined); } @@ -514,15 +515,28 @@ class RenameAdapter { let doc = this._documents.getDocumentData(resource).document; let pos = TypeConverters.toPosition(position); - return asWinJsPromise(token => this._provider.resolveRenameLocation(doc, pos, token)).then(range => { + return asWinJsPromise(token => this._provider.resolveRenameLocation(doc, pos, token)).then(rangeOrLocation => { + + let range: vscode.Range; + let text: string; + if (Range.isRange(rangeOrLocation)) { + range = rangeOrLocation; + text = doc.getText(rangeOrLocation); + + } else if (isObject(rangeOrLocation)) { + range = rangeOrLocation.range; + text = rangeOrLocation.placeholder; + } + if (!range) { return undefined; } - if (range && (!range.isSingleLine || range.start.line !== pos.line)) { - console.warn('INVALID rename context, range must be single line and on the same line'); + + if (!range.contains(pos)) { + console.warn('INVALID rename location: range must contain position'); return undefined; } - return TypeConverters.fromRange(range); + return { range: TypeConverters.fromRange(range), text }; }); } } @@ -1088,7 +1102,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._withAdapter(handle, RenameAdapter, adapter => adapter.provideRenameEdits(URI.revive(resource), position, newName)); } - $resolveRenameLocation(handle: number, resource: URI, position: IPosition): TPromise { + $resolveRenameLocation(handle: number, resource: URI, position: IPosition): TPromise { return this._withAdapter(handle, RenameAdapter, adapter => adapter.resolveRenameLocation(resource, position)); } diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index 1423c277882..d988a15447d 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -208,7 +208,7 @@ export class Position { export class Range { - static isRange(thing: any): thing is Range { + static isRange(thing: any): thing is vscode.Range { if (thing instanceof Range) { return true; }