|
|
@@ -1,91 +1,21 @@
|
|
|
import { EditorView } from '@codemirror/view'
|
|
|
-import { EditorSelection, Text } from '@codemirror/state'
|
|
|
-import { selectNextOccurrence, SearchCursor } from '@codemirror/search'
|
|
|
+import { EditorSelection, EditorState, StateCommand } from '@codemirror/state'
|
|
|
+import { SearchQuery, StringQuery, selectWord } from '@codemirror/search'
|
|
|
|
|
|
-type Spec = {
|
|
|
- caseSensitive?: boolean
|
|
|
- unquoted: string
|
|
|
-}
|
|
|
-
|
|
|
-const stringCursor = (spec: Spec, doc: Text, from: number, to: number) => {
|
|
|
- return new SearchCursor(
|
|
|
- doc,
|
|
|
- spec.unquoted,
|
|
|
- from,
|
|
|
- to,
|
|
|
- spec.caseSensitive ? undefined : x => x.toLowerCase()
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
-class QueryType {
|
|
|
- protected spec
|
|
|
-
|
|
|
- constructor(spec: Spec) {
|
|
|
- this.spec = spec
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-class StringQuery extends QueryType {
|
|
|
- // Searching in reverse is, rather than implementing inverted search
|
|
|
- // cursor, done by scanning chunk after chunk forward.
|
|
|
- prevMatchInRange(doc: Text, from: number, to: number) {
|
|
|
- for (let pos = to; ; ) {
|
|
|
- const start = Math.max(
|
|
|
- from,
|
|
|
- pos - 10000 /* ChunkSize */ - this.spec.unquoted.length
|
|
|
- )
|
|
|
- const cursor = stringCursor(this.spec, doc, start, pos)
|
|
|
- let range = null
|
|
|
-
|
|
|
- while (!cursor.nextOverlapping().done) {
|
|
|
- range = cursor.value
|
|
|
- }
|
|
|
-
|
|
|
- if (range) {
|
|
|
- return range
|
|
|
- }
|
|
|
-
|
|
|
- if (start === from) {
|
|
|
- return null
|
|
|
- }
|
|
|
-
|
|
|
- pos -= 10000 /* ChunkSize */
|
|
|
- }
|
|
|
- }
|
|
|
+export { selectNextOccurrence } from '@codemirror/search'
|
|
|
|
|
|
- prevMatch(doc: Text, curFrom: number, curTo: number) {
|
|
|
- return (
|
|
|
- this.prevMatchInRange(doc, 0, curFrom) ||
|
|
|
- this.prevMatchInRange(doc, curTo, doc.length)
|
|
|
- )
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-const selectWord = (view: EditorView) => {
|
|
|
- const { selection } = view.state
|
|
|
- const newSelection = EditorSelection.create(
|
|
|
- selection.ranges.map(
|
|
|
- range =>
|
|
|
- view.state.wordAt(range.head) || EditorSelection.cursor(range.head)
|
|
|
- ),
|
|
|
- selection.mainIndex
|
|
|
- )
|
|
|
-
|
|
|
- if (newSelection.eq(selection)) {
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- view.dispatch(view.state.update({ selection: newSelection }))
|
|
|
-
|
|
|
- return true
|
|
|
+const findPrevOccurence = (state: EditorState, search: string) => {
|
|
|
+ const searchQuery = new SearchQuery({ search, literal: true })
|
|
|
+ const query = new StringQuery(searchQuery)
|
|
|
+ const { from, to } = state.selection.main
|
|
|
+ return query.prevMatch(state, from, to)
|
|
|
}
|
|
|
|
|
|
-const selectPrevOccurrence = (view: EditorView) => {
|
|
|
- const { state } = view
|
|
|
+export const selectPrevOccurrence: StateCommand = ({ state, dispatch }) => {
|
|
|
const { ranges } = state.selection
|
|
|
|
|
|
if (ranges.some(range => range.from === range.to)) {
|
|
|
- return selectWord(view)
|
|
|
+ return selectWord({ state, dispatch })
|
|
|
}
|
|
|
|
|
|
const searchedText = state.sliceDoc(ranges[0].from, ranges[0].to)
|
|
|
@@ -98,23 +28,19 @@ const selectPrevOccurrence = (view: EditorView) => {
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
- const query = new StringQuery({ unquoted: searchedText })
|
|
|
- const { main } = state.selection
|
|
|
- const range = query.prevMatch(state.doc, main.from, main.to)
|
|
|
-
|
|
|
+ const range = findPrevOccurence(state, searchedText)
|
|
|
if (!range) {
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
- view.dispatch({
|
|
|
- selection: state.selection.addRange(
|
|
|
- EditorSelection.range(range.from, range.to)
|
|
|
- ),
|
|
|
- effects: EditorView.scrollIntoView(range.to),
|
|
|
- })
|
|
|
+ dispatch(
|
|
|
+ state.update({
|
|
|
+ selection: state.selection.addRange(
|
|
|
+ EditorSelection.range(range.from, range.to)
|
|
|
+ ),
|
|
|
+ effects: EditorView.scrollIntoView(range.to),
|
|
|
+ })
|
|
|
+ )
|
|
|
|
|
|
return true
|
|
|
}
|
|
|
-
|
|
|
-export const selectOccurrence = (forward: boolean) => (view: EditorView) =>
|
|
|
- forward ? selectNextOccurrence(view) : selectPrevOccurrence(view)
|