adopt feedback for #109923

This commit is contained in:
Martin Aeschlimann
2020-11-11 15:44:39 +01:00
parent 776193fdc0
commit e69d768e53
10 changed files with 103 additions and 62 deletions

View File

@@ -824,12 +824,27 @@ export interface DocumentHighlightProvider {
*/ */
export interface OnTypeRenameProvider { export interface OnTypeRenameProvider {
wordPattern?: RegExp;
/** /**
* Provide a list of ranges that can be live-renamed together. * 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<OnTypeRenameRanges>;
}
/**
* 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;
} }
/** /**

View File

@@ -15,7 +15,7 @@ import { Position, IPosition } from 'vs/editor/common/core/position';
import { ITextModel, IModelDeltaDecoration, TrackedRangeStickiness, IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { ITextModel, IModelDeltaDecoration, TrackedRangeStickiness, IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { IRange, Range } from 'vs/editor/common/core/range'; 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 { first, createCancelablePromise, CancelablePromise, Delayer } from 'vs/base/common/async';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { ContextKeyExpr, RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; 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<{ function getOnTypeRenameRanges(model: ITextModel, position: Position, token: CancellationToken): Promise<OnTypeRenameRanges | undefined | null> {
ranges: IRange[],
wordPattern?: RegExp
} | undefined | null> {
const orderedByScore = OnTypeRenameProviderRegistry.ordered(model); 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 // until someone response with a good result
// (good = none empty array) // (good = not null)
return first<{ return first<OnTypeRenameRanges | undefined | null>(orderedByScore.map(provider => async () => {
ranges: IRange[], try {
wordPattern?: RegExp return await provider.provideOnTypeRenameRanges(model, position, token);
} | undefined>(orderedByScore.map(provider => () => { } catch (e) {
return Promise.resolve(provider.provideOnTypeRenameRanges(model, position, token)).then((res) => { onUnexpectedExternalError(e);
if (!res) {
return undefined;
}
return {
ranges: res.ranges,
wordPattern: res.wordPattern || provider.wordPattern
};
}, (err) => {
onUnexpectedExternalError(err);
return undefined; return undefined;
}); }
}), result => !!result && arrays.isNonEmptyArray(result?.ranges)); }), result => !!result && arrays.isNonEmptyArray(result?.ranges));
} }

View File

@@ -16,6 +16,7 @@ import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
import { ITextModel } from 'vs/editor/common/model'; import { ITextModel } from 'vs/editor/common/model';
import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper'; 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 mockFile = URI.parse('test:somefile.ttt');
const mockFileSelector = { scheme: 'test' }; const mockFileSelector = { scheme: 'test' };
@@ -29,6 +30,11 @@ interface TestEditor {
redo(): void; redo(): void;
} }
const languageIdentifier = new modes.LanguageIdentifier('onTypeRenameTestLangage', 74);
LanguageConfigurationRegistry.register(languageIdentifier, {
wordPattern: /[a-zA-Z]+/
});
suite('On type rename', () => { suite('On type rename', () => {
const disposables = new DisposableStore(); const disposables = new DisposableStore();
@@ -42,8 +48,8 @@ suite('On type rename', () => {
function createMockEditor(text: string | string[]): ITestCodeEditor { function createMockEditor(text: string | string[]): ITestCodeEditor {
const model = typeof text === 'string' const model = typeof text === 'string'
? createTextModel(text, undefined, undefined, mockFile) ? createTextModel(text, undefined, languageIdentifier, mockFile)
: createTextModel(text.join('\n'), undefined, undefined, mockFile); : createTextModel(text.join('\n'), undefined, languageIdentifier, mockFile);
const editor = createTestCodeEditor({ model }); const editor = createTestCodeEditor({ model });
disposables.add(model); disposables.add(model);
@@ -55,18 +61,16 @@ suite('On type rename', () => {
function testCase( function testCase(
name: string, name: string,
initialState: { text: string | string[], responseWordPattern?: RegExp, providerWordPattern?: RegExp }, initialState: { text: string | string[], responseWordPattern?: RegExp },
operations: (editor: TestEditor) => Promise<void>, operations: (editor: TestEditor) => Promise<void>,
expectedEndText: string | string[] expectedEndText: string | string[]
) { ) {
test(name, async () => { test(name, async () => {
disposables.add(modes.OnTypeRenameProviderRegistry.register(mockFileSelector, { disposables.add(modes.OnTypeRenameProviderRegistry.register(mockFileSelector, {
wordPattern: initialState.providerWordPattern,
provideOnTypeRenameRanges(model: ITextModel, pos: IPosition) { provideOnTypeRenameRanges(model: ITextModel, pos: IPosition) {
const wordAtPos = model.getWordAtPosition(pos); const wordAtPos = model.getWordAtPosition(pos);
if (wordAtPos) { if (wordAtPos) {
const matches = model.findMatches(wordAtPos.word, false, false, true, USUAL_WORD_SEPARATORS, false); 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: matches.map(m => m.range), wordPattern: initialState.responseWordPattern };
} }
return { ranges: [], wordPattern: initialState.responseWordPattern }; return { ranges: [], wordPattern: initialState.responseWordPattern };
@@ -299,7 +303,7 @@ suite('On type rename', () => {
const state3 = { const state3 = {
...state, ...state,
providerWordPattern: /[a-yA-Y]+/ responseWordPattern: /[a-yA-Y]+/
}; };
testCase('Breakout with stop pattern - insert', state3, async (editor) => { testCase('Breakout with stop pattern - insert', state3, async (editor) => {
@@ -334,7 +338,6 @@ suite('On type rename', () => {
const state4 = { const state4 = {
...state, ...state,
providerWordPattern: /[a-yA-Y]+/,
responseWordPattern: /[a-eA-E]+/ responseWordPattern: /[a-eA-E]+/
}; };

22
src/vs/monaco.d.ts vendored
View File

@@ -5829,14 +5829,26 @@ declare namespace monaco.languages {
* the live-rename feature. * the live-rename feature.
*/ */
export interface OnTypeRenameProvider { export interface OnTypeRenameProvider {
wordPattern?: RegExp;
/** /**
* Provide a list of ranges that can be live-renamed together. * Provide a list of ranges that can be live-renamed together.
*/ */
provideOnTypeRenameRanges(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<{ provideOnTypeRenameRanges(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<OnTypeRenameRanges>;
ranges: IRange[]; }
wordPattern?: RegExp;
}>; /**
* 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;
} }
/** /**

View File

@@ -1115,19 +1115,17 @@ declare module 'vscode' {
export interface OnTypeRenameProvider { export interface OnTypeRenameProvider {
/** /**
* For a given position in a document, returns the range of the symbol at the position and all ranges * 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 have the same content and can be renamed together. Optionally a word pattern can be returned
* that describes valid contents. A rename to one of the ranges can be applied to all other ranges if the new content * to describe valid contents. A rename to one of the ranges can be applied to all other ranges if the new content
* matches the word pattern. * is valid.
* If no result-specific word pattern is provided, the word pattern defined when registering the provider is used. * 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 document The document in which the provider was invoked.
* @param position The position at which the provider was invoked. * @param position The position at which the provider was invoked.
* @param token A cancellation token. * @param token A cancellation token.
* @return A list of ranges that can be renamed together. The ranges must have * @return A list of ranges that can be renamed together
* 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.
*/ */
provideOnTypeRenameRanges(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<{ ranges: Range[]; wordPattern?: RegExp; }>; provideOnTypeRenameRanges(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<OnTypeRenameRanges>;
} }
namespace languages { namespace languages {
@@ -1140,10 +1138,28 @@ declare module 'vscode' {
* *
* @param selector A selector that defines the documents this provider is applicable to. * @param selector A selector that defines the documents this provider is applicable to.
* @param provider An 'on type' rename provider. * @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. * @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 //#endregion

View File

@@ -262,11 +262,9 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
// --- on type rename // --- on type rename
$registerOnTypeRenameProvider(handle: number, selector: IDocumentFilterDto[], wordPattern?: IRegExpDto): void { $registerOnTypeRenameProvider(handle: number, selector: IDocumentFilterDto[]): void {
const revivedWordPattern = wordPattern ? MainThreadLanguageFeatures._reviveRegExp(wordPattern) : undefined;
this._registrations.set(handle, modes.OnTypeRenameProviderRegistry.register(selector, <modes.OnTypeRenameProvider>{ this._registrations.set(handle, modes.OnTypeRenameProviderRegistry.register(selector, <modes.OnTypeRenameProvider>{
wordPattern: revivedWordPattern, provideOnTypeRenameRanges: async (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<modes.OnTypeRenameRanges | undefined> => {
provideOnTypeRenameRanges: async (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<{ ranges: IRange[]; wordPattern?: RegExp; } | undefined> => {
const res = await this._proxy.$provideOnTypeRenameRanges(handle, model.uri, position, token); const res = await this._proxy.$provideOnTypeRenameRanges(handle, model.uri, position, token);
if (res) { if (res) {
return { return {

View File

@@ -397,9 +397,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable { registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable {
return extHostLanguageFeatures.registerDocumentHighlightProvider(extension, checkSelector(selector), provider); 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); 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 { registerReferenceProvider(selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable {
return extHostLanguageFeatures.registerReferenceProvider(extension, checkSelector(selector), provider); return extHostLanguageFeatures.registerReferenceProvider(extension, checkSelector(selector), provider);
@@ -1197,6 +1197,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
NotebookEditorRevealType: extHostTypes.NotebookEditorRevealType, NotebookEditorRevealType: extHostTypes.NotebookEditorRevealType,
NotebookCellOutput: extHostTypes.NotebookCellOutput, NotebookCellOutput: extHostTypes.NotebookCellOutput,
NotebookCellOutputItem: extHostTypes.NotebookCellOutputItem, NotebookCellOutputItem: extHostTypes.NotebookCellOutputItem,
OnTypeRenameRanges: extHostTypes.OnTypeRenameRanges
}; };
}; };
} }

View File

@@ -384,7 +384,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
$registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void;
$registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void;
$registerDocumentHighlightProvider(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; $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void;
$registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string, supportsResolve: boolean): void; $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string, supportsResolve: boolean): void;
$registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; $registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void;
@@ -1395,6 +1395,11 @@ export interface ILanguageWordDefinitionDto {
regexFlags: string regexFlags: string
} }
export interface IOnTypeRenameRangesDto {
ranges: IRange[];
wordPattern?: IRegExpDto;
}
export interface ExtHostLanguageFeaturesShape { export interface ExtHostLanguageFeaturesShape {
$provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise<modes.DocumentSymbol[] | undefined>; $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise<modes.DocumentSymbol[] | undefined>;
$provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise<ICodeLensListDto | undefined>; $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise<ICodeLensListDto | undefined>;
@@ -1407,7 +1412,7 @@ export interface ExtHostLanguageFeaturesShape {
$provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.Hover | undefined>; $provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.Hover | undefined>;
$provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined>; $provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined>;
$provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.DocumentHighlight[] | undefined>; $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.DocumentHighlight[] | undefined>;
$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<IOnTypeRenameRangesDto | undefined>;
$provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise<ILocationDto[] | undefined>; $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise<ILocationDto[] | undefined>;
$provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise<ICodeActionListDto | undefined>; $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise<ICodeActionListDto | undefined>;
$resolveCodeAction(handle: number, id: ChainedCacheId, token: CancellationToken): Promise<IWorkspaceEditDto | undefined>; $resolveCodeAction(handle: number, id: ChainedCacheId, token: CancellationToken): Promise<IWorkspaceEditDto | undefined>;

View File

@@ -317,7 +317,7 @@ class OnTypeRenameAdapter {
private readonly _provider: vscode.OnTypeRenameProvider 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<modes.OnTypeRenameRanges | undefined> {
const doc = this._documents.getDocument(resource); const doc = this._documents.getDocument(resource);
const pos = typeConvert.Position.to(position); const pos = typeConvert.Position.to(position);
@@ -1564,14 +1564,13 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
// --- on type rename // --- 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 handle = this._addNewAdapter(new OnTypeRenameAdapter(this._documents, provider), extension);
const serializedWordPattern = wordPattern ? ExtHostLanguageFeatures._serializeRegExp(wordPattern) : undefined; this._proxy.$registerOnTypeRenameProvider(handle, this._transformDocumentSelector(selector));
this._proxy.$registerOnTypeRenameProvider(handle, this._transformDocumentSelector(selector), serializedWordPattern);
return this._createDisposable(handle); 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<extHostProtocol.IOnTypeRenameRangesDto | undefined> {
return this._withAdapter(handle, OnTypeRenameAdapter, async adapter => { return this._withAdapter(handle, OnTypeRenameAdapter, async adapter => {
const res = await adapter.provideOnTypeRenameRanges(URI.revive(resource), position, token); const res = await adapter.provideOnTypeRenameRanges(URI.revive(resource), position, token);
if (res) { if (res) {

View File

@@ -2891,3 +2891,9 @@ export enum StandardTokenType {
String = 2, String = 2,
RegEx = 4 RegEx = 4
} }
export class OnTypeRenameRanges {
constructor(public readonly ranges: Range[], public readonly wordPattern?: RegExp) {
}
}