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

Commit 92732a7

Browse files
committed
Merge pull request #2084 from adobe/pflynn/find-in-files-scope
Find in subset of all files
2 parents 7c6375d + a91c7bb commit 92732a7

File tree

5 files changed

+105
-38
lines changed

5 files changed

+105
-38
lines changed

src/command/Commands.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ define(function (require, exports, module) {
5858
exports.EDIT_SELECT_LINE = "edit.selectLine";
5959
exports.EDIT_FIND = "edit.find";
6060
exports.EDIT_FIND_IN_FILES = "edit.findInFiles";
61+
exports.EDIT_FIND_IN_SUBTREE = "edit.findInSubtree";
6162
exports.EDIT_FIND_NEXT = "edit.findNext";
6263
exports.EDIT_FIND_PREVIOUS = "edit.findPrevious";
6364
exports.EDIT_REPLACE = "edit.replace";

src/command/Menus.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,13 +994,17 @@ define(function (require, exports, module) {
994994
project_cmenu.addMenuItem(Commands.FILE_NEW);
995995
project_cmenu.addMenuItem(Commands.FILE_NEW_FOLDER);
996996
project_cmenu.addMenuItem(Commands.FILE_RENAME, "F2");
997+
project_cmenu.addMenuDivider();
998+
project_cmenu.addMenuItem(Commands.EDIT_FIND_IN_SUBTREE);
997999

9981000
var working_set_cmenu = registerContextMenu(ContextMenuIds.WORKING_SET_MENU);
9991001
working_set_cmenu.addMenuItem(Commands.FILE_CLOSE);
10001002
working_set_cmenu.addMenuItem(Commands.FILE_SAVE);
10011003
working_set_cmenu.addMenuItem(Commands.FILE_RENAME);
10021004
working_set_cmenu.addMenuItem(Commands.NAVIGATE_SHOW_IN_FILE_TREE);
10031005
working_set_cmenu.addMenuDivider();
1006+
working_set_cmenu.addMenuItem(Commands.EDIT_FIND_IN_SUBTREE);
1007+
working_set_cmenu.addMenuDivider();
10041008
working_set_cmenu.addMenuItem(Commands.SORT_WORKINGSET_BY_ADDED);
10051009
working_set_cmenu.addMenuItem(Commands.SORT_WORKINGSET_BY_NAME);
10061010
working_set_cmenu.addMenuItem(Commands.SORT_WORKINGSET_BY_TYPE);

src/nls/root/strings.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,16 @@ define({
107107
"NO_UPDATE_TITLE" : "You're up to date!",
108108
"NO_UPDATE_MESSAGE" : "You are running the latest version of {APP_NAME}.",
109109

110-
"FIND_IN_FILES_TITLE" : "for \"{4}\" - {0} {1} in {2} {3}",
110+
"FIND_IN_FILES_TITLE" : "for \"{4}\" {5} - {0} {1} in {2} {3}",
111+
"FIND_IN_FILES_SCOPED" : "in <span class='dialog-filename'>{0}</span>",
112+
"FIND_IN_FILES_NO_SCOPE" : "in project",
111113
"FIND_IN_FILES_FILE" : "file",
112114
"FIND_IN_FILES_FILES" : "files",
113115
"FIND_IN_FILES_MATCH" : "match",
114116
"FIND_IN_FILES_MATCHES" : "matches",
115117
"FIND_IN_FILES_MORE_THAN" : "More than ",
116118
"FIND_IN_FILES_MAX" : " (showing the first {0} matches)",
117-
"FIND_IN_FILES_FILE_PATH" : "File: <b>{0}</b>",
119+
"FIND_IN_FILES_FILE_PATH" : "File: <span class='dialog-filename'>{0}</span>",
118120
"FIND_IN_FILES_LINE" : "line:&nbsp;{0}",
119121

120122
"ERROR_FETCHING_UPDATE_INFO_TITLE" : "Error getting update info",
@@ -178,6 +180,7 @@ define({
178180
"CMD_SELECT_LINE" : "Select Line",
179181
"CMD_FIND" : "Find",
180182
"CMD_FIND_IN_FILES" : "Find in Files",
183+
"CMD_FIND_IN_SUBTREE" : "Find in...",
181184
"CMD_FIND_NEXT" : "Find Next",
182185
"CMD_FIND_PREVIOUS" : "Find Previous",
183186
"CMD_REPLACE" : "Replace",

src/project/ProjectManager.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ define(function (require, exports, module) {
269269
* If absPath lies within the project, returns a project-relative path. Else returns absPath
270270
* unmodified.
271271
* Does not support paths containing ".."
272+
* @param {!string} absPath
273+
* @return {!string}
272274
*/
273275
function makeProjectRelativeIfPossible(absPath) {
274276
if (isWithinProject(absPath)) {

src/search/FindInFiles.js

Lines changed: 93 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ define(function (require, exports, module) {
4747
Commands = require("command/Commands"),
4848
Strings = require("strings"),
4949
StringUtils = require("utils/StringUtils"),
50+
ProjectManager = require("project/ProjectManager"),
5051
DocumentManager = require("document/DocumentManager"),
5152
EditorManager = require("editor/EditorManager"),
5253
FileIndexManager = require("project/FileIndexManager"),
@@ -86,6 +87,21 @@ define(function (require, exports, module) {
8687
return new RegExp(query, "gi");
8788
}
8889

90+
/**
91+
* Returns label text to indicate the search scope. Already HTML-escaped.
92+
* @param {?Entry} scope
93+
*/
94+
function _labelForScope(scope) {
95+
var projName = ProjectManager.getProjectRoot().name;
96+
if (scope) {
97+
var displayPath = StringUtils.htmlEscape(ProjectManager.makeProjectRelativeIfPossible(scope.fullPath));
98+
return StringUtils.format(Strings.FIND_IN_FILES_SCOPED, displayPath);
99+
} else {
100+
return Strings.FIND_IN_FILES_NO_SCOPE;
101+
}
102+
}
103+
104+
89105
// This dialog class was mostly copied from QuickOpen. We should have a common dialog
90106
// class that everyone can use.
91107

@@ -126,12 +142,14 @@ define(function (require, exports, module) {
126142
/**
127143
* Shows the search dialog
128144
* @param {?string} initialString Default text to prepopulate the search field with
145+
* @param {?Entry} scope Search scope, or null to search whole proj
129146
* @returns {$.Promise} that is resolved with the string to search for
130147
*/
131-
FindInFilesDialog.prototype.showDialog = function (initialString) {
132-
var dialogHTML = Strings.CMD_FIND_IN_FILES +
133-
": <input type='text' id='findInFilesInput' style='width: 10em'> <span style='color: #888'>(" +
134-
Strings.SEARCH_REGEXP_INFO + ")</span>";
148+
FindInFilesDialog.prototype.showDialog = function (initialString, scope) {
149+
// Note the prefix label is a simple "Find:" - the "in ..." part comes after the text field
150+
var dialogHTML = Strings.CMD_FIND +
151+
": <input type='text' id='findInFilesInput' style='width: 10em'> <span id='findInFilesScope'></span> &nbsp;" +
152+
"<span style='color: #888'>(" + Strings.SEARCH_REGEXP_INFO + ")</span>";
135153
this.result = new $.Deferred();
136154
this._createDialogDiv(dialogHTML);
137155
var $searchField = $("input#findInFilesInput");
@@ -140,6 +158,8 @@ define(function (require, exports, module) {
140158
$searchField.attr("value", initialString || "");
141159
$searchField.get(0).select();
142160

161+
$("#findInFilesScope").html(_labelForScope(scope));
162+
143163
$searchField.bind("keydown", function (event) {
144164
if (event.keyCode === KeyEvent.DOM_VK_RETURN || event.keyCode === KeyEvent.DOM_VK_ESCAPE) { // Enter/Return key or Esc key
145165
event.stopPropagation();
@@ -209,7 +229,7 @@ define(function (require, exports, module) {
209229
return matches;
210230
}
211231

212-
function _showSearchResults(searchResults, query) {
232+
function _showSearchResults(searchResults, query, scope) {
213233
var $searchResultsDiv = $("#search-results");
214234

215235
if (searchResults && searchResults.length) {
@@ -229,17 +249,19 @@ define(function (require, exports, module) {
229249
}
230250
numMatchesStr += String(numMatches);
231251

252+
// This text contains some formatting, so all the strings are assumed to be already escaped
232253
var summary = StringUtils.format(
233254
Strings.FIND_IN_FILES_TITLE,
234255
numMatchesStr,
235256
(numMatches > 1) ? Strings.FIND_IN_FILES_MATCHES : Strings.FIND_IN_FILES_MATCH,
236257
searchResults.length,
237258
(searchResults.length > 1 ? Strings.FIND_IN_FILES_FILES : Strings.FIND_IN_FILES_FILE),
238-
query
259+
query,
260+
scope ? _labelForScope(scope) : ""
239261
);
240262

241263
$("#search-result-summary")
242-
.text(summary +
264+
.html(summary +
243265
(numMatches > FIND_IN_FILES_MAX ? StringUtils.format(Strings.FIND_IN_FILES_MAX, FIND_IN_FILES_MAX) : ""))
244266
.prepend("&nbsp;"); // putting a normal space before the "-" is not enough
245267

@@ -251,19 +273,16 @@ define(function (require, exports, module) {
251273
return $("<td/>").html(content);
252274
};
253275

254-
var esc = function (str) {
255-
str = str.replace(/</g, "&lt;");
256-
str = str.replace(/>/g, "&gt;");
257-
return str;
258-
};
276+
// shorthand function name
277+
var esc = StringUtils.htmlEscape;
259278

260279
var highlightMatch = function (line, start, end) {
261280
return esc(line.substr(0, start)) + "<span class='highlight'>" + esc(line.substring(start, end)) + "</span>" + esc(line.substr(end));
262281
};
263282

264283
// Add row for file name
265284
$("<tr class='file-section' />")
266-
.append("<td colspan='3'>" + StringUtils.format(Strings.FIND_IN_FILES_FILE_PATH, StringUtils.breakableUrl(item.fullPath)) + "</td>")
285+
.append("<td colspan='3'>" + StringUtils.format(Strings.FIND_IN_FILES_FILE_PATH, StringUtils.breakableUrl(esc(item.fullPath))) + "</td>")
267286
.click(function () {
268287
// Clicking file section header collapses/expands result rows for that file
269288
var $fileHeader = $(this);
@@ -313,11 +332,30 @@ define(function (require, exports, module) {
313332
EditorManager.resizeEditor();
314333
}
315334

335+
/**
336+
* @param {!FileInfo} fileInfo File in question
337+
* @param {?Entry} scope Search scope, or null if whole project
338+
* @return {boolean}
339+
*/
340+
function inScope(fileInfo, scope) {
341+
if (scope) {
342+
if (scope.isDirectory) {
343+
// Dirs always have trailing slash, so we don't have to worry about being
344+
// a substring of another dir name
345+
return fileInfo.fullPath.indexOf(scope.fullPath) === 0;
346+
} else {
347+
return fileInfo.fullPath === scope.fullPath;
348+
}
349+
}
350+
return true;
351+
}
352+
316353
/**
317354
* Displays a non-modal embedded dialog above the code mirror editor that allows the user to do
318355
* a find operation across all files in the project.
356+
* @param {?Entry} scope Project file/subfolder to search within; else searches whole project.
319357
*/
320-
function doFindInFiles() {
358+
function doFindInFiles(scope) {
321359

322360
var dialog = new FindInFilesDialog();
323361

@@ -328,7 +366,7 @@ define(function (require, exports, module) {
328366
searchResults = [];
329367
maxHitsFoundInFile = false;
330368

331-
dialog.showDialog(initialString)
369+
dialog.showDialog(initialString, scope)
332370
.done(function (query) {
333371
if (query) {
334372
var queryExpr = _getQueryRegExp(query);
@@ -341,28 +379,33 @@ define(function (require, exports, module) {
341379
Async.doInParallel(fileListResult, function (fileInfo) {
342380
var result = new $.Deferred();
343381

344-
DocumentManager.getDocumentForPath(fileInfo.fullPath)
345-
.done(function (doc) {
346-
var matches = _getSearchMatches(doc.getText(), queryExpr);
347-
348-
if (matches && matches.length) {
349-
searchResults.push({
350-
fullPath: fileInfo.fullPath,
351-
matches: matches
352-
});
353-
}
354-
result.resolve();
355-
})
356-
.fail(function (error) {
357-
// Error reading this file. This is most likely because the file isn't a text file.
358-
// Resolve here so we move on to the next file.
359-
result.resolve();
360-
});
361-
382+
if (!inScope(fileInfo, scope)) {
383+
result.resolve();
384+
} else {
385+
// Search one file
386+
DocumentManager.getDocumentForPath(fileInfo.fullPath)
387+
.done(function (doc) {
388+
var matches = _getSearchMatches(doc.getText(), queryExpr);
389+
390+
if (matches && matches.length) {
391+
searchResults.push({
392+
fullPath: fileInfo.fullPath,
393+
matches: matches
394+
});
395+
}
396+
result.resolve();
397+
})
398+
.fail(function (error) {
399+
// Error reading this file. This is most likely because the file isn't a text file.
400+
// Resolve here so we move on to the next file.
401+
result.resolve();
402+
});
403+
}
362404
return result.promise();
363405
})
364406
.done(function () {
365-
_showSearchResults(searchResults, query);
407+
// Done searching all files: show results
408+
_showSearchResults(searchResults, query, scope);
366409
StatusBar.hideBusyIndicator();
367410
})
368411
.fail(function () {
@@ -374,11 +417,23 @@ define(function (require, exports, module) {
374417
});
375418
}
376419

420+
/** Search within the file/subtree defined by the sidebar selection */
421+
function doFindInSubtree() {
422+
// Prefer project tree selection, else use working set selection
423+
var selectedEntry = ProjectManager.getSelectedItem();
424+
if (!selectedEntry) {
425+
var doc = DocumentManager.getCurrentDocument();
426+
selectedEntry = (doc && doc.file);
427+
}
428+
429+
doFindInFiles(selectedEntry);
430+
}
431+
432+
377433
// Initialize items dependent on HTML DOM
378434
AppInit.htmlReady(function () {
379435
var $searchResults = $("#search-results"),
380436
$searchContent = $("#search-results .table-container");
381-
382437
});
383438

384439
function _fileNameChangeHandler(event, oldName, newName) {
@@ -392,5 +447,7 @@ define(function (require, exports, module) {
392447
}
393448

394449
$(DocumentManager).on("fileNameChange", _fileNameChangeHandler);
395-
CommandManager.register(Strings.CMD_FIND_IN_FILES, Commands.EDIT_FIND_IN_FILES, doFindInFiles);
450+
451+
CommandManager.register(Strings.CMD_FIND_IN_FILES, Commands.EDIT_FIND_IN_FILES, doFindInFiles);
452+
CommandManager.register(Strings.CMD_FIND_IN_SUBTREE, Commands.EDIT_FIND_IN_SUBTREE, doFindInSubtree);
396453
});

0 commit comments

Comments
 (0)