diff --git a/src/command/Commands.js b/src/command/Commands.js
index 0cde9f49ddf..cfded3fdca4 100644
--- a/src/command/Commands.js
+++ b/src/command/Commands.js
@@ -47,6 +47,7 @@ define(function (require, exports, module) {
exports.FILE_LIVE_HIGHLIGHT = "file.previewHighlight";
exports.FILE_PROJECT_SETTINGS = "file.projectSettings";
exports.FILE_RENAME = "file.rename";
+ exports.FILE_DELETE = "file.delete";
exports.FILE_INSTALL_EXTENSION = "file.installExtension";
exports.FILE_EXTENSION_MANAGER = "file.extensionManager";
exports.FILE_QUIT = "file.quit"; // string must MATCH string in native code (brackets_extensions)
diff --git a/src/command/DefaultMenus.js b/src/command/DefaultMenus.js
index 1238c643b3b..d7e54907583 100644
--- a/src/command/DefaultMenus.js
+++ b/src/command/DefaultMenus.js
@@ -179,6 +179,7 @@ define(function (require, exports, module) {
project_cmenu.addMenuItem(Commands.FILE_NEW);
project_cmenu.addMenuItem(Commands.FILE_NEW_FOLDER);
project_cmenu.addMenuItem(Commands.FILE_RENAME);
+ project_cmenu.addMenuItem(Commands.FILE_DELETE);
project_cmenu.addMenuItem(Commands.NAVIGATE_SHOW_IN_OS);
project_cmenu.addMenuDivider();
project_cmenu.addMenuItem(Commands.EDIT_FIND_IN_SUBTREE);
diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js
index 3d490f57f7c..1e5109f189b 100644
--- a/src/document/DocumentCommandHandlers.js
+++ b/src/document/DocumentCommandHandlers.js
@@ -845,6 +845,11 @@ define(function (require, exports, module) {
ProjectManager.showInTree(DocumentManager.getCurrentDocument().file);
}
+ function handleFileDelete() {
+ var entry = ProjectManager.getSelectedItem();
+ ProjectManager.deleteItem(entry);
+ }
+
/** Show the selected sidebar (tree or working set) item in Finder/Explorer */
function handleShowInOS() {
var entry = ProjectManager.getSelectedItem();
@@ -857,7 +862,6 @@ define(function (require, exports, module) {
}
}
-
// Init DOM elements
AppInit.htmlReady(function () {
var $titleContainerToolbar = $("#titlebar");
@@ -878,6 +882,7 @@ define(function (require, exports, module) {
CommandManager.register(Strings.CMD_FILE_SAVE, Commands.FILE_SAVE, handleFileSave);
CommandManager.register(Strings.CMD_FILE_SAVE_ALL, Commands.FILE_SAVE_ALL, handleFileSaveAll);
CommandManager.register(Strings.CMD_FILE_RENAME, Commands.FILE_RENAME, handleFileRename);
+ CommandManager.register(Strings.CMD_FILE_DELETE, Commands.FILE_DELETE, handleFileDelete);
CommandManager.register(Strings.CMD_FILE_CLOSE, Commands.FILE_CLOSE, handleFileClose);
CommandManager.register(Strings.CMD_FILE_CLOSE_ALL, Commands.FILE_CLOSE_ALL, handleFileCloseAll);
diff --git a/src/document/DocumentManager.js b/src/document/DocumentManager.js
index 0d97db32b90..3bc87015d04 100644
--- a/src/document/DocumentManager.js
+++ b/src/document/DocumentManager.js
@@ -76,6 +76,7 @@
*
* - fileNameChange -- When the name of a file or folder has changed. The 2nd arg is the old name.
* The 3rd arg is the new name.
+ * - pathDeleted -- When a file or folder has been deleted. The 2nd arg is the path that was deleted.
*
* These are jQuery events, so to listen for them you do something like this:
* $(DocumentManager).on("eventname", handler);
@@ -478,8 +479,9 @@ define(function (require, exports, module) {
* This is a subset of notifyFileDeleted(). Use this for the user-facing Close command.
*
* @param {!FileEntry} file
+ * @param {boolean} skipAutoSelect - if true, don't automatically open and select the next document
*/
- function closeFullEditor(file) {
+ function closeFullEditor(file, skipAutoSelect) {
// If this was the current document shown in the editor UI, we're going to switch to a
// different document (or none if working set has no other options)
if (_currentDocument && _currentDocument.file.fullPath === file.fullPath) {
@@ -491,7 +493,7 @@ define(function (require, exports, module) {
}
// Switch editor to next document (or blank it out)
- if (nextFile) {
+ if (nextFile && !skipAutoSelect) {
CommandManager.execute(Commands.FILE_OPEN, { fullPath: nextFile.fullPath })
.done(function () {
// (Now we're guaranteed that the current document is not the one we're closing)
@@ -1063,10 +1065,11 @@ define(function (require, exports, module) {
* sort of "project file model," making this just a private event handler.
*
* @param {!FileEntry} file
+ * @param {boolean} skipAutoSelect - if true, don't automatically open/select the next document
*/
- function notifyFileDeleted(file) {
+ function notifyFileDeleted(file, skipAutoSelect) {
// First ensure it's not currentDocument, and remove from working set
- closeFullEditor(file);
+ closeFullEditor(file, skipAutoSelect);
// Notify all other editors to close as well
var doc = getOpenDocumentForPath(file.fullPath);
@@ -1211,6 +1214,27 @@ define(function (require, exports, module) {
$(exports).triggerHandler("fileNameChange", [oldName, newName]);
}
+ /**
+ * Called after a file or folder has been deleted. This function is responsible
+ * for updating underlying model data and notifying all views of the change.
+ *
+ * @param {string} path The path of the file/folder that has been deleted
+ */
+ function notifyPathDeleted(path) {
+ var i, docPath;
+
+ for (docPath in _openDocuments) {
+ if (FileUtils.isAffectedWhenRenaming(docPath, path)) {
+ // This will close the doc and remove from the working set
+ notifyFileDeleted(new NativeFileSystem.FileEntry(docPath), true);
+ delete _openDocuments[docPath];
+ }
+ }
+
+ // Send a "pathDeleted" event. This will trigger the views to update.
+ $(exports).triggerHandler("pathDeleted", path);
+ }
+
/**
* @private
* Update document
@@ -1262,6 +1286,7 @@ define(function (require, exports, module) {
exports.closeAll = closeAll;
exports.notifyFileDeleted = notifyFileDeleted;
exports.notifyPathNameChanged = notifyPathNameChanged;
+ exports.notifyPathDeleted = notifyPathDeleted;
// Setup preferences
_prefs = PreferencesManager.getPreferenceStorage(module);
diff --git a/src/file/NativeFileSystem.js b/src/file/NativeFileSystem.js
index f863eff1c6b..76728150585 100644
--- a/src/file/NativeFileSystem.js
+++ b/src/file/NativeFileSystem.js
@@ -365,13 +365,19 @@ define(function (require, exports, module) {
};
/**
- * Deletes a file or directory
+ * Deletes a file or directory by moving to the trash/recycle bin.
* @param {function()} successCallback Callback function for successful operations
* @param {function(DOMError)=} errorCallback Callback function for error operations
*/
NativeFileSystem.Entry.prototype.remove = function (successCallback, errorCallback) {
- // TODO (issue #241)
- // http://www.w3.org/TR/2011/WD-file-system-api-20110419/#widl-Entry-remove
+ var deleteFunc = brackets.fs.moveToTrash || brackets.fs.unlink;
+ deleteFunc(this.fullPath, function (err) {
+ if (err === brackets.fs.NO_ERROR) {
+ successCallback();
+ } else {
+ errorCallback(err);
+ }
+ });
};
/**
diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js
index 458388e9984..808ba5088b3 100644
--- a/src/nls/root/strings.js
+++ b/src/nls/root/strings.js
@@ -36,7 +36,7 @@ define({
"NOT_READABLE_ERR" : "The file could not be read.",
"NO_MODIFICATION_ALLOWED_ERR" : "The target directory cannot be modified.",
"NO_MODIFICATION_ALLOWED_ERR_FILE" : "The permissions do not allow you to make modifications.",
- "FILE_EXISTS_ERR" : "The file already exists.",
+ "FILE_EXISTS_ERR" : "The file or directory already exists.",
// Project error strings
"ERROR_LOADING_PROJECT" : "Error loading project",
@@ -53,6 +53,8 @@ define({
"ERROR_SAVING_FILE" : "An error occurred when trying to save the file {0}. {1}",
"ERROR_RENAMING_FILE_TITLE" : "Error renaming file",
"ERROR_RENAMING_FILE" : "An error occurred when trying to rename the file {0}. {1}",
+ "ERROR_DELETING_FILE_TITLE" : "Error deleting file",
+ "ERROR_DELETING_FILE" : "An error occurred when trying to delete the file {0}. {1}",
"INVALID_FILENAME_TITLE" : "Invalid file name",
"INVALID_FILENAME_MESSAGE" : "Filenames cannot contain the following characters: /?*:;{}<>\\|",
"FILE_ALREADY_EXISTS" : "The file {0} already exists.",
@@ -177,6 +179,7 @@ define({
"CMD_LIVE_HIGHLIGHT" : "Live Highlight",
"CMD_PROJECT_SETTINGS" : "Project Settings\u2026",
"CMD_FILE_RENAME" : "Rename",
+ "CMD_FILE_DELETE" : "Delete",
"CMD_INSTALL_EXTENSION" : "Install Extension\u2026",
"CMD_EXTENSION_MANAGER" : "Extension Manager\u2026",
"CMD_QUIT" : "Quit",
diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js
index 496a8016749..07761243e1e 100644
--- a/src/project/ProjectManager.js
+++ b/src/project/ProjectManager.js
@@ -1299,7 +1299,7 @@ define(function (require, exports, module) {
result.resolve();
} else {
- // Show and error alert
+ // Show an error alert
Dialogs.showModalDialog(
Dialogs.DIALOG_ID_ERROR,
Strings.ERROR_RENAMING_FILE_TITLE,
@@ -1383,6 +1383,62 @@ define(function (require, exports, module) {
});
// No fail handler: silently no-op if file doesn't exist in tree
}
+
+ /**
+ * Delete file or directore from project
+ * @param {!Entry} entry FileEntry or DirectoryEntry to delete
+ */
+ function deleteItem(entry) {
+ var result = new $.Deferred();
+
+ entry.remove(function () {
+ _findTreeNode(entry).done(function ($node) {
+ _projectTree.one("delete_node.jstree", function () {
+ // When a node is deleted, the previous node is automatically selected.
+ // This works fine as long as the previous node is a file, but doesn't
+ // work so well if the node is a folder
+ var sel = _projectTree.jstree("get_selected"),
+ entry = sel ? sel.data("entry") : null;
+
+ if (entry && entry.isDirectory) {
+ // Make sure it didn't turn into a leaf node. This happens if
+ // the only file in the directory was deleted
+ if (sel.hasClass("jstree-leaf")) {
+ sel.removeClass("jstree-leaf jstree-open");
+ sel.addClass("jstree-closed");
+ }
+ }
+ });
+ var oldSuppressToggleOpen = suppressToggleOpen;
+ suppressToggleOpen = true;
+ _projectTree.jstree("delete_node", $node);
+ suppressToggleOpen = oldSuppressToggleOpen;
+ });
+
+ // Notify that one of the project files has changed
+ $(exports).triggerHandler("projectFilesChange");
+
+ DocumentManager.notifyPathDeleted(entry.fullPath);
+
+ _redraw(true);
+ result.promise();
+ }, function (err) {
+ // Show an error alert
+ Dialogs.showModalDialog(
+ Dialogs.DIALOG_ID_ERROR,
+ Strings.ERROR_DELETING_FILE_TITLE,
+ StringUtils.format(
+ Strings.ERROR_DELETING_FILE,
+ StringUtils.htmlEscape(entry.fullPath),
+ FileUtils.getFileErrorString(err)
+ )
+ );
+
+ result.reject(err);
+ });
+
+ return result;
+ }
/**
* Forces createNewItem() to complete by removing focus from the rename field which causes
@@ -1448,6 +1504,7 @@ define(function (require, exports, module) {
exports.updateWelcomeProjectPath = updateWelcomeProjectPath;
exports.createNewItem = createNewItem;
exports.renameItemInline = renameItemInline;
+ exports.deleteItem = deleteItem;
exports.forceFinishRename = forceFinishRename;
exports.showInTree = showInTree;
});
diff --git a/src/project/WorkingSetView.js b/src/project/WorkingSetView.js
index 697ecf02ee6..54e44484b3a 100644
--- a/src/project/WorkingSetView.js
+++ b/src/project/WorkingSetView.js
@@ -385,14 +385,16 @@ define(function (require, exports, module) {
* Deletes all the list items in the view and rebuilds them from the working set model
* @private
*/
- function _rebuildWorkingSet() {
+ function _rebuildWorkingSet(forceRedraw) {
$openFilesContainer.find("ul").empty();
DocumentManager.getWorkingSet().forEach(function (file) {
_createNewListItem(file);
});
- _redraw();
+ if (forceRedraw) {
+ _redraw();
+ }
}
/**
@@ -524,7 +526,7 @@ define(function (require, exports, module) {
* @private
*/
function _handleWorkingSetSort() {
- _rebuildWorkingSet();
+ _rebuildWorkingSet(true);
_scrollSelectedDocIntoView();
}
@@ -550,7 +552,18 @@ define(function (require, exports, module) {
// Rebuild the working set if any file or folder name changed.
// We could be smarter about this and only update the
// nodes that changed, if needed...
- _rebuildWorkingSet();
+ _rebuildWorkingSet(true);
+ }
+
+ /**
+ * @private
+ * @param {string} path
+ */
+ function _handlePathDeleted(path) {
+ // Rebuild the working set if any file or folder was deleted.
+ // We could be smarter about this and only delete affected
+ // items.
+ _rebuildWorkingSet(false);
}
function refresh() {
@@ -592,6 +605,10 @@ define(function (require, exports, module) {
_handleFileNameChanged(oldName, newName);
});
+ $(DocumentManager).on("pathDeleted", function (event, path) {
+ _handlePathDeleted(path);
+ });
+
$(FileViewController).on("documentSelectionFocusChange fileViewFocusChange", _handleDocumentSelectionChange);
// Show scroller shadows when open-files-container scrolls
diff --git a/src/search/FindInFiles.js b/src/search/FindInFiles.js
index 09451ff1eb7..756cfc6805f 100644
--- a/src/search/FindInFiles.js
+++ b/src/search/FindInFiles.js
@@ -51,6 +51,7 @@ define(function (require, exports, module) {
DocumentManager = require("document/DocumentManager"),
EditorManager = require("editor/EditorManager"),
FileIndexManager = require("project/FileIndexManager"),
+ FileUtils = require("file/FileUtils"),
KeyEvent = require("utils/KeyEvent"),
AppInit = require("utils/AppInit"),
StatusBar = require("widgets/StatusBar"),
@@ -455,8 +456,21 @@ define(function (require, exports, module) {
_showSearchResults(searchResults, currentQuery, currentScope);
}
}
+
+ function _pathDeletedHandler(event, path) {
+ if ($searchResultsDiv.is(":visible")) {
+ // Update the search results
+ searchResults.forEach(function (item, idx) {
+ if (FileUtils.isAffectedWhenRenaming(item.fullPath, path)) {
+ searchResults.splice(idx, 1);
+ }
+ });
+ _showSearchResults(searchResults, currentQuery, currentScope);
+ }
+ }
$(DocumentManager).on("fileNameChange", _fileNameChangeHandler);
+ $(DocumentManager).on("pathDeleted", _pathDeletedHandler);
$(ProjectManager).on("beforeProjectClose", _hideSearchResults);
CommandManager.register(Strings.CMD_FIND_IN_FILES, Commands.EDIT_FIND_IN_FILES, doFindInFiles);