From df6f75bc705b297ebec01ffeabaf3b2f4361d371 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Jul 2017 14:43:00 -0700 Subject: [PATCH] Optimize wildcard watchers and config directory watching as now we have missing file watching as well We dont need to explicitly watch config file directory as it will be watched: - if there was no files specified, in wild card directories - if there were files specified as missing file (if the file wasnt present) --- src/server/editorServices.ts | 30 +++++----- src/server/project.ts | 104 ++++++++++++++++++++--------------- 2 files changed, 74 insertions(+), 60 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 328876e9c6f..e4e62e7dd68 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -611,7 +611,8 @@ namespace ts.server { * @param project the project that associates with this directory watcher * @param fileName the absolute file name that changed in watched directory */ - private onSourceFileInDirectoryChangedForConfiguredProject(project: ConfiguredProject, fileName: string) { + /* @internal */ + onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileName: string) { // If a change was made inside "folder/file", node will trigger the callback twice: // one with the fileName being "folder/file", and the other one with "folder". // We don't respond to the second one. @@ -623,10 +624,13 @@ namespace ts.server { this.throttledOperations.schedule( project.getConfigFilePath(), /*delay*/250, - () => this.handleChangeInSourceFileForConfiguredProject(project, fileName)); + () => this.handleFileAddOrRemoveInWatchedDirectoryOfProject(project, fileName)); } - private handleChangeInSourceFileForConfiguredProject(project: ConfiguredProject, triggerFile: string) { + private handleFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, triggerFile: string) { + // TODO: (sheetalkamat) this actually doesnt need to re-read the config file from the disk + // it just needs to update the file list of file names + // We might be able to do that by caching the info from first parse and add reusing this with the change in the file path const { projectOptions, configFileErrors } = this.convertConfigFileContentToProjectOptions(project.getConfigFilePath()); this.reportConfigFileDiagnostics(project.getProjectName(), configFileErrors, triggerFile); @@ -676,6 +680,10 @@ namespace ts.server { return; } + // TODO: (sheetalkamat) + // 1. We should only watch tsconfig/jsconfig file here instead of watching directory + // 2. We should try reloading projects with open files in Inferred project only + // 3. We should use this watcher to answer questions to findConfigFile rather than calling host everytime this.logger.info(`Detected newly added tsconfig file: ${fileName}`); this.reloadProjects(); } @@ -1117,17 +1125,13 @@ namespace ts.server { this.documentRegistry, projectOptions.configHasFilesProperty, projectOptions.compilerOptions, - projectOptions.wildcardDirectories, /*languageServiceEnabled*/ !sizeLimitExceeded, projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave); this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors); project.watchConfigFile((project, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind)); - if (!sizeLimitExceeded) { - this.watchConfigDirectoryForProject(project, projectOptions); - } - project.watchWildcards((project, path) => this.onSourceFileInDirectoryChangedForConfiguredProject(project, path)); + project.watchWildcards(projectOptions.wildcardDirectories); project.watchTypeRoots((project, path) => this.onTypeRootFileChanged(project, path)); this.configuredProjects.push(project); @@ -1135,12 +1139,6 @@ namespace ts.server { return project; } - private watchConfigDirectoryForProject(project: ConfiguredProject, options: ProjectOptions): void { - if (!options.configHasFilesProperty) { - project.watchConfigDirectory((project, path) => this.onSourceFileInDirectoryChangedForConfiguredProject(project, path)); - } - } - private addFilesToProjectAndUpdateGraph(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader, clientFileName: string, typeAcquisition: TypeAcquisition, configFileErrors: Diagnostic[]): void { for (const f of files) { const rootFilename = propertyReader.getFileName(f); @@ -1271,12 +1269,12 @@ namespace ts.server { project.setProjectErrors(configFileErrors); } project.disableLanguageService(); - project.stopWatchingDirectory(); + project.stopWatchingWildCards(); project.setProjectErrors(configFileErrors); } else { project.enableLanguageService(); - this.watchConfigDirectoryForProject(project, projectOptions); + project.watchWildcards(projectOptions.wildcardDirectories); this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave, configFileErrors); } } diff --git a/src/server/project.ts b/src/server/project.ts index ebafad825a9..0851b4c64b6 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -957,6 +957,8 @@ namespace ts.server { } } + type WildCardDirectoryWatchers = { watcher: FileWatcher, recursive: boolean }; + /** * If a file is opened, the server will look for a tsconfig (or jsconfig) * and if successfull create a ConfiguredProject for it. @@ -964,9 +966,8 @@ namespace ts.server { */ export class ConfiguredProject extends Project { private typeAcquisition: TypeAcquisition; - private projectFileWatcher: FileWatcher; - private directoryWatcher: FileWatcher; - private directoriesWatchedForWildcards: Map; + private configFileWatcher: FileWatcher; + private directoriesWatchedForWildcards: Map; private typeRootsWatchers: FileWatcher[]; readonly canonicalConfigFilePath: NormalizedPath; @@ -980,7 +981,6 @@ namespace ts.server { documentRegistry: ts.DocumentRegistry, hasExplicitListOfFiles: boolean, compilerOptions: CompilerOptions, - private wildcardDirectories: Map, languageServiceEnabled: boolean, public compileOnSaveEnabled: boolean) { super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled); @@ -1098,7 +1098,7 @@ namespace ts.server { } watchConfigFile(callback: (project: ConfiguredProject, eventKind: FileWatcherEventKind) => void) { - this.projectFileWatcher = this.projectService.host.watchFile(this.getConfigFilePath(), (_fileName, eventKind) => callback(this, eventKind)); + this.configFileWatcher = this.projectService.host.watchFile(this.getConfigFilePath(), (_fileName, eventKind) => callback(this, eventKind)); } watchTypeRoots(callback: (project: ConfiguredProject, path: string) => void) { @@ -1111,49 +1111,70 @@ namespace ts.server { this.typeRootsWatchers = watchers; } - watchConfigDirectory(callback: (project: ConfiguredProject, path: string) => void) { - if (this.directoryWatcher) { - return; - } - - const directoryToWatch = getDirectoryPath(this.getConfigFilePath()); - this.projectService.logger.info(`Add recursive watcher for: ${directoryToWatch}`); - this.directoryWatcher = this.projectService.host.watchDirectory(directoryToWatch, path => callback(this, path), /*recursive*/ true); - } - - watchWildcards(callback: (project: ConfiguredProject, path: string) => void) { - if (!this.wildcardDirectories) { - return; - } - const configDirectoryPath = getDirectoryPath(this.getConfigFilePath()); - - this.directoriesWatchedForWildcards = createMap(); - this.wildcardDirectories.forEach((flag, directory) => { - if (comparePaths(configDirectoryPath, directory, ".", !this.projectService.host.useCaseSensitiveFileNames) !== Comparison.EqualTo) { - const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; - this.projectService.logger.info(`Add ${recursive ? "recursive " : ""}watcher for: ${directory}`); - this.directoriesWatchedForWildcards.set(directory, this.projectService.host.watchDirectory( + private addWatcherForDirectory(flag: WatchDirectoryFlags, directory: string, replaceExisting: boolean) { + if (replaceExisting || !this.directoriesWatchedForWildcards.has(directory)) { + const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0; + this.projectService.logger.info(`Add ${recursive ? "recursive " : ""} watcher for: ${directory}`); + this.directoriesWatchedForWildcards.set(directory, { + watcher: this.projectService.host.watchDirectory( directory, - path => callback(this, path), + path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path), recursive - )); - } - }); + ), + recursive + }); + } } - stopWatchingDirectory() { - if (this.directoryWatcher) { - this.directoryWatcher.close(); - this.directoryWatcher = undefined; + watchWildcards(wildcardDirectories: Map) { + if (wildcardDirectories) { + if (this.directoriesWatchedForWildcards) { + this.directoriesWatchedForWildcards.forEach(({ watcher, recursive }, directory) => { + const currentFlag = wildcardDirectories.get(directory); + // Remove already watching wild card if it isnt in updated map + if (currentFlag === undefined) { + this.projectService.logger.info(`Removing ${recursive ? "recursive " : ""} watcher for: ${directory}`); + watcher.close(); + this.directoriesWatchedForWildcards.delete(directory); + } + // Or if the recursive doesnt match (add the updated one here) + else { + const currentRecursive = (currentFlag & WatchDirectoryFlags.Recursive) !== 0; + if (currentRecursive !== recursive) { + this.projectService.logger.info(`Removing ${recursive ? "recursive " : ""} watcher for: ${directory}`); + watcher.close(); + this.addWatcherForDirectory(currentFlag, directory, /*replaceExisting*/ true); + } + } + }); + } + else { + this.directoriesWatchedForWildcards = createMap(); + } + wildcardDirectories.forEach((flag, directory) => + this.addWatcherForDirectory(flag, directory, /*replaceExisting*/ false)); + } + else { + this.stopWatchingWildCards(); + } + } + + stopWatchingWildCards() { + if (this.directoriesWatchedForWildcards) { + this.directoriesWatchedForWildcards.forEach(({ watcher, recursive }, directory) => { + this.projectService.logger.info(`Removing ${recursive ? "recursive " : ""} watcher for: ${directory}`); + watcher.close(); + }); + this.directoriesWatchedForWildcards = undefined; } } close() { super.close(); - if (this.projectFileWatcher) { - this.projectFileWatcher.close(); - this.projectFileWatcher = undefined; + if (this.configFileWatcher) { + this.configFileWatcher.close(); + this.configFileWatcher = undefined; } if (this.typeRootsWatchers) { @@ -1163,12 +1184,7 @@ namespace ts.server { this.typeRootsWatchers = undefined; } - this.directoriesWatchedForWildcards.forEach(watcher => { - watcher.close(); - }); - this.directoriesWatchedForWildcards = undefined; - - this.stopWatchingDirectory(); + this.stopWatchingWildCards(); } addOpenRef() {