overleaf/services/web/frontend/js/features/source-editor/extensions/draw-selection.ts

105 lines
2.7 KiB
TypeScript
Raw Normal View History

import { EditorSelection, Prec } from '@codemirror/state'
import { EditorView, layer } from '@codemirror/view'
import { rectangleMarkerForRange } from '../utils/layer'
import { updateHasMouseDownEffect } from './visual/selection'
import browser from './browser'
import { updateHasReviewPanelToggledEffect } from './changes/change-manager'
/**
* The built-in extension which draws the cursor and selection(s) in layers,
* copied to make use of a custom version of rectangleMarkerForRange which calls
* fullHeightCoordsAtPos when in Source mode, extending the top and bottom
* of the coords to cover the full line height.
*/
export const drawSelection = () => {
return [cursorLayer, selectionLayer, Prec.highest(hideNativeSelection)]
}
const canHidePrimary = !browser.ios
const hideNativeSelection = EditorView.theme({
'.cm-line': {
'caret-color': canHidePrimary ? 'transparent !important' : null,
'& ::selection': {
backgroundColor: 'transparent !important',
},
'&::selection': {
backgroundColor: 'transparent !important',
},
},
})
const cursorLayer = layer({
above: true,
markers(view) {
const {
selection: { ranges, main },
} = view.state
const cursors = []
for (const range of ranges) {
const primary = range === main
if (!range.empty || !primary || canHidePrimary) {
const className = primary
? 'cm-cursor cm-cursor-primary'
: 'cm-cursor cm-cursor-secondary'
const cursor = range.empty
? range
: EditorSelection.cursor(
range.head,
range.head > range.anchor ? -1 : 1
)
for (const piece of rectangleMarkerForRange(view, className, cursor)) {
cursors.push(piece)
}
}
}
return cursors
},
update(update, dom) {
if (update.transactions.some(tr => tr.selection)) {
dom.style.animationName =
dom.style.animationName === 'cm-blink' ? 'cm-blink2' : 'cm-blink'
}
return (
update.docChanged ||
update.selectionSet ||
updateHasMouseDownEffect(update)
)
},
mount(dom, view) {
dom.style.animationDuration = '1200ms'
},
class: 'cm-cursorLayer',
})
const selectionLayer = layer({
above: false,
markers(view) {
const markers = []
for (const range of view.state.selection.ranges) {
if (!range.empty) {
markers.push(
...rectangleMarkerForRange(view, 'cm-selectionBackground', range)
)
}
}
return markers
},
update(update, dom) {
return (
update.docChanged ||
update.selectionSet ||
update.viewportChanged ||
updateHasMouseDownEffect(update) ||
updateHasReviewPanelToggledEffect(update)
)
},
class: 'cm-selectionLayer',
})