diff --git a/package-lock.json b/package-lock.json index 7393b52721..e6027c5bcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3384,8 +3384,8 @@ }, "node_modules/@codemirror/search": { "version": "6.4.0", - "resolved": "git+ssh://git@github.com/overleaf/codemirror-search.git#ea83364b22ad66455fc94babea7d576fa9f76a93", - "integrity": "sha512-02UOFSNY7/FamUaRPNPwcjq58V2nsRbtXRIT85/AKgfEWv6tVHj5slobCeaRKCXb6hPSOEDMztR5ShmbuxLfEw==", + "resolved": "git+ssh://git@github.com/overleaf/codemirror-search.git#6a09ea7eaad138d810f989753036eabce23cc969", + "integrity": "sha512-LblfUBGsW2+0U+orNGYcJHGKRnbUSU7V/tQDRNKlrw+hE0ZgAGW1B+dZAkcd+AITn6VwjFxKgERB1CzDkWmQWQ==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", @@ -41260,7 +41260,7 @@ "@codemirror/lang-markdown": "^6.1.1", "@codemirror/language": "^6.6.0", "@codemirror/lint": "^6.2.1", - "@codemirror/search": "github:overleaf/codemirror-search#ea83364b22ad66455fc94babea7d576fa9f76a93", + "@codemirror/search": "github:overleaf/codemirror-search#6a09ea7eaad138d810f989753036eabce23cc969", "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.14.0", "@contentful/rich-text-html-renderer": "^16.0.2", @@ -45030,9 +45030,9 @@ } }, "@codemirror/search": { - "version": "git+ssh://git@github.com/overleaf/codemirror-search.git#ea83364b22ad66455fc94babea7d576fa9f76a93", - "integrity": "sha512-02UOFSNY7/FamUaRPNPwcjq58V2nsRbtXRIT85/AKgfEWv6tVHj5slobCeaRKCXb6hPSOEDMztR5ShmbuxLfEw==", - "from": "@codemirror/search@github:overleaf/codemirror-search#ea83364b22ad66455fc94babea7d576fa9f76a93", + "version": "git+ssh://git@github.com/overleaf/codemirror-search.git#6a09ea7eaad138d810f989753036eabce23cc969", + "integrity": "sha512-LblfUBGsW2+0U+orNGYcJHGKRnbUSU7V/tQDRNKlrw+hE0ZgAGW1B+dZAkcd+AITn6VwjFxKgERB1CzDkWmQWQ==", + "from": "@codemirror/search@github:overleaf/codemirror-search#6a09ea7eaad138d810f989753036eabce23cc969", "requires": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", @@ -50242,7 +50242,7 @@ "@codemirror/lang-markdown": "^6.1.1", "@codemirror/language": "^6.6.0", "@codemirror/lint": "^6.2.1", - "@codemirror/search": "github:overleaf/codemirror-search#ea83364b22ad66455fc94babea7d576fa9f76a93", + "@codemirror/search": "github:overleaf/codemirror-search#6a09ea7eaad138d810f989753036eabce23cc969", "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.14.0", "@contentful/rich-text-html-renderer": "^16.0.2", diff --git a/services/web/frontend/js/features/source-editor/commands/select.ts b/services/web/frontend/js/features/source-editor/commands/select.ts index aaa3bcbb7a..ed19af2026 100644 --- a/services/web/frontend/js/features/source-editor/commands/select.ts +++ b/services/web/frontend/js/features/source-editor/commands/select.ts @@ -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 +export { selectNextOccurrence } from '@codemirror/search' + +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 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 */ - } - } - - 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 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) diff --git a/services/web/frontend/js/features/source-editor/extensions/shortcuts.ts b/services/web/frontend/js/features/source-editor/extensions/shortcuts.ts index c4a8c4ddff..fa3b573f4e 100644 --- a/services/web/frontend/js/features/source-editor/extensions/shortcuts.ts +++ b/services/web/frontend/js/features/source-editor/extensions/shortcuts.ts @@ -16,7 +16,7 @@ import { selectSyntaxRight, } from '@codemirror/commands' import { changeCase, duplicateSelection } from '../commands/ranges' -import { selectOccurrence } from '../commands/select' +import { selectNextOccurrence, selectPrevOccurrence } from '../commands/select' import { cloneSelectionVertically } from '../commands/cursor' import { dispatchEditorEvent } from './changes/change-manager' import { @@ -165,12 +165,12 @@ export const shortcuts = Prec.high( { key: 'Ctrl-Alt-ArrowLeft', preventDefault: true, - run: selectOccurrence(false), + run: selectPrevOccurrence, }, { key: 'Ctrl-Alt-ArrowRight', preventDefault: true, - run: selectOccurrence(true), + run: selectNextOccurrence, }, { key: 'Mod-Shift-d', diff --git a/services/web/package.json b/services/web/package.json index 2d74202736..34c5ec6000 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -76,7 +76,7 @@ "@codemirror/lang-markdown": "^6.1.1", "@codemirror/language": "^6.6.0", "@codemirror/lint": "^6.2.1", - "@codemirror/search": "github:overleaf/codemirror-search#ea83364b22ad66455fc94babea7d576fa9f76a93", + "@codemirror/search": "github:overleaf/codemirror-search#6a09ea7eaad138d810f989753036eabce23cc969", "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.14.0", "@contentful/rich-text-html-renderer": "^16.0.2",