Skip to content
This repository has been archived by the owner on Sep 6, 2021. It is now read-only.

Add "New Folder" and "Rename" features #1719

Merged
merged 9 commits into from
Oct 3, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/command/Commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ define(function (require, exports, module) {

// FILE
exports.FILE_NEW = "file.new";
exports.FILE_NEW_FOLDER = "file.newFolder";
exports.FILE_OPEN = "file.open";
exports.FILE_OPEN_FOLDER = "file.openFolder";
exports.FILE_SAVE = "file.save";
Expand All @@ -43,6 +44,7 @@ define(function (require, exports, module) {
exports.FILE_CLOSE_WINDOW = "file.close_window"; // string must MATCH string in native code (brackets_extensions)
exports.FILE_ADD_TO_WORKING_SET = "file.addToWorkingSet";
exports.FILE_LIVE_FILE_PREVIEW = "file.liveFilePreview";
exports.FILE_RENAME = "file.rename";
exports.FILE_QUIT = "file.quit"; // string must MATCH string in native code (brackets_extensions)

// EDIT
Expand Down
3 changes: 3 additions & 0 deletions src/command/Menus.js
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,7 @@ define(function (require, exports, module) {
var menu;
menu = addMenu(Strings.FILE_MENU, AppMenuBar.FILE_MENU);
menu.addMenuItem(Commands.FILE_NEW, "Ctrl-N");
menu.addMenuItem(Commands.FILE_NEW_FOLDER);
menu.addMenuItem(Commands.FILE_OPEN, "Ctrl-O");
menu.addMenuItem(Commands.FILE_OPEN_FOLDER);
menu.addMenuItem(Commands.FILE_CLOSE, "Ctrl-W");
Expand Down Expand Up @@ -962,6 +963,8 @@ define(function (require, exports, module) {
*/
var project_cmenu = registerContextMenu(ContextMenuIds.PROJECT_MENU);
project_cmenu.addMenuItem(Commands.FILE_NEW);
project_cmenu.addMenuItem(Commands.FILE_NEW_FOLDER);
project_cmenu.addMenuItem(Commands.FILE_RENAME);

var editor_cmenu = registerContextMenu(ContextMenuIds.EDITOR_MENU);
editor_cmenu.addMenuItem(Commands.TOGGLE_QUICK_EDIT);
Expand Down
89 changes: 65 additions & 24 deletions src/document/DocumentCommandHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,13 @@ define(function (require, exports, module) {
}
}

function handleCurrentDocumentChange() {
function updateDocumentTitle() {
var newDocument = DocumentManager.getCurrentDocument();
var perfTimerName = PerfUtils.markStart("DocumentCommandHandlers._onCurrentDocumentChange():\t" + (!newDocument || newDocument.file.fullPath));

// TODO: This timer is causing a "Recursive tests with the same name are not supporte"
// exception. This code should be removed (if not needed), or updated with a unique
// timer name (if needed).
// var perfTimerName = PerfUtils.markStart("DocumentCommandHandlers._onCurrentDocumentChange():\t" + (!newDocument || newDocument.file.fullPath));

if (newDocument) {
var fullPath = newDocument.file.fullPath;
Expand All @@ -113,7 +117,7 @@ define(function (require, exports, module) {
// Update title text & "dirty dot" display
updateTitle();

PerfUtils.addMeasurement(perfTimerName);
// PerfUtils.addMeasurement(perfTimerName);
}

function handleDirtyChange(event, changedDoc) {
Expand Down Expand Up @@ -253,10 +257,11 @@ define(function (require, exports, module) {
* @param {string} dir The directory to use
* @param {string} baseFileName The base to start with, "-n" will get appened to make unique
* @param {string} fileExt The file extension
* @param {boolean} isFolder True if the suggestion is for a folder name
* @return {$.Promise} a jQuery promise that will be resolved with a unique name starting with
* the given base name
*/
function _getUntitledFileSuggestion(dir, baseFileName, fileExt) {
function _getUntitledFileSuggestion(dir, baseFileName, fileExt, isFolder) {
var result = new $.Deferred();
var suggestedName = baseFileName + fileExt;
var dirEntry = new NativeFileSystem.DirectoryEntry(dir);
Expand All @@ -269,18 +274,30 @@ define(function (require, exports, module) {
}

//check this name
dirEntry.getFile(
suggestedName,
{},
function successCallback(entry) {
//file exists, notify to the next progress
result.notify(baseFileName + "-" + nextIndexToUse + fileExt, nextIndexToUse + 1);
},
function errorCallback(error) {
//most likely error is FNF, user is better equiped to handle the rest
result.resolve(suggestedName);
}
);
var successCallback = function (entry) {
//file exists, notify to the next progress
result.notify(baseFileName + "-" + nextIndexToUse + fileExt, nextIndexToUse + 1);
};
var errorCallback = function (error) {
//most likely error is FNF, user is better equiped to handle the rest
result.resolve(suggestedName);
};

if (isFolder) {
dirEntry.getDirectory(
suggestedName,
{},
successCallback,
errorCallback
);
} else {
dirEntry.getFile(
suggestedName,
{},
successCallback,
errorCallback
);
}
});

//kick it off
Expand All @@ -297,9 +314,11 @@ define(function (require, exports, module) {
* file creation call is outstanding
*/
var fileNewInProgress = false;

function handleFileNewInProject() {


/**
* Bottleneck function for creating new files and folders in the project tree.
*/
function _handleNewItemInProject(isFolder) {
if (fileNewInProgress) {
ProjectManager.forceFinishRename();
return;
Expand All @@ -320,21 +339,37 @@ define(function (require, exports, module) {

// Create the new node. The createNewItem function does all the heavy work
// of validating file name, creating the new file and selecting.
var deferred = _getUntitledFileSuggestion(baseDir, Strings.UNTITLED, ".js");
var deferred = _getUntitledFileSuggestion(baseDir, Strings.UNTITLED, isFolder ? "" : ".js", isFolder);
var createWithSuggestedName = function (suggestedName) {
ProjectManager.createNewItem(baseDir, suggestedName, false)
ProjectManager.createNewItem(baseDir, suggestedName, false, isFolder)
.pipe(deferred.resolve, deferred.reject, deferred.notify)
.always(function () { fileNewInProgress = false; })
.done(function (entry) {
FileViewController.addToWorkingSetAndSelect(entry.fullPath, FileViewController.PROJECT_MANAGER);
if (!isFolder) {
FileViewController.addToWorkingSetAndSelect(entry.fullPath, FileViewController.PROJECT_MANAGER);
}
});
};

deferred.done(createWithSuggestedName);
deferred.fail(function createWithDefault() { createWithSuggestedName("Untitled.js"); });
deferred.fail(function createWithDefault() { createWithSuggestedName(isFolder ? "Untitled" : "Untitled.js"); });
return deferred;
}

/**
* Create a new file in the project tree.
*/
function handleFileNewInProject() {
_handleNewItemInProject(false);
}

/**
* Create a new folder in the project tree.
*/
function handleNewFolderInProject() {
_handleNewItemInProject(true);
}

function showSaveFileError(code, path) {
return Dialogs.showModalDialog(
Dialogs.DIALOG_ID_ERROR,
Expand Down Expand Up @@ -711,6 +746,10 @@ define(function (require, exports, module) {
);
}

function handleFileRename() {
ProjectManager.renameSelectedItem();
}

/** Closes the window, then quits the app */
function handleFileQuit(commandData) {
return _handleWindowGoingAway(
Expand Down Expand Up @@ -791,11 +830,13 @@ define(function (require, exports, module) {
// File > New should open a new blank tab, and handleFileNewInProject should
// be called from a "+" button in the project
CommandManager.register(Strings.CMD_FILE_NEW, Commands.FILE_NEW, handleFileNewInProject);
CommandManager.register(Strings.CMD_FILE_NEW_FOLDER, Commands.FILE_NEW_FOLDER, handleNewFolderInProject);
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_CLOSE, Commands.FILE_CLOSE, handleFileClose);
CommandManager.register(Strings.CMD_FILE_CLOSE_ALL, Commands.FILE_CLOSE_ALL, handleFileCloseAll);
CommandManager.register(Strings.CMD_FILE_RENAME, Commands.FILE_RENAME, handleFileRename);
CommandManager.register(Strings.CMD_CLOSE_WINDOW, Commands.FILE_CLOSE_WINDOW, handleFileCloseWindow);
CommandManager.register(Strings.CMD_QUIT, Commands.FILE_QUIT, handleFileQuit);
CommandManager.register(Strings.CMD_REFRESH_WINDOW, Commands.DEBUG_REFRESH_WINDOW, handleFileReload);
Expand All @@ -805,7 +846,7 @@ define(function (require, exports, module) {

// Listen for changes that require updating the editor titlebar
$(DocumentManager).on("dirtyFlagChange", handleDirtyChange);
$(DocumentManager).on("currentDocumentChange", handleCurrentDocumentChange);
$(DocumentManager).on("currentDocumentChange fileNameChange", updateDocumentTitle);
}

// Define public API
Expand Down
58 changes: 56 additions & 2 deletions src/document/DocumentManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
* 2nd arg to the listener is the removed FileEntry.
* - workingSetRemoveList -- When a list of files is to be removed from the working set (e.g. project close).
* The 2nd arg to the listener is the array of removed FileEntry objects.
* - 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.
*
* These are jQuery events, so to listen for them you do something like this:
* $(DocumentManager).on("eventname", handler);
Expand Down Expand Up @@ -1061,7 +1063,58 @@ define(function (require, exports, module) {
}
}


/**
* Called after a file or folder name has changed. This function is responsible
* for updating underlying model data and notifying all views of the change.
*
* @param {string} oldName The old name of the file/folder
* @param {string} newName The new name of the file/folder
* @param {boolean} isFolder True if path is a folder; False if it is a file.
*/
function notifyPathNameChanged(oldName, newName, isFolder) {
var i, path;

// Update currentDocument
if (_currentDocument) {
FileUtils.updateFileEntryPath(_currentDocument.file, oldName, newName);
}

// Update open documents
var keysToDelete = [];
for (path in _openDocuments) {
if (_openDocuments.hasOwnProperty(path)) {
if (path.indexOf(oldName) === 0) {
// Copy value to new key
var newKey = path.replace(oldName, newName);

_openDocuments[newKey] = _openDocuments[path];
keysToDelete.push(path);

// Update document file
FileUtils.updateFileEntryPath(_openDocuments[newKey].file, oldName, newName);

if (!isFolder) {
// If the path name is a file, there can only be one matched entry in the open document
// list, which we just updated. Break out of the for .. in loop.
break;
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case of a file (as opposed to a folder), we can exit this loop after we find the first item because we know there will be at most 1. So, it might be worth adding an isFolder parameter to this function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

// Delete the old keys
for (i = 0; i < keysToDelete.length; i++) {
delete _openDocuments[keysToDelete[i]];
}

// Update working set
for (i = 0; i < _workingSet.length; i++) {
FileUtils.updateFileEntryPath(_workingSet[i], oldName, newName);
}

// Send a "fileNameChanged" event. This will trigger the views to update.
$(exports).triggerHandler("fileNameChange", [oldName, newName]);
}

// Define public API
exports.Document = Document;
exports.getCurrentDocument = getCurrentDocument;
Expand All @@ -1080,10 +1133,11 @@ define(function (require, exports, module) {
exports.closeFullEditor = closeFullEditor;
exports.closeAll = closeAll;
exports.notifyFileDeleted = notifyFileDeleted;
exports.notifyPathNameChanged = notifyPathNameChanged;

// Setup preferences
_prefs = PreferencesManager.getPreferenceStorage(PREFERENCES_CLIENT_ID);
$(exports).bind("currentDocumentChange workingSetAdd workingSetAddList workingSetRemove workingSetRemoveList", _savePreferences);
$(exports).bind("currentDocumentChange workingSetAdd workingSetAddList workingSetRemove workingSetRemoveList fileNameChange", _savePreferences);

// Performance measurements
PerfUtils.createPerfMeasurement("DOCUMENT_MANAGER_GET_DOCUMENT_FOR_PATH", "DocumentManager.getDocumentForPath()");
Expand Down
31 changes: 31 additions & 0 deletions src/file/FileUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,36 @@ define(function (require, exports, module) {
}
return path;
}

/**
* Update a file entry path after a file/folder name change.
* @param {FileEntry} entry The FileEntry or DirectoryEntry to update
* @param {string} oldName The full path of the old name
* @param {string} newName The full path of the new name
* @return {boolean} Returns true if the file entry was updated
*/
function updateFileEntryPath(entry, oldName, newName) {
if (entry.fullPath.indexOf(oldName) === 0) {
var fullPath = entry.fullPath.replace(oldName, newName);

entry.fullPath = fullPath;

// TODO: Should this be a method on Entry instead?
entry.name = null; // default if extraction fails
if (fullPath) {
var pathParts = fullPath.split("/");

// Extract name from the end of the fullPath (account for trailing slash(es))
while (!entry.name && pathParts.length) {
entry.name = pathParts.pop();
}
}

return true;
}

return false;
}

// Define public API
exports.LINE_ENDINGS_CRLF = LINE_ENDINGS_CRLF;
Expand All @@ -276,4 +306,5 @@ define(function (require, exports, module) {
exports.getNativeBracketsDirectoryPath = getNativeBracketsDirectoryPath;
exports.getNativeModuleDirectoryPath = getNativeModuleDirectoryPath;
exports.canonicalizeFolderPath = canonicalizeFolderPath;
exports.updateFileEntryPath = updateFileEntryPath;
});
Loading