mirror of
https://github.com/preservim/tagbar.git
synced 2024-11-25 17:56:29 +08:00
8e97021f74
Closes #821 This will add the `autoclose_netrw` configuration option. If set to 1 this will close out of vim if tagbar and netrw (or nerdtree) are the only remaining windows. If set to 0, then this will leave the netrw (or nerdtree) window if that is the only remaining window once tagbar is closed.
4121 lines
132 KiB
VimL
4121 lines
132 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: 3.0.0
|
|
" 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' : 'l', 'long' : 'library', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 't', 'long' : 'export', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 'i', 'long' : 'imports', 'fold' : 1, 'stl' : 0},
|
|
\ {'short' : 'D', 'long' : 'dart', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 'U', 'long' : 'pub', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 'L', 'long' : 'local', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 'P', 'long' : 'part', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 'p', 'long' : 'part of', 'fold' : 0, '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' : 'E', 'long' : 'enums', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 'e', 'long' : 'constants', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 'x', 'long' : 'mixins', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 'c', 'long' : 'classes', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 'd', 'long' : 'extends', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 'w', 'long' : 'with', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 'z', 'long' : 'implements', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 'n', 'long' : 'on', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 'r', 'long' : 'constructors', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 'a', 'long' : 'abstract functions', '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' : 'g', 'long' : 'getters', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 's', 'long' : 'setters', 'fold' : 0, 'stl' : 0},
|
|
\ {'short' : 'o', 'long' : 'operators', 'fold' : 0, 'stl' : 0},
|
|
\ ]
|
|
let type_dart.sro = ':'
|
|
let type_dart.kind2scope = {
|
|
\ 'c' : 'class',
|
|
\ 'E' : 'enum',
|
|
\ 'x' : 'mixin',
|
|
\ 'i' : 'directive'
|
|
\ }
|
|
let type_dart.scope2kind = {
|
|
\ 'class' : 'c',
|
|
\ 'enum' : 'E',
|
|
\ 'mixin' : 'x',
|
|
\ 'directive' : 'i'
|
|
\ }
|
|
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!
|
|
|
|
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()
|
|
|
|
if !g:tagbar_no_autocmds
|
|
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_show_balloon == 1 && has('balloon_eval')
|
|
autocmd WinEnter __Tagbar__.*
|
|
\ let s:beval = &beval |
|
|
\ set ballooneval
|
|
autocmd WinLeave __Tagbar__.*
|
|
\ if exists("s:beval") |
|
|
\ let &beval = s:beval |
|
|
\ endif
|
|
endif
|
|
|
|
if g:tagbar_autopreview
|
|
autocmd CursorMoved __Tagbar__.* nested call s:ShowInPreviewWin()
|
|
endif
|
|
|
|
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)
|
|
if g:tagbar_highlight_follow_insert
|
|
autocmd CursorHoldI * call
|
|
\ s:AutoUpdate(fnamemodify(expand('<afile>'), ':p'), 0)
|
|
endif
|
|
|
|
" 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()
|
|
endif
|
|
augroup END
|
|
|
|
" Separate these autocmds out from the others as we want to always perform
|
|
" these actions even if the tagbar window closes.
|
|
augroup TagbarCleanupAutoCmds
|
|
autocmd!
|
|
|
|
if !g:tagbar_no_autocmds
|
|
autocmd BufDelete,BufWipeout *
|
|
\ nested call s:HandleBufDelete(expand('<afile>'), expand('<abuf>'))
|
|
endif
|
|
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, 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, 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
|
|
setlocal colorcolumn=""
|
|
|
|
if g:tagbar_scrolloff > 0
|
|
execute 'setlocal scrolloff=' . g:tagbar_scrolloff
|
|
endif
|
|
|
|
if g:tagbar_show_balloon == 1 && has('balloon_eval')
|
|
setlocal balloonexpr=TagbarBalloonExpr()
|
|
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
|
|
if g:tagbar_wrap == 1
|
|
setlocal linebreak
|
|
elseif g:tagbar_wrap == 2
|
|
setlocal nolinebreak
|
|
endif
|
|
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
|
|
if g:tagbar_ignore_anonymous && line =~# '__anon'
|
|
call tagbar#debug#log('anonymous tag found - ignoring per tagbar configuration')
|
|
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
|
|
|
|
" 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
|
|
|
|
" Include extra type definitions - include last to allow for any
|
|
" overrides
|
|
if has_key(a:typeinfo, 'deffile') && filereadable(expand(a:typeinfo.deffile))
|
|
let ctags_args += ['--options=' . expand(a:typeinfo.deffile)]
|
|
endif
|
|
|
|
if has_key(a:typeinfo, 'regex')
|
|
for regex in a:typeinfo.regex
|
|
let ctags_args += ['--regex-' . ctags_type . '=' . regex]
|
|
endfor
|
|
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 previous 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
|
|
|
|
if g:tagbar_no_autocmds
|
|
" If no autocmds are enabled, then it doesn't make sense to highlight
|
|
" anything as the cursor can move around and any highlighting would be
|
|
" inaccurate
|
|
return
|
|
endif
|
|
|
|
let noauto = a:0 > 0 ? a:1 : 0
|
|
|
|
let tagline = 0
|
|
|
|
let force = a:0 > 1 ? a:2 : 0
|
|
|
|
if a:0 > 2
|
|
let tag = s:GetNearbyTag(g:tagbar_highlight_method, 0, a:3)
|
|
else
|
|
let tag = s:GetNearbyTag(g:tagbar_highlight_method, 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)
|
|
let identifier = '\zs\V' . escape(tag.name, '/\') . '\m\ze'
|
|
if g:tagbar_show_tag_linenumbers == 2 && tagline == tag.tline
|
|
let pattern = '/^\%' . tagline . 'l\s*' . foldpat . '[-+# ]\[[0-9]\+\] \?' . identifier . '/'
|
|
else
|
|
let pattern = '/^\%' . tagline . 'l\s*' . foldpat . '[-+# ]\?' . identifier . '/'
|
|
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, noauto)
|
|
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 = a:0 > 0 ? a:1 : s:GetTagInfo(line('.'), 1)
|
|
let force_lazy_scroll = a:0 > 1 ? a:2 : 0
|
|
|
|
if empty(taginfo) || !taginfo.isNormalTag()
|
|
" Cursor line not on a tag. Check if this is the start of a foldable
|
|
" line and if so, initiate the CloseFold() / OpenFold(). First trim
|
|
" any whitespace from the start of the line so we don't have to worry
|
|
" about multiple nested folds that are indented
|
|
"
|
|
" NOTE: This will only work with folds that are not also tags. For
|
|
" example in a class definition that acts as the start of a fold when
|
|
" there are member functions in the class, that line is also a valid
|
|
" tag, so hitting <CR> on that line will cause it to jump to the tag,
|
|
" not fold/unfold that particular fold
|
|
let line = substitute(getline('.'), '^\s*\(.\{-}\)\s*$', '\1', '')
|
|
|
|
if (match(line, g:tagbar#icon_open . '[-+ ]')) == 0
|
|
call s:CloseFold()
|
|
elseif (match(line, g:tagbar#icon_closed . '[-+ ]')) == 0
|
|
call s:OpenFold()
|
|
endif
|
|
return
|
|
endif
|
|
|
|
let tagbarwinnr = winnr()
|
|
if exists('w:autoclose')
|
|
let autoclose = w:autoclose
|
|
else
|
|
let autoclose = 0
|
|
endif
|
|
|
|
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 || force_lazy_scroll
|
|
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, 1)
|
|
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
|
|
let l:confirm = &confirm
|
|
let &confirm = 0
|
|
silent! execute
|
|
\ g:tagbar_previewwin_pos . ' pedit ' .
|
|
\ fnameescape(taginfo.fileinfo.fpath)
|
|
let &confirm = l:confirm
|
|
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, 1)
|
|
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$' || &shell =~? 'powershell\.exe$' || &shell =~? 'powershell$' || &shell =~? 'pwsh\.exe$' || &shell =~? 'pwsh$') && 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 . '__argv["errors"] = "ignore"'
|
|
exec pyx . '__pp = subprocess.Popen(**__argv, universal_newlines=True, encoding="utf8")'
|
|
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 curline = a:0 > 0 ? a:1 : line('.')
|
|
let direction = a:0 > 1 ? a:2 : -1
|
|
let ignore_curline = a:0 > 2 ? a:3 : 0
|
|
|
|
let typeinfo = fileinfo.typeinfo
|
|
let tag = {}
|
|
|
|
if direction < 0
|
|
let endline = 1
|
|
let increment = -1
|
|
else
|
|
let endline = line('$')
|
|
let increment = 1
|
|
endif
|
|
|
|
" 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, endline, increment)
|
|
if has_key(fileinfo.fline, line)
|
|
let curtag = fileinfo.fline[line]
|
|
if a:request ==# 'nearest-stl' && typeinfo.getKind(curtag.fields.kind).stl
|
|
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' || (line == curline && ignore_curline == 0)
|
|
let tag = curtag
|
|
break
|
|
endif
|
|
endif
|
|
endfor
|
|
|
|
return tag
|
|
endfunction
|
|
|
|
" s:JumpToNearbyTag() {{{2
|
|
function! s:JumpToNearbyTag(direction, request, flags) abort
|
|
let fileinfo = tagbar#state#get_current_file(0)
|
|
if empty(fileinfo)
|
|
return {}
|
|
endif
|
|
|
|
let lnum = a:direction > 0 ? line('.') + 1 : line('.') - 1
|
|
let lazy_scroll = a:flags =~# 's' ? 0 : 1
|
|
|
|
let tag = s:GetNearbyTag(a:request, 1, lnum, a:direction, 1)
|
|
|
|
if empty(tag)
|
|
" No next tag found
|
|
if a:direction > 0
|
|
echo '...no next tag found'
|
|
else
|
|
echo '...no previous tag found'
|
|
endif
|
|
return
|
|
endif
|
|
|
|
call s:JumpToTag(1, tag, lazy_scroll)
|
|
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
|
|
|
|
let file_open = s:HasOpenFileWindows()
|
|
|
|
if vim_quitting && file_open == 2 && !g:tagbar_autoclose_netrw
|
|
call tagbar#debug#log('Closing Tagbar due to QuitPre - netrw only remaining window')
|
|
call s:CloseWindow()
|
|
return
|
|
endif
|
|
|
|
if vim_quitting && file_open != 1
|
|
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
|
|
|
|
call s:known_files.rm(fnamemodify(a:bufname, ':p'))
|
|
|
|
let tagbarwinnr = bufwinnr(s:TagbarBufName())
|
|
if tagbarwinnr == -1 || a:bufname =~# '__Tagbar__.*'
|
|
return
|
|
endif
|
|
|
|
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
|
|
let netrw = 0
|
|
|
|
for i in range(1, winnr('$'))
|
|
let buf = winbufnr(i)
|
|
|
|
" If the buffer filetype is netrw (or nerdtree) then mark netrw
|
|
" for final return. If we don't find any other window, we want
|
|
" to leave the netrw window open and not close vim entirely when
|
|
" called from the HandleOnlyWindow() code path.
|
|
let buf_ft = getbufvar(buf, '&filetype')
|
|
if buf_ft ==# 'netrw' || buf_ft == 'nerdtree'
|
|
let netrw = 1
|
|
endif
|
|
|
|
" skip unlisted buffers
|
|
if !buflisted(buf)
|
|
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
|
|
|
|
if netrw
|
|
call tagbar#debug#log('netrw only window remaining')
|
|
return 2
|
|
endif
|
|
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, 1, 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 = g:tagbar_highlight_method
|
|
endif
|
|
else
|
|
let longsig = 0
|
|
let fullpath = 0
|
|
let prototype = 0
|
|
let search_method = g:tagbar_highlight_method
|
|
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
|
|
|
|
" tagbar#IsOpen() {{{2
|
|
function! tagbar#IsOpen() abort
|
|
let tagbarwinnr = bufwinnr('__Tagbar__')
|
|
if tagbarwinnr != -1
|
|
" Window open
|
|
return 1
|
|
else
|
|
" Window not open
|
|
return 0
|
|
endif
|
|
endfunction
|
|
|
|
" tagbar#jump() {{{2
|
|
function! tagbar#jump() abort
|
|
if &filetype !=# 'tagbar'
|
|
" Not in tagbar window - ignore this function call
|
|
return
|
|
endif
|
|
call s:JumpToTag(1)
|
|
endfun
|
|
|
|
" tagbar#jumpToNearbyTag() {{{2
|
|
" params:
|
|
" direction = -1:backwards search 1:forward search
|
|
" [search_method] = Search method to use for GetTagNearLine()
|
|
" [flags] = list of flags (as a string) to control behavior
|
|
" 's' - use the g:tagbar_scroll_offset setting when jumping
|
|
function! tagbar#jumpToNearbyTag(direction, ...) abort
|
|
let search_method = a:0 >= 1 ? a:1 : 'nearest-stl'
|
|
let flags = a:0 >= 2 ? a:2 : ''
|
|
|
|
call s:JumpToNearbyTag(a:direction, search_method, flags)
|
|
endfunction
|
|
|
|
" Modeline {{{1
|
|
" vim: ts=8 sw=4 sts=4 et foldenable foldmethod=marker foldcolumn=1
|