diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 4cdbc83339ecab087c36168be67a6cc9c73117e2..5392d0931cf026b8da1f40d79b9cbbf74079fcf3 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -824,12 +824,27 @@ export interface DocumentHighlightProvider { */ export interface OnTypeRenameProvider { - wordPattern?: RegExp; - /** * Provide a list of ranges that can be live-renamed together. */ - provideOnTypeRenameRanges(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<{ ranges: IRange[]; wordPattern?: RegExp; }>; + provideOnTypeRenameRanges(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult; +} + +/** + * Represents a list of ranges that can be renamed together along with a word pattern to describe valid range contents. + */ +export interface OnTypeRenameRanges { + /** + * A list of ranges that can be renamed together. The ranges must have + * identical length and contain identical text content. The ranges cannot overlap + */ + ranges: IRange[]; + + /** + * An optional word pattern that describes valid contents for the given ranges. + * If no pattern is provided, the language configuration's word pattern will be used. + */ + wordPattern?: RegExp; } /** diff --git a/src/vs/editor/contrib/rename/onTypeRename.ts b/src/vs/editor/contrib/rename/onTypeRename.ts index 105dfb2ddb2ca9fbb17ee7b3dba3231dc7568b2c..bc9962d57b2395509c8e92e4686e6dbb770ff521 100644 --- a/src/vs/editor/contrib/rename/onTypeRename.ts +++ b/src/vs/editor/contrib/rename/onTypeRename.ts @@ -15,7 +15,7 @@ import { Position, IPosition } from 'vs/editor/common/core/position'; import { ITextModel, IModelDeltaDecoration, TrackedRangeStickiness, IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IRange, Range } from 'vs/editor/common/core/range'; -import { OnTypeRenameProviderRegistry } from 'vs/editor/common/modes'; +import { OnTypeRenameProviderRegistry, OnTypeRenameRanges } from 'vs/editor/common/modes'; import { first, createCancelablePromise, CancelablePromise, Delayer } from 'vs/base/common/async'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { ContextKeyExpr, RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -419,33 +419,19 @@ registerEditorCommand(new OnTypeRenameCommand({ })); -export function getOnTypeRenameRanges(model: ITextModel, position: Position, token: CancellationToken): Promise<{ - ranges: IRange[], - wordPattern?: RegExp -} | undefined | null> { +function getOnTypeRenameRanges(model: ITextModel, position: Position, token: CancellationToken): Promise { const orderedByScore = OnTypeRenameProviderRegistry.ordered(model); - // in order of score ask the occurrences provider + // in order of score ask the on type rename provider // until someone response with a good result - // (good = none empty array) - return first<{ - ranges: IRange[], - wordPattern?: RegExp - } | undefined>(orderedByScore.map(provider => () => { - return Promise.resolve(provider.provideOnTypeRenameRanges(model, position, token)).then((res) => { - if (!res) { - return undefined; - } - - return { - ranges: res.ranges, - wordPattern: res.wordPattern || provider.wordPattern - }; - }, (err) => { - onUnexpectedExternalError(err); + // (good = not null) + return first(orderedByScore.map(provider => async () => { + try { + return await provider.provideOnTypeRenameRanges(model, position, token); + } catch (e) { + onUnexpectedExternalError(e); return undefined; - }); - + } }), result => !!result && arrays.isNonEmptyArray(result?.ranges)); } diff --git a/src/vs/editor/contrib/rename/test/onTypeRename.test.ts b/src/vs/editor/contrib/rename/test/onTypeRename.test.ts index 05dba5e4b62419bb78c3fbe9ece01f383b9a3876..354e5f12aa95a9365dade906ae6b72d0f874c9df 100644 --- a/src/vs/editor/contrib/rename/test/onTypeRename.test.ts +++ b/src/vs/editor/contrib/rename/test/onTypeRename.test.ts @@ -16,6 +16,7 @@ import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; import { ITextModel } from 'vs/editor/common/model'; import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper'; +import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; const mockFile = URI.parse('test:somefile.ttt'); const mockFileSelector = { scheme: 'test' }; @@ -29,6 +30,11 @@ interface TestEditor { redo(): void; } +const languageIdentifier = new modes.LanguageIdentifier('onTypeRenameTestLangage', 74); +LanguageConfigurationRegistry.register(languageIdentifier, { + wordPattern: /[a-zA-Z]+/ +}); + suite('On type rename', () => { const disposables = new DisposableStore(); @@ -42,8 +48,8 @@ suite('On type rename', () => { function createMockEditor(text: string | string[]): ITestCodeEditor { const model = typeof text === 'string' - ? createTextModel(text, undefined, undefined, mockFile) - : createTextModel(text.join('\n'), undefined, undefined, mockFile); + ? createTextModel(text, undefined, languageIdentifier, mockFile) + : createTextModel(text.join('\n'), undefined, languageIdentifier, mockFile); const editor = createTestCodeEditor({ model }); disposables.add(model); @@ -55,18 +61,16 @@ suite('On type rename', () => { function testCase( name: string, - initialState: { text: string | string[], responseWordPattern?: RegExp, providerWordPattern?: RegExp }, + initialState: { text: string | string[], responseWordPattern?: RegExp }, operations: (editor: TestEditor) => Promise, expectedEndText: string | string[] ) { test(name, async () => { disposables.add(modes.OnTypeRenameProviderRegistry.register(mockFileSelector, { - wordPattern: initialState.providerWordPattern, provideOnTypeRenameRanges(model: ITextModel, pos: IPosition) { const wordAtPos = model.getWordAtPosition(pos); if (wordAtPos) { const matches = model.findMatches(wordAtPos.word, false, false, true, USUAL_WORD_SEPARATORS, false); - assert.ok(matches.length > 0); return { ranges: matches.map(m => m.range), wordPattern: initialState.responseWordPattern }; } return { ranges: [], wordPattern: initialState.responseWordPattern }; @@ -299,7 +303,7 @@ suite('On type rename', () => { const state3 = { ...state, - providerWordPattern: /[a-yA-Y]+/ + responseWordPattern: /[a-yA-Y]+/ }; testCase('Breakout with stop pattern - insert', state3, async (editor) => { @@ -334,7 +338,6 @@ suite('On type rename', () => { const state4 = { ...state, - providerWordPattern: /[a-yA-Y]+/, responseWordPattern: /[a-eA-E]+/ }; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index a5a2fa1c36eb6366673591bc22d86c24450c3eee..d5e796cf31bb19853cf74970523e9469c2b3ea36 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5829,14 +5829,26 @@ declare namespace monaco.languages { * the live-rename feature. */ export interface OnTypeRenameProvider { - wordPattern?: RegExp; /** * Provide a list of ranges that can be live-renamed together. */ - provideOnTypeRenameRanges(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<{ - ranges: IRange[]; - wordPattern?: RegExp; - }>; + provideOnTypeRenameRanges(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult; + } + + /** + * Represents a list of ranges that can be renamed together along with a word pattern to describe valid range contents. + */ + export interface OnTypeRenameRanges { + /** + * A list of ranges that can be renamed together. The ranges must have + * identical length and contain identical text content. The ranges cannot overlap. + */ + ranges: IRange[]; + /** + * An optional word pattern that describes valid contents for the given ranges. + * If no pattern is provided, the language configuration's word pattern will be used. + */ + wordPattern?: RegExp; } /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index f0596e4e745637d005fecebe3d5bfc152d5d9179..e03653f0981f062fe00a166070b94a2aebb47b05 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1115,19 +1115,17 @@ declare module 'vscode' { export interface OnTypeRenameProvider { /** * For a given position in a document, returns the range of the symbol at the position and all ranges - * that have the same content and can be renamed together. Optionally a result specific word pattern can be returned as well - * that describes valid contents. A rename to one of the ranges can be applied to all other ranges if the new content - * matches the word pattern. - * If no result-specific word pattern is provided, the word pattern defined when registering the provider is used. + * that have the same content and can be renamed together. Optionally a word pattern can be returned + * to describe valid contents. A rename to one of the ranges can be applied to all other ranges if the new content + * is valid. + * If no result-specific word pattern is provided, the word pattern from the language configuration is used. * * @param document The document in which the provider was invoked. * @param position The position at which the provider was invoked. * @param token A cancellation token. - * @return A list of ranges that can be renamed together. The ranges must have - * identical length and contain identical text content. The ranges cannot overlap. Optionally a word pattern - * that overrides the word pattern defined when registering the provider can be provided. + * @return A list of ranges that can be renamed together */ - provideOnTypeRenameRanges(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<{ ranges: Range[]; wordPattern?: RegExp; }>; + provideOnTypeRenameRanges(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; } namespace languages { @@ -1140,10 +1138,28 @@ declare module 'vscode' { * * @param selector A selector that defines the documents this provider is applicable to. * @param provider An 'on type' rename provider. - * @param wordPattern A word pattern to describes valid contents of renamed ranges. * @return A [disposable](#Disposable) that unregisters this provider when being disposed. */ - export function registerOnTypeRenameProvider(selector: DocumentSelector, provider: OnTypeRenameProvider, wordPattern?: RegExp): Disposable; + export function registerOnTypeRenameProvider(selector: DocumentSelector, provider: OnTypeRenameProvider): Disposable; + } + + /** + * Represents a list of ranges that can be renamed together along with a word pattern to describe valid range contents. + */ + export class OnTypeRenameRanges { + constructor(ranges: Range[], wordPattern?: RegExp); + + /** + * A list of ranges that can be renamed together. The ranges must have + * identical length and contain identical text content. The ranges cannot overlap. + */ + readonly ranges: Range[]; + + /** + * An optional word pattern that describes valid contents for the given ranges. + * If no pattern is provided, the language configuration's word pattern will be used. + */ + readonly wordPattern?: RegExp; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 338249921425529f39d3512fb471f3a5480d48db..5e06096a342bc577c7056a0d2f9a0bc4dbbf6af3 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -262,11 +262,9 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- on type rename - $registerOnTypeRenameProvider(handle: number, selector: IDocumentFilterDto[], wordPattern?: IRegExpDto): void { - const revivedWordPattern = wordPattern ? MainThreadLanguageFeatures._reviveRegExp(wordPattern) : undefined; + $registerOnTypeRenameProvider(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.OnTypeRenameProviderRegistry.register(selector, { - wordPattern: revivedWordPattern, - provideOnTypeRenameRanges: async (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<{ ranges: IRange[]; wordPattern?: RegExp; } | undefined> => { + provideOnTypeRenameRanges: async (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => { const res = await this._proxy.$provideOnTypeRenameRanges(handle, model.uri, position, token); if (res) { return { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 284c6aff854a747d1202c34581a1419c35e9654f..1adb95a19ba31cc76e01da59215df3e596dc9189 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -397,9 +397,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable { return extHostLanguageFeatures.registerDocumentHighlightProvider(extension, checkSelector(selector), provider); }, - registerOnTypeRenameProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeRenameProvider, stopPattern?: RegExp): vscode.Disposable { + registerOnTypeRenameProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeRenameProvider): vscode.Disposable { checkProposedApiEnabled(extension); - return extHostLanguageFeatures.registerOnTypeRenameProvider(extension, checkSelector(selector), provider, stopPattern); + return extHostLanguageFeatures.registerOnTypeRenameProvider(extension, checkSelector(selector), provider); }, registerReferenceProvider(selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable { return extHostLanguageFeatures.registerReferenceProvider(extension, checkSelector(selector), provider); @@ -1197,6 +1197,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I NotebookEditorRevealType: extHostTypes.NotebookEditorRevealType, NotebookCellOutput: extHostTypes.NotebookCellOutput, NotebookCellOutputItem: extHostTypes.NotebookCellOutputItem, + OnTypeRenameRanges: extHostTypes.OnTypeRenameRanges }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 58cf92253626925314f910e56571ce7365bb5111..2d70cd338f8036135559da57bec04313cf8ce17f 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -384,7 +384,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void; - $registerOnTypeRenameProvider(handle: number, selector: IDocumentFilterDto[], stopPattern: IRegExpDto | undefined): void; + $registerOnTypeRenameProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void; $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string, supportsResolve: boolean): void; $registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; @@ -1395,6 +1395,11 @@ export interface ILanguageWordDefinitionDto { regexFlags: string } +export interface IOnTypeRenameRangesDto { + ranges: IRange[]; + wordPattern?: IRegExpDto; +} + export interface ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise; $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; @@ -1407,7 +1412,7 @@ export interface ExtHostLanguageFeaturesShape { $provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; - $provideOnTypeRenameRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<{ ranges: IRange[]; wordPattern?: IRegExpDto; } | undefined>; + $provideOnTypeRenameRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise; $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise; $resolveCodeAction(handle: number, id: ChainedCacheId, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 77c0b391594d4d7f069bd243fd77e55232147af1..ac81660326dd00bc03a3a542b2a17b6698146276 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -317,7 +317,7 @@ class OnTypeRenameAdapter { private readonly _provider: vscode.OnTypeRenameProvider ) { } - provideOnTypeRenameRanges(resource: URI, position: IPosition, token: CancellationToken): Promise<{ ranges: IRange[]; wordPattern?: RegExp; } | undefined> { + provideOnTypeRenameRanges(resource: URI, position: IPosition, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); @@ -1564,14 +1564,13 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- on type rename - registerOnTypeRenameProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.OnTypeRenameProvider, wordPattern?: RegExp): vscode.Disposable { + registerOnTypeRenameProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.OnTypeRenameProvider): vscode.Disposable { const handle = this._addNewAdapter(new OnTypeRenameAdapter(this._documents, provider), extension); - const serializedWordPattern = wordPattern ? ExtHostLanguageFeatures._serializeRegExp(wordPattern) : undefined; - this._proxy.$registerOnTypeRenameProvider(handle, this._transformDocumentSelector(selector), serializedWordPattern); + this._proxy.$registerOnTypeRenameProvider(handle, this._transformDocumentSelector(selector)); return this._createDisposable(handle); } - $provideOnTypeRenameRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<{ ranges: IRange[]; wordPattern?: extHostProtocol.IRegExpDto; } | undefined> { + $provideOnTypeRenameRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { return this._withAdapter(handle, OnTypeRenameAdapter, async adapter => { const res = await adapter.provideOnTypeRenameRanges(URI.revive(resource), position, token); if (res) { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 158eb96407d36bf7a9bc44112b33396203fbeac1..c2c5c6ebbc80b333ff824d366f3f0a736b2a5a04 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2891,3 +2891,9 @@ export enum StandardTokenType { String = 2, RegEx = 4 } + + +export class OnTypeRenameRanges { + constructor(public readonly ranges: Range[], public readonly wordPattern?: RegExp) { + } +}