From 7294e0cda409803cbd3956119eeb35cee4596ba0 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 2 Jun 2015 14:04:59 -0400 Subject: [PATCH] Change DirectorySearcher to return a DirectorySearch. --- spec/workspace-spec.coffee | 57 +++++++++++++++++------- src/default-directory-searcher.coffee | 62 ++++++++++++++++++++------- src/workspace.coffee | 21 +++++---- 3 files changed, 100 insertions(+), 40 deletions(-) diff --git a/spec/workspace-spec.coffee b/spec/workspace-spec.coffee index 6b0b1af96..19775e6fa 100644 --- a/spec/workspace-spec.coffee +++ b/spec/workspace-spec.coffee @@ -1,5 +1,6 @@ path = require 'path' temp = require 'temp' +{Disposable} = require 'event-kit' Workspace = require '../src/workspace' Pane = require '../src/pane' {View} = require '../src/space-pen-extensions' @@ -943,9 +944,13 @@ describe "Workspace", -> foreignFilePath = 'ssh://foreign-directory:8080/hello.txt' numPathsSearchedInDir2 = 1 numPathsToPretendToSearchInCustomDirectorySearcher = 10 - class CustomDirectorySearcher - canSearchDirectory: (directory) -> directory.getPath() is dir1 - search: (directory, regexSource, onSearchResult, onSearchError, onPathsSearched, options) -> + class CustomDirectorySearch + constructor: () -> + @promise = Promise.resolve() + then: (args...) -> + @promise.then.apply(@promise, args) + onDidMatch: (callback) -> + # Invoke the callback with the only result we plan to return. searchResult1 = filePath: foreignFilePath, matches: [ @@ -956,11 +961,20 @@ describe "Workspace", -> range: [[0, 0], [0, 5]], } ] - onSearchResult(searchResult1) - onPathsSearched(numPathsToPretendToSearchInCustomDirectorySearcher) - promise = Promise.resolve() - promise.cancel = -> - promise + callback(searchResult1) + new Disposable + onDidError: (callback) -> + new Disposable + onDidSearchPaths: (callback) -> + # Invoke the callback with the one notification we plan to send. + callback(numPathsToPretendToSearchInCustomDirectorySearcher) + new Disposable + cancel: -> + + class CustomDirectorySearcher + canSearchDirectory: (directory) -> directory.getPath() is dir1 + search: (directory, options) -> + new CustomDirectorySearch atom.packages.serviceHub.provide( "atom.directory-searcher", "0.1.0", new CustomDirectorySearcher()) @@ -982,16 +996,27 @@ describe "Workspace", -> it "can be cancelled by cancelling one of the DirectorySearchers", -> customDirectorySearcherPromiseInstance = null + class CustomDirectorySearchToCancel + constructor: () -> + # Note that hoisting reject in this way is generally frowned upon. + @promise = new Promise (resolve, reject) => + @hoistedReject = reject + customDirectorySearcherPromiseInstance = this + then: (args...) -> + @promise.then.apply(@promise, args) + onDidMatch: (callback) -> + new Disposable + onDidError: (callback) -> + new Disposable + onDidSearchPaths: (callback) -> + new Disposable + cancel: -> + @hoistedReject() + class CustomDirectorySearcherToCancel canSearchDirectory: (directory) -> directory.getPath() is dir1 - search: (directory, regexSource, onSearchResult, onSearchError, onPathsSearched, options) -> - # Note that hoisting reject in this way is generally frowned upon. - hoistedReject = null - promise = new Promise (resolve, reject) -> - hoistedReject = reject - promise.cancel = -> hoistedReject() - customDirectorySearcherPromiseInstance = promise - promise + search: (directory, options) -> + new CustomDirectorySearchToCancel atom.packages.serviceHub.provide( "atom.directory-searcher", "0.1.0", new CustomDirectorySearcherToCancel()) diff --git a/src/default-directory-searcher.coffee b/src/default-directory-searcher.coffee index 7adf409c6..a1304d26c 100644 --- a/src/default-directory-searcher.coffee +++ b/src/default-directory-searcher.coffee @@ -1,5 +1,47 @@ Task = require './task' +# Public: +# Implements thenable so it can be used with `Promise.all()`. +class DirectorySearch + # Public: + constructor: (directory, options) -> + @task = new Task(require.resolve('./scan-handler')) + rootPaths = [directory.getPath()] + @promise = new Promise (resolve, reject) => + myResolve = (arg) -> + resolve(arg) + @task.start(rootPaths, options.regexSource, options, myResolve) + @task.on('task:cancelled', reject) + + # Public: + # Returns `Promise`. + then: (args...) -> + @promise.then.apply(@promise, args) + + # Public: + # Returns `Disposable`. + onDidMatch: (callback) -> + @task.on 'scan:result-found', callback + + # Public: + # Returns `Disposable`. + onDidError: (callback) -> + @task.on 'scan:file-error', callback + + # Public: + # + # * `callback` {Function} called with the number of paths searched thus far. + # + # Returns `Disposable`. + onDidSearchPaths: (callback) -> + @task.on 'scan:paths-searched', callback + + # Public: + cancel: -> + # This will cause @promise to reject. + @task.cancel() + + # Default provider for the `atom.directory-searcher` service. module.exports = class DefaultDirectorySearcher @@ -17,7 +59,6 @@ class DefaultDirectorySearcher # # * `directory` {Directory} that has been accepted by this provider's `canSearchDirectory()` # predicate. - # * `regexSource` {String} regex to search with. Produced via `RegExp::source`. # (Note this reflects the "Use Regex" option exposed via the ProjectFindView UI.) # * `onSearchResult` {Function} Should be called with each matching search result. # * `searchResult` {Object} with the following keys: @@ -31,6 +72,7 @@ class DefaultDirectorySearcher # * `onPathsSearched` {Function} callback that should be invoked periodically with the number of # paths searched. # * `options` {Object} with the following properties: + # * `regexSource` {String} regex to search with. Produced via `RegExp::source`. # * `ignoreCase` {boolean} # * `inclusions` {Array} of glob patterns (as strings) to search within. Note that this # array may be empty, indicating that all files should be searched. @@ -43,19 +85,7 @@ class DefaultDirectorySearcher # * `exclusions` {Array} similar to inclusions # * `follow` {boolean} whether symlinks should be followed # - # Returns a `Promise` that includes a `cancel()` method. If invoked before the `Proimse` is + # Returns a `DirectorySearch` that includes a `cancel()` method. If invoked before the `Proimse` is # determined, it will reject the `Promise`. - search: (directory, regexSource, onSearchResult, onSearchError, onPathsSearched, options) -> - task = null - rootPaths = [directory.getPath()] - promise = new Promise (resolve, reject) -> - task = Task.once require.resolve('./scan-handler'), rootPaths, regexSource, options, resolve - task.on 'task:cancelled', reject - promise.cancel = -> - task.cancel() - - task.on 'scan:result-found', onSearchResult - task.on 'scan:file-error', onSearchError - task.on 'scan:paths-searched', onPathsSearched - - promise + search: (directory, options) -> + new DirectorySearch(directory, options) diff --git a/src/workspace.coffee b/src/workspace.coffee index a22e2409d..fe79f5d17 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -803,13 +803,14 @@ class Workspace extends Model # * `onPathsSearched` (optional) {Function} # * `iterator` {Function} callback on each file found # - # Returns a `Promise` with a `cancel()` method. + # Returns a `Promise`. scan: (regex, options={}, iterator) -> if _.isFunction(options) iterator = options options = {} searchOptions = + regexSource: regex.source ignoreCase: regex.ignoreCase inclusions: options.paths or [] includeHidden: true @@ -855,18 +856,22 @@ class Workspace extends Model # Kick off all of the searches and unify them into one Promise. allSearchPromises = [] + disposables = new CompositeDisposable for entry in searchersAndDirectories {searcher, directory} = entry + directorySearcher = searcher.search(directory, searchOptions) + disposables.add(directorySearcher.onDidMatch(onSearchResult)) + disposables.add(directorySearcher.onDidError(onSearchError)) recordNumberOfPathsSearched = onPathsSearched.bind(undefined, directory) - allSearchPromises.push(searcher.search( - directory, - regex.source, - onSearchResult, - onSearchError, - recordNumberOfPathsSearched, - searchOptions)) + disposables.add(directorySearcher.onDidSearchPaths(recordNumberOfPathsSearched)) + allSearchPromises.push(directorySearcher) searchPromise = Promise.all(allSearchPromises) + # Make sure to clean up the disposables once the searchPromise is determined. + disposeAll = (args...) -> + disposables.dispose() + searchPromise.then(disposeAll, disposeAll) + for buffer in atom.project.getBuffers() when buffer.isModified() filePath = buffer.getPath() continue unless atom.project.contains(filePath)