bracket-matcher-view.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. const {CompositeDisposable} = require('atom')
  2. const _ = require('underscore-plus')
  3. const {Range, Point} = require('atom')
  4. const TagFinder = require('./tag-finder')
  5. const MAX_ROWS_TO_SCAN = 10000
  6. const ONE_CHAR_FORWARD_TRAVERSAL = Object.freeze(Point(0, 1))
  7. const ONE_CHAR_BACKWARD_TRAVERSAL = Object.freeze(Point(0, -1))
  8. const TWO_CHARS_BACKWARD_TRAVERSAL = Object.freeze(Point(0, -2))
  9. const MAX_ROWS_TO_SCAN_FORWARD_TRAVERSAL = Object.freeze(Point(MAX_ROWS_TO_SCAN, 0))
  10. const MAX_ROWS_TO_SCAN_BACKWARD_TRAVERSAL = Object.freeze(Point(-MAX_ROWS_TO_SCAN, 0))
  11. module.exports =
  12. class BracketMatcherView {
  13. constructor(editor, editorElement, matchManager) {
  14. this.destroy = this.destroy.bind(this)
  15. this.updateMatch = this.updateMatch.bind(this)
  16. this.editor = editor
  17. this.matchManager = matchManager
  18. this.gutter = this.editor.gutterWithName('line-number')
  19. this.subscriptions = new CompositeDisposable()
  20. this.tagFinder = new TagFinder(this.editor)
  21. this.pairHighlighted = false
  22. this.tagHighlighted = false
  23. // ranges for possible selection
  24. this.bracket1Range = null
  25. this.bracket2Range = null
  26. this.subscriptions.add(
  27. this.editor.onDidTokenize(this.updateMatch),
  28. this.editor.getBuffer().onDidChangeText(this.updateMatch),
  29. this.editor.onDidChangeGrammar(this.updateMatch),
  30. this.editor.onDidChangeSelectionRange(this.updateMatch),
  31. this.editor.onDidAddCursor(this.updateMatch),
  32. this.editor.onDidRemoveCursor(this.updateMatch),
  33. atom.commands.add(editorElement, 'bracket-matcher:go-to-matching-bracket', () =>
  34. this.goToMatchingBracket()
  35. ),
  36. atom.commands.add(editorElement, 'bracket-matcher:go-to-enclosing-bracket', () =>
  37. this.gotoPrecedingStartBracket()
  38. ),
  39. atom.commands.add(editorElement, 'bracket-matcher:select-inside-brackets', () =>
  40. this.selectInsideBrackets()
  41. ),
  42. atom.commands.add(editorElement, 'bracket-matcher:close-tag', () =>
  43. this.closeTag()
  44. ),
  45. atom.commands.add(editorElement, 'bracket-matcher:remove-matching-brackets', () =>
  46. this.removeMatchingBrackets()
  47. ),
  48. atom.commands.add(editorElement, 'bracket-matcher:select-matching-brackets', () =>
  49. this.selectMatchingBrackets()
  50. ),
  51. this.editor.onDidDestroy(this.destroy)
  52. )
  53. this.updateMatch()
  54. }
  55. destroy() {
  56. this.subscriptions.dispose()
  57. }
  58. updateMatch() {
  59. if (this.pairHighlighted) {
  60. this.editor.destroyMarker(this.startMarker.id)
  61. this.editor.destroyMarker(this.endMarker.id)
  62. }
  63. this.pairHighlighted = false
  64. this.tagHighlighted = false
  65. if (!this.editor.getLastSelection().isEmpty()) return
  66. const {position, matchPosition} = this.findCurrentPair()
  67. let startRange = null
  68. let endRange = null
  69. let highlightTag = false
  70. let highlightPair = false
  71. if (position && matchPosition) {
  72. this.bracket1Range = (startRange = Range(position, position.traverse(ONE_CHAR_FORWARD_TRAVERSAL)))
  73. this.bracket2Range = (endRange = Range(matchPosition, matchPosition.traverse(ONE_CHAR_FORWARD_TRAVERSAL)))
  74. highlightPair = true
  75. } else {
  76. this.bracket1Range = null
  77. this.bracket2Range = null
  78. if (this.hasSyntaxTree()) {
  79. ({startRange, endRange} = this.findMatchingTagNameRangesWithSyntaxTree())
  80. } else {
  81. ({startRange, endRange} = this.tagFinder.findMatchingTags())
  82. if (this.isCursorOnCommentOrString()) return
  83. }
  84. if (startRange) {
  85. highlightTag = true
  86. highlightPair = true
  87. }
  88. }
  89. if (!highlightTag && !highlightPair) return
  90. this.startMarker = this.createMarker(startRange)
  91. this.endMarker = this.createMarker(endRange)
  92. this.pairHighlighted = highlightPair
  93. this.tagHighlighted = highlightTag
  94. }
  95. selectMatchingBrackets() {
  96. if (!this.bracket1Range && !this.bracket2Range) return
  97. this.editor.setSelectedBufferRanges([this.bracket1Range, this.bracket2Range])
  98. this.matchManager.changeBracketsMode = true
  99. }
  100. removeMatchingBrackets() {
  101. if (this.editor.hasMultipleCursors()) {
  102. this.editor.backspace()
  103. return
  104. }
  105. this.editor.transact(() => {
  106. if (this.editor.getLastSelection().isEmpty()) {
  107. this.editor.selectLeft()
  108. }
  109. const text = this.editor.getSelectedText()
  110. this.editor.moveRight()
  111. // check if the character to the left is part of a pair
  112. if (
  113. this.matchManager.pairedCharacters.hasOwnProperty(text) ||
  114. this.matchManager.pairedCharactersInverse.hasOwnProperty(text)
  115. ) {
  116. let {position, matchPosition, bracket} = this.findCurrentPair()
  117. if (position && matchPosition) {
  118. this.editor.setCursorBufferPosition(matchPosition)
  119. this.editor.delete()
  120. // if on the same line and the cursor is in front of an end pair
  121. // offset by one to make up for the missing character
  122. if (position.row === matchPosition.row && this.matchManager.pairedCharactersInverse.hasOwnProperty(bracket)) {
  123. position = position.traverse(ONE_CHAR_BACKWARD_TRAVERSAL)
  124. }
  125. this.editor.setCursorBufferPosition(position)
  126. this.editor.delete()
  127. } else {
  128. this.editor.backspace()
  129. }
  130. } else {
  131. this.editor.backspace()
  132. }
  133. })
  134. }
  135. findMatchingEndBracket(startBracketPosition, startBracket, endBracket) {
  136. if (startBracket === endBracket) return
  137. if (this.hasSyntaxTree()) {
  138. return this.findMatchingEndBracketWithSyntaxTree(startBracketPosition, startBracket, endBracket)
  139. } else {
  140. const scopeDescriptor = this.editor.scopeDescriptorForBufferPosition(startBracketPosition)
  141. if (this.isScopeCommentedOrString(scopeDescriptor.getScopesArray())) return
  142. return this.findMatchingEndBracketWithRegexSearch(startBracketPosition, startBracket, endBracket)
  143. }
  144. }
  145. findMatchingStartBracket(endBracketPosition, startBracket, endBracket) {
  146. if (startBracket === endBracket) return
  147. if (this.hasSyntaxTree()) {
  148. return this.findMatchingStartBracketWithSyntaxTree(endBracketPosition, startBracket, endBracket)
  149. } else {
  150. const scopeDescriptor = this.editor.scopeDescriptorForBufferPosition(endBracketPosition)
  151. if (this.isScopeCommentedOrString(scopeDescriptor.getScopesArray())) return
  152. return this.findMatchingStartBracketWithRegexSearch(endBracketPosition, startBracket, endBracket)
  153. }
  154. }
  155. findMatchingEndBracketWithSyntaxTree(bracketPosition, startBracket, endBracket) {
  156. let result
  157. const bracketEndPosition = bracketPosition.traverse([0, startBracket.length])
  158. this.editor.buffer.getLanguageMode().getSyntaxNodeContainingRange(
  159. new Range(bracketPosition, bracketEndPosition),
  160. node => {
  161. if (bracketEndPosition.isGreaterThan(node.startPosition) && bracketEndPosition.isLessThan(node.endPosition)) {
  162. const matchNode = node.children.find(child =>
  163. bracketEndPosition.isLessThanOrEqual(child.startPosition) &&
  164. child.type === endBracket
  165. )
  166. if (matchNode) result = Point.fromObject(matchNode.startPosition)
  167. return true
  168. }
  169. }
  170. )
  171. return result
  172. }
  173. findMatchingStartBracketWithSyntaxTree(bracketPosition, startBracket, endBracket) {
  174. let result
  175. const bracketEndPosition = bracketPosition.traverse([0, startBracket.length])
  176. this.editor.buffer.getLanguageMode().getSyntaxNodeContainingRange(
  177. new Range(bracketPosition, bracketEndPosition),
  178. node => {
  179. if (bracketPosition.isGreaterThan(node.startPosition)) {
  180. const matchNode = node.children.find(child =>
  181. bracketPosition.isGreaterThanOrEqual(child.endPosition) &&
  182. child.type === startBracket
  183. )
  184. if (matchNode) result = Point.fromObject(matchNode.startPosition)
  185. return true
  186. }
  187. }
  188. )
  189. return result
  190. }
  191. findMatchingTagNameRangesWithSyntaxTree() {
  192. const position = this.editor.getCursorBufferPosition()
  193. const {startTag, endTag} = this.findContainingTagsWithSyntaxTree(position)
  194. if (startTag && (startTag.range.containsPoint(position) || endTag.range.containsPoint(position))) {
  195. if (startTag === endTag) {
  196. const {range} = startTag.child(1)
  197. return {startRange: range, endRange: range}
  198. } else if (endTag.firstChild.type === '</') {
  199. return {
  200. startRange: startTag.child(1).range,
  201. endRange: endTag.child(1).range
  202. }
  203. } else {
  204. return {
  205. startRange: startTag.child(1).range,
  206. endRange: endTag.child(2).range
  207. }
  208. }
  209. } else {
  210. return {}
  211. }
  212. }
  213. findMatchingTagsWithSyntaxTree() {
  214. const position = this.editor.getCursorBufferPosition()
  215. const {startTag, endTag} = this.findContainingTagsWithSyntaxTree(position)
  216. if (startTag) {
  217. return {startRange: startTag.range, endRange: endTag.range}
  218. } else {
  219. return {}
  220. }
  221. }
  222. findContainingTagsWithSyntaxTree(position) {
  223. let startTag, endTag
  224. if (position.column === this.editor.buffer.lineLengthForRow(position.row)) position.column--;
  225. this.editor.buffer.getLanguageMode().getSyntaxNodeAtPosition(position, node => {
  226. if (node.type.includes('element') && node.childCount > 0) {
  227. const {firstChild, lastChild} = node
  228. if (
  229. firstChild.childCount > 2 &&
  230. firstChild.firstChild.type === '<'
  231. ) {
  232. if (lastChild.id === firstChild.id && firstChild.lastChild.type === '/>') {
  233. startTag = firstChild
  234. endTag = firstChild
  235. } else if (
  236. lastChild.childCount > 2 &&
  237. (lastChild.firstChild.type === '</' ||
  238. lastChild.firstChild.type === '<' && lastChild.child(1).type === '/')
  239. ) {
  240. startTag = firstChild
  241. endTag = lastChild
  242. }
  243. return true
  244. }
  245. }
  246. })
  247. return {startTag, endTag}
  248. }
  249. findMatchingEndBracketWithRegexSearch(startBracketPosition, startBracket, endBracket) {
  250. const scanRange = new Range(
  251. startBracketPosition.traverse(ONE_CHAR_FORWARD_TRAVERSAL),
  252. startBracketPosition.traverse(MAX_ROWS_TO_SCAN_FORWARD_TRAVERSAL)
  253. )
  254. let endBracketPosition = null
  255. let unpairedCount = 0
  256. this.editor.scanInBufferRange(this.matchManager.pairRegexes[startBracket], scanRange, result => {
  257. if (this.isRangeCommentedOrString(result.range)) return
  258. switch (result.match[0]) {
  259. case startBracket:
  260. unpairedCount++
  261. break
  262. case endBracket:
  263. unpairedCount--
  264. if (unpairedCount < 0) {
  265. endBracketPosition = result.range.start
  266. result.stop()
  267. }
  268. break
  269. }
  270. })
  271. return endBracketPosition
  272. }
  273. findMatchingStartBracketWithRegexSearch(endBracketPosition, startBracket, endBracket) {
  274. const scanRange = new Range(
  275. endBracketPosition.traverse(MAX_ROWS_TO_SCAN_BACKWARD_TRAVERSAL),
  276. endBracketPosition
  277. )
  278. let startBracketPosition = null
  279. let unpairedCount = 0
  280. this.editor.backwardsScanInBufferRange(this.matchManager.pairRegexes[startBracket], scanRange, result => {
  281. if (this.isRangeCommentedOrString(result.range)) return
  282. switch (result.match[0]) {
  283. case startBracket:
  284. unpairedCount--
  285. if (unpairedCount < 0) {
  286. startBracketPosition = result.range.start
  287. result.stop()
  288. break
  289. }
  290. break
  291. case endBracket:
  292. unpairedCount++
  293. }
  294. })
  295. return startBracketPosition
  296. }
  297. findPrecedingStartBracket(cursorPosition) {
  298. if (this.hasSyntaxTree()) {
  299. return this.findPrecedingStartBracketWithSyntaxTree(cursorPosition)
  300. } else {
  301. return this.findPrecedingStartBracketWithRegexSearch(cursorPosition)
  302. }
  303. }
  304. findPrecedingStartBracketWithSyntaxTree(cursorPosition) {
  305. let result
  306. this.editor.buffer.getLanguageMode().getSyntaxNodeAtPosition(cursorPosition, node => {
  307. for (const child of node.children) {
  308. if (cursorPosition.isLessThanOrEqual(child.startPosition)) break
  309. if (
  310. child.type in this.matchManager.pairedCharacters ||
  311. child.type in this.matchManager.pairedCharactersInverse
  312. ) {
  313. result = Point.fromObject(child.startPosition)
  314. return true
  315. }
  316. }
  317. })
  318. return result
  319. }
  320. findPrecedingStartBracketWithRegexSearch(cursorPosition) {
  321. const scanRange = new Range(Point.ZERO, cursorPosition)
  322. const startBracket = _.escapeRegExp(_.keys(this.matchManager.pairedCharacters).join(''))
  323. const endBracket = _.escapeRegExp(_.keys(this.matchManager.pairedCharactersInverse).join(''))
  324. const combinedRegExp = new RegExp(`[${startBracket}${endBracket}]`, 'g')
  325. const startBracketRegExp = new RegExp(`[${startBracket}]`, 'g')
  326. const endBracketRegExp = new RegExp(`[${endBracket}]`, 'g')
  327. let startPosition = null
  328. let unpairedCount = 0
  329. this.editor.backwardsScanInBufferRange(combinedRegExp, scanRange, result => {
  330. if (this.isRangeCommentedOrString(result.range)) return
  331. if (result.match[0].match(endBracketRegExp)) {
  332. unpairedCount++
  333. } else if (result.match[0].match(startBracketRegExp)) {
  334. unpairedCount--
  335. if (unpairedCount < 0) {
  336. startPosition = result.range.start
  337. result.stop()
  338. }
  339. }
  340. })
  341. return startPosition
  342. }
  343. createMarker(bufferRange) {
  344. const marker = this.editor.markBufferRange(bufferRange)
  345. this.editor.decorateMarker(marker, {type: 'highlight', class: 'bracket-matcher', deprecatedRegionClass: 'bracket-matcher'})
  346. if (atom.config.get('bracket-matcher.highlightMatchingLineNumber', {scope: this.editor.getRootScopeDescriptor()}) && this.gutter) {
  347. this.gutter.decorateMarker(marker, {type: 'highlight', class: 'bracket-matcher', deprecatedRegionClass: 'bracket-matcher'})
  348. }
  349. return marker
  350. }
  351. findCurrentPair() {
  352. const currentPosition = this.editor.getCursorBufferPosition()
  353. const previousPosition = currentPosition.traverse(ONE_CHAR_BACKWARD_TRAVERSAL)
  354. const nextPosition = currentPosition.traverse(ONE_CHAR_FORWARD_TRAVERSAL)
  355. const currentCharacter = this.editor.getTextInBufferRange(new Range(currentPosition, nextPosition))
  356. const previousCharacter = this.editor.getTextInBufferRange(new Range(previousPosition, currentPosition))
  357. let position, matchPosition, currentBracket, matchingBracket
  358. if ((matchingBracket = this.matchManager.pairedCharacters[currentCharacter])) {
  359. position = currentPosition
  360. currentBracket = currentCharacter
  361. matchPosition = this.findMatchingEndBracket(position, currentBracket, matchingBracket)
  362. } else if ((matchingBracket = this.matchManager.pairedCharacters[previousCharacter])) {
  363. position = previousPosition
  364. currentBracket = previousCharacter
  365. matchPosition = this.findMatchingEndBracket(position, currentBracket, matchingBracket)
  366. } else if ((matchingBracket = this.matchManager.pairedCharactersInverse[previousCharacter])) {
  367. position = previousPosition
  368. currentBracket = previousCharacter
  369. matchPosition = this.findMatchingStartBracket(position, matchingBracket, currentBracket)
  370. } else if ((matchingBracket = this.matchManager.pairedCharactersInverse[currentCharacter])) {
  371. position = currentPosition
  372. currentBracket = currentCharacter
  373. matchPosition = this.findMatchingStartBracket(position, matchingBracket, currentBracket)
  374. }
  375. return {position, matchPosition, bracket: currentBracket}
  376. }
  377. goToMatchingBracket() {
  378. if (!this.pairHighlighted) return this.gotoPrecedingStartBracket()
  379. const position = this.editor.getCursorBufferPosition()
  380. if (this.tagHighlighted) {
  381. let tagCharacterOffset
  382. let startRange = this.startMarker.getBufferRange()
  383. const tagLength = startRange.end.column - startRange.start.column
  384. let endRange = this.endMarker.getBufferRange()
  385. if (startRange.compare(endRange) > 0) {
  386. [startRange, endRange] = [endRange, startRange]
  387. }
  388. // include the <
  389. startRange = new Range(startRange.start.traverse(ONE_CHAR_BACKWARD_TRAVERSAL), endRange.end.traverse(ONE_CHAR_BACKWARD_TRAVERSAL))
  390. // include the </
  391. endRange = new Range(endRange.start.traverse(TWO_CHARS_BACKWARD_TRAVERSAL), endRange.end.traverse(TWO_CHARS_BACKWARD_TRAVERSAL))
  392. if (position.isLessThan(endRange.start)) {
  393. tagCharacterOffset = position.column - startRange.start.column
  394. if (tagCharacterOffset > 0) { tagCharacterOffset++ }
  395. tagCharacterOffset = Math.min(tagCharacterOffset, tagLength + 2) // include </
  396. this.editor.setCursorBufferPosition(endRange.start.traverse([0, tagCharacterOffset]))
  397. } else {
  398. tagCharacterOffset = position.column - endRange.start.column
  399. if (tagCharacterOffset > 1) { tagCharacterOffset-- }
  400. tagCharacterOffset = Math.min(tagCharacterOffset, tagLength + 1) // include <
  401. this.editor.setCursorBufferPosition(startRange.start.traverse([0, tagCharacterOffset]))
  402. }
  403. } else {
  404. const previousPosition = position.traverse(ONE_CHAR_BACKWARD_TRAVERSAL)
  405. const startPosition = this.startMarker.getStartBufferPosition()
  406. const endPosition = this.endMarker.getStartBufferPosition()
  407. if (position.isEqual(startPosition)) {
  408. this.editor.setCursorBufferPosition(endPosition.traverse(ONE_CHAR_FORWARD_TRAVERSAL))
  409. } else if (previousPosition.isEqual(startPosition)) {
  410. this.editor.setCursorBufferPosition(endPosition)
  411. } else if (position.isEqual(endPosition)) {
  412. this.editor.setCursorBufferPosition(startPosition.traverse(ONE_CHAR_FORWARD_TRAVERSAL))
  413. } else if (previousPosition.isEqual(endPosition)) {
  414. this.editor.setCursorBufferPosition(startPosition)
  415. }
  416. }
  417. }
  418. gotoPrecedingStartBracket() {
  419. if (this.pairHighlighted) return
  420. const matchPosition = this.findPrecedingStartBracket(this.editor.getCursorBufferPosition())
  421. if (matchPosition) {
  422. this.editor.setCursorBufferPosition(matchPosition)
  423. } else {
  424. let startRange, endRange
  425. if (this.hasSyntaxTree()) {
  426. ({startRange, endRange} = this.findMatchingTagsWithSyntaxTree())
  427. } else {
  428. ({startRange, endRange} = this.tagFinder.findStartEndTags())
  429. }
  430. if (startRange) {
  431. if (startRange.compare(endRange) > 0) {
  432. [startRange, endRange] = [endRange, startRange]
  433. }
  434. this.editor.setCursorBufferPosition(startRange.start)
  435. }
  436. }
  437. }
  438. multiCursorSelect() {
  439. this.editor.getCursorBufferPositions().forEach(position => {
  440. let startPosition = this.findPrecedingStartBracket(position)
  441. if(startPosition) {
  442. const startBracket = this.editor.getTextInRange(Range.fromPointWithDelta(startPosition, 0, 1))
  443. const endPosition = this.findMatchingEndBracket(startPosition, startBracket, this.matchManager.pairedCharacters[startBracket])
  444. startPosition = startPosition.traverse([0, 1])
  445. if (startPosition && endPosition) {
  446. const rangeToSelect = new Range(startPosition, endPosition)
  447. this.editor.addSelectionForBufferRange(rangeToSelect)
  448. }
  449. } else {
  450. let startRange, endRange;
  451. if (this.hasSyntaxTree()) {
  452. ({startRange, endRange} = this.findMatchingTagsWithSyntaxTree())
  453. } else {
  454. ({startRange, endRange} = this.tagFinder.findStartEndTags(true))
  455. if (startRange && startRange.compare(endRange) > 0) {
  456. [startRange, endRange] = [endRange, startRange]
  457. }
  458. }
  459. if (startRange) {
  460. const startPosition = startRange.end
  461. const endPosition = endRange.start
  462. const rangeToSelect = new Range(startPosition, endPosition)
  463. this.editor.setSelectedBufferRange(rangeToSelect)
  464. }
  465. }
  466. })
  467. }
  468. selectInsideBrackets() {
  469. let endPosition, endRange, startPosition, startRange
  470. if (this.pairHighlighted) {
  471. startRange = this.startMarker.getBufferRange()
  472. endRange = this.endMarker.getBufferRange()
  473. if (this.tagHighlighted) {
  474. if (this.hasSyntaxTree()) {
  475. ({startRange, endRange} = this.findMatchingTagsWithSyntaxTree())
  476. } else {
  477. ({startRange, endRange} = this.tagFinder.findStartEndTags(true))
  478. if (startRange && startRange.compare(endRange) > 0) {
  479. [startRange, endRange] = [endRange, startRange]
  480. }
  481. }
  482. }
  483. startPosition = startRange.end
  484. endPosition = endRange.start
  485. const rangeToSelect = new Range(startPosition, endPosition)
  486. this.editor.setSelectedBufferRange(rangeToSelect)
  487. } else {
  488. this.multiCursorSelect();
  489. }
  490. }
  491. // Insert at the current cursor position a closing tag if there exists an
  492. // open tag that is not closed afterwards.
  493. closeTag() {
  494. const cursorPosition = this.editor.getCursorBufferPosition()
  495. const preFragment = this.editor.getTextInBufferRange([Point.ZERO, cursorPosition])
  496. const postFragment = this.editor.getTextInBufferRange([cursorPosition, Point.INFINITY])
  497. const tag = this.tagFinder.closingTagForFragments(preFragment, postFragment)
  498. if (tag) {
  499. this.editor.insertText(`</${tag}>`)
  500. }
  501. }
  502. isCursorOnCommentOrString() {
  503. return this.isScopeCommentedOrString(this.editor.getLastCursor().getScopeDescriptor().getScopesArray())
  504. }
  505. isRangeCommentedOrString(range) {
  506. return this.isScopeCommentedOrString(this.editor.scopeDescriptorForBufferPosition(range.start).getScopesArray())
  507. }
  508. isScopeCommentedOrString(scopesArray) {
  509. for (let scope of scopesArray.reverse()) {
  510. scope = scope.split('.')
  511. if (scope.includes('embedded') && scope.includes('source')) return false
  512. if (scope.includes('comment') || scope.includes('string')) return true
  513. }
  514. return false
  515. }
  516. hasSyntaxTree() {
  517. return this.editor.buffer.getLanguageMode().getSyntaxNodeAtPosition
  518. }
  519. }