From 5981f4738729b1a61b6da0e86c2d3109180af0e9 Mon Sep 17 00:00:00 2001 From: Alhadis Date: Wed, 7 Sep 2016 16:44:36 +1000 Subject: [PATCH 01/14] Fire events when creating, moving or deleting items --- lib/copy-dialog.coffee | 4 +++- lib/move-dialog.coffee | 1 + lib/tree-view.coffee | 32 ++++++++++++++++++++++++-------- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/lib/copy-dialog.coffee b/lib/copy-dialog.coffee index d80863f2..5509949e 100644 --- a/lib/copy-dialog.coffee +++ b/lib/copy-dialog.coffee @@ -32,8 +32,10 @@ class CopyDialog extends Dialog try if fs.isDirectorySync(@initialPath) fs.copySync(@initialPath, newPath) + @trigger 'entry-copied', [@initialPath, newPath] else - fs.copy @initialPath, newPath, -> + fs.copy @initialPath, newPath, => + @trigger 'entry-copied', [@initialPath, newPath] atom.workspace.open newPath, activatePane: true initialLine: activeEditor?.getLastCursor().getBufferRow() diff --git a/lib/move-dialog.coffee b/lib/move-dialog.coffee index 69d50d8e..d601db5c 100644 --- a/lib/move-dialog.coffee +++ b/lib/move-dialog.coffee @@ -36,6 +36,7 @@ class MoveDialog extends Dialog try fs.makeTreeSync(directoryPath) unless fs.existsSync(directoryPath) fs.moveSync(@initialPath, newPath) + @trigger 'entry-moved', [@initialPath, newPath] if repo = repoForPath(newPath) repo.getPathStatus(@initialPath) repo.getPathStatus(newPath) diff --git a/lib/tree-view.coffee b/lib/tree-view.coffee index 3eade0df..eb9b8519 100644 --- a/lib/tree-view.coffee +++ b/lib/tree-view.coffee @@ -2,7 +2,7 @@ path = require 'path' {shell} = require 'electron' _ = require 'underscore-plus' -{BufferedProcess, CompositeDisposable} = require 'atom' +{BufferedProcess, CompositeDisposable, Emitter} = require 'atom' {repoForPath, getStyleObject, getFullExtension} = require "./helpers" {$, View} = require 'atom-space-pen-views' fs = require 'fs-plus' @@ -32,6 +32,7 @@ class TreeView extends View initialize: (state) -> @disposables = new CompositeDisposable + @emitter = new Emitter @focusAfterAttach = false @roots = [] @scrollLeftAfterAttach = -1 @@ -453,6 +454,8 @@ class TreeView extends View if oldPath MoveDialog ?= require './move-dialog' dialog = new MoveDialog(oldPath) + dialog.on 'entry-moved', (event, oldPath, newPath) => + @emitter.emit 'entry-moved', {oldPath, newPath} dialog.attach() # Get the outline of a system call to the current platform's file manager. @@ -533,6 +536,8 @@ class TreeView extends View CopyDialog ?= require './copy-dialog' dialog = new CopyDialog(oldPath) + dialog.on 'entry-copied', (event, oldPath, newPath) => + @emitter.emit 'entry-copied', {oldPath, newPath} dialog.attach() removeSelectedEntries: -> @@ -554,10 +559,12 @@ class TreeView extends View message: "Are you sure you want to delete the selected #{if selectedPaths.length > 1 then 'items' else 'item'}?" detailedMessage: "You are deleting:\n#{selectedPaths.join('\n')}" buttons: - "Move to Trash": -> + "Move to Trash": => failedDeletions = [] for selectedPath in selectedPaths - if not shell.moveItemToTrash(selectedPath) + if shell.moveItemToTrash(selectedPath) + @emitter.emit 'entry-deleted', {path: selectedPath} + else failedDeletions.push "#{selectedPath}" if repo = repoForPath(selectedPath) repo.getPathStatus(selectedPath) @@ -633,15 +640,21 @@ class TreeView extends View if fs.isDirectorySync(initialPath) # use fs.copy to copy directories since read/write will fail for directories - catchAndShowFileErrors -> fs.copySync(initialPath, newPath) + catchAndShowFileErrors => + fs.copySync(initialPath, newPath) + @emitter.emit 'entry-copied', {oldPath: initialPath, newPath} else # read the old file and write a new one at target location - catchAndShowFileErrors -> fs.writeFileSync(newPath, fs.readFileSync(initialPath)) + catchAndShowFileErrors => + fs.writeFileSync(newPath, fs.readFileSync(initialPath)) + @emitter.emit 'entry-copied', {oldPath: initialPath, newPath} else if cutPaths - # Only move the target if the cut target doesn't exists and if the newPath + # Only move the target if the cut target doesn't exist and if the newPath # is not within the initial path unless fs.existsSync(newPath) or newPath.startsWith(initialPath) - catchAndShowFileErrors -> fs.moveSync(initialPath, newPath) + catchAndShowFileErrors => + fs.moveSync(initialPath, newPath) + @emitter.emit 'entry-moved', {oldPath: initialPath, newPath} add: (isCreatingFile) -> selectedEntry = @selectedEntry() ? @roots[0] @@ -652,9 +665,11 @@ class TreeView extends View dialog.on 'directory-created', (event, createdPath) => @entryForPath(createdPath)?.reload() @selectEntryForPath(createdPath) + @emitter.emit 'directory-created', {path: createdPath} false - dialog.on 'file-created', (event, createdPath) -> + dialog.on 'file-created', (event, createdPath) => atom.workspace.open(createdPath) + @emitter.emit 'file-created', {path: createdPath} false dialog.attach() @@ -725,6 +740,7 @@ class TreeView extends View try fs.makeTreeSync(newDirectoryPath) unless fs.existsSync(newDirectoryPath) fs.moveSync(initialPath, newPath) + @emitter.emit 'entry-moved', {oldPath: initialPath, newPath} if repo = repoForPath(newPath) repo.getPathStatus(initialPath) From c0885505e57330a1626417273fe1b249fda36d58 Mon Sep 17 00:00:00 2001 From: Alhadis Date: Wed, 7 Sep 2016 18:27:07 +1000 Subject: [PATCH 02/14] Add TreeView methods to attach file-event handlers --- lib/tree-view.coffee | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/tree-view.coffee b/lib/tree-view.coffee index eb9b8519..0c20f805 100644 --- a/lib/tree-view.coffee +++ b/lib/tree-view.coffee @@ -88,6 +88,21 @@ class TreeView extends View @disposables.dispose() @detach() if @panel? + onDirectoryCreated: (callback) -> + @emitter.on('directory-created', callback) + + onEntryCopied: (callback) -> + @emitter.on('entry-copied', callback) + + onEntryDeleted: (callback) -> + @emitter.on('entry-deleted', callback) + + onEntryMoved: (callback) -> + @emitter.on('entry-moved', callback) + + onFileCreated: (callback) -> + @emitter.on('file-created', callback) + handleEvents: -> @on 'dblclick', '.tree-view-resize-handle', => @resizeToFitContent() From 1488a665f3a2d9a2c9495f839a6e885646752055 Mon Sep 17 00:00:00 2001 From: Alhadis Date: Wed, 7 Sep 2016 23:00:36 +1000 Subject: [PATCH 03/14] Add specs for 5981f47 and c088550 --- spec/tree-view-spec.coffee | 93 ++++++++++++++++++++++++++++++-------- 1 file changed, 74 insertions(+), 19 deletions(-) diff --git a/spec/tree-view-spec.coffee b/spec/tree-view-spec.coffee index 10470b6b..e7078ae4 100644 --- a/spec/tree-view-spec.coffee +++ b/spec/tree-view-spec.coffee @@ -1492,18 +1492,29 @@ describe "TreeView", -> LocalStorage.clear() describe "when attempting to paste a directory into itself", -> + handlers = null + describe "when copied", -> - it "makes a copy inside itself", -> + beforeEach -> LocalStorage['tree-view:copyPath'] = JSON.stringify([dirPath]) + handlers = treeView.emitter.handlersByEventName + it "makes a copy inside itself", -> dirView.click() newPath = path.join(dirPath, path.basename(dirPath)) expect(-> atom.commands.dispatch(treeView.element, "tree-view:paste")).not.toThrow() expect(fs.existsSync(newPath)).toBeTruthy() + it "dispatches an event to the tree-view", -> + treeView.onEntryCopied -> + spyOn(handlers['entry-copied'], '0') + + dirView.click() + expect(-> atom.commands.dispatch(treeView.element, "tree-view:paste")).not.toThrow() + expect(handlers['entry-copied'][0].callCount).toEqual(1) + it 'does not keep copying recursively', -> - LocalStorage['tree-view:copyPath'] = JSON.stringify([dirPath]) dirView.click() newPath = path.join(dirPath, path.basename(dirPath)) @@ -1538,19 +1549,21 @@ describe "TreeView", -> describe "when a file has been copied", -> describe "when a file is selected", -> - it "creates a copy of the original file in the selected file's parent directory", -> + beforeEach -> LocalStorage['tree-view:copyPath'] = JSON.stringify([filePath]) + it "creates a copy of the original file in the selected file's parent directory", -> + treeView.onEntryCopied -> + spyOn(treeView.emitter.handlersByEventName['entry-copied'], '0') fileView2.click() atom.commands.dispatch(treeView.element, "tree-view:paste") expect(fs.existsSync(path.join(dirPath2, path.basename(filePath)))).toBeTruthy() expect(fs.existsSync(filePath)).toBeTruthy() + expect(treeView.emitter.handlersByEventName['entry-copied'][0].callCount).toEqual(1) describe "when the target already exists", -> it "appends a number to the destination name", -> - LocalStorage['tree-view:copyPath'] = JSON.stringify([filePath]) - fileView.click() atom.commands.dispatch(treeView.element, "tree-view:paste") atom.commands.dispatch(treeView.element, "tree-view:paste") @@ -1696,20 +1709,25 @@ describe "TreeView", -> expect(fs.existsSync(path.join(dirPath, "test-file30.txt"))).toBeTruthy() describe "when a file has been cut", -> + handlers = null + + beforeEach -> + LocalStorage['tree-view:cutPath'] = JSON.stringify([filePath]) + handlers = treeView.emitter.handlersByEventName + treeView.onEntryMoved -> + spyOn(handlers['entry-moved'], '0') + describe "when a file is selected", -> it "creates a copy of the original file in the selected file's parent directory and removes the original", -> - LocalStorage['tree-view:cutPath'] = JSON.stringify([filePath]) - fileView2.click() atom.commands.dispatch(treeView.element, "tree-view:paste") expect(fs.existsSync(path.join(dirPath2, path.basename(filePath)))).toBeTruthy() expect(fs.existsSync(filePath)).toBeFalsy() + expect(handlers['entry-moved'][0].callCount).toEqual(1) describe 'when the target destination file exists', -> it 'does not move the cut file', -> - LocalStorage['tree-view:cutPath'] = JSON.stringify([filePath]) - filePath3 = path.join(dirPath2, "test-file.txt") fs.writeFileSync(filePath3, "doesn't matter") @@ -1717,6 +1735,7 @@ describe "TreeView", -> atom.commands.dispatch(treeView.element, "tree-view:paste") expect(fs.existsSync(filePath)).toBeTruthy() + expect(handlers['entry-moved'][0].callCount).toEqual(0) describe "when a directory is selected", -> it "creates a copy of the original file in the selected directory and removes the original", -> @@ -1727,12 +1746,19 @@ describe "TreeView", -> expect(fs.existsSync(path.join(dirPath2, path.basename(filePath)))).toBeTruthy() expect(fs.existsSync(filePath)).toBeFalsy() + expect(handlers['entry-moved'][0].callCount).toEqual(1) describe "when multiple files have been cut", -> describe "when a file is selected", -> - it "moves the selected files to the parent directory of the selected file", -> + handlers = null + + beforeEach -> LocalStorage['tree-view:cutPath'] = JSON.stringify([filePath2, filePath3]) + handlers = treeView.emitter.handlersByEventName + treeView.onEntryMoved -> + spyOn(handlers['entry-moved'], '0') + it "moves the selected files to the parent directory of the selected file", -> fileView.click() atom.commands.dispatch(treeView.element, "tree-view:paste") @@ -1740,11 +1766,10 @@ describe "TreeView", -> expect(fs.existsSync(path.join(dirPath, path.basename(filePath3)))).toBeTruthy() expect(fs.existsSync(filePath2)).toBeFalsy() expect(fs.existsSync(filePath3)).toBeFalsy() + expect(handlers['entry-moved'][0].callCount).toEqual(2) describe 'when the target destination file exists', -> it 'does not move the cut file', -> - LocalStorage['tree-view:cutPath'] = JSON.stringify([filePath2, filePath3]) - filePath4 = path.join(dirPath, "test-file2.txt") filePath5 = path.join(dirPath, "test-file3.txt") fs.writeFileSync(filePath4, "doesn't matter") @@ -1755,6 +1780,7 @@ describe "TreeView", -> expect(fs.existsSync(filePath2)).toBeTruthy() expect(fs.existsSync(filePath3)).toBeTruthy() + expect(handlers['entry-moved'][0].callCount).toEqual(0) describe "when a directory is selected", -> it "creates a copy of the original file in the selected directory and removes the original", -> @@ -1784,11 +1810,15 @@ describe "TreeView", -> expect(atom.notifications.getNotifications()[0].getDetail()).toContain 'ENOENT: no such file or directory' describe "tree-view:add-file", -> - [addPanel, addDialog] = [] + [addPanel, addDialog, handlers] = [] beforeEach -> jasmine.attachToDOM(workspaceElement) + handlers = treeView.emitter.handlersByEventName + treeView.onFileCreated -> + spyOn(handlers['file-created'], '0') + waitsForFileToOpen -> fileView.click() @@ -1813,7 +1843,7 @@ describe "TreeView", -> describe "when the path without a trailing '#{path.sep}' is changed and confirmed", -> describe "when no file exists at that location", -> - it "add a file, closes the dialog and selects the file in the tree-view", -> + it "adds a file, closes the dialog and selects the file in the tree-view", -> newPath = path.join(dirPath, "new-test-file.txt") waitsForFileToOpen -> @@ -1830,6 +1860,7 @@ describe "TreeView", -> runs -> expect(treeView.find('.selected').text()).toBe path.basename(newPath) + expect(handlers['file-created'][0]).toHaveBeenCalled() it "adds file in any project path", -> newPath = path.join(dirPath3, "new-test-file.txt") @@ -1854,6 +1885,7 @@ describe "TreeView", -> runs -> expect(treeView.find('.selected').text()).toBe path.basename(newPath) + expect(handlers['file-created'][0]).toHaveBeenCalled() describe "when a file already exists at that location", -> it "shows an error message and does not close the dialog", -> @@ -1865,9 +1897,10 @@ describe "TreeView", -> expect(addDialog.errorMessage.text()).toContain 'already exists' expect(addDialog).toHaveClass('error') expect(atom.workspace.getModalPanels()[0]).toBe addPanel + expect(handlers['file-created'][0]).not.toHaveBeenCalled() describe "when the project has no path", -> - it "add a file and closes the dialog", -> + it "adds a file and closes the dialog", -> atom.project.setPaths([]) addDialog.close() atom.commands.dispatch(treeView.element, "tree-view:add-file") @@ -1884,6 +1917,7 @@ describe "TreeView", -> expect(fs.isFileSync(newPath)).toBeTruthy() expect(atom.workspace.getModalPanels().length).toBe 0 expect(atom.workspace.getActivePaneItem().getPath()).toBe fs.realpathSync(newPath) + expect(handlers['file-created'][0]).toHaveBeenCalled() describe "when the path with a trailing '#{path.sep}' is changed and confirmed", -> it "shows an error message and does not close the dialog", -> @@ -1964,11 +1998,15 @@ describe "TreeView", -> expect(addDialog.text()).toContain("You must open a directory to create a file with a relative path") describe "tree-view:add-folder", -> - [addPanel, addDialog] = [] + [addPanel, addDialog, handlers] = [] beforeEach -> jasmine.attachToDOM(workspaceElement) + handlers = treeView.emitter.handlersByEventName + treeView.onDirectoryCreated -> + spyOn(handlers['directory-created'], '0') + waitsForFileToOpen -> fileView.click() @@ -1997,6 +2035,7 @@ describe "TreeView", -> expect(atom.workspace.getActivePaneItem().getPath()).not.toBe newPath expect(treeView.find(".tree-view")).toMatchSelector(':focus') expect(dirView.find('.directory.selected:contains(new)').length).toBe 1 + expect(handlers['directory-created'][0].callCount).toBe 1 describe "when the path with a trailing '#{path.sep}' is changed and confirmed", -> describe "when no directory exists at the given path", -> @@ -2009,6 +2048,7 @@ describe "TreeView", -> expect(atom.workspace.getActivePaneItem().getPath()).not.toBe newPath expect(treeView.find(".tree-view")).toMatchSelector(':focus') expect(dirView.find('.directory.selected:contains(new)').length).toBe(1) + expect(handlers['directory-created'][0].callCount).toBe(1) it "selects the created directory and does not change the expansion state of existing directories", -> expandedPath = path.join(dirPath, 'expanded-dir') @@ -2026,6 +2066,7 @@ describe "TreeView", -> expect(atom.workspace.getActivePaneItem().getPath()).not.toBe newPath expect(treeView.find(".tree-view")).toMatchSelector(':focus') expect(dirView.find('.directory.selected:contains(new2)').length).toBe(1) + expect(handlers['directory-created'][0].callCount).toBe(1) expect(treeView.entryForPath(expandedPath).isExpanded).toBeTruthy() describe "when the project has no path", -> @@ -2041,6 +2082,7 @@ describe "TreeView", -> addDialog.miniEditor.getModel().insertText(newPath) atom.commands.dispatch addDialog.element, 'core:confirm' expect(fs.isDirectorySync(newPath)).toBeTruthy() + expect(handlers['directory-created'][0].callCount).toBe 1 expect(atom.workspace.getModalPanels().length).toBe 0 describe "when a directory already exists at the given path", -> @@ -2053,14 +2095,19 @@ describe "TreeView", -> expect(addDialog.errorMessage.text()).toContain 'already exists' expect(addDialog).toHaveClass('error') expect(atom.workspace.getModalPanels()[0]).toBe addPanel + expect(handlers['directory-created'][0].callCount).toBe 0 describe "tree-view:move", -> describe "when a file is selected", -> - moveDialog = null + [moveDialog, handlers] = [] beforeEach -> jasmine.attachToDOM(workspaceElement) + handlers = treeView.emitter.handlersByEventName + treeView.onEntryMoved -> + spyOn(handlers['entry-moved'], '0') + waitsForFileToOpen -> fileView.click() @@ -2099,6 +2146,7 @@ describe "TreeView", -> dirView = $(treeView.roots[0].entries).find('.directory:contains(test-dir)') dirView[0].expand() expect($(dirView[0].entries).children().length).toBe 0 + expect(handlers['entry-moved'][0].callCount).toBe 1 describe "when the directories along the new path don't exist", -> it "creates the target directory before moving the file", -> @@ -2113,6 +2161,7 @@ describe "TreeView", -> runs -> expect(fs.existsSync(newPath)).toBeTruthy() expect(fs.existsSync(filePath)).toBeFalsy() + expect(handlers['entry-moved'][0].callCount).toBe 1 describe "when a file or directory already exists at the target path", -> it "shows an error message and does not close the dialog", -> @@ -2126,6 +2175,7 @@ describe "TreeView", -> expect(moveDialog.errorMessage.text()).toContain 'already exists' expect(moveDialog).toHaveClass('error') expect(moveDialog.hasParent()).toBeTruthy() + expect(handlers['entry-moved'][0].callCount).toBe 0 describe "when 'core:cancel' is triggered on the move dialog", -> it "removes the dialog and focuses the tree view", -> @@ -2648,6 +2698,10 @@ describe "TreeView", -> describe "when the file is deleted", -> it "updates the style of the directory", -> + handlers = treeView.emitter.handlersByEventName + treeView.onEntryDeleted -> + spyOn(handlers['entry-deleted'], '0') + expect(treeView.selectedEntry().getPath()).toContain(path.join('dir2', 'new2')) dirView = $(treeView.roots[0].entries).find('.directory:contains(dir2)') expect(dirView).not.toBeNull() @@ -2656,6 +2710,7 @@ describe "TreeView", -> dialog.buttons["Move to Trash"]() atom.commands.dispatch(treeView.element, 'tree-view:remove') expect(dirView[0].directory.updateStatus).toHaveBeenCalled() + expect(handlers['entry-deleted'][0]).toHaveBeenCalled() describe "when the project is a symbolic link to the repository root", -> beforeEach -> @@ -2802,7 +2857,7 @@ describe "TreeView", -> Object.defineProperty(process, "platform", {__proto__: null, value: 'win32'}) afterEach -> - # Ensure that process.platform is set back to it's original value + # Ensure that process.platform is set back to its original value Object.defineProperty(process, "platform", {__proto__: null, value: originalPlatform}) describe 'using the ctrl key', -> @@ -2821,7 +2876,7 @@ describe "TreeView", -> Object.defineProperty(process, "platform", {__proto__: null, value: 'darwin'}) afterEach -> - # Ensure that process.platform is set back to it's original value + # Ensure that process.platform is set back to its original value Object.defineProperty(process, "platform", {__proto__: null, value: originalPlatform}) describe 'using the ctrl key', -> From 74b1b7ec00e572e757d1715ff1f25e73c987d259 Mon Sep 17 00:00:00 2001 From: Alhadis Date: Wed, 7 Sep 2016 23:24:19 +1000 Subject: [PATCH 04/14] Fix typos and polish punctuation of spec titles --- spec/tree-view-spec.coffee | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/spec/tree-view-spec.coffee b/spec/tree-view-spec.coffee index e7078ae4..601ff759 100644 --- a/spec/tree-view-spec.coffee +++ b/spec/tree-view-spec.coffee @@ -2820,7 +2820,7 @@ describe "TreeView", -> fileView3 = treeView.find('.file:contains(test-file3.txt)') describe 'selecting multiple items', -> - it 'switches the contextual menu to muli-select mode', -> + it 'switches the contextual menu to multi-select mode', -> fileView1.click() fileView2.trigger($.Event('mousedown', {shiftKey: true})) expect(treeView.find('.tree-view')).toHaveClass('multi-select') @@ -2828,13 +2828,13 @@ describe "TreeView", -> expect(treeView.find('.tree-view')).toHaveClass('full-menu') describe 'selecting multiple items', -> - it 'switches the contextual menu to muli-select mode', -> + it 'switches the contextual menu to multi-select mode', -> fileView1.click() fileView2.trigger($.Event('mousedown', {shiftKey: true})) expect(treeView.find('.tree-view')).toHaveClass('multi-select') describe 'using the shift key', -> - it 'selects the items between the already selected item and the shift clicked item', -> + it 'selects the items between the already selected item and the shift-clicked item', -> fileView1.click() fileView3.trigger($.Event('mousedown', {shiftKey: true})) expect(fileView1).toHaveClass('selected') @@ -2842,7 +2842,7 @@ describe "TreeView", -> expect(fileView3).toHaveClass('selected') describe 'using the metakey(cmd) key', -> - it 'selects the cmd clicked item in addition to the original selected item', -> + it 'selects the cmd-clicked item in addition to the original selected item', -> fileView1.click() fileView3.trigger($.Event('mousedown', {metaKey: true})) expect(fileView1).toHaveClass('selected') @@ -2861,7 +2861,7 @@ describe "TreeView", -> Object.defineProperty(process, "platform", {__proto__: null, value: originalPlatform}) describe 'using the ctrl key', -> - it 'selects the ctrl clicked item in addition to the original selected item', -> + it 'selects the ctrl-clicked item in addition to the original selected item', -> fileView1.click() fileView3.trigger($.Event('mousedown', {ctrlKey: true})) expect(fileView1).toHaveClass('selected') @@ -2880,7 +2880,7 @@ describe "TreeView", -> Object.defineProperty(process, "platform", {__proto__: null, value: originalPlatform}) describe 'using the ctrl key', -> - describe "previous item is selected but the ctrl clicked item is not", -> + describe "previous item is selected but the ctrl-clicked item is not", -> it 'selects the clicked item, but deselects the previous item', -> fileView1.click() fileView3.trigger($.Event('mousedown', {ctrlKey: true})) @@ -2894,7 +2894,7 @@ describe "TreeView", -> expect(treeView.list).toHaveClass('full-menu') expect(treeView.list).not.toHaveClass('multi-select') - describe 'previous item is selected including the ctrl clicked', -> + describe 'previous item is selected including the ctrl-clicked', -> it 'displays the multi-select menu', -> fileView1.click() fileView3.trigger($.Event('mousedown', {metaKey: true})) @@ -2917,7 +2917,7 @@ describe "TreeView", -> expect(treeView.list).not.toHaveClass('multi-select') describe 'when no item is selected', -> - it 'selects the ctrl clicked item', -> + it 'selects the ctrl-clicked item', -> fileView3.trigger($.Event('mousedown', {ctrlKey: true})) expect(fileView3).toHaveClass('selected') @@ -2944,18 +2944,18 @@ describe "TreeView", -> expect(treeView.list).toHaveClass('full-menu') expect(treeView.list).not.toHaveClass('multi-select') - it 'selects right clicked item', -> + it 'selects right-clicked item', -> fileView1.click() fileView3.trigger($.Event('mousedown', {button: 2})) expect(fileView3).toHaveClass('selected') - it 'de-selects the previously selected item', -> + it 'deselects the previously selected item', -> fileView1.click() fileView3.trigger($.Event('mousedown', {button: 2})) expect(fileView1).not.toHaveClass('selected') describe 'when no item is selected', -> - it 'selects the right clicked item', -> + it 'selects the right-clicked item', -> fileView3.trigger($.Event('mousedown', {button: 2})) expect(fileView3).toHaveClass('selected') From 6d07f77b4735c91cd324d974a72b69aa2027b462 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 9 Mar 2017 19:09:35 -0800 Subject: [PATCH 05/14] Update editor paths when containing directory is renamed via move dialog --- lib/move-dialog.coffee | 8 ++++++++ spec/tree-view-spec.coffee | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lib/move-dialog.coffee b/lib/move-dialog.coffee index 69d50d8e..1498bf67 100644 --- a/lib/move-dialog.coffee +++ b/lib/move-dialog.coffee @@ -36,6 +36,7 @@ class MoveDialog extends Dialog try fs.makeTreeSync(directoryPath) unless fs.existsSync(directoryPath) fs.moveSync(@initialPath, newPath) + @updateEditorForPath(@initialPath, newPath) if repo = repoForPath(newPath) repo.getPathStatus(@initialPath) repo.getPathStatus(newPath) @@ -56,3 +57,10 @@ class MoveDialog extends Dialog oldStat.ino is newStat.ino catch true # new path does not exist so it is valid + + updateEditorForPath: (oldPath, newPath) -> + editors = atom.workspace.getTextEditors() + for editor in editors + filePath = editor.getPath() + if filePath.startsWith(oldPath) + editor.getBuffer().setPath(filePath.replace(oldPath, newPath)) diff --git a/spec/tree-view-spec.coffee b/spec/tree-view-spec.coffee index afac93fa..07569f1b 100644 --- a/spec/tree-view-spec.coffee +++ b/spec/tree-view-spec.coffee @@ -2162,6 +2162,42 @@ describe "TreeView", -> expect(moveDialog.miniEditor.getText()).toBe(atom.project.relativize(dotFilePath)) expect(moveDialog.miniEditor.getSelectedText()).toBe '.dotfile' + describe "when a subdirectory is selected", -> + moveDialog = null + + beforeEach -> + jasmine.attachToDOM(workspaceElement) + + waitsForFileToOpen -> + atom.workspace.open(filePath) + + runs -> + dirView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) + treeView.toggleFocus() + atom.commands.dispatch(treeView.element, "tree-view:move") + moveDialog = atom.workspace.getModalPanels()[0].getItem() + + afterEach -> + waits 50 # The move specs cause too many false positives because of their async nature, so wait a little bit before we cleanup + + it "opens a move dialog with the folder's current path populated", -> + extension = path.extname(dirPath) + expect(moveDialog.element).toExist() + expect(moveDialog.promptText.textContent).toBe "Enter the new path for the directory." + expect(moveDialog.miniEditor.getText()).toBe(atom.project.relativize(dirPath)) + expect(moveDialog.miniEditor.element).toHaveFocus() + + describe "when the path is changed and confirmed", -> + it "updates text editor paths accordingly", -> + editor = atom.workspace.getActiveTextEditor() + expect(editor.getPath()).toBe(filePath) + + newPath = path.join(rootDirPath, 'renamed-dir') + moveDialog.miniEditor.setText(newPath) + + atom.commands.dispatch moveDialog.element, 'core:confirm' + expect(editor.getPath()).toBe(filePath.replace('test-dir', 'renamed-dir')) + describe "when the project is selected", -> it "doesn't display the move dialog", -> treeView.roots[0].dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) From 081ead99d1ffc7b181a43f0274607c621c0ddcd0 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 9 Mar 2017 19:35:01 -0800 Subject: [PATCH 06/14] Update editor paths when containing directory is renamed via drag'n'drop --- lib/tree-view.coffee | 8 ++++++++ spec/tree-view-spec.coffee | 12 ++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/tree-view.coffee b/lib/tree-view.coffee index 5b7040aa..7a92aa6e 100644 --- a/lib/tree-view.coffee +++ b/lib/tree-view.coffee @@ -795,6 +795,7 @@ class TreeView try fs.makeTreeSync(newDirectoryPath) unless fs.existsSync(newDirectoryPath) fs.moveSync(initialPath, newPath) + @updateEditorForPath(initialPath, newPath) if repo = repoForPath(newPath) repo.getPathStatus(initialPath) @@ -803,6 +804,13 @@ class TreeView catch error atom.notifications.addWarning("Failed to move entry #{initialPath} to #{newDirectoryPath}", detail: error.message) + updateEditorForPath: (oldPath, newPath) -> + editors = atom.workspace.getTextEditors() + for editor in editors + filePath = editor.getPath() + if filePath.startsWith(oldPath) + editor.getBuffer().setPath(filePath.replace(oldPath, newPath)) + onStylesheetsChanged: => return unless @isVisible() diff --git a/spec/tree-view-spec.coffee b/spec/tree-view-spec.coffee index 07569f1b..2288829d 100644 --- a/spec/tree-view-spec.coffee +++ b/spec/tree-view-spec.coffee @@ -3191,6 +3191,7 @@ describe "TreeView", -> describe "Dragging and dropping files", -> deltaFilePath = null gammaDirPath = null + thetaFilePath = null beforeEach -> rootDirPath = fs.absolute(temp.mkdirSync('tree-view')) @@ -3206,6 +3207,7 @@ describe "TreeView", -> deltaFilePath = path.join(gammaDirPath, "delta.txt") epsilonFilePath = path.join(gammaDirPath, "epsilon.txt") thetaDirPath = path.join(gammaDirPath, "theta") + thetaFilePath = path.join(thetaDirPath, "theta.txt") fs.writeFileSync(alphaFilePath, "doesn't matter") fs.writeFileSync(zetaFilePath, "doesn't matter") @@ -3218,6 +3220,7 @@ describe "TreeView", -> fs.writeFileSync(deltaFilePath, "doesn't matter") fs.writeFileSync(epsilonFilePath, "doesn't matter") fs.makeTreeSync(thetaDirPath) + fs.writeFileSync(thetaFilePath, "doesn't matter") atom.project.setPaths([rootDirPath]) @@ -3278,11 +3281,14 @@ describe "TreeView", -> gammaDir = findDirectoryContainingText(treeView.roots[0], 'gamma') gammaDir.expand() thetaDir = gammaDir.entries.children[0] + thetaDir.expand() - [dragStartEvent, dragEnterEvent, dropEvent] = - eventHelpers.buildInternalDragEvents(thetaDir, alphaDir.querySelector('.header'), alphaDir) + waitsForFileToOpen -> + atom.workspace.open(thetaFilePath) runs -> + [dragStartEvent, dragEnterEvent, dropEvent] = + eventHelpers.buildInternalDragEvents(thetaDir, alphaDir.querySelector('.header'), alphaDir) treeView.onDragStart(dragStartEvent) treeView.onDrop(dropEvent) expect(alphaDir.children.length).toBe 2 @@ -3292,6 +3298,8 @@ describe "TreeView", -> runs -> expect(findDirectoryContainingText(treeView.roots[0], 'alpha').querySelectorAll('.entry').length).toBe 3 + editor = atom.workspace.getActiveTextEditor() + expect(editor.getPath()).toBe(thetaFilePath.replace('gamma', 'alpha')) describe "when dragging a file from the OS onto a DirectoryView's header", -> it "should move the file to the hovered directory", -> From 49c6893d7a8d8c6a7a0212f6c4ef51c3a60efdca Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 9 Mar 2017 19:37:47 -0800 Subject: [PATCH 07/14] Extract helper method updateEditorsForPath --- lib/helpers.coffee | 7 +++++++ lib/move-dialog.coffee | 11 ++--------- lib/tree-view.coffee | 11 ++--------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/lib/helpers.coffee b/lib/helpers.coffee index c67e0d97..1df45dd0 100644 --- a/lib/helpers.coffee +++ b/lib/helpers.coffee @@ -22,3 +22,10 @@ module.exports = fullExtension = extension + fullExtension filePath = path.basename(filePath, extension) fullExtension + + updateEditorsForPath: (oldPath, newPath) -> + editors = atom.workspace.getTextEditors() + for editor in editors + filePath = editor.getPath() + if filePath.startsWith(oldPath) + editor.getBuffer().setPath(filePath.replace(oldPath, newPath)) diff --git a/lib/move-dialog.coffee b/lib/move-dialog.coffee index 1498bf67..0be65527 100644 --- a/lib/move-dialog.coffee +++ b/lib/move-dialog.coffee @@ -1,7 +1,7 @@ path = require 'path' fs = require 'fs-plus' Dialog = require './dialog' -{repoForPath} = require "./helpers" +{repoForPath, updateEditorsForPath} = require "./helpers" module.exports = class MoveDialog extends Dialog @@ -36,7 +36,7 @@ class MoveDialog extends Dialog try fs.makeTreeSync(directoryPath) unless fs.existsSync(directoryPath) fs.moveSync(@initialPath, newPath) - @updateEditorForPath(@initialPath, newPath) + updateEditorsForPath(@initialPath, newPath) if repo = repoForPath(newPath) repo.getPathStatus(@initialPath) repo.getPathStatus(newPath) @@ -57,10 +57,3 @@ class MoveDialog extends Dialog oldStat.ino is newStat.ino catch true # new path does not exist so it is valid - - updateEditorForPath: (oldPath, newPath) -> - editors = atom.workspace.getTextEditors() - for editor in editors - filePath = editor.getPath() - if filePath.startsWith(oldPath) - editor.getBuffer().setPath(filePath.replace(oldPath, newPath)) diff --git a/lib/tree-view.coffee b/lib/tree-view.coffee index 7a92aa6e..8f494e7f 100644 --- a/lib/tree-view.coffee +++ b/lib/tree-view.coffee @@ -3,7 +3,7 @@ path = require 'path' _ = require 'underscore-plus' {BufferedProcess, CompositeDisposable} = require 'atom' -{repoForPath, getStyleObject, getFullExtension} = require "./helpers" +{repoForPath, getStyleObject, getFullExtension, updateEditorsForPath} = require "./helpers" fs = require 'fs-plus' AddDialog = require './add-dialog' @@ -795,7 +795,7 @@ class TreeView try fs.makeTreeSync(newDirectoryPath) unless fs.existsSync(newDirectoryPath) fs.moveSync(initialPath, newPath) - @updateEditorForPath(initialPath, newPath) + updateEditorsForPath(initialPath, newPath) if repo = repoForPath(newPath) repo.getPathStatus(initialPath) @@ -804,13 +804,6 @@ class TreeView catch error atom.notifications.addWarning("Failed to move entry #{initialPath} to #{newDirectoryPath}", detail: error.message) - updateEditorForPath: (oldPath, newPath) -> - editors = atom.workspace.getTextEditors() - for editor in editors - filePath = editor.getPath() - if filePath.startsWith(oldPath) - editor.getBuffer().setPath(filePath.replace(oldPath, newPath)) - onStylesheetsChanged: => return unless @isVisible() From 06d227f27a59f19dcc31e2ae9b0f67d296d8f8ba Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 9 Mar 2017 22:20:24 -0800 Subject: [PATCH 08/14] Close editors when containing directory is removed --- lib/helpers.coffee | 2 +- lib/tree-view.coffee | 2 +- spec/tree-view-spec.coffee | 23 +++++++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/helpers.coffee b/lib/helpers.coffee index 1df45dd0..6f31695d 100644 --- a/lib/helpers.coffee +++ b/lib/helpers.coffee @@ -27,5 +27,5 @@ module.exports = editors = atom.workspace.getTextEditors() for editor in editors filePath = editor.getPath() - if filePath.startsWith(oldPath) + if filePath?.startsWith(oldPath) editor.getBuffer().setPath(filePath.replace(oldPath, newPath)) diff --git a/lib/tree-view.coffee b/lib/tree-view.coffee index 8f494e7f..9536f427 100644 --- a/lib/tree-view.coffee +++ b/lib/tree-view.coffee @@ -607,7 +607,7 @@ class TreeView for selectedPath in selectedPaths if shell.moveItemToTrash(selectedPath) for editor in atom.workspace.getTextEditors() - if editor?.getPath() is selectedPath + if editor?.getPath()?.startsWith(selectedPath) editor.destroy() else failedDeletions.push "#{selectedPath}" diff --git a/spec/tree-view-spec.coffee b/spec/tree-view-spec.coffee index 2288829d..049a2068 100644 --- a/spec/tree-view-spec.coffee +++ b/spec/tree-view-spec.coffee @@ -2401,6 +2401,29 @@ describe "TreeView", -> expect(atom.confirm.mostRecentCall).not.toExist expect(atom.notifications.getNotifications().length).toBe 0 + describe "when a directory is removed", -> + it "closes editors with files belonging to the removed folder", -> + jasmine.attachToDOM(workspaceElement) + + waitsForFileToOpen -> + atom.workspace.open(filePath2) + + waitsForFileToOpen -> + atom.workspace.open(filePath3) + + runs -> + openFilePaths = atom.workspace.getTextEditors().map((e) -> e.getPath()) + expect(openFilePaths).toEqual([filePath2, filePath3]) + dirView2.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) + treeView.toggleFocus() + + spyOn(atom, 'confirm').andCallFake (dialog) -> + dialog.buttons["Move to Trash"]() + + atom.commands.dispatch(treeView.element, 'tree-view:remove') + openFilePaths = (editor.getPath() for editor in atom.workspace.getTextEditors()) + expect(openFilePaths).toEqual([]) + describe "file system events", -> temporaryFilePath = null From 15f87c7ec3e13b0ffe3355fdb01f36ecd192228a Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Sat, 25 Mar 2017 11:50:12 -0700 Subject: [PATCH 09/14] Update Move and CopyDialog to take callbacks --- lib/copy-dialog.coffee | 6 +++--- lib/move-dialog.coffee | 4 ++-- lib/tree-view.coffee | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/copy-dialog.coffee b/lib/copy-dialog.coffee index 3c260c61..bd484b9b 100644 --- a/lib/copy-dialog.coffee +++ b/lib/copy-dialog.coffee @@ -5,7 +5,7 @@ Dialog = require './dialog' module.exports = class CopyDialog extends Dialog - constructor: (@initialPath) -> + constructor: (@initialPath, {@onCopyCallback}) -> super prompt: 'Enter the new path for the duplicate.' initialPath: atom.project.relativize(@initialPath) @@ -32,10 +32,10 @@ class CopyDialog extends Dialog try if fs.isDirectorySync(@initialPath) fs.copySync(@initialPath, newPath) - @emitter.emit 'entry-copied', [@initialPath, newPath] + @onCopyCallback?({initialPath: @initialPath, newPath: newPath}) else fs.copy @initialPath, newPath, => - @emitter.emit 'entry-copied', [@initialPath, newPath] + @onCopyCallback?({initialPath: @initialPath, newPath: newPath}) atom.workspace.open newPath, activatePane: true initialLine: activeEditor?.getLastCursor().getBufferRow() diff --git a/lib/move-dialog.coffee b/lib/move-dialog.coffee index 9d53ac6d..4a68e9f1 100644 --- a/lib/move-dialog.coffee +++ b/lib/move-dialog.coffee @@ -5,7 +5,7 @@ Dialog = require './dialog' module.exports = class MoveDialog extends Dialog - constructor: (@initialPath) -> + constructor: (@initialPath, {@onMoveCallback}) -> if fs.isDirectorySync(@initialPath) prompt = 'Enter the new path for the directory.' else @@ -36,7 +36,7 @@ class MoveDialog extends Dialog try fs.makeTreeSync(directoryPath) unless fs.existsSync(directoryPath) fs.moveSync(@initialPath, newPath) - @emitter.emit 'entry-moved', [@initialPath, newPath] + @onMoveCallback?(initialPath: @initialPath, newPath: newPath) if repo = repoForPath(newPath) repo.getPathStatus(@initialPath) repo.getPathStatus(newPath) diff --git a/lib/tree-view.coffee b/lib/tree-view.coffee index 9234cf9e..1b8f41ac 100644 --- a/lib/tree-view.coffee +++ b/lib/tree-view.coffee @@ -509,9 +509,9 @@ class TreeView oldPath = @getActivePath() if oldPath - dialog = new MoveDialog(oldPath) - dialog.emitter.on 'entry-moved', ([oldPath, newPath]) => - @emitter.emit 'entry-moved', {oldPath, newPath} + dialog = new MoveDialog oldPath, + onMoveCallback: ({initialPath, newPath}) => + @emitter.emit 'entry-moved', {initialPath, newPath} dialog.attach() # Get the outline of a system call to the current platform's file manager. @@ -598,9 +598,9 @@ class TreeView oldPath = @getActivePath() return unless oldPath - dialog = new CopyDialog(oldPath) - dialog.emitter.on 'entry-copied', (oldPath, newPath) => - @emitter.emit 'entry-copied', {oldPath, newPath} + dialog = new CopyDialog oldPath, + onCopyCallback: ({initialPath, newPath}) => + @emitter.emit 'entry-copied', {initialPath, newPath} dialog.attach() removeSelectedEntries: -> From 2642b825720a2e6ef0b1ffcb2f3bb5aec324953d Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Sat, 25 Mar 2017 11:50:29 -0700 Subject: [PATCH 10/14] Standardize event params --- lib/tree-view.coffee | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/tree-view.coffee b/lib/tree-view.coffee index 1b8f41ac..a5610844 100644 --- a/lib/tree-view.coffee +++ b/lib/tree-view.coffee @@ -721,19 +721,19 @@ class TreeView # use fs.copy to copy directories since read/write will fail for directories catchAndShowFileErrors => fs.copySync(initialPath, newPath) - @emitter.emit 'entry-copied', {oldPath: initialPath, newPath} + @emitter.emit 'entry-copied', {initialPath, newPath} else # read the old file and write a new one at target location catchAndShowFileErrors => fs.writeFileSync(newPath, fs.readFileSync(initialPath)) - @emitter.emit 'entry-copied', {oldPath: initialPath, newPath} + @emitter.emit 'entry-copied', {initialPath, newPath} else if cutPaths # Only move the target if the cut target doesn't exist and if the newPath # is not within the initial path unless fs.existsSync(newPath) or newPath.startsWith(initialPath) catchAndShowFileErrors => fs.moveSync(initialPath, newPath) - @emitter.emit 'entry-moved', {oldPath: initialPath, newPath} + @emitter.emit 'entry-moved', {initialPath, newPath} add: (isCreatingFile) -> selectedEntry = @selectedEntry() ? @roots[0] @@ -824,7 +824,8 @@ class TreeView try fs.makeTreeSync(newDirectoryPath) unless fs.existsSync(newDirectoryPath) fs.moveSync(initialPath, newPath) - @emitter.emit 'entry-moved', {oldPath: initialPath, newPath} + console.log "MOVED!", initialPath, newPath + @emitter.emit 'entry-moved', {initialPath, newPath} if repo = repoForPath(newPath) repo.getPathStatus(initialPath) From b86a3287df628a5b67172a5112dcd98125dcbfa5 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Sat, 25 Mar 2017 11:50:38 -0700 Subject: [PATCH 11/14] Update tests --- spec/tree-view-spec.coffee | 156 +++++++++++++++++++++---------------- 1 file changed, 87 insertions(+), 69 deletions(-) diff --git a/spec/tree-view-spec.coffee b/spec/tree-view-spec.coffee index bf30c582..c7dc3af3 100644 --- a/spec/tree-view-spec.coffee +++ b/spec/tree-view-spec.coffee @@ -1493,26 +1493,24 @@ describe "TreeView", -> LocalStorage.clear() describe "when attempting to paste a directory into itself", -> - handlers = null - describe "when copied", -> beforeEach -> LocalStorage['tree-view:copyPath'] = JSON.stringify([dirPath]) - handlers = treeView.emitter.handlersByEventName - - dirView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) + it "makes a copy inside itself", -> newPath = path.join(dirPath, path.basename(dirPath)) + dirView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) expect(-> atom.commands.dispatch(treeView.element, "tree-view:paste")).not.toThrow() expect(fs.existsSync(newPath)).toBeTruthy() it "dispatches an event to the tree-view", -> - treeView.onEntryCopied -> - spyOn(handlers['entry-copied'], '0') + newPath = path.join(dirPath, path.basename(dirPath)) + callback = jasmine.createSpy("onEntryCopied") + treeView.onEntryCopied(callback) dirView.click() - expect(-> atom.commands.dispatch(treeView.element, "tree-view:paste")).not.toThrow() - expect(handlers['entry-copied'][0].callCount).toEqual(1) + atom.commands.dispatch(treeView.element, "tree-view:paste") + expect(callback).toHaveBeenCalledWith(initialPath: dirPath, newPath: newPath) it 'does not keep copying recursively', -> LocalStorage['tree-view:copyPath'] = JSON.stringify([dirPath]) @@ -1554,14 +1552,21 @@ describe "TreeView", -> LocalStorage['tree-view:copyPath'] = JSON.stringify([filePath]) it "creates a copy of the original file in the selected file's parent directory", -> - treeView.onEntryCopied -> - spyOn(treeView.emitter.handlersByEventName['entry-copied'], '0') fileView2.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) atom.commands.dispatch(treeView.element, "tree-view:paste") - expect(fs.existsSync(path.join(dirPath2, path.basename(filePath)))).toBeTruthy() + newPath = path.join(dirPath2, path.basename(filePath)) + expect(fs.existsSync(newPath)).toBeTruthy() expect(fs.existsSync(filePath)).toBeTruthy() - expect(treeView.emitter.handlersByEventName['entry-copied'][0].callCount).toEqual(1) + + it "emits an event", -> + callback = jasmine.createSpy("onEntryCopied") + treeView.onEntryCopied(callback) + fileView2.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) + atom.commands.dispatch(treeView.element, "tree-view:paste") + + newPath = path.join(dirPath2, path.basename(filePath)) + expect(callback).toHaveBeenCalledWith({initialPath: filePath, newPath: newPath}) describe "when the target already exists", -> it "appends a number to the destination name", -> @@ -1713,27 +1718,33 @@ describe "TreeView", -> expect(fs.existsSync(path.join(dirPath, "test-file30.txt"))).toBeTruthy() describe "when a file has been cut", -> - handlers = null - beforeEach -> LocalStorage['tree-view:cutPath'] = JSON.stringify([filePath]) - handlers = treeView.emitter.handlersByEventName - treeView.onEntryMoved -> - spyOn(handlers['entry-moved'], '0') describe "when a file is selected", -> it "creates a copy of the original file in the selected file's parent directory and removes the original", -> - LocalStorage['tree-view:cutPath'] = JSON.stringify([filePath]) - fileView2.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) atom.commands.dispatch(treeView.element, "tree-view:paste") - expect(fs.existsSync(path.join(dirPath2, path.basename(filePath)))).toBeTruthy() + newPath = path.join(dirPath2, path.basename(filePath)) + expect(fs.existsSync(newPath)).toBeTruthy() expect(fs.existsSync(filePath)).toBeFalsy() - expect(handlers['entry-moved'][0].callCount).toEqual(1) + + it "emits an event", -> + callback = jasmine.createSpy("onEntryMoved") + treeView.onEntryMoved(callback) + + fileView2.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) + atom.commands.dispatch(treeView.element, "tree-view:paste") + + newPath = path.join(dirPath2, path.basename(filePath)) + expect(callback).toHaveBeenCalledWith({initialPath: filePath, newPath}) describe 'when the target destination file exists', -> it 'does not move the cut file', -> + callback = jasmine.createSpy("onEntryMoved") + treeView.onEntryMoved(callback) + filePath3 = path.join(dirPath2, "test-file.txt") fs.writeFileSync(filePath3, "doesn't matter") @@ -1741,39 +1752,49 @@ describe "TreeView", -> atom.commands.dispatch(treeView.element, "tree-view:paste") expect(fs.existsSync(filePath)).toBeTruthy() - expect(handlers['entry-moved'][0].callCount).toEqual(0) + expect(callback).not.toHaveBeenCalled() describe "when a directory is selected", -> it "creates a copy of the original file in the selected directory and removes the original", -> LocalStorage['tree-view:cutPath'] = JSON.stringify([filePath]) + callback = jasmine.createSpy("onEntryMoved") + treeView.onEntryMoved(callback) dirView2.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) atom.commands.dispatch(treeView.element, "tree-view:paste") - expect(fs.existsSync(path.join(dirPath2, path.basename(filePath)))).toBeTruthy() + newPath = path.join(dirPath2, path.basename(filePath)) + expect(fs.existsSync(newPath)).toBeTruthy() expect(fs.existsSync(filePath)).toBeFalsy() - expect(handlers['entry-moved'][0].callCount).toEqual(1) + expect(callback).toHaveBeenCalledWith({initialPath: filePath, newPath}) describe "when multiple files have been cut", -> describe "when a file is selected", -> - handlers = null - beforeEach -> - handlers = treeView.emitter.handlersByEventName - treeView.onEntryMoved -> - spyOn(handlers['entry-moved'], '0') - - it "moves the selected files to the parent directory of the selected file", -> LocalStorage['tree-view:cutPath'] = JSON.stringify([filePath2, filePath3]) + it "moves the selected files to the parent directory of the selected file", -> fileView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) atom.commands.dispatch(treeView.element, "tree-view:paste") - expect(fs.existsSync(path.join(dirPath, path.basename(filePath2)))).toBeTruthy() - expect(fs.existsSync(path.join(dirPath, path.basename(filePath3)))).toBeTruthy() + newPath2 = path.join(dirPath, path.basename(filePath2)) + newPath3 = path.join(dirPath, path.basename(filePath3)) + expect(fs.existsSync(newPath2)).toBeTruthy() + expect(fs.existsSync(newPath3)).toBeTruthy() expect(fs.existsSync(filePath2)).toBeFalsy() expect(fs.existsSync(filePath3)).toBeFalsy() - expect(handlers['entry-moved'][0].callCount).toEqual(2) + + it "emits events", -> + callback = jasmine.createSpy("onEntryMoved") + treeView.onEntryMoved(callback) + fileView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) + atom.commands.dispatch(treeView.element, "tree-view:paste") + + newPath2 = path.join(dirPath, path.basename(filePath2)) + newPath3 = path.join(dirPath, path.basename(filePath3)) + expect(callback.callCount).toEqual(2) + expect(callback).toHaveBeenCalledWith({initialPath: filePath2, newPath: newPath2}) + expect(callback).toHaveBeenCalledWith({initialPath: filePath3, newPath: newPath3}) describe 'when the target destination file exists', -> it 'does not move the cut file', -> @@ -1818,14 +1839,12 @@ describe "TreeView", -> expect(atom.notifications.getNotifications()[0].getDetail()).toContain 'ENOENT: no such file or directory' describe "tree-view:add-file", -> - [addPanel, addDialog, handlers] = [] + [addPanel, addDialog, callback] = [] beforeEach -> jasmine.attachToDOM(workspaceElement) - - handlers = treeView.emitter.handlersByEventName - treeView.onFileCreated -> - spyOn(handlers['file-created'], '0') + callback = jasmine.createSpy("onFileCreated") + treeView.onFileCreated(callback) waitsForFileToOpen -> fileView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) @@ -1851,7 +1870,7 @@ describe "TreeView", -> describe "when the path without a trailing '#{path.sep}' is changed and confirmed", -> describe "when no file exists at that location", -> - it "adds a file, closes the dialog and selects the file in the tree-view", -> + it "adds a file, closes the dialog, selects the file in the tree-view, and emits an event", -> newPath = path.join(dirPath, "new-test-file.txt") waitsForFileToOpen -> @@ -1867,8 +1886,8 @@ describe "TreeView", -> dirView.entries.querySelectorAll(".file").length > 1 runs -> - expect(handlers['file-created'][0]).toHaveBeenCalled() expect(treeView.element.querySelector('.selected').textContent).toBe path.basename(newPath) + expect(callback).toHaveBeenCalledWith({path: newPath}) it "adds file in any project path", -> newPath = path.join(dirPath3, "new-test-file.txt") @@ -1892,8 +1911,8 @@ describe "TreeView", -> dirView3.entries.querySelectorAll(".file").length > 1 runs -> - expect(handlers['file-created'][0]).toHaveBeenCalled() expect(treeView.element.querySelector('.file.selected').textContent).toBe path.basename(newPath) + expect(callback).toHaveBeenCalledWith({path: newPath}) describe "when a file already exists at that location", -> it "shows an error message and does not close the dialog", -> @@ -1905,7 +1924,7 @@ describe "TreeView", -> expect(addDialog.errorMessage.textContent).toContain 'already exists' expect(addDialog.element).toHaveClass('error') expect(atom.workspace.getModalPanels()[0]).toBe addPanel - expect(handlers['file-created'][0]).not.toHaveBeenCalled() + expect(callback).not.toHaveBeenCalled() describe "when the project has no path", -> it "adds a file and closes the dialog", -> @@ -1925,7 +1944,7 @@ describe "TreeView", -> expect(fs.isFileSync(newPath)).toBeTruthy() expect(atom.workspace.getModalPanels().length).toBe 0 expect(atom.workspace.getActivePaneItem().getPath()).toBe(newPath) - expect(handlers['file-created'][0]).toHaveBeenCalled() + expect(callback).toHaveBeenCalledWith({path: newPath}) describe "when the path with a trailing '#{path.sep}' is changed and confirmed", -> it "shows an error message and does not close the dialog", -> @@ -1935,12 +1954,14 @@ describe "TreeView", -> expect(addDialog.errorMessage.textContent).toContain 'names must not end with' expect(addDialog.element).toHaveClass('error') expect(atom.workspace.getModalPanels()[0]).toBe addPanel + expect(callback).not.toHaveBeenCalled() describe "when 'core:cancel' is triggered on the add dialog", -> it "removes the dialog and focuses the tree view", -> atom.commands.dispatch addDialog.element, 'core:cancel' expect(atom.workspace.getModalPanels().length).toBe 0 expect(document.activeElement).toBe(treeView.element.querySelector(".tree-view")) + expect(callback).not.toHaveBeenCalled() describe "when the add dialog's editor loses focus", -> it "removes the dialog and focuses root view", -> @@ -1959,6 +1980,7 @@ describe "TreeView", -> runs -> expect(fs.isFileSync(newPath)).toBeTruthy() expect(atom.workspace.getActivePaneItem().getPath()).toBe newPath + expect(callback).toHaveBeenCalledWith({path: newPath}) describe "when a directory is selected", -> it "opens an add dialog with the directory's path populated", -> @@ -2006,14 +2028,12 @@ describe "TreeView", -> expect(addDialog.element.textContent).toContain("You must open a directory to create a file with a relative path") describe "tree-view:add-folder", -> - [addPanel, addDialog, handlers] = [] + [addPanel, addDialog, callback] = [] beforeEach -> jasmine.attachToDOM(workspaceElement) - - handlers = treeView.emitter.handlersByEventName - treeView.onDirectoryCreated -> - spyOn(handlers['directory-created'], '0') + callback = jasmine.createSpy("onDirectoryCreated") + treeView.onDirectoryCreated(callback) waitsForFileToOpen -> fileView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) @@ -2044,11 +2064,11 @@ describe "TreeView", -> expect(document.activeElement).toBe(treeView.element.querySelector(".tree-view")) expect(dirView.querySelector('.directory.selected').textContent).toBe('new') - expect(handlers['directory-created'][0].callCount).toBe 1 + expect(callback).toHaveBeenCalledWith({path: newPath}) describe "when the path with a trailing '#{path.sep}' is changed and confirmed", -> describe "when no directory exists at the given path", -> - it "adds a directory and closes the dialog", -> + it "adds a directory, closes the dialog, and emits an event", -> newPath = path.join(dirPath, 'new', 'dir') addDialog.miniEditor.insertText("new#{path.sep}dir#{path.sep}") atom.commands.dispatch addDialog.element, 'core:confirm' @@ -2058,7 +2078,7 @@ describe "TreeView", -> expect(document.activeElement).toBe(treeView.element.querySelector(".tree-view")) expect(dirView.querySelector('.directory.selected').textContent).toBe('new') - expect(handlers['directory-created'][0].callCount).toBe(1) + expect(callback).toHaveBeenCalledWith({path: newPath + path.sep}) it "selects the created directory and does not change the expansion state of existing directories", -> expandedPath = path.join(dirPath, 'expanded-dir') @@ -2077,8 +2097,8 @@ describe "TreeView", -> expect(document.activeElement).toBe(treeView.element.querySelector(".tree-view")) expect(dirView.querySelector('.directory.selected').textContent).toBe('new2') - expect(handlers['directory-created'][0].callCount).toBe(1) expect(treeView.entryForPath(expandedPath).isExpanded).toBeTruthy() + expect(callback).toHaveBeenCalledWith({path: newPath}) describe "when the project has no path", -> it "adds a directory and closes the dialog", -> @@ -2093,8 +2113,8 @@ describe "TreeView", -> addDialog.miniEditor.insertText(newPath) atom.commands.dispatch addDialog.element, 'core:confirm' expect(fs.isDirectorySync(newPath)).toBeTruthy() - expect(handlers['directory-created'][0].callCount).toBe 1 expect(atom.workspace.getModalPanels().length).toBe 0 + expect(callback).toHaveBeenCalledWith({path: newPath}) describe "when a directory already exists at the given path", -> it "shows an error message and does not close the dialog", -> @@ -2106,18 +2126,16 @@ describe "TreeView", -> expect(addDialog.errorMessage.textContent).toContain 'already exists' expect(addDialog.element).toHaveClass('error') expect(atom.workspace.getModalPanels()[0]).toBe addPanel - expect(handlers['directory-created'][0].callCount).toBe 0 + expect(callback).not.toHaveBeenCalled() describe "tree-view:move", -> describe "when a file is selected", -> - [moveDialog, handlers] = [] + [moveDialog, callback] = [] beforeEach -> jasmine.attachToDOM(workspaceElement) - - handlers = treeView.emitter.handlersByEventName - treeView.onEntryMoved -> - spyOn(handlers['entry-moved'], '0') + callback = jasmine.createSpy("onEntryMoved") + treeView.onEntryMoved(callback) waitsForFileToOpen -> fileView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) @@ -2140,7 +2158,7 @@ describe "TreeView", -> describe "when the path is changed and confirmed", -> describe "when all the directories along the new path exist", -> - it "moves the file, updates the tree view, and closes the dialog", -> + it "moves the file, updates the tree view, closes the dialog, and emits an event", -> newPath = path.join(rootDirPath, 'renamed-test-file.txt') moveDialog.miniEditor.setText(path.basename(newPath)) @@ -2158,7 +2176,7 @@ describe "TreeView", -> dirView = treeView.roots[0].querySelector('.directory') dirView.expand() expect(dirView.entries.children.length).toBe 0 - expect(handlers['entry-moved'][0].callCount).toBe 1 + expect(callback).toHaveBeenCalledWith({initialPath: filePath, newPath}) describe "when the directories along the new path don't exist", -> it "creates the target directory before moving the file", -> @@ -2174,7 +2192,7 @@ describe "TreeView", -> runs -> expect(fs.existsSync(newPath)).toBeTruthy() expect(fs.existsSync(filePath)).toBeFalsy() - expect(handlers['entry-moved'][0].callCount).toBe 1 + expect(callback).toHaveBeenCalledWith({initialPath: filePath, newPath}) describe "when a file or directory already exists at the target path", -> it "shows an error message and does not close the dialog", -> @@ -2188,7 +2206,7 @@ describe "TreeView", -> expect(moveDialog.errorMessage.textContent).toContain 'already exists' expect(moveDialog.element).toHaveClass('error') expect(moveDialog.element.parentElement).toBeTruthy() - expect(handlers['entry-moved'][0].callCount).toBe 0 + expect(callback).not.toHaveBeenCalled() describe "when 'core:cancel' is triggered on the move dialog", -> it "removes the dialog and focuses the tree view", -> @@ -2770,10 +2788,10 @@ describe "TreeView", -> describe "when the file is deleted", -> it "updates the style of the directory", -> - handlers = treeView.emitter.handlersByEventName - treeView.onEntryDeleted -> - spyOn(handlers['entry-deleted'], '0') + callback = jasmine.createSpy("onEntryDeleted") + treeView.onEntryDeleted(callback) + deletedPath = treeView.selectedEntry().getPath() expect(treeView.selectedEntry().getPath()).toContain(path.join('dir2', 'new2')) dirView = findDirectoryContainingText(treeView.roots[0], 'dir2') expect(dirView).not.toBeNull() @@ -2782,7 +2800,7 @@ describe "TreeView", -> dialog.buttons["Move to Trash"]() atom.commands.dispatch(treeView.element, 'tree-view:remove') expect(dirView.directory.updateStatus).toHaveBeenCalled() - expect(handlers['entry-deleted'][0]).toHaveBeenCalled() + expect(callback).toHaveBeenCalledWith({path: deletedPath}) describe "on #darwin, when the project is a symbolic link to the repository root", -> beforeEach -> From 425f3965371a7f01a1af1ee709b2a3122cc5a42b Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Sat, 25 Mar 2017 11:54:12 -0700 Subject: [PATCH 12/14] :fire: console.log --- lib/tree-view.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/tree-view.coffee b/lib/tree-view.coffee index a5610844..9f2d62f9 100644 --- a/lib/tree-view.coffee +++ b/lib/tree-view.coffee @@ -824,7 +824,6 @@ class TreeView try fs.makeTreeSync(newDirectoryPath) unless fs.existsSync(newDirectoryPath) fs.moveSync(initialPath, newPath) - console.log "MOVED!", initialPath, newPath @emitter.emit 'entry-moved', {initialPath, newPath} if repo = repoForPath(newPath) From 9a5e04b78b077b178c3ce87e12547ef934bff9b8 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Sat, 25 Mar 2017 11:59:10 -0700 Subject: [PATCH 13/14] Betterer names --- lib/copy-dialog.coffee | 6 +++--- lib/move-dialog.coffee | 4 ++-- lib/tree-view.coffee | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/copy-dialog.coffee b/lib/copy-dialog.coffee index bd484b9b..aa510d84 100644 --- a/lib/copy-dialog.coffee +++ b/lib/copy-dialog.coffee @@ -5,7 +5,7 @@ Dialog = require './dialog' module.exports = class CopyDialog extends Dialog - constructor: (@initialPath, {@onCopyCallback}) -> + constructor: (@initialPath, {@onCopy}) -> super prompt: 'Enter the new path for the duplicate.' initialPath: atom.project.relativize(@initialPath) @@ -32,10 +32,10 @@ class CopyDialog extends Dialog try if fs.isDirectorySync(@initialPath) fs.copySync(@initialPath, newPath) - @onCopyCallback?({initialPath: @initialPath, newPath: newPath}) + @onCopy?({initialPath: @initialPath, newPath: newPath}) else fs.copy @initialPath, newPath, => - @onCopyCallback?({initialPath: @initialPath, newPath: newPath}) + @onCopy?({initialPath: @initialPath, newPath: newPath}) atom.workspace.open newPath, activatePane: true initialLine: activeEditor?.getLastCursor().getBufferRow() diff --git a/lib/move-dialog.coffee b/lib/move-dialog.coffee index 4a68e9f1..e10d5c85 100644 --- a/lib/move-dialog.coffee +++ b/lib/move-dialog.coffee @@ -5,7 +5,7 @@ Dialog = require './dialog' module.exports = class MoveDialog extends Dialog - constructor: (@initialPath, {@onMoveCallback}) -> + constructor: (@initialPath, {@onMove}) -> if fs.isDirectorySync(@initialPath) prompt = 'Enter the new path for the directory.' else @@ -36,7 +36,7 @@ class MoveDialog extends Dialog try fs.makeTreeSync(directoryPath) unless fs.existsSync(directoryPath) fs.moveSync(@initialPath, newPath) - @onMoveCallback?(initialPath: @initialPath, newPath: newPath) + @onMove?(initialPath: @initialPath, newPath: newPath) if repo = repoForPath(newPath) repo.getPathStatus(@initialPath) repo.getPathStatus(newPath) diff --git a/lib/tree-view.coffee b/lib/tree-view.coffee index 9f2d62f9..cd866402 100644 --- a/lib/tree-view.coffee +++ b/lib/tree-view.coffee @@ -510,7 +510,7 @@ class TreeView if oldPath dialog = new MoveDialog oldPath, - onMoveCallback: ({initialPath, newPath}) => + onMove: ({initialPath, newPath}) => @emitter.emit 'entry-moved', {initialPath, newPath} dialog.attach() @@ -599,7 +599,7 @@ class TreeView return unless oldPath dialog = new CopyDialog oldPath, - onCopyCallback: ({initialPath, newPath}) => + onCopy: ({initialPath, newPath}) => @emitter.emit 'entry-copied', {initialPath, newPath} dialog.attach() From b5d228dc1ffd8f44ca2856526532150a6eb95fad Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Sat, 25 Mar 2017 12:42:56 -0700 Subject: [PATCH 14/14] Use new hooks for updating editors --- lib/tree-view.coffee | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/tree-view.coffee b/lib/tree-view.coffee index d4b5a16a..5dc34ada 100644 --- a/lib/tree-view.coffee +++ b/lib/tree-view.coffee @@ -77,6 +77,11 @@ class TreeView @disposables.add @onEntryMoved ({initialPath, newPath}) -> updateEditorsForPath(initialPath, newPath) + @disposables.add @onEntryDeleted ({path}) -> + for editor in atom.workspace.getTextEditors() + if editor?.getPath()?.startsWith(path) + editor.destroy() + serialize: -> directoryExpansionStates: new ((roots) -> @[root.directory.path] = root.directory.serializeExpansionState() for root in roots @@ -630,9 +635,6 @@ class TreeView for selectedPath in selectedPaths if shell.moveItemToTrash(selectedPath) @emitter.emit 'entry-deleted', {path: selectedPath} - for editor in atom.workspace.getTextEditors() - if editor?.getPath()?.startsWith(selectedPath) - editor.destroy() else failedDeletions.push "#{selectedPath}" if repo = repoForPath(selectedPath)