Skip to content

Commit 9385b84

Browse files
author
Andy Hanson
committed
Add "navtree" and "navtree-full" server commands
1 parent 3b0515f commit 9385b84

File tree

56 files changed

+1786
-141
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1786
-141
lines changed

src/harness/fourslash.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2211,6 +2211,18 @@ namespace FourSlash {
22112211
}
22122212
}
22132213

2214+
public verifyNavigationTree(json: any) {
2215+
const tree = this.languageService.getNavigationTree(this.activeFile.fileName);
2216+
if (JSON.stringify(tree, replacer) !== JSON.stringify(json)) {
2217+
this.raiseError(`verifyNavigationTree failed - expected: ${stringify(json)}, got: ${stringify(tree, replacer)}`);
2218+
}
2219+
2220+
function replacer(key: string, value: any) {
2221+
// Don't check "spans", and omit falsy values.
2222+
return key === "spans" ? undefined : (value || undefined);
2223+
}
2224+
}
2225+
22142226
public printNavigationItems(searchValue: string) {
22152227
const items = this.languageService.getNavigateToItems(searchValue);
22162228
const length = items && items.length;
@@ -3279,6 +3291,10 @@ namespace FourSlashInterface {
32793291
this.state.verifyNavigationBar(json);
32803292
}
32813293

3294+
public navigationTree(json: any) {
3295+
this.state.verifyNavigationTree(json);
3296+
}
3297+
32823298
public navigationItemsListCount(count: number, searchValue: string, matchKind?: string, fileName?: string) {
32833299
this.state.verifyNavigationItemsCount(count, searchValue, matchKind, fileName);
32843300
}

src/harness/harnessLanguageService.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,10 @@ namespace Harness.LanguageService {
459459
getNavigationBarItems(fileName: string): ts.NavigationBarItem[] {
460460
return unwrapJSONCallResult(this.shim.getNavigationBarItems(fileName));
461461
}
462+
getNavigationTree(fileName: string): ts.NavigationTree {
463+
return unwrapJSONCallResult(this.shim.getNavigationTree(fileName));
464+
}
465+
462466
getOutliningSpans(fileName: string): ts.OutliningSpan[] {
463467
return unwrapJSONCallResult(this.shim.getOutliningSpans(fileName));
464468
}

src/server/client.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ namespace ts.server {
488488
return this.lastRenameEntry.locations;
489489
}
490490

491-
decodeNavigationBarItems(items: protocol.NavigationBarItem[], fileName: string, lineMap: number[]): NavigationBarItem[] {
491+
private decodeNavigationBarItems(items: protocol.NavigationBarItem[], fileName: string, lineMap: number[]): NavigationBarItem[] {
492492
if (!items) {
493493
return [];
494494
}
@@ -497,10 +497,7 @@ namespace ts.server {
497497
text: item.text,
498498
kind: item.kind,
499499
kindModifiers: item.kindModifiers || "",
500-
spans: item.spans.map(span =>
501-
createTextSpanFromBounds(
502-
this.lineOffsetToPosition(fileName, span.start, lineMap),
503-
this.lineOffsetToPosition(fileName, span.end, lineMap))),
500+
spans: item.spans.map(span => this.decodeSpan(span, fileName, lineMap)),
504501
childItems: this.decodeNavigationBarItems(item.childItems, fileName, lineMap),
505502
indent: item.indent,
506503
bolded: false,
@@ -509,17 +506,37 @@ namespace ts.server {
509506
}
510507

511508
getNavigationBarItems(fileName: string): NavigationBarItem[] {
512-
const args: protocol.FileRequestArgs = {
513-
file: fileName
514-
};
515-
516-
const request = this.processRequest<protocol.NavBarRequest>(CommandNames.NavBar, args);
509+
const request = this.processRequest<protocol.NavBarRequest>(CommandNames.NavBar, { file: fileName });
517510
const response = this.processResponse<protocol.NavBarResponse>(request);
518511

519512
const lineMap = this.getLineMap(fileName);
520513
return this.decodeNavigationBarItems(response.body, fileName, lineMap);
521514
}
522515

516+
private decodeNavigationTree(tree: protocol.NavigationTree, fileName: string, lineMap: number[]): NavigationTree {
517+
return {
518+
text: tree.text,
519+
kind: tree.kind,
520+
kindModifiers: tree.kindModifiers,
521+
spans: tree.spans.map(span => this.decodeSpan(span, fileName, lineMap)),
522+
childItems: map(tree.childItems, item => this.decodeNavigationTree(item, fileName, lineMap))
523+
};
524+
}
525+
526+
getNavigationTree(fileName: string): NavigationTree {
527+
const request = this.processRequest<protocol.NavTreeRequest>(CommandNames.NavTree, { file: fileName });
528+
const response = this.processResponse<protocol.NavTreeResponse>(request);
529+
530+
const lineMap = this.getLineMap(fileName);
531+
return this.decodeNavigationTree(response.body, fileName, lineMap);
532+
}
533+
534+
private decodeSpan(span: protocol.TextSpan, fileName: string, lineMap: number[]) {
535+
return createTextSpanFromBounds(
536+
this.lineOffsetToPosition(fileName, span.start, lineMap),
537+
this.lineOffsetToPosition(fileName, span.end, lineMap));
538+
}
539+
523540
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan {
524541
throw new Error("Not Implemented Yet.");
525542
}

src/server/protocol.d.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ declare namespace ts.server.protocol {
110110
*/
111111
export interface TodoCommentRequestArgs extends FileRequestArgs {
112112
/**
113-
* Array of target TodoCommentDescriptors that describes TODO comments to be found
113+
* Array of target TodoCommentDescriptors that describes TODO comments to be found
114114
*/
115115
descriptors: TodoCommentDescriptor[];
116116
}
@@ -231,7 +231,7 @@ declare namespace ts.server.protocol {
231231
offset?: number;
232232

233233
/**
234-
* Position (can be specified instead of line/offset pair)
234+
* Position (can be specified instead of line/offset pair)
235235
*/
236236
position?: number;
237237
}
@@ -577,12 +577,12 @@ declare namespace ts.server.protocol {
577577

578578
/**
579579
* Represents a file in external project.
580-
* External project is project whose set of files, compilation options and open\close state
580+
* External project is project whose set of files, compilation options and open\close state
581581
* is maintained by the client (i.e. if all this data come from .csproj file in Visual Studio).
582582
* External project will exist even if all files in it are closed and should be closed explicity.
583-
* If external project includes one or more tsconfig.json/jsconfig.json files then tsserver will
583+
* If external project includes one or more tsconfig.json/jsconfig.json files then tsserver will
584584
* create configured project for every config file but will maintain a link that these projects were created
585-
* as a result of opening external project so they should be removed once external project is closed.
585+
* as a result of opening external project so they should be removed once external project is closed.
586586
*/
587587
export interface ExternalFile {
588588
/**
@@ -998,7 +998,7 @@ declare namespace ts.server.protocol {
998998
}
999999

10001000
/**
1001-
* Response for CompileOnSaveAffectedFileListRequest request;
1001+
* Response for CompileOnSaveAffectedFileListRequest request;
10021002
*/
10031003
export interface CompileOnSaveAffectedFileListResponse extends Response {
10041004
body: CompileOnSaveAffectedFileListSingleProject[];
@@ -1743,6 +1743,13 @@ declare namespace ts.server.protocol {
17431743
export interface NavBarRequest extends FileRequest {
17441744
}
17451745

1746+
/**
1747+
* NavTree request; value of command field is "navtree".
1748+
* Return response giving the navigation tree of the requested file.
1749+
*/
1750+
export interface NavTreeRequest extends FileRequest {
1751+
}
1752+
17461753
export interface NavigationBarItem {
17471754
/**
17481755
* The item's display text.
@@ -1775,7 +1782,20 @@ declare namespace ts.server.protocol {
17751782
indent: number;
17761783
}
17771784

1785+
/** protocol.NavigationTree is identical to ts.NavigationTree, except using protocol.TextSpan instead of ts.TextSpan */
1786+
export interface NavigationTree {
1787+
text: string;
1788+
kind: string;
1789+
kindModifiers: string;
1790+
spans: TextSpan[];
1791+
childItems?: NavigationTree[];
1792+
}
1793+
17781794
export interface NavBarResponse extends Response {
17791795
body?: NavigationBarItem[];
17801796
}
1797+
1798+
export interface NavTreeResponse extends Response {
1799+
body?: NavigationTree;
1800+
}
17811801
}

src/server/scriptInfo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace ts.server {
44

55
export class ScriptInfo {
66
/**
7-
* All projects that include this file
7+
* All projects that include this file
88
*/
99
readonly containingProjects: Project[] = [];
1010
private formatCodeSettings: ts.FormatCodeSettings;

src/server/session.ts

Lines changed: 51 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ namespace ts.server {
9898
export const SyntacticDiagnosticsSync = "syntacticDiagnosticsSync";
9999
export const NavBar = "navbar";
100100
export const NavBarFull = "navbar-full";
101+
export const NavTree = "navtree";
102+
export const NavTreeFull = "navtree-full";
101103
export const Navto = "navto";
102104
export const NavtoFull = "navto-full";
103105
export const Occurrences = "occurrences";
@@ -550,7 +552,7 @@ namespace ts.server {
550552
const scriptInfo = this.projectService.getScriptInfo(args.file);
551553
projects = scriptInfo.containingProjects;
552554
}
553-
// ts.filter handles case when 'projects' is undefined
555+
// ts.filter handles case when 'projects' is undefined
554556
projects = filter(projects, p => p.languageServiceEnabled);
555557
if (!projects || !projects.length) {
556558
return Errors.ThrowNoProject();
@@ -947,15 +949,8 @@ namespace ts.server {
947949
return completions.entries.reduce((result: protocol.CompletionEntry[], entry: ts.CompletionEntry) => {
948950
if (completions.isMemberCompletion || (entry.name.toLowerCase().indexOf(prefix.toLowerCase()) === 0)) {
949951
const { name, kind, kindModifiers, sortText, replacementSpan } = entry;
950-
951-
let convertedSpan: protocol.TextSpan = undefined;
952-
if (replacementSpan) {
953-
convertedSpan = {
954-
start: scriptInfo.positionToLineOffset(replacementSpan.start),
955-
end: scriptInfo.positionToLineOffset(replacementSpan.start + replacementSpan.length)
956-
};
957-
}
958-
952+
const convertedSpan: protocol.TextSpan =
953+
replacementSpan ? this.decorateSpan(replacementSpan, scriptInfo) : undefined;
959954
result.push({ name, kind, kindModifiers, sortText, replacementSpan: convertedSpan });
960955
}
961956
return result;
@@ -1093,38 +1088,54 @@ namespace ts.server {
10931088
this.projectService.closeClientFile(file);
10941089
}
10951090

1096-
private decorateNavigationBarItem(project: Project, fileName: NormalizedPath, items: ts.NavigationBarItem[]): protocol.NavigationBarItem[] {
1097-
if (!items) {
1098-
return undefined;
1099-
}
1100-
1101-
const scriptInfo = project.getScriptInfoForNormalizedPath(fileName);
1102-
1103-
return items.map(item => ({
1091+
private decorateNavigationBarItems(items: ts.NavigationBarItem[], scriptInfo: ScriptInfo): protocol.NavigationBarItem[] {
1092+
return map(items, item => ({
11041093
text: item.text,
11051094
kind: item.kind,
11061095
kindModifiers: item.kindModifiers,
1107-
spans: item.spans.map(span => ({
1108-
start: scriptInfo.positionToLineOffset(span.start),
1109-
end: scriptInfo.positionToLineOffset(ts.textSpanEnd(span))
1110-
})),
1111-
childItems: this.decorateNavigationBarItem(project, fileName, item.childItems),
1096+
spans: item.spans.map(span => this.decorateSpan(span, scriptInfo)),
1097+
childItems: this.decorateNavigationBarItems(item.childItems, scriptInfo),
11121098
indent: item.indent
11131099
}));
11141100
}
11151101

11161102
private getNavigationBarItems(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.NavigationBarItem[] | NavigationBarItem[] {
11171103
const { file, project } = this.getFileAndProject(args);
11181104
const items = project.getLanguageService(/*ensureSynchronized*/ false).getNavigationBarItems(file);
1119-
if (!items) {
1120-
return undefined;
1121-
}
1122-
1123-
return simplifiedResult
1124-
? this.decorateNavigationBarItem(project, file, items)
1105+
return !items
1106+
? undefined
1107+
: simplifiedResult
1108+
? this.decorateNavigationBarItems(items, project.getScriptInfoForNormalizedPath(file))
11251109
: items;
11261110
}
11271111

1112+
private decorateNavigationTree(tree: ts.NavigationTree, scriptInfo: ScriptInfo): protocol.NavigationTree {
1113+
return {
1114+
text: tree.text,
1115+
kind: tree.kind,
1116+
kindModifiers: tree.kindModifiers,
1117+
spans: tree.spans.map(span => this.decorateSpan(span, scriptInfo)),
1118+
childItems: map(tree.childItems, item => this.decorateNavigationTree(item, scriptInfo))
1119+
};
1120+
}
1121+
1122+
private decorateSpan(span: TextSpan, scriptInfo: ScriptInfo): protocol.TextSpan {
1123+
return {
1124+
start: scriptInfo.positionToLineOffset(span.start),
1125+
end: scriptInfo.positionToLineOffset(ts.textSpanEnd(span))
1126+
};
1127+
}
1128+
1129+
private getNavigationTree(args: protocol.FileRequestArgs, simplifiedResult: boolean): protocol.NavigationTree | NavigationTree {
1130+
const { file, project } = this.getFileAndProject(args);
1131+
const tree = project.getLanguageService(/*ensureSynchronized*/ false).getNavigationTree(file);
1132+
return !tree
1133+
? undefined
1134+
: simplifiedResult
1135+
? this.decorateNavigationTree(tree, project.getScriptInfoForNormalizedPath(file))
1136+
: tree;
1137+
}
1138+
11281139
private getNavigateToItems(args: protocol.NavtoRequestArgs, simplifiedResult: boolean): protocol.NavtoItem[] | NavigateToItem[] {
11291140
const projects = this.getProjects(args);
11301141

@@ -1212,19 +1223,11 @@ namespace ts.server {
12121223
const position = this.getPosition(args, scriptInfo);
12131224

12141225
const spans = project.getLanguageService(/*ensureSynchronized*/ false).getBraceMatchingAtPosition(file, position);
1215-
if (!spans) {
1216-
return undefined;
1217-
}
1218-
if (simplifiedResult) {
1219-
1220-
return spans.map(span => ({
1221-
start: scriptInfo.positionToLineOffset(span.start),
1222-
end: scriptInfo.positionToLineOffset(span.start + span.length)
1223-
}));
1224-
}
1225-
else {
1226-
return spans;
1227-
}
1226+
return !spans
1227+
? undefined
1228+
: simplifiedResult
1229+
? spans.map(span => this.decorateSpan(span, scriptInfo))
1230+
: spans;
12281231
}
12291232

12301233
getDiagnosticsForProject(delay: number, fileName: string) {
@@ -1509,6 +1512,12 @@ namespace ts.server {
15091512
[CommandNames.NavBarFull]: (request: protocol.FileRequest) => {
15101513
return this.requiredResponse(this.getNavigationBarItems(request.arguments, /*simplifiedResult*/ false));
15111514
},
1515+
[CommandNames.NavTree]: (request: protocol.FileRequest) => {
1516+
return this.requiredResponse(this.getNavigationTree(request.arguments, /*simplifiedResult*/ true));
1517+
},
1518+
[CommandNames.NavTreeFull]: (request: protocol.FileRequest) => {
1519+
return this.requiredResponse(this.getNavigationTree(request.arguments, /*simplifiedResult*/ false));
1520+
},
15121521
[CommandNames.Occurrences]: (request: protocol.FileLocationRequest) => {
15131522
return this.requiredResponse(this.getOccurrences(request.arguments));
15141523
},

0 commit comments

Comments
 (0)