tagbar/autoload/tagbar.vim
David P. Sicilia 6eadc15054
Add g:tagbar_jump_lazy_scroll option. (#705)
* Add g:tagbar_jump_lazy_scroll option.

When this option is on, a jump to a tag will only cause the
window to scroll if the tag line is not already visible on
the window.  If it is visible, the cursor will simply move
to that line without scrolling the window.  If the tagline
is not visible then the window will be scrolled as in current
behavior (according to the g:tagbar_jump_offset option).

Fixes #703

* Factor our new logic into a function.

* Add jump target line to doc.
2020-11-10 16:07:57 -06:00

3934 lines
125 KiB
VimL

" ============================================================================
" File: tagbar.vim
" Description: List the current file's tags in a sidebar, ordered by class etc
" Author: Jan Larres <jan@majutsushi.net>
" Licence: Vim licence
" Website: https://preservim.github.io/tagbar
" Version: 2.7
" Note: This plugin was heavily inspired by the 'Taglist' plugin by
" Yegappan Lakshmanan and uses a small amount of code from it.
"
" Original taglist copyright notice:
" Permission is hereby granted to use and distribute this code,
" with or without modifications, provided that this copyright
" notice is copied with it. Like anything else that's free,
" taglist.vim is provided *as is* and comes with no warranty of
" any kind, either expressed or implied. In no event will the
" copyright holder be liable for any damamges resulting from the
" use of this software.
" ============================================================================
scriptencoding utf-8
" Initialization {{{1
" If another plugin calls an autoloaded Tagbar function on startup before the
" plugin/tagbar.vim file got loaded, load it explicitly
if exists(':Tagbar') == 0
runtime plugin/tagbar.vim
endif
if exists(':Tagbar') == 0
echomsg 'Tagbar: Could not load plugin code, check your runtimepath!'
finish
endif
" Basic init {{{2
redir => s:ftype_out
silent filetype
redir END
if s:ftype_out !~# 'detection:ON'
echomsg 'Tagbar: Filetype detection is turned off, skipping plugin'
unlet s:ftype_out
finish
endif
unlet s:ftype_out
let g:tagbar#icon_closed = g:tagbar_iconchars[0]
let g:tagbar#icon_open = g:tagbar_iconchars[1]
let s:type_init_done = 0
let s:autocommands_done = 0
let s:statusline_in_use = 0
let s:init_done = 0
" 0: not checked yet; 1: checked and found; 2: checked and not found
let s:checked_ctags = 0
let s:checked_ctags_types = 0
let s:ctags_is_uctags = 0
let s:new_window = 1
let s:is_maximized = 0
let s:winrestcmd = ''
let s:short_help = 1
let s:nearby_disabled = 0
let s:paused = 0
let s:pwin_by_tagbar = 0
let s:buffer_seqno = 0
let s:vim_quitting = 0
let s:last_alt_bufnr = -1
let s:window_expanded = 0
let s:expand_bufnr = -1
let s:window_pos = {
\ 'pre' : { 'x' : 0, 'y' : 0 },
\ 'post' : { 'x' : 0, 'y' : 0 }
\}
let s:delayed_update_files = []
let g:loaded_tagbar = 1
let s:last_highlight_tline = 0
let s:warnings = {
\ 'type': [],
\ 'encoding': 0
\ }
let s:singular_types = {
\ 'Classes': 'Class',
\ 'Delegates': 'Delegate',
\ 'Enumeration values': 'Enumeration value',
\ 'Enumerations': 'Enumeration',
\ 'Error codes': 'Error code',
\ 'Error domains': 'Error domain',
\ 'Fields': 'Field',
\ 'Interfaces': 'Interface',
\ 'JavaScript funtions': 'JavaScript function',
\ 'Methods': 'Method',
\ 'MobiLink Conn Scripts': 'MobiLink Conn Script',
\ 'MobiLink Properties': 'MobiLink Property',
\ 'MobiLink Table Scripts': 'MobiLink Table Script',
\ 'Properties': 'Property',
\ 'Signals': 'Signal',
\ 'Structures': 'Structure',
\ 'autocommand groups': 'autocommand group',
\ 'block data': 'block data',
\ 'block label': 'block label',
\ 'chapters': 'chapter',
\ 'classes': 'class',
\ 'commands': 'command',
\ 'common blocks': 'common block',
\ 'components': 'component',
\ 'constant definitions': 'constant definition',
\ 'constants': 'constant',
\ 'constructors': 'constructor',
\ 'cursors': 'cursor',
\ 'data items': 'data item',
\ 'defines': 'define',
\ 'derived types and structures': 'derived type and structure',
\ 'domains': 'domain',
\ 'entities': 'entity',
\ 'entry points': 'entry point',
\ 'embedded': 'embedded',
\ 'enum constants': 'enum constant',
\ 'enum types': 'enum type',
\ 'enumerations': 'enumeration',
\ 'enumerators': 'enumerator',
\ 'enums': 'enum',
\ 'events': 'event',
\ 'exception declarations': 'exception declaration',
\ 'exceptions': 'exception',
\ 'features': 'feature',
\ 'fields': 'field',
\ 'file descriptions': 'file description',
\ 'formats': 'format',
\ 'fragments': 'fragment',
\ 'function definitions': 'function definition',
\ 'functions': 'function',
\ 'functor definitions': 'functor definition',
\ 'global variables': 'global variable',
\ 'group items': 'group item',
\ 'imports': 'import',
\ 'includes': 'include',
\ 'indexes': 'index',
\ 'interfaces': 'interface',
\ 'javascript functions': 'JavaScript function',
\ 'labels': 'label',
\ 'macro definitions': 'macro definition',
\ 'macros': 'macro',
\ 'maps': 'map',
\ 'members': 'member',
\ 'methods': 'method',
\ 'modules or functors': 'module or function',
\ 'modules': 'module',
\ 'mxtags': 'mxtag',
\ 'named anchors': 'named anchor',
\ 'namelists': 'namelist',
\ 'namespaces': 'namespace',
\ 'net data types': 'net data type',
\ 'packages': 'package',
\ 'package': 'package',
\ 'paragraphs': 'paragraph',
\ 'parts': 'part',
\ 'patterns': 'pattern',
\ 'ports': 'port',
\ 'procedures': 'procedure',
\ 'program ids': 'program id',
\ 'programs': 'program',
\ 'projects': 'project',
\ 'properties': 'property',
\ 'prototypes': 'prototype',
\ 'publications': 'publication',
\ 'record definitions': 'record definition',
\ 'record fields': 'record field',
\ 'records': 'record',
\ 'register data types': 'register data type',
\ 'sections': 'section',
\ 'services': 'services',
\ 'sets': 'sets',
\ 'signature declarations': 'signature declaration',
\ 'singleton methods': 'singleton method',
\ 'slots': 'slot',
\ 'structs': 'struct',
\ 'structure declarations': 'structure declaration',
\ 'structure fields': 'structure field',
\ 'subparagraphs': 'subparagraph',
\ 'subroutines': 'subroutine',
\ 'subsections': 'subsection',
\ 'subsubsections': 'subsubsection',
\ 'subtypes': 'subtype',
\ 'synonyms': 'synonym',
\ 'tables': 'table',
\ 'targets': 'target',
\ 'tasks': 'task',
\ 'triggers': 'trigger',
\ 'type definitions': 'type definition',
\ 'type names': 'type name',
\ 'typedefs': 'typedef',
\ 'types': 'type',
\ 'unions': 'union',
\ 'value bindings': 'value binding',
\ 'variables': 'variable',
\ 'views': 'view',
\ 'vimball filenames': 'vimball filename'}
" s:Init() {{{2
function! s:Init(silent) abort
if s:checked_ctags == 2 && a:silent
return 0
elseif s:checked_ctags != 1
if !s:CheckForExCtags(a:silent)
return 0
endif
endif
if !s:type_init_done
call s:InitTypes()
endif
if !s:autocommands_done
call s:CreateAutocommands()
call s:AutoUpdate(fnamemodify(expand('%'), ':p'), 0)
endif
let s:init_done = 1
return 1
endfunction
" s:InitTypes() {{{2
function! s:InitTypes() abort
call tagbar#debug#log('Initializing types')
let supported_types = s:GetSupportedFiletypes()
if s:ctags_is_uctags
let s:known_types = tagbar#types#uctags#init(supported_types)
else
let s:known_types = tagbar#types#ctags#init(supported_types)
endif
" Use dart_ctags if available
let dart_ctags = s:CheckFTCtags('dart_ctags', 'dart')
if dart_ctags !=# ''
let supported_types['dart'] = 1
call tagbar#debug#log('Detected dart_ctags, overriding typedef')
let type_dart = tagbar#prototypes#typeinfo#new()
let type_dart.ctagstype = 'dart'
let type_dart.kinds = [
\ {'short' : 'i', 'long' : 'imports', 'fold' : 1, 'stl' : 0},
\ {'short' : 'C', 'long' : 'consts', 'fold' : 0, 'stl' : 0},
\ {'short' : 'v', 'long' : 'variables', 'fold' : 0, 'stl' : 0},
\ {'short' : 'F', 'long' : 'functions', 'fold' : 0, 'stl' : 0},
\ {'short' : 'c', 'long' : 'classes', 'fold' : 0, 'stl' : 0},
\ {'short' : 'f', 'long' : 'fields', 'fold' : 0, 'stl' : 0},
\ {'short' : 'm', 'long' : 'methods', 'fold' : 0, 'stl' : 0},
\ {'short' : 'M', 'long' : 'static methods', 'fold' : 0, 'stl' : 0},
\ {'short' : 'r', 'long' : 'constructors', 'fold' : 0, 'stl' : 0},
\ {'short' : 'o', 'long' : 'operators', 'fold' : 0, 'stl' : 0},
\ {'short' : 'g', 'long' : 'getters', 'fold' : 0, 'stl' : 0},
\ {'short' : 's', 'long' : 'setters', 'fold' : 0, 'stl' : 0},
\ {'short' : 'a', 'long' : 'abstract functions', 'fold' : 0, 'stl' : 0},
\ ]
let type_dart.sro = ':'
let type_dart.kind2scope = {
\ 'c' : 'class'
\ }
let type_dart.scope2kind = {
\ 'class' : 'c'
\ }
let type_dart.ctagsbin = dart_ctags
let type_dart.ctagsargs = '-l'
let type_dart.ftype = 'dart'
call type_dart.createKinddict()
let s:known_types.dart = type_dart
endif
" Use jsctags/doctorjs if available
let jsctags = s:CheckFTCtags('jsctags', 'javascript')
if jsctags !=# ''
call tagbar#debug#log('Detected jsctags, overriding typedef')
let type_javascript = tagbar#prototypes#typeinfo#new()
let type_javascript.ctagstype = 'javascript'
let type_javascript.kinds = [
\ {'short' : 'v', 'long' : 'variables', 'fold' : 0, 'stl' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0, 'stl' : 1}
\ ]
let type_javascript.sro = '.'
let type_javascript.kind2scope = {
\ 'v' : 'namespace',
\ 'f' : 'namespace'
\ }
let type_javascript.scope2kind = {
\ 'namespace' : 'f'
\ }
let type_javascript.ctagsbin = jsctags
let type_javascript.ctagsargs = '-f -'
let type_javascript.ftype = 'javascript'
call type_javascript.createKinddict()
let s:known_types.javascript = type_javascript
endif
" Use gotags if available
let gotags = s:CheckFTCtags('gotags', 'go')
if gotags !=# ''
call tagbar#debug#log('Detected gotags, overriding typedef')
let type_go = tagbar#prototypes#typeinfo#new()
let type_go.ctagstype = 'go'
let type_go.kinds = [
\ {'short' : 'p', 'long' : 'package', 'fold' : 0, 'stl' : 0},
\ {'short' : 'i', 'long' : 'imports', 'fold' : 1, 'stl' : 0},
\ {'short' : 'c', 'long' : 'constants', 'fold' : 0, 'stl' : 0},
\ {'short' : 'v', 'long' : 'variables', 'fold' : 0, 'stl' : 0},
\ {'short' : 't', 'long' : 'types', 'fold' : 0, 'stl' : 0},
\ {'short' : 'n', 'long' : 'intefaces', 'fold' : 0, 'stl' : 0},
\ {'short' : 'w', 'long' : 'fields', 'fold' : 0, 'stl' : 0},
\ {'short' : 'e', 'long' : 'embedded', 'fold' : 0, 'stl' : 0},
\ {'short' : 'm', 'long' : 'methods', 'fold' : 0, 'stl' : 0},
\ {'short' : 'r', 'long' : 'constructors', 'fold' : 0, 'stl' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0, 'stl' : 0},
\ ]
let type_go.sro = '.'
let type_go.kind2scope = {
\ 't' : 'ctype',
\ 'n' : 'ntype'
\ }
let type_go.scope2kind = {
\ 'ctype' : 't',
\ 'ntype' : 'n'
\ }
let type_go.ctagsbin = gotags
let type_go.ctagsargs = '-sort -silent'
let type_go.ftype = 'go'
call type_go.createKinddict()
let s:known_types.go = type_go
endif
call s:LoadUserTypeDefs()
" Add an 'unknown' kind to the types for pseudotags that we can't
" determine the correct kind for since they don't have any children that
" are not pseudotags and that therefore don't provide scope information
for typeinfo in values(s:known_types)
if has_key(typeinfo, 'kind2scope')
let unknown_kind =
\ {'short' : '?', 'long' : 'unknown', 'fold' : 0, 'stl' : 1}
" Check for existence first since some types exist under more than
" one name
if index(typeinfo.kinds, unknown_kind) == -1
call add(typeinfo.kinds, unknown_kind)
endif
let typeinfo.kind2scope['?'] = 'unknown'
endif
endfor
let s:type_init_done = 1
endfunction
" s:LoadUserTypeDefs() {{{2
function! s:LoadUserTypeDefs(...) abort
if a:0 > 0
let type = a:1
let defdict = {}
let defdict[type] = g:tagbar_type_{type}
else
let defdict = tagbar#getusertypes()
endif
let transformed = {}
for [type, def] in items(defdict)
let transformed[type] = s:TransformUserTypeDef(def)
let transformed[type].ftype = type
endfor
for [key, value] in items(transformed)
call tagbar#debug#log("Initializing user type '" . key . "'")
if !has_key(s:known_types, key) || get(value, 'replace', 0)
let s:known_types[key] = tagbar#prototypes#typeinfo#new(value)
else
call extend(s:known_types[key], value)
endif
call s:known_types[key].createKinddict()
endfor
endfunction
" s:TransformUserTypeDef() {{{2
" Transform the user definitions into the internal format
function! s:TransformUserTypeDef(def) abort
let newdef = copy(a:def)
if has_key(a:def, 'kinds')
let newdef.kinds = []
let kinds = a:def.kinds
for kind in kinds
let kindlist = split(kind, ':')
let kinddict = {'short' : kindlist[0], 'long' : kindlist[1]}
let kinddict.fold = get(kindlist, 2, 0)
let kinddict.stl = get(kindlist, 3, 1)
call add(newdef.kinds, kinddict)
endfor
endif
" If the user only specified one of kind2scope and scope2kind then use it
" to generate the respective other
if has_key(a:def, 'kind2scope') && !has_key(a:def, 'scope2kind')
let newdef.scope2kind = {}
for [key, value] in items(a:def.kind2scope)
let newdef.scope2kind[value] = key
endfor
elseif has_key(a:def, 'scope2kind') && !has_key(a:def, 'kind2scope')
let newdef.kind2scope = {}
for [key, value] in items(a:def.scope2kind)
let newdef.kind2scope[value] = key
endfor
endif
return newdef
endfunction
" s:RestoreSession() {{{2
" Properly restore Tagbar after a session got loaded
function! s:RestoreSession() abort
if s:init_done
call tagbar#debug#log('Tagbar already initialized; not restoring session')
return
endif
call tagbar#debug#log('Restoring session')
let curfile = fnamemodify(bufname('%'), ':p')
let tagbarwinnr = bufwinnr(s:TagbarBufName())
if tagbarwinnr == -1
" Tagbar wasn't open in the saved session, nothing to do
return
endif
let in_tagbar = 1
if winnr() != tagbarwinnr
call s:goto_win(tagbarwinnr, 1)
let in_tagbar = 0
endif
let s:last_autofocus = 0
call s:Init(0)
call s:InitWindow(g:tagbar_autoclose)
call s:AutoUpdate(curfile, 0)
if !in_tagbar
call s:goto_win('p')
endif
endfunction
" s:MapKeys() {{{2
function! s:MapKeys() abort
call tagbar#debug#log('Mapping keys')
nnoremap <script> <silent> <buffer> <2-LeftMouse>
\ :call <SID>JumpToTag(0)<CR>
nnoremap <script> <silent> <buffer> <LeftRelease>
\ <LeftRelease>:call <SID>CheckMouseClick()<CR>
inoremap <script> <silent> <buffer> <2-LeftMouse>
\ <C-o>:call <SID>JumpToTag(0)<CR>
inoremap <script> <silent> <buffer> <LeftRelease>
\ <LeftRelease><C-o>:call <SID>CheckMouseClick()<CR>
let maps = [
\ ['jump', 'JumpToTag(0)'],
\ ['preview', 'JumpToTag(1)'],
\ ['previewwin', 'ShowInPreviewWin()'],
\ ['nexttag', 'GotoNextToplevelTag(1)'],
\ ['prevtag', 'GotoNextToplevelTag(-1)'],
\ ['showproto', 'ShowPrototype(0)'],
\ ['hidenonpublic', 'ToggleHideNonPublicTags()'],
\
\ ['openfold', 'OpenFold()'],
\ ['closefold', 'CloseFold()'],
\ ['togglefold', 'ToggleFold()'],
\ ['openallfolds', 'SetFoldLevel(99, 1)'],
\ ['closeallfolds', 'SetFoldLevel(0, 1)'],
\ ['incrementfolds', 'ChangeFoldLevel(1, 1)'],
\ ['decrementfolds', 'ChangeFoldLevel(-1, 1)'],
\ ['nextfold', 'GotoNextFold()'],
\ ['prevfold', 'GotoPrevFold()'],
\
\ ['togglesort', 'ToggleSort()'],
\ ['togglecaseinsensitive', 'ToggleCaseInsensitive()'],
\ ['toggleautoclose', 'ToggleAutoclose()'],
\ ['togglepause', 'TogglePause()'],
\ ['zoomwin', 'ZoomWindow()'],
\ ['close', 'CloseWindow()'],
\ ['help', 'ToggleHelp()'],
\ ]
let map_options = ' <script> <silent> <buffer> '
if v:version > 703 || (v:version == 703 && has('patch1261'))
let map_options .= ' <nowait> '
endif
for [map, func] in maps
let def = get(g:, 'tagbar_map_' . map)
if type(def) == type('')
let keys = [def]
else
let keys = def
endif
for key in keys
if !empty(key)
execute 'nnoremap' . map_options . key .
\ ' :call <SID>' . func . '<CR>'
endif
endfor
unlet def
endfor
let b:tagbar_mapped_keys = 1
endfunction
" s:CreateAutocommands() {{{2
function! s:CreateAutocommands() abort
call tagbar#debug#log('Creating autocommands')
augroup TagbarAutoCmds
autocmd!
if !g:tagbar_silent
autocmd CursorHold __Tagbar__.* call s:ShowPrototype(1)
endif
autocmd WinEnter __Tagbar__.* call s:SetStatusLine()
autocmd WinLeave __Tagbar__.* call s:SetStatusLine()
if g:tagbar_autopreview
autocmd CursorMoved __Tagbar__.* nested call s:ShowInPreviewWin()
endif
autocmd BufEnter * if expand('<amatch>') !~ '__Tagbar__.*' |
\ let s:last_alt_bufnr = bufnr('#') |
\ endif
if exists('##QuitPre')
autocmd QuitPre * let s:vim_quitting = 1
endif
autocmd WinEnter * nested call s:HandleOnlyWindow()
autocmd WinEnter * if bufwinnr(s:TagbarBufName()) == -1 |
\ call s:ShrinkIfExpanded() |
\ endif
autocmd BufWritePost *
\ call s:HandleBufWrite(fnamemodify(expand('<afile>'), ':p'))
autocmd CursorHold,CursorHoldI * call s:do_delayed_update()
" BufReadPost is needed for reloading the current buffer if the file
" was changed by an external command; see commit 17d199f
autocmd BufReadPost,BufEnter,CursorHold,FileType * call
\ s:AutoUpdate(fnamemodify(expand('<afile>'), ':p'), 0)
autocmd BufDelete,BufWipeout *
\ nested call s:HandleBufDelete(expand('<afile>'), expand('<abuf>'))
" Suspend Tagbar while grep commands are running, since we don't want
" to process files that only get loaded temporarily to search them
autocmd QuickFixCmdPre *grep* let s:tagbar_qf_active = 1
autocmd QuickFixCmdPost *grep* if exists('s:tagbar_qf_active') |
\ unlet s:tagbar_qf_active |
\ endif
autocmd VimEnter * call s:CorrectFocusOnStartup()
augroup END
let s:autocommands_done = 1
endfunction
" s:CheckForExCtags() {{{2
" Test whether the ctags binary is actually Exuberant Ctags and not BSD ctags
" (or something else)
function! s:CheckForExCtags(silent) abort
call tagbar#debug#log('Checking for Exuberant Ctags')
if !exists('g:tagbar_ctags_bin')
let ctagsbins = []
let ctagsbins += ['ctags-exuberant'] " Debian
let ctagsbins += ['exuberant-ctags']
let ctagsbins += ['exctags'] " FreeBSD, NetBSD
let ctagsbins += ['/usr/local/bin/ctags'] " Homebrew
let ctagsbins += ['/opt/local/bin/ctags'] " Macports
let ctagsbins += ['ectags'] " OpenBSD
let ctagsbins += ['ctags']
let ctagsbins += ['ctags.exe']
let ctagsbins += ['tags']
let ctagsbins += ['universal-ctags']
for ctags in ctagsbins
if executable(ctags)
let g:tagbar_ctags_bin = ctags
break
endif
endfor
if !exists('g:tagbar_ctags_bin')
let l:errmsg = 'Tagbar: Exuberant ctags not found!'
let l:infomsg = 'Please download Exuberant Ctags from' .
\ ' ctags.sourceforge.net and install it in a' .
\ ' directory in your $PATH or set g:tagbar_ctags_bin.'
call s:CtagsErrMsg(l:errmsg, l:infomsg, a:silent)
let s:checked_ctags = 2
return 0
endif
else
" reset 'wildignore' temporarily in case *.exe is included in it
let wildignore_save = &wildignore
set wildignore&
let g:tagbar_ctags_bin = expand(g:tagbar_ctags_bin)
let &wildignore = wildignore_save
if !executable(g:tagbar_ctags_bin)
let l:errmsg = 'Tagbar: Exuberant ctags not found at ' .
\ "'" . g:tagbar_ctags_bin . "'!"
let l:infomsg = 'Please check your g:tagbar_ctags_bin setting.'
call s:CtagsErrMsg(l:errmsg, l:infomsg, a:silent)
let s:checked_ctags = 2
return 0
endif
endif
let ctags_cmd = s:EscapeCtagsCmd(g:tagbar_ctags_bin, '--version')
if ctags_cmd ==# ''
let s:checked_ctags = 2
return 0
endif
let ctags_output = s:ExecuteCtags(ctags_cmd)
call tagbar#debug#log("Command output:\n" . ctags_output)
call tagbar#debug#log('Exit code: ' . v:shell_error)
if v:shell_error || ctags_output !~# '\(Exuberant\|Universal\) Ctags'
let l:errmsg = 'Tagbar: Ctags doesn''t seem to be Exuberant Ctags!'
let l:infomsg = 'BSD ctags will NOT WORK.' .
\ ' Please download Exuberant Ctags from ctags.sourceforge.net' .
\ ' and install it in a directory in your $PATH' .
\ ' or set g:tagbar_ctags_bin.'
call s:CtagsErrMsg(l:errmsg, l:infomsg, a:silent,
\ ctags_cmd, ctags_output, v:shell_error)
let s:checked_ctags = 2
return 0
elseif !s:CheckExCtagsVersion(ctags_output)
let l:errmsg = 'Tagbar: Exuberant Ctags is too old!'
let l:infomsg = 'You need at least version 5.5 for Tagbar to work.' .
\ ' Please download a newer version from ctags.sourceforge.net.'
call s:CtagsErrMsg(l:errmsg, l:infomsg, a:silent, ctags_cmd, ctags_output)
let s:checked_ctags = 2
return 0
else
let s:checked_ctags = 1
return 1
endif
endfunction
" s:CtagsErrMsg() {{{2
function! s:CtagsErrMsg(errmsg, infomsg, silent, ...) abort
call tagbar#debug#log(a:errmsg)
let ctags_cmd = a:0 > 0 ? a:1 : ''
let ctags_output = a:0 > 1 ? a:2 : ''
let exit_code_set = a:0 > 2
if exit_code_set
let exit_code = a:3
endif
if !a:silent
call s:warning(a:errmsg)
echomsg a:infomsg
if ctags_cmd ==# ''
return
endif
echomsg 'Executed command: "' . ctags_cmd . '"'
if ctags_output !=# ''
echomsg 'Command output:'
for line in split(ctags_output, '\n')
echomsg line
endfor
else
echomsg 'Command output is empty.'
endif
if exit_code_set
echomsg 'Exit code: ' . exit_code
endif
endif
endfunction
" s:CheckExCtagsVersion() {{{2
function! s:CheckExCtagsVersion(output) abort
call tagbar#debug#log('Checking Exuberant Ctags version')
if a:output =~? 'Universal Ctags'
call tagbar#debug#log('Found Universal Ctags, assuming compatibility')
let s:ctags_is_uctags = 1
return 1
endif
if a:output =~? 'Exuberant Ctags compatiable PHP enhancement'
call tagbar#debug#log('Found phpctags, assuming compatibility')
return 1
endif
if a:output =~? 'Exuberant Ctags Development'
call tagbar#debug#log('Found development version, assuming compatibility')
return 1
endif
let matchlist = matchlist(a:output, '\vExuberant Ctags (\d+)\.(\d+)')
let major = matchlist[1]
let minor = matchlist[2]
call tagbar#debug#log("Ctags version: major='" . major . "', minor='" . minor . "'")
return major >= 6 || (major == 5 && minor >= 5)
endfunction
" s:CheckFTCtags() {{{2
function! s:CheckFTCtags(bin, ftype) abort
if executable(a:bin)
return a:bin
endif
if exists('g:tagbar_type_' . a:ftype)
let userdef = g:tagbar_type_{a:ftype}
if has_key(userdef, 'ctagsbin')
return userdef.ctagsbin
else
return ''
endif
endif
return ''
endfunction
" s:GetSupportedFiletypes() {{{2
function! s:GetSupportedFiletypes() abort
call tagbar#debug#log('Getting filetypes supported by Exuberant Ctags')
let ctags_cmd = s:EscapeCtagsCmd(g:tagbar_ctags_bin, '--list-languages')
if ctags_cmd ==# ''
return
endif
let ctags_output = s:ExecuteCtags(ctags_cmd)
if v:shell_error
" this shouldn't happen as potential problems would have already been
" caught by the previous ctags checking
return
endif
let types = split(ctags_output, '\n\+')
let supported_types = {}
for type in types
if match(type, '\[disabled\]') == -1
let supported_types[tolower(type)] = 1
endif
endfor
return supported_types
endfunction
" Known files {{{1
let s:known_files = {
\ '_files' : {}
\ }
" s:known_files.get() {{{2
function! s:known_files.get(fname) abort dict
return get(self._files, a:fname, {})
endfunction
" s:known_files.put() {{{2
" Optional second argument is the filename
function! s:known_files.put(fileinfo, ...) abort dict
if a:0 == 1
let self._files[a:1] = a:fileinfo
else
let fname = a:fileinfo.fpath
let self._files[fname] = a:fileinfo
endif
endfunction
" s:known_files.has() {{{2
function! s:known_files.has(fname) abort dict
return has_key(self._files, a:fname)
endfunction
" s:known_files.rm() {{{2
function! s:known_files.rm(fname) abort dict
if s:known_files.has(a:fname)
call tagbar#debug#log('Removing fileinfo for [' . a:fname . ']')
call remove(self._files, a:fname)
endif
endfunction
" Window management {{{1
" s:ToggleWindow() {{{2
function! s:ToggleWindow(flags) abort
call tagbar#debug#log('ToggleWindow called')
let tagbarwinnr = bufwinnr(s:TagbarBufName())
if tagbarwinnr != -1
call s:CloseWindow()
return
endif
call s:OpenWindow(a:flags)
call tagbar#debug#log('ToggleWindow finished')
endfunction
" s:OpenWindow() {{{2
function! s:OpenWindow(flags) abort
call tagbar#debug#log("OpenWindow called with flags: '" . a:flags . "'")
let autofocus = a:flags =~# 'f'
let jump = a:flags =~# 'j'
let autoclose = a:flags =~# 'c'
let curfile = fnamemodify(bufname('%'), ':p')
let curline = line('.')
" If the tagbar window is already open check jump flag
" Also set the autoclose flag if requested
let tagbarwinnr = bufwinnr(s:TagbarBufName())
if tagbarwinnr != -1
if winnr() != tagbarwinnr && jump
call s:goto_win(tagbarwinnr)
call s:HighlightTag(g:tagbar_autoshowtag != 2, 1, curline)
endif
call tagbar#debug#log('OpenWindow finished, Tagbar already open')
return
endif
" Use the window ID if the functionality exists, this is more reliable
" since the window number can change due to the Tagbar window opening
if exists('*win_getid')
let prevwinid = win_getid()
if winnr('$') > 1
call s:goto_win('p', 1)
let pprevwinid = win_getid()
call s:goto_win('p', 1)
endif
else
let prevwinnr = winnr()
if winnr('$') > 1
call s:goto_win('p', 1)
let pprevwinnr = winnr()
call s:goto_win('p', 1)
endif
endif
" This is only needed for the CorrectFocusOnStartup() function
let s:last_autofocus = autofocus
if !s:Init(0)
return 0
endif
" Expand the Vim window to accommodate for the Tagbar window if requested
" and save the window positions to be able to restore them later.
if g:tagbar_expand >= 1 && !s:window_expanded &&
\ (has('gui_running') || g:tagbar_expand == 2)
let s:window_pos.pre.x = getwinposx()
let s:window_pos.pre.y = getwinposy()
let &columns += g:tagbar_width + 1
let s:window_pos.post.x = getwinposx()
let s:window_pos.post.y = getwinposy()
let s:window_expanded = 1
endif
let s:window_opening = 1
if g:tagbar_position =~# 'vertical'
let size = g:tagbar_width
let mode = 'vertical '
else
let size = g:tagbar_height
let mode = ''
endif
exe 'silent keepalt ' . g:tagbar_position . size . 'split ' . s:TagbarBufName()
exe 'silent ' . mode . 'resize ' . size
unlet s:window_opening
call s:InitWindow(autoclose)
" If the current file exists, but is empty, it means that it had a
" processing error before opening the window, most likely due to a call to
" currenttag() in the statusline. Remove the entry so an error message
" will be shown if the processing still fails.
if empty(s:known_files.get(curfile))
call s:known_files.rm(curfile)
endif
call s:AutoUpdate(curfile, 0)
call s:HighlightTag(g:tagbar_autoshowtag != 2, 1, curline)
if !(g:tagbar_autoclose || autofocus || g:tagbar_autofocus)
if exists('*win_getid')
if exists('pprevwinid')
noautocmd call win_gotoid(pprevwinid)
endif
call win_gotoid(prevwinid)
else
" If the Tagbar winnr is identical to one of the saved values
" then that means that the window numbers have changed.
" Just jump back to the previous window since we won't be able to
" restore the window history.
if winnr() == prevwinnr
\ || (exists('pprevwinnr') && winnr() == pprevwinnr)
call s:goto_win('p')
else
if exists('pprevwinnr')
call s:goto_win(pprevwinnr, 1)
endif
call s:goto_win(prevwinnr)
endif
endif
endif
call tagbar#debug#log('OpenWindow finished')
endfunction
" s:InitWindow() {{{2
function! s:InitWindow(autoclose) abort
call tagbar#debug#log('InitWindow called with autoclose: ' . a:autoclose)
" Buffer-local options
setlocal filetype=tagbar
setlocal noreadonly " in case the "view" mode is used
setlocal buftype=nofile
setlocal bufhidden=hide
setlocal noswapfile
setlocal nobuflisted
setlocal nomodifiable
setlocal textwidth=0
if g:tagbar_scrolloff > 0
execute 'setlocal scrolloff=' . g:tagbar_scrolloff
endif
if g:tagbar_show_balloon == 1 && has('balloon_eval')
setlocal balloonexpr=TagbarBalloonExpr()
set ballooneval
endif
" Window-local options
setlocal nolist
setlocal winfixwidth
setlocal nospell
if g:tagbar_wrap == 0
setlocal nowrap
else
setlocal wrap
if exists('+linebreak')
setlocal breakindent
setlocal breakindentopt=shift:4
endif
endif
if g:tagbar_show_linenumbers == 0
setlocal nonumber
if exists('+relativenumber')
setlocal norelativenumber
endif
elseif g:tagbar_show_linenumbers == 1
setlocal number
elseif g:tagbar_show_linenumbers == 2
setlocal relativenumber
else
set number<
if exists('+relativenumber')
set relativenumber<
endif
endif
setlocal nofoldenable
setlocal foldcolumn=0
" Reset fold settings in case a plugin set them globally to something
" expensive. Apparently 'foldexpr' gets executed even if 'foldenable' is
" off, and then for every appended line (like with :put).
setlocal foldmethod&
setlocal foldexpr&
silent! setlocal signcolumn=no
let w:autoclose = a:autoclose
call s:SetStatusLine()
let s:new_window = 1
let cpoptions_save = &cpoptions
set cpoptions&vim
if !exists('b:tagbar_mapped_keys')
call s:MapKeys()
endif
let &cpoptions = cpoptions_save
if g:tagbar_expand
let s:expand_bufnr = bufnr('%')
endif
call tagbar#debug#log('InitWindow finished')
endfunction
" s:CloseWindow() {{{2
function! s:CloseWindow() abort
call tagbar#debug#log('CloseWindow called')
let tagbarwinnr = bufwinnr(s:TagbarBufName())
if tagbarwinnr == -1
return
endif
" Close the preview window if it was opened by us
if s:pwin_by_tagbar
pclose
let tagbarwinnr = bufwinnr(s:TagbarBufName())
endif
if winnr() == tagbarwinnr
if winbufnr(2) != -1
" Other windows are open, only close the tagbar one
let curfile = tagbar#state#get_current_file(0)
let s:is_maximized = 0
close
" Try to jump to the correct window after closing
call s:goto_win('p')
if !empty(curfile)
let filebufnr = bufnr(curfile.fpath)
if bufnr('%') != filebufnr
let filewinnr = bufwinnr(filebufnr)
if filewinnr != -1
call s:goto_win(filewinnr)
endif
endif
endif
endif
else
" Go to the tagbar window, close it and then come back to the original
" window. Save a win-local variable in the original window so we can
" jump back to it even if the window number changed.
call s:mark_window()
call s:goto_win(tagbarwinnr)
close
call s:goto_markedwin()
endif
call s:ShrinkIfExpanded()
if s:autocommands_done && !s:statusline_in_use
call tagbar#StopAutoUpdate()
endif
call tagbar#debug#log('CloseWindow finished')
endfunction
" s:ShrinkIfExpanded() {{{2
" If the Vim window has been expanded, and Tagbar is not open in any other
" tabpages, shrink the window again
function! s:ShrinkIfExpanded() abort
if !s:window_expanded || &filetype ==# 'tagbar' || s:expand_bufnr == -1
return
endif
let tablist = []
for i in range(tabpagenr('$'))
call extend(tablist, tabpagebuflist(i + 1))
endfor
if index(tablist, s:expand_bufnr) == -1
let &columns -= g:tagbar_width + 1
let s:window_expanded = 0
let s:expand_bufnr = -1
" Only restore window position if it is available and if the
" window hasn't been moved manually after the expanding
if getwinposx() != -1 &&
\ getwinposx() == s:window_pos.post.x &&
\ getwinposy() == s:window_pos.post.y
execute 'winpos ' . s:window_pos.pre.x .
\ ' ' . s:window_pos.pre.y
endif
endif
" The window sizes may have changed due to the shrinking happening after
" the window closing, so equalize them again.
if &equalalways
wincmd =
endif
endfunction
" s:ZoomWindow() {{{2
function! s:ZoomWindow() abort
if s:is_maximized
execute 'vertical resize ' . g:tagbar_width
execute s:winrestcmd
let s:is_maximized = 0
else
let s:winrestcmd = winrestcmd()
if g:tagbar_zoomwidth == 1
vertical resize
elseif g:tagbar_zoomwidth == 0
let func = exists('*strdisplaywidth') ? 'strdisplaywidth' : 'strlen'
let maxline = max(map(getline(line('w0'), line('w$')),
\ func . '(v:val)'))
execute 'vertical resize ' . maxline
elseif g:tagbar_zoomwidth > 1
execute 'vertical resize ' . g:tagbar_zoomwidth
endif
let s:is_maximized = 1
endif
endfunction
" s:CorrectFocusOnStartup() {{{2
" For whatever reason the focus will be on the Tagbar window if
" tagbar#autoopen is used with a FileType autocommand on startup and
" g:tagbar_left is set. This should work around it by jumping to the window of
" the current file after startup.
function! s:CorrectFocusOnStartup() abort
if bufwinnr(s:TagbarBufName()) != -1 && !g:tagbar_autofocus && !s:last_autofocus
let curfile = tagbar#state#get_current_file(1)
if !empty(curfile) && curfile.fpath != fnamemodify(bufname('%'), ':p')
let winnr = bufwinnr(curfile.fpath)
if winnr != -1
call s:goto_win(winnr)
endif
endif
endif
endfunction
" Tag processing {{{1
" s:ProcessFile() {{{2
" Execute ctags and put the information into a 'FileInfo' object
function! s:ProcessFile(fname, ftype) abort
call tagbar#debug#log('ProcessFile called [' . a:fname . ']')
if !s:IsValidFile(a:fname, a:ftype)
call tagbar#debug#log('Not a valid file, returning')
return
endif
let l:bufnum = bufnr(a:fname)
if !bufloaded(l:bufnum)
call tagbar#debug#log('[ProcessFile] Buffer is not loaded exiting...')
return
endif
if !bufexists(l:bufnum)
call tagbar#debug#log('[ProcessFile] Buffer does not exist exiting...')
return
endif
let typeinfo = s:known_types[a:ftype]
" If the file has only been updated preserve the fold states, otherwise
" create a new entry
if s:known_files.has(a:fname) && !empty(s:known_files.get(a:fname)) &&
\ s:known_files.get(a:fname).ftype == a:ftype
let fileinfo = s:known_files.get(a:fname)
let typeinfo = fileinfo.typeinfo
call fileinfo.reset()
else
if exists('#TagbarProjects#User')
execute 'doautocmd <nomodeline> TagbarProjects User ' . a:fname
if exists('b:tagbar_type')
let typeinfo = extend(copy(typeinfo),
\ s:TransformUserTypeDef(b:tagbar_type))
call typeinfo.createKinddict()
endif
endif
let fileinfo = tagbar#prototypes#fileinfo#new(a:fname, a:ftype, typeinfo)
endif
call tagbar#debug#log('typeinfo for file to process: ' . string(typeinfo))
if g:tagbar_file_size_limit > 0
\ && fileinfo.fsize > g:tagbar_file_size_limit
\ && !exists('b:tagbar_force_update')
call tagbar#debug#log('File size exceeds defined limit')
let fileinfo.fsize_exceeded = 1
call s:known_files.put(fileinfo)
return
elseif g:tagbar_use_cache
" Use a temporary files for ctags processing instead of the original one.
" This allows using Tagbar for files accessed with netrw, and also doesn't
" slow down Tagbar for files that sit on slow network drives.
let tempfile = tempname()
let ext = fnamemodify(fileinfo.fpath, ':e')
if ext !=# ''
let tempfile .= '.' . ext
endif
call tagbar#debug#log('Caching file into: ' . tempfile)
let templines = getbufline(fileinfo.bufnr, 1, '$')
let res = writefile(templines, tempfile)
if res != 0
call tagbar#debug#log('Could not create copy '.tempfile)
return
endif
let fileinfo.mtime = getftime(tempfile)
let fileinfo.fsize_exceeded = 0
let ctags_output = s:ExecuteCtagsOnFile(tempfile, a:fname, typeinfo)
if !tagbar#debug#enabled()
call delete(tempfile)
endif
else
call tagbar#debug#log('File caching disabled')
let fileinfo.fsize_exceeded = 0
let ctags_output = s:ExecuteCtagsOnFile(a:fname, a:fname, typeinfo)
endif
if ctags_output == -1
call tagbar#debug#log('Ctags error when processing file')
" Put an empty entry into known_files so the error message is only
" shown once
call s:known_files.put({}, a:fname)
return
elseif ctags_output ==# ''
call tagbar#debug#log('Ctags output empty')
" No need to go through the tag processing if there are no tags, and
" preserving the old fold state isn't necessary either
call s:known_files.put(tagbar#prototypes#fileinfo#new(a:fname, a:ftype,
\ s:known_types[a:ftype]), a:fname)
return
endif
call tagbar#debug#log('Filetype tag kinds: ' . string(keys(typeinfo.kinddict)))
" Parse the ctags output lines
call tagbar#debug#log('Parsing ctags output')
let rawtaglist = split(ctags_output, '\n\+')
let seen = {}
for line in rawtaglist
" skip comments and duplicates (can happen when --sort=no)
if line =~# '^!_TAG_' || has_key(seen, line)
continue
endif
let seen[line] = 1
let parts = split(line, ';"')
if len(parts) == 2 " Is a valid tag line
call s:ParseTagline(parts[0], parts[1], typeinfo, fileinfo)
endif
endfor
" Create a placeholder tag for the 'kind' header for folding purposes, but
" only for non-scoped tags
for kind in typeinfo.kinds
if has_key(get(typeinfo, 'kind2scope', {}), kind.short)
continue
endif
let curtags = filter(copy(fileinfo.getTags()),
\ 'v:val.fields.kind ==# kind.short && ' .
\ '!has_key(v:val, "scope")')
call tagbar#debug#log('Processing kind: ' . kind.short .
\ ', number of tags: ' . len(curtags))
if empty(curtags)
continue
endif
let kindtag = tagbar#prototypes#kindheadertag#new(kind.long)
let kindtag.short = kind.short
let kindtag.numtags = len(curtags)
let kindtag.fileinfo = fileinfo
for tag in curtags
let tag.parent = kindtag
endfor
endfor
" Clear old folding information from previous file version to prevent leaks
call fileinfo.clearOldFolds()
" Sort the tags
call fileinfo.sortTags(typeinfo)
call s:known_files.put(fileinfo)
endfunction
" s:ExecuteCtagsOnFile() {{{2
function! s:ExecuteCtagsOnFile(fname, realfname, typeinfo) abort
call tagbar#debug#log('ExecuteCtagsOnFile called [' . a:fname . ']')
if has_key(a:typeinfo, 'ctagsargs') && type(a:typeinfo.ctagsargs) == type('')
" if ctagsargs is a string, prepend and append space separators
let ctags_args = ' ' . a:typeinfo.ctagsargs . ' '
elseif has_key(a:typeinfo, 'ctagsargs') && type(a:typeinfo.ctagsargs) == type([])
let ctags_args = a:typeinfo.ctagsargs
" otherwise ctagsargs is not defined or not defined as a valid type
else
"Prefer constructing ctags_args as a list rather than a string
"See s:EscapeCtagsCmd() - It's a best practice to shellescape()
"each arg separately because in special cases where space is
"intended to be in an argument, spaces in a single ctag_args
"string would be ambiguous. Is the space an argument separator
"or to be included in the argument
let ctags_args = []
if exists('g:tagbar_ctags_options')
for value in g:tagbar_ctags_options
call add(ctags_args, '--options='.value)
endfor
endif
" universal-ctags deprecated this argument name
if s:ctags_is_uctags
let ctags_args += [ '--extras=+F' ]
else
let ctags_args += [ '--extra=', '--file-scope=yes' ]
endif
let ctags_args = ctags_args + [
\ '-f',
\ '-',
\ '--format=2',
\ '--excmd=pattern',
\ '--fields=nksSafet',
\ '--sort=no',
\ '--append=no'
\ ]
" verbose if debug enabled
if tagbar#debug#enabled()
let ctags_args += [ '-V' ]
endif
" Include extra type definitions
if has_key(a:typeinfo, 'deffile')
let ctags_args += ['--options=' . expand(a:typeinfo.deffile)]
endif
" Third-party programs may not necessarily make use of this
if has_key(a:typeinfo, 'ctagstype')
let ctags_type = a:typeinfo.ctagstype
let ctags_kinds = ''
for kind in a:typeinfo.kinds
if kind.short !=# '?'
let ctags_kinds .= kind.short
endif
endfor
let ctags_args += ['--language-force=' . ctags_type]
let ctags_args += ['--' . ctags_type . '-kinds=' . ctags_kinds]
endif
endif
if has_key(a:typeinfo, 'ctagsbin')
" reset 'wildignore' temporarily in case *.exe is included in it
let wildignore_save = &wildignore
set wildignore&
let ctags_bin = expand(a:typeinfo.ctagsbin)
let &wildignore = wildignore_save
else
let ctags_bin = g:tagbar_ctags_bin
endif
let ctags_cmd = s:EscapeCtagsCmd(ctags_bin, ctags_args, a:fname)
if ctags_cmd ==# ''
return ''
endif
let ctags_output = s:ExecuteCtags(ctags_cmd)
if v:shell_error || ctags_output =~? 'Warning: cannot open \(source\|input\) file'
call tagbar#debug#log('Command output:')
call tagbar#debug#log(ctags_output)
call tagbar#debug#log('Exit code: ' . v:shell_error)
" Only display an error message if the Tagbar window is open and we
" haven't seen the error before.
if bufwinnr(s:TagbarBufName()) != -1 &&
\ (!s:known_files.has(a:realfname) ||
\ !empty(s:known_files.get(a:realfname)))
call s:warning('Tagbar: Could not execute ctags for ' . a:realfname . '!')
echomsg 'Executed command: "' . ctags_cmd . '"'
if !empty(ctags_output)
echomsg 'Command output:'
for line in split(ctags_output, '\n')
echomsg line
endfor
endif
echomsg 'Exit code: ' . v:shell_error
endif
return -1
endif
call tagbar#debug#log('Ctags executed successfully')
call tagbar#debug#log_ctags_output(ctags_output)
return ctags_output
endfunction
" s:ParseTagline() {{{2
" Structure of a tag line:
" tagname<TAB>filename<TAB>expattern;"fields
" fields: <TAB>name:value
" fields that are always present: kind, line
function! s:ParseTagline(part1, part2, typeinfo, fileinfo) abort
let basic_info = split(a:part1, '\t')
let tagname = basic_info[0]
let filename = basic_info[1]
" the pattern can contain tabs and thus may have been split up, so join
" the rest of the items together again
let pattern = join(basic_info[2:], "\t")
if pattern[0] ==# '/'
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 = '\V\^\C' . strpart(pattern, start, end - start) . dollar
else
let pattern = ''
endif
" When splitting fields make sure not to create empty keys or values in
" case a value illegally contains tabs
let fields = split(a:part2, '^\t\|\t\ze\w\+:')
let fielddict = {}
if fields[0] !~# ':'
let fielddict.kind = remove(fields, 0)
endif
for field in fields
" can't use split() since the value can contain ':'
let delimit = stridx(field, ':')
let key = strpart(field, 0, delimit)
" Remove all tabs that may illegally be in the value
let val = substitute(strpart(field, delimit + 1), '\t', '', 'g')
" File-restricted scoping
if key ==# 'file'
let fielddict[key] = 'yes'
endif
if len(val) > 0
if key ==# 'line' || key ==# 'column' || key ==# 'end'
let fielddict[key] = str2nr(val)
else
let fielddict[key] = val
endif
endif
endfor
" If the tag covers multiple scopes, split it up and create individual tags
" for each scope so that the hierarchy can be displayed correctly.
" This can happen with PHP's 'namespace' tags in uctags, for example.
if has_key(a:typeinfo, 'kind2scope') && has_key(a:typeinfo.kind2scope, fielddict.kind)
\ && tagname =~# '\V' . escape(a:typeinfo.sro, '\')
let tagparts = split(tagname, '\V' . escape(a:typeinfo.sro, '\'))
let scope = a:typeinfo.kind2scope[fielddict.kind]
if has_key(fielddict, scope)
let parent = fielddict[scope]
else
let parent = ''
endif
let curfielddict = fielddict
for i in range(len(tagparts))
let part = tagparts[i]
call s:ProcessTag(part, filename, pattern, curfielddict,
\ i != len(tagparts) - 1, a:typeinfo, a:fileinfo)
if parent !=# ''
let parent = parent . a:typeinfo.sro . part
else
let parent = part
endif
let curfielddict = copy(fielddict)
let curfielddict[scope] = parent
endfor
else
call s:ProcessTag(tagname, filename, pattern, fielddict, 0,
\ a:typeinfo, a:fileinfo)
endif
endfunction
" s:ProcessTag() {{{2
function! s:ProcessTag(name, filename, pattern, fields, is_split, typeinfo, fileinfo) abort
if a:is_split
let taginfo = tagbar#prototypes#splittag#new(a:name)
else
let taginfo = tagbar#prototypes#normaltag#new(a:name)
endif
let taginfo.file = a:filename
let taginfo.pattern = a:pattern
call extend(taginfo.fields, a:fields)
" Needed for jsctags
if has_key(taginfo.fields, 'lineno')
let taginfo.fields.line = str2nr(taginfo.fields.lineno)
endif
" Do some sanity checking in case ctags reports invalid line numbers
if taginfo.fields.line < 0
let taginfo.fields.line = 0
endif
" Make sure our 'end' is valid
if taginfo.fields.end < taginfo.fields.line
if a:typeinfo.getKind(taginfo.fields.kind).stl
" the config indicates this is a scoped kind due to 'stl', but we
" don't have scope vars, assume scope goes to end of file. This
" can also be the case for exhuberant ctags which doesn't support
" the --fields=e option.
" When we call the GetNearbyTag(), it will look up for the nearest
" tag, so if we have multiples that have scope to the end of the
" file it will still only grab the first one above the current line
let taginfo.fields.end = line('$')
else
let taginfo.fields.end = taginfo.fields.line
endif
endif
if !has_key(taginfo.fields, 'kind')
call tagbar#debug#log(
\ "Warning: No 'kind' field found for tag " . a:name[0] . '!')
if index(s:warnings.type, a:typeinfo.ftype) == -1
call s:warning("No 'kind' field found for tag " . a:name[0] . '!' .
\ " Please read the last section of ':help tagbar-extend'.")
call add(s:warnings.type, a:typeinfo.ftype)
endif
return
endif
let taginfo.fileinfo = a:fileinfo
let taginfo.typeinfo = a:typeinfo
let a:fileinfo.fline[taginfo.fields.line] = taginfo
if has_key(taginfo.fields, 'typeref')
let typeref = taginfo.fields.typeref
let delimit = stridx(typeref, ':')
let key = strpart(typeref, 0, delimit)
if key ==# 'typename'
let taginfo.data_type = substitute(strpart(typeref, delimit + 1), '\t', '', 'g')
else
let taginfo.data_type = key
endif
endif
" If this filetype doesn't have any scope information then we can stop
" here after adding the tag to the list
if !has_key(a:typeinfo, 'scope2kind')
call a:fileinfo.addTag(taginfo)
return
endif
" Make some information easier accessible
for scope in keys(a:typeinfo.scope2kind)
if has_key(taginfo.fields, scope)
let taginfo.scope = scope
let taginfo.path = taginfo.fields[scope]
let taginfo.fullpath = taginfo.path . a:typeinfo.sro .
\ taginfo.name
break
endif
endfor
let pathlist = split(taginfo.path, '\V' . escape(a:typeinfo.sro, '\'))
let taginfo.depth = len(pathlist)
" Needed for folding
try
call taginfo.initFoldState(s:known_files)
catch /^Vim(\a\+):E716:/ " 'Key not present in Dictionary'
" The tag has a 'kind' that doesn't exist in the type definition
call tagbar#debug#log('Warning: Unknown tag kind: ' . taginfo.fields.kind)
if index(s:warnings.type, a:typeinfo.ftype) == -1
call s:warning('Unknown tag kind encountered: ' .
\ '"' . taginfo.fields.kind . '".' .
\ ' Your ctags and Tagbar configurations are out of sync!' .
\ ' Please read '':help tagbar-extend''.')
call add(s:warnings.type, a:typeinfo.ftype)
endif
return
endtry
call s:add_tag_recursive({}, taginfo, pathlist)
endfunction
" s:add_tag_recursive() {{{2
" Add a tag recursively as a child of its parent, or if there is no parent, to
" the root tag list in the fileinfo object.
function! s:add_tag_recursive(parent, taginfo, pathlist) abort
" If the pathlist is empty we are at the correct scope for the current tag
if empty(a:pathlist)
" If a child tag got processed before a parent tag then there will
" be a pseudotag here as a placeholder. Copy the children over and
" then replace the pseudotag with the real one.
let pseudotags = []
if empty(a:parent)
let name_siblings = a:taginfo.fileinfo.getTagsByName(a:taginfo.name)
else
let name_siblings = a:parent.getChildrenByName(a:taginfo.name)
endif
" Consider a tag as replaceable if the current tag is considered to
" have more appropriate information
for tag in name_siblings
if (tag.fields.kind ==# '?'
\ || tag.fields.kind ==# a:taginfo.fields.kind)
\ && (tag.isPseudoTag()
\ || (!a:taginfo.isSplitTag() && tag.isSplitTag()))
call add(pseudotags, tag)
endif
endfor
if len(pseudotags) == 1
let pseudotag = pseudotags[0]
for child in pseudotag.getChildren()
call a:taginfo.addChild(child)
let child.parent = a:taginfo
endfor
if empty(a:parent)
call a:taginfo.fileinfo.removeTag(pseudotag)
else
call a:parent.removeChild(pseudotag)
endif
elseif len(pseudotags) > 1
echoerr 'Tagbar: Found duplicate pseudotag; this should never happen!'
\ 'Please contact the script maintainer with an example.'
\ 'Pseudotag name:' pseudotag.name
endif
" If this is a tag that got created due to splitting up a tag name,
" don't replace existing tags of the same kind.
if a:taginfo.isSplitTag()
for tag in name_siblings
if tag.fields.kind ==# a:taginfo.fields.kind
return
endif
endfor
endif
if empty(a:parent)
call a:taginfo.fileinfo.addTag(a:taginfo)
else
call a:parent.addChild(a:taginfo)
let a:taginfo.parent = a:parent
endif
return
endif
" There is still at least one more scope between the current one and the
" one of the current tag, so we have to either find or create the
" intermediate tags
let grandparent = a:parent
let parentname = remove(a:pathlist, 0)
if empty(grandparent)
let name_siblings = a:taginfo.fileinfo.getTagsByName(parentname)
else
let name_siblings = grandparent.getChildrenByName(parentname)
endif
if empty(a:pathlist)
" If the current tag is a direct child of the parent we're looking for
" then we can also filter the parents based on the scope information
let parents = []
for tag in name_siblings
if tag.fields.kind ==# '?'
\ || get(a:taginfo.typeinfo.kind2scope, tag.fields.kind, '') ==# a:taginfo.scope
call add(parents, tag)
endif
endfor
else
let parents = name_siblings
endif
if empty(parents)
" No parents found, so either the parent is a pseudotag or it hasn't
" been processed yet. Create a pseudotag as a placeholder; if the
" actual parent gets processed later it will get replaced.
if empty(a:pathlist)
let pseudokind = a:taginfo.typeinfo.scope2kind[a:taginfo.scope]
else
let pseudokind = '?'
endif
let parent = s:create_pseudotag(parentname, grandparent,
\ pseudokind, a:taginfo.typeinfo, a:taginfo.fileinfo)
if empty(grandparent)
call a:taginfo.fileinfo.addTag(parent)
else
call grandparent.addChild(parent)
endif
elseif len(parents) == 1
let parent = parents[0]
else
" If there are multiple possible parents (c.f. issue #139, or tags
" with the same name but a different kind) then we will pick the one
" that is closest above the current tag as a heuristic.
" Start at line 0 so that pseudotags get included
let minline = 0
for candidate in parents
" If the line number of the current tag is 0 then we have no way
" of determining the best candidate by comparing line numbers.
" Just use the first one we have.
if a:taginfo.fields.line == 0
let parent = candidate
break
endif
if candidate.fields.line <= a:taginfo.fields.line &&
\ candidate.fields.line >= minline
let parent = candidate
let minline = candidate.fields.line
endif
endfor
if !exists('parent')
" If we still haven't found a parent it must be below the current
" tag, so find the closest parent below the tag. This can happen
" for example in Go.
let maxline = line('$')
for candidate in parents
if candidate.fields.line >= a:taginfo.fields.line &&
\ candidate.fields.line <= maxline
let parent = candidate
let maxline = candidate.fields.line
endif
endfor
endif
endif
" If the parent is a pseudotag it may have gotten created as an in-between
" tag without proper information about its kind because all if its
" children are also pseudotags, so it may be incorrect. If the current tag
" is a direct child of a pseudotag then we can derive the correct kind, so
" replace it if necessary.
if parent.isPseudoTag() && empty(a:pathlist)
let parentkind = a:taginfo.typeinfo.scope2kind[a:taginfo.scope]
if parent.fields.kind ==# '?' || parentkind !=# parent.fields.kind
let parent.fields.kind = parentkind
call parent.initFoldState(s:known_files)
endif
endif
call s:add_tag_recursive(parent, a:taginfo, a:pathlist)
endfunction
" s:create_pseudotag() {{{2
function! s:create_pseudotag(name, parent, kind, typeinfo, fileinfo) abort
if !empty(a:parent)
let curpath = a:parent.fullpath
" If the kind is not present in the kind2scope dictionary, return an
" empty scope. This can happen due to incorrect ctags output as in #397.
let pscope = get(a:typeinfo.kind2scope, a:parent.fields.kind, '')
else
let curpath = ''
let pscope = ''
endif
let pseudotag = tagbar#prototypes#pseudotag#new(a:name)
let pseudotag.fields.kind = a:kind
let parentscope = substitute(curpath, '\V' . a:name . '$', '', '')
let parentscope = substitute(parentscope,
\ '\V\^' . escape(a:typeinfo.sro, '\') . '\$', '', '')
if pscope !=# ''
let pseudotag.fields[pscope] = parentscope
let pseudotag.scope = pscope
let pseudotag.path = parentscope
let pseudotag.fullpath =
\ pseudotag.path . a:typeinfo.sro . pseudotag.name
endif
let pseudotag.depth = len(split(pseudotag.path, '\V' . escape(a:typeinfo.sro, '\')))
let pseudotag.parent = a:parent
let pseudotag.fileinfo = a:fileinfo
let pseudotag.typeinfo = a:typeinfo
call pseudotag.initFoldState(s:known_files)
return pseudotag
endfunction
" s:ToggleSort() {{{2
function! s:ToggleSort() abort
let fileinfo = tagbar#state#get_current_file(0)
if empty(fileinfo)
return
endif
" Save the tag the cursor is currently on
let curline = line('.')
let taginfo = s:GetTagInfo(curline, 0)
match none
let compare_typeinfo = s:known_types[fileinfo.ftype]
if has_key(compare_typeinfo, 'sort')
let compare_typeinfo.sort = !compare_typeinfo.sort
else
let g:tagbar_sort = !g:tagbar_sort
endif
call fileinfo.sortTags(compare_typeinfo)
call s:RenderContent()
call s:SetStatusLine()
" If we were on a tag before sorting then jump to it, otherwise restore
" the cursor to the current line
if !empty(taginfo)
execute taginfo.tline
else
execute curline
endif
endfunction
" Display {{{1
" s:RenderContent() {{{2
function! s:RenderContent(...) abort
call tagbar#debug#log('RenderContent called')
let s:new_window = 0
if a:0 == 1
let fileinfo = a:1
else
let fileinfo = tagbar#state#get_current_file(0)
endif
if empty(fileinfo)
call tagbar#debug#log('Empty fileinfo, returning')
return
endif
let tagbarwinnr = bufwinnr(s:TagbarBufName())
if &filetype ==# 'tagbar'
let in_tagbar = 1
else
let in_tagbar = 0
let prevwinnr = winnr()
" Get the previous window number, so that we can reproduce
" the window entering history later. Do not run autocmd on
" this command, make sure nothing is interfering.
" let pprevwinnr = winnr('#') " Messes up windows for some reason
call s:goto_win('p', 1)
let pprevwinnr = winnr()
call s:goto_win(tagbarwinnr, 1)
endif
if !empty(tagbar#state#get_current_file(0)) &&
\ fileinfo.fpath ==# tagbar#state#get_current_file(0).fpath
" We're redisplaying the same file, so save the view
call tagbar#debug#log('Redisplaying file [' . fileinfo.fpath . ']')
let saveline = line('.')
let savecol = col('.')
let topline = line('w0')
endif
let lazyredraw_save = &lazyredraw
set lazyredraw
let eventignore_save = &eventignore
set eventignore=all
setlocal modifiable
silent %delete _
call s:PrintHelp()
let typeinfo = fileinfo.typeinfo
if fileinfo.fsize_exceeded == 1
if g:tagbar_compact
silent 0put ='\" File size [' . fileinfo.fsize . 'B] exceeds limit'
else
silent put ='\" File size exceeds defined limit'
silent put ='\" File Size [' . fileinfo.fsize . ' bytes]'
silent put ='\" Limit [' . g:tagbar_file_size_limit . ' bytes]'
silent put ='\" Use TagbarForceUpdate override'
endif
elseif !empty(fileinfo.getTags())
" Print tags
call s:PrintKinds(typeinfo, fileinfo)
else
call tagbar#debug#log('No tags found, skipping printing.')
if g:tagbar_compact && s:short_help
silent 0put ='\" No tags found.'
else
silent put ='\" No tags found.'
endif
endif
" Delete empty lines at the end of the buffer
for linenr in range(line('$'), 1, -1)
if getline(linenr) =~# '^$'
execute 'silent ' . linenr . 'delete _'
else
break
endif
endfor
setlocal nomodifiable
if !empty(tagbar#state#get_current_file(0)) &&
\ fileinfo.fpath ==# tagbar#state#get_current_file(0).fpath
let scrolloff_save = &scrolloff
setlocal scrolloff=0
call cursor(topline, 1)
normal! zt
call cursor(saveline, savecol)
let &l:scrolloff = scrolloff_save
else
" Make sure as much of the Tagbar content as possible is shown in the
" window by jumping to the top after drawing
execute 1
call winline()
" Invalidate highlight cache from old file
let s:last_highlight_tline = 0
endif
let &lazyredraw = lazyredraw_save
let &eventignore = eventignore_save
if !in_tagbar
call s:goto_win(pprevwinnr, 1)
call s:goto_win(prevwinnr, 1)
endif
endfunction
" s:PrintKinds() {{{2
function! s:PrintKinds(typeinfo, fileinfo) abort
call tagbar#debug#log('PrintKinds called')
" If the short or long help is being displayed then the line numbers don't
" match up with the length of the output list
let offset = g:tagbar_compact && s:short_help ? 0 : line('.')
let output = []
for kind in a:typeinfo.kinds
let curtags = filter(copy(a:fileinfo.getTags()),
\ 'v:val.fields.kind ==# kind.short')
call tagbar#debug#log('Printing kind: ' . kind.short .
\ ', number of (top-level) tags: ' . len(curtags))
if empty(curtags)
continue
endif
if has_key(get(a:typeinfo, 'kind2scope', {}), kind.short)
" Scoped tags
for tag in curtags
call s:PrintTag(tag, 0, output, a:fileinfo, a:typeinfo)
if g:tagbar_compact != 1
call add(output, '')
endif
endfor
else
" Non-scoped tags
let kindtag = curtags[0].parent
if kindtag.isFolded()
let foldmarker = g:tagbar#icon_closed
else
let foldmarker = g:tagbar#icon_open
endif
let padding = g:tagbar_show_visibility ? ' ' : ''
if g:tagbar_show_tag_count
let tag_count = ' (' . len(curtags) . ')'
call add(output, foldmarker . padding . kind.long . tag_count)
else
call add(output, foldmarker . padding . kind.long)
endif
let curline = len(output) + offset
let kindtag.tline = curline
let a:fileinfo.tline[curline] = kindtag
if !kindtag.isFolded()
for tag in curtags
let str = tag.strfmt()
call add(output, repeat(' ', g:tagbar_indent) . str)
" Save the current tagbar line in the tag for easy
" highlighting access
let curline = len(output) + offset
let tag.tline = curline
let a:fileinfo.tline[curline] = tag
endfor
endif
if g:tagbar_compact != 1
call add(output, '')
endif
endif
endfor
let outstr = join(output, "\n")
if g:tagbar_compact && !g:tagbar_help_visibility && s:short_help
silent 0put =outstr
else
silent put =outstr
endif
endfunction
" s:PrintTag() {{{2
function! s:PrintTag(tag, depth, output, fileinfo, typeinfo) abort
if g:tagbar_hide_nonpublic &&
\ get(a:tag.fields, 'access', 'public') !=# 'public'
let a:tag.tline = -1
return
endif
" Print tag indented according to depth
let tagstr = repeat(' ', a:depth * g:tagbar_indent) . a:tag.strfmt()
call add(a:output, tagstr)
" Save the current tagbar line in the tag for easy highlighting access
let offset = g:tagbar_compact && s:short_help ? 0 : line('.')
let curline = len(a:output) + offset
let a:tag.tline = curline
let a:fileinfo.tline[curline] = a:tag
" Recursively print children
if a:tag.isFoldable() && !a:tag.isFolded()
for ckind in a:typeinfo.kinds
let childfilter = 'v:val.fields.kind ==# ckind.short'
if g:tagbar_hide_nonpublic
let childfilter .=
\ ' && get(v:val.fields, "access", "public") ==# "public"'
endif
let childtags = filter(copy(a:tag.getChildren()), childfilter)
if len(childtags) > 0
" Print 'kind' header of following children, but only if they
" are not scope-defining tags (since those already have an
" identifier)
if !has_key(a:typeinfo.kind2scope, ckind.short)
let indent = (a:depth + 1) * g:tagbar_indent
let indent += g:tagbar_show_visibility
let indent += 1 " fold symbol
call add(a:output, repeat(' ', indent) . '[' . ckind.long . ']')
" Add basic tag to allow folding when on the header line
let headertag = tagbar#prototypes#basetag#new(ckind.long)
let headertag.parent = a:tag
let headertag.fileinfo = a:tag.fileinfo
let a:fileinfo.tline[len(a:output) + offset] = headertag
endif
for childtag in childtags
call s:PrintTag(childtag, a:depth + 1, a:output,
\ a:fileinfo, a:typeinfo)
endfor
endif
endfor
endif
endfunction
" s:PrintHelp() {{{2
function! s:PrintHelp() abort
if !g:tagbar_compact && !g:tagbar_help_visibility && s:short_help
silent 0put ='\" Press ' . s:get_map_str('help') . ' for help'
silent put _
elseif g:tagbar_help_visibility || !s:short_help
let help_cmds = [
\ ['jump', 'Jump to tag definition'],
\ ['preview', 'As above, but stay in tagbar window'],
\ ['previewwin', 'Show tag in preview window'],
\ ['nexttag', 'Go to next top-level tag'],
\ ['prevtag', 'Go to preveous top-level tag'],
\ ['showproto', 'Display tag prototype'],
\ ['hidenonpublic', 'Hide non-public tags'],
\ ]
let fold_cmds = [
\ ['openfold', 'Open fold'],
\ ['closefold', 'Close fold'],
\ ['togglefold', 'Toggle fold'],
\ ['openallfolds', 'Open all folds'],
\ ['closeallfolds', 'Close all folds'],
\ ['incrementfolds', 'Increment fold level by 1'],
\ ['decrementfolds', 'Decrement fold level by 1'],
\ ['nextfold', 'Go to next fold'],
\ ['prevfold', 'Go to previous fold'],
\ ]
let misc_cmds = [
\ ['togglesort', 'Toggle sort'],
\ ['togglecaseinsensitive', 'Toggle case insensitive sort option'],
\ ['toggleautoclose', 'Toggle autoclose option'],
\ ['togglepause', 'Toggle pause'],
\ ['zoomwin', 'Zoom window in/out'],
\ ['close', 'Close window'],
\ ['help', 'Toggle help'],
\ ]
silent 0put ='\" Tagbar keybindings'
silent put ='\"'
silent put ='\" --------- General ---------'
for [cmd, desc] in help_cmds
if !empty(s:get_map_str(cmd)) | silent put ='\" ' . s:get_map_str(cmd) . ': ' . desc | endif
endfor
silent put ='\"'
silent put ='\" ---------- Folds ----------'
for [cmd, desc] in fold_cmds
if !empty(s:get_map_str(cmd)) | silent put ='\" ' . s:get_map_str(cmd) . ': ' . desc | endif
endfor
silent put ='\"'
silent put ='\" ---------- Misc -----------'
for [cmd, desc] in misc_cmds
if !empty(s:get_map_str(cmd)) | silent put ='\" ' . s:get_map_str(cmd) . ': ' . desc | endif
endfor
silent put _
endif
endfunction
function! s:get_map_str(map) abort
let def = get(g:, 'tagbar_map_' . a:map)
if type(def) ==# type('')
return def
else
return join(def, ', ')
endif
endfunction
" s:RenderKeepView() {{{2
" The gist of this function was taken from NERDTree by Martin Grenfell.
function! s:RenderKeepView(...) abort
if a:0 == 1
let line = a:1
else
let line = line('.')
endif
let curcol = col('.')
let topline = line('w0')
call s:RenderContent()
let scrolloff_save = &scrolloff
setlocal scrolloff=0
call cursor(topline, 1)
normal! zt
call cursor(line, curcol)
let &l:scrolloff = scrolloff_save
redraw
endfunction
" User actions {{{1
" s:HighlightTag() {{{2
function! s:HighlightTag(openfolds, ...) abort
let tagline = 0
let force = a:0 > 0 ? a:1 : 0
if a:0 > 1
let tag = s:GetNearbyTag('nearest-stl', 0, a:2)
else
let tag = s:GetNearbyTag('nearest-stl', 0)
endif
if !empty(tag)
let tagline = tag.tline
endif
" Don't highlight the tag again if it's the same one as last time.
" This prevents the Tagbar window from jumping back after scrolling with
" the mouse.
if !force && tagline ==# s:last_highlight_tline
return
else
let s:last_highlight_tline = tagline
endif
let tagbarwinnr = bufwinnr(s:TagbarBufName())
if tagbarwinnr == -1
return
endif
if tagbarwinnr == winnr()
let in_tagbar = 1
else
let in_tagbar = 0
let prevwinnr = winnr()
call s:goto_win('p', 1)
let pprevwinnr = winnr()
call s:goto_win(tagbarwinnr, 1)
endif
try
match none
" No tag above cursor position so don't do anything
if tagline == 0
return
endif
if g:tagbar_autoshowtag == 1 || a:openfolds
call s:OpenParents(tag)
endif
" Check whether the tag is inside a closed fold and highlight the parent
" instead in that case
let tagline = tag.getClosedParentTline()
" Parent tag line number is invalid, better don't do anything
if tagline <= 0
return
endif
" Go to the line containing the tag
execute tagline
" Make sure the tag is visible in the window
call winline()
let foldpat = '[' . g:tagbar#icon_open . g:tagbar#icon_closed . ' ]'
" If printing the line number of the tag to the left, and the tag is
" visible (I.E. parent isn't folded)
if g:tagbar_show_tag_linenumbers == 2 && tagline == tag.tline
let pattern = '/^\%' . tagline . 'l\s*' . foldpat . '[-+# ]\[[0-9]\+\] \?\zs[^( ]\+\ze/'
else
let pattern = '/^\%' . tagline . 'l\s*' . foldpat . '[-+# ]\?\zs[^( ]\+\ze/'
endif
call tagbar#debug#log("Highlight pattern: '" . pattern . "'")
if hlexists('TagbarHighlight') " Safeguard in case syntax highlighting is disabled
execute 'match TagbarHighlight ' . pattern
else
execute 'match Search ' . pattern
endif
finally
if !in_tagbar
call s:goto_win(pprevwinnr, 1)
call s:goto_win(prevwinnr, 1)
endif
redraw
endtry
endfunction
" Is the given line number already visible in the window without
" any scrolling?
function! s:IsLineVisible(line) abort
let topline = line('w0')
let bottomline = line('w$')
let alreadyvisible = (a:line >= topline) && (a:line <= bottomline)
return alreadyvisible
endfunction
" s:JumpToTag() {{{2
function! s:JumpToTag(stay_in_tagbar) abort
let taginfo = s:GetTagInfo(line('.'), 1)
let autoclose = w:autoclose
if empty(taginfo) || !taginfo.isNormalTag()
return
endif
let tagbarwinnr = winnr()
call s:GotoFileWindow(taginfo.fileinfo)
" Mark current position so it can be jumped back to
mark '
" Check if the tag is already visible in the window. We must do this
" before jumping to the line.
let noscroll = 0
if g:tagbar_jump_lazy_scroll != 0
let noscroll = s:IsLineVisible(taginfo.fields.line)
endif
" 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)
call tagbar#debug#log('Jumping to line ' . taginfo.fields.line)
execute taginfo.fields.line
" If the file has been changed but not saved, the tag may not be on the
" saved line anymore, so search for it in the vicinity of the saved line
if taginfo.pattern !=# ''
call tagbar#debug#log('Searching for pattern "' . taginfo.pattern . '"')
if match(getline('.'), taginfo.pattern) == -1
let interval = 1
let forward = 1
while search(taginfo.pattern, 'W' . forward ? '' : 'b') == 0
if !forward
if interval > line('$')
break
else
let interval = interval * 2
endif
endif
let forward = !forward
endwhile
endif
endif
" If the tag is on a different line after unsaved changes update the tag
" and file infos/objects
let curline = line('.')
if taginfo.fields.line != curline
let taginfo.fields.line = curline
let taginfo.fileinfo.fline[curline] = taginfo
endif
if noscroll
" Do not scroll.
else
" Center the tag in the window and jump to the correct column if
" available, otherwise try to find it in the line
normal! z.
" If configured, adjust the jump_offset and center the window on that
" line. Then fall-through adjust the cursor() position below that
if g:tagbar_jump_offset != 0 && g:tagbar_jump_offset < curline
if g:tagbar_jump_offset > winheight(0) / 2
let jump_offset = winheight(0) / 2
elseif g:tagbar_jump_offset < -winheight(0) / 2
let jump_offset = -winheight(0) / 2
else
let jump_offset = g:tagbar_jump_offset
endif
execute curline+jump_offset
normal! z.
endif
endif
if taginfo.fields.column > 0
call cursor(taginfo.fields.line, taginfo.fields.column)
else
call cursor(taginfo.fields.line, 1)
call search('\V' . taginfo.name, 'c', line('.'))
endif
normal! zv
if a:stay_in_tagbar
call s:HighlightTag(0)
call s:goto_win(tagbarwinnr)
redraw
elseif g:tagbar_autoclose || autoclose
" Also closes preview window
call s:CloseWindow()
else
" Close the preview window if it was opened by us
if s:pwin_by_tagbar
pclose
endif
if s:is_maximized
call s:ZoomWindow()
endif
call s:HighlightTag(0)
endif
endfunction
" s:ShowInPreviewWin() {{{2
function! s:ShowInPreviewWin() abort
let pos = getpos('.')
let taginfo = s:GetTagInfo(pos[1], 1)
if empty(taginfo) || !taginfo.isNormalTag()
return
endif
let pwin_open = 0
for win in range(1, winnr('$'))
if getwinvar(win, '&previewwindow')
let pwin_open = 1
break
endif
endfor
" We want the preview window to be relative to the file window in normal
" (horizontal) mode, and relative to the Tagbar window in vertical mode,
" to make the best use of space.
if g:tagbar_position =~# 'vertical'
call s:GotoFileWindow(taginfo.fileinfo, 1)
call s:mark_window()
endif
" Open the preview window if it is not already open. This has to be done
" explicitly before the :psearch below to better control its positioning.
if !pwin_open
silent execute
\ g:tagbar_previewwin_pos . ' pedit ' .
\ fnameescape(taginfo.fileinfo.fpath)
if g:tagbar_position !~# 'vertical'
silent execute 'vertical resize ' . g:tagbar_width
endif
" Remember that the preview window was opened by Tagbar so we can
" safely close it by ourselves
let s:pwin_by_tagbar = 1
endif
if g:tagbar_position !~# 'vertical'
call s:GotoFileWindow(taginfo.fileinfo, 1)
call s:mark_window()
endif
" Use psearch instead of pedit since pedit essentially reloads the file
" and creates an empty undo entry. psearch has to be called from the file
" window, and since we only want matches in the current file we disable
" the 'include' option. Also start searching at the correct line number to
" find the correct tag in case of tags with the same name and to speed up
" the searching. Unfortunately the /\%l pattern doesn't seem to work with
" psearch.
let pattern = taginfo.pattern
if pattern ==# ''
let pattern = '\V\^' . escape(getline(taginfo.fields.line), '\') . '\$'
endif
let include_save = &include
set include=
silent! execute taginfo.fields.line . ',$psearch! /' . pattern . '/'
let &include = include_save
call s:goto_win('P', 1)
normal! zv
normal! zz
call s:goto_markedwin(1)
call s:goto_tagbar(1)
call cursor(pos[1], pos[2])
endfunction
" s:ShowPrototype() {{{2
function! s:ShowPrototype(short) abort
let taginfo = s:GetTagInfo(line('.'), 1)
if empty(taginfo)
return ''
endif
echo taginfo.getPrototype(a:short)
endfunction
" s:ToggleHelp() {{{2
function! s:ToggleHelp() abort
let s:short_help = !s:short_help
" Prevent highlighting from being off after adding/removing the help text
match none
call s:RenderContent()
execute 1
redraw
endfunction
" s:GotoNextToplevelTag() {{{2
function! s:GotoNextToplevelTag(direction) abort
let curlinenr = line('.')
let newlinenr = line('.')
if a:direction == 1
let range = range(line('.') + 1, line('$'))
else
let range = range(line('.') - 1, 1, -1)
endif
for tmplinenr in range
let taginfo = s:GetTagInfo(tmplinenr, 0)
if empty(taginfo)
continue
elseif empty(taginfo.parent)
let newlinenr = tmplinenr
break
endif
endfor
if curlinenr != newlinenr
execute newlinenr
call winline()
endif
redraw
endfunction
" Folding {{{1
" s:OpenFold() {{{2
function! s:OpenFold() abort
let fileinfo = tagbar#state#get_current_file(0)
if empty(fileinfo)
return
endif
let curline = line('.')
let tag = s:GetTagInfo(curline, 0)
if empty(tag)
return
endif
call tag.openFold()
call s:RenderKeepView()
endfunction
" s:CloseFold() {{{2
function! s:CloseFold() abort
let fileinfo = tagbar#state#get_current_file(0)
if empty(fileinfo)
return
endif
match none
let curline = line('.')
let curtag = s:GetTagInfo(curline, 0)
if empty(curtag)
return
endif
let newline = curtag.closeFold()
call s:RenderKeepView(newline)
endfunction
" s:ToggleFold() {{{2
function! s:ToggleFold() abort
let fileinfo = tagbar#state#get_current_file(0)
if empty(fileinfo)
return
endif
match none
let curtag = s:GetTagInfo(line('.'), 0)
if empty(curtag)
return
endif
let newline = line('.')
if curtag.isKindheader()
call curtag.toggleFold(tagbar#state#get_current_file(0))
elseif curtag.isFoldable()
if curtag.isFolded()
call curtag.openFold()
else
let newline = curtag.closeFold()
endif
else
let newline = curtag.closeFold()
endif
call s:RenderKeepView(newline)
endfunction
" s:ChangeFoldLevel() {{{2
function! s:ChangeFoldLevel(diff, force) abort
let fileinfo = tagbar#state#get_current_file(0)
if empty(fileinfo)
return
endif
if fileinfo.foldlevel == 99
call s:MinimizeMaxFoldLevel(fileinfo, fileinfo.getTags())
endif
let level = fileinfo.foldlevel
let level = level + a:diff
call s:SetFoldLevel(level, a:force)
endfunction
" s:SetFoldLevel() {{{2
function! s:SetFoldLevel(level, force) abort
if a:level < 0
call s:warning('Foldlevel can''t be negative')
return
endif
let fileinfo = tagbar#state#get_current_file(0)
if empty(fileinfo)
return
endif
call s:SetFoldLevelRecursive(fileinfo, fileinfo.getTags(), a:level)
let typeinfo = fileinfo.typeinfo
" Apply foldlevel to 'kind's
if a:level == 0
for kind in typeinfo.kinds
call fileinfo.closeKindFold(kind)
endfor
else
for kind in typeinfo.kinds
if a:force || !kind.fold
call fileinfo.openKindFold(kind)
endif
endfor
endif
let fileinfo.foldlevel = a:level
call s:RenderContent()
endfunction
" s:SetFoldLevelRecursive() {{{2
" Apply foldlevel to normal tags
function! s:SetFoldLevelRecursive(fileinfo, tags, level) abort
for tag in a:tags
if tag.depth >= a:level
call tag.setFolded(1)
else
call tag.setFolded(0)
endif
if !empty(tag.getChildren())
call s:SetFoldLevelRecursive(a:fileinfo, tag.getChildren(), a:level)
endif
endfor
endfunction
" s:MinimizeMaxFoldLevel() {{{2
" Set the file's fold level to the lowest value that still shows all tags
function! s:MinimizeMaxFoldLevel(fileinfo, tags) abort
let maxlvl = 0
let tags = copy(a:tags)
for tag in tags
if maxlvl < tag.depth
let maxlvl = tag.depth
endif
call tag.setFolded(0)
call extend(tags, tag.getChildren())
endfor
let a:fileinfo.foldlevel = maxlvl
endfunction
" s:OpenParents() {{{2
function! s:OpenParents(...) abort
if a:0 == 1
let tag = a:1
else
let tag = s:GetNearbyTag('nearest', 0)
endif
if !empty(tag)
call tag.openParents()
call s:RenderKeepView()
endif
endfunction
" s:GotoNextFold() {{{2
function! s:GotoNextFold() abort
let curlinenr = line('.')
let newlinenr = line('.')
let range = range(line('.') + 1, line('$'))
for linenr in range
let taginfo = s:GetTagInfo(linenr, 0)
if empty(taginfo)
continue
elseif !empty(taginfo.getChildren()) || taginfo.isKindheader()
let newlinenr = linenr
break
endif
endfor
if curlinenr != newlinenr
execute linenr
call winline()
endif
redraw
endfunction
" s:GotoPrevFold() {{{2
function! s:GotoPrevFold() abort
let curlinenr = line('.')
let newlinenr = line('.')
let curtag = s:GetTagInfo(curlinenr, 0)
let curparent = get(curtag, 'parent', {})
let range = range(line('.') - 1, 1, -1)
for linenr in range
let taginfo = s:GetTagInfo(linenr, 0)
if empty(taginfo)
continue
" Check for the first tag that is either:
" - the last tag in an open fold, that is skip all tags that have the
" same parent as the current one, or
" - a closed parent fold.
elseif (!empty(taginfo.parent) && taginfo.parent != curparent &&
\ empty(taginfo.getChildren())) ||
\ ((!empty(taginfo.getChildren()) || taginfo.isKindheader()) &&
\ taginfo.isFolded())
let newlinenr = linenr
break
endif
endfor
if curlinenr != newlinenr
execute linenr
call winline()
endif
redraw
endfunction
" Helper functions {{{1
" s:AutoUpdate() {{{2
function! s:AutoUpdate(fname, force, ...) abort
call tagbar#debug#log('AutoUpdate called [' . a:fname . ']')
" Whether we want to skip actually displaying the tags in Tagbar and only
" update the fileinfo
let no_display = a:0 > 0 ? a:1 : 0
" This file is being loaded due to a quickfix command like vimgrep, so
" don't process it
if exists('s:tagbar_qf_active')
return
elseif exists('s:window_opening')
" This can happen if another plugin causes the active window to change
" with an autocmd during the initial Tagbar window creation. In that
" case InitWindow() hasn't had a chance to run yet and things can
" break. MiniBufExplorer does this, for example. Completely disabling
" autocmds at that point is also not ideal since for example
" statusline plugins won't be able to update.
call tagbar#debug#log('Still opening window, stopping processing')
return
endif
" Get the filetype of the file we're about to process
let bufnr = bufnr(a:fname)
let ftype = getbufvar(bufnr, '&filetype')
" Don't do anything if we're in the tagbar window
if ftype ==# 'tagbar'
call tagbar#debug#log('In Tagbar window, stopping processing')
return
endif
" Only consider the main filetype in cases like 'python.django'
let sftype = get(split(ftype, '\.'), 0, '')
call tagbar#debug#log("Vim filetype: '" . ftype . "', " .
\ "sanitized filetype: '" . sftype . "'")
" Don't do anything if the file isn't supported
if !s:IsValidFile(a:fname, sftype)
call tagbar#debug#log('Not a valid file, stopping processing')
let s:nearby_disabled = 1
return
endif
let updated = 0
" Process the file if it's unknown or the information is outdated.
" Testing the mtime of the file is necessary in case it got changed
" outside of Vim, for example by checking out a different version from a
" VCS.
if s:known_files.has(a:fname)
let curfile = s:known_files.get(a:fname)
" if a:force || getbufvar(curfile.bufnr, '&modified') ||
if a:force || empty(curfile) || curfile.ftype != sftype ||
\ (filereadable(a:fname) && getftime(a:fname) > curfile.mtime)
call tagbar#debug#log('File data outdated, updating [' . a:fname . ']')
call s:ProcessFile(a:fname, sftype)
let updated = 1
else
call tagbar#debug#log('File data seems up to date [' . a:fname . ']')
endif
elseif !s:known_files.has(a:fname)
call tagbar#debug#log('New file, processing [' . a:fname . ']')
call s:ProcessFile(a:fname, sftype)
let updated = 1
endif
if no_display
return
endif
let fileinfo = s:known_files.get(a:fname)
" If we don't have an entry for the file by now something must have gone
" wrong, so don't change the tagbar content
if empty(fileinfo)
call tagbar#debug#log('fileinfo empty after processing [' . a:fname . ']')
return
endif
" Display the tagbar content if the tags have been updated or a different
" file is being displayed
if bufwinnr(s:TagbarBufName()) != -1 && !s:paused &&
\ (s:new_window || updated ||
\ (!empty(tagbar#state#get_current_file(0)) &&
\ a:fname != tagbar#state#get_current_file(0).fpath))
call s:RenderContent(fileinfo)
endif
" Call setCurrent after rendering so RenderContent can check whether the
" same file is being redisplayed
if !empty(fileinfo)
call tagbar#debug#log('Setting current file [' . a:fname . ']')
call tagbar#state#set_current_file(fileinfo)
let s:nearby_disabled = 0
endif
call s:HighlightTag(0)
call s:SetStatusLine()
call tagbar#debug#log('AutoUpdate finished successfully')
endfunction
" s:CheckMouseClick() {{{2
function! s:CheckMouseClick() abort
let line = getline('.')
let curcol = col('.')
if (match(line, g:tagbar#icon_open . '[-+ ]') + 1) == curcol
call s:CloseFold()
elseif (match(line, g:tagbar#icon_closed . '[-+ ]') + 1) == curcol
call s:OpenFold()
elseif g:tagbar_singleclick
call s:JumpToTag(0)
endif
endfunction
" s:DetectFiletype() {{{2
function! s:DetectFiletype(bufnr) abort
" Filetype has already been detected for loaded buffers, but not
" necessarily for unloaded ones
let ftype = getbufvar(a:bufnr, '&filetype')
if bufloaded(a:bufnr)
return ftype
endif
if ftype !=# ''
return ftype
endif
" Unloaded buffer with non-detected filetype, need to detect filetype
" manually
let bufname = bufname(a:bufnr)
let eventignore_save = &eventignore
set eventignore=FileType
let filetype_save = &filetype
exe 'doautocmd filetypedetect BufRead ' . bufname
let ftype = &filetype
let &filetype = filetype_save
let &eventignore = eventignore_save
return ftype
endfunction
" s:EscapeCtagsCmd() {{{2
" Assemble the ctags command line in a way that all problematic characters are
" properly escaped and converted to the system's encoding
" Optional third parameter is a file name to run ctags on
" Note: The second parameter (a:args) can be a list of args or
" a single string of the args.
" When a:args is a list, each argument in the list will be escaped for the
" current &shell type.
" When a:args is a string, all arguments should be escaped appropriately
" (if required). In most use cases no escaping is required so a string
" is acceptable. But in cases where arguments may need to be escaped
" differently for each &shell type, then pass a list of arguments.
function! s:EscapeCtagsCmd(ctags_bin, args, ...) abort
call tagbar#debug#log('EscapeCtagsCmd called')
call tagbar#debug#log('ctags_bin: ' . a:ctags_bin)
if type(a:args)==type('')
call tagbar#debug#log('ctags_args (is a string): ' . a:args)
elseif type(a:args)==type([])
call tagbar#debug#log('ctags_args (is a list): ' . string(a:args))
endif
if exists('+shellslash')
let shellslash_save = &shellslash
set noshellslash
endif
"Set up 0th argument of ctags_cmd
"a:ctags_bin may have special characters that require escaping.
if &shell =~? 'cmd\.exe$' && a:ctags_bin !~# '\s'
"For windows cmd.exe, escaping the 0th argument can cause
"problems if it references a batch file and the batch file uses %~dp0.
"So for windows cmd.exe, only escape the 0th argument iff necessary.
"Only known necessary case is when ctags_bin executable filename has
"whitespace character(s).
" Example: If 0th argument is wrapped in double quotes AND it is not
" an absolute path to ctags_bin, but rather an executable in %PATH%,
" then %~dp0 resolves to the current working directory rather than
" the batch file's directory. Batch files like this generally exepect
" and depend on %~dp0 to resolve the batch file's directory.
" Note: Documentation such as `help cmd.exe` and
" http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/cmd.mspx?mfr=true
" suggest other special characters that require escaping for command
" line completion. But tagbar.vim does not use the command line
" completion feature of cmd.exe and testing shows that the only special
" character that needs to be escaped for tagbar.vim is <space> for
" windows cmd.exe.
let ctags_cmd = a:ctags_bin
else
let ctags_cmd = shellescape(a:ctags_bin)
endif
"Add additional arguments to ctags_cmd
if type(a:args)==type('')
"When a:args is a string, append the arguments
"Note: In this case, do not attempt to shell escape a:args string.
"This function expects the string to already be escaped properly for
"the shell type. Why not escape? Because it could be ambiguous about
"whether a space is an argument separator or included in the argument.
"Since escaping rules vary from shell to shell, it is better to pass a
"list of arguments to a:args. With a list, each argument is clearly
"separated, so shellescape() can calculate the appropriate escaping
"for each argument for the current &shell.
let ctags_cmd .= ' ' . a:args
elseif type(a:args)==type([])
"When a:args is a list, shellescape() each argument and append ctags_cmd
"Note: It's a better practice to shellescape() each argument separately so that
"spaces used as a separator between arguments can be distinguished with
"spaces used inside a single argument.
for arg in a:args
let ctags_cmd .= ' ' . shellescape(arg)
endfor
endif
"if a filename was specified, add filename as final argument to ctags_cmd.
if a:0 == 1
let ctags_cmd .= ' ' . shellescape(a:1)
endif
if exists('+shellslash')
let &shellslash = shellslash_save
endif
" Needed for cases where 'encoding' is different from the system's
" encoding
if has('multi_byte')
if g:tagbar_systemenc != &encoding
let ctags_cmd = iconv(ctags_cmd, &encoding, g:tagbar_systemenc)
elseif $LANG !=# ''
let ctags_cmd = iconv(ctags_cmd, &encoding, $LANG)
endif
endif
call tagbar#debug#log('Escaped ctags command: ' . ctags_cmd)
if ctags_cmd ==# ''
if !s:warnings.encoding
call s:warning('Tagbar: Ctags command encoding conversion failed!' .
\ ' Please read ":h g:tagbar_systemenc".')
let s:warnings.encoding = 1
endif
endif
return ctags_cmd
endfunction
" run shell command in a proper way: prevent temporary window creation
function! s:run_system(cmd, version) abort
if has('win32') && !has('nvim') && a:version > 0 && (has('python3') || has('python2'))
if a:version == 3 && has('python3')
let pyx = 'py3 '
let python_eval = 'py3eval'
elseif a:version == 2 && has('python2')
let pyx = 'py2 '
let python_eval = 'pyeval'
else
let pyx = 'pyx '
let python_eval = 'pyxeval'
endif
let l:pc = 0
exec pyx . 'import subprocess, vim'
exec pyx . '__argv = {"args":vim.eval("a:cmd"), "shell":True}'
exec pyx . '__argv["stdout"] = subprocess.PIPE'
exec pyx . '__argv["stderr"] = subprocess.STDOUT'
exec pyx . '__pp = subprocess.Popen(**__argv)'
exec pyx . '__return_text = __pp.stdout.read()'
exec pyx . '__pp.stdout.close()'
exec pyx . '__return_code = __pp.wait()'
exec 'let l:hr = '. python_eval .'("__return_text")'
exec 'let l:pc = '. python_eval .'("__return_code")'
let s:shell_error = l:pc
return l:hr
endif
let hr = system(a:cmd)
let s:shell_error = v:shell_error
return hr
endfunction
" s:ExecuteCtags() {{{2
" Execute ctags with necessary shell settings
" Partially based on the discussion at
" http://vim.1045645.n5.nabble.com/bad-default-shellxquote-in-Widows-td1208284.html
function! s:ExecuteCtags(ctags_cmd) abort
call tagbar#debug#log('Executing ctags command: ' . a:ctags_cmd)
if &shell =~# 'fish$'
" Reset shell since fish isn't really compatible
let shell_save = &shell
set shell=sh
endif
if &shell =~# 'elvish'
" Reset shell since Elvish isn't really compatible
let shell_save = &shell
set shell=sh
endif
if exists('+shellslash')
let shellslash_save = &shellslash
set noshellslash
endif
if &shell =~? 'cmd\.exe'
let shellxquote_save = &shellxquote
set shellxquote=\"
let shellcmdflag_save = &shellcmdflag
set shellcmdflag=/s\ /c
endif
if tagbar#debug#enabled()
silent 5verbose let ctags_output = system(a:ctags_cmd)
call tagbar#debug#log(v:statusmsg)
call tagbar#debug#log('Exit code: ' . v:shell_error)
redraw!
else
let py_version = get(g:, 'tagbar_python', 1)
silent let ctags_output = s:run_system(a:ctags_cmd, py_version)
endif
if &shell =~? 'cmd\.exe'
let &shellxquote = shellxquote_save
let &shellcmdflag = shellcmdflag_save
endif
if exists('+shellslash')
let &shellslash = shellslash_save
endif
if exists('shell_save')
let &shell = shell_save
endif
return ctags_output
endfunction
" s:GetNearbyTag() {{{2
" Get the tag info for a file near the cursor in the current file
function! s:GetNearbyTag(request, forcecurrent, ...) abort
if s:nearby_disabled
return {}
endif
let fileinfo = tagbar#state#get_current_file(a:forcecurrent)
if empty(fileinfo)
return {}
endif
let typeinfo = fileinfo.typeinfo
if a:0 > 0
let curline = a:1
else
let curline = line('.')
endif
let tag = {}
" 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)
if has_key(fileinfo.fline, line)
let curtag = fileinfo.fline[line]
if a:request ==# 'nearest-stl'
\ && typeinfo.getKind(curtag.fields.kind).stl || line == curline
let tag = curtag
break
elseif a:request ==# 'scoped-stl'
\ && typeinfo.getKind(curtag.fields.kind).stl
\ && curtag.fields.line <= curline
\ && curline <= curtag.fields.end
let tag = curtag
break
elseif a:request ==# 'nearest'
let tag = curtag
break
endif
endif
endfor
return tag
endfunction
" s:GetTagInfo() {{{2
" 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, ignorepseudo) abort
let fileinfo = tagbar#state#get_current_file(0)
if empty(fileinfo)
return {}
endif
" Don't do anything in empty and comment lines
let curline = getbufline(bufnr(s:TagbarBufName()), a:linenr)[0]
if curline =~# '^\s*$' || curline[0] ==# '"'
return {}
endif
" 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 a:ignorepseudo && taginfo.isPseudoTag()
return {}
endif
return taginfo
endfunction
" s:GetFileWinnr() {{{2
" Get the number of the window that has Tagbar's current file loaded into it,
" or 0 if no window has loaded it. It tries the previous window first, if that
" does not have the correct buffer loaded it will look for the first one with
" the correct buffer in it.
function! s:GetFileWinnr(fileinfo) abort
let filewinnr = 0
let prevwinnr = winnr('#')
if winbufnr(prevwinnr) == a:fileinfo.bufnr &&
\ !getwinvar(prevwinnr, '&previewwindow')
let filewinnr = prevwinnr
else
" Search for the first real window that has the correct buffer loaded
" in it. Similar to bufwinnr() but skips the previewwindow.
for i in range(1, winnr('$'))
call s:goto_win(i, 1)
if bufnr('%') == a:fileinfo.bufnr && !&previewwindow
let filewinnr = winnr()
break
endif
endfor
call s:goto_tagbar(1)
endif
return filewinnr
endfunction
" s:GotoFileWindow() {{{2
" Try to switch to the window that has Tagbar's current file loaded in it, or
" open the file in an existing window otherwise.
function! s:GotoFileWindow(fileinfo, ...) abort
let noauto = a:0 > 0 ? a:1 : 0
let filewinnr = s:GetFileWinnr(a:fileinfo)
" If there is no window with the correct buffer loaded then load it
" into the first window that has a non-special buffer in it.
if filewinnr == 0
for i in range(1, winnr('$'))
call s:goto_win(i, 1)
if &buftype ==# '' && !&previewwindow
execute 'buffer ' . a:fileinfo.bufnr
break
endif
endfor
else
call s:goto_win(filewinnr, 1)
endif
" To make ctrl-w_p work we switch between the Tagbar window and the
" correct window once
call s:goto_tagbar(noauto)
call s:goto_win('p', noauto)
endfunction
" s:ToggleHideNonPublicTags() {{{2
function! s:ToggleHideNonPublicTags() abort
let fileinfo = tagbar#state#get_current_file(0)
if empty(fileinfo)
return
endif
" Save the tag the cursor is currently on
let curline = line('.')
let taginfo = s:GetTagInfo(curline, 0)
match none
let g:tagbar_hide_nonpublic = !g:tagbar_hide_nonpublic
call s:RenderKeepView()
call s:SetStatusLine()
" If we were on a tag before sorting then jump to it, otherwise restore
" the cursor to the current line
if !empty(taginfo)
execute taginfo.tline
else
execute curline
endif
endfunction
" s:ToggleCaseInsensitive() {{{2
function! s:ToggleCaseInsensitive() abort
let fileinfo = tagbar#state#get_current_file(0)
if empty(fileinfo)
return
endif
" Save the tag the cursor is currently on
let curline = line('.')
let taginfo = s:GetTagInfo(curline, 0)
match none
let g:tagbar_case_insensitive = !g:tagbar_case_insensitive
call fileinfo.sortTags(fileinfo.typeinfo)
call s:RenderKeepView()
call s:SetStatusLine()
" If we were on a tag before sorting then jump to it, otherwise restore
" the cursor to the current line
if !empty(taginfo)
execute taginfo.tline
else
execute curline
endif
endfunction
" s:ToggleAutoclose() {{{2
function! s:ToggleAutoclose() abort
let g:tagbar_autoclose = !g:tagbar_autoclose
call s:SetStatusLine()
endfunction
" s:IsValidFile() {{{2
function! s:IsValidFile(fname, ftype) abort
call tagbar#debug#log('Checking if file is valid [' . a:fname . ']')
if a:fname ==# '' || a:ftype ==# ''
call tagbar#debug#log('Empty filename or type')
return 0
endif
if !filereadable(a:fname) && getbufvar(a:fname, 'netrw_tmpfile') ==# ''
call tagbar#debug#log('File not readable')
return 0
endif
if getbufvar(a:fname, 'tagbar_ignore') == 1
call tagbar#debug#log('File is marked as ignored')
return 0
endif
let winnr = bufwinnr(a:fname)
if winnr != -1 && getwinvar(winnr, '&diff')
call tagbar#debug#log('Window is in diff mode')
return 0
endif
if &previewwindow
call tagbar#debug#log('In preview window')
return 0
endif
if !has_key(s:known_types, a:ftype)
if exists('g:tagbar_type_' . a:ftype)
" Filetype definition must have been specified in an 'ftplugin'
" file, so load it now
call s:LoadUserTypeDefs(a:ftype)
else
call tagbar#debug#log('Unsupported filetype: ' . a:ftype)
return 0
endif
endif
return 1
endfunction
" s:SetStatusLine() {{{2
function! s:SetStatusLine() abort
let tagbarwinnr = bufwinnr(s:TagbarBufName())
if tagbarwinnr == -1 || exists('g:tagbar_no_status_line')
return
endif
" Make sure we're actually in the Tagbar window
if tagbarwinnr != winnr()
let in_tagbar = 0
let prevwinnr = winnr()
call s:goto_win('p', 1)
let pprevwinnr = winnr()
call s:goto_win(tagbarwinnr, 1)
else
let in_tagbar = 1
endif
if !empty(tagbar#state#get_current_file(0))
let fileinfo = tagbar#state#get_current_file(0)
let fname = fnamemodify(fileinfo.fpath, ':t')
let sorted = get(fileinfo.typeinfo, 'sort', g:tagbar_sort)
else
let fname = ''
let sorted = g:tagbar_sort
endif
let sortstr = sorted ? 'Name' : 'Order'
let flags = []
let flags += exists('w:autoclose') && w:autoclose ? ['c'] : []
let flags += g:tagbar_autoclose ? ['C'] : []
let flags += (sorted && g:tagbar_case_insensitive) ? ['i'] : []
let flags += g:tagbar_hide_nonpublic ? ['v'] : []
if exists('g:tagbar_status_func')
let args = [in_tagbar, sortstr, fname, flags]
let &l:statusline = call(g:tagbar_status_func, args)
else
let colour = in_tagbar ? '%#StatusLine#' : '%#StatusLineNC#'
let flagstr = join(flags, '')
if flagstr !=# ''
let flagstr = '[' . flagstr . '] '
endif
let text = colour . '[' . sortstr . '] ' . flagstr . fname
let &l:statusline = text
endif
if !in_tagbar
call s:goto_win(pprevwinnr, 1)
call s:goto_win(prevwinnr, 1)
endif
endfunction
" s:HandleOnlyWindow() {{{2
function! s:HandleOnlyWindow() abort
let tagbarwinnr = bufwinnr(s:TagbarBufName())
if tagbarwinnr == -1
return
endif
let vim_quitting = s:vim_quitting
let s:vim_quitting = 0
if vim_quitting && !s:HasOpenFileWindows()
call tagbar#debug#log('Closing Tagbar window due to QuitPre event')
if winnr('$') >= 1
call s:goto_win(tagbarwinnr, 1)
endif
" Before quitting Vim, delete the tagbar buffer so that the '0 mark is
" correctly set to the previous buffer.
if tabpagenr('$') == 1
noautocmd keepalt bdelete
endif
try
try
quit
catch /.*/ " This can be E173 and maybe others
call s:OpenWindow('')
echoerr v:exception
endtry
catch /.*/
echohl ErrorMsg
echo v:exception
echohl None
endtry
endif
endfunction
" s:HandleBufDelete() {{{2
function! s:HandleBufDelete(bufname, bufnr) abort
" Ignore autocmd events generated for "set nobuflisted",
let nr = str2nr(a:bufnr)
if bufexists(nr) && !buflisted(nr)
return
endif
let tagbarwinnr = bufwinnr(s:TagbarBufName())
if tagbarwinnr == -1 || a:bufname =~# '__Tagbar__.*'
return
endif
call s:known_files.rm(fnamemodify(a:bufname, ':p'))
if !s:HasOpenFileWindows()
if tabpagenr('$') == 1 && exists('t:tagbar_buf_name')
" The last normal window closed due to a :bdelete/:bwipeout.
" In order to get a normal file window back switch to the last
" alternative buffer (or a new one if there is no alternative
" buffer), reset the Tagbar-set window options, and then re-open
" the Tagbar window.
" Ignore the buffer to be deleted, just in case
call setbufvar(a:bufname, 'tagbar_ignore', 1)
if s:last_alt_bufnr == -1 || s:last_alt_bufnr == expand('<abuf>')
if argc() > 1 && argidx() < argc() - 1
" We don't have an alternative buffer, but there are still
" files left in the argument list
next
else
enew
endif
else
" Save a local copy as the global value will change
" during buffer switching
let last_alt_bufnr = s:last_alt_bufnr
" Ignore the buffer we're switching to for now, it will get
" processed due to the OpenWindow() call anyway
call setbufvar(last_alt_bufnr, 'tagbar_ignore', 1)
execute 'keepalt buffer' last_alt_bufnr
call setbufvar(last_alt_bufnr, 'tagbar_ignore', 0)
endif
" Reset Tagbar window-local options
set winfixwidth<
call s:OpenWindow('')
elseif exists('t:tagbar_buf_name')
close
endif
endif
endfunction
" s:HandleBufWrite() {{{2
function! s:HandleBufWrite(fname) abort
if index(s:delayed_update_files, a:fname) == -1
call add(s:delayed_update_files, a:fname)
endif
endfunction
" s:do_delayed_update() {{{2
function! s:do_delayed_update() abort
let curfile = tagbar#state#get_current_file(0)
if empty(curfile)
let curfname = ''
else
let curfname = curfile.fpath
endif
while !empty(s:delayed_update_files)
let fname = remove(s:delayed_update_files, 0)
let no_display = curfname !=# fname
call s:AutoUpdate(fname, 1, no_display)
endwhile
endfunction
" s:ReopenWindow() {{{2
function! s:ReopenWindow(delbufname) abort
if expand('<amatch>') == a:delbufname
return
endif
autocmd! TagbarAutoCmds BufWinEnter
call s:OpenWindow('')
endfunction
" s:HasOpenFileWindows() {{{2
function! s:HasOpenFileWindows() abort
for i in range(1, winnr('$'))
let buf = winbufnr(i)
" skip unlisted buffers, except for netrw
if !buflisted(buf) && getbufvar(buf, '&filetype') !=# 'netrw'
continue
endif
" skip temporary buffers with buftype set
if getbufvar(buf, '&buftype') !=# ''
continue
endif
" skip the preview window
if getwinvar(i, '&previewwindow')
continue
endif
return 1
endfor
return 0
endfunction
" s:TagbarBufName() {{{2
function! s:TagbarBufName() abort
if !exists('t:tagbar_buf_name')
let s:buffer_seqno += 1
let t:tagbar_buf_name = '__Tagbar__.' . s:buffer_seqno
endif
return t:tagbar_buf_name
endfunction
" s:goto_win() {{{2
function! s:goto_win(winnr, ...) abort
"Do not go to a popup window to avoid errors.
"Hence, check first if a:winnr is an integer,
"if this integer is equal to 0,
"the window is a popup window
if has('popupwin')
if type(a:winnr) == type(0) && a:winnr == 0
return
endif
if a:winnr ==# 'p' && winnr('#') == 0
return
endif
endif
let cmd = type(a:winnr) == type(0) ? a:winnr . 'wincmd w'
\ : 'wincmd ' . a:winnr
let noauto = a:0 > 0 ? a:1 : 0
call tagbar#debug#log('goto_win(): ' . cmd . ', ' . noauto)
if noauto
noautocmd execute cmd
else
execute cmd
endif
endfunction
" s:goto_tagbar() {{{2
function! s:goto_tagbar(...) abort
let noauto = a:0 > 0 ? a:1 : 0
call s:goto_win(bufwinnr(s:TagbarBufName()), noauto)
endfunction
" s:mark_window() {{{2
" Mark window with a window-local variable so we can jump back to it even if
" the window numbers have changed.
function! s:mark_window() abort
let w:tagbar_mark = 1
endfunction
" s:goto_markedwin() {{{2
" Go to a previously marked window and delete the mark.
function! s:goto_markedwin(...) abort
let noauto = a:0 > 0 ? a:1 : 0
for window in range(1, winnr('$'))
call s:goto_win(window, noauto)
if exists('w:tagbar_mark')
unlet w:tagbar_mark
break
endif
endfor
endfunction
" s:warning() {{{2
function! s:warning(msg) abort
echohl WarningMsg
echomsg a:msg
echohl None
endfunction
" s:TogglePause() {{{2
function! s:TogglePause() abort
let s:paused = !s:paused
if s:paused
call tagbar#state#set_paused()
else
let fileinfo = tagbar#state#get_current_file(0)
let taginfo = fileinfo.getTags()[0]
call s:GotoFileWindow(taginfo.fileinfo)
call s:AutoUpdate(taginfo.fileinfo.fpath, 1)
endif
endfunction
" TagbarBalloonExpr() {{{2
function! TagbarBalloonExpr() abort
let taginfo = s:GetTagInfo(v:beval_lnum, 1)
if empty(taginfo)
return ''
endif
let prototype = taginfo.getPrototype(0)
if has('multi_byte')
if g:tagbar_systemenc != &encoding
let prototype = iconv(prototype, &encoding, g:tagbar_systemenc)
elseif $LANG !=# ''
let prototype = iconv(prototype, &encoding, $LANG)
endif
endif
return prototype
endfunction
" Autoload functions {{{1
" Wrappers {{{2
function! tagbar#ToggleWindow(...) abort
let flags = a:0 > 0 ? a:1 : ''
call s:ToggleWindow(flags)
endfunction
function! tagbar#OpenWindow(...) abort
let flags = a:0 > 0 ? a:1 : ''
call s:OpenWindow(flags)
endfunction
function! tagbar#CloseWindow() abort
call s:CloseWindow()
endfunction
function! tagbar#SetFoldLevel(level, force) abort
call s:SetFoldLevel(a:level, a:force)
endfunction
function! tagbar#highlighttag(openfolds, force) abort
let tagbarwinnr = bufwinnr(s:TagbarBufName())
if tagbarwinnr == -1
echohl WarningMsg
echomsg "Warning: Can't highlight tag, Tagbar window not open"
echohl None
return
endif
call s:HighlightTag(a:openfolds, a:force)
endfunction
function! tagbar#RestoreSession() abort
call s:RestoreSession()
endfunction
function! tagbar#StopAutoUpdate() abort
autocmd! TagbarAutoCmds
let s:autocommands_done = 0
endfunction
" }}}2
" tagbar#Update() {{{2
" Trigger an AutoUpdate() of the currently opened file
function! tagbar#Update() abort
call s:AutoUpdate(fnamemodify(expand('%'), ':p'), 0)
endfunction
" tagbar#ForceUpdate() {{{2
function! tagbar#ForceUpdate() abort
if !exists('b:tagbar_force_update')
let b:tagbar_force_update = 1
call s:AutoUpdate(fnamemodify(expand('%'), ':p'), 1)
unlet b:tagbar_force_update
endif
endfunction
" tagbar#toggle_pause() {{{2
function! tagbar#toggle_pause() abort
let s:paused = !s:paused
if s:paused
call tagbar#state#set_paused()
else
call s:AutoUpdate(fnamemodify(expand('%'), ':p'), 1)
endif
endfunction
function! tagbar#is_paused() abort
return s:paused
endfunction
" tagbar#getusertypes() {{{2
function! tagbar#getusertypes() abort
let userdefs = filter(copy(g:), 'v:key =~? "^tagbar_type_"')
let typedict = {}
for [key, val] in items(userdefs)
let type = substitute(key, '^tagbar_type_', '', '')
let typedict[type] = val
endfor
return typedict
endfunction
" tagbar#autoopen() {{{2
" Automatically open Tagbar if one of the open buffers contains a supported
" file
function! tagbar#autoopen(...) abort
call tagbar#debug#log('tagbar#autoopen called [' . bufname('%') . ']')
let always = a:0 > 0 ? a:1 : 1
call s:Init(0)
for bufnr in range(1, bufnr('$'))
if buflisted(bufnr) && (always || bufwinnr(bufnr) != -1)
let ftype = s:DetectFiletype(bufnr)
if s:IsValidFile(bufname(bufnr), ftype)
call s:OpenWindow('')
call tagbar#debug#log('tagbar#autoopen finished after finding valid ' .
\ 'file [' . bufname(bufnr) . ']')
return
endif
endif
endfor
call tagbar#debug#log('tagbar#autoopen finished without finding valid file')
endfunction
" tagbar#GetTagNearLine() {{{2
function! tagbar#GetTagNearLine(lnum, ...) abort
if a:0 >= 2
let fmt = a:1
let longsig = a:2 =~# 's'
let fullpath = a:2 =~# 'f'
let prototype = a:2 =~# 'p'
if a:0 >= 3
let search_method = a:3
else
let search_method = 'nearest-stl'
endif
else
let fmt = '%s'
let longsig = 0
let fullpath = 0
let prototype = 0
let search_method = 'nearest-stl'
endif
let taginfo = s:GetNearbyTag(search_method, 1, a:lnum)
if empty(taginfo)
return ''
endif
if prototype
return taginfo.getPrototype(1)
else
return printf(fmt, taginfo.str(longsig, fullpath))
endif
endfunction
" tagbar#currenttag() {{{2
function! tagbar#currenttag(fmt, default, ...) abort
" Indicate that the statusline functionality is being used. This prevents
" the CloseWindow() function from removing the autocommands.
let s:statusline_in_use = 1
if a:0 >= 1
" also test for non-zero value for backwards compatibility
let longsig = a:1 =~# 's' || (type(a:1) == type(0) && a:1 != 0)
let fullpath = a:1 =~# 'f'
let prototype = a:1 =~# 'p'
if a:0 >= 2
let search_method = a:2
else
let search_method = 'nearest-stl'
endif
else
let longsig = 0
let fullpath = 0
let prototype = 0
let search_method = 'nearest-stl'
endif
if !s:Init(1)
return a:default
endif
let tag = s:GetNearbyTag(search_method, 1)
if !empty(tag)
if prototype
return tag.getPrototype(1)
else
return printf(a:fmt, tag.str(longsig, fullpath))
endif
else
return a:default
endif
endfunction
" tagbar#currentfile() {{{2
function! tagbar#currentfile() abort
let filename = ''
if !empty(tagbar#state#get_current_file(1))
let filename = fnamemodify(tagbar#state#get_current_file(1).fpath, ':t')
endif
return filename
endfunction
" tagbar#gettypeconfig() {{{2
function! tagbar#gettypeconfig(type) abort
if !s:Init(1)
return ''
endif
let typeinfo = get(s:known_types, a:type, {})
if empty(typeinfo)
call s:warning('Unknown type ' . a:type . '!')
return
endif
let output = 'let g:tagbar_type_' . a:type . " = {\n"
let output .= " \\ 'kinds' : [\n"
for kind in typeinfo.kinds
let output .= " \\ '" . kind.short . ':' . kind.long
if kind.fold || !kind.stl
if kind.fold
let output .= ':1'
else
let output .= ':0'
endif
endif
if !kind.stl
let output .= ':0'
endif
let output .= "',\n"
endfor
let output .= " \\ ],\n"
let output .= "\\ }"
silent put =output
endfunction
" tagbar#inspect() {{{2
function! tagbar#inspect(var) abort
return get(s:, a:var)
endfunction
" tagbar#currenttagtype() {{{2
function! tagbar#currenttagtype(fmt, default) abort
" Indicate that the statusline functionality is being used. This prevents
" the CloseWindow() function from removing the autocommands.
let s:statusline_in_use = 1
let kind = ''
let tag = s:GetNearbyTag('scoped-stl', 1)
if empty(tag)
return a:default
endif
let kind = tag.fields.kind
if kind ==# ''
return a:default
endif
let typeinfo = tag.fileinfo.typeinfo
let plural = typeinfo.kinds[typeinfo.kinddict[kind]].long
if has_key(s:singular_types, plural)
let singular = s:singular_types[plural]
else
let singular = plural
endif
return printf(a:fmt, singular)
endfunction
" tagbar#printfileinfo() {{{2
function! tagbar#printfileinfo() abort
if !tagbar#debug#enabled()
echo 'Tagbar debug is disabled - unable to print fileinfo to tagbar log'
return
endif
let fileinfo = tagbar#state#get_current_file(0)
if empty(fileinfo)
call tagbar#debug#log('File contains no tag entries')
return
endif
let typeinfo = fileinfo.typeinfo
call tagbar#debug#log('Printing fileinfo [' . fileinfo.fpath . ' lines:' . fileinfo.lnum)
for line in range(1, fileinfo.lnum)
if has_key(fileinfo.fline, line)
let tag = fileinfo.fline[line]
call tagbar#debug#log(' '
\ . ' line:' . line
\ . ' kind:' . tag.fields.kind
\ . ' depth:' . tag.depth
\ . ' [' . tag.strfmt() . ']'
\ )
endif
endfor
call tagbar#debug#log('All tags printed')
echo 'Tagbar fileinfo printed to debug logfile'
endfunction
" Modeline {{{1
" vim: ts=8 sw=4 sts=4 et foldenable foldmethod=marker foldcolumn=1