Skip to content

Commit 6980d56

Browse files
committed
Merge pull request microsoft#4139 from hoanhtien/documentHighlights
Expose document highlighting to server.
2 parents 54f3b41 + 8a943ee commit 6980d56

File tree

7 files changed

+226
-14
lines changed

7 files changed

+226
-14
lines changed

src/harness/fourslash.ts

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2122,36 +2122,81 @@ module FourSlash {
21222122
public verifyOccurrencesAtPositionListContains(fileName: string, start: number, end: number, isWriteAccess?: boolean) {
21232123
this.taoInvalidReason = "verifyOccurrencesAtPositionListContains NYI";
21242124

2125-
let occurances = this.getOccurancesAtCurrentPosition();
2125+
let occurrences = this.getOccurancesAtCurrentPosition();
21262126

2127-
if (!occurances || occurances.length === 0) {
2128-
this.raiseError("verifyOccurancesAtPositionListContains failed - found 0 references, expected at least one.");
2127+
if (!occurrences || occurrences.length === 0) {
2128+
this.raiseError('verifyOccurancesAtPositionListContains failed - found 0 references, expected at least one.');
21292129
}
21302130

2131-
for (let i = 0; i < occurances.length; i++) {
2132-
let occurance = occurances[i];
2133-
if (occurance && occurance.fileName === fileName && occurance.textSpan.start === start && ts.textSpanEnd(occurance.textSpan) === end) {
2134-
if (typeof isWriteAccess !== "undefined" && occurance.isWriteAccess !== isWriteAccess) {
2135-
this.raiseError(`verifyOccurancesAtPositionListContains failed - item isWriteAccess value doe not match, actual: ${occurance.isWriteAccess}, expected: ${isWriteAccess}.`);
2131+
for (let occurrence of occurrences) {
2132+
if (occurrence && occurrence.fileName === fileName && occurrence.textSpan.start === start && ts.textSpanEnd(occurrence.textSpan) === end) {
2133+
if (typeof isWriteAccess !== "undefined" && occurrence.isWriteAccess !== isWriteAccess) {
2134+
this.raiseError(`verifyOccurrencesAtPositionListContains failed - item isWriteAccess value does not match, actual: ${occurrence.isWriteAccess}, expected: ${isWriteAccess}.`);
21362135
}
21372136
return;
21382137
}
21392138
}
21402139

21412140
let missingItem = { fileName: fileName, start: start, end: end, isWriteAccess: isWriteAccess };
2142-
this.raiseError(`verifyOccurancesAtPositionListContains failed - could not find the item: ${JSON.stringify(missingItem)} in the returned list: (${JSON.stringify(occurances)})`);
2141+
this.raiseError(`verifyOccurrencesAtPositionListContains failed - could not find the item: ${JSON.stringify(missingItem)} in the returned list: (${JSON.stringify(occurrences)})`);
21432142
}
21442143

21452144
public verifyOccurrencesAtPositionListCount(expectedCount: number) {
21462145
this.taoInvalidReason = "verifyOccurrencesAtPositionListCount NYI";
21472146

2148-
let occurances = this.getOccurancesAtCurrentPosition();
2149-
let actualCount = occurances ? occurances.length : 0;
2147+
let occurrences = this.getOccurancesAtCurrentPosition();
2148+
let actualCount = occurrences ? occurrences.length : 0;
21502149
if (expectedCount !== actualCount) {
21512150
this.raiseError(`verifyOccurrencesAtPositionListCount failed - actual: ${actualCount}, expected:${expectedCount}`);
21522151
}
21532152
}
21542153

2154+
private getDocumentHighlightsAtCurrentPosition(fileNamesToSearch: string[]) {
2155+
let filesToSearch = fileNamesToSearch.map(name => ts.combinePaths(this.basePath, name));
2156+
return this.languageService.getDocumentHighlights(this.activeFile.fileName, this.currentCaretPosition, filesToSearch);
2157+
}
2158+
2159+
public verifyDocumentHighlightsAtPositionListContains(fileName: string, start: number, end: number, fileNamesToSearch: string[], kind?: string) {
2160+
this.taoInvalidReason = 'verifyDocumentHighlightsAtPositionListContains NYI';
2161+
2162+
let documentHighlights = this.getDocumentHighlightsAtCurrentPosition(fileNamesToSearch);
2163+
2164+
if (!documentHighlights || documentHighlights.length === 0) {
2165+
this.raiseError('verifyDocumentHighlightsAtPositionListContains failed - found 0 highlights, expected at least one.');
2166+
}
2167+
2168+
for (let documentHighlight of documentHighlights) {
2169+
if (documentHighlight.fileName === fileName) {
2170+
let { highlightSpans } = documentHighlight;
2171+
2172+
for (let highlight of highlightSpans) {
2173+
if (highlight && highlight.textSpan.start === start && ts.textSpanEnd(highlight.textSpan) === end) {
2174+
if (typeof kind !== "undefined" && highlight.kind !== kind) {
2175+
this.raiseError('verifyDocumentHighlightsAtPositionListContains failed - item "kind" value does not match, actual: ' + highlight.kind + ', expected: ' + kind + '.');
2176+
}
2177+
return;
2178+
}
2179+
}
2180+
}
2181+
}
2182+
2183+
let missingItem = { fileName: fileName, start: start, end: end, kind: kind };
2184+
this.raiseError('verifyOccurancesAtPositionListContains failed - could not find the item: ' + JSON.stringify(missingItem) + ' in the returned list: (' + JSON.stringify(documentHighlights) + ')');
2185+
}
2186+
2187+
public verifyDocumentHighlightsAtPositionListCount(expectedCount: number, fileNamesToSearch: string[]) {
2188+
this.taoInvalidReason = 'verifyDocumentHighlightsAtPositionListCount NYI';
2189+
2190+
let documentHighlights = this.getDocumentHighlightsAtCurrentPosition(fileNamesToSearch);
2191+
let actualCount = documentHighlights
2192+
? documentHighlights.reduce((currentCount, { highlightSpans }) => currentCount + highlightSpans.length, 0)
2193+
: 0;
2194+
2195+
if (expectedCount !== actualCount) {
2196+
this.raiseError('verifyDocumentHighlightsAtPositionListCount failed - actual: ' + actualCount + ', expected:' + expectedCount);
2197+
}
2198+
}
2199+
21552200
// Get the text of the entire line the caret is currently at
21562201
private getCurrentLineContent() {
21572202
let text = this.getFileContent(this.activeFile.fileName);

src/server/client.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -527,8 +527,33 @@ namespace ts.server {
527527
});
528528
}
529529

530-
getDocumentHighlights(fileName: string, position: number): DocumentHighlights[] {
531-
throw new Error("Not Implemented Yet.");
530+
getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] {
531+
let { line, offset } = this.positionToOneBasedLineOffset(fileName, position);
532+
let args: protocol.DocumentHighlightsRequestArgs = { file: fileName, line, offset, filesToSearch };
533+
534+
let request = this.processRequest<protocol.DocumentHighlightsRequest>(CommandNames.DocumentHighlights, args);
535+
let response = this.processResponse<protocol.DocumentHighlightsResponse>(request);
536+
537+
let self = this;
538+
return response.body.map(convertToDocumentHighlights);
539+
540+
function convertToDocumentHighlights(item: ts.server.protocol.DocumentHighlightsItem): ts.DocumentHighlights {
541+
let { file, highlightSpans } = item;
542+
543+
return {
544+
fileName: file,
545+
highlightSpans: highlightSpans.map(convertHighlightSpan)
546+
};
547+
548+
function convertHighlightSpan(span: ts.server.protocol.HighlightSpan): ts.HighlightSpan {
549+
let start = self.lineOffsetToPosition(file, span.start);
550+
let end = self.lineOffsetToPosition(file, span.end);
551+
return {
552+
textSpan: ts.createTextSpanFromBounds(start, end),
553+
kind: span.kind
554+
};
555+
}
556+
}
532557
}
533558

534559
getOutliningSpans(fileName: string): OutliningSpan[] {

src/server/protocol.d.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,17 @@ declare namespace ts.server.protocol {
156156
arguments: FileLocationRequestArgs;
157157
}
158158

159+
/**
160+
* Arguments in document highlight request; include: filesToSearch, file,
161+
* line, offset.
162+
*/
163+
export interface DocumentHighlightsRequestArgs extends FileLocationRequestArgs {
164+
/**
165+
* List of files to search for document highlights.
166+
*/
167+
filesToSearch: string[];
168+
}
169+
159170
/**
160171
* Go to definition request; value of command field is
161172
* "definition". Return response giving the file locations that
@@ -238,6 +249,35 @@ declare namespace ts.server.protocol {
238249
body?: OccurrencesResponseItem[];
239250
}
240251

252+
/**
253+
* Get document highlights request; value of command field is
254+
* "documentHighlights". Return response giving spans that are relevant
255+
* in the file at a given line and column.
256+
*/
257+
export interface DocumentHighlightsRequest extends FileLocationRequest {
258+
arguments: DocumentHighlightsRequestArgs
259+
}
260+
261+
export interface HighlightSpan extends TextSpan {
262+
kind: string
263+
}
264+
265+
export interface DocumentHighlightsItem {
266+
/**
267+
* File containing highlight spans.
268+
*/
269+
file: string,
270+
271+
/**
272+
* Spans to highlight in file.
273+
*/
274+
highlightSpans: HighlightSpan[];
275+
}
276+
277+
export interface DocumentHighlightsResponse extends Response {
278+
body?: DocumentHighlightsItem[];
279+
}
280+
241281
/**
242282
* Find references request; value of command field is
243283
* "references". Return response giving the file locations that

src/server/session.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ namespace ts.server {
8989
export const NavBar = "navbar";
9090
export const Navto = "navto";
9191
export const Occurrences = "occurrences";
92+
export const DocumentHighlights = "documentHighlights";
9293
export const Open = "open";
9394
export const Quickinfo = "quickinfo";
9495
export const References = "references";
@@ -313,7 +314,7 @@ namespace ts.server {
313314
}));
314315
}
315316

316-
private getOccurrences(line: number, offset: number, fileName: string): protocol.OccurrencesResponseItem[]{
317+
private getOccurrences(line: number, offset: number, fileName: string): protocol.OccurrencesResponseItem[] {
317318
fileName = ts.normalizePath(fileName);
318319
let project = this.projectService.getProjectForFile(fileName);
319320

@@ -343,6 +344,42 @@ namespace ts.server {
343344
});
344345
}
345346

347+
private getDocumentHighlights(line: number, offset: number, fileName: string, filesToSearch: string[]): protocol.DocumentHighlightsItem[] {
348+
fileName = ts.normalizePath(fileName);
349+
let project = this.projectService.getProjectForFile(fileName);
350+
351+
if (!project) {
352+
throw Errors.NoProject;
353+
}
354+
355+
let { compilerService } = project;
356+
let position = compilerService.host.lineOffsetToPosition(fileName, line, offset);
357+
358+
let documentHighlights = compilerService.languageService.getDocumentHighlights(fileName, position, filesToSearch);
359+
360+
if (!documentHighlights) {
361+
return undefined;
362+
}
363+
364+
return documentHighlights.map(convertToDocumentHighlightsItem);
365+
366+
function convertToDocumentHighlightsItem(documentHighlights: ts.DocumentHighlights): ts.server.protocol.DocumentHighlightsItem {
367+
let { fileName, highlightSpans } = documentHighlights;
368+
369+
return {
370+
file: fileName,
371+
highlightSpans: highlightSpans.map(convertHighlightSpan)
372+
};
373+
374+
function convertHighlightSpan(highlightSpan: ts.HighlightSpan): ts.server.protocol.HighlightSpan {
375+
let { textSpan, kind } = highlightSpan;
376+
let start = compilerService.host.positionToLineOffset(fileName, textSpan.start);
377+
let end = compilerService.host.positionToLineOffset(fileName, ts.textSpanEnd(textSpan));
378+
return { start, end, kind };
379+
}
380+
}
381+
}
382+
346383
private getProjectInfo(fileName: string, needFileNameList: boolean): protocol.ProjectInfo {
347384
fileName = ts.normalizePath(fileName)
348385
let project = this.projectService.getProjectForFile(fileName)
@@ -937,6 +974,10 @@ namespace ts.server {
937974
var { line, offset, file: fileName } = <protocol.FileLocationRequestArgs>request.arguments;
938975
return {response: this.getOccurrences(line, offset, fileName), responseRequired: true};
939976
},
977+
[CommandNames.DocumentHighlights]: (request: protocol.Request) => {
978+
var { line, offset, file: fileName, filesToSearch } = <protocol.DocumentHighlightsRequestArgs>request.arguments;
979+
return {response: this.getDocumentHighlights(line, offset, fileName, filesToSearch), responseRequired: true};
980+
},
940981
[CommandNames.ProjectInfo]: (request: protocol.Request) => {
941982
var { file, needFileNameList } = <protocol.ProjectInfoRequestArgs>request.arguments;
942983
return {response: this.getProjectInfo(file, needFileNameList), responseRequired: true};

tests/cases/fourslash/fourslash.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,14 @@ module FourSlashInterface {
421421
FourSlash.currentTestState.verifyOccurrencesAtPositionListCount(expectedCount);
422422
}
423423

424+
public documentHighlightsAtPositionContains(range: Range, fileNamesToSearch: string[], kind?: string) {
425+
FourSlash.currentTestState.verifyDocumentHighlightsAtPositionListContains(range.fileName, range.start, range.end, fileNamesToSearch, kind);
426+
}
427+
428+
public documentHighlightsAtPositionCount(expectedCount: number, fileNamesToSearch: string[]) {
429+
FourSlash.currentTestState.verifyDocumentHighlightsAtPositionListCount(expectedCount, fileNamesToSearch);
430+
}
431+
424432
public completionEntryDetailIs(entryName: string, text: string, documentation?: string, kind?: string) {
425433
FourSlash.currentTestState.verifyCompletionEntryDetails(entryName, text, documentation, kind);
426434
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path="../fourslash.ts"/>
2+
3+
// @Filename: a.ts
4+
////function [|f|](x: typeof [|f|]) {
5+
//// [|f|]([|f|]);
6+
////}
7+
8+
let ranges = test.ranges();
9+
10+
for (let r of ranges) {
11+
goTo.position(r.start);
12+
verify.documentHighlightsAtPositionCount(ranges.length, ["a.ts"]);
13+
14+
for (let range of ranges) {
15+
verify.documentHighlightsAtPositionContains(range, ["a.ts"]);
16+
}
17+
}
18+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/// <reference path="../fourslash.ts"/>
2+
3+
// @Filename: a.ts
4+
////function [|foo|] () {
5+
//// return 1;
6+
////}
7+
////[|foo|]();
8+
9+
// @Filename: b.ts
10+
/////// <reference path="a.ts"/>
11+
////foo();
12+
13+
// open two files
14+
goTo.file("a.ts");
15+
goTo.file("b.ts");
16+
17+
let ranges = test.ranges();
18+
19+
for (let i = 0; i < ranges.length; ++i) {
20+
let r = ranges[i];
21+
22+
if (i < 2) {
23+
goTo.file("a.ts");
24+
}
25+
else {
26+
goTo.file("b.ts");
27+
}
28+
29+
goTo.position(r.start);
30+
verify.documentHighlightsAtPositionCount(3, ["a.ts", "b.ts"]);
31+
32+
for (let range of ranges) {
33+
verify.documentHighlightsAtPositionContains(range, ["a.ts", "b.ts"]);
34+
}
35+
}

0 commit comments

Comments
 (0)