draw-selection.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  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. /**
  7. * The built-in extension which draws the cursor and selection(s) in layers,
  8. * copied to make use of a custom version of rectangleMarkerForRange which calls
  9. * fullHeightCoordsAtPos when in Source mode, extending the top and bottom
  10. * of the coords to cover the full line height.
  11. */
  12. export const drawSelection = () => {
  13. return [cursorLayer, selectionLayer, Prec.highest(hideNativeSelection)]
  14. }
  15. const canHidePrimary = !browser.ios
  16. const hideNativeSelection = EditorView.theme({
  17. '.cm-line': {
  18. 'caret-color': canHidePrimary ? 'transparent !important' : null,
  19. '& ::selection': {
  20. backgroundColor: 'transparent !important',
  21. },
  22. '&::selection': {
  23. backgroundColor: 'transparent !important',
  24. },
  25. },
  26. })
  27. const cursorLayer = layer({
  28. above: true,
  29. markers(view) {
  30. const {
  31. selection: { ranges, main },
  32. } = view.state
  33. const cursors = []
  34. for (const range of ranges) {
  35. const primary = range === main
  36. if (!range.empty || !primary || canHidePrimary) {
  37. const className = primary
  38. ? 'cm-cursor cm-cursor-primary'
  39. : 'cm-cursor cm-cursor-secondary'
  40. const cursor = range.empty
  41. ? range
  42. : EditorSelection.cursor(
  43. range.head,
  44. range.head > range.anchor ? -1 : 1
  45. )
  46. for (const piece of rectangleMarkerForRange(view, className, cursor)) {
  47. cursors.push(piece)
  48. }
  49. }
  50. }
  51. return cursors
  52. },
  53. update(update, dom) {
  54. if (update.transactions.some(tr => tr.selection)) {
  55. dom.style.animationName =
  56. dom.style.animationName === 'cm-blink' ? 'cm-blink2' : 'cm-blink'
  57. }
  58. return (
  59. update.docChanged ||
  60. update.selectionSet ||
  61. updateHasMouseDownEffect(update)
  62. )
  63. },
  64. mount(dom) {
  65. dom.style.animationDuration = '1200ms'
  66. },
  67. class: 'cm-cursorLayer',
  68. })
  69. const selectionLayer = layer({
  70. above: false,
  71. markers(view) {
  72. const markers = []
  73. for (const range of view.state.selection.ranges) {
  74. if (!range.empty) {
  75. markers.push(
  76. ...rectangleMarkerForRange(view, 'cm-selectionBackground', range)
  77. )
  78. }
  79. }
  80. return markers
  81. },
  82. update(update) {
  83. return (
  84. update.docChanged ||
  85. update.selectionSet ||
  86. update.viewportChanged ||
  87. updateHasMouseDownEffect(update)
  88. )
  89. },
  90. class: 'cm-selectionLayer',
  91. })