From 0f5e91bfe8dd8c39442855cabecf3545f41478ad Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 31 May 2016 16:34:14 -0700 Subject: [PATCH] isolate responsibilities of LSHost --- src/server/editorServices.ts | 152 ++++++++++++++++------------------- src/server/session.ts | 56 ++++++------- 2 files changed, 99 insertions(+), 109 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index f6f9b82d15d..f2ab320c265 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -48,7 +48,6 @@ namespace ts.server { export class ScriptInfo { svc: ScriptVersionCache; - children: ScriptInfo[] = []; // files referenced by this file defaultProject: Project; // project to use by default for file fileWatcher: FileWatcher; formatCodeOptions: ts.FormatCodeOptions; @@ -135,20 +134,14 @@ namespace ts.server { } export class LSHost implements ts.LanguageServiceHost { - compilationSettings: ts.CompilerOptions; - filenameToScript: ts.FileMap; - roots: ScriptInfo[] = []; - + private compilationSettings: ts.CompilerOptions; private resolvedModuleNames: ts.FileMap>; private resolvedTypeReferenceDirectives: ts.FileMap>; private moduleResolutionHost: ts.ModuleResolutionHost; - private getCanonicalFileName: (fileName: string) => string; - constructor(public host: ServerHost, public project: Project) { - this.getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + constructor(private host: ServerHost, private project: Project) { this.resolvedModuleNames = createFileMap>(); this.resolvedTypeReferenceDirectives = createFileMap>(); - this.filenameToScript = createFileMap(); this.moduleResolutionHost = { fileExists: fileName => this.fileExists(fileName), readFile: fileName => this.host.readFile(fileName), @@ -163,7 +156,7 @@ namespace ts.server { loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T, getResult: (s: T) => R): R[] { - const path = toPath(containingFile, this.host.getCurrentDirectory(), this.getCanonicalFileName); + const path = toPath(containingFile, this.host.getCurrentDirectory(), this.project.getCanonicalFileName); const currentResolutionsInFile = cache.get(path); const newResolutions: Map = {}; @@ -225,13 +218,8 @@ namespace ts.server { return ts.combinePaths(nodeModuleBinDir, ts.getDefaultLibFileName(this.compilationSettings)); } - getScriptInfoForRootFile(fileName: string): ScriptInfo { - const path = toPath(fileName, this.host.getCurrentDirectory(), this.getCanonicalFileName); - return this.filenameToScript.get(path); - } - getScriptSnapshot(filename: string): ts.IScriptSnapshot { - const scriptInfo = this.getScriptInfo(filename); + const scriptInfo = this.project.getScriptInfo(filename); if (scriptInfo) { return scriptInfo.snap(); } @@ -250,11 +238,11 @@ namespace ts.server { } getScriptFileNames() { - return this.roots.map(root => root.fileName); + return this.project.getRootFiles(); } getScriptKind(fileName: string) { - const info = this.getScriptInfo(fileName); + const info = this.project.getScriptInfo(fileName); if (!info) { return undefined; } @@ -266,7 +254,7 @@ namespace ts.server { } getScriptVersion(filename: string) { - return this.getScriptInfo(filename).svc.latestVersion().toString(); + return this.project.getScriptInfo(filename).svc.latestVersion().toString(); } getCurrentDirectory(): string { @@ -275,73 +263,22 @@ namespace ts.server { removeReferencedFile(info: ScriptInfo) { if (!info.isOpen) { - this.filenameToScript.remove(info.path); this.resolvedModuleNames.remove(info.path); this.resolvedTypeReferenceDirectives.remove(info.path); } } - getScriptInfo(filename: string): ScriptInfo { - const path = toPath(filename, this.host.getCurrentDirectory(), this.getCanonicalFileName); - let scriptInfo = this.filenameToScript.get(path); - if (!scriptInfo) { - scriptInfo = this.project.openReferencedFile(filename); - if (scriptInfo) { - this.filenameToScript.set(path, scriptInfo); - } - } - return scriptInfo; - } - - addRoot(info: ScriptInfo) { - if (!this.filenameToScript.contains(info.path)) { - this.filenameToScript.set(info.path, info); - this.roots.push(info); - } - } - removeRoot(info: ScriptInfo) { - if (!this.filenameToScript.contains(info.path)) { - this.filenameToScript.remove(info.path); - this.roots = copyListRemovingItem(info, this.roots); - this.resolvedModuleNames.remove(info.path); - this.resolvedTypeReferenceDirectives.remove(info.path); - } - } - - saveTo(filename: string, tmpfilename: string) { - const script = this.getScriptInfo(filename); - if (script) { - const snap = script.snap(); - this.host.writeFile(tmpfilename, snap.getText(0, snap.getLength())); - } - } - - reloadScript(filename: string, tmpfilename: string, cb: () => any) { - const script = this.getScriptInfo(filename); - if (script) { - script.svc.reloadFromFile(tmpfilename, cb); - } - } - - editScript(filename: string, start: number, end: number, newText: string) { - const script = this.getScriptInfo(filename); - if (script) { - script.editContent(start, end, newText); - return; - } - - throw new Error("No script with name '" + filename + "'"); + this.resolvedModuleNames.remove(info.path); + this.resolvedTypeReferenceDirectives.remove(info.path); } resolvePath(path: string): string { - const result = this.host.resolvePath(path); - return result; + return this.host.resolvePath(path); } fileExists(path: string): boolean { - const result = this.host.fileExists(path); - return result; + return this.host.fileExists(path); } directoryExists(path: string): boolean { @@ -356,7 +293,7 @@ namespace ts.server { } export class Project { - lsHost: LSHost; + private lsHost: LSHost; languageService: LanguageService; projectFilename: string; projectFileWatcher: FileWatcher; @@ -369,7 +306,14 @@ namespace ts.server { /** Used for configured projects which may have multiple open roots */ openRefCount = 0; + getCanonicalFileName: (fileName: string) => string; + + private rootFiles: ScriptInfo[] = []; + private pathToScriptInfo: ts.FileMap; + constructor(public projectService: ProjectService, documentRegistry: ts.DocumentRegistry, public projectOptions?: ProjectOptions) { + this.pathToScriptInfo = ts.createFileMap(); + this.getCanonicalFileName = ts.createGetCanonicalFileName(this.projectService.host.useCaseSensitiveFileNames); if (projectOptions && projectOptions.files) { // If files are listed explicitly, allow all extensions projectOptions.compilerOptions.allowNonTsExtensions = true; @@ -401,7 +345,7 @@ namespace ts.server { } getRootFiles() { - return this.lsHost.roots.map(info => info.fileName); + return this.rootFiles.map(info => info.fileName); } getFileNames() { @@ -423,11 +367,14 @@ namespace ts.server { } isRoot(info: ScriptInfo) { - return this.lsHost.roots.some(root => root === info); + return this.rootFiles.some(root => root === info); } removeReferencedFile(info: ScriptInfo) { - this.lsHost.removeReferencedFile(info); + if (!info.isOpen) { + this.pathToScriptInfo.remove(info.path); + this.lsHost.removeReferencedFile(info); + } this.updateGraph(); } @@ -456,12 +403,31 @@ namespace ts.server { // add a root file to project addRoot(info: ScriptInfo) { - this.lsHost.addRoot(info); + if (!this.pathToScriptInfo.contains(info.path)) { + this.pathToScriptInfo.set(info.path, info); + this.rootFiles.push(info); + } } // remove a root file from project removeRoot(info: ScriptInfo) { - this.lsHost.removeRoot(info); + if (!this.pathToScriptInfo.contains(info.path)) { + this.pathToScriptInfo.remove(info.path); + this.rootFiles = copyListRemovingItem(info, this.rootFiles); + this.lsHost.removeRoot(info); + } + } + + getScriptInfo(fileName: string) { + const path = toPath(fileName, this.projectService.host.getCurrentDirectory(), this.getCanonicalFileName); + let scriptInfo = this.pathToScriptInfo.get(path); + if (!scriptInfo) { + scriptInfo = this.openReferencedFile(fileName); + if (scriptInfo) { + this.pathToScriptInfo.set(path, scriptInfo); + } + } + return scriptInfo; } filesToString() { @@ -478,6 +444,30 @@ namespace ts.server { this.lsHost.setCompilationSettings(projectOptions.compilerOptions); } } + saveTo(filename: string, tmpfilename: string) { + const script = this.getScriptInfo(filename); + if (script) { + const snap = script.snap(); + this.projectService.host.writeFile(tmpfilename, snap.getText(0, snap.getLength())); + } + } + + reloadScript(filename: string, tmpfilename: string, cb: () => any) { + const script = this.getScriptInfo(filename); + if (script) { + script.svc.reloadFromFile(tmpfilename, cb); + } + } + + editScript(filename: string, start: number, end: number, newText: string) { + const script = this.getScriptInfo(filename); + if (script) { + script.editContent(start, end, newText); + return; + } + + throw new Error("No script with name '" + filename + "'"); + } } export interface ProjectOpenResult { @@ -1300,7 +1290,7 @@ namespace ts.server { return errors; } else { - const oldFileNames = project.lsHost.roots.map(info => info.fileName); + const oldFileNames = project.getRootFiles(); const newFileNames = ts.filter(projectOptions.files, f => this.host.fileExists(f)); const fileNamesToRemove = oldFileNames.filter(f => newFileNames.indexOf(f) < 0); const fileNamesToAdd = newFileNames.filter(f => oldFileNames.indexOf(f) < 0); diff --git a/src/server/session.ts b/src/server/session.ts index 99e11d41b89..249a484191a 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -69,7 +69,7 @@ namespace ts.server { } function formatDiag(fileName: string, project: Project, diag: ts.Diagnostic): protocol.Diagnostic { - const scriptInfo = project.lsHost.getScriptInfoForRootFile(fileName); + const scriptInfo = project.getScriptInfo(fileName); return { start: scriptInfo.positionToLineOffset(diag.start), end: scriptInfo.positionToLineOffset(diag.start + diag.length), @@ -318,7 +318,7 @@ namespace ts.server { throw Errors.NoProject; } - const scriptInfo = project.lsHost.getScriptInfoForRootFile(file); + const scriptInfo = project.getScriptInfo(file); const position = scriptInfo.lineOffsetToPosition(line, offset); const definitions = project.languageService.getDefinitionAtPosition(file, position); @@ -327,12 +327,12 @@ namespace ts.server { } return definitions.map(def => { - const defScriptInfo = project.lsHost.getScriptInfoForRootFile(def.fileName); + const defScriptInfo = project.getScriptInfo(def.fileName); return { file: def.fileName, start: defScriptInfo.positionToLineOffset(def.textSpan.start), end: defScriptInfo.positionToLineOffset(ts.textSpanEnd(def.textSpan)) - } + }; }); } @@ -343,7 +343,7 @@ namespace ts.server { throw Errors.NoProject; } - const scriptInfo = project.lsHost.getScriptInfoForRootFile(file); + const scriptInfo = project.getScriptInfo(file); const position = scriptInfo.lineOffsetToPosition(line, offset); const definitions = project.languageService.getTypeDefinitionAtPosition(file, position); @@ -352,12 +352,12 @@ namespace ts.server { } return definitions.map(def => { - const defScriptInfo = project.lsHost.getScriptInfoForRootFile(def.fileName); + const defScriptInfo = project.getScriptInfo(def.fileName); return { file: def.fileName, start: defScriptInfo.positionToLineOffset(def.textSpan.start), end: defScriptInfo.positionToLineOffset(ts.textSpanEnd(def.textSpan)) - } + }; }); } @@ -369,7 +369,7 @@ namespace ts.server { throw Errors.NoProject; } - const scriptInfo = project.lsHost.getScriptInfo(fileName); + const scriptInfo = project.getScriptInfo(fileName); const position = scriptInfo.lineOffsetToPosition(line, offset); const occurrences = project.languageService.getOccurrencesAtPosition(fileName, position); @@ -380,7 +380,7 @@ namespace ts.server { return occurrences.map(occurrence => { const { fileName, isWriteAccess, textSpan } = occurrence; - const scriptInfo = project.lsHost.getScriptInfo(fileName); + const scriptInfo = project.getScriptInfo(fileName); const start = scriptInfo.positionToLineOffset(textSpan.start); const end = scriptInfo.positionToLineOffset(ts.textSpanEnd(textSpan)); return { @@ -400,7 +400,7 @@ namespace ts.server { throw Errors.NoProject; } - const scriptInfo = project.lsHost.getScriptInfo(fileName); + const scriptInfo = project.getScriptInfo(fileName); const position = scriptInfo.lineOffsetToPosition(line, offset); const documentHighlights = project.languageService.getDocumentHighlights(fileName, position, filesToSearch); @@ -414,7 +414,7 @@ namespace ts.server { function convertToDocumentHighlightsItem(documentHighlights: ts.DocumentHighlights): ts.server.protocol.DocumentHighlightsItem { const { fileName, highlightSpans } = documentHighlights; - const scriptInfo = project.lsHost.getScriptInfo(fileName); + const scriptInfo = project.getScriptInfo(fileName); return { file: fileName, highlightSpans: highlightSpans.map(convertHighlightSpan) @@ -454,7 +454,7 @@ namespace ts.server { const defaultProject = projects[0]; // The rename info should be the same for every project - const scriptInfo = defaultProject.lsHost.getScriptInfoForRootFile(file); + const scriptInfo = defaultProject.getScriptInfo(file); const position = scriptInfo.lineOffsetToPosition(line, offset); const renameInfo = defaultProject.languageService.getRenameInfo(file, position); if (!renameInfo) { @@ -477,12 +477,12 @@ namespace ts.server { } return renameLocations.map(location => { - const locationScriptInfo = project.lsHost.getScriptInfoForRootFile(location.fileName); + const locationScriptInfo = project.getScriptInfo(location.fileName); return { file: location.fileName, start: locationScriptInfo.positionToLineOffset(location.textSpan.start), end: locationScriptInfo.positionToLineOffset(ts.textSpanEnd(location.textSpan)), - } + }; }); }, compareRenameLocation, @@ -537,7 +537,7 @@ namespace ts.server { } const defaultProject = projects[0]; - const scriptInfo = defaultProject.lsHost.getScriptInfoForRootFile(file); + const scriptInfo = defaultProject.getScriptInfo(file); const position = scriptInfo.lineOffsetToPosition(line, offset); const nameInfo = defaultProject.languageService.getQuickInfoAtPosition(file, position); if (!nameInfo) { @@ -557,7 +557,7 @@ namespace ts.server { } return references.map(ref => { - const refScriptInfo = project.lsHost.getScriptInfoForRootFile(ref.fileName); + const refScriptInfo = project.getScriptInfo(ref.fileName); const start = refScriptInfo.positionToLineOffset(ref.textSpan.start); const refLineSpan = refScriptInfo.lineToTextSpan(start.line - 1); const lineText = refScriptInfo.snap().getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, ""); @@ -610,7 +610,7 @@ namespace ts.server { throw Errors.NoProject; } - const scriptInfo = project.lsHost.getScriptInfoForRootFile(file); + const scriptInfo = project.getScriptInfo(file); const position = scriptInfo.lineOffsetToPosition(line, offset); const quickInfo = project.languageService.getQuickInfoAtPosition(file, position); if (!quickInfo) { @@ -636,7 +636,7 @@ namespace ts.server { throw Errors.NoProject; } - const scriptInfo = project.lsHost.getScriptInfoForRootFile(file); + const scriptInfo = project.getScriptInfo(file); const startPosition = scriptInfo.lineOffsetToPosition(line, offset); const endPosition = scriptInfo.lineOffsetToPosition(endLine, endOffset); @@ -664,7 +664,7 @@ namespace ts.server { throw Errors.NoProject; } - const scriptInfo = project.lsHost.getScriptInfoForRootFile(file); + const scriptInfo = project.getScriptInfo(file); const position = scriptInfo.lineOffsetToPosition(line, offset); const formatOptions = this.projectService.getFormatCodeOptions(file); const edits = project.languageService.getFormattingEditsAfterKeystroke(file, position, key, @@ -737,7 +737,7 @@ namespace ts.server { throw Errors.NoProject; } - const scriptInfo = project.lsHost.getScriptInfoForRootFile(file); + const scriptInfo = project.getScriptInfo(file); const position = scriptInfo.lineOffsetToPosition(line, offset); const completions = project.languageService.getCompletionsAtPosition(file, position); @@ -761,7 +761,7 @@ namespace ts.server { throw Errors.NoProject; } - const scriptInfo = project.lsHost.getScriptInfoForRootFile(file); + const scriptInfo = project.getScriptInfo(file); const position = scriptInfo.lineOffsetToPosition(line, offset); return entryNames.reduce((accum: protocol.CompletionEntryDetails[], entryName: string) => { @@ -780,7 +780,7 @@ namespace ts.server { throw Errors.NoProject; } - const scriptInfo = project.lsHost.getScriptInfoForRootFile(file); + const scriptInfo = project.getScriptInfo(file); const position = scriptInfo.lineOffsetToPosition(line, offset); const helpItems = project.languageService.getSignatureHelpItems(file, position); if (!helpItems) { @@ -821,7 +821,7 @@ namespace ts.server { const file = ts.normalizePath(fileName); const project = this.projectService.getProjectForFile(file); if (project) { - const scriptInfo = project.lsHost.getScriptInfoForRootFile(file); + const scriptInfo = project.getScriptInfo(file); const start = scriptInfo.lineOffsetToPosition(line, offset); const end = scriptInfo.lineOffsetToPosition(endLine, endOffset); if (start >= 0) { @@ -839,7 +839,7 @@ namespace ts.server { if (project) { this.changeSeq++; // make sure no changes happen before this one is finished - project.lsHost.reloadScript(file, tmpfile, () => { + project.reloadScript(file, tmpfile, () => { this.output(undefined, CommandNames.Reload, reqSeq); }); } @@ -851,7 +851,7 @@ namespace ts.server { const project = this.projectService.getProjectForFile(file); if (project) { - project.lsHost.saveTo(file, tmpfile); + project.saveTo(file, tmpfile); } } @@ -868,7 +868,7 @@ namespace ts.server { return undefined; } - const scriptInfo = project.lsHost.getScriptInfoForRootFile(fileName); + const scriptInfo = project.getScriptInfo(fileName); return items.map(item => ({ text: item.text, @@ -915,7 +915,7 @@ namespace ts.server { } return navItems.map((navItem) => { - const scriptInfo = project.lsHost.getScriptInfoForRootFile(navItem.fileName); + const scriptInfo = project.getScriptInfo(navItem.fileName); const start = scriptInfo.positionToLineOffset(navItem.textSpan.start); const end = scriptInfo.positionToLineOffset(ts.textSpanEnd(navItem.textSpan)); const bakedItem: protocol.NavtoItem = { @@ -963,7 +963,7 @@ namespace ts.server { throw Errors.NoProject; } - const scriptInfo = project.lsHost.getScriptInfoForRootFile(fileName); + const scriptInfo = project.getScriptInfo(fileName); const position = scriptInfo.lineOffsetToPosition(line, offset); const spans = project.languageService.getBraceMatchingAtPosition(file, position);