closebrackets.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: https://codemirror.net/LICENSE
  3. ;(function (mod) {
  4. if (typeof exports == 'object' && typeof module == 'object')
  5. // CommonJS
  6. mod(require('../../lib/codemirror'))
  7. else if (typeof define == 'function' && define.amd)
  8. // AMD
  9. define(['../../lib/codemirror'], mod)
  10. // Plain browser env
  11. else mod(CodeMirror)
  12. })(function (CodeMirror) {
  13. var defaults = {
  14. pairs: '()[]{}\'\'""',
  15. closeBefore: ')]}\'":;>',
  16. triples: '',
  17. explode: '[]{}',
  18. }
  19. var Pos = CodeMirror.Pos
  20. CodeMirror.defineOption('autoCloseBrackets', false, function (cm, val, old) {
  21. if (old && old != CodeMirror.Init) {
  22. cm.removeKeyMap(keyMap)
  23. cm.state.closeBrackets = null
  24. }
  25. if (val) {
  26. ensureBound(getOption(val, 'pairs'))
  27. cm.state.closeBrackets = val
  28. cm.addKeyMap(keyMap)
  29. }
  30. })
  31. function getOption(conf, name) {
  32. if (name == 'pairs' && typeof conf == 'string') return conf
  33. if (typeof conf == 'object' && conf[name] != null) return conf[name]
  34. return defaults[name]
  35. }
  36. var keyMap = { Backspace: handleBackspace, Enter: handleEnter }
  37. function ensureBound(chars) {
  38. for (var i = 0; i < chars.length; i++) {
  39. var ch = chars.charAt(i),
  40. key = "'" + ch + "'"
  41. if (!keyMap[key]) keyMap[key] = handler(ch)
  42. }
  43. }
  44. ensureBound(defaults.pairs + '`')
  45. function handler(ch) {
  46. return function (cm) {
  47. return handleChar(cm, ch)
  48. }
  49. }
  50. function getConfig(cm) {
  51. var deflt = cm.state.closeBrackets
  52. if (!deflt || deflt.override) return deflt
  53. var mode = cm.getModeAt(cm.getCursor())
  54. return mode.closeBrackets || deflt
  55. }
  56. function handleBackspace(cm) {
  57. var conf = getConfig(cm)
  58. if (!conf || cm.getOption('disableInput')) return CodeMirror.Pass
  59. var pairs = getOption(conf, 'pairs')
  60. var ranges = cm.listSelections()
  61. for (var i = 0; i < ranges.length; i++) {
  62. if (!ranges[i].empty()) return CodeMirror.Pass
  63. var around = charsAround(cm, ranges[i].head)
  64. if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass
  65. }
  66. for (var i = ranges.length - 1; i >= 0; i--) {
  67. var cur = ranges[i].head
  68. cm.replaceRange('', Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), '+delete')
  69. }
  70. }
  71. function handleEnter(cm) {
  72. var conf = getConfig(cm)
  73. var explode = conf && getOption(conf, 'explode')
  74. if (!explode || cm.getOption('disableInput')) return CodeMirror.Pass
  75. var ranges = cm.listSelections()
  76. for (var i = 0; i < ranges.length; i++) {
  77. if (!ranges[i].empty()) return CodeMirror.Pass
  78. var around = charsAround(cm, ranges[i].head)
  79. if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass
  80. }
  81. cm.operation(function () {
  82. var linesep = cm.lineSeparator() || '\n'
  83. cm.replaceSelection(linesep + linesep, null)
  84. moveSel(cm, -1)
  85. ranges = cm.listSelections()
  86. for (var i = 0; i < ranges.length; i++) {
  87. var line = ranges[i].head.line
  88. cm.indentLine(line, null, true)
  89. cm.indentLine(line + 1, null, true)
  90. }
  91. })
  92. }
  93. function moveSel(cm, dir) {
  94. var newRanges = [],
  95. ranges = cm.listSelections(),
  96. primary = 0
  97. for (var i = 0; i < ranges.length; i++) {
  98. var range = ranges[i]
  99. if (range.head == cm.getCursor()) primary = i
  100. var pos = range.head.ch || dir > 0 ? { line: range.head.line, ch: range.head.ch + dir } : { line: range.head.line - 1 }
  101. newRanges.push({ anchor: pos, head: pos })
  102. }
  103. cm.setSelections(newRanges, primary)
  104. }
  105. function contractSelection(sel) {
  106. var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0
  107. return { anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)), head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1)) }
  108. }
  109. function handleChar(cm, ch) {
  110. var conf = getConfig(cm)
  111. if (!conf || cm.getOption('disableInput')) return CodeMirror.Pass
  112. var pairs = getOption(conf, 'pairs')
  113. var pos = pairs.indexOf(ch)
  114. if (pos == -1) return CodeMirror.Pass
  115. var closeBefore = getOption(conf, 'closeBefore')
  116. var triples = getOption(conf, 'triples')
  117. var identical = pairs.charAt(pos + 1) == ch
  118. var ranges = cm.listSelections()
  119. var opening = pos % 2 == 0
  120. var type
  121. for (var i = 0; i < ranges.length; i++) {
  122. var range = ranges[i],
  123. cur = range.head,
  124. curType
  125. var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1))
  126. if (opening && !range.empty()) {
  127. curType = 'surround'
  128. } else if ((identical || !opening) && next == ch) {
  129. if (identical && stringStartsAfter(cm, cur)) curType = 'both'
  130. else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch) curType = 'skipThree'
  131. else curType = 'skip'
  132. } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 && cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch) {
  133. if (cur.ch > 2 && /\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))) return CodeMirror.Pass
  134. curType = 'addFour'
  135. } else if (identical) {
  136. var prev = cur.ch == 0 ? ' ' : cm.getRange(Pos(cur.line, cur.ch - 1), cur)
  137. if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = 'both'
  138. else return CodeMirror.Pass
  139. } else if (opening && (next.length === 0 || /\s/.test(next) || closeBefore.indexOf(next) > -1)) {
  140. curType = 'both'
  141. } else {
  142. return CodeMirror.Pass
  143. }
  144. if (!type) type = curType
  145. else if (type != curType) return CodeMirror.Pass
  146. }
  147. var left = pos % 2 ? pairs.charAt(pos - 1) : ch
  148. var right = pos % 2 ? ch : pairs.charAt(pos + 1)
  149. cm.operation(function () {
  150. if (type == 'skip') {
  151. moveSel(cm, 1)
  152. } else if (type == 'skipThree') {
  153. moveSel(cm, 3)
  154. } else if (type == 'surround') {
  155. var sels = cm.getSelections()
  156. for (var i = 0; i < sels.length; i++) sels[i] = left + sels[i] + right
  157. cm.replaceSelections(sels, 'around')
  158. sels = cm.listSelections().slice()
  159. for (var i = 0; i < sels.length; i++) sels[i] = contractSelection(sels[i])
  160. cm.setSelections(sels)
  161. } else if (type == 'both') {
  162. cm.replaceSelection(left + right, null)
  163. cm.triggerElectric(left + right)
  164. moveSel(cm, -1)
  165. } else if (type == 'addFour') {
  166. cm.replaceSelection(left + left + left + left, 'before')
  167. moveSel(cm, 1)
  168. }
  169. })
  170. }
  171. function charsAround(cm, pos) {
  172. var str = cm.getRange(Pos(pos.line, pos.ch - 1), Pos(pos.line, pos.ch + 1))
  173. return str.length == 2 ? str : null
  174. }
  175. function stringStartsAfter(cm, pos) {
  176. var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))
  177. return /\bstring/.test(token.type) && token.start == pos.ch && (pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos)))
  178. }
  179. })