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

Commit 04b8718

Browse files
committed
Fix several issues raised in #7149:
* When search scope is a single file, skip exclusion filter entirely * If exclusions filter results in 0 files to search, show a message to that effect and leave search bar open so user can adjust filter * When editing a filter, show how many files are still included out of the total number of files in the current search scope Also cleans up FindInFiles to centralize the filtering code more, and simplify _doSearchInOneFile() & its call sites.
1 parent 3286e7e commit 04b8718

4 files changed

Lines changed: 206 additions & 118 deletions

File tree

src/nls/root/strings.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ define({
160160
"FIND_IN_FILES_TITLE_PART3" : "— {0} {1} {2} in {3} {4}",
161161
"FIND_IN_FILES_SCOPED" : "in <span class='dialog-filename'>{0}</span>",
162162
"FIND_IN_FILES_NO_SCOPE" : "in project",
163+
"FIND_IN_FILES_ZERO_FILES" : "Filter excludes all files {0}",
163164
"FIND_IN_FILES_FILE" : "file",
164165
"FIND_IN_FILES_FILES" : "files",
165166
"FIND_IN_FILES_MATCH" : "match",
@@ -175,9 +176,12 @@ define({
175176
"NO_FILE_FILTER" : "Exclude files\u2026",
176177
"EDIT_FILE_FILTER" : "Edit\u2026",
177178
"FILE_FILTER_DIALOG" : "Edit Filter",
178-
"FILE_FILTER_INSTRUCTIONS" : "Exclude files and folders matching any of the following strings / substrings or <a href='{0}' title='{0}'>globs</a>. Enter each string on a new line.",
179+
"FILE_FILTER_INSTRUCTIONS" : "Exclude files and folders matching any of the following strings / substrings or <a href='{0}' title='{0}'>wildcards</a>. Enter each string on a new line.",
179180
"FILE_FILTER_LIST_PREFIX" : "except",
180181
"FILE_FILTER_CLIPPED_SUFFIX" : "and {0} more",
182+
"FILTER_COUNTING_FILES" : "Counting files\u2026",
183+
"FILTER_FILE_COUNT" : "Allows {0} of {1} files {2}",
184+
"FILTER_FILE_COUNT_ALL" : "Allows all {0} files {1}",
181185

182186
// Quick Edit
183187
"ERROR_QUICK_EDIT_PROVIDER_NOT_FOUND" : "No Quick Edit provider found for current cursor position",

src/search/FileFilters.js

Lines changed: 107 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ define(function (require, exports, module) {
3232
"use strict";
3333

3434
var _ = require("thirdparty/lodash"),
35+
ProjectManager = require("project/ProjectManager"),
3536
DefaultDialogs = require("widgets/DefaultDialogs"),
3637
Dialogs = require("widgets/Dialogs"),
3738
DropdownButton = require("widgets/DropdownButton").DropdownButton,
@@ -59,44 +60,6 @@ define(function (require, exports, module) {
5960
}
6061

6162

62-
/**
63-
* Opens a dialog box to edit the given filter. When editing is finished, the value of getLastFilter() changes to
64-
* reflect the edits. If the dialog was canceled, the preference is left unchanged.
65-
* @param {!Array.<string>} filter
66-
* @return {!$.Promise} Dialog box promise
67-
*/
68-
function editFilter(filter) {
69-
var lastFocus = window.document.activeElement;
70-
71-
var html = StringUtils.format(Strings.FILE_FILTER_INSTRUCTIONS, brackets.config.glob_help_url) +
72-
"<textarea class='exclusions-editor'></textarea>";
73-
var buttons = [
74-
{ className : Dialogs.DIALOG_BTN_CLASS_NORMAL, id: Dialogs.DIALOG_BTN_CANCEL, text: Strings.CANCEL },
75-
{ className : Dialogs.DIALOG_BTN_CLASS_PRIMARY, id: Dialogs.DIALOG_BTN_OK, text: Strings.OK }
76-
];
77-
var dialog = Dialogs.showModalDialog(DefaultDialogs.DIALOG_ID_INFO, Strings.FILE_FILTER_DIALOG, html, buttons);
78-
79-
dialog.getElement().find(".exclusions-editor").val(filter.join("\n")).focus();
80-
81-
dialog.done(function (buttonId) {
82-
if (buttonId === Dialogs.DIALOG_BTN_OK) {
83-
var newFilter = dialog.getElement().find(".exclusions-editor").val().split("\n");
84-
85-
// Remove blank lines
86-
newFilter = newFilter.filter(function (glob) {
87-
return glob.trim().length;
88-
});
89-
90-
// Update saved filter preference
91-
setLastFilter(newFilter);
92-
}
93-
lastFocus.focus(); // restore focus to old pos
94-
});
95-
96-
return dialog.getPromise();
97-
}
98-
99-
10063
/**
10164
* Converts a user-specified filter object (as chosen in picker or retrieved from getFilters()) to a 'compiled' form
10265
* that can be used with filterPath()/filterFileList().
@@ -153,6 +116,109 @@ define(function (require, exports, module) {
153116
}
154117

155118

119+
/**
120+
* Returns false if the given path matches any of the exclusion globs in the given filter. Returns true
121+
* if the path does not match any of the globs. If filtering many paths at once, use filterFileList()
122+
* for much better performance.
123+
*
124+
* @param {!string} compiledFilter 'Compiled' filter object as returned by compile()
125+
* @param {!string} fullPath
126+
* @return {boolean}
127+
*/
128+
function filterPath(compiledFilter, fullPath) {
129+
if (!compiledFilter) {
130+
return true;
131+
}
132+
133+
var re = new RegExp(compiledFilter);
134+
return !fullPath.match(re);
135+
}
136+
137+
/**
138+
* Returns a copy of 'files' filtered to just those that don't match any of the exclusion globs in the filter.
139+
*
140+
* @param {!string} compiledFilter 'Compiled' filter object as returned by compile()
141+
* @param {!Array.<File>} files
142+
* @return {!Array.<File>}
143+
*/
144+
function filterFileList(compiledFilter, files) {
145+
if (!compiledFilter) {
146+
return files;
147+
}
148+
149+
var re = new RegExp(compiledFilter);
150+
return files.filter(function (f) {
151+
return !f.fullPath.match(re);
152+
});
153+
}
154+
155+
156+
/**
157+
* Opens a dialog box to edit the given filter. When editing is finished, the value of getLastFilter() changes to
158+
* reflect the edits. If the dialog was canceled, the preference is left unchanged.
159+
* @param {!Array.<string>} filter
160+
* @param {?{label:string, promise:$.Promise}} context Info on which files the filter will be applied to. If specified,
161+
* editing UI will indicate how many files are excluded by the filter. Label should be of the form "in ..."
162+
* @return {!$.Promise} Dialog box promise
163+
*/
164+
function editFilter(filter, context) {
165+
var lastFocus = window.document.activeElement;
166+
167+
var html = StringUtils.format(Strings.FILE_FILTER_INSTRUCTIONS, brackets.config.glob_help_url) +
168+
"<textarea class='exclusions-editor'></textarea><div class='exclusions-filecount'>" + Strings.FILTER_COUNTING_FILES + "</div>";
169+
var buttons = [
170+
{ className : Dialogs.DIALOG_BTN_CLASS_NORMAL, id: Dialogs.DIALOG_BTN_CANCEL, text: Strings.CANCEL },
171+
{ className : Dialogs.DIALOG_BTN_CLASS_PRIMARY, id: Dialogs.DIALOG_BTN_OK, text: Strings.OK }
172+
];
173+
var dialog = Dialogs.showModalDialog(DefaultDialogs.DIALOG_ID_INFO, Strings.FILE_FILTER_DIALOG, html, buttons);
174+
175+
var $editField = dialog.getElement().find(".exclusions-editor");
176+
$editField.val(filter.join("\n")).focus();
177+
178+
function getValue() {
179+
var newFilter = $editField.val().split("\n");
180+
181+
// Remove blank lines
182+
return newFilter.filter(function (glob) {
183+
return glob.trim().length;
184+
});
185+
}
186+
187+
dialog.done(function (buttonId) {
188+
if (buttonId === Dialogs.DIALOG_BTN_OK) {
189+
// Update saved filter preference
190+
setLastFilter(getValue());
191+
}
192+
lastFocus.focus(); // restore focus to old pos
193+
});
194+
195+
196+
// Code to update the file count readout at bottom of dialog (if context provided)
197+
var $fileCount = dialog.getElement().find(".exclusions-filecount");
198+
199+
function updateFileCount() {
200+
context.promise.done(function (files) {
201+
var filter = getValue();
202+
if (filter.length) {
203+
var filtered = filterFileList(compile(getValue()), files);
204+
$fileCount.html(StringUtils.format(Strings.FILTER_FILE_COUNT, filtered.length, files.length, context.label));
205+
} else {
206+
$fileCount.html(StringUtils.format(Strings.FILTER_FILE_COUNT_ALL, files.length, context.label));
207+
}
208+
});
209+
}
210+
211+
if (context) {
212+
$editField.on("input", _.debounce(updateFileCount, 400));
213+
updateFileCount();
214+
} else {
215+
$fileCount.hide();
216+
}
217+
218+
return dialog.getPromise();
219+
}
220+
221+
156222
/**
157223
* Marks the filter picker's currently selected item as most-recently used, and returns the corresponding
158224
* 'compiled' filter object ready for use with filterPath().
@@ -170,9 +236,10 @@ define(function (require, exports, module) {
170236
* client should call commitDropdown() when the UI containing the filter picker is confirmed (which updates the MRU
171237
* order) and then use the returned filter object as needed.
172238
*
239+
* @param {?{label:string, promise:$.Promise}} context Info on files filter will apply to - see editFilter()
173240
* @return {!jQueryObject} Picker UI. To retrieve the selected value, use commitPicker().
174241
*/
175-
function createFilterPicker() {
242+
function createFilterPicker(contextPromise) {
176243
var $picker = $("<div class='filter-picker'><span class='filter-label'></span><button class='btn no-focus'></button></div>"),
177244
$button = $picker.find("button");
178245

@@ -208,7 +275,7 @@ define(function (require, exports, module) {
208275
updatePicker();
209276

210277
$button.click(function () {
211-
editFilter(getLastFilter())
278+
editFilter(getLastFilter(), contextPromise)
212279
.done(function (buttonId) {
213280
if (buttonId === Dialogs.DIALOG_BTN_OK) {
214281
updatePicker();
@@ -220,43 +287,6 @@ define(function (require, exports, module) {
220287
}
221288

222289

223-
/**
224-
* Returns false if the given path matches any of the exclusion globs in the given filter. Returns true
225-
* if the path does not match any of the globs. If filtering many paths at once, use filterFileList()
226-
* for much better performance.
227-
*
228-
* @param {!string} compiledFilter 'Compiled' filter object as returned by compile()
229-
* @param {!string} fullPath
230-
* @return {boolean}
231-
*/
232-
function filterPath(compiledFilter, fullPath) {
233-
if (!compiledFilter) {
234-
return true;
235-
}
236-
237-
var re = new RegExp(compiledFilter);
238-
return !fullPath.match(re);
239-
}
240-
241-
/**
242-
* Returns a copy of 'files' filtered to just those that don't match any of the exclusion globs in the filter.
243-
*
244-
* @param {!string} compiledFilter 'Compiled' filter object as returned by compile()
245-
* @param {!Array.<File>} files
246-
* @return {!Array.<File>}
247-
*/
248-
function filterFileList(compiledFilter, files) {
249-
if (!compiledFilter) {
250-
return files;
251-
}
252-
253-
var re = new RegExp(compiledFilter);
254-
return files.filter(function (f) {
255-
return !f.fullPath.match(re);
256-
});
257-
}
258-
259-
260290
exports.createFilterPicker = createFilterPicker;
261291
exports.commitPicker = commitPicker;
262292
exports.getLastFilter = getLastFilter;

0 commit comments

Comments
 (0)