diff --git a/src/search/FindInFiles.js b/src/search/FindInFiles.js index ce8ec3a9b03..adf2b75ea6c 100644 --- a/src/search/FindInFiles.js +++ b/src/search/FindInFiles.js @@ -84,6 +84,9 @@ define(function (require, exports, module) { */ var searchResults = {}; + /** @type {Array.} Keeps a copy of the searched files sorted by name and with the selected file first */ + var searchFiles = []; + /** @type {Panel} Bottom panel holding the search results. Initialized in htmlReady() */ var searchResultsPanel; @@ -158,6 +161,7 @@ define(function (require, exports, module) { * @private * Returns label text to indicate the search scope. Already HTML-escaped. * @param {?Entry} scope + * @return {string} */ function _labelForScope(scope) { var projName = ProjectManager.getProjectRoot().name; @@ -236,7 +240,7 @@ define(function (require, exports, module) { * @param {string} fullPath * @param {string} contents * @param {RegExp} queryExpr - * @return {boolean} True iff matches were added to the search results + * @return {boolean} True iff the matches were added to the search results */ function _addSearchMatches(fullPath, contents, queryExpr) { var matches = _getSearchMatches(contents, queryExpr); @@ -253,7 +257,48 @@ define(function (require, exports, module) { /** * @private - * Count the total number of matches and files + * Sorts the file keys to show the results from the selected file first and the rest sorted by path + */ + function _sortResultFiles() { + var selectedEntry = ProjectManager.getSelectedItem().fullPath; + searchFiles = Object.keys(searchResults); + + searchFiles.sort(function (key1, key2) { + if (selectedEntry === key1) { + return -1; + } else if (selectedEntry === key2) { + return 1; + } + + var entryName1, entryName2, + pathParts1 = key1.split("/"), + pathParts2 = key2.split("/"), + length = Math.min(pathParts1.length, pathParts2.length), + folders1 = pathParts1.length - 1, + folders2 = pathParts2.length - 1, + index = 0; + + while (index < length) { + entryName1 = pathParts1[index]; + entryName2 = pathParts2[index]; + + if (entryName1 !== entryName2) { + if (index < folders1 && index < folders2) { + return entryName1.toLocaleLowerCase().localeCompare(entryName2.toLocaleLowerCase()); + } else if (index >= folders1 && index >= folders2) { + return FileUtils.compareFilenames(entryName1, entryName2); + } + return (index >= folders1 && index < folders2) ? 1 : -1; + } + index++; + } + return 0; + }); + } + + /** + * @private + * Counts the total number of matches and files * @return {{files: number, matches: number}} */ function _countFilesMatches() { @@ -316,13 +361,16 @@ define(function (require, exports, module) { })); // Create the results template search list - var searchItems, match, i, + var searchItems, match, i, item, searchList = [], matchesCounter = 0, showMatches = false; - _.some(searchResults, function (item, fullPath) { + // Iterates throuh the files to display the results sorted by filenamess. The loop ends as soon as + // we filled the results for one page + searchFiles.some(function (fullPath) { showMatches = true; + item = searchResults[fullPath]; // 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. @@ -503,14 +551,15 @@ define(function (require, exports, module) { } FileSystem.on("change", _fileSystemChangeHandler); + } else { - _hideSearchResults(); if (dialog) { - dialog.getDialogTextField().addClass("no-results") - .removeAttr("disabled") - .get(0).select(); + dialog.getDialogTextField() + .addClass("no-results") + .removeAttr("disabled") + .get(0).select(); $(".modal-bar .no-results-message").show(); } } @@ -666,6 +715,7 @@ define(function (require, exports, module) { } if (updateResults) { timeoutID = window.setTimeout(function () { + _sortResultFiles(); _restoreSearchResults(); timeoutID = null; }, UPDATE_TIMEOUT); @@ -693,9 +743,9 @@ define(function (require, exports, module) { } /** + * @private * Used to filter out image files when building a list of file in which to * search. Ideally this would filter out ALL binary files. - * @private * @param {FileSystemEntry} entry The entry to test * @return {boolean} Whether or not the entry's contents should be searched */ @@ -729,6 +779,7 @@ define(function (require, exports, module) { }) .done(function () { // Done searching all files: show results + _sortResultFiles(); _showSearchResults(); StatusBar.hideBusyIndicator(); PerfUtils.addMeasurement(perfTimer); @@ -928,6 +979,7 @@ define(function (require, exports, module) { // Restore the results if needed if (resultsChanged) { + _sortResultFiles(); _restoreSearchResults(); } } @@ -946,7 +998,7 @@ define(function (require, exports, module) { /* * Remove existing search results that match the given entry's path - * @param {File|Directory} + * @param {(File|Directory)} entry */ function _removeSearchResultsForEntry(entry) { Object.keys(searchResults).forEach(function (fullPath) { @@ -959,8 +1011,8 @@ define(function (require, exports, module) { /* * Add new search results for this entry and all of its children - * @param {File|Directory} - * @param {jQuery.Promise} Resolves when the results have been added + * @param {(File|Directory)} entry + * @return {jQuery.Promise} Resolves when the results have been added */ function _addSearchResultsForEntry(entry) { var addedFiles = [], @@ -1025,12 +1077,13 @@ define(function (require, exports, module) { addPromise.always(function () { // Restore the results if needed if (resultsChanged) { + _sortResultFiles(); _restoreSearchResults(); } }); - }; + // Initialize items dependent on HTML DOM AppInit.htmlReady(function () { var panelHtml = Mustache.render(searchPanelTemplate, Strings);