New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@codemirror/search

Package Overview
Dependencies
Maintainers
2
Versions
38
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@codemirror/search - npm Package Compare versions

Comparing version 0.18.1 to 0.18.2

16

CHANGELOG.md

@@ -0,1 +1,17 @@

## 0.18.2 (2021-03-19)
### Bug fixes
The search interface and cursor will no longer include overlapping matches (aligning with what all other editors are doing).
### New features
The package now exports a `RegExpCursor` which is a search cursor that matches regular expression patterns.
The search/replace interface now allows the user to use regular expressions.
The `SearchCursor` class now has a `nextOverlapping` method that includes matches that start inside the previous match.
Basic backslash escapes (\n, \r, \t, and \\) are now accepted in string search patterns in the UI.
## 0.18.1 (2021-03-15)

@@ -2,0 +18,0 @@

33

dist/index.d.ts

@@ -55,5 +55,36 @@ import { Command, KeyBinding } from '@codemirror/view';

next(): this;
/**
The `next` method will ignore matches that partially overlap a
previous match. This method behaves like `next`, but includes
such matches.
*/
nextOverlapping(): this;
private match;
}
declare class RegExpCursor implements Iterator<{
from: number;
to: number;
match: RegExpExecArray;
}> {
private to;
private iter;
private re;
private curLine;
private curLineStart;
private matchPos;
done: boolean;
value: {
from: number;
to: number;
match: RegExpExecArray;
};
constructor(text: Text, query: string, options?: {
ignoreCase?: boolean;
}, from?: number, to?: number);
private getLine;
private nextLine;
next(): this;
}
/**

@@ -146,2 +177,2 @@ Command that shows a dialog asking the user for a line number, and

export { SearchCursor, closeSearchPanel, findNext, findPrevious, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, searchKeymap, selectMatches, selectSelectionMatches };
export { RegExpCursor, SearchCursor, closeSearchPanel, findNext, findPrevious, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, searchKeymap, selectMatches, selectSelectionMatches };

403

dist/index.js

@@ -6,5 +6,6 @@ import { EditorView, Decoration, ViewPlugin, runScopeHandlers } from '@codemirror/view';

import elt from 'crelt';
import { findClusterBreak } from '@codemirror/text';
import { codePointAt, fromCodePoint, codePointSize, findClusterBreak } from '@codemirror/text';
const basicNormalize = typeof String.prototype.normalize == "function" ? x => x.normalize("NFKD") : x => x;
const basicNormalize = typeof String.prototype.normalize == "function"
? x => x.normalize("NFKD") : x => x;
/**

@@ -56,3 +57,3 @@ A search cursor provides an iterator over text matches in a

}
return this.buffer.charCodeAt(this.bufferPos);
return codePointAt(this.buffer, this.bufferPos);
}

@@ -66,2 +67,12 @@ /**

next() {
while (this.matches.length)
this.matches.pop();
return this.nextOverlapping();
}
/**
The `next` method will ignore matches that partially overlap a
previous match. This method behaves like `next`, but includes
such matches.
*/
nextOverlapping() {
for (;;) {

@@ -73,11 +84,4 @@ let next = this.peek();

}
let str = String.fromCharCode(next), start = this.bufferStart + this.bufferPos;
this.bufferPos++;
for (;;) {
let peek = this.peek();
if (peek < 0xDC00 || peek >= 0xE000)
break;
this.bufferPos++;
str += String.fromCharCode(peek);
}
let str = fromCodePoint(next), start = this.bufferStart + this.bufferPos;
this.bufferPos += codePointSize(next);
let norm = this.normalize(str);

@@ -126,2 +130,145 @@ for (let i = 0, pos = start;; i++) {

const empty = { from: -1, to: -1, match: /.*/.exec("") };
const baseFlags = "gm" + (/x/.unicode == null ? "" : "u");
class RegExpCursor {
constructor(text, query, options, from = 0, to = text.length) {
this.to = to;
this.curLine = "";
this.done = false;
this.value = empty;
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.iter = text.iter();
let startLine = text.lineAt(from);
this.curLineStart = startLine.from;
this.matchPos = from;
this.getLine(this.curLineStart);
}
getLine(skip) {
this.iter.next(skip);
if (this.iter.lineBreak) {
this.curLine = "";
}
else {
this.curLine = this.iter.value;
if (this.curLineStart + this.curLine.length > this.to)
this.curLine = this.curLine.slice(0, this.to - this.curLineStart);
this.iter.next();
}
}
nextLine() {
this.curLineStart = this.curLineStart + this.curLine.length + 1;
if (this.curLineStart > this.to)
this.curLine = "";
else
this.getLine(0);
}
next() {
for (let off = this.matchPos - this.curLineStart;;) {
this.re.lastIndex = off;
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.nextLine();
if (from < to || from > this.value.to) {
this.value = { from, to, match };
return this;
}
off = this.matchPos - this.curLineStart;
}
else if (this.curLineStart + this.curLine.length < this.to) {
this.nextLine();
off = 0;
}
else {
this.done = true;
return this;
}
}
}
}
const flattened = new WeakMap();
// Reusable (partially) flattened document strings
class FlattenedDoc {
constructor(from, text) {
this.from = from;
this.text = text;
}
get to() { return this.from + this.text.length; }
static get(doc, from, to) {
let cached = flattened.get(doc);
if (!cached || cached.from >= to || cached.to <= from) {
let flat = new FlattenedDoc(from, doc.sliceString(from, to));
flattened.set(doc, flat);
return flat;
}
if (cached.from == from && cached.to == to)
return cached;
let { text, from: cachedFrom } = cached;
if (cachedFrom > from) {
text = doc.sliceString(from, cachedFrom) + text;
cachedFrom = from;
}
if (cached.to < to)
text += doc.sliceString(cached.to, to);
flattened.set(doc, new FlattenedDoc(cachedFrom, text));
return new FlattenedDoc(from, text.slice(from - cachedFrom, to - cachedFrom));
}
}
class MultilineRegExpCursor {
constructor(text, query, options, from, to) {
this.text = text;
this.to = to;
this.done = false;
this.value = empty;
this.matchPos = 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 */));
}
chunkEnd(pos) {
return pos >= this.to ? this.to : this.text.lineAt(pos).to;
}
next() {
for (;;) {
let off = this.re.lastIndex = this.matchPos - this.flat.from;
let match = this.re.exec(this.flat.text);
// Skip empty matches directly after the last match
if (match && !match[0] && match.index == off) {
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;
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));
}
}
}
}
function validRegExp(source) {
try {
new RegExp(source, baseFlags);
return true;
}
catch (_a) {
return false;
}
}
function createLineDialog(view) {

@@ -146,3 +293,3 @@ let input = elt("input", { class: "cm-textfield", name: "line" });

}
}, elt("label", view.state.phrase("Go to line:"), " ", input), " ", elt("button", { class: "cm-button", type: "submit" }, view.state.phrase("go")));
}, elt("label", view.state.phrase("Go to line"), ": ", input), " ", elt("button", { class: "cm-button", type: "submit" }, view.state.phrase("go")));
function go() {

@@ -296,3 +443,3 @@ let match = /^([+-])?(\d+)?(:\d+)?(%)?$/.exec(input.value);

let cursor = new SearchCursor(state.doc, query, part.from, part.to);
while (!cursor.next().done) {
while (!cursor.nextOverlapping().done) {
let { from, to } = cursor.value;

@@ -327,9 +474,107 @@ if (!check || ((from == 0 || check(state.sliceDoc(from - 1, from)) != CharCategory.Word) &&

eq(other) {
return this.search == other.search && this.replace == other.replace && this.caseInsensitive == other.caseInsensitive;
return this.search == other.search && this.replace == other.replace &&
this.caseInsensitive == other.caseInsensitive && this.constructor == other.constructor;
}
}
class StringQuery extends Query {
constructor(search, replace, caseInsensitive) {
super(search, replace, caseInsensitive);
this.unquoted = search.replace(/\\([nrt\\])/g, (_, ch) => ch == "n" ? "\n" : ch == "r" ? "\r" : ch == "t" ? "\t" : "\\");
}
cursor(doc, from = 0, to = doc.length) {
return new SearchCursor(doc, this.search, from, to, this.caseInsensitive ? x => x.toLowerCase() : undefined);
return new SearchCursor(doc, this.unquoted, from, to, this.caseInsensitive ? x => x.toLowerCase() : undefined);
}
nextMatch(doc, curFrom, curTo) {
let cursor = this.cursor(doc, curTo).nextOverlapping();
if (cursor.done)
cursor = this.cursor(doc, 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) {
for (let pos = to;;) {
let start = Math.max(from, pos - 10000 /* ChunkSize */ - this.unquoted.length);
let cursor = this.cursor(doc, start, pos), range = null;
while (!cursor.nextOverlapping().done)
range = cursor.value;
if (range)
return range;
if (start == from)
return null;
pos -= 10000 /* ChunkSize */;
}
}
prevMatch(doc, curFrom, curTo) {
return this.prevMatchInRange(doc, 0, curFrom) ||
this.prevMatchInRange(doc, curTo, doc.length);
}
getReplacement(_result) { return this.replace; }
matchAll(doc, limit) {
let cursor = this.cursor(doc), ranges = [];
while (!cursor.next().done) {
if (ranges.length >= limit)
return null;
ranges.push(cursor.value);
}
return ranges;
}
highlight(doc, from, to, add) {
let cursor = this.cursor(doc, Math.max(0, from - this.unquoted.length), Math.min(to + this.unquoted.length, doc.length));
while (!cursor.next().done)
add(cursor.value.from, cursor.value.to);
}
get valid() { return !!this.search; }
}
class RegExpQuery extends Query {
constructor(search, replace, caseInsensitive) {
super(search, replace, caseInsensitive);
this.valid = !!search && validRegExp(search);
}
cursor(doc, from = 0, to = doc.length) {
return new RegExpCursor(doc, this.search, this.caseInsensitive ? { ignoreCase: true } : undefined, from, to);
}
nextMatch(doc, curFrom, curTo) {
let cursor = this.cursor(doc, curTo).next();
if (cursor.done)
cursor = this.cursor(doc, 0, curFrom).next();
return cursor.done ? null : cursor.value;
}
prevMatchInRange(doc, from, to) {
for (let size = 1;; size++) {
let start = Math.max(from, to - size * 10000 /* ChunkSize */);
let cursor = this.cursor(doc, start, to), range = null;
while (!cursor.next().done)
range = cursor.value;
if (range && (start == from || range.from > start + 10))
return range;
if (start == from)
return null;
}
}
prevMatch(doc, curFrom, curTo) {
return this.prevMatchInRange(doc, 0, curFrom) ||
this.prevMatchInRange(doc, curTo, doc.length);
}
getReplacement(result) {
return this.replace.replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
: i == "&" ? result.match[0]
: i != "0" && +i < result.match.length ? result.match[i]
: m);
}
matchAll(doc, limit) {
let cursor = this.cursor(doc), ranges = [];
while (!cursor.next().done) {
if (ranges.length >= limit)
return null;
ranges.push(cursor.value);
}
return ranges;
}
highlight(doc, from, to, add) {
let cursor = this.cursor(doc, Math.max(0, from - 250 /* HighlightMargin */), Math.min(to + 250 /* HighlightMargin */, doc.length));
while (!cursor.next().done)
add(cursor.value.from, cursor.value.to);
}
}
const setQuery = StateEffect.define();

@@ -339,3 +584,3 @@ const togglePanel = StateEffect.define();

create() {
return new SearchState(new Query("", "", false), null);
return new SearchState(new StringQuery("", "", false), null);
},

@@ -373,9 +618,12 @@ update(value, tr) {

return Decoration.none;
let state = this.view.state, viewport = this.view.viewport;
let cursor = query.cursor(state.doc, Math.max(0, viewport.from - query.search.length), Math.min(viewport.to + query.search.length, state.doc.length));
let { view } = this;
let builder = new RangeSetBuilder();
while (!cursor.next().done) {
let { from, to } = cursor.value;
let selected = state.selection.ranges.some(r => r.from == from && r.to == to);
builder.add(from, to, selected ? selectedMatchMark : matchMark);
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 */)
to = ranges[++i].to;
query.highlight(view.state.doc, 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);
});
}

@@ -393,11 +641,2 @@ return builder.finish();

}
function findNextMatch(doc, from, query) {
let cursor = query.cursor(doc, from).next();
if (cursor.done) {
cursor = query.cursor(doc, 0, from + query.search.length - 1).next();
if (cursor.done)
return null;
}
return cursor.value;
}
/**

@@ -409,5 +648,5 @@ Open the search panel if it isn't already open, and move the

*/
const findNext = searchCommand((view, state) => {
const findNext = searchCommand((view, { query }) => {
let { from, to } = view.state.selection.main;
let next = findNextMatch(view.state.doc, view.state.selection.main.from + 1, state.query);
let next = query.nextMatch(view.state.doc, from, to);
if (!next || next.from == from && next.to == to)

@@ -422,18 +661,2 @@ return false;

});
const FindPrevChunkSize = 10000;
// Searching in reverse is, rather than implementing inverted search
// cursor, done by scanning chunk after chunk forward.
function findPrevInRange(query, doc, from, to) {
for (let pos = to;;) {
let start = Math.max(from, pos - FindPrevChunkSize - query.search.length);
let cursor = query.cursor(doc, start, pos), range = null;
while (!cursor.next().done)
range = cursor.value;
if (range)
return range;
if (start == from)
return null;
pos -= FindPrevChunkSize;
}
}
/**

@@ -445,5 +668,4 @@ Move the selection to the previous instance of the search query,

const findPrevious = searchCommand((view, { query }) => {
let { state } = view;
let range = findPrevInRange(query, state.doc, 0, state.selection.main.to - 1) ||
findPrevInRange(query, state.doc, state.selection.main.from + 1, state.doc.length);
let { state } = view, { from, to } = state.selection.main;
let range = query.prevMatch(state.doc, from, to);
if (!range)

@@ -462,8 +684,8 @@ return false;

const selectMatches = searchCommand((view, { query }) => {
let cursor = query.cursor(view.state.doc), ranges = [];
while (!cursor.next().done)
ranges.push(EditorSelection.range(cursor.value.from, cursor.value.to));
if (!ranges.length)
let ranges = query.matchAll(view.state.doc, 1000);
if (!ranges || !ranges.length)
return false;
view.dispatch({ selection: EditorSelection.create(ranges) });
view.dispatch({
selection: EditorSelection.create(ranges.map(r => EditorSelection.range(r.from, r.to)))
});
return true;

@@ -494,12 +716,14 @@ });

const replaceNext = searchCommand((view, { query }) => {
let { state } = view, next = findNextMatch(state.doc, state.selection.main.from, query);
let { state } = view, { from, to } = state.selection.main;
let next = query.nextMatch(state.doc, from, from);
if (!next)
return false;
let { from, to } = state.selection.main, changes = [], selection;
let changes = [], selection, replacement;
if (next.from == from && next.to == to) {
changes.push({ from: next.from, to: next.to, insert: query.replace });
next = findNextMatch(state.doc, next.to, query);
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);
}
if (next) {
let off = changes.length == 0 || changes[0].from >= next.to ? 0 : next.to - next.from - query.replace.length;
let off = changes.length == 0 || changes[0].from >= next.to ? 0 : next.to - next.from - replacement.length;
selection = { anchor: next.from - off, head: next.to - off };

@@ -519,7 +743,6 @@ }

const replaceAll = searchCommand((view, { query }) => {
let cursor = query.cursor(view.state.doc), changes = [];
while (!cursor.next().done) {
let { from, to } = cursor.value;
changes.push({ from, to, insert: query.replace });
}
let changes = query.matchAll(view.state.doc, 1e9).map(match => {
let { from, to } = match;
return { from, to, insert: query.getReplacement(match) };
});
if (!changes.length)

@@ -595,7 +818,7 @@ return false;

function buildPanel(conf) {
function p(phrase) { return conf.view.state.phrase(phrase); }
function phrase(phrase) { return conf.view.state.phrase(phrase); }
let searchField = elt("input", {
value: conf.query.search,
placeholder: p("Find"),
"aria-label": p("Find"),
placeholder: phrase("Find"),
"aria-label": phrase("Find"),
class: "cm-textfield",

@@ -608,4 +831,4 @@ name: "search",

value: conf.query.replace,
placeholder: p("Replace"),
"aria-label": p("Replace"),
placeholder: phrase("Replace"),
"aria-label": phrase("Replace"),
class: "cm-textfield",

@@ -622,4 +845,10 @@ name: "replace",

});
let reField = elt("input", {
type: "checkbox",
name: "re",
checked: conf.query instanceof RegExpQuery,
onchange: update
});
function update() {
conf.updateQuery(new Query(searchField.value, replaceField.value, !caseField.checked));
conf.updateQuery(new (reField.checked ? RegExpQuery : StringQuery)(searchField.value, replaceField.value, !caseField.checked));
}

@@ -644,11 +873,12 @@ function keydown(e) {

searchField,
button("next", () => findNext(conf.view), [p("next")]),
button("prev", () => findPrevious(conf.view), [p("previous")]),
button("select", () => selectMatches(conf.view), [p("all")]),
elt("label", null, [caseField, "match case"]),
button("next", () => findNext(conf.view), [phrase("next")]),
button("prev", () => findPrevious(conf.view), [phrase("previous")]),
button("select", () => selectMatches(conf.view), [phrase("all")]),
elt("label", null, [caseField, phrase("match case")]),
elt("label", null, [reField, phrase("regexp")]),
elt("br"),
replaceField,
button("replace", () => replaceNext(conf.view), [p("replace")]),
button("replaceAll", () => replaceAll(conf.view), [p("replace all")]),
elt("button", { name: "close", onclick: () => closeSearchPanel(conf.view), "aria-label": p("close") }, ["×"])
button("replace", () => replaceNext(conf.view), [phrase("replace")]),
button("replaceAll", () => replaceAll(conf.view), [phrase("replace all")]),
elt("button", { name: "close", onclick: () => closeSearchPanel(conf.view), "aria-label": phrase("close") }, ["×"])
]);

@@ -660,4 +890,2 @@ return panel;

function announceMatch(view, { from, to }) {
if (view.hasFocus)
return undefined;
let lineStart = view.state.doc.lineAt(from).from, lineEnd = view.state.doc.lineAt(to).to;

@@ -696,5 +924,8 @@ let start = Math.max(lineStart, from - AnnounceMargin), end = Math.min(lineEnd, to + AnnounceMargin);

},
"& input, & button": {
margin: ".2em .5em .2em 0"
"& input, & button, & label": {
margin: ".2em .6em .2em 0"
},
"& input[type=checkbox]": {
marginRight: ".2em"
},
"& label": {

@@ -715,2 +946,2 @@ fontSize: "80%"

export { SearchCursor, closeSearchPanel, findNext, findPrevious, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, searchKeymap, selectMatches, selectSelectionMatches };
export { RegExpCursor, SearchCursor, closeSearchPanel, findNext, findPrevious, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, searchKeymap, selectMatches, selectSelectionMatches };
{
"name": "@codemirror/search",
"version": "0.18.1",
"version": "0.18.2",
"description": "Search functionality for the CodeMirror code editor",

@@ -5,0 +5,0 @@ "scripts": {

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc