From bdccb8d0eb34a95dba9d0745d721018598764275 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 20 Jun 2016 11:25:31 -0700 Subject: [PATCH] renameLocations --- src/server/editorServices.ts | 45 ++-- src/server/session.ts | 193 ++++++++++-------- .../cases/unittests/cachingInServerLSHost.ts | 2 +- 3 files changed, 127 insertions(+), 113 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 0cefde1e7b8..32ce8255570 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -395,7 +395,7 @@ namespace ts.server { } getScriptInfo(fileName: string) { - const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false); + const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false, this); if (scriptInfo && !scriptInfo.defaultProject) { scriptInfo.defaultProject = this; } @@ -1157,7 +1157,7 @@ namespace ts.server { let errors: Diagnostic[]; for (const rootFilename of files) { if (this.host.fileExists(rootFilename)) { - const info = this.getOrCreateScriptInfo(rootFilename, /*openedByClient*/ clientFileName == rootFilename); + const info = this.getOrCreateScriptInfo(rootFilename, /*openedByClient*/ clientFileName == rootFilename, project); project.addRoot(info); } else { @@ -1195,7 +1195,7 @@ namespace ts.server { for (const fileName of fileNamesToAdd) { let info = this.getScriptInfo(fileName); if (!info) { - info = this.getOrCreateScriptInfo(fileName, /*openedByClient*/ false); + info = this.getOrCreateScriptInfo(fileName, /*openedByClient*/ false, project); } else { // if the root file was opened by client, it would belong to either @@ -1268,7 +1268,7 @@ namespace ts.server { * @param filename is absolute pathname * @param fileContent is a known version of the file content that is more up to date than the one on disk */ - getOrCreateScriptInfo(fileName: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind) { + getOrCreateScriptInfo(fileName: string, openedByClient: boolean, containingProject: Project, fileContent?: string, scriptKind?: ScriptKind) { fileName = ts.normalizePath(fileName); let info = ts.lookUp(this.filenameToScriptInfo, fileName); if (!info) { @@ -1284,6 +1284,7 @@ namespace ts.server { if (content !== undefined) { info = new ScriptInfo(this.host, fileName, content, openedByClient); info.scriptKind = scriptKind; + info.defaultProject = containingProject; info.setFormatOptions(toEditorSettings(this.getFormatCodeOptions())); this.filenameToScriptInfo[fileName] = info; if (!info.isOpen) { @@ -1333,27 +1334,27 @@ namespace ts.server { findReferencingProjects(info: ScriptInfo, excludedProject?: Project) { const referencingProjects: Project[] = []; info.defaultProject = undefined; - for (let i = 0, len = this.inferredProjects.length; i < len; i++) { - const inferredProject = this.inferredProjects[i]; - inferredProject.updateGraph(); - if (inferredProject !== excludedProject) { - if (inferredProject.containsScriptInfo(info)) { - info.defaultProject = inferredProject; - referencingProjects.push(inferredProject); - } - } - } - for (let i = 0, len = this.configuredProjects.length; i < len; i++) { - const configuredProject = this.configuredProjects[i]; - configuredProject.updateGraph(); - if (configuredProject.containsScriptInfo(info)) { - info.defaultProject = configuredProject; - referencingProjects.push(configuredProject); - } + this.collectContainingProjects(info, referencingProjects, this.inferredProjects, excludedProject); + this.collectContainingProjects(info, referencingProjects, this.configuredProjects); + this.collectContainingProjects(info, referencingProjects, this.externalProjects); + if (referencingProjects.length) { + info.defaultProject = referencingProjects[0]; } return referencingProjects; } + private collectContainingProjects(info: ScriptInfo, result: Project[], projects: Project[], excludedProject?: Project) { + for (const p of projects) { + if (p === excludedProject) { + continue; + } + p.updateGraph(); + if (p.containsScriptInfo(info)) { + result.push(p); + } + } + } + /** * This function rebuilds the project for every file opened by the client */ @@ -1464,7 +1465,7 @@ namespace ts.server { if (!this.findContainingExternalProject(fileName)) { ({ configFileName, configFileErrors } = this.openOrUpdateConfiguredProjectForFile(fileName)); } - const info = this.getOrCreateScriptInfo(fileName, /*openedByClient*/ true, fileContent, scriptKind); + const info = this.getOrCreateScriptInfo(fileName, /*openedByClient*/ true, /*containingProject*/ undefined, fileContent, scriptKind); this.addOpenFile(info); this.printProjects(); return { configFileName, configFileErrors }; diff --git a/src/server/session.ts b/src/server/session.ts index 3d17fff507b..a0e0eb67bc0 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -135,6 +135,7 @@ namespace ts.server { export const Reload = "reload"; export const Rename = "rename"; export const RenameInfoFull = "rename-full"; + export const RenameLocationsFull = "renameLocations-full"; export const Saveto = "saveto"; export const SignatureHelp = "signatureHelp"; export const SignatureHelpFull = "signatureHelp-full"; @@ -512,7 +513,7 @@ namespace ts.server { } const scriptInfo = project.getScriptInfo(fileName); - const position = this.getPosition(args, scriptInfo); + const position = this.getPosition(args, scriptInfo); const documentHighlights = project.languageService.getDocumentHighlights(fileName, position, args.filesToSearch); @@ -563,72 +564,109 @@ namespace ts.server { private getRenameInfo(args: protocol.FileLocationRequestArgs) { const { file, project } = this.getFileAndProject(args.file); const scriptInfo = project.getScriptInfo(file); - const position = this.getPosition(args, scriptInfo) + const position = this.getPosition(args, scriptInfo); return project.languageService.getRenameInfo(file, position); } - private getRenameLocations(line: number, offset: number, fileName: string, findInComments: boolean, findInStrings: boolean): protocol.RenameResponseBody { - const file = ts.normalizePath(fileName); - const info = this.projectService.getScriptInfo(file); - const projects = this.projectService.findReferencingProjects(info); - if (!projects.length) { + private getProjects(args: protocol.FileRequestArgs) { + let projects: Project[]; + if (args.projectFileName) { + const project = this.getProject(args.projectFileName); + if (project) { + projects = [project]; + } + } + else { + const file = normalizePath(args.file); + const info = this.projectService.getScriptInfo(file); + projects = this.projectService.findReferencingProjects(info); + } + if (!projects || !projects.length) { throw Errors.NoProject; } + return projects; + } - const defaultProject = projects[0]; - // The rename info should be the same for every project - const scriptInfo = defaultProject.getScriptInfo(file); - const position = scriptInfo.lineOffsetToPosition(line, offset); - const renameInfo = defaultProject.languageService.getRenameInfo(file, position); - if (!renameInfo) { - return undefined; + private getRenameLocations(args: protocol.RenameRequestArgs, simplifiedResult: boolean): protocol.RenameResponseBody | RenameLocation[] { + const file = ts.normalizePath(args.file); + const info = this.projectService.getScriptInfo(file); + const position = this.getPosition(args, info); + const projects = this.getProjects(args); + if (simplifiedResult) { + + const defaultProject = projects[0]; + // The rename info should be the same for every project + const renameInfo = defaultProject.languageService.getRenameInfo(file, position); + if (!renameInfo) { + return undefined; + } + + if (!renameInfo.canRename) { + return { + info: renameInfo, + locs: [] + }; + } + + const fileSpans = combineProjectOutput( + projects, + (project: Project) => { + const renameLocations = project.languageService.findRenameLocations(file, position, args.findInStrings, args.findInComments); + if (!renameLocations) { + return []; + } + + return renameLocations.map(location => { + const locationScriptInfo = project.getScriptInfo(location.fileName); + return { + file: location.fileName, + start: locationScriptInfo.positionToLineOffset(location.textSpan.start), + end: locationScriptInfo.positionToLineOffset(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; + } + } + 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 }; + } + else { + return combineProjectOutput( + projects, + p => p.languageService.findRenameLocations(file, position, args.findInStrings, args.findInComments), + /*comparer*/ undefined, + renameLocationIsEqualTo + ); } - if (!renameInfo.canRename) { - return { - info: renameInfo, - locs: [] - }; + function renameLocationIsEqualTo(a: RenameLocation, b: RenameLocation) { + if (a === b) { + return true; + } + if (!a || !b) { + return false; + } + return a.fileName === b.fileName && + a.textSpan.start === b.textSpan.start && + a.textSpan.length === b.textSpan.length; } - const fileSpans = combineProjectOutput( - projects, - (project: Project) => { - const renameLocations = project.languageService.findRenameLocations(file, position, findInStrings, findInComments); - if (!renameLocations) { - return []; - } - - return renameLocations.map(location => { - const locationScriptInfo = project.getScriptInfo(location.fileName); - return { - file: location.fileName, - start: locationScriptInfo.positionToLineOffset(location.textSpan.start), - end: locationScriptInfo.positionToLineOffset(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; - } - } - 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; @@ -651,22 +689,9 @@ namespace ts.server { } } - private getReferences(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.ReferencesResponseBody | ReferencedSymbol[] { + private getReferences(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.ReferencesResponseBody | ReferencedSymbol[] { const file = ts.normalizePath(args.file); - let projects: Project[]; - if (args.projectFileName) { - const project = this.getProject(args.projectFileName); - if (project) { - projects = [project]; - } - } - else { - const info = this.projectService.getScriptInfo(file); - projects = this.projectService.findReferencingProjects(info); - } - if (!projects || !projects.length) { - throw Errors.NoProject; - } + const projects = this.getProjects(args); const defaultProject = projects[0]; const scriptInfo = defaultProject.getScriptInfo(file); @@ -1133,21 +1158,7 @@ namespace ts.server { } private getNavigateToItems(args: protocol.NavtoRequestArgs, simplifiedResult: boolean): protocol.NavtoItem[] | NavigateToItem[] { - let projects: Project[]; - if (args.projectFileName) { - const project = this.getProject(args.projectFileName); - if (project) { - projects = [project]; - } - } - else { - const file = normalizePath(args.file); - const info = this.projectService.getScriptInfo(file); - projects = this.projectService.findReferencingProjects(info); - } - if (!projects || !projects.length) { - throw Errors.NoProject; - } + const projects = this.getProjects(args); if (simplifiedResult) { return combineProjectOutput( @@ -1352,8 +1363,10 @@ namespace ts.server { return this.requiredResponse(this.getReferences(request.arguments, /*simplifiedResult*/ false)); }, [CommandNames.Rename]: (request: protocol.Request) => { - const renameArgs = request.arguments; - return this.requiredResponse(this.getRenameLocations(renameArgs.line, renameArgs.offset, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings)); + return this.requiredResponse(this.getRenameLocations(request.arguments, /*simplifiedResult*/ true)); + }, + [CommandNames.RenameLocationsFull]: (request: protocol.RenameRequest) => { + return this.requiredResponse(this.getRenameLocations(request.arguments, /*simplifiedResult*/ false)); }, [CommandNames.RenameInfoFull]: (request: protocol.FileLocationRequest) => { return this.requiredResponse(this.getRenameInfo(request.arguments)); @@ -1476,7 +1489,7 @@ namespace ts.server { [CommandNames.Reload]: (request: protocol.Request) => { const reloadArgs = request.arguments; this.reload(reloadArgs.file, reloadArgs.tmpfile, request.seq); - return {response: { reloadFinished: true }, responseRequired: true}; + return { response: { reloadFinished: true }, responseRequired: true }; }, [CommandNames.Saveto]: (request: protocol.Request) => { const savetoArgs = request.arguments; diff --git a/tests/cases/unittests/cachingInServerLSHost.ts b/tests/cases/unittests/cachingInServerLSHost.ts index b14b9a74350..211dd774919 100644 --- a/tests/cases/unittests/cachingInServerLSHost.ts +++ b/tests/cases/unittests/cachingInServerLSHost.ts @@ -80,7 +80,7 @@ namespace ts { }; const projectService = new server.ProjectService(serverHost, logger, { isCancellationRequested: () => false }); - const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */true); + const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */true, /*containingProject*/ undefined); const project = projectService.createAndAddInferredProject(rootScriptInfo); project.setCompilerOptions({ module: ts.ModuleKind.AMD } ); return {