diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index bc7a8718a76..bb144742f45 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1932,7 +1932,7 @@ module FourSlash { } var missingItem = { name: name, kind: kind }; - this.raiseError('verifyGetScriptLexicalStructureListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(items) + ')'); + this.raiseError('verifyGetScriptLexicalStructureListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(items, null, " ") + ')'); } private navigationBarItemsContains(items: ts.NavigationBarItem[], name: string, kind: string) { diff --git a/src/server/client.ts b/src/server/client.ts index 960c0723c30..d23bc5c7359 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -370,8 +370,32 @@ module ts.server { }); } + decodeNavigationBarItems(items: ServerProtocol.NavigationBarItem[], fileName: string): NavigationBarItem[] { + if (!items) { + return []; + } + + return items.map(item => ({ + text: item.text, + kind: item.kind, + kindModifiers: item.kindModifiers || "", + spans: item.spans.map(span=> createTextSpanFromBounds(this.lineColToPosition(fileName, span.start), this.lineColToPosition(fileName, span.end))), + childItems: this.decodeNavigationBarItems(item.childItems, fileName), + indent: 0, + bolded: false, + grayed: false + })); + } + getNavigationBarItems(fileName: string): NavigationBarItem[] { - throw new Error("Not Implemented Yet."); + var args: ServerProtocol.FileRequestArgs = { + file: fileName + }; + + var request = this.processRequest(CommandNames.NavBar, args); + var response = this.processResponse(request); + + return this.decodeNavigationBarItems(response.body, fileName); } getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan { diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 1404b3ea4b1..0e6a0ff8f65 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -4,13 +4,6 @@ /// /// -module ts { - export interface NavigationBarItem { - displayString?: string; - docString?: string; - } -} - module ts.server { var paddedLength = 8; @@ -129,6 +122,7 @@ module ts.server { export var Format = "format"; export var Formatonkey = "formatonkey"; export var Geterr = "geterr"; + export var NavBar = "navbar"; export var Navto = "navto"; export var Open = "open"; export var Quickinfo = "quickinfo"; @@ -426,7 +420,7 @@ module ts.server { if (!project) { throw Errors.NoProject; } - + var compilerService = project.compilerService; var startPosition = compilerService.host.lineColToPosition(file, line, col); var endPosition = compilerService.host.lineColToPosition(file, endLine, endCol); @@ -436,7 +430,7 @@ module ts.server { if (!edits) { throw Errors.NoContent; } - + return edits.map((edit) => { return { start: compilerService.host.positionToLineCol(file, edit.span.start), @@ -500,7 +494,7 @@ module ts.server { var compilerService = project.compilerService; var position = compilerService.host.lineColToPosition(file, line, col); - + var completions = compilerService.languageService.getCompletionsAtPosition(file, position); if (!completions) { throw Errors.NoContent; @@ -526,7 +520,7 @@ module ts.server { return result; }, []); } - + geterr(delay: number, fileNames: string[]) { var checkList = fileNames.reduce((accum: PendingErrorCheck[], fileName: string) => { fileName = ts.normalizePath(fileName); @@ -587,40 +581,39 @@ module ts.server { this.projectService.closeClientFile(file); } - decorateNavBarItem(navBarItem: ts.NavigationBarItem, compilerService: CompilerService, file: string) { - if (navBarItem.spans.length == 1) { - var span = navBarItem.spans[0]; - var offset = span.start; - var textForSpan = compilerService.host.getScriptSnapshot(file).getText(offset, offset + span.length); - var adj = textForSpan.indexOf(navBarItem.text); - if (adj > 0) { - offset += adj; - } - var quickInfo = compilerService.languageService.getQuickInfoAtPosition(file, - offset + (navBarItem.text.length / 2)); - if (quickInfo) { - var displayString = ts.displayPartsToString(quickInfo.displayParts); - var docString = ts.displayPartsToString(quickInfo.documentation); - navBarItem.displayString = displayString; - navBarItem.docString = docString; - } + decorateNavigationBarItem(project: Project, fileName: string, items: ts.NavigationBarItem[]): ServerProtocol.NavigationBarItem[] { + if (!items) { + return undefined; } - if (navBarItem.childItems.length > 0) { - navBarItem.childItems = - navBarItem.childItems.map(navBarItem => this.decorateNavBarItem(navBarItem, compilerService, file)); - } - return navBarItem; + + var compilerService = project.compilerService; + + return items.map(item => ({ + text: item.text, + kind: item.kind, + kindModifiers: item.kindModifiers, + spans: item.spans.map(span => ({ + start: compilerService.host.positionToLineCol(fileName, span.start), + end: compilerService.host.positionToLineCol(fileName, ts.textSpanEnd(span)) + })), + childItems: this.decorateNavigationBarItem(project, fileName, item.childItems) + })); } - navbar(rawfile: string, reqSeq = 0) { - var file = ts.normalizePath(rawfile); + navbar(fileName: string): ServerProtocol.NavigationBarItem[] { + var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); - if (project) { - var compilerService = project.compilerService; - var navBarItems = compilerService.languageService.getNavigationBarItems(file); - var bakedNavBarItems = navBarItems.map(navBarItem => this.decorateNavBarItem(navBarItem, compilerService, file)); - this.sendLineToClient(JSON.stringify(bakedNavBarItems, null, " ")); + if (!project) { + throw Errors.NoProject; } + + var compilerService = project.compilerService; + var items = compilerService.languageService.getNavigationBarItems(file); + if (!items) { + throw Errors.NoContent; + } + + return this.decorateNavigationBarItem(project, fileName, items); } navto(searchTerm: string, fileName: string): ServerProtocol.NavtoItem[] { @@ -765,6 +758,11 @@ module ts.server { response = this.getBraceMatching(braceArguments.line, braceArguments.col, braceArguments.file); break; } + case CommandNames.NavBar: { + var navBarArgs = request.arguments; + response = this.navbar(navBarArgs.file); + break; + } default: { this.projectService.log("Unrecognized JSON command: " + message); this.output(undefined, CommandNames.Unknown, request.seq, "Unrecognized JSON command: " + request.command); diff --git a/src/server/protodef.d.ts b/src/server/protodef.d.ts index fb88d30d2ef..12fd7163c1a 100644 --- a/src/server/protodef.d.ts +++ b/src/server/protodef.d.ts @@ -562,6 +562,34 @@ declare module ServerProtocol { export interface BraceRequest extends CodeLocationRequest { } + /** + NavBar itesm request; value of command field is "navbar". + Return response giving the list of navigation bar entries + extracted from the requested file. + */ + export interface NavBarRequest extends FileRequest { + } + + export interface NavigationBarItem { + /** The item's display text */ + text: string; + + /** The symbol's kind (such as 'className' or 'parameterName') */ + kind: string; + + /** Optional modifiers for the kind (such as 'public') */ + kindModifiers?: string; + + /** The definition locations of the item */ + spans: TextSpan[]; + + /** Optional children */ + childItems?: NavigationBarItem[]; + } + + export interface NavBarResponse extends Response { + body?: NavigationBarItem[]; + } } diff --git a/tests/cases/fourslash/server/navbar.ts b/tests/cases/fourslash/server/navbar.ts new file mode 100644 index 00000000000..e18f49c1db9 --- /dev/null +++ b/tests/cases/fourslash/server/navbar.ts @@ -0,0 +1,52 @@ +/// + +////// Interface +////{| "itemName": "IPoint", "kind": "interface", "parentName": "" |}interface IPoint { +//// {| "itemName": "getDist", "kind": "method", "parentName": "IPoint" |}getDist(): number; +//// {| "itemName": "new()", "kind": "construct", "parentName": "IPoint" |}new(): IPoint; +//// {| "itemName": "()", "kind": "call", "parentName": "IPoint" |}(): any; +//// {| "itemName": "[]", "kind": "index", "parentName": "IPoint" |}[x:string]: number; +//// {| "itemName": "prop", "kind": "property", "parentName": "IPoint" |}prop: string; +////} +//// +/////// Module +////{| "itemName": "Shapes", "kind": "module", "parentName": "" |}module Shapes { +//// +//// // Class +//// {| "itemName": "Point", "kind": "class", "parentName": "Shapes" |}export class Point implements IPoint { +//// {| "itemName": "constructor", "kind": "constructor", "parentName": "Shapes.Point" |}constructor (public x: number, public y: number) { } +//// +//// // Instance member +//// {| "itemName": "getDist", "kind": "method", "parentName": "Shapes.Point" |}getDist() { return Math.sqrt(this.x * this.x + this.y * this.y); } +//// +//// // Getter +//// {| "itemName": "value", "kind": "getter", "parentName": "Shapes.Point" |}get value(): number { return 0; } +//// +//// // Setter +//// {| "itemName": "value", "kind": "setter", "parentName": "Shapes.Point" |}set value(newValue: number) { return; } +//// +//// // Static member +//// {| "itemName": "origin", "kind": "property", "parentName": "Shapes.Point" |}static origin = new Point(0, 0); +//// +//// // Static method +//// {| "itemName": "getOrigin", "kind": "method", "parentName": "Shapes.Point" |}private static getOrigin() { return Point.origin;} +//// } +//// +//// {| "itemName": "Values", "kind": "enum", "parentName": "Shapes" |}enum Values { +//// value1, +//// {| "itemName": "value2", "kind": "property", "parentName": "Shapes.Values" |}value2, +//// value3, +//// } +////} +//// +////// Local variables +////{| "itemName": "p", "kind": "var", "parentName": "" |}var p: IPoint = new Shapes.Point(3, 4); +////{| "itemName": "dist", "kind": "var", "parentName": "" |}var dist = p.getDist(); + +test.markers().forEach((marker) => { + if (marker.data) { + verify.getScriptLexicalStructureListContains(marker.data.itemName, marker.data.kind, marker.fileName, marker.data.parentName); + } +}); + +verify.getScriptLexicalStructureListCount(23);