├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── autoload ├── which_key.vim └── which_key │ ├── char_handler.vim │ ├── error.vim │ ├── mappings.vim │ ├── renderer.vim │ └── window.vim ├── doc └── vim-which-key.txt ├── ftplugin └── which_key.vim ├── plugin └── which_key.vim └── syntax └── which_key.vim /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: https://www.paypal.me/liuchengxu 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | _Instructions: Replace the template text and remove irrelevant text (including this line)_ 11 | **Warning: if you don't fill this issue template and provide the reproducible steps the issue could be closed directly.** 12 | 13 | **Environment (please complete the following information):** 14 | - OS: ??? 15 | 16 | - (Neo)Vim version: ??? 17 | 18 | - vim-which-key version: ??? 19 | 20 | - Have you reproduced with a minimal vimrc: ??? 21 | 22 | **Describe the bug** 23 | A clear and concise description of what the bug is. 24 | 25 | **To Reproduce** 26 | Steps to reproduce the behavior: 27 | 28 | 1. Create the minimal vimrc `min.vim`: 29 | 30 | ```vim 31 | set nocompatible 32 | set runtimepath^=/path/to/vim-which-key 33 | syntax on 34 | filetype plugin indent on 35 | 36 | " Here place the configuration that can cause this issue. 37 | ``` 38 | 39 | 2. Start (neo)vim with command: `vim -u min.vim` 40 | 41 | 3. Type '....' 42 | 43 | 4. See error 44 | 45 | **Expected behavior** 46 | A clear and concise description of what you expected to happen. 47 | 48 | **Screenshots** 49 | If applicable, add screenshots to help explain your problem. 50 | 51 | **Additional context** 52 | Add any other context about the problem here. 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Liu-Cheng Xu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-which-key 2 | 3 | 4 | 5 | * [Introduction](#introduction) 6 | * [Pros.](#pros) 7 | * [Installation](#installation) 8 | * [Plugin Manager](#plugin-manager) 9 | * [Package management](#package-management) 10 | * [Vim 8](#vim-8) 11 | * [NeoVim](#neovim) 12 | * [Requirement](#requirement) 13 | * [Usage](#usage) 14 | * [`timeoutlen`](#timeoutlen) 15 | * [Special keys](#special-keys) 16 | * [Configuration](#configuration) 17 | * [Minimal Configuration](#minimal-configuration) 18 | * [Example](#example) 19 | * [Hide statusline](#hide-statusline) 20 | * [Commands](#commands) 21 | * [Options](#options) 22 | * [FAQ](#faq) 23 | * [How to map some special keys like ``?](#how-to-map-some-special-keys-like-bs) 24 | * [Credit](#credit) 25 | 26 | 27 | 28 | ## Introduction 29 | 30 | vim-which-key is vim port of [emacs-which-key](https://github.com/justbur/emacs-which-key) that displays available keybindings in popup. 31 | 32 | [emacs-which-key](https://github.com/justbur/emacs-which-key) started as a rewrite of [guide-key](https://github.com/kai2nenobu/guide-key), very likely, [vim-which-key](https://github.com/liuchengxu/vim-which-key) heavily rewrote [vim-leader-guide](https://github.com/hecal3/vim-leader-guide). The features of vim-which-key has evolved a lot since then. 33 | 34 |

35 | 36 |
37 | Vim config in the screenshot is space-vim. 38 |

39 | 40 | ## Pros. 41 | 42 | - Better UI, vim's `popup` and neovim's `floating_win` are supported. 43 | - Show all mappings following a prefix, e.g., ``, ``, etc. 44 | - Instant response for your every single input. 45 | - Dynamic update on every call. 46 | - Define group names and arbitrary descriptions. 47 | 48 | ## Installation 49 | 50 | ### Plugin Manager 51 | 52 | Assuming you are using [vim-plug](https://github.com/junegunn/vim-plug): 53 | 54 | ```vim 55 | Plug 'liuchengxu/vim-which-key' 56 | 57 | " On-demand lazy load 58 | Plug 'liuchengxu/vim-which-key', { 'on': ['WhichKey', 'WhichKey!'] } 59 | 60 | " To register the descriptions when using the on-demand load feature, 61 | " use the autocmd hook to call which_key#register(), e.g., register for the Space key: 62 | " autocmd! User vim-which-key call which_key#register('', 'g:which_key_map') 63 | ``` 64 | 65 | For other plugin managers please refer to their document for more details. 66 | 67 | ### Package management 68 | 69 | #### Vim 8 70 | 71 | ```bash 72 | $ mkdir -p ~/.vim/pack/git-plugins/start 73 | $ git clone https://github.com/liuchengxu/vim-which-key.git --depth=1 ~/.vim/pack/git-plugins/start/vim-which-key 74 | ``` 75 | 76 | #### NeoVim 77 | 78 | ```bash 79 | $ mkdir -p ~/.local/share/nvim/site/pack/git-plugins/start 80 | $ git clone https://github.com/liuchengxu/vim-which-key.git --depth=1 ~/.local/share/nvim/site/pack/git-plugins/start/vim-which-key 81 | ``` 82 | 83 | ## Requirement 84 | 85 | vim-which-key requires option `timeout` is on, see `:h timeout`. 86 | 87 | Since `timeout` is on by default, all you need is not to `set notimeout` in your `.vimrc`. 88 | 89 | ## Usage 90 | 91 | ### `timeoutlen` 92 | 93 | Let's say SPC is your leader key and you use it to trigger vim-which-key: 94 | 95 | ```vim 96 | nnoremap :WhichKey '' 97 | ``` 98 | 99 | After pressing leader the guide buffer will pop up when there are no further keystrokes within `timeoutlen`. 100 | 101 | ```vim 102 | " By default timeoutlen is 1000 ms 103 | set timeoutlen=500 104 | ``` 105 | 106 | Pressing other keys within `timeoutlen` will either complete the mapping or open a subgroup. In the screenshot above SPCb will open up the buffer menu. 107 | 108 | Please note that no matter which mappings and menus you configure, your original leader mappings will remain unaffected. The key guide is an additional layer. It will only activate, when you do not complete your input during the timeoutlen duration. 109 | 110 | ### Special keys 111 | 112 | - Use BS to show the upper level mappings. 113 | 114 | ### Configuration 115 | 116 | - For neovim, [nvim-whichkey-setup.lua](https://github.com/AckslD/nvim-whichkey-setup.lua) provides a wrapper around vim-which-key to simplify configuration in lua. 117 | It also solves issues (see #126) when the mapped command is more complex and makes it easy to also map `localleader`. 118 | 119 | #### Minimal Configuration 120 | 121 | `:WhichKey` and `:WhichKeyVisual` are the primary way of interacting with this plugin. 122 | 123 | Assuming your `leader` and `localleader` key are `` and `,`, respectively, even no description dictionary has been registered, all `` and `,` related mappings will be displayed regardless. 124 | 125 | ```vim 126 | let g:mapleader = "\" 127 | let g:maplocalleader = ',' 128 | nnoremap :WhichKey '' 129 | nnoremap :WhichKey ',' 130 | ``` 131 | 132 | The raw content displayed is normally not adequate to serve as a cheatsheet. See the following section for configuring it properly. 133 | 134 | If no description dictionary is available, the right-hand-side of all mappings will be displayed: 135 | 136 |

137 | 138 | The dictionary configuration is necessary to provide group names or a description text: 139 | 140 | ```vim 141 | let g:which_key_map = {} 142 | let g:which_key_map['w'] = { 143 | \ 'name' : '+windows' , 144 | \ 'w' : ['w' , 'other-window'] , 145 | \ 'd' : ['c' , 'delete-window'] , 146 | \ '-' : ['s' , 'split-window-below'] , 147 | \ '|' : ['v' , 'split-window-right'] , 148 | \ '2' : ['v' , 'layout-double-columns'] , 149 | \ 'h' : ['h' , 'window-left'] , 150 | \ 'j' : ['j' , 'window-below'] , 151 | \ 'l' : ['l' , 'window-right'] , 152 | \ 'k' : ['k' , 'window-up'] , 153 | \ 'H' : ['5<' , 'expand-window-left'] , 154 | \ 'J' : [':resize +5' , 'expand-window-below'] , 155 | \ 'L' : ['5>' , 'expand-window-right'] , 156 | \ 'K' : [':resize -5' , 'expand-window-up'] , 157 | \ '=' : ['=' , 'balance-window'] , 158 | \ 's' : ['s' , 'split-window-below'] , 159 | \ 'v' : ['v' , 'split-window-below'] , 160 | \ '?' : ['Windows' , 'fzf-window'] , 161 | \ } 162 | call which_key#register('', "g:which_key_map") 163 | ``` 164 | 165 |

166 | 167 | If you wish to hide a mapping from the menu set it's description to `'which_key_ignore'`. Useful for instance, to hide a list of [1-9] window swapping mappings. For example the below mapping will not be shown in the menu. 168 | 169 | ```vim 170 | nnoremap 1 :1wincmd w 171 | let g:which_key_map.1 = 'which_key_ignore' 172 | ``` 173 | 174 | If you want to hide a group of non-top level mappings, set the `name` to `'which_key_ignore'`. For example, 175 | 176 | ```vim 177 | nnoremap _a :echom '_a' 178 | nnoremap _b :echom '_b' 179 | let g:which_key_map['_'] = { 'name': 'which_key_ignore' } 180 | ``` 181 | 182 | If you want to hide all mappings outside of the elements of the description dictionary, use: `let g:which_key_ignore_outside_mappings = 1`. 183 | 184 | #### Example 185 | 186 | You can configure a Dict for each prefix so that the display is more readable. 187 | 188 | To make the guide pop up **Register the description dictionary for the prefix first**. Assuming `Space` is your leader key and the Dict for configuring `Space` is `g:which_key_map`: 189 | 190 | ```vim 191 | nnoremap :WhichKey '' 192 | vnoremap :WhichKeyVisual '' 193 | 194 | call which_key#register('', "g:which_key_map") 195 | ``` 196 | 197 | The above registers the same description dictionary for both normal and visual modes. To use a separate description dictionary for each mode: add a third argument specifying which mode: 198 | ```vim 199 | call which_key#register('', "g:which_key_map", 'n') 200 | call which_key#register('', "g:which_key_map_visual", 'v') 201 | ``` 202 | 203 | The next step is to add items to `g:which_key_map`: 204 | 205 | ```vim 206 | " Define prefix dictionary 207 | let g:which_key_map = {} 208 | 209 | " Second level dictionaries: 210 | " 'name' is a special field. It will define the name of the group, e.g., leader-f is the "+file" group. 211 | " Unnamed groups will show a default empty string. 212 | 213 | " ======================================================= 214 | " Create menus based on existing mappings 215 | " ======================================================= 216 | " You can pass a descriptive text to an existing mapping. 217 | 218 | let g:which_key_map.f = { 'name' : '+file' } 219 | 220 | nnoremap fs :update 221 | let g:which_key_map.f.s = 'save-file' 222 | 223 | nnoremap fd :e $MYVIMRC 224 | let g:which_key_map.f.d = 'open-vimrc' 225 | 226 | nnoremap oq :copen 227 | nnoremap ol :lopen 228 | let g:which_key_map.o = { 229 | \ 'name' : '+open', 230 | \ 'q' : 'open-quickfix' , 231 | \ 'l' : 'open-locationlist', 232 | \ } 233 | 234 | " ======================================================= 235 | " Create menus not based on existing mappings: 236 | " ======================================================= 237 | " Provide commands(ex-command, // mapping, etc.) 238 | " and descriptions for the existing mappings. 239 | " 240 | " Note: 241 | " Some complicated ex-cmd may not work as expected since they'll be 242 | " feed into `feedkeys()`, in which case you have to define a decicated 243 | " Command or function wrapper to make it work with vim-which-key. 244 | " Ref issue #126, #133 etc. 245 | let g:which_key_map.b = { 246 | \ 'name' : '+buffer' , 247 | \ '1' : ['b1' , 'buffer 1'] , 248 | \ '2' : ['b2' , 'buffer 2'] , 249 | \ 'd' : ['bd' , 'delete-buffer'] , 250 | \ 'f' : ['bfirst' , 'first-buffer'] , 251 | \ 'h' : ['Startify' , 'home-buffer'] , 252 | \ 'l' : ['blast' , 'last-buffer'] , 253 | \ 'n' : ['bnext' , 'next-buffer'] , 254 | \ 'p' : ['bprevious' , 'previous-buffer'] , 255 | \ '?' : ['Buffers' , 'fzf-buffer'] , 256 | \ } 257 | 258 | let g:which_key_map.l = { 259 | \ 'name' : '+lsp', 260 | \ 'f' : ['spacevim#lang#util#Format()' , 'formatting'] , 261 | \ 'r' : ['spacevim#lang#util#FindReferences()' , 'references'] , 262 | \ 'R' : ['spacevim#lang#util#Rename()' , 'rename'] , 263 | \ 's' : ['spacevim#lang#util#DocumentSymbol()' , 'document-symbol'] , 264 | \ 'S' : ['spacevim#lang#util#WorkspaceSymbol()' , 'workspace-symbol'] , 265 | \ 'g' : { 266 | \ 'name': '+goto', 267 | \ 'd' : ['spacevim#lang#util#Definition()' , 'definition'] , 268 | \ 't' : ['spacevim#lang#util#TypeDefinition()' , 'type-definition'] , 269 | \ 'i' : ['spacevim#lang#util#Implementation()' , 'implementation'] , 270 | \ }, 271 | \ } 272 | ``` 273 | 274 | The guide will be up to date at all times. Native vim mappings will always take precedence over dictionary-only mappings. 275 | 276 | It is possible to call the guide for keys other than `leader`: 277 | 278 | ```vim 279 | nnoremap :WhichKey ',' 280 | vnoremap :WhichKeyVisual ',' 281 | ``` 282 | 283 | - Refer to [space-vim](https://github.com/liuchengxu/space-vim/blob/master/core/autoload/spacevim/map/leader.vim) for more detailed example. 284 | 285 | #### Hide statusline 286 | 287 |

288 | 289 | Since the theme of provided statusline is not flexible and all the information has been echoed already, I prefer to hide it. 290 | 291 | ```vim 292 | autocmd! FileType which_key 293 | autocmd FileType which_key set laststatus=0 noshowmode noruler 294 | \| autocmd BufLeave set laststatus=2 showmode ruler 295 | ``` 296 | 297 | ### Commands 298 | 299 | See more details about commands and options via `:h vim-which-key`. 300 | 301 | | Command | Description | 302 | | :------------------- | :---------------------------------------------------: | 303 | | `:WhichKey {prefix}` | Open the guide window for the given prefix | 304 | | `:WhichKey! {dict}` | Open the guide window for a given dictionary directly | 305 | 306 | ### Options 307 | 308 | | Variable | Default | Description | 309 | | :--------------------- | :--------: | :-----------------------------------------: | 310 | | `g:which_key_vertical` | 0 | show popup vertically | 311 | | `g:which_key_position` | `botright` | split a window at the bottom | 312 | | `g:which_key_hspace` | 5 | minimum horizontal space between columns | 313 | | `g:which_key_centered` | 1 | make all keybindings centered in the middle | 314 | 315 | ### FAQ 316 | 317 | #### How to map some special keys like ``? 318 | 319 | See [#178](https://github.com/liuchengxu/vim-which-key/issues/178). 320 | 321 | #### How to set keybindings on filetype or other condition? 322 | 323 | You may use BufEnter/BufLeave [#132](https://github.com/liuchengxu/vim-which-key/issues/132), a `dictionary-function` [#209](https://github.com/liuchengxu/vim-which-key/pull/209), or *[experimental]* setup per buffer [#48](https://github.com/liuchengxu/vim-which-key/pull/48). 324 | 325 | #### How to map lua functions? 326 | 327 | This is possible via [nvim-whichkey-setup.lua](https://github.com/AckslD/nvim-whichkey-setup.lua). For example, if one wanted to map [spectre's](https://github.com/windwp/nvim-spectre) `open` to `S`, which in vimscipt would be `nnoremap S lua require('spectre').open()`, one could use the following in one's `init.vim`: 328 | 329 | ```vim 330 | lua<', 'Search'}, 335 | } 336 | 337 | wk.register_keymap('leader', keymap) 338 | EOF 339 | ``` 340 | 341 | NB that keymaps can only be registered once. The entirety of one's `vim-which-key` configuration must be ported to [nvim-whichkey-setup.lua](https://github.com/AckslD/nvim-whichkey-setup.lua) in order to enable this functionality. 342 | 343 | ## Credit 344 | 345 | - [vim-leader-guide](https://github.com/hecal3/vim-leader-guide) 346 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /autoload/which_key.vim: -------------------------------------------------------------------------------- 1 | scriptencoding utf-8 2 | 3 | let s:desc = get(s:, 'desc', { 'n': {}, 'v': {} }) 4 | let s:cache = get(s:, 'cache', { 'n': {}, 'v': {} }) 5 | let s:TYPE = { 6 | \ 'list': type([]), 7 | \ 'dict': type({}), 8 | \ 'number': type(0), 9 | \ 'string': type(''), 10 | \ 'funcref': type(function('call')) 11 | \ } 12 | " For the purpose of mapping a keypress to internal data-structures 13 | let s:KEYCODES = { 14 | \ "\": '', 15 | \ "\": '', 16 | \ "\": '', 17 | \ "\": '', 18 | \ "\": '' 19 | \ } 20 | " For the purposes of merging identical keycodes in internal data-structures 21 | let s:MERGE_INTO = { 22 | \ '': ' ', 23 | \ '': '', 24 | \ '': '', 25 | \ '': '', 26 | \ '': '', 27 | \ '': '', 28 | \ '': '', 29 | \ '': '<', 30 | \ '': '\', 31 | \ '': '|' 32 | \ } 33 | let s:REQUIRES_REGEX_ESCAPE = ['$', '*', '~', '.'] 34 | 35 | let g:which_key#TYPE = s:TYPE 36 | 37 | let s:should_note_winid = exists('*win_getid') 38 | 39 | function! which_key#register(prefix, dict, ...) abort 40 | let key = has_key(s:MERGE_INTO, a:prefix) ? 41 | \ s:MERGE_INTO[a:prefix] : a:prefix 42 | let val = a:dict 43 | if a:0 == 1 44 | call extend(s:desc[a:1], {key:val}) 45 | else 46 | call extend(s:desc['n'], {key:val}) 47 | call extend(s:desc['v'], {key:val}) 48 | endif 49 | endfunction 50 | 51 | " No need to open which-key window, execute the acction according to the current input. 52 | function! s:handle_char_on_start_is_ok(c) abort 53 | if which_key#char_handler#is_exit_code(a:c) 54 | return 1 55 | endif 56 | let char = type(a:c) == s:TYPE.number ? nr2char(a:c) : a:c 57 | if has_key(s:KEYCODES, char) 58 | let char = s:KEYCODES[char] 59 | else 60 | let char = which_key#char_handler#parse_raw(char) 61 | endif 62 | let displaynames = which_key#renderer#get_displaynames() 63 | let s:which_key_trigger .= ' '.get(displaynames, toupper(char), char) 64 | let next_level = get(s:runtime, char) 65 | let ty = type(next_level) 66 | if ty == s:TYPE.dict 67 | let s:runtime = next_level 68 | return 0 69 | elseif ty == s:TYPE.list && (!g:which_key_fallback_to_native_key || 70 | \ g:which_key_fallback_to_native_key && 71 | \ next_level[0] !=# 'which_key#error#missing_mapping()') 72 | call s:execute(next_level[0]) 73 | return 1 74 | elseif g:which_key_fallback_to_native_key 75 | call s:execute_native_fallback(0) 76 | return 1 77 | else 78 | call which_key#error#undefined_key(s:which_key_trigger) 79 | return 1 80 | endif 81 | endfunction 82 | 83 | function! which_key#start(vis, bang, prefix) " {{{ 84 | let s:vis = a:vis ? 'gv' : '' 85 | let mode = a:vis ? 'v' : 'n' 86 | let prefix = a:prefix 87 | let s:count = v:count != 0 ? v:count : '' 88 | let s:which_key_trigger = '' 89 | 90 | if s:should_note_winid 91 | let g:which_key_origin_winid = win_getid() 92 | endif 93 | 94 | if a:bang 95 | for kv in keys(prefix) 96 | call s:cache_key(mode, kv) 97 | endfor 98 | let s:runtime = deepcopy(prefix) 99 | call s:merge(s:runtime, s:cache[mode]) 100 | else 101 | if has_key(s:KEYCODES, prefix) 102 | let prefix = s:KEYCODES[prefix] 103 | else 104 | let prefix = which_key#char_handler#parse_raw(prefix) 105 | endif 106 | if has_key(s:MERGE_INTO, prefix) 107 | let prefix = s:MERGE_INTO[prefix] 108 | endif 109 | let key = prefix 110 | let displaynames = which_key#renderer#get_displaynames() 111 | let s:which_key_trigger = get(displaynames, toupper(key), key) 112 | call s:cache_key(mode, key) 113 | 114 | " s:runtime is a dictionary combining the native key mapping dictionary 115 | " parsed by vim-which-key itself with user defined prefix dictionary if avaliable. 116 | let s:runtime = s:create_runtime(mode, key) 117 | 118 | if getchar(1) 119 | while 1 120 | try 121 | let c = getchar() 122 | catch /^Vim:Interrupt$/ 123 | return '' 124 | endtry 125 | if s:handle_char_on_start_is_ok(c) 126 | return 127 | endif 128 | " When there are next level options, wait another timeoutlen. 129 | " https://github.com/liuchengxu/vim-which-key/issues/3 130 | " https://github.com/liuchengxu/vim-which-key/issues/4 131 | if which_key#char_handler#wait_with_timeout() 132 | break 133 | endif 134 | endwhile 135 | endif 136 | endif 137 | 138 | let s:last_runtime_stack = [copy(s:runtime)] 139 | call which_key#window#show(s:runtime) 140 | endfunction 141 | 142 | function! s:cache_key(mode, key) 143 | let mode = a:mode 144 | let key = a:key 145 | if !has_key(s:cache[mode], key) || g:which_key_run_map_on_popup 146 | let s:cache[mode][key] = {} 147 | call which_key#mappings#parse(key, s:cache[mode], mode) 148 | endif 149 | endfunction 150 | 151 | function! s:create_runtime(mode, key) 152 | let mode = a:mode 153 | let key = a:key 154 | if has_key(s:desc[mode], key) 155 | if type(s:desc[mode][key]) == s:TYPE.dict 156 | let runtime = deepcopy(s:desc[mode][key]) 157 | else 158 | let runtime = deepcopy({s:desc[mode][key]}) 159 | endif 160 | let native = s:cache[mode][key] 161 | call s:merge(runtime, native) 162 | else 163 | let runtime = s:cache[mode][key] 164 | endif 165 | return runtime 166 | endfunction 167 | 168 | function! s:merge(target, native) " {{{ 169 | let target = a:target 170 | let native = a:native 171 | " e.g. is merged into , '' is merged into ' ' 172 | call map(target, {k,v -> 173 | \ has_key(s:MERGE_INTO, k) ? 174 | \ (has_key(target, s:MERGE_INTO[k]) ? 175 | \ extend(target[s:MERGE_INTO[k]], target[k], 'keep') : 176 | \ extend(target, {s:MERGE_INTO[k]: target[k]})) : 177 | \ v}) 178 | call filter(target, {k,_ -> !has_key(s:MERGE_INTO, k)}) 179 | for [k, V] in items(target) 180 | " Support a `Dictionary-function` for on-the-fly mappings 181 | while type(target[k]) == s:TYPE.funcref 182 | " Evaluate the funcref, to allow the result to be processed 183 | let target[k] = target[k]() 184 | endwhile 185 | 186 | if type(V) == s:TYPE.dict 187 | if has_key(native, k) 188 | if type(native[k]) == s:TYPE.dict 189 | if has_key(V, 'name') 190 | let native[k].name = V.name 191 | endif 192 | call s:merge(target[k], native[k]) 193 | elseif type(native[k]) == s:TYPE.list 194 | let target[k] = native[k] 195 | endif 196 | else 197 | " Process leaf nodes 198 | call s:merge(target[k], {}) 199 | endif 200 | " Support add a description to an existing map without dual definition 201 | elseif type(V) == s:TYPE.string && k !=# 'name' 202 | if has_key(native, k) 203 | let target[k] = [native[k][0], V] 204 | else 205 | let target[k] = ['which_key#error#missing_mapping()', V] 206 | endif 207 | endif 208 | endfor 209 | 210 | if !g:which_key_ignore_outside_mappings 211 | call extend(target, native, 'keep') 212 | endif 213 | endfunction 214 | 215 | function! s:echo_prompt() abort 216 | echohl Keyword 217 | echo s:which_key_trigger.'- ' 218 | echohl None 219 | 220 | echohl String 221 | echon which_key#window#name() 222 | echohl None 223 | endfunction 224 | 225 | if has('lambda') 226 | function! s:has_children(input) abort 227 | if index(s:REQUIRES_REGEX_ESCAPE, a:input) != -1 228 | let group = map(keys(s:runtime), {_,v -> v =~# '^\'.a:input}) 229 | else 230 | let group = map(keys(s:runtime), {_,v -> v =~# '^'.a:input}) 231 | endif 232 | return len(filter(group, 'v:val == 1')) > 1 233 | endfunction 234 | else 235 | function! s:has_children(input) abort 236 | if index(s:REQUIRES_REGEX_ESCAPE, a:input) != -1 237 | let regex = '^\'.a:input 238 | else 239 | let regex = '^'.a:input 240 | endif 241 | let cnt = 0 242 | for each in keys(s:runtime) 243 | if each =~# regex 244 | let cnt += 1 245 | if cnt > 1 246 | return 1 247 | endif 248 | endif 249 | endfor 250 | return 0 251 | endfunction 252 | endif 253 | 254 | function! s:show_upper_level_mappings() abort 255 | " Top level 256 | if empty(s:last_runtime_stack) 257 | call which_key#window#show(s:runtime) 258 | return 259 | endif 260 | 261 | let last_runtime = s:last_runtime_stack[-1] 262 | let s:runtime = last_runtime 263 | 264 | if len(s:last_runtime_stack) > 1 265 | let s:which_key_trigger = join(split(s:which_key_trigger)[:-2], ' ') 266 | endif 267 | 268 | unlet s:last_runtime_stack[-1] 269 | 270 | call which_key#window#show(last_runtime) 271 | endfunction 272 | 273 | function! s:getchar() abort 274 | try 275 | let c = getchar() 276 | " Handle 277 | catch /^Vim:Interrupt$/ 278 | call which_key#window#close() 279 | redraw! 280 | return '' 281 | endtry 282 | 283 | if which_key#char_handler#is_exit_code(c) 284 | call which_key#window#close() 285 | redraw! 286 | return '' 287 | endif 288 | 289 | " Allow to go back to the upper level. 290 | if c == "\" 291 | call s:show_upper_level_mappings() 292 | return '' 293 | endif 294 | 295 | let input = which_key#char_handler#parse_raw(c) 296 | 297 | if s:has_children(input) 298 | while 1 299 | if !which_key#char_handler#timeout_for_next_char() 300 | let input .= which_key#char_handler#parse_raw(getchar()) 301 | else 302 | break 303 | endif 304 | endwhile 305 | endif 306 | 307 | " Convert special keys to internal data structure that use String as the 308 | " key, e.g., "\" => "" 309 | if has_key(s:KEYCODES, input) 310 | let input = s:KEYCODES[input] 311 | elseif has_key(s:MERGE_INTO, input) 312 | let input = s:MERGE_INTO[input] 313 | endif 314 | 315 | return input 316 | endfunction 317 | 318 | function! which_key#wait_for_input() " {{{ 319 | " redraw is needed! 320 | redraw 321 | 322 | " Append the prompt in the buffer at last when using floating or 323 | " popup wnidow, otherwise show it in the cmdline. 324 | if !g:which_key_use_floating_win 325 | call s:echo_prompt() 326 | endif 327 | 328 | let char = s:getchar() 329 | if char ==# '' 330 | return 331 | endif 332 | 333 | let s:cur_char = char 334 | 335 | call s:handle_input(get(s:runtime, char)) 336 | endfunction 337 | 338 | function! s:show_next_level_mappings(next_runtime) abort 339 | let displaynames = which_key#renderer#get_displaynames() 340 | let s:which_key_trigger .= ' '.get(displaynames, toupper(s:cur_char), s:cur_char) 341 | call add(s:last_runtime_stack, copy(s:runtime)) 342 | let s:runtime = a:next_runtime 343 | call which_key#window#show(s:runtime) 344 | endfunction 345 | 346 | function! s:handle_input(input) " {{{ 347 | let ty = type(a:input) 348 | 349 | if ty == s:TYPE.dict 350 | call s:show_next_level_mappings(a:input) 351 | return 352 | endif 353 | 354 | if ty == s:TYPE.list && (!g:which_key_fallback_to_native_key || 355 | \ g:which_key_fallback_to_native_key && 356 | \ a:input[0] !=# 'which_key#error#missing_mapping()') 357 | call which_key#window#close() 358 | call s:execute(a:input[0]) 359 | elseif g:which_key_fallback_to_native_key 360 | call which_key#window#close() 361 | " Is redraw needed here? 362 | " redraw! 363 | call s:execute_native_fallback(1) 364 | else 365 | if g:which_key_ignore_invalid_key 366 | call which_key#wait_for_input() 367 | else 368 | call which_key#window#close() 369 | redraw! 370 | call which_key#error#undefined_key(s:which_key_trigger) 371 | endif 372 | endif 373 | endfunction 374 | 375 | function! s:execute_native_fallback(append) abort 376 | let l:reg = s:get_register() 377 | let l:fallback_cmd = s:vis.l:reg.s:count.substitute(substitute(s:which_key_trigger, ' ', '', 'g'), '', ' ', 'g') 378 | if (a:append) 379 | let l:fallback_cmd = l:fallback_cmd.get(s:, 'cur_char', '') 380 | endif 381 | try 382 | call feedkeys(l:fallback_cmd, 'n') 383 | catch 384 | call which_key#error#report('Exception: '.v:exception.' occurs for the fallback mapping: '.l:fallback_cmd) 385 | endtry 386 | endfunction 387 | 388 | function! s:join(...) abort 389 | return join(a:000, ' ') 390 | endfunction 391 | 392 | function! s:execute(cmd) abort 393 | let reg = s:get_register() 394 | if s:vis.reg.s:count !=# '' 395 | execute 'normal!' s:vis.reg.s:count 396 | endif 397 | redraw 398 | let Cmd = a:cmd 399 | try 400 | if type(Cmd) == s:TYPE.funcref 401 | call call(Cmd, []) 402 | return 403 | endif 404 | if Cmd =~? '^.\+' || Cmd =~? '^.\+' || Cmd =~? '^<.\+>$' 405 | let Cmd = s:join('call', 'feedkeys("\'.Cmd.'")') 406 | elseif Cmd =~? '.(*)$' && match(Cmd, '\') == -1 407 | let Cmd = s:join('call', Cmd) 408 | elseif exists(':'.Cmd) || Cmd =~# '^:' || Cmd =~? '^call feedkeys(.*)$' 409 | if !empty(s:vis) 410 | let Cmd = line('v').','.line('.').Cmd 411 | endif 412 | else 413 | let Cmd = s:join('call', 'feedkeys("'.Cmd.'")') 414 | endif 415 | execute Cmd 416 | catch 417 | echom v:exception 418 | endtry 419 | endfunction 420 | 421 | " -------------------------------------- 422 | " Misc 423 | " -------------------------------------- 424 | function! s:register() abort 425 | let clipboard = &clipboard 426 | if clipboard ==# 'unnamedplus' 427 | return '+' 428 | elseif clipboard ==# 'unnamed' 429 | return '*' 430 | else 431 | return '"' 432 | endif 433 | endfunction 434 | 435 | function! s:get_register() abort 436 | if has('nvim') && !exists('s:reg') 437 | let s:reg = '' 438 | else 439 | let s:reg = v:register != s:register() ? '"'.v:register : '' 440 | endif 441 | return s:reg 442 | endfunction 443 | 444 | " Update the cache manually by calling this function. 445 | function! which_key#parse_mappings() " {{{ 446 | for [mode, d] in items(s:cache) 447 | for k in keys(d) 448 | call which_key#mappings#parse(k, d, mode) 449 | endfor 450 | endfor 451 | endfunction " }}} 452 | 453 | function! which_key#format(mapping) abort 454 | let l:ret = a:mapping 455 | let l:ret = substitute(l:ret, '\c$', '', '') 456 | let l:ret = substitute(l:ret, '^:', '', '') 457 | let l:ret = substitute(l:ret, '^\c', '', '') 458 | " let l:ret = substitute(l:ret, '^', '', '') 459 | return l:ret 460 | endfunction 461 | 462 | function! which_key#statusline() abort 463 | let key = '%#WhichKeyTrigger# %{get(s:, "which_key_trigger", "")} %*' 464 | let name = '%#WhichKeyName# %{which_key#window#name()} %*' 465 | return key.name 466 | endfunction 467 | 468 | function! which_key#trigger() abort 469 | return get(s:, 'which_key_trigger', '') 470 | endfunction 471 | 472 | function! which_key#get_sep() abort 473 | return get(g:, 'which_key_sep', '→') 474 | endfunction 475 | -------------------------------------------------------------------------------- /autoload/which_key/char_handler.vim: -------------------------------------------------------------------------------- 1 | let s:TYPE = g:which_key#TYPE 2 | 3 | " ASCII printable 4 | let s:chars = map(range(32, 126), 'nr2char(v:val)') 5 | 6 | let s:special_keys = { 7 | \ "\": '', 8 | \ "\": '', 9 | \ "\": '', 10 | \ "\": '', 11 | \ "\": '', 12 | \ "\": '', 13 | \ "\": '', 14 | \ "\": '', 15 | \ "\": '', 16 | \ "\<2-LeftMouse>": '<2-LeftMouse>', 17 | \ "\": '', 18 | \ "\": '', 19 | \ "\": '', 20 | \ "\": '', 21 | \ "\": '', 22 | \ "\": '', 23 | \ "\": '', 24 | \ "\": '', 25 | \ "\": '', 26 | \ } 27 | 28 | 29 | " Generate a key mapping string based on a mode (empty string or one of C/S/M) 30 | " and a key name. 31 | function! s:gen_key_mapping(mode,key) 32 | let repr = '<' 33 | if a:mode != '' 34 | let repr = l:repr . a:mode . '-' 35 | endif 36 | if a:key ==# '"' 37 | let repr = l:repr . '\' 38 | endif 39 | let l:repr = l:repr . a:key . '>' 40 | let code = eval('"\' . l:repr . '"') 41 | return [l:repr, l:code] 42 | endfunction 43 | 44 | " Add M-* key mappings 45 | for c in s:chars 46 | let [key, code] = s:gen_key_mapping('M',c) 47 | let s:special_keys[code] = key 48 | endfor 49 | 50 | " Add function keys and related combos 51 | for fk in range(1,37) 52 | for p in [ "" , "S" , "C" , "M" ] 53 | let [key, code] = s:gen_key_mapping(p, 'F' . fk) 54 | let s:special_keys[code] = key 55 | endfor 56 | endfor 57 | 58 | function! which_key#char_handler#parse_raw(raw_char) 59 | if type(a:raw_char) == g:which_key#TYPE.number 60 | return nr2char(a:raw_char) 61 | elseif has_key(s:special_keys, a:raw_char) 62 | " Special characters 63 | return s:special_keys[a:raw_char] 64 | else 65 | return a:raw_char 66 | endif 67 | endfunction 68 | 69 | function! s:initialize_exit_code() abort 70 | if exists('g:which_key_exit') 71 | let ty = type(g:which_key_exit) 72 | if ty == s:TYPE.number 73 | let s:exit_code = [nr2char(g:which_key_exit)] 74 | elseif ty == s:TYPE.string 75 | let s:exit_code = [g:which_key_exit] 76 | elseif ty == s:TYPE.list 77 | let s:exit_code = map(g:which_key_exit, {_, val -> 78 | \ type(val) == s:TYPE.number ? nr2char(val) : val}) 79 | else 80 | echohl ErrorMsg 81 | echon '[which-key] '.g:which_key_exit.' is invalid for option g:which_key_exit' 82 | echohl None 83 | return 1 84 | endif 85 | else 86 | " , : 27 87 | let s:exit_code = ["\"] 88 | endif 89 | endfunction 90 | 91 | if !exists('s:exit_code') 92 | call s:initialize_exit_code() 93 | endif 94 | 95 | " Argument: v:t_number or v:t_string as returned by getchar() 96 | function! which_key#char_handler#is_exit_code(raw_char) abort 97 | return -1 != index(s:exit_code, 98 | \ type(a:raw_char) == s:TYPE.number ? nr2char(a:raw_char) : a:raw_char) 99 | endfunction 100 | 101 | " Returns true if timed out 102 | function! s:wait_with_timeout(timeout) abort 103 | let timeout = a:timeout 104 | while timeout > 0 105 | if getchar(1) 106 | return 0 107 | endif 108 | sleep 20m 109 | let timeout -= 20 110 | endwhile 111 | return !getchar(1) 112 | endfunction 113 | 114 | " Wait timtout to see if there are more input chars. 115 | function! which_key#char_handler#wait_with_timeout() abort 116 | return s:wait_with_timeout(g:which_key_timeout) 117 | endfunction 118 | 119 | " Wait timtout to see if user is about to input more chars. 120 | function! which_key#char_handler#timeout_for_next_char() abort 121 | return s:wait_with_timeout(g:which_key_timeout) 122 | endfunction 123 | -------------------------------------------------------------------------------- /autoload/which_key/error.vim: -------------------------------------------------------------------------------- 1 | function! which_key#error#report(err_msg) abort 2 | echohl ErrorMsg 3 | echom '[which-key] '.a:err_msg 4 | echohl None 5 | endfunction 6 | 7 | function! which_key#error#undefined_key(key) abort 8 | echohl ErrorMsg 9 | echom '[which-key] '.a:key.' is undefined' 10 | echohl None 11 | endfunction 12 | 13 | function! which_key#error#missing_mapping() abort 14 | echohl ErrorMsg 15 | echom '[which-key] Fail to execute, no such mapping' 16 | echohl None 17 | endfunction 18 | -------------------------------------------------------------------------------- /autoload/which_key/mappings.vim: -------------------------------------------------------------------------------- 1 | let s:TYPE = g:which_key#TYPE 2 | 3 | function! s:string_to_keys(input) abort 4 | let input = a:input 5 | " Avoid special case: <> 6 | if match(input, '<.\+>') != -1 7 | let retlist = [] 8 | let si = 0 9 | let go = 1 10 | while si < len(input) 11 | if go 12 | call add(retlist, input[si]) 13 | else 14 | let retlist[-1] .= input[si] 15 | endif 16 | if input[si] ==? '<' 17 | let go = 0 18 | elseif input[si] ==? '>' 19 | let go = 1 20 | end 21 | let si += 1 22 | endwhile 23 | return retlist 24 | else 25 | return split(input, '\zs') 26 | endif 27 | endfunction " }}} 28 | 29 | function! s:execute(cmd) 30 | if exists("*execute") 31 | return execute(a:cmd) 32 | else 33 | redir => l:output 34 | silent! execute a:cmd 35 | redir END 36 | return l:output 37 | endif 38 | endfunction 39 | 40 | function! s:get_raw_map_info(key) abort 41 | return split(s:execute('map '.a:key), "\n") 42 | endfunction 43 | 44 | " Parse key-mappings gathered by `:map` and feed them into dict 45 | function! which_key#mappings#parse(key, dict, visual) " {{{ 46 | let key = a:key ==? ' ' ? '' : (a:key ==? '' ? '' : a:key) 47 | let dk = a:key ==? '' ? ' ' : (a:key ==? '' ? '' : a:key) 48 | if !has_key(a:dict, dk) 49 | let a:dict[dk] = {} 50 | endif 51 | let visual = a:visual ==# 'v' 52 | 53 | let lines = s:get_raw_map_info(key) 54 | if key ==# '' 55 | call extend(lines, s:get_raw_map_info('')) 56 | endif 57 | " In vim older than vim8.2.0815, Alt key as `` togethr with English alphabet raise mapd.lhs 58 | " only contain eval() value, not '' 59 | if !has('nvim') && key[0:2] ==# '.*' || mapd.lhs =~? '.*' 66 | continue 67 | endif 68 | if has_key(mapd, 'desc') 69 | let mapd.rhs = mapd.desc 70 | unlet mapd.desc 71 | " NOTE: nvim's built-in lua functions have `callback` key in mapd, it must be deleted. 72 | " Acctually, nvim's runtime script contain these functions, mapd.rhs could be rebuilt 73 | " so the built-in lua function could be parsed, maybe it is not a beautiful resolution but workable. 74 | elseif has_key(mapd, 'callback') 75 | unlet mapd.callback 76 | try 77 | let sp = split(split(maparg(raw_sp[0], line[0])[:-2])[-1], ":") 78 | " `fl` is nvim runtime script 79 | let fl = expand(sp[0]) 80 | " `ln` is the line where the lua function layed, 81 | let ln = str2nr(sp[-1]) - 1 82 | let rhs = trim(readfile(fl)[ln]) 83 | let rhs = split(rhs, 'M.')[1] 84 | " create api from file name 85 | let api = split(substitute(fl, "\\", "/", 'g'), 'runtime/lua/')[1] 86 | let api = substitute(api, 'lua$', '', 'g') 87 | let api = substitute(api, '/', '.', 'g') 88 | let mapd.rhs = "lua " . api . rhs . '' 89 | catch /.*/ 90 | let mapd.rhs = "lua function not show" 91 | endtry 92 | endif 93 | 94 | let mapd.display = call(g:WhichKeyFormatFunc, [mapd.rhs]) 95 | 96 | let mapd.lhs = substitute(mapd.lhs, key, '', '') 97 | " EasyMotion workaround, is default easymotion prefix 98 | if mapd.lhs ==? '' && mapcheck('', 'n') =~ 'easymotion' 99 | continue 100 | endif 101 | let mapd.lhs = substitute(mapd.lhs, '', ' ', 'g') 102 | let mapd.lhs = substitute(mapd.lhs, '', '', 'g') 103 | let mapd.rhs = substitute(mapd.rhs, '', ''.mapd['sid'].'_', 'g') 104 | 105 | " eval the expression as the final {rhs} 106 | " Ref #60 107 | if mapd.expr 108 | let mapd.rhs = eval(mapd.rhs) 109 | endif 110 | 111 | if mapd.lhs !=# '' && mapd.display !~# 'WhichKey.*' 112 | if (match(mapd.mode, visual ? '[vx ]' : '[n ]') >= 0) 113 | let mapd.lhs = s:string_to_keys(mapd.lhs) 114 | call s:add_map_to_dict(mapd, 0, a:dict[dk]) 115 | endif 116 | endif 117 | endfor 118 | endfunction 119 | 120 | function! s:escape(mapping) abort " {{{ 121 | let feedkeyargs = a:mapping.noremap ? 'nt' : 'mt' 122 | let rhs = substitute(a:mapping.rhs, '\', '\\\\', 'g') 123 | let rhs = substitute(rhs, '<\([^<>]*\)>', '\\<\1>', 'g') 124 | let rhs = substitute(rhs, '"', '\\"', 'g') 125 | let rhs = 'call feedkeys("'.rhs.'", "'.feedkeyargs.'")' 126 | return rhs 127 | endfunction " }}} 128 | 129 | function! s:add_map_to_dict(map, level, dict) " {{{ 130 | 131 | let cmd = s:escape(a:map) 132 | 133 | if len(a:map.lhs) > a:level+1 134 | let curkey = a:map.lhs[a:level] 135 | let nlevel = a:level+1 136 | 137 | if !has_key(a:dict, curkey) 138 | let a:dict[curkey] = { 'name' : g:which_key_default_group_name } 139 | " mapping defined already, flatten this map 140 | elseif type(a:dict[curkey]) == s:TYPE.list 141 | 142 | if g:which_key_flatten 143 | let curkey = join(a:map.lhs[a:level+0:], '') 144 | let nlevel = a:level 145 | if !has_key(a:dict, curkey) 146 | let a:dict[curkey] = [cmd, a:map.display] 147 | endif 148 | else 149 | let curkey = curkey.'m' 150 | if !has_key(a:dict, curkey) 151 | let a:dict[curkey] = { 'name' : g:which_key_default_group_name } 152 | endif 153 | endif 154 | endif 155 | " next level 156 | if type(a:dict[curkey]) == s:TYPE.dict 157 | call s:add_map_to_dict(a:map, nlevel, a:dict[curkey]) 158 | endif 159 | 160 | else 161 | 162 | let lhs_at_level = a:map.lhs[a:level] 163 | 164 | if !has_key(a:dict, lhs_at_level) 165 | let a:dict[lhs_at_level] = [cmd, a:map.display] 166 | " spot is taken already, flatten existing submaps 167 | elseif type(a:dict[lhs_at_level]) == s:TYPE.dict 168 | \ && g:which_key_flatten 169 | let childmap = s:flatten(a:dict[lhs_at_level], lhs_at_level) 170 | for it in keys(childmap) 171 | let a:dict[it] = childmap[it] 172 | endfor 173 | let a:dict[lhs_at_level] = [cmd, a:map.display] 174 | endif 175 | 176 | endif 177 | endfunction 178 | 179 | " Flatten map 180 | function! s:flatten(dict, str) abort 181 | let flat = {} 182 | for kv in keys(a:dict) 183 | let ty = type(a:dict[kv]) 184 | if ty == s:TYPE.list 185 | let toret = {} 186 | let toret[a:str.kv] = a:dict[kv] 187 | return toret 188 | elseif ty == s:TYPE.dict 189 | call extend(flat, s:flatten(a:dict[kv], a:str.kv)) 190 | endif 191 | endfor 192 | return flat 193 | endfunction 194 | -------------------------------------------------------------------------------- /autoload/which_key/renderer.vim: -------------------------------------------------------------------------------- 1 | let s:TYPE = g:which_key#TYPE 2 | let s:default_displaynames = { 3 | \ ' ': 'SPC', 4 | \ '': 'BS', 5 | \ '': 'TAB', 6 | \ } 7 | 8 | function! which_key#renderer#prepare(runtime) abort 9 | let layout = s:calc_layout(a:runtime) 10 | let rows = s:create_rows(layout, a:runtime) 11 | 12 | return [layout, rows] 13 | endfunction 14 | 15 | function! which_key#renderer#get_displaynames() 16 | if exists('g:which_key_display_names') 17 | return g:which_key_display_names 18 | else 19 | return s:default_displaynames 20 | endif 21 | endfunction 22 | 23 | function! s:calc_layout(mappings) abort " {{{ 24 | let layout = {} 25 | let smap = filter(copy(a:mappings), 'v:key !=# "name" && !(type(v:val) == s:TYPE.list && v:val[1] ==# "which_key_ignore")') 26 | let layout.n_items = len(smap) 27 | let displaynames = which_key#renderer#get_displaynames() 28 | 29 | let prefix_length = values(map(copy(smap), 30 | \ 'strdisplaywidth(get(displaynames, toupper(v:key), v:key))')) 31 | let suffix_length = values(map(smap, 32 | \ 'strdisplaywidth(type(v:val) ==s:TYPE.dict ?'. 33 | \ 'get(v:val, "name", "") : v:val[1])')) 34 | 35 | let maxlength = max(prefix_length) + max(suffix_length) 36 | \ + strdisplaywidth(g:which_key_sep) + 2 37 | 38 | if g:which_key_vertical 39 | 40 | " TODO multiple pages. 41 | if g:which_key_floating_relative_win 42 | let layout.n_rows = winheight(g:which_key_origin_winid) - 2 43 | else 44 | let layout.n_rows = winheight(0) - 2 45 | endif 46 | 47 | let layout.n_cols = layout.n_items / layout.n_rows + (layout.n_items != layout.n_rows) 48 | let layout.col_width = maxlength 49 | let layout.win_dim = layout.n_cols * layout.col_width 50 | 51 | let s:target_winwidth = layout.col_width 52 | 53 | else 54 | let maxlength += g:which_key_hspace 55 | 56 | if g:which_key_floating_relative_win 57 | let winwidth = winwidth(g:which_key_origin_winid) 58 | else 59 | let winwidth = &columns 60 | endif 61 | 62 | if maxlength > winwidth 63 | let layout.n_cols = 1 64 | else 65 | let layout.n_cols = winwidth / maxlength 66 | endif 67 | 68 | let layout.n_rows = layout.n_items / layout.n_cols + (fmod(layout.n_items,layout.n_cols) > 0 ? 1 : 0) 69 | let layout.col_width = winwidth / layout.n_cols 70 | let layout.win_dim = layout.n_rows 71 | 72 | let s:target_winwidth = winwidth 73 | endif 74 | 75 | if g:which_key_max_size 76 | let layout.win_dim = min([g:which_key_max_size, layout.win_dim]) 77 | endif 78 | 79 | return layout 80 | endfunction " }}} 81 | 82 | function! s:create_rows(layout, mappings) abort 83 | let l = a:layout 84 | let mappings = a:mappings 85 | 86 | let l.capacity = l.n_rows * l.n_cols 87 | let overcap = l.capacity - l.n_items 88 | let overh = l.n_cols - overcap 89 | let n_rows = l.n_rows - 1 90 | 91 | let rows = [] 92 | let row_max_size = 0 93 | let row = 0 94 | let col = 0 95 | 96 | " Separate leaves and dict keys depending on which_key_group_dicts_together 97 | if exists('g:which_key_group_dicts') && g:which_key_group_dicts != '' 98 | 99 | let leaf_keys = [] 100 | let dict_keys = [] 101 | 102 | for key in sort(filter(keys(mappings), 'v:val !=# "name"'), 'i') 103 | if type(mappings[key]) == s:TYPE.dict 104 | call add(dict_keys, key) 105 | else 106 | call add(leaf_keys, key) 107 | endif 108 | endfor 109 | 110 | " Decide what's shown first leaves or dicts 111 | if g:which_key_group_dicts ==? 'end' 112 | let smap = leaf_keys + dict_keys 113 | else 114 | let smap = dict_keys + leaf_keys 115 | endif 116 | 117 | else 118 | let smap = sort(filter(keys(mappings), 'v:val !=# "name"'), 'i') 119 | endif 120 | 121 | let displaynames = which_key#renderer#get_displaynames() 122 | if get(g:, 'which_key_align_by_seperator', 1) 123 | let key_max_len = 0 124 | for k in smap 125 | let key = get(displaynames, toupper(k), k) 126 | let width = strdisplaywidth(key) 127 | if width > key_max_len 128 | let key_max_len = width 129 | endif 130 | endfor 131 | endif 132 | 133 | for k in smap 134 | let key = get(displaynames, toupper(k), k) 135 | let desc = type(mappings[k]) == s:TYPE.dict ? get(mappings[k], 'name', '') : mappings[k][1] 136 | if desc ==# 'which_key_ignore' 137 | continue 138 | endif 139 | 140 | if get(g:, 'which_key_align_by_seperator', 1) 141 | let width = strdisplaywidth(key) 142 | if key_max_len > width 143 | let key = repeat(' ', key_max_len - width).key 144 | endif 145 | endif 146 | 147 | let item = s:combine(key, desc) 148 | 149 | let crow = get(rows, row, []) 150 | if empty(crow) 151 | call add(crow, "") 152 | call add(rows, crow) 153 | endif 154 | if col == l.n_cols-1 155 | let item = item 156 | else 157 | let item = item.repeat(' ', l.col_width - strdisplaywidth(item)) 158 | endif 159 | call add(crow, item) 160 | let row_max_size = max([row_max_size, strdisplaywidth(join(crow, ""))]) 161 | 162 | if !g:which_key_sort_horizontal 163 | if row >= n_rows - 1 164 | if overh > 0 && row < n_rows 165 | let overh -= 1 166 | let row += 1 167 | else 168 | let row = 0 169 | let col += 1 170 | endif 171 | else 172 | let row += 1 173 | endif 174 | else 175 | if col == l.n_cols - 1 176 | let row +=1 177 | let col = 0 178 | else 179 | let col += 1 180 | endif 181 | endif 182 | " This would cause bugs when using vim popup 183 | "silent execute "cnoremap ".substitute(k, "|", "", ""). " " . s:escape_keys(k) ."" 184 | endfor 185 | 186 | " Doesnt work in vertical 187 | if g:which_key_centered && !g:which_key_vertical 188 | let sign_column_size = exists('&signcolumn') && &signcolumn ==# 'yes' ? 2 : 0 189 | let line_number_size = &number ? len(string(line('$'))) : 0 190 | let centered_offset = sign_column_size + line_number_size 191 | 192 | let display_cap = g:which_key_floating_relative_win ? winwidth(g:which_key_origin_winid) : &columns 193 | let max_display_size = display_cap - centered_offset 194 | 195 | let left_padding_size = float2nr(floor((max_display_size - row_max_size) / 2)) 196 | 197 | for row in range(len(rows)) 198 | let rows[row][0] = repeat(' ', left_padding_size) 199 | endfor 200 | endif 201 | call map(rows, 'join(v:val, "")') 202 | 203 | return rows 204 | endfunction " }}} 205 | 206 | function! s:combine(key, desc) abort 207 | let item = join([a:key, g:which_key_sep, a:desc], ' ') 208 | if strdisplaywidth(item) > s:target_winwidth 209 | return item[ : s:target_winwidth - 4].'..' 210 | else 211 | return item 212 | endif 213 | endfunction 214 | 215 | function! s:escape_keys(inp) abort " {{{ 216 | " :h <> 217 | let l:ret = a:inp 218 | let l:ret = substitute(l:ret, '<', '', '') 219 | let l:ret = substitute(l:ret, '|', '', '') 220 | return l:ret 221 | endfunction " }}} 222 | -------------------------------------------------------------------------------- /autoload/which_key/window.vim: -------------------------------------------------------------------------------- 1 | let s:bufnr = -1 2 | let s:winnr = -1 3 | 4 | let s:use_popup = exists('*popup_create') && g:which_key_use_floating_win 5 | 6 | if !hlexists('WhichKeyFloating') 7 | hi default link WhichKeyFloating Pmenu 8 | endif 9 | 10 | function! s:hide_cursor() abort 11 | " Hides/restores cursor at the start/end of the guide, works in vim 12 | " Snippets from vim-game-code-break 13 | augroup which_key_cursor 14 | autocmd! 15 | execute 'autocmd BufLeave set t_ve=' . escape(&t_ve, '|') 16 | execute 'autocmd VimLeave set t_ve=' . escape(&t_ve, '|') 17 | augroup END 18 | setlocal t_ve= 19 | endfunction 20 | 21 | function! s:split_or_new() abort 22 | let position = g:which_key_position ==? 'topleft' ? 'topleft' : 'botright' 23 | 24 | if g:which_key_use_floating_win 25 | let qfbuf = &buftype ==# 'quickfix' 26 | let splitcmd = g:which_key_vertical ? '1vsplit' : '1split' 27 | noautocmd execute 'keepjumps' position splitcmd '+buffer'.s:bufnr 28 | cmapclear 29 | if qfbuf 30 | noautocmd execute bufnr('%').'bwipeout!' 31 | endif 32 | else 33 | let splitcmd = g:which_key_vertical ? '1vnew' : '1new' 34 | noautocmd execute 'keepjumps' position splitcmd 35 | let s:bufnr = bufnr('%') 36 | augroup which_key_leave 37 | autocmd! 38 | autocmd WinLeave call which_key#window#close() 39 | augroup END 40 | endif 41 | endfunction 42 | 43 | function! s:append_prompt(rows) abort 44 | let rows = a:rows 45 | let prompt = which_key#trigger().'- '.which_key#window#name() 46 | let rows += ['', prompt] 47 | return rows 48 | endfunction 49 | 50 | function! s:floating_win_col_offset() abort 51 | if g:which_key_disable_default_offset 52 | return 0 53 | else 54 | return (&number ? strlen(line('$')) : 0) + (exists('&signcolumn') && &signcolumn ==# 'yes' ? 2: 0) 55 | endif 56 | endfunction 57 | 58 | function! s:show_popup(rows) abort 59 | if !exists('s:popup_id') 60 | let s:popup_id = popup_create([], {'highlight': 'WhichKeyFloating'}) 61 | call popup_hide(s:popup_id) 62 | call setbufvar(winbufnr(s:popup_id), '&filetype', 'which_key') 63 | call win_execute(s:popup_id, 'setlocal nonumber nowrap') 64 | endif 65 | 66 | let rows = s:append_prompt(a:rows) 67 | let offset = s:floating_win_col_offset() 68 | if g:which_key_floating_relative_win 69 | let col = offset + win_screenpos(g:which_key_origin_winid)[1] + 1 70 | let maxwidth = winwidth(g:which_key_origin_winid) - offset 71 | else 72 | let col = offset + 1 73 | let maxwidth = &columns - offset 74 | endif 75 | call popup_move(s:popup_id, { 76 | \ 'col': col, 77 | \ 'line': &lines - len(rows) - &cmdheight, 78 | \ 'maxwidth': maxwidth, 79 | \ 'minwidth': maxwidth, 80 | \ }) 81 | call popup_settext(s:popup_id, rows) 82 | call popup_show(s:popup_id) 83 | endfunction 84 | 85 | function! s:apply_custom_floating_opts(opts) abort 86 | let opts = a:opts 87 | if exists('g:which_key_floating_opts') 88 | for [key, val] in items(g:which_key_floating_opts) 89 | if has_key(opts, key) 90 | let opts[key] = opts[key] + eval('0'.val) 91 | endif 92 | endfor 93 | endif 94 | return opts 95 | endfunction 96 | 97 | function! s:show_floating_win(rows, layout) abort 98 | let rows = s:append_prompt(a:rows) 99 | 100 | if !bufexists(s:bufnr) 101 | let s:bufnr = nvim_create_buf(v:false, v:false) 102 | endif 103 | 104 | call setbufvar(s:bufnr, '&modifiable', 1) 105 | call setbufvar(s:bufnr, '&filetype', 'which_key') 106 | 107 | silent call nvim_buf_set_lines(s:bufnr, 0, -1, 0, rows) 108 | 109 | let row_offset = &cmdheight + (&laststatus > 0 ? 1 : 0) 110 | 111 | let opts = { 112 | \ 'row': &lines - nvim_buf_line_count(s:bufnr) - row_offset, 113 | \ 'height': a:layout.win_dim + 2, 114 | \ } 115 | 116 | if !exists('s:origin_lnum_width') 117 | let s:origin_lnum_width = strlen(string(line('$'))) 118 | endif 119 | 120 | if g:which_key_floating_relative_win 121 | let opts.col = g:which_key_disable_default_offset ? 0 : s:origin_lnum_width 122 | let opts.width = winwidth(g:which_key_origin_winid) - opts.col 123 | let opts.win = g:which_key_origin_winid 124 | let opts.relative = 'win' 125 | else 126 | let opts.col = g:which_key_disable_default_offset ? 0 : s:origin_lnum_width 127 | let opts.width = &columns - opts.col 128 | let opts.relative = 'editor' 129 | endif 130 | 131 | let opts = s:apply_custom_floating_opts(opts) 132 | 133 | if !exists('s:floating_winid') 134 | silent let s:floating_winid = nvim_open_win(s:bufnr, v:true, opts) 135 | call s:hide_cursor() 136 | call setbufvar(s:bufnr, '&ft', 'which_key') 137 | call setwinvar(s:floating_winid, '&winhl', 'Normal:WhichKeyFloating') 138 | else 139 | call nvim_win_set_config(s:floating_winid, opts) 140 | endif 141 | endfunction 142 | 143 | function! s:show_old_win(rows, layout) abort 144 | if s:winnr == -1 145 | call s:open_split_win() 146 | endif 147 | 148 | let resize = g:which_key_vertical ? 'vertical resize' : 'resize' 149 | noautocmd execute resize a:layout.win_dim 150 | setlocal modifiable 151 | " Delete all lines in the buffer 152 | " Use black hole register to avoid affecting the normal registers. :h quote_ 153 | silent 1,$delete _ 154 | call setline(1, a:rows) 155 | setlocal nomodifiable 156 | endfunction 157 | 158 | function! which_key#window#show(runtime) abort 159 | let s:name = get(a:runtime, 'name', '') 160 | let [layout, rows] = which_key#renderer#prepare(a:runtime) 161 | 162 | if s:use_popup 163 | call s:show_popup(rows) 164 | elseif g:which_key_use_floating_win 165 | call s:show_floating_win(rows, layout) 166 | else 167 | call s:show_old_win(rows, layout) 168 | endif 169 | 170 | call which_key#wait_for_input() 171 | endfunction 172 | 173 | function! s:open_split_win() abort 174 | let s:pos = [winsaveview(), winnr(), winrestcmd()] 175 | call s:split_or_new() 176 | call s:hide_cursor() 177 | setlocal filetype=which_key 178 | let s:winnr = winnr() 179 | endfunction 180 | 181 | function! s:close_split_win() abort 182 | noautocmd execute s:winnr.'wincmd w' 183 | if winnr() == s:winnr 184 | close! 185 | execute s:pos[-1] 186 | noautocmd execute s:pos[1].'wincmd w' 187 | call winrestview(s:pos[0]) 188 | let s:winnr = -1 189 | endif 190 | endfunction 191 | 192 | function! which_key#window#close() abort 193 | if exists('s:origin_lnum_width') 194 | unlet s:origin_lnum_width 195 | endif 196 | 197 | if exists('s:floating_winid') 198 | call nvim_win_close(s:floating_winid, v:true) 199 | unlet s:floating_winid 200 | elseif exists('s:popup_id') 201 | call popup_close(s:popup_id) 202 | unlet s:popup_id 203 | else 204 | call s:close_split_win() 205 | endif 206 | 207 | if exists('*lightline#update') 208 | call lightline#update() 209 | endif 210 | endfunction 211 | 212 | function! which_key#window#name() abort 213 | return get(s:, 'name', '') 214 | endfunction 215 | -------------------------------------------------------------------------------- /doc/vim-which-key.txt: -------------------------------------------------------------------------------- 1 | *vim-which-key.txt* 2 | *vim-which-key* 3 | 4 | ============================================================================== 5 | CONTENTS *vim-which-key-contents* 6 | 1. Introduction........................................|vim-which-key-intro| 7 | 2. Usage...............................................|vim-which-key-usage| 8 | 3. Configuration......................................|vim-which-key-config| 9 | 3.1 Highlights..................................|vim-which-key-highlights| 10 | 4. Commands.........................................|vim-which-key-commands| 11 | 5. Functions.......................................|vim-which-key-functions| 12 | 13 | ============================================================================== 14 | INTRODUCTION *vim-which-key-intro* 15 | 16 | vim-which-key is vim port of {emacs-which-key}{1} that displays available 17 | keybindings in popup. 18 | 19 | {emacs-which-key}{1} started as a rewrite of {guide-key}{2}, very likely, 20 | {vim-which-key}{3} heavily rewrote {vim-leader-guide}{4} with a goal of going 21 | further in vim world. The features of vim-which-key have evolved a lot since 22 | then. 23 | 24 | {1} https://github.com/justbur/emacs-which-key 25 | 26 | {2} https://github.com/kai2nenobu/guide-key 27 | 28 | {3} https://github.com/liuchengxu/vim-which-key 29 | 30 | {4} https://github.com/hecal3/vim-leader-guide 31 | 32 | ============================================================================== 33 | USAGE *vim-which-key-usage* 34 | 35 | Let's say `SPC` is your leader key and you use it to trigger vim-which-key: 36 | > 37 | nnoremap :WhichKey '' 38 | < 39 | After pressing leader the guide buffer will pop up when there are no further 40 | keystrokes within `timeoutlen`. 41 | > 42 | " By default timeoutlen is 1000 ms 43 | set timeoutlen=500 44 | < 45 | Pressing other keys within `timeoutlen` will either complete the mapping or 46 | open a subgroup. 47 | 48 | Please note that no matter which mappings and menus you configure, your 49 | original leader mappings will remain unaffected. The key guide is an 50 | additional layer. It will only activate when you do not complete your input 51 | during the timeoutlen duration. 52 | 53 | Note that a description dictionary is not necessary, all mappings will be 54 | displayed regardless. 55 | > 56 | nnoremap :WhichKey '' 57 | < 58 | 59 | However, the dictionary configuration is necessary to provide group names or a 60 | description text. 61 | 62 | Example: 63 | > 64 | Define prefix dictionary 65 | let g:which_key_map = {} 66 | 67 | Second level dictionaries: 68 | 'name' is a special field. It will define the name of the group. 69 | leader-f is the "+file" group. 70 | Unnamed groups will show a default string 71 | let g:which_key_map.f = { 'name' : '+file' } 72 | 73 | nnoremap fs :update 74 | let g:which_key_map.f.s = ['update', 'save-file'] 75 | 76 | Provide commands and descriptions for existing mappings 77 | command, // mapping are supported 78 | nnoremap fd :e $MYVIMRC 79 | let g:which_key_map.f.d = ['e $MYVIMRC', 'open-vimrc'] 80 | 81 | let g:which_key_map.o = { 'name' : '+open' } 82 | 83 | nnoremap oq :copen 84 | let g:which_key_map.o.q = ['copen', 'open-quickfix'] 85 | 86 | nnoremap ol :lopen 87 | let g:which_key_map.o.l = ['lopen', 'open-locationlist'] 88 | 89 | Create new menus not based on existing mappings: 90 | let g:which_key_map.b = { 91 | \ 'name' : '+buffer' , 92 | \ '1' : ['b1' , 'buffer 1'] , 93 | \ '2' : ['b2' , 'buffer 2'] , 94 | \ 'd' : ['bd' , 'delete-buffer'] , 95 | \ 'f' : ['bfirst' , 'first-buffer'] , 96 | \ 'h' : ['Startify' , 'home-buffer'] , 97 | \ 'l' : ['blast' , 'last-buffer'] , 98 | \ 'n' : ['bnext' , 'next-buffer'] , 99 | \ 'p' : ['bprevious' , 'previous-buffer'] , 100 | \ '?' : ['Buffers' , 'fzf-buffer'] , 101 | \ } 102 | 103 | let g:which_key_map.l = { 104 | \ 'name' : '+lsp' , 105 | \ 'f' : ['LanguageClient#textDocument_formatting()' , 'formatting'] 106 | , 107 | \ 'h' : ['LanguageClient#textDocument_hover()' , 'hover'] 108 | , 109 | \ 'r' : ['LanguageClient#textDocument_references()' , 'references'] 110 | , 111 | \ 'R' : ['LanguageClient#textDocument_rename()' , 'rename'] 112 | , 113 | \ 's' : ['LanguageClient#textDocument_documentSymbol()' , 'document- 114 | symbol'] , 115 | \ 'S' : ['LanguageClient#workspace_symbol()' , 'workspace- 116 | symbol'] , 117 | \ 'g' : { 118 | \ 'name': '+goto', 119 | \ 'd' : ['LanguageClient#textDocument_definition()' , 120 | 'definition'] , 121 | \ 't' : ['LanguageClient#textDocument_typeDefinition()' , 'type- 122 | definition'] , 123 | \ 'i' : ['LanguageClient#textDocument_implementation()' , 124 | 'implementation'] , 125 | \ }, 126 | \ } 127 | < 128 | To make the guide pop up **Register the description dictionary for the prefix 129 | first** (assuming `Space` is your leader key): 130 | > 131 | call which_key#register('', "g:which_key_map") 132 | nnoremap :WhichKey '' 133 | vnoremap :WhichKeyVisual '' 134 | < 135 | It is possible to register a separate description dictionary for normal and visual modes: 136 | > 137 | call which_key#register('', "g:which_key_map", 'n') 138 | call which_key#register('', "g:which_key_map_visual", 'v') 139 | < 140 | It is possible to call the guide for keys other than `leader`: 141 | > 142 | nnoremap :WhichKey ',' 143 | vnoremap :WhichKeyVisual ',' 144 | < 145 | 146 | ============================================================================== 147 | CONFIGURATION *vim-which-key-config* 148 | 149 | *g:which_key_sep* 150 | Type: |String| 151 | Default: `'→'` 152 | 153 | Set the separator used between keys and descriptions. Change this setting to 154 | an ASCII character if your font does not show the default arrow. 155 | > 156 | let g:which_key_sep = '→' 157 | < 158 | *g:which_key_hspace* 159 | Type: |Number| 160 | Default: `5` 161 | 162 | Set the minimum horizontal space between the displayed columns. 163 | > 164 | let g:which_key_hspace = 5 165 | < 166 | 167 | *g:which_key_flatten* 168 | Type: |Number| 169 | Default: `1` 170 | 171 | Try to flatten out the keymappings if necessary. Possible value: `{0, 1}` . 172 | > 173 | let g:which_key_flatten = 1 174 | < 175 | *g:which_key_max_size* 176 | Type: |Number| 177 | Default: `0` 178 | 179 | Set the maximum height/width of the guide window. Set to 0 for unlimited size. 180 | > 181 | let g:which_key_max_size = 0 182 | < 183 | 184 | *g:which_key_vertical* 185 | Type: |Number| 186 | Default: `0` 187 | 188 | If set to a value other than 0, the guide buffer will pop up in vertical split 189 | window. Possible value: {0, 1}. 190 | > 191 | let g:which_key_vertical = 0 192 | < 193 | 194 | *g:which_key_position* 195 | Type: |String| 196 | Default: `botright` 197 | 198 | Set the direction from which the guide-buffer should pop up. Possible value: 199 | 'botright', 'topleft'. 200 | > 201 | let g:which_key_position = 'botright' 202 | < 203 | *g:which_key_sort_horizontal* 204 | Type: |Number| 205 | Default: `0` 206 | 207 | If set to a value other than 0, the entries will be sorted horizontal. 208 | Possible value: `{0, 1}` . 209 | > 210 | let g:which_key_sort_horizontal = 0 211 | < 212 | *g:which_key_align_by_seperator* 213 | Type: |Number| 214 | Default: `0` 215 | 216 | If set to a value other than 1, the entries of each column will be aligned by 217 | the initial character instead of the separator. Possible value: `{0, 1}` . 218 | > 219 | let g:which_key_align_by_seperator = 1 220 | < 221 | *g:which_key_use_floating_win* 222 | Type: |Number| 223 | Default: `0` 224 | 225 | Use neovim's floating or vim's popup window if available. The advantage is to 226 | put your current window layout untouched compared to splitting a new window. 227 | > 228 | let g:which_key_use_floating_win = 1 229 | < 230 | 231 | *g:which_key_floating_relative_win* 232 | Type: |Number| 233 | Default: `0` 234 | 235 | Make the vim popup or neovim floating_win relative to the current window. 236 | 237 | *g:which_key_floating_opts* 238 | Type: |Dict| 239 | Default: `Undefined` 240 | 241 | You could use this variable to adjust the floating window layout. The 242 | key is either `row`, `col`, `width` or `height`. The value is a String of `+[number]` 243 | or `-[number]` which means plus or minus `number` based on the default value 244 | calculated by vim-which-key. 245 | > 246 | let g:which_key_floating_opts = { 'row': '-1' } 247 | < 248 | *g:which_key_run_map_on_popup* 249 | Type: |Number| 250 | Default: `0` 251 | 252 | Mappings change during a vim-session, e.g., changing the filetype or buffer. 253 | Thus, when disabling this option the displayed mappings might get outdated. 254 | The delay when opening the guide is negligible. 255 | 256 | The update is almost instantaneous and will only run when the guide actually 257 | pops up. Apart from that the automatic update has no performance impact. 258 | 259 | Note: It is not recommended to disable this option. If set to 0, 260 | |vim-leader-guide| will not parse mappings when opening a guide buffer. Cached 261 | values will be used instead. 262 | 263 | Possible value: `{0, 1}` . 264 | > 265 | let g:which_key_run_map_on_popup = 1 266 | < 267 | *g:which_key_default_group_name* 268 | Type: |String| 269 | Default: `''` 270 | 271 | Allows to set a default group name. This name is shown as group description 272 | when the configuration dictionary has no corresponding entry. 273 | > 274 | let g:which_key_default_group_name = '' 275 | < 276 | *g:WhichKeyFormatFunc* 277 | 278 | Type: |Funcref| 279 | Default: `function('which_key#format')` 280 | 281 | WhichKeyFormatFunc is a Funcref called on every display item, which could be 282 | used to hide all trailing as well as all leading substring in the 283 | guide buffer. 284 | > 285 | let g:WhichKeyFormatFunc = function('which_key#format') 286 | < 287 | 288 | *g:which_key_timeout* 289 | Type: |Number| 290 | Default: `&timeoutlen` 291 | 292 | Similar to `timeoutlen`, but for vim-which-key particularly. By default it's 293 | same with option `timeoutlen`. 294 | > 295 | let g:which_key_timeout = 300 296 | < 297 | *g:which_key_exit* 298 | 299 | Type: |Number| | |String| | |List| 300 | Default: `["\", "\"]` 301 | 302 | Exit which-key when the key is triggered. For example, use `` to exit: > 303 | 304 | let g:which_key_exit = "\" 305 | < 306 | *g:which_key_ignore_invalid_key* 307 | Type: |Number| 308 | Default: `1` 309 | 310 | Ignore the invalid key and let you continue to input the valid ones. Otherwise 311 | an error message will appear in a popup and abort the guide buffer. 312 | Only applicable if |g:which_key_fallback_to_native_key| is 0, otherwise this 313 | setting is ignored. 314 | *g:which_key_ignore_outside_mappings* 315 | Type: |Number| 316 | Default: `0` 317 | 318 | Hides all mappings outside of the elements of the description dictionary. 319 | *'which_key_ignore'* 320 | If you wish to hide a specific mapping from the menu, set its description to 321 | `'which_key_ignore'`: 322 | > 323 | nnoremap 1 :1wincmd w 324 | let g:which_key_map.1 = 'which_key_ignore' 325 | < 326 | *g:which_key_fallback_to_native_key* 327 | Type: |Number| 328 | Default: `0` 329 | 330 | Executes native commands if keymap is not defined. 331 | For example, you can use `:WhichKey 'g'` and get `gg` work correct: 332 | > 333 | let g:which_key_fallback_to_native_key=1 334 | < 335 | 336 | *g:which_key_display_names* 337 | Type: |Dict| 338 | Default: `{ ' ': 'SPC', '': 'BS', '': 'TAB', '': 'TAB', }` 339 | 340 | Override the default symbols used for mappings. Dictionary keys should be all 341 | uppercase. 342 | > 343 | let g:which_key_display_names = {'': '↵', '': '⇆'} 344 | < 345 | 346 | *g:which_key_disable_default_offset* 347 | Type: |Number| 348 | Default: `0` 349 | 350 | The which-key floating window applies an offset according to the width of 351 | line number and signcolumn of the current window by default. Set this option 352 | to 1 if you do not want to have this offset. 353 | > 354 | let g:which_key_disable_default_offset = 1 355 | < 356 | 357 | *g:which_key_centered* 358 | Type: |Number| 359 | Default: `1` 360 | 361 | Make all of the keybindings appear in the middle instead of the left side of 362 | the which_key window 363 | Possible value: `{0, 1}` . 364 | > 365 | let g:which_key_centered = 0 366 | < 367 | 368 | *g:which_key_group_dicts* 369 | Type: |String| 370 | Default: `end` 371 | 372 | Separate key and dictionary bindings, placing all the dictionary items 373 | accordingly. Set this option to empty string if you want to sort all which-key 374 | items alphabetically in spite of their type. 375 | Possible value: `{'', 'start', 'end'}` . 376 | > 377 | let g:which_key_group_dicts = 'end' 378 | < 379 | 380 | ============================================================================== 381 | COMMANDS *vim-which-key-commands* 382 | 383 | :WhichKey[!] *:WhichKey* 384 | 385 | Open the guide window for the given prefix. This command, together with 386 | |:WhichKeyVisual| is the primary way of interacting with the plugin. 387 | 388 | Example: 389 | > 390 | nnoremap :WhichKey '' 391 | < 392 | 393 | Use |:WhichKey!| to open the guide window for a given dictionary directly. 394 | 395 | Example: 396 | > 397 | :WhichKey! g:which_key_dict 398 | < 399 | 400 | :[range]WhichKeyVisual[!] *:WhichKeyVisual* 401 | 402 | Same as |:WhichKey|, but opening the menu in visual mode. 403 | 404 | 405 | ------------------------------------------------------------------------------ 406 | |HIGHLIGHTS| *vim-which-key-highlights* 407 | 408 | > 409 | highlight default link WhichKey Function 410 | highlight default link WhichKeySeperator DiffAdded 411 | highlight default link WhichKeyGroup Keyword 412 | highlight default link WhichKeyDesc Identifier 413 | 414 | highlight default link WhichKeyFloating Pmenu 415 | < 416 | 417 | ============================================================================== 418 | FUNCTIONS *vim-which-key-functions* 419 | 420 | which_key#register({prefix}, {dict}[, {mode}]) *which_key#register()* 421 | 422 | Provide the guide with a {prefix} and its description dictionary. This 423 | dictionary {dict} is used to provide convenient names for mappings to 424 | display in the guide popup. Additionally it is used to provide group names. 425 | Optionally a separate description dictionary may be provided for each {mode}. 426 | 427 | Possible value: {prefix}: String, {dict}: String, {mode}: 'n'|'v' 428 | 429 | Example: 430 | > 431 | " register dictionary for the -prefix in both normal and visual mode 432 | call which_key#register(' ', "g:space_prefix_dict") 433 | 434 | " register dictionary for the ,-prefix in both modes 435 | call which_key#register(',', "g:comma_prefix_dict") 436 | 437 | " register dictionary for the -prefix in both normal and visual mode 438 | call which_key#register(' ', "g:space_prefix_dict", 'n') 439 | call which_key#register(' ', "g:space_prefix_dict", 'v') 440 | 441 | " register separate dictionaries for the -prefix for each mode 442 | call which_key#register(' ', "g:space_prefix_dict_normal", 'n') 443 | call which_key#register(' ', "g:space_prefix_dict_visual", 'v') 444 | < 445 | 446 | which_key#start({vis}, {bang}, {prefix}) *which_key#start()* 447 | 448 | This is the implementation for |:WhichKey| and |:WhichKey!|. 449 | 450 | which_key#parse_mappings() *which_key#parse_mappings()* 451 | 452 | Update the cache manually by calling this function. 453 | 454 | which_key#format({mapping}) *which_key#format()* 455 | 456 | This function is called on all for every display string in the guide buffer. 457 | By default |vim-which-key| will hide all trailing as well as all 458 | leading and `:`. 459 | 460 | 461 | vim:tw=78:ts=8:ft=help:norl: 462 | -------------------------------------------------------------------------------- /ftplugin/which_key.vim: -------------------------------------------------------------------------------- 1 | " No usual did_ftplugin check here as we NEED to run this always 2 | 3 | setlocal 4 | \ nonumber 5 | \ norelativenumber 6 | \ nolist 7 | \ nowrap 8 | \ nofoldenable 9 | \ nopaste 10 | \ nomodeline 11 | \ noswapfile 12 | \ nocursorline 13 | \ nocursorcolumn 14 | \ winfixwidth 15 | \ winfixheight 16 | \ colorcolumn= 17 | \ nobuflisted 18 | \ buftype=nofile 19 | \ bufhidden=unload 20 | \ nospell 21 | 22 | let &l:statusline = which_key#statusline() 23 | 24 | " Problematic on vim and some older neovim version, refer to #40. 25 | " Comment it for now. 26 | " setlocal listchars= 27 | 28 | hi WhichKeyTrigger ctermfg=232 ctermbg=178 guifg=#333300 guibg=#ffbb7d 29 | hi WhichKeyName cterm=bold ctermfg=171 ctermbg=239 gui=bold guifg=#d75fd7 guibg=#4e4e4e 30 | -------------------------------------------------------------------------------- /plugin/which_key.vim: -------------------------------------------------------------------------------- 1 | scriptencoding utf-8 2 | 3 | if exists('g:loaded_vim_which_key') 4 | finish 5 | endif 6 | let g:loaded_vim_which_key = 1 7 | 8 | let s:save_cpo = &cpo 9 | set cpo&vim 10 | 11 | let g:which_key_sep = get(g:, 'which_key_sep', '→') 12 | let g:which_key_hspace = get(g:, 'which_key_hspace', 5) 13 | let g:which_key_flatten = get(g:, 'which_key_flatten', 1) 14 | let g:which_key_timeout = get(g:, 'which_key_timeout', &timeoutlen) 15 | let g:which_key_max_size = get(g:, 'which_key_max_size', 0) 16 | let g:which_key_vertical = get(g:, 'which_key_vertical', 0) 17 | let g:which_key_position = get(g:, 'which_key_position', 'botright') 18 | let g:which_key_centered = get(g:, 'which_key_centered', 1) 19 | let g:which_key_group_dicts = get(g:, 'which_key_group_dicts', 'end') 20 | let g:which_key_sort_horizontal = get(g:, 'which_key_sort_horizontal', 0) 21 | let g:which_key_run_map_on_popup = get(g:, 'which_key_run_map_on_popup', 1) 22 | let g:which_key_align_by_seperator = get(g:, 'which_key_align_by_seperator', 1) 23 | let g:which_key_ignore_invalid_key = get(g:, 'which_key_ignore_invalid_key', 1) 24 | let g:which_key_ignore_outside_mappings = get(g:, 'which_key_ignore_outside_mappings', 0) 25 | let g:which_key_fallback_to_native_key = get(g:, 'which_key_fallback_to_native_key', 0) 26 | let g:which_key_default_group_name = get(g:, 'which_key_default_group_name', '+prefix') 27 | let g:which_key_use_floating_win = (exists('*nvim_open_win') || exists('*popup_create')) && get(g:, 'which_key_use_floating_win', 1) 28 | let g:which_key_floating_relative_win = get(g:, 'which_key_floating_relative_win', 0) 29 | let g:which_key_disable_default_offset = get(g:, 'which_key_disable_default_offset', 0) 30 | let g:WhichKeyFormatFunc = get(g:, 'WhichKeyFormatFunc', function('which_key#format')) 31 | 32 | command! -bang -nargs=1 WhichKey call which_key#start(0, 0, ) 33 | command! -bang -nargs=1 -range WhichKeyVisual call which_key#start(1, 0, ) 34 | 35 | let &cpo = s:save_cpo 36 | unlet s:save_cpo 37 | -------------------------------------------------------------------------------- /syntax/which_key.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | let b:current_syntax = 'which_key' 5 | 6 | let s:sep = which_key#get_sep() 7 | 8 | execute 'syntax match WhichKeySeperator' '/'.s:sep.'/' 'contained' 9 | execute 'syntax match WhichKey' '/\(^\s*\|\s\{2,}\)\S.\{-}'.s:sep.'/' 'contains=WhichKeySeperator' 10 | syntax match WhichKeyGroup / +[0-9A-Za-z_/-]*/ 11 | syntax region WhichKeyDesc start="^" end="$" contains=WhichKey, WhichKeyGroup, WhichKeySeperator 12 | 13 | highlight default link WhichKey Function 14 | highlight default link WhichKeySeperator DiffAdded 15 | highlight default link WhichKeyGroup Keyword 16 | highlight default link WhichKeyDesc Identifier 17 | --------------------------------------------------------------------------------