├── README.md └── plugin └── follow-my-lead.vim /README.md: -------------------------------------------------------------------------------- 1 | Follow My Lead 2 | ============== 3 | Vim plugin for showing all your `` mappings in a readable table including the descriptions. 4 | 5 | Installation 6 | ------------ 7 | Copy `plugin/follow-my-lead.vim` to `~/.vim/plugin` 8 | 9 | Or add a GitHub repository entry if you are using a Plugin Manager such as `Vundle`: 10 | 11 | ```vim 12 | Plugin 'ktonga/vim-follow-my-lead' 13 | ``` 14 | 15 | Features 16 | -------- 17 | * List `` mappings defined by the user and the plugins 18 | * By default only mappings defined in `.vimrc` are listed. It honours `$MYVIMRC` variable 19 | * Mappings from all scripts sourced by Vim can be listed if specified by configuration (see Options) 20 | * If the line previous to the mapping is a comment it will be used as the description 21 | * If no comment is available the **rhs** of the mapping is used as description 22 | * Mappings are shown in a table, grouped by **source**, with the following columns 23 | * Mode: which mode the mapping applies to 24 | * LHS: left hand side of the mapping (without ``) 25 | * Description: The mapping comment if present. The **rhs** otherwise 26 | 27 | Screenshots 28 | ----------- 29 | ![FML screenshot](http://drive.google.com/uc?export=download&id=0BxOk4ZkCuP9uaHZyRGJwbllTVGc) 30 | 31 | Shortcuts 32 | --------- 33 | * `fml`: Default mapping for triggering the plugin. It shows the mapping in a new window 34 | * `q`: Closes the mappings window 35 | 36 | Options 37 | ------- 38 | * `g:fml_all_sources`: if `1` all sources are used, if `0` just `$MYVIMRC` is used. Default `0` 39 | * _More options coming soon_ 40 | 41 | License 42 | ------- 43 | 44 | Copyright (C) 2015 Gaston Tonietti 45 | 46 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 47 | 48 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 49 | 50 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 51 | -------------------------------------------------------------------------------- /plugin/follow-my-lead.vim: -------------------------------------------------------------------------------- 1 | if(!exists("g:fml_all_sources")) 2 | let g:fml_all_sources=0 3 | endif 4 | 5 | let s:fml_leader = exists('mapleader') ? mapleader : '\' 6 | if(s:fml_leader == ' ') 7 | let s:fml_leader = '' 8 | endif 9 | let s:fml_escaped_leader = escape(s:fml_leader, '\') 10 | 11 | function! FMLGetLeaderMappingsBySource() 12 | let all_maps = "" 13 | let old_lang = v:lang 14 | lang message C 15 | redir => all_maps 16 | silent execute "verbose map" 17 | redir END 18 | silent execute "lang message" old_lang 19 | let lines = split(all_maps, "\n") 20 | let linesLen = len(lines) 21 | let mappings_by_source = {} 22 | let idx = 0 23 | while idx < linesLen 24 | let mapping = lines[idx] 25 | if(mapping =~? '\V\^\(\a\| \)\s\+' . s:fml_escaped_leader . '\S') 26 | let source = split(split(lines[idx + 1], 'from ')[1], ' line')[0] 27 | let is_vimrc = FMLIsVimrc(source) 28 | if(g:fml_all_sources || is_vimrc) 29 | let mappings = get(mappings_by_source, source, []) 30 | let mappings_by_source[source] = add(mappings, FMLParseMapping(mapping)) 31 | endif 32 | endif 33 | let idx = idx + 2 34 | endwhile 35 | let with_desc = map(mappings_by_source, 'FMLAddDescription(v:key, reverse(v:val))') 36 | if(exists("s:vimrc_glob")) 37 | let vimrc_val = remove(with_desc, s:vimrc_glob) 38 | let vimrc_first = [{ 'source': s:vimrc_glob, 'mappings': vimrc_val }] 39 | else 40 | let vimrc_first = [] 41 | endif 42 | let vimrc_first += values(map(with_desc, '{ "source": v:key, "mappings": v:val }')) 43 | return vimrc_first 44 | endfunction 45 | 46 | function! FMLIsVimrc(src_glob) 47 | if(exists("s:vimrc_glob")) 48 | return s:vimrc_glob == a:src_glob 49 | elseif(glob(a:src_glob) == $MYVIMRC) 50 | let s:vimrc_glob = a:src_glob 51 | return 1 52 | else 53 | return 0 54 | endif 55 | endfunction 56 | 57 | function! FMLParseMapping(mapping_line) 58 | let pattern = '\V\^\(\a\| \)\s\+' . s:fml_escaped_leader . '\(\S\+\)\s\+\%(*\| \)\%(@\| \)\(\.\+\)' 59 | let groups = matchlist(a:mapping_line, pattern) 60 | return { 'id': substitute(groups[1] . '_' . groups[2], ' ', '', ''), 'mode': groups[1], 'lhs': groups[2], 'rhs': groups[3] } 61 | endfunction 62 | 63 | function! FMLAddDescription(src, mappings) 64 | let src_lines = readfile(glob(a:src)) 65 | let lines_with_index = map(deepcopy(src_lines), '[v:key, v:val]') 66 | let comments_by_id = {} 67 | for [idx, line] in lines_with_index 68 | let lhs = matchlist(line, '\c\m^\(\a\?\)\a*map.*\(\S\+\)') 69 | if(!empty(lhs)) 70 | let prev_line = src_lines[idx - 1] 71 | let comment = matchlist(prev_line, '^"\s*\(.*\)') 72 | if(!empty(comment)) 73 | let comments_by_id[lhs[1] . '_' . lhs[2]] = comment[1] 74 | endif 75 | endif 76 | endfor 77 | return map(a:mappings, 'has_key(comments_by_id, v:val.id) ? extend(v:val, {"desc": comments_by_id[v:val.id]}) : v:val') 78 | endfunction 79 | 80 | function! FMLFormatMappings(source, mappings) 81 | let mapping_width = FMLCalcMappingWidth(a:mappings) 82 | let formatted = map(a:mappings, 'printf(" %1s | %-' . mapping_width . 's | %s", v:val.mode, v:val.lhs, get(v:val, "desc", v:val.rhs))') 83 | return a:source. "\n" . repeat('-', strchars(a:source)) . "\n\n" . join(formatted, "\n") 84 | endfunction 85 | 86 | function! FMLCalcMappingWidth(mappings) 87 | let mapping_width = 1 88 | for val in a:mappings 89 | let mapping_width = max([mapping_width, strlen(val.lhs)]) 90 | endfor 91 | return mapping_width 92 | endfunction 93 | 94 | function! FMLShow() 95 | let formattedMappings = join(map(FMLGetLeaderMappingsBySource(), 'FMLFormatMappings(v:val.source, v:val.mappings)'), "\n\n") 96 | 97 | if(exists('s:fml_bufnr') && bufwinnr(s:fml_bufnr) != -1) 98 | execute bufwinnr(s:fml_bufnr) . 'wincmd w' 99 | else 100 | new 101 | " Make it an unlisted scratch buffer 102 | setlocal buftype=nofile 103 | setlocal bufhidden=hide 104 | setlocal noswapfile 105 | setlocal nobuflisted 106 | 107 | nnoremap q :bdelete 108 | silent file [Follow My Lead] 109 | let s:fml_bufnr = bufnr('%') 110 | endif 111 | 112 | normal! ggdG 113 | put =formattedMappings 114 | normal! gg 115 | 116 | endfunction 117 | 118 | nnoremap (FollowMyLead) :call FMLShow() 119 | 120 | " Open Leader mappings in new window 121 | nmap fml (FollowMyLead) 122 | --------------------------------------------------------------------------------