├── README.md ├── autoload └── sidemenu.vim └── plugin └── sidemenu.vim /README.md: -------------------------------------------------------------------------------- 1 | # vim-sidemenu 2 | 3 | > A small side-menu useful for Vim terminal users 4 | 5 | ## Install 6 | 7 | Use your favorite package-manager. 8 | 9 | ## Setup 10 | 11 | ```vim 12 | nmap l (sidemenu) 13 | xmap l (sidemenu-visual) 14 | ``` 15 | 16 | ## Credits & Contribution 17 | 18 | Thanks to [junegunn/vim-peekaboo](https://github.com/junegunn/vim-peekaboo) 19 | for the original code I used to achieve the behavior I was looking for. 20 | 21 | ## License 22 | 23 | MIT 24 | -------------------------------------------------------------------------------- /autoload/sidemenu.vim: -------------------------------------------------------------------------------- 1 | " Sidemenu.vim 2 | " The MIT License (MIT) 3 | " 4 | " Maintainer: Rafael Bodill https://github.com/rafi/vim-sidemenu 5 | " Based on: Junegunn Choi https://github.com/junegunn/vim-peekaboo 6 | 7 | let s:cpo_save = &cpoptions 8 | set cpoptions&vim 9 | 10 | " Private variables 11 | " --- 12 | let s:RETURN = 13 13 | let s:ESCAPE = 27 14 | 15 | let s:items = {} 16 | let s:buf_sidemenu = 0 17 | let s:scroll = { 18 | \ "\": "\", "\": "\", 19 | \ "\": "\", "\": "\", 20 | \ "\": "\", "\": "\", 21 | \ "\": "\", "\": "\", 22 | \ "\": "\", "\": "\" 23 | \ } 24 | 25 | " Default options 26 | let s:default_window = 'vertical botright 30new' 27 | let s:default_compact = 0 28 | let s:default_open_immediately = 0 29 | let s:default_verbosity = 1 30 | 31 | " Public function 32 | " --- 33 | 34 | " Open sidemenu 35 | function! sidemenu#open(visualmode) abort 36 | if s:is_open() 37 | call s:close() 38 | endif 39 | 40 | let verbose = get(g:, 'sidemenu_verbosity', s:default_verbosity) 41 | let open_immediately = 42 | \ get(g:, 'sidemenu_open_immediately', s:default_open_immediately) 43 | 44 | let positions = { 'current': s:getpos() } 45 | call s:open() 46 | let positions.sidemenu = s:getpos() 47 | 48 | let inplace = positions.current.tab == positions.sidemenu.tab && 49 | \ positions.current.win == positions.sidemenu.win 50 | let visible = ! inplace && s:is_visible(positions.current) 51 | 52 | call s:gv(a:visualmode, visible) 53 | 54 | let [stl, lst] = [&showtabline, &laststatus] 55 | let zoom = 0 56 | let keys = [] 57 | try 58 | " Start event-loop to capture keystrokes 59 | while 1 60 | let char = getchar() 61 | let key = tolower(nr2char(char)) 62 | let scroll_key = get(s:scroll, char, get(s:scroll, key, '')) 63 | 64 | " Has user pressed or ? 65 | if char == s:RETURN || char == s:ESCAPE 66 | if zoom 67 | tab close 68 | endif 69 | break 70 | 71 | " Is user scrolling window? 72 | elseif ! empty(scroll_key) 73 | execute 'normal!' scroll_key 74 | call s:gv(a:visualmode, visible) 75 | 76 | " Is user trying to zoom with ? 77 | elseif key ==# ' ' 78 | if zoom 79 | tab close 80 | let [&showtabline, &laststatus] = [stl, lst] 81 | call s:gv(a:visualmode, visible) 82 | else 83 | tab split 84 | set showtabline=0 laststatus=0 85 | endif 86 | let zoom = ! zoom 87 | redraw 88 | else 89 | " Let's look for matches... 90 | let command = join(keys, '') . key 91 | let match = 0 92 | for [item_key, item_value] in items(s:items) 93 | if stridx(item_key, command) == 0 94 | let match = match + 1 95 | endif 96 | endfor 97 | 98 | if match == 0 99 | " No match found. Use the last key that was pressed, but reset keys 100 | " all previous keys from stack. Intentionally disabling ability to 101 | " immediately run a wrong command. 102 | let keys = [] 103 | let command = key 104 | setlocal nocursorline 105 | endif 106 | 107 | call add(keys, key) 108 | 109 | if match == 1 && open_immediately 110 | break 111 | endif 112 | 113 | syntax clear sidemenuSelected 114 | execute 'syntax match sidemenuSelected "\v^ '.command.'"' 115 | \ .' contained contains=sidemenuSelectedSpace' 116 | 117 | if has_key(s:items, command) 118 | " There is a candidate for an exact match 119 | let line = s:items[command][0] 120 | execute line 121 | setlocal cursorline 122 | endif 123 | call s:gv(a:visualmode, visible) 124 | 125 | if verbose && match == 1 126 | echo join(s:items[command][3:], ': ') 127 | endif 128 | endif 129 | endwhile 130 | 131 | " - Make sure that we're back to the original tab/window/buffer 132 | " - e.g. g:sidemenu_window = 'tabnew' / 'enew' 133 | if inplace 134 | noautocmd execute positions.current.win.'wincmd w' 135 | noautocmd execute 'buf' positions.current.buf 136 | else 137 | noautocmd execute 'tabnext' positions.current.tab 138 | call s:close() 139 | noautocmd execute positions.current.win.'wincmd w' 140 | endif 141 | if a:visualmode 142 | normal! gv 143 | endif 144 | catch /^Vim:Interrupt$/ 145 | return 146 | finally 147 | let [&showtabline, &laststatus] = [stl, lst] 148 | call s:close() 149 | redraw 150 | endtry 151 | 152 | if char == s:RETURN || (open_immediately && char != s:ESCAPE) 153 | if len(keys) > 0 154 | let exe = s:items[join(keys, '')][2] 155 | if verbose | echomsg 'Executing: ' exe | endif 156 | execute exe 157 | endif 158 | endif 159 | endfunction 160 | 161 | " Private functions 162 | " --- 163 | 164 | " Checks if sidemenu buffer is open 165 | function! s:is_open() abort 166 | return s:buf_sidemenu 167 | endfunction 168 | 169 | " Appends macro list for the specified group to sidemenu window 170 | function! s:append_group(title, items) abort 171 | let compact = get(g:, 'sidemenu_compact', s:default_compact) 172 | if ! compact | call append(line('$'), a:title.':') | endif 173 | for r in a:items 174 | try 175 | if empty(r[0]) 176 | continue 177 | endif 178 | let s:items[printf('%s', r[0])] = [line('$')] + r 179 | call append(line('$'), printf(' %s: %s', r[0], r[2])) 180 | catch 181 | endtry 182 | endfor 183 | if ! compact | call append(line('$'), '') | endif 184 | endfunction 185 | 186 | " Closes sidemenu buffer 187 | function! s:close() abort 188 | silent! execute 'bd' s:buf_sidemenu 189 | let s:buf_sidemenu = 0 190 | execute s:winrestcmd 191 | endfunction 192 | 193 | " Opens sidemenu window 194 | function! s:open() abort 195 | let s:winrestcmd = winrestcmd() 196 | execute get(g:, 'sidemenu_window', s:default_window) 197 | let s:buf_sidemenu = bufnr('') 198 | setlocal nonumber buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap 199 | \ modifiable statusline=>\ Main-menu nocursorline nofoldenable 200 | if exists('&relativenumber') 201 | setlocal norelativenumber 202 | endif 203 | 204 | setfiletype sidemenu 205 | 206 | syntax clear 207 | syntax match sidemenuTitle /^[A-Za-z-_ &()#!]*/ contained 208 | syntax match sidemenuTitleColon /^[A-Za-z-_ &()#!]*:/ contains=sidemenuTitle 209 | syntax match sidemenuReg /^ [^: ]\{1,3}/ contained contains=sidemenuSelected 210 | syntax match sidemenuRegColon /^ [^: ]\{1,3}:/ contains=sidemenuReg 211 | syntax match sidemenuSelectedSpace /^ / contained 212 | highlight default link sidemenuTitle Title 213 | highlight default link sidemenuTitleColon NonText 214 | highlight default link sidemenuReg Label 215 | highlight default link sidemenuRegColon NonText 216 | highlight default link sidemenuSelected SpellRare 217 | 218 | augroup sidemenu 219 | autocmd! 220 | autocmd CursorMoved bd 221 | augroup END 222 | 223 | let s:items = {} 224 | for group in g:sidemenu 225 | call s:append_group(group['title'], group['children']) 226 | endfor 227 | silent! normal! "_dd 228 | endfunction 229 | 230 | " Checks if the buffer for the position is visible on screen 231 | function! s:is_visible(pos) abort 232 | return a:pos.tab == tabpagenr() && bufwinnr(a:pos.buf) != -1 233 | endfunction 234 | 235 | " Triggers gv to keep visual highlight on 236 | function! s:gv(visualmode, visible) abort 237 | if a:visualmode && a:visible 238 | wincmd p 239 | normal! gv 240 | redraw 241 | wincmd p 242 | else 243 | redraw 244 | endif 245 | endfunction 246 | 247 | " Returns the position of the current buffer as a dictionary 248 | function! s:getpos() abort 249 | return {'tab': tabpagenr(), 'buf': bufnr(''), 'win': winnr(), 'cnt': winnr('$')} 250 | endfunction 251 | 252 | let &cpoptions = s:cpo_save 253 | unlet s:cpo_save 254 | 255 | " vim: set ts=2 sw=2 tw=80 noet : 256 | -------------------------------------------------------------------------------- /plugin/sidemenu.vim: -------------------------------------------------------------------------------- 1 | " Sidemenu.vim 2 | " The MIT License (MIT) 3 | " 4 | " Maintainer: Rafael Bodill https://github.com/rafi/vim-sidemenu 5 | " Based on: Junegunn Choi https://github.com/junegunn/vim-peekaboo 6 | 7 | nnoremap (sidemenu) :call sidemenu#open(0) 8 | xnoremap (sidemenu-visual) :call sidemenu#open(1) 9 | 10 | let g:sidemenu = [ 11 | \ { 'title': 'General & Plugins', 12 | \ 'children': [ 13 | \ ['ga', 'call dein#update()', 'Plugins update', 'Update all plugins'], 14 | \ ['gb', 'lua require"plugins.telescope".pickers.plugin_directories()', 'Plugins list'], 15 | \ ['gc', 'Telescope session-lens search_session', 'Load session'], 16 | \ ['gd', 'Telescope search_history', 'Search history'], 17 | \ ['gf', 'Telescope command_history', 'Command history'], 18 | \ ['gg', 'Telescope help_tags', 'Vim help'], 19 | \ ]}, 20 | \ { 'title': 'Project', 21 | \ 'children': [ 22 | \ ['po', 'SymbolsOutline', 'Symbols outline'], 23 | \ ['ps', 'Gina status -s', 'Git status'], 24 | \ ['pm', 'SignatureListGlobalMarks', 'Bookmarks'], 25 | \ ['pu', 'UndotreeToggle', 'Undo tree'], 26 | \ ]}, 27 | \ { 'title': 'Files', 28 | \ 'children': [ 29 | \ ['fe', 'Fern -toggle -drawer .', 'File explorer'], 30 | \ ['fg', 'Telescope live_grep', 'Find in files…'], 31 | \ ['ff', 'Telescope find_files', 'Find files'], 32 | \ ['fb', 'Telescope buffers', 'Buffers'], 33 | \ ['fo', 'Telescope oldfiles', 'Most Recent'], 34 | \ ]}, 35 | \ { 'title': 'Tools', 36 | \ 'children': [ 37 | \ ['a', 'Neogit', 'Neogit'], 38 | \ ['b', 'DiffviewOpen', 'Diffview'], 39 | \ ['c', 'Gina log --graph --all', 'Git log'], 40 | \ ['d', 'Gina status -s', 'Git status'], 41 | \ ['e', 'Gina changes', 'Git changed'], 42 | \ ['f', 'lua vim.lsp.diagnostic.set_loclist()', 'Diagnostics'], 43 | \ ['g', 'Telescope registers', 'Paste from registers…'], 44 | \ ['h', 'ZenMode', 'Distraction-free'], 45 | \ ['i', 'ThesaurusQueryReplaceCurrentWord', 'Thesaurus query word…'], 46 | \ ]}, 47 | \ { 'title': 'System', 48 | \ 'children': [ 49 | \ ['1', 'checkhealth', 'Check health'], 50 | \ ['2', 'echo dein#get_updates_log()', 'View updates log'], 51 | \ ['3', 'echo dein#get_log()', 'View dein log'], 52 | \ ]}, 53 | \ ] 54 | 55 | " vim: set ts=2 sw=2 tw=80 noet : 56 | --------------------------------------------------------------------------------