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

Commit

Permalink
Merge pull request #1719 from adobe/glenn/new-features
Browse files Browse the repository at this point in the history
Add "New Folder" and "Rename" features
  • Loading branch information
jasonsanjose committed Oct 3, 2012
2 parents 04bcf56 + edda6b5 commit 1c3c822
Show file tree
Hide file tree
Showing 14 changed files with 672 additions and 80 deletions.
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;
}
}
}
}
// 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

0 comments on commit 1c3c822

Please sign in to comment.