Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite code for maintainability #15

Merged
merged 18 commits into from Jan 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 5 additions & 31 deletions README.md
Expand Up @@ -10,7 +10,10 @@

- `quick-highlight:toggle` toggle highlight for selected or cursor word.
- `quick-highlight:clear` clear all highlight.
- `vim-mode-plus-user:quick-highlight` Operator for [vim-mode-plus](https://atom.io/packages/vim-mode-plus).

And following two operator for [vim-mode-plus](https://atom.io/packages/vim-mode-plus) user.
- `vim-mode-plus-user:quick-highlight`: Operator to highlight by text-object or motion.
- `vim-mode-plus-user:quick-highlight-word` Highlight cursor word, similar to `quick-highlight:toggle`, but well fit for vim's block cursor orientation. And `.` repeatable.

# Keymap

Expand All @@ -24,17 +27,10 @@ e.g.
'cmd-k M': 'quick-highlight:clear'
```

* vim-mode user
```coffeescript
'atom-text-editor.vim-mode.normal-mode, atom-text-editor.vim-mode.visual-mode':
'space m': 'quick-highlight:toggle'
'space M': 'quick-highlight:clear'
```

* vim-mode-plus user
```coffeescript
'atom-text-editor.vim-mode-plus.normal-mode, atom-text-editor.vim-mode-plus.visual-mode':
'space m': 'quick-highlight:toggle'
'space m': 'vim-mode-plus-user:quick-highlight-word'
'space M': 'quick-highlight:clear'
'g m': 'vim-mode-plus-user:quick-highlight'
```
Expand All @@ -54,28 +50,6 @@ atom-text-editor .quick-highlight.box-selection .region {
background-color: transparent;
border-color: @syntax-text-color;
}

// For manual-highlight(0 to 7) color
//===================================
// Less mixin to make manual-highlight to underlined style
.quick-highlight-underline(@name) {
.quick-highlight.@{name} .region {
border-width: 0px;
border-radius: 0px;
border-bottom-width: 2px;
border-bottom-style: solid;
}
}

atom-text-editor {
.quick-highlight-underline(box-01);
.quick-highlight-underline(box-02);
.quick-highlight-underline(box-03);
.quick-highlight-underline(box-04);
.quick-highlight-underline(box-05);
.quick-highlight-underline(box-06);
.quick-highlight-underline(box-07);
}
```

## vim-mode-plus operator
Expand Down
38 changes: 38 additions & 0 deletions lib/keyword-manager.coffee
@@ -0,0 +1,38 @@
{Emitter} = require 'atom'

module.exports =
class KeywordManager
colorNumbers: ['01', '02', '03', '04', '05', '06', '07']
latestKeyword: null

onDidChangeKeyword: (fn) -> @emitter.on('did-change-keyword', fn)
emitDidChangeKeyword: -> @emitter.emit('did-change-keyword')

onDidClearKeyword: (fn) -> @emitter.on('did-clear-keyword', fn)
emitDidClearKeyword: -> @emitter.emit('did-clear-keyword')

constructor: ->
@emitter = new Emitter
@reset()

reset: ->
@keywordToColor = Object.create(null)
@colorIndex = -1

toggle: (keyword) ->
if keyword of @keywordToColor
delete @keywordToColor[keyword]
else
@keywordToColor[keyword] = @getNextColor()
@latestKeyword = keyword
@emitDidChangeKeyword()

clear: ->
@reset()
@emitDidClearKeyword()

getNextColor: ->
@colorIndex = (@colorIndex + 1) % @colorNumbers.length
@colorNumbers[@colorIndex]

destroy: ->
194 changes: 23 additions & 171 deletions lib/main.coffee
@@ -1,201 +1,47 @@
{CompositeDisposable, Disposable, Emitter, Range} = require 'atom'
_ = require 'underscore-plus'
StatusBarManager = require './status-bar-manager'
settings = require './settings'
QuickHighlightView = require './quick-highlight-view'
KeywordManager = require './keyword-manager'
StatusBarManager = require './status-bar-manager'

{
matchScope
getVisibleEditors
getVisibleBufferRange
getCountForKeyword
getCursorWord
} = require './utils'

# Utils
# -------------------------
class KeywordManager
colors: ['01', '02', '03', '04', '05', '06', '07']
index: null
constructor: ->
@reset()
add: (keyword) ->
@index = (@index + 1) % @colors.length
@kw2color[keyword] = @colors[@index]
delete: (keyword) ->
delete @kw2color[keyword]
has: (keyword) ->
keyword of @kw2color
reset: (keyword) ->
@kw2color = Object.create(null)
@index = -1
each: (fn) ->
fn(keyword, color) for keyword, color of @kw2color

module.exports =
config: settings.config

activate: (state) ->
@subscriptions = new CompositeDisposable
@emitter = new Emitter
@decorationsByEditor = new Map
@keywords = new KeywordManager()
@viewByEditor = new Map
@keywordManager = new KeywordManager
@statusBarManager = new StatusBarManager

toggle = @toggle.bind(this)
@subscribe atom.commands.add 'atom-text-editor:not([mini])',
@subscriptions.add atom.commands.add 'atom-text-editor:not([mini])',
'quick-highlight:toggle': -> toggle(@getModel())
'quick-highlight:clear': => @clear()

debouncedhighlightSelection = null
settings.observe 'highlightSelectionDelay', (delay) =>
debouncedhighlightSelection = _.debounce(@highlightSelection.bind(this), delay)

@subscribe atom.workspace.observeTextEditors (editor) =>
editorSubs = new CompositeDisposable
editorSubs.add editor.onDidStopChanging =>
return unless atom.workspace.getActiveTextEditor() is editor
URI = editor.getURI()
for e in getVisibleEditors() when (e.getURI() is URI)
@refreshEditor(e)

editorElement = editor.element
editorSubs.add(editor.element.onDidChangeScrollTop => @refreshEditor(editor))

# [FIXME]
# @refreshEditor depends on editorElement.getVisibleRowRange() but it return
# [undefined, undefined] when it called on editorElement which is not attached yet.
# So we separately need to cover this case from Atom v1.1.0
editorSubs.add(editorElement.onDidAttach => @refreshEditor(editor))

editorSubs.add editor.onDidChangeSelectionRange ({selection}) =>
if selection.isLastSelection() and not @isLocked()
debouncedhighlightSelection(editor)
editorSubs.add(editorElement.onDidChangeScrollTop => @highlightSelection(editor))

editorSubs.add editor.onDidDestroy =>
@clearEditor(editor)
editorSubs.dispose()
@unsubscribe(editorSubs)

@subscribe(editorSubs)

@subscribe atom.workspace.onDidChangeActivePaneItem (item) =>
@statusBarManager.clear()
if item?.getText? # Check if instance of TextEditor
@refreshEditor(item)
@highlightSelection(item)

subscribe: (args...) ->
@subscriptions.add args...

unsubscribe: (arg) ->
@subscriptions.remove(arg)
'quick-highlight:clear': => @keywordManager.clear()

clearSelectionDecoration: ->
d.getMarker().destroy() for d in @selectionDecorations ? []
@selectionDecorations = null

shouldExcludeEditor: (editor) ->
scopes = settings.get('highlightSelectionExcludeScopes')
scopes.some (scope) ->
matchScope(editor.element, scope)

highlightSelection: (editor) ->
@clearSelectionDecoration()
return if @shouldExcludeEditor(editor)
selection = editor.getLastSelection()
return unless @needToHighlightSelection(selection)
keyword = selection.getText()
return unless scanRange = getVisibleBufferRange(editor)
@selectionDecorations = @highlightKeyword(editor, scanRange, keyword, 'box-selection')

needToHighlightSelection: (selection) ->
switch
when (not settings.get('highlightSelection'))
, selection.isEmpty()
, not selection.getBufferRange().isSingleLine()
, selection.getText().length < settings.get('highlightSelectionMinimumLength')
, /[^\S]/.test(selection.getText())
false
else
true
@subscriptions.add atom.workspace.observeTextEditors (editor) =>
view = new QuickHighlightView(editor, {@keywordManager, @statusBarManager})
@viewByEditor.set(editor, view)

deactivate: ->
@clear()
@clearSelectionDecoration()
@keywordManager.destroy()
@viewByEditor.forEach (view) -> view.destroy()
@subscriptions.dispose()
{@decorationsByEditor, @subscriptions, @keywords} = {}

locked: false
isLocked: ->
@locked

withLock: (fn) ->
try
@locked = true
value = fn()
finally
@locked = false
value
@subscriptions = null

toggle: (editor, keyword) ->
keyword ?= editor.getSelectedText() or @withLock(-> getCursorWord(editor))
if @keywords.has(keyword)
@keywords.delete(keyword)
@statusBarManager.clear()
else
@keywords.add(keyword)
if settings.get('displayCountOnStatusBar')
@statusBarManager.update(@getCountForKeyword(editor, keyword))
@refreshEditor(editor) for editor in getVisibleEditors()

refreshEditor: (editor) ->
@clearEditor(editor)
@renderEditor(editor)

renderEditor: (editor) ->
return unless scanRange = getVisibleBufferRange(editor)
decorations = []
decorationStyle = settings.get('decorate')
@keywords.each (keyword, color) =>
color = "#{decorationStyle}-#{color}"
decorations = decorations.concat(@highlightKeyword(editor, scanRange, keyword, color))
@decorationsByEditor.set(editor, decorations)

highlightKeyword: (editor, scanRange, keyword, color) ->
return [] unless editor.isAlive()
classNames = "quick-highlight #{color}"
pattern = ///#{_.escapeRegExp(keyword)}///g
decorations = []
editor.scanInBufferRange pattern, scanRange, ({range}) =>
decorations.push(@decorateRange(editor, range, {classNames}))
decorations

clearEditor: (editor) ->
if @decorationsByEditor.has(editor)
d.getMarker().destroy() for d in @decorationsByEditor.get(editor)
@decorationsByEditor.delete(editor)

clear: ->
@decorationsByEditor.forEach (decorations, editor) =>
@clearEditor(editor)
@decorationsByEditor.clear()
@keywords.reset()
@statusBarManager.clear()

decorateRange: (editor, range, {classNames}) ->
marker = editor.markBufferRange(range, invalidate: 'inside')
editor.decorateMarker(marker, {type: 'highlight', class: classNames})

getCountForKeyword: (editor, keyword) ->
getCountForKeyword(editor, keyword)
keyword ?= editor.getSelectedText() or getCursorWord(editor)
@keywordManager.toggle(keyword)

consumeStatusBar: (statusBar) ->
@statusBarManager.initialize(statusBar)
@statusBarManager.attach()
@subscriptions.add new Disposable =>
@statusBarManager.detach()
@statusBarManager = null
@subscriptions.add(new Disposable => @statusBarManager.detach())

consumeVim: ({Base}) ->
toggle = @toggle.bind(this)
Expand All @@ -207,4 +53,10 @@ module.exports =
mutateSelection: (selection) ->
toggle(@editor, selection.getText())

@subscribe(QuickHighlight.registerCommand())
class QuickHighlightWord extends QuickHighlight
target: "InnerWord"

@subscriptions.add(
QuickHighlight.registerCommand()
QuickHighlightWord.registerCommand()
)