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

Commit 1c3c822

Browse files
committed
Merge pull request #1719 from adobe/glenn/new-features
Add "New Folder" and "Rename" features
2 parents 04bcf56 + edda6b5 commit 1c3c822

File tree

14 files changed

+672
-80
lines changed

14 files changed

+672
-80
lines changed

src/command/Commands.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ define(function (require, exports, module) {
3434

3535
// FILE
3636
exports.FILE_NEW = "file.new";
37+
exports.FILE_NEW_FOLDER = "file.newFolder";
3738
exports.FILE_OPEN = "file.open";
3839
exports.FILE_OPEN_FOLDER = "file.openFolder";
3940
exports.FILE_SAVE = "file.save";
@@ -43,6 +44,7 @@ define(function (require, exports, module) {
4344
exports.FILE_CLOSE_WINDOW = "file.close_window"; // string must MATCH string in native code (brackets_extensions)
4445
exports.FILE_ADD_TO_WORKING_SET = "file.addToWorkingSet";
4546
exports.FILE_LIVE_FILE_PREVIEW = "file.liveFilePreview";
47+
exports.FILE_RENAME = "file.rename";
4648
exports.FILE_QUIT = "file.quit"; // string must MATCH string in native code (brackets_extensions)
4749

4850
// EDIT

src/command/Menus.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,7 @@ define(function (require, exports, module) {
848848
var menu;
849849
menu = addMenu(Strings.FILE_MENU, AppMenuBar.FILE_MENU);
850850
menu.addMenuItem(Commands.FILE_NEW, "Ctrl-N");
851+
menu.addMenuItem(Commands.FILE_NEW_FOLDER);
851852
menu.addMenuItem(Commands.FILE_OPEN, "Ctrl-O");
852853
menu.addMenuItem(Commands.FILE_OPEN_FOLDER);
853854
menu.addMenuItem(Commands.FILE_CLOSE, "Ctrl-W");
@@ -962,6 +963,8 @@ define(function (require, exports, module) {
962963
*/
963964
var project_cmenu = registerContextMenu(ContextMenuIds.PROJECT_MENU);
964965
project_cmenu.addMenuItem(Commands.FILE_NEW);
966+
project_cmenu.addMenuItem(Commands.FILE_NEW_FOLDER);
967+
project_cmenu.addMenuItem(Commands.FILE_RENAME);
965968

966969
var editor_cmenu = registerContextMenu(ContextMenuIds.EDITOR_MENU);
967970
editor_cmenu.addMenuItem(Commands.TOGGLE_QUICK_EDIT);

src/document/DocumentCommandHandlers.js

Lines changed: 65 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,13 @@ define(function (require, exports, module) {
9595
}
9696
}
9797

98-
function handleCurrentDocumentChange() {
98+
function updateDocumentTitle() {
9999
var newDocument = DocumentManager.getCurrentDocument();
100-
var perfTimerName = PerfUtils.markStart("DocumentCommandHandlers._onCurrentDocumentChange():\t" + (!newDocument || newDocument.file.fullPath));
100+
101+
// TODO: This timer is causing a "Recursive tests with the same name are not supporte"
102+
// exception. This code should be removed (if not needed), or updated with a unique
103+
// timer name (if needed).
104+
// var perfTimerName = PerfUtils.markStart("DocumentCommandHandlers._onCurrentDocumentChange():\t" + (!newDocument || newDocument.file.fullPath));
101105

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

116-
PerfUtils.addMeasurement(perfTimerName);
120+
// PerfUtils.addMeasurement(perfTimerName);
117121
}
118122

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

271276
//check this name
272-
dirEntry.getFile(
273-
suggestedName,
274-
{},
275-
function successCallback(entry) {
276-
//file exists, notify to the next progress
277-
result.notify(baseFileName + "-" + nextIndexToUse + fileExt, nextIndexToUse + 1);
278-
},
279-
function errorCallback(error) {
280-
//most likely error is FNF, user is better equiped to handle the rest
281-
result.resolve(suggestedName);
282-
}
283-
);
277+
var successCallback = function (entry) {
278+
//file exists, notify to the next progress
279+
result.notify(baseFileName + "-" + nextIndexToUse + fileExt, nextIndexToUse + 1);
280+
};
281+
var errorCallback = function (error) {
282+
//most likely error is FNF, user is better equiped to handle the rest
283+
result.resolve(suggestedName);
284+
};
285+
286+
if (isFolder) {
287+
dirEntry.getDirectory(
288+
suggestedName,
289+
{},
290+
successCallback,
291+
errorCallback
292+
);
293+
} else {
294+
dirEntry.getFile(
295+
suggestedName,
296+
{},
297+
successCallback,
298+
errorCallback
299+
);
300+
}
284301
});
285302

286303
//kick it off
@@ -297,9 +314,11 @@ define(function (require, exports, module) {
297314
* file creation call is outstanding
298315
*/
299316
var fileNewInProgress = false;
300-
301-
function handleFileNewInProject() {
302-
317+
318+
/**
319+
* Bottleneck function for creating new files and folders in the project tree.
320+
*/
321+
function _handleNewItemInProject(isFolder) {
303322
if (fileNewInProgress) {
304323
ProjectManager.forceFinishRename();
305324
return;
@@ -320,21 +339,37 @@ define(function (require, exports, module) {
320339

321340
// Create the new node. The createNewItem function does all the heavy work
322341
// of validating file name, creating the new file and selecting.
323-
var deferred = _getUntitledFileSuggestion(baseDir, Strings.UNTITLED, ".js");
342+
var deferred = _getUntitledFileSuggestion(baseDir, Strings.UNTITLED, isFolder ? "" : ".js", isFolder);
324343
var createWithSuggestedName = function (suggestedName) {
325-
ProjectManager.createNewItem(baseDir, suggestedName, false)
344+
ProjectManager.createNewItem(baseDir, suggestedName, false, isFolder)
326345
.pipe(deferred.resolve, deferred.reject, deferred.notify)
327346
.always(function () { fileNewInProgress = false; })
328347
.done(function (entry) {
329-
FileViewController.addToWorkingSetAndSelect(entry.fullPath, FileViewController.PROJECT_MANAGER);
348+
if (!isFolder) {
349+
FileViewController.addToWorkingSetAndSelect(entry.fullPath, FileViewController.PROJECT_MANAGER);
350+
}
330351
});
331352
};
332353

333354
deferred.done(createWithSuggestedName);
334-
deferred.fail(function createWithDefault() { createWithSuggestedName("Untitled.js"); });
355+
deferred.fail(function createWithDefault() { createWithSuggestedName(isFolder ? "Untitled" : "Untitled.js"); });
335356
return deferred;
336357
}
358+
359+
/**
360+
* Create a new file in the project tree.
361+
*/
362+
function handleFileNewInProject() {
363+
_handleNewItemInProject(false);
364+
}
337365

366+
/**
367+
* Create a new folder in the project tree.
368+
*/
369+
function handleNewFolderInProject() {
370+
_handleNewItemInProject(true);
371+
}
372+
338373
function showSaveFileError(code, path) {
339374
return Dialogs.showModalDialog(
340375
Dialogs.DIALOG_ID_ERROR,
@@ -711,6 +746,10 @@ define(function (require, exports, module) {
711746
);
712747
}
713748

749+
function handleFileRename() {
750+
ProjectManager.renameSelectedItem();
751+
}
752+
714753
/** Closes the window, then quits the app */
715754
function handleFileQuit(commandData) {
716755
return _handleWindowGoingAway(
@@ -791,11 +830,13 @@ define(function (require, exports, module) {
791830
// File > New should open a new blank tab, and handleFileNewInProject should
792831
// be called from a "+" button in the project
793832
CommandManager.register(Strings.CMD_FILE_NEW, Commands.FILE_NEW, handleFileNewInProject);
833+
CommandManager.register(Strings.CMD_FILE_NEW_FOLDER, Commands.FILE_NEW_FOLDER, handleNewFolderInProject);
794834
CommandManager.register(Strings.CMD_FILE_SAVE, Commands.FILE_SAVE, handleFileSave);
795835
CommandManager.register(Strings.CMD_FILE_SAVE_ALL, Commands.FILE_SAVE_ALL, handleFileSaveAll);
796836

797837
CommandManager.register(Strings.CMD_FILE_CLOSE, Commands.FILE_CLOSE, handleFileClose);
798838
CommandManager.register(Strings.CMD_FILE_CLOSE_ALL, Commands.FILE_CLOSE_ALL, handleFileCloseAll);
839+
CommandManager.register(Strings.CMD_FILE_RENAME, Commands.FILE_RENAME, handleFileRename);
799840
CommandManager.register(Strings.CMD_CLOSE_WINDOW, Commands.FILE_CLOSE_WINDOW, handleFileCloseWindow);
800841
CommandManager.register(Strings.CMD_QUIT, Commands.FILE_QUIT, handleFileQuit);
801842
CommandManager.register(Strings.CMD_REFRESH_WINDOW, Commands.DEBUG_REFRESH_WINDOW, handleFileReload);
@@ -805,7 +846,7 @@ define(function (require, exports, module) {
805846

806847
// Listen for changes that require updating the editor titlebar
807848
$(DocumentManager).on("dirtyFlagChange", handleDirtyChange);
808-
$(DocumentManager).on("currentDocumentChange", handleCurrentDocumentChange);
849+
$(DocumentManager).on("currentDocumentChange fileNameChange", updateDocumentTitle);
809850
}
810851

811852
// Define public API

src/document/DocumentManager.js

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@
6464
* 2nd arg to the listener is the removed FileEntry.
6565
* - workingSetRemoveList -- When a list of files is to be removed from the working set (e.g. project close).
6666
* The 2nd arg to the listener is the array of removed FileEntry objects.
67+
* - fileNameChange -- When the name of a file or folder has changed. The 2nd arg is the old name.
68+
* The 3rd arg is the new name.
6769
*
6870
* These are jQuery events, so to listen for them you do something like this:
6971
* $(DocumentManager).on("eventname", handler);
@@ -1061,7 +1063,58 @@ define(function (require, exports, module) {
10611063
}
10621064
}
10631065

1064-
1066+
/**
1067+
* Called after a file or folder name has changed. This function is responsible
1068+
* for updating underlying model data and notifying all views of the change.
1069+
*
1070+
* @param {string} oldName The old name of the file/folder
1071+
* @param {string} newName The new name of the file/folder
1072+
* @param {boolean} isFolder True if path is a folder; False if it is a file.
1073+
*/
1074+
function notifyPathNameChanged(oldName, newName, isFolder) {
1075+
var i, path;
1076+
1077+
// Update currentDocument
1078+
if (_currentDocument) {
1079+
FileUtils.updateFileEntryPath(_currentDocument.file, oldName, newName);
1080+
}
1081+
1082+
// Update open documents
1083+
var keysToDelete = [];
1084+
for (path in _openDocuments) {
1085+
if (_openDocuments.hasOwnProperty(path)) {
1086+
if (path.indexOf(oldName) === 0) {
1087+
// Copy value to new key
1088+
var newKey = path.replace(oldName, newName);
1089+
1090+
_openDocuments[newKey] = _openDocuments[path];
1091+
keysToDelete.push(path);
1092+
1093+
// Update document file
1094+
FileUtils.updateFileEntryPath(_openDocuments[newKey].file, oldName, newName);
1095+
1096+
if (!isFolder) {
1097+
// If the path name is a file, there can only be one matched entry in the open document
1098+
// list, which we just updated. Break out of the for .. in loop.
1099+
break;
1100+
}
1101+
}
1102+
}
1103+
}
1104+
// Delete the old keys
1105+
for (i = 0; i < keysToDelete.length; i++) {
1106+
delete _openDocuments[keysToDelete[i]];
1107+
}
1108+
1109+
// Update working set
1110+
for (i = 0; i < _workingSet.length; i++) {
1111+
FileUtils.updateFileEntryPath(_workingSet[i], oldName, newName);
1112+
}
1113+
1114+
// Send a "fileNameChanged" event. This will trigger the views to update.
1115+
$(exports).triggerHandler("fileNameChange", [oldName, newName]);
1116+
}
1117+
10651118
// Define public API
10661119
exports.Document = Document;
10671120
exports.getCurrentDocument = getCurrentDocument;
@@ -1080,10 +1133,11 @@ define(function (require, exports, module) {
10801133
exports.closeFullEditor = closeFullEditor;
10811134
exports.closeAll = closeAll;
10821135
exports.notifyFileDeleted = notifyFileDeleted;
1136+
exports.notifyPathNameChanged = notifyPathNameChanged;
10831137

10841138
// Setup preferences
10851139
_prefs = PreferencesManager.getPreferenceStorage(PREFERENCES_CLIENT_ID);
1086-
$(exports).bind("currentDocumentChange workingSetAdd workingSetAddList workingSetRemove workingSetRemoveList", _savePreferences);
1140+
$(exports).bind("currentDocumentChange workingSetAdd workingSetAddList workingSetRemove workingSetRemoveList fileNameChange", _savePreferences);
10871141

10881142
// Performance measurements
10891143
PerfUtils.createPerfMeasurement("DOCUMENT_MANAGER_GET_DOCUMENT_FOR_PATH", "DocumentManager.getDocumentForPath()");

src/file/FileUtils.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,36 @@ define(function (require, exports, module) {
261261
}
262262
return path;
263263
}
264+
265+
/**
266+
* Update a file entry path after a file/folder name change.
267+
* @param {FileEntry} entry The FileEntry or DirectoryEntry to update
268+
* @param {string} oldName The full path of the old name
269+
* @param {string} newName The full path of the new name
270+
* @return {boolean} Returns true if the file entry was updated
271+
*/
272+
function updateFileEntryPath(entry, oldName, newName) {
273+
if (entry.fullPath.indexOf(oldName) === 0) {
274+
var fullPath = entry.fullPath.replace(oldName, newName);
275+
276+
entry.fullPath = fullPath;
277+
278+
// TODO: Should this be a method on Entry instead?
279+
entry.name = null; // default if extraction fails
280+
if (fullPath) {
281+
var pathParts = fullPath.split("/");
282+
283+
// Extract name from the end of the fullPath (account for trailing slash(es))
284+
while (!entry.name && pathParts.length) {
285+
entry.name = pathParts.pop();
286+
}
287+
}
288+
289+
return true;
290+
}
291+
292+
return false;
293+
}
264294

265295
// Define public API
266296
exports.LINE_ENDINGS_CRLF = LINE_ENDINGS_CRLF;
@@ -276,4 +306,5 @@ define(function (require, exports, module) {
276306
exports.getNativeBracketsDirectoryPath = getNativeBracketsDirectoryPath;
277307
exports.getNativeModuleDirectoryPath = getNativeModuleDirectoryPath;
278308
exports.canonicalizeFolderPath = canonicalizeFolderPath;
309+
exports.updateFileEntryPath = updateFileEntryPath;
279310
});

0 commit comments

Comments
 (0)