Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prototype definition and span API #40045

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ namespace Configuration {
export const nameSuggestions = 'nameSuggestions';
export const quickSuggestionsForPaths = 'quickSuggestionsForPaths';
export const autoImportSuggestions = 'autoImportSuggestions.enabled';

}

export default class TypeScriptCompletionItemProvider implements CompletionItemProvider {
Expand Down
45 changes: 43 additions & 2 deletions extensions/typescript/src/features/definitionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,53 @@
* 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, DefinitionAndSpan } from 'vscode';
import * as Proto from '../protocol';

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

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(
document: TextDocument,
position: Position,
token: CancellationToken | boolean
): Promise<DefinitionAndSpan | Location[] | undefined> {
if (this.client.apiVersion.has270Features()) {
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);
const locations: Proto.FileSpan[] = (response && response.body && response.body.definitions) || [];
if (!locations) {
return [];
}
const span = new Location(document.uri, tsTextSpanToVsRange(response.body.textSpan));

if (!span) {
return [];
}

return new DefinitionAndSpan(span,
locations
.map(location => tsFileSpanToVsLocation(this.client, location))
.filter(x => x) as Location[]);
} catch {
return [];
}
}

return this.getSymbolLocations('definition', document, position, token);
}
}
16 changes: 7 additions & 9 deletions extensions/typescript/src/features/definitionProviderBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import { TextDocument, Position, CancellationToken, Location } from 'vscode';

import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import { tsTextSpanToVsRange, vsPositionToTsFileLocation } from '../utils/convert';
import { vsPositionToTsFileLocation, tsFileSpanToVsLocation } from '../utils/convert';

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

protected async getSymbolLocations(
definitionType: 'definition' | 'implementation' | 'typeDefinition',
Expand All @@ -28,15 +29,12 @@ export default class TypeScriptDefinitionProviderBase {
try {
const response = await this.client.execute(definitionType, args, token);
const locations: Proto.FileSpan[] = (response && response.body) || [];
if (!locations || locations.length === 0) {
if (!locations) {
return [];
}
return locations.map(location => {
const resource = this.client.asUrl(location.file);
return resource
? new Location(resource, tsTextSpanToVsRange(location))
: undefined;
}).filter(x => x) as Location[];
return locations
.map(location => tsFileSpanToVsLocation(this.client, location))
.filter(x => x) as Location[];
} catch {
return [];
}
Expand Down
4 changes: 4 additions & 0 deletions extensions/typescript/src/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,8 @@ export default class API {
public has262Features(): boolean {
return semver.gte(this.version, '2.6.2');
}

public has270Features(): boolean {
return semver.gte(this.version, '2.7.0');
}
}
11 changes: 9 additions & 2 deletions extensions/typescript/src/utils/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import * as vscode from 'vscode';
import * as Proto from '../protocol';

import { ITypeScriptServiceClient } from '../typescriptService';

export const tsTextSpanToVsRange = (span: Proto.TextSpan) =>
new vscode.Range(
Expand All @@ -27,4 +27,11 @@ export const vsRangeToTsFileRange = (file: string, range: vscode.Range): Proto.F
startOffset: range.start.character + 1,
endLine: range.end.line + 1,
endOffset: range.end.character + 1
});
});

export const tsFileSpanToVsLocation = (client: ITypeScriptServiceClient, span: Proto.FileSpan): vscode.Location | undefined => {
const resource = client.asUrl(span.file);
return resource
? new vscode.Location(resource, tsTextSpanToVsRange(span))
: undefined;
};
6 changes: 5 additions & 1 deletion src/vs/editor/common/modes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,10 @@ export interface Location {
*/
range: IRange;
}
export interface DefinitionAndSpan {
span?: Location;
definition: 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 @@ -460,7 +464,7 @@ export interface DefinitionProvider {
/**
* Provide the definition of the symbol at the given position and document.
*/
provideDefinition(model: editorCommon.IReadOnlyModel, position: Position, token: CancellationToken): Definition | Thenable<Definition>;
provideDefinition(model: editorCommon.IReadOnlyModel, position: Position, token: CancellationToken): Definition | DefinitionAndSpan | Thenable<Definition | DefinitionAndSpan>;
}

/**
Expand Down
38 changes: 27 additions & 11 deletions src/vs/editor/contrib/goToDeclaration/goToDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,35 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IReadOnlyModel } from 'vs/editor/common/editorCommon';
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, DefinitionAndSpan } 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';

function outputResults(promises: TPromise<Location | Location[]>[]) {
function locationToDefinitionAndSpan(location: Location): DefinitionAndSpan {
return {
definition: location
};
}

function outputResults(promises: TPromise<Location | Location[] | DefinitionAndSpan[]>[]): TPromise<DefinitionAndSpan[]> {
return TPromise.join(promises).then(allReferences => {
let result: Location[] = [];
let result: DefinitionAndSpan[] = [];
for (let references of allReferences) {
if (!references) {
continue;
}

if (Array.isArray(references)) {
result.push(...references);
} else if (references) {
result.push(references);
for (const item of references) {
if ((item as DefinitionAndSpan).definition) {
result.push(item as DefinitionAndSpan);
} else {
result.push(locationToDefinitionAndSpan(item as Location));
}
}
} else {
result.push(locationToDefinitionAndSpan(references as Location));
}
}
return result;
Expand All @@ -33,8 +49,8 @@ function getDefinitions<T>(
model: IReadOnlyModel,
position: Position,
registry: LanguageFeatureRegistry<T>,
provide: (provider: T, model: IReadOnlyModel, position: Position, token: CancellationToken) => Location | Location[] | Thenable<Location | Location[]>
): TPromise<Location[]> {
provide: (provider: T, model: IReadOnlyModel, position: Position, token: CancellationToken) => Location | Location[] | DefinitionAndSpan | Thenable<Location | Location[] | DefinitionAndSpan>
): TPromise<DefinitionAndSpan[]> {
const provider = registry.ordered(model);

// get results
Expand All @@ -50,19 +66,19 @@ function getDefinitions<T>(
}


export function getDefinitionsAtPosition(model: IReadOnlyModel, position: Position): TPromise<Location[]> {
export function getDefinitionsAtPosition(model: IReadOnlyModel, position: Position): TPromise<DefinitionAndSpan[]> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change currently breaks the vscode.executeDefinitionProvider command. It previously returned Location[] but will now return a DefinitionAndSpan[]

return getDefinitions(model, position, DefinitionProviderRegistry, (provider, model, position, token) => {
return provider.provideDefinition(model, position, token);
});
}

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

export function getTypeDefinitionsAtPosition(model: IReadOnlyModel, position: Position): TPromise<Location[]> {
export function getTypeDefinitionsAtPosition(model: IReadOnlyModel, position: Position): TPromise<DefinitionAndSpan[]> {
return getDefinitions(model, position, TypeDefinitionProviderRegistry, (provider, model, position, token) => {
return provider.provideTypeDefinition(model, position, token);
});
Expand Down
16 changes: 8 additions & 8 deletions src/vs/editor/contrib/goToDeclaration/goToDeclarationCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { IMessageService } from 'vs/platform/message/common/message';
import { Range } from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { registerEditorAction, IActionOptions, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions';
import { Location } from 'vs/editor/common/modes';
import { Location, DefinitionAndSpan } 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 @@ -68,12 +68,12 @@ 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 reference of references) {
let location = reference.definition;
if (!reference || !location.range) {
continue;
}
let { uri, range } = reference;
let { uri, range } = location;
let newLen = result.push({
uri,
range
Expand Down Expand Up @@ -112,7 +112,7 @@ export class DefinitionAction extends EditorAction {
return definitionPromise;
}

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

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

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

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

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

Expand Down
22 changes: 15 additions & 7 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, DefinitionAndSpan } 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 @@ -101,7 +101,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
this.throttler.queue(() => {
return state.validate(this.editor)
? this.findDefinition(mouseEvent.target)
: TPromise.wrap<Location[]>(null);
: TPromise.wrap<DefinitionAndSpan[]>(null);

}).then(results => {
if (!results || !results.length || !state.validate(this.editor)) {
Expand All @@ -121,19 +121,19 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
else {
let result = results[0];

if (!result.uri) {
if (!result.definition.uri) {
return;
}

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

if (!ref.object || !ref.object.textEditorModel) {
ref.dispose();
return;
}

const { object: { textEditorModel } } = ref;
const { startLineNumber } = result.range;
const { startLineNumber } = result.definition.range;

if (textEditorModel.getLineMaxColumn(startLineNumber) === 0) {
ref.dispose();
Expand All @@ -156,8 +156,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 (result.span) {
const range = result.span.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 @@ -193,7 +201,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
DefinitionProviderRegistry.has(this.editor.getModel());
}

private findDefinition(target: IMouseTarget): TPromise<Location[]> {
private findDefinition(target: IMouseTarget): TPromise<DefinitionAndSpan[]> {
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 @@ -4677,6 +4677,11 @@ declare module monaco.languages {
range: IRange;
}

export interface DefinitionAndSpan {
span?: Location;
definition: 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 @@ -4693,7 +4698,7 @@ declare module monaco.languages {
/**
* Provide the definition of the symbol at the given position and document.
*/
provideDefinition(model: editor.IReadOnlyModel, position: Position, token: CancellationToken): Definition | Thenable<Definition>;
provideDefinition(model: editor.IReadOnlyModel, position: Position, token: CancellationToken): Definition | DefinitionAndSpan | Thenable<Definition | DefinitionAndSpan>;
}

/**
Expand Down
9 changes: 8 additions & 1 deletion src/vs/vscode.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,13 @@ declare module 'vscode' {
resolveCodeLens?(codeLens: CodeLens, token: CancellationToken): ProviderResult<CodeLens>;
}

export class DefinitionAndSpan {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want to make this use a single definition instead:

 class DefinitionAndSpan {
 		span: Location;
 		definition: Location;
}

My original thinking was to bundle the concept of a definition into a class, i.e. this text range corresponds to these X definitions. However I'm not sure this makes sense since we later flatten out the definition list in the UI anyways

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I'm thinking of renaming this type. Something like DefinitionAndDefiningSpan or BoundedDefinition perhaps

span: Location;
definitions: Location[];

constructor(span: 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 @@ -1919,7 +1926,7 @@ declare module 'vscode' {
* @return A definition or a thenable that resolves to such. The lack of a result can be
* signaled by returning `undefined` or `null`.
*/
provideDefinition(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<Definition>;
provideDefinition(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<Definition | DefinitionAndSpan>;
}

/**
Expand Down
Loading