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

Commit 0f9574c

Browse files
author
Narciso Jaramillo
committed
Merge pull request #8169 from adobe/nj/replace-across-files
Land 'Replace In Files' in master
2 parents dae45b1 + dbc8906 commit 0f9574c

File tree

136 files changed

+7121
-2290
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

136 files changed

+7121
-2290
lines changed

Gruntfile.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ module.exports = function (grunt) {
200200
'test/**/*.js',
201201
'!test/perf/*-files/**/*.js',
202202
'!test/spec/*-files/**/*.js',
203+
'!test/spec/*-known-goods/**/*.js',
204+
'!test/spec/FindReplace-test-files-*/**/*.js',
203205
'!test/smokes/**',
204206
'!test/temp/**',
205207
'!test/thirdparty/**',

src/base-config/keyboard.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,9 @@
151151
"cmd.findInFiles": [
152152
"Ctrl-Shift-F"
153153
],
154+
"cmd.replaceInFiles": [
155+
"Ctrl-Alt-Shift-F"
156+
],
154157
"cmd.findNext": [
155158
{
156159
"key": "F3"

src/brackets.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ define(function (require, exports, module) {
124124
require("editor/EditorCommandHandlers");
125125
require("editor/EditorOptionHandlers");
126126
require("help/HelpCommandHandlers");
127-
require("search/FindInFiles");
127+
require("search/FindInFilesUI");
128128
require("search/FindReplace");
129129
require("extensibility/InstallExtensionDialog");
130130
require("extensibility/ExtensionManagerDialog");
@@ -164,16 +164,20 @@ define(function (require, exports, module) {
164164
Dialogs : Dialogs,
165165
DocumentCommandHandlers : DocumentCommandHandlers,
166166
DocumentManager : DocumentManager,
167+
DocumentModule : require("document/Document"),
167168
DOMAgent : require("LiveDevelopment/Agents/DOMAgent"),
168169
DragAndDrop : DragAndDrop,
169170
EditorManager : EditorManager,
170171
ExtensionLoader : ExtensionLoader,
171172
ExtensionUtils : ExtensionUtils,
173+
File : require("filesystem/File"),
172174
FileFilters : require("search/FileFilters"),
173175
FileSyncManager : FileSyncManager,
174176
FileSystem : FileSystem,
175177
FileViewController : FileViewController,
178+
FileUtils : require("file/FileUtils"),
176179
FindInFiles : require("search/FindInFiles"),
180+
FindInFilesUI : require("search/FindInFilesUI"),
177181
HTMLInstrumentation : require("language/HTMLInstrumentation"),
178182
Inspector : require("LiveDevelopment/Inspector/Inspector"),
179183
InstallExtensionDialog : require("extensibility/InstallExtensionDialog"),

src/command/Commands.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,18 @@ define(function (require, exports, module) {
107107

108108
// FIND
109109
exports.CMD_FIND = "cmd.find"; // FindReplace.js _launchFind()
110-
exports.CMD_FIND_IN_FILES = "cmd.findInFiles"; // FindInFiles.js _doFindInFiles()
111-
exports.CMD_FIND_IN_SELECTED = "cmd.findInSelected"; // FindInFiles.js _doFindInSubtree()
112-
exports.CMD_FIND_IN_SUBTREE = "cmd.findInSubtree"; // FindInFiles.js _doFindInSubtree()
110+
exports.CMD_FIND_IN_FILES = "cmd.findInFiles"; // FindInFilesUI.js _showFindBar()
111+
exports.CMD_FIND_IN_SELECTED = "cmd.findInSelected"; // FindInFilesUI.js _showFindBarForSubtree()
112+
exports.CMD_FIND_IN_SUBTREE = "cmd.findInSubtree"; // FindInFilesUI.js _showFindBarForSubtree()
113113
exports.CMD_FIND_NEXT = "cmd.findNext"; // FindReplace.js _findNext()
114114
exports.CMD_FIND_PREVIOUS = "cmd.findPrevious"; // FindReplace.js _findPrevious()
115115
exports.CMD_FIND_ALL_AND_SELECT = "cmd.findAllAndSelect"; // FindReplace.js _findAllAndSelect()
116116
exports.CMD_ADD_NEXT_MATCH = "cmd.addNextMatch"; // FindReplace.js _expandAndAddNextToSelection()
117117
exports.CMD_SKIP_CURRENT_MATCH = "cmd.skipCurrentMatch"; // FindReplace.js _skipCurrentMatch()
118118
exports.CMD_REPLACE = "cmd.replace"; // FindReplace.js _replace()
119+
exports.CMD_REPLACE_IN_FILES = "cmd.replaceInFiles"; // FindInFilesUI.js _showReplaceBar()
120+
exports.CMD_REPLACE_IN_SELECTED = "cmd.replaceInSelected"; // FindInFilesUI.js _showReplaceBarForSubtree()
121+
exports.CMD_REPLACE_IN_SUBTREE = "cmd.replaceInSubtree"; // FindInFilesUI.js _showReplaceBarForSubtree()
119122

120123
// VIEW
121124
exports.VIEW_HIDE_SIDEBAR = "view.hideSidebar"; // SidebarView.js toggle()

src/command/DefaultMenus.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ define(function (require, exports, module) {
111111
menu.addMenuItem(Commands.CMD_FIND_IN_SELECTED);
112112
menu.addMenuDivider();
113113
menu.addMenuItem(Commands.CMD_REPLACE);
114+
menu.addMenuItem(Commands.CMD_REPLACE_IN_FILES);
115+
menu.addMenuItem(Commands.CMD_REPLACE_IN_SELECTED);
114116

115117
/*
116118
* View menu
@@ -209,6 +211,7 @@ define(function (require, exports, module) {
209211
project_cmenu.addMenuItem(Commands.NAVIGATE_SHOW_IN_OS);
210212
project_cmenu.addMenuDivider();
211213
project_cmenu.addMenuItem(Commands.CMD_FIND_IN_SUBTREE);
214+
project_cmenu.addMenuItem(Commands.CMD_REPLACE_IN_SUBTREE);
212215
project_cmenu.addMenuDivider();
213216
project_cmenu.addMenuItem(Commands.FILE_REFRESH);
214217

@@ -220,6 +223,7 @@ define(function (require, exports, module) {
220223
working_set_cmenu.addMenuItem(Commands.NAVIGATE_SHOW_IN_OS);
221224
working_set_cmenu.addMenuDivider();
222225
working_set_cmenu.addMenuItem(Commands.CMD_FIND_IN_SUBTREE);
226+
working_set_cmenu.addMenuItem(Commands.CMD_REPLACE_IN_SUBTREE);
223227
working_set_cmenu.addMenuDivider();
224228
working_set_cmenu.addMenuItem(Commands.FILE_CLOSE);
225229

src/document/Document.js

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ define(function (require, exports, module) {
7878

7979
this.file = file;
8080
this._updateLanguage();
81-
this.refreshText(rawText, initialTimestamp);
81+
this.refreshText(rawText, initialTimestamp, true);
8282
}
8383

8484
/**
@@ -275,14 +275,27 @@ define(function (require, exports, module) {
275275
// _handleEditorChange() triggers "change" event
276276
};
277277

278+
/**
279+
* @private
280+
* Triggers the appropriate events when a change occurs: "change" on the Document instance
281+
* and "documentChange" on the Document module.
282+
* @param {Object} changeList Changelist in CodeMirror format
283+
*/
284+
Document.prototype._notifyDocumentChange = function (changeList) {
285+
$(this).triggerHandler("change", [this, changeList]);
286+
$(exports).triggerHandler("documentChange", [this, changeList]);
287+
};
288+
278289
/**
279290
* Sets the contents of the document. Treated as reloading the document from disk: the document
280291
* will be marked clean with a new timestamp, the undo/redo history is cleared, and we re-check
281292
* the text's line-ending style. CAN be called even if there is no backing editor.
282293
* @param {!string} text The text to replace the contents of the document with.
283294
* @param {!Date} newTimestamp Timestamp of file at the time we read its new contents from disk.
295+
* @param {boolean} initial True if this is the initial load of the document. In that case,
296+
* we don't send change events.
284297
*/
285-
Document.prototype.refreshText = function (text, newTimestamp) {
298+
Document.prototype.refreshText = function (text, newTimestamp, initial) {
286299
var perfTimerName = PerfUtils.markStart("refreshText:\t" + (!this.file || this.file.fullPath));
287300

288301
// If clean, don't transiently mark dirty during refresh
@@ -294,12 +307,15 @@ define(function (require, exports, module) {
294307
// _handleEditorChange() triggers "change" event for us
295308
} else {
296309
this._text = text;
297-
// We fake a change record here that looks like CodeMirror's text change records, but
298-
// omits "from" and "to", by which we mean the entire text has changed.
299-
// TODO: Dumb to split it here just to join it again in the change handler, but this is
300-
// the CodeMirror change format. Should we document our change format to allow this to
301-
// either be an array of lines or a single string?
302-
$(this).triggerHandler("change", [this, [{text: text.split(/\r?\n/)}]]);
310+
311+
if (!initial) {
312+
// We fake a change record here that looks like CodeMirror's text change records, but
313+
// omits "from" and "to", by which we mean the entire text has changed.
314+
// TODO: Dumb to split it here just to join it again in the change handler, but this is
315+
// the CodeMirror change format. Should we document our change format to allow this to
316+
// either be an array of lines or a single string?
317+
this._notifyDocumentChange([{text: text.split(/\r?\n/)}]);
318+
}
303319
}
304320
this._updateTimestamp(newTimestamp);
305321

@@ -391,6 +407,9 @@ define(function (require, exports, module) {
391407
* @private
392408
*/
393409
Document.prototype._handleEditorChange = function (event, editor, changeList) {
410+
// TODO: This needs to be kept in sync with SpecRunnerUtils.createMockActiveDocument(). In the
411+
// future, we should fix things so that we either don't need mock documents or that this
412+
// is factored so it will just run in both.
394413
if (!this._refreshInProgress) {
395414
// Sync isDirty from CodeMirror state
396415
var wasDirty = this.isDirty;
@@ -403,11 +422,7 @@ define(function (require, exports, module) {
403422
}
404423

405424
// Notify that Document's text has changed
406-
// TODO: This needs to be kept in sync with SpecRunnerUtils.createMockDocument(). In the
407-
// future, we should fix things so that we either don't need mock documents or that this
408-
// is factored so it will just run in both.
409-
$(this).triggerHandler("change", [this, changeList]);
410-
$(exports).triggerHandler("documentChange", [this, changeList]);
425+
this._notifyDocumentChange(changeList);
411426
};
412427

413428
/**

src/document/DocumentCommandHandlers.js

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -701,10 +701,13 @@ define(function (require, exports, module) {
701701
* Reverts the Document to the current contents of its file on disk. Discards any unsaved changes
702702
* in the Document.
703703
* @param {Document} doc
704-
* @return {$.Promise} a Promise that's resolved when done, or rejected with a FileSystemError if the
705-
* file cannot be read (after showing an error dialog to the user).
704+
* @param {boolean=} suppressError If true, then a failure to read the file will be ignored and the
705+
* resulting promise will be resolved rather than rejected.
706+
* @return {$.Promise} a Promise that's resolved when done, or (if suppressError is false)
707+
* rejected with a FileSystemError if the file cannot be read (after showing an error
708+
* dialog to the user).
706709
*/
707-
function doRevert(doc) {
710+
function doRevert(doc, suppressError) {
708711
var result = new $.Deferred();
709712

710713
FileUtils.readAsText(doc.file)
@@ -713,10 +716,14 @@ define(function (require, exports, module) {
713716
result.resolve();
714717
})
715718
.fail(function (error) {
716-
FileUtils.showFileOpenError(error, doc.file.fullPath)
717-
.done(function () {
718-
result.reject(error);
719-
});
719+
if (suppressError) {
720+
result.resolve();
721+
} else {
722+
FileUtils.showFileOpenError(error, doc.file.fullPath)
723+
.done(function () {
724+
result.reject(error);
725+
});
726+
}
720727
});
721728

722729
return result.promise();
@@ -1105,13 +1112,19 @@ define(function (require, exports, module) {
11051112
// copy of whatever's on disk.
11061113
doClose(file);
11071114

1108-
// Only reload from disk if we've executed the Close for real,
1109-
// *and* if at least one other view still exists
1110-
if (!promptOnly && DocumentManager.getOpenDocumentForPath(file.fullPath)) {
1111-
doRevert(doc)
1112-
.then(result.resolve, result.reject);
1113-
} else {
1115+
// Only reload from disk if we've executed the Close for real.
1116+
if (promptOnly) {
11141117
result.resolve();
1118+
} else {
1119+
// Even if there are no listeners attached to the document at this point, we want
1120+
// to do the revert anyway, because clients who are listening to the global documentChange
1121+
// event from the Document module (rather than attaching to the document directly),
1122+
// such as the Find in Files panel, should get a change event. However, in that case,
1123+
// we want to ignore errors during the revert, since we don't want a failed revert
1124+
// to throw a dialog if the document isn't actually open in the UI.
1125+
var suppressError = !DocumentManager.getOpenDocumentForPath(file.fullPath);
1126+
doRevert(doc, suppressError)
1127+
.then(result.resolve, result.reject);
11151128
}
11161129
}
11171130
});
@@ -1131,8 +1144,9 @@ define(function (require, exports, module) {
11311144
* @param {!Array.<FileEntry>} list
11321145
* @param {boolean} promptOnly
11331146
* @param {boolean} clearCurrentDoc
1147+
* @param {boolean} _forceClose Whether to force all the documents to close even if they have unsaved changes. For unit testing only.
11341148
*/
1135-
function _closeList(list, promptOnly, clearCurrentDoc) {
1149+
function _closeList(list, promptOnly, clearCurrentDoc, _forceClose) {
11361150
var result = new $.Deferred(),
11371151
unsavedDocs = [];
11381152

@@ -1143,8 +1157,8 @@ define(function (require, exports, module) {
11431157
}
11441158
});
11451159

1146-
if (unsavedDocs.length === 0) {
1147-
// No unsaved changes, so we can proceed without a prompt
1160+
if (unsavedDocs.length === 0 || _forceClose) {
1161+
// No unsaved changes or we want to ignore them, so we can proceed without a prompt
11481162
result.resolve();
11491163

11501164
} else if (unsavedDocs.length === 1) {
@@ -1160,17 +1174,7 @@ define(function (require, exports, module) {
11601174

11611175
} else {
11621176
// Multiple unsaved files: show a single bulk prompt listing all files
1163-
var message = Strings.SAVE_CLOSE_MULTI_MESSAGE;
1164-
1165-
message += "<ul class='dialog-list'>";
1166-
unsavedDocs.forEach(function (doc) {
1167-
var fullPath = doc.file.fullPath;
1168-
1169-
message += "<li><span class='dialog-filename'>";
1170-
message += StringUtils.breakableUrl(_shortTitleForDocument(doc));
1171-
message += "</span></li>";
1172-
});
1173-
message += "</ul>";
1177+
var message = Strings.SAVE_CLOSE_MULTI_MESSAGE + FileUtils.makeDialogFileList(_.map(unsavedDocs, _shortTitleForDocument));
11741178

11751179
Dialogs.showModalDialog(
11761180
DefaultDialogs.DIALOG_ID_SAVE_CLOSE,
@@ -1228,14 +1232,17 @@ define(function (require, exports, module) {
12281232
/**
12291233
* Closes all open documents; equivalent to calling handleFileClose() for each document, except
12301234
* that unsaved changes are confirmed once, in bulk.
1231-
* @param {?{promptOnly: boolean}} If true, only displays the relevant confirmation UI and does NOT
1235+
* @param {?{promptOnly: boolean, _forceClose: boolean}}
1236+
* If promptOnly is true, only displays the relevant confirmation UI and does NOT
12321237
* actually close any documents. This is useful when chaining close-all together with
12331238
* other user prompts that may be cancelable.
1239+
* If _forceClose is true, forces the files to close with no confirmation even if dirty.
1240+
* Should only be used for unit test cleanup.
12341241
* @return {$.Promise} a promise that is resolved when all files are closed
12351242
*/
12361243
function handleFileCloseAll(commandData) {
12371244
return _closeList(DocumentManager.getWorkingSet(),
1238-
(commandData && commandData.promptOnly), true).done(function () {
1245+
(commandData && commandData.promptOnly), true, (commandData && commandData._forceClose)).done(function () {
12391246
if (!DocumentManager.getCurrentDocument()) {
12401247
EditorManager._closeCustomViewer();
12411248
}

src/document/DocumentManager.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -735,23 +735,31 @@ define(function (require, exports, module) {
735735
* Differs from plain FileUtils.readAsText() in two ways: (a) line endings are still normalized
736736
* as in Document.getText(); (b) unsaved changes are returned if there are any.
737737
*
738-
* @param {!File} file
739-
* @return {!string}
738+
* @param {!File} file The file to get the text for.
739+
* @param {boolean=} checkLineEndings Whether to return line ending information. Default false (slightly more efficient).
740+
* @return {$.Promise}
741+
* A promise that is resolved with three parameters:
742+
* contents - string: the document's text
743+
* 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)
744+
* lineEndings - string: the original line endings of the file, one of the FileUtils.LINE_ENDINGS_* constants;
745+
* will be null if checkLineEndings was false.
746+
* or rejected with a filesystem error.
740747
*/
741-
function getDocumentText(file) {
748+
function getDocumentText(file, checkLineEndings) {
742749
var result = new $.Deferred(),
743750
doc = getOpenDocumentForPath(file.fullPath);
744751
if (doc) {
745-
result.resolve(doc.getText());
752+
result.resolve(doc.getText(), doc.diskTimestamp, checkLineEndings ? doc._lineEndings : null);
746753
} else {
747-
file.read(function (err, contents) {
754+
file.read(function (err, contents, stat) {
748755
if (err) {
749756
result.reject(err);
750757
} else {
751758
// Normalize line endings the same way Document would, but don't actually
752759
// new up a Document (which entails a bunch of object churn).
760+
var originalLineEndings = checkLineEndings ? FileUtils.sniffLineEndings(contents) : null;
753761
contents = DocumentModule.Document.normalizeText(contents);
754-
result.resolve(contents);
762+
result.resolve(contents, stat.mtime, originalLineEndings);
755763
}
756764
});
757765
}

0 commit comments

Comments
 (0)