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 #7809 from adobe/nj/replace-on-disk
Browse files Browse the repository at this point in the history
[replace-across-files] Replace in Files
  • Loading branch information
njx committed Jun 13, 2014
2 parents c7651e7 + f6d3efc commit 11c5a93
Show file tree
Hide file tree
Showing 134 changed files with 5,769 additions and 1,480 deletions.
3 changes: 3 additions & 0 deletions src/base-config/keyboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@
"cmd.findInFiles": [
"Ctrl-Shift-F"
],
"cmd.replaceInFiles": [
"Ctrl-Alt-Shift-F"
],
"cmd.findNext": [
{
"key": "F3"
Expand Down
6 changes: 5 additions & 1 deletion src/brackets.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ define(function (require, exports, module) {
require("editor/EditorCommandHandlers");
require("editor/EditorOptionHandlers");
require("help/HelpCommandHandlers");
require("search/FindInFiles");
require("search/FindInFilesUI");
require("search/FindReplace");
require("extensibility/InstallExtensionDialog");
require("extensibility/ExtensionManagerDialog");
Expand Down Expand Up @@ -161,16 +161,20 @@ define(function (require, exports, module) {
Dialogs : Dialogs,
DocumentCommandHandlers : DocumentCommandHandlers,
DocumentManager : DocumentManager,
DocumentModule : require("document/Document"),
DOMAgent : require("LiveDevelopment/Agents/DOMAgent"),
DragAndDrop : DragAndDrop,
EditorManager : EditorManager,
ExtensionLoader : ExtensionLoader,
ExtensionUtils : ExtensionUtils,
File : require("filesystem/File"),
FileFilters : require("search/FileFilters"),
FileSyncManager : FileSyncManager,
FileSystem : FileSystem,
FileViewController : FileViewController,
FileUtils : require("file/FileUtils"),
FindInFiles : require("search/FindInFiles"),
FindInFilesUI : require("search/FindInFilesUI"),
HTMLInstrumentation : require("language/HTMLInstrumentation"),
Inspector : require("LiveDevelopment/Inspector/Inspector"),
InstallExtensionDialog : require("extensibility/InstallExtensionDialog"),
Expand Down
9 changes: 6 additions & 3 deletions src/command/Commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,18 @@ define(function (require, exports, module) {

// FIND
exports.CMD_FIND = "cmd.find"; // FindReplace.js _launchFind()
exports.CMD_FIND_IN_FILES = "cmd.findInFiles"; // FindInFiles.js _doFindInFiles()
exports.CMD_FIND_IN_SELECTED = "cmd.findInSelected"; // FindInFiles.js _doFindInSubtree()
exports.CMD_FIND_IN_SUBTREE = "cmd.findInSubtree"; // FindInFiles.js _doFindInSubtree()
exports.CMD_FIND_IN_FILES = "cmd.findInFiles"; // FindInFilesUI.js _showFindBar()
exports.CMD_FIND_IN_SELECTED = "cmd.findInSelected"; // FindInFilesUI.js _showFindBarForSubtree()
exports.CMD_FIND_IN_SUBTREE = "cmd.findInSubtree"; // FindInFilesUI.js _showFindBarForSubtree()
exports.CMD_FIND_NEXT = "cmd.findNext"; // FindReplace.js _findNext()
exports.CMD_FIND_PREVIOUS = "cmd.findPrevious"; // FindReplace.js _findPrevious()
exports.CMD_FIND_ALL_AND_SELECT = "cmd.findAllAndSelect"; // FindReplace.js _findAllAndSelect()
exports.CMD_ADD_NEXT_MATCH = "cmd.addNextMatch"; // FindReplace.js _expandAndAddNextToSelection()
exports.CMD_SKIP_CURRENT_MATCH = "cmd.skipCurrentMatch"; // FindReplace.js _skipCurrentMatch()
exports.CMD_REPLACE = "cmd.replace"; // FindReplace.js _replace()
exports.CMD_REPLACE_IN_FILES = "cmd.replaceInFiles"; // FindInFilesUI.js _showReplaceBar()
exports.CMD_REPLACE_IN_SELECTED = "cmd.replaceInSelected"; // FindInFilesUI.js _showReplaceBarForSubtree()
exports.CMD_REPLACE_IN_SUBTREE = "cmd.replaceInSubtree"; // FindInFilesUI.js _showReplaceBarForSubtree()

// VIEW
exports.VIEW_HIDE_SIDEBAR = "view.hideSidebar"; // SidebarView.js toggle()
Expand Down
4 changes: 4 additions & 0 deletions src/command/DefaultMenus.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ define(function (require, exports, module) {
menu.addMenuItem(Commands.CMD_FIND_IN_SELECTED);
menu.addMenuDivider();
menu.addMenuItem(Commands.CMD_REPLACE);
menu.addMenuItem(Commands.CMD_REPLACE_IN_FILES);
menu.addMenuItem(Commands.CMD_REPLACE_IN_SELECTED);

/*
* View menu
Expand Down Expand Up @@ -205,6 +207,7 @@ define(function (require, exports, module) {
project_cmenu.addMenuItem(Commands.NAVIGATE_SHOW_IN_OS);
project_cmenu.addMenuDivider();
project_cmenu.addMenuItem(Commands.CMD_FIND_IN_SUBTREE);
project_cmenu.addMenuItem(Commands.CMD_REPLACE_IN_SUBTREE);
project_cmenu.addMenuDivider();
project_cmenu.addMenuItem(Commands.FILE_REFRESH);

Expand All @@ -216,6 +219,7 @@ define(function (require, exports, module) {
working_set_cmenu.addMenuItem(Commands.NAVIGATE_SHOW_IN_OS);
working_set_cmenu.addMenuDivider();
working_set_cmenu.addMenuItem(Commands.CMD_FIND_IN_SUBTREE);
working_set_cmenu.addMenuItem(Commands.CMD_REPLACE_IN_SUBTREE);
working_set_cmenu.addMenuDivider();
working_set_cmenu.addMenuItem(Commands.FILE_CLOSE);

Expand Down
23 changes: 15 additions & 8 deletions src/document/Document.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ define(function (require, exports, module) {

this.file = file;
this._updateLanguage();
this.refreshText(rawText, initialTimestamp);
this.refreshText(rawText, initialTimestamp, true);
}

/**
Expand Down Expand Up @@ -281,8 +281,10 @@ define(function (require, exports, module) {
* the text's line-ending style. CAN be called even if there is no backing editor.
* @param {!string} text The text to replace the contents of the document with.
* @param {!Date} newTimestamp Timestamp of file at the time we read its new contents from disk.
* @param {boolean} initial True if this is the initial load of the document. In that case,
* we don't send change events.
*/
Document.prototype.refreshText = function (text, newTimestamp) {
Document.prototype.refreshText = function (text, newTimestamp, initial) {
var perfTimerName = PerfUtils.markStart("refreshText:\t" + (!this.file || this.file.fullPath));

// If clean, don't transiently mark dirty during refresh
Expand All @@ -294,12 +296,17 @@ define(function (require, exports, module) {
// _handleEditorChange() triggers "change" event for us
} else {
this._text = text;
// We fake a change record here that looks like CodeMirror's text change records, but
// omits "from" and "to", by which we mean the entire text has changed.
// TODO: Dumb to split it here just to join it again in the change handler, but this is
// the CodeMirror change format. Should we document our change format to allow this to
// either be an array of lines or a single string?
$(this).triggerHandler("change", [this, [{text: text.split(/\r?\n/)}]]);

if (!initial) {
// We fake a change record here that looks like CodeMirror's text change records, but
// omits "from" and "to", by which we mean the entire text has changed.
// TODO: Dumb to split it here just to join it again in the change handler, but this is
// the CodeMirror change format. Should we document our change format to allow this to
// either be an array of lines or a single string?
var fakeChangeList = [{text: text.split(/\r?\n/)}];
$(this).triggerHandler("change", [this, fakeChangeList]);
$(exports).triggerHandler("documentChange", [this, fakeChangeList]);
}
}
this._updateTimestamp(newTimestamp);

Expand Down
60 changes: 32 additions & 28 deletions src/document/DocumentCommandHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -666,10 +666,13 @@ define(function (require, exports, module) {
* Reverts the Document to the current contents of its file on disk. Discards any unsaved changes
* in the Document.
* @param {Document} doc
* @return {$.Promise} a Promise that's resolved when done, or rejected with a FileSystemError if the
* file cannot be read (after showing an error dialog to the user).
* @param {boolean=} suppressError If true, then a failure to read the file will be ignored and the
* resulting promise will be resolved rather than rejected.
* @return {$.Promise} a Promise that's resolved when done, or (if suppressError is false)
* rejected with a FileSystemError if the file cannot be read (after showing an error
* dialog to the user).
*/
function doRevert(doc) {
function doRevert(doc, suppressError) {
var result = new $.Deferred();

FileUtils.readAsText(doc.file)
Expand All @@ -678,10 +681,14 @@ define(function (require, exports, module) {
result.resolve();
})
.fail(function (error) {
FileUtils.showFileOpenError(error, doc.file.fullPath)
.done(function () {
result.reject(error);
});
if (suppressError) {
result.resolve();
} else {
FileUtils.showFileOpenError(error, doc.file.fullPath)
.done(function () {
result.reject(error);
});
}
});

return result.promise();
Expand Down Expand Up @@ -1070,13 +1077,19 @@ define(function (require, exports, module) {
// copy of whatever's on disk.
doClose(file);

// Only reload from disk if we've executed the Close for real,
// *and* if at least one other view still exists
if (!promptOnly && DocumentManager.getOpenDocumentForPath(file.fullPath)) {
doRevert(doc)
.then(result.resolve, result.reject);
} else {
// Only reload from disk if we've executed the Close for real.
if (promptOnly) {
result.resolve();
} else {
// Even if there are no listeners attached to the document at this point, we want
// to do the revert anyway, because clients who are listening to the global documentChange
// event from the Document module (rather than attaching to the document directly),
// such as the Find in Files panel, should get a change event. However, in that case,
// we want to ignore errors during the revert, since we don't want a failed revert
// to throw a dialog if the document isn't actually open in the UI.
var suppressError = !DocumentManager.getOpenDocumentForPath(file.fullPath);
doRevert(doc, suppressError)
.then(result.resolve, result.reject);
}
}
});
Expand All @@ -1096,8 +1109,9 @@ define(function (require, exports, module) {
* @param {!Array.<FileEntry>} list
* @param {boolean} promptOnly
* @param {boolean} clearCurrentDoc
* @param {boolean} _forceClose Whether to force all the documents to close even if they have unsaved changes. For unit testing only.
*/
function _closeList(list, promptOnly, clearCurrentDoc) {
function _closeList(list, promptOnly, clearCurrentDoc, _forceClose) {
var result = new $.Deferred(),
unsavedDocs = [];

Expand All @@ -1108,8 +1122,8 @@ define(function (require, exports, module) {
}
});

if (unsavedDocs.length === 0) {
// No unsaved changes, so we can proceed without a prompt
if (unsavedDocs.length === 0 || _forceClose) {
// No unsaved changes or we want to ignore them, so we can proceed without a prompt
result.resolve();

} else if (unsavedDocs.length === 1) {
Expand All @@ -1125,17 +1139,7 @@ define(function (require, exports, module) {

} else {
// Multiple unsaved files: show a single bulk prompt listing all files
var message = Strings.SAVE_CLOSE_MULTI_MESSAGE;

message += "<ul class='dialog-list'>";
unsavedDocs.forEach(function (doc) {
var fullPath = doc.file.fullPath;

message += "<li><span class='dialog-filename'>";
message += StringUtils.breakableUrl(_shortTitleForDocument(doc));
message += "</span></li>";
});
message += "</ul>";
var message = Strings.SAVE_CLOSE_MULTI_MESSAGE + StringUtils.makeDialogFileList(_.map(unsavedDocs, _shortTitleForDocument));

Dialogs.showModalDialog(
DefaultDialogs.DIALOG_ID_SAVE_CLOSE,
Expand Down Expand Up @@ -1200,7 +1204,7 @@ define(function (require, exports, module) {
*/
function handleFileCloseAll(commandData) {
return _closeList(DocumentManager.getWorkingSet(),
(commandData && commandData.promptOnly), true).done(function () {
(commandData && commandData.promptOnly), true, (commandData && commandData._forceClose)).done(function () {
if (!DocumentManager.getCurrentDocument()) {
EditorManager._closeCustomViewer();
}
Expand Down
20 changes: 14 additions & 6 deletions src/document/DocumentManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -735,23 +735,31 @@ define(function (require, exports, module) {
* Differs from plain FileUtils.readAsText() in two ways: (a) line endings are still normalized
* as in Document.getText(); (b) unsaved changes are returned if there are any.
*
* @param {!File} file
* @return {!string}
* @param {!File} file The file to get the text for.
* @param {boolean=} checkLineEndings Whether to return line ending information. Default false (slightly more efficient).
* @return {$.Promise}
* A promise that is resolved with three parameters:
* contents - string: the document's text
* timestamp - Date: the last time the document was changed on disk (might not be the same as the last time it was changed in memory)
* lineEndings - string: the original line endings of the file, one of the FileUtils.LINE_ENDINGS_* constants;
* will be null if checkLineEndings was false.
* or rejected with a filesystem error.
*/
function getDocumentText(file) {
function getDocumentText(file, checkLineEndings) {
var result = new $.Deferred(),
doc = getOpenDocumentForPath(file.fullPath);
if (doc) {
result.resolve(doc.getText());
result.resolve(doc.getText(), doc.diskTimestamp, checkLineEndings ? doc._lineEndings : null);
} else {
file.read(function (err, contents) {
file.read(function (err, contents, stat) {
if (err) {
result.reject(err);
} else {
// Normalize line endings the same way Document would, but don't actually
// new up a Document (which entails a bunch of object churn).
var originalLineEndings = checkLineEndings ? FileUtils.sniffLineEndings(contents) : null;
contents = DocumentModule.Document.normalizeText(contents);
result.resolve(contents);
result.resolve(contents, stat.mtime, originalLineEndings);
}
});
}
Expand Down
33 changes: 33 additions & 0 deletions src/file/FileUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,39 @@ define(function (require, exports, module) {

return extFirst ? (cmpExt || cmpNames) : (cmpNames || cmpExt);
}

/**
* Compares two paths. Useful for sorting.
* @param {string} filename1
* @param {string} filename2
* @param {boolean} extFirst If true it compares the extensions first and then the file names.
* @return {number} The result of the local compare function
*/
function comparePaths(path1, path2) {
var entryName1, entryName2,
pathParts1 = path1.split("/"),
pathParts2 = path2.split("/"),
length = Math.min(pathParts1.length, pathParts2.length),
folders1 = pathParts1.length - 1,
folders2 = pathParts2.length - 1,
index = 0;

while (index < length) {
entryName1 = pathParts1[index];
entryName2 = pathParts2[index];

if (entryName1 !== entryName2) {
if (index < folders1 && index < folders2) {
return entryName1.toLocaleLowerCase().localeCompare(entryName2.toLocaleLowerCase());
} else if (index >= folders1 && index >= folders2) {
return compareFilenames(entryName1, entryName2);
}
return (index >= folders1 && index < folders2) ? 1 : -1;
}
index++;
}
return 0;
}

// Define public API
exports.LINE_ENDINGS_CRLF = LINE_ENDINGS_CRLF;
Expand All @@ -465,4 +497,5 @@ define(function (require, exports, module) {
exports.getFileExtension = getFileExtension;
exports.getSmartFileExtension = getSmartFileExtension;
exports.compareFilenames = compareFilenames;
exports.comparePaths = comparePaths;
});
Loading

0 comments on commit 11c5a93

Please sign in to comment.