Created
November 22, 2018 23:30
-
-
Save ithil/98949b5dd02365623954fd8790768103 to your computer and use it in GitHub Desktop.
Old Jumpy with multiple modes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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