Merge "CodeMirrorSearch: catch exceptions from invalid regex input"

This commit is contained in:
jenkins-bot 2024-11-22 23:21:05 +00:00 committed by Gerrit Code Review
commit b61fe12dfe
7 changed files with 56 additions and 26 deletions

View file

@ -242,6 +242,7 @@
"codemirror-prefs-title",
"codemirror-previous",
"codemirror-regexp",
"codemirror-regexp-invalid",
"codemirror-replace",
"codemirror-replace-all",
"codemirror-replace-placeholder",

View file

@ -30,6 +30,7 @@
"codemirror-all-tooltip": "Select all matches",
"codemirror-match-case": "Match case",
"codemirror-regexp": "Regular expression",
"codemirror-regexp-invalid": "Invalid regular expression",
"codemirror-by-word": "By word",
"codemirror-replace": "Replace",
"codemirror-replace-placeholder": "Replace",

View file

@ -35,6 +35,7 @@
"codemirror-all-tooltip": "Tooltip shown when hovering over the 'All' button in the CodeMirror search panel.",
"codemirror-match-case": "Tooltip for the 'Match case' button in the CodeMirror search panel.",
"codemirror-regexp": "Tooltip for the 'Regular expression' button in the CodeMirror search panel.",
"codemirror-regexp-invalid": "Error message shown when the regular expression entered in the CodeMirror search panel is invalid.",
"codemirror-by-word": "Tooltip for the 'By word' button in the CodeMirror search panel.",
"codemirror-replace": "Label for the 'Replace' button in the CodeMirror search panel.",
"codemirror-replace-placeholder": "Placeholder text for the 'Replace' input in the CodeMirror search panel.",

8
package-lock.json generated
View file

@ -9,7 +9,7 @@
"@codemirror/autocomplete": "6.12.0",
"@codemirror/commands": "6.2.5",
"@codemirror/language": "6.9.3",
"@codemirror/search": "6.5.6",
"@codemirror/search": "6.5.8",
"@codemirror/state": "6.2.1",
"@codemirror/view": "6.22.2",
"@lezer/highlight": "1.2.0",
@ -544,9 +544,9 @@
}
},
"node_modules/@codemirror/search": {
"version": "6.5.6",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz",
"integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==",
"version": "6.5.8",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.8.tgz",
"integrity": "sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==",
"dev": true,
"dependencies": {
"@codemirror/state": "^6.0.0",

View file

@ -21,7 +21,7 @@
"@codemirror/autocomplete": "6.12.0",
"@codemirror/commands": "6.2.5",
"@codemirror/language": "6.9.3",
"@codemirror/search": "6.5.6",
"@codemirror/search": "6.5.8",
"@codemirror/state": "6.2.1",
"@codemirror/view": "6.22.2",
"@lezer/highlight": "1.2.0",

View file

@ -37,6 +37,10 @@ class CodeMirrorSearch extends CodeMirrorPanel {
* @type {HTMLInputElement}
*/
this.searchInput = undefined;
/**
* @type {HTMLDivElement}
*/
this.searchInputWrapper = undefined;
/**
* @type {HTMLInputElement}
*/
@ -128,10 +132,11 @@ class CodeMirrorSearch extends CodeMirrorPanel {
);
this.searchInput = searchInput;
this.searchInput.setAttribute( 'main-field', 'true' );
this.searchInputWrapper = searchInputWrapper;
this.findResultsText = document.createElement( 'span' );
this.findResultsText.className = 'cm-mw-find-results';
searchInputWrapper.appendChild( this.findResultsText );
firstRow.appendChild( searchInputWrapper );
this.searchInputWrapper.appendChild( this.findResultsText );
firstRow.appendChild( this.searchInputWrapper );
this.appendPrevAndNextButtons( firstRow );
@ -374,9 +379,24 @@ class CodeMirrorSearch extends CodeMirrorPanel {
* @param {SearchQuery} [query]
*/
updateNumMatchesText( query ) {
if ( !!this.searchQuery.search && this.searchQuery.regexp && !this.searchQuery.valid ) {
this.searchInputWrapper.classList.add( 'cdx-text-input--status-error' );
this.findResultsText.textContent = mw.msg( 'codemirror-regexp-invalid' );
return;
}
const cursor = query ?
query.getCursor( this.view.state ) :
getSearchQuery( this.view.state ).getCursor( this.view.state );
// Clear error state
this.searchInputWrapper.classList.remove( 'cdx-text-input--status-error' );
// Remove messaging if there's no search query.
if ( !this.searchQuery.search ) {
this.findResultsText.textContent = '';
return;
}
let count = 0,
current = 1;
const { from, to } = this.view.state.selection.main;
@ -398,6 +418,7 @@ class CodeMirrorSearch extends CodeMirrorPanel {
*/
getTextInput( name, value = '', placeholder = '' ) {
const [ container, input ] = super.getTextInput( name, value, placeholder );
input.autocomplete = 'off';
input.addEventListener( 'change', this.commit.bind( this ) );
input.addEventListener( 'keyup', this.commit.bind( this ) );
return [ container, input ];

View file

@ -23449,6 +23449,7 @@ class SearchCursor {
let str = fromCodePoint(next), start = this.bufferStart + this.bufferPos;
this.bufferPos += codePointSize(next);
let norm = this.normalize(str);
if (norm.length)
for (let i = 0, pos = start;; i++) {
let code = norm.charCodeAt(i);
let match = this.match(code, pos, this.bufferPos + this.bufferStart);
@ -24018,9 +24019,11 @@ class StringQuery extends QueryType {
}
nextMatch(state, curFrom, curTo) {
let cursor = stringCursor(this.spec, state, curTo, state.doc.length).nextOverlapping();
if (cursor.done)
cursor = stringCursor(this.spec, state, 0, curFrom).nextOverlapping();
return cursor.done ? null : cursor.value;
if (cursor.done) {
let end = Math.min(state.doc.length, curFrom + this.spec.unquoted.length);
cursor = stringCursor(this.spec, state, 0, end).nextOverlapping();
}
return cursor.done || cursor.value.from == curFrom && cursor.value.to == curTo ? null : cursor.value;
}
// Searching in reverse is, rather than implementing an inverted search
// cursor, done by scanning chunk after chunk forward.
@ -24038,8 +24041,10 @@ class StringQuery extends QueryType {
}
}
prevMatch(state, curFrom, curTo) {
return this.prevMatchInRange(state, 0, curFrom) ||
this.prevMatchInRange(state, curTo, state.doc.length);
let found = this.prevMatchInRange(state, 0, curFrom);
if (!found)
found = this.prevMatchInRange(state, Math.max(0, curTo - this.spec.unquoted.length), state.doc.length);
return found && (found.from != curFrom || found.to != curTo) ? found : null;
}
getReplacement(_result) { return this.spec.unquote(this.spec.replace); }
matchAll(state, limit) {
@ -24283,9 +24288,10 @@ const replaceNext = /*@__PURE__*/searchCommand((view, { query }) => {
let { state } = view, { from, to } = state.selection.main;
if (state.readOnly)
return false;
let next = query.nextMatch(state, from, from);
if (!next)
let match = query.nextMatch(state, from, from);
if (!match)
return false;
let next = match;
let changes = [], selection, replacement;
let effects = [];
if (next.from == from && next.to == to) {
@ -24295,7 +24301,7 @@ const replaceNext = /*@__PURE__*/searchCommand((view, { query }) => {
effects.push(EditorView.announce.of(state.phrase("replaced match on line $", state.doc.lineAt(from).number) + "."));
}
if (next) {
let off = changes.length == 0 || changes[0].from >= next.to ? 0 : next.to - next.from - replacement.length;
let off = changes.length == 0 || changes[0].from >= match.to ? 0 : match.to - match.from - replacement.length;
selection = EditorSelection.single(next.from - off, next.to - off);
effects.push(announceMatch(view, next));
effects.push(state.facet(searchConfigFacet).scrollToMatch(selection.main, view));