Home

Awesome

What is it ?

Vim has got several whichkey like plugins for keymap hints and I've tried each of them one by one and found them always lacking in some way. As a result, I've made the decision to create my own plugin, which is similar to whichkey but with some exciting enhancements.

Features

Installation

Plug 'skywind3000/vim-quickui'
Plug 'skywind3000/vim-navigator'

vim-quickui is required, because it provides unified API to access popup in Vim and floatwin in NVim.

Quick start

Put this in you .vimrc:

" initialize global keymap and declare prefix key
let g:navigator = {'prefix':'<tab><tab>'}

" buffer management
let g:navigator.b = {
            \ 'name' : '+buffer' ,
            \ '1' : [':b1'        , 'buffer 1']        ,
            \ '2' : [':b2'        , 'buffer 2']        ,
            \ 'd' : [':bd'        , 'delete-buffer']   ,
            \ 'f' : [':bfirst'    , 'first-buffer']    ,
            \ 'h' : [':Startify'  , 'home-buffer']     ,
            \ 'l' : [':blast'     , 'last-buffer']     ,
            \ 'n' : [':bnext'     , 'next-buffer']     ,
            \ 'p' : [':bprevious' , 'previous-buffer'] ,
            \ '?' : [':Leaderf buffer'   , 'fzf-buffer']      ,
            \ }

" tab management
let g:navigator.t = {
            \ 'name': '+tab',
            \ '1' : ['<key>1gt', 'tab-1'],
            \ '2' : ['<key>2gt', 'tab-2'],
            \ '3' : ['<key>3gt', 'tab-3'],
            \ 'c' : [':tabnew', 'new-tab'],
            \ 'q' : [':tabclose', 'close-current-tab'],
            \ 'n' : [':tabnext', 'next-tab'],
            \ 'p' : [':tabprev', 'previous-tab'],
            \ 'o' : [':tabonly', 'close-all-other-tabs'],
            \ }

" Easymotion
let g:navigator.m = ['<plug>(easymotion-bd-w)', 'easy-motion-bd-w']
let g:navigator.n = ['<plug>(easymotion-s)', 'easy-motion-s']

By default, I prefer not to use leader key timeout method to trigger Navigator. Let's assign a dedicated key, hit <tab> twice:

nnoremap <silent><tab><tab> :Navigator g:navigator<cr>

Command :Navigator will find the following variable g:navigator and read its keymap configuration.

Restart your vim and hit <tab> twice, you may see the Navigator window in the screen bottom:

All the items defined previously will be listed in the navigator window, you can press a key to execute its command, enter a sub-group, or press ESC to quit without performing any action.

Commands

Default command:

:Navigator {varname}

This command will open navigator window and read keymap from {varname}. So, if you have your navigator keymap in the variable g:my_keymap, the command :Navigator g:my_keymap will read keymap from it.

Visual mode command:

:NavigatorVisual {varname}

Same as :Navigator command but dedicated for visual mode, and can be used with vmap or vnoremap:

vnoremap <silent><tab><tab> :NavigatorVisual g:keymap_visual<cr>

The {varname} in both :Navigator and :NavigatorVisual is a standard VimScript variable name with a slight extension: if the {varname} starts with a star and a colon (*:), navigator will search for the variable name in both the global scope (g:) and the buffer local scope (b:).

Buffer local keymaps

Just define a b:navigator variable for certain buffer:

let g:_navigator_cpp = {...}
let g:_navigator_python = {...}

autocmd FileType c,cpp let b:navigator = g:_navigator_cpp
autocmd FileType python let b:navigator = b:_navigator_python

And run :Navigator command and replace the original varname g:navigator with *:navigator

nnoremap <silent><tab><tab> :Navigator *:navigator<cr>

Different from the previous command, here we have a *: before the variable name. After that :Navigator will find variables named navigator in both global scope and buffer local scope (g:navigator and b:navigator) and evaluate them, then merge the result into one dictionary.

Keybinding

Once Navigator window is open,

it accepts these keybinding:

KeyAction
<c-j>next page
<c-k>previous page
<PageDown>next page
<PageUp>previous page
<bs>return to parent level
<esc>exit navigator

If there are too many items cannot be displayed in one window, they will be splited into different pages. From the left bottom corner, you will see:

(page 1/1)

It represents the total page number and current page index.

Configuration

Initialize an empty keymap configuration:

let g:keymap = {'prefix': "<space>"}

You can describe the prefix keys like this, but it is optional.

After that you can defined an item:

let g:keymap.o = [':tabonly', 'close-other-tabpage']

Each item is a list of command and description, where the first element represents the command. For convenience, the command has several forms:

PrefixMeaningSample
:Ex command:wincmd p
<key>Key sequence<key><c-w>p (this will feed <c-w>p to vim)
<KEY>Key sequence<key><c-w>p (feed <c-w>p without remap)
^[a-zA-Z0-9_#]\+(.*)$Function callMyFunction()
<plug>Plug trigger<plug>(easymotion-bd-w)

A group is a subset to hold items and child groups:

let g:keymap.w = {
    \ 'name': '+window',
	\ 'p': [':wincmd p', 'jump-previous-window'],
	\ 'h': [':wincmd h', 'jump-left-window'],
	\ 'j': [':wincmd j', 'jump-belowing-window'],
	\ 'k': [':wincmd k', 'jump-aboving-window'],
	\ 'l': [':wincmd l', 'jump-right-window'],
	\ 'x': {
	\       'name': '+management',
	\       'o': ['wincmd o', 'close-other-windows'],
	\   },
	\ }

In the "Quick start" section, we defined a g:navigator variable to store keymaps and paired with the command:

:Navigator g:navigator

Here we use another variable g:keymap so its command will be:

:Navigator g:keymap

Visual mode

Setup a keymap for visual mode only and use it with :NavigatorVisual:

let g:keymap_visual = {'prefix':'<tab><tab>'}
let g:keymap_visual['='] = ['<key>=', 'indent-block']
let g:keymap_visual.q = ['<key>gq', 'format-block']

vnoremap <silent><tab><tab> :NavigatorVisual *:keymap_visual<cr>

When you hit <tab><tab>q in visual mode, the gq will be feed into vim and the selected text will be formatted.

Runtime evaluation

Configuration can be generated at runtime by providing a function name like this:

function! GenerateSubKeymap() abort
    return {
        \ 'name': '+coding',
        \ 'a': [':echo 1', 'command-a'],
        \ 'b': [':echo 2', 'command-b'],
        \ 'c': [':echo 3', 'command-c'],                
        \ }
endfunc

let keymap.c = '%{GenerateSubKeymap()}'

The function will be called each time before opening Navigator window, it should returns the latest configuration.

This allows you generate context sensitive keymaps.

Customize

Available options:

GlobalLocalDefault ValueDescription
g:navigator_icon_separatoricon_separator'=>'separator style, can be set to an empty string
g:navigator_bracketbracket0set to 1 to display brackets around key character
g:navigator_spacingspacing3horizontal spaces between items
g:navigator_paddingpadding[2,0,2,0]left, top, right, bottom padding to the window edge
g:navigator_verticalvertical0set to 1 to use a vertical split window
g:navigator_positionposition'botright'split position
g:navigator_fallbackfallback0set to 1 to allow fallback to native keymap if key does not exist
g:navigator_max_heightmax_height20maximum horizontal window height
g:navigator_min_heightmin_height5minimal horizontal window height
g:navigator_max_widthmax_width60maxmum vertical window width
g:navigator_max_widthmax_width20minimal vertical window width
g:navigator_popuppopup0set to 1 to use popup or floatwin if available
g:navigator_popup_positionpopup_position'bottom'can be set to 'bottom', 'top', and 'center'
g:navigator_popup_widthpopup_width'60%'centered popup window width
g:navigator_popup_heightpopup_height'40%'centered popup window height
g:navigator_popup_borderpopup_border1centered popup window border, set to 0 for borderless window, and 2-4 for unicode border

Global options can be directly defined like:

let g:navigator_icon_separator = '→'

Local options have higher priority than the global options with same name. They can be defined as a config member of your keymap dictionary variable:

let g:my_keymap.config = {
    \ 'icon_separator': '→',
    \ 'popup': 1,
    \ 'popup_position': 'center',
    \ 'popup_width': 40,
    \ 'popup_height': 5,
    \ }

The local settings defined above will override the corresponding global settings when you are using:

:Navigator g:my_keymap

There can be multiple navigator keymaps existing simultaneously with different window sizes and positions.

TODO

Credit