From 2612a74f633cd74fd0ed32cd1b5cfc813762dff0 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Mon, 19 Sep 2016 16:47:15 -0700 Subject: [PATCH] Working version --- src/server/editorServices.ts | 21 +++++++++++++++++++-- src/server/lsHost.ts | 11 +++++++++++ src/server/project.ts | 26 ++++++++++++++++++++++++++ src/server/utilities.ts | 4 ++++ src/services/services.ts | 13 +++++++++++++ src/services/shims.ts | 5 +++++ 6 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 34e438d37f0..0596dffa680 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -363,6 +363,10 @@ namespace ts.server { this.printProjects(); } + private onTypeRootFileChanged(project: ConfiguredProject, fileName: string) { + this.onSourceFileInDirectoryChangedForConfiguredProject(project, fileName); + } + /** * This is the callback function when a watched directory has added or removed source code files. * @param project the project that associates with this directory watcher @@ -389,17 +393,29 @@ namespace ts.server { const newRootFiles = projectOptions.files.map((f => this.getCanonicalFileName(f))); const currentRootFiles = project.getRootFiles().map((f => this.getCanonicalFileName(f))); + const lastUpdateTypesRoot: number = Math.max.apply(Math, project.getEffectiveTypeRoots().map(root => { + this.logger.info('Compute for ' + root); + if (this.host.directoryExists(root)) { + return +this.host.getModifiedTime(root); + } + return 0; + })); + this.logger.info('Last type roots update = ' + lastUpdateTypesRoot + ', last was ' + project.lastUpdatedTypesRootTime); // We check if the project file list has changed. If so, we update the project. - if (!arrayIsEqualTo(currentRootFiles.sort(), newRootFiles.sort())) { + if (!arrayIsEqualTo(currentRootFiles.sort(), newRootFiles.sort()) || (lastUpdateTypesRoot > project.lastUpdatedTypesRootTime)) { // For configured projects, the change is made outside the tsconfig file, and // it is not likely to affect the project for other files opened by the client. We can // just update the current project. + + this.logger.info('Updating configured project'); this.updateConfiguredProject(project); // Call refreshInferredProjects to clean up inferred projects we may have // created for the new files this.refreshInferredProjects(); + + project.lastUpdatedTypesRootTime = lastUpdateTypesRoot; } } @@ -771,13 +787,14 @@ namespace ts.server { this.watchConfigDirectoryForProject(project, projectOptions); } project.watchWildcards((project, path) => this.onSourceFileInDirectoryChangedForConfiguredProject(project, path)); + project.watchTypeRoots((project, path) => this.onTypeRootFileChanged(project, path)); this.configuredProjects.push(project); return project; } private watchConfigDirectoryForProject(project: ConfiguredProject, options: ProjectOptions): void { - if (!options.configHasFilesProperty) { + if (!options.configHasFilesProperty || (options.compilerOptions.types === undefined)) { project.watchConfigDirectory((project, path) => this.onSourceFileInDirectoryChangedForConfiguredProject(project, path)); } } diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index f6530953a58..e6fc699f23e 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -145,6 +145,17 @@ namespace ts.server { return this.project.getRootFilesLSHost(); } + getTypeRootsVersion() { + const roots = ts.getEffectiveTypeRoots(this.project.getCompilerOptions(), this); + if (roots && roots.length > 0) { + return Math.max.apply(Math, roots.map(root => { + return +this.host.getModifiedTime(root); + })); + } else { + return 0; + } + } + getScriptKind(fileName: string) { const info = this.project.getScriptInfoLSHost(fileName); return info && info.scriptKind; diff --git a/src/server/project.ts b/src/server/project.ts index 9b4c7619fbb..17d33b04138 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -569,9 +569,11 @@ namespace ts.server { private projectFileWatcher: FileWatcher; private directoryWatcher: FileWatcher; private directoriesWatchedForWildcards: Map; + private typeRootsWatchers: FileWatcher[]; /** Used for configured projects which may have multiple open roots */ openRefCount = 0; + lastUpdatedTypesRootTime = 0; constructor(readonly configFileName: NormalizedPath, projectService: ProjectService, @@ -608,6 +610,19 @@ namespace ts.server { this.projectFileWatcher = this.projectService.host.watchFile(this.configFileName, _ => callback(this)); } + watchTypeRoots(callback: (project: ConfiguredProject, path: string) => void) { + const roots = ts.getEffectiveTypeRoots(this.getCompilerOptions(), this.projectService.host); + this.projectService.logger.info(`Add type roots watchers for: ${roots}`); + const watchers: FileWatcher[] = []; + if (roots) { + for (const root of roots) { + this.projectService.logger.info(`Add type root watcher for: ${root}`); + watchers.push(this.projectService.host.watchDirectory(root, path => callback(this, path), true)); + } + } + this.typeRootsWatchers = watchers; + } + watchConfigDirectory(callback: (project: ConfiguredProject, path: string) => void) { if (this.directoryWatcher) { return; @@ -651,6 +666,13 @@ namespace ts.server { this.projectFileWatcher.close(); } + if (this.typeRootsWatchers) { + for (const watcher of this.typeRootsWatchers) { + watcher.close(); + } + this.typeRootsWatchers = undefined; + } + for (const id in this.directoriesWatchedForWildcards) { this.directoriesWatchedForWildcards[id].close(); } @@ -667,6 +689,10 @@ namespace ts.server { this.openRefCount--; return this.openRefCount; } + + getEffectiveTypeRoots() { + return ts.getEffectiveTypeRoots(this.getCompilerOptions(), this.projectService.host); + } } export class ExternalProject extends Project { diff --git a/src/server/utilities.ts b/src/server/utilities.ts index a7a0160ea62..1814e5920e2 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -216,6 +216,10 @@ namespace ts.server { * true if config file explicitly listed files **/ configHasFilesProperty?: boolean; + /** + * true if config file explicitly listed type names + **/ + configHasTypesProperty?: boolean; /** * these fields can be present in the project file **/ diff --git a/src/services/services.ts b/src/services/services.ts index 8c233c56982..e369aefa4bd 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1185,6 +1185,11 @@ namespace ts { readFile?(path: string, encoding?: string): string; fileExists?(path: string): boolean; + /* + * LS host can optionally implement these methods to support automatic updating when new type libraries are installed + */ + getTypeRootsVersion?(): number; + /* * LS host can optionally implement this method if it wants to be completely in charge of module name resolution. * if implementation is omitted then language service will use built-in module resolution logic and get answers to @@ -3099,6 +3104,7 @@ namespace ts { let ruleProvider: formatting.RulesProvider; let program: Program; let lastProjectVersion: string; + let lastTypesRootVersion = 0; const useCaseSensitivefileNames = host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(); const cancellationToken = new CancellationTokenObject(host.getCancellationToken && host.getCancellationToken()); @@ -3215,6 +3221,13 @@ namespace ts { }; } + const typeRootsVersion = host.getTypeRootsVersion ? host.getTypeRootsVersion() : 0; + if (lastTypesRootVersion !== typeRootsVersion) { + log('TypeRoots version has changed; provide new program'); + program = undefined; + lastTypesRootVersion = typeRootsVersion; + } + const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings); const newProgram = createProgram(hostCache.getRootFileNames(), newSettings, compilerHost, program); diff --git a/src/services/shims.ts b/src/services/shims.ts index f107809f3d5..e5646254518 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -67,6 +67,7 @@ namespace ts { getProjectVersion?(): string; useCaseSensitiveFileNames?(): boolean; + getTypeRootsVersion?(): number; readDirectory(rootDir: string, extension: string, basePaths?: string, excludeEx?: string, includeFileEx?: string, includeDirEx?: string, depth?: number): string; readFile(path: string, encoding?: string): string; fileExists(path: string): boolean; @@ -358,6 +359,10 @@ namespace ts { return this.shimHost.getProjectVersion(); } + public getTypeRootsVersion(): number { + return this.shimHost.getTypeRootsVersion(); + } + public useCaseSensitiveFileNames(): boolean { return this.shimHost.useCaseSensitiveFileNames ? this.shimHost.useCaseSensitiveFileNames() : false; }