From 55f4b02ed4a75087ce22ad9a0c54cbe668c14ced Mon Sep 17 00:00:00 2001 From: zhengbli Date: Wed, 2 Mar 2016 14:25:55 -0800 Subject: [PATCH 1/7] Enable rename on all projects for a file --- src/server/session.ts | 99 +++++++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 42 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index f0975a3f947b2..db5511c0174fe 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -412,14 +412,15 @@ namespace ts.server { private getRenameLocations(line: number, offset: number, fileName: string, findInComments: boolean, findInStrings: boolean): protocol.RenameResponseBody { const file = ts.normalizePath(fileName); - const project = this.projectService.getProjectForFile(file); - if (!project) { + const defaultProject = this.projectService.getProjectForFile(file); + if (!defaultProject) { throw Errors.NoProject; } - const compilerService = project.compilerService; - const position = compilerService.host.lineOffsetToPosition(file, line, offset); - const renameInfo = compilerService.languageService.getRenameInfo(file, position); + // The rename info should be the same for every project + const defaultProjectCompilerService = defaultProject.compilerService; + const position = defaultProjectCompilerService.host.lineOffsetToPosition(file, line, offset); + const renameInfo = defaultProjectCompilerService.languageService.getRenameInfo(file, position); if (!renameInfo) { return undefined; } @@ -431,51 +432,65 @@ namespace ts.server { }; } - const renameLocations = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments); - if (!renameLocations) { - return undefined; - } - - const bakedRenameLocs = renameLocations.map(location => ({ - file: location.fileName, - start: compilerService.host.positionToLineOffset(location.fileName, location.textSpan.start), - end: compilerService.host.positionToLineOffset(location.fileName, ts.textSpanEnd(location.textSpan)), - })).sort((a, b) => { - if (a.file < b.file) { - return -1; - } - else if (a.file > b.file) { - return 1; + const locsMap: Map = {}; + const info = this.projectService.getScriptInfo(file); + const projects = this.projectService.findReferencingProjects(info); + for (const project of projects) { + const compilerService = project.compilerService; + const renameLocations = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments); + if (!renameLocations) { + continue; } - else { - // reverse sort assuming no overlap - if (a.start.line < b.start.line) { - return 1; - } - else if (a.start.line > b.start.line) { + + const bakedRenameLocs = renameLocations.map(location => ({ + file: location.fileName, + start: compilerService.host.positionToLineOffset(location.fileName, location.textSpan.start), + end: compilerService.host.positionToLineOffset(location.fileName, ts.textSpanEnd(location.textSpan)), + })).sort((a, b) => { + if (a.file < b.file) { return -1; } + else if (a.file > b.file) { + return 1; + } else { - return b.start.offset - a.start.offset; + // reverse sort assuming no overlap + if (a.start.line < b.start.line) { + return 1; + } + else if (a.start.line > b.start.line) { + return -1; + } + else { + return b.start.offset - a.start.offset; + } } - } - }).reduce((accum: protocol.SpanGroup[], cur: protocol.FileSpan) => { - let curFileAccum: protocol.SpanGroup; - if (accum.length > 0) { - curFileAccum = accum[accum.length - 1]; - if (curFileAccum.file != cur.file) { - curFileAccum = undefined; + }).reduce((accum: protocol.SpanGroup[], cur: protocol.FileSpan) => { + let curFileAccum: protocol.SpanGroup; + if (accum.length > 0) { + curFileAccum = accum[accum.length - 1]; + if (curFileAccum.file != cur.file) { + curFileAccum = undefined; + } } + if (!curFileAccum) { + curFileAccum = { file: cur.file, locs: [] }; + accum.push(curFileAccum); + } + curFileAccum.locs.push({ start: cur.start, end: cur.end }); + return accum; + }, []); + + for (const bakedRenameLoc of bakedRenameLocs) { + locsMap[bakedRenameLoc.file] = bakedRenameLoc; } - if (!curFileAccum) { - curFileAccum = { file: cur.file, locs: [] }; - accum.push(curFileAccum); - } - curFileAccum.locs.push({ start: cur.start, end: cur.end }); - return accum; - }, []); + } - return { info: renameInfo, locs: bakedRenameLocs }; + const locs: protocol.SpanGroup[] = []; + for (const key in locsMap) { + locs.push(locsMap[key]); + } + return { info: renameInfo, locs }; } private getReferences(line: number, offset: number, fileName: string): protocol.ReferencesResponseBody { From 7663a96a7b45afb90a0a86412b75f5b5466a81ba Mon Sep 17 00:00:00 2001 From: zhengbli Date: Wed, 2 Mar 2016 15:36:47 -0800 Subject: [PATCH 2/7] Enable findReferences on all projects for a file --- src/compiler/core.ts | 13 +++++-- src/server/session.ts | 91 +++++++++++++++++++++++++------------------ 2 files changed, 63 insertions(+), 41 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 702ded96a3fdb..df8e3d1bce45b 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -91,10 +91,15 @@ namespace ts { return undefined; } - export function contains(array: T[], value: T): boolean { + export function contains(array: T[], value: T, areEqual?: (a: T, b: T) => boolean): boolean { if (array) { for (const v of array) { - if (v === value) { + if (areEqual) { + if (areEqual(v, value)) { + return true; + } + } + else if (v === value) { return true; } } @@ -156,12 +161,12 @@ namespace ts { return array1.concat(array2); } - export function deduplicate(array: T[]): T[] { + export function deduplicate(array: T[], areEqual?: (a: T, b: T) => boolean): T[] { let result: T[]; if (array) { result = []; for (const item of array) { - if (!contains(result, item)) { + if (!contains(result, item, areEqual)) { result.push(item); } } diff --git a/src/server/session.ts b/src/server/session.ts index db5511c0174fe..bfb3e9154b1a9 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -432,7 +432,7 @@ namespace ts.server { }; } - const locsMap: Map = {}; + const locs: protocol.SpanGroup[] = []; const info = this.projectService.getScriptInfo(file); const projects = this.projectService.findReferencingProjects(info); for (const project of projects) { @@ -481,63 +481,80 @@ namespace ts.server { return accum; }, []); - for (const bakedRenameLoc of bakedRenameLocs) { - locsMap[bakedRenameLoc.file] = bakedRenameLoc; - } + ts.addRange(locs, bakedRenameLocs); } - const locs: protocol.SpanGroup[] = []; - for (const key in locsMap) { - locs.push(locsMap[key]); + return { info: renameInfo, locs: ts.deduplicate(locs, areSpanGroupsForTheSameFile) }; + + function areSpanGroupsForTheSameFile(a: protocol.SpanGroup, b: protocol.SpanGroup) { + if (a && b) { + return a.file === b.file; + } + return false; } - return { info: renameInfo, locs }; } private getReferences(line: number, offset: number, fileName: string): 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 const file = ts.normalizePath(fileName); - const project = this.projectService.getProjectForFile(file); - if (!project) { + const defaultProject = this.projectService.getProjectForFile(file); + if (!defaultProject) { throw Errors.NoProject; } - const compilerService = project.compilerService; - const position = compilerService.host.lineOffsetToPosition(file, line, offset); - - const references = compilerService.languageService.getReferencesAtPosition(file, position); - if (!references) { - return undefined; - } - - const nameInfo = compilerService.languageService.getQuickInfoAtPosition(file, position); + const position = defaultProject.compilerService.host.lineOffsetToPosition(file, line, offset); + const nameInfo = defaultProject.compilerService.languageService.getQuickInfoAtPosition(file, position); if (!nameInfo) { return undefined; } const displayString = ts.displayPartsToString(nameInfo.displayParts); const nameSpan = nameInfo.textSpan; - const nameColStart = compilerService.host.positionToLineOffset(file, nameSpan.start).offset; - const nameText = compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan)); - const bakedRefs: protocol.ReferencesResponseItem[] = references.map(ref => { - const start = compilerService.host.positionToLineOffset(ref.fileName, ref.textSpan.start); - const refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1); - const snap = compilerService.host.getScriptSnapshot(ref.fileName); - const lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, ""); - return { - file: ref.fileName, - start: start, - lineText: lineText, - end: compilerService.host.positionToLineOffset(ref.fileName, ts.textSpanEnd(ref.textSpan)), - isWriteAccess: ref.isWriteAccess - }; - }).sort(compareFileStart); + const nameColStart = defaultProject.compilerService.host.positionToLineOffset(file, nameSpan.start).offset; + const nameText = defaultProject.compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan)); + + const info = this.projectService.getScriptInfo(file); + const projects = this.projectService.findReferencingProjects(info); + const refs: protocol.ReferencesResponseItem[] = []; + for (const project of projects) + { + const compilerService = project.compilerService; + const references = compilerService.languageService.getReferencesAtPosition(file, position); + if (!references) { + continue; + } + + const bakedRefs: protocol.ReferencesResponseItem[] = references.map(ref => { + const start = compilerService.host.positionToLineOffset(ref.fileName, ref.textSpan.start); + const refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1); + const snap = compilerService.host.getScriptSnapshot(ref.fileName); + const lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, ""); + return { + file: ref.fileName, + start: start, + lineText: lineText, + end: compilerService.host.positionToLineOffset(ref.fileName, ts.textSpanEnd(ref.textSpan)), + isWriteAccess: ref.isWriteAccess + }; + }).sort(compareFileStart); + + ts.addRange(refs, bakedRefs); + } + return { - refs: bakedRefs, + refs: ts.deduplicate(refs, areReferencesResponseItemsForTheSameLocation), symbolName: nameText, symbolStartOffset: nameColStart, symbolDisplayString: displayString }; + + function areReferencesResponseItemsForTheSameLocation(a: protocol.ReferencesResponseItem, b: protocol.ReferencesResponseItem) { + if (a && b) { + return a.file === b.file && + a.start === b.start && + a.end === b.end; + } + return false; + } } /** From 2835e259aa05873256006ef1631f184b762ad048 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Wed, 2 Mar 2016 15:50:00 -0800 Subject: [PATCH 3/7] Enable navigateTo on all projects for a file --- src/server/session.ts | 82 ++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index bfb3e9154b1a9..199e6df21f457 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -481,10 +481,10 @@ namespace ts.server { return accum; }, []); - ts.addRange(locs, bakedRenameLocs); + addRange(locs, bakedRenameLocs); } - return { info: renameInfo, locs: ts.deduplicate(locs, areSpanGroupsForTheSameFile) }; + return { info: renameInfo, locs: deduplicate(locs, areSpanGroupsForTheSameFile) }; function areSpanGroupsForTheSameFile(a: protocol.SpanGroup, b: protocol.SpanGroup) { if (a && b) { @@ -537,11 +537,11 @@ namespace ts.server { }; }).sort(compareFileStart); - ts.addRange(refs, bakedRefs); + addRange(refs, bakedRefs); } return { - refs: ts.deduplicate(refs, areReferencesResponseItemsForTheSameLocation), + refs: deduplicate(refs, areReferencesResponseItemsForTheSameLocation), symbolName: nameText, symbolStartOffset: nameColStart, symbolDisplayString: displayString @@ -868,41 +868,57 @@ namespace ts.server { private getNavigateToItems(searchValue: string, fileName: string, maxResultCount?: number): protocol.NavtoItem[] { const file = ts.normalizePath(fileName); - const project = this.projectService.getProjectForFile(file); - if (!project) { + const defaultProject = this.projectService.getProjectForFile(file); + if (!defaultProject) { throw Errors.NoProject; } - const compilerService = project.compilerService; - const navItems = compilerService.languageService.getNavigateToItems(searchValue, maxResultCount); - if (!navItems) { - return undefined; + const info = this.projectService.getScriptInfo(file); + const projects = this.projectService.findReferencingProjects(info); + const allNavToItems: protocol.NavtoItem[] = []; + for (const project of projects) { + const compilerService = project.compilerService; + const navItems = compilerService.languageService.getNavigateToItems(searchValue, maxResultCount); + if (!navItems) { + continue; + } + + const bakedNavItems = navItems.map((navItem) => { + const start = compilerService.host.positionToLineOffset(navItem.fileName, navItem.textSpan.start); + const end = compilerService.host.positionToLineOffset(navItem.fileName, ts.textSpanEnd(navItem.textSpan)); + const bakedItem: protocol.NavtoItem = { + name: navItem.name, + kind: navItem.kind, + file: navItem.fileName, + start: start, + end: end, + }; + if (navItem.kindModifiers && (navItem.kindModifiers != "")) { + bakedItem.kindModifiers = navItem.kindModifiers; + } + if (navItem.matchKind !== "none") { + bakedItem.matchKind = navItem.matchKind; + } + if (navItem.containerName && (navItem.containerName.length > 0)) { + bakedItem.containerName = navItem.containerName; + } + if (navItem.containerKind && (navItem.containerKind.length > 0)) { + bakedItem.containerKind = navItem.containerKind; + } + return bakedItem; + }); + addRange(allNavToItems, bakedNavItems); } + return deduplicate(allNavToItems, areNavToItemsForTheSameLocation); - return navItems.map((navItem) => { - const start = compilerService.host.positionToLineOffset(navItem.fileName, navItem.textSpan.start); - const end = compilerService.host.positionToLineOffset(navItem.fileName, ts.textSpanEnd(navItem.textSpan)); - const bakedItem: protocol.NavtoItem = { - name: navItem.name, - kind: navItem.kind, - file: navItem.fileName, - start: start, - end: end, - }; - if (navItem.kindModifiers && (navItem.kindModifiers != "")) { - bakedItem.kindModifiers = navItem.kindModifiers; - } - if (navItem.matchKind !== "none") { - bakedItem.matchKind = navItem.matchKind; - } - if (navItem.containerName && (navItem.containerName.length > 0)) { - bakedItem.containerName = navItem.containerName; - } - if (navItem.containerKind && (navItem.containerKind.length > 0)) { - bakedItem.containerKind = navItem.containerKind; + function areNavToItemsForTheSameLocation(a: protocol.NavtoItem, b: protocol.NavtoItem) { + if (a && b) { + return a.file === b.file && + a.start === b.start && + a.end === b.end; } - return bakedItem; - }); + return false; + } } private getBraceMatching(line: number, offset: number, fileName: string): protocol.TextSpan[] { From 8294a18150cdb39e89ffa9b46f60407f26db7bd2 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Wed, 2 Mar 2016 16:13:52 -0800 Subject: [PATCH 4/7] Add tests --- .../findReferencesAcrossMultipleProjects.ts | 17 +++++++++++++++++ .../goToDefinitionAcrossMultipleProjects.ts | 17 +++++++++++++++++ .../fourslash/renameAcrossMultipleProjects.ts | 17 +++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 tests/cases/fourslash/findReferencesAcrossMultipleProjects.ts create mode 100644 tests/cases/fourslash/goToDefinitionAcrossMultipleProjects.ts create mode 100644 tests/cases/fourslash/renameAcrossMultipleProjects.ts diff --git a/tests/cases/fourslash/findReferencesAcrossMultipleProjects.ts b/tests/cases/fourslash/findReferencesAcrossMultipleProjects.ts new file mode 100644 index 0000000000000..0ef7745f8f9ca --- /dev/null +++ b/tests/cases/fourslash/findReferencesAcrossMultipleProjects.ts @@ -0,0 +1,17 @@ +/// + +//@Filename: a.ts +////var /*1*/x: number; + +//@Filename: b.ts +/////// +////x++; + +//@Filename: c.ts +/////// +////x++; + +goTo.file("a.ts"); +goTo.marker("1"); + +verify.referencesCountIs(3); \ No newline at end of file diff --git a/tests/cases/fourslash/goToDefinitionAcrossMultipleProjects.ts b/tests/cases/fourslash/goToDefinitionAcrossMultipleProjects.ts new file mode 100644 index 0000000000000..bac47638be6d6 --- /dev/null +++ b/tests/cases/fourslash/goToDefinitionAcrossMultipleProjects.ts @@ -0,0 +1,17 @@ +/// + +//@Filename: a.ts +////var x: number; + +//@Filename: b.ts +////var x: number; + +//@Filename: c.ts +/////// +/////// +/////**/x++; + +goTo.file("c.ts"); +goTo.marker(); + +verify.definitionCountIs(2); \ No newline at end of file diff --git a/tests/cases/fourslash/renameAcrossMultipleProjects.ts b/tests/cases/fourslash/renameAcrossMultipleProjects.ts new file mode 100644 index 0000000000000..44b5c0baef425 --- /dev/null +++ b/tests/cases/fourslash/renameAcrossMultipleProjects.ts @@ -0,0 +1,17 @@ +/// + +//@Filename: a.ts +////var /*1*/[|x|]: number; + +//@Filename: b.ts +/////// +////[|x|]++; + +//@Filename: c.ts +/////// +////[|x|]++; + +goTo.file("a.ts"); +goTo.marker("1"); + +verify.renameLocations( /*findInStrings*/ false, /*findInComments*/ false); \ No newline at end of file From fb0d720da71391b8a178630c6cac1ac7a1bd555c Mon Sep 17 00:00:00 2001 From: zhengbli Date: Thu, 7 Apr 2016 14:03:48 -0700 Subject: [PATCH 5/7] refactor cr --- src/server/editorServices.ts | 5 + src/server/session.ts | 318 ++++++++++++++++++----------------- 2 files changed, 165 insertions(+), 158 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 71907735b9125..85285a4f3bd39 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -464,6 +464,11 @@ namespace ts.server { return copiedList; } + export function forEachProject(projects: Project[], action: (project: Project) => T[], comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean) { + const result = projects.reduce((previous, current) => concatenate(previous, action(current)), []).sort(comparer); + return projects.length > 1 ? deduplicate(result, areEqual) : result; + } + export interface ProjectServiceEventHandler { (eventName: string, project: Project, fileName: string): void; } diff --git a/src/server/session.ts b/src/server/session.ts index 199e6df21f457..db64d7edde259 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -141,8 +141,8 @@ namespace ts.server { ) { this.projectService = new ProjectService(host, logger, (eventName, project, fileName) => { - this.handleEvent(eventName, project, fileName); - }); + this.handleEvent(eventName, project, fileName); + }); } private handleEvent(eventName: string, project: Project, fileName: string) { @@ -412,11 +412,13 @@ namespace ts.server { private getRenameLocations(line: number, offset: number, fileName: string, findInComments: boolean, findInStrings: boolean): protocol.RenameResponseBody { const file = ts.normalizePath(fileName); - const defaultProject = this.projectService.getProjectForFile(file); - if (!defaultProject) { + const info = this.projectService.getScriptInfo(file); + const projects = this.projectService.findReferencingProjects(info); + if (!projects.length) { throw Errors.NoProject; } + const defaultProject = projects[0]; // The rename info should be the same for every project const defaultProjectCompilerService = defaultProject.compilerService; const position = defaultProjectCompilerService.host.lineOffsetToPosition(file, line, offset); @@ -432,75 +434,73 @@ namespace ts.server { }; } - const locs: protocol.SpanGroup[] = []; - const info = this.projectService.getScriptInfo(file); - const projects = this.projectService.findReferencingProjects(info); - for (const project of projects) { - const compilerService = project.compilerService; - const renameLocations = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments); - if (!renameLocations) { - continue; - } + const fileSpans = forEachProject( + projects, + (project: Project) => { + const compilerService = project.compilerService; + const renameLocations = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments); + if (!renameLocations) { + return []; + } - const bakedRenameLocs = renameLocations.map(location => ({ - file: location.fileName, - start: compilerService.host.positionToLineOffset(location.fileName, location.textSpan.start), - end: compilerService.host.positionToLineOffset(location.fileName, ts.textSpanEnd(location.textSpan)), - })).sort((a, b) => { - if (a.file < b.file) { - return -1; + return renameLocations.map(location => ({ + file: location.fileName, + start: compilerService.host.positionToLineOffset(location.fileName, location.textSpan.start), + end: compilerService.host.positionToLineOffset(location.fileName, ts.textSpanEnd(location.textSpan)), + })); + }, + compareRenameLocation, + (a, b) => a.file === b.file && a.start.line === b.start.line && a.start.offset === b.start.offset + ); + const locs = fileSpans.reduce((accum, cur) => { + let curFileAccum: protocol.SpanGroup; + if (accum.length > 0) { + curFileAccum = accum[accum.length - 1]; + if (curFileAccum.file !== cur.file) { + curFileAccum = undefined; } - else if (a.file > b.file) { + } + if (!curFileAccum) { + curFileAccum = { file: cur.file, locs: [] }; + accum.push(curFileAccum); + } + curFileAccum.locs.push({ start: cur.start, end: cur.end }); + return accum; + }, []); + + return { info: renameInfo, locs }; + + function compareRenameLocation(a: protocol.FileSpan, b: protocol.FileSpan) { + if (a.file < b.file) { + return -1; + } + else if (a.file > b.file) { + return 1; + } + else { + // reverse sort assuming no overlap + if (a.start.line < b.start.line) { return 1; } - else { - // reverse sort assuming no overlap - if (a.start.line < b.start.line) { - return 1; - } - else if (a.start.line > b.start.line) { - return -1; - } - else { - return b.start.offset - a.start.offset; - } - } - }).reduce((accum: protocol.SpanGroup[], cur: protocol.FileSpan) => { - let curFileAccum: protocol.SpanGroup; - if (accum.length > 0) { - curFileAccum = accum[accum.length - 1]; - if (curFileAccum.file != cur.file) { - curFileAccum = undefined; - } + else if (a.start.line > b.start.line) { + return -1; } - if (!curFileAccum) { - curFileAccum = { file: cur.file, locs: [] }; - accum.push(curFileAccum); + else { + return b.start.offset - a.start.offset; } - curFileAccum.locs.push({ start: cur.start, end: cur.end }); - return accum; - }, []); - - addRange(locs, bakedRenameLocs); - } - - return { info: renameInfo, locs: deduplicate(locs, areSpanGroupsForTheSameFile) }; - - function areSpanGroupsForTheSameFile(a: protocol.SpanGroup, b: protocol.SpanGroup) { - if (a && b) { - return a.file === b.file; } - return false; } } private getReferences(line: number, offset: number, fileName: string): protocol.ReferencesResponseBody { const file = ts.normalizePath(fileName); - const defaultProject = this.projectService.getProjectForFile(file); - if (!defaultProject) { + const info = this.projectService.getScriptInfo(file); + const projects = this.projectService.findReferencingProjects(info); + if (!projects.length) { throw Errors.NoProject; } + const defaultProject = projects[0]; const position = defaultProject.compilerService.host.lineOffsetToPosition(file, line, offset); const nameInfo = defaultProject.compilerService.languageService.getQuickInfoAtPosition(file, position); if (!nameInfo) { @@ -511,34 +511,31 @@ namespace ts.server { const nameSpan = nameInfo.textSpan; const nameColStart = defaultProject.compilerService.host.positionToLineOffset(file, nameSpan.start).offset; const nameText = defaultProject.compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan)); + const refs = forEachProject( + projects, + (project: Project) => { + const compilerService = project.compilerService; + const references = compilerService.languageService.getReferencesAtPosition(file, position); + if (!references) { + return []; + } - const info = this.projectService.getScriptInfo(file); - const projects = this.projectService.findReferencingProjects(info); - const refs: protocol.ReferencesResponseItem[] = []; - for (const project of projects) - { - const compilerService = project.compilerService; - const references = compilerService.languageService.getReferencesAtPosition(file, position); - if (!references) { - continue; - } - - const bakedRefs: protocol.ReferencesResponseItem[] = references.map(ref => { - const start = compilerService.host.positionToLineOffset(ref.fileName, ref.textSpan.start); - const refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1); - const snap = compilerService.host.getScriptSnapshot(ref.fileName); - const lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, ""); - return { - file: ref.fileName, - start: start, - lineText: lineText, - end: compilerService.host.positionToLineOffset(ref.fileName, ts.textSpanEnd(ref.textSpan)), - isWriteAccess: ref.isWriteAccess - }; - }).sort(compareFileStart); - - addRange(refs, bakedRefs); - } + return references.map(ref => { + const start = compilerService.host.positionToLineOffset(ref.fileName, ref.textSpan.start); + const refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1); + const snap = compilerService.host.getScriptSnapshot(ref.fileName); + const lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, ""); + return { + file: ref.fileName, + start: start, + lineText: lineText, + end: compilerService.host.positionToLineOffset(ref.fileName, ts.textSpanEnd(ref.textSpan)), + isWriteAccess: ref.isWriteAccess + }; + }); + }, + compareFileStart + ); return { refs: deduplicate(refs, areReferencesResponseItemsForTheSameLocation), @@ -550,8 +547,8 @@ namespace ts.server { function areReferencesResponseItemsForTheSameLocation(a: protocol.ReferencesResponseItem, b: protocol.ReferencesResponseItem) { if (a && b) { return a.file === b.file && - a.start === b.start && - a.end === b.end; + a.start === b.start && + a.end === b.end; } return false; } @@ -868,54 +865,57 @@ namespace ts.server { private getNavigateToItems(searchValue: string, fileName: string, maxResultCount?: number): protocol.NavtoItem[] { const file = ts.normalizePath(fileName); - const defaultProject = this.projectService.getProjectForFile(file); + const info = this.projectService.getScriptInfo(file); + const projects = this.projectService.findReferencingProjects(info); + const defaultProject = projects[0]; if (!defaultProject) { throw Errors.NoProject; } - const info = this.projectService.getScriptInfo(file); - const projects = this.projectService.findReferencingProjects(info); - const allNavToItems: protocol.NavtoItem[] = []; - for (const project of projects) { - const compilerService = project.compilerService; - const navItems = compilerService.languageService.getNavigateToItems(searchValue, maxResultCount); - if (!navItems) { - continue; - } - - const bakedNavItems = navItems.map((navItem) => { - const start = compilerService.host.positionToLineOffset(navItem.fileName, navItem.textSpan.start); - const end = compilerService.host.positionToLineOffset(navItem.fileName, ts.textSpanEnd(navItem.textSpan)); - const bakedItem: protocol.NavtoItem = { - name: navItem.name, - kind: navItem.kind, - file: navItem.fileName, - start: start, - end: end, - }; - if (navItem.kindModifiers && (navItem.kindModifiers != "")) { - bakedItem.kindModifiers = navItem.kindModifiers; - } - if (navItem.matchKind !== "none") { - bakedItem.matchKind = navItem.matchKind; + const allNavToItems = forEachProject( + projects, + (project: Project) => { + const compilerService = project.compilerService; + const navItems = compilerService.languageService.getNavigateToItems(searchValue, maxResultCount); + if (!navItems) { + return []; } - if (navItem.containerName && (navItem.containerName.length > 0)) { - bakedItem.containerName = navItem.containerName; - } - if (navItem.containerKind && (navItem.containerKind.length > 0)) { - bakedItem.containerKind = navItem.containerKind; - } - return bakedItem; - }); - addRange(allNavToItems, bakedNavItems); - } - return deduplicate(allNavToItems, areNavToItemsForTheSameLocation); + + return navItems.map((navItem) => { + const start = compilerService.host.positionToLineOffset(navItem.fileName, navItem.textSpan.start); + const end = compilerService.host.positionToLineOffset(navItem.fileName, ts.textSpanEnd(navItem.textSpan)); + const bakedItem: protocol.NavtoItem = { + name: navItem.name, + kind: navItem.kind, + file: navItem.fileName, + start: start, + end: end, + }; + if (navItem.kindModifiers && (navItem.kindModifiers != "")) { + bakedItem.kindModifiers = navItem.kindModifiers; + } + if (navItem.matchKind !== "none") { + bakedItem.matchKind = navItem.matchKind; + } + if (navItem.containerName && (navItem.containerName.length > 0)) { + bakedItem.containerName = navItem.containerName; + } + if (navItem.containerKind && (navItem.containerKind.length > 0)) { + bakedItem.containerKind = navItem.containerKind; + } + return bakedItem; + }); + }, + /*comparer*/ undefined, + areNavToItemsForTheSameLocation + ); + return allNavToItems; function areNavToItemsForTheSameLocation(a: protocol.NavtoItem, b: protocol.NavtoItem) { if (a && b) { return a.file === b.file && - a.start === b.start && - a.end === b.end; + a.start === b.start && + a.end === b.end; } return false; } @@ -992,129 +992,131 @@ namespace ts.server { exit() { } - private handlers: Map<(request: protocol.Request) => {response?: any, responseRequired?: boolean}> = { + private handlers: Map<(request: protocol.Request) => { response?: any, responseRequired?: boolean }> = { [CommandNames.Exit]: () => { this.exit(); - return { responseRequired: false}; + return { responseRequired: false }; }, [CommandNames.Definition]: (request: protocol.Request) => { const defArgs = request.arguments; - return {response: this.getDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true}; + return { response: this.getDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true }; }, [CommandNames.TypeDefinition]: (request: protocol.Request) => { const defArgs = request.arguments; - return {response: this.getTypeDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true}; + return { response: this.getTypeDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true }; }, [CommandNames.References]: (request: protocol.Request) => { const defArgs = request.arguments; - return {response: this.getReferences(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true}; + return { response: this.getReferences(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true }; }, [CommandNames.Rename]: (request: protocol.Request) => { const renameArgs = request.arguments; - return {response: this.getRenameLocations(renameArgs.line, renameArgs.offset, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings), responseRequired: true}; + return { response: this.getRenameLocations(renameArgs.line, renameArgs.offset, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings), responseRequired: true }; }, [CommandNames.Open]: (request: protocol.Request) => { const openArgs = request.arguments; this.openClientFile(openArgs.file, openArgs.fileContent); - return {responseRequired: false}; + return { responseRequired: false }; }, [CommandNames.Quickinfo]: (request: protocol.Request) => { const quickinfoArgs = request.arguments; - return {response: this.getQuickInfo(quickinfoArgs.line, quickinfoArgs.offset, quickinfoArgs.file), responseRequired: true}; + return { response: this.getQuickInfo(quickinfoArgs.line, quickinfoArgs.offset, quickinfoArgs.file), responseRequired: true }; }, [CommandNames.Format]: (request: protocol.Request) => { const formatArgs = request.arguments; - return {response: this.getFormattingEditsForRange(formatArgs.line, formatArgs.offset, formatArgs.endLine, formatArgs.endOffset, formatArgs.file), responseRequired: true}; + return { response: this.getFormattingEditsForRange(formatArgs.line, formatArgs.offset, formatArgs.endLine, formatArgs.endOffset, formatArgs.file), responseRequired: true }; }, [CommandNames.Formatonkey]: (request: protocol.Request) => { const formatOnKeyArgs = request.arguments; - return {response: this.getFormattingEditsAfterKeystroke(formatOnKeyArgs.line, formatOnKeyArgs.offset, formatOnKeyArgs.key, formatOnKeyArgs.file), responseRequired: true}; + return { response: this.getFormattingEditsAfterKeystroke(formatOnKeyArgs.line, formatOnKeyArgs.offset, formatOnKeyArgs.key, formatOnKeyArgs.file), responseRequired: true }; }, [CommandNames.Completions]: (request: protocol.Request) => { const completionsArgs = request.arguments; - return {response: this.getCompletions(completionsArgs.line, completionsArgs.offset, completionsArgs.prefix, completionsArgs.file), responseRequired: true}; + return { response: this.getCompletions(completionsArgs.line, completionsArgs.offset, completionsArgs.prefix, completionsArgs.file), responseRequired: true }; }, [CommandNames.CompletionDetails]: (request: protocol.Request) => { const completionDetailsArgs = request.arguments; - return {response: this.getCompletionEntryDetails(completionDetailsArgs.line, completionDetailsArgs.offset, - completionDetailsArgs.entryNames, completionDetailsArgs.file), responseRequired: true}; + return { + response: this.getCompletionEntryDetails(completionDetailsArgs.line, completionDetailsArgs.offset, + completionDetailsArgs.entryNames, completionDetailsArgs.file), responseRequired: true + }; }, [CommandNames.SignatureHelp]: (request: protocol.Request) => { const signatureHelpArgs = request.arguments; - return {response: this.getSignatureHelpItems(signatureHelpArgs.line, signatureHelpArgs.offset, signatureHelpArgs.file), responseRequired: true}; + return { response: this.getSignatureHelpItems(signatureHelpArgs.line, signatureHelpArgs.offset, signatureHelpArgs.file), responseRequired: true }; }, [CommandNames.Geterr]: (request: protocol.Request) => { const geterrArgs = request.arguments; - return {response: this.getDiagnostics(geterrArgs.delay, geterrArgs.files), responseRequired: false}; + return { response: this.getDiagnostics(geterrArgs.delay, geterrArgs.files), responseRequired: false }; }, [CommandNames.GeterrForProject]: (request: protocol.Request) => { const { file, delay } = request.arguments; - return {response: this.getDiagnosticsForProject(delay, file), responseRequired: false}; + return { response: this.getDiagnosticsForProject(delay, file), responseRequired: false }; }, [CommandNames.Change]: (request: protocol.Request) => { const changeArgs = request.arguments; this.change(changeArgs.line, changeArgs.offset, changeArgs.endLine, changeArgs.endOffset, - changeArgs.insertString, changeArgs.file); - return {responseRequired: false}; + changeArgs.insertString, changeArgs.file); + return { responseRequired: false }; }, [CommandNames.Configure]: (request: protocol.Request) => { const configureArgs = request.arguments; this.projectService.setHostConfiguration(configureArgs); this.output(undefined, CommandNames.Configure, request.seq); - return {responseRequired: false}; + return { responseRequired: false }; }, [CommandNames.Reload]: (request: protocol.Request) => { const reloadArgs = request.arguments; this.reload(reloadArgs.file, reloadArgs.tmpfile, request.seq); - return {responseRequired: false}; + return { responseRequired: false }; }, [CommandNames.Saveto]: (request: protocol.Request) => { const savetoArgs = request.arguments; this.saveToTmp(savetoArgs.file, savetoArgs.tmpfile); - return {responseRequired: false}; + return { responseRequired: false }; }, [CommandNames.Close]: (request: protocol.Request) => { const closeArgs = request.arguments; this.closeClientFile(closeArgs.file); - return {responseRequired: false}; + return { responseRequired: false }; }, [CommandNames.Navto]: (request: protocol.Request) => { const navtoArgs = request.arguments; - return {response: this.getNavigateToItems(navtoArgs.searchValue, navtoArgs.file, navtoArgs.maxResultCount), responseRequired: true}; + return { response: this.getNavigateToItems(navtoArgs.searchValue, navtoArgs.file, navtoArgs.maxResultCount), responseRequired: true }; }, [CommandNames.Brace]: (request: protocol.Request) => { const braceArguments = request.arguments; - return {response: this.getBraceMatching(braceArguments.line, braceArguments.offset, braceArguments.file), responseRequired: true}; + return { response: this.getBraceMatching(braceArguments.line, braceArguments.offset, braceArguments.file), responseRequired: true }; }, [CommandNames.NavBar]: (request: protocol.Request) => { const navBarArgs = request.arguments; - return {response: this.getNavigationBarItems(navBarArgs.file), responseRequired: true}; + return { response: this.getNavigationBarItems(navBarArgs.file), responseRequired: true }; }, [CommandNames.Occurrences]: (request: protocol.Request) => { const { line, offset, file: fileName } = request.arguments; - return {response: this.getOccurrences(line, offset, fileName), responseRequired: true}; + return { response: this.getOccurrences(line, offset, fileName), responseRequired: true }; }, [CommandNames.DocumentHighlights]: (request: protocol.Request) => { const { line, offset, file: fileName, filesToSearch } = request.arguments; - return {response: this.getDocumentHighlights(line, offset, fileName, filesToSearch), responseRequired: true}; + return { response: this.getDocumentHighlights(line, offset, fileName, filesToSearch), responseRequired: true }; }, [CommandNames.ProjectInfo]: (request: protocol.Request) => { const { file, needFileNameList } = request.arguments; - return {response: this.getProjectInfo(file, needFileNameList), responseRequired: true}; + return { response: this.getProjectInfo(file, needFileNameList), responseRequired: true }; }, [CommandNames.ReloadProjects]: (request: protocol.ReloadProjectsRequest) => { this.reloadProjects(); - return {responseRequired: false}; + return { responseRequired: false }; } }; - public addProtocolHandler(command: string, handler: (request: protocol.Request) => {response?: any, responseRequired: boolean}) { + public addProtocolHandler(command: string, handler: (request: protocol.Request) => { response?: any, responseRequired: boolean }) { if (this.handlers[command]) { throw new Error(`Protocol handler already exists for command "${command}"`); } this.handlers[command] = handler; } - public executeCommand(request: protocol.Request): {response?: any, responseRequired?: boolean} { + public executeCommand(request: protocol.Request): { response?: any, responseRequired?: boolean } { const handler = this.handlers[request.command]; if (handler) { return handler(request); @@ -1122,7 +1124,7 @@ namespace ts.server { else { this.projectService.log("Unrecognized JSON command: " + JSON.stringify(request)); this.output(undefined, CommandNames.Unknown, request.seq, "Unrecognized JSON command: " + request.command); - return {responseRequired: false}; + return { responseRequired: false }; } } From db6f5bd8326783e44951caf51a581181e11bd9c0 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Fri, 8 Apr 2016 12:53:19 -0700 Subject: [PATCH 6/7] Rename the `forEachProject` function to something sane --- src/compiler/core.ts | 7 +------ src/server/editorServices.ts | 2 +- src/server/session.ts | 8 ++++---- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index df8e3d1bce45b..c6f1f9bcbaa1d 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -94,12 +94,7 @@ namespace ts { export function contains(array: T[], value: T, areEqual?: (a: T, b: T) => boolean): boolean { if (array) { for (const v of array) { - if (areEqual) { - if (areEqual(v, value)) { - return true; - } - } - else if (v === value) { + if (areEqual ? areEqual(v, value) : v === value) { return true; } } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 85285a4f3bd39..e3bf97f2736b8 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -464,7 +464,7 @@ namespace ts.server { return copiedList; } - export function forEachProject(projects: Project[], action: (project: Project) => T[], comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean) { + export function processEachProjectThenConcatSortDeduplicateResults(projects: Project[], action: (project: Project) => T[], comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean) { const result = projects.reduce((previous, current) => concatenate(previous, action(current)), []).sort(comparer); return projects.length > 1 ? deduplicate(result, areEqual) : result; } diff --git a/src/server/session.ts b/src/server/session.ts index db64d7edde259..d81b0ee286eff 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -434,7 +434,7 @@ namespace ts.server { }; } - const fileSpans = forEachProject( + const fileSpans = processEachProjectThenConcatSortDeduplicateResults( projects, (project: Project) => { const compilerService = project.compilerService; @@ -511,7 +511,7 @@ namespace ts.server { const nameSpan = nameInfo.textSpan; const nameColStart = defaultProject.compilerService.host.positionToLineOffset(file, nameSpan.start).offset; const nameText = defaultProject.compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan)); - const refs = forEachProject( + const refs = processEachProjectThenConcatSortDeduplicateResults( projects, (project: Project) => { const compilerService = project.compilerService; @@ -872,7 +872,7 @@ namespace ts.server { throw Errors.NoProject; } - const allNavToItems = forEachProject( + const allNavToItems = processEachProjectThenConcatSortDeduplicateResults( projects, (project: Project) => { const compilerService = project.compilerService; @@ -891,7 +891,7 @@ namespace ts.server { start: start, end: end, }; - if (navItem.kindModifiers && (navItem.kindModifiers != "")) { + if (navItem.kindModifiers && (navItem.kindModifiers !== "")) { bakedItem.kindModifiers = navItem.kindModifiers; } if (navItem.matchKind !== "none") { From 19596deb7f0211102e2c655175947cf890b2d745 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Mon, 18 Apr 2016 16:20:45 -0700 Subject: [PATCH 7/7] remove extra deduplicate --- src/server/editorServices.ts | 5 ++++- src/server/session.ts | 11 ++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index e3bf97f2736b8..0393caa75bb66 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -464,7 +464,10 @@ namespace ts.server { return copiedList; } - export function processEachProjectThenConcatSortDeduplicateResults(projects: Project[], action: (project: Project) => T[], comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean) { + /** + * This helper funciton processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project. + */ + export function combineProjectOutput(projects: Project[], action: (project: Project) => T[], comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean) { const result = projects.reduce((previous, current) => concatenate(previous, action(current)), []).sort(comparer); return projects.length > 1 ? deduplicate(result, areEqual) : result; } diff --git a/src/server/session.ts b/src/server/session.ts index d81b0ee286eff..defdd577577aa 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -434,7 +434,7 @@ namespace ts.server { }; } - const fileSpans = processEachProjectThenConcatSortDeduplicateResults( + const fileSpans = combineProjectOutput( projects, (project: Project) => { const compilerService = project.compilerService; @@ -511,7 +511,7 @@ namespace ts.server { const nameSpan = nameInfo.textSpan; const nameColStart = defaultProject.compilerService.host.positionToLineOffset(file, nameSpan.start).offset; const nameText = defaultProject.compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan)); - const refs = processEachProjectThenConcatSortDeduplicateResults( + const refs = combineProjectOutput( projects, (project: Project) => { const compilerService = project.compilerService; @@ -534,11 +534,12 @@ namespace ts.server { }; }); }, - compareFileStart + compareFileStart, + areReferencesResponseItemsForTheSameLocation ); return { - refs: deduplicate(refs, areReferencesResponseItemsForTheSameLocation), + refs, symbolName: nameText, symbolStartOffset: nameColStart, symbolDisplayString: displayString @@ -872,7 +873,7 @@ namespace ts.server { throw Errors.NoProject; } - const allNavToItems = processEachProjectThenConcatSortDeduplicateResults( + const allNavToItems = combineProjectOutput( projects, (project: Project) => { const compilerService = project.compilerService;