close-bracket-config.ts 2.7 KB

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