Skip to content
This repository was archived by the owner on Sep 6, 2021. It is now read-only.
3 changes: 1 addition & 2 deletions src/nls/root/strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,7 @@ define({
"BUTTON_NO" : "No",

// Find, Replace, Find in Files
"FIND_RESULT_COUNT" : "{0} results",
"FIND_RESULT_COUNT_SINGLE" : "1 result",
"FIND_MATCH_INDEX" : "{0} of {1}",
"FIND_NO_RESULTS" : "No results",
"FIND_QUERY_PLACEHOLDER" : "Find\u2026",
"REPLACE_PLACEHOLDER" : "Replace with\u2026",
Expand Down
109 changes: 78 additions & 31 deletions src/search/FindReplace.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ define(function (require, exports, module) {
this.queryInfo = null;
this.foundAny = false;
this.marked = [];
this.resultSet = [];
this.matchIndex = -1;
this.markedCurrent = null;
}

Expand Down Expand Up @@ -139,30 +141,71 @@ define(function (require, exports, module) {
state.parsedQuery = parseQuery(queryInfo);
}
}


/**
* @private
* Show the current match index by finding matchRange in the resultSet stored
* in the search state if this is the first call for a new search query. For
* subsequent calls, just compare matchRange with the next match in the resultSet
* based on the search direction and show the next match if they are the same.
* If not, then find the match index by searching matchRange in the entire resultSet.
*
* @param {!SearchState} state The search state that has the array of search result
* @param {!{from: {line: number, ch: number}, to: {line: number, ch: number}}} matchRange - the range of current match
* @param {!boolean} searchBackwards true if searching backwards
*/
function _updateFindBarWithMatchInfo(state, matchRange, searchBackwards) {
// Bail if there is no result set.
if (!state.foundAny) {
return;
}

if (findBar) {
if (state.matchIndex === -1) {
state.matchIndex = _.findIndex(state.resultSet, matchRange);
} else {
state.matchIndex = searchBackwards ? state.matchIndex - 1 : state.matchIndex + 1;
// Adjust matchIndex for modulo wraparound
state.matchIndex = (state.matchIndex + state.resultSet.length) % state.resultSet.length;

// Confirm that we find the right matchIndex. If not, then search
// matchRange in the entire resultSet.
if (!_.isEqual(state.resultSet[state.matchIndex], matchRange)) {
state.matchIndex = _.findIndex(state.resultSet, matchRange);
}
}

if (state.matchIndex !== -1) {
// Convert to 1-based by adding one before showing the index.
findBar.showFindCount(StringUtils.format(Strings.FIND_MATCH_INDEX,
state.matchIndex + 1, state.marked.length));
}
}
}

/**
* @private
* Returns the next match for the current query (from the search state) before/after the given position. Wraps around
* the end of the document if no match is found before the end.
*
* @param {!Editor} editor The editor to search in
* @param {boolean} rev True to search backwards
* @param {boolean} searchBackwards true to search backwards
* @param {{line: number, ch: number}=} pos The position to start from. Defaults to the current primary selection's
* head cursor position.
* @param {boolean=} wrap Whether to wrap the search around if we hit the end of the document. Default true.
* @return {?{start: {line: number, ch: number}, end: {line: number, ch: number}}} The range for the next match, or
* null if there is no match.
*/
function _getNextMatch(editor, rev, pos, wrap) {
function _getNextMatch(editor, searchBackwards, pos, wrap) {
var cm = editor._codeMirror;
var state = getSearchState(cm);
var cursor = getSearchCursor(cm, state, pos || editor.getCursorPos(false, rev ? "start" : "end"));
var cursor = getSearchCursor(cm, state, pos || editor.getCursorPos(false, searchBackwards ? "start" : "end"));

state.lastMatch = cursor.find(rev);
state.lastMatch = cursor.find(searchBackwards);
if (!state.lastMatch && wrap !== false) {
// If no result found before hitting edge of file, try wrapping around
cursor = getSearchCursor(cm, state, rev ? {line: cm.lineCount() - 1} : {line: 0, ch: 0});
state.lastMatch = cursor.find(rev);
cursor = getSearchCursor(cm, state, searchBackwards ? {line: cm.lineCount() - 1} : {line: 0, ch: 0});
state.lastMatch = cursor.find(searchBackwards);
}
if (!state.lastMatch) {
// No result found, period: clear selection & bail
Expand Down Expand Up @@ -377,24 +420,26 @@ define(function (require, exports, module) {
}

/**
* Selects the next match (or prev match, if rev==true) starting from either the current position
* Selects the next match (or prev match, if searchBackwards==true) starting from either the current position
* (if pos unspecified) or the given position (if pos specified explicitly). The starting position
* need not be an existing match. If a new match is found, sets to state.lastMatch either the regex
* match result, or simply true for a plain-string match. If no match found, sets state.lastMatch
* to false.
* @param {!Editor} editor
* @param {?boolean} rev
* @param {?boolean} searchBackwards
* @param {?boolean} preferNoScroll
* @param {?Pos} pos
*/
function findNext(editor, rev, preferNoScroll, pos) {
function findNext(editor, searchBackwards, preferNoScroll, pos) {
var cm = editor._codeMirror;
cm.operation(function () {
var state = getSearchState(cm);
clearCurrentMatchHighlight(cm, state);

var nextMatch = _getNextMatch(editor, rev, pos);
var nextMatch = _getNextMatch(editor, searchBackwards, pos);
if (nextMatch) {
_updateFindBarWithMatchInfo(getSearchState(editor._codeMirror),
{from: nextMatch.start, to: nextMatch.end}, searchBackwards);
_selectAndScrollTo(editor, [nextMatch], true, preferNoScroll);
state.markedCurrent = cm.markText(nextMatch.start, nextMatch.end,
{ className: "searching-current-match", startStyle: "searching-first", endStyle: "searching-last" });
Expand All @@ -416,6 +461,9 @@ define(function (require, exports, module) {
state.markedCurrent = null;

ScrollTrackMarkers.clear();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function needs jsDoc clearHighlights

state.resultSet = [];
state.matchIndex = -1;
}

function clearSearch(cm) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs jsDoc

Expand Down Expand Up @@ -477,37 +525,36 @@ define(function (require, exports, module) {
var cursor = getSearchCursor(cm, state);
if (cm.getValue().length <= FIND_MAX_FILE_SIZE) {
// FUTURE: if last query was prefix of this one, could optimize by filtering last result set
var resultSet = [];
state.resultSet = [];
while (cursor.findNext()) {
resultSet.push(cursor.pos); // pos is unique obj per search result
state.resultSet.push(cursor.pos); // pos is unique obj per search result
}

// Highlight all matches if there aren't too many
if (resultSet.length <= FIND_HIGHLIGHT_MAX) {
if (state.resultSet.length <= FIND_HIGHLIGHT_MAX) {
toggleHighlighting(editor, true);

resultSet.forEach(function (result) {
state.resultSet.forEach(function (result) {
state.marked.push(cm.markText(result.from, result.to,
{ className: "CodeMirror-searching", startStyle: "searching-first", endStyle: "searching-last" }));
});
var scrollTrackPositions = resultSet.map(function (result) {
var scrollTrackPositions = state.resultSet.map(function (result) {
return result.from;
});

ScrollTrackMarkers.addTickmarks(editor, scrollTrackPositions);
}

var countInfo;
if (resultSet.length === 0) {
countInfo = Strings.FIND_NO_RESULTS;
} else if (resultSet.length === 1) {
countInfo = Strings.FIND_RESULT_COUNT_SINGLE;
} else {
countInfo = StringUtils.format(Strings.FIND_RESULT_COUNT, resultSet.length);
// Here we only update find bar with no result. In the case of a match
// a findNext() call is guaranteed to be followed by this function call,
// and findNext() in turn calls _updateFindBarWithMatchInfo() to show the
// match index.
if (state.resultSet.length === 0) {
findBar.showFindCount(Strings.FIND_NO_RESULTS);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to add a note here that findNext()'s call to _showMatchIndex() is what upates this label in the other case -- and that any call to updateResultSet() is guaranteed to be followed by a call to findNext() (unless there's no query, in which case we've already cleared the find count label above).

}
findBar.showFindCount(countInfo);
state.foundAny = (resultSet.length > 0);
indicateHasMatches(resultSet.length);

state.foundAny = (state.resultSet.length > 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like this isn't really necessary since state.reslultSet.lengh can just be used. You could add a getter for foundAny if this is just to make life easier in other places:

this.__defineGetter__("foundAny", function() { return this.resultSet.length > 0; });

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like foundAny has other uses so disregard that

indicateHasMatches(state.resultSet.length);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

state.foundAny should be passed here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JeffryBooher The method actually needs the count, not just a boolean

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thrown off by the name of the function. Maybe we could clean that up? "has" would seem to indicated it takes a boolean.


} else {
// On huge documents, just look for first match & then stop
Expand Down Expand Up @@ -593,8 +640,8 @@ define(function (require, exports, module) {
.on("queryChange.FindReplace", function (e) {
handleQueryChange(editor, state);
})
.on("doFind.FindReplace", function (e, rev) {
findNext(editor, rev);
.on("doFind.FindReplace", function (e, searchBackwards) {
findNext(editor, searchBackwards);
})
.on("close.FindReplace", function (e) {
// Clear highlights but leave search state in place so Find Next/Previous work after closing
Expand All @@ -612,12 +659,12 @@ define(function (require, exports, module) {

/**
* If no search pending, opens the Find dialog. If search bar already open, moves to
* next/prev result (depending on 'rev')
* next/prev result (depending on 'searchBackwards')
*/
function doSearch(editor, rev) {
function doSearch(editor, searchBackwards) {
var state = getSearchState(editor._codeMirror);
if (state.parsedQuery) {
findNext(editor, rev);
findNext(editor, searchBackwards);
return;
}

Expand Down
Loading