diff --git a/appveyor.yml b/appveyor.yml index 8801256c..540410af 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,7 +20,7 @@ environment: - ATOM_CHANNEL: beta install: - - ps: Install-Product node 6 + - ps: Install-Product node 12 build_script: - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/atom/ci/master/build-package.ps1')) diff --git a/keymaps/tree-view.cson b/keymaps/tree-view.cson index 98828de7..00f24cf1 100644 --- a/keymaps/tree-view.cson +++ b/keymaps/tree-view.cson @@ -76,6 +76,7 @@ 'shift-a': 'tree-view:add-folder' 'd': 'tree-view:duplicate' 'delete': 'tree-view:remove' + 'shift-delete': 'tree-view:remove-permanently' 'backspace': 'tree-view:remove' 'k': 'core:move-up' 'j': 'core:move-down' diff --git a/lib/tree-view-package.js b/lib/tree-view-package.js index f624e03d..af7ba343 100644 --- a/lib/tree-view-package.js +++ b/lib/tree-view-package.js @@ -15,7 +15,8 @@ class TreeViewPackage { 'tree-view:add-file': () => this.getTreeViewInstance().add(true), 'tree-view:add-folder': () => this.getTreeViewInstance().add(false), 'tree-view:duplicate': () => this.getTreeViewInstance().copySelectedEntry(), - 'tree-view:remove': () => this.getTreeViewInstance().removeSelectedEntries(), + 'tree-view:remove': () => this.getTreeViewInstance().removeSelectedEntries(false), + 'tree-view:remove-permanently': () => this.getTreeViewInstance().removeSelectedEntries(true), 'tree-view:rename': () => this.getTreeViewInstance().moveSelectedEntry(), 'tree-view:show-current-file-in-file-manager': () => this.getTreeViewInstance().showCurrentFileInFileManager() })) diff --git a/lib/tree-view.coffee b/lib/tree-view.coffee index 61795fa3..dafdc672 100644 --- a/lib/tree-view.coffee +++ b/lib/tree-view.coffee @@ -5,6 +5,7 @@ _ = require 'underscore-plus' {BufferedProcess, CompositeDisposable, Emitter} = require 'atom' {repoForPath, getStyleObject, getFullExtension} = require "./helpers" fs = require 'fs-plus' +del = require 'del' AddDialog = require './add-dialog' MoveDialog = require './move-dialog' @@ -612,7 +613,7 @@ class TreeView @emitter.emit 'entry-copied', {initialPath, newPath} dialog.attach() - removeSelectedEntries: -> + removeSelectedEntries: (shouldDeletePermanently = false) -> if @hasFocus() selectedPaths = @selectedPaths() selectedEntries = @getSelectedEntries() @@ -632,11 +633,13 @@ class TreeView return atom.confirm({ - message: "Are you sure you want to delete the selected #{if selectedPaths.length > 1 then 'items' else 'item'}?", + message: "Are you sure you want to #{if shouldDeletePermanently then 'permanently ' else ''}delete the selected #{if selectedPaths.length > 1 then 'items' else 'item'}?", detailedMessage: "You are deleting:\n#{selectedPaths.join('\n')}", - buttons: ['Move to Trash', 'Cancel'] + buttons: [(if shouldDeletePermanently then 'Permanently Delete ⚠️' else 'Move to Trash'), 'Cancel'] }, (response) => if response is 0 # Move to Trash + if shouldDeletePermanently + return @removeSelectedPathsPermanently(selectedPaths, selectedEntries) failedDeletions = [] for selectedPath in selectedPaths # Don't delete entries which no longer exist. This can happen, for example, when: @@ -656,21 +659,18 @@ class TreeView repo.getPathStatus(selectedPath) if failedDeletions.length > 0 - atom.notifications.addError @formatTrashFailureMessage(failedDeletions), + atom.notifications.addError @formatTrashFailureMessage(failedDeletions, false), description: @formatTrashEnabledMessage() detail: "#{failedDeletions.join('\n')}" dismissable: true - # Focus the first parent folder - if firstSelectedEntry = selectedEntries[0] - @selectEntry(firstSelectedEntry.closest('.directory:not(.selected)')) - @updateRoots() if atom.config.get('tree-view.squashDirectoryNames') + @finishRemoval(selectedEntries[0]) ) - formatTrashFailureMessage: (failedDeletions) -> + formatTrashFailureMessage: (failedDeletions, shouldDeletePermanently = false) -> fileText = if failedDeletions.length > 1 then 'files' else 'file' - "The following #{fileText} couldn't be moved to the trash." + "The following #{fileText} couldn't be #{if shouldDeletePermanently then "deleted permanently" else "moved to the trash."}" formatTrashEnabledMessage: -> switch process.platform @@ -678,6 +678,29 @@ class TreeView when 'darwin' then 'Is Trash enabled on the volume where the files are stored?' when 'win32' then 'Is there a Recycle Bin on the drive where the files are stored?' + finishRemoval: (firstSelectedEntry) -> + # Focus the first parent folder + if firstSelectedEntry + @selectEntry(firstSelectedEntry.closest('.directory:not(.selected)')) + @updateRoots() if atom.config.get('tree-view.squashDirectoryNames') + + removeSelectedPathsPermanently: (selectedPaths, selectedEntries) -> + for selectedPath in selectedPaths + @emitter.emit 'will-delete-entry', {pathToDelete: selectedPath} + return del(selectedPaths, {force: true}) + .then( (deletedPaths) => + for deletedPath in deletedPaths + @emitter.emit 'entry-deleted', {pathToDelete: deletedPath} + ) + .catch((err) => + atom.notifications.addError @formatTrashFailureMessage(selectedPaths, true), + description: err + dismissable: true + for selectedPath in selectedPaths + @emitter.emit 'delete-entry-failed', {pathToDelete: selectedPath} + ) + .finally( => @finishRemoval(selectedEntries[0])) + # Public: Copy the path of the selected entry element. # Save the path in localStorage, so that copying from 2 different # instances of atom works as intended diff --git a/menus/tree-view.cson b/menus/tree-view.cson index f108645a..fd7d689b 100644 --- a/menus/tree-view.cson +++ b/menus/tree-view.cson @@ -28,6 +28,7 @@ {'label': 'Rename', 'command': 'tree-view:move'} {'label': 'Duplicate', 'command': 'tree-view:duplicate'} {'label': 'Delete', 'command': 'tree-view:remove'} + {'label': 'Delete Permanently', 'command': 'tree-view:remove-permanently'} {'label': 'Copy', 'command': 'tree-view:copy'} {'label': 'Cut', 'command': 'tree-view:cut'} {'label': 'Paste', 'command': 'tree-view:paste'} @@ -57,6 +58,7 @@ {'label': 'Rename', 'command': 'tree-view:move'} {'label': 'Duplicate', 'command': 'tree-view:duplicate'} {'label': 'Delete', 'command': 'tree-view:remove'} + {'label': 'Delete Permanently', 'command': 'tree-view:remove-permanently'} {'label': 'Copy', 'command': 'tree-view:copy'} {'label': 'Cut', 'command': 'tree-view:cut'} {'label': 'Paste', 'command': 'tree-view:paste'} @@ -86,6 +88,7 @@ '.tree-view .multi-select': [ {'label': 'Delete', 'command': 'tree-view:remove'} + {'label': 'Delete Permanently', 'command': 'tree-view:remove-permanently'} {'label': 'Copy', 'command': 'tree-view:copy'} {'label': 'Cut', 'command': 'tree-view:cut'} {'label': 'Paste', 'command': 'tree-view:paste'} diff --git a/package-lock.json b/package-lock.json index d070ae7c..88653799 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,29 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@nodelib/fs.scandir": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "requires": { + "@nodelib/fs.stat": "2.0.4", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==" + }, + "@nodelib/fs.walk": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "requires": { + "@nodelib/fs.scandir": "2.1.4", + "fastq": "^1.6.0" + } + }, "acorn": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", @@ -27,6 +50,15 @@ } } }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, "ajv": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", @@ -82,6 +114,11 @@ "es-abstract": "^1.7.0" } }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + }, "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", @@ -136,6 +173,14 @@ "concat-map": "0.0.1" } }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -206,6 +251,11 @@ "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", "dev": true }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" + }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -366,6 +416,51 @@ "uniq": "^1.0.1" } }, + "del": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", + "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", + "requires": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "requires": { + "path-type": "^4.0.0" + }, + "dependencies": { + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + } + } + }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -842,6 +937,19 @@ "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", "dev": true }, + "fast-glob": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + } + }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", @@ -854,6 +962,14 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fastq": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz", + "integrity": "sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==", + "requires": { + "reusify": "^1.0.4" + } + }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -873,6 +989,14 @@ "object-assign": "^4.0.1" } }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, "find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -957,12 +1081,40 @@ } } }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "requires": { + "is-glob": "^4.0.1" + } + }, "globals": { "version": "11.10.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.10.0.tgz", "integrity": "sha512-0GZF1RiPKU97IHUO5TORo9w1PwrH/NBPl+fS7oMLdaTRiYmYbwK4NWoZWrAdd0/abG9R2BU+OiwyQpTpE6pdfQ==", "dev": true }, + "globby": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", + "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" + } + } + }, "graceful-fs": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", @@ -1033,6 +1185,11 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1096,12 +1253,40 @@ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", "dev": true }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==" + }, + "is-path-inside": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", + "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==" + }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -1239,6 +1424,20 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", @@ -1397,6 +1596,14 @@ "p-limit": "^1.1.0" } }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "requires": { + "aggregate-error": "^3.0.0" + } + }, "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", @@ -1466,6 +1673,11 @@ } } }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -1649,6 +1861,11 @@ "signal-exit": "^3.0.2" } }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, "rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -1669,8 +1886,7 @@ "run-parallel": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", - "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", - "dev": true + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" }, "rx-lite": { "version": "4.0.8", @@ -1730,6 +1946,11 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + }, "slice-ansi": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", @@ -1911,6 +2132,14 @@ "os-tmpdir": "~1.0.2" } }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, "type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", diff --git a/package.json b/package.json index b82c2be9..093c91cd 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "private": true, "dependencies": { + "del": "^6.0.0", "fs-plus": "^3.0.0", "minimatch": "~0.3.0", "pathwatcher": "^8.1.0", diff --git a/spec/tree-view-package-spec.coffee b/spec/tree-view-package-spec.coffee index 85a2233d..14805447 100644 --- a/spec/tree-view-package-spec.coffee +++ b/spec/tree-view-package-spec.coffee @@ -3133,6 +3133,77 @@ describe "TreeView", -> runs -> expect(atom.notifications.getNotifications().length).toBe 0 + describe "treev-view:remove-permanently", -> + beforeEach -> + jasmine.attachToDOM(workspaceElement) + + it "won't remove the root directory", -> + spyOn(atom, 'confirm') + treeView.focus() + root1.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) + atom.commands.dispatch(treeView.element, 'tree-view:remove-permanently') + + args = atom.confirm.mostRecentCall.args[0] + expect(args.buttons).toEqual ['OK'] + + it "shows the native alert dialog", -> + spyOn(atom, 'confirm') + + waitForWorkspaceOpenEvent -> + fileView.dispatchEvent(new MouseEvent('click', {bubbles: true, detail: 1})) + + runs -> + atom.commands.dispatch(treeView.element, 'tree-view:remove-permanently') + args = atom.confirm.mostRecentCall.args[0] + expect(args.buttons).toEqual ['Permanently Delete ⚠️', 'Cancel'] + + + it "calls removeSelectedPathsPermanently, onWillDeleteEntry, onEntryDeleted, finishRemoval", -> + spyOn(atom, 'confirm').andCallFake (options, callback) -> callback(0) + onEntryDeletedSpy = jasmine.createSpy('onEntryDeleted') + treeView.onEntryDeleted(onEntryDeletedSpy) + + onWillDeleteEntrySpy = jasmine.createSpy('onWillDeleteEntry') + treeView.onWillDeleteEntry(onWillDeleteEntrySpy) + + finishRemovalSpy = spyOn(treeView, 'finishRemoval').andCallThrough() + + removeSelectedPathsPermanentlySpy = spyOn(treeView, 'removeSelectedPathsPermanently').andCallThrough() + removeSelectedEntriesSpy = spyOn(treeView, 'removeSelectedEntries').andCallThrough() + + filePath = path.join(os.tmpdir(), 'non-project-file.txt') + fs.writeFileSync(filePath, 'test') + + waitsForPromise -> + atom.workspace.open(filePath) + + waitsForPromise -> + atom.commands.dispatch(treeView.element, 'tree-view:remove-permanently') + + waitsFor 'removeSelectedEntries amd removeSelectedPathsPermanently to be called', -> + removeSelectedEntriesSpy.callCount is 1 and + removeSelectedEntriesSpy.mostRecentCall.args[0] is true and + removeSelectedPathsPermanentlySpy.mostRecentCall.args[0][0] is filePath + + runs: -> + # The internal functionality of the followings are already tested in treeview:remove + expect( + onWillDeleteEntrySpy.callCount is 1 and + onWillDeleteEntrySpy.mostRecentCall.args[0].pathToDelete is filePath + ).toBe(true, 'it calls onWillDeleteEntry') + + expect( + onEntryDeletedSpy.callCount is 1 and + onEntryDeletedSpy.mostRecentCall.args[0].pathToDelete is filePath + ).toBe(true, 'it calls onEntryDeleted') + + expect( + finishRemovalSpy.callCount is 1 and + finishRemovalSpy.mostRecentCall.args[0] is removeSelectedPathsPermanentlySpy.mostRecentCall.args[1][0] + ).toBe(true, 'it calls finishRemoval') + + expect(fs.existsSync(filePath)).toBe(false, 'it deletes the file') + describe "file system events", -> temporaryFilePath = null