From 843c5e2f3158af4acb9b1e502569b3e567e73e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Thu, 20 Jun 2013 20:25:29 -0300 Subject: [PATCH 1/9] Find in Files Pagination --- src/nls/root/strings.js | 4 +- src/search/FindInFiles.js | 276 +++++++++++++++++++++++++++++--------- 2 files changed, 218 insertions(+), 62 deletions(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 9043d3331fe..9d3afbb787c 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -128,7 +128,9 @@ define({ "FIND_IN_FILES_MATCH" : "match", "FIND_IN_FILES_MATCHES" : "matches", "FIND_IN_FILES_MORE_THAN" : "More than ", - "FIND_IN_FILES_MAX" : " (showing the first {0} matches)", + "FIND_IN_FILES_PAGING" : " (showing matches {0} to {1})", + "FIND_IN_FILES_LESS" : " Less", + "FIND_IN_FILES_MORE" : " More", "FIND_IN_FILES_FILE_PATH" : "File: {0}", "FIND_IN_FILES_LINE" : "line: {0}", diff --git a/src/search/FindInFiles.js b/src/search/FindInFiles.js index f287b636b50..f860bb3f01c 100644 --- a/src/search/FindInFiles.js +++ b/src/search/FindInFiles.js @@ -53,9 +53,11 @@ define(function (require, exports, module) { EditorManager = require("editor/EditorManager"), PanelManager = require("view/PanelManager"), FileIndexManager = require("project/FileIndexManager"), + FileViewController = require("project/FileViewController"), FileUtils = require("file/FileUtils"), KeyEvent = require("utils/KeyEvent"), AppInit = require("utils/AppInit"), + CollectionUtils = require("utils/CollectionUtils"), StatusBar = require("widgets/StatusBar"), ModalBar = require("widgets/ModalBar").ModalBar; @@ -63,23 +65,44 @@ define(function (require, exports, module) { searchPanelTemplate = require("text!htmlContent/search-panel.html"), searchResultsTemplate = require("text!htmlContent/search-results.html"); + /** @cost Constants used to define the maximum results show per page and found in a single file */ + var RESULTS_PER_PAGE = 100, + FIND_IN_FILE_MAX = 300; + + /** + * Map of all the last search results + * @type {Object., collapsed: boolean}>} + */ + var searchResults = {}; + + /** @type {Panel} Bottom panel holding the search results. Initialized in htmlReady() */ + var searchResultsPanel; + + /** @type {number} The index of the first result that is displayed */ + var currentStart = 0; + + /** @type {string} The current search query */ + var currentQuery = ""; + + /** @type {Array.} An array of the files where it should look or null/empty to search the entire project */ + var currentScope = null; + + /** @type {boolean} True if the matches in a file reached FIND_IN_FILE_MAX */ + var maxHitsFoundInFile = false; + /** @type {$.Element} jQuery elements used in the search results */ var $searchResults, $searchSummary, $searchContent, $selectedRow; - var searchResults = []; - - var FIND_IN_FILES_MAX = 100, - maxHitsFoundInFile = false, - currentQuery = "", - currentScope; - - // Bottom panel holding the search results. Initialized in htmlReady(). - var searchResultsPanel; - + /** + * @private + * Returns a regular expression from the given query and shows an error in the modal-bar if it was invalid + * @param {!string} query - The query from the modal-bar input + * @return {RegExp} + */ function _getQueryRegExp(query) { // Clear any pending RegEx error message $(".modal-bar .message").css("display", "inline-block"); @@ -176,20 +199,21 @@ define(function (require, exports, module) { var that = this; $searchField.get(0).select(); - $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(); - event.preventDefault(); - - var query = $searchField.val(); - - if (event.keyCode === KeyEvent.DOM_VK_ESCAPE) { - query = null; + $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(); + event.preventDefault(); + + var query = $searchField.val(); + + if (event.keyCode === KeyEvent.DOM_VK_ESCAPE) { + query = null; + } + + that._close(query); } - - that._close(query); - } - }) + }) .bind("input", function (event) { // Check the query expression on every input event. This way the user is alerted // to any RegEx syntax errors immediately. @@ -248,7 +272,7 @@ define(function (require, exports, module) { // We have the max hits in just this 1 file. Stop searching this file. // This fixed issue #1829 where code hangs on too many hits. - if (matches.length >= FIND_IN_FILES_MAX) { + if (matches.length >= FIND_IN_FILE_MAX) { queryExpr.lastIndex = 0; maxHitsFoundInFile = true; break; @@ -257,16 +281,45 @@ define(function (require, exports, module) { return matches; } + + /** + * @private + * Searches and stores the match results for the given file, if there are matches + * @param {string} fullPath + * @param {string} contents + * @param {RegExp} queryExpr + */ + function _addSearchMatches(fullPath, contents, queryExpr) { + var matches = _getSearchMatches(contents, queryExpr); - function _showSearchResults(searchResults, query, scope) { - if (searchResults && searchResults.length) { + if (matches && matches.length) { + searchResults[fullPath] = { + matches: matches, + collapsed: false + }; + } + } + + + /** + * @private + * Shows the results in a table and adds the necesary event listeners + */ + function _showSearchResults() { + if (!$.isEmptyObject(searchResults)) { // Count the total number of matches - var numMatches = 0; - searchResults.forEach(function (item) { + var numFiles = 0, numMatches = 0; + CollectionUtils.forEach(searchResults, function (item) { + numFiles++; numMatches += item.matches.length; }); + // No more pages to show + if (currentStart > numMatches || currentStart < 0) { + return; + } + // Show result summary in header var numMatchesStr = ""; if (maxHitsFoundInFile) { @@ -279,16 +332,21 @@ define(function (require, exports, module) { Strings.FIND_IN_FILES_TITLE, numMatchesStr, (numMatches > 1) ? Strings.FIND_IN_FILES_MATCHES : Strings.FIND_IN_FILES_MATCH, - searchResults.length, - (searchResults.length > 1 ? Strings.FIND_IN_FILES_FILES : Strings.FIND_IN_FILES_FILE), - StringUtils.htmlEscape(query), - scope ? _labelForScope(scope) : "" + numFiles, + (numFiles > 1 ? Strings.FIND_IN_FILES_FILES : Strings.FIND_IN_FILES_FILE), + StringUtils.htmlEscape(currentQuery), + currentScope ? _labelForScope(currentScope) : "" ); + // The last result index displayed + var last = currentStart + RESULTS_PER_PAGE > numMatches ? numMatches : currentStart + RESULTS_PER_PAGE; + // Insert the search summary $searchSummary .html(summary + - (numMatches > FIND_IN_FILES_MAX ? StringUtils.format(Strings.FIND_IN_FILES_MAX, FIND_IN_FILES_MAX) : "")) + (numMatches > RESULTS_PER_PAGE ? StringUtils.format(Strings.FIND_IN_FILES_PAGING, currentStart + 1, last) : "") + + (currentStart > 0 ? Strings.FIND_IN_FILES_LESS : "") + + (last < numMatches ? Strings.FIND_IN_FILES_MORE : "")) .prepend(" "); // putting a normal space before the "-" is not enough // Create the results template search list @@ -296,13 +354,30 @@ define(function (require, exports, module) { var resultsDisplayed = 0, i; var searchItems, match; - searchResults.forEach(function (item) { - if (item && resultsDisplayed < FIND_IN_FILES_MAX) { - i = 0; + CollectionUtils.some(searchResults, function (item, fullPath) { + // Skip the items that will not fit in the results page + if (resultsDisplayed + item.matches.length < currentStart) { + resultsDisplayed += item.matches.length; + i = -1; + + // Only the first matches will be displayed filling the remaining space of the table + } else if (resultsDisplayed < currentStart) { + i = currentStart - resultsDisplayed; + resultsDisplayed = currentStart; + // All the matches can be displayed + } else if (resultsDisplayed < last) { + i = 0; + + // We can't display more items by now. Break the loop + } else { + return true; + } + + if (i >= 0 && i < item.matches.length) { // Add a row for each match in the file searchItems = []; - while (i < item.matches.length && resultsDisplayed < FIND_IN_FILES_MAX) { + while (i < item.matches.length && resultsDisplayed < last) { match = item.matches[i]; searchItems.push({ file: searchList.length, @@ -321,16 +396,15 @@ define(function (require, exports, module) { // Add a row for each file var displayFileName = StringUtils.format( Strings.FIND_IN_FILES_FILE_PATH, - StringUtils.breakableUrl(item.fullPath) + StringUtils.breakableUrl(fullPath) ); searchList.push({ file: searchList.length, filename: displayFileName, - fullPath: item.fullPath, + fullPath: fullPath, items: searchItems }); - } }); @@ -345,6 +419,20 @@ define(function (require, exports, module) { _hideSearchResults(); }); + // The link to go the previous page + $searchResults.find(".find-less") + .one("click", function () { + currentStart -= RESULTS_PER_PAGE; + _showSearchResults(); + }); + + // The link to go to the next page + $searchResults.find(".find-more") + .one("click", function () { + currentStart += RESULTS_PER_PAGE; + _showSearchResults(); + }); + // Add the click event listener directly on the table parent $searchContent .off(".searchList") // Remove the old events @@ -368,6 +456,8 @@ define(function (require, exports, module) { var $triangle = $(".disclosure-triangle", $row); $triangle.toggleClass("expanded").toggleClass("collapsed"); + + searchResults[fullPath].collapsed = !searchResults[fullPath].collapsed; // This is a file row, show the result on click } else { @@ -381,9 +471,33 @@ define(function (require, exports, module) { }); } } + }) + // Add the file to the working set on double click + .on("dblclick.searchList", function (e) { + var $row = $(e.target).closest("tr"); + if ($row.length && !$row.hasClass("file-section")) { + // Grab the required item data + var item = searchList[$row.data("file")]; + + FileViewController.addToWorkingSetAndSelect(item.fullPath); + } + }) + // Restore the collapsed files + .find(".file-section").each(function () { + var fullPath = searchList[$(this).data("file")].fullPath; + + if (searchResults[fullPath].collapsed) { + searchResults[fullPath].collapsed = false; + $(this).trigger("click"); + } }); + if ($selectedRow) { + $selectedRow.removeClass("selected"); + $selectedRow = null; + } searchResultsPanel.show(); + } else { _hideSearchResults(); } @@ -413,16 +527,15 @@ define(function (require, exports, module) { * @param {?Entry} scope Project file/subfolder to search within; else searches whole project. */ function doFindInFiles(scope) { - var dialog = new FindInFilesDialog(); // Default to searching for the current selection var currentEditor = EditorManager.getActiveEditor(); var initialString = currentEditor && currentEditor.getSelectedText(); - currentQuery = ""; - currentScope = scope; - searchResults = []; + searchResults = {}; + currentQuery = ""; + currentScope = scope; maxHitsFoundInFile = false; dialog.showDialog(initialString, scope) @@ -445,14 +558,7 @@ define(function (require, exports, module) { // Search one file DocumentManager.getDocumentForPath(fileInfo.fullPath) .done(function (doc) { - var matches = _getSearchMatches(doc.getText(), queryExpr); - - if (matches && matches.length) { - searchResults.push({ - fullPath: fileInfo.fullPath, - matches: matches - }); - } + _addSearchMatches(fileInfo.fullPath, doc.getText(), queryExpr); result.resolve(); }) .fail(function (error) { @@ -465,7 +571,7 @@ define(function (require, exports, module) { }) .done(function () { // Done searching all files: show results - _showSearchResults(searchResults, query, scope); + _showSearchResults(); StatusBar.hideBusyIndicator(); }) .fail(function () { @@ -483,25 +589,73 @@ define(function (require, exports, module) { doFindInFiles(selectedEntry); } + + /** + * @private + * Shows the search results and tryes to restore the previous scroll and selection + */ + function _restoreSearchResults() { + var scrollTop = $searchContent.scrollTop(); + var index = $selectedRow ? $selectedRow.index() : null; + + _showSearchResults(); + + $searchContent.scrollTop(scrollTop); + if ($selectedRow) { + $selectedRow = $searchContent.find("tr:eq(" + index + ")"); + $selectedRow.addClass("selected"); + } + } + + /** + * @private + * Moves the search results from the previous path to the new one and updates the results list, if required + * @param {$.Event} event + * @param {string} oldName + * @param {string} newName + */ function _fileNameChangeHandler(event, oldName, newName) { + var resultsChanged = false; + if (searchResultsPanel.isVisible()) { // Update the search results - searchResults.forEach(function (item) { - item.fullPath = item.fullPath.replace(oldName, newName); + CollectionUtils.forEach(searchResults, function (item, fullPath) { + if (fullPath.match(oldName)) { + searchResults[fullPath.replace(oldName, newName)] = item; + delete searchResults[fullPath]; + resultsChanged = true; + } }); - _showSearchResults(searchResults, currentQuery, currentScope); + + // Restore the reesults if needed + if (resultsChanged) { + _restoreSearchResults(); + } } } - + + /** + * @private + * Deletes the results from the deleted file and updates the results list, if required + * @param {$.Event} event + * @param {string} path + */ function _pathDeletedHandler(event, path) { + var resultsChanged = false; + if (searchResultsPanel.isVisible()) { // Update the search results - searchResults.forEach(function (item, idx) { - if (FileUtils.isAffectedWhenRenaming(item.fullPath, path)) { - searchResults.splice(idx, 1); + CollectionUtils.forEach(searchResults, function (item, fullPath) { + if (FileUtils.isAffectedWhenRenaming(fullPath, path)) { + delete searchResults[fullPath]; + resultsChanged = true; } }); - _showSearchResults(searchResults, currentQuery, currentScope); + + // Restore the reesults if needed + if (resultsChanged) { + _restoreSearchResults(); + } } } From 926589ba7c39d355ad13ac9248460bc8654fcb2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Thu, 20 Jun 2013 20:36:39 -0300 Subject: [PATCH 2/9] Fix tab issue --- src/search/FindInFiles.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search/FindInFiles.js b/src/search/FindInFiles.js index f860bb3f01c..291ea64ce9b 100644 --- a/src/search/FindInFiles.js +++ b/src/search/FindInFiles.js @@ -65,10 +65,10 @@ define(function (require, exports, module) { searchPanelTemplate = require("text!htmlContent/search-panel.html"), searchResultsTemplate = require("text!htmlContent/search-results.html"); - /** @cost Constants used to define the maximum results show per page and found in a single file */ - var RESULTS_PER_PAGE = 100, + /** @cost Constants used to define the maximum results show per page and found in a single file */ + var RESULTS_PER_PAGE = 100, FIND_IN_FILE_MAX = 300; - + /** * Map of all the last search results * @type {Object., collapsed: boolean}>} From c92611c7268846baeb5b3cab5b3511b9d037bc66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Tue, 16 Jul 2013 23:00:31 -0300 Subject: [PATCH 3/9] Design changes after review --- src/htmlContent/search-panel.html | 1 - src/htmlContent/search-summary.html | 6 ++++++ src/nls/root/strings.js | 7 +++---- src/search/FindInFiles.js | 17 ++++++++++------- src/styles/brackets.less | 14 ++++++++++++++ 5 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 src/htmlContent/search-summary.html diff --git a/src/htmlContent/search-panel.html b/src/htmlContent/search-panel.html index e46d823f469..0929275fd4b 100644 --- a/src/htmlContent/search-panel.html +++ b/src/htmlContent/search-panel.html @@ -1,6 +1,5 @@
-
{{SEARCH_RESULTS}}
×
diff --git a/src/htmlContent/search-summary.html b/src/htmlContent/search-summary.html new file mode 100644 index 00000000000..4be29a44fb6 --- /dev/null +++ b/src/htmlContent/search-summary.html @@ -0,0 +1,6 @@ +{{{summary}}} +{{#hasPages}} + +{{{results}}} + +{{/hasPages}} \ No newline at end of file diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 196d38b997b..12722852f2c 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -123,15 +123,15 @@ define({ "NO_UPDATE_TITLE" : "You're up to date!", "NO_UPDATE_MESSAGE" : "You are running the latest version of {APP_NAME}.", - "FIND_IN_FILES_TITLE" : "for \"{4}\" {5} - {0} {1} in {2} {3}", + "FIND_IN_FILES_TITLE" : "\"{4}\" found {5} — {0} {1} in {2} {3}:", "FIND_IN_FILES_SCOPED" : "in {0}", "FIND_IN_FILES_NO_SCOPE" : "in project", "FIND_IN_FILES_FILE" : "file", "FIND_IN_FILES_FILES" : "files", "FIND_IN_FILES_MATCH" : "match", "FIND_IN_FILES_MATCHES" : "matches", - "FIND_IN_FILES_MORE_THAN" : "More than ", - "FIND_IN_FILES_PAGING" : " (showing matches {0} to {1})", + "FIND_IN_FILES_MORE_THAN" : "Over ", + "FIND_IN_FILES_PAGING" : "{0}—{1}", "FIND_IN_FILES_LESS" : " Less", "FIND_IN_FILES_MORE" : " More", "FIND_IN_FILES_FILE_PATH" : "File: {0}", @@ -275,7 +275,6 @@ define({ // Strings for main-view.html "EXPERIMENTAL_BUILD" : "experimental build", "DEVELOPMENT_BUILD" : "development build", - "SEARCH_RESULTS" : "Search Results", "OK" : "OK", "DONT_SAVE" : "Don't Save", "SAVE" : "Save", diff --git a/src/search/FindInFiles.js b/src/search/FindInFiles.js index cfb0435fed4..f56168afe46 100644 --- a/src/search/FindInFiles.js +++ b/src/search/FindInFiles.js @@ -64,6 +64,7 @@ define(function (require, exports, module) { var searchDialogTemplate = require("text!htmlContent/search-dialog.html"), searchPanelTemplate = require("text!htmlContent/search-panel.html"), + searchSummaryTemplate = require("text!htmlContent/search-summary.html"), searchResultsTemplate = require("text!htmlContent/search-results.html"); /** @cost Constants used to define the maximum results show per page and found in a single file */ @@ -344,11 +345,13 @@ define(function (require, exports, module) { // Insert the search summary $searchSummary - .html(summary + - (numMatches > RESULTS_PER_PAGE ? StringUtils.format(Strings.FIND_IN_FILES_PAGING, currentStart + 1, last) : "") + - (currentStart > 0 ? Strings.FIND_IN_FILES_LESS : "") + - (last < numMatches ? Strings.FIND_IN_FILES_MORE : "")) - .prepend(" "); // putting a normal space before the "-" is not enough + .html(Mustache.render(searchSummaryTemplate, { + summary: summary, + hasPages: numMatches > RESULTS_PER_PAGE, + results: StringUtils.format(Strings.FIND_IN_FILES_PAGING, currentStart + 1, last), + hasLess: currentStart > 0, + hasMore: last < numMatches + })); // Create the results template search list var searchList = []; @@ -421,14 +424,14 @@ define(function (require, exports, module) { }); // The link to go the previous page - $searchResults.find(".find-less") + $searchResults.find(".left-triangle:not(.disabled)") .one("click", function () { currentStart -= RESULTS_PER_PAGE; _showSearchResults(); }); // The link to go to the next page - $searchResults.find(".find-more") + $searchResults.find(".right-triangle:not(.disabled)") .one("click", function () { currentStart += RESULTS_PER_PAGE; _showSearchResults(); diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 9e4f4de0378..6c514447d01 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -702,6 +702,20 @@ a, img { /* Find in Files results panel - temporary UI, to be replaced with a richer search feature later */ +#search-result-summary { + .left-triangle, .right-triangle { + .jstree-sprite; + display: inline-block; + background-position: -2px -3px; + } + .left-triangle { + -webkit-transform: translateZ(0) translateY(-2px) rotate(180deg); + } + .disabled { + opacity: 0.3; + } +} + #search-results .disclosure-triangle { .jstree-sprite; display: inline-block; From 68d95f35e771d2b25338142c96b5a7d9fb4bb8fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Fri, 19 Jul 2013 18:07:58 -0300 Subject: [PATCH 4/9] Code fixes after review --- src/htmlContent/search-summary.html | 6 +- src/search/FindInFiles.js | 176 ++++++++++++++-------------- 2 files changed, 94 insertions(+), 88 deletions(-) diff --git a/src/htmlContent/search-summary.html b/src/htmlContent/search-summary.html index 4be29a44fb6..11437d9f937 100644 --- a/src/htmlContent/search-summary.html +++ b/src/htmlContent/search-summary.html @@ -1,6 +1,6 @@ {{{summary}}} {{#hasPages}} - -{{{results}}} - + + {{{results}}} + {{/hasPages}} \ No newline at end of file diff --git a/src/search/FindInFiles.js b/src/search/FindInFiles.js index f56168afe46..e08603ce73f 100644 --- a/src/search/FindInFiles.js +++ b/src/search/FindInFiles.js @@ -25,7 +25,7 @@ /*global define, $, PathUtils, window, Mustache */ /* - * Adds a "find in files" command to allow the user to find all occurances of a string in all files in + * Adds a "find in files" command to allow the user to find all occurrences of a string in all files in * the project. * * The keyboard shortcut is Cmd(Ctrl)-Shift-F. @@ -34,7 +34,7 @@ * - Proper UI for both dialog and results * - Refactor dialog class and share with Quick File Open * - Search files in working set that are *not* in the project - * - Handle matches that span mulitple lines + * - Handle matches that span multiple lines * - Refactor UI from functionality to enable unit testing */ @@ -137,6 +137,7 @@ define(function (require, exports, module) { } /** + * @private * Returns label text to indicate the search scope. Already HTML-escaped. * @param {?Entry} scope */ @@ -184,21 +185,21 @@ 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 + * @param {?Entry} scope Search scope, or null to search whole project * @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 templateVars = { - value: initialString || "", - label: _labelForScope(scope) - }; - var dialogHTML = Mustache.render(searchDialogTemplate, $.extend(templateVars, Strings)); + value: initialString || "", + label: _labelForScope(scope) + }, + dialogHTML = Mustache.render(searchDialogTemplate, $.extend(templateVars, Strings)), + that = this; - this.result = new $.Deferred(); - this.modalBar = new ModalBar(dialogHTML, false); + this.result = new $.Deferred(); + this.modalBar = new ModalBar(dialogHTML, false); var $searchField = $("input#searchInput"); - var that = this; $searchField.get(0).select(); $searchField @@ -230,6 +231,10 @@ define(function (require, exports, module) { }; + /** + * @private + * Hides the Search Results Panel + */ function _hideSearchResults() { if (searchResultsPanel.isVisible()) { searchResultsPanel.hide(); @@ -239,7 +244,7 @@ define(function (require, exports, module) { /** * @private - * Searches throught the contents an returns an array of matches + * Searches through 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}>} @@ -250,18 +255,15 @@ define(function (require, exports, module) { return null; } - var trimmedContents = contents; - var startPos = 0; - var matchStart; - var matches = []; + var match, lineNum, line, ch, matchLength, + lines = StringUtils.getLines(contents), + 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 matchLength = match[0].length; + lineNum = StringUtils.offsetToLineNum(lines, match.index); + line = lines[lineNum]; + ch = match.index - contents.lastIndexOf("\n", match.index) - 1; // 0-based index + matchLength = match[0].length; // Don't store more than 200 chars per line line = line.substr(0, Math.min(200, line.length)); @@ -305,7 +307,7 @@ define(function (require, exports, module) { /** * @private - * Shows the results in a table and adds the necesary event listeners + * Shows the results in a table and adds the necessary event listeners */ function _showSearchResults() { if (!$.isEmptyObject(searchResults)) { @@ -317,11 +319,6 @@ define(function (require, exports, module) { numMatches += item.matches.length; }); - // No more pages to show - if (currentStart > numMatches || currentStart < 0) { - return; - } - // Show result summary in header var numMatchesStr = ""; if (maxHitsFoundInFile) { @@ -344,33 +341,39 @@ define(function (require, exports, module) { var last = currentStart + RESULTS_PER_PAGE > numMatches ? numMatches : currentStart + RESULTS_PER_PAGE; // Insert the search summary - $searchSummary - .html(Mustache.render(searchSummaryTemplate, { - summary: summary, - hasPages: numMatches > RESULTS_PER_PAGE, - results: StringUtils.format(Strings.FIND_IN_FILES_PAGING, currentStart + 1, last), - hasLess: currentStart > 0, - hasMore: last < numMatches - })); + $searchSummary.html(Mustache.render(searchSummaryTemplate, { + summary: summary, + hasPages: numMatches > RESULTS_PER_PAGE, + results: StringUtils.format(Strings.FIND_IN_FILES_PAGING, currentStart + 1, last), + hasPrev: currentStart > 0, + hasNext: last < numMatches + })); // Create the results template search list - var searchList = []; - var resultsDisplayed = 0, i; - var searchItems, match; + var searchItems, match, i, + searchList = [], + matchesCounter = 0, + showMatches = false; CollectionUtils.some(searchResults, function (item, fullPath) { - // Skip the items that will not fit in the results page - if (resultsDisplayed + item.matches.length < currentStart) { - resultsDisplayed += item.matches.length; - i = -1; + showMatches = true; - // Only the first matches will be displayed filling the remaining space of the table - } else if (resultsDisplayed < currentStart) { - i = currentStart - resultsDisplayed; - resultsDisplayed = currentStart; - - // All the matches can be displayed - } else if (resultsDisplayed < last) { + // Since the amount of matches on this item plus the amount of matches we skipped until + // now is still smaller than the first match that we want to display, skip these. + if (matchesCounter + item.matches.length < currentStart) { + matchesCounter += item.matches.length; + showMatches = false; + + // If we still haven't skipped enough items to get to the first match, but adding the + // item matches to the skipped ones is greater the the first match we want to display, + // then we can display the matches from this item skipping the first ones + } else if (matchesCounter < currentStart) { + i = currentStart - matchesCounter; + matchesCounter = currentStart; + + // If we already skipped enough matches to get to the first match to display, we can start + // displaying from the first match of this item + } else if (matchesCounter < last) { i = 0; // We can't display more items by now. Break the loop @@ -378,10 +381,12 @@ define(function (require, exports, module) { return true; } - if (i >= 0 && i < item.matches.length) { + if (!showMatches && i < item.matches.length) { // Add a row for each match in the file searchItems = []; - while (i < item.matches.length && resultsDisplayed < last) { + + // Add matches until we get to the last match of this item, or filling the page + while (i < item.matches.length && matchesCounter < last) { match = item.matches[i]; searchItems.push({ file: searchList.length, @@ -393,7 +398,7 @@ define(function (require, exports, module) { start: match.start, end: match.end }); - resultsDisplayed++; + matchesCounter++; i++; } @@ -418,24 +423,21 @@ define(function (require, exports, module) { .append(Mustache.render(searchResultsTemplate, {searchList: searchList})) .scrollTop(0); // otherwise scroll pos from previous contents is remembered - $searchResults.find(".close") - .one("click", function () { - _hideSearchResults(); - }); + $searchResults.one("click", ".close", function () { + _hideSearchResults(); + }); // The link to go the previous page - $searchResults.find(".left-triangle:not(.disabled)") - .one("click", function () { - currentStart -= RESULTS_PER_PAGE; - _showSearchResults(); - }); + $searchResults.one("click", ".left-triangle:not(.disabled)", function () { + currentStart -= RESULTS_PER_PAGE; + _showSearchResults(); + }); // The link to go to the next page - $searchResults.find(".right-triangle:not(.disabled)") - .one("click", function () { - currentStart += RESULTS_PER_PAGE; - _showSearchResults(); - }); + $searchResults.one("click", ".right-triangle:not(.disabled)", function () { + currentStart += RESULTS_PER_PAGE; + _showSearchResults(); + }); // Add the click event listener directly on the table parent $searchContent @@ -450,8 +452,8 @@ define(function (require, exports, module) { $row.addClass("selected"); $selectedRow = $row; - var searchItem = searchList[$row.data("file")]; - var fullPath = searchItem.fullPath; + var searchItem = searchList[$row.data("file")], + fullPath = searchItem.fullPath; // This is a file title row, expand/collapse on click if ($row.hasClass("file-section")) { @@ -508,11 +510,12 @@ define(function (require, exports, module) { } /** + * @private * @param {!FileInfo} fileInfo File in question * @param {?Entry} scope Search scope, or null if whole project * @return {boolean} */ - function inScope(fileInfo, scope) { + function _inScope(fileInfo, scope) { if (scope) { if (scope.isDirectory) { // Dirs always have trailing slash, so we don't have to worry about being @@ -526,20 +529,20 @@ define(function (require, exports, module) { } /** + * @private * Displays a non-modal embedded dialog above the code mirror editor that allows the user to do * a find operation across all files in the project. * @param {?Entry} scope Project file/subfolder to search within; else searches whole project. */ - function doFindInFiles(scope) { + function _doFindInFiles(scope) { if (scope instanceof NativeFileSystem.InaccessibleFileEntry) { return; } - var dialog = new FindInFilesDialog(); - // Default to searching for the current selection - var currentEditor = EditorManager.getActiveEditor(); - var initialString = currentEditor && currentEditor.getSelectedText(); + var currentEditor = EditorManager.getActiveEditor(), + initialString = currentEditor && currentEditor.getSelectedText(), + dialog = new FindInFilesDialog(); searchResults = {}; currentQuery = ""; @@ -560,7 +563,7 @@ define(function (require, exports, module) { Async.doInParallel(fileListResult, function (fileInfo) { var result = new $.Deferred(); - if (!inScope(fileInfo, scope)) { + if (!_inScope(fileInfo, scope)) { result.resolve(); } else { // Search one file @@ -591,20 +594,23 @@ define(function (require, exports, module) { }); } - /** Search within the file/subtree defined by the sidebar selection */ - function doFindInSubtree() { + /** + * @private + * Search within the file/subtree defined by the sidebar selection + */ + function _doFindInSubtree() { var selectedEntry = ProjectManager.getSelectedItem(); - doFindInFiles(selectedEntry); + _doFindInFiles(selectedEntry); } /** * @private - * Shows the search results and tryes to restore the previous scroll and selection + * Shows the search results and tries to restore the previous scroll and selection */ function _restoreSearchResults() { - var scrollTop = $searchContent.scrollTop(); - var index = $selectedRow ? $selectedRow.index() : null; + var scrollTop = $searchContent.scrollTop(), + index = $selectedRow ? $selectedRow.index() : null; _showSearchResults(); @@ -635,7 +641,7 @@ define(function (require, exports, module) { } }); - // Restore the reesults if needed + // Restore the results if needed if (resultsChanged) { _restoreSearchResults(); } @@ -660,7 +666,7 @@ define(function (require, exports, module) { } }); - // Restore the reesults if needed + // Restore the results if needed if (resultsChanged) { _restoreSearchResults(); } @@ -684,6 +690,6 @@ define(function (require, exports, module) { $(ProjectManager).on("beforeProjectClose", _hideSearchResults); // 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); + 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); }); From e02bda45c1bf2f7b401a587345680f3636e6c034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Fri, 19 Jul 2013 18:45:54 -0300 Subject: [PATCH 5/9] Additional fixes --- src/search/FindInFiles.js | 55 ++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/src/search/FindInFiles.js b/src/search/FindInFiles.js index e08603ce73f..8f25a7773d0 100644 --- a/src/search/FindInFiles.js +++ b/src/search/FindInFiles.js @@ -381,7 +381,7 @@ define(function (require, exports, module) { return true; } - if (!showMatches && i < item.matches.length) { + if (showMatches && i < item.matches.length) { // Add a row for each match in the file searchItems = []; @@ -390,7 +390,7 @@ define(function (require, exports, module) { match = item.matches[i]; searchItems.push({ file: searchList.length, - item: i, + item: searchItems.length, 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), @@ -417,31 +417,31 @@ define(function (require, exports, module) { } }); + // Add the listeners for close, prev and next + $searchResults + .off(".searchList") // Remove the old events + .one("click.searchList", ".close", function () { + _hideSearchResults(); + }) + // The link to go the previous page + .one("click.searchList", ".left-triangle:not(.disabled)", function () { + currentStart -= RESULTS_PER_PAGE; + _showSearchResults(); + }) + // The link to go to the next page + .one("click.searchList", ".right-triangle:not(.disabled)", function () { + currentStart += RESULTS_PER_PAGE; + _showSearchResults(); + }); + // Insert the search results $searchContent .empty() .append(Mustache.render(searchResultsTemplate, {searchList: searchList})) - .scrollTop(0); // otherwise scroll pos from previous contents is remembered - - $searchResults.one("click", ".close", function () { - _hideSearchResults(); - }); - - // The link to go the previous page - $searchResults.one("click", ".left-triangle:not(.disabled)", function () { - currentStart -= RESULTS_PER_PAGE; - _showSearchResults(); - }); - - // The link to go to the next page - $searchResults.one("click", ".right-triangle:not(.disabled)", function () { - currentStart += RESULTS_PER_PAGE; - _showSearchResults(); - }); - - // Add the click event listener directly on the table parent - $searchContent + .scrollTop(0) // Otherwise scroll pos from previous contents is remembered .off(".searchList") // Remove the old events + + // Add the click event listener directly on the table parent .on("click.searchList", function (e) { var $row = $(e.target).closest("tr"); @@ -479,14 +479,9 @@ define(function (require, exports, module) { } }) // Add the file to the working set on double click - .on("dblclick.searchList", function (e) { - var $row = $(e.target).closest("tr"); - if ($row.length && !$row.hasClass("file-section")) { - // Grab the required item data - var item = searchList[$row.data("file")]; - - FileViewController.addToWorkingSetAndSelect(item.fullPath); - } + .on("dblclick.searchList", "tr:not(.file-section)", function (e) { + var item = searchList[$(this).data("file")]; + FileViewController.addToWorkingSetAndSelect(item.fullPath); }) // Restore the collapsed files .find(".file-section").each(function () { From 070f48827537b32963ebb159a96325bf85cbc9d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Tue, 6 Aug 2013 00:47:11 -0300 Subject: [PATCH 6/9] Fixed the arrows position. --- src/styles/brackets.less | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/styles/brackets.less b/src/styles/brackets.less index c546695b9ed..512ed602512 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -726,11 +726,12 @@ a, img { #search-result-summary { .left-triangle, .right-triangle { .jstree-sprite; + width: 10px; display: inline-block; - background-position: -2px -3px; + background-position: 0px 4px; } .left-triangle { - -webkit-transform: translateZ(0) translateY(-2px) rotate(180deg); + -webkit-transform: translateZ(0) translateY(-3px) rotate(180deg); } .disabled { opacity: 0.3; From 12e40aecad5cfb9ff3741f2c885c0c77bbee8341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Tue, 6 Aug 2013 01:39:38 -0300 Subject: [PATCH 7/9] Updated the find in files title --- src/nls/root/strings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 073dbb6b91c..0783c15fb59 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -125,7 +125,7 @@ define({ "NO_UPDATE_TITLE" : "You're up to date!", "NO_UPDATE_MESSAGE" : "You are running the latest version of {APP_NAME}.", - "FIND_IN_FILES_TITLE" : "\"{4}\" found {5} — {0} {1} in {2} {3}:", + "FIND_IN_FILES_TITLE" : "\"{4}\" found {5} — {0} {1} in {2} {3}", "FIND_IN_FILES_SCOPED" : "in {0}", "FIND_IN_FILES_NO_SCOPE" : "in project", "FIND_IN_FILES_FILE" : "file", From b37ccb2d1a7a3b868f16b7817f2fbded49b11a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Tue, 6 Aug 2013 20:38:10 -0300 Subject: [PATCH 8/9] Go to first/last page --- src/htmlContent/search-summary.html | 6 ++-- src/search/FindInFiles.js | 14 +++++++-- src/styles/brackets.less | 18 ++++++++--- src/styles/images/jsTreeSprites.svg | 46 ++++++++++++++++++++++++++--- 4 files changed, 72 insertions(+), 12 deletions(-) diff --git a/src/htmlContent/search-summary.html b/src/htmlContent/search-summary.html index 11437d9f937..4a9577b5229 100644 --- a/src/htmlContent/search-summary.html +++ b/src/htmlContent/search-summary.html @@ -1,6 +1,8 @@ {{{summary}}} {{#hasPages}} - + + {{{results}}} - + + {{/hasPages}} \ No newline at end of file diff --git a/src/search/FindInFiles.js b/src/search/FindInFiles.js index 87035bdf874..e8155d5fa73 100644 --- a/src/search/FindInFiles.js +++ b/src/search/FindInFiles.js @@ -423,15 +423,25 @@ define(function (require, exports, module) { .one("click.searchList", ".close", function () { _hideSearchResults(); }) + // The link to go the first page + .one("click.searchList", ".first-page:not(.disabled)", function () { + currentStart = 0; + _showSearchResults(); + }) // The link to go the previous page - .one("click.searchList", ".left-triangle:not(.disabled)", function () { + .one("click.searchList", ".prev-page:not(.disabled)", function () { currentStart -= RESULTS_PER_PAGE; _showSearchResults(); }) // The link to go to the next page - .one("click.searchList", ".right-triangle:not(.disabled)", function () { + .one("click.searchList", ".next-page:not(.disabled)", function () { currentStart += RESULTS_PER_PAGE; _showSearchResults(); + }) + // The link to go to the last page + .one("click.searchList", ".last-page:not(.disabled)", function () { + currentStart = Math.floor(numMatches / RESULTS_PER_PAGE) * RESULTS_PER_PAGE; + _showSearchResults(); }); // Insert the search results diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 512ed602512..ee2f98a80c2 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -724,14 +724,24 @@ a, img { /* Find in Files results panel - temporary UI, to be replaced with a richer search feature later */ #search-result-summary { - .left-triangle, .right-triangle { + .first-page, + .prev-page, + .next-page, + .last-page { .jstree-sprite; - width: 10px; + width: 6px; display: inline-block; background-position: 0px 4px; } - .left-triangle { - -webkit-transform: translateZ(0) translateY(-3px) rotate(180deg); + .first-page { + background-position: 0px -80px; + margin-left: 10px; + } + .prev-page { + background-position: 0px -24px; + } + .last-page { + background-position: 0px -52px; } .disabled { opacity: 0.3; diff --git a/src/styles/images/jsTreeSprites.svg b/src/styles/images/jsTreeSprites.svg index 215abedf14d..afaddb4a16a 100644 --- a/src/styles/images/jsTreeSprites.svg +++ b/src/styles/images/jsTreeSprites.svg @@ -1,14 +1,52 @@ - - Slice 1 + + jsTreeSprites Created with Sketch (http://www.bohemiancoding.com/sketch) - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 326463bbd74943e261452301da22f5927f435857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Malbr=C3=A1n?= Date: Tue, 6 Aug 2013 23:48:05 -0300 Subject: [PATCH 9/9] Added margin to the arrows --- src/styles/brackets.less | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/brackets.less b/src/styles/brackets.less index ee2f98a80c2..b4663b81142 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -731,6 +731,7 @@ a, img { .jstree-sprite; width: 6px; display: inline-block; + margin: 0 2px; background-position: 0px 4px; } .first-page {