├── Flavorfile.lock ├── Gemfile ├── t ├── shortcut_test │ ├── resolve_caller_SID_from_scriptnames │ └── resolve_caller_SID_from_stacktrace └── shortcut_test.vim ├── Gemfile.lock ├── doc ├── tags └── shortcut.txt ├── README.md └── plugin └── shortcut.vim /Flavorfile.lock: -------------------------------------------------------------------------------- 1 | kana/vim-vspec (1.9.0) 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'vim-flavor', '~> 4.0' 3 | -------------------------------------------------------------------------------- /t/shortcut_test/resolve_caller_SID_from_scriptnames: -------------------------------------------------------------------------------- 1 | Shortcut description map shortcut :call handler() 2 | 3 | function! s:handler() abort 4 | let g:shortcut_handler_called = 1 5 | endfunction 6 | 7 | " vim: filetype=vim 8 | -------------------------------------------------------------------------------- /t/shortcut_test/resolve_caller_SID_from_stacktrace: -------------------------------------------------------------------------------- 1 | function! s:define_shortcut() abort 2 | Shortcut description map shortcut :call handler() 3 | endfunction 4 | 5 | function! s:handler() abort 6 | let g:shortcut_handler_called = 1 7 | endfunction 8 | 9 | call s:define_shortcut() 10 | 11 | " vim: filetype=vim 12 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | parslet (2.0.0) 5 | pastel (0.8.0) 6 | tty-color (~> 0.5) 7 | thor (1.2.1) 8 | tty-color (0.6.0) 9 | vim-flavor (4.0.2) 10 | parslet (>= 1.8, < 3.0) 11 | pastel (~> 0.7) 12 | thor (>= 0.20, < 2.0) 13 | 14 | PLATFORMS 15 | ruby 16 | 17 | DEPENDENCIES 18 | vim-flavor (~> 4.0) 19 | 20 | BUNDLED WITH 21 | 2.2.17 22 | -------------------------------------------------------------------------------- /doc/tags: -------------------------------------------------------------------------------- 1 | :Shortcut shortcut.txt /*:Shortcut* 2 | :Shortcut! shortcut.txt /*:Shortcut!* 3 | :Shortcuts shortcut.txt /*:Shortcuts* 4 | :Shortcuts! shortcut.txt /*:Shortcuts!* 5 | :ShortcutsRangeless shortcut.txt /*:ShortcutsRangeless* 6 | :ShortcutsRangeless! shortcut.txt /*:ShortcutsRangeless!* 7 | g:shortcuts shortcut.txt /*g:shortcuts* 8 | g:shortcuts_overwrite_warning shortcut.txt /*g:shortcuts_overwrite_warning* 9 | shortcut-cmd shortcut.txt /*shortcut-cmd* 10 | shortcut-install shortcut.txt /*shortcut-install* 11 | shortcut-intro shortcut.txt /*shortcut-intro* 12 | shortcut-setup shortcut.txt /*shortcut-setup* 13 | shortcut-usage shortcut.txt /*shortcut-usage* 14 | shortcut-var shortcut.txt /*shortcut-var* 15 | shortcut.vim shortcut.txt /*shortcut.vim* 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shortcut.vim 2 | 3 | This plugin provides a _discoverable_ shortcut system for Vim that is inspired 4 | by [Spacemacs] and powered by [fzf.vim]. It displays a searchable menu of 5 | shortcuts when you pause partway while typing a shortcut, say, because you 6 | forgot the rest of it or because you just want to see the shortcut menu again 7 | to discover what else is available. You can interactively filter the menu by 8 | typing more shortcut keys or parts of shortcut descriptions shown in the menu. 9 | 10 | [![asciicast](https://asciinema.org/a/104572.png)](https://asciinema.org/a/104572?autoplay=1) 11 | 12 | ## Requirements 13 | 14 | * [fzf.vim] plugin. 15 | 16 | ## Installation 17 | 18 | 1. Clone this Git repository as follows, or [download and extract its 19 | contents]( https://github.com/sunaku/vim-shortcut/archive/master.zip ). 20 | 21 | git clone https://github.com/sunaku/vim-shortcut.git ~/vim-shortcut 22 | 23 | 2. Run the following commands in Vim to start using this plugin immediately, 24 | or add them to your *vimrc* file to automate this whenever you start Vim. 25 | 26 | :set runtimepath+=~/vim-shortcut 27 | :runtime plugin/shortcut.vim 28 | 29 | 3. Run the following command inside Vim to learn more about using this plugin. 30 | 31 | :help shortcut.vim 32 | 33 | ## Usage 34 | 35 | * Use the `Shortcut!` prefix (with a bang) to describe existing shortcuts. 36 | 37 | * Use the `Shortcut` prefix (without a bang) to define brand new shortcuts. 38 | 39 | * Use the `:Shortcuts` command to display a searchable menu of shortcuts. 40 | 41 | * Use the `g:shortcuts` variable to access shortcuts keys and descriptions. 42 | 43 | * Use the `g:shortcuts_overwrite_warning` variable to detect any conflicts. 44 | 45 | ### Discovery & fallback shortcuts 46 | 47 | I recommend that you define these two shortcuts for discovery and fallback 48 | (feel free to change the `` key to your own commonly used prefix): 49 | 50 | ```vim 51 | Shortcut show shortcut menu and run chosen shortcut 52 | \ noremap :Shortcuts 53 | 54 | Shortcut fallback to shortcut menu on partial entry 55 | \ noremap :Shortcuts 56 | ``` 57 | 58 | The fallback shortcut's keys should represent the common prefix used by most 59 | of your shortcuts so that it can automatically launch the shortcut menu for 60 | you when you pause partway while typing a shortcut, say, because you forgot 61 | the rest of it or because you just want to see the shortcut menu again to 62 | discover what else is available. However, this is not a strict requirement 63 | because you might find it useful to map shortcuts with uncommon prefixes when 64 | you know them by heart and you thereby feel that a fallback is unnecessary. 65 | As a result, you can map any keys to any shortcut, regardless of the prefix! 66 | Furthermore, you can set up multiple fallback shortcuts too, one per prefix. 67 | 68 | ### Describing existing shortcuts 69 | 70 | Use `Shortcut!` with a bang to describe shortcuts that are already defined: 71 | 72 | ```vim 73 | Shortcut! keys description 74 | ``` 75 | 76 | For more examples, [see my vimrc]( 77 | https://github.com/sunaku/.vim/blob/qwerty/bundle/motion/unimpaired.vim 78 | ): 79 | 80 | ```vim 81 | Shortcut! [f go to previous file in current file's directory 82 | Shortcut! ]f go to next file in current file's directory 83 | ``` 84 | 85 | Any extra whitespace is ignored. 86 | 87 | ### Defining new shortcuts 88 | 89 | Simply prefix any existing `map` command with `Shortcut` and a description. 90 | 91 | For example, take this mapping: 92 | 93 | ```vim 94 | map definition 95 | ``` 96 | 97 | Add `Shortcut` and description: 98 | 99 | ```vim 100 | Shortcut description map definition 101 | ``` 102 | 103 | You can use multiple lines too: 104 | 105 | ```vim 106 | Shortcut description 107 | \ map definition 108 | ``` 109 | 110 | For more examples, [see my vimrc]( 111 | https://github.com/sunaku/.vim/blob/qwerty/plugin/format.vim 112 | ): 113 | 114 | ```vim 115 | Shortcut duplicate before cursor and then comment-out 116 | \ map cP NERDCommenterYank`[P 117 | ``` 118 | 119 | ```vim 120 | Shortcut fzf files in directory and go to chosen file 121 | \ nnoremap ef :Files 122 | ``` 123 | 124 | ```vim 125 | Shortcut save file as... 126 | \ nnoremap yf :call feedkeys(":saveas %\t", "t") 127 | ``` 128 | 129 | ```vim 130 | for i in range(1,9) 131 | execute 'Shortcut go to tab number '. i .' ' 132 | \ 'nnoremap '. i .'t :tabfirst'. i .'tabnext' 133 | endfor 134 | ``` 135 | 136 | ```vim 137 | Shortcut comment-out using FIGlet ASCII art decoration 138 | \ nnoremap c@ V:call CommentUsingFIGlet() 139 | \|vnoremap c@ :call CommentUsingFIGlet() 140 | 141 | function! CommentUsingFIGlet() 142 | " ... 143 | endfunction 144 | ``` 145 | 146 | Any extra whitespace is ignored. 147 | 148 | ## Documentation 149 | 150 | Run `:help shortcut.vim` or see the `doc/shortcut.txt` file. 151 | 152 | ## Testing 153 | 154 | Developers can run the [vim-vspec]( https://github.com/kana/vim-vspec ) tests: 155 | 156 | ```sh 157 | gem install bundler # first time 158 | bundle install # first time 159 | bundle exec vim-flavor test # every time 160 | ``` 161 | 162 | ## License 163 | 164 | [Spare A Life]: https://sunaku.github.io/vegan-for-life.html 165 | > Like my work? 👍 Please [spare a life] today as thanks! 🐄🐖🐑🐔🐣🐟✨🙊✌ 166 | > Why? For 💕 ethics, the 🌎 environment, and 💪 health; see link above. 🙇 167 | 168 | Copyright 2015 Suraj N. Kurapati 169 | 170 | Distributed under [the same terms as Vim itself][LICENSE]. 171 | 172 | [LICENSE]: http://vimdoc.sourceforge.net/htmldoc/uganda.html#license 173 | [Spacemacs]: http://spacemacs.org 174 | [fzf.vim]: https://github.com/junegunn/fzf.vim 175 | -------------------------------------------------------------------------------- /t/shortcut_test.vim: -------------------------------------------------------------------------------- 1 | source plugin/shortcut.vim 2 | 3 | describe 'ShortcutLeaderKeys()' 4 | it 'compiles leader keys into \ when not defined' 5 | Expect ShortcutLeaderKeys('') == '\' 6 | Expect ShortcutLeaderKeys('') == '\' 7 | Expect ShortcutLeaderKeys('') == '\\' 8 | Expect ShortcutLeaderKeys('') == '\' 9 | Expect ShortcutLeaderKeys('') == '\' 10 | Expect ShortcutLeaderKeys('') == '\\' 11 | end 12 | 13 | it 'compiles leader keys into their defined values' 14 | let g:mapleader = 'x' 15 | Expect ShortcutLeaderKeys('') == 'x' 16 | unlet g:mapleader 17 | 18 | let g:maplocalleader = 'y' 19 | Expect ShortcutLeaderKeys('') == 'y' 20 | unlet g:maplocalleader 21 | end 22 | end 23 | 24 | describe 'ShortcutKeystrokes()' 25 | it 'preserves non-symbolic keys during compile' 26 | Expect ShortcutKeystrokes('') == '' 27 | Expect ShortcutKeystrokes('x') == 'x' 28 | end 29 | 30 | it 'compiles symbolic keys into their keycodes' 31 | Expect ShortcutKeystrokes('') == "\r" 32 | Expect ShortcutKeystrokes('') == "\r\r" 33 | end 34 | 35 | it 'preserves backslashes, used for compilation' 36 | Expect ShortcutKeystrokes('\') == '\' 37 | Expect ShortcutKeystrokes('\\') == '\\' 38 | end 39 | 40 | it 'preserves double quotes, used for compilation' 41 | Expect ShortcutKeystrokes('"') == '"' 42 | Expect ShortcutKeystrokes('""') == '""' 43 | end 44 | 45 | it 'compiles leader keys into \ when not defined' 46 | Expect ShortcutKeystrokes('') == '\' 47 | Expect ShortcutKeystrokes('') == '\' 48 | Expect ShortcutKeystrokes('') == '\\' 49 | Expect ShortcutKeystrokes('') == '\' 50 | Expect ShortcutKeystrokes('') == '\' 51 | Expect ShortcutKeystrokes('') == '\\' 52 | end 53 | 54 | it 'compiles leader keys into their defined values' 55 | let g:mapleader = 'x' 56 | Expect ShortcutKeystrokes('') == 'x' 57 | unlet g:mapleader 58 | 59 | let g:maplocalleader = 'y' 60 | Expect ShortcutKeystrokes('') == 'y' 61 | unlet g:maplocalleader 62 | end 63 | end 64 | 65 | describe ':Shortcut!' 66 | it 'merely remembers the description of a shortcut' 67 | let g:shortcuts = {} 68 | Shortcut! shortcut description 69 | Expect g:shortcuts == {'shortcut': 'description'} 70 | end 71 | 72 | it 'squeezes whitespace between words in description' 73 | let g:shortcuts = {} 74 | Shortcut! shortcut description goes here 75 | Expect g:shortcuts == {'shortcut': 'description goes here'} 76 | end 77 | 78 | it 'does not define any keybinding Vim knows about' 79 | Shortcut! shortcut description 80 | redir => output 81 | map shortcut 82 | redir END 83 | Expect output =~# 'No mapping found' 84 | end 85 | end 86 | 87 | describe 'ShortcutParseDescribeCommand()' 88 | it 'throws an error if the shortcut is not given' 89 | Expect expr { ShortcutParseDescribeCommand('') } to_throw 90 | end 91 | 92 | it 'throws an error if the description is not given' 93 | Expect expr { ShortcutParseDescribeCommand('x') } to_throw 94 | end 95 | 96 | it 'parses description and shortcut; not definition' 97 | Expect ShortcutParseDescribeCommand('shortcut description') 98 | \ == ['shortcut', 'description'] 99 | end 100 | end 101 | 102 | describe ':Shortcut' 103 | it 'actually defines a keybinding Vim knows about' 104 | Shortcut description map shortcut expression 105 | redir => output 106 | map shortcut 107 | redir END 108 | Expect output =~ '\v\n\s+shortcut\s+expression\n' 109 | end 110 | 111 | it 'resolves s in definition to caller script' 112 | call s:assert_resolves_SID('t/shortcut_test/resolve_caller_SID_from_stacktrace') 113 | call s:assert_resolves_SID('t/shortcut_test/resolve_caller_SID_from_scriptnames') 114 | end 115 | 116 | function! s:assert_resolves_SID(test_script_file) abort 117 | " execute the test script 118 | execute 'source' fnameescape(a:test_script_file) 119 | redir => output 120 | silent scriptnames 121 | redir END 122 | let test_script_SNR = count(output, "\n") 123 | 124 | " assert shortcut defined 125 | redir => output 126 | map shortcut 127 | redir END 128 | Expect output =~ '\v\n\s+shortcut\s+\V:call '. test_script_SNR .'_handler()' 129 | 130 | " assert shortcut handler 131 | let g:shortcut_handler_called = 0 132 | normal shortcut 133 | Expect g:shortcut_handler_called == 1 134 | endfunction 135 | 136 | it 'handles multiple s in the same definition' 137 | Shortcut description map shortcut :call foo()call baz() 138 | redir => output 139 | map shortcut 140 | redir END 141 | Expect output =~ '\v\n\s+shortcut\s+\V:call 2_foo()|call 2_baz()' 142 | end 143 | 144 | it 'also remembers the description of the shortcut' 145 | let g:shortcuts = {} 146 | Shortcut description map shortcut expression 147 | Expect g:shortcuts == {'shortcut': 'description'} 148 | end 149 | 150 | it 'preserves a space between words in description' 151 | let g:shortcuts = {} 152 | Shortcut description goes here map shortcut expression 153 | Expect g:shortcuts == {'shortcut': 'description goes here'} 154 | end 155 | end 156 | 157 | describe 'ShortcutParseDefineCommand()' 158 | it 'throws error if "map" directive is not given' 159 | Expect expr { ShortcutParseDefineCommand('') } to_throw 160 | Expect expr { ShortcutParseDefineCommand('x') } to_throw 161 | Expect expr { ShortcutParseDefineCommand('x map') } to_throw 162 | Expect expr { ShortcutParseDefineCommand('x map y') } to_throw 163 | Expect ShortcutParseDefineCommand('x map y z') == ['y', 'x', 'map y z'] 164 | end 165 | 166 | it 'parses description, shortcut, and definition' 167 | for mode_flag in ['', 'n', 'v', 'x', 's', 'o', 'i', 'l', 'c', 't'] 168 | for recursive in ['', 'nore'] 169 | for argument in ['', '', '', '', '', '