├── .gitignore ├── .travis.yml ├── README.md ├── autoload └── lookup.vim └── test ├── fixture ├── autoload │ └── auto │ │ └── foo.vim └── plugin │ └── auto.vim ├── run ├── tests ├── autoload.vader ├── command.vader ├── local.vader └── tagstack.vader └── vimrc /.gitignore: -------------------------------------------------------------------------------- 1 | /test/vader.vim 2 | /test/vim 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: false 3 | 4 | cache: 5 | directories: 6 | - test/vim 7 | - test/vader.vim 8 | 9 | script: 10 | - test/run 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/mhinz/vim-lookup.svg?branch=master)](https://travis-ci.org/mhinz/vim-lookup) 2 | 3 | # vim-lookup 4 | 5 | This plugin is meant for VimL programmers. It jumps to definitions of variables, 6 | functions, and commands as if tags were used, without needing a tags file. It simply 7 | uses your [runtimepath](https://neovim.io/doc/user/options.html#'rtp'). 8 | 9 | - [x] `s:var` 10 | - [x] `s:func()` 11 | - [x] `func()` 12 | - [x] `autoload#foo#var` 13 | - [x] `autoload#foo#func()` 14 | - [x] `'autoload#foo#func'` 15 | - [x] `Command` 16 | 17 | Sometimes a function `foo#func()` is not found in `autoload/foo.vim` but 18 | `plugin/foo.vim`. This case is handled as well. 19 | 20 | It also works for global functions if they're defined or found in the current 21 | file: 22 | 23 | - [x] `GlobalFunc()` 24 | - [x] `g:GlobalFunc()` 25 | 26 | ### Usage 27 | 28 | - Use `lookup#lookup()` to jump to the defintion of the identifier under the 29 | cursor. 30 | - Use `lookup#pop()` (or the default mapping 31 | [``](https://github.com/mhinz/vim-galore/#changelist-jumplist)) to jump 32 | back. 33 | 34 | ### Configuration 35 | 36 | ```viml 37 | autocmd FileType vim nnoremap :call lookup#lookup() 38 | ``` 39 | 40 | Alternatively, you can replace the default mappings Vim uses for 41 | [tagstack](https://neovim.io/doc/user/tagsrch.html#tag-stack) navigation: 42 | 43 | ```viml 44 | autocmd FileType vim nnoremap :call lookup#lookup() 45 | autocmd FileType vim nnoremap :call lookup#pop() 46 | ``` 47 | 48 | ### Other useful VimL plugins 49 | 50 | - [exception.vim](https://github.com/tweekmonster/exception.vim) 51 | - [helpful.vim](https://github.com/tweekmonster/helpful.vim) 52 | - [vim-scriptease](https://github.com/tpope/vim-scriptease) 53 | -------------------------------------------------------------------------------- /autoload/lookup.vim: -------------------------------------------------------------------------------- 1 | " lookup#lookup() {{{1 2 | " 3 | " Entry point. Map this function to your favourite keys. 4 | " 5 | " autocmd FileType vim nnoremap :call lookup#lookup() 6 | " 7 | function! lookup#lookup() abort 8 | let dispatch = [ 9 | \ [function('s:find_local_var_def'), function('s:find_local_func_def')], 10 | \ [function('s:find_autoload_var_def'), function('s:find_autoload_func_def')]] 11 | let isk = &iskeyword 12 | setlocal iskeyword+=:,<,>,# 13 | let name = matchstr(getline('.'), '\k*\%'.col('.').'c\k*[("'']\?') 14 | let plug = matchstr(getline('.'), '\c\k*\%'.col('.').'c\k*[("'']\?') 15 | let &iskeyword = isk 16 | let is_func = name =~ '($' ? 1 : 0 17 | let could_be_funcref = name =~ '[''"]$' ? 1 : 0 18 | let is_cmd = name =~# '\v^\u\w*>' 19 | let name = matchstr(name, '\c\v^%(s:|\)?\zs.{-}\ze[\("'']?$') 20 | let is_auto = name =~ '#' ? 1 : 0 21 | let position = s:getcurpos() 22 | try 23 | if is_cmd && s:find_local_cmd_def(name) 24 | " Found command. 25 | elseif !dispatch[is_auto][is_func](name) && !is_func && could_be_funcref 26 | let is_func = 1 27 | call dispatch[is_auto][is_func](name) 28 | elseif !empty(plug) && s:find_plug_map_def(plug) 29 | " Found plug. 30 | endif 31 | catch /^Vim\%((\a\+)\)\=:/ 32 | echohl ErrorMsg 33 | " Strip off the :edit command prefix to make it look like a normal vim 34 | " error message. 35 | echomsg substitute(v:exception, "^[^:]*:", "", "") 36 | echohl NONE 37 | return 0 38 | endtry 39 | let didmove = position != s:getcurpos() ? 1 : 0 40 | if didmove 41 | call s:push(position, name) 42 | else 43 | echo 'No match' 44 | return 0 45 | endif 46 | normal! zv 47 | return didmove 48 | endfunction 49 | 50 | " lookup#pop() {{{1 51 | function! lookup#pop() 52 | if !has_key(w:, 'lookup_stack') || empty(w:lookup_stack) 53 | echohl ErrorMsg 54 | echo "lookup stack empty" 55 | echohl NONE 56 | return 57 | endif 58 | let pos = remove(w:lookup_stack, 0) 59 | execute 'silent!' (bufexists(pos[0]) ? 'buffer' : 'edit') fnameescape(pos[0]) 60 | call cursor(pos[2:]) 61 | endfunction 62 | 63 | " s:find_local_func_def() {{{1 64 | function! s:find_local_func_def(funcname) abort 65 | if search('\c\v)\zs\V'.a:funcname.'\>', 'bsw') != 0 66 | return 67 | endif 68 | 69 | call s:jump_to_file_defining('function', a:funcname) 70 | let fn = substitute(a:funcname, '^g:', '', '') 71 | return search('\c\v', 'bsw') 72 | endfunction 73 | 74 | " s:find_local_cmd_def() {{{1 75 | function! s:find_local_cmd_def(cmdname) abort 76 | let pattern = '\c\v' 77 | if search(pattern, 'bsw') != 0 78 | return 79 | endif 80 | 81 | call s:jump_to_file_defining('command', a:cmdname) 82 | return search(pattern, 'bsw') 83 | endfunction 84 | 85 | " s:find_plug_map_def() {{{1 86 | function! s:find_plug_map_def(plugname) abort 87 | let pattern = '\c\v<[nvxsoilct]?(nore)?m%[ap]\s*(\<[bnseu]\w+\>\s*)*\s+\zs\V'.a:plugname.'\>' 88 | if search(pattern, 'bsw') != 0 89 | return 90 | endif 91 | 92 | call s:jump_to_file_defining('map', a:plugname) 93 | return search(pattern, 'bsw') 94 | endfunction 95 | 96 | " s:jump_to_file_defining() {{{1 97 | " Expects symbol_type = 'command' or 'function' 98 | function! s:jump_to_file_defining(symbol_type, symbol_name) abort 99 | let lang = v:lang 100 | language message C 101 | redir => location 102 | silent! execute 'verbose ' a:symbol_type a:symbol_name 103 | redir END 104 | let failed = 0 105 | if a:symbol_type == 'command' 106 | let failed = location =~# 'No user-defined commands found' 107 | endif 108 | silent! execute 'language message' lang 109 | 110 | if failed || location =~# 'E\d\{2,3}:' 111 | return 112 | endif 113 | 114 | let matches = matchlist(location, '\v.*Last set from (.*) line (\d+)>') 115 | execute 'silent edit +'. matches[2] matches[1] 116 | endfunction 117 | 118 | " s:find_local_var_def() {{{1 119 | function! s:find_local_var_def(name) abort 120 | return search('\c\v', 'bsw') 121 | endfunction 122 | 123 | " s:find_autoload_func_def() {{{1 124 | function! s:find_autoload_func_def(name) abort 125 | let [path, func] = split(a:name, '.*\zs#') 126 | let pattern = '\c\v' 127 | return s:find_autoload_def(path, pattern) 128 | endfunction 129 | 130 | " s:find_autoload_var_def() {{{1 131 | function! s:find_autoload_var_def(name) abort 132 | let [path, var] = split(a:name, '.*\zs#') 133 | let pattern = '\c\v' 134 | return s:find_autoload_def(path, pattern) 135 | endfunction 136 | 137 | " s:find_autoload_def() {{{1 138 | function! s:find_autoload_def(name, pattern) abort 139 | for dir in ['autoload', 'plugin'] 140 | let path = printf('%s/%s.vim', dir, substitute(a:name, '#', '/', 'g')) 141 | let aufiles = globpath(&runtimepath, path, '', 1) 142 | if empty(aufiles) && exists('b:git_dir') 143 | let aufiles = [fnamemodify(b:git_dir, ':h') .'/'. path] 144 | endif 145 | if empty(aufiles) 146 | return search(a:pattern) 147 | else 148 | for file in aufiles 149 | if !filereadable(file) 150 | continue 151 | endif 152 | let lnum = match(readfile(file), a:pattern) 153 | if lnum > -1 154 | execute 'silent edit +'. (lnum+1) fnameescape(file) 155 | call search(a:pattern) 156 | return 1 157 | break 158 | endif 159 | endfor 160 | endif 161 | endfor 162 | return 0 163 | endfunction 164 | 165 | " s:push() {{{1 166 | function! s:push(position, tagname) abort 167 | call s:pushtagstack(a:position[1:], a:tagname) 168 | if !has_key(w:, 'lookup_stack') || empty(w:lookup_stack) 169 | let w:lookup_stack = [a:position] 170 | return 171 | endif 172 | if w:lookup_stack[0] != a:position 173 | call insert(w:lookup_stack, a:position) 174 | endif 175 | endfunction 176 | 177 | " s:pushtagstack() {{{1 178 | function! s:pushtagstack(curpos, tagname) abort 179 | if !exists('*gettagstack') || !exists('*settagstack') || !has('patch-8.2.0077') " patch that adds 't' argument 180 | " do nothing 181 | return 182 | endif 183 | 184 | let item = {'bufnr': a:curpos[0], 'from': a:curpos, 'tagname': a:tagname} 185 | 186 | let winid = win_getid() 187 | let stack = gettagstack(winid) 188 | let stack['items'] = [item] 189 | call settagstack(winid, stack, 't') 190 | endfunction 191 | 192 | " s:getcurpos() {{{1 193 | function! s:getcurpos() abort 194 | let pos = getcurpos() 195 | " getcurpos always returns bufnr 0. 196 | let pos[0] = bufnr('%') 197 | return [expand('%:p')] + pos 198 | endfunction 199 | -------------------------------------------------------------------------------- /test/fixture/autoload/auto/foo.vim: -------------------------------------------------------------------------------- 1 | function! auto#foo#func(...) 2 | return 'func' 3 | endfunction 4 | 5 | let auto#foo#var = 'var' 6 | -------------------------------------------------------------------------------- /test/fixture/plugin/auto.vim: -------------------------------------------------------------------------------- 1 | function! Foo(...) 2 | endfunction 3 | 4 | call Foo(auto#foo#var,v:lang) 5 | silent! echomsg auto#foo#var 6 | 7 | call Foo(auto#foo#func(1,2)) 8 | silent! echomsg auto#foo#func() 9 | 10 | let Bar = function('auto#foo#func') 11 | -------------------------------------------------------------------------------- /test/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | vim="$(command -v vim)" 6 | 7 | # Change to the directory of this script. 8 | cd "$( dirname "${BASH_SOURCE[0]}" )" || exit 1 9 | 10 | if [[ "$TRAVIS" = true ]] || [[ -z $vim ]]; then 11 | # The "test/vim" directory is created and cached by Travis CI. 12 | if [[ ! -d vim/src ]]; then 13 | git clone --depth=1 https://github.com/vim/vim.git 14 | fi 15 | 16 | # Build vim executable if needed. 17 | if [[ ! -x vim/src/vim ]]; then 18 | (cd vim; ./configure && make) 19 | fi 20 | vim=vim/src/vim 21 | fi 22 | 23 | # The "test/vader" directory is created and cached by Travis CI. 24 | if [[ ! -d vader.vim/plugin ]]; then 25 | git clone --depth=1 https://github.com/junegunn/vader.vim.git 26 | fi 27 | 28 | $vim -Nnu vimrc -i NONE +'Vader! tests/*.vader' 29 | 30 | echo -e "\nExecutable used: $vim\n" 31 | -------------------------------------------------------------------------------- /test/tests/autoload.vader: -------------------------------------------------------------------------------- 1 | After: 2 | bwipeout 3 | 4 | Execute (:Lookup jumps to autoload variable): 5 | edit fixture/plugin/auto.vim 6 | normal! 5G^3w 7 | call lookup#lookup() 8 | AssertEqual 'fixture/autoload/auto/foo.vim', expand('%') 9 | AssertEqual [5, 5], [line('.'), col('.')] 10 | call lookup#pop() 11 | normal! k 12 | AssertEqual 'fixture/plugin/auto.vim', expand('%') 13 | call lookup#lookup() 14 | AssertEqual 'fixture/autoload/auto/foo.vim', expand('%') 15 | AssertEqual [5, 5], [line('.'), col('.')] 16 | 17 | Execute (:Lookup jumps to autoload function): 18 | edit fixture/plugin/auto.vim 19 | normal! 8G^3w 20 | call lookup#lookup() 21 | AssertEqual 'fixture/autoload/auto/foo.vim', expand('%') 22 | AssertEqual [1, 11], [line('.'), col('.')] 23 | call lookup#pop() 24 | normal! k 25 | AssertEqual 'fixture/plugin/auto.vim', expand('%') 26 | call lookup#lookup() 27 | AssertEqual 'fixture/autoload/auto/foo.vim', expand('%') 28 | AssertEqual [1, 11], [line('.'), col('.')] 29 | call lookup#pop() 30 | normal! 10G23| 31 | AssertEqual 'fixture/plugin/auto.vim', expand('%') 32 | call lookup#lookup() 33 | AssertEqual 'fixture/autoload/auto/foo.vim', expand('%') 34 | AssertEqual [1, 11], [line('.'), col('.')] 35 | -------------------------------------------------------------------------------- /test/tests/command.vader: -------------------------------------------------------------------------------- 1 | Given vim: 2 | command! -nargs=+ TestIt call s:func() 3 | execute "command! -nargs=0 TestFlip let s:var = 'hello'" 4 | execute 'let s:var = "bar"' 5 | echomsg s:var 6 | function! s:nested(a, b) 7 | TestIt 8 | TestFlip 9 | endfunc 10 | 11 | Execute (:call lookup#lookup() to find definition of commands): 12 | " on TestIt 13 | normal! 6G 14 | AssertEqual [6, 3], [line('.'), col('.')] 15 | call lookup#lookup() 16 | AssertEqual [1, 22], [line('.'), col('.')] 17 | 18 | " on TestFlip 19 | normal! 7G 20 | call lookup#lookup() 21 | AssertEqual [2, 31], [line('.'), col('.')] 22 | 23 | -------------------------------------------------------------------------------- /test/tests/local.vader: -------------------------------------------------------------------------------- 1 | Given vim: 2 | function! s:func(a, b) 3 | endfunc 4 | let s:var = 'foo' 5 | execute 'let s:var = "bar"' 6 | echomsg s:var 7 | let s:var = 'foo' 8 | function! s:func(a, b) 9 | endfunc 10 | let foo = function('s:func') 11 | let foo = function("s:func") 12 | 13 | Execute (:call lookup#lookup() cycles through script-local variables): 14 | normal! 3GwE 15 | AssertEqual [3, 9], [line('.'), col('.')] 16 | call lookup#lookup() 17 | AssertEqual [3, 7], [line('.'), col('.')] 18 | call lookup#lookup() 19 | AssertEqual [6, 7], [line('.'), col('.')] 20 | call lookup#lookup() 21 | AssertEqual [4, 16], [line('.'), col('.')] 22 | call lookup#lookup() 23 | AssertEqual [3, 7], [line('.'), col('.')] 24 | 25 | Execute (:call lookup#lookup() cycles through script-local functions): 26 | normal! 9G7w 27 | AssertEqual [9, 23], [line('.'), col('.')] 28 | call lookup#lookup() 29 | AssertEqual [7, 13], [line('.'), col('.')] 30 | call lookup#pop() 31 | normal! j 32 | AssertEqual [10, 23], [line('.'), col('.')] 33 | call lookup#lookup() 34 | AssertEqual [7, 13], [line('.'), col('.')] 35 | call lookup#lookup() 36 | AssertEqual [1, 13], [line('.'), col('.')] 37 | call lookup#lookup() 38 | AssertEqual [7, 13], [line('.'), col('.')] 39 | -------------------------------------------------------------------------------- /test/tests/tagstack.vader: -------------------------------------------------------------------------------- 1 | Given vim: 2 | function! s:func(a, b) 3 | return s:nested(a, b) 4 | endfunc 5 | execute 'let s:var = "bar"' 6 | let s:const = "foo" 7 | echomsg s:var 8 | function! s:nested(a, b) 9 | return [s:var + a:a, s:const + a:b] 10 | endfunc 11 | 12 | Execute (lookup#lookup() a couple levels deep and C-t to go back): 13 | " on s:nested 14 | normal! 2G4e 15 | AssertEqual [2, 17], [line('.'), col('.')] 16 | call lookup#lookup() 17 | AssertEqual [7, 13], [line('.'), col('.')] 18 | 19 | " on s:var 20 | normal! j 21 | AssertEqual [8, 13], [line('.'), col('.')] 22 | call lookup#lookup() 23 | AssertEqual [4, 16], [line('.'), col('.')] 24 | exec "normal! \" 25 | AssertEqual [8, 13], [line('.'), col('.')] 26 | 27 | " on s:const 28 | normal! fc 29 | AssertEqual [8, 26], [line('.'), col('.')] 30 | call lookup#lookup() 31 | AssertEqual [5, 7], [line('.'), col('.')] 32 | exec "normal! \" 33 | AssertEqual [8, 26], [line('.'), col('.')] 34 | 35 | exec "normal! \" 36 | AssertEqual [2, 17], [line('.'), col('.')] 37 | 38 | 39 | Execute (lookup#lookup() a couple levels deep and lookup#pop to go back): 40 | " on s:nested 41 | normal! 2G4e 42 | AssertEqual [2, 17], [line('.'), col('.')] 43 | call lookup#lookup() 44 | AssertEqual [7, 13], [line('.'), col('.')] 45 | 46 | " on s:var 47 | normal! j 48 | AssertEqual [8, 13], [line('.'), col('.')] 49 | call lookup#lookup() 50 | AssertEqual [4, 16], [line('.'), col('.')] 51 | call lookup#pop() 52 | AssertEqual [8, 13], [line('.'), col('.')] 53 | 54 | " on s:const 55 | normal! fc 56 | AssertEqual [8, 26], [line('.'), col('.')] 57 | call lookup#lookup() 58 | AssertEqual [5, 7], [line('.'), col('.')] 59 | call lookup#pop() 60 | AssertEqual [8, 26], [line('.'), col('.')] 61 | 62 | call lookup#pop() 63 | AssertEqual [2, 17], [line('.'), col('.')] 64 | 65 | 66 | Execute (lookup#lookup() across files and C-t to go back): 67 | edit fixture/plugin/auto.vim 68 | normal! 5G^3w 69 | AssertEqual [5, 17], [line('.'), col('.')] 70 | call lookup#lookup() 71 | AssertEqual expand('fixture/autoload/auto/foo.vim'), expand('%') 72 | AssertEqual [5, 5], [line('.'), col('.')] 73 | exec "normal! \" 74 | AssertEqual expand('fixture/plugin/auto.vim'), expand('%') 75 | AssertEqual [5, 17], [line('.'), col('.')] 76 | 77 | normal! k 78 | AssertEqual expand('fixture/plugin/auto.vim'), expand('%') 79 | AssertEqual [4, 17], [line('.'), col('.')] 80 | call lookup#lookup() 81 | AssertEqual expand('fixture/autoload/auto/foo.vim'), expand('%') 82 | AssertEqual [5, 5], [line('.'), col('.')] 83 | exec "normal! \" 84 | AssertEqual expand('fixture/plugin/auto.vim'), expand('%') 85 | AssertEqual [4, 17], [line('.'), col('.')] 86 | 87 | 88 | Execute (lookup#lookup() across files and lookup#pop to go back): 89 | edit fixture/plugin/auto.vim 90 | normal! 5G^3w 91 | AssertEqual [5, 17], [line('.'), col('.')] 92 | call lookup#lookup() 93 | AssertEqual expand('fixture/autoload/auto/foo.vim'), expand('%') 94 | AssertEqual [5, 5], [line('.'), col('.')] 95 | call lookup#pop() 96 | AssertEqual expand('fixture/plugin/auto.vim'), expand('%') 97 | AssertEqual [5, 17], [line('.'), col('.')] 98 | 99 | normal! k 100 | AssertEqual expand('fixture/plugin/auto.vim'), expand('%') 101 | AssertEqual [4, 17], [line('.'), col('.')] 102 | call lookup#lookup() 103 | AssertEqual expand('fixture/autoload/auto/foo.vim'), expand('%') 104 | AssertEqual [5, 5], [line('.'), col('.')] 105 | call lookup#pop() 106 | AssertEqual expand('fixture/plugin/auto.vim'), expand('%') 107 | AssertEqual [4, 17], [line('.'), col('.')] 108 | 109 | -------------------------------------------------------------------------------- /test/vimrc: -------------------------------------------------------------------------------- 1 | set runtimepath+=vader.vim 2 | set runtimepath+=fixture 3 | set runtimepath+=.. 4 | set hidden 5 | --------------------------------------------------------------------------------