From 439835c468ba1655e412efab92bb7370eea223ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Tue, 2 Apr 2013 22:44:20 -0300 Subject: [PATCH] Use templates for the search dialog and results --- src/htmlContent/search-dialog.html | 7 + src/htmlContent/search-results.html | 16 ++ src/nls/root/strings.js | 2 +- src/search/FindInFiles.js | 240 ++++++++++++++++------------ 4 files changed, 163 insertions(+), 102 deletions(-) create mode 100644 src/htmlContent/search-dialog.html create mode 100644 src/htmlContent/search-results.html diff --git a/src/htmlContent/search-dialog.html b/src/htmlContent/search-dialog.html new file mode 100644 index 00000000000..bbe242dce0a --- /dev/null +++ b/src/htmlContent/search-dialog.html @@ -0,0 +1,7 @@ +{{CMD_FIND}}: + +
+ {{{label}}} + ({{SEARCH_REGEXP_INFO}}) +
+
diff --git a/src/htmlContent/search-results.html b/src/htmlContent/search-results.html new file mode 100644 index 00000000000..0083b724e5b --- /dev/null +++ b/src/htmlContent/search-results.html @@ -0,0 +1,16 @@ + + + {{#searchList}} + + + + {{#items}} + + + + + + {{/items}} + {{/searchList}} + +
{{{filename}}}
{{line}}{{pre}}{{highlight}}{{post}}
diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 97f1e21fd75..8fedc00d283 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -120,7 +120,7 @@ define({ "FIND_IN_FILES_MORE_THAN" : "More than ", "FIND_IN_FILES_MAX" : " (showing the first {0} matches)", "FIND_IN_FILES_FILE_PATH" : "File: {0}", - "FIND_IN_FILES_LINE" : "line: {0}", + "FIND_IN_FILES_LINE" : "line: {0}", "ERROR_FETCHING_UPDATE_INFO_TITLE" : "Error getting update info", "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.", diff --git a/src/search/FindInFiles.js b/src/search/FindInFiles.js index 106981ac661..4faa1bdad4a 100644 --- a/src/search/FindInFiles.js +++ b/src/search/FindInFiles.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ -/*global define, $, PathUtils, window */ +/*global define, $, PathUtils, Mustache, window */ /* * 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 @@ define(function (require, exports, module) { "use strict"; - var Async = require("utils/Async"), - CommandManager = require("command/CommandManager"), - Commands = require("command/Commands"), - Strings = require("strings"), - StringUtils = require("utils/StringUtils"), - ProjectManager = require("project/ProjectManager"), - DocumentManager = require("document/DocumentManager"), - EditorManager = require("editor/EditorManager"), - FileIndexManager = require("project/FileIndexManager"), - KeyEvent = require("utils/KeyEvent"), - AppInit = require("utils/AppInit"), - StatusBar = require("widgets/StatusBar"), - ModalBar = require("widgets/ModalBar").ModalBar; - + var Async = require("utils/Async"), + CommandManager = require("command/CommandManager"), + Commands = require("command/Commands"), + Strings = require("strings"), + StringUtils = require("utils/StringUtils"), + ProjectManager = require("project/ProjectManager"), + DocumentManager = require("document/DocumentManager"), + EditorManager = require("editor/EditorManager"), + FileIndexManager = require("project/FileIndexManager"), + KeyEvent = require("utils/KeyEvent"), + AppInit = require("utils/AppInit"), + StatusBar = require("widgets/StatusBar"), + ModalBar = require("widgets/ModalBar").ModalBar, + SearchDialogTemplate = require("text!htmlContent/search-dialog.html"), + SearchResultsTemplate = require("text!htmlContent/search-results.html"); + + + /** @type {$.Element} jQuery elements used in the search results */ + var $searchResults, + $searchSummary, + $searchContent, + $selectedRow; + + var searchResults = []; var FIND_IN_FILES_MAX = 100, @@ -113,18 +123,17 @@ define(function (require, exports, module) { // class that everyone can use. /** - * FindInFilesDialog class - * @constructor - * - */ + * FindInFilesDialog class + * @constructor + */ function FindInFilesDialog() { this.closed = false; this.result = null; // $.Deferred } /** - * Closes the search dialog and resolves the promise that showDialog returned - */ + * Closes the search dialog and resolves the promise that showDialog returned + */ FindInFilesDialog.prototype._close = function (value) { if (this.closed) { return; @@ -137,26 +146,25 @@ define(function (require, exports, module) { }; /** - * Shows the search dialog - * @param {?string} initialString Default text to prepopulate the search field with - * @param {?Entry} scope Search scope, or null to search whole proj - * @returns {$.Promise} that is resolved with the string to search for - */ + * Shows the search dialog + * @param {?string} initialString Default text to prepopulate the search field with + * @param {?Entry} scope Search scope, or null to search whole proj + * @returns {$.Promise} that is resolved with the string to search for + */ FindInFilesDialog.prototype.showDialog = function (initialString, scope) { // Note the prefix label is a simple "Find:" - the "in ..." part comes after the text field - var dialogHTML = Strings.CMD_FIND + - ":  " + - "
(" + Strings.SEARCH_REGEXP_INFO + ")
"; + var templateVars = { + value: initialString || "", + label: _labelForScope(scope) + }; + var dialogHTML = Mustache.render(SearchDialogTemplate, $.extend(templateVars, Strings)); + this.result = new $.Deferred(); this.modalBar = new ModalBar(dialogHTML, false); - var $searchField = $("input#findInFilesInput"); + var $searchField = $("input#searchInput"); var that = this; - $searchField.attr("value", initialString || ""); $searchField.get(0).select(); - - $("#findInFilesScope").html(_labelForScope(scope)); - $searchField.bind("keydown", function (event) { if (event.keyCode === KeyEvent.DOM_VK_RETURN || event.keyCode === KeyEvent.DOM_VK_ESCAPE) { // Enter/Return key or Esc key event.stopPropagation(); @@ -184,7 +192,13 @@ define(function (require, exports, module) { return this.result.promise(); }; - + /** + * @private + * Searches throught the contents an returns an array of matches + * @param {string} contents + * @param {RegExp} queryExpr + * @return {Array.<{start: {line:number,ch:number}, end: {line:number,ch:number}, line: string}>} + */ function _getSearchMatches(contents, queryExpr) { // Quick exit if not found if (contents.search(queryExpr) === -1) { @@ -196,13 +210,12 @@ define(function (require, exports, module) { var matchStart; var matches = []; - var match; var lines = StringUtils.getLines(contents); while ((match = queryExpr.exec(contents)) !== null) { - var lineNum = StringUtils.offsetToLineNum(lines, match.index); - var line = lines[lineNum]; - var ch = match.index - contents.lastIndexOf("\n", match.index) - 1; // 0-based index + var lineNum = StringUtils.offsetToLineNum(lines, match.index); + var line = lines[lineNum]; + var ch = match.index - contents.lastIndexOf("\n", match.index) - 1; // 0-based index var matchLength = match[0].length; // Don't store more than 200 chars per line @@ -210,8 +223,8 @@ define(function (require, exports, module) { matches.push({ start: {line: lineNum, ch: ch}, - end: {line: lineNum, ch: ch + matchLength}, - line: line + end: {line: lineNum, ch: ch + matchLength}, + line: line }); // We have the max hits in just this 1 file. Stop searching this file. @@ -227,11 +240,7 @@ define(function (require, exports, module) { } function _showSearchResults(searchResults, query, scope) { - var $searchResultsDiv = $("#search-results"); - if (searchResults && searchResults.length) { - var $resultTable = $("") - .append(""); // Count the total number of matches var numMatches = 0; @@ -257,78 +266,103 @@ define(function (require, exports, module) { scope ? _labelForScope(scope) : "" ); - $("#search-result-summary") + // Insert the search summary + $searchSummary .html(summary + (numMatches > FIND_IN_FILES_MAX ? StringUtils.format(Strings.FIND_IN_FILES_MAX, FIND_IN_FILES_MAX) : "")) .prepend(" "); // putting a normal space before the "-" is not enough - var resultsDisplayed = 0; + // Create the results template search list + var searchList = []; + var resultsDisplayed = 0, i; + var searchItems, match; searchResults.forEach(function (item) { if (item && resultsDisplayed < FIND_IN_FILES_MAX) { - var makeCell = function (content) { - return $("") - .append("") - .click(function () { - // Clicking file section header collapses/expands result rows for that file - var $fileHeader = $(this); - $fileHeader.nextUntil(".file-section").toggle(); - - var $triangle = $(".disclosure-triangle", $fileHeader); - $triangle.toggleClass("expanded").toggleClass("collapsed"); - }) - .appendTo($resultTable); + // Add a row for each match in the file + searchItems = []; + while (i < item.matches.length && resultsDisplayed < FIND_IN_FILES_MAX) { + match = item.matches[i]; + searchItems.push({ + file: searchList.length, + item: i, + line: StringUtils.format(Strings.FIND_IN_FILES_LINE, (match.start.line + 1)), + pre: match.line.substr(0, match.start.ch), + highlight: match.line.substring(match.start.ch, match.end.ch), + post: match.line.substr(match.end.ch), + start: match.start, + end: match.end + }); + resultsDisplayed++; + i++; + } - // Add row for each match in file - item.matches.forEach(function (match) { - if (resultsDisplayed < FIND_IN_FILES_MAX) { - var $row = $("") - .append(makeCell(" ")) // Indent - .append(makeCell(StringUtils.format(Strings.FIND_IN_FILES_LINE, (match.start.line + 1)))) - .append(makeCell(highlightMatch(match.line, match.start.ch, match.end.ch))) - .appendTo($resultTable); - - $row.click(function () { - CommandManager.execute(Commands.FILE_OPEN, {fullPath: item.fullPath}) - .done(function (doc) { - // Opened document is now the current main editor - EditorManager.getCurrentFullEditor().setSelection(match.start, match.end, true); - }); - }); - resultsDisplayed++; - } + // Add a row for each file + searchList.push({ + file: searchList.length, + filename: StringUtils.breakableUrl(StringUtils.htmlEscape(item.fullPath)), + fullPath: item.fullPath, + items: searchItems }); } }); - $("#search-results .table-container") + // Insert the search results + $searchContent .empty() - .append($resultTable) + .append(Mustache.render(SearchResultsTemplate, {searchList: searchList})) .scrollTop(0); // otherwise scroll pos from previous contents is remembered - $("#search-results .close") + $searchResults.find(".close") .one("click", function () { - $searchResultsDiv.hide(); + $searchResults.hide(); EditorManager.resizeEditor(); }); - $searchResultsDiv.show(); + // Add the click event listener directly on the table parent + $searchContent + .off(".searchList") // Remove the old events + .on("click.searchList", function (e) { + var $row = $(e.target).closest("tr"); + + if ($row.length) { + if ($selectedRow) { + $selectedRow.removeClass("selected"); + } + $row.addClass("selected"); + $selectedRow = $row; + + var searchItem = searchList[$row.data("file")]; + var fullPath = searchItem.fullPath; + + // This is a file title row, expand/collapse on click + if ($row.hasClass("file-section")) { + // Clicking the file section header collapses/expands result rows for that file + $row.nextUntil(".file-section").toggle(); + + var $triangle = $(".disclosure-triangle", $row); + $triangle.toggleClass("expanded").toggleClass("collapsed"); + + // This is a file row, show the result on click + } else { + // Grab the required item data + var item = searchItem.items[$row.data("item")]; + + CommandManager.execute(Commands.FILE_OPEN, {fullPath: fullPath}) + .done(function (doc) { + // Opened document is now the current main editor + EditorManager.getCurrentFullEditor().setSelection(item.start, item.end, true); + }); + } + } + }); + + $searchResults.show(); } else { - $searchResultsDiv.hide(); + $searchResults.hide(); } EditorManager.resizeEditor(); @@ -435,12 +469,6 @@ define(function (require, exports, module) { } - // Initialize items dependent on HTML DOM - AppInit.htmlReady(function () { - var $searchResults = $("#search-results"), - $searchContent = $("#search-results .table-container"); - }); - function _fileNameChangeHandler(event, oldName, newName) { if ($("#search-results").is(":visible")) { // Update the search results @@ -451,8 +479,18 @@ define(function (require, exports, module) { } } + + // Initialize items dependent on HTML DOM + AppInit.htmlReady(function () { + $searchResults = $("#search-results"); + $searchSummary = $("#search-result-summary"); + $searchContent = $("#search-results .table-container"); + }); + + // Initialize: register listeners $(DocumentManager).on("fileNameChange", _fileNameChangeHandler); + // Initialize: command handlers CommandManager.register(Strings.CMD_FIND_IN_FILES, Commands.EDIT_FIND_IN_FILES, doFindInFiles); CommandManager.register(Strings.CMD_FIND_IN_SUBTREE, Commands.EDIT_FIND_IN_SUBTREE, doFindInSubtree); });
").html(content); - }; - - // shorthand function name - var esc = StringUtils.htmlEscape; - - var highlightMatch = function (line, start, end) { - return esc(line.substr(0, start)) + "" + esc(line.substring(start, end)) + "" + esc(line.substr(end)); - }; + i = 0; - // Add row for file name - var displayFileName = StringUtils.format(Strings.FIND_IN_FILES_FILE_PATH, - StringUtils.breakableUrl(esc(item.fullPath))); - $("
" + displayFileName + "