function performReplacements(results, replaceText, options) {
        return Async.doInParallel_aggregateErrors(Object.keys(results), function (fullPath) {
            return _doReplaceInOneFile(fullPath, results[fullPath], replaceText, options);
        }).done(function () {
            if (options && options.forceFilesOpen) {
                // If the currently selected document wasn't modified by the search, or there is no open document,
                // then open the first modified document.
                var doc = DocumentManager.getCurrentDocument();
                if (!doc ||
                        !results[doc.file.fullPath] ||
                        !hasCheckedMatches(results[doc.file.fullPath])) {
                    // Figure out the first modified document. This logic is slightly different from
                    // SearchResultsView._getSortedFiles() because it doesn't sort the currently open file to
                    // the top. But if the currently open file were in the search results, we wouldn't be
                    // doing this anyway.
                    var sortedPaths = Object.keys(results).sort(FileUtils.comparePaths),
                        firstPath = _.find(sortedPaths, function (path) {
                            return hasCheckedMatches(results[path]);
                        });

                    if (firstPath) {
                        var newDoc = DocumentManager.getOpenDocumentForPath(firstPath);
                        // newDoc might be null if the replacement failed.
                        if (newDoc) {
                            // @todo change the `_edit` call to this:
                            //
                            ///    CommandManager.execute(Commands.FILE_OPEN, {fullPath: firstPath});
                            //
                            // The problem with doing that is that the promise returned by this
                            // function has already been resolved by `Async.doInParallel()` and
                            // `CommandManager.execute` is an asynchronous operation.
                            // An asynchronous open can't be waited on (since the promise has been
                            //  resolved already) so use the synchronous version so that the next `done`
                            //  handler is blocked until the open completes
                            MainViewManager._edit(MainViewManager.ACTIVE_PANE, newDoc);
                        }
                    }
                }
            }
        });
    }
Given a set of search results, replaces them with the given replaceText, either on disk or in memory.
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
the time performReplacements() is called and the time the replacement actually happens.

@param {Object.<fullPath: string, {matches: Array.<{start: {line:number,ch:number}, end: {line:number,ch:number}, startOffset: number, endOffset: number, line: string}>, collapsed: boolean}>} results
     The list of results to replace, as returned from _doSearch..
@param {string} replaceText The text to replace each result with.
@param {?Object} options An options object:
     forceFilesOpen: boolean - Whether to open all files in editors and do replacements there rather than doing the
         replacements on disk. Note that even if this is false, files that are already open in editors will have replacements
         done in memory.
     isRegexp: boolean - Whether the original query was a regexp. If true, $-substitution is performed on the replaceText.
@return {$.Promise} A promise that's resolved when the replacement is finished or rejected with an array of errors
     if there were one or more errors. Each individual item in the array will be a {item: string, error: string} object,
     where item is the full path to the file that could not be updated, and error is either a FileSystem error or one
     of the `FindUtils.ERROR_*` constants.