draw-selection.ts 2.8 KB

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