"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" "" 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 "" 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\+\)\(.....\) "\(.*\)"') 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('', ) augroup BuffergatorCatalogViewer au! autocmd CursorHold,CursorHoldI,CursorMoved,CursorMovedI,BufEnter,BufLeave call b:buffergator_catalog_viewer.highlight_current_line() autocmd BufLeave 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 " . key . " " 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 cs :call b:buffergator_catalog_viewer.cycle_sort_regime() noremap cd :call b:buffergator_catalog_viewer.cycle_display_regime() noremap r :call b:buffergator_catalog_viewer.rebuild_catalog() noremap q :call b:buffergator_catalog_viewer.close(1) noremap d :call b:buffergator_catalog_viewer.delete_target(0, 0) noremap D :call b:buffergator_catalog_viewer.delete_target(0, 1) noremap x :call b:buffergator_catalog_viewer.delete_target(1, 0) noremap X :call b:buffergator_catalog_viewer.delete_target(1, 1) """"" Selection: show target and switch focus noremap :call b:buffergator_catalog_viewer.visit_target(!g:buffergator_autodismiss_on_select, 0, "") noremap o :call b:buffergator_catalog_viewer.visit_target(!g:buffergator_autodismiss_on_select, 0, "") " gryf: let's keep it stright: s should split, v should vsplit noremap s :call b:buffergator_catalog_viewer.visit_target(!g:buffergator_autodismiss_on_select, 0, "sb") noremap v :call b:buffergator_catalog_viewer.visit_target(!g:buffergator_autodismiss_on_select, 0, "vert sb") noremap t :call b:buffergator_catalog_viewer.visit_target(!g:buffergator_autodismiss_on_select, 0, "tab sb") """"" Preview: show target , keeping focus on catalog " gryf: also here, I want to have consistent shortcuts noremap O :call b:buffergator_catalog_viewer.visit_target(1, 1, "") noremap go :call b:buffergator_catalog_viewer.visit_target(1, 1, "") noremap S :call b:buffergator_catalog_viewer.visit_target(1, 1, "sb") noremap gs :call b:buffergator_catalog_viewer.visit_target(1, 1, "sb") noremap V :call b:buffergator_catalog_viewer.visit_target(1, 1, "vert sb") noremap gv :call b:buffergator_catalog_viewer.visit_target(1, 1, "vert sb") noremap T :call b:buffergator_catalog_viewer.visit_target(1, 1, "tab sb") noremap :call b:buffergator_catalog_viewer.goto_index_entry("n", 1, 1) noremap :call b:buffergator_catalog_viewer.goto_index_entry("p", 1, 1) noremap :call b:buffergator_catalog_viewer.goto_index_entry("p", 1, 1) noremap :call b:buffergator_catalog_viewer.goto_index_entry("n", 1, 1) noremap :call b:buffergator_catalog_viewer.goto_index_entry("p", 1, 1) """"" Preview: go to existing window showing target noremap E :call b:buffergator_catalog_viewer.visit_open_target(1, !g:buffergator_autodismiss_on_select, "") noremap eo :call b:buffergator_catalog_viewer.visit_open_target(0, !g:buffergator_autodismiss_on_select, "") noremap es :call b:buffergator_catalog_viewer.visit_open_target(0, !g:buffergator_autodismiss_on_select, "vert sb") noremap ei :call b:buffergator_catalog_viewer.visit_open_target(0, !g:buffergator_autodismiss_on_select, "sb") noremap et :call b:buffergator_catalog_viewer.visit_open_target(0, !g:buffergator_autodismiss_on_select, "tab sb") else """" Catalog management noremap s :call b:buffergator_catalog_viewer.cycle_sort_regime() noremap i :call b:buffergator_catalog_viewer.cycle_display_regime() noremap u :call b:buffergator_catalog_viewer.rebuild_catalog() noremap q :call b:buffergator_catalog_viewer.close(1) noremap d :call b:buffergator_catalog_viewer.delete_target(0, 0) noremap D :call b:buffergator_catalog_viewer.delete_target(0, 1) noremap x :call b:buffergator_catalog_viewer.delete_target(1, 0) noremap X :call b:buffergator_catalog_viewer.delete_target(1, 1) " open target noremap :call b:buffergator_catalog_viewer.visit_target(!g:buffergator_autodismiss_on_select, 0, "") " show target line in other window, keeping catalog open and in focus noremap . :call b:buffergator_catalog_viewer.visit_target(1, 1, "") noremap po :call b:buffergator_catalog_viewer.visit_target(1, 1, "") noremap ps :call b:buffergator_catalog_viewer.visit_target(1, 1, "sb") noremap pv :call b:buffergator_catalog_viewer.visit_target(1, 1, "vert sb") noremap pt :call b:buffergator_catalog_viewer.visit_target(1, 1, "tab sb") noremap :call b:buffergator_catalog_viewer.goto_index_entry("n", 1, 1) noremap :call b:buffergator_catalog_viewer.goto_index_entry("p", 1, 1) noremap :call b:buffergator_catalog_viewer.goto_index_entry("p", 1, 1) noremap :call b:buffergator_catalog_viewer.goto_index_entry("n", 1, 1) noremap :call b:buffergator_catalog_viewer.goto_index_entry("p", 1, 1) " go to target line in other window, keeping catalog open noremap o :call b:buffergator_catalog_viewer.visit_target(1, 0, "") noremap ws :call b:buffergator_catalog_viewer.visit_target(1, 0, "sb") noremap wv :call b:buffergator_catalog_viewer.visit_target(1, 0, "vert sb") noremap t :call b:buffergator_catalog_viewer.visit_target(1, 0, "tab sb") " open target line in other window, closing catalog noremap O :call b:buffergator_catalog_viewer.visit_target(0, 0, "") noremap wS :call b:buffergator_catalog_viewer.visit_target(0, 0, "sb") noremap wV :call b:buffergator_catalog_viewer.visit_target(0, 0, "vert sb") noremap T :call b:buffergator_catalog_viewer.visit_target(0, 0, "tab sb") endif " other noremap A :call b:buffergator_catalog_viewer.toggle_zoom() 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 cd :call b:buffergator_catalog_viewer.cycle_display_regime() noremap r :call b:buffergator_catalog_viewer.rebuild_catalog() noremap q :call b:buffergator_catalog_viewer.close(1) noremap :call b:buffergator_catalog_viewer.visit_target() noremap o :call b:buffergator_catalog_viewer.visit_target() noremap :call b:buffergator_catalog_viewer.goto_index_entry("n") noremap :call b:buffergator_catalog_viewer.goto_index_entry("p") noremap :call b:buffergator_catalog_viewer.goto_index_entry("p") noremap :call b:buffergator_catalog_viewer.goto_win_entry("n") noremap :call b:buffergator_catalog_viewer.goto_win_entry("p") noremap A :call b:buffergator_catalog_viewer.toggle_zoom() 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('')) autocmd BufNewFile * call s:_update_mru(expand('')) autocmd BufWritePost * call s:_update_mru(expand('')) " 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 ToggleBuffergator() command! BuffergatorClose :call CloseBuffergator() command! BuffergatorOpen :call OpenBuffergator() command! BuffergatorTabsToggle :call ToggleBuffergatorTabs() command! BuffergatorTabsOpen :call OpenBuffergatorTabs() command! BuffergatorTabsClose :call CloseBuffergatorTabs() if !exists('g:buffergator_suppress_keymaps') || !g:buffergator_suppress_keymaps " nnoremap :BuffergatorToggle nnoremap b :BuffergatorOpen nnoremap B :BuffergatorClose nnoremap t :BuffergatorTabsOpen nnoremap T :BuffergatorTabsClose endif " 1}}} " Restore State {{{1 " ============================================================================ " restore options let &cpo = s:save_cpo " 1}}} " vim:foldlevel=4: