Definition link API (#52230)

* Definition link

Add a new `DefinitionLink` type. This type allows definition providers to return additional metadata about a definition, such as the defining span.

Hook up this new provider for typescript

This PR replaces #48001

* Correctly mark field optional

* Small code fixes

- Use lift
- Remove unused param

* Adding documentation
This commit is contained in:
Matt Bierner 2018-06-20 11:52:47 -07:00 committed by GitHub
parent 04c1ad98b3
commit 0532c31e4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 169 additions and 22 deletions

View file

@ -11,7 +11,7 @@ import * as typeConverters from '../utils/typeConverters';
export default class TypeScriptDefinitionProviderBase {
constructor(
private readonly client: ITypeScriptServiceClient
protected readonly client: ITypeScriptServiceClient
) { }
protected async getSymbolLocations(

View file

@ -4,11 +4,57 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import * as typeConverters from '../utils/typeConverters';
import DefinitionProviderBase from './definitionProviderBase';
class TypeScriptDefinitionProvider extends DefinitionProviderBase implements vscode.DefinitionProvider {
public provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken | boolean): Promise<vscode.Definition | undefined> {
export default class TypeScriptDefinitionProvider extends DefinitionProviderBase implements vscode.DefinitionProvider {
constructor(
client: ITypeScriptServiceClient
) {
super(client);
}
public async provideDefinition() {
// Implemented by provideDefinition2
return undefined;
}
public async provideDefinition2(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken | boolean
): Promise<vscode.DefinitionLink[] | vscode.Definition | undefined> {
if (this.client.apiVersion.gte(API.v270)) {
const filepath = this.client.toPath(document.uri);
if (!filepath) {
return undefined;
}
const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
try {
const response = await this.client.execute('definitionAndBoundSpan', args, token);
const locations: Proto.FileSpan[] = (response && response.body && response.body.definitions) || [];
if (!locations) {
return undefined;
}
const span = response.body.textSpan ? typeConverters.Range.fromTextSpan(response.body.textSpan) : undefined;
return locations
.map(location => {
const loc = typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location);
return {
origin: span,
...loc,
};
});
} catch {
return [];
}
}
return this.getSymbolLocations('definition', document, position, token);
}
}

View file

@ -59,6 +59,7 @@ export interface ITypeScriptServiceClient {
execute(command: 'completionEntryDetails', args: Proto.CompletionDetailsRequestArgs, token?: CancellationToken): Promise<Proto.CompletionDetailsResponse>;
execute(command: 'signatureHelp', args: Proto.SignatureHelpRequestArgs, token?: CancellationToken): Promise<Proto.SignatureHelpResponse>;
execute(command: 'definition', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise<Proto.DefinitionResponse>;
execute(command: 'definitionAndBoundSpan', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise<Proto.DefinitionInfoAndBoundSpanReponse>;
execute(command: 'implementation', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise<Proto.ImplementationResponse>;
execute(command: 'typeDefinition', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise<Proto.TypeDefinitionResponse>;
execute(command: 'references', args: Proto.FileLocationRequestArgs, token?: CancellationToken): Promise<Proto.ReferencesResponse>;

View file

@ -538,6 +538,13 @@ export interface Location {
*/
export type Definition = Location | Location[];
export interface DefinitionLink {
origin?: IRange;
uri: URI;
range: IRange;
selectionRange?: IRange;
}
/**
* The definition provider interface defines the contract between extensions and
* the [go to definition](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-definition)
@ -547,7 +554,7 @@ export interface DefinitionProvider {
/**
* Provide the definition of the symbol at the given position and document.
*/
provideDefinition(model: model.ITextModel, position: Position, token: CancellationToken): Definition | Thenable<Definition>;
provideDefinition(model: model.ITextModel, position: Position, token: CancellationToken): DefinitionLink | Thenable<DefinitionLink[]>;
}
/**

View file

@ -10,7 +10,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { ITextModel } from 'vs/editor/common/model';
import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry';
import { DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, Location } from 'vs/editor/common/modes';
import { DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, Location, DefinitionLink } from 'vs/editor/common/modes';
import { CancellationToken } from 'vs/base/common/cancellation';
import { asWinJsPromise } from 'vs/base/common/async';
import { Position } from 'vs/editor/common/core/position';
@ -21,11 +21,11 @@ function getDefinitions<T>(
position: Position,
registry: LanguageFeatureRegistry<T>,
provide: (provider: T, model: ITextModel, position: Position, token: CancellationToken) => Location | Location[] | Thenable<Location | Location[]>
): TPromise<Location[]> {
): TPromise<DefinitionLink[]> {
const provider = registry.ordered(model);
// get results
const promises = provider.map((provider, idx): TPromise<Location | Location[]> => {
const promises = provider.map((provider): TPromise<DefinitionLink | DefinitionLink[]> => {
return asWinJsPromise((token) => {
return provide(provider, model, position, token);
}).then(undefined, err => {
@ -39,7 +39,7 @@ function getDefinitions<T>(
}
export function getDefinitionsAtPosition(model: ITextModel, position: Position): TPromise<Location[]> {
export function getDefinitionsAtPosition(model: ITextModel, position: Position): TPromise<DefinitionLink[]> {
return getDefinitions(model, position, DefinitionProviderRegistry, (provider, model, position, token) => {
return provider.provideDefinition(model, position, token);
});

View file

@ -14,7 +14,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IModeService } from 'vs/editor/common/services/modeService';
import { Range } from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { Location, DefinitionProviderRegistry } from 'vs/editor/common/modes';
import { DefinitionProviderRegistry, DefinitionLink } from 'vs/editor/common/modes';
import { ICodeEditor, IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { getDefinitionsAtPosition } from './goToDefinition';
@ -102,7 +102,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
this.throttler.queue(() => {
return state.validate(this.editor)
? this.findDefinition(mouseEvent.target)
: TPromise.wrap<Location[]>(null);
: TPromise.wrap<DefinitionLink[]>(null);
}).then(results => {
if (!results || !results.length || !state.validate(this.editor)) {
@ -157,8 +157,15 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
const previewRange = new Range(startLineNumber, 1, endLineNumber + 1, 1);
const value = textEditorModel.getValueInRange(previewRange).replace(new RegExp(`^\\s{${minIndent - 1}}`, 'gm'), '').trim();
let wordRange: Range;
if (result.origin) {
wordRange = Range.lift(result.origin);
} else {
wordRange = new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn);
}
this.addDecoration(
new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn),
wordRange,
new MarkdownString().appendCodeblock(this.modeService.getModeIdByFilenameOrFirstLine(textEditorModel.uri.fsPath), value)
);
ref.dispose();
@ -194,7 +201,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
DefinitionProviderRegistry.has(this.editor.getModel());
}
private findDefinition(target: IMouseTarget): TPromise<Location[]> {
private findDefinition(target: IMouseTarget): TPromise<DefinitionLink[]> {
const model = this.editor.getModel();
if (!model) {
return TPromise.as(null);

9
src/vs/monaco.d.ts vendored
View file

@ -4805,6 +4805,13 @@ declare namespace monaco.languages {
*/
export type Definition = Location | Location[];
export interface DefinitionLink {
origin?: IRange;
uri: Uri;
range: IRange;
selectionRange?: IRange;
}
/**
* The definition provider interface defines the contract between extensions and
* the [go to definition](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-definition)
@ -4814,7 +4821,7 @@ declare namespace monaco.languages {
/**
* Provide the definition of the symbol at the given position and document.
*/
provideDefinition(model: editor.ITextModel, position: Position, token: CancellationToken): Definition | Thenable<Definition>;
provideDefinition(model: editor.ITextModel, position: Position, token: CancellationToken): DefinitionLink | Thenable<DefinitionLink[]>;
}
/**

View file

@ -727,4 +727,47 @@ declare module 'vscode' {
}
//#endregion
//#region Matt: Deinition range
/**
* Information about where a symbol is defined.
*
* Provides additional metadata over normal [location](#Location) definitions, including the range of
* the defining symbol
*/
export interface DefinitionLink {
/**
* Span of the symbol being defined in the source file.
*
* Used as the underlined span for mouse definition hover. Defaults to the word range at
* the definition position.
*/
origin?: Range;
/**
* The resource identifier of the definition.
*/
uri: Uri;
/**
* The full range of the definition.
*
* For a class definition for example, this would be the entire body of the class definition.
*/
range: Range;
/**
* The span of the symbol definition.
*
* For a class definition, this would be the class name itself in the class definition.
*/
selectionRange?: Range;
}
export interface DefinitionProvider {
provideDefinition2?(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<Definition | DefinitionLink[]>;
}
//#endregion
}

View file

@ -14,7 +14,7 @@ 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 } from 'vs/editor/common/core/range';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter } from '../node/extHost.protocol';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto } from '../node/extHost.protocol';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration';
import { IHeapService } from './mainThreadHeapService';
@ -72,6 +72,20 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
}
}
private static _reviveDefinitionLinkDto(data: DefinitionLinkDto): modes.DefinitionLink;
private static _reviveDefinitionLinkDto(data: DefinitionLinkDto[]): modes.DefinitionLink[];
private static _reviveDefinitionLinkDto(data: DefinitionLinkDto | DefinitionLinkDto[]): modes.DefinitionLink | modes.DefinitionLink[] {
if (!data) {
return <modes.DefinitionLink>data;
} else if (Array.isArray(data)) {
data.forEach(l => MainThreadLanguageFeatures._reviveDefinitionLinkDto(l));
return <modes.DefinitionLink[]>data;
} else {
data.uri = URI.revive(data.uri);
return <modes.DefinitionLink>data;
}
}
private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto): search.IWorkspaceSymbol;
private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto[]): search.IWorkspaceSymbol[];
private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto | WorkspaceSymbolDto[]): search.IWorkspaceSymbol | search.IWorkspaceSymbol[] {
@ -139,8 +153,8 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
$registerDeclaractionSupport(handle: number, selector: ISerializedDocumentFilter[]): void {
this._registrations[handle] = modes.DefinitionProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.DefinitionProvider>{
provideDefinition: (model, position, token): Thenable<modes.Definition> => {
return wireCancellationToken(token, this._proxy.$provideDefinition(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveLocationDto);
provideDefinition: (model, position, token): Thenable<modes.DefinitionLink[]> => {
return wireCancellationToken(token, this._proxy.$provideDefinition(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveDefinitionLinkDto);
}
});
}

View file

@ -752,6 +752,13 @@ export interface LocationDto {
range: IRange;
}
export interface DefinitionLinkDto {
origin?: IRange;
uri: UriComponents;
range: IRange;
selectionRange?: IRange;
}
export interface WorkspaceSymbolDto extends IdObject {
name: string;
containerName?: string;
@ -808,7 +815,7 @@ export interface ExtHostLanguageFeaturesShape {
$provideDocumentSymbols(handle: number, resource: UriComponents): TPromise<modes.DocumentSymbol[]>;
$provideCodeLenses(handle: number, resource: UriComponents): TPromise<modes.ICodeLensSymbol[]>;
$resolveCodeLens(handle: number, resource: UriComponents, symbol: modes.ICodeLensSymbol): TPromise<modes.ICodeLensSymbol>;
$provideDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<LocationDto | LocationDto[]>;
$provideDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<DefinitionLinkDto[]>;
$provideImplementation(handle: number, resource: UriComponents, position: IPosition): TPromise<LocationDto | LocationDto[]>;
$provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<LocationDto | LocationDto[]>;
$provideHover(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.Hover>;

View file

@ -150,18 +150,33 @@ class DefinitionAdapter {
private readonly _provider: vscode.DefinitionProvider
) { }
provideDefinition(resource: URI, position: IPosition): TPromise<modes.Definition> {
provideDefinition(resource: URI, position: IPosition): TPromise<modes.DefinitionLink[]> {
let doc = this._documents.getDocumentData(resource).document;
let pos = typeConvert.Position.to(position);
return asWinJsPromise(token => this._provider.provideDefinition(doc, pos, token)).then(value => {
return asWinJsPromise(token => this._provider.provideDefinition2 ? this._provider.provideDefinition2(doc, pos, token) : this._provider.provideDefinition(doc, pos, token)).then((value): modes.DefinitionLink[] => {
if (Array.isArray(value)) {
return value.map(typeConvert.location.from);
return (value as (vscode.DefinitionLink | vscode.Location)[]).map(x => DefinitionAdapter.convertDefinitionLink(x));
} else if (value) {
return typeConvert.location.from(value);
return [DefinitionAdapter.convertDefinitionLink(value)];
}
return undefined;
});
}
private static convertDefinitionLink(value: vscode.Location | vscode.DefinitionLink): modes.DefinitionLink {
const definitionLink = <vscode.DefinitionLink>value;
return {
origin: definitionLink.origin
? typeConvert.Range.from(definitionLink.origin)
: undefined,
uri: value.uri,
range: typeConvert.Range.from(value.range),
selectionRange: definitionLink.selectionRange
? typeConvert.Range.from(definitionLink.selectionRange)
: undefined,
};
}
}
class ImplementationAdapter {
@ -974,7 +989,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
return this._createDisposable(handle);
}
$provideDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.Definition> {
$provideDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.DefinitionLink[]> {
return this._withAdapter(handle, DefinitionAdapter, adapter => adapter.provideDefinition(URI.revive(resource), position));
}