Skip to content

Commit

Permalink
Add experimental recursive version of executeHoverProvider command (#…
Browse files Browse the repository at this point in the history
…221993)

* Add test recursive version of command

For #218240

Adds a `executeHoverProvider_recursive` version of the command. This avoids calling any exclusive providers

Not super happy with this as it duplicates code for a very specialized use. However it does work and is considerably simpler than some of the other approaches I explored in #218240

Instead of adding duplicate commands, we could instead use an argument or somehow sneak this information in on the uri. Changing the uri is a little scary tho as we have to make sure the modified uri doesn't leak back and confuse providers

* Add versions for go to commands too
  • Loading branch information
mjbvz authored Jul 22, 2024
1 parent edfa11f commit 54f90d6
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 58 deletions.
44 changes: 25 additions & 19 deletions src/vs/editor/common/languageFeatureRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import { LanguageFilter, LanguageSelector, score } from 'vs/editor/common/langua
import { URI } from 'vs/base/common/uri';

interface Entry<T> {
selector: LanguageSelector;
provider: T;
readonly selector: LanguageSelector;
readonly provider: T;
_score: number;
_time: number;
readonly _time: number;
}

function isExclusive(selector: LanguageSelector): boolean {
Expand All @@ -40,14 +40,16 @@ class MatchCandidate {
readonly uri: URI,
readonly languageId: string,
readonly notebookUri: URI | undefined,
readonly notebookType: string | undefined
readonly notebookType: string | undefined,
readonly recursive: boolean,
) { }

equals(other: MatchCandidate): boolean {
return this.notebookType === other.notebookType
&& this.languageId === other.languageId
&& this.uri.toString() === other.uri.toString()
&& this.notebookUri?.toString() === other.notebookUri?.toString();
&& this.notebookUri?.toString() === other.notebookUri?.toString()
&& this.recursive === other.recursive;
}
}

Expand Down Expand Up @@ -96,7 +98,7 @@ export class LanguageFeatureRegistry<T> {
return [];
}

this._updateScores(model);
this._updateScores(model, false);
const result: T[] = [];

// from registry
Expand All @@ -113,9 +115,9 @@ export class LanguageFeatureRegistry<T> {
return this._entries.map(entry => entry.provider);
}

ordered(model: ITextModel): T[] {
ordered(model: ITextModel, recursive = false): T[] {
const result: T[] = [];
this._orderedForEach(model, entry => result.push(entry.provider));
this._orderedForEach(model, recursive, entry => result.push(entry.provider));
return result;
}

Expand All @@ -124,7 +126,7 @@ export class LanguageFeatureRegistry<T> {
let lastBucket: T[];
let lastBucketScore: number;

this._orderedForEach(model, entry => {
this._orderedForEach(model, false, entry => {
if (lastBucket && lastBucketScore === entry._score) {
lastBucket.push(entry.provider);
} else {
Expand All @@ -137,9 +139,9 @@ export class LanguageFeatureRegistry<T> {
return result;
}

private _orderedForEach(model: ITextModel, callback: (provider: Entry<T>) => any): void {
private _orderedForEach(model: ITextModel, recursive: boolean, callback: (provider: Entry<T>) => any): void {

this._updateScores(model);
this._updateScores(model, recursive);

for (const entry of this._entries) {
if (entry._score > 0) {
Expand All @@ -150,15 +152,15 @@ export class LanguageFeatureRegistry<T> {

private _lastCandidate: MatchCandidate | undefined;

private _updateScores(model: ITextModel): void {
private _updateScores(model: ITextModel, recursive: boolean): void {

const notebookInfo = this._notebookInfoResolver?.(model.uri);

// use the uri (scheme, pattern) of the notebook info iff we have one
// otherwise it's the model's/document's uri
const candidate = notebookInfo
? new MatchCandidate(model.uri, model.getLanguageId(), notebookInfo.uri, notebookInfo.type)
: new MatchCandidate(model.uri, model.getLanguageId(), undefined, undefined);
? new MatchCandidate(model.uri, model.getLanguageId(), notebookInfo.uri, notebookInfo.type, recursive)
: new MatchCandidate(model.uri, model.getLanguageId(), undefined, undefined, recursive);

if (this._lastCandidate?.equals(candidate)) {
// nothing has changed
Expand All @@ -171,13 +173,17 @@ export class LanguageFeatureRegistry<T> {
entry._score = score(entry.selector, candidate.uri, candidate.languageId, shouldSynchronizeModel(model), candidate.notebookUri, candidate.notebookType);

if (isExclusive(entry.selector) && entry._score > 0) {
// support for one exclusive selector that overwrites
// any other selector
for (const entry of this._entries) {
if (recursive) {
entry._score = 0;
} else {
// support for one exclusive selector that overwrites
// any other selector
for (const entry of this._entries) {
entry._score = 0;
}
entry._score = 1000;
break;
}
entry._score = 1000;
break;
}
}

Expand Down
14 changes: 7 additions & 7 deletions src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export abstract class SymbolNavigationAction extends EditorAction2 {
export class DefinitionAction extends SymbolNavigationAction {

protected async _getLocationModel(languageFeaturesService: ILanguageFeaturesService, model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<ReferencesModel> {
return new ReferencesModel(await getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, position, token), nls.localize('def.title', 'Definitions'));
return new ReferencesModel(await getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, position, false, token), nls.localize('def.title', 'Definitions'));
}

protected _getNoResultFoundMessage(info: IWordAtPosition | null): string {
Expand Down Expand Up @@ -380,7 +380,7 @@ registerAction2(class PeekDefinitionAction extends DefinitionAction {
class DeclarationAction extends SymbolNavigationAction {

protected async _getLocationModel(languageFeaturesService: ILanguageFeaturesService, model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<ReferencesModel> {
return new ReferencesModel(await getDeclarationsAtPosition(languageFeaturesService.declarationProvider, model, position, token), nls.localize('decl.title', 'Declarations'));
return new ReferencesModel(await getDeclarationsAtPosition(languageFeaturesService.declarationProvider, model, position, false, token), nls.localize('decl.title', 'Declarations'));
}

protected _getNoResultFoundMessage(info: IWordAtPosition | null): string {
Expand Down Expand Up @@ -467,7 +467,7 @@ registerAction2(class PeekDeclarationAction extends DeclarationAction {
class TypeDefinitionAction extends SymbolNavigationAction {

protected async _getLocationModel(languageFeaturesService: ILanguageFeaturesService, model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<ReferencesModel> {
return new ReferencesModel(await getTypeDefinitionsAtPosition(languageFeaturesService.typeDefinitionProvider, model, position, token), nls.localize('typedef.title', 'Type Definitions'));
return new ReferencesModel(await getTypeDefinitionsAtPosition(languageFeaturesService.typeDefinitionProvider, model, position, false, token), nls.localize('typedef.title', 'Type Definitions'));
}

protected _getNoResultFoundMessage(info: IWordAtPosition | null): string {
Expand Down Expand Up @@ -553,7 +553,7 @@ registerAction2(class PeekTypeDefinitionAction extends TypeDefinitionAction {
class ImplementationAction extends SymbolNavigationAction {

protected async _getLocationModel(languageFeaturesService: ILanguageFeaturesService, model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<ReferencesModel> {
return new ReferencesModel(await getImplementationsAtPosition(languageFeaturesService.implementationProvider, model, position, token), nls.localize('impl.title', 'Implementations'));
return new ReferencesModel(await getImplementationsAtPosition(languageFeaturesService.implementationProvider, model, position, false, token), nls.localize('impl.title', 'Implementations'));
}

protected _getNoResultFoundMessage(info: IWordAtPosition | null): string {
Expand Down Expand Up @@ -695,7 +695,7 @@ registerAction2(class GoToReferencesAction extends ReferencesAction {
}

protected async _getLocationModel(languageFeaturesService: ILanguageFeaturesService, model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<ReferencesModel> {
return new ReferencesModel(await getReferencesAtPosition(languageFeaturesService.referenceProvider, model, position, true, token), nls.localize('ref.title', 'References'));
return new ReferencesModel(await getReferencesAtPosition(languageFeaturesService.referenceProvider, model, position, true, false, token), nls.localize('ref.title', 'References'));
}
});

Expand Down Expand Up @@ -723,7 +723,7 @@ registerAction2(class PeekReferencesAction extends ReferencesAction {
}

protected async _getLocationModel(languageFeaturesService: ILanguageFeaturesService, model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise<ReferencesModel> {
return new ReferencesModel(await getReferencesAtPosition(languageFeaturesService.referenceProvider, model, position, false, token), nls.localize('ref.title', 'References'));
return new ReferencesModel(await getReferencesAtPosition(languageFeaturesService.referenceProvider, model, position, false, false, token), nls.localize('ref.title', 'References'));
}
});

Expand Down Expand Up @@ -846,7 +846,7 @@ CommandsRegistry.registerCommand({
return undefined;
}

const references = createCancelablePromise(token => getReferencesAtPosition(languageFeaturesService.referenceProvider, control.getModel(), corePosition.Position.lift(position), false, token).then(references => new ReferencesModel(references, nls.localize('ref.title', 'References'))));
const references = createCancelablePromise(token => getReferencesAtPosition(languageFeaturesService.referenceProvider, control.getModel(), corePosition.Position.lift(position), false, false, token).then(references => new ReferencesModel(references, nls.localize('ref.title', 'References'))));
const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column);
return Promise.resolve(controller.toggleWidget(range, references, false));
});
Expand Down
62 changes: 46 additions & 16 deletions src/vs/editor/contrib/gotoSymbol/browser/goToSymbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ async function getLocationLinks<T>(
model: ITextModel,
position: Position,
registry: LanguageFeatureRegistry<T>,
recursive: boolean,
provide: (provider: T, model: ITextModel, position: Position) => ProviderResult<LocationLink | LocationLink[]>
): Promise<LocationLink[]> {
const provider = registry.ordered(model);
const provider = registry.ordered(model, recursive);

// get results
const promises = provider.map((provider): Promise<LocationLink | LocationLink[] | undefined> => {
Expand All @@ -49,32 +50,32 @@ async function getLocationLinks<T>(
return coalesce(values.flat()).filter(loc => shouldIncludeLocationLink(model, loc));
}

export function getDefinitionsAtPosition(registry: LanguageFeatureRegistry<DefinitionProvider>, model: ITextModel, position: Position, token: CancellationToken): Promise<LocationLink[]> {
return getLocationLinks(model, position, registry, (provider, model, position) => {
export function getDefinitionsAtPosition(registry: LanguageFeatureRegistry<DefinitionProvider>, model: ITextModel, position: Position, recursive: boolean, token: CancellationToken): Promise<LocationLink[]> {
return getLocationLinks(model, position, registry, recursive, (provider, model, position) => {
return provider.provideDefinition(model, position, token);
});
}

export function getDeclarationsAtPosition(registry: LanguageFeatureRegistry<DeclarationProvider>, model: ITextModel, position: Position, token: CancellationToken): Promise<LocationLink[]> {
return getLocationLinks(model, position, registry, (provider, model, position) => {
export function getDeclarationsAtPosition(registry: LanguageFeatureRegistry<DeclarationProvider>, model: ITextModel, position: Position, recursive: boolean, token: CancellationToken): Promise<LocationLink[]> {
return getLocationLinks(model, position, registry, recursive, (provider, model, position) => {
return provider.provideDeclaration(model, position, token);
});
}

export function getImplementationsAtPosition(registry: LanguageFeatureRegistry<ImplementationProvider>, model: ITextModel, position: Position, token: CancellationToken): Promise<LocationLink[]> {
return getLocationLinks(model, position, registry, (provider, model, position) => {
export function getImplementationsAtPosition(registry: LanguageFeatureRegistry<ImplementationProvider>, model: ITextModel, position: Position, recursive: boolean, token: CancellationToken): Promise<LocationLink[]> {
return getLocationLinks(model, position, registry, recursive, (provider, model, position) => {
return provider.provideImplementation(model, position, token);
});
}

export function getTypeDefinitionsAtPosition(registry: LanguageFeatureRegistry<TypeDefinitionProvider>, model: ITextModel, position: Position, token: CancellationToken): Promise<LocationLink[]> {
return getLocationLinks(model, position, registry, (provider, model, position) => {
export function getTypeDefinitionsAtPosition(registry: LanguageFeatureRegistry<TypeDefinitionProvider>, model: ITextModel, position: Position, recursive: boolean, token: CancellationToken): Promise<LocationLink[]> {
return getLocationLinks(model, position, registry, recursive, (provider, model, position) => {
return provider.provideTypeDefinition(model, position, token);
});
}

export function getReferencesAtPosition(registry: LanguageFeatureRegistry<ReferenceProvider>, model: ITextModel, position: Position, compact: boolean, token: CancellationToken): Promise<LocationLink[]> {
return getLocationLinks(model, position, registry, async (provider, model, position) => {
export function getReferencesAtPosition(registry: LanguageFeatureRegistry<ReferenceProvider>, model: ITextModel, position: Position, compact: boolean, recursive: boolean, token: CancellationToken): Promise<LocationLink[]> {
return getLocationLinks(model, position, registry, recursive, async (provider, model, position) => {
const result = (await provider.provideReferences(model, position, { includeDeclaration: true }, token))?.filter(ref => shouldIncludeLocationLink(model, ref));
if (!compact || !result || result.length !== 2) {
return result;
Expand All @@ -99,30 +100,59 @@ async function _sortedAndDeduped(callback: () => Promise<LocationLink[]>): Promi

registerModelAndPositionCommand('_executeDefinitionProvider', (accessor, model, position) => {
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
const promise = getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, position, CancellationToken.None);
const promise = getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, position, false, CancellationToken.None);
return _sortedAndDeduped(() => promise);
});

registerModelAndPositionCommand('_executeDefinitionProvider_recursive', (accessor, model, position) => {
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
const promise = getDefinitionsAtPosition(languageFeaturesService.definitionProvider, model, position, true, CancellationToken.None);
return _sortedAndDeduped(() => promise);
});

registerModelAndPositionCommand('_executeTypeDefinitionProvider', (accessor, model, position) => {
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
const promise = getTypeDefinitionsAtPosition(languageFeaturesService.typeDefinitionProvider, model, position, CancellationToken.None);
const promise = getTypeDefinitionsAtPosition(languageFeaturesService.typeDefinitionProvider, model, position, false, CancellationToken.None);
return _sortedAndDeduped(() => promise);
});

registerModelAndPositionCommand('_executeTypeDefinitionProvider_recursive', (accessor, model, position) => {
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
const promise = getTypeDefinitionsAtPosition(languageFeaturesService.typeDefinitionProvider, model, position, true, CancellationToken.None);
return _sortedAndDeduped(() => promise);
});

registerModelAndPositionCommand('_executeDeclarationProvider', (accessor, model, position) => {
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
const promise = getDeclarationsAtPosition(languageFeaturesService.declarationProvider, model, position, CancellationToken.None);
const promise = getDeclarationsAtPosition(languageFeaturesService.declarationProvider, model, position, false, CancellationToken.None);
return _sortedAndDeduped(() => promise);
});
registerModelAndPositionCommand('_executeDeclarationProvider_recursive', (accessor, model, position) => {
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
const promise = getDeclarationsAtPosition(languageFeaturesService.declarationProvider, model, position, true, CancellationToken.None);
return _sortedAndDeduped(() => promise);
});

registerModelAndPositionCommand('_executeReferenceProvider', (accessor, model, position) => {
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
const promise = getReferencesAtPosition(languageFeaturesService.referenceProvider, model, position, false, CancellationToken.None);
const promise = getReferencesAtPosition(languageFeaturesService.referenceProvider, model, position, false, false, CancellationToken.None);
return _sortedAndDeduped(() => promise);
});

registerModelAndPositionCommand('_executeReferenceProvider_recursive', (accessor, model, position) => {
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
const promise = getReferencesAtPosition(languageFeaturesService.referenceProvider, model, position, false, true, CancellationToken.None);
return _sortedAndDeduped(() => promise);
});

registerModelAndPositionCommand('_executeImplementationProvider', (accessor, model, position) => {
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
const promise = getImplementationsAtPosition(languageFeaturesService.implementationProvider, model, position, CancellationToken.None);
const promise = getImplementationsAtPosition(languageFeaturesService.implementationProvider, model, position, false, CancellationToken.None);
return _sortedAndDeduped(() => promise);
});

registerModelAndPositionCommand('_executeImplementationProvider_recursive', (accessor, model, position) => {
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
const promise = getImplementationsAtPosition(languageFeaturesService.implementationProvider, model, position, true, CancellationToken.None);
return _sortedAndDeduped(() => promise);
});
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri
return Promise.resolve(null);
}

return getDefinitionsAtPosition(this.languageFeaturesService.definitionProvider, model, position, token);
return getDefinitionsAtPosition(this.languageFeaturesService.definitionProvider, model, position, false, token);
}

private gotoDefinition(position: Position, openToSide: boolean): Promise<any> {
Expand Down
Loading

0 comments on commit 54f90d6

Please sign in to comment.