Skip to content

Get type definition at position #2966

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

Merged
merged 8 commits into from
May 4, 2015
Merged
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
34 changes: 33 additions & 1 deletion src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,28 @@ module FourSlash {
this.currentCaretPosition = definition.textSpan.start;
}

public goToTypeDefinition(definitionIndex: number) {
if (definitionIndex === 0) {
this.scenarioActions.push('<GoToTypeDefinition />');
}
else {
this.taoInvalidReason = 'GoToTypeDefinition not supported for non-zero definition indices';
}

var definitions = this.languageService.getTypeDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
if (!definitions || !definitions.length) {
this.raiseError('goToTypeDefinition failed - expected to at least one definition location but got 0');
}

if (definitionIndex >= definitions.length) {
this.raiseError('goToTypeDefinition failed - definitionIndex value (' + definitionIndex + ') exceeds definition list size (' + definitions.length + ')');
}

var definition = definitions[definitionIndex];
this.openFile(definition.fileName);
this.currentCaretPosition = definition.textSpan.start;
}

public verifyDefinitionLocationExists(negative: boolean) {
this.taoInvalidReason = 'verifyDefinitionLocationExists NYI';

Expand All @@ -1589,8 +1611,18 @@ module FourSlash {
var assertFn = negative ? assert.notEqual : assert.equal;

var definitions = this.languageService.getDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
var actualCount = definitions && definitions.length || 0;

assertFn(actualCount, expectedCount, this.messageAtLastKnownMarker("Definitions Count"));
}

public verifyTypeDefinitionsCount(negative: boolean, expectedCount: number) {
var assertFn = negative ? assert.notEqual : assert.equal;

var definitions = this.languageService.getTypeDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition);
var actualCount = definitions && definitions.length || 0;

assertFn(definitions.length, expectedCount, this.messageAtLastKnownMarker("Definitions Count"));
assertFn(actualCount, expectedCount, this.messageAtLastKnownMarker("Type definitions Count"));
}

public verifyDefinitionsName(negative: boolean, expectedName: string, expectedContainerName: string) {
Expand Down
3 changes: 3 additions & 0 deletions src/harness/harnessLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,9 @@ module Harness.LanguageService {
getDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] {
return unwrapJSONCallResult(this.shim.getDefinitionAtPosition(fileName, position));
}
getTypeDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[]{
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the difference between getDefinitionAtPositoin and getTypeDefinitionAtPosition?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One gets you the place where the "symbol" is defined, where the other takes you to the definition location of the "type".

it is a very useful feature when you want to know what is the shape of value.property, hovering gives you a type name, but to know the shape of the type you need to first jump to the declaration of property, then jump to the declaration of its type. it gets even harder with inferred types, cause you need to go to the definition of the function, or variable that produces the contextual type then go to the type from there.

editors like Eclipse and monaco have had this feature for long time. also @basarat has added support for it recently on the atom plugin if i am not mistaken.

Copy link
Contributor

Choose a reason for hiding this comment

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

That makes sense. We shoudl drive this through on the Roslyn side as well though. Definitely open up a bug on them to support this. That way we can get a command in for VB/C#/TS for this scenario, and the experience can be consistent between all three languages.

return unwrapJSONCallResult(this.shim.getTypeDefinitionAtPosition(fileName, position));
}
getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] {
return unwrapJSONCallResult(this.shim.getReferencesAtPosition(fileName, position));
}
Expand Down
26 changes: 26 additions & 0 deletions src/server/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,32 @@ module ts.server {
});
}

getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] {
var lineOffset = this.positionToOneBasedLineOffset(fileName, position);
var args: protocol.FileLocationRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset,
};

var request = this.processRequest<protocol.TypeDefinitionRequest>(CommandNames.TypeDefinition, args);
var response = this.processResponse<protocol.TypeDefinitionResponse>(request);

return response.body.map(entry => {
var fileName = entry.file;
var start = this.lineOffsetToPosition(fileName, entry.start);
var end = this.lineOffsetToPosition(fileName, entry.end);
return {
containerKind: "",
containerName: "",
fileName: fileName,
textSpan: ts.createTextSpanFromBounds(start, end),
kind: "",
name: ""
};
});
}

findReferences(fileName: string, position: number): ReferencedSymbol[]{
// Not yet implemented.
return [];
Expand Down
4 changes: 2 additions & 2 deletions src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,7 @@ module ts.server {
findConfigFile(searchPath: string): string {
while (true) {
var fileName = ts.combinePaths(searchPath, "tsconfig.json");
if (sys.fileExists(fileName)) {
if (this.host.fileExists(fileName)) {
return fileName;
}
var parentPath = ts.getDirectoryPath(searchPath);
Expand Down Expand Up @@ -923,7 +923,7 @@ module ts.server {
var proj = this.createProject(configFilename, projectOptions);
for (var i = 0, len = parsedCommandLine.fileNames.length; i < len; i++) {
var rootFilename = parsedCommandLine.fileNames[i];
if (ts.sys.fileExists(rootFilename)) {
if (this.host.fileExists(rootFilename)) {
var info = this.openFile(rootFilename, clientFileName == rootFilename);
proj.addRoot(info);
}
Expand Down
15 changes: 15 additions & 0 deletions src/server/protocol.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,14 @@ declare module ts.server.protocol {
export interface DefinitionRequest extends FileLocationRequest {
}

/**
* Go to type request; value of command field is
* "typeDefinition". Return response giving the file locations that
* define the type for the symbol found in file at location line, col.
*/
export interface TypeDefinitionRequest extends FileLocationRequest {
}

/**
* Location in source code expressed as (one-based) line and character offset.
*/
Expand Down Expand Up @@ -165,6 +173,13 @@ declare module ts.server.protocol {
body?: FileSpan[];
}

/**
* Definition response message. Gives text range for definition.
*/
export interface TypeDefinitionResponse extends Response {
body?: FileSpan[];
}

/**
* Get occurrences request; value of command field is
* "occurrences". Return response giving spans that are relevant
Expand Down
30 changes: 29 additions & 1 deletion src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ module ts.server {
export var Rename = "rename";
export var Saveto = "saveto";
export var SignatureHelp = "signatureHelp";
export var TypeDefinition = "typeDefinition";
export var Unknown = "unknown";
}

Expand Down Expand Up @@ -285,7 +286,29 @@ module ts.server {
}));
}

getOccurrences(line: number, offset: number, fileName: string): protocol.OccurrencesResponseItem[] {
getTypeDefinition(line: number, offset: number, fileName: string): protocol.FileSpan[] {
var file = ts.normalizePath(fileName);
var project = this.projectService.getProjectForFile(file);
if (!project) {
throw Errors.NoProject;
}

var compilerService = project.compilerService;
var position = compilerService.host.lineOffsetToPosition(file, line, offset);

var definitions = compilerService.languageService.getTypeDefinitionAtPosition(file, position);
if (!definitions) {
return undefined;
}

return definitions.map(def => ({
file: def.fileName,
start: compilerService.host.positionToLineOffset(def.fileName, def.textSpan.start),
end: compilerService.host.positionToLineOffset(def.fileName, ts.textSpanEnd(def.textSpan))
}));
}

getOccurrences(line: number, offset: number, fileName: string): protocol.OccurrencesResponseItem[]{
Copy link
Member

Choose a reason for hiding this comment

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

Space before {

fileName = ts.normalizePath(fileName);
let project = this.projectService.getProjectForFile(fileName);

Expand Down Expand Up @@ -817,6 +840,11 @@ module ts.server {
response = this.getDefinition(defArgs.line, defArgs.offset, defArgs.file);
break;
}
case CommandNames.TypeDefinition: {
var defArgs = <protocol.FileLocationRequestArgs>request.arguments;
response = this.getTypeDefinition(defArgs.line, defArgs.offset, defArgs.file);
break;
}
case CommandNames.References: {
var refArgs = <protocol.FileLocationRequestArgs>request.arguments;
response = this.getReferences(refArgs.line, refArgs.offset, refArgs.file);
Expand Down
Loading