1
0
Fork 0
mirror of https://github.com/DanielnetoDotCom/YouPHPTube synced 2025-10-06 03:50:04 +02:00
This commit is contained in:
DanieL 2023-02-13 14:41:08 -03:00
parent 64c36d9f4e
commit 0d0338876d
1197 changed files with 121461 additions and 179724 deletions

View file

@ -11,6 +11,6 @@ jobs:
with:
# You should create a personal access token and store it in your repository
token: ${{ secrets.DISPATCH_AUTH }}
repo: codemirror.next
repo: dev
owner: codemirror
event_type: push

View file

@ -1,3 +1,51 @@
## 6.2.3 (2022-11-14)
### Bug fixes
Fix a bug that hid the search dialog's close button when the editor was read-only.
## 6.2.2 (2022-10-18)
### Bug fixes
When `literal` is off, \n, \r, and \t escapes are now also supported in replacement text.
Make sure search dialog inputs don't get treated as form fields when the editor is created inside a form.
Fix a bug in `RegExpCursor` that would cause it to stop matching in the middle of a line when its current match position was equal to the length of the line.
## 6.2.1 (2022-09-26)
### Bug fixes
By-word search queries will now skip any result that had word characters both before and after a match boundary.
## 6.2.0 (2022-08-25)
### New features
A new `wholeWord` search query flag can be used to limit matches to whole words.
`SearchCursor` and `RegExpCursor` now support a `test` parameter that can be used to ignore certain matches.
## 6.1.0 (2022-08-16)
### Bug fixes
Fix an infinite loop when the match position of a `RegExpCursor` ended up in the middle of an UTF16 surrogate pair.
### New features
The `literal` search option can now be set to make literal queries the default.
The new `searchPanelOpen` function can be used to find out whether the search panel is open for a given state.
## 6.0.1 (2022-07-22)
### Bug fixes
`findNext` and `findPrevious` will now return to the current result (and scroll it into view) if no other matches are found.
## 6.0.0 (2022-06-08)
### Bug fixes

View file

@ -1,13 +1,13 @@
# @codemirror/search [![NPM version](https://img.shields.io/npm/v/@codemirror/search.svg)](https://www.npmjs.org/package/@codemirror/search)
[ [**WEBSITE**](https://codemirror.net/6/) | [**DOCS**](https://codemirror.net/6/docs/ref/#search) | [**ISSUES**](https://github.com/codemirror/codemirror.next/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/search/blob/main/CHANGELOG.md) ]
[ [**WEBSITE**](https://codemirror.net/) | [**DOCS**](https://codemirror.net/docs/ref/#search) | [**ISSUES**](https://github.com/codemirror/dev/issues) | [**FORUM**](https://discuss.codemirror.net/c/next/) | [**CHANGELOG**](https://github.com/codemirror/search/blob/main/CHANGELOG.md) ]
This package implements search functionality for the
[CodeMirror](https://codemirror.net/6/) code editor.
[CodeMirror](https://codemirror.net/) code editor.
The [project page](https://codemirror.net/6/) has more information, a
number of [examples](https://codemirror.net/6/examples/) and the
[documentation](https://codemirror.net/6/docs/).
The [project page](https://codemirror.net/) has more information, a
number of [examples](https://codemirror.net/examples/) and the
[documentation](https://codemirror.net/docs/).
This code is released under an
[MIT license](https://github.com/codemirror/search/tree/main/LICENSE).

View file

@ -30,7 +30,8 @@ class SearchCursor {
[`.normalize("NFKD")`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize)
(when supported).
*/
constructor(text, query, from = 0, to = text.length, normalize) {
constructor(text, query, from = 0, to = text.length, normalize, test) {
this.test = test;
/**
The current match (only holds a meaningful value after
[`next`](https://codemirror.net/6/docs/ref/#search.SearchCursor.next) has been called and when
@ -124,6 +125,8 @@ class SearchCursor {
else
this.matches.push(1, pos);
}
if (match && this.test && !this.test(match.from, match.to, this.buffer, this.bufferPos))
match = null;
return match;
}
}
@ -144,6 +147,7 @@ class RegExpCursor {
`new RegExp`).
*/
constructor(text, query, options, from = 0, to = text.length) {
this.text = text;
this.to = to;
this.curLine = "";
/**
@ -160,10 +164,11 @@ class RegExpCursor {
if (/\\[sWDnr]|\n|\r|\[\^/.test(query))
return new MultilineRegExpCursor(text, query, options, from, to);
this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));
this.test = options === null || options === void 0 ? void 0 : options.test;
this.iter = text.iter();
let startLine = text.lineAt(from);
this.curLineStart = startLine.from;
this.matchPos = from;
this.matchPos = toCharEnd(text, from);
this.getLine(this.curLineStart);
}
getLine(skip) {
@ -194,10 +199,10 @@ class RegExpCursor {
let match = this.matchPos <= this.to && this.re.exec(this.curLine);
if (match) {
let from = this.curLineStart + match.index, to = from + match[0].length;
this.matchPos = to + (from == to ? 1 : 0);
if (from == this.curLine.length)
this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));
if (from == this.curLineStart + this.curLine.length)
this.nextLine();
if (from < to || from > this.value.to) {
if ((from < to || from > this.value.to) && (!this.test || this.test(from, to, match))) {
this.value = { from, to, match };
return this;
}
@ -248,9 +253,10 @@ class MultilineRegExpCursor {
this.to = to;
this.done = false;
this.value = empty;
this.matchPos = from;
this.matchPos = toCharEnd(text, from);
this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));
this.flat = FlattenedDoc.get(text, from, this.chunkEnd(from + 5000 /* Base */));
this.test = options === null || options === void 0 ? void 0 : options.test;
this.flat = FlattenedDoc.get(text, from, this.chunkEnd(from + 5000 /* Chunk.Base */));
}
chunkEnd(pos) {
return pos >= this.to ? this.to : this.text.lineAt(pos).to;
@ -264,24 +270,23 @@ class MultilineRegExpCursor {
this.re.lastIndex = off + 1;
match = this.re.exec(this.flat.text);
}
// If a match goes almost to the end of a noncomplete chunk, try
// again, since it'll likely be able to match more
if (match && this.flat.to < this.to && match.index + match[0].length > this.flat.text.length - 10)
match = null;
if (match) {
let from = this.flat.from + match.index, to = from + match[0].length;
this.value = { from, to, match };
this.matchPos = to + (from == to ? 1 : 0);
return this;
}
else {
if (this.flat.to == this.to) {
this.done = true;
// If a match goes almost to the end of a noncomplete chunk, try
// again, since it'll likely be able to match more
if ((this.flat.to >= this.to || match.index + match[0].length <= this.flat.text.length - 10) &&
(!this.test || this.test(from, to, match))) {
this.value = { from, to, match };
this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));
return this;
}
// Grow the flattened doc
this.flat = FlattenedDoc.get(this.text, this.flat.from, this.chunkEnd(this.flat.from + this.flat.text.length * 2));
}
if (this.flat.to == this.to) {
this.done = true;
return this;
}
// Grow the flattened doc
this.flat = FlattenedDoc.get(this.text, this.flat.from, this.chunkEnd(this.flat.from + this.flat.text.length * 2));
}
}
}
@ -298,6 +303,14 @@ function validRegExp(source) {
return false;
}
}
function toCharEnd(text, pos) {
if (pos >= text.length)
return pos;
let line = text.lineAt(pos), next;
while (pos < line.to && (next = line.text.charCodeAt(pos - line.from)) >= 0xDC00 && next < 0xE000)
pos++;
return pos;
}
function createLineDialog(view) {
let input = elt__default["default"]("input", { class: "cm-textfield", name: "line" });
@ -549,12 +562,13 @@ const selectNextOccurrence = ({ state: state$1, dispatch }) => {
const searchConfigFacet = state.Facet.define({
combine(configs) {
var _a;
return {
top: configs.reduce((val, conf) => val !== null && val !== void 0 ? val : conf.top, undefined) || false,
caseSensitive: configs.reduce((val, conf) => val !== null && val !== void 0 ? val : conf.caseSensitive, undefined) || false,
createPanel: ((_a = configs.find(c => c.createPanel)) === null || _a === void 0 ? void 0 : _a.createPanel) || (view => new SearchPanel(view))
};
return state.combineConfig(configs, {
top: false,
caseSensitive: false,
literal: false,
wholeWord: false,
createPanel: view => new SearchPanel(view)
});
}
});
/**
@ -576,17 +590,27 @@ class SearchQuery {
constructor(config) {
this.search = config.search;
this.caseSensitive = !!config.caseSensitive;
this.literal = !!config.literal;
this.regexp = !!config.regexp;
this.replace = config.replace || "";
this.valid = !!this.search && (!this.regexp || validRegExp(this.search));
this.unquoted = config.literal ? this.search : this.search.replace(/\\([nrt\\])/g, (_, ch) => ch == "n" ? "\n" : ch == "r" ? "\r" : ch == "t" ? "\t" : "\\");
this.unquoted = this.unquote(this.search);
this.wholeWord = !!config.wholeWord;
}
/**
@internal
*/
unquote(text) {
return this.literal ? text :
text.replace(/\\([nrt\\])/g, (_, ch) => ch == "n" ? "\n" : ch == "r" ? "\r" : ch == "t" ? "\t" : "\\");
}
/**
Compare this query to another query.
*/
eq(other) {
return this.search == other.search && this.replace == other.replace &&
this.caseSensitive == other.caseSensitive && this.regexp == other.regexp;
this.caseSensitive == other.caseSensitive && this.regexp == other.regexp &&
this.wholeWord == other.wholeWord;
}
/**
@internal
@ -596,10 +620,13 @@ class SearchQuery {
}
/**
Get a search cursor for this query, searching through the given
range in the given document.
range in the given state.
*/
getCursor(doc, from = 0, to = doc.length) {
return this.regexp ? regexpCursor(this, doc, from, to) : stringCursor(this, doc, from, to);
getCursor(state$1, from = 0, to) {
let st = state$1.doc ? state$1 : state.EditorState.create({ doc: state$1 });
if (to == null)
to = st.doc.length;
return this.regexp ? regexpCursor(this, st, from, to) : stringCursor(this, st, from, to);
}
}
class QueryType {
@ -607,41 +634,53 @@ class QueryType {
this.spec = spec;
}
}
function stringCursor(spec, doc, from, to) {
return new SearchCursor(doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase());
function stringCursor(spec, state, from, to) {
return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : undefined);
}
function stringWordTest(doc, categorizer) {
return (from, to, buf, bufPos) => {
if (bufPos > from || bufPos + buf.length < to) {
bufPos = Math.max(0, from - 2);
buf = doc.sliceString(bufPos, Math.min(doc.length, to + 2));
}
return (categorizer(charBefore(buf, from - bufPos)) != state.CharCategory.Word ||
categorizer(charAfter(buf, from - bufPos)) != state.CharCategory.Word) &&
(categorizer(charAfter(buf, to - bufPos)) != state.CharCategory.Word ||
categorizer(charBefore(buf, to - bufPos)) != state.CharCategory.Word);
};
}
class StringQuery extends QueryType {
constructor(spec) {
super(spec);
}
nextMatch(doc, curFrom, curTo) {
let cursor = stringCursor(this.spec, doc, curTo, doc.length).nextOverlapping();
nextMatch(state, curFrom, curTo) {
let cursor = stringCursor(this.spec, state, curTo, state.doc.length).nextOverlapping();
if (cursor.done)
cursor = stringCursor(this.spec, doc, 0, curFrom).nextOverlapping();
cursor = stringCursor(this.spec, state, 0, curFrom).nextOverlapping();
return cursor.done ? null : cursor.value;
}
// Searching in reverse is, rather than implementing inverted search
// cursor, done by scanning chunk after chunk forward.
prevMatchInRange(doc, from, to) {
prevMatchInRange(state, from, to) {
for (let pos = to;;) {
let start = Math.max(from, pos - 10000 /* ChunkSize */ - this.spec.unquoted.length);
let cursor = stringCursor(this.spec, doc, start, pos), range = null;
let start = Math.max(from, pos - 10000 /* FindPrev.ChunkSize */ - this.spec.unquoted.length);
let cursor = stringCursor(this.spec, state, start, pos), range = null;
while (!cursor.nextOverlapping().done)
range = cursor.value;
if (range)
return range;
if (start == from)
return null;
pos -= 10000 /* ChunkSize */;
pos -= 10000 /* FindPrev.ChunkSize */;
}
}
prevMatch(doc, curFrom, curTo) {
return this.prevMatchInRange(doc, 0, curFrom) ||
this.prevMatchInRange(doc, curTo, doc.length);
prevMatch(state, curFrom, curTo) {
return this.prevMatchInRange(state, 0, curFrom) ||
this.prevMatchInRange(state, curTo, state.doc.length);
}
getReplacement(_result) { return this.spec.replace; }
matchAll(doc, limit) {
let cursor = stringCursor(this.spec, doc, 0, doc.length), ranges = [];
getReplacement(_result) { return this.spec.unquote(this.spec.replace); }
matchAll(state, limit) {
let cursor = stringCursor(this.spec, state, 0, state.doc.length), ranges = [];
while (!cursor.next().done) {
if (ranges.length >= limit)
return null;
@ -649,26 +688,42 @@ class StringQuery extends QueryType {
}
return ranges;
}
highlight(doc, from, to, add) {
let cursor = stringCursor(this.spec, doc, Math.max(0, from - this.spec.unquoted.length), Math.min(to + this.spec.unquoted.length, doc.length));
highlight(state, from, to, add) {
let cursor = stringCursor(this.spec, state, Math.max(0, from - this.spec.unquoted.length), Math.min(to + this.spec.unquoted.length, state.doc.length));
while (!cursor.next().done)
add(cursor.value.from, cursor.value.to);
}
}
function regexpCursor(spec, doc, from, to) {
return new RegExpCursor(doc, spec.search, spec.caseSensitive ? undefined : { ignoreCase: true }, from, to);
function regexpCursor(spec, state, from, to) {
return new RegExpCursor(state.doc, spec.search, {
ignoreCase: !spec.caseSensitive,
test: spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : undefined
}, from, to);
}
function charBefore(str, index) {
return str.slice(state.findClusterBreak(str, index, false), index);
}
function charAfter(str, index) {
return str.slice(index, state.findClusterBreak(str, index));
}
function regexpWordTest(categorizer) {
return (_from, _to, match) => !match[0].length ||
(categorizer(charBefore(match.input, match.index)) != state.CharCategory.Word ||
categorizer(charAfter(match.input, match.index)) != state.CharCategory.Word) &&
(categorizer(charAfter(match.input, match.index + match[0].length)) != state.CharCategory.Word ||
categorizer(charBefore(match.input, match.index + match[0].length)) != state.CharCategory.Word);
}
class RegExpQuery extends QueryType {
nextMatch(doc, curFrom, curTo) {
let cursor = regexpCursor(this.spec, doc, curTo, doc.length).next();
nextMatch(state, curFrom, curTo) {
let cursor = regexpCursor(this.spec, state, curTo, state.doc.length).next();
if (cursor.done)
cursor = regexpCursor(this.spec, doc, 0, curFrom).next();
cursor = regexpCursor(this.spec, state, 0, curFrom).next();
return cursor.done ? null : cursor.value;
}
prevMatchInRange(doc, from, to) {
prevMatchInRange(state, from, to) {
for (let size = 1;; size++) {
let start = Math.max(from, to - size * 10000 /* ChunkSize */);
let cursor = regexpCursor(this.spec, doc, start, to), range = null;
let start = Math.max(from, to - size * 10000 /* FindPrev.ChunkSize */);
let cursor = regexpCursor(this.spec, state, start, to), range = null;
while (!cursor.next().done)
range = cursor.value;
if (range && (start == from || range.from > start + 10))
@ -677,18 +732,18 @@ class RegExpQuery extends QueryType {
return null;
}
}
prevMatch(doc, curFrom, curTo) {
return this.prevMatchInRange(doc, 0, curFrom) ||
this.prevMatchInRange(doc, curTo, doc.length);
prevMatch(state, curFrom, curTo) {
return this.prevMatchInRange(state, 0, curFrom) ||
this.prevMatchInRange(state, curTo, state.doc.length);
}
getReplacement(result) {
return this.spec.replace.replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
return this.spec.unquote(this.spec.replace.replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
: i == "&" ? result.match[0]
: i != "0" && +i < result.match.length ? result.match[i]
: m);
: m));
}
matchAll(doc, limit) {
let cursor = regexpCursor(this.spec, doc, 0, doc.length), ranges = [];
matchAll(state, limit) {
let cursor = regexpCursor(this.spec, state, 0, state.doc.length), ranges = [];
while (!cursor.next().done) {
if (ranges.length >= limit)
return null;
@ -696,8 +751,8 @@ class RegExpQuery extends QueryType {
}
return ranges;
}
highlight(doc, from, to, add) {
let cursor = regexpCursor(this.spec, doc, Math.max(0, from - 250 /* HighlightMargin */), Math.min(to + 250 /* HighlightMargin */, doc.length));
highlight(state, from, to, add) {
let cursor = regexpCursor(this.spec, state, Math.max(0, from - 250 /* RegExp.HighlightMargin */), Math.min(to + 250 /* RegExp.HighlightMargin */, state.doc.length));
while (!cursor.next().done)
add(cursor.value.from, cursor.value.to);
}
@ -733,6 +788,13 @@ function getSearchQuery(state) {
let curState = state.field(searchState, false);
return curState ? curState.query.spec : defaultQuery(state);
}
/**
Query whether the search panel is open in the given editor state.
*/
function searchPanelOpen(state) {
var _a;
return ((_a = state.field(searchState, false)) === null || _a === void 0 ? void 0 : _a.panel) != null;
}
class SearchState {
constructor(query, panel) {
this.query = query;
@ -757,9 +819,9 @@ const searchHighlighter = view.ViewPlugin.fromClass(class {
let builder = new state.RangeSetBuilder();
for (let i = 0, ranges = view$1.visibleRanges, l = ranges.length; i < l; i++) {
let { from, to } = ranges[i];
while (i < l - 1 && to > ranges[i + 1].from - 2 * 250 /* HighlightMargin */)
while (i < l - 1 && to > ranges[i + 1].from - 2 * 250 /* RegExp.HighlightMargin */)
to = ranges[++i].to;
query.highlight(view$1.state.doc, from, to, (from, to) => {
query.highlight(view$1.state, from, to, (from, to) => {
let selected = view$1.state.selection.ranges.some(r => r.from == from && r.to == to);
builder.add(from, to, selected ? selectedMatchMark : matchMark);
});
@ -782,9 +844,9 @@ Will wrap around to the start of the document when it reaches the
end.
*/
const findNext = searchCommand((view, { query }) => {
let { from, to } = view.state.selection.main;
let next = query.nextMatch(view.state.doc, from, to);
if (!next || next.from == from && next.to == to)
let { to } = view.state.selection.main;
let next = query.nextMatch(view.state, to, to);
if (!next)
return false;
view.dispatch({
selection: { anchor: next.from, head: next.to },
@ -800,8 +862,8 @@ before the current main selection. Will wrap past the start
of the document to start searching at the end again.
*/
const findPrevious = searchCommand((view, { query }) => {
let { state } = view, { from, to } = state.selection.main;
let range = query.prevMatch(state.doc, from, to);
let { state } = view, { from } = state.selection.main;
let range = query.prevMatch(state, from, from);
if (!range)
return false;
view.dispatch({
@ -816,7 +878,7 @@ const findPrevious = searchCommand((view, { query }) => {
Select all instances of the search query.
*/
const selectMatches = searchCommand((view, { query }) => {
let ranges = query.matchAll(view.state.doc, 1000);
let ranges = query.matchAll(view.state, 1000);
if (!ranges || !ranges.length)
return false;
view.dispatch({
@ -854,7 +916,7 @@ const replaceNext = searchCommand((view$1, { query }) => {
let { state } = view$1, { from, to } = state.selection.main;
if (state.readOnly)
return false;
let next = query.nextMatch(state.doc, from, from);
let next = query.nextMatch(state, from, from);
if (!next)
return false;
let changes = [], selection, replacement;
@ -862,7 +924,7 @@ const replaceNext = searchCommand((view$1, { query }) => {
if (next.from == from && next.to == to) {
replacement = state.toText(query.getReplacement(next));
changes.push({ from: next.from, to: next.to, insert: replacement });
next = query.nextMatch(state.doc, next.from, next.to);
next = query.nextMatch(state, next.from, next.to);
announce.push(view.EditorView.announce.of(state.phrase("replaced match on line $", state.doc.lineAt(from).number) + "."));
}
if (next) {
@ -885,7 +947,7 @@ replacement.
const replaceAll = searchCommand((view$1, { query }) => {
if (view$1.state.readOnly)
return false;
let changes = query.matchAll(view$1.state.doc, 1e9).map(match => {
let changes = query.matchAll(view$1.state, 1e9).map(match => {
let { from, to } = match;
return { from, to, insert: query.getReplacement(match) };
});
@ -903,11 +965,18 @@ function createSearchPanel(view) {
return view.state.facet(searchConfigFacet).createPanel(view);
}
function defaultQuery(state, fallback) {
var _a;
var _a, _b, _c, _d;
let sel = state.selection.main;
let selText = sel.empty || sel.to > sel.from + 100 ? "" : state.sliceDoc(sel.from, sel.to);
let caseSensitive = (_a = fallback === null || fallback === void 0 ? void 0 : fallback.caseSensitive) !== null && _a !== void 0 ? _a : state.facet(searchConfigFacet).caseSensitive;
return fallback && !selText ? fallback : new SearchQuery({ search: selText.replace(/\n/g, "\\n"), caseSensitive });
if (fallback && !selText)
return fallback;
let config = state.facet(searchConfigFacet);
return new SearchQuery({
search: ((_a = fallback === null || fallback === void 0 ? void 0 : fallback.literal) !== null && _a !== void 0 ? _a : config.literal) ? selText : selText.replace(/\n/g, "\\n"),
caseSensitive: (_b = fallback === null || fallback === void 0 ? void 0 : fallback.caseSensitive) !== null && _b !== void 0 ? _b : config.caseSensitive,
literal: (_c = fallback === null || fallback === void 0 ? void 0 : fallback.literal) !== null && _c !== void 0 ? _c : config.literal,
wholeWord: (_d = fallback === null || fallback === void 0 ? void 0 : fallback.wholeWord) !== null && _d !== void 0 ? _d : config.wholeWord
});
}
/**
Make sure the search panel is open and focused.
@ -977,6 +1046,7 @@ class SearchPanel {
"aria-label": phrase(view, "Find"),
class: "cm-textfield",
name: "search",
form: "",
"main-field": "true",
onchange: this.commit,
onkeyup: this.commit
@ -987,21 +1057,31 @@ class SearchPanel {
"aria-label": phrase(view, "Replace"),
class: "cm-textfield",
name: "replace",
form: "",
onchange: this.commit,
onkeyup: this.commit
});
this.caseField = elt__default["default"]("input", {
type: "checkbox",
name: "case",
form: "",
checked: query.caseSensitive,
onchange: this.commit
});
this.reField = elt__default["default"]("input", {
type: "checkbox",
name: "re",
form: "",
checked: query.regexp,
onchange: this.commit
});
this.wordField = elt__default["default"]("input", {
type: "checkbox",
name: "word",
form: "",
checked: query.wholeWord,
onchange: this.commit
});
function button(name, onclick, content) {
return elt__default["default"]("button", { class: "cm-button", name, onclick, type: "button" }, content);
}
@ -1012,18 +1092,19 @@ class SearchPanel {
button("select", () => selectMatches(view), [phrase(view, "all")]),
elt__default["default"]("label", null, [this.caseField, phrase(view, "match case")]),
elt__default["default"]("label", null, [this.reField, phrase(view, "regexp")]),
elt__default["default"]("label", null, [this.wordField, phrase(view, "by word")]),
...view.state.readOnly ? [] : [
elt__default["default"]("br"),
this.replaceField,
button("replace", () => replaceNext(view), [phrase(view, "replace")]),
button("replaceAll", () => replaceAll(view), [phrase(view, "replace all")]),
elt__default["default"]("button", {
name: "close",
onclick: () => closeSearchPanel(view),
"aria-label": phrase(view, "close"),
type: "button"
}, ["×"])
]
button("replaceAll", () => replaceAll(view), [phrase(view, "replace all")])
],
elt__default["default"]("button", {
name: "close",
onclick: () => closeSearchPanel(view),
"aria-label": phrase(view, "close"),
type: "button"
}, ["×"])
]);
}
commit() {
@ -1031,7 +1112,8 @@ class SearchPanel {
search: this.searchField.value,
caseSensitive: this.caseField.checked,
regexp: this.reField.checked,
replace: this.replaceField.value
wholeWord: this.wordField.checked,
replace: this.replaceField.value,
});
if (!query.eq(this.query)) {
this.query = query;
@ -1064,6 +1146,7 @@ class SearchPanel {
this.replaceField.value = query.replace;
this.caseField.checked = query.caseSensitive;
this.reField.checked = query.regexp;
this.wordField.checked = query.wholeWord;
}
mount() {
this.searchField.select();
@ -1144,6 +1227,7 @@ exports.replaceAll = replaceAll;
exports.replaceNext = replaceNext;
exports.search = search;
exports.searchKeymap = searchKeymap;
exports.searchPanelOpen = searchPanelOpen;
exports.selectMatches = selectMatches;
exports.selectNextOccurrence = selectNextOccurrence;
exports.selectSelectionMatches = selectSelectionMatches;

View file

@ -10,6 +10,7 @@ declare class SearchCursor implements Iterator<{
from: number;
to: number;
}> {
private test?;
private iter;
/**
The current match (only holds a meaningful value after
@ -43,7 +44,7 @@ declare class SearchCursor implements Iterator<{
[`.normalize("NFKD")`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize)
(when supported).
*/
constructor(text: Text, query: string, from?: number, to?: number, normalize?: (string: string) => string);
constructor(text: Text, query: string, from?: number, to?: number, normalize?: (string: string) => string, test?: ((from: number, to: number, buffer: string, bufferPos: number) => boolean) | undefined);
private peek;
/**
Look for the next match. Updates the iterator's
@ -65,6 +66,10 @@ declare class SearchCursor implements Iterator<{
}>;
}
interface RegExpCursorOptions {
ignoreCase?: boolean;
test?: (from: number, to: number, match: RegExpExecArray) => boolean;
}
/**
This class is similar to [`SearchCursor`](https://codemirror.net/6/docs/ref/#search.SearchCursor)
but searches for a regular expression pattern instead of a plain
@ -75,9 +80,11 @@ declare class RegExpCursor implements Iterator<{
to: number;
match: RegExpExecArray;
}> {
private text;
private to;
private iter;
private re;
private test?;
private curLine;
private curLineStart;
private matchPos;
@ -101,9 +108,7 @@ declare class RegExpCursor implements Iterator<{
document. `query` should be the raw pattern (as you'd pass it to
`new RegExp`).
*/
constructor(text: Text, query: string, options?: {
ignoreCase?: boolean;
}, from?: number, to?: number);
constructor(text: Text, query: string, options?: RegExpCursorOptions, from?: number, to?: number);
private getLine;
private nextLine;
/**
@ -177,6 +182,15 @@ interface SearchConfig {
*/
caseSensitive?: boolean;
/**
Whether to treat string searches literally by default (defaults to false).
*/
literal?: boolean;
/**
Controls whether the default query has by-word matching enabled.
Defaults to false.
*/
wholeWord?: boolean;
/**
Can be used to override the way the search panel is implemented.
Should create a [Panel](https://codemirror.net/6/docs/ref/#view.Panel) that contains a form
which lets the user:
@ -188,6 +202,9 @@ interface SearchConfig {
- Notice external changes to the query by reacting to the
appropriate [state effect](https://codemirror.net/6/docs/ref/#search.setSearchQuery).
- Run some of the search commands.
The field that should be focused when opening the panel must be
tagged with a `main-field=true` DOM attribute.
*/
createPanel?: (view: EditorView) => Panel;
}
@ -211,7 +228,13 @@ declare class SearchQuery {
*/
readonly caseSensitive: boolean;
/**
Then true, the search string is interpreted as a regular
By default, string search will replace `\n`, `\r`, and `\t` in
the query with newline, return, and tab characters. When this
is set to true, that behavior is disabled.
*/
readonly literal: boolean;
/**
When true, the search string is interpreted as a regular
expression.
*/
readonly regexp: boolean;
@ -226,6 +249,11 @@ declare class SearchQuery {
*/
readonly valid: boolean;
/**
When true, matches that contain words are ignored when there are
further word characters around them.
*/
readonly wholeWord: boolean;
/**
Create a query object.
*/
constructor(config: {
@ -251,6 +279,10 @@ declare class SearchQuery {
The replace text.
*/
replace?: string;
/**
Enable whole-word matching.
*/
wholeWord?: boolean;
});
/**
Compare this query to another query.
@ -258,9 +290,9 @@ declare class SearchQuery {
eq(other: SearchQuery): boolean;
/**
Get a search cursor for this query, searching through the given
range in the given document.
range in the given state.
*/
getCursor(doc: Text, from?: number, to?: number): Iterator<{
getCursor(state: EditorState | Text, from?: number, to?: number): Iterator<{
from: number;
to: number;
}>;
@ -278,6 +310,10 @@ Get the current search query from an editor state.
*/
declare function getSearchQuery(state: EditorState): SearchQuery;
/**
Query whether the search panel is open in the given editor state.
*/
declare function searchPanelOpen(state: EditorState): boolean;
/**
Open the search panel if it isn't already open, and move the
selection to the first match after the current main selection.
Will wrap around to the start of the document when it reaches the
@ -326,4 +362,4 @@ Default search-related key bindings.
*/
declare const searchKeymap: readonly KeyBinding[];
export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };
export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };

View file

@ -1,5 +1,5 @@
import { showPanel, EditorView, getPanel, Decoration, ViewPlugin, runScopeHandlers } from '@codemirror/view';
import { codePointAt, fromCodePoint, codePointSize, StateEffect, StateField, EditorSelection, Facet, combineConfig, CharCategory, RangeSetBuilder, Prec } from '@codemirror/state';
import { codePointAt, fromCodePoint, codePointSize, StateEffect, StateField, EditorSelection, Facet, combineConfig, CharCategory, RangeSetBuilder, Prec, EditorState, findClusterBreak } from '@codemirror/state';
import elt from 'crelt';
const basicNormalize = typeof String.prototype.normalize == "function"
@ -22,7 +22,8 @@ class SearchCursor {
[`.normalize("NFKD")`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize)
(when supported).
*/
constructor(text, query, from = 0, to = text.length, normalize) {
constructor(text, query, from = 0, to = text.length, normalize, test) {
this.test = test;
/**
The current match (only holds a meaningful value after
[`next`](https://codemirror.net/6/docs/ref/#search.SearchCursor.next) has been called and when
@ -116,6 +117,8 @@ class SearchCursor {
else
this.matches.push(1, pos);
}
if (match && this.test && !this.test(match.from, match.to, this.buffer, this.bufferPos))
match = null;
return match;
}
}
@ -136,6 +139,7 @@ class RegExpCursor {
`new RegExp`).
*/
constructor(text, query, options, from = 0, to = text.length) {
this.text = text;
this.to = to;
this.curLine = "";
/**
@ -152,10 +156,11 @@ class RegExpCursor {
if (/\\[sWDnr]|\n|\r|\[\^/.test(query))
return new MultilineRegExpCursor(text, query, options, from, to);
this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));
this.test = options === null || options === void 0 ? void 0 : options.test;
this.iter = text.iter();
let startLine = text.lineAt(from);
this.curLineStart = startLine.from;
this.matchPos = from;
this.matchPos = toCharEnd(text, from);
this.getLine(this.curLineStart);
}
getLine(skip) {
@ -186,10 +191,10 @@ class RegExpCursor {
let match = this.matchPos <= this.to && this.re.exec(this.curLine);
if (match) {
let from = this.curLineStart + match.index, to = from + match[0].length;
this.matchPos = to + (from == to ? 1 : 0);
if (from == this.curLine.length)
this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));
if (from == this.curLineStart + this.curLine.length)
this.nextLine();
if (from < to || from > this.value.to) {
if ((from < to || from > this.value.to) && (!this.test || this.test(from, to, match))) {
this.value = { from, to, match };
return this;
}
@ -240,9 +245,10 @@ class MultilineRegExpCursor {
this.to = to;
this.done = false;
this.value = empty;
this.matchPos = from;
this.matchPos = toCharEnd(text, from);
this.re = new RegExp(query, baseFlags + ((options === null || options === void 0 ? void 0 : options.ignoreCase) ? "i" : ""));
this.flat = FlattenedDoc.get(text, from, this.chunkEnd(from + 5000 /* Base */));
this.test = options === null || options === void 0 ? void 0 : options.test;
this.flat = FlattenedDoc.get(text, from, this.chunkEnd(from + 5000 /* Chunk.Base */));
}
chunkEnd(pos) {
return pos >= this.to ? this.to : this.text.lineAt(pos).to;
@ -256,24 +262,23 @@ class MultilineRegExpCursor {
this.re.lastIndex = off + 1;
match = this.re.exec(this.flat.text);
}
// If a match goes almost to the end of a noncomplete chunk, try
// again, since it'll likely be able to match more
if (match && this.flat.to < this.to && match.index + match[0].length > this.flat.text.length - 10)
match = null;
if (match) {
let from = this.flat.from + match.index, to = from + match[0].length;
this.value = { from, to, match };
this.matchPos = to + (from == to ? 1 : 0);
return this;
}
else {
if (this.flat.to == this.to) {
this.done = true;
// If a match goes almost to the end of a noncomplete chunk, try
// again, since it'll likely be able to match more
if ((this.flat.to >= this.to || match.index + match[0].length <= this.flat.text.length - 10) &&
(!this.test || this.test(from, to, match))) {
this.value = { from, to, match };
this.matchPos = toCharEnd(this.text, to + (from == to ? 1 : 0));
return this;
}
// Grow the flattened doc
this.flat = FlattenedDoc.get(this.text, this.flat.from, this.chunkEnd(this.flat.from + this.flat.text.length * 2));
}
if (this.flat.to == this.to) {
this.done = true;
return this;
}
// Grow the flattened doc
this.flat = FlattenedDoc.get(this.text, this.flat.from, this.chunkEnd(this.flat.from + this.flat.text.length * 2));
}
}
}
@ -290,6 +295,14 @@ function validRegExp(source) {
return false;
}
}
function toCharEnd(text, pos) {
if (pos >= text.length)
return pos;
let line = text.lineAt(pos), next;
while (pos < line.to && (next = line.text.charCodeAt(pos - line.from)) >= 0xDC00 && next < 0xE000)
pos++;
return pos;
}
function createLineDialog(view) {
let input = elt("input", { class: "cm-textfield", name: "line" });
@ -541,12 +554,13 @@ const selectNextOccurrence = ({ state, dispatch }) => {
const searchConfigFacet = /*@__PURE__*/Facet.define({
combine(configs) {
var _a;
return {
top: configs.reduce((val, conf) => val !== null && val !== void 0 ? val : conf.top, undefined) || false,
caseSensitive: configs.reduce((val, conf) => val !== null && val !== void 0 ? val : conf.caseSensitive, undefined) || false,
createPanel: ((_a = configs.find(c => c.createPanel)) === null || _a === void 0 ? void 0 : _a.createPanel) || (view => new SearchPanel(view))
};
return combineConfig(configs, {
top: false,
caseSensitive: false,
literal: false,
wholeWord: false,
createPanel: view => new SearchPanel(view)
});
}
});
/**
@ -568,17 +582,27 @@ class SearchQuery {
constructor(config) {
this.search = config.search;
this.caseSensitive = !!config.caseSensitive;
this.literal = !!config.literal;
this.regexp = !!config.regexp;
this.replace = config.replace || "";
this.valid = !!this.search && (!this.regexp || validRegExp(this.search));
this.unquoted = config.literal ? this.search : this.search.replace(/\\([nrt\\])/g, (_, ch) => ch == "n" ? "\n" : ch == "r" ? "\r" : ch == "t" ? "\t" : "\\");
this.unquoted = this.unquote(this.search);
this.wholeWord = !!config.wholeWord;
}
/**
@internal
*/
unquote(text) {
return this.literal ? text :
text.replace(/\\([nrt\\])/g, (_, ch) => ch == "n" ? "\n" : ch == "r" ? "\r" : ch == "t" ? "\t" : "\\");
}
/**
Compare this query to another query.
*/
eq(other) {
return this.search == other.search && this.replace == other.replace &&
this.caseSensitive == other.caseSensitive && this.regexp == other.regexp;
this.caseSensitive == other.caseSensitive && this.regexp == other.regexp &&
this.wholeWord == other.wholeWord;
}
/**
@internal
@ -588,10 +612,13 @@ class SearchQuery {
}
/**
Get a search cursor for this query, searching through the given
range in the given document.
range in the given state.
*/
getCursor(doc, from = 0, to = doc.length) {
return this.regexp ? regexpCursor(this, doc, from, to) : stringCursor(this, doc, from, to);
getCursor(state, from = 0, to) {
let st = state.doc ? state : EditorState.create({ doc: state });
if (to == null)
to = st.doc.length;
return this.regexp ? regexpCursor(this, st, from, to) : stringCursor(this, st, from, to);
}
}
class QueryType {
@ -599,41 +626,53 @@ class QueryType {
this.spec = spec;
}
}
function stringCursor(spec, doc, from, to) {
return new SearchCursor(doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase());
function stringCursor(spec, state, from, to) {
return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : undefined);
}
function stringWordTest(doc, categorizer) {
return (from, to, buf, bufPos) => {
if (bufPos > from || bufPos + buf.length < to) {
bufPos = Math.max(0, from - 2);
buf = doc.sliceString(bufPos, Math.min(doc.length, to + 2));
}
return (categorizer(charBefore(buf, from - bufPos)) != CharCategory.Word ||
categorizer(charAfter(buf, from - bufPos)) != CharCategory.Word) &&
(categorizer(charAfter(buf, to - bufPos)) != CharCategory.Word ||
categorizer(charBefore(buf, to - bufPos)) != CharCategory.Word);
};
}
class StringQuery extends QueryType {
constructor(spec) {
super(spec);
}
nextMatch(doc, curFrom, curTo) {
let cursor = stringCursor(this.spec, doc, curTo, doc.length).nextOverlapping();
nextMatch(state, curFrom, curTo) {
let cursor = stringCursor(this.spec, state, curTo, state.doc.length).nextOverlapping();
if (cursor.done)
cursor = stringCursor(this.spec, doc, 0, curFrom).nextOverlapping();
cursor = stringCursor(this.spec, state, 0, curFrom).nextOverlapping();
return cursor.done ? null : cursor.value;
}
// Searching in reverse is, rather than implementing inverted search
// cursor, done by scanning chunk after chunk forward.
prevMatchInRange(doc, from, to) {
prevMatchInRange(state, from, to) {
for (let pos = to;;) {
let start = Math.max(from, pos - 10000 /* ChunkSize */ - this.spec.unquoted.length);
let cursor = stringCursor(this.spec, doc, start, pos), range = null;
let start = Math.max(from, pos - 10000 /* FindPrev.ChunkSize */ - this.spec.unquoted.length);
let cursor = stringCursor(this.spec, state, start, pos), range = null;
while (!cursor.nextOverlapping().done)
range = cursor.value;
if (range)
return range;
if (start == from)
return null;
pos -= 10000 /* ChunkSize */;
pos -= 10000 /* FindPrev.ChunkSize */;
}
}
prevMatch(doc, curFrom, curTo) {
return this.prevMatchInRange(doc, 0, curFrom) ||
this.prevMatchInRange(doc, curTo, doc.length);
prevMatch(state, curFrom, curTo) {
return this.prevMatchInRange(state, 0, curFrom) ||
this.prevMatchInRange(state, curTo, state.doc.length);
}
getReplacement(_result) { return this.spec.replace; }
matchAll(doc, limit) {
let cursor = stringCursor(this.spec, doc, 0, doc.length), ranges = [];
getReplacement(_result) { return this.spec.unquote(this.spec.replace); }
matchAll(state, limit) {
let cursor = stringCursor(this.spec, state, 0, state.doc.length), ranges = [];
while (!cursor.next().done) {
if (ranges.length >= limit)
return null;
@ -641,26 +680,42 @@ class StringQuery extends QueryType {
}
return ranges;
}
highlight(doc, from, to, add) {
let cursor = stringCursor(this.spec, doc, Math.max(0, from - this.spec.unquoted.length), Math.min(to + this.spec.unquoted.length, doc.length));
highlight(state, from, to, add) {
let cursor = stringCursor(this.spec, state, Math.max(0, from - this.spec.unquoted.length), Math.min(to + this.spec.unquoted.length, state.doc.length));
while (!cursor.next().done)
add(cursor.value.from, cursor.value.to);
}
}
function regexpCursor(spec, doc, from, to) {
return new RegExpCursor(doc, spec.search, spec.caseSensitive ? undefined : { ignoreCase: true }, from, to);
function regexpCursor(spec, state, from, to) {
return new RegExpCursor(state.doc, spec.search, {
ignoreCase: !spec.caseSensitive,
test: spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : undefined
}, from, to);
}
function charBefore(str, index) {
return str.slice(findClusterBreak(str, index, false), index);
}
function charAfter(str, index) {
return str.slice(index, findClusterBreak(str, index));
}
function regexpWordTest(categorizer) {
return (_from, _to, match) => !match[0].length ||
(categorizer(charBefore(match.input, match.index)) != CharCategory.Word ||
categorizer(charAfter(match.input, match.index)) != CharCategory.Word) &&
(categorizer(charAfter(match.input, match.index + match[0].length)) != CharCategory.Word ||
categorizer(charBefore(match.input, match.index + match[0].length)) != CharCategory.Word);
}
class RegExpQuery extends QueryType {
nextMatch(doc, curFrom, curTo) {
let cursor = regexpCursor(this.spec, doc, curTo, doc.length).next();
nextMatch(state, curFrom, curTo) {
let cursor = regexpCursor(this.spec, state, curTo, state.doc.length).next();
if (cursor.done)
cursor = regexpCursor(this.spec, doc, 0, curFrom).next();
cursor = regexpCursor(this.spec, state, 0, curFrom).next();
return cursor.done ? null : cursor.value;
}
prevMatchInRange(doc, from, to) {
prevMatchInRange(state, from, to) {
for (let size = 1;; size++) {
let start = Math.max(from, to - size * 10000 /* ChunkSize */);
let cursor = regexpCursor(this.spec, doc, start, to), range = null;
let start = Math.max(from, to - size * 10000 /* FindPrev.ChunkSize */);
let cursor = regexpCursor(this.spec, state, start, to), range = null;
while (!cursor.next().done)
range = cursor.value;
if (range && (start == from || range.from > start + 10))
@ -669,18 +724,18 @@ class RegExpQuery extends QueryType {
return null;
}
}
prevMatch(doc, curFrom, curTo) {
return this.prevMatchInRange(doc, 0, curFrom) ||
this.prevMatchInRange(doc, curTo, doc.length);
prevMatch(state, curFrom, curTo) {
return this.prevMatchInRange(state, 0, curFrom) ||
this.prevMatchInRange(state, curTo, state.doc.length);
}
getReplacement(result) {
return this.spec.replace.replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
return this.spec.unquote(this.spec.replace.replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
: i == "&" ? result.match[0]
: i != "0" && +i < result.match.length ? result.match[i]
: m);
: m));
}
matchAll(doc, limit) {
let cursor = regexpCursor(this.spec, doc, 0, doc.length), ranges = [];
matchAll(state, limit) {
let cursor = regexpCursor(this.spec, state, 0, state.doc.length), ranges = [];
while (!cursor.next().done) {
if (ranges.length >= limit)
return null;
@ -688,8 +743,8 @@ class RegExpQuery extends QueryType {
}
return ranges;
}
highlight(doc, from, to, add) {
let cursor = regexpCursor(this.spec, doc, Math.max(0, from - 250 /* HighlightMargin */), Math.min(to + 250 /* HighlightMargin */, doc.length));
highlight(state, from, to, add) {
let cursor = regexpCursor(this.spec, state, Math.max(0, from - 250 /* RegExp.HighlightMargin */), Math.min(to + 250 /* RegExp.HighlightMargin */, state.doc.length));
while (!cursor.next().done)
add(cursor.value.from, cursor.value.to);
}
@ -725,6 +780,13 @@ function getSearchQuery(state) {
let curState = state.field(searchState, false);
return curState ? curState.query.spec : defaultQuery(state);
}
/**
Query whether the search panel is open in the given editor state.
*/
function searchPanelOpen(state) {
var _a;
return ((_a = state.field(searchState, false)) === null || _a === void 0 ? void 0 : _a.panel) != null;
}
class SearchState {
constructor(query, panel) {
this.query = query;
@ -749,9 +811,9 @@ const searchHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class {
let builder = new RangeSetBuilder();
for (let i = 0, ranges = view.visibleRanges, l = ranges.length; i < l; i++) {
let { from, to } = ranges[i];
while (i < l - 1 && to > ranges[i + 1].from - 2 * 250 /* HighlightMargin */)
while (i < l - 1 && to > ranges[i + 1].from - 2 * 250 /* RegExp.HighlightMargin */)
to = ranges[++i].to;
query.highlight(view.state.doc, from, to, (from, to) => {
query.highlight(view.state, from, to, (from, to) => {
let selected = view.state.selection.ranges.some(r => r.from == from && r.to == to);
builder.add(from, to, selected ? selectedMatchMark : matchMark);
});
@ -774,9 +836,9 @@ Will wrap around to the start of the document when it reaches the
end.
*/
const findNext = /*@__PURE__*/searchCommand((view, { query }) => {
let { from, to } = view.state.selection.main;
let next = query.nextMatch(view.state.doc, from, to);
if (!next || next.from == from && next.to == to)
let { to } = view.state.selection.main;
let next = query.nextMatch(view.state, to, to);
if (!next)
return false;
view.dispatch({
selection: { anchor: next.from, head: next.to },
@ -792,8 +854,8 @@ before the current main selection. Will wrap past the start
of the document to start searching at the end again.
*/
const findPrevious = /*@__PURE__*/searchCommand((view, { query }) => {
let { state } = view, { from, to } = state.selection.main;
let range = query.prevMatch(state.doc, from, to);
let { state } = view, { from } = state.selection.main;
let range = query.prevMatch(state, from, from);
if (!range)
return false;
view.dispatch({
@ -808,7 +870,7 @@ const findPrevious = /*@__PURE__*/searchCommand((view, { query }) => {
Select all instances of the search query.
*/
const selectMatches = /*@__PURE__*/searchCommand((view, { query }) => {
let ranges = query.matchAll(view.state.doc, 1000);
let ranges = query.matchAll(view.state, 1000);
if (!ranges || !ranges.length)
return false;
view.dispatch({
@ -846,7 +908,7 @@ const replaceNext = /*@__PURE__*/searchCommand((view, { query }) => {
let { state } = view, { from, to } = state.selection.main;
if (state.readOnly)
return false;
let next = query.nextMatch(state.doc, from, from);
let next = query.nextMatch(state, from, from);
if (!next)
return false;
let changes = [], selection, replacement;
@ -854,7 +916,7 @@ const replaceNext = /*@__PURE__*/searchCommand((view, { query }) => {
if (next.from == from && next.to == to) {
replacement = state.toText(query.getReplacement(next));
changes.push({ from: next.from, to: next.to, insert: replacement });
next = query.nextMatch(state.doc, next.from, next.to);
next = query.nextMatch(state, next.from, next.to);
announce.push(EditorView.announce.of(state.phrase("replaced match on line $", state.doc.lineAt(from).number) + "."));
}
if (next) {
@ -877,7 +939,7 @@ replacement.
const replaceAll = /*@__PURE__*/searchCommand((view, { query }) => {
if (view.state.readOnly)
return false;
let changes = query.matchAll(view.state.doc, 1e9).map(match => {
let changes = query.matchAll(view.state, 1e9).map(match => {
let { from, to } = match;
return { from, to, insert: query.getReplacement(match) };
});
@ -895,11 +957,18 @@ function createSearchPanel(view) {
return view.state.facet(searchConfigFacet).createPanel(view);
}
function defaultQuery(state, fallback) {
var _a;
var _a, _b, _c, _d;
let sel = state.selection.main;
let selText = sel.empty || sel.to > sel.from + 100 ? "" : state.sliceDoc(sel.from, sel.to);
let caseSensitive = (_a = fallback === null || fallback === void 0 ? void 0 : fallback.caseSensitive) !== null && _a !== void 0 ? _a : state.facet(searchConfigFacet).caseSensitive;
return fallback && !selText ? fallback : new SearchQuery({ search: selText.replace(/\n/g, "\\n"), caseSensitive });
if (fallback && !selText)
return fallback;
let config = state.facet(searchConfigFacet);
return new SearchQuery({
search: ((_a = fallback === null || fallback === void 0 ? void 0 : fallback.literal) !== null && _a !== void 0 ? _a : config.literal) ? selText : selText.replace(/\n/g, "\\n"),
caseSensitive: (_b = fallback === null || fallback === void 0 ? void 0 : fallback.caseSensitive) !== null && _b !== void 0 ? _b : config.caseSensitive,
literal: (_c = fallback === null || fallback === void 0 ? void 0 : fallback.literal) !== null && _c !== void 0 ? _c : config.literal,
wholeWord: (_d = fallback === null || fallback === void 0 ? void 0 : fallback.wholeWord) !== null && _d !== void 0 ? _d : config.wholeWord
});
}
/**
Make sure the search panel is open and focused.
@ -969,6 +1038,7 @@ class SearchPanel {
"aria-label": phrase(view, "Find"),
class: "cm-textfield",
name: "search",
form: "",
"main-field": "true",
onchange: this.commit,
onkeyup: this.commit
@ -979,21 +1049,31 @@ class SearchPanel {
"aria-label": phrase(view, "Replace"),
class: "cm-textfield",
name: "replace",
form: "",
onchange: this.commit,
onkeyup: this.commit
});
this.caseField = elt("input", {
type: "checkbox",
name: "case",
form: "",
checked: query.caseSensitive,
onchange: this.commit
});
this.reField = elt("input", {
type: "checkbox",
name: "re",
form: "",
checked: query.regexp,
onchange: this.commit
});
this.wordField = elt("input", {
type: "checkbox",
name: "word",
form: "",
checked: query.wholeWord,
onchange: this.commit
});
function button(name, onclick, content) {
return elt("button", { class: "cm-button", name, onclick, type: "button" }, content);
}
@ -1004,18 +1084,19 @@ class SearchPanel {
button("select", () => selectMatches(view), [phrase(view, "all")]),
elt("label", null, [this.caseField, phrase(view, "match case")]),
elt("label", null, [this.reField, phrase(view, "regexp")]),
elt("label", null, [this.wordField, phrase(view, "by word")]),
...view.state.readOnly ? [] : [
elt("br"),
this.replaceField,
button("replace", () => replaceNext(view), [phrase(view, "replace")]),
button("replaceAll", () => replaceAll(view), [phrase(view, "replace all")]),
elt("button", {
name: "close",
onclick: () => closeSearchPanel(view),
"aria-label": phrase(view, "close"),
type: "button"
}, ["×"])
]
button("replaceAll", () => replaceAll(view), [phrase(view, "replace all")])
],
elt("button", {
name: "close",
onclick: () => closeSearchPanel(view),
"aria-label": phrase(view, "close"),
type: "button"
}, ["×"])
]);
}
commit() {
@ -1023,7 +1104,8 @@ class SearchPanel {
search: this.searchField.value,
caseSensitive: this.caseField.checked,
regexp: this.reField.checked,
replace: this.replaceField.value
wholeWord: this.wordField.checked,
replace: this.replaceField.value,
});
if (!query.eq(this.query)) {
this.query = query;
@ -1056,6 +1138,7 @@ class SearchPanel {
this.replaceField.value = query.replace;
this.caseField.checked = query.caseSensitive;
this.reField.checked = query.regexp;
this.wordField.checked = query.wholeWord;
}
mount() {
this.searchField.select();
@ -1122,4 +1205,4 @@ const searchExtensions = [
baseTheme
];
export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };
export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };

View file

@ -1,6 +1,6 @@
{
"name": "@codemirror/search",
"version": "6.0.0",
"version": "6.2.3",
"description": "Search functionality for the CodeMirror code editor",
"scripts": {
"test": "cm-runtests",