tagbar/plugin/tagbar.vim

1055 lines
31 KiB
VimL
Raw Normal View History

2011-01-11 17:36:49 +08:00
" ============================================================================
" File: tagbar.vim
" Description: List the current file's tags in a sidebar, ordered by class etc
" Maintainer: Jan Larres <jan@majutsushi.net>
" Licence: Vim licence
" Website: http://github.com/majutsushi/tagbar
" Note: This plugin was heavily inspired by the 'Taglist' plugin by
" Yegappan Lakshamanan and uses some small portions of code from
" it.
" ============================================================================
if &cp || exists('g:loaded_tagbar')
finish
endif
if !exists('*system')
echomsg 'Tagbar: No system() function available, skipping plugin'
finish
endif
if !exists('g:tagbar_ctags_bin')
if executable('ctags-exuberant')
let g:tagbar_ctags_bin = 'ctags-exuberant'
2011-01-11 17:36:49 +08:00
elseif executable('exctags')
let g:tagbar_ctags_bin = 'exctags'
2011-01-11 17:36:49 +08:00
elseif executable('ctags')
let g:tagbar_ctags_bin = 'ctags'
2011-01-11 17:36:49 +08:00
elseif executable('ctags.exe')
let g:tagbar_ctags_bin = 'ctags.exe'
2011-01-11 17:36:49 +08:00
elseif executable('tags')
let g:tagbar_ctags_bin = 'tags'
2011-01-11 17:36:49 +08:00
else
echomsg 'Tagbar: Exuberant ctags not found, skipping plugin'
finish
endif
endif
let g:loaded_tagbar = 1
if !exists('g:tagbar_left')
let g:tagbar_left = 0
endif
if !exists('g:tagbar_width')
2011-01-14 18:12:26 +08:00
let g:tagbar_width = 40
2011-01-11 17:36:49 +08:00
endif
2011-01-12 17:48:04 +08:00
if !exists('g:tagbar_types')
let g:tagbar_types = {}
endif
2011-01-12 18:30:38 +08:00
if !exists('g:tagbar_autoclose')
let g:tagbar_autoclose = 0
endif
if !exists('g:tagbar_sort')
let g:tagbar_sort = 1
endif
2011-01-12 17:48:04 +08:00
function! s:InitTypes()
" Dictionary of the already processed files, indexed by file name with
2011-01-20 11:47:29 +08:00
" complete path.
" The entries are again dictionaries with the following fields:
" - mtime: File modification time
2011-01-20 13:16:57 +08:00
" - ftype: The vim file type
" - tags: List of the tags that are present in the file, sorted
" according to the value of 'g:tagbar_sort'
2011-01-20 11:47:29 +08:00
" - fline: Dictionary of the tags, indexed by line number in the file
" - tline: Dictionary of the tags, indexed by line number in the tagbar
2011-01-12 17:48:04 +08:00
let s:known_files = {}
2011-01-20 11:47:29 +08:00
2011-01-12 17:48:04 +08:00
let s:known_types = {}
let type_cpp = {}
let type_cpp.ctagstype = 'c++'
2011-01-20 20:38:14 +08:00
let type_cpp.scopes = [
\ 'namespace',
\ 'class',
\ 'struct',
\ 'enum',
\ 'union'
\ ]
2011-01-14 18:12:26 +08:00
let type_cpp.sro = '::'
2011-01-12 17:48:04 +08:00
let type_cpp.kinds = [
\ 'd:macros',
\ 'n:namespaces',
\ 'c:classes',
\ 's:structs',
2011-01-16 22:19:11 +08:00
\ 'g:enum',
2011-01-20 20:38:14 +08:00
\ 'e:enumerators',
2011-01-12 17:48:04 +08:00
\ 'u:unions',
2011-01-21 15:11:15 +08:00
\ 't:typedefs',
2011-01-16 22:19:11 +08:00
\ 'p:prototypes',
\ 'f:functions',
\ 'm:members',
\ 'v:variables'
2011-01-14 18:12:26 +08:00
\ ]
2011-01-16 12:46:14 +08:00
let type_cpp.kind2scope = {
\ 'n' : 'namespace',
\ 'c' : 'class',
2011-01-20 20:38:14 +08:00
\ 's' : 'struct',
\ 'g' : 'enum',
\ 'u' : 'union'
2011-01-16 12:46:14 +08:00
\ }
2011-01-17 17:58:31 +08:00
let type_cpp.scope2kind = {
\ 'namespace' : 'n',
\ 'class' : 'c',
2011-01-20 20:38:14 +08:00
\ 'struct' : 's',
\ 'enum' : 'g',
\ 'union' : 'u'
2011-01-17 17:58:31 +08:00
\ }
2011-01-12 17:48:04 +08:00
let s:known_types.cpp = type_cpp
2011-01-14 18:12:26 +08:00
let type_python = {}
let type_python.ctagstype = 'python'
let type_python.scopes = ['class', 'function']
let type_python.sro = '.'
let type_python.kinds = [
\ 'i:imports',
\ 'c:classes',
\ 'f:functions',
\ 'm:members',
\ 'v:variables'
\ ]
2011-01-16 12:46:14 +08:00
let type_python.kind2scope = {
\ 'c' : 'class',
\ 'f' : 'function',
\ 'm' : 'function'
2011-01-14 18:12:26 +08:00
\ }
2011-01-17 17:58:31 +08:00
let type_python.scope2kind = {
\ 'class' : 'c',
\ 'function' : 'f'
\ }
2011-01-14 18:12:26 +08:00
let s:known_types.python = type_python
2011-01-12 17:48:04 +08:00
call extend(s:known_types, g:tagbar_types)
" Create a dictionary of the kind order for fast
" access in sorting functions
for type in values(s:known_types)
let i = 0
let type.kinddict = {}
for kind in type.kinds
let type.kinddict[kind[0]] = i
let i += 1
endfor
endfor
2011-01-12 17:48:04 +08:00
endfunction
call s:InitTypes()
2011-01-11 17:36:49 +08:00
function! s:ToggleWindow()
let tagbarwinnr = bufwinnr("__Tagbar__")
if tagbarwinnr != -1
call s:CloseWindow()
return
endif
call s:OpenWindow()
endfunction
function! s:OpenWindow()
" If the tagbar window is already open jump to it
let tagbarwinnr = bufwinnr('__Tagbar__')
if tagbarwinnr != -1 && winnr() != tagbarwinnr
2011-01-21 19:27:50 +08:00
execute tagbarwinnr . 'wincmd w'
2011-01-11 17:36:49 +08:00
return
endif
let openpos = g:tagbar_left ? 'topleft vertical ' : 'botright vertical '
2011-01-20 17:18:33 +08:00
exe 'silent! keepalt ' . openpos . g:tagbar_width . 'split ' . '__Tagbar__'
2011-01-11 17:36:49 +08:00
setlocal noreadonly " in case the "view" mode is used
setlocal buftype=nofile
setlocal bufhidden=hide
2011-01-11 17:36:49 +08:00
setlocal noswapfile
setlocal nobuflisted
setlocal nomodifiable
setlocal filetype=tagbar
setlocal nolist
setlocal nonumber
setlocal nowrap
setlocal winfixwidth
2011-01-18 10:03:27 +08:00
if exists('+relativenumber')
setlocal norelativenumber
endif
2011-01-11 17:36:49 +08:00
setlocal foldenable
setlocal foldminlines=0
setlocal foldmethod=manual
setlocal foldlevel=9999
setlocal foldcolumn=1
setlocal foldtext=v:folddashes.getline(v:foldstart)
2011-01-20 13:16:57 +08:00
" Variable for saving the current file for functions that are called from
" the tagbar window
let s:current_file = ''
2011-01-21 19:27:50 +08:00
" Script-local variable needed since compare functions can't
" take extra arguments
let s:compare_typeinfo = {}
2011-01-20 16:20:29 +08:00
let s:is_maximized = 0
2011-01-21 19:27:50 +08:00
let s:short_help = 1
2011-01-20 13:16:57 +08:00
2011-01-20 11:51:50 +08:00
syntax match Comment '^" .*' " Comments
syntax match Identifier '^[^: ]\+$' " Non-scoped kinds
syntax match Title '[^:(* ]\+\ze\*\? :' " Scope names
syntax match Type ': \zs.*' " Scope types
syntax match SpecialKey '(.*)' " Signatures
syntax match NonText '\*\ze :' " Pseudo-tag identifiers
if has('balloon_eval')
setlocal balloonexpr=TagbarBalloonExpr()
set ballooneval
endif
2011-01-11 17:36:49 +08:00
let cpoptions_save = &cpoptions
set cpoptions&vim
nnoremap <script> <silent> <buffer> s :call <SID>ToggleSort()<CR>
nnoremap <script> <silent> <buffer> <CR> :call <SID>JumpToTag()<CR>
nnoremap <script> <silent> <buffer> <2-LeftMouse>
\ :call <SID>JumpToTag()<CR>
nnoremap <script> <silent> <buffer> <Space> :call <SID>ShowPrototype()<CR>
2011-01-20 16:20:29 +08:00
nnoremap <script> <silent> <buffer> x :call <SID>ZoomWindow()<CR>
2011-01-20 16:13:00 +08:00
nnoremap <script> <silent> <buffer> q :close<CR>
2011-01-21 19:27:50 +08:00
nnoremap <script> <silent> <buffer> <F1> :call <SID>ToggleHelp()<CR>
2011-01-11 17:36:49 +08:00
augroup TagbarAutoCmds
autocmd!
autocmd BufEnter __Tagbar__ nested call s:QuitIfOnlyWindow()
autocmd BufUnload __Tagbar__ call s:CleanUp()
autocmd CursorHold __Tagbar__ call s:ShowPrototype()
2011-01-11 17:36:49 +08:00
" autocmd TabEnter * silent call s:Tlist_Refresh_Folds()
autocmd BufEnter,CursorHold * silent call s:AutoUpdate(
\ fnamemodify(bufname('%'), ':p'))
2011-01-11 17:36:49 +08:00
augroup END
let &cpoptions = cpoptions_save
execute 'wincmd p'
" Jump back to the tagbar window if autoclose is set. Can't just stay in
" it since it wouldn't trigger the update event
if g:tagbar_autoclose
let tagbarwinnr = bufwinnr('__Tagbar__')
execute tagbarwinnr . 'wincmd w'
endif
2011-01-11 17:36:49 +08:00
endfunction
function! s:CloseWindow()
let tagbarwinnr = bufwinnr('__Tagbar__')
if tagbarwinnr == -1
return
endif
if winnr() == tagbarwinnr
if winbufnr(2) != -1
" Other windows are open, only close the tagbar one
close
endif
else
" Go to the tagbar window, close it and then come back to the
" original window
let curbufnr = bufnr('%')
2011-01-20 11:47:29 +08:00
execute tagbarwinnr . 'wincmd w'
2011-01-11 17:36:49 +08:00
close
" Need to jump back to the original window only if we are not
" already in that window
let winnum = bufwinnr(curbufnr)
if winnr() != winnum
exe winnum . 'wincmd w'
endif
endif
endfunction
2011-01-20 16:20:29 +08:00
function! s:ZoomWindow()
if s:is_maximized
execute 'vert resize ' . g:tagbar_width
let s:is_maximized = 0
else
vert resize
let s:is_maximized = 1
endif
endfunction
2011-01-11 17:36:49 +08:00
function! s:CleanUp()
silent! autocmd! TagbarAutoCmds
2011-01-21 19:27:50 +08:00
unlet s:current_file
unlet s:is_maximized
unlet s:compare_typeinfo
unlet s:short_help
2011-01-11 17:36:49 +08:00
endfunction
function! s:QuitIfOnlyWindow()
" Before quitting Vim, delete the tagbar buffer so that
" the '0 mark is correctly set to the previous buffer.
if winbufnr(2) == -1
" Check if there is more than one tab page
if tabpagenr('$') == 1
bdelete
quit
else
close
endif
endif
endfunction
function! s:AutoUpdate(fname)
call s:RefreshContent(a:fname)
2011-01-11 17:36:49 +08:00
let tagbarwinnr = bufwinnr('__Tagbar__')
if tagbarwinnr == -1 || &filetype == 'tagbar'
return
endif
if !has_key(s:known_files, a:fname)
return
endif
2011-01-20 13:16:57 +08:00
let s:current_file = a:fname
call s:HighlightTag(a:fname)
endfunction
function! s:RefreshContent(fname)
" Don't do anything if we're in the tagbar window
if &filetype == 'tagbar'
return
endif
if has_key(s:known_files, a:fname)
if s:known_files[a:fname].mtime != getftime(a:fname)
call s:ProcessFile(a:fname, &filetype)
2011-01-11 17:36:49 +08:00
endif
else
call s:ProcessFile(a:fname, &filetype)
2011-01-11 17:36:49 +08:00
endif
2011-01-12 18:30:38 +08:00
let tagbarwinnr = bufwinnr('__Tagbar__')
if tagbarwinnr != -1
call s:RenderContent(a:fname, &filetype)
2011-01-12 18:30:38 +08:00
endif
2011-01-11 17:36:49 +08:00
endfunction
function! s:IsValidFile(fname, ftype)
if a:fname == '' || a:ftype == ''
return 0
endif
if !filereadable(a:fname)
return 0
endif
2011-01-12 17:48:04 +08:00
if !has_key(s:known_types, a:ftype)
2011-01-11 17:36:49 +08:00
return 0
endif
return 1
endfunction
function! s:ProcessFile(fname, ftype)
2011-01-16 17:02:52 +08:00
if !s:IsValidFile(a:fname, a:ftype)
return
endif
let typeinfo = s:known_types[a:ftype]
2011-01-19 13:44:08 +08:00
let ctags_args = ' -f - '
let ctags_args .= ' --format=2 '
let ctags_args .= ' --excmd=pattern '
let ctags_args .= ' --fields=nksSaz '
let ctags_args .= ' --extra= '
let ctags_args .= ' --sort=yes '
2011-01-12 17:48:04 +08:00
let ctags_type = typeinfo.ctagstype
2011-01-17 18:40:50 +08:00
2011-01-12 17:48:04 +08:00
let ctags_kinds = ""
for kind in typeinfo.kinds
2011-01-12 17:48:04 +08:00
let [short, full] = split(kind, ':')
let ctags_kinds .= short
endfor
let ctags_args .= ' --language-force=' . ctags_type .
\ ' --' . ctags_type . '-kinds=' . ctags_kinds . ' '
2011-01-11 17:36:49 +08:00
let ctags_cmd = g:tagbar_ctags_bin . ctags_args . shellescape(a:fname)
2011-01-11 17:36:49 +08:00
let ctags_output = system(ctags_cmd)
if v:shell_error
let msg = 'Tagbar: Could not generate tags for ' . a:fname
call s:PrintWarningMsg(msg)
if !empty(ctags_output)
call s:PrintWarningMsg(ctags_output)
endif
return
endif
let fileinfo = {}
let fileinfo.mtime = getftime(a:fname)
let rawtaglist = split(ctags_output, '\n\+')
2011-01-11 17:36:49 +08:00
2011-01-20 13:16:57 +08:00
let fileinfo.ftype = a:ftype
let fileinfo.tags = []
let fileinfo.fline = {}
let fileinfo.tline = {}
2011-01-11 17:36:49 +08:00
for line in rawtaglist
2011-01-18 10:03:27 +08:00
let parts = split(line, ';"')
if len(parts) == 2 " Is a valid tag line
let taginfo = s:ParseTagline(parts[0], parts[1], typeinfo)
let fileinfo.fline[taginfo.fields.line] = taginfo
2011-01-18 10:03:27 +08:00
call add(fileinfo.tags, taginfo)
endif
2011-01-11 17:36:49 +08:00
endfor
if has_key(typeinfo, 'scopes') && !empty(typeinfo.scopes)
let scopedtags = []
for scope in typeinfo.scopes
let is_scoped = 'has_key(typeinfo.kind2scope, v:val.fields.kind) ||
\ has_key(v:val.fields, scope)'
let scopedtags += filter(copy(fileinfo.tags), is_scoped)
call filter(fileinfo.tags, '!(' . is_scoped . ')')
endfor
let processedtags = []
2011-01-21 15:11:15 +08:00
for scope in typeinfo.scopes
call s:AddChildren(scopedtags, processedtags, '',
2011-01-21 17:29:48 +08:00
\ '', scope, 1, typeinfo)
2011-01-21 15:11:15 +08:00
endfor
2011-01-17 17:58:31 +08:00
" 'scopedtags' can still contain some tags that don't have any
" children
call extend(fileinfo.tags, scopedtags)
2011-01-21 15:11:15 +08:00
call extend(fileinfo.tags, processedtags)
endif
2011-01-19 12:36:10 +08:00
let s:compare_typeinfo = typeinfo
2011-01-20 12:11:27 +08:00
if g:tagbar_sort
call s:SortTags(fileinfo.tags, 's:CompareByKind')
else
call s:SortTags(fileinfo.tags, 's:CompareByLine')
endif
2011-01-11 17:36:49 +08:00
let s:known_files[a:fname] = fileinfo
endfunction
2011-01-21 15:11:15 +08:00
" Structure of a tag line:
" tagname<TAB>filename<TAB>expattern;"fields
2011-01-14 18:12:26 +08:00
" fields: <TAB>name:value
" fields that are always present: kind, line
function! s:ParseTagline(part1, part2, typeinfo)
2011-01-11 17:36:49 +08:00
let taginfo = {}
2011-01-18 10:03:27 +08:00
let basic_info = split(a:part1, '\t')
2011-01-12 17:48:04 +08:00
let taginfo.name = basic_info[0]
let taginfo.file = basic_info[1]
let pattern = basic_info[2]
let start = 2 " skip the slash and the ^
let end = strlen(pattern) - 1
if pattern[end - 1] == '$'
let end -= 1
let dollar = '\$'
else
let dollar = ''
endif
let pattern = strpart(pattern, start, end - start)
let taginfo.pattern = '\V\^' . pattern . dollar
let prototype = substitute(pattern, '^[[:space:]]\+', '', '')
let prototype = substitute(prototype, '[[:space:]]\+$', '', '')
let taginfo.prototype = prototype
2011-01-11 17:36:49 +08:00
2011-01-14 18:12:26 +08:00
let taginfo.fields = {}
2011-01-18 10:03:27 +08:00
let fields = split(a:part2, '\t')
2011-01-11 17:36:49 +08:00
for field in fields
2011-01-12 17:48:04 +08:00
" can't use split() since the value can contain ':'
2011-01-14 18:12:26 +08:00
let delimit = stridx(field, ':')
let key = strpart(field, 0, delimit)
let val = strpart(field, delimit + 1)
let taginfo.fields[key] = val
2011-01-11 17:36:49 +08:00
endfor
" Cache the parent path for fast access in filter() calls
for scope in a:typeinfo.scopes
if has_key(taginfo.fields, scope)
let index = strridx(taginfo.fields[scope], a:typeinfo.sro)
let taginfo.parentpath = strpart(taginfo.fields[scope], 0, index)
break
endif
endfor
2011-01-11 17:36:49 +08:00
return taginfo
endfunction
2011-01-21 15:11:15 +08:00
" Extract children from the tag list and correctly add it to their parents.
" Unfortunately the parents aren't necessarily actually there -- for example,
" in C++ a class can be defined in a header file and implemented in a .cpp
" file (so the class itself doesn't appear in the .cpp file and thus doesn't
" genereate a tag). Another example are anonymous
" namespaces/structs/enums/unions that also don't get a tag themselves. These
" tags are thus called 'pseudo-tags' in Tagbar.
2011-01-22 17:16:49 +08:00
" This (in conjunction with ProcessPseudoTag) is probably the most cryptic
" function since it has to deal with things that aren't actually there and
" several corner cases. Try not to think about it too much.
2011-01-21 15:11:15 +08:00
function! s:AddChildren(tags, processedtags, curpath,
\ pscope, scope, depth, typeinfo)
let is_child = 'has_key(v:val.fields, a:scope)'
if !empty(a:curpath)
let is_child .= ' && v:val.parentpath == a:curpath'
2011-01-21 15:11:15 +08:00
endif
let is_cur_child = is_child . ' && len(split(v:val.fields[a:scope],
\ a:typeinfo.sro)) == a:depth'
let curchildren = filter(copy(a:tags), is_cur_child)
" 'curchildren' are children at the current depth
if !empty(curchildren)
call filter(a:tags, '!(' . is_cur_child . ')')
2011-01-21 15:11:15 +08:00
for child in curchildren
2011-01-22 17:16:49 +08:00
let parentname = substitute(child.fields[a:scope],a:curpath, '', '')
let parentname = substitute(parentname,'^' . a:typeinfo.sro, '', '')
let parentpath = split(parentname, a:typeinfo.sro)
let maxpathlen = a:depth - 1 - len(split(a:curpath, a:typeinfo.sro))
let parentlist = s:ExtractParentList(a:tags, a:processedtags,
\ join(parentpath[:maxpathlen], a:typeinfo.sro),
\ a:scope, a:typeinfo)
2011-01-19 12:19:15 +08:00
2011-01-21 15:11:15 +08:00
if empty(parentlist)
2011-01-22 17:16:49 +08:00
" If we don't have a parent at this point it must be a
" pseudo-tag, so create an entry for it
call s:ProcessPseudoTag(a:tags, a:processedtags, child,
\ a:curpath, parentpath, parentname,
\ a:pscope, a:scope, a:typeinfo)
2011-01-17 17:58:31 +08:00
else
2011-01-21 15:11:15 +08:00
let parent = parentlist[0]
if has_key(parent, 'children')
call add(parent.children, child)
else
let parent.children = [child]
endif
2011-01-22 17:16:49 +08:00
call add(a:processedtags, parent)
2011-01-17 17:58:31 +08:00
endif
endfor
2011-01-21 15:11:15 +08:00
" Recursively add children
for tag in a:processedtags
2011-01-19 12:19:15 +08:00
if !has_key(tag, 'children')
continue
endif
2011-01-21 15:11:15 +08:00
if empty(a:curpath)
let fullpath = tag.name
2011-01-19 12:19:15 +08:00
else
2011-01-21 15:11:15 +08:00
let fullpath = a:curpath . a:typeinfo.sro . tag.name
2011-01-19 12:19:15 +08:00
endif
2011-01-21 15:11:15 +08:00
let parentscope = a:typeinfo.kind2scope[tag.fields.kind]
for childscope in a:typeinfo.scopes
call s:AddChildren(a:tags, tag.children, fullpath,
\ parentscope, childscope, a:depth + 1, a:typeinfo)
endfor
2011-01-17 17:58:31 +08:00
endfor
2011-01-19 12:19:15 +08:00
endif
2011-01-17 17:58:31 +08:00
2011-01-21 15:11:15 +08:00
" Grandchildren are children that are not direct ancestors of a tag. This
" can happen when pseudo-tags are in between.
let is_grandchild = is_child . ' && len(split(v:val.fields[a:scope],
\ a:typeinfo.sro)) > a:depth'
let grandchildren = filter(copy(a:tags), is_grandchild)
if !empty(grandchildren)
for childscope in a:typeinfo.scopes
call s:AddChildren(a:tags, a:processedtags, a:curpath,
\ a:pscope, childscope, a:depth + 1, a:typeinfo)
endfor
endif
endfunction
2011-01-22 17:16:49 +08:00
function! s:ProcessPseudoTag(tags, processedtags, child, curpath, parentpath,
\ parentname, pscope, scope, typeinfo)
" First check if the pseudo-tag is child of an existing tag.
let parentpathlen = len(a:parentpath)
let pseudoparentlist = []
for i in range(parentpathlen - 2, 0, -1)
let pseudoparentpath = a:parentpath[:i]
for scope in a:typeinfo.scopes
let pseudoparentlist = s:ExtractParentList(a:tags, a:processedtags,
\ join(pseudoparentpath, a:typeinfo.sro),
\ scope, a:typeinfo)
if !empty(pseudoparentlist)
break
endif
endfor
if !empty(pseudoparentlist)
2011-01-21 15:11:15 +08:00
break
endif
endfor
2011-01-22 17:16:49 +08:00
if !empty(pseudoparentlist)
" The pseudo-tag is child of an existing (real) tag -- so we have to
" add the real tag to the list of processed tags, create a pseudo-tag,
" add the pseudo-tag to the children of the real tag and add the
" /current/ tag ('child') to the children of the pseudo-tag. Yuck.
let pseudoparent = pseudoparentlist[0]
let parentname = substitute(a:parentname, pseudoparent.name, '', '')
let parentname = substitute(parentname, '^' . a:typeinfo.sro, '', '')
if has_key(pseudoparent, 'children')
let is_existingparent = 'v:val.name == parentname &&
\ v:val.fields.kind == a:typeinfo.scope2kind[a:scope]'
let existingparent = filter(copy(pseudoparent.children),
\ is_existingparent)
if !empty(existingparent)
call filter(pseudoparent.children,
\ '!(' . is_existingparent . ')')
let parent = existingparent[0]
call add(parent.children, a:child)
else
let parent = s:CreatePseudoTag(parentname, a:curpath, a:pscope,
\ a:scope, a:typeinfo)
let parent.children = [a:child]
endif
call add(pseudoparent.children, parent)
else
let parent = s:CreatePseudoTag(parentname, a:curpath, a:pscope,
\ a:scope, a:typeinfo)
let parent.children = [a:child]
let pseudoparent.children = [parent]
endif
call add(a:processedtags, pseudoparent)
else
let parent = s:CreatePseudoTag(a:parentname, a:curpath, a:pscope,
\ a:scope, a:typeinfo)
let parent.children = [a:child]
call add(a:processedtags, parent)
endif
endfunction
function! s:ExtractParentList(tags, processedtags, path, scope, typeinfo)
let is_parent = 'has_key(a:typeinfo.kind2scope, v:val.fields.kind) &&
\ a:typeinfo.kind2scope[v:val.fields.kind] == a:scope &&
\ v:val.name == a:path'
let parentlist = filter(copy(a:processedtags), is_parent)
if !empty(parentlist)
call filter(a:processedtags, '!(' . is_parent . ')')
2011-01-21 15:11:15 +08:00
else
2011-01-22 17:16:49 +08:00
let parentlist = filter(copy(a:tags), is_parent)
if !empty(parentlist)
call filter(a:tags, '!(' . is_parent . ')')
endif
2011-01-19 12:19:15 +08:00
endif
2011-01-22 17:16:49 +08:00
return parentlist
endfunction
function! s:CreatePseudoTag(name, curpath, pscope, scope, typeinfo)
let pseudotag = {}
let pseudotag.name = a:name
let pseudotag.fields = {}
let pseudotag.fields.kind = a:typeinfo.scope2kind[a:scope]
let pseudotag.fields.line = 0
let parentscope = substitute(a:curpath, a:name . '$', '', '')
let parentscope = substitute(parentscope, a:typeinfo.sro . '$', '', '')
2011-01-22 17:16:49 +08:00
if a:pscope != ''
let pseudotag.fields[a:pscope] = parentscope
endif
let index = strridx(parentscope, a:typeinfo.sro)
let pseudotag.parentpath = strpart(parentscope, 0, index)
2011-01-19 12:19:15 +08:00
return pseudotag
2011-01-17 17:58:31 +08:00
endfunction
2011-01-20 13:16:57 +08:00
function! s:SortTags(tags, comparemethod)
call sort(a:tags, a:comparemethod)
for tag in a:tags
if has_key(tag, 'children')
call s:SortTags(tag.children, a:comparemethod)
endif
endfor
endfunction
function! s:CompareByKind(tag1, tag2)
let typeinfo = s:compare_typeinfo
if typeinfo.kinddict[a:tag1.fields.kind] <
\ typeinfo.kinddict[a:tag2.fields.kind]
return -1
elseif typeinfo.kinddict[a:tag1.fields.kind] >
\ typeinfo.kinddict[a:tag2.fields.kind]
return 1
else
if a:tag1.name <= a:tag2.name
return -1
else
return 1
endif
endif
endfunction
function! s:CompareByLine(tag1, tag2)
return a:tag1.fields.line - a:tag2.fields.line
endfunction
2011-01-11 17:36:49 +08:00
function! s:RenderContent(fname, ftype)
let tagbarwinnr = bufwinnr('__Tagbar__')
2011-01-20 13:16:57 +08:00
if &filetype == 'tagbar'
let in_tagbar = 1
else
let in_tagbar = 0
execute tagbarwinnr . 'wincmd w'
endif
2011-01-11 17:36:49 +08:00
2011-01-12 17:48:04 +08:00
let lazyredraw_save = &lazyredraw
set lazyredraw
2011-01-11 17:36:49 +08:00
setlocal modifiable
silent! %delete _
2011-01-21 19:27:50 +08:00
call s:PrintHelp()
2011-01-16 17:02:52 +08:00
if !s:IsValidFile(a:fname, a:ftype)
silent! put ='- File type not supported -'
2011-01-20 13:16:57 +08:00
let s:current_file = ''
2011-01-16 17:02:52 +08:00
setlocal nomodifiable
let &lazyredraw = lazyredraw_save
2011-01-20 13:16:57 +08:00
if !in_tagbar
execute 'wincmd p'
endif
2011-01-16 17:02:52 +08:00
return
endif
2011-01-12 17:48:04 +08:00
let typeinfo = s:known_types[a:ftype]
let fileinfo = s:known_files[a:fname]
2011-01-16 12:46:14 +08:00
" Print tags
2011-01-12 17:48:04 +08:00
for kind in typeinfo.kinds
2011-01-20 17:18:33 +08:00
let curtags = filter(copy(fileinfo.tags),
\ 'v:val.fields.kind == kind[0]')
2011-01-12 17:48:04 +08:00
if empty(curtags)
continue
endif
if has_key(typeinfo.kind2scope, kind[0])
" Scoped tags
for tag in curtags
2011-01-19 12:19:15 +08:00
let taginfo = ''
if tag.fields.line == 0 " Tag is a pseudo-tag
let taginfo .= '*'
endif
if has_key(tag.fields, 'signature')
let taginfo .= tag.fields.signature
endif
let taginfo .= ' : ' . typeinfo.kind2scope[kind[0]]
silent! put =tag.name . taginfo
2011-01-12 17:48:04 +08:00
" Save the current tagbar line in the tag for easy
" highlighting access
let curline = line('.')
let tag.tline = curline
let fileinfo.tline[curline] = tag
2011-01-21 15:11:15 +08:00
if has_key(tag, 'children')
for childtag in tag.children
call s:PrintTag(childtag, 1, fileinfo, typeinfo)
endfor
endif
2011-01-12 17:48:04 +08:00
silent! put _
endfor
else
" Non-scoped tags
silent! put =strpart(kind, 2)
for tag in curtags
let taginfo = ''
if has_key(tag.fields, 'signature')
let taginfo .= tag.fields.signature
endif
silent! put =' ' . tag.name . taginfo
" Save the current tagbar line in the tag for easy
" highlighting access
let curline = line('.')
let tag.tline = curline
let fileinfo.tline[curline] = tag
endfor
silent! put _
endif
2011-01-11 17:36:49 +08:00
endfor
setlocal nomodifiable
2011-01-12 17:48:04 +08:00
let &lazyredraw = lazyredraw_save
2011-01-20 13:16:57 +08:00
if !in_tagbar
execute 'wincmd p'
endif
2011-01-11 17:36:49 +08:00
endfunction
2011-01-21 19:27:50 +08:00
function! s:PrintHelp()
if s:short_help
call append(0, '" Press F1 for help')
else
call append(0, '" <Enter> : Jump to tag definition')
call append(1, '" <Space> : Display tag prototype')
call append(2, '" s : Toggle sort')
call append(3, '" x : Zoom window in/out')
call append(4, '" q : Close window')
call append(5, '" <F1> : Remove help')
endif
endfunction
function! s:PrintTag(tag, depth, fileinfo, typeinfo)
2011-01-16 12:46:14 +08:00
let taginfo = ''
2011-01-19 12:19:15 +08:00
if a:tag.fields.line == 0 " Tag is a pseudo-tag
let taginfo .= '*'
endif
2011-01-16 12:46:14 +08:00
if has_key(a:tag.fields, 'signature')
let taginfo .= a:tag.fields.signature
endif
if has_key(a:typeinfo.kind2scope, a:tag.fields.kind)
let taginfo .= ' : ' . a:typeinfo.kind2scope[a:tag.fields.kind]
endif
" Print tag indented according to depth
silent! put =repeat(' ', a:depth * 2) . a:tag.name . taginfo
" Save the current tagbar line in the tag for easy
" highlighting access
let curline = line('.')
let a:tag.tline = curline
let a:fileinfo.tline[curline] = a:tag
2011-01-16 12:46:14 +08:00
" Recursively print children
if has_key(a:tag, 'children')
for childtag in a:tag.children
call s:PrintTag(childtag, a:depth + 1, a:fileinfo, a:typeinfo)
2011-01-16 12:46:14 +08:00
endfor
endif
endfunction
function! s:HighlightTag(fname)
let fileinfo = s:known_files[a:fname]
let curline = line('.')
let tagline = 0
2011-01-21 17:29:48 +08:00
" If a tag appears in a file more than once (for example namespaces in
" C++) only one of them has a 'tline' entry and can thus be highlighted.
" The only way to solve this would be to go over the whole tag list again,
" making everything slower. Since this should be a rare occurence and
" highlighting isn't /that/ important ignore it for now.
for line in range(curline, 1, -1)
2011-01-21 16:16:46 +08:00
if has_key(fileinfo.fline, line) &&
\ has_key(fileinfo.fline[line], 'tline')
let tagline = fileinfo.fline[line].tline
break
endif
endfor
let eventignore_save = &eventignore
set eventignore=all
let tagbarwinnr = bufwinnr('__Tagbar__')
execute tagbarwinnr . 'wincmd w'
match none
if tagline == 0
execute 1
call winline()
execute 'wincmd p'
let &eventignore = eventignore_save
return
endif
" Go to the line containing the tag
execute tagline
if foldclosed('.') != -1
.foldopen!
endif
" Make sure the tag is visible in the window
call winline()
let pattern = '/^\%' . tagline . 'l\s*\zs[^( ]\+\ze/'
execute 'match Search ' . pattern
execute 'wincmd p'
let &eventignore = eventignore_save
endfunction
function! s:JumpToTag()
let taginfo = s:GetTagInfo(line('.'))
if empty(taginfo)
return
endif
execute 'wincmd p'
" Jump to the line where the tag is defined. Don't use the search pattern
" since it doesn't take the scope into account and thus can fail if tags
" with the same name are defined in different scopes (e.g. classes)
execute taginfo.fields.line
" Center the tag in the window
normal! z.
if foldclosed('.') != -1
.foldopen!
endif
if g:tagbar_autoclose
call s:CloseWindow()
else
call s:HighlightTag(s:current_file)
endif
endfunction
function! s:ShowPrototype()
let taginfo = s:GetTagInfo(line('.'))
if empty(taginfo)
return
endif
echo taginfo.prototype
endfunction
function! TagbarBalloonExpr()
let taginfo = s:GetTagInfo(v:beval_lnum)
if empty(taginfo)
return
endif
return taginfo.prototype
endfunction
" Return the info dictionary of the tag on the specified line. If the line
" does not contain a valid tag (for example because it is empty or only
" contains a pseudo-tag) return an empty dictionary.
function! s:GetTagInfo(linenr)
if !has_key(s:known_files, s:current_file)
return {}
endif
" Don't do anything in empty and comment lines
let curline = getline(a:linenr)
if curline =~ '^\s*$' || curline[0] == '"'
return {}
endif
let fileinfo = s:known_files[s:current_file]
" Check if there is a tag on the current line
if !has_key(fileinfo.tline, a:linenr)
return {}
endif
let taginfo = fileinfo.tline[a:linenr]
" Check if the current tag is not a pseudo-tag
if taginfo.fields.line == 0
return {}
endif
return taginfo
endfunction
2011-01-20 13:16:57 +08:00
function! s:ToggleSort()
if !has_key(s:known_files, s:current_file)
return
endif
let curline = line('.')
let fileinfo = s:known_files[s:current_file]
match none
let g:tagbar_sort = !g:tagbar_sort
let s:compare_typeinfo = s:known_types[fileinfo.ftype]
if g:tagbar_sort
call s:SortTags(fileinfo.tags, 's:CompareByKind')
else
call s:SortTags(fileinfo.tags, 's:CompareByLine')
endif
call s:RenderContent(s:current_file, fileinfo.ftype)
execute curline
endfunction
2011-01-21 19:27:50 +08:00
function! s:ToggleHelp()
let s:short_help = !s:short_help
2011-01-22 17:16:49 +08:00
" Prevent highlighting from being off after adding/removing the help text
match none
2011-01-21 19:27:50 +08:00
if s:current_file == ''
call s:RenderContent(s:current_file, '')
else
let fileinfo = s:known_files[s:current_file]
call s:RenderContent(s:current_file, fileinfo.ftype)
endif
execute 1
endfunction
2011-01-11 17:36:49 +08:00
function! s:PrintWarningMsg(msg)
echohl WarningMsg
echomsg a:msg
echohl None
endfunction
command! -nargs=0 TagbarToggle call s:ToggleWindow()
command! -nargs=0 TagbarOpen call s:OpenWindow()
command! -nargs=0 TagbarClose call s:CloseWindow()
" vim: ts=8 sw=4 sts=4 et foldenable foldmethod=syntax foldcolumn=1