mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-11 10:00:13 -06:00
Logging of the watch add/remove/event
This commit is contained in:
parent
0365901381
commit
404aa8f0be
@ -303,6 +303,31 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export const enum WatchType {
|
||||
ConfigFilePath = "Config file for the program",
|
||||
MissingFilePath = "Missing file from program",
|
||||
WildCardDirectories = "Wild card directory",
|
||||
TypeRoot = "Type root of the project",
|
||||
ClosedScriptInfo = "Closed Script info"
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export const enum WatcherCloseReason {
|
||||
ProjectClose = "Project close",
|
||||
NotNeeded = "After project update isnt required any more",
|
||||
FileCreated = "File got created",
|
||||
RecursiveChanged = "Recursive changed for the watch",
|
||||
ProjectReloadHitMaxSize = "Project reloaded and hit the max file size capacity",
|
||||
OrphanScriptInfoWithChange = "Orphan script info, Detected change in file thats not needed any more",
|
||||
OrphanScriptInfo = "Removing Orphan script info as part of cleanup",
|
||||
FileDeleted = "File was deleted",
|
||||
FileOpened = "File opened"
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export type ServerDirectoryWatcherCallback = (path: NormalizedPath) => void;
|
||||
|
||||
export interface ProjectServiceOptions {
|
||||
host: ServerHost;
|
||||
logger: Logger;
|
||||
@ -599,7 +624,7 @@ namespace ts.server {
|
||||
if (!info.isScriptOpen()) {
|
||||
if (info.containingProjects.length === 0) {
|
||||
// Orphan script info, remove it as we can always reload it on next open file request
|
||||
info.stopWatcher();
|
||||
this.stopWatchingScriptInfo(info, WatcherCloseReason.OrphanScriptInfoWithChange);
|
||||
this.filenameToScriptInfo.delete(info.path);
|
||||
}
|
||||
else {
|
||||
@ -613,9 +638,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private handleDeletedFile(info: ScriptInfo) {
|
||||
this.logger.info(`${info.fileName} deleted`);
|
||||
|
||||
info.stopWatcher();
|
||||
this.stopWatchingScriptInfo(info, WatcherCloseReason.FileDeleted);
|
||||
|
||||
// TODO: handle isOpen = true case
|
||||
|
||||
@ -644,7 +667,8 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
private onTypeRootFileChanged(project: ConfiguredProject, fileName: NormalizedPath) {
|
||||
/* @internal */
|
||||
onTypeRootFileChanged(project: ConfiguredProject, fileName: NormalizedPath) {
|
||||
this.logger.info(`Type root file ${fileName} changed`);
|
||||
project.getCachedServerHost().addOrDeleteFileOrFolder(fileName);
|
||||
project.updateTypes();
|
||||
@ -667,10 +691,10 @@ namespace ts.server {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.info(`Detected source file changes: ${fileName}`);
|
||||
const configFilename = project.getConfigFilePath();
|
||||
this.logger.info(`Project: ${configFilename} Detected source file add/remove: ${fileName}`);
|
||||
|
||||
const configFileSpecs = project.configFileSpecs;
|
||||
const configFilename = normalizePath(project.getConfigFilePath());
|
||||
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), project.getCachedServerHost(), this.hostConfiguration.extraFileExtensions);
|
||||
const errors = project.getAllProjectErrors();
|
||||
if (result.fileNames.length === 0) {
|
||||
@ -693,9 +717,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) {
|
||||
const configFileName = project.getConfigFilePath();
|
||||
if (eventKind === FileWatcherEventKind.Deleted) {
|
||||
this.logger.info(`Config file deleted: ${configFileName}`);
|
||||
this.removeProject(project);
|
||||
// Reload the configured projects for these open files in the project as
|
||||
// they could be held up by another config file somewhere in the parent directory
|
||||
@ -704,7 +726,6 @@ namespace ts.server {
|
||||
this.delayInferredProjectsRefresh();
|
||||
}
|
||||
else {
|
||||
this.logger.info(`Config file changed: ${configFileName}`);
|
||||
project.pendingReload = true;
|
||||
this.delayUpdateProjectGraphAndInferredProjectsRefresh(project);
|
||||
}
|
||||
@ -894,7 +915,7 @@ namespace ts.server {
|
||||
this.filenameToScriptInfo.forEach(info => {
|
||||
if (!info.isScriptOpen() && info.containingProjects.length === 0) {
|
||||
// if there are not projects that include this script info - delete it
|
||||
info.stopWatcher();
|
||||
this.stopWatchingScriptInfo(info, WatcherCloseReason.OrphanScriptInfo);
|
||||
this.filenameToScriptInfo.delete(info.path);
|
||||
}
|
||||
});
|
||||
@ -1155,22 +1176,26 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, configFileErrors: Diagnostic[], configFileSpecs: ConfigFileSpecs, cachedServerHost: CachedServerHost, clientFileName?: string) {
|
||||
const sizeLimitExceeded = this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader);
|
||||
const languageServiceEnabled = !this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader);
|
||||
const project = new ConfiguredProject(
|
||||
configFileName,
|
||||
this,
|
||||
this.documentRegistry,
|
||||
projectOptions.configHasFilesProperty,
|
||||
projectOptions.compilerOptions,
|
||||
/*languageServiceEnabled*/ !sizeLimitExceeded,
|
||||
languageServiceEnabled,
|
||||
projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave,
|
||||
cachedServerHost);
|
||||
this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors);
|
||||
|
||||
project.configFileSpecs = configFileSpecs;
|
||||
project.watchConfigFile((project, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind));
|
||||
project.watchWildcards(projectOptions.wildcardDirectories);
|
||||
project.watchTypeRoots((project, path) => this.onTypeRootFileChanged(project, path));
|
||||
project.configFileWatcher = this.addFileWatcher(WatchType.ConfigFilePath, project,
|
||||
configFileName, (_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind)
|
||||
);
|
||||
if (languageServiceEnabled) {
|
||||
project.watchWildcards(projectOptions.wildcardDirectories);
|
||||
project.watchTypeRoots();
|
||||
}
|
||||
|
||||
this.configuredProjects.push(project);
|
||||
this.sendProjectTelemetry(project.getConfigFilePath(), project, projectOptions);
|
||||
@ -1314,11 +1339,13 @@ namespace ts.server {
|
||||
private updateConfiguredProject(project: ConfiguredProject, projectOptions: ProjectOptions, configFileErrors: Diagnostic[]) {
|
||||
if (this.exceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader)) {
|
||||
project.disableLanguageService();
|
||||
project.stopWatchingWildCards();
|
||||
project.stopWatchingWildCards(WatcherCloseReason.ProjectReloadHitMaxSize);
|
||||
project.stopWatchingTypeRoots(WatcherCloseReason.ProjectReloadHitMaxSize);
|
||||
}
|
||||
else {
|
||||
project.enableLanguageService();
|
||||
project.watchWildcards(projectOptions.wildcardDirectories);
|
||||
project.watchTypeRoots();
|
||||
}
|
||||
this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave, configFileErrors);
|
||||
}
|
||||
@ -1357,11 +1384,21 @@ namespace ts.server {
|
||||
return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
|
||||
}
|
||||
|
||||
watchClosedScriptInfo(info: ScriptInfo) {
|
||||
private watchClosedScriptInfo(info: ScriptInfo) {
|
||||
Debug.assert(!info.fileWatcher);
|
||||
// do not watch files with mixed content - server doesn't know how to interpret it
|
||||
if (!info.hasMixedContent) {
|
||||
const { fileName } = info;
|
||||
info.setWatcher(this.host.watchFile(fileName, (_fileName, eventKind) => this.onSourceFileChanged(fileName, eventKind)));
|
||||
info.fileWatcher = this.addFileWatcher(WatchType.ClosedScriptInfo, /*project*/ undefined, fileName,
|
||||
(_fileName, eventKind) => this.onSourceFileChanged(fileName, eventKind)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private stopWatchingScriptInfo(info: ScriptInfo, reason: WatcherCloseReason) {
|
||||
if (info.fileWatcher) {
|
||||
this.closeFileWatcher(WatchType.ClosedScriptInfo, /*project*/ undefined, info.fileName, info.fileWatcher, reason);
|
||||
info.fileWatcher = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1387,7 +1424,7 @@ namespace ts.server {
|
||||
}
|
||||
if (info) {
|
||||
if (openedByClient && !info.isScriptOpen()) {
|
||||
info.stopWatcher();
|
||||
this.stopWatchingScriptInfo(info, WatcherCloseReason.FileOpened);
|
||||
info.open(fileContent);
|
||||
if (hasMixedContent) {
|
||||
info.registerFileUpdate();
|
||||
@ -1408,7 +1445,6 @@ namespace ts.server {
|
||||
return this.filenameToScriptInfo.get(fileName);
|
||||
}
|
||||
|
||||
|
||||
setHostConfiguration(args: protocol.ConfigureRequestArguments) {
|
||||
if (args.file) {
|
||||
const info = this.getScriptInfoForNormalizedPath(toNormalizedPath(args.file));
|
||||
@ -1436,6 +1472,37 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
closeFileWatcher(watchType: WatchType, project: Project, file: string, watcher: FileWatcher, reason: WatcherCloseReason) {
|
||||
this.logger.info(`FileWatcher:: Close: ${file} Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType} Reason: ${reason}`);
|
||||
watcher.close();
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
addFileWatcher(watchType: WatchType, project: Project, file: string, cb: FileWatcherCallback) {
|
||||
this.logger.info(`FileWatcher:: Added: ${file} Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`);
|
||||
return this.host.watchFile(file, (fileName, eventKind) => {
|
||||
this.logger.info(`FileWatcher:: File ${FileWatcherEventKind[eventKind]}: ${file} Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`);
|
||||
cb(fileName, eventKind);
|
||||
});
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
closeDirectoryWatcher(watchType: WatchType, project: Project, directory: string, watcher: FileWatcher, recursive: boolean, reason: WatcherCloseReason) {
|
||||
this.logger.info(`DirectoryWatcher ${recursive ? "recursive" : ""}:: Close: ${directory} Project: ${project.getProjectName()} WatchType: ${watchType} Reason: ${reason}`);
|
||||
watcher.close();
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
addDirectoryWatcher(watchType: WatchType, project: Project, directory: string, cb: ServerDirectoryWatcherCallback, recursive: boolean) {
|
||||
this.logger.info(`DirectoryWatcher ${recursive ? "recursive" : ""}:: Added: ${directory} Project: ${project.getProjectName()} WatchType: ${watchType}`);
|
||||
return this.host.watchDirectory(directory, fileName => {
|
||||
const path = toNormalizedPath(getNormalizedAbsolutePath(fileName, directory));
|
||||
this.logger.info(`DirectoryWatcher:: EventOn: ${directory} Trigger: ${fileName} Path: ${path} Project: ${project.getProjectName()} WatchType: ${watchType}`);
|
||||
cb(path);
|
||||
}, recursive);
|
||||
}
|
||||
|
||||
closeLog() {
|
||||
this.logger.close();
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ namespace ts.server {
|
||||
private rootFilesMap: Map<ProjectRoot> = createMap<ProjectRoot>();
|
||||
private program: Program;
|
||||
private externalFiles: SortedReadonlyArray<string>;
|
||||
private missingFilesMap: Map<FileWatcher> = createMap<FileWatcher>();
|
||||
private missingFilesMap: Map<FileWatcher>;
|
||||
|
||||
private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap();
|
||||
private lastCachedUnresolvedImportsList: SortedReadonlyArray<string>;
|
||||
@ -327,7 +327,9 @@ namespace ts.server {
|
||||
this.lsHost = undefined;
|
||||
|
||||
// Clean up file watchers waiting for missing files
|
||||
this.missingFilesMap.forEach(fileWatcher => fileWatcher.close());
|
||||
cleanExistingMap(this.missingFilesMap, (missingFilePath, fileWatcher) => {
|
||||
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.ProjectClose);
|
||||
});
|
||||
this.missingFilesMap = undefined;
|
||||
|
||||
// signal language service to release source files acquired from document registry
|
||||
@ -639,38 +641,38 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
const missingFilePaths = this.program.getMissingFilePaths();
|
||||
const missingFilePathsSet = arrayToSet(missingFilePaths);
|
||||
const newMissingFilePathMap = arrayToSet(missingFilePaths);
|
||||
// Update the missing file paths watcher
|
||||
this.missingFilesMap = mutateExistingMapWithNewSet(
|
||||
this.missingFilesMap, newMissingFilePathMap,
|
||||
// Watch the missing files
|
||||
missingFilePath => {
|
||||
const fileWatcher = this.projectService.addFileWatcher(
|
||||
WatchType.MissingFilePath, this, missingFilePath,
|
||||
(filename: string, eventKind: FileWatcherEventKind) => {
|
||||
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
|
||||
this.missingFilesMap.delete(missingFilePath);
|
||||
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.FileCreated);
|
||||
|
||||
// Files that are no longer missing (e.g. because they are no longer required)
|
||||
// should no longer be watched.
|
||||
this.missingFilesMap.forEach((fileWatcher, missingFilePath) => {
|
||||
if (!missingFilePathsSet.has(missingFilePath)) {
|
||||
this.missingFilesMap.delete(missingFilePath);
|
||||
fileWatcher.close();
|
||||
}
|
||||
});
|
||||
if (this.projectKind === ProjectKind.Configured) {
|
||||
const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath));
|
||||
(this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath));
|
||||
}
|
||||
|
||||
// Missing files that are not yet watched should be added to the map.
|
||||
for (const missingFilePath of missingFilePaths) {
|
||||
if (!this.missingFilesMap.has(missingFilePath)) {
|
||||
const fileWatcher = this.projectService.host.watchFile(missingFilePath, (filename: string, eventKind: FileWatcherEventKind) => {
|
||||
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
|
||||
fileWatcher.close();
|
||||
this.missingFilesMap.delete(missingFilePath);
|
||||
|
||||
if (this.projectKind === ProjectKind.Configured) {
|
||||
const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath));
|
||||
(this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath));
|
||||
// When a missing file is created, we should update the graph.
|
||||
this.markAsDirty();
|
||||
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
|
||||
}
|
||||
|
||||
// When a missing file is created, we should update the graph.
|
||||
this.markAsDirty();
|
||||
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
|
||||
}
|
||||
});
|
||||
this.missingFilesMap.set(missingFilePath, fileWatcher);
|
||||
);
|
||||
return fileWatcher;
|
||||
},
|
||||
// Files that are no longer missing (e.g. because they are no longer required)
|
||||
// should no longer be watched.
|
||||
(missingFilePath, fileWatcher) => {
|
||||
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const oldExternalFiles = this.externalFiles || emptyArray as SortedReadonlyArray<string>;
|
||||
@ -694,7 +696,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
isWatchedMissingFile(path: Path) {
|
||||
return this.missingFilesMap.has(path);
|
||||
return this.missingFilesMap && this.missingFilesMap.has(path);
|
||||
}
|
||||
|
||||
getScriptInfoLSHost(fileName: string) {
|
||||
@ -976,9 +978,10 @@ namespace ts.server {
|
||||
*/
|
||||
export class ConfiguredProject extends Project {
|
||||
private typeAcquisition: TypeAcquisition;
|
||||
private configFileWatcher: FileWatcher;
|
||||
/* @internal */
|
||||
configFileWatcher: FileWatcher;
|
||||
private directoriesWatchedForWildcards: Map<WildCardDirectoryWatchers>;
|
||||
private typeRootsWatchers: FileWatcher[];
|
||||
private typeRootsWatchers: Map<FileWatcher>;
|
||||
readonly canonicalConfigFilePath: NormalizedPath;
|
||||
|
||||
/* @internal */
|
||||
@ -1023,7 +1026,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getConfigFilePath() {
|
||||
return this.getProjectName();
|
||||
return asNormalizedPath(this.getProjectName());
|
||||
}
|
||||
|
||||
enablePlugins() {
|
||||
@ -1131,94 +1134,83 @@ namespace ts.server {
|
||||
}));
|
||||
}
|
||||
|
||||
watchConfigFile(callback: (project: ConfiguredProject, eventKind: FileWatcherEventKind) => void) {
|
||||
this.configFileWatcher = this.projectService.host.watchFile(this.getConfigFilePath(), (_fileName, eventKind) => callback(this, eventKind));
|
||||
}
|
||||
|
||||
watchTypeRoots(callback: (project: ConfiguredProject, path: NormalizedPath) => void) {
|
||||
const roots = this.getEffectiveTypeRoots();
|
||||
const watchers: FileWatcher[] = [];
|
||||
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, toNormalizedPath(getNormalizedAbsolutePath(path, root))), /*recursive*/ false));
|
||||
}
|
||||
this.typeRootsWatchers = watchers;
|
||||
}
|
||||
|
||||
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 => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, toNormalizedPath(getNormalizedAbsolutePath(path, directory))),
|
||||
recursive
|
||||
),
|
||||
recursive
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
watchWildcards(wildcardDirectories: Map<WatchDirectoryFlags>) {
|
||||
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<WildCardDirectoryWatchers>();
|
||||
}
|
||||
wildcardDirectories.forEach((flag, directory) =>
|
||||
this.addWatcherForDirectory(flag, directory, /*replaceExisting*/ false));
|
||||
}
|
||||
else {
|
||||
this.stopWatchingWildCards();
|
||||
}
|
||||
this.directoriesWatchedForWildcards = mutateExistingMap(
|
||||
this.directoriesWatchedForWildcards, wildcardDirectories,
|
||||
// Watcher is same if the recursive flags match
|
||||
({ recursive: existingRecursive }, flag) => {
|
||||
// If the recursive dont match, it needs update
|
||||
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
|
||||
return existingRecursive !== recursive;
|
||||
},
|
||||
// Create new watch
|
||||
(directory, flag) => {
|
||||
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
|
||||
return {
|
||||
watcher: this.projectService.addDirectoryWatcher(
|
||||
WatchType.WildCardDirectories, this, directory,
|
||||
path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path),
|
||||
recursive
|
||||
),
|
||||
recursive
|
||||
};
|
||||
},
|
||||
// Close existing watch thats not needed any more
|
||||
(directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher(
|
||||
WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.NotNeeded
|
||||
),
|
||||
// Close existing watch that doesnt match in recursive flag
|
||||
(directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher(
|
||||
WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.RecursiveChanged
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
stopWatchingWildCards(reason: WatcherCloseReason) {
|
||||
cleanExistingMap(
|
||||
this.directoriesWatchedForWildcards,
|
||||
(directory, { watcher, recursive }) =>
|
||||
this.projectService.closeDirectoryWatcher(WatchType.WildCardDirectories, this,
|
||||
directory, watcher, recursive, reason)
|
||||
);
|
||||
this.directoriesWatchedForWildcards = undefined;
|
||||
}
|
||||
|
||||
watchTypeRoots() {
|
||||
const newTypeRoots = arrayToSet(this.getEffectiveTypeRoots(), dir => this.projectService.toCanonicalFileName(dir));
|
||||
this.typeRootsWatchers = mutateExistingMapWithNewSet(
|
||||
this.typeRootsWatchers, newTypeRoots,
|
||||
// Create new watch
|
||||
root => this.projectService.addDirectoryWatcher(WatchType.TypeRoot, this, root,
|
||||
path => this.projectService.onTypeRootFileChanged(this, path), /*recursive*/ false
|
||||
),
|
||||
// Close existing watch thats not needed any more
|
||||
(directory, watcher) => this.projectService.closeDirectoryWatcher(
|
||||
WatchType.TypeRoot, this, directory, watcher, /*recursive*/ false, WatcherCloseReason.NotNeeded
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
stopWatchingTypeRoots(reason: WatcherCloseReason) {
|
||||
cleanExistingMap(
|
||||
this.typeRootsWatchers,
|
||||
(directory, watcher) =>
|
||||
this.projectService.closeDirectoryWatcher(WatchType.TypeRoot, this,
|
||||
directory, watcher, /*recursive*/ false, reason)
|
||||
);
|
||||
this.typeRootsWatchers = undefined;
|
||||
}
|
||||
|
||||
close() {
|
||||
super.close();
|
||||
|
||||
if (this.configFileWatcher) {
|
||||
this.configFileWatcher.close();
|
||||
this.projectService.closeFileWatcher(WatchType.ConfigFilePath, this, this.getConfigFilePath(), this.configFileWatcher, WatcherCloseReason.ProjectClose);
|
||||
this.configFileWatcher = undefined;
|
||||
}
|
||||
|
||||
if (this.typeRootsWatchers) {
|
||||
for (const watcher of this.typeRootsWatchers) {
|
||||
watcher.close();
|
||||
}
|
||||
this.typeRootsWatchers = undefined;
|
||||
}
|
||||
|
||||
this.stopWatchingWildCards();
|
||||
this.stopWatchingTypeRoots(WatcherCloseReason.ProjectClose);
|
||||
this.stopWatchingWildCards(WatcherCloseReason.ProjectClose);
|
||||
}
|
||||
|
||||
addOpenRef() {
|
||||
|
||||
@ -149,7 +149,8 @@ namespace ts.server {
|
||||
readonly containingProjects: Project[] = [];
|
||||
private formatCodeSettings: FormatCodeSettings;
|
||||
|
||||
private fileWatcher: FileWatcher;
|
||||
/* @internal */
|
||||
fileWatcher: FileWatcher;
|
||||
private textStorage: TextStorage;
|
||||
|
||||
private isOpen: boolean;
|
||||
@ -291,18 +292,6 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
setWatcher(watcher: FileWatcher): void {
|
||||
this.stopWatcher();
|
||||
this.fileWatcher = watcher;
|
||||
}
|
||||
|
||||
stopWatcher() {
|
||||
if (this.fileWatcher) {
|
||||
this.fileWatcher.close();
|
||||
this.fileWatcher = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
getLatestVersion() {
|
||||
return this.textStorage.getVersion();
|
||||
}
|
||||
|
||||
@ -226,6 +226,79 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
export function cleanExistingMap<T>(
|
||||
existingMap: Map<T>,
|
||||
onDeleteExistingValue: (key: string, existingValue: T) => void) {
|
||||
if (existingMap) {
|
||||
// Remove all
|
||||
existingMap.forEach((existingValue, key) => {
|
||||
existingMap.delete(key);
|
||||
onDeleteExistingValue(key, existingValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function mutateExistingMapWithNewSet<T>(
|
||||
existingMap: Map<T>, newMap: Map<true>,
|
||||
createNewValue: (key: string) => T,
|
||||
onDeleteExistingValue: (key: string, existingValue: T) => void
|
||||
): Map<T> {
|
||||
return mutateExistingMap(
|
||||
existingMap, newMap,
|
||||
// Same value if the value is set in the map
|
||||
/*isSameValue*/(_existingValue, _valueInNewMap) => true,
|
||||
/*createNewValue*/(key, _valueInNewMap) => createNewValue(key),
|
||||
onDeleteExistingValue,
|
||||
// Should never be called since we say yes to same values all the time
|
||||
/*OnDeleteExistingMismatchValue*/(_key, _existingValue) => notImplemented()
|
||||
);
|
||||
}
|
||||
|
||||
export function mutateExistingMap<T, U>(
|
||||
existingMap: Map<T>, newMap: Map<U>,
|
||||
isSameValue: (existingValue: T, valueInNewMap: U) => boolean,
|
||||
createNewValue: (key: string, valueInNewMap: U) => T,
|
||||
onDeleteExistingValue: (key: string, existingValue: T) => void,
|
||||
OnDeleteExistingMismatchValue: (key: string, existingValue: T) => void
|
||||
): Map<T> {
|
||||
// If there are new values update them
|
||||
if (newMap) {
|
||||
if (existingMap) {
|
||||
// Needs update
|
||||
existingMap.forEach((existingValue, key) => {
|
||||
const valueInNewMap = newMap.get(key);
|
||||
// Existing value - remove it
|
||||
if (valueInNewMap === undefined) {
|
||||
existingMap.delete(key);
|
||||
onDeleteExistingValue(key, existingValue);
|
||||
}
|
||||
// different value - remove it
|
||||
else if (!isSameValue(existingValue, valueInNewMap)) {
|
||||
existingMap.delete(key);
|
||||
OnDeleteExistingMismatchValue(key, existingValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Create new
|
||||
existingMap = createMap<T>();
|
||||
}
|
||||
|
||||
// Add new values that are not already present
|
||||
newMap.forEach((valueInNewMap, key) => {
|
||||
if (!existingMap.has(key)) {
|
||||
// New values
|
||||
existingMap.set(key, createNewValue(key, valueInNewMap));
|
||||
}
|
||||
});
|
||||
|
||||
return existingMap;
|
||||
}
|
||||
|
||||
cleanExistingMap(existingMap, onDeleteExistingValue);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export class ThrottledOperations {
|
||||
private pendingTimeouts: Map<any> = createMap<any>();
|
||||
constructor(private readonly host: ServerHost) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user