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

[replace-across-files] Replace in Files #7809

Merged
merged 56 commits into from
Jun 13, 2014
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
2ca021e
Refactors the search results to be able to use paging in Replace All
TomMalbran Mar 3, 2014
b60ba25
Make regexp searches multiline in FindInFiles to match single-file be…
njx Apr 30, 2014
eaa4d38
Merge with Master
TomMalbran May 2, 2014
32ea44f
Several fixes and stuff
TomMalbran May 2, 2014
5971e7e
Implement basic textual find/replace in files
njx Apr 30, 2014
d70b40f
Don't replace on disk if the file has changed since we searched it
njx May 6, 2014
b5aa82d
Add test for gracefully handling write failure
njx May 6, 2014
ad367bf
Do replacements in memory for files that are already open
njx May 6, 2014
094a75d
Add way to force replacements to be done in memory instead of on disk
njx May 6, 2014
0dd3eca
Handle regexp replacement
njx May 7, 2014
b170527
Start hooking up Replace in Files UI
njx May 7, 2014
3e113db
Tweak Find Bar to only show Replace All... and relayout in replace-ac…
njx May 7, 2014
51dda43
Add logic to force in-memory replacements for 10 or fewer files, and …
njx May 8, 2014
8ad4acd
Add simple unit test for Replace All checkboxes
njx May 8, 2014
ba02433
Add unit tests for paging Find in Files results
njx May 8, 2014
cda467a
Merge from tom/search-results
njx May 8, 2014
660fddb
Use batchOperation() instead of edit op merging for replace all
njx May 8, 2014
f804242
Integrate results panel with Replace in Files
njx May 9, 2014
c2826a1
Remove search panel templates that are no longer used
njx May 9, 2014
79a873a
Improve Enter key behavior in Replace in Files
njx May 9, 2014
39a9bed
Tweak labels for batch replace buttons in both single and multi-file …
njx May 9, 2014
8e0e518
Tweak layout of Find bar for find/replace across files
njx May 9, 2014
bb6ab8a
Disable Replace button when query is blank or invalid
njx May 9, 2014
f820a12
Automatically open a changed file if current editor hasn't been chang…
njx May 9, 2014
d571a6f
Use original line endings when comparing in-memory doc to known goods…
njx May 9, 2014
c33add6
Bump up in-memory replace limit to 20. Tweak dialog to show the limit.
njx May 10, 2014
aeca946
Ignore unchecked matches when computing number of files to replace in…
njx May 10, 2014
6141347
Beginning of refactoring to unify view code:
njx May 10, 2014
ab95c67
Rename resultsModel to searchModel since it has other stuff besides r…
njx May 10, 2014
59e39ab
Clean up some global state in FindInFiles that can just use the searc…
njx May 10, 2014
7a9af00
Just reverse the matches instead of sorting them in reverse since the…
njx May 10, 2014
28207e2
Fix summary display
njx May 10, 2014
5b5d760
Remove unused var
njx May 10, 2014
72b7121
Make single-file Replace All just use Find in Files. Plus fixes:
njx May 12, 2014
4a4c9ca
Move the rest of the query-related globals into SearchModel
njx May 12, 2014
abfd249
Refactor all UI code out of FindInFiles - this is now a pure engine c…
njx May 13, 2014
8c0c59b
Merge branch 'nj/unify-find-ui' into nj/replace-on-disk
njx May 13, 2014
ced86cf
Re-add error log statement that got dropped
njx May 13, 2014
5e6a35d
Cleanup and bugfixes for Replace in Files:
njx May 14, 2014
ea51cc3
Make limit on found results much larger, and apply it to total number…
njx May 14, 2014
ffc5ffb
Move FindInFiles tests into a separate file
njx May 14, 2014
37ac1b0
Remove unused parameter
njx May 14, 2014
c9e4b56
Add error dialog if problems occur during replace. Add a few more uni…
njx May 14, 2014
770a389
Added tests for panel closure. Need to fix a bug that causes one of t…
njx May 15, 2014
268cfd8
Fixed bug in panel title. Simplified construction of summary area.
njx May 15, 2014
47fbda5
Tweak flaky unit test - seems to pass consistently now
njx May 15, 2014
2778827
Add unit test for find/replace in dirty document.
njx May 15, 2014
2e62bcd
More Replace in Files unit test work:
njx May 16, 2014
ac10ba2
Ensure that Find in Files panel closes when document is reverted on c…
njx May 16, 2014
6e9f6ec
Directly include FindInFilesUI instead of FindInFiles, since the form…
njx May 16, 2014
1917878
Make sure we propagate error from then()
njx May 16, 2014
6490a03
add Replace in... to context menu
redmunds Jun 3, 2014
b20b2ee
unit tests for replace in...
redmunds Jun 3, 2014
20876e4
update comments
redmunds Jun 3, 2014
737b27a
Replace in Selected File/Folder in Find menu
redmunds Jun 4, 2014
f6d3efc
Merge pull request #8029 from adobe/randy/replace-in
njx Jun 13, 2014
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
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
1 change: 1 addition & 0 deletions src/command/Commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ define(function (require, exports, module) {
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"; // FindInFiles.js _doReplaceInFiles()

// VIEW
exports.VIEW_HIDE_SIDEBAR = "view.hideSidebar"; // SidebarView.js toggle()
Expand Down
1 change: 1 addition & 0 deletions src/command/DefaultMenus.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ 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);

/*
* View menu
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) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change was to make it so we get a documentChange event from the global Document module consistently on reverts, even if there is no masterEditor (this is used by FindInFiles to know when a document has changed). However, we don't want to get such an event when the Document is first created, because that's not really a "change".

// 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]);
Copy link
Member

Choose a reason for hiding this comment

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

Since this pair of calls occurs in two places now, should be centralize it into a helper function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed. Also fixed up the case in _handleEditorChange() that was supposed to be kept in sync with SpecRunnerUtils.createMockActiveDocument(), and fixed up the latter too.

}
}
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.
Copy link
Member

Choose a reason for hiding this comment

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

Should we specify that this also suppresses the user-visible UI?

Copy link
Member

Choose a reason for hiding this comment

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

Actually, it seems like that's the only part of the change that's actually required -- we could have it still headlessly reject the Promise. Maybe that would be more generally useful?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the case where we pass true for this, we really don't want to hear about the rejection anyway, because we're just closing the file. So we would have to convert the reject into a resolve in that case anyway, complicating the logic in the caller.

* @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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change is also part of making sure that we get documentChange events for reverts.

// 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) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The changes here are just a convenience for unit tests (similar to the _forceClose argument to single-file FILE_CLOSE).

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));
Copy link
Member

Choose a reason for hiding this comment

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

StringUtils seems like an odd place for something relatively specific like this. FileUtils is where other file IO error dialog stuff lives... or it could become a Mustache template, I guess...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FileUtils doesn't really seem right, since those are really about file operations. I'll create a DialogUtils module to put it in.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, I see what you mean about the error dialog stuff. I can put it in FileUtils.


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 () {
Copy link
Member

Choose a reason for hiding this comment

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

Should add to JSDocs above

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Currently, this block of info about @return gets dropped from API Doc. May want to move above @param list for now.

Copy link
Member

Choose a reason for hiding this comment

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

Pretty sure we have many methods where @return comes after @param, so if that never works it seems better to fix the docs-generator bug than reorder all our comments in a nonstandard way...

Does moving the "A promise that..." part onto the same line as @return fix it? I wonder if the issue is just that the initial line is blank.

Copy link
Contributor

Choose a reason for hiding this comment

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

Has nothing to do with @return following @param -- that happens in every case I've noticed. I was referring to moving detailed description of all possible return values up to the main description -- not the @return tag.

I just noticed that the entire @return is dropped. I played around with a few combinations and couldn't get @return to show up, so not sure why.

FYI, by installing apify and then brackets-apify extension, you can quickly render a temporary version of API Docs to see what your annotations look like before submitting PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

I just remembered a bug recently fixed by @jbalsas where @return was not recognized by functions (only class methods), but it hasn't been rolled into a new node release. Sorry, I filed that bug so I should have remembered it :(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK - so sounds like I don't need to change this then?

Copy link
Contributor

Choose a reason for hiding this comment

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

@njx apify has been updated so that @return is recognized on Functions, so the content is displayed in API Docs. But markdown formatting is not (yet) supported on @return, so all of the text formatting is lost, so it's just a paragraph of text.

I opened an issue: jbalsas/apify#69

*/
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;
Copy link
Member

Choose a reason for hiding this comment

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

This stuff seems use-case-specific enough that I wonder if the Replace code should just call File.read() directly, instead of adding complexity here that arguably no other callers will ever use. (It can still get at the Document.normalizeText() public API if that part is needed).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We want to get the document text if it's already open, though. I don't want to duplicate all the functionality in this method. Plus, I imagine that we could easily have other batch-processing functionality in the future that wants to do something similar across files, where it wants the document text if it's in memory but on-disk text with line ending information if it's on disk.

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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: This Param doesn't exists and the other 2 have the wrong names.

Should it explain better how it compares the paths since is not as simple and comparing just the strings? It splits the paths into the directories and file, and it then compares the nth-part of each path. If those are the same it passes to the next one, if one is a directory, that one goes first. If both are directories, it compares the names. And if both are filenames it uses the compareFilenames function. The result should be the same as how the project tree (when fully open) is sorted in Windows.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

* @return {number} The result of the local compare function
*/
function comparePaths(path1, path2) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was hoisted out of the FindInFiles code, since it seemed generally useful (not specific to FindInFiles) and at one point I thought I needed it in multiple places. (Not sure if that's still true, but it seems like a good thing to have available.)

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;
});
47 changes: 27 additions & 20 deletions src/htmlContent/findreplace-bar.html
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
<div id="find-group"><!--
--><div class="search-input-container"><input type="text" id="find-what" placeholder="{{queryPlaceholder}}" value="{{initialQuery}}" /><div class="error"></div><span id="find-counter"></span></div><!--
--><button id="find-case-sensitive" class="btn no-focus" tabindex="-1" title="{{Strings.BUTTON_CASESENSITIVE_HINT}}"><div class="button-icon"></div></button><!--
--><button id="find-regexp" class="btn no-focus" tabindex="-1" title="{{Strings.BUTTON_REGEXP_HINT}}"><div class="button-icon"></div></button><!--
{{#navigator}}
--><div class="navigator"><!--
--><button id="find-prev" class="btn no-focus" tabindex="-1" title="{{Strings.BUTTON_PREV_HINT}}">{{Strings.BUTTON_PREV}}</button><!--
--><button id="find-next" class="btn no-focus" tabindex="-1" title="{{Strings.BUTTON_NEXT_HINT}}">{{Strings.BUTTON_NEXT}}</button><!--
--></div><!--
{{/navigator}}
-->
<div class="find-input-group">
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The DOM structure here changed slightly since the original unification so that I could make the scope controls display: inline-block while the replace controls were display: block in the Replace in Files case (so there's a line break before the Replace controls, but the scope controls start on the same line as the Replace controls).

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't quite like how the Replace in Files bar has usually 2 lines, but the fix I am thinking requires the multiple exclusions select.

My idea is to make the Exclusions select take the exact same space as the navigation, since Find/Replace have navigation only and Find/Replace in Files have only exclusions. The other thing to fix is the scope that can take a lots of space. I was thinking of just placing the current folder / in project text inside #find-counter and have a tooltip to show the complete path.

Copy link
Contributor

Choose a reason for hiding this comment

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

@TomMalbran We'll have Exclusions on Find/Replace soon.

I agree that the controls should all be on 1 line if there is enough horizontal width for it.

Copy link
Contributor

Choose a reason for hiding this comment

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

It is already in master, so we can apply this fix by making the exclusions sets select have always a fixed width that is the exact same width as the prev and next buttons combined. And if we then move the scope to another place, the Replace in Files bar can have the exact same width as the Replace bar which does look nice.

Copy link
Member

Choose a reason for hiding this comment

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

@TomMalbran It seems like we couldn't really shrink down the exclusion-picker dropdown to the tiny width that the next+prev buttons currently occupy, though. (And it seems like you'd have to fit the scope label in that amount of space too, which would be basically impossible even if you change it to only show the last folder name). And if we didn't shrink that set of UI way down, then the problem remains that there's not enough width to reliably fit it, thus it needs to remain wrappable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had tried that and it looks funny too. Basically, there isn't really a good solution that doesn't leave an awkward set of whitespace somewhere (without doing something more radical). For now, if we're just going to keep all the controls in the bar, it seemed better to just have the fields line up vertically. It would be nice to find a more compact solution eventually.

Copy link
Contributor

Choose a reason for hiding this comment

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

It did look good to me with that change, and I didn't got any space. But I only tried the solution in the dev tools and I changed the replace group to be inline-block so that it was placed after the exceptions button. I also changed the exceptions text + edit button with the new button, since this branch doesn't have the new exclusions sets.

I don't like how it looks right now. Even just Find in Files looks kind of bad with the scope in between the find group and the exclusions set. Maybe the scope can later become a select button, so that you can change the scope after opening the find bar.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, I didn't try just having the scope in the second line. But having the exclusions in the top line with everything else seems like it might get crowded too.

I'll try to fiddle with it a bit if I get time, but if not, maybe you can put up a PR with your suggestion for @larz0 to review after this lands.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure. Notice that I am using the new exclusions button, since is a lot cleaner for the UI and I gave it a fixed width, since right now the width changes depending on the name of the exclusion set and it can potentially take a lot of space.

Copy link
Member

Choose a reason for hiding this comment

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

Fwiw, I also like the idea of putting the find+replace on one line and the scope+exclusions on the second line... I think it would read a little more cleanly than the layout today. Not urgent though -- something we could tweak after this all lands in master, or even in a later sprint...

<div id="find-group"><!--
--><div class="search-input-container"><input type="text" id="find-what" placeholder="{{queryPlaceholder}}" value="{{initialQuery}}" /><div class="error"></div><span id="find-counter"></span></div><!--
--><button id="find-case-sensitive" class="btn no-focus" tabindex="-1" title="{{Strings.BUTTON_CASESENSITIVE_HINT}}"><div class="button-icon"></div></button><!--
--><button id="find-regexp" class="btn no-focus" tabindex="-1" title="{{Strings.BUTTON_REGEXP_HINT}}"><div class="button-icon"></div></button><!--
{{#navigator}}
--><div class="navigator"><!--
--><button id="find-prev" class="btn no-focus" tabindex="-1" title="{{Strings.BUTTON_PREV_HINT}}">{{Strings.BUTTON_PREV}}</button><!--
--><button id="find-next" class="btn no-focus" tabindex="-1" title="{{Strings.BUTTON_NEXT_HINT}}">{{Strings.BUTTON_NEXT}}</button><!--
--></div><!--
{{/navigator}}
--></div>

{{#replace}}
<div id="replace-group" {{#scope}}class="has-scope"{{/scope}}><!--
--><input type="text" id="replace-with" placeholder="{{Strings.REPLACE_PLACEHOLDER}}"/><!--
{{^replaceAllOnly}}
--><button id="replace-yes" class="btn no-focus" tabindex="-1">{{Strings.BUTTON_REPLACE}}</button><!--
{{/replaceAllOnly}}
--><button id="replace-all" class="btn no-focus {{#replaceAllOnly}}solo{{/replaceAllOnly}}" tabindex="-1">{{replaceAllLabel}}</button></div>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

replaceAllOnly is used in the Replace in Files bar, where there's no "Replace" button, just the "Replace All" button (which appears as "Replace...").

{{/replace}}
</div>

{{#scope}}
<div class="scope-group">
<div class="no-results-message">{{Strings.FIND_NO_RESULTS}}</div>
<div class="message">
<span id="searchlabel">{{{scopeLabel}}}</span>
</div>
</div><!--

{{#replace}}
--><div id="replace-group"><!--
--><input type="text" id="replace-with" placeholder="{{Strings.REPLACE_PLACEHOLDER}}"/><!--
--><button id="replace-yes" class="btn no-focus" tabindex="-1">{{Strings.BUTTON_REPLACE}}</button><!--
--><button id="replace-all" class="btn no-focus" tabindex="-1">{{Strings.BUTTON_REPLACE_ALL}}</button><!--
--></div>
{{/replace}}
</div>
{{/scope}}
2 changes: 1 addition & 1 deletion src/htmlContent/search-panel.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div id="search-results" class="bottom-panel vert-resizable top-resizer no-focus">
<div id="{{panelID}}" class="search-results bottom-panel vert-resizable top-resizer no-focus">
<div class="toolbar simple-toolbar-layout">
<div class="title"></div>
<a href="#" class="close">&times;</a>
Expand Down
17 changes: 0 additions & 17 deletions src/htmlContent/search-replace-panel.html

This file was deleted.

Loading