diff --git a/src/document/Document.js b/src/document/Document.js index c928a288639..809dfe69777 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -275,6 +275,17 @@ define(function (require, exports, module) { // _handleEditorChange() triggers "change" event }; + /** + * @private + * Triggers the appropriate events when a change occurs: "change" on the Document instance + * and "documentChange" on the Document module. + * @param {Object} changeList Changelist in CodeMirror format + */ + Document.prototype._notifyDocumentChange = function (changeList) { + $(this).triggerHandler("change", [this, changeList]); + $(exports).triggerHandler("documentChange", [this, changeList]); + }; + /** * Sets the contents of the document. Treated as reloading the document from disk: the document * will be marked clean with a new timestamp, the undo/redo history is cleared, and we re-check @@ -303,9 +314,7 @@ define(function (require, exports, module) { // TODO: Dumb to split it here just to join it again in the change handler, but this is // the CodeMirror change format. Should we document our change format to allow this to // either be an array of lines or a single string? - var fakeChangeList = [{text: text.split(/\r?\n/)}]; - $(this).triggerHandler("change", [this, fakeChangeList]); - $(exports).triggerHandler("documentChange", [this, fakeChangeList]); + this._notifyDocumentChange([{text: text.split(/\r?\n/)}]); } } this._updateTimestamp(newTimestamp); @@ -410,11 +419,10 @@ define(function (require, exports, module) { } // Notify that Document's text has changed - // TODO: This needs to be kept in sync with SpecRunnerUtils.createMockDocument(). In the + // TODO: This needs to be kept in sync with SpecRunnerUtils.createMockActiveDocument(). In the // future, we should fix things so that we either don't need mock documents or that this // is factored so it will just run in both. - $(this).triggerHandler("change", [this, changeList]); - $(exports).triggerHandler("documentChange", [this, changeList]); + this._notifyDocumentChange(changeList); }; /** diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index f36fd35f4a4..dfc9312ac93 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -1174,7 +1174,7 @@ define(function (require, exports, module) { } else { // Multiple unsaved files: show a single bulk prompt listing all files - var message = Strings.SAVE_CLOSE_MULTI_MESSAGE + StringUtils.makeDialogFileList(_.map(unsavedDocs, _shortTitleForDocument)); + var message = Strings.SAVE_CLOSE_MULTI_MESSAGE + FileUtils.makeDialogFileList(_.map(unsavedDocs, _shortTitleForDocument)); Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_SAVE_CLOSE, @@ -1232,9 +1232,12 @@ define(function (require, exports, module) { /** * Closes all open documents; equivalent to calling handleFileClose() for each document, except * that unsaved changes are confirmed once, in bulk. - * @param {?{promptOnly: boolean}} If true, only displays the relevant confirmation UI and does NOT + * @param {?{promptOnly: boolean, _forceClose: boolean}} + * If promptOnly is true, only displays the relevant confirmation UI and does NOT * actually close any documents. This is useful when chaining close-all together with * other user prompts that may be cancelable. + * If _forceClose is true, forces the files to close with no confirmation even if dirty. + * Should only be used for unit test cleanup. * @return {$.Promise} a promise that is resolved when all files are closed */ function handleFileCloseAll(commandData) { diff --git a/src/file/FileUtils.js b/src/file/FileUtils.js index 76356c6c8ee..4e503326e2c 100644 --- a/src/file/FileUtils.js +++ b/src/file/FileUtils.js @@ -183,6 +183,21 @@ define(function (require, exports, module) { ); } + /** + * Creates an HTML string for a list of files to be reported on, suitable for use in a dialog. + * @param {Array.} Array of filenames or paths to display. + */ + function makeDialogFileList(paths) { + var result = ""; + return result; + } + /** * Convert a URI path to a native path. * On both platforms, this unescapes the URI @@ -446,11 +461,12 @@ define(function (require, exports, module) { } /** - * Compares two paths. Useful for sorting. - * @param {string} filename1 - * @param {string} filename2 - * @param {boolean} extFirst If true it compares the extensions first and then the file names. - * @return {number} The result of the local compare function + * Compares two paths segment-by-segment, used for sorting. Sorts folders before files, + * and sorts files based on `compareFilenames()`. + * @param {string} path1 + * @param {string} path2 + * @return {number} -1, 0, or 1 depending on whether path1 is less than, equal to, or greater than + * path2 according to this ordering. */ function comparePaths(path1, path2) { var entryName1, entryName2, @@ -486,6 +502,7 @@ define(function (require, exports, module) { exports.translateLineEndings = translateLineEndings; exports.showFileOpenError = showFileOpenError; exports.getFileErrorString = getFileErrorString; + exports.makeDialogFileList = makeDialogFileList; exports.readAsText = readAsText; exports.writeText = writeText; exports.convertToNativePath = convertToNativePath; diff --git a/src/htmlContent/search-results.html b/src/htmlContent/search-results.html index 8f8126e633a..7c5a671b9cf 100644 --- a/src/htmlContent/search-results.html +++ b/src/htmlContent/search-results.html @@ -1,14 +1,14 @@ {{#searchList}} - + {{#items}} - + {{#replace}}{{/replace}} diff --git a/src/htmlContent/search-summary-paging.html b/src/htmlContent/search-summary-paging.html deleted file mode 100644 index 5d4a88d9f6d..00000000000 --- a/src/htmlContent/search-summary-paging.html +++ /dev/null @@ -1,9 +0,0 @@ -{{#hasPages}} -
- - - {{{results}}} - - -
-{{/hasPages}} diff --git a/src/htmlContent/search-summary.html b/src/htmlContent/search-summary.html index cd22ad3d897..3160d952a6f 100644 --- a/src/htmlContent/search-summary.html +++ b/src/htmlContent/search-summary.html @@ -13,7 +13,15 @@
{{{scope}}}
{{/scope}}
{{{summary}}}
-{{>paging}} +{{#hasPages}} +
+ + + {{{results}}} + + +
+{{/hasPages}} {{#replace}}
diff --git a/src/search/FindInFiles.js b/src/search/FindInFiles.js index d3243bdebf0..e085c701281 100644 --- a/src/search/FindInFiles.js +++ b/src/search/FindInFiles.js @@ -79,15 +79,15 @@ define(function (require, exports, module) { /** * @private - * Searches through the contents an returns an array of matches + * Searches through the contents and 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}>} + * @return {!Array.<{start: {line:number,ch:number}, end: {line:number,ch:number}, line: string}>} */ function _getSearchMatches(contents, queryExpr) { // Quick exit if not found or if we hit the limit if (searchModel.foundMaximum || contents.search(queryExpr) === -1) { - return null; + return []; } var match, lineNum, line, ch, matchLength, @@ -129,13 +129,14 @@ define(function (require, exports, module) { * Searches and stores the match results for the given file, if there are matches * @param {string} fullPath * @param {string} contents - * @param {RegExp} queryExpr + * @param {!RegExp} queryExpr + * @param {!Date} timestamp * @return {boolean} True iff the matches were added to the search results */ function _addSearchMatches(fullPath, contents, queryExpr, timestamp) { var matches = _getSearchMatches(contents, queryExpr); - if (matches && matches.length) { + if (matches.length) { searchModel.addResultMatches(fullPath, matches, timestamp); return true; } @@ -201,7 +202,7 @@ define(function (require, exports, module) { // Searches only over the lines that changed matches = _getSearchMatches(lines.join("\r\n"), searchModel.queryExpr); - if (matches && matches.length) { + if (matches.length) { // Updates the line numbers, since we only searched part of the file matches.forEach(function (value, key) { matches[key].start.line += change.from.line; @@ -232,7 +233,8 @@ define(function (require, exports, module) { }); if (resultsChanged) { - searchModel.fireChanged(); + // Debounce document changes since the user might be typing quickly. + searchModel.fireChanged(true); } } @@ -270,6 +272,8 @@ define(function (require, exports, module) { /** * Finds all candidate files to search in the given scope's subtree that are not binary content. Does NOT apply * the current filter yet. + * @param {?FileSystemEntry} scope Search scope, or null if whole project + * @return {$.Promise} A promise that will be resolved with the list of files in the scope. Never rejected. */ function getCandidateFiles(scope) { function filter(file) { @@ -278,8 +282,7 @@ define(function (require, exports, module) { // If the scope is a single file, just check if the file passes the filter directly rather than // trying to use ProjectManager.getAllFiles(), both for performance and because an individual - // in-memory file might be an untitled document or external file that doesn't show up in - // getAllFiles(). + // in-memory file might be an untitled document that doesn't show up in getAllFiles(). if (scope && scope.isFile) { return new $.Deferred().resolve(filter(scope) ? [scope] : []).promise(); } else { @@ -437,7 +440,8 @@ define(function (require, exports, module) { * @param {{query: string, caseSensitive: boolean, isRegexp: boolean}} queryInfo Query info object * @param {?Entry} scope Project file/subfolder to search within; else searches whole project. * @param {?string} filter A "compiled" filter as returned by FileFilters.compile(), or null for no filter - * @param {?string} replaceText If this is a replacement, the text to replace matches with. + * @param {?string} replaceText If this is a replacement, the text to replace matches with. This is just + * stored in the model for later use - the replacement is not actually performed right now. * @param {?$.Promise} candidateFilesPromise If specified, a promise that should resolve with the same set of files that * getCandidateFiles(scope) would return. * @return {$.Promise} A promise that's resolved with the search results or rejected when the find competes. @@ -487,7 +491,7 @@ define(function (require, exports, module) { // Update the search results _.forEach(searchModel.results, function (item, fullPath) { - if (fullPath.match(oldName)) { + if (fullPath.indexOf(oldName) === 0) { searchModel.results[fullPath.replace(oldName, newName)] = item; delete searchModel.results[fullPath]; resultsChanged = true; diff --git a/src/search/FindInFilesUI.js b/src/search/FindInFilesUI.js index dfd7f985191..45c88c0ea7b 100644 --- a/src/search/FindInFilesUI.js +++ b/src/search/FindInFilesUI.js @@ -25,13 +25,9 @@ /*global define, $, window, Mustache */ /* - * 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. + * UI and controller logic for find/replace across multiple files within the project. * * FUTURE: - * - Search files in working set that are *not* in the project * - Handle matches that span multiple lines */ define(function (require, exports, module) { @@ -44,6 +40,7 @@ define(function (require, exports, module) { DefaultDialogs = require("widgets/DefaultDialogs"), EditorManager = require("editor/EditorManager"), FileFilters = require("search/FileFilters"), + FileUtils = require("file/FileUtils"), FindBar = require("search/FindBar").FindBar, FindInFiles = require("search/FindInFiles"), FindUtils = require("search/FindUtils"), @@ -76,7 +73,7 @@ define(function (require, exports, module) { * @return {$.Promise} A promise that's resolved with the search results or rejected when the find competes. */ function searchAndShowResults(queryInfo, scope, filter, replaceText, candidateFilesPromise) { - FindInFiles.doSearchInScope(queryInfo, scope, filter, replaceText, candidateFilesPromise) + return FindInFiles.doSearchInScope(queryInfo, scope, filter, replaceText, candidateFilesPromise) .done(function (zeroFilesToken) { // Done searching all files: show results if (FindInFiles.searchModel.hasResults()) { @@ -263,7 +260,7 @@ define(function (require, exports, module) { // Clone the search results so that they don't get updated in the middle of the replacement. var resultsClone = _.cloneDeep(model.results), - replacedFiles = _.filter(Object.keys(resultsClone), function (path) { + replacedFiles = Object.keys(resultsClone).filter(function (path) { return FindUtils.hasCheckedMatches(resultsClone[path]); }), isRegexp = model.queryInfo.isRegexp, @@ -273,8 +270,8 @@ define(function (require, exports, module) { StatusBar.showBusyIndicator(true); FindInFiles.doReplace(resultsClone, replaceText, { forceFilesOpen: forceFilesOpen, isRegexp: isRegexp }) .fail(function (errors) { - var message = Strings.REPLACE_IN_FILES_ERRORS + StringUtils.makeDialogFileList( - _.map(errors, function (errorInfo) { + var message = Strings.REPLACE_IN_FILES_ERRORS + FileUtils.makeDialogFileList( + errors.map(function (errorInfo) { return ProjectManager.makeProjectRelativeIfPossible(errorInfo.item); }) ); @@ -370,7 +367,7 @@ define(function (require, exports, module) { var model = FindInFiles.searchModel; _resultsView = new SearchResultsView(model, "find-in-files-results", "find-in-files.results"); $(_resultsView) - .on("doReplaceAll", function () { + .on("replaceAll", function () { _finishReplaceAll(model); }) .on("close", function () { diff --git a/src/search/FindReplace.js b/src/search/FindReplace.js index 81ebd145f51..6795ae9990a 100644 --- a/src/search/FindReplace.js +++ b/src/search/FindReplace.js @@ -66,12 +66,6 @@ define(function (require, exports, module) { */ var FIND_HIGHLIGHT_MAX = 2000; - /** - * Maximum number of matches to collect for Replace All; any additional matches are not listed in the panel & are not replaced - * @const {number} - */ - var REPLACE_ALL_MAX = 10000; - /** * Instance of the currently opened document when replaceAllPanel is visible * @type {?Document} diff --git a/src/search/FindUtils.js b/src/search/FindUtils.js index 2f2431bef34..fa9872477d9 100644 --- a/src/search/FindUtils.js +++ b/src/search/FindUtils.js @@ -73,8 +73,11 @@ define(function (require, exports, module) { function _doReplaceInDocument(doc, matchInfo, replaceText, isRegexp) { // Double-check that the open document's timestamp matches the one we recorded. This // should normally never go out of sync, because if it did we wouldn't start the - // replace in the first place, but we want to double-check. This will *not* handle - // cases where the document has been edited in memory since the matchInfo was generated. + // replace in the first place (due to the fact that we immediately close the search + // results panel whenever we detect a filesystem change that affects the results), + // but we want to double-check in case we don't happen to get the change in time. + // This will *not* handle cases where the document has been edited in memory since + // the matchInfo was generated. if (doc.diskTimestamp.getTime() !== matchInfo.timestamp.getTime()) { return new $.Deferred().reject(exports.ERROR_FILE_CHANGED).promise(); } @@ -171,6 +174,8 @@ define(function (require, exports, module) { * Checks timestamps to ensure replacements are not performed in files that have changed on disk since * the original search results were generated. However, does *not* check whether edits have been performed * in in-memory documents since the search; it's up to the caller to guarantee this hasn't happened. + * (When called from the standard Find in Files UI, SearchResultsView guarantees this. If called headlessly, + * the caller needs to track changes.) * * Replacements in documents that are already open in memory at the start of the replacement are guaranteed to * happen synchronously; replacements in files on disk will return an error if the on-disk file changes between @@ -211,6 +216,7 @@ define(function (require, exports, module) { if (firstPath) { var newDoc = DocumentManager.getOpenDocumentForPath(firstPath); + // newDoc might be null if the replacement failed. if (newDoc) { DocumentManager.setCurrentDocument(newDoc); } diff --git a/src/search/SearchModel.js b/src/search/SearchModel.js index e9aa42d6687..ca3fc22e803 100644 --- a/src/search/SearchModel.js +++ b/src/search/SearchModel.js @@ -79,7 +79,7 @@ define(function (require, exports, module) { /** * The file/folder path representing the scope that this query was performed in. - * @type {string} + * @type {FileSystemEntry} */ SearchModel.prototype.scope = null; @@ -121,8 +121,8 @@ define(function (require, exports, module) { this.queryInfo = queryInfo; this.queryExpr = null; - // TODO: only apparent difference between this one and the one in FindReplace is that this one returns - // null instead of "" for a bad query, and this always returns a regexp even for simple strings. Reconcile. + // TODO: only major difference between this one and the one in FindReplace is that + // this always returns a regexp even for simple strings. Reconcile. if (!queryInfo || !queryInfo.query) { return {empty: true}; } @@ -151,12 +151,13 @@ define(function (require, exports, module) { /** * Adds the given result matches to the search results - * @param {string} fullpath - * @param {Array.} matches - * @return true if at least some matches were added, false if we've hit the limit on how many can be added + * @param {string} fullpath Full path to the file containing the matches. + * @param {!Array.} matches Array of matches, in the format returned by FindInFiles._getSearchMatches() + * @param {!Date} timestamp The timestamp of the document at the time we searched it. + * @return {boolean} true if at least some matches were added, false if we've hit the limit on how many can be added */ SearchModel.prototype.addResultMatches = function (fullpath, matches, timestamp) { - if (this.foundMaximum) { + if (this.foundMaximum || !matches.length) { return false; } @@ -175,7 +176,7 @@ define(function (require, exports, module) { }; /** - * @return true if there are any results in this model. + * @return {boolean} true if there are any results in this model. */ SearchModel.prototype.hasResults = function () { return Object.keys(this.results).length > 0; @@ -201,10 +202,7 @@ define(function (require, exports, module) { * @return {Array.} */ SearchModel.prototype.getSortedFiles = function (firstFile) { - var searchFiles = Object.keys(this.results), - self = this; - - searchFiles.sort(function (key1, key2) { + return Object.keys(this.results).sort(function (key1, key2) { if (firstFile === key1) { return -1; } else if (firstFile === key2) { @@ -212,8 +210,6 @@ define(function (require, exports, module) { } return FileUtils.comparePaths(key1, key2); }); - - return searchFiles; }; /** diff --git a/src/search/SearchResultsView.js b/src/search/SearchResultsView.js index e370a0fb93f..75de81f266f 100644 --- a/src/search/SearchResultsView.js +++ b/src/search/SearchResultsView.js @@ -23,6 +23,9 @@ /*global define, $, window, Mustache */ +/* + * Panel showing search results for a Find/Replace in Files operation. + */ define(function (require, exports, module) { "use strict"; @@ -41,13 +44,22 @@ define(function (require, exports, module) { searchPanelTemplate = require("text!htmlContent/search-panel.html"), searchResultsTemplate = require("text!htmlContent/search-results.html"), - searchPagingTemplate = require("text!htmlContent/search-summary-paging.html"), searchSummaryTemplate = require("text!htmlContent/search-summary.html"); - /** @const Constants used to define the maximum results show per page and found in a single file */ - var RESULTS_PER_PAGE = 100, - UPDATE_TIMEOUT = 400; + /** + * @const + * The maximum results to show per page. + * @type {number} + */ + var RESULTS_PER_PAGE = 100; + + /** + * @const + * Debounce time for document changes updating the search results view. + * @type {number} + */ + var UPDATE_TIMEOUT = 400; /** * @constructor @@ -74,7 +86,7 @@ define(function (require, exports, module) { /** * Array with content used in the Results Panel - * @type {Array.<{file: number, filename: string, fullPath: string, items: Array.}>} + * @type {Array.<{fileIndex: number, filename: string, fullPath: string, items: Array.}>} */ SearchResultsView.prototype._searchList = []; @@ -163,7 +175,7 @@ define(function (require, exports, module) { // Add the file to the working set on double click .on("dblclick.searchResults", ".table-container tr:not(.file-section)", function (e) { - var item = self._searchList[$(this).data("file")]; + var item = self._searchList[$(this).data("file-index")]; FileViewController.addToWorkingSetAndSelect(item.fullPath); }) @@ -178,7 +190,7 @@ define(function (require, exports, module) { $row.addClass("selected"); self._$selectedRow = $row; - var searchItem = self._searchList[$row.data("file")], + var searchItem = self._searchList[$row.data("file-index")], fullPath = searchItem.fullPath; // This is a file title row, expand/collapse on click @@ -194,7 +206,7 @@ define(function (require, exports, module) { } $titleRows.each(function () { - fullPath = self._searchList[$(this).data("file")].fullPath; + fullPath = self._searchList[$(this).data("file-index")].fullPath; searchItem = self._model.results[fullPath]; if (searchItem.collapsed !== collapsed) { @@ -214,7 +226,7 @@ define(function (require, exports, module) { // This is a file row, show the result on click } else { // Grab the required item data - var item = searchItem.items[$row.data("item")]; + var item = searchItem.items[$row.data("item-index")]; CommandManager.execute(Commands.FILE_OPEN, {fullPath: fullPath}) .done(function (doc) { @@ -241,8 +253,8 @@ define(function (require, exports, module) { }) .on("click.searchResults", ".check-one", function (e) { var $row = $(e.target).closest("tr"), - item = self._searchList[$row.data("file")], - match = self._model.results[item.fullPath].matches[$row.data("index")], + item = self._searchList[$row.data("file-index")], + match = self._model.results[item.fullPath].matches[$row.data("match-index")], $checkAll = self._panel.$panel.find(".check-all"); match.isChecked = $(this).is(":checked"); @@ -252,7 +264,7 @@ define(function (require, exports, module) { e.stopPropagation(); }) .on("click.searchResults", ".replace-checked", function (e) { - $(self).triggerHandler("doReplaceAll"); + $(self).triggerHandler("replaceAll"); }); } }; @@ -285,8 +297,8 @@ define(function (require, exports, module) { ); this._$summary.html(Mustache.render(searchSummaryTemplate, { - query: _.escape((this._model.queryInfo.query && this._model.queryInfo.query.toString()) || ""), - replaceWith: _.escape(this._model.replaceText), + query: (this._model.queryInfo.query && this._model.queryInfo.query.toString()) || "", + replaceWith: this._model.replaceText, titleLabel: this._model.isReplace ? Strings.FIND_REPLACE_TITLE_LABEL : Strings.FIND_TITLE_LABEL, scope: this._model.scope ? " " + FindUtils.labelForScope(this._model.scope) + " " : "", summary: summary, @@ -297,7 +309,7 @@ define(function (require, exports, module) { hasNext: lastIndex < count.matches, replace: this._model.isReplace, Strings: Strings - }, { paging: searchPagingTemplate })); + })); }; /** @@ -355,16 +367,16 @@ define(function (require, exports, module) { multiLine = match.start.line !== match.end.line; searchItems.push({ - file: self._searchList.length, - item: searchItems.length, - index: i, - line: match.start.line + 1, - pre: match.line.substr(0, match.start.ch), - highlight: match.line.substring(match.start.ch, multiLine ? undefined : match.end.ch), - post: multiLine ? "\u2026" : match.line.substr(match.end.ch), - start: match.start, - end: match.end, - isChecked: match.isChecked + fileIndex: self._searchList.length, + itemIndex: searchItems.length, + matchIndex: i, + line: match.start.line + 1, + pre: match.line.substr(0, match.start.ch), + highlight: match.line.substring(match.start.ch, multiLine ? undefined : match.end.ch), + post: multiLine ? "\u2026" : match.line.substr(match.end.ch), + start: match.start, + end: match.end, + isChecked: match.isChecked }); matchesCounter++; i++; @@ -381,10 +393,10 @@ define(function (require, exports, module) { ); self._searchList.push({ - file: self._searchList.length, - filename: displayFileName, - fullPath: fullPath, - items: searchItems + fileIndex: self._searchList.length, + filename: displayFileName, + fullPath: fullPath, + items: searchItems }); } }); @@ -400,7 +412,7 @@ define(function (require, exports, module) { })) // Restore the collapsed files .find(".file-section").each(function () { - var fullPath = self._searchList[$(this).data("file")].fullPath; + var fullPath = self._searchList[$(this).data("file-index")].fullPath; if (self._model.results[fullPath].collapsed) { self._model.results[fullPath].collapsed = false; @@ -421,6 +433,8 @@ define(function (require, exports, module) { * Updates the results view after a model change, preserving scroll position and selection. */ SearchResultsView.prototype._updateResults = function () { + // In general this shouldn't get called if the panel is closed, but in case some + // asynchronous process kicks this (e.g. a debounced model change), we double-check. if (this._panel.isVisible()) { var scrollTop = this._$table.scrollTop(), index = this._$selectedRow ? this._$selectedRow.index() : null, @@ -442,7 +456,7 @@ define(function (require, exports, module) { /** * @private - * Returns the last result index displayed + * Returns one past the last result index displayed for the current page. * @param {number} numMatches * @return {number} */ diff --git a/src/utils/Async.js b/src/utils/Async.js index 19d69a755cd..b90dd3a6e2c 100644 --- a/src/utils/Async.js +++ b/src/utils/Async.js @@ -422,8 +422,9 @@ define(function (require, exports, module) { } /** - * Utility for converting a method that takes an errback to one that returns a promise; useful - * for using FileSystem methods in a promise-oriented workflow. For example, instead of + * Utility for converting a method that takes (error, callback) to one that returns a promise; + * useful for using FileSystem methods (or other Node-style API methods) in a promise-oriented + * workflow. For example, instead of * * var deferred = new $.Deferred(); * file.read(function (err, contents) { diff --git a/src/utils/StringUtils.js b/src/utils/StringUtils.js index cec90be7122..2f709a4be68 100644 --- a/src/utils/StringUtils.js +++ b/src/utils/StringUtils.js @@ -198,21 +198,6 @@ define(function (require, exports, module) { return returnVal; } - /** - * Creates an HTML string for a list of files to be reported on, suitable for use in a dialog. - * @param {Array.} Array of filenames or paths to display. - */ - function makeDialogFileList(paths) { - var result = "
    "; - paths.forEach(function (path) { - result += "
  • "; - result += breakableUrl(path); - result += "
  • "; - }); - result += "
"; - return result; - } - // Define public API exports.format = format; exports.htmlEscape = htmlEscape; @@ -224,5 +209,4 @@ define(function (require, exports, module) { exports.breakableUrl = breakableUrl; exports.endsWith = endsWith; exports.prettyPrintBytes = prettyPrintBytes; - exports.makeDialogFileList = makeDialogFileList; }); diff --git a/test/spec/FindInFiles-test.js b/test/spec/FindInFiles-test.js index ce0c3437294..ce4fe5a9a65 100644 --- a/test/spec/FindInFiles-test.js +++ b/test/spec/FindInFiles-test.js @@ -211,7 +211,7 @@ define(function (require, exports, module) { expectedMessage = StringUtils.format(Strings.FILTER_FILE_COUNT_ALL, 0, Strings.FIND_IN_FILES_NO_SCOPE); }); - // Message loads asynchronously, but dialog should evetually state: "Allows all 0 files in project" + // Message loads asynchronously, but dialog should eventually state: "Allows all 0 files in project" waitsFor(function () { actualMessage = $dlg.find(".exclusions-filecount").text(); return (actualMessage === expectedMessage); @@ -541,16 +541,16 @@ define(function (require, exports, module) { // Check for presence of file and first/last item rows within each file options.matchRanges.forEach(function (range) { - var $fileRow = $("#find-in-files-results tr.file-section[data-file='" + range.file + "']"); + var $fileRow = $("#find-in-files-results tr.file-section[data-file-index='" + range.file + "']"); expect($fileRow.length).toBe(1); expect($fileRow.find(".dialog-filename").text()).toEqual(range.filename); - var $firstMatchRow = $("#find-in-files-results tr[data-file='" + range.file + "'][data-item='" + range.first + "']"); + var $firstMatchRow = $("#find-in-files-results tr[data-file-index='" + range.file + "'][data-item-index='" + range.first + "']"); expect($firstMatchRow.length).toBe(1); expect($firstMatchRow.find(".line-number").text().match("\\b" + range.firstLine + "\\b")).toBeTruthy(); expect($firstMatchRow.find(".line-text").text().match(range.pattern)).toBeTruthy(); - var $lastMatchRow = $("#find-in-files-results tr[data-file='" + range.file + "'][data-item='" + range.last + "']"); + var $lastMatchRow = $("#find-in-files-results tr[data-file-index='" + range.file + "'][data-item-index='" + range.last + "']"); expect($lastMatchRow.length).toBe(1); expect($lastMatchRow.find(".line-number").text().match("\\b" + range.lastLine + "\\b")).toBeTruthy(); expect($lastMatchRow.find(".line-text").text().match(range.pattern)).toBeTruthy(); diff --git a/test/spec/SpecRunnerUtils.js b/test/spec/SpecRunnerUtils.js index 9dc49d32540..f782c317c77 100644 --- a/test/spec/SpecRunnerUtils.js +++ b/test/spec/SpecRunnerUtils.js @@ -334,7 +334,7 @@ define(function (require, exports, module) { // TODO: This needs to be kept in sync with Document._handleEditorChange(). In the // future, we should fix things so that we either don't need mock documents or that this // is factored so it will just run in both. - $(this).triggerHandler("change", [this, changeList]); + this._notifyDocumentChange(changeList); }; docToShim.notifySaved = function () { throw new Error("Cannot notifySaved() a unit-test dummy Document");
{{{filename}}}
{{line}} {{pre}}{{highlight}}{{post}}