From 404f6371016e552e8692f3df5ffc2a0c65b8e55c Mon Sep 17 00:00:00 2001 From: Corey Johnson & Kevin Sawicki Date: Wed, 12 Dec 2012 12:26:39 -0800 Subject: [PATCH] Populate fuzzy-finder asynchronously --- native/v8_extensions/native.js | 3 ++ native/v8_extensions/native.mm | 52 +++++++++++++++++++ spec/app/project-spec.coffee | 31 ++++++++--- src/app/project.coffee | 44 +++------------- src/app/select-list.coffee | 9 ++++ .../spec/fuzzy-finder-spec.coffee | 24 ++++++--- .../fuzzy-finder/src/fuzzy-finder.coffee | 4 +- src/stdlib/fs.coffee | 3 ++ 8 files changed, 119 insertions(+), 51 deletions(-) diff --git a/native/v8_extensions/native.js b/native/v8_extensions/native.js index be38bc543..ea54e8841 100644 --- a/native/v8_extensions/native.js +++ b/native/v8_extensions/native.js @@ -16,6 +16,9 @@ var $native = {}; native function traverseTree(path, onFile, onDirectory); $native.traverseTree = traverseTree; + native function getAllPathsAsync(path); + $native.getAllPathsAsync = getAllPathsAsync; + native function isFile(path); $native.isFile = isFile; diff --git a/native/v8_extensions/native.mm b/native/v8_extensions/native.mm index 93df8ea84..229993ab3 100644 --- a/native/v8_extensions/native.mm +++ b/native/v8_extensions/native.mm @@ -103,6 +103,58 @@ bool Native::Execute(const CefString& name, return true; } + else if (name == "getAllPathsAsync") { + std::string argument = arguments[0]->GetStringValue().ToString(); + CefRefPtr callback = arguments[1]; + CefRefPtr context = CefV8Context::GetCurrentContext(); + + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(queue, ^{ + int rootPathLength = argument.size() + 1; + char rootPath[rootPathLength]; + strcpy(rootPath, argument.c_str()); + char * const treePaths[] = {rootPath, NULL}; + + FTS *tree = fts_open(treePaths, FTS_COMFOLLOW | FTS_PHYSICAL| FTS_NOCHDIR | FTS_NOSTAT, NULL); + std::vector paths; + + if (tree != NULL) { + FTSENT *entry; + int arrayIndex = 0; + while ((entry = fts_read(tree)) != NULL) { + if (entry->fts_level == 0) { + continue; + } + + bool isFile = entry->fts_info == FTS_NSOK; + bool isDir = entry->fts_info == FTS_D; + if (!isFile && !isDir) { + continue; + } + + int pathLength = entry->fts_pathlen - rootPathLength; + char relative[pathLength + 1]; + relative[pathLength] = '\0'; + strncpy(relative, entry->fts_path + rootPathLength, pathLength); + paths.push_back(relative); + } + } + + dispatch_queue_t mainQueue = dispatch_get_main_queue(); + dispatch_async(mainQueue, ^{ + context->Enter(); + CefRefPtr v8Paths = CefV8Value::CreateArray(paths.size()); + for (int i = 0; i < paths.size(); i++) { + v8Paths->SetValue(i, CefV8Value::CreateString(paths[i])); + } + CefV8ValueList callbackArgs; + callbackArgs.push_back(v8Paths); + callback->ExecuteFunction(callback, callbackArgs); + context->Exit(); + }); + }); + return true; + } else if (name == "traverseTree") { std::string argument = arguments[0]->GetStringValue().ToString(); int rootPathLength = argument.size() + 1; diff --git a/spec/app/project-spec.coffee b/spec/app/project-spec.coffee index c066bb4cb..579fc282a 100644 --- a/spec/app/project-spec.coffee +++ b/spec/app/project-spec.coffee @@ -110,19 +110,34 @@ describe "Project", -> expect(project.getRootDirectory()?).toBeFalsy() describe ".getFilePaths()", -> - it "ignores files that return true from atom.ignorePath(path)", -> - spyOn(project, 'ignoreDirectory').andCallFake (path) -> fs.base(path).match /a$/ - spyOn(project, 'ignoreFile').andCallFake (path) -> fs.base(path).match /a$/ + it "asynchronously returns file paths using a promise", -> + paths = null + waitsForPromise -> + project.getFilePaths().done (foundPaths) -> paths = foundPaths - paths = project.getFilePaths() - expect(paths).not.toContain('a') - expect(paths).toContain('b') + runs -> + expect(paths.length).toBeGreaterThan 0 + + it "ignores files that return true from atom.ignorePath(path)", -> + spyOn(project, 'isPathIgnored').andCallFake (path) -> fs.base(path).match /a$/ + + paths = null + waitsForPromise -> + project.getFilePaths().done (foundPaths) -> paths = foundPaths + + runs -> + expect(paths).not.toContain('a') + expect(paths).toContain('b') it "ignores files in gitignore for projects in a git tree", -> project.setHideIgnoredFiles(true) project.setPath(require.resolve('fixtures/git/working-dir')) - paths = project.getFilePaths() - expect(paths).not.toContain('ignored.txt') + paths = null + waitsForPromise -> + project.getFilePaths().done (foundPaths) -> paths = foundPaths + + runs -> + expect(paths).not.toContain('ignored.txt') describe ".scan(options, callback)", -> describe "when called with a regex", -> diff --git a/src/app/project.coffee b/src/app/project.coffee index d3645ed09..8cc207a8c 100644 --- a/src/app/project.coffee +++ b/src/app/project.coffee @@ -24,13 +24,10 @@ class Project @setPath(path) @editSessions = [] @buffers = [] - @ignoredFolderNames = [ + @ignoredNames = [ '.git' - ] - @ignoredFileNames = [ '.DS_Store' ] - @ignoredPathRegexes = [] @repo = new Git(path) destroy: -> @@ -55,52 +52,27 @@ class Project @rootDirectory getFilePaths: -> - filePaths = [] + deferred = $.Deferred() + fs.getAllPathsAsync @getPath(), (paths) => + paths = paths.filter (path) => not @isPathIgnored(path) + deferred.resolve(paths) + deferred.promise() - onFile = (path) => - filePaths.push(path) unless @ignoreFile(path) - - onDirectory = (path) => - return not @ignoreDirectory(path) - - fs.traverseTree @getPath(), onFile, onDirectory - filePaths - - ignoreDirectory: (path) -> + isPathIgnored: (path) -> lastSlash = path.lastIndexOf('/') if lastSlash isnt -1 name = path.substring(lastSlash + 1) else name = path - for ignored in @ignoredFolderNames + for ignored in @ignoredNames return true if name is ignored - for regex in @ignoredPathRegexes - return true if path.match(regex) - - @ignoreRepositoryPath(path) - - ignoreFile: (path) -> - lastSlash = path.lastIndexOf('/') - if lastSlash isnt -1 - name = path.substring(lastSlash + 1) - else - name = path - - for ignored in @ignoredFileNames - return true if name is ignored - for regex in @ignoredPathRegexes - return true if path.match(regex) - @ignoreRepositoryPath(path) ignoreRepositoryPath: (path) -> @hideIgnoredFiles and @repo.isPathIgnored(fs.join(@getPath(), path)) - ignorePathRegex: -> - @ignoredPathRegexes.map((regex) -> "(#{regex.source})").join("|") - resolve: (filePath) -> filePath = fs.join(@getPath(), filePath) unless filePath[0] == '/' fs.absolute filePath diff --git a/src/app/select-list.coffee b/src/app/select-list.coffee index 4e81b2589..b8e92cca2 100644 --- a/src/app/select-list.coffee +++ b/src/app/select-list.coffee @@ -9,6 +9,7 @@ class SelectList extends View @div class: @viewClass(), => @subview 'miniEditor', new Editor(mini: true) @div class: 'error', outlet: 'error' + @div class: 'loading', outlet: 'loading' @ol outlet: 'list' @viewClass: -> 'select-list' @@ -38,6 +39,7 @@ class SelectList extends View setArray: (@array) -> @populateList() @selectItem(@list.find('li:first')) + @setLoading() setError: (message) -> if not message or message.length == "" @@ -49,6 +51,13 @@ class SelectList extends View @error.show() @addClass("error") + setLoading: (message) -> + if not message or message.length == "" + @loading.text("").hide() + else + @setError() + @loading.text(message).show() + populateList: -> filterQuery = @miniEditor.getText() if filterQuery.length diff --git a/src/extensions/fuzzy-finder/spec/fuzzy-finder-spec.coffee b/src/extensions/fuzzy-finder/spec/fuzzy-finder-spec.coffee index 434d8a61d..2c77805d8 100644 --- a/src/extensions/fuzzy-finder/spec/fuzzy-finder-spec.coffee +++ b/src/extensions/fuzzy-finder/spec/fuzzy-finder-spec.coffee @@ -18,7 +18,7 @@ describe 'FuzzyFinder', -> describe "file-finder behavior", -> describe "toggling", -> describe "when the root view's project has a path", -> - it "shows the FuzzyFinder or hides it and returns focus to the active editor if it already showing", -> + it "shows the FuzzyFinder or hides it nad returns focus to the active editor if it already showing", -> rootView.attachToDom() expect(rootView.find('.fuzzy-finder')).not.toExist() rootView.find('.editor').trigger 'editor:split-right' @@ -40,13 +40,25 @@ describe 'FuzzyFinder', -> expect(finder.miniEditor.getText()).toBe '' it "shows all relative file paths for the current project and selects the first", -> + rootView.attachToDom() finder.maxItems = Infinity rootView.trigger 'fuzzy-finder:toggle-file-finder' - paths = rootView.project.getFilePaths() - expect(finder.list.children('li').length).toBe paths.length, finder.maxResults - for path in paths - expect(finder.list.find("li:contains(#{path})")).toExist() - expect(finder.list.children().first()).toHaveClass 'selected' + paths = null + expect(finder.find(".loading")).toBeVisible() + expect(finder.find(".loading")).toHaveText "Indexing..." + + waitsForPromise -> + rootView.project.getFilePaths().done (foundPaths) -> paths = foundPaths + + waitsFor -> + finder.list.children('li').length > 0 + + runs -> + expect(finder.list.children('li').length).toBe paths.length, finder.maxResults + for path in paths + expect(finder.list.find("li:contains(#{path})")).toExist() + expect(finder.list.children().first()).toHaveClass 'selected' + expect(finder.find(".loading")).not.toBeVisible() describe "when root view's project has no path", -> beforeEach -> diff --git a/src/extensions/fuzzy-finder/src/fuzzy-finder.coffee b/src/extensions/fuzzy-finder/src/fuzzy-finder.coffee index 016e442f6..077ce9809 100644 --- a/src/extensions/fuzzy-finder/src/fuzzy-finder.coffee +++ b/src/extensions/fuzzy-finder/src/fuzzy-finder.coffee @@ -51,7 +51,9 @@ class FuzzyFinder extends SelectList @attach() if @paths?.length populateProjectPaths: -> - @setArray(@rootView.project.getFilePaths()) + @setLoading("Indexing...") + @rootView.project.getFilePaths().done (paths) => + @setArray(paths) populateOpenBufferPaths: -> @paths = @rootView.getOpenBufferPaths().map (path) => diff --git a/src/stdlib/fs.coffee b/src/stdlib/fs.coffee index 93146d5ec..696dd8036 100644 --- a/src/stdlib/fs.coffee +++ b/src/stdlib/fs.coffee @@ -112,6 +112,9 @@ module.exports = @makeTree(@directory(path)) @makeDirectory(path) + getAllPathsAsync: (rootPath, callback) -> + $native.getAllPathsAsync(rootPath, callback) + traverseTree: (rootPath, onFile, onDirectory) -> $native.traverseTree(rootPath, onFile, onDirectory)