├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── coffeelint.json ├── keymaps └── lazy-motion.cson ├── lib ├── hover.coffee ├── main.coffee ├── match.coffee ├── settings.coffee ├── ui.coffee └── utils.coffee ├── package.json └── styles └── lazy-motion.less /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.6.0 Deprecated 2 | - Add `[Deprecated]` on package description and README. 3 | 4 | ## 0.5.2 5 | - Fix: Make it work in Atom-1.14.0-beta1. #15 6 | 7 | ## 0.5.1 8 | - Fix: Most of command in ui-input did not worked in v0.5.0. 9 | 10 | ## 0.5.0 11 | - Fix: for Atom-beta. Bump engine to "^1.13.0-beta1" 12 | 13 | ## 0.4.0 14 | - Fix: use displayLayer introduced in Atom 1.9.0 15 | 16 | ## 0.3.1 17 | - Revert to fuzzaldrin from fuzzaldrin-plus for `:` matching is better for CoffeeScript, not investigated deep. 18 | 19 | ## 0.3.0 20 | - New: New config param `clearSearchTextOnEverySearch` to clear search text on every search. #7. 21 | 22 | ## 0.2.0 23 | - Now use atom's overlay decoration instead of direct pixel calculation. 24 | - Refactoring: Rewritten 30% of whole code. 25 | - New: #4 Support space separated multi search keyword. right keyword is searched from line where left keyword was found. 26 | - New: Use fuzzaldrin-plus but not much difference for behavior. 27 | - Breaking: Remove `autoLand`, `minimumInputLength` configuration parameter. 28 | - Remove dependency of atom-config-plus module. 29 | 30 | ## 0.1.17 31 | - Fix #10 Setting view cannot accessible from Atom v1.4.0. 32 | - Fix #11 Throw error when search single occurrence with autoLand is enabled. 33 | 34 | ## 0.1.16 - FIX 35 | - Just for releasing v0.1.14. fix minor mistake for releasing version. 36 | 37 | ## 0.1.14 - FIX 38 | - FIX: findFoldMarkers need explicit filter query object from Atom 1.3.0. #9 39 | 40 | ## 0.1.13 - Improve 41 | - Now hover indicator follow scroll. Useful when quickly visit, scroll then cancel. 42 | Until this improvement hover sticked absolute pixel position and disturbed your sight. 43 | 44 | ## 0.1.12 - Improve 45 | - Update readme to follow vim-mode's rename from command-mode to normal-mode 46 | - Refactoring. 47 | - FIX historyManager, get 'next' was not return correct entry. 48 | 49 | ## 0.1.11 - Improve 50 | - If match is under cursor, get next entry #4. 51 | 52 | ## 0.1.10 - Search history support. 53 | - Search history #4. 54 | - Set cursor word as search. 55 | 56 | ## 0.1.9 - Refactoring 57 | - Cleanup code. 58 | - Change base style and add style change example in README.md. 59 | 60 | ## 0.1.8 - Refactoring 61 | - Remove unnecessary `UI::setDirection` method 62 | 63 | ## 0.1.7 - Improve 64 | - `selectToBufferPosition` if start with selection. 65 | 66 | ## 0.1.6 - FIX 67 | - [FIX] land() throw error if there is no matches when confirmed(). 68 | 69 | ## 0.1.5 - Improve 70 | - Better fold restore. 71 | - Refactored, improve readability. 72 | 73 | ## 0.1.4 - Improve 74 | - [FIX] Incorrect flash screen area when fold exists. 75 | - Now restore fold if canceled. 76 | 77 | ## 0.1.3 - Improve 78 | - Doc fix `lazy-motion:search-forward` should be `lazy-motion:forward`. 79 | - Modify CSS padding on input panel. 80 | 81 | ## 0.1.2 - Refactoring 82 | - Cleanup code, fix minor bug. 83 | - Change default: Enable hover indicator by default. 84 | 85 | ## 0.1.1 - Rename package name 86 | - Rename package name from rapid-motion to lazy-motion. 87 | 88 | ## 0.1.0 - First Release 89 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 t9md 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # New maintainer is @jrajav 2 | 3 | This project is now maintained by @jrajav. 4 | Go to current maintainer's page. 5 | 6 | https://github.com/jrajav/atom-lazy-motion 7 | 8 | Since I'm no longer maintainer reporting issue to here has **no-effect**. 9 | -------------------------------------------------------------------------------- /coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "max_line_length": { 3 | "level": "ignore" 4 | }, 5 | "no_empty_param_list": { 6 | "level": "error" 7 | }, 8 | "arrow_spacing": { 9 | "level": "error" 10 | }, 11 | "no_interpolation_in_single_quotes": { 12 | "level": "error" 13 | }, 14 | "no_debugger": { 15 | "level": "error" 16 | }, 17 | "prefer_english_operator": { 18 | "level": "error" 19 | }, 20 | "colon_assignment_spacing": { 21 | "spacing": { 22 | "left": 0, 23 | "right": 1 24 | }, 25 | "level": "ignore" 26 | }, 27 | "braces_spacing": { 28 | "spaces": 0, 29 | "level": "error" 30 | }, 31 | "spacing_after_comma": { 32 | "level": "error" 33 | }, 34 | "no_stand_alone_at": { 35 | "level": "error" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /keymaps/lazy-motion.cson: -------------------------------------------------------------------------------- 1 | 'atom-text-editor.lazy-motion': 2 | 'tab': 'lazy-motion:forward' 3 | 'shift-tab': 'lazy-motion:backward' 4 | -------------------------------------------------------------------------------- /lib/hover.coffee: -------------------------------------------------------------------------------- 1 | class Hover extends HTMLElement 2 | marker: null 3 | 4 | createdCallback: -> 5 | @classList.add 'lazy-motion-hover' 6 | this 7 | 8 | show: (editor, match, @textContent) -> 9 | @reset() 10 | @classList.add(match.getClassList()...) 11 | @createOverlay editor, match 12 | 13 | createOverlay: (editor, {range}) -> 14 | @marker = editor.markBufferRange(range, invalidate: "never") 15 | editor.decorateMarker @marker, 16 | type: 'overlay' 17 | item: this 18 | position: 'head' 19 | 20 | reset: -> 21 | @classList.remove('first', 'last', 'current') 22 | @marker?.destroy() 23 | @marker = null 24 | 25 | destroy: -> 26 | @reset() 27 | @remove() 28 | 29 | Hover = document.registerElement 'lazy-motion-hover', 30 | prototype: Hover.prototype 31 | extends: 'div' 32 | 33 | module.exports = Hover 34 | -------------------------------------------------------------------------------- /lib/main.coffee: -------------------------------------------------------------------------------- 1 | {CompositeDisposable, Range} = require 'atom' 2 | _ = require 'underscore-plus' 3 | 4 | Hover = require './hover' 5 | settings = require './settings' 6 | {MatchList} = require './match' 7 | {getHistoryManager, saveEditorState, flashScreen} = require './utils' 8 | UI = require './ui' 9 | 10 | showDeprecationWarningOnce = (pkgName) -> 11 | param = "#{pkgName}.showDeprecationWarning" 12 | if atom.config.get(param) 13 | atom.config.set(param, false) 14 | message = """ 15 | ## Deprecated: `#{pkgName}` 16 | - Because I no longer use this package. 17 | - Sorry and thank you for using my package. 18 | - This warning is not displayed on next activation. 19 | """.replace(/_/g, ' ') 20 | atom.notifications.addWarning(message, dismissable: true) 21 | 22 | module.exports = 23 | subscriptions: null 24 | searchHistory: null 25 | 26 | activate: -> 27 | showDeprecationWarningOnce('lazy-motion') 28 | 29 | @ui = new UI().initialize(this) 30 | @searchHistory = getHistoryManager(max: settings.get('historySize')) 31 | settings.notifyAndRemoveDeprecate('autoLand', 'minimumInputLength') 32 | @observeUI() 33 | 34 | @subscriptions = new CompositeDisposable 35 | @subscriptions.add atom.commands.add 'atom-text-editor', 36 | 'lazy-motion:forward': => @start('next') 37 | 'lazy-motion:backward': => @start('prev') 38 | 'lazy-motion:forward-again': => @start('next', action: 'again') 39 | 'lazy-motion:backward-again': => @start('prev', action: 'again') 40 | 'lazy-motion:forward-cursor-word': => @start('next', action: 'cursorWord') 41 | 'lazy-motion:backward-cursor-word': => @start('prev', action: 'cursorWord') 42 | 43 | observeUI: -> 44 | @ui.onDidChange ({text}) => 45 | @search(text) 46 | @updateCounter() 47 | 48 | @ui.onDidConfirm ({text}) => 49 | @searchHistory.save text 50 | @land() 51 | 52 | @ui.onDidCancel ({text}) => 53 | if settings.get('saveHistoryOnCancel') 54 | @searchHistory.save text 55 | @cancel() 56 | 57 | @ui.onCommand (command) => 58 | @handleCommand(command) 59 | 60 | deactivate: -> 61 | @ui?.destroy() 62 | @subscriptions.dispose() 63 | @searchHistory.destroy() 64 | {@searchHistory, @ui, @subscriptions} = {} 65 | @reset() 66 | 67 | start: (@direction, {action}={}) -> 68 | unless @ui.isVisible() 69 | @editor = atom.workspace.getActiveTextEditor() 70 | @restoreEditorState = saveEditorState @editor 71 | @matchList = new MatchList(@editor, @getWordPattern()) 72 | switch action 73 | when 'again' then @handleCommand('set-history-prev') 74 | when 'cursorWord' then @handleCommand('set-cursor-word') 75 | else 76 | unless settings.get('clearSearchTextOnEverySearch') 77 | text = @ui.getText('') 78 | @ui.setText(text) unless _.isEmpty(text) 79 | @ui.focus() 80 | else 81 | return if @matchList.isEmpty() 82 | @matchList.visit(@direction) 83 | @updateCounter() 84 | 85 | updateCounter: -> 86 | {total, current} = @matchList.getInfo() 87 | content = if total isnt 0 then "#{current} / #{total}" else "0" 88 | @ui.updateCounter(content) 89 | 90 | if settings.get('showHoverIndicator') and total isnt 0 91 | @hover ?= new Hover() 92 | @hover.show @editor, @matchList.get(), "#{current}/#{total}" 93 | 94 | handleCommand: (command) -> 95 | switch command 96 | when 'set-history-next' then @ui.setText(@searchHistory.get('next')) 97 | when 'set-history-prev' then @ui.setText(@searchHistory.get('prev')) 98 | when 'set-cursor-word' 99 | # [NOTE] # Instead of cursor::wordRegExp(), we use lazy-motion.wordRegExp setting. 100 | cursorWord = @editor.getWordUnderCursor({wordRegex: @getWordPattern()}) 101 | @ui.setText(cursorWord) 102 | 103 | search: (text) -> 104 | @matchList.reset() 105 | return unless text 106 | @matchList.filter(text) 107 | if @matchList.isEmpty() 108 | flashScreen @editor, {timeout: 100, class: 'lazy-motion-flash'} 109 | @hover?.reset() 110 | return 111 | @matchList.visit() 112 | 113 | cancel: -> 114 | @restoreEditorState() 115 | @reset() 116 | 117 | land: -> 118 | point = @matchList.get().range.start 119 | if @editor.getLastSelection().isEmpty() 120 | @editor.setCursorBufferPosition(point) 121 | else 122 | @editor.selectToBufferPosition(point) 123 | @reset() 124 | 125 | reset: -> 126 | @searchHistory.reset() 127 | @hover?.reset() 128 | @matchList?.destroy() 129 | {@restoreEditorState, @matchList, @direction} = {} 130 | 131 | getWordPattern: -> 132 | scope = @editor.getRootScopeDescriptor() 133 | pattern = settings.get('wordRegExp', {scope}) 134 | 135 | try 136 | new RegExp(pattern, 'g') 137 | catch error 138 | content = """ 139 | lazy-motion: 140 | * Invalid regular expression `#{pattern}` on scope `#{scope}`. 141 | """ 142 | atom.notifications.addWarning content, dismissable: true 143 | finally 144 | if error 145 | @ui.cancel() 146 | -------------------------------------------------------------------------------- /lib/match.coffee: -------------------------------------------------------------------------------- 1 | _ = require 'underscore-plus' 2 | fuzzaldrin = require 'fuzzaldrin' 3 | {CompositeDisposable} = require 'atom' 4 | {selectVisibleBy, getIndex, flash} = require './utils' 5 | 6 | class MatchList 7 | tokens: null 8 | entries: null 9 | index: -1 10 | 11 | constructor: (@editor, @pattern) -> 12 | @entries = [] 13 | @subscriptions = new CompositeDisposable 14 | refresh = @refresh.bind(this) 15 | @subscribe(@editor.element.onDidChangeScrollTop(refresh)) 16 | @subscribe(@editor.element.onDidChangeScrollLeft(refresh)) 17 | 18 | subscribe: (args...) -> 19 | @subscriptions.add args... 20 | 21 | isEmpty: -> 22 | @entries.length is 0 23 | 24 | getTokens: -> 25 | unless @tokens? 26 | @tokens = [] 27 | @editor.scan @pattern, ({range, matchText}) => 28 | @tokens.push(new Match(@editor, {range, matchText})) 29 | @tokens 30 | 31 | filter: (text) -> 32 | @entries = @getTokens() 33 | matches = [] 34 | for text, index in text.trim().split(/\s+/) 35 | found = fuzzaldrin.filter(@entries, text, key: 'matchText') 36 | if index is 0 37 | matches = found 38 | else 39 | matches = found.filter (found) -> 40 | _.detect(matches, (match) -> found.isFollowing(match)) 41 | 42 | @entries = _.sortBy(matches, (match) -> match.getScore()) 43 | return unless @entries.length 44 | 45 | index = 0 46 | point = @editor.getCursorBufferPosition() 47 | for match, i in @entries when match.range.start.isGreaterThan(point) 48 | index = i 49 | break 50 | @setIndex(index) 51 | 52 | @refresh() 53 | 54 | visit: (direction) -> 55 | return unless match = @get(direction) 56 | 57 | {range} = match 58 | point = range.start 59 | @editor.scrollToBufferPosition(point, center: true) 60 | @editor.unfoldBufferRow point.row if @editor.isFoldedAtBufferRow(point.row) 61 | 62 | flash @editor, range, 63 | class: 'lazy-motion-flash' 64 | timeout: 150 65 | 66 | @refresh() 67 | 68 | refresh: -> 69 | @reset() 70 | for match in @getVisible() 71 | match.show() 72 | 73 | reset: -> 74 | for match in @entries 75 | match.reset() 76 | 77 | getVisible: -> 78 | selectVisibleBy @editor, @entries, (match) -> match.range 79 | 80 | setIndex: (index) -> 81 | @entries[@index]?.current = false 82 | @index = getIndex(index, @entries) 83 | @entries[@index]?.current = true 84 | [first, others..., last] = @entries 85 | first.first = true 86 | last?.last = true 87 | 88 | get: (direction=null) -> 89 | switch direction 90 | when 'next' then @setIndex(@index + 1) 91 | when 'prev' then @setIndex(@index - 1) 92 | match = @entries[@index] 93 | match 94 | 95 | getInfo: -> 96 | total: @entries?.length ? 0, 97 | current: if @isEmpty() then 0 else @index + 1 98 | 99 | destroy: -> 100 | marker.destroy() for marker in (@entries ? []) 101 | marker.destroy() for marker in (@tokens ? []) 102 | @subscriptions.dispose() 103 | {@index, @tokens, @entries, @subscriptions} = {} 104 | 105 | class Match 106 | constructor: (@editor, {@range, @matchText}) -> 107 | 108 | getClassList: -> 109 | # first and last is exclusive, prioritize 'first'. 110 | classes = [] 111 | classes.push('first') if @first 112 | classes.push('last') if (not @first and @last) 113 | classes.push('current') if @current 114 | classes 115 | 116 | isFollowing: ({range: {start: otherStart}}) -> 117 | {start} = @range 118 | if start.row is otherStart.row 119 | start.isGreaterThan(otherStart) 120 | else 121 | false 122 | 123 | show: -> 124 | classes = ['lazy-motion-match'].concat(@getClassList()...) 125 | @marker = @editor.markBufferRange(@range, invalidate: 'never') 126 | @editor.decorateMarker(@marker, type: 'highlight', class: classes.join(" ")) 127 | 128 | getScore: -> 129 | unless @score? 130 | {row, column} = @range.start 131 | @score = row * 1000 + column 132 | @score 133 | 134 | reset: -> 135 | @marker?.destroy() 136 | 137 | destroy: -> 138 | @reset() 139 | {@range, @score, @editor, @marker} = {} 140 | 141 | module.exports = {MatchList} 142 | -------------------------------------------------------------------------------- /lib/settings.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | scope: 'lazy-motion' 3 | 4 | notifyAndRemoveDeprecate: (params...) -> 5 | deprecatedParams = (param for param in params when @has(param)) 6 | return if deprecatedParams.length is 0 7 | 8 | content = [ 9 | "#{@scope}: Config options deprecated. ", 10 | "Automatically removed from your `connfig.cson` " 11 | ] 12 | for param in deprecatedParams 13 | @delete(param) 14 | content.push "- `#{param}`" 15 | atom.notifications.addWarning content.join("\n"), dismissable: true 16 | 17 | has: (param) -> 18 | param of atom.config.get(@scope) 19 | 20 | delete: (param) -> 21 | @set(param, undefined) 22 | 23 | get: (param) -> 24 | atom.config.get("#{@scope}.#{param}") 25 | 26 | set: (param, value) -> 27 | atom.config.set("#{@scope}.#{param}", value) 28 | -------------------------------------------------------------------------------- /lib/ui.coffee: -------------------------------------------------------------------------------- 1 | {Emitter, CompositeDisposable} = require 'atom' 2 | settings = require './settings' 3 | {ElementBuilder} = require './utils' 4 | 5 | class UI extends HTMLElement 6 | ElementBuilder.includeInto(this) 7 | 8 | createdCallback: -> 9 | @emitter = new Emitter 10 | @className = 'lazy-motion-ui' 11 | 12 | @appendChild( 13 | @counterContainer = @div 14 | classList: ['counter'] 15 | ) 16 | @appendChild( 17 | @editorContainer = @div 18 | classList: ['editor-container'] 19 | ).appendChild( 20 | @editorElement = @atomTextEditor 21 | classList: ['editor', 'lazy-motion'] 22 | attribute: {mini: ''} 23 | ) 24 | 25 | @editor = @editorElement.getModel() 26 | @editor.setMini(true) 27 | @panel = atom.workspace.addBottomPanel(item: this, visible: false) 28 | this 29 | 30 | onDidChange: (fn) -> @emitter.on 'did-change', fn 31 | onDidConfirm: (fn) -> @emitter.on 'did-confirm', fn 32 | onDidCancel: (fn) -> @emitter.on 'did-cancel', fn 33 | onDidUnfocus: (fn) -> @emitter.on 'did-unfocus', fn 34 | onCommand: (fn) -> @emitter.on 'command', fn 35 | 36 | emitCommand: (command) -> 37 | @emitter.emit('command', command) 38 | 39 | initialize: (@main) -> 40 | @subscriptions = new CompositeDisposable 41 | @subscriptions.add atom.commands.add @editorElement, 42 | 'core:confirm': => @confirm() 43 | 'core:cancel': => @cancel() 44 | 'core:move-down': => @emitCommand('set-history-next') 45 | 'core:move-up': => @emitCommand('set-history-prev') 46 | 'lazy-motion:set-history-next': => @emitCommand('set-history-next') 47 | 'lazy-motion:set-history-prev': => @emitCommand('set-history-prev') 48 | 'lazy-motion:set-cursor-word': => @emitCommand('set-cursor-word') 49 | 50 | @subscriptions.add atom.workspace.onDidChangeActivePaneItem => 51 | @cancel() if @isVisible() 52 | 53 | @editor.onDidChange => 54 | return if @finishing 55 | @emitter.emit 'did-change', {text: @getText()} 56 | this 57 | 58 | updateCounter: (text) -> 59 | @counterContainer.textContent = "Lazy Motion: #{text}" 60 | 61 | setText: (text) -> 62 | @editor.setText(text) 63 | 64 | getText: -> 65 | @editor.getText() 66 | 67 | focus: -> 68 | @panel.show() 69 | @editorElement.focus() 70 | @updateCounter('0') 71 | 72 | unFocus: -> 73 | @setText '' if settings.get('clearSearchTextOnEverySearch') 74 | @panel.hide() 75 | atom.workspace.getActivePane().activate() 76 | @finishing = false 77 | 78 | confirm: -> 79 | return if @main.matchList.isEmpty() 80 | @finishing = true 81 | @emitter.emit 'did-confirm', {text: @getText()} 82 | @unFocus() 83 | 84 | cancel: -> 85 | # [NOTE] blur event happen on confirmed() in this case we shouldn't cancel 86 | return if @finishing 87 | @finishing = true 88 | @emitter.emit 'did-cancel', {text: @getText()} 89 | @unFocus() 90 | 91 | isVisible: -> 92 | @panel.isVisible() 93 | 94 | destroy: -> 95 | @emitter.dispose() 96 | @panel.destroy() 97 | @editor.destroy() 98 | @subscriptions.dispose() 99 | {@emitter, @panel, @editor, @subscriptions} = {} 100 | @remove() 101 | 102 | module.exports = document.registerElement 'lazy-motion-ui', 103 | extends: 'div' 104 | prototype: UI.prototype 105 | -------------------------------------------------------------------------------- /lib/utils.coffee: -------------------------------------------------------------------------------- 1 | {Range} = require 'atom' 2 | _ = require 'underscore-plus' 3 | 4 | 5 | saveEditorState = (editor) -> 6 | editorElement = editor.element 7 | scrollTop = editorElement.getScrollTop() 8 | 9 | foldStartRows = editor.displayLayer.foldsMarkerLayer.findMarkers({}).map (m) -> m.getStartPosition().row 10 | -> 11 | for row in foldStartRows.reverse() when not editor.isFoldedAtBufferRow(row) 12 | editor.foldBufferRow(row) 13 | editorElement.setScrollTop(scrollTop) 14 | 15 | # Return adjusted index fit whitin length 16 | # Return -1 if list is empty. 17 | getIndex = (index, list) -> 18 | if list.length is 0 19 | -1 20 | else 21 | index = index % list.length 22 | if (index >= 0) 23 | index 24 | else 25 | list.length + index 26 | 27 | getVisibleBufferRange = (editor) -> 28 | [startRow, endRow] = getVisibleBufferRowRange(editor) 29 | new Range([startRow, 0], [endRow, Infinity]) 30 | 31 | getVisibleBufferRowRange = (editor) -> 32 | [startRow, endRow] = editor.element.getVisibleRowRange().map (row) -> 33 | editor.bufferRowForScreenRow row 34 | 35 | # NOTE: depending on getVisibleRowRange 36 | selectVisibleBy = (editor, entries, fn) -> 37 | range = getVisibleBufferRange(editor) 38 | (e for e in entries when range.containsRange(fn(e))) 39 | 40 | getHistoryManager = ({max}={}) -> 41 | entries = [] 42 | index = -1 43 | max ?= 20 44 | 45 | get: (direction) -> 46 | switch direction 47 | when 'prev' then index += 1 unless (index + 1) is entries.length 48 | when 'next' then index -= 1 unless (index is -1) 49 | entries[index] ? '' 50 | 51 | save: (entry) -> 52 | return if _.isEmpty(entry) 53 | entries.unshift entry 54 | entries = _.uniq(entries) # Eliminate duplicates 55 | if entries.length > max 56 | entries.splice(max) 57 | 58 | reset: -> 59 | index = -1 60 | 61 | destroy: -> 62 | {entries, index} = {} 63 | 64 | flash = (editor, range, options) -> 65 | marker = editor.markBufferRange(range, invalidate: 'never') 66 | editor.decorateMarker(marker, type: 'highlight', class: options.class) 67 | 68 | setTimeout -> 69 | marker.destroy() 70 | , options.timeout 71 | 72 | flashScreen = (editor, options) -> 73 | flash(editor, getVisibleBufferRange(editor), options) 74 | 75 | ElementBuilder = 76 | includeInto: (target) -> 77 | for name, value of this when name isnt "includeInto" 78 | target::[name] = value.bind(this) 79 | 80 | div: (params) -> 81 | @createElement 'div', params 82 | 83 | span: (params) -> 84 | @createElement 'div', params 85 | 86 | atomTextEditor: (params) -> 87 | @createElement 'atom-text-editor', params 88 | 89 | createElement: (element, {classList, id, textContent, attribute}={}) -> 90 | element = document.createElement element 91 | 92 | element.id = id if id? 93 | element.classList.add classList... if classList? 94 | element.textContent = textContent if textContent? 95 | for name, value of attribute ? {} 96 | element.setAttribute(name, value) 97 | element 98 | 99 | module.exports = { 100 | saveEditorState 101 | getVisibleBufferRange 102 | getVisibleBufferRowRange 103 | getIndex 104 | selectVisibleBy 105 | getHistoryManager 106 | flash 107 | flashScreen 108 | ElementBuilder 109 | } 110 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lazy-motion", 3 | "main": "./lib/main", 4 | "version": "0.6.0", 5 | "description": "[Deprecated] Rapid cursor positioning with fuzzy search", 6 | "keywords": [], 7 | "repository": "https://github.com/t9md/atom-lazy-motion", 8 | "license": "MIT", 9 | "engines": { 10 | "atom": "^1.13.0" 11 | }, 12 | "activationCommands": { 13 | "atom-text-editor": [ 14 | "lazy-motion:forward", 15 | "lazy-motion:backward", 16 | "lazy-motion:forward-again", 17 | "lazy-motion:backward-again", 18 | "lazy-motion:forward-cursor-word", 19 | "lazy-motion:backward-cursor-word" 20 | ] 21 | }, 22 | "dependencies": { 23 | "fuzzaldrin": "^2.1.0", 24 | "underscore-plus": "^1.6.6" 25 | }, 26 | "configSchema": { 27 | "wordRegExp": { 28 | "order": 2, 29 | "type": "string", 30 | "default": "[@\\w-.():?]+", 31 | "description": "Used to build candidate List" 32 | }, 33 | "showHoverIndicator": { 34 | "order": 3, 35 | "type": "boolean", 36 | "default": true 37 | }, 38 | "historySize": { 39 | "order": 4, 40 | "type": "integer", 41 | "minimum": 0, 42 | "default": 30 43 | }, 44 | "saveHistoryOnCancel": { 45 | "order": 5, 46 | "type": "boolean", 47 | "default": true, 48 | "description": "If false, canceled search won't saved to history." 49 | }, 50 | "clearSearchTextOnEverySearch": { 51 | "order": 6, 52 | "type": "boolean", 53 | "default": true, 54 | "description": "Search text is cleared on every search" 55 | }, 56 | "showDeprecationWarning": { 57 | "order": 7, 58 | "type": "boolean", 59 | "default": true 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /styles/lazy-motion.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | @import "syntax-variables"; 3 | 4 | 5 | .lazy-motion { 6 | &.round-box { 7 | box-sizing: border-box; 8 | border-radius: @component-border-radius; 9 | } 10 | } 11 | 12 | atom-text-editor { 13 | .lazy-motion-unmatch .region { 14 | .lazy-motion.round-box; 15 | } 16 | .lazy-motion-match { 17 | .region { 18 | .lazy-motion.round-box; 19 | background-color: @syntax-result-marker-color; 20 | border: 2px solid transparent; 21 | transition: border-color .2s; 22 | } 23 | &.current .region { 24 | border-color: @syntax-result-marker-color-selected; 25 | transition-duration: .1s; 26 | } 27 | &.first .region { 28 | background-color: fadeout(@syntax-color-renamed, 60%); 29 | border-color: @syntax-color-renamed; 30 | } 31 | &.last .region { 32 | background-color: fadeout(@syntax-color-removed, 60%); 33 | border-color: @syntax-color-removed; 34 | } 35 | } 36 | } 37 | 38 | @flash-color: fadeout(darken(@syntax-selection-flash-color, 10%), 20%); 39 | 40 | @keyframes lazy-motion-flash { 41 | from { background-color: @flash-color; } 42 | to { background-color: transparent; } 43 | } 44 | 45 | atom-text-editor { 46 | .lazy-motion-flash .region { 47 | animation-name: lazy-motion-flash; 48 | animation-duration: .5s; 49 | animation-iteration-count: 1; 50 | } 51 | .lazy-motion-cursor .region { 52 | background-color: @syntax-cursor-color; 53 | } 54 | } 55 | 56 | .lazy-motion-hover { 57 | color: @text-color-highlight; 58 | background-color: @base-background-color; 59 | border-radius: @component-border-radius; 60 | box-shadow: 0 0 10px @syntax-text-color; 61 | margin-top: -2.5em; 62 | padding-left: 0.2em; 63 | padding-right: 0.2em; 64 | margin-left: -0.1em; 65 | text-align: center; 66 | &.first { 67 | background-color: @background-color-info; 68 | } 69 | &.last { 70 | background-color: @background-color-error; 71 | } 72 | } 73 | 74 | .lazy-motion-ui { 75 | .editor-container { 76 | padding: @component-padding/4 @component-padding; 77 | } 78 | .counter { 79 | padding: @component-padding/2 @component-padding; 80 | font-size: 1.3em; 81 | color: @text-color; 82 | } 83 | } 84 | --------------------------------------------------------------------------------