UNPKG

alm

Version:

The best IDE for TypeScript

272 lines (271 loc) 11.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var sw = require("../../utils/simpleWorker"); var contract = require("./fileListingContract"); var fsu = require("../../utils/fsu"); var glob = require("glob"); var chokidar = require("chokidar"); var utils_1 = require("../../../common/utils"); var types = require("../../../common/types"); var maxFileCount = 100000; /** The directory to watch */ var directoryUnderWatch; var Worker; (function (Worker) { Worker.echo = function (q) { return exports.master.increment(q).then(function (res) { return { text: q.text, num: res.num }; }); }; Worker.setupWatch = function (q) { directoryUnderWatch = q.directory; var completed = false; var liveList = {}; // Effectively a list of the mutations that `liveList` is going through after initial sync var bufferedAdded = []; var bufferedRemoved = []; var filterName = function (filePath) { return ( // Remove .git we have no use for that here !filePath.endsWith('.git') && !filePath.includes('/.git/') // MAC && !filePath.endsWith('.DS_Store')); }; // Utility to send new file list var sendNewFileList = function () { var filePaths = Object.keys(liveList) .filter(filterName) .sort(function (a, b) { // sub dir wins! if (b.startsWith(a)) { return -1; } if (a.startsWith(b)) { return 1; } // The next sorts are slow and only done after initial listing! if (!completed) { return a.length - b.length; } // sort by name return a.toLowerCase().localeCompare(b.toLowerCase()); }) .map(function (filePath) { var type = liveList[filePath]; return { filePath: filePath, type: type }; }); exports.master.fileListUpdated({ filePaths: filePaths, completed: completed }); // Send out the delta as well // Unless of course this is the *initial* sending of file listing if (bufferedAdded.length || bufferedRemoved.length) { exports.master.fileListingDelta({ addedFilePaths: bufferedAdded.filter(function (x) { return filterName(x.filePath); }), removedFilePaths: bufferedRemoved.filter(function (x) { return filterName(x.filePath); }) }); bufferedAdded = []; bufferedRemoved = []; } }; /** * Slower version for * - initial partial serach * - later updates which might be called a lot because of some directory of files removed */ var sendNewFileListThrottled = utils_1.throttle(sendNewFileList, 1500); /** * Utility function to get the listing from a directory * No side effects in this function */ var getListing = function (dirPath) { return new Promise(function (resolve) { var mg = new glob.Glob('**', { cwd: dirPath, dot: true }, function (e, globResult) { if (e) { console.error('Globbing error:', e); } var list = globResult.map(function (nl) { var p = fsu.resolve(dirPath, nl); var type = mg.cache[p] && mg.cache[p] == 'FILE' ? types.FilePathType.File : types.FilePathType.Dir; return { filePath: fsu.consistentPath(p), type: type, }; }); resolve(list); }); }); }; // create initial list using 10x faster glob.Glob! (function () { /** These things are coming on a mac for some reason */ var ignoreThisPathThatGlobGivesForUnknownReasons = function (filePath) { return filePath.includes('0.0.0.0') || (filePath.includes('[object Object]')); }; var cwd = q.directory; var mg = new glob.Glob('**', { cwd: cwd, dot: true }, function (e, newList) { if (e) { checkGlobbingError(e); if (abortGlobbing) { mg.abort(); // if we don't exit then glob keeps globbing + erroring despite mg.abort() process.exit(); return; } console.error('Globbing error:', e); } var list = newList.map(function (nl) { var p = fsu.resolve(cwd, nl); // NOTE: the glob cache also uses consistent path even on windows, hence `fsu.resolve` ^ :) var type = mg.cache[p] && mg.cache[p] == 'FILE' ? types.FilePathType.File : types.FilePathType.Dir; if (ignoreThisPathThatGlobGivesForUnknownReasons(nl)) { // console.log(nl, mg.cache[p]); /// DEBUG return null; } return { filePath: fsu.consistentPath(p), type: type, }; }).filter(function (x) { return !!x; }); // Initial search complete! completed = true; list.forEach(function (entry) { return liveList[entry.filePath] = entry.type; }); sendNewFileList(); }); var matchLength = 0; /** Still send the listing while globbing so user gets immediate feedback */ mg.on('match', function (match) { matchLength++; if (matchLength > maxFileCount) { abortDueToTooManyFiles(); return; } var p = fsu.resolve(cwd, match); if (mg.cache[p]) { if (ignoreThisPathThatGlobGivesForUnknownReasons(match)) { return; } liveList[fsu.consistentPath(p)] = mg.cache[p] == 'FILE' ? types.FilePathType.File : types.FilePathType.Dir; sendNewFileListThrottled(); } }); })(); function fileAdded(filePath) { filePath = fsu.consistentPath(filePath); // Only send if we don't know about this already (because of faster initial scan) if (!liveList[filePath]) { var type = types.FilePathType.File; liveList[filePath] = type; bufferedAdded.push({ filePath: filePath, type: type }); sendNewFileListThrottled(); } } function dirAdded(dirPath) { dirPath = fsu.consistentPath(dirPath); liveList[dirPath] = types.FilePathType.Dir; bufferedAdded.push({ filePath: dirPath, type: types.FilePathType.Dir }); /** * - glob the folder * - send the folder throttled */ getListing(dirPath).then(function (res) { res.forEach(function (fpDetails) { if (!liveList[fpDetails.filePath]) { var type = fpDetails.type; liveList[fpDetails.filePath] = type; bufferedAdded.push({ filePath: fpDetails.filePath, type: type }); } }); sendNewFileListThrottled(); }).catch(function (res) { console.error('[FLW] DirPath listing failed:', dirPath, res); }); } function fileDeleted(filePath) { filePath = fsu.consistentPath(filePath); delete liveList[filePath]; bufferedRemoved.push({ filePath: filePath, type: types.FilePathType.File }); sendNewFileListThrottled(); } function dirDeleted(dirPath) { dirPath = fsu.consistentPath(dirPath); Object.keys(liveList).forEach(function (filePath) { if (filePath.startsWith(dirPath)) { bufferedRemoved.push({ filePath: filePath, type: liveList[filePath] }); delete liveList[filePath]; } }); sendNewFileListThrottled(); } /** Create watcher */ var watcher = chokidar.watch(directoryUnderWatch, { /** Don't care about initial as we did that using glob as its faster */ ignoreInitial: true, }); // Just the ones that impact file listing // https://github.com/paulmillr/chokidar#methods--events watcher.on('add', fileAdded); watcher.on('addDir', dirAdded); watcher.on('unlink', fileDeleted); watcher.on('unlinkDir', dirDeleted); // Just for changes watcher.on('change', function (filePath) { // We have no use for this right now }); return Promise.resolve({}); }; })(Worker || (Worker = {})); // Ensure that the namespace follows the contract var _checkTypes = Worker; // run worker exports.master = sw.runWorker({ workerImplementation: Worker, masterContract: contract.master }).master; function debug() { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } console.error.apply(console, args); } /** * If its a permission error warn the user that they should start in some project directory */ var abortGlobbing = false; function checkGlobbingError(err) { if (err.path && (err.code == 'EPERM' /*win*/ || err.code == 'EACCES' /*mac*/)) { abortGlobbing = true; var errorMessage = "Exiting: Permission error when trying to list " + err.path + ".\n- Start the IDE in a project folder (e.g. '/your/project')\n- Check access to the path"; exports.master.abort({ errorMessage: errorMessage }); } } function abortDueToTooManyFiles() { abortGlobbing = true; var errorMessage = "Exiting: Too many files in folder. Currently we limit to " + maxFileCount + ".\n- Start the IDE in a project folder (e.g. '/your/project')"; exports.master.abort({ errorMessage: errorMessage }); } process.on('error', function () { console.log('here'); process.exit(); });