select-highlighted-or-nearest-token.ts 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. import { EditorState } from '@codemirror/state'
  2. import { isCursorOnEmptyLine } from './is-cursor-on-empty-line'
  3. /**
  4. * Returns the current highlighted range, or the nearest non-whitespace token
  5. * to the cursor when there is no active selection.
  6. *
  7. * "Token" here means any contiguous run of non-whitespace characters (`/\S+/`),
  8. * not necessarily an alphanumeric word. When two tokens are equidistant the
  9. * earlier one (closer to document start) wins.
  10. *
  11. * Only searches the current line.
  12. *
  13. * Only called on user-initiated actions (e.g. "Add comment"), not on every render.
  14. * Returns null when the cursor is on an empty/whitespace-only line.
  15. */
  16. export function selectHighlightedOrNearestToken(
  17. state: EditorState
  18. ): { from: number; to: number } | null {
  19. if (isCursorOnEmptyLine(state)) {
  20. return null
  21. }
  22. const { main } = state.selection
  23. if (!main.empty) {
  24. return { from: main.from, to: main.to }
  25. }
  26. const pos = main.head
  27. const line = state.doc.lineAt(pos)
  28. let bestFrom = 0
  29. let bestTo = 0
  30. let bestDist = Infinity
  31. for (const match of line.text.matchAll(/\S+/g)) {
  32. const from = line.from + match.index!
  33. const to = from + match[0].length
  34. let dist = 0
  35. if (pos < from) {
  36. dist = from - pos
  37. } else if (pos > to) {
  38. dist = pos - to
  39. }
  40. if (dist === 0) {
  41. return { from, to }
  42. }
  43. if (dist < bestDist) {
  44. bestFrom = from
  45. bestTo = to
  46. bestDist = dist
  47. }
  48. // Past the cursor — no closer match possible on this line
  49. if (from > pos) {
  50. break
  51. }
  52. }
  53. if (bestDist < Infinity) {
  54. return { from: bestFrom, to: bestTo }
  55. }
  56. return null
  57. }