diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index a09587997eebd..c7fd1b7b243f2 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -856,8 +856,14 @@ export interface WorkspaceEdit { rejectReason?: string; // TODO@joh, move to rename } +export interface RenameInitialValue { + range: IRange; + text?: string; +} + export interface RenameProvider { provideRenameEdits(model: model.ITextModel, position: Position, newName: string, token: CancellationToken): WorkspaceEdit | Thenable; + resolveInitialRenameValue?(model: model.ITextModel, position: Position, token: CancellationToken): RenameInitialValue | Thenable; } diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index b25b9b3f74b0c..e164c10e766e1 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -6,7 +6,7 @@ 'use strict'; import * as nls from 'vs/nls'; -import { isPromiseCanceledError, illegalArgument } from 'vs/base/common/errors'; +import { isPromiseCanceledError, illegalArgument, onUnexpectedExternalError } from 'vs/base/common/errors'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import Severity from 'vs/base/common/severity'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -25,7 +25,7 @@ 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 { sequence, asWinJsPromise } from 'vs/base/common/async'; -import { WorkspaceEdit, RenameProviderRegistry } from 'vs/editor/common/modes'; +import { WorkspaceEdit, RenameProviderRegistry, RenameInitialValue } from 'vs/editor/common/modes'; import { Position } from 'vs/editor/common/core/position'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { Range } from 'vs/editor/common/core/range'; @@ -79,6 +79,19 @@ export function rename(model: ITextModel, position: Position, newName: string): }); } +function resolveInitialRenameValue(model: ITextModel, position: Position): TPromise { + const supports = RenameProviderRegistry.ordered(model); + return asWinJsPromise((token) => + supports.length > 0 + ? supports[0].resolveInitialRenameValue(model, position, token) //Use first rename provider so that we always use the same for resolving the location and for the actual rename + : undefined + ).then(result => { + return !result ? undefined : result; + }, err => { + onUnexpectedExternalError(err); + return TPromise.wrapError(new Error('provider failed')); + }); +} // --- register actions and commands @@ -116,34 +129,64 @@ class RenameController implements IEditorContribution { return RenameController.ID; } - public run(): TPromise { + public async run(): TPromise { + const selection = this.editor.getSelection(); + + let lineNumber = selection.startLineNumber, + selectionStart = 0, + selectionEnd = 0, + wordRange: Range, + word: string; - const selection = this.editor.getSelection(), - word = this.editor.getModel().getWordAtPosition(selection.getStartPosition()); + let initialValue = await resolveInitialRenameValue(this.editor.getModel(), this.editor.getPosition()); + + if(initialValue) { + lineNumber = initialValue.range.startLineNumber; + if(initialValue.text) { + word = initialValue.text; + } + else { + word = this.editor.getModel().getValueInRange(initialValue.range); + } + selectionEnd = word.length; + + if (!selection.isEmpty() && selection.startLineNumber === selection.endLineNumber) { + selectionStart = Math.max(0, selection.startColumn - initialValue.range.startColumn); + selectionEnd = Math.min(initialValue.range.endColumn, selection.endColumn) - initialValue.range.startColumn; + } + + wordRange = new Range( + lineNumber, + initialValue.range.startColumn, + lineNumber, + initialValue.range.endColumn + ); - if (!word) { - return undefined; } + else { + const wordAtPosition = this.editor.getModel().getWordAtPosition(selection.getStartPosition()); - let lineNumber = selection.startLineNumber, - selectionStart = 0, - selectionEnd = word.word.length, - wordRange: Range; - - wordRange = new Range( - lineNumber, - word.startColumn, - lineNumber, - 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; + if (!wordAtPosition) { + return undefined; + } + word = wordAtPosition.word; + selectionEnd = word.length; + + if (!selection.isEmpty() && selection.startLineNumber === selection.endLineNumber) { + selectionStart = Math.max(0, selection.startColumn - wordAtPosition.startColumn); + selectionEnd = Math.min(wordAtPosition.endColumn, selection.endColumn) - wordAtPosition.startColumn; + } + + wordRange = new Range( + lineNumber, + wordAtPosition.startColumn, + lineNumber, + wordAtPosition.endColumn + ); } this._renameInputVisible.set(true); - return this._renameInputField.getInput(wordRange, word.word, selectionStart, selectionEnd).then(newName => { + return this._renameInputField.getInput(wordRange, word, selectionStart, selectionEnd).then(newName => { this._renameInputVisible.reset(); this.editor.focus(); @@ -166,7 +209,7 @@ class RenameController implements IEditorContribution { this.editor.setSelection(selection); } // alert - alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", word.word, newName, edit.ariaMessage())); + alert(nls.localize('aria', "Successfully renamed '{0}' to '{1}'. Summary: {2}", word, newName, edit.ariaMessage())); }); }, err => { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index a4857e585023e..de1d4a87e4504 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4956,8 +4956,14 @@ declare module monaco.languages { rejectReason?: string; } + export interface RenameInitialValue { + range: IRange; + text?: string; + } + export interface RenameProvider { provideRenameEdits(model: editor.ITextModel, position: Position, newName: string, token: CancellationToken): WorkspaceEdit | Thenable; + resolveInitialRenameValue?(model: editor.ITextModel, position: Position, token: CancellationToken): RenameInitialValue | Thenable; } export interface Command { diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 60976131c0273..b1ca5266c4f29 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2664,6 +2664,11 @@ declare module 'vscode' { appendVariable(name: string, defaultValue: string | ((snippet: SnippetString) => any)): SnippetString; } + export interface RenameInitialValue { + range: Range + text?: string + } + /** * The rename provider interface defines the contract between extensions and * the [rename](https://code.visualstudio.com/docs/editor/editingevolved#_rename-symbol)-feature. @@ -2682,6 +2687,8 @@ declare module 'vscode' { * signaled by returning `undefined` or `null`. */ provideRenameEdits(document: TextDocument, position: Position, newName: string, token: CancellationToken): ProviderResult; + + resolveInitialRenameValue?(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 1d658c9e33aa4..df18b3837a5b8 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts @@ -251,10 +251,14 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- rename - $registerRenameSupport(handle: number, selector: vscode.DocumentSelector): void { + $registerRenameSupport(handle: number, selector: vscode.DocumentSelector, supportsResolveInitialValues: boolean): void { this._registrations[handle] = modes.RenameProviderRegistry.register(toLanguageSelector(selector), { provideRenameEdits: (model: ITextModel, position: EditorPosition, newName: string, token: CancellationToken): Thenable => { return wireCancellationToken(token, this._proxy.$provideRenameEdits(handle, model.uri, position, newName)).then(reviveWorkspaceEditDto); + }, + resolveInitialRenameValue: supportsResolveInitialValues + ? (model: ITextModel, position: EditorPosition, token: CancellationToken): Thenable => wireCancellationToken(token, this._proxy.$resolveInitialRenameValue(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 8d51f8782f5c4..e289cf18c822f 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -277,7 +277,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerRangeFormattingSupport(handle: number, selector: vscode.DocumentSelector): void; $registerOnTypeFormattingSupport(handle: number, selector: vscode.DocumentSelector, autoFormatTriggerCharacters: string[]): void; $registerNavigateTypeSupport(handle: number): void; - $registerRenameSupport(handle: number, selector: vscode.DocumentSelector): void; + $registerRenameSupport(handle: number, selector: vscode.DocumentSelector, supportsResolveInitialValues: boolean): void; $registerSuggestSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacters: string[], supportsResolveDetails: boolean): void; $registerSignatureHelpProvider(handle: number, selector: vscode.DocumentSelector, triggerCharacter: string[]): void; $registerDocumentLinkProvider(handle: number, selector: vscode.DocumentSelector): void; @@ -669,6 +669,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; + $resolveInitialRenameValue(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 164f55f6ad1fd..7bf9029a45ea8 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -470,6 +470,10 @@ class NavigateTypeAdapter { class RenameAdapter { + static supportsResolving(provider: vscode.RenameProvider): boolean { + return typeof provider.resolveInitialRenameValue === 'function'; + } + private _documents: ExtHostDocuments; private _provider: vscode.RenameProvider; @@ -505,6 +509,22 @@ class RenameAdapter { } }); } + + resolveInitialRenameValue(resource: URI, position: IPosition) : TPromise { + if (typeof this._provider.resolveInitialRenameValue !== 'function') { + return TPromise.as(undefined); + } + + let doc = this._documents.getDocumentData(resource).document; + let pos = TypeConverters.toPosition(position); + + return asWinJsPromise(token => this._provider.resolveInitialRenameValue(doc, pos, token)).then((value) => { + return { + range: TypeConverters.fromRange(value.range), + text: value.text + }; + }); + } } @@ -1010,7 +1030,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { registerRenameProvider(selector: vscode.DocumentSelector, provider: vscode.RenameProvider): vscode.Disposable { const handle = this._addNewAdapter(new RenameAdapter(this._documents, provider)); - this._proxy.$registerRenameSupport(handle, selector); + this._proxy.$registerRenameSupport(handle, selector, RenameAdapter.supportsResolving(provider)); return this._createDisposable(handle); } @@ -1018,6 +1038,10 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._withAdapter(handle, RenameAdapter, adapter => adapter.provideRenameEdits(URI.revive(resource), position, newName)); } + $resolveInitialRenameValue(handle: number, resource: URI, position: IPosition): TPromise { + return this._withAdapter(handle, RenameAdapter, adapter => adapter.resolveInitialRenameValue(resource, position)); + } + // --- suggestion registerCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable {