From 8c52e95e27cd65252d1325f6fefa2777161864d0 Mon Sep 17 00:00:00 2001 From: Ku7eKam Date: Wed, 5 Mar 2025 15:55:57 +0530 Subject: [PATCH 1/3] fix: reset search match counter when switching files (issue #3361) --- client/utils/codemirror-search.js | 150 ++++++++++++++++++++++-------- 1 file changed, 112 insertions(+), 38 deletions(-) diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index 6108e87086..aacfc88f77 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -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) { @@ -42,6 +44,7 @@ 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; @@ -49,6 +52,7 @@ function SearchState() { this.caseInsensitive = true; this.wholeWord = false; this.replaceStarted = false; + this.lastFileName = 'sketch.js'; } function getSearchState(cm) { @@ -60,6 +64,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; @@ -88,6 +137,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) { @@ -101,8 +153,8 @@ function persistentDialog(cm, text, deflt, onEnter, replaceOpened, onKeyDown) { }); CodeMirror.on(closeButton, 'click', function () { - clearSearch(cm); dialog.parentNode.removeChild(dialog); + clearSearch(cm); cm.focus(); }); @@ -349,44 +401,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; + } } } From dabc713c70473b155efc4e6f771e4407222e32fa Mon Sep 17 00:00:00 2001 From: Ku7eKam Date: Wed, 5 Mar 2025 16:11:57 +0530 Subject: [PATCH 2/3] fix: reset search match counter when switching files (issue #3361) --- client/utils/codemirror-search.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index aacfc88f77..df56bc234a 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -52,7 +52,8 @@ function SearchState() { this.caseInsensitive = true; this.wholeWord = false; this.replaceStarted = false; - this.lastFileName = 'sketch.js'; + this.lastFileName = + document.querySelector('.editor__file-name span')?.innerText || null; } function getSearchState(cm) { From 602222b37ddf9d290856ad0ed04827f3b66c09c7 Mon Sep 17 00:00:00 2001 From: raclim Date: Mon, 10 Mar 2025 11:29:47 -0400 Subject: [PATCH 3/3] remove extra comments --- client/utils/codemirror-search.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/client/utils/codemirror-search.js b/client/utils/codemirror-search.js index df56bc234a..9e5de16e7c 100644 --- a/client/utils/codemirror-search.js +++ b/client/utils/codemirror-search.js @@ -18,7 +18,6 @@ import upArrow from '../images/up-arrow.svg?byContent'; import exitIcon from '../images/exit.svg?byContent'; function searchOverlay(query, caseInsensitive) { - // 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, '\\$&'), @@ -44,7 +43,6 @@ 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; @@ -98,7 +96,6 @@ function watchFileChanges(cm, searchState, searchField) { setupObserver(); - // continuously check for the dialog's existence (every 500ms) setInterval(() => { var searchDialog = document.querySelector('.CodeMirror-dialog'); if (!searchDialog && observer) { @@ -140,11 +137,9 @@ function persistentDialog(cm, text, deflt, onEnter, replaceOpened, onKeyDown) { 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) { - // not enter and more than 1 character to search startSearch(cm, getSearchState(cm), searchField.value); } else if (searchField.value.length < 1) { cm.display.wrapper.querySelector( @@ -376,7 +371,6 @@ function parseString(string) { function parseQuery(query, state) { var emptyQuery = 'x^'; // matches nothing if (query === '') { - // empty string matches nothing query = emptyQuery; } else { if (state.regexp === false) { @@ -409,7 +403,7 @@ function startSearch(cm, state, query) { ?.innerText; if (state.lastFileName !== currentFileName) { - state.lastFileName = currentFileName; // update stored filename + state.lastFileName = currentFileName; state.queryText = null; state.lastQuery = null; state.query = null; @@ -440,16 +434,14 @@ function startSearch(cm, state, query) { ); } - // 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 + cm.removeOverlay(state.overlay, state.caseInsensitive); } else { var next = cm.state.search.annotate.matches.findIndex((s) => {