close-bracket-config.ts 2.9 KB

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