Skip to content

Instantly share code, notes, and snippets.

@ithil
Created November 22, 2018 23:30
Show Gist options
  • Save ithil/98949b5dd02365623954fd8790768103 to your computer and use it in GitHub Desktop.
Save ithil/98949b5dd02365623954fd8790768103 to your computer and use it in GitHub Desktop.
Old Jumpy with multiple modes
# FIXME: Beacon code (currently broken in shadow). This will probably return
# in the form of a decoration with a "flash", not sure yet.
# TODO: Merge in @willdady's code for better accuracy.
# TODO: Remove space-pen?
### global atom ###
{CompositeDisposable, Point, Range} = require 'atom'
{View, $} = require 'space-pen'
_ = require 'lodash'
path = require 'path'
fs = require 'fs'
lowerCharacters =
(String.fromCharCode(a) for a in ['a'.charCodeAt()..'z'.charCodeAt()])
upperCharacters =
(String.fromCharCode(a) for a in ['A'.charCodeAt()..'Z'.charCodeAt()])
keys = []
# A little ugly.
# I used itertools.permutation in python.
# Couldn't find a good one in npm. Don't worry this takes < 1ms once.
for c1 in lowerCharacters
for c2 in lowerCharacters
keys.push c1 + c2
for c1 in upperCharacters
for c2 in lowerCharacters
keys.push c1 + c2
for c1 in lowerCharacters
for c2 in upperCharacters
keys.push c1 + c2
class JumpyView extends View
@content: ->
@div ''
initialize: () ->
@disposables = new CompositeDisposable()
@decorations = []
@commands = new CompositeDisposable()
@commands.add atom.commands.add 'atom-workspace',
'jumpy:toggle': => @toggle()
'jumpy:reset': => @reset()
'jumpy:clear': => @clearJumpMode()
commands = {}
for characterSet in [lowerCharacters, upperCharacters]
for c in characterSet
do (c) => commands['jumpy:' + c] = => @getKey(c)
@commands.add atom.commands.add 'atom-workspace', commands
@customPatterns()
# TODO: consider moving this into toggle for new bindings.
@backedUpKeyBindings = _.clone atom.keymaps.keyBindings
@workspaceElement = atom.views.getView(atom.workspace)
@statusBar = document.querySelector 'status-bar'
@statusBar?.addLeftTile
item: $('<div id="status-bar-jumpy" class="inline-block"></div>')
priority: -1
@statusBarJumpy = document.getElementById 'status-bar-jumpy'
getKey: (character) ->
@statusBarJumpy?.classList.remove 'no-match'
isMatchOfCurrentLabels = (character, labelPosition) =>
found = false
@disposables.add atom.workspace.observeTextEditors (editor) =>
editorView = atom.views.getView(editor)
return if $(editorView).is ':not(:visible)'
for decoration in @decorations
element = decoration.getProperties().item
if element.textContent[labelPosition] == character
found = true
return false
return found
# Assert: labelPosition will start at 0!
labelPosition = (if not @firstChar then 0 else 1)
if !isMatchOfCurrentLabels character, labelPosition
@statusBarJumpy?.classList.add 'no-match'
@statusBarJumpyStatus?.innerHTML = 'No match!'
return
if not @firstChar
@firstChar = character
@statusBarJumpyStatus?.innerHTML = @firstChar
# TODO: Refactor this so not 2 calls to observeTextEditors
@disposables.add atom.workspace.observeTextEditors (editor) =>
editorView = atom.views.getView(editor)
return if $(editorView).is ':not(:visible)'
for decoration in @decorations
element = decoration.getProperties().item
if element.textContent.indexOf(@firstChar) != 0
element.classList.add 'irrelevant'
else if not @secondChar
@secondChar = character
if @secondChar
@jump() # Jump first. Currently need the placement of the labels.
@clearJumpMode()
clearKeys: ->
@firstChar = null
@secondChar = null
reset: ->
@clearKeys()
for decoration in @decorations
decoration.getProperties().item.classList.remove 'irrelevant'
@statusBarJumpy?.classList.remove 'no-match'
@statusBarJumpyStatus?.innerHTML = 'Jump Mode!'
getFilteredJumpyKeys: ->
atom.keymaps.keyBindings.filter (keymap) ->
keymap.command
.indexOf('jumpy') > -1 if typeof keymap.command is 'string'
turnOffSlowKeys: ->
atom.keymaps.keyBindings = @getFilteredJumpyKeys()
toggle: (opt) ->
@clearJumpMode()
# Set dirty for @clearJumpMode
@cleared = false
opt = opt or { }
# TODO: Can the following few lines be singleton'd up? ie. instance var?
wordsPattern = new RegExp ( opt.customWordsPattern or (atom.config.get 'jumpy.matchPattern')), 'g'
fontSize = atom.config.get 'jumpy.fontSize'
fontSize = .75 if isNaN(fontSize) or fontSize > 1
fontSize = (fontSize * 100) + '%'
highContrast = atom.config.get 'jumpy.highContrast'
@turnOffSlowKeys()
@statusBarJumpy?.classList.remove 'no-match'
@statusBarJumpy?.innerHTML =
'Jumpy: <span class="status">Jump Mode!</span>'
@statusBarJumpyStatus =
document.querySelector '#status-bar-jumpy .status'
@allPositions = {}
nextKeys = _.clone keys
@disposables.add atom.workspace.observeTextEditors (editor) =>
editorView = atom.views.getView(editor)
$editorView = $(editorView)
return if $editorView.is ':not(:visible)'
# 'jumpy-jump-mode is for keymaps and utilized by tests
editorView.classList.add 'jumpy-jump-mode'
getVisibleColumnRange = (editorView) ->
charWidth = editorView.getDefaultCharacterWidth()
# FYI: asserts:
# numberOfVisibleColumns = editorView.getWidth() / charWidth
minColumn = (editorView.getScrollLeft() / charWidth) - 1
maxColumn = editorView.getScrollRight() / charWidth
return [
minColumn
maxColumn
]
drawLabels = (lineNumber, column) =>
return unless nextKeys.length
keyLabel = nextKeys.shift()
position = {row: lineNumber, column: column}
# creates a reference:
@allPositions[keyLabel] =
editor: editor.id
position: position
marker = editor.markScreenRange new Range(
new Point(lineNumber, column),
new Point(lineNumber, column)),
invalidate: 'touch'
labelElement = document.createElement('div')
labelElement.textContent = keyLabel
labelElement.style.fontSize = fontSize
labelElement.classList.add 'jumpy-label'
if highContrast
labelElement.classList.add 'high-contrast'
decoration = editor.decorateMarker marker,
type: 'overlay'
item: labelElement
position: 'head'
@decorations.push decoration
[minColumn, maxColumn] = getVisibleColumnRange editorView
rows = editor.getVisibleRowRange()
if rows
[firstVisibleRow, lastVisibleRow] = rows
# TODO: Right now there are issues with lastVisbleRow
for lineNumber in [firstVisibleRow...lastVisibleRow]
lineContents = editor.lineTextForScreenRow(lineNumber)
if editor.isFoldedAtScreenRow(lineNumber)
drawLabels lineNumber, 0
else
while ((word = wordsPattern.exec(lineContents)) != null)
column = word.index
# Do not do anything... markers etc.
# if the columns are out of bounds...
if column > minColumn && column < maxColumn
drawLabels lineNumber, column
@initializeClearEvents(editorView)
clearJumpModeHandler: =>
@clearJumpMode()
initializeClearEvents: (editorView) ->
@disposables.add editorView.onDidChangeScrollTop =>
@clearJumpModeHandler()
@disposables.add editorView.onDidChangeScrollLeft =>
@clearJumpModeHandler()
for e in ['blur', 'click']
editorView.addEventListener e, @clearJumpModeHandler, true
clearJumpMode: ->
clearAllMarkers = =>
for decoration in @decorations
decoration.getMarker().destroy()
@decorations = [] # Very important for GC.
# Verifiable in Dev Tools -> Timeline -> Nodes.
if @cleared
return
@cleared = true
@clearKeys()
@statusBarJumpy?.innerHTML = ''
@disposables.add atom.workspace.observeTextEditors (editor) =>
editorView = atom.views.getView(editor)
editorView.classList.remove 'jumpy-jump-mode'
for e in ['blur', 'click']
editorView.removeEventListener e, @clearJumpModeHandler, true
atom.keymaps.keyBindings = @backedUpKeyBindings
clearAllMarkers()
@disposables?.dispose()
@detach()
jump: ->
location = @findLocation()
if location == null
return
@disposables.add atom.workspace.observeTextEditors (currentEditor) ->
editorView = atom.views.getView(currentEditor)
# Prevent other editors from jumping cursors as well
# TODO: make a test for this return if
return if currentEditor.id != location.editor
pane = atom.workspace.paneForItem(currentEditor)
pane.activate()
isVisualMode = editorView.classList.contains 'visual-mode'
isSelected = (currentEditor.getSelections().length == 1 &&
currentEditor.getSelectedText() != '')
if (isVisualMode || isSelected)
currentEditor.selectToScreenPosition location.position
else
currentEditor.setCursorScreenPosition location.position
useHomingBeacon =
atom.config.get 'jumpy.useHomingBeaconEffectOnJumps'
if useHomingBeacon
cursor = editorView.shadowRoot.querySelector '.cursors .cursor'
if cursor
cursor.classList.add 'beacon'
setTimeout ->
cursor.classList.remove 'beacon'
, 150
findLocation: ->
label = "#{@firstChar}#{@secondChar}"
if label of @allPositions
return @allPositions[label]
return null
# Returns an object that can be retrieved when package is activated
serialize: ->
# Tear down any state and detach
destroy: ->
@commands?.dispose()
@clearJumpMode()
customPatterns: ->
filePath = atom.config.get('jumpy.customPatternsConfigPath')
return {} unless fs.existsSync(filePath)
try
CSON = require 'season'
config = CSON.readFileSync(filePath)
catch error
message = '[jumpy] custom config file couldn\'t be parsed'
options =
detail: error.message
atom.notifications.addError message, options
thisClass = this
generalFn = (thisClass, regexp) ->
console.log regexp
opt = { }
opt.customWordsPattern = regexp
thisClass.toggle(opt)
for key, value of config
# customFn = =>
# opt = { }
# opt.customWordsPattern = value['regexp']
# thisClass.toggle(opt)
# customFn = ->
# opt = { }
# opt.customWordsPattern = value['regexp']
# thisClass.toggle(opt)
regexp = value['regexp']
obj = { }
obj["jumpy:custom-#{key}"] = generalFn.bind this, thisClass, regexp
atom.commands.add('atom-workspace', obj)
module.exports = JumpyView
JumpyView = require './jumpy-view'
module.exports =
jumpyView: null
config:
fontSize:
description: 'The font size of jumpy labels.'
type: 'number'
default: .75
minimum: 0
maximum: 1
highContrast:
description: 'This will display a high contrast label,
usually green. It is dynamic per theme.'
type: 'boolean'
default: false
useHomingBeaconEffectOnJumps:
description: 'This will animate a short lived homing beacon upon
jump. It is *temporarily* not working due to architectural
changes in Atom.'
type: 'boolean'
default: true
matchPattern:
description: 'Jumpy will create labels based on this pattern.'
type: 'string'
default: '([A-Z]+([0-9a-z])*)|[a-z0-9]{2,}'
customPatternsConfigPath:
description: 'filePath for custom patterns'
type: 'string'
default: require('path').join(atom.getConfigDirPath(), 'jumpyCustomPatterns.cson')
activate: (state) ->
@jumpyView = new JumpyView state.jumpyViewState
deactivate: ->
@jumpyView.destroy()
@jumpyView = null
serialize: ->
jumpyViewState: @jumpyView.serialize()
'quotes':
'regexp': '[\'"][^\'"]'
'whitespace':
'regexp': '\\s+'
'numbers':
'regexp': '\\d+\\.\\d+|\\d+'
'dot':
'regexp': '\\.'
'punctuation':
'regexp': '[\\.,:;\\?\\!]+'
'slash':
'regexp': '[/\\\\]+'
'operators':
'regexp': '[\\+\\*=\\-/%<>\\!&\\|]+'
# 'bracket':
# 'regexp': '\\([^\\(\\)]+|\\)'
'bracket':
'regexp': '\\([^\\(\\)]+|\\)|\\[[^\\[\\]]+|\\]|\\{[^\\{\\}]+|\\}'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment