1
0
mirror of https://github.com/gryf/.vim.git synced 2025-12-17 11:30:29 +01:00
Files
.vim/plugin/buffergator.vim

1641 lines
65 KiB
VimL

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
"" Buffergator
""
"" Vim document buffer navigation utility
""
"" Copyright 2011 Jeet Sukumaran.
""
"" This program is free software; you can redistribute it and/or modify
"" it under the terms of the GNU General Public License as published by
"" the Free Software Foundation; either version 3 of the License, or
"" (at your option) any later version.
""
"" This program is distributed in the hope that it will be useful,
"" but WITHOUT ANY WARRANTY; without even the implied warranty of
"" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
"" GNU General Public License <http://www.gnu.org/licenses/>
"" for more details.
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Reload and Compatibility Guard {{{1
" ============================================================================
" Reload protection.
if (exists('g:did_buffergator') && g:did_buffergator) || &cp || version < 700
finish
endif
let g:did_buffergator = 1
" avoid line continuation issues (see ':help user_41.txt')
let s:save_cpo = &cpo
set cpo&vim
" 1}}}
" Global Plugin Options {{{1
" =============================================================================
if !exists("g:buffergator_viewport_split_policy")
let g:buffergator_viewport_split_policy = "L"
endif
if !exists("g:buffergator_move_wrap")
let g:buffergator_move_wrap = 1
endif
if !exists("g:buffergator_autodismiss_on_select")
let g:buffergator_autodismiss_on_select = 1
endif
if !exists("g:buffergator_autoexpand_on_split")
let g:buffergator_autoexpand_on_split = 1
endif
if !exists("g:buffergator_split_size")
let g:buffergator_split_size = 40
endif
if !exists("g:buffergator_sort_regime")
let g:buffergator_sort_regime = "bufnum"
endif
if !exists("g:buffergator_display_regime")
let g:buffergator_display_regime = "basename"
endif
" 1}}}
" Script Data and Variables {{{1
" =============================================================================
" Split Modes {{{2
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
" Split modes are indicated by a single letter. Upper-case letters indicate
" that the SCREEN (i.e., the entire application "window" from the operating
" system's perspective) should be split, while lower-case letters indicate
" that the VIEWPORT (i.e., the "window" in Vim's terminology, referring to the
" various subpanels or splits within Vim) should be split.
" Split policy indicators and their corresponding modes are:
" ``/`d`/`D' : use default splitting mode
" `n`/`N` : NO split, use existing window.
" `L` : split SCREEN vertically, with new split on the left
" `l` : split VIEWPORT vertically, with new split on the left
" `R` : split SCREEN vertically, with new split on the right
" `r` : split VIEWPORT vertically, with new split on the right
" `T` : split SCREEN horizontally, with new split on the top
" `t` : split VIEWPORT horizontally, with new split on the top
" `B` : split SCREEN horizontally, with new split on the bottom
" `b` : split VIEWPORT horizontally, with new split on the bottom
let s:buffergator_viewport_split_modes = {
\ "d" : "sp",
\ "D" : "sp",
\ "N" : "buffer",
\ "n" : "buffer",
\ "L" : "topleft vert sbuffer",
\ "l" : "leftabove vert sbuffer",
\ "R" : "botright vert sbuffer",
\ "r" : "rightbelow vert sbuffer",
\ "T" : "topleft sbuffer",
\ "t" : "leftabove sbuffer",
\ "B" : "botright sbuffer",
\ "b" : "rightbelow sbuffer",
\ }
" 2}}}
" Catalog Sort Regimes {{{2
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let s:buffergator_catalog_sort_regimes = ['basename', 'filepath', 'extension', 'bufnum', 'mru']
let s:buffergator_catalog_sort_regime_desc = {
\ 'basename' : ["basename", "by basename (followed by directory)"],
\ 'filepath' : ["filepath", "by (full) filepath"],
\ 'extension' : ["ext", "by extension (followed by full filepath)"],
\ 'bufnum' : ["bufnum", "by buffer number"],
\ 'mru' : ["mru", "by most recently used"],
\ }
let s:buffergator_default_catalog_sort_regime = "bufnum"
" 2}}}
" Catalog Display Regimes {{{2
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let s:buffergator_catalog_display_regimes = ['basename', 'filepath', 'bufname']
let s:buffergator_catalog_display_regime_desc = {
\ 'basename' : ["basename", "basename (followed by directory)"],
\ 'filepath' : ["filepath", "full filepath"],
\ 'bufname' : ["bufname", "buffer name"],
\ }
let s:buffergator_default_display_regime = "basename"
" 2}}}
" MRU {{{2
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let s:buffergator_mru = []
" 2}}}
" 1}}}
" Utilities {{{1
" ==============================================================================
" Text Formatting {{{2
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function! s:_format_align_left(text, width, fill_char)
let l:fill = repeat(a:fill_char, a:width-len(a:text))
return a:text . l:fill
endfunction
function! s:_format_align_right(text, width, fill_char)
let l:fill = repeat(a:fill_char, a:width-len(a:text))
return l:fill . a:text
endfunction
function! s:_format_time(secs)
if exists("*strftime")
return strftime("%Y-%m-%d %H:%M:%S", a:secs)
else
return (localtime() - a:secs) . " secs ago"
endif
endfunction
function! s:_format_escaped_filename(file)
if exists('*fnameescape')
return fnameescape(a:file)
else
return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
endif
endfunction
" trunc: -1 = truncate left, 0 = no truncate, +1 = truncate right
function! s:_format_truncated(str, max_len, trunc)
if len(a:str) > a:max_len
if a:trunc > 0
return strpart(a:str, a:max_len - 4) . " ..."
elseif a:trunc < 0
return '... ' . strpart(a:str, len(a:str) - a:max_len + 4)
endif
else
return a:str
endif
endfunction
" Pads/truncates text to fit a given width.
" align: -1/0 = align left, 0 = no align, 1 = align right
" trunc: -1 = truncate left, 0 = no truncate, +1 = truncate right
function! s:_format_filled(str, width, align, trunc)
let l:prepped = a:str
if a:trunc != 0
let l:prepped = s:Format_Truncate(a:str, a:width, a:trunc)
endif
if len(l:prepped) < a:width
if a:align > 0
let l:prepped = s:_format_align_right(l:prepped, a:width, " ")
elseif a:align < 0
let l:prepped = s:_format_align_left(l:prepped, a:width, " ")
endif
endif
return l:prepped
endfunction
" 2}}}
" Messaging {{{2
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function! s:NewMessenger(name)
" allocate a new pseudo-object
let l:messenger = {}
let l:messenger["name"] = a:name
if empty(a:name)
let l:messenger["title"] = "buffergator"
else
let l:messenger["title"] = "buffergator (" . l:messenger["name"] . ")"
endif
function! l:messenger.format_message(leader, msg) dict
return self.title . ": " . a:leader.a:msg
endfunction
function! l:messenger.format_exception( msg) dict
return a:msg
endfunction
function! l:messenger.send_error(msg) dict
redraw
echohl ErrorMsg
echomsg self.format_message("[ERROR] ", a:msg)
echohl None
endfunction
function! l:messenger.send_warning(msg) dict
redraw
echohl WarningMsg
echomsg self.format_message("[WARNING] ", a:msg)
echohl None
endfunction
function! l:messenger.send_status(msg) dict
redraw
echohl None
echomsg self.format_message("", a:msg)
endfunction
function! l:messenger.send_info(msg) dict
redraw
echohl None
echo self.format_message("", a:msg)
endfunction
return l:messenger
endfunction
" 2}}}
" Catalog, Buffer, Windows, Files, etc. Management {{{2
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
" Searches for all buffers that have a buffer-scoped variable `varname`
" with value that matches the expression `expr`. Returns list of buffer
" numbers that meet the criterion.
function! s:_find_buffers_with_var(varname, expr)
let l:results = []
for l:bni in range(1, bufnr("$"))
if !bufexists(l:bni)
continue
endif
let l:bvar = getbufvar(l:bni, "")
if empty(a:varname)
call add(l:results, l:bni)
elseif has_key(l:bvar, a:varname) && l:bvar[a:varname] =~ a:expr
call add(l:results, l:bni)
endif
endfor
return l:results
endfunction
" Returns split mode to use for a new Buffergator viewport.
function! s:_get_split_mode()
if has_key(s:buffergator_viewport_split_modes, g:buffergator_viewport_split_policy)
return s:buffergator_viewport_split_modes[g:buffergator_viewport_split_policy]
else
call s:_buffergator_messenger.send_error("Unrecognized split mode specified by 'g:buffergator_viewport_split_policy': " . g:buffergator_viewport_split_policy)
endif
endfunction
" Detect filetype. From the 'taglist' plugin.
" Copyright (C) 2002-2007 Yegappan Lakshmanan
function! s:_detect_filetype(fname)
" Ignore the filetype autocommands
let old_eventignore = &eventignore
set eventignore=FileType
" Save the 'filetype', as this will be changed temporarily
let old_filetype = &filetype
" Run the filetypedetect group of autocommands to determine
" the filetype
exe 'doautocmd filetypedetect BufRead ' . a:fname
" Save the detected filetype
let ftype = &filetype
" Restore the previous state
let &filetype = old_filetype
let &eventignore = old_eventignore
return ftype
endfunction
function! s:_is_full_width_window(win_num)
if winwidth(a:win_num) == &columns
return 1
else
return 0
endif
endfunction!
function! s:_is_full_height_window(win_num)
if winheight(a:win_num) + &cmdheight + 1 == &lines
return 1
else
return 0
endif
endfunction!
" Moves (or adds) the given buffer number to the top of the list
function! s:_update_mru(acmd_bufnr)
let bnum = a:acmd_bufnr + 0
if bnum == 0
return
endif
call filter(s:buffergator_mru, 'v:val !=# bnum')
call insert(s:buffergator_mru, bnum, 0)
endfunction
" 2}}}
" Sorting {{{2
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
" comparison function used for sorting dictionaries by value
function! s:_compare_dicts_by_value(m1, m2, key)
if a:m1[a:key] < a:m2[a:key]
return -1
elseif a:m1[a:key] > a:m2[a:key]
return 1
else
return 0
endif
endfunction
" comparison function used for sorting buffers catalog by buffer number
function! s:_compare_dicts_by_bufnum(m1, m2)
return s:_compare_dicts_by_value(a:m1, a:m2, "bufnum")
endfunction
" comparison function used for sorting buffers catalog by buffer name
function! s:_compare_dicts_by_bufname(m1, m2)
return s:_compare_dicts_by_value(a:m1, a:m2, "bufname")
endfunction
" comparison function used for sorting buffers catalog by (full) filepath
function! s:_compare_dicts_by_filepath(m1, m2)
if a:m1["parentdir"] < a:m2["parentdir"]
return -1
elseif a:m1["parentdir"] > a:m2["parentdir"]
return 1
else
if a:m1["basename"] < a:m2["basename"]
return -1
elseif a:m1["basename"] > a:m2["basename"]
return 1
else
return 0
endif
endif
endfunction
" comparison function used for sorting buffers catalog by extension
function! s:_compare_dicts_by_extension(m1, m2)
if a:m1["extension"] < a:m2["extension"]
return -1
elseif a:m1["extension"] > a:m2["extension"]
return 1
else
return s:_compare_dicts_by_filepath(a:m1, a:m2)
endif
endfunction
" comparison function used for sorting buffers catalog by basename
function! s:_compare_dicts_by_basename(m1, m2)
return s:_compare_dicts_by_value(a:m1, a:m2, "basename")
endfunction
" comparison function used for sorting buffers catalog by mru
function! s:_compare_dicts_by_mru(m1, m2)
let l:i1 = index(s:buffergator_mru, a:m1['bufnum'])
let l:i2 = index(s:buffergator_mru, a:m2['bufnum'])
if l:i1 < l:i2
return -1
elseif l:i1 > l:i2
return 1
else
return 0
endif
endfunction
" 2}}}
" 1}}}
" CatalogViewer {{{1
" ============================================================================
function! s:NewCatalogViewer(name, title)
" initialize
let l:catalog_viewer = {}
let l:catalog_viewer["bufname"] = a:name
let l:catalog_viewer["title"] = a:title
let l:buffergator_bufs = s:_find_buffers_with_var("is_buffergator_buffer", 1)
if len(l:buffergator_bufs) > 0
let l:catalog_viewer["bufnum"] = l:buffergator_bufs[0]
endif
let l:catalog_viewer["jump_map"] = {}
let l:catalog_viewer["split_mode"] = s:_get_split_mode()
let l:catalog_viewer["sort_regime"] = g:buffergator_sort_regime
let l:catalog_viewer["display_regime"] = g:buffergator_display_regime
let l:catalog_viewer["is_zoomed"] = 0
let l:catalog_viewer["columns_expanded"] = 0
let l:catalog_viewer["lines_expanded"] = 0
" Initialize object state.
let l:catalog_viewer["bufnum"] = -1
function! l:catalog_viewer.list_buffers() dict
let bcat = []
redir => buffers_output
execute('silent ls')
redir END
let l:buffers_output_rows = split(l:buffers_output, "\n")
for l:buffers_output_row in l:buffers_output_rows
let l:parts = matchlist(l:buffers_output_row, '^\s*\(\d\+\)\(.....\) "\(.*\)"\s\+\w\+ \d\+$')
let l:info = {}
let l:info["bufnum"] = l:parts[1] + 0
if l:parts[2][0] == "u"
let l:info["is_unlisted"] = 1
let l:info["is_listed"] = 0
else
let l:info["is_unlisted"] = 0
let l:info["is_listed"] = 1
endif
if l:parts[2][1] == "%"
let l:info["is_current"] = 1
let l:info["is_alternate"] = 0
elseif l:parts[2][1] == "#"
let l:info["is_current"] = 0
let l:info["is_alternate"] = 1
else
let l:info["is_current"] = 0
let l:info["is_alternate"] = 0
endif
if l:parts[2][2] == "a"
let l:info["is_active"] = 1
let l:info["is_loaded"] = 1
let l:info["is_visible"] = 1
elseif l:parts[2][2] == "h"
let l:info["is_active"] = 0
let l:info["is_loaded"] = 1
let l:info["is_visible"] = 0
else
let l:info["is_active"] = 0
let l:info["is_loaded"] = 0
let l:info["is_visible"] = 0
endif
if l:parts[2][3] == "-"
let l:info["is_modifiable"] = 0
let l:info["is_readonly"] = 0
elseif l:parts[2][3] == "="
let l:info["is_modifiable"] = 1
let l:info["is_readonly"] = 1
else
let l:info["is_modifiable"] = 1
let l:info["is_readonly"] = 0
endif
if l:parts[2][4] == "+"
let l:info["is_modified"] = 1
let l:info["is_readerror"] = 0
elseif l:parts[2][4] == "x"
let l:info["is_modified"] = 0
let l:info["is_readerror"] = 0
else
let l:info["is_modified"] = 0
let l:info["is_readerror"] = 0
endif
let l:info["bufname"] = parts[3]
let l:info["filepath"] = fnamemodify(l:info["bufname"], ":p")
let l:info["basename"] = fnamemodify(l:info["bufname"], ":t")
let l:info["parentdir"] = fnamemodify(l:info["bufname"], ":p:h")
let l:info["extension"] = fnamemodify(l:info["bufname"], ":e")
call add(bcat, l:info)
" let l:buffers_info[l:info[l:key]] = l:info
endfor
let l:sort_func = "s:_compare_dicts_by_" . self.sort_regime
return sort(bcat, l:sort_func)
endfunction
" Opens viewer if closed, closes viewer if open.
function! l:catalog_viewer.toggle() dict
" get buffer number of the catalog view buffer, creating it if neccessary
if self.bufnum < 0 || !bufexists(self.bufnum)
call self.open()
else
let l:bfwn = bufwinnr(self.bufnum)
if l:bfwn >= 0
call self.close(1)
else
call self.open()
endif
endif
endfunction
" Creates a new buffer, renders and opens it.
function! l:catalog_viewer.create_buffer() dict
" get a new buf reference
let self.bufnum = bufnr(self.bufname, 1)
" get a viewport onto it
call self.activate_viewport()
" initialize it (includes "claiming" it)
call self.initialize_buffer()
" render it
call self.render_buffer()
endfunction
" Opens a viewport on the buffer according, creating it if neccessary
" according to the spawn mode. Valid buffer number must already have been
" obtained before this is called.
function! l:catalog_viewer.activate_viewport() dict
let l:bfwn = bufwinnr(self.bufnum)
if l:bfwn == winnr()
" viewport wth buffer already active and current
return
elseif l:bfwn >= 0
" viewport with buffer exists, but not current
execute(l:bfwn . " wincmd w")
else
" create viewport
let self.split_mode = s:_get_split_mode()
" gryf: PLEASE! Stop this annoying resizing.
"call self.expand_screen()
execute("silent keepalt keepjumps " . self.split_mode . " " . self.bufnum)
if g:buffergator_viewport_split_policy =~ '[RrLl]' && g:buffergator_split_size
execute("vertical resize " . g:buffergator_split_size)
setlocal winfixwidth
elseif g:buffergator_viewport_split_policy =~ '[TtBb]' && g:buffergator_split_size
execute("resize " . g:buffergator_split_size)
setlocal winfixheight
endif
endif
endfunction
" Sets up buffer environment.
function! l:catalog_viewer.initialize_buffer() dict
call self.claim_buffer()
call self.setup_buffer_opts()
call self.setup_buffer_syntax()
call self.setup_buffer_commands()
call self.setup_buffer_keymaps()
call self.setup_buffer_folding()
call self.setup_buffer_statusline()
endfunction
" 'Claims' a buffer by setting it to point at self.
function! l:catalog_viewer.claim_buffer() dict
call setbufvar("%", "is_buffergator_buffer", 1)
call setbufvar("%", "buffergator_catalog_viewer", self)
call setbufvar("%", "buffergator_last_render_time", 0)
call setbufvar("%", "buffergator_cur_line", 0)
endfunction
" 'Unclaims' a buffer by stripping all buffergator vars
function! l:catalog_viewer.unclaim_buffer() dict
for l:var in ["is_buffergator_buffer",
\ "buffergator_catalog_viewer",
\ "buffergator_last_render_time",
\ "buffergator_cur_line"
\ ]
if exists("b:" . l:var)
unlet b:{l:var}
endif
endfor
endfunction
" Sets buffer options.
function! l:catalog_viewer.setup_buffer_opts() dict
setlocal buftype=nofile
setlocal noswapfile
setlocal nowrap
set bufhidden=hide
setlocal nobuflisted
setlocal nolist
setlocal noinsertmode
setlocal nonumber
setlocal cursorline
setlocal nospell
endfunction
" Sets buffer commands.
function! l:catalog_viewer.setup_buffer_commands() dict
" command! -bang -nargs=* Bdfilter :call b:buffergator_catalog_viewer.set_filter('<bang>', <q-args>)
augroup BuffergatorCatalogViewer
au!
autocmd CursorHold,CursorHoldI,CursorMoved,CursorMovedI,BufEnter,BufLeave <buffer> call b:buffergator_catalog_viewer.highlight_current_line()
autocmd BufLeave <buffer> let s:_buffergator_last_catalog_viewed = b:buffergator_catalog_viewer
augroup END
endfunction
function! l:catalog_viewer.disable_editing_keymaps() dict
"""" Disabling of unused modification keys
for key in [".", "p", "P", "C", "x", "X", "r", "R", "i", "I", "a", "A", "D", "S", "U"]
try
execute "nnoremap <buffer> " . key . " <NOP>"
catch //
endtry
endfor
endfunction
" Sets buffer folding.
function! l:catalog_viewer.setup_buffer_folding() dict
" if has("folding")
" "setlocal foldcolumn=3
" setlocal foldmethod=syntax
" setlocal foldlevel=4
" setlocal foldenable
" setlocal foldtext=BuffergatorFoldText()
" " setlocal fillchars=fold:\ "
" setlocal fillchars=fold:.
" endif
endfunction
" Close and quit the viewer.
function! l:catalog_viewer.close(restore_prev_window) dict
if self.bufnum < 0 || !bufexists(self.bufnum)
return
endif
call self.contract_screen()
if a:restore_prev_window
if !self.is_usable_viewport(winnr("#")) && self.first_usable_viewport() ==# -1
else
try
if !self.is_usable_viewport(winnr("#"))
execute(self.first_usable_viewport() . "wincmd w")
else
execute('wincmd p')
endif
catch //
endtry
endif
endif
execute("bwipe " . self.bufnum)
endfunction
function! l:catalog_viewer.expand_screen() dict
if has("gui_running") && g:buffergator_autoexpand_on_split && g:buffergator_split_size
if g:buffergator_viewport_split_policy =~ '[RL]'
let self.pre_expand_columns = &columns
let &columns += g:buffergator_split_size
let self.columns_expanded = &columns - self.pre_expand_columns
else
let self.columns_expanded = 0
endif
if g:buffergator_viewport_split_policy =~ '[TB]'
let self.pre_expand_lines = &lines
let &lines += g:buffergator_split_size
let self.lines_expanded = &lines - self.pre_expand_lines
else
let self.lines_expanded = 0
endif
endif
endfunction
function! l:catalog_viewer.contract_screen() dict
if self.columns_expanded
\ && &columns - self.columns_expanded > 20
let new_size = &columns - self.columns_expanded
if new_size < self.pre_expand_columns
let new_size = self.pre_expand_columns
endif
let &columns = new_size
endif
if self.lines_expanded
\ && &lines - self.lines_expanded > 20
let new_size = &lines - self.lines_expanded
if new_size < self.pre_expand_lines
let new_size = self.pre_expand_lines
endif
let &lines = new_size
endif
endfunction
function! l:catalog_viewer.highlight_current_line()
" if line(".") != b:buffergator_cur_line
let l:prev_line = b:buffergator_cur_line
let b:buffergator_cur_line = line(".")
3match none
exec '3match BuffergatorCurrentEntry /^\%'. b:buffergator_cur_line .'l.*/'
" endif
endfunction
" Clears the buffer contents.
function! l:catalog_viewer.clear_buffer() dict
call cursor(1, 1)
exec 'silent! normal! "_dG'
endfunction
" from NERD_Tree, via VTreeExplorer: determine the number of windows open
" to this buffer number.
function! l:catalog_viewer.num_viewports_on_buffer(bnum) dict
let cnt = 0
let winnum = 1
while 1
let bufnum = winbufnr(winnum)
if bufnum < 0
break
endif
if bufnum ==# a:bnum
let cnt = cnt + 1
endif
let winnum = winnum + 1
endwhile
return cnt
endfunction
" from NERD_Tree: find the window number of the first normal window
function! l:catalog_viewer.first_usable_viewport() dict
let i = 1
while i <= winnr("$")
let bnum = winbufnr(i)
if bnum != -1 && getbufvar(bnum, '&buftype') ==# ''
\ && !getwinvar(i, '&previewwindow')
\ && (!getbufvar(bnum, '&modified') || &hidden)
return i
endif
let i += 1
endwhile
return -1
endfunction
" from NERD_Tree: returns 0 if opening a file from the tree in the given
" window requires it to be split, 1 otherwise
function! l:catalog_viewer.is_usable_viewport(winnumber) dict
"gotta split if theres only one window (i.e. the NERD tree)
if winnr("$") ==# 1
return 0
endif
let oldwinnr = winnr()
execute(a:winnumber . "wincmd p")
let specialWindow = getbufvar("%", '&buftype') != '' || getwinvar('%', '&previewwindow')
let modified = &modified
execute(oldwinnr . "wincmd p")
"if its a special window e.g. quickfix or another explorer plugin then we
"have to split
if specialWindow
return 0
endif
if &hidden
return 1
endif
return !modified || self.num_viewports_on_buffer(winbufnr(a:winnumber)) >= 2
endfunction
" Acquires a viewport to show the source buffer. Returns the split command
" to use when switching to the buffer.
function! l:catalog_viewer.acquire_viewport(split_cmd)
if self.split_mode == "buffer" && empty(a:split_cmd)
" buffergator used original buffer's viewport,
" so the the buffergator viewport is the viewport to use
return ""
endif
if !self.is_usable_viewport(winnr("#")) && self.first_usable_viewport() ==# -1
" no appropriate viewport is available: create new using default
" split mode
" TODO: maybe use g:buffergator_viewport_split_policy?
if empty(a:split_cmd)
return "sb"
else
return a:split_cmd
endif
else
try
if !self.is_usable_viewport(winnr("#"))
execute(self.first_usable_viewport() . "wincmd w")
else
execute('wincmd p')
endif
catch /^Vim\%((\a\+)\)\=:E37/
echo v:exception
catch /^Vim\%((\a\+)\)\=:/
echo v:exception
endtry
return a:split_cmd
endif
endfunction
" Finds next occurrence of specified pattern.
function! l:catalog_viewer.goto_pattern(pattern, direction) dict range
if a:direction == "b" || a:direction == "p"
let l:flags = "b"
" call cursor(line(".")-1, 0)
else
let l:flags = ""
" call cursor(line(".")+1, 0)
endif
if g:buffergator_move_wrap
let l:flags .= "w"
else
let l:flags .= "W"
endif
let l:flags .= "e"
let l:lnum = -1
for i in range(v:count1)
if search(a:pattern, l:flags) < 0
break
else
let l:lnum = 1
endif
endfor
if l:lnum < 0
if l:flags[0] == "b"
call s:_buffergator_messenger.send_info("No previous results")
else
call s:_buffergator_messenger.send_info("No more results")
endif
return 0
else
return 1
endif
endfunction
" Cycles sort regime.
function! l:catalog_viewer.cycle_sort_regime() dict
let l:cur_regime = index(s:buffergator_catalog_sort_regimes, self.sort_regime)
let l:cur_regime += 1
if l:cur_regime < 0 || l:cur_regime >= len(s:buffergator_catalog_sort_regimes)
let self.sort_regime = s:buffergator_catalog_sort_regimes[0]
else
let self.sort_regime = s:buffergator_catalog_sort_regimes[l:cur_regime]
endif
call self.open(1)
let l:sort_desc = get(s:buffergator_catalog_sort_regime_desc, self.sort_regime, ["??", "in unspecified order"])[1]
call s:_buffergator_messenger.send_info("sorted " . l:sort_desc)
endfunction
" Cycles display regime.
function! l:catalog_viewer.cycle_display_regime() dict
let l:cur_regime = index(s:buffergator_catalog_display_regimes, self.display_regime)
let l:cur_regime += 1
if l:cur_regime < 0 || l:cur_regime >= len(s:buffergator_catalog_display_regimes)
let self.display_regime = s:buffergator_catalog_display_regimes[0]
else
let self.display_regime = s:buffergator_catalog_display_regimes[l:cur_regime]
endif
call self.open(1)
let l:display_desc = get(s:buffergator_catalog_display_regime_desc, self.display_regime, ["??", "in unspecified order"])[1]
call s:_buffergator_messenger.send_info("displaying " . l:display_desc)
endfunction
" Rebuilds catalog.
function! l:catalog_viewer.rebuild_catalog() dict
call self.open(1)
endfunction
" Zooms/unzooms window.
function! l:catalog_viewer.toggle_zoom() dict
let l:bfwn = bufwinnr(self.bufnum)
if l:bfwn < 0
return
endif
if self.is_zoomed
" if s:_is_full_height_window(l:bfwn) && !s:_is_full_width_window(l:bfwn)
if g:buffergator_viewport_split_policy =~ '[RrLl]'
if !g:buffergator_split_size
let l:new_size = &columns / 3
else
let l:new_size = g:buffergator_split_size
endif
if l:new_size > 0
execute("vertical resize " . string(l:new_size))
endif
let self.is_zoomed = 0
" elseif s:_is_full_width_window(l:bfwn) && !s:_is_full_height_window(l:bfwn)
elseif g:buffergator_viewport_split_policy =~ '[TtBb]'
if !g:buffergator_split_size
let l:new_size = &lines / 3
else
let l:new_size = g:buffergator_split_size
endif
if l:new_size > 0
execute("resize " . string(l:new_size))
endif
let self.is_zoomed = 0
endif
else
" if s:_is_full_height_window(l:bfwn) && !s:_is_full_width_window(l:bfwn)
if g:buffergator_viewport_split_policy =~ '[RrLl]'
if &columns > 20
execute("vertical resize " . string(&columns-10))
let self.is_zoomed = 1
endif
" elseif s:_is_full_width_window(l:bfwn) && !s:_is_full_height_window(l:bfwn)
elseif g:buffergator_viewport_split_policy =~ '[TtBb]'
if &lines > 20
execute("resize " . string(&lines-10))
let self.is_zoomed = 1
endif
endif
endif
endfunction
" functions to be implemented by derived classes
function! l:catalog_viewer.update_buffers_info() dict
endfunction
function! l:catalog_viewer.open(...) dict
endfunction
function! l:catalog_viewer.setup_buffer_syntax() dict
endfunction
function! l:catalog_viewer.setup_buffer_keymaps() dict
endfunction
function! l:catalog_viewer.render_buffer() dict
endfunction
function! l:catalog_viewer.setup_buffer_statusline() dict
endfunction
function! l:catalog_viewer.append_line(text, jump_to_bufnum) dict
endfunction
return l:catalog_viewer
endfunction
" 1}}}
" BufferCatalogViewer {{{1
" ============================================================================
function! s:NewBufferCatalogViewer()
" initialize
let l:catalog_viewer = s:NewCatalogViewer("[[buffergator: buffers]]", "buffergator")
let l:catalog_viewer["calling_bufnum"] = -1
let l:catalog_viewer["buffers_catalog"] = {}
" Populates the buffer list
function! l:catalog_viewer.update_buffers_info() dict
let self.buffers_catalog = self.list_buffers()
return self.buffers_catalog
endfunction
" Opens the buffer for viewing, creating it if needed.
" First argument, if given, should be false if the buffers info is *not*
" to be repopulated; defaults to 1
" Second argument, if given, should be number of calling window.
function! l:catalog_viewer.open(...) dict
" populate data
if (a:0 == 0 || a:1 > 0)
call self.update_buffers_info()
endif
" store calling buffer
if (a:0 >= 2 && a:2)
let self.calling_bufnum = a:2
else
let self.calling_bufnum = bufnr("%")
endif
" get buffer number of the catalog view buffer, creating it if neccessary
if self.bufnum < 0 || !bufexists(self.bufnum)
" create and render a new buffer
call self.create_buffer()
else
" buffer exists: activate a viewport on it according to the
" spawning mode, re-rendering the buffer with the catalog if needed
call self.activate_viewport()
call self.render_buffer()
" if (a:0 > 0 && a:1) || b:buffergator_catalog_viewer != self
" call self.render_buffer()
" else
" " search for calling buffer number in jump map,
" " when found, go to that line
" endif
endif
endfunction
" Sets buffer syntax.
function! l:catalog_viewer.setup_buffer_syntax() dict
if has("syntax")
syn region BuffergatorModifiedFileLine start='^\[\s\{-}.\{-1,}\s\{-}\] + ' keepend oneline end='$'
syn region BuffergatorUnmodifiedFileLine start='^\[\s\{-}.\{-1,}\s\{-}\] ' keepend oneline end='$'
syn match BuffergatorModifiedFileSyntaxKey '^\zs\[\s\{-}.\{-1,}\s\{-}\]\ze' containedin=BuffergatorModifiedFileLine nextgroup=BuffergatorModifiedFilename
syn match BuffergatorUnmodifiedFileSyntaxKey '^\zs\[\s\{-}.\{-1,}\s\{-}\]\ze' containedin=BuffergatorUnmodifiedFileLine nextgroup=BuffergatorUnmodifiedFilename
syn match BuffergatorModifiedFilename ' + .\+$' containedin=BuffergatorModifiedFilenameEntry
syn match BuffergatorUnmodifiedFilename ' .\+$' containedin=BuffergatorUnmodifiedFileLine
highlight! link BuffergatorModifiedFileSyntaxKey LineNr
highlight! link BuffergatorUnmodifiedFileSyntaxKey LineNr
highlight! link BuffergatorModifiedFileFlag WarningMsg
highlight! link BuffergatorModifiedFilename WarningMsg
highlight! def BuffergatorCurrentEntry gui=reverse cterm=reverse term=reverse
endif
endfunction
" Sets buffer key maps.
function! l:catalog_viewer.setup_buffer_keymaps() dict
call self.disable_editing_keymaps()
if !exists("g:buffergator_use_new_keymap") || !g:buffergator_use_new_keymap
"""" Catalog management
noremap <buffer> <silent> cs :call b:buffergator_catalog_viewer.cycle_sort_regime()<CR>
noremap <buffer> <silent> cd :call b:buffergator_catalog_viewer.cycle_display_regime()<CR>
noremap <buffer> <silent> r :call b:buffergator_catalog_viewer.rebuild_catalog()<CR>
noremap <buffer> <silent> q :call b:buffergator_catalog_viewer.close(1)<CR>
noremap <buffer> <silent> d :<C-U>call b:buffergator_catalog_viewer.delete_target(0, 0)<CR>
noremap <buffer> <silent> D :<C-U>call b:buffergator_catalog_viewer.delete_target(0, 1)<CR>
noremap <buffer> <silent> x :<C-U>call b:buffergator_catalog_viewer.delete_target(1, 0)<CR>
noremap <buffer> <silent> X :<C-U>call b:buffergator_catalog_viewer.delete_target(1, 1)<CR>
""""" Selection: show target and switch focus
noremap <buffer> <silent> <CR> :<C-U>call b:buffergator_catalog_viewer.visit_target(!g:buffergator_autodismiss_on_select, 0, "")<CR>
noremap <buffer> <silent> o :<C-U>call b:buffergator_catalog_viewer.visit_target(!g:buffergator_autodismiss_on_select, 0, "")<CR>
" gryf: let's keep it stright: s should h split, v should vertical
" split
noremap <buffer> <silent> s :<C-U>call b:buffergator_catalog_viewer.visit_target(!g:buffergator_autodismiss_on_select, 0, "sb")<CR>
noremap <buffer> <silent> v :<C-U>call b:buffergator_catalog_viewer.visit_target(!g:buffergator_autodismiss_on_select, 0, "vert sb")<CR>
noremap <buffer> <silent> t :<C-U>call b:buffergator_catalog_viewer.visit_target(!g:buffergator_autodismiss_on_select, 0, "tab sb")<CR>
""""" Preview: show target , keeping focus on catalog
noremap <buffer> <silent> O :<C-U>call b:buffergator_catalog_viewer.visit_target(1, 1, "")<CR>
noremap <buffer> <silent> go :<C-U>call b:buffergator_catalog_viewer.visit_target(1, 1, "")<CR>
noremap <buffer> <silent> S :<C-U>call b:buffergator_catalog_viewer.visit_target(1, 1, "vert sb")<CR>
noremap <buffer> <silent> gs :<C-U>call b:buffergator_catalog_viewer.visit_target(1, 1, "vert sb")<CR>
noremap <buffer> <silent> I :<C-U>call b:buffergator_catalog_viewer.visit_target(1, 1, "sb")<CR>
noremap <buffer> <silent> gi :<C-U>call b:buffergator_catalog_viewer.visit_target(1, 1, "sb")<CR>
noremap <buffer> <silent> T :<C-U>call b:buffergator_catalog_viewer.visit_target(1, 1, "tab sb")<CR>
noremap <buffer> <silent> <SPACE> :<C-U>call b:buffergator_catalog_viewer.goto_index_entry("n", 1, 1)<CR>
noremap <buffer> <silent> <C-SPACE> :<C-U>call b:buffergator_catalog_viewer.goto_index_entry("p", 1, 1)<CR>
noremap <buffer> <silent> <C-@> :<C-U>call b:buffergator_catalog_viewer.goto_index_entry("p", 1, 1)<CR>
noremap <buffer> <silent> <C-N> :<C-U>call b:buffergator_catalog_viewer.goto_index_entry("n", 1, 1)<CR>
noremap <buffer> <silent> <C-P> :<C-U>call b:buffergator_catalog_viewer.goto_index_entry("p", 1, 1)<CR>
""""" Preview: go to existing window showing target
noremap <buffer> <silent> E :<C-U>call b:buffergator_catalog_viewer.visit_open_target(1, !g:buffergator_autodismiss_on_select, "")<CR>
noremap <buffer> <silent> eo :<C-U>call b:buffergator_catalog_viewer.visit_open_target(0, !g:buffergator_autodismiss_on_select, "")<CR>
noremap <buffer> <silent> es :<C-U>call b:buffergator_catalog_viewer.visit_open_target(0, !g:buffergator_autodismiss_on_select, "vert sb")<CR>
noremap <buffer> <silent> ei :<C-U>call b:buffergator_catalog_viewer.visit_open_target(0, !g:buffergator_autodismiss_on_select, "sb")<CR>
noremap <buffer> <silent> et :<C-U>call b:buffergator_catalog_viewer.visit_open_target(0, !g:buffergator_autodismiss_on_select, "tab sb")<CR>
else
"""" Catalog management
noremap <buffer> <silent> s :call b:buffergator_catalog_viewer.cycle_sort_regime()<CR>
noremap <buffer> <silent> i :call b:buffergator_catalog_viewer.cycle_display_regime()<CR>
noremap <buffer> <silent> u :call b:buffergator_catalog_viewer.rebuild_catalog()<CR>
noremap <buffer> <silent> q :call b:buffergator_catalog_viewer.close(1)<CR>
noremap <buffer> <silent> d :call b:buffergator_catalog_viewer.delete_target(0, 0)<CR>
noremap <buffer> <silent> D :call b:buffergator_catalog_viewer.delete_target(0, 1)<CR>
noremap <buffer> <silent> x :call b:buffergator_catalog_viewer.delete_target(1, 0)<CR>
noremap <buffer> <silent> X :call b:buffergator_catalog_viewer.delete_target(1, 1)<CR>
" open target
noremap <buffer> <silent> <CR> :call b:buffergator_catalog_viewer.visit_target(!g:buffergator_autodismiss_on_select, 0, "")<CR>
" show target line in other window, keeping catalog open and in focus
noremap <buffer> <silent> . :call b:buffergator_catalog_viewer.visit_target(1, 1, "")<CR>
noremap <buffer> <silent> po :call b:buffergator_catalog_viewer.visit_target(1, 1, "")<CR>
noremap <buffer> <silent> ps :call b:buffergator_catalog_viewer.visit_target(1, 1, "sb")<CR>
noremap <buffer> <silent> pv :call b:buffergator_catalog_viewer.visit_target(1, 1, "vert sb")<CR>
noremap <buffer> <silent> pt :call b:buffergator_catalog_viewer.visit_target(1, 1, "tab sb")<CR>
noremap <buffer> <silent> <SPACE> :<C-U>call b:buffergator_catalog_viewer.goto_index_entry("n", 1, 1)<CR>
noremap <buffer> <silent> <C-SPACE> :<C-U>call b:buffergator_catalog_viewer.goto_index_entry("p", 1, 1)<CR>
noremap <buffer> <silent> <C-@> :<C-U>call b:buffergator_catalog_viewer.goto_index_entry("p", 1, 1)<CR>
noremap <buffer> <silent> <C-N> :<C-U>call b:buffergator_catalog_viewer.goto_index_entry("n", 1, 1)<CR>
noremap <buffer> <silent> <C-P> :<C-U>call b:buffergator_catalog_viewer.goto_index_entry("p", 1, 1)<CR>
" go to target line in other window, keeping catalog open
noremap <buffer> <silent> o :call b:buffergator_catalog_viewer.visit_target(1, 0, "")<CR>
noremap <buffer> <silent> ws :call b:buffergator_catalog_viewer.visit_target(1, 0, "sb")<CR>
noremap <buffer> <silent> wv :call b:buffergator_catalog_viewer.visit_target(1, 0, "vert sb")<CR>
noremap <buffer> <silent> t :call b:buffergator_catalog_viewer.visit_target(1, 0, "tab sb")<CR>
" open target line in other window, closing catalog
noremap <buffer> <silent> O :call b:buffergator_catalog_viewer.visit_target(0, 0, "")<CR>
noremap <buffer> <silent> wS :call b:buffergator_catalog_viewer.visit_target(0, 0, "sb")<CR>
noremap <buffer> <silent> wV :call b:buffergator_catalog_viewer.visit_target(0, 0, "vert sb")<CR>
noremap <buffer> <silent> T :call b:buffergator_catalog_viewer.visit_target(0, 0, "tab sb")<CR>
endif
" other
noremap <buffer> <silent> A :call b:buffergator_catalog_viewer.toggle_zoom()<CR>
endfunction
" Populates the buffer with the catalog index.
function! l:catalog_viewer.render_buffer() dict
setlocal modifiable
call self.claim_buffer()
call self.clear_buffer()
call self.setup_buffer_syntax()
let self.jump_map = {}
let l:initial_line = 1
for l:bufinfo in self.buffers_catalog
if self.calling_bufnum == l:bufinfo.bufnum
let l:initial_line = line("$")
endif
let l:bufnum_str = s:_format_filled(l:bufinfo.bufnum, 3, 1, 0)
let l:line = "[" . l:bufnum_str . "] "
if l:bufinfo.is_modified
let l:line .= "+ "
else
let l:line .= " "
endif
if self.display_regime == "basename"
let l:line .= s:_format_align_left(l:bufinfo.basename, 30, " ")
let l:line .= l:bufinfo.parentdir
elseif self.display_regime == "filepath"
let l:line .= l:bufinfo.filepath
elseif self.display_regime == "bufname"
let l:line .= l:bufinfo.bufname
else
throw s:_buffergator_messenger.format_exception("Invalid display regime: '" . self.display_regime . "'")
endif
call self.append_line(l:line, l:bufinfo.bufnum)
endfor
let b:buffergator_last_render_time = localtime()
try
" remove extra last line
execute('normal! GV"_X')
catch //
endtry
setlocal nomodifiable
call cursor(l:initial_line, 1)
" call self.goto_index_entry("n", 0, 1)
endfunction
" Visits the specified buffer in the previous window, if it is already
" visible there. If not, then it looks for the first window with the
" buffer showing and visits it there. If no windows are showing the
" buffer, ... ?
function! l:catalog_viewer.visit_buffer(bufnum, split_cmd) dict
" acquire window
let l:split_cmd = self.acquire_viewport(a:split_cmd)
" switch to buffer in acquired window
let l:old_switch_buf = &switchbuf
if empty(l:split_cmd)
" explicit split command not given: switch to buffer in current
" window
let &switchbuf="useopen"
execute("silent buffer " . a:bufnum)
else
" explcit split command given: split current window
let &switchbuf="split"
execute("silent keepalt keepjumps " . l:split_cmd . " " . a:bufnum)
endif
let &switchbuf=l:old_switch_buf
endfunction
function! l:catalog_viewer.get_target_bufnum(cmd_count) dict
if a:cmd_count == 0
let l:cur_line = line(".")
if !has_key(l:self.jump_map, l:cur_line)
call s:_buffergator_messenger.send_info("Not a valid navigation line")
return -1
endif
let [l:jump_to_bufnum] = self.jump_map[l:cur_line].target
return l:jump_to_bufnum
else
let l:jump_to_bufnum = a:cmd_count
if bufnr(l:jump_to_bufnum) == -1
call s:_buffergator_messenger.send_info("Not a valid buffer number: " . string(l:jump_to_bufnum) )
return -1
endif
for lnum in range(1, line("$"))
if self.jump_map[lnum].target[0] == l:jump_to_bufnum
call cursor(lnum, 1)
return l:jump_to_bufnum
endif
endfor
call s:_buffergator_messenger.send_info("Not a listed buffer number: " . string(l:jump_to_bufnum) )
return -1
endif
endfunction
" Go to the selected buffer.
function! l:catalog_viewer.visit_target(keep_catalog, refocus_catalog, split_cmd) dict range
let l:jump_to_bufnum = self.get_target_bufnum(v:count)
if l:jump_to_bufnum == -1
return 0
endif
let l:cur_tab_num = tabpagenr()
if !a:keep_catalog
call self.close(0)
endif
call self.visit_buffer(l:jump_to_bufnum, a:split_cmd)
if a:keep_catalog && a:refocus_catalog
execute("tabnext " . l:cur_tab_num)
execute(bufwinnr(self.bufnum) . "wincmd w")
endif
call s:_buffergator_messenger.send_info(expand(bufname(l:jump_to_bufnum)))
endfunction
" Go to the selected buffer, preferentially using a window that already is
" showing it; if not, create a window using split_cmd
function! l:catalog_viewer.visit_open_target(unconditional, keep_catalog, split_cmd) dict range
let l:jump_to_bufnum = self.get_target_bufnum(v:count)
if l:jump_to_bufnum == -1
return 0
endif
let wnr = bufwinnr(l:jump_to_bufnum)
if wnr != -1
execute(wnr . "wincmd w")
if !a:keep_catalog
call self.close(0)
endif
return
endif
let l:cur_tab_num = tabpagenr()
for tabnum in range(1, tabpagenr('$'))
execute("tabnext " . tabnum)
let wnr = bufwinnr(l:jump_to_bufnum)
if wnr != -1
execute(wnr . "wincmd w")
if !a:keep_catalog
call self.close(0)
endif
return
endif
endfor
execute("tabnext " . l:cur_tab_num)
if !a:unconditional
call self.visit_target(a:keep_catalog, 0, a:split_cmd)
endif
endfunction
function! l:catalog_viewer.delete_target(wipe, force) dict range
let l:bufnum_to_delete = self.get_target_bufnum(v:count)
if l:bufnum_to_delete == -1
return 0
endif
if !bufexists(l:bufnum_to_delete)
call s:_buffergator_messenger.send_info("Not a valid or existing buffer")
return 0
endif
if a:wipe && a:force
let l:operation_desc = "unconditionally wipe"
let l:cmd = "bw!"
elseif a:wipe && !a:force
let l:operation_desc = "wipe"
let l:cmd = "bw"
elseif !a:wipe && a:force
let l:operation_desc = "unconditionally delete"
let l:cmd = "bd!"
elseif !a:wipe && !a:force
let l:operation_desc = "delete"
let l:cmd = "bd"
endif
" store current window number
let l:cur_win_num = winnr()
" find alternate buffer to switch to
let l:alternate_buffer = -1
for abufnum in range(l:bufnum_to_delete, 1, -1)
if bufexists(abufnum) && buflisted(abufnum) && abufnum != l:bufnum_to_delete
let l:alternate_buffer = abufnum
break
endif
endfor
if l:alternate_buffer == -1 && bufnr("$") > l:bufnum_to_delete
for abufnum in range(l:bufnum_to_delete+1, bufnr("$"))
if bufexists(abufnum) && buflisted(abufnum) && abufnum != l:bufnum_to_delete
let l:alternate_buffer = abufnum
break
endif
endfor
endif
if l:alternate_buffer == -1
call s:_buffergator_messenger.send_warning("Cowardly refusing to delete last listed buffer")
return 0
endif
let l:changed_win_bufs = []
for winnum in range(1, winnr('$'))
let wbufnum = winbufnr(winnum)
if wbufnum == l:bufnum_to_delete
call add(l:changed_win_bufs, winnum)
execute(winnum . "wincmd w")
execute("silent keepalt keepjumps buffer " . l:alternate_buffer)
endif
endfor
let l:bufname = expand(bufname(l:bufnum_to_delete))
try
execute(l:cmd . string(l:bufnum_to_delete))
call self.open(1, l:alternate_buffer)
let l:message = l:bufname . " " . l:operation_desc . "d"
call s:_buffergator_messenger.send_info(l:message)
catch /E89/
for winnum in l:changed_win_bufs
execute(winnum . "wincmd w")
execute("silent keepalt keepjumps buffer " . l:bufnum_to_delete)
endfor
execute(l:cur_win_num . "wincmd w")
let l:message = 'Failed to ' . l:operation_desc . ' "' . l:bufname . '" because it is modified; use unconditional version of this command to force operation'
call s:_buffergator_messenger.send_error(l:message)
catch //
for winnum in l:changed_win_bufs
execute(winnum . "wincmd w")
execute("silent keepalt keepjumps buffer " . l:bufnum_to_delete)
endfor
execute(l:cur_win_num . "wincmd w")
let l:message = 'Failed to ' . l:operation_desc . ' "' . l:bufname . '"'
call s:_buffergator_messenger.send_error(l:message)
endtry
endfunction
" Finds next line with occurrence of a rendered index
function! l:catalog_viewer.goto_index_entry(direction, visit_target, refocus_catalog) dict range
if v:count > 0
let l:target_bufnum = v:count
if bufnr(l:target_bufnum) == -1
call s:_buffergator_messenger.send_info("Not a valid buffer number: " . string(l:target_bufnum) )
return -1
endif
let l:ok = 0
for lnum in range(1, line("$"))
if self.jump_map[lnum].target[0] == l:target_bufnum
call cursor(lnum, 1)
let l:ok = 1
break
endif
endfor
if !l:ok
call s:_buffergator_messenger.send_info("Not a listed buffer number: " . string(l:target_bufnum) )
return -1
endif
else
let l:ok = self.goto_pattern("^\[", a:direction)
execute("normal! zz")
endif
if l:ok && a:visit_target
call self.visit_target(1, a:refocus_catalog, "")
endif
endfunction
" Sets buffer status line.
function! l:catalog_viewer.setup_buffer_statusline() dict
setlocal statusline=%{BuffergatorBuffersStatusLine()}
endfunction
" Appends a line to the buffer and registers it in the line log.
function! l:catalog_viewer.append_line(text, jump_to_bufnum) dict
let l:line_map = {
\ "target" : [a:jump_to_bufnum],
\ }
if a:0 > 0
call extend(l:line_map, a:1)
endif
let self.jump_map[line("$")] = l:line_map
call append(line("$")-1, a:text)
endfunction
" return object
return l:catalog_viewer
endfunction
" 1}}}
" TabCatalogViewer {{{1
" ============================================================================
function! s:NewTabCatalogViewer()
" initialize
let l:catalog_viewer = s:NewCatalogViewer("[[buffergator: tabs]]", "buffergator")
let l:catalog_viewer["tab_catalog"] = []
" Opens the buffer for viewing, creating it if needed.
" First argument, if given, should be false if the buffers info is *not*
" to be repopulated; defaults to 1
function! l:catalog_viewer.open(...) dict
" populate data
if (a:0 == 0 || a:1 > 0)
call self.update_buffers_info()
endif
" get buffer number of the catalog view buffer, creating it if neccessary
if self.bufnum < 0 || !bufexists(self.bufnum)
" create and render a new buffer
call self.create_buffer()
else
" buffer exists: activate a viewport on it according to the
" spawning mode, re-rendering the buffer with the catalog if needed
call self.activate_viewport()
call self.render_buffer()
endif
endfunction
" Populates the buffer list
function! l:catalog_viewer.update_buffers_info() dict
let self.tab_catalog = []
for tabnum in range(1, tabpagenr('$'))
call add(self.tab_catalog, tabpagebuflist(tabnum))
endfor
return self.tab_catalog
endfunction
" Populates the buffer with the catalog index.
function! l:catalog_viewer.render_buffer() dict
setlocal modifiable
let l:cur_tab_num = tabpagenr()
call self.claim_buffer()
call self.clear_buffer()
call self.setup_buffer_syntax()
let self.jump_map = {}
let l:initial_line = 1
for l:tidx in range(len(self.tab_catalog))
let l:tabinfo = self.tab_catalog[tidx]
if l:cur_tab_num - 1 == l:tidx
let l:initial_line = line("$")
endif
" let l:tabfield = "==== Tab Page [" . string(l:tidx+1) . "] ===="
let l:tabfield = "TAB PAGE " . string(l:tidx+1) . ":"
call self.append_line(l:tabfield, l:tidx+1, 1)
for widx in range(len(l:tabinfo))
let l:tabbufnum = l:tabinfo[widx]
let l:tabbufname = bufname(l:tabbufnum)
let subline = "[" . s:_format_filled(l:tabbufnum, 3, 1, 0) . "] "
if getbufvar(l:tabbufnum, "&mod") == 1
let subline .= "+ "
else
let subline .= " "
endif
if self.display_regime == "basename"
let l:subline .= s:_format_align_left(fnamemodify(l:tabbufname, ":t"), 30, " ")
let l:subline .= fnamemodify(l:tabbufname, ":p:h")
elseif self.display_regime == "filepath"
let l:subline .= fnamemodify(l:tabbufname, ":p")
elseif self.display_regime == "bufname"
let l:subline .= l:tabbufname
else
throw s:_buffergator_messenger.format_exception("Invalid display regime: '" . self.display_regime . "'")
endif
call self.append_line(l:subline, l:tidx+1, l:widx+1)
endfor
endfor
let b:buffergator_last_render_time = localtime()
try
" remove extra last line
execute('normal! GV"_X')
catch //
endtry
setlocal nomodifiable
call cursor(l:initial_line, 1)
" call self.goto_index_entry("n", 0, 1)
endfunction
function! l:catalog_viewer.setup_buffer_syntax() dict
if has("syntax")
syn match BuffergatorTabPageLine '^TAB PAGE \d\+\:$'
" syn match BuffergatorTabPageLineStart '^==== Tab Page \[' nextgroup=BuffergatorTabPageNumber
" syn match BuffergatorTabPageNumber '\d\+' nextgroup=BuffergatorTabPageLineEnd
" syn match BuffergatorTabPageLineEnd '\] ====$'
syn region BuffergatorModifiedFileLine start='^\[\s\{-}.\{-1,}\s\{-}\] + ' keepend oneline end='$'
syn region BuffergatorUnmodifiedFileLine start='^\[\s\{-}.\{-1,}\s\{-}\] ' keepend oneline end='$'
syn match BuffergatorModifiedFileSyntaxKey '^\zs\[\s\{-}.\{-1,}\s\{-}\]\ze' containedin=BuffergatorModifiedFileLine nextgroup=BuffergatorModifiedFilename
syn match BuffergatorUnmodifiedFileSyntaxKey '^\zs\[\s\{-}.\{-1,}\s\{-}\]\ze' containedin=BuffergatorUnmodifiedFileLine nextgroup=BuffergatorUnmodifiedFilename
syn match BuffergatorModifiedFilename ' + .\+$' containedin=BuffergatorModifiedFilenameEntry
syn match BuffergatorUnmodifiedFilename ' .\+$' containedin=BuffergatorUnmodifiedFileLine
highlight! link BuffergatorModifiedFileSyntaxKey LineNr
highlight! link BuffergatorUnmodifiedFileSyntaxKey LineNr
highlight! link BuffergatorModifiedFileFlag WarningMsg
highlight! link BuffergatorModifiedFilename WarningMsg
highlight! link BuffergatorTabPageLine Title
" highlight! link BufergatorModifiedFilename NonText
" highlight! link BufergatorUnmodifiedFilename NonText
" highlight! link BuffergatorTabPageLineStart Title
" highlight! link BuffergatorTabPageNumber Special
" highlight! link BuffergatorTabPageLineEnd Title
highlight! def BuffergatorCurrentEntry gui=reverse cterm=reverse term=reverse
endif
endfunction
function! l:catalog_viewer.setup_buffer_keymaps() dict
call self.disable_editing_keymaps()
noremap <buffer> <silent> cd :call b:buffergator_catalog_viewer.cycle_display_regime()<CR>
noremap <buffer> <silent> r :call b:buffergator_catalog_viewer.rebuild_catalog()<CR>
noremap <buffer> <silent> q :call b:buffergator_catalog_viewer.close(1)<CR>
noremap <buffer> <silent> <CR> :call b:buffergator_catalog_viewer.visit_target()<CR>
noremap <buffer> <silent> o :call b:buffergator_catalog_viewer.visit_target()<CR>
noremap <buffer> <silent> <SPACE> :<C-U>call b:buffergator_catalog_viewer.goto_index_entry("n")<CR>
noremap <buffer> <silent> <C-SPACE> :<C-U>call b:buffergator_catalog_viewer.goto_index_entry("p")<CR>
noremap <buffer> <silent> <C-@> :<C-U>call b:buffergator_catalog_viewer.goto_index_entry("p")<CR>
noremap <buffer> <silent> <C-N> :<C-U>call b:buffergator_catalog_viewer.goto_win_entry("n")<CR>
noremap <buffer> <silent> <C-P> :<C-U>call b:buffergator_catalog_viewer.goto_win_entry("p")<CR>
noremap <buffer> <silent> A :call b:buffergator_catalog_viewer.toggle_zoom()<CR>
endfunction
" Appends a line to the buffer and registers it in the line log.
function! l:catalog_viewer.append_line(text, jump_to_tabnum, jump_to_winnum) dict
let l:line_map = {
\ "target" : [a:jump_to_tabnum, a:jump_to_winnum],
\ }
if a:0 > 0
call extend(l:line_map, a:1)
endif
let self.jump_map[line("$")] = l:line_map
call append(line("$")-1, a:text)
endfunction
function! l:catalog_viewer.goto_index_entry(direction) dict
let l:ok = self.goto_pattern("^T", a:direction)
execute("normal! zz")
" if l:ok && a:visit_target
" call self.visit_target(1, a:refocus_catalog, "")
" endif
endfunction
function! l:catalog_viewer.goto_win_entry(direction) dict
let l:ok = self.goto_pattern('^\[', a:direction)
execute("normal! zz")
endfunction
" Go to the selected buffer.
function! l:catalog_viewer.visit_target() dict
let l:cur_line = line(".")
if !has_key(l:self.jump_map, l:cur_line)
call s:_buffergator_messenger.send_info("Not a valid navigation line")
return 0
endif
let [l:jump_to_tabnum, l:jump_to_winnum] = self.jump_map[l:cur_line].target
call self.close(0)
execute("tabnext " . l:jump_to_tabnum)
execute(l:jump_to_winnum . "wincmd w")
" call s:_buffergator_messenger.send_info(expand(bufname(l:jump_to_bufnum)))
endfunction
function! l:catalog_viewer.setup_buffer_statusline() dict
setlocal statusline=%{BuffergatorTabsStatusLine()}
endfunction
" return object
return l:catalog_viewer
endfunction
" 1}}}
" Global Functions {{{1
" ==============================================================================
function! BuffergatorBuffersStatusLine()
let l:line = line(".")
let l:status_line = "[[buffergator]]"
if has_key(b:buffergator_catalog_viewer.jump_map, l:line)
let l:status_line .= " Buffer " . string(l:line) . " of " . string(len(b:buffergator_catalog_viewer.buffers_catalog))
endif
return l:status_line
endfunction
function! BuffergatorTabsStatusLine()
let l:status_line = "[[buffergator]]"
let l:line = line(".")
if has_key(b:buffergator_catalog_viewer.jump_map, l:line)
let l:status_line .= " Tab Page: " . b:buffergator_catalog_viewer.jump_map[l:line].target[0]
let l:status_line .= ", Window: " . b:buffergator_catalog_viewer.jump_map[l:line].target[1]
endif
return l:status_line
endfunction
" 1}}}
" Global Initialization {{{1
" ==============================================================================
if exists("s:_buffergator_messenger")
unlet s:_buffergator_messenger
endif
let s:_buffergator_messenger = s:NewMessenger("")
let s:_catalog_viewer = s:NewBufferCatalogViewer()
let s:_tab_catalog_viewer = s:NewTabCatalogViewer()
" Autocommands that update the most recenly used buffers
autocmd BufRead * call s:_update_mru(expand('<abuf>'))
autocmd BufNewFile * call s:_update_mru(expand('<abuf>'))
autocmd BufWritePost * call s:_update_mru(expand('<abuf>'))
" 1}}}
" Functions Supporting User Commands {{{1
" ==============================================================================
function! s:OpenBuffergator()
call s:_tab_catalog_viewer.close(1)
call s:_catalog_viewer.open()
endfunction
function! s:OpenBuffergatorTabs()
call s:_catalog_viewer.close(1)
call s:_tab_catalog_viewer.open(1)
endfunction
function! s:CloseBuffergator()
call s:_catalog_viewer.close(1)
call s:_tab_catalog_viewer.close(1)
endfunction
function! s:ToggleBuffergator()
call s:_tab_catalog_viewer.close(1)
call s:_catalog_viewer.toggle()
endfunction
function! s:ToggleBuffergatorTabs()
call s:_catalog_viewer.close(1)
call s:_tab_catalog_viewer.toggle()
endfunction
" 1}}}
" Public Command and Key Maps {{{1
" ==============================================================================
command! BuffergatorToggle :call <SID>ToggleBuffergator()
command! BuffergatorClose :call <SID>CloseBuffergator()
command! BuffergatorOpen :call <SID>OpenBuffergator()
command! BuffergatorTabsToggle :call <SID>ToggleBuffergatorTabs()
command! BuffergatorTabsOpen :call <SID>OpenBuffergatorTabs()
command! BuffergatorTabsClose :call <SID>CloseBuffergatorTabs()
if !exists('g:buffergator_suppress_keymaps') || !g:buffergator_suppress_keymaps
" nnoremap <silent> <Leader><Leader> :BuffergatorToggle<CR>
nnoremap <silent> <Leader>b :BuffergatorOpen<CR>
nnoremap <silent> <Leader>B :BuffergatorClose<CR>
nnoremap <silent> <Leader>t :BuffergatorTabsOpen<CR>
nnoremap <silent> <Leader>T :BuffergatorTabsClose<CR>
endif
" 1}}}
" Restore State {{{1
" ============================================================================
" restore options
let &cpo = s:save_cpo
" 1}}}
" vim:foldlevel=4: