Skip to content

Commit

Permalink
Symbol Definition API
Browse files Browse the repository at this point in the history
**Problem**
See microsoft#10037

**Proposal**
Add a new `SymbolDefinition` class to the VSCode API. This bundles up a location and the span of the defining symbol
  • Loading branch information
mjbvz committed Apr 16, 2018
1 parent e4c9755 commit bfa410f
Show file tree
Hide file tree
Showing 15 changed files with 222 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,56 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { DefinitionProvider, TextDocument, Position, CancellationToken, Definition } from 'vscode';
import { DefinitionProvider, TextDocument, Position, CancellationToken, Location, SymbolDefinition } from 'vscode';
import * as Proto from '../protocol';

import DefinitionProviderBase from './definitionProviderBase';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as typeConverters from '../utils/typeConverters';

export default class TypeScriptDefinitionProvider extends DefinitionProviderBase implements DefinitionProvider {
public provideDefinition(document: TextDocument, position: Position, token: CancellationToken | boolean): Promise<Definition | undefined> {
constructor(
client: ITypeScriptServiceClient
) {
super(client);
}

public async provideDefinition() {
// Implemented by provideDefinition2
return undefined;
}

public async provideDefinition2(
document: TextDocument,
position: Position,
token: CancellationToken | boolean
): Promise<SymbolDefinition | Location[] | undefined> {
if (this.client.apiVersion.has270Features()) {
const filepath = this.client.normalizePath(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 [];
}

const span = response.body.textSpan ? typeConverters.Location.fromTextSpan(document.uri, response.body.textSpan) : undefined;
return {
definingSpan: span,
definitions: locations
.map(location => typeConverters.Location.fromTextSpan(this.client.asUrl(location.file), location))
.filter(x => x) as Location[]
};
} catch {
return [];
}
}

return this.getSymbolLocations('definition', document, position, token);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import * as typeConverters from '../utils/typeConverters';

export default class TypeScriptDefinitionProviderBase {
constructor(
private readonly client: ITypeScriptServiceClient
protected client: ITypeScriptServiceClient
) { }

protected async getSymbolLocations(
Expand Down
6 changes: 5 additions & 1 deletion src/vs/editor/common/modes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,10 @@ export interface Location {
*/
range: IRange;
}
export interface SymbolDefinition {
definingSpan?: Location;
definitions: Location[];
}
/**
* The definition of a symbol represented as one or many [locations](#Location).
* For most programming languages there is only one location at which a symbol is
Expand All @@ -536,7 +540,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): Definition | SymbolDefinition | Thenable<Definition | SymbolDefinition>;
}

/**
Expand Down
29 changes: 20 additions & 9 deletions src/vs/editor/contrib/goToDeclaration/goToDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, SymbolDefinition } 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';
Expand All @@ -20,38 +20,49 @@ function getDefinitions<T>(
model: ITextModel,
position: Position,
registry: LanguageFeatureRegistry<T>,
provide: (provider: T, model: ITextModel, position: Position, token: CancellationToken) => Location | Location[] | Thenable<Location | Location[]>
): TPromise<Location[]> {
provide: (provider: T, model: ITextModel, position: Position, token: CancellationToken) => Location | Location[] | SymbolDefinition | Thenable<Location | Location[] | SymbolDefinition>
): TPromise<SymbolDefinition> {
const provider = registry.ordered(model);

let definingSpan: Location | undefined = undefined;

// get results
const promises = provider.map((provider, idx): TPromise<Location | Location[]> => {
return asWinJsPromise((token) => {
return provide(provider, model, position, token);
}).then(undefined, err => {
}).then(result => {
if (result && (result as any).definitions) {
definingSpan = definingSpan || (result as any).definingSpan;
return (result as any).definitions;
}
return result;
}, err => {
onUnexpectedExternalError(err);
return null;
});
});

return TPromise.join(promises)
.then(flatten)
.then(references => references.filter(x => !!x));
.then(references => references.filter(x => !!x))
.then((definitions): SymbolDefinition => {
return { definingSpan, definitions };
});
}


export function getDefinitionsAtPosition(model: ITextModel, position: Position): TPromise<Location[]> {
export function getDefinitionsAtPosition(model: ITextModel, position: Position): TPromise<SymbolDefinition> {
return getDefinitions(model, position, DefinitionProviderRegistry, (provider, model, position, token) => {
return provider.provideDefinition(model, position, token);
});
}

export function getImplementationsAtPosition(model: ITextModel, position: Position): TPromise<Location[]> {
export function getImplementationsAtPosition(model: ITextModel, position: Position): TPromise<SymbolDefinition> {
return getDefinitions(model, position, ImplementationProviderRegistry, (provider, model, position, token) => {
return provider.provideImplementation(model, position, token);
});
}

export function getTypeDefinitionsAtPosition(model: ITextModel, position: Position): TPromise<Location[]> {
export function getTypeDefinitionsAtPosition(model: ITextModel, position: Position): TPromise<SymbolDefinition> {
return getDefinitions(model, position, TypeDefinitionProviderRegistry, (provider, model, position, token) => {
return provider.provideTypeDefinition(model, position, token);
});
Expand Down
18 changes: 9 additions & 9 deletions src/vs/editor/contrib/goToDeclaration/goToDeclarationCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IEditorService } from 'vs/platform/editor/common/editor';
import { Range } from 'vs/editor/common/core/range';
import { registerEditorAction, IActionOptions, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions';
import { Location } from 'vs/editor/common/modes';
import { Location, SymbolDefinition } from 'vs/editor/common/modes';
import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition } from './goToDeclaration';
import { ReferencesController } from 'vs/editor/contrib/referenceSearch/referencesController';
import { ReferencesModel } from 'vs/editor/contrib/referenceSearch/referencesModel';
Expand Down Expand Up @@ -56,7 +56,8 @@ export class DefinitionAction extends EditorAction {
const model = editor.getModel();
const pos = editor.getPosition();

const definitionPromise = this._getDeclarationsAtPosition(model, pos).then(references => {
const definitionPromise = this._getDeclarationsAtPosition(model, pos).then(symbolDefinition => {
const references = symbolDefinition.definitions;

if (model.isDisposed() || editor.getModel() !== model) {
// new model, no more model
Expand All @@ -67,12 +68,11 @@ export class DefinitionAction extends EditorAction {
// * find reference at the current pos
let idxOfCurrent = -1;
let result: Location[] = [];
for (let i = 0; i < references.length; i++) {
let reference = references[i];
if (!reference || !reference.range) {
for (const location of references) {
if (!location || !location.range) {
continue;
}
let { uri, range } = reference;
let { uri, range } = location;
let newLen = result.push({
uri,
range
Expand Down Expand Up @@ -111,7 +111,7 @@ export class DefinitionAction extends EditorAction {
return definitionPromise;
}

protected _getDeclarationsAtPosition(model: ITextModel, position: corePosition.Position): TPromise<Location[]> {
protected _getDeclarationsAtPosition(model: ITextModel, position: corePosition.Position): TPromise<SymbolDefinition> {
return getDefinitionsAtPosition(model, position);
}

Expand Down Expand Up @@ -248,7 +248,7 @@ export class PeekDefinitionAction extends DefinitionAction {
}

export class ImplementationAction extends DefinitionAction {
protected _getDeclarationsAtPosition(model: ITextModel, position: corePosition.Position): TPromise<Location[]> {
protected _getDeclarationsAtPosition(model: ITextModel, position: corePosition.Position): TPromise<SymbolDefinition> {
return getImplementationsAtPosition(model, position);
}

Expand Down Expand Up @@ -304,7 +304,7 @@ export class PeekImplementationAction extends ImplementationAction {
}

export class TypeDefinitionAction extends DefinitionAction {
protected _getDeclarationsAtPosition(model: ITextModel, position: corePosition.Position): TPromise<Location[]> {
protected _getDeclarationsAtPosition(model: ITextModel, position: corePosition.Position): TPromise<SymbolDefinition> {
return getTypeDefinitionsAtPosition(model, position);
}

Expand Down
26 changes: 17 additions & 9 deletions src/vs/editor/contrib/goToDeclaration/goToDeclarationMouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, SymbolDefinition } from 'vs/editor/common/modes';
import { ICodeEditor, IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { getDefinitionsAtPosition } from './goToDeclaration';
Expand Down Expand Up @@ -102,25 +102,25 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
this.throttler.queue(() => {
return state.validate(this.editor)
? this.findDefinition(mouseEvent.target)
: TPromise.wrap<Location[]>(null);
: TPromise.wrap<SymbolDefinition>(null);

}).then(results => {
if (!results || !results.length || !state.validate(this.editor)) {
}).then(symbolDefinition => {
if (!symbolDefinition || !symbolDefinition.definitions.length || !state.validate(this.editor)) {
this.removeDecorations();
return;
}

// Multiple results
if (results.length > 1) {
if (symbolDefinition.definitions.length > 1) {
this.addDecoration(
new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn),
new MarkdownString().appendText(nls.localize('multipleResults', "Click to show {0} definitions.", results.length))
new MarkdownString().appendText(nls.localize('multipleResults', "Click to show {0} definitions.", symbolDefinition.definitions.length))
);
}

// Single result
else {
let result = results[0];
let result = symbolDefinition.definitions[0];

if (!result.uri) {
return;
Expand Down Expand Up @@ -157,8 +157,16 @@ 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 (symbolDefinition.definingSpan) {
const range = symbolDefinition.definingSpan.range;
wordRange = new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
} 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();
Expand Down Expand Up @@ -194,7 +202,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
DefinitionProviderRegistry.has(this.editor.getModel());
}

private findDefinition(target: IMouseTarget): TPromise<Location[]> {
private findDefinition(target: IMouseTarget): TPromise<SymbolDefinition> {
let model = this.editor.getModel();
if (!model) {
return TPromise.as(null);
Expand Down
7 changes: 6 additions & 1 deletion src/vs/monaco.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4743,6 +4743,11 @@ declare namespace monaco.languages {
range: IRange;
}

export interface SymbolDefinition {
definingSpan?: Location;
definitions: Location[];
}

/**
* The definition of a symbol represented as one or many [locations](#Location).
* For most programming languages there is only one location at which a symbol is
Expand All @@ -4759,7 +4764,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): Definition | SymbolDefinition | Thenable<Definition | SymbolDefinition>;
}

/**
Expand Down
15 changes: 15 additions & 0 deletions src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -870,4 +870,19 @@ declare module 'vscode' {
}

//#endregion

//#region Defintion symbol range: mjbvz

export interface SymbolDefinition {
definingSpan?: Location;

definitions: Location[];
}

export interface DefinitionProvider {

provideDefinition2?(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<Definition | SymbolDefinition>;
}

//#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -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, IRange } from 'vs/editor/common/core/range';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, SymbolInformationDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter } from '../node/extHost.protocol';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, SymbolInformationDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, SymbolDefinitionDto } 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';
Expand Down Expand Up @@ -71,6 +71,19 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
}
}

private static _reviveSymbolDefinitionDto(data: SymbolDefinitionDto): modes.SymbolDefinition {
if (!data) {
return <modes.SymbolDefinition>data;
}

if (data.definingSpan) {
data.definingSpan = this._reviveLocationDto(data.definingSpan);
}

data.definitions = data.definitions.map(x => this._reviveLocationDto(x));
return <modes.SymbolDefinition>data;
}

private static _reviveSymbolInformationDto(data: SymbolInformationDto): modes.SymbolInformation;
private static _reviveSymbolInformationDto(data: SymbolInformationDto[]): modes.SymbolInformation[];
private static _reviveSymbolInformationDto(data: SymbolInformationDto | SymbolInformationDto[]): modes.SymbolInformation | modes.SymbolInformation[] {
Expand Down Expand Up @@ -137,8 +150,14 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha

$registerDeclaractionSupport(handle: number, selector: ISerializedDocumentFilter[]): void {
this._registrations[handle] = modes.DefinitionProviderRegistry.register(toLanguageSelector(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.Definition | modes.SymbolDefinition> => {
return wireCancellationToken(token, this._proxy.$provideDefinition(handle, model.uri, position)).then(x => {
if ((x as any).definitions) {
return MainThreadLanguageFeatures._reviveSymbolDefinitionDto(x as any);
}

return MainThreadLanguageFeatures._reviveLocationDto(x as any);
});
}
});
}
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/api/node/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,7 @@ export function createApiFactory(
CompletionList: extHostTypes.CompletionList,
CompletionTriggerKind: extHostTypes.CompletionTriggerKind,
DebugAdapterExecutable: extHostTypes.DebugAdapterExecutable,
SymbolDefinition: extHostTypes.SymbolDefinition,
Diagnostic: extHostTypes.Diagnostic,
DiagnosticRelatedInformation: extHostTypes.DiagnosticRelatedInformation,
DiagnosticSeverity: extHostTypes.DiagnosticSeverity,
Expand Down
Loading

0 comments on commit bfa410f

Please sign in to comment.