quick-highlight-view.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. const _ = require('underscore-plus')
  2. const {CompositeDisposable} = require('atom')
  3. function matchScopes ({classList}, scopes = []) {
  4. return scopes.some(scope => scope.split('.').every(name => classList.contains(name)))
  5. }
  6. function getConfig (name) {
  7. return atom.config.get(`auto-highlight.${name}`)
  8. }
  9. function getVisibleEditors () {
  10. return atom.workspace
  11. .getPanes()
  12. .map(pane => pane.getActiveEditor())
  13. .filter(v => v)
  14. }
  15. function isVisibleEditor (editor) {
  16. return getVisibleEditors().includes(editor)
  17. }
  18. function isActiveEditor (editor) {
  19. return atom.workspace.getActiveTextEditor() === editor
  20. }
  21. // - Refresh onDidChangeActivePaneItem
  22. // - But dont't refresh invisible editor
  23. // - Update statusbar count on activeEditor was changed
  24. // - Clear marker for invisible editor?: Decide to skip to avoid clere/re-render.
  25. // - Update only keyword added/remove: Achived by diffing.
  26. class QuickHighlightView {
  27. static initClass () { this.viewByEditor = new Map() } // prettier-ignore
  28. static register (view) { this.viewByEditor.set(view.editor, view) } // prettier-ignore
  29. static unregister (view) { this.viewByEditor.delete(view.editor) } // prettier-ignore
  30. static destroyAll () { this.viewByEditor.forEach(view => view.destroy()) } // prettier-ignore
  31. static clearAll () { this.viewByEditor.forEach(view => view.clear()) } // prettier-ignore
  32. static refreshVisibles () {
  33. const editors = getVisibleEditors()
  34. for (const editor of editors) {
  35. const view = this.viewByEditor.get(editor)
  36. if (view) {
  37. view.refresh()
  38. }
  39. }
  40. }
  41. constructor (editor, {keywordManager, statusBarManager, emitter}) {
  42. this.editor = editor
  43. this.keywordManager = keywordManager
  44. this.statusBarManager = statusBarManager
  45. this.emitter = emitter
  46. this.markerLayerByKeyword = new Map()
  47. this.highlightSelection = this.highlightSelection.bind(this)
  48. let highlightSelection
  49. this.disposables = new CompositeDisposable(
  50. atom.config.observe('auto-highlight.highlightSelectionDelay', delay => {
  51. highlightSelection = _.debounce(this.highlightSelection, delay)
  52. }),
  53. this.editor.onDidDestroy(() => this.destroy()),
  54. this.editor.onDidChangeSelectionRange(({selection}) => {
  55. if (selection.isEmpty()) {
  56. this.clearSelectionHighlight()
  57. } else {
  58. highlightSelection(selection)
  59. }
  60. }),
  61. this.editor.onDidStopChanging(() => {
  62. this.clear()
  63. if (isVisibleEditor(this.editor)) {
  64. this.refresh()
  65. }
  66. })
  67. )
  68. QuickHighlightView.register(this)
  69. }
  70. needSelectionHighlight (text) {
  71. return (
  72. getConfig('highlightSelection') &&
  73. !matchScopes(this.editor.element, getConfig('highlightSelectionExcludeScopes')) &&
  74. text.length >= getConfig('highlightSelectionMinimumLength') &&
  75. !/\n/.test(text) &&
  76. /\S/.test(text)
  77. )
  78. }
  79. highlightSelection (selection) {
  80. this.clearSelectionHighlight()
  81. const keyword = selection.getText()
  82. if (this.needSelectionHighlight(keyword)) {
  83. this.markerLayerForSelectionHighlight = this.highlight(keyword, 'box-selection')
  84. }
  85. }
  86. clearSelectionHighlight () {
  87. if (this.markerLayerForSelectionHighlight) this.markerLayerForSelectionHighlight.destroy()
  88. this.markerLayerForSelectionHighlight = null
  89. }
  90. highlight (keyword, color) {
  91. const markerLayer = this.editor.addMarkerLayer()
  92. this.editor.decorateMarkerLayer(markerLayer, {type: 'highlight', class: `auto-highlight ${color}`})
  93. const regex = new RegExp(`${_.escapeRegExp(keyword)}`, 'g')
  94. this.editor.scan(regex, ({range}) => markerLayer.markBufferRange(range, {invalidate: 'inside'}))
  95. if (markerLayer.getMarkerCount()) {
  96. this.emitter.emit('did-change-highlight', {
  97. editor: this.editor,
  98. markers: markerLayer.getMarkers(),
  99. color: color
  100. })
  101. }
  102. return markerLayer
  103. }
  104. getDiff () {
  105. const masterKeywords = this.keywordManager.getKeywords()
  106. const currentKeywords = [...this.markerLayerByKeyword.keys()]
  107. const newKeywords = _.without(masterKeywords, ...currentKeywords)
  108. const oldKeywords = _.without(currentKeywords, ...masterKeywords)
  109. if (newKeywords.length || oldKeywords.length) {
  110. return {newKeywords, oldKeywords}
  111. }
  112. }
  113. render ({newKeywords, oldKeywords}) {
  114. // Delete
  115. for (const keyword of oldKeywords) {
  116. this.markerLayerByKeyword.get(keyword).destroy()
  117. this.markerLayerByKeyword.delete(keyword)
  118. }
  119. // Add
  120. for (const keyword of newKeywords) {
  121. const color = this.keywordManager.getColorForKeyword(keyword)
  122. if (color) {
  123. const decorationColor = atom.config.get('auto-highlight.decorate')
  124. const markerLayer = this.highlight(keyword, `${decorationColor}-${color}`)
  125. this.markerLayerByKeyword.set(keyword, markerLayer)
  126. }
  127. }
  128. }
  129. clear () {
  130. this.markerLayerByKeyword.forEach(layer => layer.destroy())
  131. this.markerLayerByKeyword.clear()
  132. }
  133. refresh () {
  134. const diff = this.getDiff()
  135. if (diff) {
  136. this.render(diff)
  137. }
  138. this.updateStatusBarIfNecesssary()
  139. }
  140. updateStatusBarIfNecesssary () {
  141. if (getConfig('displayCountOnStatusBar') && isActiveEditor(this.editor)) {
  142. this.statusBarManager.clear()
  143. const keyword = this.keywordManager.latestKeyword
  144. const layer = this.markerLayerByKeyword.get(keyword)
  145. const count = layer ? layer.getMarkerCount() : 0
  146. if (count) this.statusBarManager.update(count)
  147. }
  148. }
  149. destroy () {
  150. this.disposables.dispose()
  151. this.clear()
  152. this.clearSelectionHighlight()
  153. QuickHighlightView.unregister(this)
  154. }
  155. }
  156. QuickHighlightView.initClass()
  157. module.exports = QuickHighlightView