Does a set of replacements in a single document in memory.
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 (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();
}
// Do the replacements in reverse document order so the offsets continue to be correct.
doc.batchOperation(function () {
matchInfo.matches.reverse().forEach(function (match) {
if (match.isChecked) {
doc.replaceRange(isRegexp ? parseDollars(replaceText, match.result) : replaceText, match.start, match.end);
}
});
});
return new $.Deferred().resolve().promise();
}
Does a set of replacements in a single file. If the file is already open in a Document in memory, will do the replacement there, otherwise does it directly on disk.
function _doReplaceInOneFile(fullPath, matchInfo, replaceText, options) {
var doc = DocumentManager.getOpenDocumentForPath(fullPath);
options = options || {};
// If we're forcing files open, or if the document is in the working set but not actually open
// yet, we want to open the file and do the replacement in memory.
if (!doc && (options.forceFilesOpen || MainViewManager.findInWorkingSet(MainViewManager.ALL_PANES, fullPath) !== -1)) {
return DocumentManager.getDocumentForPath(fullPath).then(function (newDoc) {
return _doReplaceInDocument(newDoc, matchInfo, replaceText, options.isRegexp);
});
} else if (doc) {
return _doReplaceInDocument(doc, matchInfo, replaceText, options.isRegexp);
} else {
return _doReplaceOnDisk(fullPath, matchInfo, replaceText, options.isRegexp);
}
}
Does a set of replacements in a single file on disk.
function _doReplaceOnDisk(fullPath, matchInfo, replaceText, isRegexp) {
var file = FileSystem.getFileForPath(fullPath);
return DocumentManager.getDocumentText(file, true).then(function (contents, timestamp, lineEndings) {
if (timestamp.getTime() !== matchInfo.timestamp.getTime()) {
// Return a promise that we'll reject immediately. (We can't just return the
// error since this is the success handler.)
return new $.Deferred().reject(exports.ERROR_FILE_CHANGED).promise();
}
// Note that this assumes that the matches are sorted.
// TODO: is there a more efficient way to do this in a large string?
var result = [],
lastIndex = 0;
matchInfo.matches.forEach(function (match) {
if (match.isChecked) {
result.push(contents.slice(lastIndex, match.startOffset));
result.push(isRegexp ? parseDollars(replaceText, match.result) : replaceText);
lastIndex = match.endOffset;
}
});
result.push(contents.slice(lastIndex));
var newContents = result.join("");
// TODO: duplicated logic from Document - should refactor this?
if (lineEndings === FileUtils.LINE_ENDINGS_CRLF) {
newContents = newContents.replace(/\n/g, "\r\n");
}
return Async.promisify(file, "write", newContents);
});
}
returns true if the used instant search in his preferences
function _prefInstantSearchDisabled() {
return !PreferencesManager.get("findInFiles.instantSearch");
}
returns true if the used disabled node based search in his preferences
function _prefNodeSearchDisabled() {
return !PreferencesManager.get("findInFiles.nodeSearch");
}
Returns the health data pertaining to Find in files
function getHealthReport() {
return {
prefNodeSearchDisabled : _prefNodeSearchDisabled(),
prefInstantSearchDisabled : _prefInstantSearchDisabled()
};
}
exports.parseDollars = parseDollars;
exports.hasCheckedMatches = hasCheckedMatches;
exports.performReplacements = performReplacements;
exports.labelForScope = labelForScope;
exports.parseQueryInfo = parseQueryInfo;
exports.prioritizeOpenFile = prioritizeOpenFile;
exports.getOpenFilePath = getOpenFilePath;
exports.setNodeSearchDisabled = setNodeSearchDisabled;
exports.isNodeSearchDisabled = isNodeSearchDisabled;
exports.setInstantSearchDisabled = setInstantSearchDisabled;
exports.isInstantSearchDisabled = isInstantSearchDisabled;
exports.isNodeSearchInProgress = isNodeSearchInProgress;
exports.isIndexingInProgress = isIndexingInProgress;
exports.setCollapseResults = setCollapseResults;
exports.isCollapsedResults = isCollapsedResults;
exports.getHealthReport = getHealthReport;
exports.ERROR_FILE_CHANGED = "fileChanged";
// event notification functions
exports.notifyFileFiltersChanged = notifyFileFiltersChanged;
exports.notifySearchScopeChanged = notifySearchScopeChanged;
exports.notifyNodeSearchStarted = notifyNodeSearchStarted;
exports.notifyNodeSearchFinished = notifyNodeSearchFinished;
exports.notifyIndexingStarted = notifyIndexingStarted;
exports.notifyIndexingFinished = notifyIndexingFinished;
// events raised by FindUtils
exports.SEARCH_FILE_FILTERS_CHANGED = "fileFiltersChanged";
exports.SEARCH_SCOPE_CHANGED = "searchScopeChanged";
exports.SEARCH_INDEXING_STARTED = "searchIndexingStarted";
exports.SEARCH_INDEXING_FINISHED = "searchIndexingFinished";
exports.SEARCH_COLLAPSE_RESULTS = "searchCollapseResults";
});
Returns the path of the currently open file or null if there isn't one open
function getOpenFilePath() {
var currentDoc = DocumentManager.getCurrentDocument();
return currentDoc ? currentDoc.file.fullPath : null;
}
function hasCheckedMatches(result) {
return result.matches.some(function (match) { return match.isChecked; });
}
check if results should be collapsed
function isCollapsedResults() {
return collapseResults;
}
Return true if indexing is in pregress in node
function isIndexingInProgress() {
return indexingInProgress;
}
if instant search is disabled, this will return true we can only do instant search through node
function isInstantSearchDisabled() {
return _prefNodeSearchDisabled() || _prefInstantSearchDisabled() || nodeSearchDisabled || instantSearchDisabled;
}
if node search is disabled, this will return true
function isNodeSearchDisabled() {
return _prefNodeSearchDisabled() || nodeSearchDisabled;
}
check if a search is progressing in node
function isNodeSearchInProgress() {
if (nodeSearchCount === 0) {
return false;
} else {
return true;
}
}
Returns label text to indicate the search scope. Already HTML-escaped.
function labelForScope(scope) {
if (scope) {
return StringUtils.format(
Strings.FIND_IN_FILES_SCOPED,
StringUtils.breakableUrl(
ProjectManager.makeProjectRelativeIfPossible(scope.fullPath)
)
);
} else {
return Strings.FIND_IN_FILES_NO_SCOPE;
}
}
Raises an event when the file filters applied to a search changes
function notifyFileFiltersChanged() {
exports.trigger(exports.SEARCH_FILE_FILTERS_CHANGED);
}
Notifies that a node has finished indexing the files
function notifyIndexingFinished() {
indexingInProgress = false;
exports.trigger(exports.SEARCH_INDEXING_FINISHED);
}
Notifies that a node has started indexing the files
function notifyIndexingStarted() {
indexingInProgress = true;
exports.trigger(exports.SEARCH_INDEXING_STARTED);
}
Notifies that a node search has finished so that we FindUtils can figure out if any outstanding node search requests are pendind
function notifyNodeSearchFinished() {
nodeSearchCount--;
}
Notifies that a node search has started so that we FindUtils can figure out if any outstanding node search requests are pendind
function notifyNodeSearchStarted() {
nodeSearchCount++;
}
Raises an event when the search scope changes[say search in a sub drictory in the project]
function notifySearchScopeChanged() {
exports.trigger(exports.SEARCH_SCOPE_CHANGED);
}
Given a replace string that contains $-expressions, replace them with data from the given regexp match info. NOTE: we can't just use the ordinary replace() function here because the string has been extracted from the original text and so might be missing some context that the regexp matched.
function parseDollars(replaceWith, match) {
replaceWith = replaceWith.replace(/(\$+)(\d{1,2}|&)/g, function (whole, dollars, index) {
if (dollars.length % 2 === 1) { // make sure dollar signs don't escape themselves (like $$1, $$$$&)
if (index === "&") { // handle $&
// slice the first dollar (but leave any others to get unescaped below) and return the
// whole match
return dollars.substr(1) + (match[0] || "");
} else {
// now we're sure index is an integer, so we can parse it
var parsedIndex = parseInt(index, 10);
if (parsedIndex !== 0) { // handle $n or $nn, but don't handle $0 or $00
// slice the first dollar (but leave any others to get unescaped below) and return the
// the corresponding match
return dollars.substr(1) + (match[parsedIndex] || "");
}
}
}
// this code gets called if the dollar signs escape themselves or if $0/$00 (not handled) was present
return whole; // return everything to get handled below
});
// replace escaped dollar signs (i.e. $$, $$$$, ...) with single ones (unescaping)
replaceWith = replaceWith.replace(/\$\$/g, "$");
return replaceWith;
}
Parses the given query into a regexp, and returns whether it was valid or not.
function parseQueryInfo(queryInfo) {
var queryExpr;
if (!queryInfo || !queryInfo.query) {
return {empty: true};
}
// For now, treat all matches as multiline (i.e. ^/$ match on every line, not the whole
// document). This is consistent with how single-file find works. Eventually we should add
// an option for this.
var flags = "gm";
if (!queryInfo.isCaseSensitive) {
flags += "i";
}
// Is it a (non-blank) regex?
if (queryInfo.isRegexp) {
try {
queryExpr = new RegExp(queryInfo.query, flags);
} catch (e) {
return {valid: false, error: e.message};
}
} else {
// Query is a plain string. Turn it into a regexp
queryExpr = new RegExp(StringUtils.regexEscape(queryInfo.query), flags);
}
return {valid: true, queryExpr: queryExpr};
}
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.
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);
}
}
}
}
});
}
Prioritizes the open file and then the working set files to the starting of the list of files
function prioritizeOpenFile(files, firstFile) {
var workingSetFiles = MainViewManager.getWorkingSet(MainViewManager.ALL_PANES),
workingSetFileFound = {},
fileSetWithoutWorkingSet = [],
startingWorkingFileSet = [],
propertyName = "",
i = 0;
firstFile = firstFile || "";
// Create a working set path map which indicates if a file in working set is found in file list
for (i = 0; i < workingSetFiles.length; i++) {
workingSetFileFound[workingSetFiles[i].fullPath] = false;
}
// Remove all the working set files from the filtration list
fileSetWithoutWorkingSet = files.filter(function (key) {
if (workingSetFileFound[key] !== undefined) {
workingSetFileFound[key] = true;
return false;
}
return true;
});
//push in the first file
if (workingSetFileFound[firstFile] === true) {
startingWorkingFileSet.push(firstFile);
workingSetFileFound[firstFile] = false;
}
//push in the rest of working set files already present in file list
for (propertyName in workingSetFileFound) {
if (workingSetFileFound.hasOwnProperty(propertyName) && workingSetFileFound[propertyName]) {
startingWorkingFileSet.push(propertyName);
}
}
return startingWorkingFileSet.concat(fileSetWithoutWorkingSet);
}
Set if we need to collapse all results in the results pane
function setCollapseResults(collapse) {
collapseResults = collapse;
exports.trigger(exports.SEARCH_COLLAPSE_RESULTS);
}
enable/disable instant search
function setInstantSearchDisabled(disable) {
instantSearchDisabled = disable;
}
enable/disable node based search
function setNodeSearchDisabled(disable) {
if (disable) {
// only set disable. Enabling node earch doesnt mean we have to enable instant search.
setInstantSearchDisabled(disable);
}
nodeSearchDisabled = disable;
}