close-bracket-config.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import {
  2. CharCategory,
  3. EditorState,
  4. SelectionRange,
  5. Text,
  6. } from '@codemirror/state'
  7. import {
  8. CloseBracketConfig,
  9. nextChar,
  10. prevChar,
  11. } from '@codemirror/autocomplete'
  12. export const closeBracketConfig: CloseBracketConfig = {
  13. brackets: ['$', '$$', '[', '{', '('],
  14. buildInsert(
  15. state: EditorState,
  16. range: SelectionRange,
  17. open: string,
  18. close: string
  19. ): string {
  20. switch (open) {
  21. // close for $ or $$
  22. case '$': {
  23. const prev = prevChar(state.doc, range.head)
  24. if (prev === '\\') {
  25. const preprev = prevChar(state.doc, range.head - prev.length)
  26. // add an unprefixed closing dollar to \\$
  27. if (preprev === '\\') {
  28. return open + '$'
  29. }
  30. // don't auto-close \$
  31. return open
  32. }
  33. const next = nextChar(state.doc, range.head)
  34. if (next === '\\') {
  35. // avoid auto-closing $ before a TeX command
  36. const pos = range.head + prev.length
  37. const postnext = nextChar(state.doc, pos)
  38. if (state.charCategorizer(pos)(postnext) !== CharCategory.Word) {
  39. return open + '$'
  40. }
  41. // don't auto-close $\command
  42. return open
  43. }
  44. // avoid creating an odd number of dollar signs
  45. const count = countSurroundingCharacters(state.doc, range.from, open)
  46. if (count % 2 !== 0) {
  47. return open
  48. }
  49. return open + close
  50. }
  51. // close for [ or \[
  52. case '[': {
  53. const prev = prevChar(state.doc, range.head)
  54. if (prev === '\\') {
  55. const preprev = prevChar(state.doc, range.head - prev.length)
  56. // add an unprefixed closing bracket to \\[
  57. if (preprev === '\\') {
  58. return open + ']'
  59. }
  60. return open + '\\' + close
  61. }
  62. return open + close
  63. }
  64. // only close for \(
  65. case '(': {
  66. const prev = prevChar(state.doc, range.head)
  67. if (prev === '\\') {
  68. const preprev = prevChar(state.doc, range.head - prev.length)
  69. // don't auto-close \\(
  70. if (preprev === '\\') {
  71. return open
  72. }
  73. return open + '\\' + close
  74. }
  75. return open
  76. }
  77. // only close for {
  78. case '{': {
  79. const prev = prevChar(state.doc, range.head)
  80. if (prev === '\\') {
  81. const preprev = prevChar(state.doc, range.head - prev.length)
  82. // add an unprefixed closing bracket to \\{
  83. if (preprev === '\\') {
  84. return open + '}'
  85. }
  86. // don't auto-close \{
  87. return open
  88. }
  89. return open + close
  90. }
  91. default:
  92. return open + close
  93. }
  94. },
  95. }
  96. function countSurroundingCharacters(doc: Text, pos: number, insert: string) {
  97. let count = 0
  98. // count backwards
  99. let to = pos
  100. do {
  101. const char = doc.sliceString(to - insert.length, to)
  102. if (char !== insert) {
  103. break
  104. }
  105. count++
  106. to--
  107. } while (to > 1)
  108. // count forwards
  109. let from = pos
  110. do {
  111. const char = doc.sliceString(from, from + insert.length)
  112. if (char !== insert) {
  113. break
  114. }
  115. count++
  116. from++
  117. } while (from < doc.length)
  118. return count
  119. }