/ src / default-directory-searcher.js
default-directory-searcher.js
  1  const Task = require('./task');
  2  
  3  // Searches local files for lines matching a specified regex. Implements `.then()`
  4  // so that it can be used with `Promise.all()`.
  5  class DirectorySearch {
  6    constructor(rootPaths, regex, options) {
  7      const scanHandlerOptions = {
  8        ignoreCase: regex.ignoreCase,
  9        inclusions: options.inclusions,
 10        includeHidden: options.includeHidden,
 11        excludeVcsIgnores: options.excludeVcsIgnores,
 12        globalExclusions: options.exclusions,
 13        follow: options.follow
 14      };
 15      const searchOptions = {
 16        leadingContextLineCount: options.leadingContextLineCount,
 17        trailingContextLineCount: options.trailingContextLineCount
 18      };
 19      this.task = new Task(require.resolve('./scan-handler'));
 20      this.task.on('scan:result-found', options.didMatch);
 21      this.task.on('scan:file-error', options.didError);
 22      this.task.on('scan:paths-searched', options.didSearchPaths);
 23      this.promise = new Promise((resolve, reject) => {
 24        this.task.on('task:cancelled', reject);
 25        this.task.start(
 26          rootPaths,
 27          regex.source,
 28          scanHandlerOptions,
 29          searchOptions,
 30          () => {
 31            this.task.terminate();
 32            resolve();
 33          }
 34        );
 35      });
 36    }
 37  
 38    then(...args) {
 39      return this.promise.then.apply(this.promise, args);
 40    }
 41  
 42    cancel() {
 43      // This will cause @promise to reject.
 44      this.task.cancel();
 45    }
 46  }
 47  
 48  // Default provider for the `atom.directory-searcher` service.
 49  module.exports = class DefaultDirectorySearcher {
 50    // Determines whether this object supports search for a `Directory`.
 51    //
 52    // * `directory` {Directory} whose search needs might be supported by this object.
 53    //
 54    // Returns a `boolean` indicating whether this object can search this `Directory`.
 55    canSearchDirectory(directory) {
 56      return true;
 57    }
 58  
 59    // Performs a text search for files in the specified `Directory`, subject to the
 60    // specified parameters.
 61    //
 62    // Results are streamed back to the caller by invoking methods on the specified `options`,
 63    // such as `didMatch` and `didError`.
 64    //
 65    // * `directories` {Array} of {Directory} objects to search, all of which have been accepted by
 66    // this searcher's `canSearchDirectory()` predicate.
 67    // * `regex` {RegExp} to search with.
 68    // * `options` {Object} with the following properties:
 69    //   * `didMatch` {Function} call with a search result structured as follows:
 70    //     * `searchResult` {Object} with the following keys:
 71    //       * `filePath` {String} absolute path to the matching file.
 72    //       * `matches` {Array} with object elements with the following keys:
 73    //         * `lineText` {String} The full text of the matching line (without a line terminator character).
 74    //         * `lineTextOffset` {Number} If > 0, the provided line text is truncated and starts at this offset
 75    //         * `matchText` {String} The text that matched the `regex` used for the search.
 76    //         * `range` {Range} Identifies the matching region in the file. (Likely as an array of numeric arrays.)
 77    //   * `didError` {Function} call with an Error if there is a problem during the search.
 78    //   * `didSearchPaths` {Function} periodically call with the number of paths searched thus far.
 79    //   * `inclusions` {Array} of glob patterns (as strings) to search within. Note that this
 80    //   array may be empty, indicating that all files should be searched.
 81    //
 82    //   Each item in the array is a file/directory pattern, e.g., `src` to search in the "src"
 83    //   directory or `*.js` to search all JavaScript files. In practice, this often comes from the
 84    //   comma-delimited list of patterns in the bottom text input of the ProjectFindView dialog.
 85    //   * `includeHidden` {boolean} whether to ignore hidden files.
 86    //   * `excludeVcsIgnores` {boolean} whether to exclude VCS ignored paths.
 87    //   * `exclusions` {Array} similar to inclusions
 88    //   * `follow` {boolean} whether symlinks should be followed.
 89    //
 90    // Returns a *thenable* `DirectorySearch` that includes a `cancel()` method. If `cancel()` is
 91    // invoked before the `DirectorySearch` is determined, it will resolve the `DirectorySearch`.
 92    search(directories, regex, options) {
 93      const rootPaths = directories.map(directory => directory.getPath());
 94      let isCancelled = false;
 95      const directorySearch = new DirectorySearch(rootPaths, regex, options);
 96      const promise = new Promise(function(resolve, reject) {
 97        directorySearch.then(resolve, function() {
 98          if (isCancelled) {
 99            resolve();
100          } else {
101            reject(); // eslint-disable-line prefer-promise-reject-errors
102          }
103        });
104      });
105      return {
106        then: promise.then.bind(promise),
107        catch: promise.catch.bind(promise),
108        cancel() {
109          isCancelled = true;
110          directorySearch.cancel();
111        }
112      };
113    }
114  };