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

Commit 439835c

Browse files
committed
Use templates for the search dialog and results
1 parent 35fcc66 commit 439835c

4 files changed

Lines changed: 163 additions & 102 deletions

File tree

src/htmlContent/search-dialog.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{{CMD_FIND}}:
2+
<input type="text" id="searchInput" value="{{value}}" style="width: 10em" />
3+
<div class="message">
4+
<span id="searchlabel">{{{label}}}</span>
5+
<span style="color: #888">({{SEARCH_REGEXP_INFO}})</span>
6+
</div>
7+
<div class="error"></div>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<table class="zebra-striped condensed-table">
2+
<tbody>
3+
{{#searchList}}
4+
<tr class="file-section" data-file="{{file}}">
5+
<td colspan="3"><span class="disclosure-triangle expanded"></span>{{{filename}}}</td>
6+
</tr>
7+
{{#items}}
8+
<tr data-file="{{file}}" data-item="{{item}}">
9+
<td></td>
10+
<td style="white-space: nowrap">{{line}}</td>
11+
<td>{{pre}}<span class="highlight">{{highlight}}</span>{{post}}</td>
12+
</tr>
13+
{{/items}}
14+
{{/searchList}}
15+
</tbody>
16+
</table>

src/nls/root/strings.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ define({
120120
"FIND_IN_FILES_MORE_THAN" : "More than ",
121121
"FIND_IN_FILES_MAX" : " (showing the first {0} matches)",
122122
"FIND_IN_FILES_FILE_PATH" : "File: <span class='dialog-filename'>{0}</span>",
123-
"FIND_IN_FILES_LINE" : "line:&nbsp;{0}",
123+
"FIND_IN_FILES_LINE" : "line: {0}",
124124

125125
"ERROR_FETCHING_UPDATE_INFO_TITLE" : "Error getting update info",
126126
"ERROR_FETCHING_UPDATE_INFO_MSG" : "There was a problem getting the latest update information from the server. Please make sure you are connected to the internet and try again.",

src/search/FindInFiles.js

Lines changed: 139 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
*/
2323

2424
/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
25-
/*global define, $, PathUtils, window */
25+
/*global define, $, PathUtils, Mustache, window */
2626

2727
/*
2828
* Adds a "find in files" command to allow the user to find all occurances of a string in all files in
@@ -42,20 +42,30 @@
4242
define(function (require, exports, module) {
4343
"use strict";
4444

45-
var Async = require("utils/Async"),
46-
CommandManager = require("command/CommandManager"),
47-
Commands = require("command/Commands"),
48-
Strings = require("strings"),
49-
StringUtils = require("utils/StringUtils"),
50-
ProjectManager = require("project/ProjectManager"),
51-
DocumentManager = require("document/DocumentManager"),
52-
EditorManager = require("editor/EditorManager"),
53-
FileIndexManager = require("project/FileIndexManager"),
54-
KeyEvent = require("utils/KeyEvent"),
55-
AppInit = require("utils/AppInit"),
56-
StatusBar = require("widgets/StatusBar"),
57-
ModalBar = require("widgets/ModalBar").ModalBar;
58-
45+
var Async = require("utils/Async"),
46+
CommandManager = require("command/CommandManager"),
47+
Commands = require("command/Commands"),
48+
Strings = require("strings"),
49+
StringUtils = require("utils/StringUtils"),
50+
ProjectManager = require("project/ProjectManager"),
51+
DocumentManager = require("document/DocumentManager"),
52+
EditorManager = require("editor/EditorManager"),
53+
FileIndexManager = require("project/FileIndexManager"),
54+
KeyEvent = require("utils/KeyEvent"),
55+
AppInit = require("utils/AppInit"),
56+
StatusBar = require("widgets/StatusBar"),
57+
ModalBar = require("widgets/ModalBar").ModalBar,
58+
SearchDialogTemplate = require("text!htmlContent/search-dialog.html"),
59+
SearchResultsTemplate = require("text!htmlContent/search-results.html");
60+
61+
62+
/** @type {$.Element} jQuery elements used in the search results */
63+
var $searchResults,
64+
$searchSummary,
65+
$searchContent,
66+
$selectedRow;
67+
68+
5969
var searchResults = [];
6070

6171
var FIND_IN_FILES_MAX = 100,
@@ -113,18 +123,17 @@ define(function (require, exports, module) {
113123
// class that everyone can use.
114124

115125
/**
116-
* FindInFilesDialog class
117-
* @constructor
118-
*
119-
*/
126+
* FindInFilesDialog class
127+
* @constructor
128+
*/
120129
function FindInFilesDialog() {
121130
this.closed = false;
122131
this.result = null; // $.Deferred
123132
}
124133

125134
/**
126-
* Closes the search dialog and resolves the promise that showDialog returned
127-
*/
135+
* Closes the search dialog and resolves the promise that showDialog returned
136+
*/
128137
FindInFilesDialog.prototype._close = function (value) {
129138
if (this.closed) {
130139
return;
@@ -137,26 +146,25 @@ define(function (require, exports, module) {
137146
};
138147

139148
/**
140-
* Shows the search dialog
141-
* @param {?string} initialString Default text to prepopulate the search field with
142-
* @param {?Entry} scope Search scope, or null to search whole proj
143-
* @returns {$.Promise} that is resolved with the string to search for
144-
*/
149+
* Shows the search dialog
150+
* @param {?string} initialString Default text to prepopulate the search field with
151+
* @param {?Entry} scope Search scope, or null to search whole proj
152+
* @returns {$.Promise} that is resolved with the string to search for
153+
*/
145154
FindInFilesDialog.prototype.showDialog = function (initialString, scope) {
146155
// Note the prefix label is a simple "Find:" - the "in ..." part comes after the text field
147-
var dialogHTML = Strings.CMD_FIND +
148-
": <input type='text' id='findInFilesInput' style='width: 10em'> <span id='findInFilesScope'></span> &nbsp;" +
149-
"<div class='message'><span style='color: #888'>(" + Strings.SEARCH_REGEXP_INFO + ")</span></div><div class='error'></div>";
156+
var templateVars = {
157+
value: initialString || "",
158+
label: _labelForScope(scope)
159+
};
160+
var dialogHTML = Mustache.render(SearchDialogTemplate, $.extend(templateVars, Strings));
161+
150162
this.result = new $.Deferred();
151163
this.modalBar = new ModalBar(dialogHTML, false);
152-
var $searchField = $("input#findInFilesInput");
164+
var $searchField = $("input#searchInput");
153165
var that = this;
154166

155-
$searchField.attr("value", initialString || "");
156167
$searchField.get(0).select();
157-
158-
$("#findInFilesScope").html(_labelForScope(scope));
159-
160168
$searchField.bind("keydown", function (event) {
161169
if (event.keyCode === KeyEvent.DOM_VK_RETURN || event.keyCode === KeyEvent.DOM_VK_ESCAPE) { // Enter/Return key or Esc key
162170
event.stopPropagation();
@@ -184,7 +192,13 @@ define(function (require, exports, module) {
184192
return this.result.promise();
185193
};
186194

187-
195+
/**
196+
* @private
197+
* Searches throught the contents an returns an array of matches
198+
* @param {string} contents
199+
* @param {RegExp} queryExpr
200+
* @return {Array.<{start: {line:number,ch:number}, end: {line:number,ch:number}, line: string}>}
201+
*/
188202
function _getSearchMatches(contents, queryExpr) {
189203
// Quick exit if not found
190204
if (contents.search(queryExpr) === -1) {
@@ -196,22 +210,21 @@ define(function (require, exports, module) {
196210
var matchStart;
197211
var matches = [];
198212

199-
200213
var match;
201214
var lines = StringUtils.getLines(contents);
202215
while ((match = queryExpr.exec(contents)) !== null) {
203-
var lineNum = StringUtils.offsetToLineNum(lines, match.index);
204-
var line = lines[lineNum];
205-
var ch = match.index - contents.lastIndexOf("\n", match.index) - 1; // 0-based index
216+
var lineNum = StringUtils.offsetToLineNum(lines, match.index);
217+
var line = lines[lineNum];
218+
var ch = match.index - contents.lastIndexOf("\n", match.index) - 1; // 0-based index
206219
var matchLength = match[0].length;
207220

208221
// Don't store more than 200 chars per line
209222
line = line.substr(0, Math.min(200, line.length));
210223

211224
matches.push({
212225
start: {line: lineNum, ch: ch},
213-
end: {line: lineNum, ch: ch + matchLength},
214-
line: line
226+
end: {line: lineNum, ch: ch + matchLength},
227+
line: line
215228
});
216229

217230
// We have the max hits in just this 1 file. Stop searching this file.
@@ -227,11 +240,7 @@ define(function (require, exports, module) {
227240
}
228241

229242
function _showSearchResults(searchResults, query, scope) {
230-
var $searchResultsDiv = $("#search-results");
231-
232243
if (searchResults && searchResults.length) {
233-
var $resultTable = $("<table class='zebra-striped condensed-table' />")
234-
.append("<tbody>");
235244

236245
// Count the total number of matches
237246
var numMatches = 0;
@@ -257,78 +266,103 @@ define(function (require, exports, module) {
257266
scope ? _labelForScope(scope) : ""
258267
);
259268

260-
$("#search-result-summary")
269+
// Insert the search summary
270+
$searchSummary
261271
.html(summary +
262272
(numMatches > FIND_IN_FILES_MAX ? StringUtils.format(Strings.FIND_IN_FILES_MAX, FIND_IN_FILES_MAX) : ""))
263273
.prepend("&nbsp;"); // putting a normal space before the "-" is not enough
264274

265-
var resultsDisplayed = 0;
275+
// Create the results template search list
276+
var searchList = [];
277+
var resultsDisplayed = 0, i;
278+
var searchItems, match;
266279

267280
searchResults.forEach(function (item) {
268281
if (item && resultsDisplayed < FIND_IN_FILES_MAX) {
269-
var makeCell = function (content) {
270-
return $("<td/>").html(content);
271-
};
272-
273-
// shorthand function name
274-
var esc = StringUtils.htmlEscape;
275-
276-
var highlightMatch = function (line, start, end) {
277-
return esc(line.substr(0, start)) + "<span class='highlight'>" + esc(line.substring(start, end)) + "</span>" + esc(line.substr(end));
278-
};
282+
i = 0;
279283

280-
// Add row for file name
281-
var displayFileName = StringUtils.format(Strings.FIND_IN_FILES_FILE_PATH,
282-
StringUtils.breakableUrl(esc(item.fullPath)));
283-
$("<tr class='file-section' />")
284-
.append("<td colspan='3'><span class='disclosure-triangle expanded'></span>" + displayFileName + "</td>")
285-
.click(function () {
286-
// Clicking file section header collapses/expands result rows for that file
287-
var $fileHeader = $(this);
288-
$fileHeader.nextUntil(".file-section").toggle();
289-
290-
var $triangle = $(".disclosure-triangle", $fileHeader);
291-
$triangle.toggleClass("expanded").toggleClass("collapsed");
292-
})
293-
.appendTo($resultTable);
284+
// Add a row for each match in the file
285+
searchItems = [];
286+
while (i < item.matches.length && resultsDisplayed < FIND_IN_FILES_MAX) {
287+
match = item.matches[i];
288+
searchItems.push({
289+
file: searchList.length,
290+
item: i,
291+
line: StringUtils.format(Strings.FIND_IN_FILES_LINE, (match.start.line + 1)),
292+
pre: match.line.substr(0, match.start.ch),
293+
highlight: match.line.substring(match.start.ch, match.end.ch),
294+
post: match.line.substr(match.end.ch),
295+
start: match.start,
296+
end: match.end
297+
});
298+
resultsDisplayed++;
299+
i++;
300+
}
294301

295-
// Add row for each match in file
296-
item.matches.forEach(function (match) {
297-
if (resultsDisplayed < FIND_IN_FILES_MAX) {
298-
var $row = $("<tr/>")
299-
.append(makeCell(" ")) // Indent
300-
.append(makeCell(StringUtils.format(Strings.FIND_IN_FILES_LINE, (match.start.line + 1))))
301-
.append(makeCell(highlightMatch(match.line, match.start.ch, match.end.ch)))
302-
.appendTo($resultTable);
303-
304-
$row.click(function () {
305-
CommandManager.execute(Commands.FILE_OPEN, {fullPath: item.fullPath})
306-
.done(function (doc) {
307-
// Opened document is now the current main editor
308-
EditorManager.getCurrentFullEditor().setSelection(match.start, match.end, true);
309-
});
310-
});
311-
resultsDisplayed++;
312-
}
302+
// Add a row for each file
303+
searchList.push({
304+
file: searchList.length,
305+
filename: StringUtils.breakableUrl(StringUtils.htmlEscape(item.fullPath)),
306+
fullPath: item.fullPath,
307+
items: searchItems
313308
});
314309

315310
}
316311
});
317312

318-
$("#search-results .table-container")
313+
// Insert the search results
314+
$searchContent
319315
.empty()
320-
.append($resultTable)
316+
.append(Mustache.render(SearchResultsTemplate, {searchList: searchList}))
321317
.scrollTop(0); // otherwise scroll pos from previous contents is remembered
322318

323-
$("#search-results .close")
319+
$searchResults.find(".close")
324320
.one("click", function () {
325-
$searchResultsDiv.hide();
321+
$searchResults.hide();
326322
EditorManager.resizeEditor();
327323
});
328324

329-
$searchResultsDiv.show();
325+
// Add the click event listener directly on the table parent
326+
$searchContent
327+
.off(".searchList") // Remove the old events
328+
.on("click.searchList", function (e) {
329+
var $row = $(e.target).closest("tr");
330+
331+
if ($row.length) {
332+
if ($selectedRow) {
333+
$selectedRow.removeClass("selected");
334+
}
335+
$row.addClass("selected");
336+
$selectedRow = $row;
337+
338+
var searchItem = searchList[$row.data("file")];
339+
var fullPath = searchItem.fullPath;
340+
341+
// This is a file title row, expand/collapse on click
342+
if ($row.hasClass("file-section")) {
343+
// Clicking the file section header collapses/expands result rows for that file
344+
$row.nextUntil(".file-section").toggle();
345+
346+
var $triangle = $(".disclosure-triangle", $row);
347+
$triangle.toggleClass("expanded").toggleClass("collapsed");
348+
349+
// This is a file row, show the result on click
350+
} else {
351+
// Grab the required item data
352+
var item = searchItem.items[$row.data("item")];
353+
354+
CommandManager.execute(Commands.FILE_OPEN, {fullPath: fullPath})
355+
.done(function (doc) {
356+
// Opened document is now the current main editor
357+
EditorManager.getCurrentFullEditor().setSelection(item.start, item.end, true);
358+
});
359+
}
360+
}
361+
});
362+
363+
$searchResults.show();
330364
} else {
331-
$searchResultsDiv.hide();
365+
$searchResults.hide();
332366
}
333367

334368
EditorManager.resizeEditor();
@@ -435,12 +469,6 @@ define(function (require, exports, module) {
435469
}
436470

437471

438-
// Initialize items dependent on HTML DOM
439-
AppInit.htmlReady(function () {
440-
var $searchResults = $("#search-results"),
441-
$searchContent = $("#search-results .table-container");
442-
});
443-
444472
function _fileNameChangeHandler(event, oldName, newName) {
445473
if ($("#search-results").is(":visible")) {
446474
// Update the search results
@@ -451,8 +479,18 @@ define(function (require, exports, module) {
451479
}
452480
}
453481

482+
483+
// Initialize items dependent on HTML DOM
484+
AppInit.htmlReady(function () {
485+
$searchResults = $("#search-results");
486+
$searchSummary = $("#search-result-summary");
487+
$searchContent = $("#search-results .table-container");
488+
});
489+
490+
// Initialize: register listeners
454491
$(DocumentManager).on("fileNameChange", _fileNameChangeHandler);
455492

493+
// Initialize: command handlers
456494
CommandManager.register(Strings.CMD_FIND_IN_FILES, Commands.EDIT_FIND_IN_FILES, doFindInFiles);
457495
CommandManager.register(Strings.CMD_FIND_IN_SUBTREE, Commands.EDIT_FIND_IN_SUBTREE, doFindInSubtree);
458496
});

0 commit comments

Comments
 (0)