tagbar/autoload/tagbar.vim
2012-02-12 18:36:45 +13:00

3177 lines
97 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: http://majutsushi.github.com/tagbar/
" Version: 2.3
" 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
" Basic init {{{2
if !exists('g:tagbar_ctags_bin')
if executable('ctags-exuberant')
let g:tagbar_ctags_bin = 'ctags-exuberant'
elseif executable('exuberant-ctags')
let g:tagbar_ctags_bin = 'exuberant-ctags'
elseif executable('exctags')
let g:tagbar_ctags_bin = 'exctags'
elseif has('macunix') && executable('/usr/local/bin/ctags')
" Homebrew default location
let g:tagbar_ctags_bin = '/usr/local/bin/ctags'
elseif has('macunix') && executable('/opt/local/bin/ctags')
" Macports default location
let g:tagbar_ctags_bin = '/opt/local/bin/ctags'
elseif executable('ctags')
let g:tagbar_ctags_bin = 'ctags'
elseif executable('ctags.exe')
let g:tagbar_ctags_bin = 'ctags.exe'
elseif executable('tags')
let g:tagbar_ctags_bin = 'tags'
else
echomsg 'Tagbar: Exuberant ctags not found, skipping plugin'
finish
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)
echomsg "Tagbar: Exuberant ctags not found at " .
\ "'" . g:tagbar_ctags_bin . "', " .
\ "skipping plugin"
finish
endif
endif
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 s:icon_closed = g:tagbar_iconchars[0]
let s:icon_open = g:tagbar_iconchars[1]
let s:type_init_done = 0
let s:autocommands_done = 0
let s:checked_ctags = 0
let s:checked_ctags_types = 0
let s:ctags_types = {}
let s:window_expanded = 0
let s:access_symbols = {
\ 'public' : '+',
\ 'protected' : '#',
\ 'private' : '-'
\ }
let g:loaded_tagbar = 1
let s:last_highlight_tline = 0
let s:debug = 0
let s:debug_file = ''
" s:Init() {{{2
function! s:Init()
if !s:checked_ctags
if !s:CheckForExCtags()
return 0
endif
endif
if !s:checked_ctags_types
call s:GetSupportedFiletypes()
endif
if !s:type_init_done
call s:InitTypes()
endif
return 1
endfunction
" s:InitTypes() {{{2
function! s:InitTypes()
call s:LogDebugMessage('Initializing types')
let s:known_types = {}
" Ant {{{3
let type_ant = {}
let type_ant.ctagstype = 'ant'
let type_ant.kinds = [
\ {'short' : 'p', 'long' : 'projects', 'fold' : 0},
\ {'short' : 't', 'long' : 'targets', 'fold' : 0}
\ ]
let s:known_types.ant = type_ant
" Asm {{{3
let type_asm = {}
let type_asm.ctagstype = 'asm'
let type_asm.kinds = [
\ {'short' : 'm', 'long' : 'macros', 'fold' : 0},
\ {'short' : 't', 'long' : 'types', 'fold' : 0},
\ {'short' : 'd', 'long' : 'defines', 'fold' : 0},
\ {'short' : 'l', 'long' : 'labels', 'fold' : 0}
\ ]
let s:known_types.asm = type_asm
" ASP {{{3
let type_aspvbs = {}
let type_aspvbs.ctagstype = 'asp'
let type_aspvbs.kinds = [
\ {'short' : 'd', 'long' : 'constants', 'fold' : 0},
\ {'short' : 'c', 'long' : 'classes', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0},
\ {'short' : 's', 'long' : 'subroutines', 'fold' : 0},
\ {'short' : 'v', 'long' : 'variables', 'fold' : 0}
\ ]
let s:known_types.aspvbs = type_aspvbs
" Awk {{{3
let type_awk = {}
let type_awk.ctagstype = 'awk'
let type_awk.kinds = [
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0}
\ ]
let s:known_types.awk = type_awk
" Basic {{{3
let type_basic = {}
let type_basic.ctagstype = 'basic'
let type_basic.kinds = [
\ {'short' : 'c', 'long' : 'constants', 'fold' : 0},
\ {'short' : 'g', 'long' : 'enumerations', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0},
\ {'short' : 'l', 'long' : 'labels', 'fold' : 0},
\ {'short' : 't', 'long' : 'types', 'fold' : 0},
\ {'short' : 'v', 'long' : 'variables', 'fold' : 0}
\ ]
let s:known_types.basic = type_basic
" BETA {{{3
let type_beta = {}
let type_beta.ctagstype = 'beta'
let type_beta.kinds = [
\ {'short' : 'f', 'long' : 'fragments', 'fold' : 0},
\ {'short' : 's', 'long' : 'slots', 'fold' : 0},
\ {'short' : 'v', 'long' : 'patterns', 'fold' : 0}
\ ]
let s:known_types.beta = type_beta
" C {{{3
let type_c = {}
let type_c.ctagstype = 'c'
let type_c.kinds = [
\ {'short' : 'd', 'long' : 'macros', 'fold' : 1},
\ {'short' : 'p', 'long' : 'prototypes', 'fold' : 1},
\ {'short' : 'g', 'long' : 'enums', 'fold' : 0},
\ {'short' : 'e', 'long' : 'enumerators', 'fold' : 0},
\ {'short' : 't', 'long' : 'typedefs', 'fold' : 0},
\ {'short' : 's', 'long' : 'structs', 'fold' : 0},
\ {'short' : 'u', 'long' : 'unions', 'fold' : 0},
\ {'short' : 'm', 'long' : 'members', 'fold' : 0},
\ {'short' : 'v', 'long' : 'variables', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0}
\ ]
let type_c.sro = '::'
let type_c.kind2scope = {
\ 'g' : 'enum',
\ 's' : 'struct',
\ 'u' : 'union'
\ }
let type_c.scope2kind = {
\ 'enum' : 'g',
\ 'struct' : 's',
\ 'union' : 'u'
\ }
let s:known_types.c = type_c
" C++ {{{3
let type_cpp = {}
let type_cpp.ctagstype = 'c++'
let type_cpp.kinds = [
\ {'short' : 'd', 'long' : 'macros', 'fold' : 1},
\ {'short' : 'p', 'long' : 'prototypes', 'fold' : 1},
\ {'short' : 'g', 'long' : 'enums', 'fold' : 0},
\ {'short' : 'e', 'long' : 'enumerators', 'fold' : 0},
\ {'short' : 't', 'long' : 'typedefs', 'fold' : 0},
\ {'short' : 'n', 'long' : 'namespaces', 'fold' : 0},
\ {'short' : 'c', 'long' : 'classes', 'fold' : 0},
\ {'short' : 's', 'long' : 'structs', 'fold' : 0},
\ {'short' : 'u', 'long' : 'unions', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0},
\ {'short' : 'm', 'long' : 'members', 'fold' : 0},
\ {'short' : 'v', 'long' : 'variables', 'fold' : 0}
\ ]
let type_cpp.sro = '::'
let type_cpp.kind2scope = {
\ 'g' : 'enum',
\ 'n' : 'namespace',
\ 'c' : 'class',
\ 's' : 'struct',
\ 'u' : 'union'
\ }
let type_cpp.scope2kind = {
\ 'enum' : 'g',
\ 'namespace' : 'n',
\ 'class' : 'c',
\ 'struct' : 's',
\ 'union' : 'u'
\ }
let s:known_types.cpp = type_cpp
" C# {{{3
let type_cs = {}
let type_cs.ctagstype = 'c#'
let type_cs.kinds = [
\ {'short' : 'd', 'long' : 'macros', 'fold' : 1},
\ {'short' : 'f', 'long' : 'fields', 'fold' : 0},
\ {'short' : 'g', 'long' : 'enums', 'fold' : 0},
\ {'short' : 'e', 'long' : 'enumerators', 'fold' : 0},
\ {'short' : 't', 'long' : 'typedefs', 'fold' : 0},
\ {'short' : 'n', 'long' : 'namespaces', 'fold' : 0},
\ {'short' : 'i', 'long' : 'interfaces', 'fold' : 0},
\ {'short' : 'c', 'long' : 'classes', 'fold' : 0},
\ {'short' : 's', 'long' : 'structs', 'fold' : 0},
\ {'short' : 'E', 'long' : 'events', 'fold' : 0},
\ {'short' : 'm', 'long' : 'methods', 'fold' : 0},
\ {'short' : 'p', 'long' : 'properties', 'fold' : 0}
\ ]
let type_cs.sro = '.'
let type_cs.kind2scope = {
\ 'n' : 'namespace',
\ 'i' : 'interface',
\ 'c' : 'class',
\ 's' : 'struct',
\ 'g' : 'enum'
\ }
let type_cs.scope2kind = {
\ 'namespace' : 'n',
\ 'interface' : 'i',
\ 'class' : 'c',
\ 'struct' : 's',
\ 'enum' : 'g'
\ }
let s:known_types.cs = type_cs
" COBOL {{{3
let type_cobol = {}
let type_cobol.ctagstype = 'cobol'
let type_cobol.kinds = [
\ {'short' : 'd', 'long' : 'data items', 'fold' : 0},
\ {'short' : 'f', 'long' : 'file descriptions', 'fold' : 0},
\ {'short' : 'g', 'long' : 'group items', 'fold' : 0},
\ {'short' : 'p', 'long' : 'paragraphs', 'fold' : 0},
\ {'short' : 'P', 'long' : 'program ids', 'fold' : 0},
\ {'short' : 's', 'long' : 'sections', 'fold' : 0}
\ ]
let s:known_types.cobol = type_cobol
" DOS Batch {{{3
let type_dosbatch = {}
let type_dosbatch.ctagstype = 'dosbatch'
let type_dosbatch.kinds = [
\ {'short' : 'l', 'long' : 'labels', 'fold' : 0},
\ {'short' : 'v', 'long' : 'variables', 'fold' : 0}
\ ]
let s:known_types.dosbatch = type_dosbatch
" Eiffel {{{3
let type_eiffel = {}
let type_eiffel.ctagstype = 'eiffel'
let type_eiffel.kinds = [
\ {'short' : 'c', 'long' : 'classes', 'fold' : 0},
\ {'short' : 'f', 'long' : 'features', 'fold' : 0}
\ ]
let type_eiffel.sro = '.' " Not sure, is nesting even possible?
let type_eiffel.kind2scope = {
\ 'c' : 'class',
\ 'f' : 'feature'
\ }
let type_eiffel.scope2kind = {
\ 'class' : 'c',
\ 'feature' : 'f'
\ }
let s:known_types.eiffel = type_eiffel
" Erlang {{{3
let type_erlang = {}
let type_erlang.ctagstype = 'erlang'
let type_erlang.kinds = [
\ {'short' : 'm', 'long' : 'modules', 'fold' : 0},
\ {'short' : 'd', 'long' : 'macro definitions', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0},
\ {'short' : 'r', 'long' : 'record definitions', 'fold' : 0}
\ ]
let type_erlang.sro = '.' " Not sure, is nesting even possible?
let type_erlang.kind2scope = {
\ 'm' : 'module'
\ }
let type_erlang.scope2kind = {
\ 'module' : 'm'
\ }
let s:known_types.erlang = type_erlang
" Flex {{{3
" Vim doesn't support Flex out of the box, this is based on rough
" guesses and probably requires
" http://www.vim.org/scripts/script.php?script_id=2909
" Improvements welcome!
let type_mxml = {}
let type_mxml.ctagstype = 'flex'
let type_mxml.kinds = [
\ {'short' : 'v', 'long' : 'global variables', 'fold' : 0},
\ {'short' : 'c', 'long' : 'classes', 'fold' : 0},
\ {'short' : 'm', 'long' : 'methods', 'fold' : 0},
\ {'short' : 'p', 'long' : 'properties', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0},
\ {'short' : 'x', 'long' : 'mxtags', 'fold' : 0}
\ ]
let type_mxml.sro = '.'
let type_mxml.kind2scope = {
\ 'c' : 'class'
\ }
let type_mxml.scope2kind = {
\ 'class' : 'c'
\ }
let s:known_types.mxml = type_mxml
" Fortran {{{3
let type_fortran = {}
let type_fortran.ctagstype = 'fortran'
let type_fortran.kinds = [
\ {'short' : 'm', 'long' : 'modules', 'fold' : 0},
\ {'short' : 'p', 'long' : 'programs', 'fold' : 0},
\ {'short' : 'k', 'long' : 'components', 'fold' : 0},
\ {'short' : 't', 'long' : 'derived types and structures', 'fold' : 0},
\ {'short' : 'c', 'long' : 'common blocks', 'fold' : 0},
\ {'short' : 'b', 'long' : 'block data', 'fold' : 0},
\ {'short' : 'e', 'long' : 'entry points', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0},
\ {'short' : 's', 'long' : 'subroutines', 'fold' : 0},
\ {'short' : 'l', 'long' : 'labels', 'fold' : 0},
\ {'short' : 'n', 'long' : 'namelists', 'fold' : 0},
\ {'short' : 'v', 'long' : 'variables', 'fold' : 0}
\ ]
let type_fortran.sro = '.' " Not sure, is nesting even possible?
let type_fortran.kind2scope = {
\ 'm' : 'module',
\ 'p' : 'program',
\ 'f' : 'function',
\ 's' : 'subroutine'
\ }
let type_fortran.scope2kind = {
\ 'module' : 'm',
\ 'program' : 'p',
\ 'function' : 'f',
\ 'subroutine' : 's'
\ }
let s:known_types.fortran = type_fortran
" HTML {{{3
let type_html = {}
let type_html.ctagstype = 'html'
let type_html.kinds = [
\ {'short' : 'f', 'long' : 'JavaScript funtions', 'fold' : 0},
\ {'short' : 'a', 'long' : 'named anchors', 'fold' : 0}
\ ]
let s:known_types.html = type_html
" Java {{{3
let type_java = {}
let type_java.ctagstype = 'java'
let type_java.kinds = [
\ {'short' : 'p', 'long' : 'packages', 'fold' : 1},
\ {'short' : 'f', 'long' : 'fields', 'fold' : 0},
\ {'short' : 'g', 'long' : 'enum types', 'fold' : 0},
\ {'short' : 'e', 'long' : 'enum constants', 'fold' : 0},
\ {'short' : 'i', 'long' : 'interfaces', 'fold' : 0},
\ {'short' : 'c', 'long' : 'classes', 'fold' : 0},
\ {'short' : 'm', 'long' : 'methods', 'fold' : 0}
\ ]
let type_java.sro = '.'
let type_java.kind2scope = {
\ 'g' : 'enum',
\ 'i' : 'interface',
\ 'c' : 'class'
\ }
let type_java.scope2kind = {
\ 'enum' : 'g',
\ 'interface' : 'i',
\ 'class' : 'c'
\ }
let s:known_types.java = type_java
" JavaScript {{{3
" JavaScript is weird -- it does have scopes, but ctags doesn't seem to
" properly generate the information for them, instead it simply uses the
" complete name. So ctags has to be fixed before I can do anything here.
" Alternatively jsctags/doctorjs will be used if available.
let type_javascript = {}
let type_javascript.ctagstype = 'javascript'
let jsctags = s:CheckFTCtags('jsctags', 'javascript')
if jsctags != ''
let type_javascript.kinds = [
\ {'short' : 'v', 'long' : 'variables', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0}
\ ]
let type_javascript.sro = '.'
let type_javascript.kind2scope = {
\ 'v' : 'namespace',
\ 'f' : 'namespace'
\ }
let type_javascript.scope2kind = {
\ 'namespace' : 'v'
\ }
let type_javascript.ctagsbin = jsctags
let type_javascript.ctagsargs = '-f -'
else
let type_javascript.kinds = [
\ {'short' : 'v', 'long' : 'global variables', 'fold' : 0},
\ {'short' : 'c', 'long' : 'classes', 'fold' : 0},
\ {'short' : 'p', 'long' : 'properties', 'fold' : 0},
\ {'short' : 'm', 'long' : 'methods', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0}
\ ]
endif
let s:known_types.javascript = type_javascript
" Lisp {{{3
let type_lisp = {}
let type_lisp.ctagstype = 'lisp'
let type_lisp.kinds = [
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0}
\ ]
let s:known_types.lisp = type_lisp
" Lua {{{3
let type_lua = {}
let type_lua.ctagstype = 'lua'
let type_lua.kinds = [
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0}
\ ]
let s:known_types.lua = type_lua
" Make {{{3
let type_make = {}
let type_make.ctagstype = 'make'
let type_make.kinds = [
\ {'short' : 'm', 'long' : 'macros', 'fold' : 0}
\ ]
let s:known_types.make = type_make
" Matlab {{{3
let type_matlab = {}
let type_matlab.ctagstype = 'matlab'
let type_matlab.kinds = [
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0}
\ ]
let s:known_types.matlab = type_matlab
" Ocaml {{{3
let type_ocaml = {}
let type_ocaml.ctagstype = 'ocaml'
let type_ocaml.kinds = [
\ {'short' : 'M', 'long' : 'modules or functors', 'fold' : 0},
\ {'short' : 'v', 'long' : 'global variables', 'fold' : 0},
\ {'short' : 'c', 'long' : 'classes', 'fold' : 0},
\ {'short' : 'C', 'long' : 'constructors', 'fold' : 0},
\ {'short' : 'm', 'long' : 'methods', 'fold' : 0},
\ {'short' : 'e', 'long' : 'exceptions', 'fold' : 0},
\ {'short' : 't', 'long' : 'type names', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0},
\ {'short' : 'r', 'long' : 'structure fields', 'fold' : 0}
\ ]
let type_ocaml.sro = '.' " Not sure, is nesting even possible?
let type_ocaml.kind2scope = {
\ 'M' : 'Module',
\ 'c' : 'class',
\ 't' : 'type'
\ }
let type_ocaml.scope2kind = {
\ 'Module' : 'M',
\ 'class' : 'c',
\ 'type' : 't'
\ }
let s:known_types.ocaml = type_ocaml
" Pascal {{{3
let type_pascal = {}
let type_pascal.ctagstype = 'pascal'
let type_pascal.kinds = [
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0},
\ {'short' : 'p', 'long' : 'procedures', 'fold' : 0}
\ ]
let s:known_types.pascal = type_pascal
" Perl {{{3
let type_perl = {}
let type_perl.ctagstype = 'perl'
let type_perl.kinds = [
\ {'short' : 'p', 'long' : 'packages', 'fold' : 1},
\ {'short' : 'c', 'long' : 'constants', 'fold' : 0},
\ {'short' : 'f', 'long' : 'formats', 'fold' : 0},
\ {'short' : 'l', 'long' : 'labels', 'fold' : 0},
\ {'short' : 's', 'long' : 'subroutines', 'fold' : 0}
\ ]
let s:known_types.perl = type_perl
" PHP {{{3
let type_php = {}
let type_php.ctagstype = 'php'
let type_php.kinds = [
\ {'short' : 'i', 'long' : 'interfaces', 'fold' : 0},
\ {'short' : 'c', 'long' : 'classes', 'fold' : 0},
\ {'short' : 'd', 'long' : 'constant definitions', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0},
\ {'short' : 'v', 'long' : 'variables', 'fold' : 0},
\ {'short' : 'j', 'long' : 'javascript functions', 'fold' : 0}
\ ]
let s:known_types.php = type_php
" Python {{{3
let type_python = {}
let type_python.ctagstype = 'python'
let type_python.kinds = [
\ {'short' : 'i', 'long' : 'imports', 'fold' : 1},
\ {'short' : 'c', 'long' : 'classes', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0},
\ {'short' : 'm', 'long' : 'members', 'fold' : 0},
\ {'short' : 'v', 'long' : 'variables', 'fold' : 0}
\ ]
let type_python.sro = '.'
let type_python.kind2scope = {
\ 'c' : 'class',
\ 'f' : 'function',
\ 'm' : 'function'
\ }
let type_python.scope2kind = {
\ 'class' : 'c',
\ 'function' : 'f'
\ }
let s:known_types.python = type_python
" REXX {{{3
let type_rexx = {}
let type_rexx.ctagstype = 'rexx'
let type_rexx.kinds = [
\ {'short' : 's', 'long' : 'subroutines', 'fold' : 0}
\ ]
let s:known_types.rexx = type_rexx
" Ruby {{{3
let type_ruby = {}
let type_ruby.ctagstype = 'ruby'
let type_ruby.kinds = [
\ {'short' : 'm', 'long' : 'modules', 'fold' : 0},
\ {'short' : 'c', 'long' : 'classes', 'fold' : 0},
\ {'short' : 'f', 'long' : 'methods', 'fold' : 0},
\ {'short' : 'F', 'long' : 'singleton methods', 'fold' : 0}
\ ]
let type_ruby.sro = '.'
let type_ruby.kind2scope = {
\ 'c' : 'class',
\ 'm' : 'class'
\ }
let type_ruby.scope2kind = {
\ 'class' : 'c'
\ }
let s:known_types.ruby = type_ruby
" Scheme {{{3
let type_scheme = {}
let type_scheme.ctagstype = 'scheme'
let type_scheme.kinds = [
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0},
\ {'short' : 's', 'long' : 'sets', 'fold' : 0}
\ ]
let s:known_types.scheme = type_scheme
" Shell script {{{3
let type_sh = {}
let type_sh.ctagstype = 'sh'
let type_sh.kinds = [
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0}
\ ]
let s:known_types.sh = type_sh
let s:known_types.csh = type_sh
let s:known_types.zsh = type_sh
" SLang {{{3
let type_slang = {}
let type_slang.ctagstype = 'slang'
let type_slang.kinds = [
\ {'short' : 'n', 'long' : 'namespaces', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0}
\ ]
let s:known_types.slang = type_slang
" SML {{{3
let type_sml = {}
let type_sml.ctagstype = 'sml'
let type_sml.kinds = [
\ {'short' : 'e', 'long' : 'exception declarations', 'fold' : 0},
\ {'short' : 'f', 'long' : 'function definitions', 'fold' : 0},
\ {'short' : 'c', 'long' : 'functor definitions', 'fold' : 0},
\ {'short' : 's', 'long' : 'signature declarations', 'fold' : 0},
\ {'short' : 'r', 'long' : 'structure declarations', 'fold' : 0},
\ {'short' : 't', 'long' : 'type definitions', 'fold' : 0},
\ {'short' : 'v', 'long' : 'value bindings', 'fold' : 0}
\ ]
let s:known_types.sml = type_sml
" SQL {{{3
" The SQL ctags parser seems to be buggy for me, so this just uses the
" normal kinds even though scopes should be available. Improvements
" welcome!
let type_sql = {}
let type_sql.ctagstype = 'sql'
let type_sql.kinds = [
\ {'short' : 'P', 'long' : 'packages', 'fold' : 1},
\ {'short' : 'c', 'long' : 'cursors', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0},
\ {'short' : 'F', 'long' : 'record fields', 'fold' : 0},
\ {'short' : 'L', 'long' : 'block label', 'fold' : 0},
\ {'short' : 'p', 'long' : 'procedures', 'fold' : 0},
\ {'short' : 's', 'long' : 'subtypes', 'fold' : 0},
\ {'short' : 't', 'long' : 'tables', 'fold' : 0},
\ {'short' : 'T', 'long' : 'triggers', 'fold' : 0},
\ {'short' : 'v', 'long' : 'variables', 'fold' : 0},
\ {'short' : 'i', 'long' : 'indexes', 'fold' : 0},
\ {'short' : 'e', 'long' : 'events', 'fold' : 0},
\ {'short' : 'U', 'long' : 'publications', 'fold' : 0},
\ {'short' : 'R', 'long' : 'services', 'fold' : 0},
\ {'short' : 'D', 'long' : 'domains', 'fold' : 0},
\ {'short' : 'V', 'long' : 'views', 'fold' : 0},
\ {'short' : 'n', 'long' : 'synonyms', 'fold' : 0},
\ {'short' : 'x', 'long' : 'MobiLink Table Scripts', 'fold' : 0},
\ {'short' : 'y', 'long' : 'MobiLink Conn Scripts', 'fold' : 0}
\ ]
let s:known_types.sql = type_sql
" Tcl {{{3
let type_tcl = {}
let type_tcl.ctagstype = 'tcl'
let type_tcl.kinds = [
\ {'short' : 'c', 'long' : 'classes', 'fold' : 0},
\ {'short' : 'm', 'long' : 'methods', 'fold' : 0},
\ {'short' : 'p', 'long' : 'procedures', 'fold' : 0}
\ ]
let s:known_types.tcl = type_tcl
" LaTeX {{{3
let type_tex = {}
let type_tex.ctagstype = 'tex'
let type_tex.kinds = [
\ {'short' : 'p', 'long' : 'parts', 'fold' : 0},
\ {'short' : 'c', 'long' : 'chapters', 'fold' : 0},
\ {'short' : 's', 'long' : 'sections', 'fold' : 0},
\ {'short' : 'u', 'long' : 'subsections', 'fold' : 0},
\ {'short' : 'b', 'long' : 'subsubsections', 'fold' : 0},
\ {'short' : 'P', 'long' : 'paragraphs', 'fold' : 0},
\ {'short' : 'G', 'long' : 'subparagraphs', 'fold' : 0}
\ ]
let s:known_types.tex = type_tex
" Vera {{{3
" Why are variables 'virtual'?
let type_vera = {}
let type_vera.ctagstype = 'vera'
let type_vera.kinds = [
\ {'short' : 'd', 'long' : 'macros', 'fold' : 1},
\ {'short' : 'g', 'long' : 'enums', 'fold' : 0},
\ {'short' : 'T', 'long' : 'typedefs', 'fold' : 0},
\ {'short' : 'c', 'long' : 'classes', 'fold' : 0},
\ {'short' : 'e', 'long' : 'enumerators', 'fold' : 0},
\ {'short' : 'm', 'long' : 'members', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0},
\ {'short' : 't', 'long' : 'tasks', 'fold' : 0},
\ {'short' : 'v', 'long' : 'variables', 'fold' : 0},
\ {'short' : 'p', 'long' : 'programs', 'fold' : 0}
\ ]
let type_vera.sro = '.' " Nesting doesn't seem to be possible
let type_vera.kind2scope = {
\ 'g' : 'enum',
\ 'c' : 'class',
\ 'v' : 'virtual'
\ }
let type_vera.scope2kind = {
\ 'enum' : 'g',
\ 'class' : 'c',
\ 'virtual' : 'v'
\ }
let s:known_types.vera = type_vera
" Verilog {{{3
let type_verilog = {}
let type_verilog.ctagstype = 'verilog'
let type_verilog.kinds = [
\ {'short' : 'c', 'long' : 'constants', 'fold' : 0},
\ {'short' : 'e', 'long' : 'events', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0},
\ {'short' : 'm', 'long' : 'modules', 'fold' : 0},
\ {'short' : 'n', 'long' : 'net data types', 'fold' : 0},
\ {'short' : 'p', 'long' : 'ports', 'fold' : 0},
\ {'short' : 'r', 'long' : 'register data types', 'fold' : 0},
\ {'short' : 't', 'long' : 'tasks', 'fold' : 0}
\ ]
let s:known_types.verilog = type_verilog
" VHDL {{{3
" The VHDL ctags parser unfortunately doesn't generate proper scopes
let type_vhdl = {}
let type_vhdl.ctagstype = 'vhdl'
let type_vhdl.kinds = [
\ {'short' : 'P', 'long' : 'packages', 'fold' : 1},
\ {'short' : 'c', 'long' : 'constants', 'fold' : 0},
\ {'short' : 't', 'long' : 'types', 'fold' : 0},
\ {'short' : 'T', 'long' : 'subtypes', 'fold' : 0},
\ {'short' : 'r', 'long' : 'records', 'fold' : 0},
\ {'short' : 'e', 'long' : 'entities', 'fold' : 0},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0},
\ {'short' : 'p', 'long' : 'procedures', 'fold' : 0}
\ ]
let s:known_types.vhdl = type_vhdl
" Vim {{{3
let type_vim = {}
let type_vim.ctagstype = 'vim'
let type_vim.kinds = [
\ {'short' : 'v', 'long' : 'variables', 'fold' : 1},
\ {'short' : 'f', 'long' : 'functions', 'fold' : 0},
\ {'short' : 'a', 'long' : 'autocommand groups', 'fold' : 1},
\ {'short' : 'c', 'long' : 'commands', 'fold' : 0},
\ {'short' : 'm', 'long' : 'maps', 'fold' : 1}
\ ]
let s:known_types.vim = type_vim
" YACC {{{3
let type_yacc = {}
let type_yacc.ctagstype = 'yacc'
let type_yacc.kinds = [
\ {'short' : 'l', 'long' : 'labels', 'fold' : 0}
\ ]
let s:known_types.yacc = type_yacc
" }}}3
let user_defs = s:GetUserTypeDefs()
for [key, value] in items(user_defs)
if !has_key(s:known_types, key) ||
\ (has_key(value, 'replace') && value.replace)
let s:known_types[key] = value
else
call extend(s:known_types[key], value)
endif
endfor
" Create a dictionary of the kind order for fast
" access in sorting functions
for type in values(s:known_types)
let i = 0
let type.kinddict = {}
for kind in type.kinds
let type.kinddict[kind.short] = i
let i += 1
endfor
endfor
let s:type_init_done = 1
endfunction
" s:GetUserTypeDefs() {{{2
function! s:GetUserTypeDefs()
call s:LogDebugMessage('Initializing user types')
redir => defs
silent execute 'let g:'
redir END
let deflist = split(defs, '\n')
call map(deflist, 'substitute(v:val, ''^\S\+\zs.*'', "", "")')
call filter(deflist, 'v:val =~ "^tagbar_type_"')
let defdict = {}
for defstr in deflist
let type = substitute(defstr, '^tagbar_type_', '', '')
execute 'let defdict["' . type . '"] = g:' . defstr
endfor
" If the user only specified one of kind2scope and scope2kind use it to
" generate the other one
" Also, transform the 'kind' definitions into dictionary format
for def in values(defdict)
if has_key(def, 'kinds')
let kinds = def.kinds
let def.kinds = []
for kind in kinds
let kindlist = split(kind, ':')
let kinddict = {'short' : kindlist[0], 'long' : kindlist[1]}
if len(kindlist) == 3
let kinddict.fold = kindlist[2]
else
let kinddict.fold = 0
endif
call add(def.kinds, kinddict)
endfor
endif
if has_key(def, 'kind2scope') && !has_key(def, 'scope2kind')
let def.scope2kind = {}
for [key, value] in items(def.kind2scope)
let def.scope2kind[value] = key
endfor
elseif has_key(def, 'scope2kind') && !has_key(def, 'kind2scope')
let def.kind2scope = {}
for [key, value] in items(def.scope2kind)
let def.kind2scope[value] = key
endfor
endif
endfor
return defdict
endfunction
" s:RestoreSession() {{{2
" Properly restore Tagbar after a session got loaded
function! s:RestoreSession()
call s:LogDebugMessage('Restoring session')
let curfile = fnamemodify(bufname('%'), ':p')
let tagbarwinnr = bufwinnr('__Tagbar__')
if tagbarwinnr == -1
" Tagbar wasn't open in the saved session, nothing to do
return
else
let in_tagbar = 1
if winnr() != tagbarwinnr
call s:winexec(tagbarwinnr . 'wincmd w')
let in_tagbar = 0
endif
endif
call s:Init()
call s:InitWindow(g:tagbar_autoclose)
call s:AutoUpdate(curfile)
if !in_tagbar
call s:winexec('wincmd p')
endif
endfunction
" s:MapKeys() {{{2
function! s:MapKeys()
call s:LogDebugMessage('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>
nnoremap <script> <silent> <buffer> <CR> :call <SID>JumpToTag(0)<CR>
nnoremap <script> <silent> <buffer> p :call <SID>JumpToTag(1)<CR>
nnoremap <script> <silent> <buffer> <Space> :call <SID>ShowPrototype()<CR>
nnoremap <script> <silent> <buffer> + :call <SID>OpenFold()<CR>
nnoremap <script> <silent> <buffer> <kPlus> :call <SID>OpenFold()<CR>
nnoremap <script> <silent> <buffer> zo :call <SID>OpenFold()<CR>
nnoremap <script> <silent> <buffer> - :call <SID>CloseFold()<CR>
nnoremap <script> <silent> <buffer> <kMinus> :call <SID>CloseFold()<CR>
nnoremap <script> <silent> <buffer> zc :call <SID>CloseFold()<CR>
nnoremap <script> <silent> <buffer> o :call <SID>ToggleFold()<CR>
nnoremap <script> <silent> <buffer> za :call <SID>ToggleFold()<CR>
nnoremap <script> <silent> <buffer> * :call <SID>SetFoldLevel(99)<CR>
nnoremap <script> <silent> <buffer> <kMultiply>
\ :call <SID>SetFoldLevel(99)<CR>
nnoremap <script> <silent> <buffer> zR :call <SID>SetFoldLevel(99)<CR>
nnoremap <script> <silent> <buffer> = :call <SID>SetFoldLevel(0)<CR>
nnoremap <script> <silent> <buffer> zM :call <SID>SetFoldLevel(0)<CR>
nnoremap <script> <silent> <buffer> <C-N>
\ :call <SID>GotoNextToplevelTag(1)<CR>
nnoremap <script> <silent> <buffer> <C-P>
\ :call <SID>GotoNextToplevelTag(-1)<CR>
nnoremap <script> <silent> <buffer> s :call <SID>ToggleSort()<CR>
nnoremap <script> <silent> <buffer> x :call <SID>ZoomWindow()<CR>
nnoremap <script> <silent> <buffer> q :call <SID>CloseWindow()<CR>
nnoremap <script> <silent> <buffer> <F1> :call <SID>ToggleHelp()<CR>
endfunction
" s:CreateAutocommands() {{{2
function! s:CreateAutocommands()
call s:LogDebugMessage('Creating autocommands')
augroup TagbarAutoCmds
autocmd!
autocmd BufEnter __Tagbar__ nested call s:QuitIfOnlyWindow()
autocmd BufUnload __Tagbar__ call s:CleanUp()
autocmd CursorHold __Tagbar__ call s:ShowPrototype()
autocmd BufWritePost *
\ if line('$') < g:tagbar_updateonsave_maxlines |
\ call s:AutoUpdate(fnamemodify(expand('<afile>'), ':p')) |
\ endif
autocmd BufEnter,CursorHold,FileType * call
\ s:AutoUpdate(fnamemodify(expand('<afile>'), ':p'))
autocmd BufDelete * call
\ s:CleanupFileinfo(fnamemodify(expand('<afile>'), ':p'))
augroup END
let s:autocommands_done = 1
endfunction
" s:CheckForExCtags() {{{2
" Test whether the ctags binary is actually Exuberant Ctags and not GNU ctags
" (or something else)
function! s:CheckForExCtags()
call s:LogDebugMessage('Checking for Exuberant Ctags')
let ctags_cmd = s:EscapeCtagsCmd(g:tagbar_ctags_bin, '--version')
if ctags_cmd == ''
return
endif
let ctags_output = s:ExecuteCtags(ctags_cmd)
if v:shell_error || ctags_output !~# 'Exuberant Ctags'
echoerr 'Tagbar: Ctags doesn''t seem to be Exuberant Ctags!'
echomsg 'GNU 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.'
echomsg 'Executed command: "' . ctags_cmd . '"'
if !empty(ctags_output)
echomsg 'Command output:'
for line in split(ctags_output, '\n')
echomsg line
endfor
endif
return 0
elseif !s:CheckExCtagsVersion(ctags_output)
echoerr 'Tagbar: Exuberant Ctags is too old!'
echomsg 'You need at least version 5.5 for Tagbar to work.'
\ 'Please download a newer version from ctags.sourceforge.net.'
echomsg 'Executed command: "' . ctags_cmd . '"'
if !empty(ctags_output)
echomsg 'Command output:'
for line in split(ctags_output, '\n')
echomsg line
endfor
endif
return 0
else
let s:checked_ctags = 1
return 1
endif
endfunction
" s:CheckExCtagsVersion() {{{2
function! s:CheckExCtagsVersion(output)
call s:LogDebugMessage('Checking Exuberant Ctags version')
if a:output =~ 'Exuberant Ctags Development'
return 1
endif
let matchlist = matchlist(a:output, '\vExuberant Ctags (\d+)\.(\d+)')
let major = matchlist[1]
let minor = matchlist[2]
return major >= 6 || (major == 5 && minor >= 5)
endfunction
" s:CheckFTCtags() {{{2
function! s:CheckFTCtags(bin, ftype)
if executable(a:bin)
return a:bin
endif
if exists('g:tagbar_type_' . a:ftype)
execute '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()
call s:LogDebugMessage('Getting filetypes sypported 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\+')
for type in types
let s:ctags_types[tolower(type)] = 1
endfor
let s:checked_ctags_types = 1
endfunction
" Prototypes {{{1
" Base tag {{{2
let s:BaseTag = {}
" s:BaseTag._init() {{{3
function! s:BaseTag._init(name) dict
let self.name = a:name
let self.fields = {}
let self.fields.line = 0
let self.path = ''
let self.fullpath = a:name
let self.depth = 0
let self.parent = {}
let self.tline = -1
let self.fileinfo = {}
endfunction
" s:BaseTag.isNormalTag() {{{3
function! s:BaseTag.isNormalTag() dict
return 0
endfunction
" s:BaseTag.isPseudoTag() {{{3
function! s:BaseTag.isPseudoTag() dict
return 0
endfunction
" s:BaseTag.isKindheader() {{{3
function! s:BaseTag.isKindheader() dict
return 0
endfunction
" s:BaseTag.getPrototype() {{{3
function! s:BaseTag.getPrototype() dict
return ''
endfunction
" s:BaseTag._getPrefix() {{{3
function! s:BaseTag._getPrefix() dict
let fileinfo = self.fileinfo
if has_key(self, 'children') && !empty(self.children)
if fileinfo.tagfolds[self.fields.kind][self.fullpath]
let prefix = s:icon_closed
else
let prefix = s:icon_open
endif
else
let prefix = ' '
endif
if has_key(self.fields, 'access')
let prefix .= get(s:access_symbols, self.fields.access, ' ')
else
let prefix .= ' '
endif
return prefix
endfunction
" s:BaseTag.initFoldState() {{{3
function! s:BaseTag.initFoldState() dict
let fileinfo = self.fileinfo
if s:known_files.has(fileinfo.fpath) &&
\ has_key(fileinfo._tagfolds_old[self.fields.kind], self.fullpath)
" The file has been updated and the tag was there before, so copy its
" old fold state
let fileinfo.tagfolds[self.fields.kind][self.fullpath] =
\ fileinfo._tagfolds_old[self.fields.kind][self.fullpath]
elseif self.depth >= fileinfo.foldlevel
let fileinfo.tagfolds[self.fields.kind][self.fullpath] = 1
else
let fileinfo.tagfolds[self.fields.kind][self.fullpath] =
\ fileinfo.kindfolds[self.fields.kind]
endif
endfunction
" s:BaseTag.getClosedParentTline() {{{3
function! s:BaseTag.getClosedParentTline() dict
let tagline = self.tline
let fileinfo = self.fileinfo
let parent = self.parent
while !empty(parent)
if parent.isFolded()
let tagline = parent.tline
break
endif
let parent = parent.parent
endwhile
return tagline
endfunction
" s:BaseTag.isFoldable() {{{3
function! s:BaseTag.isFoldable() dict
return has_key(self, 'children') && !empty(self.children)
endfunction
" s:BaseTag.isFolded() {{{3
function! s:BaseTag.isFolded() dict
return self.fileinfo.tagfolds[self.fields.kind][self.fullpath]
endfunction
" s:BaseTag.openFold() {{{3
function! s:BaseTag.openFold() dict
if self.isFoldable()
let self.fileinfo.tagfolds[self.fields.kind][self.fullpath] = 0
endif
endfunction
" s:BaseTag.closeFold() {{{3
function! s:BaseTag.closeFold() dict
let newline = line('.')
if !empty(self.parent) && self.parent.isKindheader()
" Tag is child of generic 'kind'
call self.parent.closeFold()
let newline = self.parent.tline
elseif self.isFoldable() && !self.isFolded()
" Tag is parent of a scope and is not folded
let self.fileinfo.tagfolds[self.fields.kind][self.fullpath] = 1
let newline = self.tline
elseif !empty(self.parent)
" Tag is normal child, so close parent
let parent = self.parent
let self.fileinfo.tagfolds[parent.fields.kind][parent.fullpath] = 1
let newline = parent.tline
endif
return newline
endfunction
" s:BaseTag.setFolded() {{{3
function! s:BaseTag.setFolded(folded) dict
let self.fileinfo.tagfolds[self.fields.kind][self.fullpath] = a:folded
endfunction
" s:BaseTag.openParents() {{{3
function! s:BaseTag.openParents() dict
let parent = self.parent
while !empty(parent)
call parent.openFold()
let parent = parent.parent
endwhile
endfunction
" Normal tag {{{2
let s:NormalTag = copy(s:BaseTag)
" s:NormalTag.New() {{{3
function! s:NormalTag.New(name) dict
let newobj = copy(self)
call newobj._init(a:name)
return newobj
endfunction
" s:NormalTag.isNormalTag() {{{3
function! s:NormalTag.isNormalTag() dict
return 1
endfunction
" s:NormalTag.str() {{{3
function! s:NormalTag.str() dict
let fileinfo = self.fileinfo
let typeinfo = s:known_types[fileinfo.ftype]
let suffix = get(self.fields, 'signature', '')
if has_key(self.fields, 'type')
let suffix .= ' : ' . self.fields.type
elseif has_key(typeinfo, 'kind2scope') &&
\ has_key(typeinfo.kind2scope, self.fields.kind)
let suffix .= ' : ' . typeinfo.kind2scope[self.fields.kind]
endif
return self._getPrefix() . self.name . suffix . "\n"
endfunction
" s:NormalTag.getPrototype() {{{3
function! s:NormalTag.getPrototype() dict
return self.prototype
endfunction
" Pseudo tag {{{2
let s:PseudoTag = copy(s:BaseTag)
" s:PseudoTag.New() {{{3
function! s:PseudoTag.New(name) dict
let newobj = copy(self)
call newobj._init(a:name)
return newobj
endfunction
" s:PseudoTag.isPseudoTag() {{{3
function! s:PseudoTag.isPseudoTag() dict
return 1
endfunction
" s:PseudoTag.str() {{{3
function! s:PseudoTag.str() dict
let fileinfo = self.fileinfo
let typeinfo = s:known_types[fileinfo.ftype]
let suffix = get(self.fields, 'signature', '')
if has_key(typeinfo.kind2scope, self.fields.kind)
let suffix .= ' : ' . typeinfo.kind2scope[self.fields.kind]
endif
return self._getPrefix() . self.name . '*' . suffix
endfunction
" Kind header {{{2
let s:KindheaderTag = copy(s:BaseTag)
" s:KindheaderTag.New() {{{3
function! s:KindheaderTag.New(name) dict
let newobj = copy(self)
call newobj._init(a:name)
return newobj
endfunction
" s:KindheaderTag.isKindheader() {{{3
function! s:KindheaderTag.isKindheader() dict
return 1
endfunction
" s:KindheaderTag.getPrototype() {{{3
function! s:KindheaderTag.getPrototype() dict
return self.name . ': ' .
\ self.numtags . ' ' . (self.numtags > 1 ? 'tags' : 'tag')
endfunction
" s:KindheaderTag.isFoldable() {{{3
function! s:KindheaderTag.isFoldable() dict
return 1
endfunction
" s:KindheaderTag.isFolded() {{{3
function! s:KindheaderTag.isFolded() dict
return self.fileinfo.kindfolds[self.short]
endfunction
" s:KindheaderTag.openFold() {{{3
function! s:KindheaderTag.openFold() dict
let self.fileinfo.kindfolds[self.short] = 0
endfunction
" s:KindheaderTag.closeFold() {{{3
function! s:KindheaderTag.closeFold() dict
let self.fileinfo.kindfolds[self.short] = 1
return line('.')
endfunction
" s:KindheaderTag.toggleFold() {{{3
function! s:KindheaderTag.toggleFold() dict
let fileinfo = s:known_files.getCurrent()
let fileinfo.kindfolds[self.short] = !fileinfo.kindfolds[self.short]
endfunction
" File info {{{2
let s:FileInfo = {}
" s:FileInfo.New() {{{3
function! s:FileInfo.New(fname, ftype) dict
let newobj = copy(self)
" The complete file path
let newobj.fpath = a:fname
" File modification time
let newobj.mtime = getftime(a:fname)
" The vim file type
let newobj.ftype = a:ftype
" List of the tags that are present in the file, sorted according to the
" value of 'g:tagbar_sort'
let newobj.tags = []
" Dictionary of the tags, indexed by line number in the file
let newobj.fline = {}
" Dictionary of the tags, indexed by line number in the tagbar
let newobj.tline = {}
" Dictionary of the folding state of 'kind's, indexed by short name
let newobj.kindfolds = {}
let typeinfo = s:known_types[a:ftype]
" copy the default fold state from the type info
for kind in typeinfo.kinds
let newobj.kindfolds[kind.short] =
\ g:tagbar_foldlevel == 0 ? 1 : kind.fold
endfor
" Dictionary of dictionaries of the folding state of individual tags,
" indexed by kind and full path
let newobj.tagfolds = {}
for kind in typeinfo.kinds
let newobj.tagfolds[kind.short] = {}
endfor
" The current foldlevel of the file
let newobj.foldlevel = g:tagbar_foldlevel
return newobj
endfunction
" s:FileInfo.reset() {{{3
" Reset stuff that gets regenerated while processing a file and save the old
" tag folds
function! s:FileInfo.reset() dict
let self.mtime = getftime(self.fpath)
let self.tags = []
let self.fline = {}
let self.tline = {}
let self._tagfolds_old = self.tagfolds
let self.tagfolds = {}
let typeinfo = s:known_types[self.ftype]
for kind in typeinfo.kinds
let self.tagfolds[kind.short] = {}
endfor
endfunction
" s:FileInfo.clearOldFolds() {{{3
function! s:FileInfo.clearOldFolds() dict
if exists('self._tagfolds_old')
unlet self._tagfolds_old
endif
endfunction
" s:FileInfo.sortTags() {{{3
function! s:FileInfo.sortTags() dict
if has_key(s:compare_typeinfo, 'sort')
if s:compare_typeinfo.sort
call s:SortTags(self.tags, 's:CompareByKind')
else
call s:SortTags(self.tags, 's:CompareByLine')
endif
elseif g:tagbar_sort
call s:SortTags(self.tags, 's:CompareByKind')
else
call s:SortTags(self.tags, 's:CompareByLine')
endif
endfunction
" s:FileInfo.openKindFold() {{{3
function! s:FileInfo.openKindFold(kind) dict
let self.kindfolds[a:kind.short] = 0
endfunction
" s:FileInfo.closeKindFold() {{{3
function! s:FileInfo.closeKindFold(kind) dict
let self.kindfolds[a:kind.short] = 1
endfunction
" Known files {{{2
let s:known_files = {
\ '_current' : {},
\ '_files' : {}
\ }
" s:known_files.getCurrent() {{{3
function! s:known_files.getCurrent() dict
return self._current
endfunction
" s:known_files.setCurrent() {{{3
function! s:known_files.setCurrent(fileinfo) dict
let self._current = a:fileinfo
endfunction
" s:known_files.get() {{{3
function! s:known_files.get(fname) dict
return get(self._files, a:fname, {})
endfunction
" s:known_files.put() {{{3
" Optional second argument is the filename
function! s:known_files.put(fileinfo, ...) 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() {{{3
function! s:known_files.has(fname) dict
return has_key(self._files, a:fname)
endfunction
" s:known_files.rm() {{{3
function! s:known_files.rm(fname) dict
if s:known_files.has(a:fname)
call remove(self._files, a:fname)
endif
endfunction
" Window management {{{1
" s:ToggleWindow() {{{2
function! s:ToggleWindow()
call s:LogDebugMessage('ToggleWindow called')
let tagbarwinnr = bufwinnr("__Tagbar__")
if tagbarwinnr != -1
call s:CloseWindow()
return
endif
call s:OpenWindow('')
call s:LogDebugMessage('ToggleWindow finished')
endfunction
" s:OpenWindow() {{{2
function! s:OpenWindow(flags)
call s:LogDebugMessage("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')
" If the tagbar window is already open check jump flag
" Also set the autoclose flag if requested
let tagbarwinnr = bufwinnr('__Tagbar__')
if tagbarwinnr != -1
if winnr() != tagbarwinnr && jump
call s:winexec(tagbarwinnr . 'wincmd w')
if autoclose
let w:autoclose = autoclose
endif
endif
return
endif
if !s:Init()
return 0
endif
" Expand the Vim window to accomodate for the Tagbar window if requested
if g:tagbar_expand && !s:window_expanded && has('gui_running')
let &columns += g:tagbar_width + 1
let s:window_expanded = 1
endif
let eventignore_save = &eventignore
set eventignore=all
let openpos = g:tagbar_left ? 'topleft vertical ' : 'botright vertical '
exe 'silent keepalt ' . openpos . g:tagbar_width . 'split ' . '__Tagbar__'
let &eventignore = eventignore_save
call s:InitWindow(autoclose)
call s:AutoUpdate(curfile)
if !(g:tagbar_autoclose || autofocus || g:tagbar_autofocus)
call s:winexec('wincmd p')
endif
call s:LogDebugMessage('OpenWindow finished')
endfunction
" s:InitWindow() {{{2
function! s:InitWindow(autoclose)
call s:LogDebugMessage('InitWindow called with autoclose: ' . a:autoclose)
setlocal noreadonly " in case the "view" mode is used
setlocal buftype=nofile
setlocal bufhidden=hide
setlocal noswapfile
setlocal nobuflisted
setlocal nomodifiable
setlocal filetype=tagbar
setlocal nolist
setlocal nonumber
setlocal nowrap
setlocal winfixwidth
setlocal textwidth=0
setlocal nocursorline
setlocal nocursorcolumn
if exists('+relativenumber')
setlocal norelativenumber
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&
" Earlier versions have a bug in local, evaluated statuslines
if v:version > 701 || (v:version == 701 && has('patch097'))
setlocal statusline=%!TagbarGenerateStatusline()
else
setlocal statusline=Tagbar
endif
" Script-local variable needed since compare functions can't
" take extra arguments
let s:compare_typeinfo = {}
let s:is_maximized = 0
let s:short_help = 1
let w:autoclose = a:autoclose
if has('balloon_eval')
setlocal balloonexpr=TagbarBalloonExpr()
set ballooneval
endif
let cpoptions_save = &cpoptions
set cpoptions&vim
if !hasmapto('JumpToTag', 'n')
call s:MapKeys()
endif
if !s:autocommands_done
call s:CreateAutocommands()
endif
let &cpoptions = cpoptions_save
call s:LogDebugMessage('InitWindow finished')
endfunction
" s:CloseWindow() {{{2
function! s:CloseWindow()
call s:LogDebugMessage('CloseWindow called')
let tagbarwinnr = bufwinnr('__Tagbar__')
if tagbarwinnr == -1
return
endif
let tagbarbufnr = winbufnr(tagbarwinnr)
if winnr() == tagbarwinnr
if winbufnr(2) != -1
" Other windows are open, only close the tagbar one
let curfile = s:known_files.getCurrent()
call s:winexec('close')
" Try to jump to the correct window after closing
call s:winexec('wincmd p')
if !empty(curfile)
let filebufnr = bufnr(curfile.fpath)
if bufnr('%') != filebufnr
let filewinnr = bufwinnr(filebufnr)
if filewinnr != -1
call s:winexec(filewinnr . 'wincmd w')
endif
endif
endif
endif
else
" Go to the tagbar window, close it and then come back to the
" original window
let curbufnr = bufnr('%')
call s:winexec(tagbarwinnr . 'wincmd w')
close
" Need to jump back to the original window only if we are not
" already in that window
let winnum = bufwinnr(curbufnr)
if winnr() != winnum
call s:winexec(winnum . 'wincmd w')
endif
endif
" If the Vim window has been expanded, and Tagbar is not open in any other
" tabpages, shrink the window again
if s:window_expanded
let tablist = []
for i in range(tabpagenr('$'))
call extend(tablist, tabpagebuflist(i + 1))
endfor
if index(tablist, tagbarbufnr) == -1
let &columns -= g:tagbar_width + 1
let s:window_expanded = 0
endif
endif
call s:LogDebugMessage('CloseWindow finished')
endfunction
" s:ZoomWindow() {{{2
function! s:ZoomWindow()
if s:is_maximized
execute 'vert resize ' . g:tagbar_width
let s:is_maximized = 0
else
vert resize
let s:is_maximized = 1
endif
endfunction
" Tag processing {{{1
" s:ProcessFile() {{{2
" Execute ctags and put the information into a 'FileInfo' object
function! s:ProcessFile(fname, ftype)
call s:LogDebugMessage('ProcessFile called on ' . a:fname)
if !s:IsValidFile(a:fname, a:ftype)
call s:LogDebugMessage('Not a valid file, returning')
return
endif
let ctags_output = s:ExecuteCtagsOnFile(a:fname, a:ftype)
if ctags_output == -1
call s:LogDebugMessage('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 s:LogDebugMessage('Ctags output empty')
" No need to go through the tag processing if there are no tags, and
" preserving the old fold state also isn't necessary
call s:known_files.put(s:FileInfo.New(a:fname, a:ftype), a:fname)
return
endif
" If the file has only been updated preserve the fold states, otherwise
" create a new entry
if s:known_files.has(a:fname)
let fileinfo = s:known_files.get(a:fname)
call fileinfo.reset()
else
let fileinfo = s:FileInfo.New(a:fname, a:ftype)
endif
let typeinfo = s:known_types[a:ftype]
" Parse the ctags output lines
call s:LogDebugMessage('Parsing ctags output')
let rawtaglist = split(ctags_output, '\n\+')
for line in rawtaglist
" skip comments
if line =~# '^!_TAG_'
continue
endif
let parts = split(line, ';"')
if len(parts) == 2 " Is a valid tag line
let taginfo = s:ParseTagline(parts[0], parts[1], typeinfo, fileinfo)
let fileinfo.fline[taginfo.fields.line] = taginfo
call add(fileinfo.tags, taginfo)
endif
endfor
" Process scoped tags
let processedtags = []
if has_key(typeinfo, 'kind2scope')
call s:LogDebugMessage('Processing scoped tags')
let scopedtags = []
let is_scoped = 'has_key(typeinfo.kind2scope, v:val.fields.kind) ||
\ has_key(v:val, "scope")'
let scopedtags += filter(copy(fileinfo.tags), is_scoped)
call filter(fileinfo.tags, '!(' . is_scoped . ')')
call s:AddScopedTags(scopedtags, processedtags, {}, 0,
\ typeinfo, fileinfo)
if !empty(scopedtags)
echoerr 'Tagbar: ''scopedtags'' not empty after processing,'
\ 'this should never happen!'
\ 'Please contact the script maintainer with an example.'
endif
endif
call s:LogDebugMessage('Number of top-level tags: ' . len(processedtags))
" Create a placeholder tag for the 'kind' header for folding purposes
for kind in typeinfo.kinds
let curtags = filter(copy(fileinfo.tags),
\ 'v:val.fields.kind ==# kind.short')
call s:LogDebugMessage('Processing kind: ' . kind.short .
\ ', number of tags: ' . len(curtags))
if empty(curtags)
continue
endif
let kindtag = s: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
if !empty(processedtags)
call extend(fileinfo.tags, processedtags)
endif
" Clear old folding information from previous file version to prevent leaks
call fileinfo.clearOldFolds()
" Sort the tags
let s:compare_typeinfo = typeinfo
call fileinfo.sortTags()
call s:known_files.put(fileinfo)
endfunction
" s:ExecuteCtagsOnFile() {{{2
function! s:ExecuteCtagsOnFile(fname, ftype)
call s:LogDebugMessage('ExecuteCtagsOnFile called on ' . a:fname)
let typeinfo = s:known_types[a:ftype]
if has_key(typeinfo, 'ctagsargs')
let ctags_args = ' ' . typeinfo.ctagsargs . ' '
else
let ctags_args = ' -f - '
let ctags_args .= ' --format=2 '
let ctags_args .= ' --excmd=pattern '
let ctags_args .= ' --fields=nksSa '
let ctags_args .= ' --extra= '
let ctags_args .= ' --sort=yes '
" Include extra type definitions
if has_key(typeinfo, 'deffile')
let ctags_args .= ' --options=' . typeinfo.deffile . ' '
endif
let ctags_type = typeinfo.ctagstype
let ctags_kinds = ''
for kind in typeinfo.kinds
let ctags_kinds .= kind.short
endfor
let ctags_args .= ' --language-force=' . ctags_type .
\ ' --' . ctags_type . '-kinds=' . ctags_kinds . ' '
endif
if has_key(typeinfo, 'ctagsbin')
" reset 'wildignore' temporarily in case *.exe is included in it
let wildignore_save = &wildignore
set wildignore&
let ctags_bin = expand(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 file'
echoerr 'Tagbar: Could not execute ctags for ' . a:fname . '!'
echomsg 'Executed command: "' . ctags_cmd . '"'
if !empty(ctags_output)
call s:LogDebugMessage('Command output:')
call s:LogDebugMessage(ctags_output)
echomsg 'Command output:'
for line in split(ctags_output, '\n')
echomsg line
endfor
endif
return -1
endif
call s:LogDebugMessage('Ctags executed successfully')
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)
let basic_info = split(a:part1, '\t')
let taginfo = s:NormalTag.New(basic_info[0])
let taginfo.file = 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")
let start = 2 " skip the slash and the ^
let end = strlen(pattern) - 1
if pattern[end - 1] ==# '$'
let end -= 1
let dollar = '\$'
else
let dollar = ''
endif
let pattern = strpart(pattern, start, end - start)
let taginfo.pattern = '\V\^\C' . pattern . dollar
let prototype = substitute(pattern, '^[[:space:]]\+', '', '')
let prototype = substitute(prototype, '[[:space:]]\+$', '', '')
let taginfo.prototype = prototype
let fields = split(a:part2, '\t')
let taginfo.fields.kind = remove(fields, 0)
for field in fields
" can't use split() since the value can contain ':'
let delimit = stridx(field, ':')
let key = strpart(field, 0, delimit)
let val = strpart(field, delimit + 1)
if len(val) > 0
let taginfo.fields[key] = val
endif
endfor
" Needed for jsctags
if has_key(taginfo.fields, 'lineno')
let taginfo.fields.line = taginfo.fields.lineno
endif
" Make some information easier accessible
if has_key(a:typeinfo, 'scope2kind')
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 taginfo.depth = len(split(taginfo.path, '\V' . a:typeinfo.sro))
endif
let taginfo.fileinfo = a:fileinfo
" Needed for folding
try
call taginfo.initFoldState()
catch /^Vim(\a\+):E716:/ " 'Key not present in Dictionary'
" The tag has a 'kind' that doesn't exist in the type definition
echoerr 'Your ctags and Tagbar configurations are out of sync!'
\ 'Please read '':help tagbar-extend''.'
endtry
return taginfo
endfunction
" s:AddScopedTags() {{{2
" Recursively process tags. Unfortunately there is a problem: not all tags in
" a hierarchy are actually there. For example, in C++ a class can be defined
" in a header file and implemented in a .cpp file (so the class itself doesn't
" appear in the .cpp file and thus doesn't generate a tag). Another example
" are anonymous structures like namespaces, structs, enums, and unions, that
" also don't get a tag themselves. These tags are thus called 'pseudo-tags' in
" Tagbar. Properly parsing them is quite tricky, so try not to think about it
" too much.
function! s:AddScopedTags(tags, processedtags, parent, depth,
\ typeinfo, fileinfo)
if !empty(a:parent)
let curpath = a:parent.fullpath
let pscope = a:typeinfo.kind2scope[a:parent.fields.kind]
else
let curpath = ''
let pscope = ''
endif
let is_cur_tag = 'v:val.depth == a:depth'
if !empty(curpath)
" Check whether the tag is either a direct child at the current depth
" or at least a proper grandchild with pseudo-tags in between. If it
" is a direct child also check for matching scope.
let is_cur_tag .= ' &&
\ (v:val.path ==# curpath ||
\ match(v:val.path, ''\V\^\C'' . curpath . a:typeinfo.sro) == 0) &&
\ (v:val.path ==# curpath ? (v:val.scope ==# pscope) : 1)'
endif
let curtags = filter(copy(a:tags), is_cur_tag)
if !empty(curtags)
call filter(a:tags, '!(' . is_cur_tag . ')')
let realtags = []
let pseudotags = []
while !empty(curtags)
let tag = remove(curtags, 0)
if tag.path != curpath
" tag is child of a pseudo-tag, so create a new pseudo-tag and
" add all its children to it
let pseudotag = s:ProcessPseudoTag(curtags, tag, a:parent,
\ a:typeinfo, a:fileinfo)
call add(pseudotags, pseudotag)
else
call add(realtags, tag)
endif
endwhile
" Recursively add the children of the tags on the current level
for tag in realtags
let tag.parent = a:parent
if !has_key(a:typeinfo.kind2scope, tag.fields.kind)
continue
endif
if !has_key(tag, 'children')
let tag.children = []
endif
call s:AddScopedTags(a:tags, tag.children, tag, a:depth + 1,
\ a:typeinfo, a:fileinfo)
endfor
call extend(a:processedtags, realtags)
" Recursively add the children of the tags that are children of the
" pseudo-tags on the current level
for tag in pseudotags
call s:ProcessPseudoChildren(a:tags, tag, a:depth, a:typeinfo,
\ a:fileinfo)
endfor
call extend(a:processedtags, pseudotags)
endif
" Now we have to check if there are any pseudo-tags at the current level
" so we have to check for real tags at a lower level, i.e. grandchildren
let is_grandchild = 'v:val.depth > a:depth'
if !empty(curpath)
let is_grandchild .=
\ ' && match(v:val.path, ''\V\^\C'' . curpath . a:typeinfo.sro) == 0'
endif
let grandchildren = filter(copy(a:tags), is_grandchild)
if !empty(grandchildren)
call s:AddScopedTags(a:tags, a:processedtags, a:parent, a:depth + 1,
\ a:typeinfo, a:fileinfo)
endif
endfunction
" s:ProcessPseudoTag() {{{2
function! s:ProcessPseudoTag(curtags, tag, parent, typeinfo, fileinfo)
let curpath = !empty(a:parent) ? a:parent.fullpath : ''
let pseudoname = substitute(a:tag.path, curpath, '', '')
let pseudoname = substitute(pseudoname, '\V\^' . a:typeinfo.sro, '', '')
let pseudotag = s:CreatePseudoTag(pseudoname, a:parent, a:tag.scope,
\ a:typeinfo, a:fileinfo)
let pseudotag.children = [a:tag]
" get all the other (direct) children of the current pseudo-tag
let ispseudochild = 'v:val.path ==# a:tag.path && v:val.scope ==# a:tag.scope'
let pseudochildren = filter(copy(a:curtags), ispseudochild)
if !empty(pseudochildren)
call filter(a:curtags, '!(' . ispseudochild . ')')
call extend(pseudotag.children, pseudochildren)
endif
return pseudotag
endfunction
" s:ProcessPseudoChildren() {{{2
function! s:ProcessPseudoChildren(tags, tag, depth, typeinfo, fileinfo)
for childtag in a:tag.children
let childtag.parent = a:tag
if !has_key(a:typeinfo.kind2scope, childtag.fields.kind)
continue
endif
if !has_key(childtag, 'children')
let childtag.children = []
endif
call s:AddScopedTags(a:tags, childtag.children, childtag, a:depth + 1,
\ a:typeinfo, a:fileinfo)
endfor
let is_grandchild = 'v:val.depth > a:depth &&
\ match(v:val.path, ''^\C'' . a:tag.fullpath) == 0'
let grandchildren = filter(copy(a:tags), is_grandchild)
if !empty(grandchildren)
call s:AddScopedTags(a:tags, a:tag.children, a:tag, a:depth + 1,
\ a:typeinfo, a:fileinfo)
endif
endfunction
" s:CreatePseudoTag() {{{2
function! s:CreatePseudoTag(name, parent, scope, typeinfo, fileinfo)
if !empty(a:parent)
let curpath = a:parent.fullpath
let pscope = a:typeinfo.kind2scope[a:parent.fields.kind]
else
let curpath = ''
let pscope = ''
endif
let pseudotag = s:PseudoTag.New(a:name)
let pseudotag.fields.kind = a:typeinfo.scope2kind[a:scope]
let parentscope = substitute(curpath, a:name . '$', '', '')
let parentscope = substitute(parentscope,
\ '\V\^' . 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' . a:typeinfo.sro))
let pseudotag.parent = a:parent
let pseudotag.fileinfo = a:fileinfo
call pseudotag.initFoldState()
return pseudotag
endfunction
" Sorting {{{1
" s:SortTags() {{{2
function! s:SortTags(tags, comparemethod)
call sort(a:tags, a:comparemethod)
for tag in a:tags
if has_key(tag, 'children')
call s:SortTags(tag.children, a:comparemethod)
endif
endfor
endfunction
" s:CompareByKind() {{{2
function! s:CompareByKind(tag1, tag2)
let typeinfo = s:compare_typeinfo
if typeinfo.kinddict[a:tag1.fields.kind] <#
\ typeinfo.kinddict[a:tag2.fields.kind]
return -1
elseif typeinfo.kinddict[a:tag1.fields.kind] >#
\ typeinfo.kinddict[a:tag2.fields.kind]
return 1
else
" Ignore '~' prefix for C++ destructors to sort them directly under
" the constructors
if a:tag1.name[0] ==# '~'
let name1 = a:tag1.name[1:]
else
let name1 = a:tag1.name
endif
if a:tag2.name[0] ==# '~'
let name2 = a:tag2.name[1:]
else
let name2 = a:tag2.name
endif
if name1 <=# name2
return -1
else
return 1
endif
endif
endfunction
" s:CompareByLine() {{{2
function! s:CompareByLine(tag1, tag2)
return a:tag1.fields.line - a:tag2.fields.line
endfunction
" s:ToggleSort() {{{2
function! s:ToggleSort()
let fileinfo = s:known_files.getCurrent()
if empty(fileinfo)
return
endif
let curline = line('.')
match none
let s:compare_typeinfo = s:known_types[fileinfo.ftype]
if has_key(s:compare_typeinfo, 'sort')
let s:compare_typeinfo.sort = !s:compare_typeinfo.sort
else
let g:tagbar_sort = !g:tagbar_sort
endif
call fileinfo.sortTags()
call s:RenderContent()
execute curline
endfunction
" Display {{{1
" s:RenderContent() {{{2
function! s:RenderContent(...)
call s:LogDebugMessage('RenderContent called')
if a:0 == 1
let fileinfo = a:1
else
let fileinfo = s:known_files.getCurrent()
endif
if empty(fileinfo)
call s:LogDebugMessage('Empty fileinfo, returning')
return
endif
let tagbarwinnr = bufwinnr('__Tagbar__')
if &filetype == 'tagbar'
let in_tagbar = 1
else
let in_tagbar = 0
let prevwinnr = winnr()
call s:winexec(tagbarwinnr . 'wincmd w')
endif
if !empty(s:known_files.getCurrent()) &&
\ fileinfo.fpath ==# s:known_files.getCurrent().fpath
" We're redisplaying the same file, so save the view
call s:LogDebugMessage('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 = s:known_types[fileinfo.ftype]
" Print tags
call s:PrintKinds(typeinfo, fileinfo)
" 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(s:known_files.getCurrent()) &&
\ fileinfo.fpath ==# s:known_files.getCurrent().fpath
let scrolloff_save = &scrolloff
set scrolloff=0
call cursor(topline, 1)
normal! zt
call cursor(saveline, savecol)
let &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:winexec(prevwinnr . 'wincmd w')
endif
endfunction
" s:PrintKinds() {{{2
function! s:PrintKinds(typeinfo, fileinfo)
call s:LogDebugMessage('PrintKinds called')
let first_tag = 1
for kind in a:typeinfo.kinds
let curtags = filter(copy(a:fileinfo.tags),
\ 'v:val.fields.kind ==# kind.short')
call s:LogDebugMessage('Printing kind: ' . kind.short .
\ ', number of (top-level) tags: ' . len(curtags))
if empty(curtags)
continue
endif
if has_key(a:typeinfo, 'kind2scope') &&
\ has_key(a:typeinfo.kind2scope, kind.short)
" Scoped tags
for tag in curtags
if g:tagbar_compact && first_tag && s:short_help
silent 0put =tag.str()
else
silent put =tag.str()
endif
" Save the current tagbar line in the tag for easy
" highlighting access
let curline = line('.')
let tag.tline = curline
let a:fileinfo.tline[curline] = tag
" Print children
if tag.isFoldable() && !tag.isFolded()
for ckind in a:typeinfo.kinds
let childtags = filter(copy(tag.children),
\ 'v:val.fields.kind ==# ckind.short')
if len(childtags) > 0
" Print 'kind' header of following children
if !has_key(a:typeinfo.kind2scope, ckind.short)
silent put =' [' . ckind.long . ']'
let a:fileinfo.tline[line('.')] = tag
endif
for childtag in childtags
call s:PrintTag(childtag, 1,
\ a:fileinfo, a:typeinfo)
endfor
endif
endfor
endif
if !g:tagbar_compact
silent put _
endif
let first_tag = 0
endfor
else
" Non-scoped tags
let kindtag = curtags[0].parent
if kindtag.isFolded()
let foldmarker = s:icon_closed
else
let foldmarker = s:icon_open
endif
if g:tagbar_compact && first_tag && s:short_help
silent 0put =foldmarker . ' ' . kind.long
else
silent put =foldmarker . ' ' . kind.long
endif
let curline = line('.')
let kindtag.tline = curline
let a:fileinfo.tline[curline] = kindtag
if !kindtag.isFolded()
for tag in curtags
let str = tag.str()
silent put =' ' . str
" Save the current tagbar line in the tag for easy
" highlighting access
let curline = line('.')
let tag.tline = curline
let a:fileinfo.tline[curline] = tag
let tag.depth = 1
endfor
endif
if !g:tagbar_compact
silent put _
endif
let first_tag = 0
endif
endfor
endfunction
" s:PrintTag() {{{2
function! s:PrintTag(tag, depth, fileinfo, typeinfo)
" Print tag indented according to depth
silent put =repeat(' ', a:depth * 2) . a:tag.str()
" Save the current tagbar line in the tag for easy
" highlighting access
let curline = line('.')
let a:tag.tline = curline
let a:fileinfo.tline[curline] = a:tag
" Recursively print children
if a:tag.isFoldable() && !a:tag.isFolded()
for ckind in a:typeinfo.kinds
let childtags = filter(copy(a:tag.children),
\ 'v:val.fields.kind ==# ckind.short')
if len(childtags) > 0
" Print 'kind' header of following children
if !has_key(a:typeinfo.kind2scope, ckind.short)
silent put =' ' . repeat(' ', a:depth * 2) .
\ '[' . ckind.long . ']'
let a:fileinfo.tline[line('.')] = a:tag
endif
for childtag in childtags
call s:PrintTag(childtag, a:depth + 1,
\ a:fileinfo, a:typeinfo)
endfor
endif
endfor
endif
endfunction
" s:PrintHelp() {{{2
function! s:PrintHelp()
if !g:tagbar_compact && s:short_help
silent 0put ='\" Press <F1> for help'
silent put _
elseif !s:short_help
silent 0put ='\" Tagbar keybindings'
silent put ='\"'
silent put ='\" --------- General ---------'
silent put ='\" <Enter> : Jump to tag definition'
silent put ='\" <Space> : Display tag prototype'
silent put ='\"'
silent put ='\" ---------- Folds ----------'
silent put ='\" +, zo : Open fold'
silent put ='\" -, zc : Close fold'
silent put ='\" o, za : Toggle fold'
silent put ='\" *, zR : Open all folds'
silent put ='\" =, zM : Close all folds'
silent put ='\"'
silent put ='\" ---------- Misc -----------'
silent put ='\" s : Toggle sort'
silent put ='\" x : Zoom window in/out'
silent put ='\" q : Close window'
silent put ='\" <F1> : Remove help'
silent put _
endif
endfunction
" s:RenderKeepView() {{{2
" The gist of this function was taken from NERDTree by Martin Grenfell.
function! s:RenderKeepView(...)
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
set scrolloff=0
call cursor(topline, 1)
normal! zt
call cursor(line, curcol)
let &scrolloff = scrolloff_save
redraw
endfunction
" User actions {{{1
" s:HighlightTag() {{{2
function! s:HighlightTag()
let tagline = 0
let tag = s:GetNearbyTag()
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 tagline == s:last_highlight_tline
return
else
let s:last_highlight_tline = tagline
endif
let tagbarwinnr = bufwinnr('__Tagbar__')
let prevwinnr = winnr()
call s:winexec(tagbarwinnr . 'wincmd w')
match none
" No tag above cursor position so don't do anything
if tagline == 0
call s:winexec(prevwinnr . 'wincmd w')
redraw
return
endif
if g:tagbar_autoshowtag
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()
" Go to the line containing the tag
execute tagline
" Make sure the tag is visible in the window
call winline()
let foldpat = '[' . s:icon_open . s:icon_closed . ' ]'
let pattern = '/^\%' . tagline . 'l\s*' . foldpat . '[-+# ]\zs[^( ]\+\ze/'
execute 'match TagbarHighlight ' . pattern
call s:winexec(prevwinnr . 'wincmd w')
redraw
endfunction
" s:JumpToTag() {{{2
function! s:JumpToTag(stay_in_tagbar)
let taginfo = s:GetTagInfo(line('.'), 1)
let autoclose = w:autoclose
if empty(taginfo) || has_key(taginfo, 'numtags')
return
endif
let tagbarwinnr = winnr()
" This elaborate construct will try to switch to the correct
" buffer/window; if the buffer isn't currently shown in a window it will
" open it in the first window with a non-special buffer in it
call s:winexec('wincmd p')
let filebufnr = bufnr(taginfo.fileinfo.fpath)
if bufnr('%') != filebufnr
let filewinnr = bufwinnr(filebufnr)
if filewinnr != -1
call s:winexec(filewinnr . 'wincmd w')
else
for i in range(1, winnr('$'))
call s:winexec(i . 'wincmd w')
if &buftype == ''
execute 'buffer ' . filebufnr
break
endif
endfor
endif
" To make ctrl-w_p work we switch between the Tagbar window and the
" correct window once
call s:winexec(tagbarwinnr . 'wincmd w')
call s:winexec('wincmd p')
endif
" Mark current position so it can be jumped back to
mark '
" Jump to the line where the tag is defined. Don't use the search pattern
" since it doesn't take the scope into account and thus can fail if tags
" with the same name are defined in different scopes (e.g. classes)
execute taginfo.fields.line
" 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 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
" 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
" Center the tag in the window
normal! z.
if foldclosed('.') != -1
.foldopen!
endif
redraw
if a:stay_in_tagbar
call s:HighlightTag()
call s:winexec(tagbarwinnr . 'wincmd w')
elseif g:tagbar_autoclose || autoclose
call s:CloseWindow()
else
call s:HighlightTag()
endif
endfunction
" s:ShowPrototype() {{{2
function! s:ShowPrototype()
let taginfo = s:GetTagInfo(line('.'), 1)
if empty(taginfo)
return
endif
echo taginfo.getPrototype()
endfunction
" s:ToggleHelp() {{{2
function! s:ToggleHelp()
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)
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()
let fileinfo = s:known_files.getCurrent()
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()
let fileinfo = s:known_files.getCurrent()
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()
let fileinfo = s:known_files.getCurrent()
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()
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:SetFoldLevel() {{{2
function! s:SetFoldLevel(level)
if a:level < 0
echoerr 'Foldlevel can''t be negative'
return
endif
let fileinfo = s:known_files.getCurrent()
if empty(fileinfo)
return
endif
call s:SetFoldLevelRecursive(fileinfo, fileinfo.tags, a:level)
let typeinfo = s:known_types[fileinfo.ftype]
" 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
call fileinfo.openKindFold(kind)
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)
for tag in a:tags
if tag.depth >= a:level
call tag.setFolded(1)
else
call tag.setFolded(0)
endif
if has_key(tag, 'children')
call s:SetFoldLevelRecursive(a:fileinfo, tag.children, a:level)
endif
endfor
endfunction
" s:OpenParents() {{{2
function! s:OpenParents(...)
let tagline = 0
if a:0 == 1
let tag = a:1
else
let tag = s:GetNearbyTag()
endif
call tag.openParents()
call s:RenderKeepView()
endfunction
" Helper functions {{{1
" s:CleanUp() {{{2
function! s:CleanUp()
silent autocmd! TagbarAutoCmds
unlet s:is_maximized
unlet s:compare_typeinfo
unlet s:short_help
endfunction
" s:CleanupFileinfo() {{{2
function! s:CleanupFileinfo(fname)
call s:known_files.rm(a:fname)
endfunction
" s:QuitIfOnlyWindow() {{{2
function! s:QuitIfOnlyWindow()
" Check if there is more than window
if winbufnr(2) == -1
" Check if there is more than one tab page
if tabpagenr('$') == 1
" Before quitting Vim, delete the tagbar buffer so that
" the '0 mark is correctly set to the previous buffer.
bdelete
quitall
else
close
endif
endif
endfunction
" s:AutoUpdate() {{{2
function! s:AutoUpdate(fname)
call s:LogDebugMessage('AutoUpdate called on ' . a:fname)
" 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 tagbar is not open or if we're in the tagbar window
let tagbarwinnr = bufwinnr('__Tagbar__')
if tagbarwinnr == -1 || ftype == 'tagbar'
call s:LogDebugMessage('Tagbar window not open or in Tagbar window')
return
endif
" Only consider the main filetype in cases like 'python.django'
let sftype = get(split(ftype, '\.'), 0, '')
call s:LogDebugMessage("Vim filetype: '" . ftype . "', " .
\ "sanitized filetype: '" . sftype . "'")
" Don't do anything if the file isn't supported
if !s:IsValidFile(a:fname, sftype)
call s:LogDebugMessage('Not a valid file, stopping processing')
return
endif
" Process the file if it's unknown or the information is outdated
" Also test for entries that exist but are empty, which will be the case
" if there was an error during the ctags execution
if s:known_files.has(a:fname) && !empty(s:known_files.get(a:fname))
if s:known_files.get(a:fname).mtime != getftime(a:fname)
call s:LogDebugMessage('File data outdated, updating ' . a:fname)
call s:ProcessFile(a:fname, sftype)
endif
elseif !s:known_files.has(a:fname)
call s:LogDebugMessage('New file, processing ' . a:fname)
call s:ProcessFile(a:fname, sftype)
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 s:LogDebugMessage('fileinfo empty after processing ' . a:fname)
return
endif
" Display the tagbar content
call s:RenderContent(fileinfo)
" Call setCurrent after rendering so RenderContent can check whether the
" same file is redisplayed
if !empty(fileinfo)
call s:LogDebugMessage('Setting current file to ' . a:fname)
call s:known_files.setCurrent(fileinfo)
endif
call s:HighlightTag()
call s:LogDebugMessage('AutoUpdate finished successfully')
endfunction
" s:IsValidFile() {{{2
function! s:IsValidFile(fname, ftype)
if a:fname == '' || a:ftype == ''
call s:LogDebugMessage('Empty filename or type')
return 0
endif
if !filereadable(a:fname)
call s:LogDebugMessage('File not readable')
return 0
endif
if !has_key(s:known_types, a:ftype)
call s:LogDebugMessage('Unsupported filetype: ' . a:ftype)
return 0
endif
return 1
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
function! s:EscapeCtagsCmd(ctags_bin, args, ...)
call s:LogDebugMessage('EscapeCtagsCmd called')
call s:LogDebugMessage('ctags_bin: ' . a:ctags_bin)
call s:LogDebugMessage('ctags_args: ' . a:args)
if exists('+shellslash')
let shellslash_save = &shellslash
set noshellslash
endif
if a:0 == 1
let fname = shellescape(a:1)
else
let fname = ''
endif
let ctags_cmd = shellescape(a:ctags_bin) . ' ' . a:args . ' ' . fname
if exists('+shellslash')
let &shellslash = shellslash_save
endif
" Needed for cases where 'encoding' is different from the system's
" encoding
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
call s:LogDebugMessage('Escaped ctags command: ' . ctags_cmd)
if ctags_cmd == ''
echoerr 'Tagbar: Encoding conversion failed!'
\ 'Please make sure your system is set up correctly'
\ 'and that Vim is compiled with the "iconv" feature.'
endif
return ctags_cmd
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)
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
let ctags_output = system(a:ctags_cmd)
if &shell =~ 'cmd\.exe'
let &shellxquote = shellxquote_save
let &shellcmdflag = shellcmdflag_save
endif
if exists('+shellslash')
let &shellslash = shellslash_save
endif
return ctags_output
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)
let fileinfo = s:known_files.getCurrent()
if empty(fileinfo)
return {}
endif
" Don't do anything in empty and comment lines
let curline = getline(a:linenr)
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:GetNearbyTag() {{{2
" Get the tag info for a file near the cursor in the current file
function! s:GetNearbyTag()
let fileinfo = s:known_files.getCurrent()
let curline = line('.')
let tag = {}
" If a tag appears in a file more than once (for example namespaces in
" C++) only one of them has a 'tline' entry and can thus be highlighted.
" The only way to solve this would be to go over the whole tag list again,
" making everything slower. Since this should be a rare occurence and
" highlighting isn't /that/ important ignore it for now.
for line in range(curline, 1, -1)
if has_key(fileinfo.fline, line)
let tag = fileinfo.fline[line]
break
endif
endfor
return tag
endfunction
" s:CheckMouseClick() {{{2
function! s:CheckMouseClick()
let line = getline('.')
let curcol = col('.')
if (match(line, s:icon_open . '[-+ ]') + 1) == curcol
call s:CloseFold()
elseif (match(line, s:icon_closed . '[-+ ]') + 1) == curcol
call s:OpenFold()
elseif g:tagbar_singleclick
call s:JumpToTag(0)
endif
endfunction
" s:DetectFiletype() {{{2
function! s:DetectFiletype(bufnr)
" 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:winexec() {{{2
function! s:winexec(cmd)
let eventignore_save = &eventignore
set eventignore=BufEnter
execute a:cmd
let &eventignore = eventignore_save
endfunction
" TagbarBalloonExpr() {{{2
function! TagbarBalloonExpr()
let taginfo = s:GetTagInfo(v:beval_lnum, 1)
if empty(taginfo)
return
endif
return taginfo.getPrototype()
endfunction
" TagbarGenerateStatusline() {{{2
function! TagbarGenerateStatusline()
if g:tagbar_sort
let text = '[Name]'
else
let text = '[Order]'
endif
if !empty(s:known_files.getCurrent())
let filename = fnamemodify(s:known_files.getCurrent().fpath, ':t')
let text .= ' ' . filename
endif
return text
endfunction
" Debugging {{{1
" s:StartDebug() {{{2
function! s:StartDebug(filename)
if empty(a:filename)
let s:debug_file = 'tagbardebug.log'
else
let s:debug_file = a:filename
endif
" Empty log file
exe 'redir! > ' . s:debug_file
redir END
" Check whether the log file could be created
if !filewritable(s:debug_file)
echomsg 'Tagbar: Unable to create log file ' . s:debug_file
let s:debug_file = ''
return
endif
let s:debug = 1
endfunction
" s:StopDebug() {{{2
function! s:StopDebug()
let s:debug = 0
let s:debug_file = ''
endfunction
" s:LogDebugMessage() {{{2
function! s:LogDebugMessage(msg)
if s:debug
exe 'redir >> ' . s:debug_file
silent echon strftime('%H:%M:%S') . ': ' . a:msg . "\n"
redir END
endif
endfunction
" Autoload functions {{{1
function! tagbar#ToggleWindow()
call s:ToggleWindow()
endfunction
function! tagbar#OpenWindow(...)
let flags = a:0 > 0 ? a:1 : ''
call s:OpenWindow(flags)
endfunction
function! tagbar#CloseWindow()
call s:CloseWindow()
endfunction
function! tagbar#SetFoldLevel(...)
call s:SetFoldLevel(a:1)
endfunction
function! tagbar#OpenParents()
call s:OpenParents()
endfunction
function! tagbar#StartDebug(...)
let filename = a:0 > 0 ? a:1 : ''
call s:StartDebug(filename)
endfunction
function! tagbar#StopDebug()
call s:StopDebug()
endfunction
function! tagbar#RestoreSession()
call s:RestoreSession()
endfunction
" Automatically open Tagbar if one of the open buffers contains a supported
" file
function! tagbar#autoopen(...)
let always = a:0 > 0 ? a:1 : 1
call s:Init()
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('')
return
endif
endif
endfor
endfunction
" Modeline {{{1
" vim: ts=8 sw=4 sts=4 et foldenable foldmethod=marker foldcolumn=1