Commits

Audrius Kažukauskas committed 2f8ce60

Upgrade to Tagbar 2.5.

Comments (0)

Files changed (5)

autoload/tagbar.vim

 " Author:      Jan Larres <jan@majutsushi.net>
 " Licence:     Vim licence
 " Website:     http://majutsushi.github.com/tagbar/
-" Version:     2.4.1
+" Version:     2.5
 " Note:        This plugin was heavily inspired by the 'Taglist' plugin by
 "              Yegappan Lakshmanan and uses a small amount of code from it.
 "
 
 let s:type_init_done      = 0
 let s:autocommands_done   = 0
+let s:autocommands_enabled = 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_types         = {}
-let s:window_expanded     = 0
-
-
-let s:access_symbols = {
+
+let s:new_window      = 1
+let s:is_maximized    = 0
+let s:short_help      = 1
+let s:nearby_disabled = 0
+
+let s:window_expanded   = 0
+let s:window_pos = {
+    \ 'pre'  : { 'x' : 0, 'y' : 0 },
+    \ 'post' : { 'x' : 0, 'y' : 0 }
+\}
+
+" Script-local variable needed since compare functions can't
+" take extra arguments
+let s:compare_typeinfo = {}
+
+
+let s:visibility_symbols = {
     \ 'public'    : '+',
     \ 'protected' : '#',
     \ 'private'   : '-'
 let s:debug_file = ''
 
 " s:Init() {{{2
-function! s:Init(silent)
+function! s:Init(silent) abort
     if s:checked_ctags == 2 && a:silent
         return 0
     elseif s:checked_ctags != 1
 
     if !s:autocommands_done
         call s:CreateAutocommands()
-        doautocmd CursorHold
+        call s:AutoUpdate(fnamemodify(expand('%'), ':p'), 0)
     endif
 
     return 1
 endfunction
 
 " s:InitTypes() {{{2
-function! s:InitTypes()
+function! s:InitTypes() abort
     call s:LogDebugMessage('Initializing types')
 
     let s:known_types = {}
         \ {'short' : 'f', 'long' : 'functions', 'fold' : 0, 'stl' : 1}
     \ ]
     let s:known_types.lisp = type_lisp
+    let s:known_types.clojure = type_lisp
     " Lua {{{3
     let type_lua = s:TypeInfo.New()
     let type_lua.ctagstype = 'lua'
 endfunction
 
 " s:LoadUserTypeDefs() {{{2
-function! s:LoadUserTypeDefs(...)
+function! s:LoadUserTypeDefs(...) abort
     if a:0 > 0
         let type = a:1
 
 endfunction
 
 " s:CreateTypeKinddict() {{{2
-function! s:CreateTypeKinddict(type)
+function! s:CreateTypeKinddict(type) abort
     " Create a dictionary of the kind order for fast access in sorting
     " functions
     let i = 0
 
 " s:RestoreSession() {{{2
 " Properly restore Tagbar after a session got loaded
-function! s:RestoreSession()
+function! s:RestoreSession() abort
     call s:LogDebugMessage('Restoring session')
 
     let curfile = fnamemodify(bufname('%'), ':p')
 
     call s:InitWindow(g:tagbar_autoclose)
 
-    call s:AutoUpdate(curfile)
+    call s:AutoUpdate(curfile, 0)
 
     if !in_tagbar
         call s:winexec('wincmd p')
 endfunction
 
 " s:MapKeys() {{{2
-function! s:MapKeys()
+function! s:MapKeys() abort
     call s:LogDebugMessage('Mapping keys')
 
     nnoremap <script> <silent> <buffer> <2-LeftMouse>
 
     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> <Space> :call <SID>ShowPrototype(0)<CR>
 
     nnoremap <script> <silent> <buffer> +        :call <SID>OpenFold()<CR>
     nnoremap <script> <silent> <buffer> <kPlus>  :call <SID>OpenFold()<CR>
 endfunction
 
 " s:CreateAutocommands() {{{2
-function! s:CreateAutocommands()
+function! s:CreateAutocommands() abort
     call s:LogDebugMessage('Creating autocommands')
 
     augroup TagbarAutoCmds
         autocmd!
         autocmd BufEnter   __Tagbar__ nested call s:QuitIfOnlyWindow()
-        autocmd CursorHold __Tagbar__ call s:ShowPrototype()
-
-        autocmd BufWritePost *
-            \ if line('$') < g:tagbar_updateonsave_maxlines |
-                \ call s:AutoUpdate(fnamemodify(expand('<afile>'), ':p')) |
-            \ endif
+        autocmd CursorHold __Tagbar__ call s:ShowPrototype(1)
+
+        autocmd BufReadPost,BufWritePost * call
+                    \ s:AutoUpdate(fnamemodify(expand('<afile>'), ':p'), 1)
         autocmd BufEnter,CursorHold,FileType * call
-                    \ s:AutoUpdate(fnamemodify(expand('<afile>'), ':p'))
+                    \ s:AutoUpdate(fnamemodify(expand('<afile>'), ':p'), 0)
         autocmd BufDelete,BufUnload,BufWipeout * call
                     \ s:known_files.rm(fnamemodify(expand('<afile>'), ':p'))
 
     augroup END
 
     let s:autocommands_done = 1
+    let s:autocommands_enabled = 1
+endfunction
+
+" s:PauseAutocommands() {{{2
+" Toggle autocommands 
+function! s:PauseAutocommands() abort
+    if s:autocommands_enabled == 1
+        autocmd! TagbarAutoCmds
+        let s:autocommands_enabled = 0
+    else
+        call s:CreateAutocommands()
+        call s:AutoUpdate(fnamemodify(expand('%'), ':p'), 0)
+    endif
 endfunction
 
 " s:CheckForExCtags() {{{2
 " Test whether the ctags binary is actually Exuberant Ctags and not GNU ctags
 " (or something else)
-function! s:CheckForExCtags(silent)
+function! s:CheckForExCtags(silent) abort
     call s:LogDebugMessage('Checking for Exuberant Ctags')
 
     if !exists('g:tagbar_ctags_bin')
 endfunction
 
 " s:CheckExCtagsVersion() {{{2
-function! s:CheckExCtagsVersion(output)
+function! s:CheckExCtagsVersion(output) abort
     call s:LogDebugMessage('Checking Exuberant Ctags version')
 
     if a:output =~ 'Exuberant Ctags Development'
 endfunction
 
 " s:CheckFTCtags() {{{2
-function! s:CheckFTCtags(bin, ftype)
+function! s:CheckFTCtags(bin, ftype) abort
     if executable(a:bin)
         return a:bin
     endif
 endfunction
 
 " s:GetSupportedFiletypes() {{{2
-function! s:GetSupportedFiletypes()
+function! s:GetSupportedFiletypes() abort
     call s:LogDebugMessage('Getting filetypes sypported by Exuberant Ctags')
 
     let ctags_cmd = s:EscapeCtagsCmd(g:tagbar_ctags_bin, '--list-languages')
 let s:BaseTag = {}
 
 " s:BaseTag.New() {{{3
-function! s:BaseTag.New(name) dict
+function! s:BaseTag.New(name) abort dict
     let newobj = copy(self)
 
     call newobj._init(a:name)
 endfunction
 
 " s:BaseTag._init() {{{3
-function! s:BaseTag._init(name) dict
+function! s:BaseTag._init(name) abort dict
     let self.name          = a:name
     let self.fields        = {}
     let self.fields.line   = 0
     let self.fields.column = 1
+    let self.prototype     = ''
     let self.path          = ''
     let self.fullpath      = a:name
     let self.depth         = 0
 endfunction
 
 " s:BaseTag.isNormalTag() {{{3
-function! s:BaseTag.isNormalTag() dict
+function! s:BaseTag.isNormalTag() abort dict
     return 0
 endfunction
 
 " s:BaseTag.isPseudoTag() {{{3
-function! s:BaseTag.isPseudoTag() dict
+function! s:BaseTag.isPseudoTag() abort dict
     return 0
 endfunction
 
 " s:BaseTag.isKindheader() {{{3
-function! s:BaseTag.isKindheader() dict
+function! s:BaseTag.isKindheader() abort dict
     return 0
 endfunction
 
 " s:BaseTag.getPrototype() {{{3
-function! s:BaseTag.getPrototype() dict
-    return ''
+function! s:BaseTag.getPrototype(short) abort dict
+    return self.prototype
 endfunction
 
 " s:BaseTag._getPrefix() {{{3
-function! s:BaseTag._getPrefix() dict
+function! s:BaseTag._getPrefix() abort dict
     let fileinfo = self.fileinfo
 
     if has_key(self, 'children') && !empty(self.children)
     else
         let prefix = ' '
     endif
-    if has_key(self.fields, 'access')
-        let prefix .= get(s:access_symbols, self.fields.access, ' ')
-    else
-        let prefix .= ' '
+    " Visibility is called 'access' in the ctags output
+    if g:tagbar_show_visibility
+        if has_key(self.fields, 'access')
+            let prefix .= get(s:visibility_symbols, self.fields.access, ' ')
+        else
+            let prefix .= ' '
+        endif
     endif
 
     return prefix
 endfunction
 
 " s:BaseTag.initFoldState() {{{3
-function! s:BaseTag.initFoldState() dict
+function! s:BaseTag.initFoldState() abort dict
     let fileinfo = self.fileinfo
 
     if s:known_files.has(fileinfo.fpath) &&
+     \ has_key(fileinfo, '_tagfolds_old') &&
      \ 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
 endfunction
 
 " s:BaseTag.getClosedParentTline() {{{3
-function! s:BaseTag.getClosedParentTline() dict
+function! s:BaseTag.getClosedParentTline() abort dict
     let tagline  = self.tline
     let fileinfo = self.fileinfo
 
 endfunction
 
 " s:BaseTag.isFoldable() {{{3
-function! s:BaseTag.isFoldable() dict
+function! s:BaseTag.isFoldable() abort dict
     return has_key(self, 'children') && !empty(self.children)
 endfunction
 
 " s:BaseTag.isFolded() {{{3
-function! s:BaseTag.isFolded() dict
+function! s:BaseTag.isFolded() abort dict
     return self.fileinfo.tagfolds[self.fields.kind][self.fullpath]
 endfunction
 
 " s:BaseTag.openFold() {{{3
-function! s:BaseTag.openFold() dict
+function! s:BaseTag.openFold() abort 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
+function! s:BaseTag.closeFold() abort dict
     let newline = line('.')
 
     if !empty(self.parent) && self.parent.isKindheader()
 endfunction
 
 " s:BaseTag.setFolded() {{{3
-function! s:BaseTag.setFolded(folded) dict
+function! s:BaseTag.setFolded(folded) abort dict
     let self.fileinfo.tagfolds[self.fields.kind][self.fullpath] = a:folded
 endfunction
 
 " s:BaseTag.openParents() {{{3
-function! s:BaseTag.openParents() dict
+function! s:BaseTag.openParents() abort dict
     let parent = self.parent
 
     while !empty(parent)
 let s:NormalTag = copy(s:BaseTag)
 
 " s:NormalTag.isNormalTag() {{{3
-function! s:NormalTag.isNormalTag() dict
+function! s:NormalTag.isNormalTag() abort dict
     return 1
 endfunction
 
 " s:NormalTag.strfmt() {{{3
-function! s:NormalTag.strfmt() dict
+function! s:NormalTag.strfmt() abort dict
     let fileinfo = self.fileinfo
     let typeinfo = self.typeinfo
 
 endfunction
 
 " s:NormalTag.str() {{{3
-function! s:NormalTag.str(longsig, full) dict
+function! s:NormalTag.str(longsig, full) abort dict
     if a:full && self.path != ''
         let str = self.path . self.typeinfo.sro . self.name
     else
 endfunction
 
 " s:NormalTag.getPrototype() {{{3
-function! s:NormalTag.getPrototype() dict
-    return self.prototype
+function! s:NormalTag.getPrototype(short) abort dict
+    if self.prototype != ''
+        let prototype = self.prototype
+    else
+        let bufnr = self.fileinfo.bufnr
+
+        let line = getbufline(bufnr, self.fields.line)[0]
+        let list = split(line, '\zs')
+
+        let start = index(list, '(')
+        if start == -1
+            return substitute(line, '^\s\+', '', '')
+        endif
+
+        let opening = count(list, '(', 0, start)
+        let closing = count(list, ')', 0, start)
+        if closing >= opening
+            return substitute(line, '^\s\+', '', '')
+        endif
+
+        let balance = opening - closing
+
+        let prototype = line
+        let curlinenr = self.fields.line + 1
+        while balance > 0
+            let curline = getbufline(bufnr, curlinenr)[0]
+            let curlist = split(curline, '\zs')
+            let balance += count(curlist, '(')
+            let balance -= count(curlist, ')')
+            let prototype .= "\n" . curline
+            let curlinenr += 1
+        endwhile
+
+        let self.prototype = prototype
+    endif
+
+    if a:short
+        " join all lines and remove superfluous spaces
+        let prototype = substitute(prototype, '^\s\+', '', '')
+        let prototype = substitute(prototype, '\_s\+', ' ', 'g')
+        let prototype = substitute(prototype, '(\s\+', '(', 'g')
+        let prototype = substitute(prototype, '\s\+)', ')', 'g')
+        " Avoid hit-enter prompts
+        let maxlen = &columns - 12
+        if len(prototype) > maxlen
+            let prototype = prototype[:maxlen - 1 - 3]
+            let prototype .= '...'
+        endif
+    endif
+
+    return prototype
 endfunction
 
 " Pseudo tag {{{2
 let s:PseudoTag = copy(s:BaseTag)
 
 " s:PseudoTag.isPseudoTag() {{{3
-function! s:PseudoTag.isPseudoTag() dict
+function! s:PseudoTag.isPseudoTag() abort dict
     return 1
 endfunction
 
 " s:PseudoTag.strfmt() {{{3
-function! s:PseudoTag.strfmt() dict
+function! s:PseudoTag.strfmt() abort dict
     let fileinfo = self.fileinfo
     let typeinfo = self.typeinfo
 
 let s:KindheaderTag = copy(s:BaseTag)
 
 " s:KindheaderTag.isKindheader() {{{3
-function! s:KindheaderTag.isKindheader() dict
+function! s:KindheaderTag.isKindheader() abort dict
     return 1
 endfunction
 
 " s:KindheaderTag.getPrototype() {{{3
-function! s:KindheaderTag.getPrototype() dict
+function! s:KindheaderTag.getPrototype(short) abort dict
     return self.name . ': ' .
          \ self.numtags . ' ' . (self.numtags > 1 ? 'tags' : 'tag')
 endfunction
 
 " s:KindheaderTag.isFoldable() {{{3
-function! s:KindheaderTag.isFoldable() dict
+function! s:KindheaderTag.isFoldable() abort dict
     return 1
 endfunction
 
 " s:KindheaderTag.isFolded() {{{3
-function! s:KindheaderTag.isFolded() dict
+function! s:KindheaderTag.isFolded() abort dict
     return self.fileinfo.kindfolds[self.short]
 endfunction
 
 " s:KindheaderTag.openFold() {{{3
-function! s:KindheaderTag.openFold() dict
+function! s:KindheaderTag.openFold() abort dict
     let self.fileinfo.kindfolds[self.short] = 0
 endfunction
 
 " s:KindheaderTag.closeFold() {{{3
-function! s:KindheaderTag.closeFold() dict
+function! s:KindheaderTag.closeFold() abort dict
     let self.fileinfo.kindfolds[self.short] = 1
     return line('.')
 endfunction
 
 " s:KindheaderTag.toggleFold() {{{3
-function! s:KindheaderTag.toggleFold() dict
+function! s:KindheaderTag.toggleFold() abort dict
     let fileinfo = s:known_files.getCurrent()
 
     let fileinfo.kindfolds[self.short] = !fileinfo.kindfolds[self.short]
 let s:TypeInfo = {}
 
 " s:TypeInfo.New() {{{3
-function! s:TypeInfo.New(...) dict
+function! s:TypeInfo.New(...) abort dict
     let newobj = copy(self)
 
     let newobj.kinddict = {}
 endfunction
 
 " s:TypeInfo.getKind() {{{3
-function! s:TypeInfo.getKind(kind) dict
+function! s:TypeInfo.getKind(kind) abort dict
     let idx = self.kinddict[a:kind]
     return self.kinds[idx]
 endfunction
 let s:FileInfo = {}
 
 " s:FileInfo.New() {{{3
-function! s:FileInfo.New(fname, ftype) dict
+function! s:FileInfo.New(fname, ftype) abort dict
     let newobj = copy(self)
 
     " The complete file path
     let newobj.fpath = a:fname
 
+    let newobj.bufnr = bufnr(a:fname)
+
     " File modification time
     let newobj.mtime = getftime(a:fname)
 
 " s:FileInfo.reset() {{{3
 " Reset stuff that gets regenerated while processing a file and save the old
 " tag folds
-function! s:FileInfo.reset() dict
+function! s:FileInfo.reset() abort dict
     let self.mtime = getftime(self.fpath)
     let self.tags  = []
     let self.fline = {}
 endfunction
 
 " s:FileInfo.clearOldFolds() {{{3
-function! s:FileInfo.clearOldFolds() dict
+function! s:FileInfo.clearOldFolds() abort dict
     if exists('self._tagfolds_old')
         unlet self._tagfolds_old
     endif
 endfunction
 
 " s:FileInfo.sortTags() {{{3
-function! s:FileInfo.sortTags() dict
+function! s:FileInfo.sortTags() abort dict
     if has_key(s:compare_typeinfo, 'sort')
         if s:compare_typeinfo.sort
             call s:SortTags(self.tags, 's:CompareByKind')
 endfunction
 
 " s:FileInfo.openKindFold() {{{3
-function! s:FileInfo.openKindFold(kind) dict
+function! s:FileInfo.openKindFold(kind) abort dict
     let self.kindfolds[a:kind.short] = 0
 endfunction
 
 " s:FileInfo.closeKindFold() {{{3
-function! s:FileInfo.closeKindFold(kind) dict
+function! s:FileInfo.closeKindFold(kind) abort dict
     let self.kindfolds[a:kind.short] = 1
 endfunction
 
 \ }
 
 " s:known_files.getCurrent() {{{3
-function! s:known_files.getCurrent() dict
+function! s:known_files.getCurrent() abort dict
     return self._current
 endfunction
 
 " s:known_files.setCurrent() {{{3
-function! s:known_files.setCurrent(fileinfo) dict
+function! s:known_files.setCurrent(fileinfo) abort dict
     let self._current = a:fileinfo
 endfunction
 
 " s:known_files.get() {{{3
-function! s:known_files.get(fname) dict
+function! s:known_files.get(fname) abort 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
+function! s:known_files.put(fileinfo, ...) abort dict
     if a:0 == 1
         let self._files[a:1] = a:fileinfo
     else
 endfunction
 
 " s:known_files.has() {{{3
-function! s:known_files.has(fname) dict
+function! s:known_files.has(fname) abort dict
     return has_key(self._files, a:fname)
 endfunction
 
 " s:known_files.rm() {{{3
-function! s:known_files.rm(fname) dict
+function! s:known_files.rm(fname) abort dict
     if s:known_files.has(a:fname)
         call remove(self._files, a:fname)
     endif
 
 " Window management {{{1
 " s:ToggleWindow() {{{2
-function! s:ToggleWindow()
+function! s:ToggleWindow() abort
     call s:LogDebugMessage('ToggleWindow called')
 
     let tagbarwinnr = bufwinnr("__Tagbar__")
 endfunction
 
 " s:OpenWindow() {{{2
-function! s:OpenWindow(flags)
+function! s:OpenWindow(flags) abort
     call s:LogDebugMessage("OpenWindow called with flags: '" . a:flags . "'")
 
     let autofocus = a:flags =~# 'f'
     if tagbarwinnr != -1
         if winnr() != tagbarwinnr && jump
             call s:winexec(tagbarwinnr . 'wincmd w')
-            if autoclose
-                let w:autoclose = autoclose
-            endif
-            call s:HighlightTag(1, curline)
+            call s:HighlightTag(1, 1, curline)
         endif
         call s:LogDebugMessage("OpenWindow finished, Tagbar already open")
         return
     endif
 
     " Expand the Vim window to accomodate for the Tagbar window if requested
+    " and save the window positions to be able to restore them later.
     if g:tagbar_expand && !s:window_expanded && has('gui_running')
+        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
 
 
     call s:InitWindow(autoclose)
 
-    call s:AutoUpdate(curfile)
-    call s:HighlightTag(1, curline)
+    " 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(1, 1, curline)
 
     if !(g:tagbar_autoclose || autofocus || g:tagbar_autofocus)
         call s:winexec('wincmd p')
 endfunction
 
 " s:InitWindow() {{{2
-function! s:InitWindow(autoclose)
+function! s:InitWindow(autoclose) abort
     call s:LogDebugMessage('InitWindow called with autoclose: ' . a:autoclose)
 
     setlocal filetype=tagbar
     setlocal nowrap
     setlocal winfixwidth
     setlocal textwidth=0
-    setlocal nocursorline
-    setlocal nocursorcolumn
     setlocal nospell
 
     if exists('+relativenumber')
         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 s:new_window   = 1
+    let s:new_window = 1
 
     let w:autoclose = a:autoclose
 
 endfunction
 
 " s:CloseWindow() {{{2
-function! s:CloseWindow()
+function! s:CloseWindow() abort
     call s:LogDebugMessage('CloseWindow called')
 
     let tagbarwinnr = bufwinnr('__Tagbar__')
             endif
         endif
     else
-        " Go to the tagbar window, close it and then come back to the
-        " original window
-        let curbufnr = bufnr('%')
+        " 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.
+        let w:tagbar_returnhere = 1
         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
+
+        for window in range(1, winnr('$'))
+            call s:winexec(window . 'wincmd w')
+            if exists('w:tagbar_returnhere')
+                unlet w:tagbar_returnhere
+                break
+            endif
+        endfor
     endif
 
     " If the Vim window has been expanded, and Tagbar is not open in any other
         if index(tablist, tagbarbufnr) == -1
             let &columns -= g:tagbar_width + 1
             let s:window_expanded = 0
+            " Only restore window position if it hasn't been moved manually
+            " after the expanding
+            if 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
     endif
 
 endfunction
 
 " s:ZoomWindow() {{{2
-function! s:ZoomWindow()
+function! s:ZoomWindow() abort
     if s:is_maximized
         execute 'vert resize ' . g:tagbar_width
         let s:is_maximized = 0
 " 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()
+function! s:CorrectFocusOnStartup() abort
     if bufwinnr('__Tagbar__') != -1 && !g:tagbar_autofocus && !s:last_autofocus
         let curfile = s:known_files.getCurrent()
         if !empty(curfile) && curfile.fpath != fnamemodify(bufname('%'), ':p')
 " 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)
+function! s:ProcessFile(fname, ftype) abort
+    call s:LogDebugMessage('ProcessFile called [' . 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 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))
+        let fileinfo = s:known_files.get(a:fname)
+        call fileinfo.reset()
+    else
+        let fileinfo = s:FileInfo.New(a:fname, a:ftype)
+    endif
+
+    " 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 writefile(getbufline(fileinfo.bufnr, 1, '$'), tempfile)
+    let fileinfo.mtime = getftime(tempfile)
+
+    let ctags_output = s:ExecuteCtagsOnFile(tempfile, a:fname, a:ftype)
+
+    call delete(tempfile)
 
     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
+        " 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
+        " preserving the old fold state isn't necessary either
         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 = fileinfo.typeinfo
 
+    call s:LogDebugMessage('Filetype tag kinds: ' .
+                         \ string(keys(typeinfo.kinddict)))
+
     " Parse the ctags output lines
     call s:LogDebugMessage('Parsing ctags output')
     let rawtaglist = split(ctags_output, '\n\+')
 endfunction
 
 " s:ExecuteCtagsOnFile() {{{2
-function! s:ExecuteCtagsOnFile(fname, ftype)
-    call s:LogDebugMessage('ExecuteCtagsOnFile called on ' . a:fname)
+function! s:ExecuteCtagsOnFile(fname, realfname, ftype) abort
+    call s:LogDebugMessage('ExecuteCtagsOnFile called [' . a:fname . ']')
 
     let typeinfo = s:known_types[a:ftype]
 
     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
+        " Only display an error message if the Tagbar window is open and we
+        " haven't seen the error before.
+        if bufwinnr("__Tagbar__") != -1 &&
+         \ (!s:known_files.has(a:realfname) ||
+         \ !empty(s:known_files.get(a:realfname)))
+            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
         endif
         return -1
     endif
 " tagname<TAB>filename<TAB>expattern;"fields
 " fields: <TAB>name:value
 " fields that are always present: kind, line
-function! s:ParseTagline(part1, part2, typeinfo, fileinfo)
+function! s:ParseTagline(part1, part2, typeinfo, fileinfo) abort
     let basic_info  = split(a:part1, '\t')
 
     let taginfo      = s:NormalTag.New(basic_info[0])
     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 pattern         = strpart(pattern, start, end - start)
+    let taginfo.pattern = '\V\^\C' . pattern . dollar
+
+    " 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 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)
+        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')
         if len(val) > 0
             let taginfo.fields[key] = val
         endif
         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!'
+        call s:LogDebugMessage('ERROR Unknown tag kind: ' . taginfo.fields.kind)
+        echoerr 'Unknown tag kind encountered: ' . taginfo.fields.kind
+              \ 'Your ctags and Tagbar configurations are out of sync!'
               \ 'Please read '':help tagbar-extend''.'
     endtry
 
 " 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)
+                        \ typeinfo, fileinfo) abort
     if !empty(a:parent)
         let curpath = a:parent.fullpath
         let pscope  = a:typeinfo.kind2scope[a:parent.fields.kind]
 endfunction
 
 " s:ProcessPseudoTag() {{{2
-function! s:ProcessPseudoTag(curtags, tag, parent, typeinfo, fileinfo)
+function! s:ProcessPseudoTag(curtags, tag, parent, typeinfo, fileinfo) abort
     let curpath = !empty(a:parent) ? a:parent.fullpath : ''
 
     let pseudoname = substitute(a:tag.path, curpath, '', '')
 endfunction
 
 " s:ProcessPseudoChildren() {{{2
-function! s:ProcessPseudoChildren(tags, tag, depth, typeinfo, fileinfo)
+function! s:ProcessPseudoChildren(tags, tag, depth, typeinfo, fileinfo) abort
     for childtag in a:tag.children
         let childtag.parent = a:tag
 
 endfunction
 
 " s:CreatePseudoTag() {{{2
-function! s:CreatePseudoTag(name, parent, scope, typeinfo, fileinfo)
+function! s:CreatePseudoTag(name, parent, scope, typeinfo, fileinfo) abort
     if !empty(a:parent)
         let curpath = a:parent.fullpath
         let pscope  = a:typeinfo.kind2scope[a:parent.fields.kind]
 
 " Sorting {{{1
 " s:SortTags() {{{2
-function! s:SortTags(tags, comparemethod)
+function! s:SortTags(tags, comparemethod) abort
     call sort(a:tags, a:comparemethod)
 
     for tag in a:tags
 endfunction
 
 " s:CompareByKind() {{{2
-function! s:CompareByKind(tag1, tag2)
+function! s:CompareByKind(tag1, tag2) abort
     let typeinfo = s:compare_typeinfo
 
     if typeinfo.kinddict[a:tag1.fields.kind] <#
 endfunction
 
 " s:CompareByLine() {{{2
-function! s:CompareByLine(tag1, tag2)
+function! s:CompareByLine(tag1, tag2) abort
     return a:tag1.fields.line - a:tag2.fields.line
 endfunction
 
 " s:ToggleSort() {{{2
-function! s:ToggleSort()
+function! s:ToggleSort() abort
     let fileinfo = s:known_files.getCurrent()
     if empty(fileinfo)
         return
 
 " Display {{{1
 " s:RenderContent() {{{2
-function! s:RenderContent(...)
+function! s:RenderContent(...) abort
     call s:LogDebugMessage('RenderContent called')
     let s:new_window = 0
 
     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.
+        call s:winexec('noautocmd wincmd p')
+        let pprevwinnr = 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)
+        call s:LogDebugMessage('Redisplaying file [' . fileinfo.fpath . ']')
         let saveline = line('.')
         let savecol  = col('.')
         let topline  = line('w0')
     let &eventignore = eventignore_save
 
     if !in_tagbar
+        call s:winexec(pprevwinnr . 'wincmd w')
         call s:winexec(prevwinnr . 'wincmd w')
     endif
 endfunction
 
 " s:PrintKinds() {{{2
-function! s:PrintKinds(typeinfo, fileinfo)
+function! s:PrintKinds(typeinfo, fileinfo) abort
     call s:LogDebugMessage('PrintKinds called')
 
     let first_tag = 1
                             " only if they are not scope-defining tags (since
                             " those already have an identifier)
                             if !has_key(a:typeinfo.kind2scope, ckind.short)
-                                silent put ='    [' . ckind.long . ']'
+                                let indent  = g:tagbar_indent
+                                let indent += g:tagbar_show_visibility
+                                let indent += 1 " fold symbol
+                                silent put =repeat(' ', indent) .
+                                                 \ '[' . ckind.long . ']'
                                 " Add basic tag to allow folding when on the
                                 " header line
                                 let headertag = s:BaseTag.New(ckind.long)
                 let foldmarker = s:icon_open
             endif
 
+            let padding = g:tagbar_show_visibility ? ' ' : ''
             if g:tagbar_compact && first_tag && s:short_help
-                silent 0put =foldmarker . ' ' . kind.long
+                silent 0put =foldmarker . padding . kind.long
             else
-                silent  put =foldmarker . ' ' . kind.long
+                silent  put =foldmarker . padding . kind.long
             endif
 
             let curline                   = line('.')
             if !kindtag.isFolded()
                 for tag in curtags
                     let str = tag.strfmt()
-                    silent put ='  ' . str
+                    silent put =repeat(' ', g:tagbar_indent) . str
 
                     " Save the current tagbar line in the tag for easy
                     " highlighting access
 endfunction
 
 " s:PrintTag() {{{2
-function! s:PrintTag(tag, depth, fileinfo, typeinfo)
+function! s:PrintTag(tag, depth, fileinfo, typeinfo) abort
     " Print tag indented according to depth
-    silent put =repeat(' ', a:depth * 2) . a:tag.strfmt()
+    silent put =repeat(' ', a:depth * g:tagbar_indent) . a:tag.strfmt()
 
     " Save the current tagbar line in the tag for easy
     " highlighting access
                 " are not scope-defining tags (since those already have an
                 " identifier)
                 if !has_key(a:typeinfo.kind2scope, ckind.short)
-                    silent put ='    ' . repeat(' ', a:depth * 2) .
-                              \ '[' . ckind.long . ']'
+                    let indent  = (a:depth + 1) * g:tagbar_indent
+                    let indent += g:tagbar_show_visibility
+                    let indent += 1 " fold symbol
+                    silent put =repeat(' ', indent) . '[' . ckind.long . ']'
                     " Add basic tag to allow folding when on the header line
                     let headertag = s:BaseTag.New(ckind.long)
                     let headertag.parent = a:tag
 endfunction
 
 " s:PrintHelp() {{{2
-function! s:PrintHelp()
+function! s:PrintHelp() abort
     if !g:tagbar_compact && s:short_help
         silent 0put ='\" Press <F1> for help'
         silent  put _
 
 " s:RenderKeepView() {{{2
 " The gist of this function was taken from NERDTree by Martin Grenfell.
-function! s:RenderKeepView(...)
+function! s:RenderKeepView(...) abort
     if a:0 == 1
         let line = a:1
     else
 
 " User actions {{{1
 " s:HighlightTag() {{{2
-function! s:HighlightTag(openfolds, ...)
+function! s:HighlightTag(openfolds, ...) abort
     let tagline = 0
 
-    if a:0 > 0
-        let tag = s:GetNearbyTag(1, a:1)
+    let force = a:0 > 0 ? a:1 : 0
+
+    if a:0 > 1
+        let tag = s:GetNearbyTag(1, a:2)
     else
         let tag = s:GetNearbyTag(1)
     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
+    if !force && tagline == s:last_highlight_tline
         return
     else
         let s:last_highlight_tline = tagline
     if tagbarwinnr == -1
         return
     endif
-    let prevwinnr   = winnr()
+    let prevwinnr = winnr()
     call s:winexec(tagbarwinnr . 'wincmd w')
 
     match none
     call s:LogDebugMessage("Highlight pattern: '" . pattern . "'")
     execute 'match TagbarHighlight ' . pattern
 
-    if a:0 == 0 " no line explicitly given, so assume we were in the file window
+    if a:0 <= 1 " no line explicitly given, so assume we were in the file window
         call s:winexec(prevwinnr . 'wincmd w')
     endif
 
 endfunction
 
 " s:JumpToTag() {{{2
-function! s:JumpToTag(stay_in_tagbar)
+function! s:JumpToTag(stay_in_tagbar) abort
     let taginfo = s:GetTagInfo(line('.'), 1)
 
     let autoclose = w:autoclose
 
     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:GotoPreviousWindow(taginfo.fileinfo)
+
+    " 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 and jump to the correct column if available
+    normal! z.
+    call cursor(taginfo.fields.line, taginfo.fields.column)
+
+    if foldclosed('.') != -1
+        .foldopen
+    endif
+
+    redraw
+
+    if a:stay_in_tagbar
+        call s:HighlightTag(0)
+        call s:winexec(tagbarwinnr . 'wincmd w')
+    elseif g:tagbar_autoclose || autoclose
+        call s:CloseWindow()
+    else
+        call s:HighlightTag(0)
+    endif
+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 = 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() abort
+    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() abort
+    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, force) abort
+    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 = 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 has_key(tag, 'children')
+            call s:SetFoldLevelRecursive(a:fileinfo, tag.children, a:level)
+        endif
+    endfor
+endfunction
+
+" s:OpenParents() {{{2
+function! s:OpenParents(...) abort
+    let tagline = 0
+
+    if a:0 == 1
+        let tag = a:1
+    else
+        let tag = s:GetNearbyTag(1)
+    endif
+
+    if !empty(tag)
+        call tag.openParents()
+        call s:RenderKeepView()
+    endif
+endfunction
+
+" Helper functions {{{1
+" s:AutoUpdate() {{{2
+function! s:AutoUpdate(fname, force) abort
+    call s:LogDebugMessage('AutoUpdate called [' . 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 we're in the tagbar window
+    if ftype == 'tagbar'
+        call s:LogDebugMessage('In Tagbar window, stopping processing')
+        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')
+        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) ||
+         \ (filereadable(a:fname) && getftime(a:fname) > curfile.mtime)
+            call s:LogDebugMessage('File data outdated, updating' .
+                                 \ ' [' . a:fname . ']')
+            call s:ProcessFile(a:fname, sftype)
+            let updated = 1
+        else
+            call s:LogDebugMessage('File data seems up to date' .
+                                 \ ' [' . a:fname . ']')
+        endif
+    elseif !s:known_files.has(a:fname)
+        call s:LogDebugMessage('New file, processing [' . a:fname . ']')
+        call s:ProcessFile(a:fname, sftype)
+        let updated = 1
+    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 if the tags have been updated or a different
+    " file is being displayed
+    if bufwinnr('__Tagbar__') != -1 &&
+     \ (s:new_window || updated ||
+      \ (!empty(s:known_files.getCurrent()) &&
+       \ a:fname != s:known_files.getCurrent().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 s:LogDebugMessage('Setting current file [' . a:fname . ']')
+        call s:known_files.setCurrent(fileinfo)
+        let s:nearby_disabled = 0
+    endif
+
+    call s:HighlightTag(0)
+    call s:LogDebugMessage('AutoUpdate finished successfully')
+endfunction
+
+" s:CheckMouseClick() {{{2
+function! s:CheckMouseClick() abort
+    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) 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
+function! s:EscapeCtagsCmd(ctags_bin, args, ...) abort
+    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
+
+    " Stupid cmd.exe quoting
+    if &shell =~ 'cmd\.exe'
+        let reserved_chars = '&()@^'
+        " not allowed in filenames, but escape anyway just in case
+        let reserved_chars .= '<>|'
+        let pattern = join(split(reserved_chars, '\zs'), '\|')
+        let ctags_cmd = substitute(ctags_cmd, '\V\(' . pattern . '\)',
+                                 \ '^\0', 'g')
+    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 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) abort
+    call s:LogDebugMessage('Executing ctags command: ' . a: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:GetNearbyTag() {{{2
+" Get the tag info for a file near the cursor in the current file
+function! s:GetNearbyTag(all, ...) abort
+    if s:nearby_disabled
+        return {}
+    endif
+
+    let fileinfo = s:known_files.getCurrent()
+    if empty(fileinfo)
+        return {}
+    endif
+
+    let typeinfo = fileinfo.typeinfo
+    if a:0 > 0
+        let curline = a:1
+    else
+        let curline = line('.')
+    endif
+    let tag = {}
+
+    " If a tag appears in a file more than once (for example namespaces in
+    " C++) only one of them has a 'tline' entry and can thus be highlighted.
+    " The only way to solve this would be to go over the whole tag list again,
+    " making everything slower. Since this should be a rare occurence and
+    " highlighting isn't /that/ important ignore it for now.
+    for line in range(curline, 1, -1)
+        if has_key(fileinfo.fline, line)
+            let curtag = fileinfo.fline[line]
+            if a:all || typeinfo.getKind(curtag.fields.kind).stl
+                let tag = curtag
+                break
+            endif
+        endif
+    endfor
+
+    return tag
+endfunction
+
+" s:GetTagInfo() {{{2
+" Return the info dictionary of the tag on the specified line. If the line
+" does not contain a valid tag (for example because it is empty or only
+" contains a pseudo-tag) return an empty dictionary.
+function! s:GetTagInfo(linenr, ignorepseudo) abort
+    let fileinfo = s:known_files.getCurrent()
+
+    if empty(fileinfo)
+        return {}
+    endif
+
+    " Don't do anything in empty and comment lines
+    let curline = getbufline(bufnr('__Tagbar__'), 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:GotoPreviousWindow() {{{2
+" Try to switch to the previous buffer/window; if the buffer isn't currently
+" shown in a window Tagbar will open it in the first window that has a
+" non-special buffer in it.
+function! s:GotoPreviousWindow(fileinfo) abort
+    let tagbarwinnr = bufwinnr('__Tagbar__')
+
     call s:winexec('wincmd p')
-    let filebufnr = bufnr(taginfo.fileinfo.fpath)
+
+    let filebufnr = bufnr(a:fileinfo.fpath)
     if bufnr('%') != filebufnr
         let filewinnr = bufwinnr(filebufnr)
         if filewinnr != -1
         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 and jump to the correct column if available
-    normal! z.
-    call cursor(taginfo.fields.line, taginfo.fields.column)
-
-    if foldclosed('.') != -1
-        .foldopen!
-    endif
-
-    redraw
-
-    if a:stay_in_tagbar
-        call s:HighlightTag(0)
-        call s:winexec(tagbarwinnr . 'wincmd w')
-    elseif g:tagbar_autoclose || autoclose
-        call s:CloseWindow()
-    else
-        call s:HighlightTag(0)
-    endif
+    return winnr()
 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, force)
-    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 = 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)
-    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(1)
-    endif
-
-    call tag.openParents()
-
-    call s:RenderKeepView()
-endfunction
-
-" Helper functions {{{1
-" 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 we're in the tagbar window
-    if 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
-
-    let updated = 0
-
-    " 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)
-            let updated = 1
-        endif
-    elseif !s:known_files.has(a:fname)
-        call s:LogDebugMessage('New file, processing ' . a:fname)
-        call s:ProcessFile(a:fname, sftype)
-        let updated = 1
-    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 if the tags have been updated or a different
-    " file is being displayed
-    if bufwinnr('__Tagbar__') != -1 &&
-     \ (s:new_window || updated || a:fname != s:known_files.getCurrent().fpath)
-        call s:RenderContent(fileinfo)
-    endif
-
-    " 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(0)
-    call s:LogDebugMessage('AutoUpdate finished successfully')
-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: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
-
-    " Stupid cmd.exe quoting
-    if &shell =~ 'cmd\.exe'
-        let ctags_cmd = substitute(ctags_cmd, '\(&\|\^\)', '^\0', 'g')
-    endif
-
-    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')