draw-selection.ts 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import { EditorSelection, Prec } from '@codemirror/state'
  2. import { EditorView, layer } from '@codemirror/view'
  3. import { rectangleMarkerForRange } from '../utils/layer'
  4. import { updateHasMouseDownEffect } from './visual/selection'
  5. import browser from './browser'
  6. // This is mostly a copy of CodeMirror's built-in drawSelection extension. We
  7. // have our own copy so that we can use our own implementation of
  8. // rectangleMarkerForRange
  9. export const drawSelection = () => {
  10. return [cursorLayer, selectionLayer, hideNativeSelection]
  11. }
  12. const canHidePrimary = !browser.ios
  13. const hideNativeSelection = Prec.highest(
  14. EditorView.theme({
  15. '.cm-line': {
  16. 'caret-color': canHidePrimary ? 'transparent !important' : null,
  17. '& ::selection': {
  18. backgroundColor: 'transparent !important',
  19. },
  20. '&::selection': {
  21. backgroundColor: 'transparent !important',
  22. },
  23. },
  24. })
  25. )
  26. const cursorLayer = layer({
  27. above: true,
  28. markers(view) {
  29. const {
  30. selection: { ranges, main },
  31. } = view.state
  32. const cursors = []
  33. for (const range of ranges) {
  34. const primary = range === main
  35. if (!range.empty || !primary || canHidePrimary) {
  36. const className = primary
  37. ? 'cm-cursor cm-cursor-primary'
  38. : 'cm-cursor cm-cursor-secondary'
  39. const cursor = range.empty
  40. ? range
  41. : EditorSelection.cursor(
  42. range.head,
  43. range.head > range.anchor ? -1 : 1
  44. )
  45. for (const piece of rectangleMarkerForRange(view, className, cursor)) {
  46. cursors.push(piece)
  47. }
  48. }
  49. }
  50. return cursors
  51. },
  52. update(update, dom) {
  53. if (update.transactions.some(tr => tr.selection)) {
  54. dom.style.animationName =
  55. dom.style.animationName === 'cm-blink' ? 'cm-blink2' : 'cm-blink'
  56. }
  57. return (
  58. update.docChanged ||
  59. update.selectionSet ||
  60. updateHasMouseDownEffect(update)
  61. )
  62. },
  63. mount(dom, view) {
  64. dom.style.animationDuration = '1200ms'
  65. },
  66. class: 'cm-cursorLayer',
  67. })
  68. const selectionLayer = layer({
  69. above: false,
  70. markers(view) {
  71. const markers = []
  72. for (const range of view.state.selection.ranges) {
  73. if (!range.empty) {
  74. markers.push(
  75. ...rectangleMarkerForRange(view, 'cm-selectionBackground', range)
  76. )
  77. }
  78. }
  79. return markers
  80. },
  81. update(update, dom) {
  82. return (
  83. update.docChanged ||
  84. update.selectionSet ||
  85. update.viewportChanged ||
  86. updateHasMouseDownEffect(update)
  87. )
  88. },
  89. class: 'cm-selectionLayer',
  90. })