From b228c8947f862e9a3fcd29159bfe68998a01aa86 Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Sat, 24 Apr 2021 18:14:24 -0500 Subject: [PATCH] feat: use chokidar to watch file changes --- lib/paths-cache.js | 115 ++++++++++++++++++++++++++++++++++++++------- package.json | 1 + pnpm-lock.yaml | 29 +++++------- 3 files changed, 113 insertions(+), 32 deletions(-) diff --git a/lib/paths-cache.js b/lib/paths-cache.js index 5fca633..050cd5f 100644 --- a/lib/paths-cache.js +++ b/lib/paths-cache.js @@ -5,6 +5,7 @@ import { Directory, File } from "atom" import { union } from "./utils" import { globifyPath, globifyDirectory, globifyGitIgnoreFile } from "./path-utils" import glob from "fast-glob" +import * as chokidar from "chokidar" export default class PathsCache extends EventEmitter { constructor() { @@ -48,7 +49,7 @@ export default class PathsCache extends EventEmitter { await this._cacheProjectPathsAndRepositories() const results = await this._cachePaths() - this._addWatchers() + await this._addWatchers() this.emit("rebuild-cache-done") return results @@ -73,8 +74,8 @@ export default class PathsCache extends EventEmitter { * @param {boolean} isPackageDispose */ dispose(isPackageDispose) { - this._fileWatchersByDirectory.forEach((watcher) => { - watcher.dispose() + this._fileWatchersByDirectory.forEach(async (watcher) => { + await watcher.close() }) this._fileWatchersByDirectory = new Map() this._filePathsByProjectDirectory = new Map() @@ -164,16 +165,89 @@ export default class PathsCache extends EventEmitter { * @param {Directory} projectDirectory * @private */ - _addWatcherForDirectory(projectDirectory) { - // check if it is not already added + async _addWatcherForDirectory(projectDirectory) { + // close if already added let watcher = this._fileWatchersByDirectory.get(projectDirectory) - if (watcher !== undefined) { - // add a watcher to run `this._onDirectoryChanged` - watcher = projectDirectory.onDidChange(() => { - return this._onDirectoryChanged(projectDirectory, projectDirectory) + if (watcher !== undefined && typeof watcher.close === "function") { + await watcher.close() + } + // add a watcher to run `this._onDirectoryChanged` + const projectPath = projectDirectory.getPath() + const ignored = await this._getAllIgnoredGlob(projectPath) + // TODO smarter handling of directory changes + // TODO get paths from the watcher itself + // TODO track gitignore file + watcher = chokidar + .watch([projectPath, ...ignored], { + persistent: true, + }) + .on("add", (addedFile) => { + this.onAddFile(projectDirectory, addedFile) + }) + .on("unlink", (removedFile) => { + this.onRemoveFile(projectDirectory, removedFile) }) - this._fileWatchersByDirectory.set(projectDirectory, watcher) + .on("addDir", (addedDir) => { + this.onAddDir(projectDirectory, addedDir) + }) + .on("unlinkDir", (removedDir) => { + this.onRemoveDir(projectDirectory, removedDir) + }) + this._fileWatchersByDirectory.set(projectDirectory, watcher) + } + + /** + * @param projectDirectory {Directory} + * @param addedFile {string} + */ + onAddFile(projectDirectory, addedFile) { + this.emit("rebuild-cache") + const filePaths = this._filePathsByProjectDirectory.get(projectDirectory.path) + filePaths.push(addedFile) + this._filePathsByProjectDirectory.set(projectDirectory.path, filePaths) + this.emit("rebuild-cache-done") + } + + /** + * @param projectDirectory {Directory} + * @param removedFile {string} + */ + onRemoveFile(projectDirectory, removedFile) { + this.emit("rebuild-cache") + /** @type {string[]} */ + const filePaths = this._filePathsByProjectDirectory.get(projectDirectory.path) + + // delete the removed file + for (let iFile = 0; iFile < filePaths.length; iFile++) { + const file = filePaths[iFile] + if (file === removedFile) { + delete filePaths[iFile] + } } + this._filePathsByProjectDirectory.set(projectDirectory.path, filePaths) + this.emit("rebuild-cache-done") + } + + /** + * @param projectDirectory {Directory} + * @param addedDir {string} + */ + async onAddDir(projectDirectory, addedDir) { + this.emit("rebuild-cache") + const directory = new Directory(addedDir) + await this._cachePathsForDirectory(projectDirectory, directory) + this.emit("rebuild-cache-done") + } + + /** + * @param projectDirectory {Directory} + * @param removedDir {string} + */ + onRemoveDir(projectDirectory, removedDir) { + this.emit("rebuild-cache") + const directory = new Directory(removedDir) + this._removeFilePathsForDirectory(projectDirectory, directory) + this.emit("rebuild-cache-done") } /** @@ -197,9 +271,10 @@ export default class PathsCache extends EventEmitter { * @private */ _cleanWatchersForDirectory(directory) { - this._fileWatchersByDirectory.forEach((watcher, otherDirectory) => { + // TODO promise all + this._fileWatchersByDirectory.forEach(async (watcher, otherDirectory) => { if (directory.contains(otherDirectory.path)) { - watcher.dispose() + await await watcher.close() this._fileWatchersByDirectory.delete(otherDirectory) } }) @@ -383,6 +458,17 @@ export default class PathsCache extends EventEmitter { return [] } + /** + * Get all ignored glob using `this._getGitIgnoreGlob` and `this._getIgnoredPatternsGlob` + * @param {string} directoryPath the given directory path + * @returns {Promise} + */ + async _getAllIgnoredGlob(directoryPath) { + const gitignoreGlob = await this._getGitIgnoreGlob(directoryPath) + const ignoredPatternsGlob = await this._getIgnoredPatternsGlob(directoryPath) + return [...gitignoreGlob, ...ignoredPatternsGlob] + } + /** * Populates cache for the given directory * @param {string} directoryPath the given directory path @@ -391,11 +477,8 @@ export default class PathsCache extends EventEmitter { */ async _cachePathsForDirectoryWithGlob(directoryPath) { const directoryGlob = globifyDirectory(directoryPath) - const gitignoreGlob = await this._getGitIgnoreGlob(directoryPath) - const ignoredPatternsGlob = await this._getIgnoredPatternsGlob(directoryPath) - const files = await glob( - [directoryGlob, ...gitignoreGlob, ...ignoredPatternsGlob], + [directoryGlob, ...(await this._getAllIgnoredGlob(directoryPath))], // glob options { dot: true, diff --git a/package.json b/package.json index 70ecbb7..133e628 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ } }, "dependencies": { + "chokidar": "^3.5.1", "fast-glob": "^3.2.5", "is-glob": "^4.0.1", "is-valid-path": "^0.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e2109e..eb3a2c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,5 @@ dependencies: + chokidar: 3.5.1 fast-glob: 3.2.5 is-glob: 4.0.1 is-valid-path: 0.1.1 @@ -3574,10 +3575,8 @@ packages: dependencies: normalize-path: 3.0.0 picomatch: 2.2.2 - dev: true engines: node: '>= 8' - optional: true resolution: integrity: sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== /argparse/1.0.10: @@ -4018,10 +4017,8 @@ packages: resolution: integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== /binary-extensions/2.2.0: - dev: true engines: node: '>=8' - optional: true resolution: integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== /bindings/1.5.0: @@ -4313,17 +4310,15 @@ packages: dependencies: anymatch: 3.1.1 braces: 3.0.2 - glob-parent: 5.1.1 + glob-parent: 5.1.2 is-binary-path: 2.1.0 is-glob: 4.0.1 normalize-path: 3.0.0 readdirp: 3.5.0 - dev: true engines: node: '>= 8.10.0' - optional: true optionalDependencies: - fsevents: 2.3.1 + fsevents: 2.3.2 resolution: integrity: sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== /chrome-trace-event/1.0.2: @@ -5969,15 +5964,14 @@ packages: dev: true resolution: integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - /fsevents/2.3.1: - dev: true + /fsevents/2.3.2: engines: node: ^8.16.0 || ^10.6.0 || >=11.0.0 optional: true os: - darwin resolution: - integrity: sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw== + integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== /function-bind/1.1.1: dev: true resolution: @@ -6045,6 +6039,13 @@ packages: node: '>= 6' resolution: integrity: sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + /glob-parent/5.1.2: + dependencies: + is-glob: 4.0.1 + engines: + node: '>= 6' + resolution: + integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== /glob/7.1.6: dependencies: fs.realpath: 1.0.0 @@ -6494,10 +6495,8 @@ packages: /is-binary-path/2.1.0: dependencies: binary-extensions: 2.2.0 - dev: true engines: node: '>=8' - optional: true resolution: integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== /is-buffer/1.1.6: @@ -7469,7 +7468,6 @@ packages: resolution: integrity: sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= /normalize-path/3.0.0: - dev: true engines: node: '>=0.10.0' resolution: @@ -8551,10 +8549,8 @@ packages: /readdirp/3.5.0: dependencies: picomatch: 2.2.2 - dev: true engines: node: '>=8.10.0' - optional: true resolution: integrity: sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== /rechoir/0.6.2: @@ -9932,6 +9928,7 @@ specifiers: '@types/underscore': ^1.11.0 babel-preset-atomic: ^3.0.3 build-commit: 0.1.4 + chokidar: ^3.5.1 cross-env: 7.0.3 eslint: ^7.22.0 eslint-config-atomic: ^1.12.4