| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- /**
- * This file is adapted from CodeMirror 6, licensed under the MIT license:
- * https://github.com/codemirror/search/blob/main/src/selection-match.ts
- */
- import { EditorView, layer, RectangleMarker } from '@codemirror/view'
- import {
- CharCategory,
- EditorSelection,
- EditorState,
- Extension,
- } from '@codemirror/state'
- import { SearchCursor } from '@codemirror/search'
- import { rectangleMarkerForRange } from '../utils/layer'
- /*
- This extension highlights text that matches the selection.
- It uses the `"cm-selectionMatch"` class for the highlighting.
- */
- export const highlightSelectionMatches = (): Extension => [
- layer({
- above: false,
- markers(view) {
- return buildMarkers(view, view.state)
- },
- update(update) {
- return update.docChanged || update.selectionSet || update.viewportChanged
- },
- class: 'ol-cm-selectionMatchesLayer',
- }),
- EditorView.baseTheme({
- '.ol-cm-selectionMatchesLayer': {
- contain: 'size style',
- pointerEvents: 'none',
- },
- '.cm-selectionMatch': {
- position: 'absolute',
- },
- }),
- ]
- // Whether the characters directly outside the given positions are non-word characters
- function insideWordBoundaries(
- check: (char: string) => CharCategory,
- state: EditorState,
- from: number,
- to: number
- ): boolean {
- return (
- (from === 0 ||
- check(state.sliceDoc(from - 1, from)) !== CharCategory.Word) &&
- (to === state.doc.length ||
- check(state.sliceDoc(to, to + 1)) !== CharCategory.Word)
- )
- }
- // Whether the characters directly at the given positions are word characters
- function insideWord(
- check: (char: string) => CharCategory,
- state: EditorState,
- from: number,
- to: number
- ): boolean {
- return (
- check(state.sliceDoc(from, from + 1)) === CharCategory.Word &&
- check(state.sliceDoc(to - 1, to)) === CharCategory.Word
- )
- }
- const buildMarkers = (
- view: EditorView,
- state: EditorState
- ): RectangleMarker[] => {
- const sel = state.selection
- if (sel.ranges.length > 1) {
- return []
- }
- const range = sel.main
- if (range.empty) {
- return []
- }
- const len = range.to - range.from
- if (len < 3 || len > 200) {
- return []
- }
- const query = state.sliceDoc(range.from, range.to) // TODO: allow and include leading/trailing space?
- if (query === '') {
- return []
- }
- const check = state.charCategorizer(range.head)
- if (
- !(
- insideWordBoundaries(check, state, range.from, range.to) &&
- insideWord(check, state, range.from, range.to)
- )
- ) {
- return []
- }
- const markers: RectangleMarker[] = []
- for (const part of view.visibleRanges) {
- const cursor = new SearchCursor(state.doc, query, part.from, part.to)
- while (!cursor.next().done) {
- const { from, to } = cursor.value
- if (!check || insideWordBoundaries(check, state, from, to)) {
- markers.push(
- ...rectangleMarkerForRange(
- view,
- 'cm-selectionMatch',
- EditorSelection.range(from, to)
- )
- )
- if (markers.length > 100) {
- return []
- }
- }
- }
- }
- return markers
- }
|