Merge pull request #4139 from hoanhtien/documentHighlights

Expose document highlighting to server.
This commit is contained in:
tien 2015-08-06 14:45:52 -07:00
commit 6980d56bfd
7 changed files with 226 additions and 14 deletions

View File

@ -2122,36 +2122,81 @@ module FourSlash {
public verifyOccurrencesAtPositionListContains(fileName: string, start: number, end: number, isWriteAccess?: boolean) {
this.taoInvalidReason = "verifyOccurrencesAtPositionListContains NYI";
let occurances = this.getOccurancesAtCurrentPosition();
let occurrences = this.getOccurancesAtCurrentPosition();
if (!occurances || occurances.length === 0) {
this.raiseError("verifyOccurancesAtPositionListContains failed - found 0 references, expected at least one.");
if (!occurrences || occurrences.length === 0) {
this.raiseError('verifyOccurancesAtPositionListContains failed - found 0 references, expected at least one.');
}
for (let i = 0; i < occurances.length; i++) {
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}.`);
for (let occurrence of occurrences) {
if (occurrence && occurrence.fileName === fileName && occurrence.textSpan.start === start && ts.textSpanEnd(occurrence.textSpan) === end) {
if (typeof isWriteAccess !== "undefined" && occurrence.isWriteAccess !== isWriteAccess) {
this.raiseError(`verifyOccurrencesAtPositionListContains failed - item isWriteAccess value does not match, actual: ${occurrence.isWriteAccess}, expected: ${isWriteAccess}.`);
}
return;
}
}
let missingItem = { fileName: fileName, start: start, end: end, isWriteAccess: isWriteAccess };
this.raiseError(`verifyOccurancesAtPositionListContains failed - could not find the item: ${JSON.stringify(missingItem)} in the returned list: (${JSON.stringify(occurances)})`);
this.raiseError(`verifyOccurrencesAtPositionListContains failed - could not find the item: ${JSON.stringify(missingItem)} in the returned list: (${JSON.stringify(occurrences)})`);
}
public verifyOccurrencesAtPositionListCount(expectedCount: number) {
this.taoInvalidReason = "verifyOccurrencesAtPositionListCount NYI";
let occurances = this.getOccurancesAtCurrentPosition();
let actualCount = occurances ? occurances.length : 0;
let occurrences = this.getOccurancesAtCurrentPosition();
let actualCount = occurrences ? occurrences.length : 0;
if (expectedCount !== actualCount) {
this.raiseError(`verifyOccurrencesAtPositionListCount failed - actual: ${actualCount}, expected:${expectedCount}`);
}
}
private getDocumentHighlightsAtCurrentPosition(fileNamesToSearch: string[]) {
let filesToSearch = fileNamesToSearch.map(name => ts.combinePaths(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 documentHighlight of documentHighlights) {
if (documentHighlight.fileName === fileName) {
let { highlightSpans } = documentHighlight;
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, { highlightSpans }) => currentCount + 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);

View File

@ -527,8 +527,33 @@ namespace ts.server {
});
}
getDocumentHighlights(fileName: string, position: number): DocumentHighlights[] {
throw new Error("Not Implemented Yet.");
getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] {
let { line, offset } = this.positionToOneBasedLineOffset(fileName, position);
let args: protocol.DocumentHighlightsRequestArgs = { file: fileName, line, offset, filesToSearch };
let request = this.processRequest<protocol.DocumentHighlightsRequest>(CommandNames.DocumentHighlights, args);
let response = this.processResponse<protocol.DocumentHighlightsResponse>(request);
let self = this;
return response.body.map(convertToDocumentHighlights);
function convertToDocumentHighlights(item: ts.server.protocol.DocumentHighlightsItem): ts.DocumentHighlights {
let { file, highlightSpans } = item;
return {
fileName: file,
highlightSpans: highlightSpans.map(convertHighlightSpan)
};
function convertHighlightSpan(span: ts.server.protocol.HighlightSpan): ts.HighlightSpan {
let start = self.lineOffsetToPosition(file, span.start);
let end = self.lineOffsetToPosition(file, span.end);
return {
textSpan: ts.createTextSpanFromBounds(start, end),
kind: span.kind
};
}
}
}
getOutliningSpans(fileName: string): OutliningSpan[] {

View File

@ -156,6 +156,17 @@ declare namespace ts.server.protocol {
arguments: FileLocationRequestArgs;
}
/**
* Arguments in document highlight request; include: filesToSearch, file,
* line, offset.
*/
export interface DocumentHighlightsRequestArgs extends FileLocationRequestArgs {
/**
* List of files to search for document highlights.
*/
filesToSearch: string[];
}
/**
* Go to definition request; value of command field is
* "definition". Return response giving the file locations that
@ -238,6 +249,35 @@ 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 {
arguments: DocumentHighlightsRequestArgs
}
export interface HighlightSpan extends TextSpan {
kind: string
}
export interface DocumentHighlightsItem {
/**
* File containing highlight spans.
*/
file: string,
/**
* Spans to highlight in file.
*/
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

View File

@ -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, filesToSearch: 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 documentHighlights = compilerService.languageService.getDocumentHighlights(fileName, position, filesToSearch);
if (!documentHighlights) {
return undefined;
}
return documentHighlights.map(convertToDocumentHighlightsItem);
function convertToDocumentHighlightsItem(documentHighlights: ts.DocumentHighlights): ts.server.protocol.DocumentHighlightsItem {
let { fileName, highlightSpans } = documentHighlights;
return {
file: fileName,
highlightSpans: highlightSpans.map(convertHighlightSpan)
};
function convertHighlightSpan(highlightSpan: ts.HighlightSpan): ts.server.protocol.HighlightSpan {
let { textSpan, kind } = highlightSpan;
let start = compilerService.host.positionToLineOffset(fileName, textSpan.start);
let end = compilerService.host.positionToLineOffset(fileName, ts.textSpanEnd(textSpan));
return { start, end, 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 } = <protocol.FileLocationRequestArgs>request.arguments;
return {response: this.getOccurrences(line, offset, fileName), responseRequired: true};
},
[CommandNames.DocumentHighlights]: (request: protocol.Request) => {
var { line, offset, file: fileName, filesToSearch } = <protocol.DocumentHighlightsRequestArgs>request.arguments;
return {response: this.getDocumentHighlights(line, offset, fileName, filesToSearch), responseRequired: true};
},
[CommandNames.ProjectInfo]: (request: protocol.Request) => {
var { file, needFileNameList } = <protocol.ProjectInfoRequestArgs>request.arguments;
return {response: this.getProjectInfo(file, needFileNameList), responseRequired: true};

View File

@ -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[]) {
FourSlash.currentTestState.verifyDocumentHighlightsAtPositionListCount(expectedCount, fileNamesToSearch);
}
public completionEntryDetailIs(entryName: string, text: string, documentation?: string, kind?: string) {
FourSlash.currentTestState.verifyCompletionEntryDetails(entryName, text, documentation, kind);
}

View File

@ -0,0 +1,18 @@
/// <reference path="../fourslash.ts"/>
// @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"]);
}
}

View File

@ -0,0 +1,35 @@
/// <reference path="../fourslash.ts"/>
// @Filename: a.ts
////function [|foo|] () {
//// return 1;
////}
////[|foo|]();
// @Filename: b.ts
/////// <reference path="a.ts"/>
////foo();
// open two files
goTo.file("a.ts");
goTo.file("b.ts");
let ranges = test.ranges();
for (let i = 0; i < ranges.length; ++i) {
let r = ranges[i];
if (i < 2) {
goTo.file("a.ts");
}
else {
goTo.file("b.ts");
}
goTo.position(r.start);
verify.documentHighlightsAtPositionCount(3, ["a.ts", "b.ts"]);
for (let range of ranges) {
verify.documentHighlightsAtPositionContains(range, ["a.ts", "b.ts"]);
}
}