From 31163f1d8cd28281a74e2283fca4d3153e7ffcef Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 7 May 2012 19:30:07 -0600 Subject: [PATCH] Convert FileFinder to a proper extension and remove references from RootView code --- spec/app/project-spec.coffee | 8 -- spec/app/root-view-spec.coffee | 61 ----------- spec/extensions/file-finder-spec.coffee | 138 +++++++++++++++++------- src/app/keymaps/file-finder.coffee | 4 +- src/app/project.coffee | 4 +- src/app/root-view.coffee | 21 +--- src/extensions/file-finder.coffee | 39 ++++--- src/stdlib/jquery-extensions.coffee | 3 + 8 files changed, 132 insertions(+), 146 deletions(-) diff --git a/spec/app/project-spec.coffee b/spec/app/project-spec.coffee index 8407459be..5627c81ab 100644 --- a/spec/app/project-spec.coffee +++ b/spec/app/project-spec.coffee @@ -39,14 +39,6 @@ describe "Project", -> expect(buffer.path).toBeUndefined() expect(newBufferHandler).toHaveBeenCalledWith(buffer) - describe ".getFilePaths()", -> - it "returns a promise which resolves to a list of all file paths in the project, recursively", -> - expectedPaths = (path.replace(project.path, '') for path in fs.listTree(project.path) when fs.isFile path) - - waitsForPromise -> - project.getFilePaths().done (result) -> - expect(result).toEqual(expectedPaths) - describe ".resolve(path)", -> it "returns an absolute path based on the project's root", -> absolutePath = require.resolve('fixtures/dir/a') diff --git a/spec/app/root-view-spec.coffee b/spec/app/root-view-spec.coffee index d6e948d41..c534abb0b 100644 --- a/spec/app/root-view-spec.coffee +++ b/spec/app/root-view-spec.coffee @@ -341,67 +341,6 @@ describe "RootView", -> rootView.deactivate() expect(extension.deactivate).toHaveBeenCalled() - describe "the file finder", -> - describe "when the toggle-file-finder event is triggered", -> - describe "when there is a project", -> - it "shows the FileFinder when it is not on screen and hides it when it is", -> - rootView.attachToDom() - expect(rootView.find('.file-finder')).not.toExist() - - rootView.find('.editor').trigger 'split-right' - [editor1, editor2] = rootView.find('.editor').map -> $(this).view() - - rootView.trigger 'toggle-file-finder' - - expect(rootView.find('.file-finder')).toExist() - expect(rootView.find('.file-finder input:focus')).toExist() - rootView.trigger 'toggle-file-finder' - - expect(editor1.isFocused).toBeFalsy() - expect(editor2.isFocused).toBeTruthy() - expect(rootView.find('.editor:has(:focus)')).toExist() - expect(rootView.find('.file-finder')).not.toExist() - - it "shows all relative file paths for the current project", -> - rootView.trigger 'toggle-file-finder' - - project.getFilePaths().done (paths) -> - expect(rootView.fileFinder.pathList.children('li').length).toBe paths.length - - for path in paths - relativePath = project.relativize(path) - expect(rootView.fileFinder.pathList.find("li:contains(#{relativePath}):not(:contains(#{project.path}))")).toExist() - - describe "when there is no project", -> - beforeEach -> - rootView = new RootView - - it "does not open the FileFinder", -> - expect(rootView.activeEditor().buffer.path).toBeUndefined() - expect(rootView.find('.file-finder')).not.toExist() - rootView.trigger 'toggle-file-finder' - expect(rootView.find('.file-finder')).not.toExist() - - describe "when a path is selected in the file finder", -> - it "opens the file associated with that path in the editor", -> - rootView.attachToDom() - rootView.find('.editor').trigger 'split-right' - [editor1, editor2] = rootView.find('.editor').map -> $(this).view() - - rootView.trigger 'toggle-file-finder' - rootView.fileFinder.trigger 'move-down' - selectedLi = rootView.fileFinder.find('li:eq(1)') - - expectedPath = fs.join(project.path, selectedLi.text()) - expect(editor1.buffer.path).not.toBe expectedPath - expect(editor2.buffer.path).not.toBe expectedPath - - rootView.fileFinder.trigger 'file-finder:select-file' - - expect(editor1.buffer.path).not.toBe expectedPath - expect(editor2.buffer.path).toBe expectedPath - expect(editor2.isFocused).toBeTruthy() - describe "keymap wiring", -> commandHandler = null beforeEach -> diff --git a/spec/extensions/file-finder-spec.coffee b/spec/extensions/file-finder-spec.coffee index 3ffbb42a3..9398fadd0 100644 --- a/spec/extensions/file-finder-spec.coffee +++ b/spec/extensions/file-finder-spec.coffee @@ -1,40 +1,82 @@ +RootView = require 'root-view' FileFinder = require 'file-finder' - +$ = require 'jquery' {$$} = require 'space-pen' describe 'FileFinder', -> - finder = null - paths = null + [rootView, finder] = [] beforeEach -> - paths = ['app.coffee', 'buffer.coffee', 'atom/app.coffee', 'atom/buffer.coffee'] - finder = new FileFinder({paths}) - finder.enableKeymap() + rootView = new RootView(pathToOpen: require.resolve('fixtures/sample.js')) + rootView.enableKeymap() + rootView.activateExtension(FileFinder) + finder = FileFinder.instance - describe "initialize", -> - it "populates the ol with all paths and selects the first element", -> - expect(finder.pathList.find('li').length).toBe paths.length - expect(finder.pathList.find('li:first')).toHaveClass('selected') - expect(finder.pathList.find('li.selected').length).toBe 1 + describe "when the file-finder:toggle event is triggered on the root view", -> + describe "when there is a project", -> + it "shows or hides the FileFinder, returning focus to the active editor when hiding it", -> + rootView.attachToDom() + expect(rootView.find('.file-finder')).not.toExist() + rootView.find('.editor').trigger 'split-right' + [editor1, editor2] = rootView.find('.editor').map -> $(this).view() + + rootView.trigger 'file-finder:toggle' + expect(rootView.find('.file-finder')).toExist() + expect(rootView.find('.file-finder input:focus')).toExist() + + rootView.trigger 'file-finder:toggle' + expect(editor1.isFocused).toBeFalsy() + expect(editor2.isFocused).toBeTruthy() + expect(rootView.find('.file-finder')).not.toExist() + + it "shows all relative file paths for the current project and selects the first", -> + rootView.trigger 'file-finder:toggle' + rootView.project.getFilePaths().done (paths) -> + expect(finder.pathList.children('li').length).toBe paths.length + for path in paths + expect(finder.pathList.find("li:contains(#{path})")).toExist() + expect(finder.pathList.children().first()).toHaveClass 'selected' + + describe "when root view's project has no path", -> + beforeEach -> + rootView.project.path = undefined + + it "does not open the FileFinder", -> + expect(rootView.find('.file-finder')).not.toExist() + rootView.trigger 'file-finder:toggle' + expect(rootView.find('.file-finder')).not.toExist() + + describe "file-finder:cancel event", -> + it "hides the finder", -> + rootView.trigger 'file-finder:toggle' + expect(finder.hasParent()).toBeTruthy() + + finder.trigger 'file-finder:cancel' + expect(finder.hasParent()).toBeFalsy() describe "when characters are typed into the input element", -> it "displays matching paths in the ol element and selects the first", -> - finder.editor.insertText('ap') + rootView.trigger 'file-finder:toggle' - expect(finder.pathList.children().length).toBe 2 - expect(finder.pathList.find('li:contains(app.coffee)').length).toBe 2 - expect(finder.pathList.find('li:contains(atom/app.coffee)').length).toBe 1 + listLengthBefore = finder.pathList.children().length + + finder.editor.insertText('samp') + + expect(finder.pathList.children().length).toBeLessThan(listLengthBefore) expect(finder.pathList.find('li:first')).toHaveClass 'selected' expect(finder.pathList.find('li.selected').length).toBe 1 # we should clear the list before re-populating it - finder.editor.setCursorScreenPosition([0, 0]) - finder.editor.insertText('a/') + finder.editor.insertText('txt') expect(finder.pathList.children().length).toBe 1 - expect(finder.pathList.find('li:contains(atom/app.coffee)').length).toBe 1 + expect(finder.pathList.find('li:first')).toHaveClass 'selected' + expect(finder.pathList.find('li:first')).toHaveText 'sample.txt' describe "move-down / move-up events", -> + beforeEach -> + rootView.trigger 'file-finder:toggle' + it "selects the next / previous path in the list", -> expect(finder.find('li:eq(0)')).toHaveClass "selected" expect(finder.find('li:eq(2)')).not.toHaveClass "selected" @@ -56,41 +98,55 @@ describe 'FileFinder', -> finder.editor.trigger keydownEvent('up') expect(finder.find('li:first')).toHaveClass "selected" - for i in [1..paths.length+10] + for i in [1..finder.pathList.children().length+2] finder.editor.trigger keydownEvent('down') expect(finder.find('li:last')).toHaveClass "selected" - describe "select", -> - selectedCallback = jasmine.createSpy 'selected' + describe "select-file events", -> + [editor1, editor2] = [] beforeEach -> - finder = new FileFinder({paths, selected: selectedCallback}) - finder.enableKeymap() + rootView.find('.editor').trigger 'split-right' + [editor1, editor2] = rootView.find('.editor').map -> $(this).view() - it "when a file is selected Editor.open is called", -> - spyOn(finder, 'remove') - finder.moveDown() - finder.editor.trigger keydownEvent('enter') - expect(selectedCallback).toHaveBeenCalledWith(paths[1]) - expect(finder.remove).toHaveBeenCalled() + rootView.trigger 'file-finder:toggle' - it "when no file is selected, does nothing", -> - spyOn(atom, 'open') - finder.editor.insertText('this-will-match-nothing-hopefully') - finder.populatePathList() - finder.editor.trigger keydownEvent('enter') - expect(atom.open).not.toHaveBeenCalled() + describe "when there is a path selected", -> + it "opens the file associated with that path in the editor", -> + finder.trigger 'move-down' + selectedLi = finder.find('li:eq(1)') + + expectedPath = rootView.project.resolve(selectedLi.text()) + expect(editor1.buffer.path).not.toBe expectedPath + expect(editor2.buffer.path).not.toBe expectedPath + + finder.trigger 'file-finder:select-file' + + expect(finder.hasParent()).toBeFalsy() + expect(editor1.buffer.path).not.toBe expectedPath + expect(editor2.buffer.path).toBe expectedPath + expect(editor2.isFocused).toBeTruthy() + + describe "when there is no path selected", -> + it "does nothing", -> + finder.editor.insertText('this should match nothing, because no one wants to drink battery acid') + finder.trigger 'file-finder:select-file' + expect(finder.hasParent()).toBeTruthy() describe "findMatches(queryString)", -> - it "returns up to finder.maxResults paths if queryString is empty", -> - expect(paths.length).toBeLessThan finder.maxResults - expect(finder.findMatches('').length).toBe paths.length + beforeEach -> + rootView.trigger 'file-finder:toggle' - finder.maxResults = paths.length - 1 + it "returns up to finder.maxResults paths if queryString is empty", -> + expect(finder.paths.length).toBeLessThan finder.maxResults + expect(finder.findMatches('').length).toBe finder.paths.length + + finder.maxResults = finder.paths.length - 1 expect(finder.findMatches('').length).toBe finder.maxResults it "returns paths sorted by score of match against the given query", -> - expect(finder.findMatches('ap')).toEqual ["app.coffee", "atom/app.coffee"] - expect(finder.findMatches('a/ap')).toEqual ["atom/app.coffee"] + finder.paths = ["app.coffee", "atom/app.coffee"] + expect(finder.findMatches('app.co')).toEqual ["app.coffee", "atom/app.coffee"] + expect(finder.findMatches('atom/app.co')).toEqual ["atom/app.coffee"] diff --git a/src/app/keymaps/file-finder.coffee b/src/app/keymaps/file-finder.coffee index d51c9dde1..eb63864a6 100644 --- a/src/app/keymaps/file-finder.coffee +++ b/src/app/keymaps/file-finder.coffee @@ -1,6 +1,6 @@ window.keymap.bindKeys '*' - 'meta-t': 'toggle-file-finder' + 'meta-t': 'file-finder:toggle' window.keymap.bindKeys ".file-finder .editor", 'enter': 'file-finder:select-file', - 'escape': 'file-finder:close' + 'escape': 'file-finder:cancel' diff --git a/src/app/project.coffee b/src/app/project.coffee index 0eb226331..84fe52602 100644 --- a/src/app/project.coffee +++ b/src/app/project.coffee @@ -32,8 +32,8 @@ class Project getFilePaths: -> projectPath = @path - fs.async.listTree(@path).pipe (paths) -> - path.replace(projectPath, "") for path in paths when fs.isFile(path) + fs.async.listTree(@path).pipe (paths) => + @relativize(path) for path in paths when fs.isFile(path) open: (filePath) -> if filePath? diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 98c07dcc3..dd63ceae5 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -6,7 +6,6 @@ _ = require 'underscore' {View} = require 'space-pen' Buffer = require 'buffer' Editor = require 'editor' -FileFinder = require 'file-finder' Project = require 'project' VimMode = require 'vim-mode' CommandPanel = require 'command-panel' @@ -29,7 +28,6 @@ class RootView extends View extensionStates: null initialize: ({ pathToOpen, projectPath, panesViewState, @extensionStates }) -> - @on 'toggle-file-finder', => @toggleFileFinder() @on 'show-console', => window.showConsole() @on 'focus', (e) => if @activeEditor() @@ -74,7 +72,7 @@ class RootView extends View serializeExtensions: -> extensionStates = {} for name, extension of @extensions - extensionStates[name] = extension.serialize() + extensionStates[name] = extension.serialize?() extensionStates @@ -91,7 +89,7 @@ class RootView extends View deactivate: -> atom.rootViewStates[$windowNumber] = @serialize() - extension.deactivate() for name, extension of @extensions + extension.deactivate?() for name, extension of @extensions @remove() open: (path, changeFocus=true) -> @@ -139,21 +137,6 @@ class RootView extends View rootPane?.css(width: '100%', height: '100%', top: 0, left: 0) rootPane?.adjustDimensions() - toggleFileFinder: -> - return unless @project.getPath()? - - if @fileFinder and @fileFinder.parent()[0] - @fileFinder.remove() - @fileFinder = null - else - @project.getFilePaths().done (paths) => - relativePaths = (@project.relativize(path) for path in paths) - @fileFinder = new FileFinder - paths: relativePaths - selected: (relativePath) => @open(relativePath) - @append @fileFinder - @fileFinder.editor.focus() - remove: -> editor.remove() for editor in @editors() super diff --git a/src/extensions/file-finder.coffee b/src/extensions/file-finder.coffee index f90e153eb..26d061b26 100644 --- a/src/extensions/file-finder.coffee +++ b/src/extensions/file-finder.coffee @@ -1,11 +1,14 @@ -$ = require 'jquery' -{View} = require 'space-pen' +{View, $$} = require 'space-pen' stringScore = require 'stringscore' fuzzyFilter = require 'fuzzy-filter' Editor = require 'editor' module.exports = class FileFinder extends View + @activate: (rootView) -> + @instance = new FileFinder(rootView) + rootView.on 'file-finder:toggle', => @instance.toggle() + @content: -> @div class: 'file-finder', => @ol outlet: 'pathList' @@ -14,13 +17,11 @@ class FileFinder extends View paths: null maxResults: null - initialize: ({@paths, @selected}) -> + initialize: (@rootView) -> requireStylesheet 'file-finder.css' @maxResults = 10 - @populatePathList() - - @on 'file-finder:close', => @remove() + @on 'file-finder:cancel', => @detach() @on 'move-up', => @moveUp() @on 'move-down', => @moveDown() @on 'file-finder:select-file', => @select() @@ -29,10 +30,25 @@ class FileFinder extends View @editor.buffer.on 'change', => @populatePathList() @editor.off 'move-up move-down' + toggle: -> + if @hasParent() + @detach() + else + @attach() if @rootView.project.path? + + attach: -> + @rootView.project.getFilePaths().done (@paths) => @populatePathList() + @rootView.append(this) + @editor.focus() + + detach: -> + @rootView.focus() + super + populatePathList: -> @pathList.empty() for path in @findMatches(@editor.buffer.getText()) - @pathList.append $("
  • #{path}
  • ") + @pathList.append $$ -> @li path @pathList.children('li:first').addClass 'selected' @@ -40,8 +56,9 @@ class FileFinder extends View @pathList.children('li.selected') select: -> - filePath = @findSelectedLi().text() - @selected(filePath) if filePath and @selected + selectedLi = @findSelectedLi() + return unless selectedLi.length + @rootView.open(selectedLi.text()) @remove() moveUp: -> @@ -60,7 +77,3 @@ class FileFinder extends View findMatches: (query) -> fuzzyFilter(@paths, query, maxResults: @maxResults) - - remove: -> - $('#root-view').focus() - super diff --git a/src/stdlib/jquery-extensions.coffee b/src/stdlib/jquery-extensions.coffee index 089884618..5ff8b7dab 100644 --- a/src/stdlib/jquery-extensions.coffee +++ b/src/stdlib/jquery-extensions.coffee @@ -22,3 +22,6 @@ $.fn.preempt = (eventName, handler) -> eventNameWithoutNamespace = eventName.split('.')[0] handlers = @data('events')[eventNameWithoutNamespace] handlers.unshift(handlers.pop()) + +$.fn.hasParent = -> + @parent()[0]?