selection.ts 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. import { EditorSelection, StateEffect, Line, Text } from '@codemirror/state'
  2. import { EditorView } from '@codemirror/view'
  3. import { hasEffect, updateHasEffect } from '../../utils/effects'
  4. export const selectionIntersects = (
  5. selection: EditorSelection,
  6. extents: { from: number; to: number }
  7. ) =>
  8. selection.ranges.some(
  9. range =>
  10. // Case 1: from is inside node
  11. (extents.from <= range.from && extents.to >= range.from) ||
  12. // Case 2: to is inside node
  13. (extents.from <= range.to && extents.to >= range.to)
  14. )
  15. export const placeSelectionInsideBlock = (
  16. view: EditorView,
  17. event: MouseEvent
  18. ) => {
  19. const line = view.lineBlockAtHeight(event.pageY - view.documentTop)
  20. const selectionRange = EditorSelection.cursor(line.to)
  21. const selection = event.ctrlKey
  22. ? view.state.selection.addRange(selectionRange)
  23. : selectionRange
  24. return { selection, effects: EditorView.scrollIntoView(line.to) }
  25. }
  26. export const extendBackwardsOverEmptyLines = (doc: Text, line: Line) => {
  27. let { number, from } = line
  28. for (let lineNumber = number - 1; lineNumber > 0; lineNumber--) {
  29. const line = doc.line(lineNumber)
  30. if (line.text.trim().length > 0) {
  31. break
  32. }
  33. from = line.from
  34. }
  35. return from
  36. }
  37. export const extendForwardsOverEmptyLines = (doc: Text, line: Line) => {
  38. let { number, to } = line
  39. for (let lineNumber = number + 1; lineNumber <= doc.lines; lineNumber++) {
  40. const line = doc.line(lineNumber)
  41. if (line.text.trim().length > 0) {
  42. break
  43. }
  44. to = line.to
  45. }
  46. return to
  47. }
  48. export const mouseDownEffect = StateEffect.define<boolean>()
  49. export const hasMouseDownEffect = hasEffect(mouseDownEffect)
  50. export const updateHasMouseDownEffect = updateHasEffect(mouseDownEffect)
  51. /**
  52. * A listener for mousedown and mouseup events, dispatching an event
  53. * to record the current mousedown status, which is stored in a state field.
  54. */
  55. export const mouseDownListener = EditorView.domEventHandlers({
  56. mousedown: (event, view) => {
  57. // not wrapped in a timeout, so update listeners know that the mouse is down before they process the selection
  58. view.dispatch({
  59. effects: mouseDownEffect.of(true),
  60. })
  61. },
  62. mouseup: (event, view) => {
  63. // wrap in a timeout, so update listeners receive this effect after the new selection has finished being handled
  64. window.setTimeout(() => {
  65. view.dispatch({
  66. effects: mouseDownEffect.of(false),
  67. })
  68. })
  69. },
  70. })