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:
parent
04c1ad98b3
commit
0532c31e4c
11 changed files with 169 additions and 22 deletions
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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
9
src/vs/monaco.d.ts
vendored
|
@ -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[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
43
src/vs/vscode.proposed.d.ts
vendored
43
src/vs/vscode.proposed.d.ts
vendored
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue