diff --git a/lib/highlight-selected.js b/lib/highlight-selected.js index ecb49df..0585f50 100644 --- a/lib/highlight-selected.js +++ b/lib/highlight-selected.js @@ -1,5 +1,7 @@ const { CompositeDisposable } = require('atom'); const HighlightedAreaView = require('./highlighted-area-view'); +const ScrollMarkersService = require('./scroll-markers/scroll-markers-service'); +const StatusBarService = require('./status-bar/status-bar-service'); module.exports = { config: { @@ -79,14 +81,21 @@ module.exports = { }, deactivate() { - if (this.areaView != null) { + if (this.areaView) { this.areaView.destroy(); } this.areaView = null; - if (this.subscriptions != null) { + if (this.subscriptions) { this.subscriptions.dispose(); } this.subscriptions = null; + if (this.scrollMarkersService) { + this.scrollMarkersService.destroy(); + } + this.scrollMarkersService = null; + if (this.statusBarService) { + this.statusBarService = null; + } }, provideHighlightSelectedV1Deprecated() { @@ -98,7 +107,7 @@ module.exports = { }, consumeStatusBar(statusBar) { - return this.areaView.setStatusBar(statusBar); + this.statusBarService = new StatusBarService(statusBar, this.areaView); }, toggle() { @@ -113,6 +122,6 @@ module.exports = { }, consumeScrollMarker(scrollMarkerAPI) { - return this.areaView.setScrollMarker(scrollMarkerAPI); + this.scrollMarkersService = new ScrollMarkersService(this.areaView, scrollMarkerAPI); } }; diff --git a/lib/highlighted-area-view.js b/lib/highlighted-area-view.js index 7570fa4..ec7712a 100644 --- a/lib/highlighted-area-view.js +++ b/lib/highlighted-area-view.js @@ -1,6 +1,7 @@ -const { Range, CompositeDisposable, Emitter } = require('atom'); -const StatusBarView = require('./status-bar-view'); -const escapeRegExp = require('./escape-reg-exp'); +const { CompositeDisposable, Emitter } = require('atom'); +const escapeRegExp = require('./utils/escape-reg-exp'); +const getNonWordCharacters = require('./utils/non-word-characters'); +const isWordSelected = require('./utils/is-word-selected'); class EarlyTerminationSignal extends Error {} @@ -51,81 +52,9 @@ module.exports = class HighlightedAreaView { return outcome; } - static getNonWordCharacters(editor, point) { - const scopeDescriptor = editor.scopeDescriptorForBufferPosition(point); - const nonWordCharacters = atom.config.get('editor.nonWordCharacters', { - scope: scopeDescriptor - }); - - return nonWordCharacters; - } - - static ensureScrollViewInstalled() { - if (!atom.inSpecMode()) { - /* eslint-disable */ - require('atom-package-deps').install('highlight-selected', true); - /* eslint-enable */ - } - } - - static isWordSelected(selection) { - if (selection.getBufferRange().isSingleLine()) { - const selectionRange = selection.getBufferRange(); - const lineRange = HighlightedAreaView.getActiveEditor().bufferRangeForBufferRow( - selectionRange.start.row - ); - const nonWordCharacterToTheLeft = - selectionRange.start.isEqual(lineRange.start) || - HighlightedAreaView.isNonWordCharacterToTheLeft(selection); - const nonWordCharacterToTheRight = - selectionRange.end.isEqual(lineRange.end) || - HighlightedAreaView.isNonWordCharacterToTheRight(selection); - - return nonWordCharacterToTheLeft && nonWordCharacterToTheRight; - } - return false; - } - - static isNonWord(editor, range) { - const nonWordCharacters = HighlightedAreaView.getNonWordCharacters(editor, range.start); - const text = editor.getTextInBufferRange(range); - return new RegExp(`[ \t${escapeRegExp(nonWordCharacters)}]`).test(text); - } - - static isNonWordCharacterToTheLeft(selection) { - const selectionStart = selection.getBufferRange().start; - const range = Range.fromPointWithDelta(selectionStart, 0, -1); - return HighlightedAreaView.isNonWord(HighlightedAreaView.getActiveEditor(), range); - } - - static isNonWordCharacterToTheRight(selection) { - const selectionEnd = selection.getBufferRange().end; - const range = Range.fromPointWithDelta(selectionEnd, 0, 1); - return HighlightedAreaView.isNonWord(HighlightedAreaView.getActiveEditor(), range); - } - constructor() { - this.destroy = this.destroy.bind(this); - this.onDidAddMarker = this.onDidAddMarker.bind(this); - this.onDidAddSelectedMarker = this.onDidAddSelectedMarker.bind(this); - this.onDidAddMarkerForEditor = this.onDidAddMarkerForEditor.bind(this); - this.onDidAddSelectedMarkerForEditor = this.onDidAddSelectedMarkerForEditor.bind(this); - this.onDidRemoveAllMarkers = this.onDidRemoveAllMarkers.bind(this); - this.disable = this.disable.bind(this); - this.enable = this.enable.bind(this); - this.setStatusBar = this.setStatusBar.bind(this); this.debouncedHandleSelection = this.debouncedHandleSelection.bind(this); - this.handleSelection = this.handleSelection.bind(this); - this.removeAllMarkers = this.removeAllMarkers.bind(this); - this.removeMarkers = this.removeMarkers.bind(this); - this.setupStatusBar = this.setupStatusBar.bind(this); - this.removeStatusBar = this.removeStatusBar.bind(this); - this.listenForStatusBarChange = this.listenForStatusBarChange.bind(this); - this.selectAll = this.selectAll.bind(this); - this.setScrollMarker = this.setScrollMarker.bind(this); - this.setupMarkerLayers = this.setupMarkerLayers.bind(this); - this.setScrollMarkerView = this.setScrollMarkerView.bind(this); - this.destroyScrollMarkers = this.destroyScrollMarkers.bind(this); + this.emitter = new Emitter(); this.editorToMarkerLayerMap = {}; this.markerLayers = []; @@ -135,7 +64,6 @@ module.exports = class HighlightedAreaView { this.editorSubscriptions.add( atom.workspace.observeTextEditors(editor => { this.setupMarkerLayers(editor); - return this.setScrollMarkerView(editor); }) ); @@ -147,7 +75,6 @@ module.exports = class HighlightedAreaView { const editor = item.item; this.removeMarkers(editor.id); delete this.editorToMarkerLayerMap[editor.id]; - this.destroyScrollMarkers(editor); }) ); @@ -158,39 +85,17 @@ module.exports = class HighlightedAreaView { return this.subscribeToActiveTextEditor(); }); this.subscribeToActiveTextEditor(); - this.listenForStatusBarChange(); - - this.enableScrollViewObserveSubscription = atom.config.observe( - 'highlight-selected.showResultsOnScrollBar', - enabled => { - if (enabled) { - HighlightedAreaView.ensureScrollViewInstalled(); - return atom.workspace.getTextEditors().forEach(this.setScrollMarkerView); - } - return atom.workspace.getTextEditors().forEach(this.destroyScrollMarkers); - } - ); } destroy() { clearTimeout(this.handleSelectionTimeout); this.activeItemSubscription.dispose(); - if (this.selectionSubscription != null) { + if (this.selectionSubscription) { this.selectionSubscription.dispose(); } - if (this.enableScrollViewObserveSubscription != null) { - this.enableScrollViewObserveSubscription.dispose(); - } - if (this.editorSubscriptions != null) { + if (this.editorSubscriptions) { this.editorSubscriptions.dispose(); } - if (this.statusBarView != null) { - this.statusBarView.removeElement(); - } - if (this.statusBarTile != null) { - this.statusBarTile.destroy(); - } - this.statusBarTile = null; } onDidAddMarker(callback) { @@ -213,6 +118,10 @@ module.exports = class HighlightedAreaView { this.emitter.on('did-add-selected-marker-for-editor', callback); } + onDidFinishAddingMarkers(callback) { + this.emitter.on('did-finish-adding-markers', callback); + } + onDidRemoveAllMarkers(callback) { this.emitter.on('did-remove-marker-layer', callback); } @@ -227,11 +136,6 @@ module.exports = class HighlightedAreaView { return this.debouncedHandleSelection(); } - setStatusBar(statusBar) { - this.statusBar = statusBar; - return this.setupStatusBar(); - } - debouncedHandleSelection() { clearTimeout(this.handleSelectionTimeout); this.handleSelectionTimeout = setTimeout(() => { @@ -247,7 +151,7 @@ module.exports = class HighlightedAreaView { } subscribeToActiveTextEditor() { - if (this.selectionSubscription != null) { + if (this.selectionSubscription) { this.selectionSubscription.dispose(); } @@ -301,11 +205,11 @@ module.exports = class HighlightedAreaView { let regexSearch = escapeRegExp(text); if (atom.config.get('highlight-selected.onlyHighlightWholeWords')) { - if (!HighlightedAreaView.isWordSelected(lastSelection)) { + if (!isWordSelected(editor, lastSelection)) { return; } const selectionStart = lastSelection.getBufferRange().start; - const nonWordCharacters = HighlightedAreaView.getNonWordCharacters(editor, selectionStart); + const nonWordCharacters = getNonWordCharacters(editor, selectionStart); const allowedCharactersToSelect = atom.config.get( 'highlight-selected.allowedCharactersToSelect' ); @@ -340,22 +244,20 @@ module.exports = class HighlightedAreaView { this.highlightSelectionInEditor(editor, regexSearch, regexFlags); } - if (this.statusBarElement) { - this.statusBarElement.updateCount(this.resultCount); - } + this.emitter.emit('did-finish-adding-markers'); } highlightSelectionInEditor(editor, regexSearch, regexFlags, originalEditor) { - if (editor == null) { + if (!editor) { return; } const maximumHighlights = atom.config.get('highlight-selected.maximumHighlights'); - if (!(this.resultCount < maximumHighlights)) { + if (this.resultCount > maximumHighlights) { return; } const markerLayers = this.editorToMarkerLayerMap[editor.id]; - if (markerLayers == null) { + if (!markerLayers) { return; } const markerLayer = markerLayers.visibleMarkerLayer; @@ -385,7 +287,7 @@ module.exports = class HighlightedAreaView { }); } - if (newResult == null) { + if (!newResult) { return; } this.resultCount += 1; @@ -425,11 +327,13 @@ module.exports = class HighlightedAreaView { } removeAllMarkers() { - return Object.keys(this.editorToMarkerLayerMap).forEach(this.removeMarkers); + return Object.keys(this.editorToMarkerLayerMap).forEach(editorId => + this.removeMarkers(editorId) + ); } removeMarkers(editorId) { - if (this.editorToMarkerLayerMap[editorId] == null) { + if (!this.editorToMarkerLayerMap[editorId]) { return; } @@ -439,51 +343,14 @@ module.exports = class HighlightedAreaView { markerLayer.clear(); selectedMarkerLayer.clear(); - if (this.statusBarElement != null) { - this.statusBarElement.updateCount(0); - } - + this.resultCount = 0; this.emitter.emit('did-remove-marker-layer'); } - setupStatusBar() { - if (this.statusBarElement != null) { - return; - } - if (!atom.config.get('highlight-selected.showInStatusBar')) { - return; - } - this.statusBarElement = new StatusBarView(); - this.statusBarTile = this.statusBar.addLeftTile({ - item: this.statusBarElement.getElement(), - priority: 100 - }); - } - - removeStatusBar() { - if (this.statusBarElement == null) { - return; - } - if (this.statusBarTile != null) { - this.statusBarTile.destroy(); - } - this.statusBarTile = null; - this.statusBarElement = null; - } - - listenForStatusBarChange() { - return atom.config.onDidChange('highlight-selected.showInStatusBar', changed => { - if (changed.newValue) { - return this.setupStatusBar(); - } - return this.removeStatusBar(); - }); - } - selectAll() { const editor = HighlightedAreaView.getActiveEditor(); const markerLayers = this.editorToMarkerLayerMap[editor.id]; - if (markerLayers == null) { + if (!markerLayers) { return; } const ranges = []; @@ -498,18 +365,10 @@ module.exports = class HighlightedAreaView { } } - setScrollMarker(scrollMarkerAPI) { - this.scrollMarker = scrollMarkerAPI; - if (atom.config.get('highlight-selected.showResultsOnScrollBar')) { - HighlightedAreaView.ensureScrollViewInstalled(); - atom.workspace.getTextEditors().forEach(this.setScrollMarkerView); - } - } - setupMarkerLayers(editor) { let markerLayer; let markerLayerForHiddenMarkers; - if (this.editorToMarkerLayerMap[editor.id] != null) { + if (this.editorToMarkerLayerMap[editor.id]) { markerLayer = this.editorToMarkerLayerMap[editor.id].visibleMarkerLayer; markerLayerForHiddenMarkers = this.editorToMarkerLayerMap[editor.id].selectedMarkerLayer; } @@ -520,32 +379,4 @@ module.exports = class HighlightedAreaView { selectedMarkerLayer: markerLayerForHiddenMarkers }; } - - setScrollMarkerView(editor) { - if (!atom.config.get('highlight-selected.showResultsOnScrollBar')) { - return; - } - if (this.scrollMarker == null) { - return; - } - - const scrollMarkerView = this.scrollMarker.scrollMarkerViewForEditor(editor); - - const markerLayer = this.editorToMarkerLayerMap[editor.id].visibleMarkerLayer; - const { selectedMarkerLayer } = this.editorToMarkerLayerMap[editor.id]; - - scrollMarkerView.getLayer('highlight-selected-marker-layer').syncToMarkerLayer(markerLayer); - scrollMarkerView - .getLayer('highlight-selected-selected-marker-layer') - .syncToMarkerLayer(selectedMarkerLayer); - } - - destroyScrollMarkers(editor) { - if (this.scrollMarker == null) { - return; - } - - const scrollMarkerView = this.scrollMarker.scrollMarkerViewForEditor(editor); - scrollMarkerView.destroy(); - } }; diff --git a/lib/scroll-markers/scroll-markers-service.js b/lib/scroll-markers/scroll-markers-service.js new file mode 100644 index 0000000..671205f --- /dev/null +++ b/lib/scroll-markers/scroll-markers-service.js @@ -0,0 +1,90 @@ +const { CompositeDisposable } = require('atom'); + +module.exports = class ScrollMarkersService { + static ensureScrollViewInstalled() { + if (!atom.inSpecMode()) { + /* eslint-disable */ + require('atom-package-deps').install('highlight-selected', true); + /* eslint-enable */ + } + } + + constructor(areaView, scrollMarkerAPI) { + this.areaView = areaView; + this.scrollMarkerAPI = scrollMarkerAPI; + + if (atom.config.get('highlight-selected.showResultsOnScrollBar')) { + ScrollMarkersService.ensureScrollViewInstalled(); + atom.workspace.getTextEditors().forEach(editor => this.setScrollMarkerView(editor)); + } + + this.setupEditorSubscriptions(); + this.setupConfigObserver(); + } + + destroy() { + this.editorSubscriptions.dispose(); + this.enableScrollViewObserveSubscription.dispose(); + } + + setupEditorSubscriptions() { + this.editorSubscriptions = new CompositeDisposable(); + this.editorSubscriptions.add( + atom.workspace.observeTextEditors(editor => { + this.setScrollMarkerView(editor); + }) + ); + + this.editorSubscriptions.add( + atom.workspace.onWillDestroyPaneItem(item => { + if (item.item.constructor.name !== 'TextEditor') { + return; + } + const editor = item.item; + this.destroyScrollMarkers(editor); + }) + ); + } + + setupConfigObserver() { + this.enableScrollViewObserveSubscription = atom.config.observe( + 'highlight-selected.showResultsOnScrollBar', + enabled => { + if (enabled) { + ScrollMarkersService.ensureScrollViewInstalled(); + atom.workspace.getTextEditors().forEach(editor => this.setScrollMarkerView(editor)); + } else { + atom.workspace.getTextEditors().forEach(editor => this.destroyScrollMarkers(editor)); + } + } + ); + } + + setScrollMarkerView(editor) { + if (!atom.config.get('highlight-selected.showResultsOnScrollBar')) { + return; + } + if (!this.scrollMarkerAPI) { + return; + } + + const scrollMarkerView = this.scrollMarkerAPI.scrollMarkerViewForEditor(editor); + + const markerLayer = this.areaView.editorToMarkerLayerMap[editor.id].visibleMarkerLayer; + const { selectedMarkerLayer } = this.areaView.editorToMarkerLayerMap[editor.id]; + + scrollMarkerView.getLayer('highlight-selected-marker-layer').syncToMarkerLayer(markerLayer); + scrollMarkerView + .getLayer('highlight-selected-selected-marker-layer') + .syncToMarkerLayer(selectedMarkerLayer); + } + + destroyScrollMarkers(editor) { + if (!this.scrollMarkerAPI) { + return; + } + + const scrollMarkerView = this.scrollMarkerAPI.scrollMarkerViewForEditor(editor); + scrollMarkerView.destroy(); + } +}; diff --git a/lib/status-bar/status-bar-service.js b/lib/status-bar/status-bar-service.js new file mode 100644 index 0000000..b5e2d0f --- /dev/null +++ b/lib/status-bar/status-bar-service.js @@ -0,0 +1,69 @@ +const StatusBarView = require('./status-bar-view'); + +module.exports = class StatusBarService { + constructor(statusBar, areaView) { + this.statusBar = statusBar; + this.areaView = areaView; + + this.updateCount = this.updateCount.bind(this); + + this.listenForStatusBarConfigChange(); + this.setupListeners(); + + this.setupStatusBarView(); + } + + destroy() { + this.removeStatusBarView(); + if (this.selectionSubscription) { + this.selectionSubscription.dispose(); + } + } + + listenForStatusBarConfigChange() { + return atom.config.onDidChange('highlight-selected.showInStatusBar', changed => { + if (changed.newValue) { + return this.setupStatusBarView(); + } + return this.removeStatusBarView(); + }); + } + + setupListeners() { + this.areaView.onDidFinishAddingMarkers(this.updateCount); + this.areaView.onDidRemoveAllMarkers(this.updateCount); + } + + setupStatusBarView() { + if (this.statusBarElement) { + return; + } + if (!atom.config.get('highlight-selected.showInStatusBar')) { + return; + } + this.statusBarElement = new StatusBarView(); + this.statusBarTile = this.statusBar.addLeftTile({ + item: this.statusBarElement.getElement(), + priority: 100 + }); + } + + removeStatusBarView() { + if (!this.statusBarElement) { + return; + } + this.statusBarElement.removeElement(); + if (this.statusBarTile) { + this.statusBarTile.destroy(); + } + this.statusBarTile = null; + this.statusBarElement = null; + } + + updateCount() { + if (!this.statusBarElement) { + return; + } + this.statusBarElement.updateCount(this.areaView.resultCount); + } +}; diff --git a/lib/status-bar-view.js b/lib/status-bar/status-bar-view.js similarity index 100% rename from lib/status-bar-view.js rename to lib/status-bar/status-bar-view.js diff --git a/lib/escape-reg-exp.js b/lib/utils/escape-reg-exp.js similarity index 100% rename from lib/escape-reg-exp.js rename to lib/utils/escape-reg-exp.js diff --git a/lib/utils/is-word-selected.js b/lib/utils/is-word-selected.js new file mode 100644 index 0000000..59c64c0 --- /dev/null +++ b/lib/utils/is-word-selected.js @@ -0,0 +1,36 @@ +const { Range } = require('atom'); +const escapeRegExp = require('./escape-reg-exp'); +const getNonWordCharacters = require('./non-word-characters'); + +function isNonWord(editor, range) { + const nonWordCharacters = getNonWordCharacters(editor, range.start); + const text = editor.getTextInBufferRange(range); + return new RegExp(`[ \t${escapeRegExp(nonWordCharacters)}]`).test(text); +} + +function isNonWordCharacterToTheLeft(editor, selection) { + const selectionStart = selection.getBufferRange().start; + const range = Range.fromPointWithDelta(selectionStart, 0, -1); + return isNonWord(editor, range); +} + +function isNonWordCharacterToTheRight(editor, selection) { + const selectionEnd = selection.getBufferRange().end; + const range = Range.fromPointWithDelta(selectionEnd, 0, 1); + return isNonWord(editor, range); +} + +module.exports = function isWordSelected(editor, selection) { + if (selection.getBufferRange().isSingleLine()) { + const selectionRange = selection.getBufferRange(); + const lineRange = editor.bufferRangeForBufferRow(selectionRange.start.row); + const nonWordCharacterToTheLeft = + selectionRange.start.isEqual(lineRange.start) || + isNonWordCharacterToTheLeft(editor, selection); + const nonWordCharacterToTheRight = + selectionRange.end.isEqual(lineRange.end) || isNonWordCharacterToTheRight(editor, selection); + + return nonWordCharacterToTheLeft && nonWordCharacterToTheRight; + } + return false; +}; diff --git a/lib/utils/non-word-characters.js b/lib/utils/non-word-characters.js new file mode 100644 index 0000000..b9258d9 --- /dev/null +++ b/lib/utils/non-word-characters.js @@ -0,0 +1,8 @@ +module.exports = function getNonWordCharacters(editor, point) { + const scopeDescriptor = editor.scopeDescriptorForBufferPosition(point); + const nonWordCharacters = atom.config.get('editor.nonWordCharacters', { + scope: scopeDescriptor + }); + + return nonWordCharacters; +};