Skip to content

Fixing Search (CTRL+F) Doesn't Auto-Update After Content Changes Dyna… #3368

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 121 additions & 38 deletions client/utils/codemirror-search.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ import upArrow from '../images/up-arrow.svg?byContent';
import exitIcon from '../images/exit.svg?byContent';

function searchOverlay(query, caseInsensitive) {
if (typeof query == 'string')
// if the query is a string, we need to convert it into a regular expression
if (typeof query == 'string') {
query = new RegExp(
query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'),
caseInsensitive ? 'gi' : 'g'
);
else if (!query.global)
} else if (!query.global) {
query = new RegExp(query.source, query.ignoreCase ? 'gi' : 'g');
}

return {
token: function (stream) {
Expand All @@ -42,13 +44,16 @@ function searchOverlay(query, caseInsensitive) {
};
}

// SearchState is a constructor function that initializes an object to keep track of search-related settings
function SearchState() {
this.posFrom = this.posTo = this.lastQuery = this.query = null;
this.overlay = null;
this.regexp = false;
this.caseInsensitive = true;
this.wholeWord = false;
this.replaceStarted = false;
this.lastFileName =
document.querySelector('.editor__file-name span')?.innerText || null;
}

function getSearchState(cm) {
Expand All @@ -60,6 +65,51 @@ function getSearchCursor(cm, query, pos) {
return cm.getSearchCursor(query, pos, getSearchState(cm).caseInsensitive);
}

function watchFileChanges(cm, searchState, searchField) {
let observer = null;

function setupObserver() {
var fileNameElement = document.querySelector('.editor__file-name span');

if (!fileNameElement) {
setTimeout(setupObserver, 500);
return;
}

if (observer) {
return;
}

observer = new MutationObserver(() => {
if (searchField.value.length > 1) {
startSearch(cm, searchState, searchField.value);
}
});

observer.observe(fileNameElement, { characterData: true, subtree: true });
}

function disconnectObserver() {
if (observer) {
observer.disconnect();
observer = null;
}
}

setupObserver();

// continuously check for the dialog's existence (every 500ms)
setInterval(() => {
var searchDialog = document.querySelector('.CodeMirror-dialog');
if (!searchDialog && observer) {
disconnectObserver();
return;
} else if (searchDialog && !observer) {
setupObserver();
}
}, 500);
}

function isMouseClick(event) {
if (event.detail > 0) return true;
else return false;
Expand Down Expand Up @@ -88,6 +138,9 @@ function persistentDialog(cm, text, deflt, onEnter, replaceOpened, onKeyDown) {

var state = getSearchState(cm);

watchFileChanges(cm, getSearchState(cm), searchField);

// this runs when the user types in the search box
CodeMirror.on(searchField, 'keyup', function (e) {
state.replaceStarted = false;
if (e.keyCode !== 13 && searchField.value.length > 1) {
Expand All @@ -101,8 +154,8 @@ function persistentDialog(cm, text, deflt, onEnter, replaceOpened, onKeyDown) {
});

CodeMirror.on(closeButton, 'click', function () {
clearSearch(cm);
dialog.parentNode.removeChild(dialog);
clearSearch(cm);
cm.focus();
});

Expand Down Expand Up @@ -349,44 +402,66 @@ function parseQuery(query, state) {
}

function startSearch(cm, state, query) {
state.queryText = query;
state.lastQuery = query;
state.query = parseQuery(query, state);
cm.removeOverlay(state.overlay, state.caseInsensitive);
state.overlay = searchOverlay(state.query, state.caseInsensitive);
cm.addOverlay(state.overlay);
if (cm.showMatchesOnScrollbar) {
if (state.annotate) {
state.annotate.clear();
state.annotate = null;
var searchDialog = document.querySelector('.CodeMirror-dialog');
if (searchDialog) {
// check if the file has changed
let currentFileName = document.querySelector('.editor__file-name span')
?.innerText;

if (state.lastFileName !== currentFileName) {
state.lastFileName = currentFileName; // update stored filename
state.queryText = null;
state.lastQuery = null;
state.query = null;
cm.removeOverlay(state.overlay);
state.overlay = null;

if (searchDialog) {
cm.display.wrapper.querySelector(
'.CodeMirror-search-results'
).innerText = '0/0';
}
}
state.annotate = cm.showMatchesOnScrollbar(
state.query,
state.caseInsensitive
);
}

//Updating the UI everytime the search input changes
var cursor = getSearchCursor(cm, state.query);
cursor.findNext();
var num_match = cm.state.search.annotate.matches.length;
//no matches found
if (num_match == 0) {
cm.display.wrapper.querySelector(
'.CodeMirror-search-results'
).innerText = i18n.t('CodemirrorFindAndReplace.NoResults');
state.queryText = query;
state.lastQuery = query;
state.query = parseQuery(query, state);
cm.removeOverlay(state.overlay, state.caseInsensitive);
} else {
var next =
cm.state.search.annotate.matches.findIndex((s) => {
return (
s.from.ch === cursor.from().ch && s.from.line === cursor.from().line
);
}) + 1;
var text_match = next + '/' + num_match;
cm.display.wrapper.querySelector(
'.CodeMirror-search-results'
).innerText = text_match;
state.overlay = searchOverlay(state.query, state.caseInsensitive);
cm.addOverlay(state.overlay);
if (cm.showMatchesOnScrollbar) {
if (state.annotate) {
state.annotate.clear();
state.annotate = null;
}
state.annotate = cm.showMatchesOnScrollbar(
state.query,
state.caseInsensitive
);
}

// Updating the UI everytime the search input changes
var cursor = getSearchCursor(cm, state.query);
cursor.findNext();
var num_match = cm.state.search.annotate.matches.length;
// no matches found
if (num_match == 0) {
cm.display.wrapper.querySelector(
'.CodeMirror-search-results'
).innerText = i18n.t('CodemirrorFindAndReplace.NoResults');
cm.removeOverlay(state.overlay, state.caseInsensitive); // removes any existing search highlights
} else {
var next =
cm.state.search.annotate.matches.findIndex((s) => {
return (
s.from.ch === cursor.from().ch && s.from.line === cursor.from().line
);
}) + 1;
var text_match = next + '/' + num_match;
cm.display.wrapper.querySelector(
'.CodeMirror-search-results'
).innerText = text_match;
}
}
}

Expand Down Expand Up @@ -448,6 +523,14 @@ function doSearch(cm, rev, persistent, immediate, ignoreQuery) {
startSearch(cm, state, q);
findNext(cm, rev);
}

cm.on("change", function () {
var state = getSearchState(cm);
if (state.query) {
startSearch(cm, state, state.queryText);
}
});

} else {
dialog(cm, queryDialog, 'Search for:', q, function (query) {
if (query && !state.query)
Expand Down