Skip to content

Commit

Permalink
Add defining span to definition provider API
Browse files Browse the repository at this point in the history
Supersedes #40045

**Problem**
See #10037

**Proposal**
Adds a new `resolveDefiniotionContext` to `DefinitionProvider`. This allows a definition provider to return the defining symbol span.
  • Loading branch information
mjbvz committed Mar 8, 2018
1 parent ccc8f5a commit 9f87b44
Show file tree
Hide file tree
Showing 13 changed files with 202 additions and 43 deletions.
28 changes: 27 additions & 1 deletion extensions/typescript/src/features/definitionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,38 @@
* 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, Definition, DefinitionContext } from 'vscode';

import DefinitionProviderBase from './definitionProviderBase';
import { vsPositionToTsFileLocation, tsTextSpanToVsRange } from '../utils/convert';

export default class TypeScriptDefinitionProvider extends DefinitionProviderBase implements DefinitionProvider {
public provideDefinition(document: TextDocument, position: Position, token: CancellationToken | boolean): Promise<Definition | undefined> {
return this.getSymbolLocations('definition', document, position, token);
}

async resolveDefinitionContext(
document: TextDocument,
position: Position,
token: CancellationToken
): Promise<DefinitionContext | undefined> {
const filepath = this.client.normalizePath(document.uri);
if (!filepath) {
return undefined;
}

const args = vsPositionToTsFileLocation(filepath, position);
try {
const response = await this.client.execute('definitionAndBoundSpan', args, token);
if (response && response.body && response.body.textSpan) {
return {
definingSymbolRange: tsTextSpanToVsRange(response.body.textSpan)
};
}
} catch {
// noop
}

return undefined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { tsTextSpanToVsRange, vsPositionToTsFileLocation } from '../utils/conver

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

protected async getSymbolLocations(
Expand Down
1 change: 1 addition & 0 deletions extensions/typescript/src/typescriptService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,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>;
Expand Down
9 changes: 9 additions & 0 deletions src/vs/editor/common/modes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,10 @@ export interface Location {
*/
export type Definition = Location | Location[];

export interface DefinitionContext {
definingSymbolRange?: 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)
Expand All @@ -532,6 +536,11 @@ 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>;

/**
* Resolve additional information about the definition.
*/
resolveDefinitionContext?(model: model.ITextModel, position: Position, token: CancellationToken): DefinitionContext | Thenable<DefinitionContext>;
}

/**
Expand Down
52 changes: 37 additions & 15 deletions src/vs/editor/contrib/goToDeclaration/goToDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,53 +10,75 @@ 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, DefinitionProvider, ImplementationProvider, TypeDefinitionProvider } 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';
import { flatten } from 'vs/base/common/arrays';

export interface DefintionsResult<T> {
firstProvider: T | undefined;
definitions: Location[];
}

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[]> {
): TPromise<DefintionsResult<T>> {
const provider = registry.ordered(model);
let firstProvider: T;

// get results
const promises = provider.map((provider, idx): TPromise<Location | Location[]> => {
const promises = provider.map((provider, idx) => {
return asWinJsPromise((token) => {
return provide(provider, model, position, token);
}).then(undefined, err => {
onUnexpectedExternalError(err);
return null;
});
})
.then<Location | Location[]>(undefined, err => {
onUnexpectedExternalError(err);
return null;
}).then(locations => {
if (!firstProvider) {
if ((Array.isArray(locations) && locations.length) || locations) {
firstProvider = provider;
}
}
return locations;
});
});
return TPromise.join(promises)
.then(flatten)
.then(references => references.filter(x => !!x));
.then(locations => locations.filter(x => !!x))
.then(locations => ({
definitions: locations,
firstProvider
}));
}


export function getDefinitionsAtPosition(model: ITextModel, position: Position): TPromise<Location[]> {
export function getDefinitionsAtPosition(model: ITextModel, position: Position): TPromise<DefintionsResult<DefinitionProvider>> {
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<DefintionsResult<ImplementationProvider>> {
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<DefintionsResult<TypeDefinitionProvider>> {
return getDefinitions(model, position, TypeDefinitionProviderRegistry, (provider, model, position, token) => {
return provider.provideTypeDefinition(model, position, token);
});
}

registerDefaultLanguageCommand('_executeDefinitionProvider', getDefinitionsAtPosition);
registerDefaultLanguageCommand('_executeImplementationProvider', getImplementationsAtPosition);
registerDefaultLanguageCommand('_executeTypeDefinitionProvider', getTypeDefinitionsAtPosition);
registerDefaultLanguageCommand('_executeDefinitionProvider', (model: ITextModel, position: Position) =>
getDefinitionsAtPosition(model, position).then(result => result.definitions));

registerDefaultLanguageCommand('_executeImplementationProvider', (model: ITextModel, position: Position) =>
getImplementationsAtPosition(model, position).then(result => result.definitions));

registerDefaultLanguageCommand('_executeTypeDefinitionProvider', (model: ITextModel, position: Position) =>
getTypeDefinitionsAtPosition(model, position).then(result => result.definitions));
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export class DefinitionAction extends EditorAction {
}

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

protected _getNoResultFoundMessage(info?: IWordAtPosition): string {
Expand Down Expand Up @@ -249,7 +249,7 @@ export class PeekDefinitionAction extends DefinitionAction {

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

protected _getNoResultFoundMessage(info?: IWordAtPosition): string {
Expand Down Expand Up @@ -305,7 +305,7 @@ export class PeekImplementationAction extends ImplementationAction {

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

protected _getNoResultFoundMessage(info?: IWordAtPosition): string {
Expand Down
39 changes: 27 additions & 12 deletions src/vs/editor/contrib/goToDeclaration/goToDeclarationMouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@

import 'vs/css!./goToDeclarationMouse';
import * as nls from 'vs/nls';
import { Throttler } from 'vs/base/common/async';
import { Throttler, asWinJsPromise } from 'vs/base/common/async';
import { onUnexpectedError } from 'vs/base/common/errors';
import { MarkdownString } from 'vs/base/common/htmlContent';
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, DefinitionProvider } 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';
import { getDefinitionsAtPosition, DefintionsResult } from './goToDeclaration';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
Expand Down Expand Up @@ -102,31 +102,32 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
this.throttler.queue(() => {
return state.validate(this.editor)
? this.findDefinition(mouseEvent.target)
: TPromise.wrap<Location[]>(null);
: TPromise.wrap<DefintionsResult<DefinitionProvider>>(null);

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

// Multiple results
if (results.length > 1) {
if (locations.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.", locations.length))
);
}

// Single result
else {
let result = results[0];
let result = locations[0];

if (!result.uri) {
return;
}

this.textModelResolverService.createModelReference(result.uri).then(ref => {
this.textModelResolverService.createModelReference(result.uri).then(async ref => {

if (!ref.object || !ref.object.textEditorModel) {
ref.dispose();
Expand Down Expand Up @@ -157,8 +158,22 @@ 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 (definitionResult.firstProvider && definitionResult.firstProvider.resolveDefinitionContext) {
const result = await asWinJsPromise(token =>
definitionResult.firstProvider.resolveDefinitionContext(textEditorModel, position, token));
if (result && result.definingSymbolRange) {
const range = result.definingSymbolRange;
wordRange = new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
}
}

if (!wordRange) {
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 +209,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
DefinitionProviderRegistry.has(this.editor.getModel());
}

private findDefinition(target: IMouseTarget): TPromise<Location[]> {
private findDefinition(target: IMouseTarget): TPromise<DefintionsResult<DefinitionProvider> | null> {
let model = this.editor.getModel();
if (!model) {
return TPromise.as(null);
Expand Down
8 changes: 8 additions & 0 deletions src/vs/monaco.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4698,6 +4698,10 @@ declare module monaco.languages {
*/
export type Definition = Location | Location[];

export interface DefinitionContext {
definingSymbolRange?: 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)
Expand All @@ -4708,6 +4712,10 @@ declare module 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>;
/**
* Resolve additional information about the definition.
*/
resolveDefinitionContext?(model: editor.ITextModel, position: Position, token: CancellationToken): DefinitionContext | Thenable<DefinitionContext>;
}

/**
Expand Down
13 changes: 13 additions & 0 deletions src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,10 @@ declare module 'vscode' {
text?: string;
}

export interface DefinitionContext {
definingSymbolRange?: Range;
}

export namespace languages {

/**
Expand All @@ -371,7 +375,16 @@ declare module 'vscode' {
export interface RenameProvider2 extends RenameProvider {
resolveInitialRenameValue?(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<RenameInitialValue>;
}

export interface DefinitionProvider2 extends DefinitionProvider {

/**
* Provides additional information about a definition.
*/
resolveDefinitionContext?(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<DefinitionContext>;
}
}

export interface FoldingProvider {
provideFoldingRanges(document: TextDocument, token: CancellationToken): ProviderResult<FoldingRangeList>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
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);
},
resolveDefinitionContext: (model, position, token): Thenable<modes.DefinitionContext> => {
return wireCancellationToken(token, this._proxy.$resolveDefinitionContext(handle, model.uri, position));
}
});
}
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/api/node/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ export interface ExtHostLanguageFeaturesShape {
$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[]>;
$resolveDefinitionContext(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.DefinitionContext>;
$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>;
Expand Down
Loading

0 comments on commit 9f87b44

Please sign in to comment.