@codemirror/search
Advanced tools
Comparing version 0.18.2 to 0.18.3
@@ -0,1 +1,11 @@ | ||
## 0.18.3 (2021-05-18) | ||
### Bug fixes | ||
Fix a bug where the first search command in a new editor wouldn't properly open the panel. | ||
### New features | ||
New command `selectNextOccurrence` that selects the next occurrence of the selected word (bound to Mod-d in the search keymap). | ||
## 0.18.2 (2021-03-19) | ||
@@ -2,0 +12,0 @@ |
@@ -64,2 +64,7 @@ import { Command, KeyBinding } from '@codemirror/view'; | ||
/** | ||
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 | ||
string. | ||
*/ | ||
declare class RegExpCursor implements Iterator<{ | ||
@@ -76,3 +81,12 @@ from: number; | ||
private matchPos; | ||
/** | ||
Set to `true` when the cursor has reached the end of the search | ||
range. | ||
*/ | ||
done: boolean; | ||
/** | ||
Will contain an object with the extent of the match and the | ||
match object when [`next`](https://codemirror.net/6/docs/ref/#search.RegExpCursor.next) | ||
sucessfully finds a match. | ||
*/ | ||
value: { | ||
@@ -83,2 +97,7 @@ from: number; | ||
}; | ||
/** | ||
Create a cursor that will search the given range in the given | ||
document. `query` should be the raw pattern (as you'd pass it to | ||
`new RegExp`). | ||
*/ | ||
constructor(text: Text, query: string, options?: { | ||
@@ -89,2 +108,5 @@ ignoreCase?: boolean; | ||
private nextLine; | ||
/** | ||
Move to the next match, if there is one. | ||
*/ | ||
next(): this; | ||
@@ -131,2 +153,7 @@ } | ||
declare function highlightSelectionMatches(options?: HighlightOptions): Extension; | ||
/** | ||
Select next occurrence of the current selection. | ||
Expand selection to the word when selection range is empty. | ||
*/ | ||
declare const selectNextOccurrence: StateCommand; | ||
@@ -178,5 +205,6 @@ /** | ||
- Alt-g: [`gotoLine`](https://codemirror.net/6/docs/ref/#search.gotoLine) | ||
- Mod-d: [`selectNextOccurrence`](https://codemirror.net/6/docs/ref/#search.selectNextOccurrence) | ||
*/ | ||
declare const searchKeymap: readonly KeyBinding[]; | ||
export { RegExpCursor, 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, selectNextOccurrence, selectSelectionMatches }; |
@@ -6,3 +6,3 @@ import { EditorView, Decoration, ViewPlugin, runScopeHandlers } from '@codemirror/view'; | ||
import elt from 'crelt'; | ||
import { codePointAt, fromCodePoint, codePointSize, findClusterBreak } from '@codemirror/text'; | ||
import { codePointAt, fromCodePoint, codePointSize } from '@codemirror/text'; | ||
@@ -127,9 +127,28 @@ const basicNormalize = typeof String.prototype.normalize == "function" | ||
const empty = { from: -1, to: -1, match: /.*/.exec("") }; | ||
const empty = { from: -1, to: -1, match: /*@__PURE__*//.*/.exec("") }; | ||
const baseFlags = "gm" + (/x/.unicode == null ? "" : "u"); | ||
/** | ||
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 | ||
string. | ||
*/ | ||
class RegExpCursor { | ||
/** | ||
Create a cursor that will search the given range in the given | ||
document. `query` should be the raw pattern (as you'd pass it to | ||
`new RegExp`). | ||
*/ | ||
constructor(text, query, options, from = 0, to = text.length) { | ||
this.to = to; | ||
this.curLine = ""; | ||
/** | ||
Set to `true` when the cursor has reached the end of the search | ||
range. | ||
*/ | ||
this.done = false; | ||
/** | ||
Will contain an object with the extent of the match and the | ||
match object when [`next`](https://codemirror.net/6/docs/ref/#search.RegExpCursor.next) | ||
sucessfully finds a match. | ||
*/ | ||
this.value = empty; | ||
@@ -164,2 +183,5 @@ if (/\\[sWDnr]|\n|\r|\[\^/.test(query)) | ||
} | ||
/** | ||
Move to the next match, if there is one. | ||
*/ | ||
next() { | ||
@@ -191,3 +213,3 @@ for (let off = this.matchPos - this.curLineStart;;) { | ||
} | ||
const flattened = new WeakMap(); | ||
const flattened = /*@__PURE__*/new WeakMap(); | ||
// Reusable (partially) flattened document strings | ||
@@ -320,4 +342,4 @@ class FlattenedDoc { | ||
} | ||
const dialogEffect = StateEffect.define(); | ||
const dialogField = StateField.define({ | ||
const dialogEffect = /*@__PURE__*/StateEffect.define(); | ||
const dialogField = /*@__PURE__*/StateField.define({ | ||
create() { return true; }, | ||
@@ -357,3 +379,3 @@ update(value, tr) { | ||
}; | ||
const baseTheme$1 = EditorView.baseTheme({ | ||
const baseTheme$1 = /*@__PURE__*/EditorView.baseTheme({ | ||
".cm-panel.cm-gotoLine": { | ||
@@ -370,3 +392,3 @@ padding: "2px 6px 4px", | ||
}; | ||
const highlightConfig = Facet.define({ | ||
const highlightConfig = /*@__PURE__*/Facet.define({ | ||
combine(options) { | ||
@@ -392,22 +414,5 @@ return combineConfig(options, defaultHighlightOptions, { | ||
} | ||
function wordAt(doc, pos, check) { | ||
let line = doc.lineAt(pos); | ||
let from = pos - line.from, to = pos - line.from; | ||
while (from > 0) { | ||
let prev = findClusterBreak(line.text, from, false); | ||
if (check(line.text.slice(prev, from)) != CharCategory.Word) | ||
break; | ||
from = prev; | ||
} | ||
while (to < line.length) { | ||
let next = findClusterBreak(line.text, to); | ||
if (check(line.text.slice(to, next)) != CharCategory.Word) | ||
break; | ||
to = next; | ||
} | ||
return from == to ? null : line.text.slice(from, to); | ||
} | ||
const matchDeco = Decoration.mark({ class: "cm-selectionMatch" }); | ||
const mainMatchDeco = Decoration.mark({ class: "cm-selectionMatch cm-selectionMatch-main" }); | ||
const matchHighlighter = ViewPlugin.fromClass(class { | ||
const matchDeco = /*@__PURE__*/Decoration.mark({ class: "cm-selectionMatch" }); | ||
const mainMatchDeco = /*@__PURE__*/Decoration.mark({ class: "cm-selectionMatch cm-selectionMatch-main" }); | ||
const matchHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class { | ||
constructor(view) { | ||
@@ -429,6 +434,7 @@ this.decorations = this.getDeco(view); | ||
return Decoration.none; | ||
let word = state.wordAt(range.head); | ||
if (!word) | ||
return Decoration.none; | ||
check = state.charCategorizer(range.head); | ||
query = wordAt(state.doc, range.head, check); | ||
if (!query) | ||
return Decoration.none; | ||
query = state.sliceDoc(word.from, word.to); | ||
} | ||
@@ -464,6 +470,49 @@ else { | ||
}); | ||
const defaultTheme = EditorView.baseTheme({ | ||
const defaultTheme = /*@__PURE__*/EditorView.baseTheme({ | ||
".cm-selectionMatch": { backgroundColor: "#99ff7780" }, | ||
".cm-searchMatch .cm-selectionMatch": { backgroundColor: "transparent" } | ||
}); | ||
// Select the words around the cursors. | ||
const selectWord = ({ state, dispatch }) => { | ||
let { selection } = state; | ||
let newSel = EditorSelection.create(selection.ranges.map(range => state.wordAt(range.head) || EditorSelection.cursor(range.head)), selection.mainIndex); | ||
if (newSel.eq(selection)) | ||
return false; | ||
dispatch(state.update({ selection: newSel })); | ||
return true; | ||
}; | ||
// Find next occurrence of query relative to last cursor. Wrap around | ||
// the document if there are no more matches. | ||
function findNextOccurrence(state, query) { | ||
let { ranges } = state.selection; | ||
let ahead = new SearchCursor(state.doc, query, ranges[ranges.length - 1].to).next(); | ||
if (!ahead.done) | ||
return ahead.value; | ||
let cursor = new SearchCursor(state.doc, query, 0, Math.max(0, ranges[ranges.length - 1].from - 1)); | ||
while (!cursor.next().done) { | ||
if (!ranges.some(r => r.from === cursor.value.from)) | ||
return cursor.value; | ||
} | ||
return null; | ||
} | ||
/** | ||
Select next occurrence of the current selection. | ||
Expand selection to the word when selection range is empty. | ||
*/ | ||
const selectNextOccurrence = ({ state, dispatch }) => { | ||
let { ranges } = state.selection; | ||
if (ranges.some(sel => sel.from === sel.to)) | ||
return selectWord({ state, dispatch }); | ||
let searchedText = state.sliceDoc(ranges[0].from, ranges[0].to); | ||
if (state.selection.ranges.some(r => state.sliceDoc(r.from, r.to) != searchedText)) | ||
return false; | ||
let range = findNextOccurrence(state, searchedText); | ||
if (!range) | ||
return false; | ||
dispatch(state.update({ | ||
selection: state.selection.addRange(EditorSelection.range(range.from, range.to)), | ||
scrollIntoView: true | ||
})); | ||
return true; | ||
}; | ||
@@ -582,7 +631,7 @@ class Query { | ||
} | ||
const setQuery = StateEffect.define(); | ||
const togglePanel = StateEffect.define(); | ||
const searchState = StateField.define({ | ||
const setQuery = /*@__PURE__*/StateEffect.define(); | ||
const togglePanel = /*@__PURE__*/StateEffect.define(); | ||
const searchState = /*@__PURE__*/StateField.define({ | ||
create() { | ||
return new SearchState(new StringQuery("", "", false), null); | ||
return new SearchState(new StringQuery("", "", false), createSearchPanel); | ||
}, | ||
@@ -606,4 +655,4 @@ update(value, tr) { | ||
} | ||
const matchMark = Decoration.mark({ class: "cm-searchMatch" }), selectedMatchMark = Decoration.mark({ class: "cm-searchMatch cm-searchMatch-selected" }); | ||
const searchHighlighter = ViewPlugin.fromClass(class { | ||
const matchMark = /*@__PURE__*/Decoration.mark({ class: "cm-searchMatch" }), selectedMatchMark = /*@__PURE__*/Decoration.mark({ class: "cm-searchMatch cm-searchMatch-selected" }); | ||
const searchHighlighter = /*@__PURE__*/ViewPlugin.fromClass(class { | ||
constructor(view) { | ||
@@ -649,3 +698,3 @@ this.view = view; | ||
*/ | ||
const findNext = searchCommand((view, { query }) => { | ||
const findNext = /*@__PURE__*/searchCommand((view, { query }) => { | ||
let { from, to } = view.state.selection.main; | ||
@@ -667,3 +716,3 @@ let next = query.nextMatch(view.state.doc, from, to); | ||
*/ | ||
const findPrevious = searchCommand((view, { query }) => { | ||
const findPrevious = /*@__PURE__*/searchCommand((view, { query }) => { | ||
let { state } = view, { from, to } = state.selection.main; | ||
@@ -683,3 +732,3 @@ let range = query.prevMatch(state.doc, from, to); | ||
*/ | ||
const selectMatches = searchCommand((view, { query }) => { | ||
const selectMatches = /*@__PURE__*/searchCommand((view, { query }) => { | ||
let ranges = query.matchAll(view.state.doc, 1000); | ||
@@ -715,3 +764,3 @@ if (!ranges || !ranges.length) | ||
*/ | ||
const replaceNext = searchCommand((view, { query }) => { | ||
const replaceNext = /*@__PURE__*/searchCommand((view, { query }) => { | ||
let { state } = view, { from, to } = state.selection.main; | ||
@@ -742,3 +791,3 @@ let next = query.nextMatch(state.doc, from, from); | ||
*/ | ||
const replaceAll = searchCommand((view, { query }) => { | ||
const replaceAll = /*@__PURE__*/searchCommand((view, { query }) => { | ||
let changes = query.matchAll(view.state.doc, 1e9).map(match => { | ||
@@ -808,2 +857,3 @@ let { from, to } = match; | ||
- Alt-g: [`gotoLine`](https://codemirror.net/6/docs/ref/#search.gotoLine) | ||
- Mod-d: [`selectNextOccurrence`](https://codemirror.net/6/docs/ref/#search.selectNextOccurrence) | ||
*/ | ||
@@ -816,3 +866,4 @@ const searchKeymap = [ | ||
{ key: "Mod-Shift-l", run: selectSelectionMatches }, | ||
{ key: "Alt-g", run: gotoLine } | ||
{ key: "Alt-g", run: gotoLine }, | ||
{ key: "Mod-d", run: selectNextOccurrence }, | ||
]; | ||
@@ -907,3 +958,3 @@ function buildPanel(conf) { | ||
} | ||
const baseTheme = EditorView.baseTheme({ | ||
const baseTheme = /*@__PURE__*/EditorView.baseTheme({ | ||
".cm-panel.cm-search": { | ||
@@ -929,3 +980,4 @@ padding: "2px 6px 4px", | ||
"& label": { | ||
fontSize: "80%" | ||
fontSize: "80%", | ||
whiteSpace: "pre" | ||
} | ||
@@ -940,6 +992,6 @@ }, | ||
searchState, | ||
Prec.override(searchHighlighter), | ||
/*@__PURE__*/Prec.override(searchHighlighter), | ||
baseTheme | ||
]; | ||
export { RegExpCursor, 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, selectNextOccurrence, selectSelectionMatches }; |
{ | ||
"name": "@codemirror/search", | ||
"version": "0.18.2", | ||
"version": "0.18.3", | ||
"description": "Search functionality for the CodeMirror code editor", | ||
"scripts": { | ||
"test": "mocha test/test-*.js", | ||
"test": "cm-runtests", | ||
"prepare": "cm-buildhelper src/search.ts" | ||
@@ -31,3 +31,3 @@ }, | ||
"@codemirror/rangeset": "^0.18.0", | ||
"@codemirror/state": "^0.18.0", | ||
"@codemirror/state": "^0.18.6", | ||
"@codemirror/text": "^0.18.0", | ||
@@ -38,6 +38,3 @@ "@codemirror/view": "^0.18.0", | ||
"devDependencies": { | ||
"@codemirror/buildhelper": "^0.1.0", | ||
"@types/mocha": "^5.2.0", | ||
"ist": "^1.1.6", | ||
"mocha": "^7.1.1" | ||
"@codemirror/buildhelper": "^0.1.5" | ||
}, | ||
@@ -44,0 +41,0 @@ "repository": { |
Sorry, the diff of this file is not supported yet
85739
1
2138
Updated@codemirror/state@^0.18.6