mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/CodeMirror
synced 2024-11-23 13:56:44 +00:00
Merge "CodeMirrorSearch: catch exceptions from invalid regex input"
This commit is contained in:
commit
b61fe12dfe
|
@ -242,6 +242,7 @@
|
|||
"codemirror-prefs-title",
|
||||
"codemirror-previous",
|
||||
"codemirror-regexp",
|
||||
"codemirror-regexp-invalid",
|
||||
"codemirror-replace",
|
||||
"codemirror-replace-all",
|
||||
"codemirror-replace-placeholder",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
8
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 ];
|
||||
|
|
|
@ -23449,19 +23449,20 @@ class SearchCursor {
|
|||
let str = fromCodePoint(next), start = this.bufferStart + this.bufferPos;
|
||||
this.bufferPos += codePointSize(next);
|
||||
let norm = this.normalize(str);
|
||||
for (let i = 0, pos = start;; i++) {
|
||||
let code = norm.charCodeAt(i);
|
||||
let match = this.match(code, pos, this.bufferPos + this.bufferStart);
|
||||
if (i == norm.length - 1) {
|
||||
if (match) {
|
||||
this.value = match;
|
||||
return this;
|
||||
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);
|
||||
if (i == norm.length - 1) {
|
||||
if (match) {
|
||||
this.value = match;
|
||||
return this;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
if (pos == start && i < str.length && str.charCodeAt(i) == code)
|
||||
pos++;
|
||||
}
|
||||
if (pos == start && i < str.length && str.charCodeAt(i) == code)
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
match(code, pos, end) {
|
||||
|
@ -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));
|
||||
|
|
Loading…
Reference in a new issue