comments.ts 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { ensureSyntaxTree } from '@codemirror/language'
  2. import { EditorState } from '@codemirror/state'
  3. import { SyntaxNode } from '@lezer/common'
  4. const HUNDRED_MS = 100
  5. /**
  6. * Does this Comment node look like '% {' ?
  7. * */
  8. export const commentIsOpenFold = (
  9. node: SyntaxNode,
  10. state: EditorState
  11. ): boolean => {
  12. const content = state.doc.sliceString(node.from, node.to)
  13. return !!content.match(/%\s*\{\s*/)
  14. }
  15. /**
  16. * Does this Comment node look like '% }' ?
  17. * */
  18. export const commentIsCloseFold = (
  19. node: SyntaxNode,
  20. state: EditorState
  21. ): boolean => {
  22. const content = state.doc.sliceString(node.from, node.to)
  23. return !!content.match(/%\s*\}\s*/)
  24. }
  25. const SEARCH_FORWARD_LIMIT = 6000
  26. /**
  27. * Given an opening fold Comment, find its corresponding closing Comment,
  28. * accounting for nesting.
  29. * */
  30. export const findClosingFoldComment = (
  31. node: SyntaxNode,
  32. state: EditorState
  33. ): SyntaxNode | undefined => {
  34. const start = node.to + 1
  35. const upto = Math.min(start + SEARCH_FORWARD_LIMIT, state.doc.length)
  36. const tree = ensureSyntaxTree(state, upto, HUNDRED_MS)
  37. if (!tree) {
  38. return
  39. }
  40. let closingFoldNode: SyntaxNode | undefined
  41. let nestingLevel = 0
  42. tree.iterate({
  43. from: start,
  44. to: upto,
  45. enter: n => {
  46. if (closingFoldNode) {
  47. return false
  48. }
  49. if (n.node.type.is('Comment')) {
  50. if (commentIsOpenFold(n.node, state)) {
  51. nestingLevel++
  52. } else if (commentIsCloseFold(n.node, state)) {
  53. if (nestingLevel > 0) {
  54. nestingLevel--
  55. } else {
  56. closingFoldNode = n.node
  57. return false
  58. }
  59. }
  60. }
  61. },
  62. })
  63. return closingFoldNode
  64. }
  65. /**
  66. * Given two Comment nodes, get the positions we want to actually fold between,
  67. * accounting for the opening and closing brace.
  68. *
  69. * The resulting fold looks like `% {----}` in the editor.
  70. *
  71. */
  72. export const getFoldRange = (
  73. startNode: SyntaxNode,
  74. endNode: SyntaxNode,
  75. state: EditorState
  76. ): { from: number; to: number } | null => {
  77. const startContent = state.doc.sliceString(startNode.from, startNode.to)
  78. const endContent = state.doc.sliceString(endNode.from, endNode.to)
  79. const openBracePos = startContent.indexOf('{')
  80. const closeBracePos = endContent.indexOf('}')
  81. if (openBracePos < 0 || closeBracePos < 0) {
  82. return null
  83. }
  84. return {
  85. from: startNode.from + openBracePos + 1,
  86. to: endNode.from + closeBracePos,
  87. }
  88. }