From d53cfdcb5b70e7f165c580745a12102f6590fc51 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Mon, 3 Aug 2015 20:45:39 -0700 Subject: [PATCH] Expose document highlighting to server. --- src/harness/fourslash.ts | 47 ++++++++++++++++++- src/server/client.ts | 24 +++++++++- src/server/protocol.d.ts | 22 +++++++++ src/server/session.ts | 43 ++++++++++++++++- tests/cases/fourslash/fourslash.ts | 8 ++++ .../fourslash/server/documentHighlights01.ts | 18 +++++++ 6 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 tests/cases/fourslash/server/documentHighlights01.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 5d73e04c8cb..a82346d888e 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2132,7 +2132,7 @@ module FourSlash { let occurance = occurances[i]; if (occurance && occurance.fileName === fileName && occurance.textSpan.start === start && ts.textSpanEnd(occurance.textSpan) === end) { if (typeof isWriteAccess !== "undefined" && occurance.isWriteAccess !== isWriteAccess) { - this.raiseError('verifyOccurancesAtPositionListContains failed - item isWriteAccess value doe not match, actual: ' + occurance.isWriteAccess + ', expected: ' + isWriteAccess + '.'); + this.raiseError('verifyOccurancesAtPositionListContains failed - item isWriteAccess value does not match, actual: ' + occurance.isWriteAccess + ', expected: ' + isWriteAccess + '.'); } return; } @@ -2152,6 +2152,51 @@ module FourSlash { } } + private getDocumentHighlightsAtCurrentPosition(fileNamesToSearch: string[]) { + let filesToSearch = fileNamesToSearch.map(name => this.basePath + "/" + name); + return this.languageService.getDocumentHighlights(this.activeFile.fileName, this.currentCaretPosition, filesToSearch); + } + + public verifyDocumentHighlightsAtPositionListContains(fileName: string, start: number, end: number, fileNamesToSearch: string[], kind?: string) { + this.taoInvalidReason = 'verifyDocumentHighlightsAtPositionListContains NYI'; + + let documentHighlights = this.getDocumentHighlightsAtCurrentPosition(fileNamesToSearch); + + if (!documentHighlights || documentHighlights.length === 0) { + this.raiseError('verifyDocumentHighlightsAtPositionListContains failed - found 0 highlights, expected at least one.'); + } + + for (let i = 0; i < documentHighlights.length; i++) if (documentHighlights[i].fileName === fileName) { + let { highlightSpans } = documentHighlights[i]; + + for (let highlight of highlightSpans) { + if (highlight && highlight.textSpan.start === start && ts.textSpanEnd(highlight.textSpan) === end) { + if (typeof kind !== "undefined" && highlight.kind !== kind) { + this.raiseError('verifyDocumentHighlightsAtPositionListContains failed - item "kind" value does not match, actual: ' + highlight.kind + ', expected: ' + kind + '.'); + } + return; + } + } + } + + let missingItem = { fileName: fileName, start: start, end: end, kind: kind }; + this.raiseError('verifyOccurancesAtPositionListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(documentHighlights) + ')'); + } + + public verifyDocumentHighlightsAtPositionListCount(expectedCount: number, fileNamesToSearch: string[]) { + this.taoInvalidReason = 'verifyDocumentHighlightsAtPositionListCount NYI'; + + let documentHighlights = this.getDocumentHighlightsAtCurrentPosition(fileNamesToSearch); + let actualCount = documentHighlights + ? documentHighlights.reduce((currentCount, currentDocumentHighlights) => { + return currentCount + currentDocumentHighlights.highlightSpans.length}, 0) + : 0; + + if (expectedCount !== actualCount) { + this.raiseError('verifyDocumentHighlightsAtPositionListCount failed - actual: ' + actualCount + ', expected:' + expectedCount); + } + } + // Get the text of the entire line the caret is currently at private getCurrentLineContent() { let text = this.getFileContent(this.activeFile.fileName); diff --git a/src/server/client.ts b/src/server/client.ts index 3ad7230cf33..f02961f02ad 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -528,7 +528,29 @@ namespace ts.server { } getDocumentHighlights(fileName: string, position: number): DocumentHighlights[] { - throw new Error("Not Implemented Yet."); + var lineOffset = this.positionToOneBasedLineOffset(fileName, position); + var args: protocol.FileLocationRequestArgs = { + file: fileName, + line: lineOffset.line, + offset: lineOffset.offset, + }; + + var request = this.processRequest(CommandNames.DocumentHighlights, args); + var response = this.processResponse(request); + + return response.body.map(entry => { // convert ts.server.protocol.DocumentHighlightsItem to ts.DocumentHighlights + return { + fileName: entry.file, + highlightSpans: entry.highlightSpans.map(span => { // convert ts.server.protocol.HighlightSpan to ts.HighlighSpan + var start = this.lineOffsetToPosition(entry.file, span.start); + var end = this.lineOffsetToPosition(entry.file, span.end); + return { + textSpan: ts.createTextSpanFromBounds(start, end), + kind: span.kind + }; + }) + }; + }); } getOutliningSpans(fileName: string): OutliningSpan[] { diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 017a8c1d81a..0822f537365 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -238,6 +238,28 @@ declare namespace ts.server.protocol { body?: OccurrencesResponseItem[]; } + /** + * Get document highlights request; value of command field is + * "documentHighlights". Return response giving spans that are relevant + * in the file at a given line and column. + */ + export interface DocumentHighlightsRequest extends FileLocationRequest { + } + + export interface HighLightSpan extends TextSpan { + kind: string + } + + export interface DocumentHighlightsItem { + file: string, + highlightSpans: HighLightSpan[]; + } + + export interface DocumentHighlightsResponse extends Response { + body?: DocumentHighlightsItem[]; + } + + /** * Find references request; value of command field is * "references". Return response giving the file locations that diff --git a/src/server/session.ts b/src/server/session.ts index 9a5cee32264..c91cf385c0f 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -89,6 +89,7 @@ namespace ts.server { export const NavBar = "navbar"; export const Navto = "navto"; export const Occurrences = "occurrences"; + export const DocumentHighlights = "documentHighlights"; export const Open = "open"; export const Quickinfo = "quickinfo"; export const References = "references"; @@ -313,7 +314,7 @@ namespace ts.server { })); } - private getOccurrences(line: number, offset: number, fileName: string): protocol.OccurrencesResponseItem[]{ + private getOccurrences(line: number, offset: number, fileName: string): protocol.OccurrencesResponseItem[] { fileName = ts.normalizePath(fileName); let project = this.projectService.getProjectForFile(fileName); @@ -343,6 +344,42 @@ namespace ts.server { }); } + private getDocumentHighlights(line: number, offset: number, fileName: string): protocol.DocumentHighlightsItem[] { + fileName = ts.normalizePath(fileName); + let project = this.projectService.getProjectForFile(fileName); + + if (!project) { + throw Errors.NoProject; + } + + let { compilerService } = project; + let position = compilerService.host.lineOffsetToPosition(fileName, line, offset); + let filesToSearch = [fileName]; // only search for highlights inside the current file + + let documentHighlights = compilerService.languageService.getDocumentHighlights(fileName, position, filesToSearch); + + if (!documentHighlights) { + return undefined; + } + + return documentHighlights.map(documentHighlight => { // convert ts.DocumentHighlights to ts.server.protocol.DocumentHighlightsItem + var file = documentHighlight.fileName; + return { + file: file, + highlightSpans: documentHighlight.highlightSpans.map(highlightSpan => { // convert to ts.HighlightSpan to ts.server.protocol.HighlightSpan + let { textSpan, kind } = highlightSpan; + let start = compilerService.host.positionToLineOffset(file, textSpan.start); + let end = compilerService.host.positionToLineOffset(file, ts.textSpanEnd(textSpan)); + return { + start: start, + end: end, + kind: kind + } + }) + } + }); + } + private getProjectInfo(fileName: string, needFileNameList: boolean): protocol.ProjectInfo { fileName = ts.normalizePath(fileName) let project = this.projectService.getProjectForFile(fileName) @@ -937,6 +974,10 @@ namespace ts.server { var { line, offset, file: fileName } = request.arguments; return {response: this.getOccurrences(line, offset, fileName), responseRequired: true}; }, + [CommandNames.DocumentHighlights]: (request: protocol.Request) => { + var { line, offset, file: fileName } = request.arguments; + return {response: this.getDocumentHighlights(line, offset, fileName), responseRequired: true}; + }, [CommandNames.ProjectInfo]: (request: protocol.Request) => { var { file, needFileNameList } = request.arguments; return {response: this.getProjectInfo(file, needFileNameList), responseRequired: true}; diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index cbba2e916ce..e809834e568 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -421,6 +421,14 @@ module FourSlashInterface { FourSlash.currentTestState.verifyOccurrencesAtPositionListCount(expectedCount); } + public documentHighlightsAtPositionContains(range: Range, fileNamesToSearch: string[], kind?: string) { + FourSlash.currentTestState.verifyDocumentHighlightsAtPositionListContains(range.fileName, range.start, range.end, fileNamesToSearch, kind); + } + + public documentHighlightsAtPositionCount(expectedCount: number, fileNamesToSearch: string[], kind?: string) { + FourSlash.currentTestState.verifyDocumentHighlightsAtPositionListCount(expectedCount, fileNamesToSearch); + } + public completionEntryDetailIs(entryName: string, text: string, documentation?: string, kind?: string) { FourSlash.currentTestState.verifyCompletionEntryDetails(entryName, text, documentation, kind); } diff --git a/tests/cases/fourslash/server/documentHighlights01.ts b/tests/cases/fourslash/server/documentHighlights01.ts new file mode 100644 index 00000000000..d4be88f9357 --- /dev/null +++ b/tests/cases/fourslash/server/documentHighlights01.ts @@ -0,0 +1,18 @@ +/// + +// @Filename: a.ts +////function [|f|](x: typeof [|f|]) { +//// [|f|]([|f|]); +////} + +let ranges = test.ranges(); + +for (let r of ranges) { + goTo.position(r.start); + verify.documentHighlightsAtPositionCount(ranges.length, ["a.ts"]); + + for (let range of ranges) { + verify.documentHighlightsAtPositionContains(range, ["a.ts"]); + } +} +