diff --git a/src/server/client.ts b/src/server/client.ts index aba77c28b4..9d71f62700 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -104,7 +104,7 @@ module ts.server { var response: T = JSON.parse(responseBody); } catch (e) { - throw new Error("Malformed response: Failed to parse server response: " + lastMessage + ". \r\n Error detailes: " + e.message); + throw new Error("Malformed response: Failed to parse server response: " + lastMessage + ". \r\n Error details: " + e.message); } // verify the sequence numbers @@ -446,6 +446,7 @@ module ts.server { if (!response.body) { return undefined; } + var helpItems: protocol.SignatureHelpItems = response.body; var span = helpItems.applicableSpan; var start = this.lineOffsetToPosition(fileName, span.start); @@ -465,7 +466,26 @@ module ts.server { } getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] { - 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.Occurrences, args); + var response = this.processResponse(request); + + return response.body.map(entry => { + var fileName = entry.file; + var start = this.lineOffsetToPosition(fileName, entry.start); + var end = this.lineOffsetToPosition(fileName, entry.end); + return { + fileName, + textSpan: ts.createTextSpanFromBounds(start, end), + isWriteAccess: entry.isWriteAccess, + }; + }); } getDocumentHighlights(fileName: string, position: number): DocumentHighlights[] { diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 1d685be5d3..5bc1ffbe4d 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -165,6 +165,25 @@ declare module ts.server.protocol { body?: FileSpan[]; } + /** + * Get occurrences request; value of command field is + * "occurrences". Return response giving spans that are relevant + * in the file at a given line and column. + */ + export interface OccurrencesRequest extends FileLocationRequest { + } + + export interface OccurrencesResponseItem extends FileSpan { + /** + * True if the occurrence is a write location, false otherwise. + */ + isWriteAccess: boolean; + } + + export interface OccurrencesResponse extends Response { + body?: OccurrencesResponseItem[]; + } + /** * 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 560f5869c0..09373d6bf9 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -89,6 +89,7 @@ module ts.server { export var Geterr = "geterr"; export var NavBar = "navbar"; export var Navto = "navto"; + export var Occurrences = "occurrences"; export var Open = "open"; export var Quickinfo = "quickinfo"; export var References = "references"; @@ -117,7 +118,7 @@ module ts.server { constructor(private host: ServerHost, private logger: Logger) { this.projectService = - new ProjectService(host, logger, (eventName,project,fileName) => { + new ProjectService(host, logger, (eventName, project, fileName) => { this.handleEvent(eventName, project, fileName); }); } @@ -262,7 +263,7 @@ module ts.server { } } - getDefinition(line: number, offset: number, fileName: string): protocol.FileSpan[] { + getDefinition({ line, offset, file: fileName }: protocol.FileLocationRequestArgs): protocol.FileSpan[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -284,7 +285,37 @@ module ts.server { })); } - getRenameLocations(line: number, offset: number, fileName: string,findInComments: boolean, findInStrings: boolean): protocol.RenameResponseBody { + getOccurrences({ line, offset, file: fileName }: protocol.FileLocationRequestArgs): protocol.OccurrencesResponseItem[] { + 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 occurrences = compilerService.languageService.getOccurrencesAtPosition(fileName, position); + + if (!occurrences) { + return undefined; + } + + return occurrences.map(occurrence => { + let { fileName, isWriteAccess, textSpan } = occurrence; + let start = compilerService.host.positionToLineOffset(fileName, textSpan.start); + let end = compilerService.host.positionToLineOffset(fileName, ts.textSpanEnd(textSpan)); + return { + start, + end, + file: fileName, + isWriteAccess + } + }); + } + + getRenameLocations({line, offset, file: fileName, findInComments, findInStrings }: protocol.RenameRequestArgs): protocol.RenameResponseBody { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -352,7 +383,7 @@ module ts.server { return { info: renameInfo, locs: bakedRenameLocs }; } - getReferences(line: number, offset: number, fileName: string): protocol.ReferencesResponseBody { + getReferences({ line, offset, file: fileName }: protocol.FileLocationRequestArgs): protocol.ReferencesResponseBody { // TODO: get all projects for this file; report refs for all projects deleting duplicates // can avoid duplicates by eliminating same ref file from subsequent projects var file = ts.normalizePath(fileName); @@ -378,7 +409,7 @@ module ts.server { var nameSpan = nameInfo.textSpan; var nameColStart = compilerService.host.positionToLineOffset(file, nameSpan.start).offset; var nameText = compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan)); - var bakedRefs: protocol.ReferencesResponseItem[] = references.map((ref) => { + var bakedRefs: protocol.ReferencesResponseItem[] = references.map(ref => { var start = compilerService.host.positionToLineOffset(ref.fileName, ref.textSpan.start); var refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1); var snap = compilerService.host.getScriptSnapshot(ref.fileName); @@ -399,12 +430,12 @@ module ts.server { }; } - openClientFile(fileName: string) { + openClientFile({ file: fileName }: protocol.OpenRequestArgs) { var file = ts.normalizePath(fileName); this.projectService.openClientFile(file); } - getQuickInfo(line: number, offset: number, fileName: string): protocol.QuickInfoResponseBody { + getQuickInfo({ line, offset, file: fileName }: protocol.FileLocationRequestArgs): protocol.QuickInfoResponseBody { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -430,7 +461,7 @@ module ts.server { }; } - getFormattingEditsForRange(line: number, offset: number, endLine: number, endOffset: number, fileName: string): protocol.CodeEdit[] { + getFormattingEditsForRange({line, offset, endLine, endOffset, file: fileName}: protocol.FormatRequestArgs): protocol.CodeEdit[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -457,7 +488,7 @@ module ts.server { }); } - getFormattingEditsAfterKeystroke(line: number, offset: number, key: string, fileName: string): protocol.CodeEdit[] { + getFormattingEditsAfterKeystroke({line, offset, key, file: fileName}: protocol.FormatOnKeyRequestArgs): protocol.CodeEdit[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); @@ -530,7 +561,7 @@ module ts.server { }); } - getCompletions(line: number, offset: number, prefix: string, fileName: string): protocol.CompletionEntry[] { + getCompletions({ line, offset, prefix, file: fileName}: protocol.CompletionsRequestArgs): protocol.CompletionEntry[] { if (!prefix) { prefix = ""; } @@ -556,8 +587,7 @@ module ts.server { }, []).sort((a, b) => a.name.localeCompare(b.name)); } - getCompletionEntryDetails(line: number, offset: number, - entryNames: string[], fileName: string): protocol.CompletionEntryDetails[] { + getCompletionEntryDetails({ line, offset, entryNames, file: fileName}: protocol.CompletionDetailsRequestArgs): protocol.CompletionEntryDetails[] { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -576,20 +606,20 @@ module ts.server { }, []); } - getSignatureHelpItems(line: number, offset: number, fileName: string): protocol.SignatureHelpItems { + getSignatureHelpItems({ line, offset, file: fileName }: protocol.SignatureHelpRequestArgs): protocol.SignatureHelpItems { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { throw Errors.NoProject; } - + var compilerService = project.compilerService; var position = compilerService.host.lineOffsetToPosition(file, line, offset); var helpItems = compilerService.languageService.getSignatureHelpItems(file, position); if (!helpItems) { return undefined; } - + var span = helpItems.applicableSpan; var result: protocol.SignatureHelpItems = { items: helpItems.items, @@ -601,11 +631,11 @@ module ts.server { argumentIndex: helpItems.argumentIndex, argumentCount: helpItems.argumentCount, } - + return result; } - - getDiagnostics(delay: number, fileNames: string[]) { + + getDiagnostics({ delay, files: fileNames }: protocol.GeterrRequestArgs): void { var checkList = fileNames.reduce((accum: PendingErrorCheck[], fileName: string) => { fileName = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(fileName); @@ -616,11 +646,11 @@ module ts.server { }, []); if (checkList.length > 0) { - this.updateErrorCheck(checkList, this.changeSeq,(n) => n == this.changeSeq, delay) + this.updateErrorCheck(checkList, this.changeSeq, (n) => n == this.changeSeq, delay) } } - change(line: number, offset: number, endLine: number, endOffset: number, insertString: string, fileName: string) { + change({ line, offset, endLine, endOffset, insertString, file: fileName }: protocol.ChangeRequestArgs): void { var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (project) { @@ -635,7 +665,7 @@ module ts.server { } } - reload(fileName: string, tempFileName: string, reqSeq = 0) { + reload({ file: fileName, tmpfile: tempFileName }: protocol.ReloadRequestArgs, reqSeq = 0): void { var file = ts.normalizePath(fileName); var tmpfile = ts.normalizePath(tempFileName); var project = this.projectService.getProjectForFile(file); @@ -648,7 +678,7 @@ module ts.server { } } - saveToTmp(fileName: string, tempFileName: string) { + saveToTmp({ file: fileName, tmpfile: tempFileName }: protocol.SavetoRequestArgs): void { var file = ts.normalizePath(fileName); var tmpfile = ts.normalizePath(tempFileName); @@ -658,7 +688,7 @@ module ts.server { } } - closeClientFile(fileName: string) { + closeClientFile({ file: fileName }: protocol.FileRequestArgs) { var file = ts.normalizePath(fileName); this.projectService.closeClientFile(file); } @@ -682,7 +712,7 @@ module ts.server { })); } - getNavigationBarItems(fileName: string): protocol.NavigationBarItem[] { + getNavigationBarItems({ file: fileName }: protocol.FileRequestArgs): protocol.NavigationBarItem[]{ var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -698,7 +728,7 @@ module ts.server { return this.decorateNavigationBarItem(project, fileName, items); } - getNavigateToItems(searchValue: string, fileName: string, maxResultCount?: number): protocol.NavtoItem[] { + getNavigateToItems({ searchValue, file: fileName, maxResultCount }: protocol.NavtoRequestArgs): protocol.NavtoItem[]{ var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); if (!project) { @@ -737,7 +767,7 @@ module ts.server { }); } - getBraceMatching(line: number, offset: number, fileName: string): protocol.TextSpan[] { + getBraceMatching({ line, offset, file: fileName }: protocol.FileLocationRequestArgs): protocol.TextSpan[]{ var file = ts.normalizePath(fileName); var project = this.projectService.getProjectForFile(file); @@ -779,109 +809,91 @@ module ts.server { break; } case CommandNames.Definition: { - var defArgs = request.arguments; - response = this.getDefinition(defArgs.line, defArgs.offset, defArgs.file); + response = this.getDefinition(request.arguments); break; } case CommandNames.References: { - var refArgs = request.arguments; - response = this.getReferences(refArgs.line, refArgs.offset, refArgs.file); + response = this.getReferences(request.arguments); break; } case CommandNames.Rename: { - var renameArgs = request.arguments; - response = this.getRenameLocations(renameArgs.line, renameArgs.offset, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings); + response = this.getRenameLocations(request.arguments); break; } case CommandNames.Open: { - var openArgs = request.arguments; - this.openClientFile(openArgs.file); + this.openClientFile(request.arguments); responseRequired = false; break; } case CommandNames.Quickinfo: { - var quickinfoArgs = request.arguments; - response = this.getQuickInfo(quickinfoArgs.line, quickinfoArgs.offset, quickinfoArgs.file); + response = this.getQuickInfo(request.arguments); break; } case CommandNames.Format: { - var formatArgs = request.arguments; - response = this.getFormattingEditsForRange(formatArgs.line, formatArgs.offset, formatArgs.endLine, formatArgs.endOffset, formatArgs.file); + response = this.getFormattingEditsForRange(request.arguments); break; } case CommandNames.Formatonkey: { - var formatOnKeyArgs = request.arguments; - response = this.getFormattingEditsAfterKeystroke(formatOnKeyArgs.line, formatOnKeyArgs.offset, formatOnKeyArgs.key, formatOnKeyArgs.file); + response = this.getFormattingEditsAfterKeystroke(request.arguments); break; } case CommandNames.Completions: { - var completionsArgs = request.arguments; - response = this.getCompletions(completionsArgs.line, completionsArgs.offset, completionsArgs.prefix, completionsArgs.file); + response = this.getCompletions(request.arguments); break; } case CommandNames.CompletionDetails: { - var completionDetailsArgs = request.arguments; - response = - this.getCompletionEntryDetails(completionDetailsArgs.line,completionDetailsArgs.offset, - completionDetailsArgs.entryNames,completionDetailsArgs.file); + response = this.getCompletionEntryDetails(request.arguments); break; } case CommandNames.SignatureHelp: { - var signatureHelpArgs = request.arguments; - response = this.getSignatureHelpItems(signatureHelpArgs.line, signatureHelpArgs.offset, signatureHelpArgs.file); + response = this.getSignatureHelpItems(request.arguments); break; } case CommandNames.Geterr: { - var geterrArgs = request.arguments; - response = this.getDiagnostics(geterrArgs.delay, geterrArgs.files); + this.getDiagnostics(request.arguments); responseRequired = false; break; } case CommandNames.Change: { - var changeArgs = request.arguments; - this.change(changeArgs.line, changeArgs.offset, changeArgs.endLine, changeArgs.endOffset, - changeArgs.insertString, changeArgs.file); + this.change(request.arguments); responseRequired = false; break; } case CommandNames.Configure: { - var configureArgs = request.arguments; - this.projectService.setHostConfiguration(configureArgs); + this.projectService.setHostConfiguration(request.arguments); this.output(undefined, CommandNames.Configure, request.seq); responseRequired = false; break; } case CommandNames.Reload: { - var reloadArgs = request.arguments; - this.reload(reloadArgs.file, reloadArgs.tmpfile, request.seq); + this.reload(request.arguments); responseRequired = false; break; } case CommandNames.Saveto: { - var savetoArgs = request.arguments; - this.saveToTmp(savetoArgs.file, savetoArgs.tmpfile); + this.saveToTmp(request.arguments); responseRequired = false; break; } case CommandNames.Close: { - var closeArgs = request.arguments; - this.closeClientFile(closeArgs.file); + this.closeClientFile(request.arguments); responseRequired = false; break; } case CommandNames.Navto: { - var navtoArgs = request.arguments; - response = this.getNavigateToItems(navtoArgs.searchValue, navtoArgs.file, navtoArgs.maxResultCount); + response = this.getNavigateToItems(request.arguments); break; } case CommandNames.Brace: { - var braceArguments = request.arguments; - response = this.getBraceMatching(braceArguments.line, braceArguments.offset, braceArguments.file); + response = this.getBraceMatching(request.arguments); break; } case CommandNames.NavBar: { - var navBarArgs = request.arguments; - response = this.getNavigationBarItems(navBarArgs.file); + response = this.getNavigationBarItems(request.arguments); + break; + } + case CommandNames.Occurrences: { + response = this.getOccurrences(request.arguments); break; } default: { diff --git a/tests/cases/fourslash/server/brace.ts b/tests/cases/fourslash/server/brace01.ts similarity index 91% rename from tests/cases/fourslash/server/brace.ts rename to tests/cases/fourslash/server/brace01.ts index fc8a71197d..916a2e0b33 100644 --- a/tests/cases/fourslash/server/brace.ts +++ b/tests/cases/fourslash/server/brace01.ts @@ -1,4 +1,4 @@ -/// +/// //////curly braces ////module Foo [|{ diff --git a/tests/cases/fourslash/server/completions.ts b/tests/cases/fourslash/server/completions01.ts similarity index 87% rename from tests/cases/fourslash/server/completions.ts rename to tests/cases/fourslash/server/completions01.ts index 2af0393283..a837a74849 100644 --- a/tests/cases/fourslash/server/completions.ts +++ b/tests/cases/fourslash/server/completions01.ts @@ -1,4 +1,4 @@ -/// +/// ////var x: string[] = []; ////x.forEach(function (y) { y/*1*/ diff --git a/tests/cases/fourslash/server/completions2.ts b/tests/cases/fourslash/server/completions02.ts similarity index 84% rename from tests/cases/fourslash/server/completions2.ts rename to tests/cases/fourslash/server/completions02.ts index 3e7e77b588..b6c39a5c72 100644 --- a/tests/cases/fourslash/server/completions2.ts +++ b/tests/cases/fourslash/server/completions02.ts @@ -1,4 +1,4 @@ -/// +/// ////class Foo { ////} diff --git a/tests/cases/fourslash/server/definition.ts b/tests/cases/fourslash/server/definition01.ts similarity index 79% rename from tests/cases/fourslash/server/definition.ts rename to tests/cases/fourslash/server/definition01.ts index f403965fe1..8bf6e73c84 100644 --- a/tests/cases/fourslash/server/definition.ts +++ b/tests/cases/fourslash/server/definition01.ts @@ -1,4 +1,4 @@ -/// +/// // @Filename: b.ts ////import n = require('a/*1*/'); diff --git a/tests/cases/fourslash/server/format.ts b/tests/cases/fourslash/server/format01.ts similarity index 75% rename from tests/cases/fourslash/server/format.ts rename to tests/cases/fourslash/server/format01.ts index 75d06eef0c..f454115220 100644 --- a/tests/cases/fourslash/server/format.ts +++ b/tests/cases/fourslash/server/format01.ts @@ -1,4 +1,4 @@ -/// +/// /////**/module Default{var x= ( { } ) ;} diff --git a/tests/cases/fourslash/server/formatonkey.ts b/tests/cases/fourslash/server/formatonkey01.ts similarity index 76% rename from tests/cases/fourslash/server/formatonkey.ts rename to tests/cases/fourslash/server/formatonkey01.ts index 64cd74ca85..73e817ce3b 100644 --- a/tests/cases/fourslash/server/formatonkey.ts +++ b/tests/cases/fourslash/server/formatonkey01.ts @@ -1,4 +1,4 @@ -/// +/// ////switch (1) { //// case 1: diff --git a/tests/cases/fourslash/server/navbar.ts b/tests/cases/fourslash/server/navbar01.ts similarity index 96% rename from tests/cases/fourslash/server/navbar.ts rename to tests/cases/fourslash/server/navbar01.ts index 67e14fe100..82ded5e6f5 100644 --- a/tests/cases/fourslash/server/navbar.ts +++ b/tests/cases/fourslash/server/navbar01.ts @@ -1,4 +1,4 @@ -/// +/// ////// Interface ////{| "itemName": "IPoint", "kind": "interface", "parentName": "" |}interface IPoint { diff --git a/tests/cases/fourslash/server/navto.ts b/tests/cases/fourslash/server/navto01.ts similarity index 94% rename from tests/cases/fourslash/server/navto.ts rename to tests/cases/fourslash/server/navto01.ts index ba907e985f..1072c64494 100644 --- a/tests/cases/fourslash/server/navto.ts +++ b/tests/cases/fourslash/server/navto01.ts @@ -1,4 +1,4 @@ -/// +/// /////// Module ////{| "itemName": "MyShapes", "kind": "module", "parentName": "", "matchKind": "substring" |}module MyShapes { diff --git a/tests/cases/fourslash/server/occurrences01.ts b/tests/cases/fourslash/server/occurrences01.ts new file mode 100644 index 0000000000..db2fa64f38 --- /dev/null +++ b/tests/cases/fourslash/server/occurrences01.ts @@ -0,0 +1,22 @@ +/// + +////foo: [|switch|] (10) { +//// [|case|] 1: +//// [|case|] 2: +//// [|case|] 3: +//// [|break|]; +//// [|break|] foo; +//// continue; +//// continue foo; +////} + +let ranges = test.ranges(); + +for (let r of ranges) { + goTo.position(r.start); + verify.occurrencesAtPositionCount(ranges.length); + + for (let range of ranges) { + verify.occurrencesAtPositionContains(range, /*isWriteAccess*/ false); + } +} \ No newline at end of file diff --git a/tests/cases/fourslash/server/occurrences02.ts b/tests/cases/fourslash/server/occurrences02.ts new file mode 100644 index 0000000000..e0d39cb92f --- /dev/null +++ b/tests/cases/fourslash/server/occurrences02.ts @@ -0,0 +1,16 @@ +/// + +////function [|f|](x: typeof [|f|]) { +//// [|f|]([|f|]); +////} + +let ranges = test.ranges(); + +for (let r of ranges) { + goTo.position(r.start); + verify.occurrencesAtPositionCount(ranges.length); + + for (let range of ranges) { + verify.occurrencesAtPositionContains(range); + } +} \ No newline at end of file diff --git a/tests/cases/fourslash/server/quickinfo.ts b/tests/cases/fourslash/server/quickinfo01.ts similarity index 88% rename from tests/cases/fourslash/server/quickinfo.ts rename to tests/cases/fourslash/server/quickinfo01.ts index 5292eb6c2c..9008505e44 100644 --- a/tests/cases/fourslash/server/quickinfo.ts +++ b/tests/cases/fourslash/server/quickinfo01.ts @@ -1,4 +1,4 @@ -/// +/// ////interface One { //// commonProperty: number; diff --git a/tests/cases/fourslash/server/references.ts b/tests/cases/fourslash/server/references01.ts similarity index 85% rename from tests/cases/fourslash/server/references.ts rename to tests/cases/fourslash/server/references01.ts index 55b2161555..0ecbeb43de 100644 --- a/tests/cases/fourslash/server/references.ts +++ b/tests/cases/fourslash/server/references01.ts @@ -1,4 +1,4 @@ -/// +/// // Global class reference. diff --git a/tests/cases/fourslash/server/rename.ts b/tests/cases/fourslash/server/rename01.ts similarity index 84% rename from tests/cases/fourslash/server/rename.ts rename to tests/cases/fourslash/server/rename01.ts index 4f8b7b98cd..2da25f87ba 100644 --- a/tests/cases/fourslash/server/rename.ts +++ b/tests/cases/fourslash/server/rename01.ts @@ -1,4 +1,4 @@ -/// +/// /////// diff --git a/tests/cases/fourslash/server/signatureHelp.ts b/tests/cases/fourslash/server/signatureHelp01.ts similarity index 84% rename from tests/cases/fourslash/server/signatureHelp.ts rename to tests/cases/fourslash/server/signatureHelp01.ts index 294df367f0..73303ecce4 100644 --- a/tests/cases/fourslash/server/signatureHelp.ts +++ b/tests/cases/fourslash/server/signatureHelp01.ts @@ -1,4 +1,4 @@ -/// +/// ////function foo(data: number) { ////}