diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 093eadd9cef..d08be68aaac 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -30,6 +30,23 @@ namespace ts { mtime?: Date; } + /* @internal */ + export enum WatchPriority { + High, + Medium, + Low + } + + const pollingIntervalsForPriority = [250, 1000, 4000]; + function pollingInterval(watchPriority: WatchPriority): number { + return pollingIntervalsForPriority[watchPriority]; + } + + /* @internal */ + export function watchFileUsingPriorityPollingInterval(host: System, fileName: string, callback: FileWatcherCallback, watchPriority: WatchPriority): FileWatcher { + return host.watchFile(fileName, callback, pollingInterval(watchPriority)); + } + /** * Partial interface of the System thats needed to support the caching of directory structure */ diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 4913e54f043..d5004ba3920 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -255,16 +255,13 @@ namespace ts { const watchLogLevel = compilerOptions.extendedDiagnostics ? WatchLogLevel.Verbose : compilerOptions.diagnostics ? WatchLogLevel.TriggerOnly : WatchLogLevel.None; const writeLog: (s: string) => void = watchLogLevel !== WatchLogLevel.None ? s => { system.write(s); system.write(system.newLine); } : noop; - const watchFile = createWatchFile(watchLogLevel, writeLog); - const watchFilePath = createWatchFilePath(watchLogLevel, writeLog); - const watchDirectoryWorker = createWatchDirectory(watchLogLevel, writeLog); - watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty); const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost; + const { watchFile, watchFilePath, watchDirectory: watchDirectoryWorker } = getWatchFactory(system, watchLogLevel, writeLog); const directoryStructureHost = configFileName ? createCachedDirectoryStructureHost(system) : system; if (configFileName) { - watchFile(system, configFileName, scheduleProgramReload, /*pollingInterval*/ undefined); + watchFile(system, configFileName, scheduleProgramReload, WatchPriority.Low); } const getCurrentDirectory = memoize(() => directoryStructureHost.getCurrentDirectory()); @@ -417,7 +414,7 @@ namespace ts { hostSourceFile.sourceFile = sourceFile; sourceFile.version = hostSourceFile.version.toString(); if (!hostSourceFile.fileWatcher) { - hostSourceFile.fileWatcher = watchFilePath(system, fileName, onSourceFileChange, /*pollingInterval*/ undefined, path); + hostSourceFile.fileWatcher = watchFilePath(system, fileName, onSourceFileChange, WatchPriority.High, path); } } else { @@ -430,7 +427,7 @@ namespace ts { let fileWatcher: FileWatcher; if (sourceFile) { sourceFile.version = "0"; - fileWatcher = watchFilePath(system, fileName, onSourceFileChange, /*pollingInterval*/ undefined, path); + fileWatcher = watchFilePath(system, fileName, onSourceFileChange, WatchPriority.High, path); sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher }); } else { @@ -604,7 +601,7 @@ namespace ts { } function watchMissingFilePath(missingFilePath: Path) { - return watchFilePath(system, missingFilePath, onMissingFileChange, /*pollingInterval*/ undefined, missingFilePath); + return watchFilePath(system, missingFilePath, onMissingFileChange, WatchPriority.Medium, missingFilePath); } function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) { diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index 70d7ca63ee9..b9d6e340712 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -96,43 +96,63 @@ namespace ts { Verbose } - export type WatchFile = (host: System, file: string, callback: FileWatcherCallback, pollingInterval?: number, detailInfo1?: X, detailInfo2?: Y) => FileWatcher; + export type WatchFile = (host: System, file: string, callback: FileWatcherCallback, watchPriority: WatchPriority, detailInfo1?: X, detailInfo2?: Y) => FileWatcher; export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void; - export type WatchFilePath = (host: System, file: string, callback: FilePathWatcherCallback, pollingInterval: number | undefined, path: Path, detailInfo1?: X, detailInfo2?: Y) => FileWatcher; + export type WatchFilePath = (host: System, file: string, callback: FilePathWatcherCallback, watchPriority: WatchPriority, path: Path, detailInfo1?: X, detailInfo2?: Y) => FileWatcher; export type WatchDirectory = (host: System, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, detailInfo1?: X, detailInfo2?: Y) => FileWatcher; - export function createWatchFile(watchLogLevel: WatchLogLevel, log: (s: string) => void, getDetailWatchInfo?: GetDetailWatchInfo): WatchFile { - const createFileWatcher: CreateFileWatcher = getCreateFileWatcher(watchLogLevel, watchFile); - return (host, file, callback, pollingInterval, detailInfo1, detailInfo2) => - createFileWatcher(host, file, callback, pollingInterval, /*passThrough*/ undefined, detailInfo1, detailInfo2, watchFile, log, "FileWatcher", getDetailWatchInfo); + export interface WatchFactory { + watchFile: WatchFile; + watchFilePath: WatchFilePath; + watchDirectory: WatchDirectory; } - export function createWatchFilePath(watchLogLevel: WatchLogLevel, log: (s: string) => void, getDetailWatchInfo?: GetDetailWatchInfo): WatchFilePath { - const createFileWatcher: CreateFileWatcher = getCreateFileWatcher(watchLogLevel, watchFilePath); - return (host, file, callback, pollingInterval, path, detailInfo1, detailInfo2) => - createFileWatcher(host, file, callback, pollingInterval, path, detailInfo1, detailInfo2, watchFile, log, "FileWatcher", getDetailWatchInfo); + export function getWatchFactory(host: System, watchLogLevel: WatchLogLevel, log: (s: string) => void, getDetailWatchInfo?: GetDetailWatchInfo): WatchFactory { + const value = host.getEnvironmentVariable("TSC_WATCHFILE"); + switch (value) { + case "PriorityPollingInterval": + // Use polling interval based on priority when create watch using host.watchFile + return getWatchFactoryWith(watchLogLevel, log, getDetailWatchInfo, watchFileUsingPriorityPollingInterval, watchDirectory); + default: + return getDefaultWatchFactory(watchLogLevel, log, getDetailWatchInfo); + } } - export function createWatchDirectory(watchLogLevel: WatchLogLevel, log: (s: string) => void, getDetailWatchInfo?: GetDetailWatchInfo): WatchDirectory { - const createFileWatcher: CreateFileWatcher = getCreateFileWatcher(watchLogLevel, watchDirectory); - return (host, directory, callback, flags, detailInfo1, detailInfo2) => - createFileWatcher(host, directory, callback, flags, /*passThrough*/ undefined, detailInfo1, detailInfo2, watchDirectory, log, "DirectoryWatcher", getDetailWatchInfo); + export function getDefaultWatchFactory(watchLogLevel: WatchLogLevel, log: (s: string) => void, getDetailWatchInfo?: GetDetailWatchInfo): WatchFactory { + // Current behaviour in which polling interval is always 250 ms + return getWatchFactoryWith(watchLogLevel, log, getDetailWatchInfo, watchFile, watchDirectory); } - function watchFile(host: System, file: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher { - return host.watchFile(file, callback, pollingInterval); + function getWatchFactoryWith(watchLogLevel: WatchLogLevel, log: (s: string) => void, getDetailWatchInfo: GetDetailWatchInfo | undefined, + watchFile: (host: System, file: string, callback: FileWatcherCallback, watchPriority: WatchPriority) => FileWatcher, + watchDirectory: (host: System, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags) => FileWatcher): WatchFactory { + const createFileWatcher: CreateFileWatcher = getCreateFileWatcher(watchLogLevel, watchFile); + const createFilePathWatcher: CreateFileWatcher = watchLogLevel === WatchLogLevel.None ? watchFilePath : createFileWatcher; + const createDirectoryWatcher: CreateFileWatcher = getCreateFileWatcher(watchLogLevel, watchDirectory); + return { + watchFile: (host, file, callback, pollingInterval, detailInfo1, detailInfo2) => + createFileWatcher(host, file, callback, pollingInterval, /*passThrough*/ undefined, detailInfo1, detailInfo2, watchFile, log, "FileWatcher", getDetailWatchInfo), + watchFilePath: (host, file, callback, pollingInterval, path, detailInfo1, detailInfo2) => + createFilePathWatcher(host, file, callback, pollingInterval, path, detailInfo1, detailInfo2, watchFile, log, "FileWatcher", getDetailWatchInfo), + watchDirectory: (host, directory, callback, flags, detailInfo1, detailInfo2) => + createDirectoryWatcher(host, directory, callback, flags, /*passThrough*/ undefined, detailInfo1, detailInfo2, watchDirectory, log, "DirectoryWatcher", getDetailWatchInfo) + }; + + function watchFilePath(host: System, file: string, callback: FilePathWatcherCallback, watchPriority: WatchPriority, path: Path): FileWatcher { + return watchFile(host, file, (fileName, eventKind) => callback(fileName, eventKind, path), watchPriority); + } } - function watchFilePath(host: System, file: string, callback: FilePathWatcherCallback, pollingInterval: number | undefined, path: Path): FileWatcher { - return host.watchFile(file, (fileName, eventKind) => callback(fileName, eventKind, path), pollingInterval); + function watchFile(host: System, file: string, callback: FileWatcherCallback, _watchPriority: WatchPriority): FileWatcher { + return host.watchFile(file, callback); } function watchDirectory(host: System, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher { return host.watchDirectory(directory, callback, (flags & WatchDirectoryFlags.Recursive) !== 0); } - export type WatchCallback = (fileName: string, cbOptional?: T, passThrough?: U) => void; - export type AddWatch = (host: System, file: string, cb: WatchCallback, flags: T, passThrough?: V, detailInfo1?: undefined, detailInfo2?: undefined) => FileWatcher; + type WatchCallback = (fileName: string, cbOptional?: T, passThrough?: U) => void; + type AddWatch = (host: System, file: string, cb: WatchCallback, flags: T, passThrough?: V, detailInfo1?: undefined, detailInfo2?: undefined) => FileWatcher; export type GetDetailWatchInfo = (detailInfo1: X, detailInfo2: Y) => string; type CreateFileWatcher = (host: System, file: string, cb: WatchCallback, flags: T, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo | undefined) => FileWatcher; @@ -170,7 +190,7 @@ namespace ts { } function getWatchInfo(file: string, flags: T, detailInfo1: X | undefined, detailInfo2: Y | undefined, getDetailWatchInfo: GetDetailWatchInfo | undefined) { - return `WatchInfo: ${file} ${flags} ${getDetailWatchInfo ? getDetailWatchInfo(detailInfo1, detailInfo2) : ""}` + return `WatchInfo: ${file} ${flags} ${getDetailWatchInfo ? getDetailWatchInfo(detailInfo1, detailInfo2) : ""}`; } export function closeFileWatcher(watcher: FileWatcher) { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index e6ba7fb3894..43cb678d743 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -410,11 +410,7 @@ namespace ts.server { private readonly seenProjects = createMap(); /*@internal*/ - readonly watchFile: WatchFile; - /*@internal*/ - readonly watchFilePath: WatchFilePath; - /*@internal*/ - readonly watchDirectory: WatchDirectory; + readonly watchFactory: WatchFactory; constructor(opts: ProjectServiceOptions) { this.host = opts.host; @@ -457,9 +453,7 @@ namespace ts.server { const watchLogLevel = this.logger.hasLevel(LogLevel.verbose) ? WatchLogLevel.Verbose : this.logger.loggingEnabled() ? WatchLogLevel.TriggerOnly : WatchLogLevel.None; const log: (s: string) => void = watchLogLevel !== WatchLogLevel.None ? (s => this.logger.info(s)) : noop; - this.watchFile = createWatchFile(watchLogLevel, log, getDetailWatchInfo); - this.watchFilePath = createWatchFilePath(watchLogLevel, log, getDetailWatchInfo); - this.watchDirectory = createWatchDirectory(watchLogLevel, log, getDetailWatchInfo); + this.watchFactory = getDefaultWatchFactory(watchLogLevel, log, getDetailWatchInfo); } toPath(fileName: string) { @@ -792,7 +786,7 @@ namespace ts.server { */ /*@internal*/ watchWildcardDirectory(directory: Path, flags: WatchDirectoryFlags, project: ConfiguredProject) { - return this.watchDirectory( + return this.watchFactory.watchDirectory( this.host, directory, fileOrDirectory => { @@ -1122,11 +1116,11 @@ namespace ts.server { canonicalConfigFilePath: string, configFileExistenceInfo: ConfigFileExistenceInfo ) { - configFileExistenceInfo.configFileWatcherForRootOfInferredProject = this.watchFile( + configFileExistenceInfo.configFileWatcherForRootOfInferredProject = this.watchFactory.watchFile( this.host, configFileName, (_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind), - /*pollingInterval*/ undefined, + WatchPriority.Low, WatchType.ConfigFileForInferredRoot ); this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.UpdatedCallback); @@ -1506,11 +1500,11 @@ namespace ts.server { project.configFileSpecs = configFileSpecs; // TODO: We probably should also watch the configFiles that are extended - project.configFileWatcher = this.watchFile( + project.configFileWatcher = this.watchFactory.watchFile( this.host, configFileName, (_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind), - /*pollingInterval*/ undefined, + WatchPriority.Low, WatchType.ConfigFilePath, project ); @@ -1733,11 +1727,11 @@ namespace ts.server { // do not watch files with mixed content - server doesn't know how to interpret it if (!info.isDynamicOrHasMixedContent()) { const { fileName } = info; - info.fileWatcher = this.watchFilePath( + info.fileWatcher = this.watchFactory.watchFilePath( this.host, fileName, (fileName, eventKind, path) => this.onSourceFileChanged(fileName, eventKind, path), - /*pollingInterval*/ undefined, + WatchPriority.Medium, info.path, WatchType.ClosedScriptInfo ); diff --git a/src/server/project.ts b/src/server/project.ts index 48a019dd97e..c7ea875f072 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -376,7 +376,7 @@ namespace ts.server { /*@internal*/ watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) { - return this.projectService.watchDirectory( + return this.projectService.watchFactory.watchDirectory( this.projectService.host, directory, cb, @@ -393,7 +393,7 @@ namespace ts.server { /*@internal*/ watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) { - return this.projectService.watchDirectory( + return this.projectService.watchFactory.watchDirectory( this.projectService.host, directory, cb, @@ -916,7 +916,7 @@ namespace ts.server { } private addMissingFileWatcher(missingFilePath: Path) { - const fileWatcher = this.projectService.watchFile( + const fileWatcher = this.projectService.watchFactory.watchFile( this.projectService.host, missingFilePath, (fileName, eventKind) => { @@ -932,7 +932,7 @@ namespace ts.server { this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } }, - /*pollingInterval*/ undefined, + WatchPriority.Medium, WatchType.MissingFilePath, this );