From 1498676df4daffca9bfa6e0871d45be5750df4b2 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Fri, 3 Jun 2016 18:07:28 -0700 Subject: [PATCH] added delta computation --- src/server/editorServices.ts | 121 ++++++++++++++++++++++++++++++----- src/server/session.ts | 4 +- 2 files changed, 107 insertions(+), 18 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 0d16f3f0722..82c5dd70ab9 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -297,7 +297,7 @@ namespace ts.server { private readonly lsHost: LSHost; readonly languageService: LanguageService; - private program: ts.Program; + protected program: ts.Program; constructor( readonly projectKind: ProjectKind, @@ -453,7 +453,86 @@ namespace ts.server { } } - class ConfiguredProject extends Project { + interface Delta { + addedFiles: string[]; + removedFiles: string[]; + replacedFiles: string[]; + projectName: string; + version: number; + } + + const id = (x: any) => x; + + abstract class VersionedProject extends Project { + + private lastReportedFileNames: Map; + private lastReportedVersion: number = 0; + private currentVersion: number = 1; + + updateGraph() { + const oldProgram = this.program; + + super.updateGraph(); + + if (!oldProgram || !oldProgram.structureIsReused) { + this.currentVersion++; + } + } + + getDeltaFromVersion(lastKnownVersion?: number): Delta { + if (this.lastReportedVersion === this.currentVersion) { + return { + projectName: this.getProjectFileName(), + addedFiles: [], + removedFiles: [], + version: this.currentVersion, + replacedFiles: [] + }; + } + if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) { + const lastReportedFileNames = this.lastReportedFileNames; + const currentFiles = arrayToMap(this.getFileNames(), id); + + const addedFiles: string[] = []; + const removedFiles: string[] = []; + for (const id in currentFiles) { + if (hasProperty(currentFiles, id) && !hasProperty(lastReportedFileNames, id)) { + addedFiles.push(id); + } + } + for (const id in lastReportedFileNames) { + if (hasProperty(lastReportedFileNames, id) && !hasProperty(currentFiles, id)) { + removedFiles.push(id); + } + } + this.lastReportedFileNames = currentFiles; + + this.lastReportedFileNames = currentFiles; + this.lastReportedVersion = this.currentVersion; + return { + projectName: this.getProjectFileName(), + addedFiles, + removedFiles, + version: this.currentVersion, + replacedFiles: [] + }; + } + else { + // unknown version - return everything + const projectFileNames = this.getFileNames(); + this.lastReportedFileNames = arrayToMap(projectFileNames, id); + return { + projectName: this.getProjectFileName(), + addedFiles: [], + removedFiles: [], + version: this.currentVersion, + replacedFiles: projectFileNames + }; + } + } + } + + class ConfiguredProject extends VersionedProject { private projectFileWatcher: FileWatcher; private directoryWatcher: FileWatcher; /** Used for configured projects which may have multiple open roots */ @@ -498,7 +577,7 @@ namespace ts.server { } } - class ExternalProject extends Project { + class ExternalProject extends VersionedProject { constructor(readonly projectFileName: string, projectService: ProjectService, documentRegistry: ts.DocumentRegistry, compilerOptions: CompilerOptions) { super(ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, compilerOptions); } @@ -1261,28 +1340,38 @@ namespace ts.server { this.psLogger.endGroup(); } - loadExternalProject(externalProject: protocol.ExternalProject): Project { + loadExternalProject(externalProject: protocol.ExternalProject): Delta[] { const project = this.findConfiguredProjectByConfigFile(externalProject.projectFileName); if (project) { this.updateConfiguredProjectWorker(project, externalProject.rootFiles, externalProject.options); - return project; + return [project.getDeltaFromVersion()]; } else { - // check if root files contain tsconfig.json - // if yes - treat project as configured - const tsconfigFile = forEach(externalProject.rootFiles, f => getBaseFileName(f) === "tsconfig.json" && f); - if (tsconfigFile) { - const newRootFiles = copyListRemovingItem(tsconfigFile, externalProject.rootFiles); - const { success, project, errors } = this.openConfigFile(tsconfigFile); - if (success) { - // keep project alive - project.addOpenRef(); + let tsConfigFiles: string[]; + const rootFiles: string[] = []; + for (const file of externalProject.rootFiles) { + if (getBaseFileName(file) === "tsconfig.json") { + (tsConfigFiles || (tsConfigFiles = [])).push(file); } - return project; + else { + rootFiles.push(file); + } + } + if (tsConfigFiles) { + const deltas: Delta[] = []; + for (const tsconfigFile of tsConfigFiles) { + const { success, project, errors } = this.openConfigFile(tsconfigFile); + if (success) { + // keep project alive + project.addOpenRef(); + deltas.push(project.getDeltaFromVersion()); + } + } + return deltas; } else { const { project, errors } = this.createAndAddExternalProject(externalProject.projectFileName, externalProject.rootFiles, externalProject.options); - return project; + return [project.getDeltaFromVersion()]; } } } diff --git a/src/server/session.ts b/src/server/session.ts index de7d9ec059d..d943acc0ef1 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1029,8 +1029,8 @@ namespace ts.server { private handlers: Map<(request: protocol.Request) => { response?: any, responseRequired?: boolean }> = { [CommandNames.LoadExternalProject]: (request: protocol.Request) => { - const project = this.projectService.loadExternalProject(request.arguments); - return { responseRequired: true, response: { files: project.getFileNames() } }; + const deltas = this.projectService.loadExternalProject(request.arguments); + return { responseRequired: true, response: { files: deltas } }; }, [CommandNames.Exit]: () => { this.exit();