├── autoload └── buffersaurus.vim ├── doc └── buffersaurus.txt └── plugin └── buffersaurus.vim /autoload/buffersaurus.vim: -------------------------------------------------------------------------------- 1 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | 3 | "" Buffersaurus 4 | "" 5 | "" Vim document buffer indexing and navigation utility 6 | "" 7 | "" Copyright 2010 Jeet Sukumaran. 8 | "" 9 | "" This program is free software; you can redistribute it and/or modify 10 | "" it under the terms of the GNU General Public License as published by 11 | "" the Free Software Foundation; either version 3 of the License, or 12 | "" (at your option) any later version. 13 | "" 14 | "" This program is distributed in the hope that it will be useful, 15 | "" but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | "" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | "" GNU General Public License 18 | "" for more details. 19 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 20 | 21 | " Compatibility Guard {{{1 22 | " ============================================================================ 23 | let g:did_buffersaurus = 1 24 | " avoid line continuation issues (see ':help user_41.txt') 25 | let s:save_cpo = &cpo 26 | set cpo&vim 27 | " 1}}} 28 | 29 | " Global Plugin Options {{{1 30 | " ============================================================================= 31 | if !exists("g:buffersaurus_autodismiss_on_select") 32 | let g:buffersaurus_autodismiss_on_select = 1 33 | endif 34 | if !exists("g:buffersaurus_sort_regime") 35 | let g:buffersaurus_sort_regime = 'fl' 36 | endif 37 | if !exists("g:buffersaurus_show_context") 38 | let g:buffersaurus_show_context = 0 39 | endif 40 | if !exists("g:buffersaurus_context_size") 41 | let g:buffersaurus_context_size = [4, 4] 42 | endif 43 | if !exists("g:buffersaurus_viewport_split_policy") 44 | let g:buffersaurus_viewport_split_policy = "B" 45 | endif 46 | if !exists("g:buffersaurus_move_wrap") 47 | let g:buffersaurus_move_wrap = 1 48 | endif 49 | if !exists("g:buffersaurus_flash_jumped_line") 50 | let g:buffersaurus_flash_jumped_line = 1 51 | endif 52 | " 1}}} 53 | 54 | " Script Data and Variables {{{1 55 | " ============================================================================= 56 | 57 | " Display column sizes {{{2 58 | " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 59 | " Display columns. 60 | let s:buffersaurus_lnum_field_width = 6 61 | let s:buffersaurus_entry_label_field_width = 4 62 | " TODO: populate the following based on user setting, as well as allow 63 | " abstraction from the actual Vim command (e.g., option "top" => "zt") 64 | let s:buffersaurus_post_move_cmd = "normal! zz" 65 | 66 | " 2}}} 67 | 68 | " Split Modes {{{2 69 | " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 70 | " Split modes are indicated by a single letter. Upper-case letters indicate 71 | " that the SCREEN (i.e., the entire application "window" from the operating 72 | " system's perspective) should be split, while lower-case letters indicate 73 | " that the VIEWPORT (i.e., the "window" in Vim's terminology, referring to the 74 | " various subpanels or splits within Vim) should be split. 75 | " Split policy indicators and their corresponding modes are: 76 | " ``/`d`/`D' : use default splitting mode 77 | " `n`/`N` : NO split, use existing window. 78 | " `L` : split SCREEN vertically, with new split on the left 79 | " `l` : split VIEWPORT vertically, with new split on the left 80 | " `R` : split SCREEN vertically, with new split on the right 81 | " `r` : split VIEWPORT vertically, with new split on the right 82 | " `T` : split SCREEN horizontally, with new split on the top 83 | " `t` : split VIEWPORT horizontally, with new split on the top 84 | " `B` : split SCREEN horizontally, with new split on the bottom 85 | " `b` : split VIEWPORT horizontally, with new split on the bottom 86 | let s:buffersaurus_viewport_split_modes = { 87 | \ "d" : "sp", 88 | \ "D" : "sp", 89 | \ "N" : "buffer", 90 | \ "n" : "buffer", 91 | \ "L" : "topleft vert sbuffer", 92 | \ "l" : "leftabove vert sbuffer", 93 | \ "R" : "botright vert sbuffer", 94 | \ "r" : "rightbelow vert sbuffer", 95 | \ "T" : "topleft sbuffer", 96 | \ "t" : "leftabove sbuffer", 97 | \ "B" : "botright sbuffer", 98 | \ "b" : "rightbelow sbuffer", 99 | \ } 100 | " 2}}} 101 | 102 | " Catalog Sort Regimes {{{2 103 | " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 104 | let s:buffersaurus_catalog_sort_regimes = ['fl', 'fa', 'a'] 105 | let s:buffersaurus_catalog_sort_regime_desc = { 106 | \ 'fl' : ["F(L#)", "by filepath, then by line number"], 107 | \ 'fa' : ["F(A-Z)", "by filepath, then by line text"], 108 | \ 'a' : ["A-Z", "by line text"], 109 | \ } 110 | " 2}}} 111 | 112 | " 1}}} 113 | 114 | " Utilities {{{1 115 | " ============================================================================== 116 | 117 | " Text Formatting {{{2 118 | " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 119 | function! s:Format_AlignLeft(text, width, fill_char) 120 | let l:fill = repeat(a:fill_char, a:width-len(a:text)) 121 | return a:text . l:fill 122 | endfunction 123 | 124 | function! s:Format_AlignRight(text, width, fill_char) 125 | let l:fill = repeat(a:fill_char, a:width-len(a:text)) 126 | return l:fill . a:text 127 | endfunction 128 | 129 | function! s:Format_Time(secs) 130 | if exists("*strftime") 131 | return strftime("%Y-%m-%d %H:%M:%S", a:secs) 132 | else 133 | return (localtime() - a:secs) . " secs ago" 134 | endif 135 | endfunction 136 | 137 | function! s:Format_EscapedFilename(file) 138 | if exists('*fnameescape') 139 | return fnameescape(a:file) 140 | else 141 | return escape(a:file," \t\n*?[{`$\\%#'\"|!<") 142 | endif 143 | endfunction 144 | 145 | " trunc: -1 = truncate left, 0 = no truncate, +1 = truncate right 146 | function! s:Format_Truncate(str, max_len, trunc) 147 | if len(a:str) > a:max_len 148 | if a:trunc > 0 149 | return strpart(a:str, a:max_len - 4) . " ..." 150 | elseif a:trunc < 0 151 | return '... ' . strpart(a:str, len(a:str) - a:max_len + 4) 152 | endif 153 | else 154 | return a:str 155 | endif 156 | endfunction 157 | 158 | " Pads/truncates text to fit a given width. 159 | " align: -1/0 = align left, 0 = no align, 1 = align right 160 | " trunc: -1 = truncate left, 0 = no truncate, +1 = truncate right 161 | function! s:Format_Fill(str, width, align, trunc) 162 | let l:prepped = a:str 163 | if a:trunc != 0 164 | let l:prepped = s:Format_Truncate(a:str, a:width, a:trunc) 165 | endif 166 | if len(l:prepped) < a:width 167 | if a:align > 0 168 | let l:prepped = s:Format_AlignRight(l:prepped, a:width, " ") 169 | elseif a:align < 0 170 | let l:prepped = s:Format_AlignLeft(l:prepped, a:width, " ") 171 | endif 172 | endif 173 | return l:prepped 174 | endfunction 175 | 176 | " 2}}} 177 | 178 | " Messaging {{{2 179 | " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 180 | 181 | function! s:NewMessenger(name) 182 | 183 | " allocate a new pseudo-object 184 | let messenger = {} 185 | let messenger["name"] = a:name 186 | if empty(a:name) 187 | let messenger["title"] = "buffersaurus" 188 | else 189 | let messenger["title"] = "buffersaurus (" . messenger["name"] . ")" 190 | endif 191 | 192 | function! messenger.format_message(leader, msg) dict 193 | return self.title . ": " . a:leader.a:msg 194 | endfunction 195 | 196 | function! messenger.format_exception( msg) dict 197 | return a:msg 198 | endfunction 199 | 200 | function! messenger.send_error(msg) dict 201 | redraw 202 | echohl ErrorMsg 203 | echomsg self.format_message("[ERROR] ", a:msg) 204 | echohl None 205 | endfunction 206 | 207 | function! messenger.send_warning(msg) dict 208 | redraw 209 | echohl WarningMsg 210 | echomsg self.format_message("[WARNING] ", a:msg) 211 | echohl None 212 | endfunction 213 | 214 | function! messenger.send_status(msg) dict 215 | redraw 216 | echohl None 217 | echomsg self.format_message("", a:msg) 218 | endfunction 219 | 220 | function! messenger.send_info(msg) dict 221 | redraw 222 | echohl None 223 | echo self.format_message("", a:msg) 224 | endfunction 225 | 226 | return messenger 227 | 228 | endfunction 229 | " 2}}} 230 | 231 | " 1}}} 232 | 233 | " BufferManager {{{1 234 | " ============================================================================== 235 | 236 | " Creates the script-wide buffer/window manager. 237 | function! s:NewBufferManager() 238 | 239 | " initialize 240 | let buffer_manager = {} 241 | 242 | " Returns a list of all existing buffer numbers, excluding unlisted ones 243 | " unless `include_unlisted` is non-empty. 244 | function! buffer_manager.get_buf_nums(include_unlisted) 245 | let l:buf_num_list = [] 246 | for l:idx in range(1, bufnr("$")) 247 | if bufexists(l:idx) && (empty(a:include_unlisted) || buflisted(l:idx)) 248 | call add(l:buf_num_list, l:idx) 249 | endif 250 | endfor 251 | return l:buf_num_list 252 | endfunction 253 | 254 | " Returns a list of all existing buffer names, excluding unlisted ones 255 | " unless `include_unlisted` is non-empty. 256 | function! buffer_manager.get_buf_names(include_unlisted, expand_modifiers) 257 | let l:buf_name_list = [] 258 | for l:idx in range(1, bufnr("$")) 259 | if bufexists(l:idx) && (empty(!a:include_unlisted) || buflisted(l:idx)) 260 | call add(l:buf_name_list, expand(bufname(l:idx).a:expand_modifiers)) 261 | endif 262 | endfor 263 | return l:buf_name_list 264 | endfunction 265 | 266 | " Searches for all windows that have a window-scoped variable `varname` 267 | " with value that matches the expression `expr`. Returns list of window 268 | " numbers that meet the criterion. 269 | function! buffer_manager.find_windows_with_var(varname, expr) 270 | let l:results = [] 271 | for l:wni in range(1, winnr("$")) 272 | let l:wvar = getwinvar(l:wni, "") 273 | if empty(a:varname) 274 | call add(l:results, l:wni) 275 | elseif has_key(l:wvar, a:varname) && l:wvar[a:varname] =~ a:expr 276 | call add(l:results, l:wni) 277 | endif 278 | endfor 279 | return l:results 280 | endfunction 281 | 282 | " Searches for all buffers that have a buffer-scoped variable `varname` 283 | " with value that matches the expression `expr`. Returns list of buffer 284 | " numbers that meet the criterion. 285 | function! buffer_manager.find_buffers_with_var(varname, expr) 286 | let l:results = [] 287 | for l:bni in range(1, bufnr("$")) 288 | if !bufexists(l:bni) 289 | continue 290 | endif 291 | let l:bvar = getbufvar(l:bni, "") 292 | if empty(a:varname) 293 | call add(l:results, l:bni) 294 | elseif has_key(l:bvar, a:varname) && l:bvar[a:varname] =~ a:expr 295 | call add(l:results, l:bni) 296 | endif 297 | endfor 298 | return l:results 299 | endfunction 300 | 301 | " Returns a dictionary with the buffer number as keys (if `key` is empty) 302 | " and the parsed information regarding each buffer as values. If `key` is 303 | " given (e.g. key='num'; key='name', key='filepath') then that field will 304 | " be used as the dictionary keys instead. 305 | function! buffer_manager.get_buffers_info(key) dict 306 | if empty(a:key) 307 | let l:key = "num" 308 | else 309 | let l:key = a:key 310 | endif 311 | redir => buffers_output 312 | execute('silent ls') 313 | redir END 314 | let l:buffers_info = {} 315 | let l:buffers_output_rows = split(l:buffers_output, "\n") 316 | for l:buffers_output_row in l:buffers_output_rows 317 | let l:parts = matchlist(l:buffers_output_row, '^\s*\(\d\+\)\(.....\) "\(.*\)"\s\+line \d\+$') 318 | let l:info = {} 319 | let l:info["num"] = l:parts[1] + 0 320 | if l:parts[2][0] == "u" 321 | let l:info["is_unlisted"] = 1 322 | let l:info["is_listed"] = 0 323 | else 324 | let l:info["is_unlisted"] = 0 325 | let l:info["is_listed"] = 1 326 | endif 327 | if l:parts[2][1] == "%" 328 | let l:info["is_current"] = 1 329 | let l:info["is_alternate"] = 0 330 | elseif l:parts[2][1] == "#" 331 | let l:info["is_current"] = 0 332 | let l:info["is_alternate"] = 1 333 | else 334 | let l:info["is_current"] = 0 335 | let l:info["is_alternate"] = 0 336 | endif 337 | if l:parts[2][2] == "a" 338 | let l:info["is_active"] = 1 339 | let l:info["is_loaded"] = 1 340 | let l:info["is_visible"] = 1 341 | elseif l:parts[2][2] == "h" 342 | let l:info["is_active"] = 0 343 | let l:info["is_loaded"] = 1 344 | let l:info["is_visible"] = 0 345 | else 346 | let l:info["is_active"] = 0 347 | let l:info["is_loaded"] = 0 348 | let l:info["is_visible"] = 0 349 | endif 350 | if l:parts[2][3] == "-" 351 | let l:info["is_modifiable"] = 0 352 | let l:info["is_readonly"] = 0 353 | elseif l:parts[2][3] == "=" 354 | let l:info["is_modifiable"] = 1 355 | let l:info["is_readonly"] = 1 356 | else 357 | let l:info["is_modifiable"] = 1 358 | let l:info["is_readonly"] = 0 359 | endif 360 | if l:parts[2][4] == "+" 361 | let l:info["is_modified"] = 1 362 | let l:info["is_readerror"] = 0 363 | elseif l:parts[2][4] == "x" 364 | let l:info["is_modified"] = 0 365 | let l:info["is_readerror"] = 0 366 | else 367 | let l:info["is_modified"] = 0 368 | let l:info["is_readerror"] = 0 369 | endif 370 | let l:info["name"] = parts[3] 371 | let l:info["filepath"] = fnamemodify(l:info["name"], ":p") 372 | if !has_key(l:info, l:key) 373 | throw s:_buffersaurus_messenger.format_exception("Invalid key requested: '" . l:key . "'") 374 | endif 375 | let l:buffers_info[l:info[l:key]] = l:info 376 | endfor 377 | return l:buffers_info 378 | endfunction 379 | 380 | " Returns split mode to use for a new Buffersaurus viewport. 381 | function! buffer_manager.get_split_mode() dict 382 | if has_key(s:buffersaurus_viewport_split_modes, g:buffersaurus_viewport_split_policy) 383 | return s:buffersaurus_viewport_split_modes[g:buffersaurus_viewport_split_policy] 384 | else 385 | call s:_buffersaurus_messenger.send_error("Unrecognized split mode specified by 'g:buffersaurus_viewport_split_policy': " . g:buffersaurus_viewport_split_policy) 386 | endif 387 | endfunction 388 | 389 | " Detect filetype. From the 'taglist' plugin. 390 | " Copyright (C) 2002-2007 Yegappan Lakshmanan 391 | function! buffer_manager.detect_filetype(fname) 392 | " Ignore the filetype autocommands 393 | let old_eventignore = &eventignore 394 | set eventignore=FileType 395 | " Save the 'filetype', as this will be changed temporarily 396 | let old_filetype = &filetype 397 | " Run the filetypedetect group of autocommands to determine 398 | " the filetype 399 | exe 'doautocmd filetypedetect BufRead ' . a:fname 400 | " Save the detected filetype 401 | let ftype = &filetype 402 | " Restore the previous state 403 | let &filetype = old_filetype 404 | let &eventignore = old_eventignore 405 | return ftype 406 | endfunction 407 | 408 | return buffer_manager 409 | endfunction 410 | 411 | " 1}}} 412 | 413 | " Indexer {{{1 414 | " ============================================================================= 415 | 416 | " create and return the an Indexer pseudo-object, which is a Catalog factory 417 | function! s:NewIndexer() 418 | 419 | " create/clear 420 | let indexer = {} 421 | 422 | " set up filetype vocabulary 423 | let indexer["filetype_term_map"] = { 424 | \ 'bib' : '^@\w\+\s*{\s*\zs\S\{-}\ze\s*,' 425 | \ , 'c' : '^[[:alnum:]#].*' 426 | \ , 'cpp' : '^[[:alnum:]#].*' 427 | \ , 'html' : '\(\|<\(html\|head\|body\|div\|script\|a\s\+name=\).\{-}>\|<.\{-}\\)' 428 | \ , 'java' : '^\s*\(\(package\|import\|private\|public\|protected\|void\|int\|boolean\)\s\+\|\u\).*' 429 | \ , 'javascript' : '^\(var\s\+.\{-}\|\s*\w\+\s*:\s*\S.\{-}[,{]\)\s*$' 430 | \ , 'perl' : '^\([$%@]\|\s*\(use\|sub\)\>\).*' 431 | \ , 'php' : '^\(\w\|\s*\(class\|function\|var\|require\w*\|include\w*\)\>\).*' 432 | \ , 'python' : '^\s*\(import\|class\|def\)\s\+[A-Za-z_]\i\+(.*' 433 | \ , 'ruby' : '\C^\(if\>\|\s*\(class\|module\|def\|require\|private\|public\|protected\|module_functon\|alias\|attr\(_reader\|_writer\|_accessor\)\?\)\>\|\s*[[:upper:]_]\+\s*=\).*' 434 | \ , 'scheme' : '^\s*(define.*' 435 | \ , 'sh' : '^\s*\(\(export\|function\|while\|case\|if\)\>\|\w\+\s*()\s*{\).*' 436 | \ , 'tcl' : '^\s*\(source\|proc\)\>.*' 437 | \ , 'tex' : '\C\\\(label\|\(sub\)*\(section\|paragraph\|part\)\)\>.*' 438 | \ , 'vim' : '\C^\(fu\%[nction]\|com\%[mand]\|if\|wh\%[ile]\)\>.*' 439 | \ } 440 | if exists("g:buffersaurus_filetype_term_map") 441 | " User-defined patterns have higher priority 442 | call extend(indexer["filetype_term_map"], g:buffersaurus_filetype_term_map, 'force') 443 | endif 444 | 445 | " set up element vocabulary 446 | let indexer["element_term_map"] = { 447 | \ 'PyClass' : '^\s*class\s\+[A-Za-z_]\i\+(.*' 448 | \ , 'PyDef' : '^\s*def\s\+[A-Za-z_]\i\+(.*' 449 | \ , 'VimFunction' : '^\C[:[:space:]]*fu\%[nction]\>!\=\s*\S\+\s*(' 450 | \ , 'VimMapping' : '^\C[:[:space:]]*[nvxsoilc]\=\(\%(nore\|un\)\=map\>\|mapclear\)\>' 451 | \ , 'VimCommand' : '^\C[:[:space:]]*com\%[mand]\>' 452 | \ , 'CppClass' : '^\s*\(\(public\|private\|protected\)\s*:\)\=\s*\(class\|struct\)\s\+\w\+\>\(\s*;\)\@!' 453 | \ , 'CppTypedef' : '^\s*\(\(public\|private\|protected\)\s*:\)\=\s*typedef\>' 454 | \ , 'CppEnum' : '^\s*\(\(public\|private\|protected\)\s*:\)\=\s*enum\>' 455 | \ , 'CppTemplate' : '^\s*template\($\|<\)' 456 | \ , 'CppPreproc' : '^#' 457 | \ } 458 | 459 | if exists("g:buffersaurus_element_term_map") 460 | " User-defined patterns have higher priority 461 | call extend(indexer["element_term_map"], g:buffersaurus_element_term_map, 'force') 462 | endif 463 | 464 | " Indexes all files given by the list `filepaths` for the regular 465 | " expression(s) defined in the element vocabulary for `term_id`. If 466 | " `term_id` is empty, the default filetype pattern is used. If 467 | " `filepaths` is empty, then all 468 | " listed buffers are indexed. 469 | function! indexer.index_terms(filepaths, term_id, sort_regime) dict 470 | let l:old_hidden = &hidden 471 | set hidden 472 | let l:worklist = self.ensure_buffers(a:filepaths) 473 | let l:desc = "Catalog of" 474 | if !empty(a:term_id) 475 | let l:desc .= "'" . a:term_id . "'" 476 | endif 477 | let l:desc .= " terms" 478 | if empty(a:filepaths) 479 | let l:desc .= " (in all buffers)" 480 | elseif len(a:filepaths) == 1 481 | let l:desc .= ' (in "' . expand(a:filepaths[0]) . '")' 482 | else 483 | let l:desc .= " (in multiple files)" 484 | endif 485 | let catalog = s:NewCatalog("term", l:desc, a:sort_regime) 486 | for buf_ref in l:worklist 487 | let l:pattern = self.get_buffer_term_pattern(buf_ref, a:term_id) 488 | call catalog.map_buffer(buf_ref, l:pattern) 489 | endfor 490 | let &hidden=l:old_hidden 491 | return catalog 492 | endfunction 493 | 494 | " Indexes all files given by the list `filepaths` for tags. 495 | function! indexer.index_tags(filepaths) dict 496 | let l:old_hidden = &hidden 497 | set hidden 498 | let l:worklist = self.ensure_buffers(a:filepaths) 499 | let l:desc = "Catalog of tags" 500 | if empty(a:filepaths) 501 | let l:desc .= " (in all buffers)" 502 | elseif len(a:filepaths) == 1 503 | let l:desc .= ' (in "' . a:filepaths[0] . '")' 504 | else 505 | let l:desc .= " (in multiple files)" 506 | endif 507 | let catalog = s:NewTagCatalog("tag", l:desc) 508 | for buf_ref in l:worklist 509 | call catalog.map_buffer(buf_ref) 510 | endfor 511 | let &hidden=l:old_hidden 512 | return catalog 513 | endfunction 514 | 515 | " Indexes all files given by the list `filepaths` for the regular 516 | " expression given by `pattern`. If `filepaths` is empty, then all 517 | " listed buffers are indexed. 518 | function! indexer.index_pattern(filepaths, pattern, sort_regime) dict 519 | let l:old_hidden = &hidden 520 | set hidden 521 | let l:worklist = self.ensure_buffers(a:filepaths) 522 | 523 | let l:desc = "Catalog of pattern '" . a:pattern . "'" 524 | if empty(a:filepaths) 525 | let l:desc .= " (in all buffers)" 526 | elseif len(a:filepaths) == 1 527 | let l:desc .= ' (in "' . a:filepaths[0] . '")' 528 | else 529 | let l:desc .= " (in multiple files)" 530 | endif 531 | let catalog = s:NewCatalog("pattern", l:desc, a:sort_regime) 532 | for buf_ref in l:worklist 533 | call catalog.map_buffer(buf_ref, a:pattern) 534 | endfor 535 | let &hidden=l:old_hidden 536 | return catalog 537 | endfunction 538 | 539 | " returns pattern to be used when indexing terms for a particular buffer 540 | function! indexer.get_buffer_term_pattern(buf_num, term_id) dict 541 | let l:pattern = "" 542 | if !empty(a:term_id) 543 | try 544 | let l:term_id_matches = filter(keys(self.element_term_map), 545 | \ "v:val =~ '" . a:term_id . ".*'") 546 | catch /E15:/ 547 | throw s:_buffersaurus_messenger.format_exception("Invalid name: '" . a:term_id . "': ".v:exception) 548 | endtry 549 | if len(l:term_id_matches) > 1 550 | throw s:_buffersaurus_messenger.format_exception("Multiple matches for index pattern name starting with '".a:term_id."': ".join(l:term_id_matches, ", ")) 551 | elseif len(l:term_id_matches) == 0 552 | throw s:_buffersaurus_messenger.format_exception("Index pattern with name '" . a:term_id . "' not found") 553 | end 554 | let l:pattern = self.element_term_map[l:term_id_matches[0]] 555 | else 556 | let l:pattern = get(self.filetype_term_map, getbufvar(a:buf_num, "&filetype"), "") 557 | if empty(l:pattern) 558 | let l:pattern = '^\w.*' 559 | endif 560 | endif 561 | return l:pattern 562 | endfunction 563 | 564 | " Given a list of buffer references, `buf_refs` this will ensure than 565 | " all the files/buffers are loaded and return a list of the buffer names. 566 | " If `buf_refs` is empty, then all listed buffers are loaded. 567 | function! indexer.ensure_buffers(buf_refs) 568 | let l:cur_pos = getpos(".") 569 | let l:cur_buf_num = bufnr("%") 570 | if empty(a:buf_refs) 571 | let l:req_buf_list = s:_buffersaurus_buffer_manager.get_buf_nums(0) 572 | else 573 | let l:req_buf_list = [] 574 | for l:buf_ref in a:buf_refs 575 | if type(l:buf_ref) == type(0) 576 | let l:buf_num = l:buf_ref 577 | else 578 | let l:buf_num = bufnr(l:buf_ref) 579 | endif 580 | call add(l:req_buf_list, l:buf_num) 581 | endfor 582 | endif 583 | let l:work_list = [] 584 | for l:buf_num in l:req_buf_list 585 | if !bufexists(l:buf_num) 586 | " throw s:_buffersaurus_messenger.format_exception('Buffer does not exist: "' . l:buf_num . '"') 587 | elseif !buflisted(l:buf_num) 588 | " call s:_buffersaurus_messenger.send_warning('Skipping unlisted buffer: [' . l:buf_num . '] "' . bufname(l:buf_num) . '"') 589 | elseif !empty(getbufvar(l:buf_num, "is_buffersaurus_buffer")) 590 | " call s:_buffersaurus_messenger.send_warning('Skipping buffersaurus buffer: [' . l:buf_num . '] "' . bufname(l:buf_num) . '"') 591 | else 592 | call add(l:work_list, l:buf_num) 593 | if !bufloaded(l:buf_num) 594 | execute("silent keepjumps keepalt buffer " . l:buf_num) 595 | endif 596 | endif 597 | endfor 598 | " execute("silent keepjumps keepalt e ".l:cur_buf_name) 599 | execute("silent keepjumps keepalt buffer ".l:cur_buf_num) 600 | call setpos(".", l:cur_pos) 601 | return l:work_list 602 | endfunction 603 | 604 | return indexer 605 | 606 | endfunction 607 | 608 | " 1}}} 609 | 610 | " Catalog {{{1 611 | " ============================================================================== 612 | 613 | " The main workhorse pseudo-object is created here ... 614 | function! s:NewCatalog(catalog_domain, catalog_desc, default_sort) 615 | 616 | " increment catalog counter, creating it if it does not already exist 617 | if !exists("s:buffersaurus_catalog_count") 618 | let s:buffersaurus_catalog_count = 1 619 | else 620 | let s:buffersaurus_catalog_count += 1 621 | endif 622 | 623 | " initialize fields 624 | let l:var_name = a:catalog_domain 625 | let catalog = { 626 | \ "catalog_id" : s:buffersaurus_catalog_count, 627 | \ "catalog_domain" : a:catalog_domain, 628 | \ "catalog_desc" : a:catalog_desc, 629 | \ "show_context" : exists("g:buffersaurus_" . l:var_name . "_show_context") ? g:buffersaurus_{l:var_name}_show_context : g:buffersaurus_show_context, 630 | \ "context_size" : exists("g:buffersaurus_" . l:var_name . "_context_size") ? g:buffersaurus_{l:var_name}_context_size : g:buffersaurus_context_size, 631 | \ "search_profile" : [], 632 | \ "matched_lines" : [], 633 | \ "search_history" : [], 634 | \ "searched_files" : {}, 635 | \ "last_search_time" : 0, 636 | \ "last_search_hits" : 0, 637 | \ "entry_indexes" : [], 638 | \ "entry_labels" : {}, 639 | \ "last_compile_time" : 0, 640 | \ "sort_regime" : empty(a:default_sort) ? g:buffersaurus_sort_regime : a:default_sort, 641 | \} 642 | 643 | " sets the display context 644 | function! catalog.set_context_size(...) dict 645 | let l:context = self.context_size 646 | for l:carg in range(a:0) 647 | if a:000[l:carg] == "" 648 | return 649 | endif 650 | if a:000[l:carg] =~ '\d\+' 651 | let l:context[0] = str2nr(a:000[l:carg]) 652 | let l:context[1] = str2nr(a:000[l:carg]) 653 | elseif a:000[l:carg] =~ '-\d\+' 654 | let l:context[0] = str2nr(a:000[l:carg][1:]) 655 | elseif a:000[l:carg] =! '+\d\+' 656 | let l:context[1] = str2nr(a:000[l:carg][1:]) 657 | else 658 | call s:_buffersaurus_messenger.send_error("Invalid argument ".l:carg.": ".a:000[l:carg]) 659 | return 660 | endif 661 | endfor 662 | let self.context_size = l:context 663 | return self.context_size 664 | endfunction 665 | 666 | " determine whether or not context should be shown 667 | function! catalog.is_show_context() dict 668 | if !self.show_context 669 | return 0 670 | else 671 | if self.context_size[0] == 0 && self.context_size[1] == 0 672 | return 0 673 | else 674 | return 1 675 | endif 676 | endif 677 | endfunction 678 | 679 | " clears all items 680 | function! catalog.clear() dict 681 | let self.matched_lines = [] 682 | let self.search_history = [] 683 | let self.searched_files = {} 684 | let self.last_search_time = 0 685 | let self.last_search_hits = 0 686 | let self.entry_indexes = [] 687 | let self.entry_labels = {} 688 | let self.last_compile_time = 0 689 | endfunction 690 | 691 | " number of entries in the catalog 692 | function! catalog.size() dict 693 | return len(self.matched_lines) 694 | endfunction 695 | 696 | " carry out search given in the search profile 697 | function catalog.build(...) dict 698 | call self.clear() 699 | if a:0 >= 1 700 | let self.search_profile = a:1 701 | endif 702 | for l:search in self.search_profile 703 | call self.map_buffer(l:search.filepath, l:search.pattern) 704 | endfor 705 | endfunction 706 | 707 | " repeat last search 708 | function catalog.rebuild() dict 709 | if empty(self.search_history) 710 | raise s:_buffersaurus_messenger.format_exception("Search history is empty") 711 | endif 712 | let self.search_profile = [] 713 | for search in self.search_history 714 | call add(self.search_profile, search) 715 | endfor 716 | call self.clear() 717 | call self.build() 718 | call self.compile_entry_indexes() 719 | endfunction 720 | 721 | " index all occurrences of `pattern` in buffer `buf_ref` 722 | function! catalog.map_buffer(buf_ref, pattern) dict 723 | if type(a:buf_ref) == type(0) 724 | let l:buf_num = a:buf_ref 725 | let l:buf_name = bufname(l:buf_num) 726 | else 727 | let l:buf_name = expand(a:buf_ref) 728 | let l:buf_num = bufnr(l:buf_name) + 0 729 | endif 730 | let l:filepath = fnamemodify(expand(l:buf_name), ":p") 731 | let l:buf_search_log = { 732 | \ "buf_name" : l:buf_name, 733 | \ 'buf_num': l:buf_num, 734 | \ "filepath" : l:filepath, 735 | \ "pattern" : a:pattern, 736 | \ "num_lines_searched" : 0, 737 | \ "num_lines_matched" : 0, 738 | \ "last_searched" : 0, 739 | \ } 740 | let self.last_search_hits = 0 741 | let l:lnum = 1 742 | while 1 743 | let l:buf_lines = getbufline(l:buf_num, l:lnum) 744 | if empty(l:buf_lines) 745 | break 746 | endif 747 | let l:pos = match(l:buf_lines[0], a:pattern) 748 | let l:buf_search_log["num_lines_searched"] += 1 749 | if l:pos >= 0 750 | let self.last_search_hits += 1 751 | let l:search_order = len(self.matched_lines) + 1 752 | call add(self.matched_lines, { 753 | \ 'buf_name': l:buf_name, 754 | \ 'buf_num': l:buf_num, 755 | \ 'filepath' : l:filepath, 756 | \ 'lnum': l:lnum, 757 | \ 'col': l:pos + 1, 758 | \ 'sort_text' : substitute(l:buf_lines[0], '^\s*', '', 'g'), 759 | \ 'search_order' : l:search_order, 760 | \ 'entry_label' : string(l:search_order), 761 | \ }) 762 | let l:buf_search_log["num_lines_matched"] += 1 763 | endif 764 | let l:lnum += 1 765 | endwhile 766 | let l:buf_search_log["last_searched"] = localtime() 767 | let self.last_search_time = l:buf_search_log["last_searched"] 768 | call add(self.search_history, l:buf_search_log) 769 | if has_key(self.searched_files, l:filepath) 770 | let self.searched_files[l:filepath] += self.last_search_hits 771 | else 772 | let self.searched_files[l:filepath] = self.last_search_hits 773 | endif 774 | endfunction 775 | 776 | " open the catalog for viewing 777 | function! catalog.open() dict 778 | if !has_key(self, "catalog_viewer") || empty(self.catalog_viewer) 779 | let self["catalog_viewer"] = s:NewCatalogViewer(self, self.catalog_desc) 780 | endif 781 | call self.catalog_viewer.open() 782 | return self.catalog_viewer 783 | endfunction 784 | 785 | " returns indexes of matched lines, compiling them if 786 | " needed 787 | function! catalog.get_index_groups() dict 788 | if self.last_compile_time < self.last_search_time 789 | call self.compile_entry_indexes() 790 | endif 791 | return self.entry_indexes 792 | endfunction 793 | 794 | " returns true if sort regime dictates that indexes are grouped 795 | function! catalog.is_sort_grouped() dict 796 | if self.sort_regime == 'a' 797 | return 0 798 | else 799 | return 1 800 | endif 801 | endfunction 802 | 803 | " apply a sort regime 804 | function! catalog.apply_sort(regime) dict 805 | if index(s:buffersaurus_catalog_sort_regimes, a:regime) == - 1 806 | throw s:_buffersaurus_messenger.format_exception("Unrecognized sort regime: '" . a:regime . "'") 807 | endif 808 | let self.sort_regime = a:regime 809 | return self.compile_entry_indexes() 810 | endfunction 811 | 812 | " cycle through sort regimes 813 | function! catalog.cycle_sort_regime() dict 814 | let l:cur_regime = index(s:buffersaurus_catalog_sort_regimes, self.sort_regime) 815 | let l:cur_regime += 1 816 | if l:cur_regime < 0 || l:cur_regime >= len(s:buffersaurus_catalog_sort_regimes) 817 | let self.sort_regime = s:buffersaurus_catalog_sort_regimes[0] 818 | else 819 | let self.sort_regime = s:buffersaurus_catalog_sort_regimes[l:cur_regime] 820 | endif 821 | return self.compile_entry_indexes() 822 | endfunction 823 | 824 | " compiles matches into index 825 | function! catalog.compile_entry_indexes() dict 826 | let self.entry_indexes = [] 827 | let self.entry_labels = {} 828 | if self.sort_regime == 'fl' 829 | call sort(self.matched_lines, "s:compare_matched_lines_fl") 830 | elseif self.sort_regime == 'fa' 831 | call sort(self.matched_lines, "s:compare_matched_lines_fa") 832 | elseif self.sort_regime == 'a' 833 | call sort(self.matched_lines, "s:compare_matched_lines_a") 834 | else 835 | throw s:_buffersaurus_messenger.format_exception("Unrecognized sort regime: '" . self.sort_regime . "'") 836 | endif 837 | if self.sort_regime == 'a' 838 | call add(self.entry_indexes, ['', []]) 839 | for l:matched_line_idx in range(0, len(self.matched_lines) - 1) 840 | call add(self.entry_indexes[-1][1], l:matched_line_idx) 841 | let self.entry_labels[l:matched_line_idx] = self.matched_lines[l:matched_line_idx].entry_label 842 | endfor 843 | else 844 | let l:cur_group = "" 845 | for l:matched_line_idx in range(0, len(self.matched_lines) - 1) 846 | if self.matched_lines[l:matched_line_idx].filepath != l:cur_group 847 | let l:cur_group = self.matched_lines[l:matched_line_idx].filepath 848 | call add(self.entry_indexes, [l:cur_group, []]) 849 | endif 850 | call add(self.entry_indexes[-1][1], l:matched_line_idx) 851 | let self.entry_labels[l:matched_line_idx] = self.matched_lines[l:matched_line_idx].entry_label 852 | endfor 853 | endif 854 | let self.last_compile_time = localtime() 855 | return self.entry_indexes 856 | endfunction 857 | 858 | " Describes catalog status. 859 | function! catalog.describe() dict 860 | call s:_buffersaurus_messenger.send_info(self.format_status_message() . " (sorted " . self.format_sort_status() . ")") 861 | endfunction 862 | 863 | " Describes catalog status in detail. 864 | function! catalog.describe_detail() dict 865 | echon self.format_status_message() . ":\n" 866 | let l:rows = [] 867 | let l:header = self.format_describe_detail_row([ 868 | \ "#", 869 | \ "File", 870 | \ "Found", 871 | \ "Total", 872 | \ "Pattern", 873 | \]) 874 | echohl Title 875 | echo l:header "\n" 876 | echohl None 877 | for search_log in self.search_history 878 | let l:row = self.format_describe_detail_row([ 879 | \ bufnr(search_log.filepath), 880 | \ bufname(search_log.filepath), 881 | \ string(search_log.num_lines_matched), 882 | \ string(search_log.num_lines_searched), 883 | \ search_log.pattern, 884 | \]) 885 | call add(l:rows, row) 886 | endfor 887 | echon join(l:rows, "\n") 888 | endfunction 889 | 890 | " Formats a single row in the detail catalog description 891 | function! catalog.format_describe_detail_row(fields) 892 | let l:row = join([ 893 | \ s:Format_Fill(a:fields[0], 3, 2, 1), 894 | \ s:Format_Fill(a:fields[1], ((&columns - 14) / 3), -1, -1), 895 | \ s:Format_Fill(a:fields[2], 6, 1, 0), 896 | \ s:Format_Fill(a:fields[3], 6, 1, 0), 897 | \ a:fields[4], 898 | \ ], " ") 899 | return l:row 900 | endfunction 901 | 902 | " Composes message indicating size of catalog. 903 | function! catalog.format_status_message() dict 904 | let l:message = "" 905 | let catalog_size = self.size() 906 | let l:num_searched_files = len(self.searched_files) 907 | if catalog_size == 0 908 | let l:message .= "no entries" 909 | elseif catalog_size == 1 910 | let l:message .= "1 entry" 911 | else 912 | let l:message .= catalog_size . " entries" 913 | endif 914 | let l:message .= " in " 915 | if l:num_searched_files == 0 916 | let l:message .= "no files" 917 | elseif l:num_searched_files == 1 918 | let l:message .= "1 file" 919 | else 920 | let l:message .= l:num_searched_files . " files" 921 | endif 922 | return l:message 923 | endfunction 924 | 925 | " Composes message indicating sort regime of catalog. 926 | function! catalog.format_sort_status() dict 927 | let l:message = get(s:buffersaurus_catalog_sort_regime_desc, self.sort_regime, ["??", "in unspecified order"])[1] 928 | return l:message 929 | endfunction 930 | 931 | " return pseudo-object 932 | return catalog 933 | 934 | endfunction 935 | 936 | " comparison function used for sorting matched lines: sort first by 937 | " filepath, then by line number 938 | function! s:compare_matched_lines_fl(m1, m2) 939 | if a:m1.filepath < a:m2.filepath 940 | return -1 941 | elseif a:m1.filepath > a:m2.filepath 942 | return 1 943 | else 944 | if a:m1.lnum < a:m2.lnum 945 | return -1 946 | elseif a:m1.lnum > a:m2.lnum 947 | return 1 948 | else 949 | return 0 950 | endif 951 | endif 952 | endfunction 953 | 954 | " comparison function used for sorting matched lines: sort first by 955 | " filepath, then by text 956 | function! s:compare_matched_lines_fa(m1, m2) 957 | if a:m1.filepath < a:m2.filepath 958 | return -1 959 | elseif a:m1.filepath > a:m2.filepath 960 | return 1 961 | else 962 | return s:compare_matched_lines_a(a:m1, a:m2) 963 | endif 964 | endfunction 965 | 966 | " comparison function used for sorting matched lines: sort by 967 | " text 968 | function! s:compare_matched_lines_a(m1, m2) 969 | if a:m1.sort_text < a:m2.sort_text 970 | return -1 971 | elseif a:m1.sort_text > a:m2.sort_text 972 | return 1 973 | else 974 | return 0 975 | endif 976 | endfunction 977 | 978 | " 1}}} 979 | 980 | " NewMarksCatalog {{{1 981 | " ============================================================================ 982 | 983 | " The main workhorse pseudo-object is created here ... 984 | function! s:NewMarksCatalog(catalog_domain, catalog_desc) 985 | let catalog = s:NewCatalog(a:catalog_domain, a:catalog_desc, "") 986 | 987 | " Returns dictionary of marks. If `global` is true then upper-case marks 988 | " will be included as well. Otherwise, only lower-case marks. 989 | function! catalog.get_mark_list(global) dict 990 | if !empty(a:global) && a:global == "!" 991 | let l:marks = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 992 | else 993 | let l:marks = "abcdefghijklmnopqrstuvwxyz" 994 | endif 995 | let l:markstr = "" 996 | try 997 | redir => l:markstr | execute("silent marks ".l:marks) | redir END 998 | catch /E283:/ 999 | return {} 1000 | endtry 1001 | let l:markstr_rows = split(l:markstr, "\n") 1002 | let l:mark_list = [] 1003 | for l:row in l:markstr_rows[1:] 1004 | let l:mark_parts = matchlist(l:row, '^\s\{-1,}\(\a\)\s\{-1,}\(\d\{-1,}\)\s\{-1,}\(\d\{-1,}\)\s\{-1,}\(.*\)$') 1005 | if len(l:mark_parts) < 4 1006 | continue 1007 | endif 1008 | if l:mark_parts[1] =~ '[a-z]' 1009 | let l:fpath = expand("%:p") 1010 | else 1011 | let l:fpath = l:mark_parts[4] 1012 | endif 1013 | call add(l:mark_list, [l:mark_parts[1], l:fpath, str2nr(l:mark_parts[2]), str2nr(l:mark_parts[3])]) 1014 | endfor 1015 | return l:mark_list 1016 | endfunction 1017 | 1018 | return catalog 1019 | endfunction 1020 | 1021 | " 1}}} 1022 | 1023 | " CatalogViewer {{{1 1024 | " ============================================================================== 1025 | 1026 | " Display the catalog. 1027 | function! s:NewCatalogViewer(catalog, desc, ...) 1028 | 1029 | " abort if catalog is empty 1030 | " if len(a:catalog.matched_lines) == 0 1031 | " throw s:_buffersaurus_messenger.format_exception("CatalogViewer() called on empty catalog") 1032 | " endif 1033 | 1034 | " initialize 1035 | let catalog_viewer = {} 1036 | 1037 | " Initialize object state. 1038 | let catalog_viewer["catalog"] = a:catalog 1039 | let catalog_viewer["description"] = a:desc 1040 | let catalog_viewer["buf_num"] = -1 1041 | let catalog_viewer["buf_name"] = "[[buffersaurus]]" 1042 | let catalog_viewer["title"] = "buffersaurus" 1043 | let l:buffersaurus_bufs = s:_buffersaurus_buffer_manager.find_buffers_with_var("is_buffersaurus_buffer", 1) 1044 | if len(l:buffersaurus_bufs) > 0 1045 | let catalog_viewer["buf_num"] = l:buffersaurus_bufs[0] 1046 | endif 1047 | let catalog_viewer["jump_map"] = {} 1048 | let catalog_viewer["split_mode"] = s:_buffersaurus_buffer_manager.get_split_mode() 1049 | let catalog_viewer["filter_regime"] = 0 1050 | let catalog_viewer["filter_pattern"] = "" 1051 | let catalog_viewer["match_highlight_id"] = 0 1052 | 1053 | " Opens the buffer for viewing, creating it if needed. If non-empty first 1054 | " argument is given, forces re-rendering of buffer. 1055 | function! catalog_viewer.open(...) dict 1056 | " get buffer number of the catalog view buffer, creating it if neccessary 1057 | if self.buf_num < 0 || !bufexists(self.buf_num) 1058 | " create and render a new buffer 1059 | call self.create_buffer() 1060 | else 1061 | " buffer exists: activate a viewport on it according to the 1062 | " spawning mode, re-rendering the buffer with the catalog if needed 1063 | call self.activate_viewport() 1064 | if b:buffersaurus_last_render_time < self.catalog.last_search_time || (a:0 > 0 && a:1) || b:buffersaurus_catalog_viewer != self 1065 | call self.render_buffer() 1066 | endif 1067 | endif 1068 | endfunction 1069 | 1070 | " Creates a new buffer, renders and opens it. 1071 | function! catalog_viewer.create_buffer() dict 1072 | " get a new buf reference 1073 | let self.buf_num = bufnr(self.buf_name, 1) 1074 | " get a viewport onto it 1075 | call self.activate_viewport() 1076 | " initialize it (includes "claiming" it) 1077 | call self.initialize_buffer() 1078 | " render it 1079 | call self.render_buffer() 1080 | endfunction 1081 | 1082 | " Opens a viewport on the buffer according, creating it if neccessary 1083 | " according to the spawn mode. Valid buffer number must already have been 1084 | " obtained before this is called. 1085 | function! catalog_viewer.activate_viewport() dict 1086 | let l:bfwn = bufwinnr(self.buf_num) 1087 | if l:bfwn == winnr() 1088 | " viewport wth buffer already active and current 1089 | return 1090 | elseif l:bfwn >= 0 1091 | " viewport with buffer exists, but not current 1092 | execute(l:bfwn . " wincmd w") 1093 | else 1094 | " create viewport 1095 | let self.split_mode = s:_buffersaurus_buffer_manager.get_split_mode() 1096 | execute("silent keepalt keepjumps " . self.split_mode . " " . self.buf_num) 1097 | endif 1098 | endfunction 1099 | 1100 | " Sets up buffer environment. 1101 | function! catalog_viewer.initialize_buffer() dict 1102 | call self.claim_buffer() 1103 | call self.setup_buffer_opts() 1104 | call self.setup_buffer_syntax() 1105 | call self.setup_buffer_commands() 1106 | call self.setup_buffer_keymaps() 1107 | call self.setup_buffer_folding() 1108 | call self.setup_buffer_statusline() 1109 | endfunction 1110 | 1111 | " 'Claims' a buffer by setting it to point at self. 1112 | function! catalog_viewer.claim_buffer() dict 1113 | call setbufvar("%", "is_buffersaurus_buffer", 1) 1114 | call setbufvar("%", "buffersaurus_catalog_domain", self.catalog.catalog_domain) 1115 | call setbufvar("%", "buffersaurus_catalog_viewer", self) 1116 | call setbufvar("%", "buffersaurus_last_render_time", 0) 1117 | call setbufvar("%", "buffersaurus_cur_line", 0) 1118 | endfunction 1119 | 1120 | " 'Unclaims' a buffer by stripping all buffersaurus vars 1121 | function! catalog_viewer.unclaim_buffer() dict 1122 | for l:var in ["is_buffersaurus_buffer", 1123 | \ "buffersaurus_catalog_domain", 1124 | \ "buffersaurus_catalog_viewer", 1125 | \ "buffersaurus_last_render_time", 1126 | \ "buffersaurus_cur_line" 1127 | \ ] 1128 | if exists("b:" . l:var) 1129 | unlet b:{l:var} 1130 | endif 1131 | endfor 1132 | endfunction 1133 | 1134 | " Sets buffer options. 1135 | function! catalog_viewer.setup_buffer_opts() dict 1136 | setlocal buftype=nofile 1137 | setlocal noswapfile 1138 | setlocal nowrap 1139 | set bufhidden=hide 1140 | setlocal nobuflisted 1141 | setlocal nolist 1142 | setlocal noinsertmode 1143 | " setlocal nonumber 1144 | setlocal cursorline 1145 | setlocal nospell 1146 | endfunction 1147 | 1148 | " Sets buffer syntax. 1149 | function! catalog_viewer.setup_buffer_syntax() dict 1150 | if has("syntax") 1151 | syntax clear 1152 | if self.catalog.is_show_context() 1153 | syn region BuffersaurusSyntaxFileGroup matchgroup=BuffersaurusSyntaxFileGroupTitle start='^[^ ]' keepend end='\(^[^ ]\)\@=' fold 1154 | syn region BuffersaurusSyntaxContextedEntry start='^ \[' end='\(^ \[\|^[^ ]\)\@=' fold containedin=BuffersaurusSyntaxFileGroup 1155 | syn region BuffersaurusSyntaxContextedKeyRow start='^ \[\s\{-}.\{-1,}\s\{-}\]' keepend oneline end='$' containedin=BuffersaurusSyntaxContextedEntry 1156 | syn region BuffersaurusSyntaxContextLines start='^ \s*\d\+ :' oneline end='$' containedin=BuffersaurusSyntaxContextedEntry 1157 | syn region BuffersaurusSyntaxMatchedLines start='^ \s*\d\+ >' oneline end='$' containedin=BuffersaurusSyntaxContextedEntry 1158 | 1159 | syn match BuffersaurusSyntaxFileGroupTitle ':: .\+ :::' containedin=BuffersaurusSyntaxFileGroup 1160 | syn match BuffersaurusSyntaxKey '^ \zs\[\s\{-}.\{-1,}\s\{-}\]\ze' containedin=BuffersaurusSyntaxcOntextedKeyRow 1161 | syn match BuffersaurusSyntaxContextedKeyFilename ' \zs".\+"\ze, L\d\+-\d\+:' containedin=BuffersaurusSyntaxContextedKeyRow 1162 | syn match BuffersaurusSyntaxContextedKeyLines ', \zsL\d\+-\d\+\ze:' containedin=BuffersaurusSyntaxContextedKeyRow 1163 | syn match BuffersaurusSyntaxContextedKeyDesc ': .*$' containedin=BuffersaurusSyntaxContextedKeyRow 1164 | 1165 | syn match BuffersaurusSyntaxContextLineNum '^ \zs\s*\d\+\s*\ze:' containedin=BuffersaurusSyntaxContextLines 1166 | syn match BuffersaurusSyntaxContextLineText ': \zs.*\ze' containedin=BuffersaurusSyntaxContextLines 1167 | 1168 | syn match BuffersaurusSyntaxMatchedLineNum '^ \zs\s*\d\+\s*\ze>' containedin=BuffersaurusSyntaxMatchedLines 1169 | syn match BuffersaurusSyntaxMatchedLineText '> \zs.*\ze' containedin=BuffersaurusSyntaxMatchedLines 1170 | else 1171 | syn match BuffersaurusSyntaxFileGroupTitle '^\zs::: .* :::\ze.*$' nextgroup=BuffersaurusSyntaxKey 1172 | syn match BuffersaurusSyntaxKey '^ \zs\[\s\{-}.\{-1,}\s\{-}\]\ze' nextgroup=BuffersaurusSyntaxUncontextedLineNum 1173 | syn match BuffersaurusSyntaxUncontextedLineNum '\s\+\s*\zs\d\+\ze:' nextgroup=BuffersaurusSyntaxUncontextedLineText 1174 | endif 1175 | highlight! link BuffersaurusSyntaxFileGroupTitle Title 1176 | highlight! link BuffersaurusSyntaxKey Identifier 1177 | highlight! link BuffersaurusSyntaxContextedKeyFilename Comment 1178 | highlight! link BuffersaurusSyntaxContextedKeyLines Comment 1179 | highlight! link BuffersaurusSyntaxContextedKeyDesc Comment 1180 | highlight! link BuffersaurusSyntaxContextLineNum Normal 1181 | highlight! link BuffersaurusSyntaxContextLineText Normal 1182 | highlight! link BuffersaurusSyntaxMatchedLineNum Question 1183 | highlight! link BuffersaurusSyntaxMatchedLineText Question 1184 | highlight! link BuffersaurusSyntaxUncontextedLineNum Question 1185 | highlight! link BuffersaurusSyntaxUncontextedLineText Normal 1186 | highlight! def BuffersaurusCurrentEntry gui=reverse cterm=reverse term=reverse 1187 | endif 1188 | endfunction 1189 | 1190 | " Sets buffer commands. 1191 | function! catalog_viewer.setup_buffer_commands() dict 1192 | command! -buffer -bang -nargs=* Bsfilter :call b:buffersaurus_catalog_viewer.set_filter('', ) 1193 | command! -buffer -bang -nargs=* Bssubstitute :call b:buffersaurus_catalog_viewer.search_and_replace('', , 0) 1194 | augroup BuffersaurusCatalogViewer 1195 | au! 1196 | autocmd CursorHold,CursorHoldI,CursorMoved,CursorMovedI,BufEnter,BufLeave call b:buffersaurus_catalog_viewer.highlight_current_line() 1197 | autocmd BufLeave let s:_buffersaurus_last_catalog_viewed = b:buffersaurus_catalog_viewer 1198 | augroup END 1199 | endfunction 1200 | 1201 | " Sets buffer key maps. 1202 | function! catalog_viewer.setup_buffer_keymaps() dict 1203 | 1204 | """" Disabling of unused modification keys 1205 | for key in [".", "p", "P", "C", "x", "X", "r", "R", "i", "I", "a", "A", "D", "S", "U"] 1206 | try 1207 | execute "nnoremap " . key . " " 1208 | catch // 1209 | endtry 1210 | endfor 1211 | 1212 | if !exists("g:buffersaurus_use_new_keymap") || !g:buffergator_use_new_keymap 1213 | 1214 | """" Index buffer management 1215 | noremap cc :call b:buffersaurus_catalog_viewer.toggle_context() 1216 | noremap C :call b:buffersaurus_catalog_viewer.toggle_context() 1217 | noremap cs :call b:buffersaurus_catalog_viewer.cycle_sort_regime() 1218 | noremap cq :call b:buffersaurus_catalog_viewer.cycle_autodismiss_modes() 1219 | noremap f :call b:buffersaurus_catalog_viewer.toggle_filter() 1220 | noremap F :call b:buffersaurus_catalog_viewer.prompt_and_apply_filter() 1221 | noremap r :call b:buffersaurus_catalog_viewer.rebuild_catalog() 1222 | noremap :call b:buffersaurus_catalog_viewer.catalog.describe() 1223 | noremap g :call b:buffersaurus_catalog_viewer.catalog.describe_detail() 1224 | noremap q :call b:buffersaurus_catalog_viewer.close(1) 1225 | noremap :call b:buffersaurus_catalog_viewer.close(1) 1226 | 1227 | """" Movement within buffer 1228 | 1229 | " flash matched line 1230 | noremap P :let g:buffersaurus_flash_jumped_line = !g:buffersaurus_flash_jumped_line 1231 | 1232 | " jump to next/prev key entry 1233 | noremap :call b:buffersaurus_catalog_viewer.goto_index_entry("n", 0, 1) 1234 | noremap :call b:buffersaurus_catalog_viewer.goto_index_entry("p", 0, 1) 1235 | 1236 | " jump to next/prev file entry 1237 | noremap ]f :call b:buffersaurus_catalog_viewer.goto_file_start("n", 0, 1) 1238 | noremap [f :call b:buffersaurus_catalog_viewer.goto_file_start("p", 0, 1) 1239 | 1240 | """"" Selection: show target and switch focus 1241 | noremap :call b:buffersaurus_catalog_viewer.visit_target(!g:buffersaurus_autodismiss_on_select, 0, "") 1242 | noremap o :call b:buffersaurus_catalog_viewer.visit_target(!g:buffersaurus_autodismiss_on_select, 0, "") 1243 | noremap s :call b:buffersaurus_catalog_viewer.visit_target(!g:buffersaurus_autodismiss_on_select, 0, "vert sb") 1244 | noremap :call b:buffersaurus_catalog_viewer.visit_target(!g:buffersaurus_autodismiss_on_select, 0, "vert sb") 1245 | noremap i :call b:buffersaurus_catalog_viewer.visit_target(!g:buffersaurus_autodismiss_on_select, 0, "sb") 1246 | noremap :call b:buffersaurus_catalog_viewer.visit_target(!g:buffersaurus_autodismiss_on_select, 0, "sb") 1247 | noremap t :call b:buffersaurus_catalog_viewer.visit_target(!g:buffersaurus_autodismiss_on_select, 0, "tab sb") 1248 | noremap :call b:buffersaurus_catalog_viewer.visit_target(!g:buffersaurus_autodismiss_on_select, 0, "tab sb") 1249 | 1250 | """"" Selection: show target and switch focus, preserving the catalog regardless of the autodismiss setting 1251 | noremap po :call b:buffersaurus_catalog_viewer.visit_target(1, 0, "") 1252 | noremap ps :call b:buffersaurus_catalog_viewer.visit_target(1, 0, "vert sb") 1253 | noremap pi :call b:buffersaurus_catalog_viewer.visit_target(1, 0, "sb") 1254 | noremap pt :call b:buffersaurus_catalog_viewer.visit_target(1, 0, "tab sb") 1255 | 1256 | """"" Preview: show target , keeping focus on catalog 1257 | noremap O :call b:buffersaurus_catalog_viewer.visit_target(1, 1, "") 1258 | noremap go :call b:buffersaurus_catalog_viewer.visit_target(1, 1, "") 1259 | noremap S :call b:buffersaurus_catalog_viewer.visit_target(1, 1, "vert sb") 1260 | noremap gs :call b:buffersaurus_catalog_viewer.visit_target(1, 1, "vert sb") 1261 | noremap I :call b:buffersaurus_catalog_viewer.visit_target(1, 1, "sb") 1262 | noremap gi :call b:buffersaurus_catalog_viewer.visit_target(1, 1, "sb") 1263 | noremap T :call b:buffersaurus_catalog_viewer.visit_target(1, 1, "tab sb") 1264 | noremap :call b:buffersaurus_catalog_viewer.goto_index_entry("n", 1, 1) 1265 | noremap :call b:buffersaurus_catalog_viewer.goto_index_entry("p", 1, 1) 1266 | noremap :call b:buffersaurus_catalog_viewer.goto_index_entry("p", 1, 1) 1267 | noremap :call b:buffersaurus_catalog_viewer.goto_index_entry("n", 1, 1) 1268 | noremap :call b:buffersaurus_catalog_viewer.goto_index_entry("p", 1, 1) 1269 | 1270 | """"" Special operations 1271 | nnoremap x :call b:buffersaurus_catalog_viewer.execute_command("", 0, 1) 1272 | nnoremap X :call b:buffersaurus_catalog_viewer.execute_command("", 1, 1) 1273 | nnoremap R :call b:buffersaurus_catalog_viewer.search_and_replace("", 0, 1) 1274 | nnoremap :call b:buffersaurus_catalog_viewer.search_and_replace("", 0, 1) 1275 | nnoremap & :call b:buffersaurus_catalog_viewer.search_and_replace("", 0, 1) 1276 | 1277 | else 1278 | 1279 | """" Index buffer management 1280 | noremap c :call b:buffersaurus_catalog_viewer.toggle_context() 1281 | noremap s :call b:buffersaurus_catalog_viewer.cycle_sort_regime() 1282 | noremap f :call b:buffersaurus_catalog_viewer.toggle_filter() 1283 | noremap F :call b:buffersaurus_catalog_viewer.prompt_and_apply_filter() 1284 | noremap u :call b:buffersaurus_catalog_viewer.rebuild_catalog() 1285 | noremap :call b:buffersaurus_catalog_viewer.catalog.describe() 1286 | noremap g :call b:buffersaurus_catalog_viewer.catalog.describe_detail() 1287 | noremap q :call b:buffersaurus_catalog_viewer.close(1) 1288 | 1289 | """" Selection 1290 | noremap :call b:buffersaurus_catalog_viewer.visit_target(!g:buffersaurus_autodismiss_on_select, 0, "") 1291 | 1292 | """" Movement within buffer 1293 | 1294 | " jump to next/prev key entry 1295 | noremap :call b:buffersaurus_catalog_viewer.goto_index_entry("n", 0, 1) 1296 | noremap :call b:buffersaurus_catalog_viewer.goto_index_entry("p", 0, 1) 1297 | 1298 | " jump to next/prev file entry 1299 | noremap ]f :call b:buffersaurus_catalog_viewer.goto_file_start("n", 0, 1) 1300 | noremap [f :call b:buffersaurus_catalog_viewer.goto_file_start("p", 0, 1) 1301 | 1302 | """" Movement within buffer that updates the other window 1303 | 1304 | " show target line in other window, keeping catalog open and in focus 1305 | noremap . :call b:buffersaurus_catalog_viewer.visit_target(1, 1, "") 1306 | noremap po :call b:buffersaurus_catalog_viewer.visit_target(1, 1, "") 1307 | noremap ps :call b:buffersaurus_catalog_viewer.visit_target(1, 1, "sb") 1308 | noremap pv :call b:buffersaurus_catalog_viewer.visit_target(1, 1, "vert sb") 1309 | noremap pt :call b:buffersaurus_catalog_viewer.visit_target(1, 1, "tab sb") 1310 | noremap :call b:buffersaurus_catalog_viewer.goto_index_entry("n", 1, 1) 1311 | noremap :call b:buffersaurus_catalog_viewer.goto_index_entry("p", 1, 1) 1312 | noremap :call b:buffersaurus_catalog_viewer.goto_index_entry("p", 1, 1) 1313 | noremap :call b:buffersaurus_catalog_viewer.goto_index_entry("n", 1, 1) 1314 | noremap :call b:buffersaurus_catalog_viewer.goto_index_entry("p", 1, 1) 1315 | 1316 | """" Movement that moves to the current search target 1317 | 1318 | " go to target line in other window, keeping catalog open 1319 | noremap o :call b:buffersaurus_catalog_viewer.visit_target(1, 0, "") 1320 | noremap ws :call b:buffersaurus_catalog_viewer.visit_target(1, 0, "sb") 1321 | noremap wv :call b:buffersaurus_catalog_viewer.visit_target(1, 0, "vert sb") 1322 | noremap t :call b:buffersaurus_catalog_viewer.visit_target(1, 0, "tab sb") 1323 | 1324 | " open target line in other window, closing catalog 1325 | noremap O :call b:buffersaurus_catalog_viewer.visit_target(0, 0, "") 1326 | noremap wS :call b:buffersaurus_catalog_viewer.visit_target(0, 0, "sb") 1327 | noremap wV :call b:buffersaurus_catalog_viewer.visit_target(0, 0, "vert sb") 1328 | noremap T :call b:buffersaurus_catalog_viewer.visit_target(0, 0, "tab sb") 1329 | 1330 | endif 1331 | 1332 | endfunction 1333 | 1334 | " Sets buffer folding. 1335 | function! catalog_viewer.setup_buffer_folding() dict 1336 | if has("folding") 1337 | "setlocal foldcolumn=3 1338 | setlocal foldmethod=syntax 1339 | setlocal foldlevel=4 1340 | setlocal foldenable 1341 | setlocal foldtext=BuffersaurusFoldText() 1342 | " setlocal fillchars=fold:\ " 1343 | setlocal fillchars=fold:. 1344 | endif 1345 | endfunction 1346 | 1347 | " Search and replace 1348 | function! catalog_viewer.search_and_replace(bang, sr_pattern, assume_last_search_pattern) dict 1349 | if a:bang 1350 | let l:include_context_lines = 1 1351 | else 1352 | let l:include_context_lines = 0 1353 | endif 1354 | if empty(a:sr_pattern) 1355 | if a:assume_last_search_pattern 1356 | let l:pattern = s:last_searched_pattern 1357 | else 1358 | let l:pattern = input("Search for: ", s:last_searched_pattern) 1359 | if empty(l:pattern) 1360 | return 1361 | endif 1362 | endif 1363 | let l:replace = input("Replace with: ", l:pattern) 1364 | if empty(l:replace) 1365 | return 1366 | endif 1367 | for separator in ["/", "@", "'", "|", "!", "#", "$", "%", "^", "&", "*", "(", ")", "_", "-", "+", "=", ":"] 1368 | if !(l:pattern =~ '\'.separator || l:replace =~ '\'.separator) 1369 | break 1370 | endif 1371 | endfor 1372 | let l:command = "s" . l:separator . l:pattern . l:separator . l:replace . l:separator . "ge" 1373 | else 1374 | let l:command = "s" . a:sr_pattern 1375 | endif 1376 | call self.execute_command(l:command, l:include_context_lines, 1) 1377 | endfunction 1378 | 1379 | " Applies filter. 1380 | function! catalog_viewer.set_filter(regime, pattern) dict 1381 | if (type(a:regime) == type(0) && a:regime != 0) || (type(a:regime) == type("") && a:regime != "!") 1382 | if a:pattern == "*" || a:pattern == ".*" 1383 | let self.filter_pattern = "" 1384 | let self.filter_regime = 0 1385 | call s:_buffersaurus_messenger.send_info("clearing filter") 1386 | else 1387 | if !empty(a:pattern) 1388 | let self.filter_pattern = a:pattern 1389 | endif 1390 | if !empty(self.filter_pattern) 1391 | let self.filter_regime = 1 1392 | call s:_buffersaurus_messenger.send_info("filtering for: " . self.filter_pattern) 1393 | else 1394 | let l:ipattern = input("Enter filter pattern: ") 1395 | if empty(l:ipattern) 1396 | return 1397 | else 1398 | let self.filter_pattern = l:ipattern 1399 | let self.filter_regime = 1 1400 | endif 1401 | endif 1402 | endif 1403 | else 1404 | if a:pattern == "*" || a:pattern == ".*" 1405 | let self.filter_pattern = "" 1406 | let self.filter_regime = 0 1407 | call s:_buffersaurus_messenger.send_info("clearing filter") 1408 | else 1409 | let self.filter_regime = 0 1410 | if empty(self.filter_pattern) 1411 | call s:_buffersaurus_messenger.send_info("filter pattern not set") 1412 | else 1413 | call s:_buffersaurus_messenger.send_info("removing filter") 1414 | endif 1415 | endif 1416 | endif 1417 | call self.render_buffer() 1418 | endfunction 1419 | 1420 | " Toggles filter. 1421 | function! catalog_viewer.toggle_filter() dict 1422 | call self.set_filter(!self.filter_regime, "") 1423 | endfunction 1424 | 1425 | " Ask user for filter pattern, and, if given, set and apply it. 1426 | function! catalog_viewer.prompt_and_apply_filter() 1427 | let l:ipattern = input("Enter filter pattern: ") 1428 | if empty(l:ipattern) 1429 | return 1430 | else 1431 | call self.set_filter(1, l:ipattern) 1432 | endif 1433 | endfunction 1434 | 1435 | " Return true if the line is NOT to be filtered out. 1436 | function! catalog_viewer.is_pass_filter(text) dict 1437 | if !self.filter_regime || empty(self.filter_pattern) || a:text =~ self.filter_pattern 1438 | return 1 1439 | else 1440 | return 0 1441 | endif 1442 | endfunction 1443 | 1444 | " Sets buffer status line. 1445 | function! catalog_viewer.setup_buffer_statusline() dict 1446 | " setlocal statusline=\-buffersaurus\-\|\ %{BuffersaurusStatusLineCurrentLineInfo()}%<%=\|%{BuffersaurusStatusLineSortRegime()}\|%{BuffersaurusStatusLineFilterRegime()} 1447 | setlocal statusline=[[buffersaurus]]%{BuffersaurusStatusLineCurrentLineInfo()}%<%=\|%{BuffersaurusStatusLineSortRegime()} 1448 | endfunction 1449 | 1450 | " Populates the buffer with the catalog index. 1451 | function! catalog_viewer.render_buffer() dict 1452 | setlocal modifiable 1453 | call self.claim_buffer() 1454 | call self.clear_buffer() 1455 | let self.jump_map = {} 1456 | let l:show_context = self.catalog.is_show_context() 1457 | let l:context_size = self.catalog.context_size 1458 | call self.setup_buffer_syntax() 1459 | let catalog_index_groups = self.catalog.get_index_groups() 1460 | let prev_entry_index_group_label = '' 1461 | for l:entry_index_group in catalog_index_groups 1462 | let [l:entry_index_group_label, l:entry_indexes] = l:entry_index_group 1463 | if prev_entry_index_group_label != l:entry_index_group_label 1464 | call self.append_line('::: ' . l:entry_index_group_label . ' :::', 1465 | \ -1, 1466 | \ self.catalog.matched_lines[l:entry_indexes[0]].buf_num, 1467 | \ 1, 1468 | \ 1, 1469 | \ 0, 1470 | \ 0, 1471 | \ {"proxy_entry_index": l:entry_indexes[0]}) 1472 | endif 1473 | for l:entry_index in l:entry_indexes 1474 | if self.catalog.is_show_context() 1475 | call self.render_contexted_entry(l:entry_index, self.catalog.matched_lines[l:entry_index], l:context_size) 1476 | else 1477 | call self.render_uncontexted_entry(l:entry_index, self.catalog.matched_lines[l:entry_index]) 1478 | endif 1479 | endfor 1480 | endfor 1481 | let b:buffersaurus_last_render_time = localtime() 1482 | try 1483 | " remove extra last line 1484 | execute('normal! GV"_X') 1485 | catch // 1486 | endtry 1487 | setlocal nomodifiable 1488 | call cursor(1, 1) 1489 | call self.goto_index_entry("n", 0, 1) 1490 | endfunction 1491 | 1492 | " Renders contexted entry. 1493 | function! catalog_viewer.render_contexted_entry(index, entry, context_size) dict 1494 | let l:lnum = a:entry.lnum 1495 | let l:buf_num = a:entry.buf_num 1496 | let l:matched_line = self.fetch_buf_line(l:buf_num, l:lnum) 1497 | if self.is_pass_filter(l:matched_line) 1498 | let l:buf_name = a:entry.buf_name 1499 | let l:col = a:entry.col 1500 | let l:ln1 = max([1, l:lnum - a:context_size[0]]) 1501 | let l:ln2 = l:lnum + a:context_size[1] 1502 | let l:src_lines = self.fetch_buf_lines(l:buf_num, l:ln1, l:ln2) 1503 | let l:indexed_line_summary = substitute(l:matched_line, '^\s*', '', 'g') 1504 | let l:index_row = self.render_entry_index(a:index) . ' "' . l:buf_name . '", L' . l:ln1 . '-' . l:ln2 . ": " . l:indexed_line_summary 1505 | call self.append_line(l:index_row, a:index, l:buf_num, l:lnum, l:col, 0, 0) 1506 | for l:lnx in range(0, len(l:src_lines)-1) 1507 | let l:src_lnum = l:lnx + l:ln1 1508 | let l:rendered = " " 1509 | " let l:rendered .= repeat(" ", s:buffersaurus_entry_label_field_width + 1) 1510 | if l:src_lnum == l:lnum 1511 | let l:lborder = ">" 1512 | let l:rborder = ">" 1513 | let l:is_matched_line = 1 1514 | else 1515 | let l:lborder = ":" 1516 | let l:rborder = ":" 1517 | let l:is_matched_line = 0 1518 | endif 1519 | let l:rendered .= s:Format_AlignRight(l:src_lnum, s:buffersaurus_lnum_field_width, " ") . " " . l:rborder 1520 | let l:rendered .= " ".l:src_lines[l:lnx] 1521 | call self.append_line(l:rendered, a:index, l:buf_num, l:src_lnum, l:col, 1, l:is_matched_line) 1522 | endfor 1523 | endif 1524 | endfunction 1525 | 1526 | " Renders an uncontexted entry. 1527 | function! catalog_viewer.render_uncontexted_entry(index, entry) dict 1528 | let l:index_field = self.render_entry_index(a:index) 1529 | let l:lnum_field = s:Format_AlignRight(a:entry.lnum, 14 - len(l:index_field), " ") 1530 | let l:src_line = self.fetch_buf_line(a:entry.buf_num, a:entry.lnum) 1531 | if self.is_pass_filter(l:src_line) 1532 | let l:rendered_line = "" . l:index_field . " ".l:lnum_field . ": " . l:src_line 1533 | call self.append_line(l:rendered_line, a:index, a:entry.buf_num, a:entry.lnum, a:entry.col, 1, 1) 1534 | endif 1535 | endfunction 1536 | 1537 | " Renders the index. 1538 | function! catalog_viewer.render_entry_index(index) dict 1539 | return " [" . get(self.catalog.entry_labels, a:index, string(a:index)) . "] " 1540 | endfunction 1541 | 1542 | " Appends a line to the buffer and registers it in the line log. 1543 | function! catalog_viewer.append_line(text, entry_index, jump_to_buf_num, jump_to_lnum, jump_to_col, is_matched_line, is_content_line, ...) dict 1544 | let l:line_map = { 1545 | \ "entry_index" : a:entry_index, 1546 | \ "entry_label" : get(self.catalog.entry_labels, a:entry_index, string(a:entry_index)), 1547 | \ "target" : [a:jump_to_buf_num, a:jump_to_lnum, a:jump_to_col, 0], 1548 | \ "is_matched_line" : a:is_matched_line, 1549 | \ "is_content_line" : a:is_content_line, 1550 | \ } 1551 | if a:0 > 0 1552 | call extend(l:line_map, a:1) 1553 | endif 1554 | let self.jump_map[line("$")] = l:line_map 1555 | call append(line("$")-1, a:text) 1556 | endfunction 1557 | 1558 | " Close and quit the viewer. 1559 | function! catalog_viewer.close(restore_prev_window) dict 1560 | if self.buf_num < 0 || !bufexists(self.buf_num) 1561 | return 1562 | endif 1563 | if a:restore_prev_window 1564 | if !self.is_usable_viewport(winnr("#")) && self.first_usable_viewport() ==# -1 1565 | else 1566 | try 1567 | if !self.is_usable_viewport(winnr("#")) 1568 | execute(self.first_usable_viewport() . "wincmd w") 1569 | else 1570 | execute('wincmd p') 1571 | endif 1572 | catch // 1573 | endtry 1574 | endif 1575 | endif 1576 | execute("bwipe " . self.buf_num) 1577 | endfunction 1578 | 1579 | function! catalog_viewer.highlight_current_line() 1580 | " if line(".") != b:buffersaurus_cur_line 1581 | let l:prev_line = b:buffersaurus_cur_line 1582 | let b:buffersaurus_cur_line = line(".") 1583 | if exists("self.match_highlight_id") && self.match_highlight_id != 0 1584 | try 1585 | call matchdelete(self.match_highlight_id) 1586 | catch // " 803: ID not found 1587 | endtry 1588 | endif 1589 | let self.match_highlight_id = matchadd("BuffersaurusCurrentEntry", '\%'. b:buffersaurus_cur_line .'l') 1590 | " endif 1591 | endfunction 1592 | 1593 | " Clears the buffer contents. 1594 | function! catalog_viewer.clear_buffer() dict 1595 | call cursor(1, 1) 1596 | exec 'silent! normal! "_dG' 1597 | endfunction 1598 | 1599 | " Returns a string corresponding to line `ln1` from buffer ``buf``. 1600 | " If the line is unavailable, then "#INVALID#LINE#" is returned. 1601 | function! catalog_viewer.fetch_buf_line(buf, ln1) 1602 | let l:lines = getbufline(a:buf, a:ln1) 1603 | if len(l:lines) > 0 1604 | return l:lines[0] 1605 | else 1606 | return "#INVALID#LINE#" 1607 | endif 1608 | endfunction 1609 | 1610 | " Returns a list of strings corresponding to the contents of lines from 1611 | " `ln1` to `ln2` from buffer `buf`. If lines are not available, returns a 1612 | " list with (ln2-ln1+1) elements consisting of copies of the string 1613 | " "#INVALID LINE#". 1614 | function! catalog_viewer.fetch_buf_lines(buf, ln1, ln2) 1615 | let l:lines = getbufline(a:buf, a:ln1, a:ln2) 1616 | if len(l:lines) > 0 1617 | return l:lines 1618 | else 1619 | let l:lines = [] 1620 | for l:idx in range(a:ln1, a:ln2) 1621 | call add(l:lines, "#INVALID#LINE#") 1622 | endfor 1623 | return l:lines 1624 | endif 1625 | endfunction 1626 | 1627 | " from NERD_Tree, via VTreeExplorer: determine the number of windows open 1628 | " to this buffer number. 1629 | function! catalog_viewer.num_viewports_on_buffer(bnum) dict 1630 | let cnt = 0 1631 | let winnum = 1 1632 | while 1 1633 | let bufnum = winbufnr(winnum) 1634 | if bufnum < 0 1635 | break 1636 | endif 1637 | if bufnum ==# a:bnum 1638 | let cnt = cnt + 1 1639 | endif 1640 | let winnum = winnum + 1 1641 | endwhile 1642 | return cnt 1643 | endfunction 1644 | 1645 | " from NERD_Tree: find the window number of the first normal window 1646 | function! catalog_viewer.first_usable_viewport() dict 1647 | let i = 1 1648 | while i <= winnr("$") 1649 | let bnum = winbufnr(i) 1650 | if bnum != -1 && getbufvar(bnum, '&buftype') ==# '' 1651 | \ && !getwinvar(i, '&previewwindow') 1652 | \ && (!getbufvar(bnum, '&modified') || &hidden) 1653 | return i 1654 | endif 1655 | 1656 | let i += 1 1657 | endwhile 1658 | return -1 1659 | endfunction 1660 | 1661 | " from NERD_Tree: returns 0 if opening a file from the tree in the given 1662 | " window requires it to be split, 1 otherwise 1663 | function! catalog_viewer.is_usable_viewport(winnumber) dict 1664 | "gotta split if theres only one window (i.e. the NERD tree) 1665 | if winnr("$") ==# 1 1666 | return 0 1667 | endif 1668 | let oldwinnr = winnr() 1669 | execute(a:winnumber . "wincmd p") 1670 | let specialWindow = getbufvar("%", '&buftype') != '' || getwinvar('%', '&previewwindow') 1671 | let modified = &modified 1672 | execute(oldwinnr . "wincmd p") 1673 | "if its a special window e.g. quickfix or another explorer plugin then we 1674 | "have to split 1675 | if specialWindow 1676 | return 0 1677 | endif 1678 | if &hidden 1679 | return 1 1680 | endif 1681 | return !modified || self.num_viewports_on_buffer(winbufnr(a:winnumber)) >= 2 1682 | endfunction 1683 | 1684 | " Acquires a viewport to show the source buffer. Returns the split command 1685 | " to use when switching to the buffer. 1686 | function! catalog_viewer.acquire_viewport(split_cmd) 1687 | if self.split_mode == "buffer" && empty(a:split_cmd) 1688 | " buffersaurus used original buffer's viewport, 1689 | " so the the buffersaurus viewport is the viewport to use 1690 | return "" 1691 | endif 1692 | if !self.is_usable_viewport(winnr("#")) && self.first_usable_viewport() ==# -1 1693 | " no appropriate viewport is available: create new using default 1694 | " split mode 1695 | " TODO: maybe use g:buffersaurus_viewport_split_policy? 1696 | if empty(a:split_cmd) 1697 | return "sb" 1698 | else 1699 | return a:split_cmd 1700 | endif 1701 | else 1702 | try 1703 | if !self.is_usable_viewport(winnr("#")) 1704 | execute(self.first_usable_viewport() . "wincmd w") 1705 | else 1706 | execute('wincmd p') 1707 | endif 1708 | catch /^Vim\%((\a\+)\)\=:E37/ 1709 | echo v:exception 1710 | " call s:putCursorInTreeWin() 1711 | " throw "NERDTree.FileAlreadyOpenAndModifiedError: ". self.path.str() ." is already open and modified." 1712 | catch /^Vim\%((\a\+)\)\=:/ 1713 | echo v:exception 1714 | endtry 1715 | return a:split_cmd 1716 | endif 1717 | endfunction 1718 | 1719 | " Perform run command on all lines in the catalog 1720 | function! catalog_viewer.execute_command(command_text, include_context_lines, rebuild_catalog) dict 1721 | if a:command_text == "" 1722 | let l:command_text = input("Command: ") 1723 | else 1724 | let l:command_text = a:command_text 1725 | endif 1726 | let catalog_buf_num = bufnr("%") 1727 | let catalog_buf_pos = getpos(".") 1728 | let working_buf_num = catalog_buf_num 1729 | let start_pos = getpos(".") 1730 | for l:cur_line in range(1, line("$")) 1731 | if !has_key(l:self.jump_map, l:cur_line) 1732 | continue 1733 | endif 1734 | let l:jump_entry = self.jump_map[l:cur_line] 1735 | if (!l:jump_entry.is_matched_line) && !(a:include_context_lines && l:jump_entry.is_content_line) 1736 | continue 1737 | endif 1738 | let [l:jump_to_buf_num, l:jump_to_lnum, l:jump_to_col, l:dummy] = l:jump_entry.target 1739 | if l:jump_to_buf_num != working_buf_num 1740 | if working_buf_num != catalog_buf_num 1741 | call setpos('.', start_pos) 1742 | endif 1743 | endif 1744 | try 1745 | execute("silent! keepalt keepjumps buffer " . l:jump_to_buf_num) 1746 | catch // 1747 | continue 1748 | endtry 1749 | let working_buf_num = l:jump_to_buf_num 1750 | let start_pos = getpos(".") 1751 | " execute "silent! " . l:jump_to_lnum . command_text 1752 | execute "" . l:jump_to_lnum . command_text 1753 | endfor 1754 | if working_buf_num != catalog_buf_num 1755 | call setpos('.', start_pos) 1756 | endif 1757 | execute("silent! keepalt keepjumps buffer " . catalog_buf_num) 1758 | if a:rebuild_catalog 1759 | call self.rebuild_catalog() 1760 | endif 1761 | call setpos('.', catalog_buf_pos) 1762 | endfunction 1763 | 1764 | " Visits the specified buffer in the previous window, if it is already 1765 | " visible there. If not, then it looks for the first window with the 1766 | " buffer showing and visits it there. If no windows are showing the 1767 | " buffer, ... ? 1768 | function! catalog_viewer.visit_buffer(buf_num, split_cmd) dict 1769 | " acquire window 1770 | let l:split_cmd = self.acquire_viewport(a:split_cmd) 1771 | " switch to buffer in acquired window 1772 | let l:old_switch_buf = &switchbuf 1773 | if empty(l:split_cmd) 1774 | " explicit split command not given: switch to buffer in current 1775 | " window 1776 | let &switchbuf="useopen" 1777 | execute("silent keepalt keepjumps buffer " . a:buf_num) 1778 | else 1779 | " explcit split command given: split current window 1780 | let &switchbuf="split" 1781 | execute("silent keepalt keepjumps " . l:split_cmd . " " . a:buf_num) 1782 | endif 1783 | let &switchbuf=l:old_switch_buf 1784 | endfunction 1785 | 1786 | " Go to the line mapped to by the current line/index of the catalog 1787 | " viewer. 1788 | function! catalog_viewer.visit_target(keep_catalog, refocus_catalog, split_cmd) dict 1789 | let l:cur_line = line(".") 1790 | if !has_key(l:self.jump_map, l:cur_line) 1791 | call s:_buffersaurus_messenger.send_info("Not a valid navigation line") 1792 | return 0 1793 | endif 1794 | let [l:jump_to_buf_num, l:jump_to_lnum, l:jump_to_col, l:dummy] = self.jump_map[l:cur_line].target 1795 | let l:cur_tab_num = tabpagenr() 1796 | if !a:keep_catalog 1797 | call self.close(0) 1798 | endif 1799 | call self.visit_buffer(l:jump_to_buf_num, a:split_cmd) 1800 | call setpos('.', [l:jump_to_buf_num, l:jump_to_lnum, l:jump_to_col, l:dummy]) 1801 | execute(s:buffersaurus_post_move_cmd) 1802 | if g:buffersaurus_flash_jumped_line 1803 | exec 'silent! match BuffersaurusFlashMatchedLineHighlight1 /\%'. line('.') .'l.*/' 1804 | redraw 1805 | sleep 75m 1806 | exec 'silent! match BuffersaurusFlashMatchedLineHighlight2 /\%'. line('.') .'l.*/' 1807 | redraw 1808 | sleep 75m 1809 | exec 'silent! match BuffersaurusFlashMatchedLineHighlight1 /\%'. line('.') .'l.*/' 1810 | redraw 1811 | sleep 75m 1812 | exec 'silent! match BuffersaurusFlashMatchedLineHighlight2 /\%'. line('.') .'l.*/' 1813 | redraw 1814 | sleep 75m 1815 | match none 1816 | endif 1817 | if a:keep_catalog && a:refocus_catalog 1818 | execute("tabnext " . l:cur_tab_num) 1819 | execute(bufwinnr(self.buf_num) . "wincmd w") 1820 | endif 1821 | let l:report = "" 1822 | if self.jump_map[l:cur_line].entry_index >= 0 1823 | let l:report .= "(" . string(self.jump_map[l:cur_line].entry_index + 1). " of " . self.catalog.size() . "): " 1824 | let l:report .= '"' . expand(bufname(l:jump_to_buf_num)) . '", Line ' . l:jump_to_lnum 1825 | else 1826 | let l:report .= 'File: "' . expand(bufname(l:jump_to_buf_num)) . '"' 1827 | endif 1828 | 1829 | call s:_buffersaurus_messenger.send_info(l:report) 1830 | endfunction 1831 | 1832 | " Finds next line with occurrence of a rendered index 1833 | function! catalog_viewer.goto_index_entry(direction, visit_target, refocus_catalog) dict 1834 | let l:ok = self.goto_pattern("^ \[", a:direction) 1835 | execute("normal! zz") 1836 | if l:ok && a:visit_target 1837 | call self.visit_target(1, a:refocus_catalog, "") 1838 | endif 1839 | endfunction 1840 | 1841 | " Finds next line with occurrence of a file pattern. 1842 | function! catalog_viewer.goto_file_start(direction, visit_target, refocus_catalog) dict 1843 | let l:ok = self.goto_pattern("^:::", a:direction) 1844 | execute("normal! zz") 1845 | if l:ok && a:visit_target 1846 | call self.visit_target(1, a:refocus_catalog, "") 1847 | endif 1848 | endfunction 1849 | 1850 | " Finds next occurrence of specified pattern. 1851 | function! catalog_viewer.goto_pattern(pattern, direction) dict range 1852 | if a:direction == "b" || a:direction == "p" 1853 | let l:flags = "b" 1854 | " call cursor(line(".")-1, 0) 1855 | else 1856 | let l:flags = "" 1857 | " call cursor(line(".")+1, 0) 1858 | endif 1859 | if g:buffersaurus_move_wrap 1860 | let l:flags .= "W" 1861 | else 1862 | let l:flags .= "w" 1863 | endif 1864 | let l:flags .= "e" 1865 | let l:lnum = -1 1866 | for i in range(v:count1) 1867 | if search(a:pattern, l:flags) < 0 1868 | break 1869 | else 1870 | let l:lnum = 1 1871 | endif 1872 | endfor 1873 | if l:lnum < 0 1874 | if l:flags[0] == "b" 1875 | call s:_buffersaurus_messenger.send_info("No previous results") 1876 | else 1877 | call s:_buffersaurus_messenger.send_info("No more results") 1878 | endif 1879 | return 0 1880 | else 1881 | return 1 1882 | endif 1883 | endfunction 1884 | 1885 | " Toggles context on/off. 1886 | function! catalog_viewer.toggle_context() dict 1887 | let self.catalog.show_context = !self.catalog.show_context 1888 | let l:line = line(".") 1889 | if has_key(b:buffersaurus_catalog_viewer.jump_map, l:line) 1890 | let l:jump_line = b:buffersaurus_catalog_viewer.jump_map[l:line] 1891 | if l:jump_line.entry_index > 0 1892 | let l:entry_index = l:jump_line.entry_index 1893 | elseif has_key(l:jump_line, "proxy_key") 1894 | let l:entry_index = l:jump_line.proxy_key 1895 | else 1896 | let l:entry_index = "" 1897 | endif 1898 | else 1899 | let l:entry_index = "" 1900 | endif 1901 | call self.open(1) 1902 | if !empty(l:entry_index) 1903 | let l:rendered_entry_index = self.render_entry_index(l:entry_index) 1904 | let l:lnum = search('^'.escape(l:rendered_entry_index, '[]'), "e") 1905 | if l:lnum > 0 1906 | call setpos(".", [bufnr("%"), l:lnum, 0, 0]) 1907 | execute("normal! zz") 1908 | endif 1909 | endif 1910 | endfunction 1911 | 1912 | " Cycles sort regime. 1913 | function! catalog_viewer.cycle_sort_regime() dict 1914 | call self.catalog.cycle_sort_regime() 1915 | call self.open(1) 1916 | call s:_buffersaurus_messenger.send_info("sorted " . self.catalog.format_sort_status()) 1917 | endfunction 1918 | 1919 | " Rebuilds catalog. 1920 | function! catalog_viewer.rebuild_catalog() dict 1921 | call self.catalog.rebuild() 1922 | call s:_buffersaurus_messenger.send_info("updated index: found " . self.catalog.format_status_message()) 1923 | call self.open(1) 1924 | endfunction 1925 | 1926 | " Cycles autodismiss modes 1927 | function! catalog_viewer.cycle_autodismiss_modes() dict 1928 | if (g:buffersaurus_autodismiss_on_select) 1929 | let g:buffersaurus_autodismiss_on_select = 0 1930 | call s:_buffersaurus_messenger.send_info("will stay open on selection (autodismiss-on-select: OFF)") 1931 | else 1932 | let g:buffersaurus_autodismiss_on_select = 1 1933 | call s:_buffersaurus_messenger.send_info("will close on selection (autodismiss-on-select: ON)") 1934 | endif 1935 | endfunction 1936 | 1937 | " return object 1938 | return catalog_viewer 1939 | 1940 | endfunction 1941 | 1942 | " 1}}} 1943 | 1944 | " Command Interface {{{1 1945 | " ============================================================================= 1946 | 1947 | function! s:ComposeBufferTargetList(bang) 1948 | if (exists('g:buffersaurus_default_single_file') && g:buffersaurus_default_single_file && empty(a:bang)) 1949 | \ || !empty(a:bang) 1950 | return ["%"] 1951 | else 1952 | return "" 1953 | endif 1954 | endfunction 1955 | 1956 | function! s:ActivateCatalog(domain, catalog) 1957 | let s:_buffersaurus_last_catalog_built = a:catalog 1958 | let s:_buffersaurus_last_catalog_viewed = a:catalog.open() 1959 | if a:catalog.size() > 0 1960 | call a:catalog.describe() 1961 | else 1962 | call s:_buffersaurus_messenger.send_status("no matches") 1963 | endif 1964 | endfunction 1965 | 1966 | function! s:GetLastActiveCatalog() 1967 | if !exists("s:_buffersaurus_last_catalog_viewed") && !exists("s:_buffersaurus_last_catalog_built") 1968 | return 0 1969 | endif 1970 | if exists("s:_buffersaurus_last_catalog_viewed") 1971 | let catalog = s:_buffersaurus_last_catalog_viewed.catalog 1972 | elseif exists("s:_buffersaurus_last_catalog_built") 1973 | let catalog = s:_buffersaurus_last_catalog_built.catalog 1974 | endif 1975 | return catalog 1976 | endfunction 1977 | 1978 | function! buffersaurus#IndexTerms(term_name, bang, sort_regime) 1979 | let l:worklist = s:ComposeBufferTargetList(a:bang) 1980 | let catalog = s:_buffersaurus_indexer.index_terms(l:worklist, a:term_name, a:sort_regime) 1981 | call s:ActivateCatalog("term", catalog) 1982 | endfunction 1983 | 1984 | function! buffersaurus#IndexTags(bang) 1985 | let l:worklist = s:ComposeBufferTargetList(a:bang) 1986 | let catalog = s:_buffersaurus_indexer.index_tags(l:worklist) 1987 | call s:ActivateCatalog("tags", catalog) 1988 | endfunction 1989 | 1990 | function! buffersaurus#GlobalSearchAndReplace() 1991 | let l:pattern = input("Search for: ", s:last_searched_pattern) 1992 | if empty(l:pattern) 1993 | return 1994 | endif 1995 | let l:worklist = s:ComposeBufferTargetList(0) 1996 | let catalog = s:_buffersaurus_indexer.index_pattern(l:worklist, l:pattern, '') 1997 | let s:last_searched_pattern = l:pattern 1998 | let s:_buffersaurus_last_catalog_built = catalog 1999 | let s:_buffersaurus_last_catalog_viewed = catalog.open() 2000 | if catalog.size() > 0 2001 | call catalog.describe() 2002 | call s:_buffersaurus_last_catalog_viewed.search_and_replace(0, "", 1) 2003 | else 2004 | call s:_buffersaurus_messenger.send_status("no matches") 2005 | endif 2006 | endfunction 2007 | 2008 | function! buffersaurus#IndexPatterns(pattern, bang, sort_regime) 2009 | if empty(a:pattern) 2010 | call s:_buffersaurus_messenger.send_error("search pattern must be specified") 2011 | return 2012 | endif 2013 | let l:worklist = s:ComposeBufferTargetList(a:bang) 2014 | let catalog = s:_buffersaurus_indexer.index_pattern(l:worklist, a:pattern, a:sort_regime) 2015 | let s:last_searched_pattern = a:pattern 2016 | call s:ActivateCatalog("pattern", catalog) 2017 | if !exists("g:buffersaurus_set_search_register") || g:buffersaurus_set_search_register 2018 | let @/=a:pattern 2019 | endif 2020 | " if !exists("g:buffersaurus_set_search_highlight") || g:buffersaurus_set_search_highlight 2021 | " set hlsearch 2022 | " endif 2023 | endfunction 2024 | 2025 | function! buffersaurus#OpenLastActiveCatalog() 2026 | if !exists("s:_buffersaurus_last_catalog_viewed") && !exists("s:_buffersaurus_last_catalog_built") 2027 | call s:_buffersaurus_messenger.send_error("No index available for viewing") 2028 | return 0 2029 | elseif exists("s:_buffersaurus_last_catalog_viewed") 2030 | call s:_buffersaurus_last_catalog_viewed.open() 2031 | elseif exists("s:_buffersaurus_last_catalog_built") 2032 | let s:_buffersaurus_last_catalog_viewed = s:_buffersaurus_last_catalog_built.open() 2033 | endif 2034 | return 1 2035 | endfunction 2036 | 2037 | function! buffersaurus#GotoEntry(direction) 2038 | if buffersaurus#OpenLastActiveCatalog() 2039 | call s:_buffersaurus_last_catalog_viewed.goto_index_entry(a:direction, 1, 0) 2040 | endif 2041 | endfunction 2042 | 2043 | function! buffersaurus#ShowCatalogStatus(full) 2044 | let catalog = s:GetLastActiveCatalog() 2045 | if type(catalog) == type(0) && catalog == 0 2046 | call s:_buffersaurus_messenger.send_error("No index available") 2047 | elseif empty(a:full) 2048 | call catalog.describe() 2049 | else 2050 | call catalog.describe_detail() 2051 | endif 2052 | endfunction 2053 | 2054 | " 1}}} 2055 | 2056 | " Global Functions {{{1 2057 | " ============================================================================== 2058 | 2059 | function! BuffersaurusStatusLineCurrentLineInfo() 2060 | if !exists("b:buffersaurus_catalog_viewer") 2061 | return "[not a valid catalog]" 2062 | endif 2063 | let l:line = line(".") 2064 | let l:status_line = " | " 2065 | if b:buffersaurus_catalog_viewer.filter_regime && !empty(b:buffersaurus_catalog_viewer.filter_pattern) 2066 | let l:status_line .= "*filtered* | " 2067 | endif 2068 | if has_key(b:buffersaurus_catalog_viewer.jump_map, l:line) 2069 | let l:jump_line = b:buffersaurus_catalog_viewer.jump_map[l:line] 2070 | if l:jump_line.entry_index >= 0 2071 | let l:status_line .= string(l:jump_line.entry_index + 1) . " of " . b:buffersaurus_catalog_viewer.catalog.size() 2072 | let l:status_line .= " | " 2073 | let l:status_line .= 'File: "' . expand(bufname(l:jump_line.target[0])) 2074 | let l:status_line .= '" (L:' . l:jump_line.target[1] . ', C:' . l:jump_line.target[2] . ')' 2075 | else 2076 | let l:status_line .= '(Indexed File) | "' . expand(bufname(l:jump_line.target[0])) . '"' 2077 | endif 2078 | else 2079 | let l:status_line .= "(not a valid indexed line)" 2080 | endif 2081 | return l:status_line 2082 | endfunction 2083 | 2084 | function! BuffersaurusStatusLineSortRegime() 2085 | if exists("b:buffersaurus_catalog_viewer") 2086 | let l:sort_desc = get(s:buffersaurus_catalog_sort_regime_desc, b:buffersaurus_catalog_viewer.catalog.sort_regime, ["??", "invalid sort"]) 2087 | return "sort: " . l:sort_desc[0] . "" 2088 | else 2089 | return "" 2090 | endif 2091 | endfunction 2092 | 2093 | function! BuffersaurusStatusLineFilterRegime() 2094 | if exists("b:buffersaurus_catalog_viewer") 2095 | if b:buffersaurus_catalog_viewer.filter_regime && !empty(b:buffersaurus_catalog_viewer.filter_pattern) 2096 | return "filter: /" . b:buffersaurus_catalog_viewer.filter_pattern . "/" 2097 | else 2098 | return "filter: OFF" 2099 | endif 2100 | else 2101 | return "" 2102 | endif 2103 | endfunction 2104 | 2105 | function! BuffersaurusFoldText() 2106 | return substitute(getline(v:foldstart), '^\s\{-1,}\(\[\s*\d\+\s*\]\) .\{-1,}, L\d\+-\d\+: ', '\1 ', "g") 2107 | endfunction 2108 | " 1}}} 2109 | 2110 | " Global Initialization {{{1 2111 | " ============================================================================== 2112 | if exists("s:_buffersaurus_buffer_manager") 2113 | unlet s:_buffersaurus_buffer_manager 2114 | endif 2115 | let s:_buffersaurus_buffer_manager = s:NewBufferManager() 2116 | let s:last_searched_pattern = "" 2117 | if exists("s:_buffersaurus_messenger") 2118 | unlet s:_buffersaurus_messenger 2119 | endif 2120 | let s:_buffersaurus_messenger = s:NewMessenger("") 2121 | if exists("s:_buffersaurus_indexer") 2122 | unlet s:_buffersaurus_indexer 2123 | endif 2124 | let s:_buffersaurus_indexer = s:NewIndexer() 2125 | hi! BuffersaurusFlashMatchedLineHighlight1 guifg=#000000 guibg=#ff00ff ctermfg=0 ctermbg=164 term=reverse 2126 | hi! BuffersaurusFlashMatchedLineHighlight2 guifg=#ff00ff guibg=#000000 ctermfg=164 ctermbg=0 term=reverse 2127 | " 1}}} 2128 | 2129 | " Completion {{{1 2130 | " ============================================================================== 2131 | function! buffersaurus#Complete_bsterm(A,L,P) 2132 | let l:possible_matchs = sort(keys(s:_buffersaurus_indexer["element_term_map"])) 2133 | if len(a:A) == 0 2134 | return l:possible_matchs 2135 | endif 2136 | call filter(l:possible_matchs, 'v:val[:' . (len(a:A)-1) . '] ==? ''' . substitute(a:A, "'", "''", 'g') . '''') 2137 | return possible_matchs 2138 | endfunction 2139 | " 1}}} 2140 | 2141 | " Restore State {{{1 2142 | " ============================================================================ 2143 | " restore options 2144 | let &cpo = s:save_cpo 2145 | " 1}}} 2146 | 2147 | " vim:foldlevel=4: 2148 | -------------------------------------------------------------------------------- /doc/buffersaurus.txt: -------------------------------------------------------------------------------- 1 | *buffersaurus.txt* For Vim version 7.3 Last change: 2011 December 20 2 | 3 | =============================================================================== 4 | CONTENTS *buffersaurus* *buffersaurus-contents* 5 | 6 | 1. Introduction ....................... |buffersaurus-introduction| 7 | 2. Basic Usage ........................ |buffersaurus-basic-usage| 8 | 3. Special Searches ................... |buffersaurus-special-searches| 9 | 3. Special Operations ................. |buffersaurus-special-operations| 10 | 4. Commands ........................... |buffersaurus-commands| 11 | 5. Key Mappings ....................... |buffersaurus-keys| 12 | 6. Options and Settings ............... |buffersaurus-options| 13 | 14 | =============================================================================== 15 | INTRODUCTION *buffersaurus-introduction* 16 | 17 | Buffersaurus is a plugin for searching and indexing the contents of buffers 18 | for regular expression patterns or collections of regular expression patterns. 19 | Results are displayed in separate buffer, and can be (optionally) viewed with 20 | user-specifiable lines of context (i.e., a number of lines before and/or after 21 | the line matching the search pattern) or filtered for specific patterns. 22 | 23 | =============================================================================== 24 | BASIC USAGE *buffersaurus-basic-usage* 25 | 26 | The command "|:Bsgrep| {pattern}" is the most general way of invoking 27 | Buffersaurus. It will search all listed buffers for lines of text matching the 28 | given regular expression pattern. If you want Buffersaurus to search ONLY the 29 | current buffer, then follow the command with a bang operator to restrict the 30 | search scope: ":Bsgrep! {pattern}". 31 | 32 | The results are stored in memory (the "catalog"), and a new window (the 33 | "catalog viewer") will open up to display the results. Each matching line will 34 | result in an index entry in the catalog, and by default be displayed in its 35 | own row, grouped by the buffer file in which it occurs. You can choose to list 36 | the lines ungrouped, and sorted by line number, by filepath + line number, or 37 | alphabetically. 38 | 39 | All standard Vim movement keys (e.g., "h", "j", "k", "l", , etc.) 40 | work within the catalog viewer. Moving to an entry (i.e., indexed line) and 41 | typing (or "o") will open the target buffer in a previous window and go 42 | to the the matching line. Numerous other key maps are available to customize 43 | how the matching line is opened: e.g., a new vertical split ("s"), horizontal 44 | split ("i"), or tab page ("t"). In addition, the target can be opened 45 | "silently" or previewed in the background in the previous window ("go" or 46 | "O"), new "vertical split ("gs" or "S"), new horizontal split ("gi" or "I") or 47 | new tab page ("T"). Special movement keys are available to quickly take you 48 | to the next or previous file group ("f" and "F", respectively). You can also 49 | simultaneously go to the next or previous indexed line and preview it ( 50 | and ", respectively) without leaving the catalog viewer. 51 | 52 | When in the catalog viewer, you can cycle through the sort regimes by typing 53 | "cs", or refresh the catalog (re-index the lines) by pressing "r". 54 | 55 | A special feature is the "contexted" mode. You can type "cc" (or "C") to 56 | toggle contexted mode on and off. When contexted mode is off, each indexed 57 | line is displayed by itself on a single row. After you press "cc" to switch on 58 | contexted mode, each indexed line is shown with a number of lines of context 59 | that occur before and after it in the source file. The exact number of lines 60 | is controlled by the "g:buffersaurus_context_size" variable. By default, this 61 | is set to "[4, 4]", which means to show 4 lines before and 4 lines after the 62 | matched line. You can set this to any pair of values you prefer in your 63 | $VIMRUNTIME. When viewing results in contexted mode, multiple levels of 64 | folding are available, to allow you to fold away context or file groups. 65 | 66 | If you want to refine the results further, you can use the command ":Bsfilter 67 | {filter-pattern}" from within the catalog viewer. This will filter out any 68 | indexed lines that do not match "{filter-pattern}". The filter can be switched 69 | off by ":Bsfilter!". 70 | 71 | You can close the catalog viewer by pressing "q". The last search results are 72 | retained in memory, and the command "|:Bsopen|" will open the viewer again to 73 | re-display these; you may want to type "r" to re-run the search if any of your 74 | buffers have changed. 75 | 76 | Once you have carried out a search and jumped to a matched line, and/or closed 77 | the catalog viewer, you can jump to the next or previous matching line 78 | directly from a working listed buffer without going back to re-opening the 79 | catalog viewer. You can just use the commands "|:Bsnext|" or "|:Bsprev|". For 80 | convenience, you may want to map some keys to these commands if you find 81 | yourself using them a lot. 82 | 83 | In additon, Buffersaurus supports substitution operations ("|:Bssubstitute|" or 84 | "R"//"&") or even the execution of arbitrary commands on the matched 85 | lines (|buffersaurus-special-operations|). A global command for searching and 86 | replacing ("|:Bsreplace|") is also provided. 87 | 88 | =============================================================================== 89 | SPECIAL SEARCHES *buffersaurus-special-searches* 90 | 91 | Buffersaurus provides two other approaches to search beyond the explicitly 92 | single regular expression pattern-based approach described above. 93 | 94 | ------------------------------------------------------------------------------- 95 | *buffersaurus-filetype-patterns* 96 | "Table-of-Contents" Search~ 97 | 98 | For some types of files, a "table-of-contents" can be generated using the 99 | command "|:Bstoc|". This will search for a set of patterns that define 100 | "important" terms given the file types. For example, if dealing with common 101 | programming language source code files, all class and method/function 102 | definition lines are targeted. The results can be browsed and navigated just 103 | as above. Note that this only works if the filetype is known, and some clearly 104 | defined terms are available for that filetype: Python, C++, C, etc. With 105 | other filetypes, e.g. text, the pattern defaults to any lines with a 106 | non-whitepsace as the first character, which may or may not be useful. In 107 | these, as well as other cases, it makes sense to use the "|:Bsterm|" command. 108 | 109 | You can add more definitions by providing a |Dictionary| in the variable 110 | |g:buffersaurus_filetype_term_map|. Keys should be the file types (as in the 111 | |'filetype'| setting), and value should be a pattern that globs any keywords. 112 | 113 | ------------------------------------------------------------------------------- 114 | *buffersaurus-term-patterns* 115 | "Term" Search~ 116 | 117 | The command "|:Bsterm| {term-name}" takes a single argument, which should be a 118 | key defined in a dictionary that maps term-names to regular expression 119 | patterns. Executing this command will search for the regular expression 120 | referenced by the term. In essence, you can consider terms as "pre-defined" or 121 | "saved" search patterns, that can quickly and easily be run when needed . 122 | 123 | Buffersaurus comes with some example terms pre-defined. 124 | 125 | * PyClass and PyDef will match Python class definitions and function definitions 126 | * CppClass, CppEnum, CppPreProc, CppTypedef, CppTemplate will match C++ 127 | classes and struct definitions, enums, preprocessor macros, typedefs and 128 | templates 129 | * VimCommand, VimFunction and VimMapping will match commands, functions and 130 | mappings definitions inside vimscripts 131 | 132 | "PyClass" : '^\s*class\s\+[A-Za-z_]\i\+(.*', 133 | "PyDef" : '^\s*def\s\+[A-Za-z_]\i\+(.*', 134 | 135 | You can add more definitions by providing the dictionary definition in your 136 | |$VIMRUNTIME| using the variable "|g:buffersaurus_element_term_map|". These 137 | definitions will extend Buffersaurus' built-in defintions. 138 | 139 | =============================================================================== 140 | SPECIAL OPERATIONS *buffersaurus-special-operations* 141 | 142 | Buffersaurus lets you execute arbitrary commands on matched lines 143 | using the "x" and "X" key maps. A specialized global replace operation is 144 | available via the ":|Bssubstitute|" command or "R"/""/"&" key maps. 145 | 146 | =============================================================================== 147 | COMMANDS *buffersaurus-commands* 148 | 149 | ------------------------------------------------------------------------------- 150 | Catalog generation commands~ 151 | 152 | Those commands generate a catalog. 153 | If |g:buffersaurus_default_single_file| is not defined or false, then all 154 | buffers will be searched, otherwise only the current buffer will be searched. 155 | However the banged versions of the commands will invert this behaviour. 156 | 157 | :Bsg[rep][!] {search-pattern} *:Bsgrep* 158 | Search buffer(s) for the regular expression |pattern| 159 | given by {search-pattern}. 160 | If "|g:buffersaurus_set_search_register|" is not false, 161 | then the pattern will also be assigned to the search 162 | register. 163 | 164 | :Bstoc[!] *:Bstoc* 165 | Generate a "table of contents" type catalog for buffers, 166 | based on the specific filetype of each buffer. 167 | See |buffersaurus-filetype-patterns| for more information 168 | on the patterns to be matched. 169 | 170 | :Bsterm[!] {term-name} *:Bsterm* 171 | Generate a catalog of occurrences of the term pattern 172 | named {term-name} in buffers. 173 | See |buffersaurus-term-patterns| for information on 174 | defining and using terms. 175 | 176 | :Bsreplace *:Bsreplace* 177 | Global search and replace. Will be prompted for a pattern 178 | for which to search for, and, if any matches, a string 179 | with which to replace the pattern. 180 | 181 | ------------------------------------------------------------------------------- 182 | Other commands~ 183 | 184 | :Bso[pen] *:Bsopen* 185 | (Re-)open the most recent catalog. 186 | 187 | :Bsn[ext] *:Bsnext* 188 | Go to the next matched line from the most recent catalog 189 | (like |:cnext|). 190 | 191 | :Bsp[rev] *:Bsprev* 192 | Go to the previous matched line from the most recent 193 | catalog (like |:cprev|). 194 | 195 | :Bsi[nfo][!] *:Bsinfo* 196 | Show brief (without "!") or detailed (with "!") 197 | description of the most recent catalog. 198 | 199 | :Bsf[ilter][!] [filter-pattern] *:Bsfilter* 200 | [Only available when inside a Buffergator catalog viewer 201 | buffer] Without "!": filter results to only show lines 202 | matching the regular expression |pattern| 203 | [filter-pattern]. If [filter-pattern] is not given, then 204 | the previous filter pattern will be used. If there is no 205 | previous filter pattern, then the user will be prompted 206 | for a new pattern. With "!": remove filter. In all cases 207 | if [filter-pattern] is "*" or ".*", then the saved pattern 208 | will be cleared and the filter removed. 209 | 210 | :Bss[ubstitute][!] [substitute-pattern-string] *:Bssubstitute* 211 | [Only available when inside a Buffergator catalog viewer 212 | buffer] Apply substitution command (|:substitute|) using 213 | [substitute-pattern-string] to all listed (unfilterd) 214 | matched lines. If "!" is specified, then substitution will 215 | be applied to all listed context lines, if any, as well. 216 | [substitute-pattern-string] is anything that follows 217 | the |:substitute| command, e.g. 218 | > 219 | :Bssub /foo/bar/gc 220 | :Bssub @field@plain@e 221 | :Bssub /def\s\+pr\(\w\+\)_out/def pr\1_err/g 222 | < 223 | If [substitution-pattern-string] is not given, then you 224 | will be prompted for a pattern to search for and a string 225 | with which to replace it. In this case, the flags "ge" 226 | will automatically be applied. 227 | 228 | =============================================================================== 229 | KEY MAPPINGS (CATALOG VIEWER) *buffersaurus-keys* 230 | 231 | Search results are stored a "catalog", and displayed in special buffer 232 | referred to as a "catalog viewer". The following key mappings are available 233 | in the catalog viewer. 234 | 235 | ------------------------------------------------------------------------------- 236 | Catalog Management~ 237 | 238 | cc, C Toggle contexted view on/off. 239 | Also see |g:buffersaurus_show_context| 240 | P Toggle flashing of indexed line on/off 241 | Also see |g:buffersaurus_flash_jumped_line| 242 | cs Cycle through sort regimes. 243 | Also see |g:buffersaurus_sort_regime| 244 | cq Toggle "pinning": whether or the catalog viewer is closed 245 | on selection. See |g:buffersaurus_autodismiss_on_select|. 246 | f Filter results for lines matching pattern. 247 | F Enter and apply a new filter pattern 248 | r Rebuild/refresh the index. 249 | Show short status of index/catalog. 250 | g Show detailed status of index/catalog. 251 | q Quit the index/catalog window. 252 | 253 | ------------------------------------------------------------------------------- 254 | Movement Within the Catalog Viewer~ 255 | 256 | Go to the next index entry. 257 | Go to the previous index entry. 258 | ]f Go to the next file entry. 259 | [f Go the previous file entry. 260 | 261 | ------------------------------------------------------------------------------- 262 | Mappings That Jump To Results~ 263 | 264 | Several mappings are defined so that you can navigate quickly. A first series 265 | of mappings jumps to the target and hides the catalog viewer if the 266 | autodismiss settings is on (default). A second series will always leave the 267 | catalog viewer window visible (mappings begin with p) and a third series will 268 | keep the focus on it (mappings in capital letters). 269 | For each series there is a mapping that displays the target in the previous 270 | window (involving letter o), two mappings display it in a new split (s for a 271 | vertical split, i for an horizontal split), and a mapping displays the target 272 | in a new tab (t). 273 | 274 | +--------------------------------------+-----------+----------+----------+---------+ 275 | | Location of the target | previous | vert | horiz | new | 276 | | | window | split | split | tab | 277 | +--------------------------------------+-----------+----------+----------+---------+ 278 | | Jump to target and dismiss the | | | | | 279 | | catalog viewer if | , o | , s | , i | t | 280 | | |g:buffersaurus_autodismiss_on_select| | | | | | 281 | +--------------------------------------+-----------+----------+----------+---------+ 282 | | Jump to target and leave the | po | ps | pi | pt | 283 | | catalog viewer visible | | | | | 284 | +--------------------------------------+-----------+----------+----------+---------+ 285 | | Preview target, keep focus on the | O, go | S, gs | I, gi | T | 286 | | catalog viewer | | | | | 287 | | (next entry) | | | | | 288 | | (prev entry) | | | | | 289 | +--------------------------------------+-----------+----------+----------+---------+ 290 | 291 | ------------------------------------------------------------------------------- 292 | Mappings That Carry Out Special Operations~ 293 | 294 | x Execute command on all matched lines. 295 | X Execute command all all matched and all context lines. 296 | & Replace search pattern on all matched lines (will prompt for 297 | replace string). 298 | Replace search pattern on all matched lines (will prompt for 299 | replace string). 300 | R Search and replace operation on all matched lines (will 301 | prompt for search pattern as well as replace string). 302 | 303 | =============================================================================== 304 | OPTIONS AND SETTINGS *buffersaurus-options* 305 | 306 | The following options can be used to customize the behavior of this plugin. 307 | 308 | g:buffersaurus_viewport_split_policy~ 309 | Default: "B" *g:buffersaurus_viewport_split_policy* 310 | Determines how a new Buffersaurus window will be opened. Can be one of the 311 | following values: 312 | "d" : user-specified Vim default 313 | "D" : user-specified Vim default 314 | "N" : no split, reuse current buffer 315 | "n" : no split, reuse current buffer 316 | "L" : vertical left (full screen height) 317 | "l" : vertical left (within current window) 318 | "R" : vertical right (full screen height) 319 | "r" : vertical right (within current window) 320 | "T" : horizontal top (full screen width) 321 | "t" : horizontal top (within current window) 322 | "B" : horizontal bottom (full screen width) [default] 323 | "b" : horizontal bottom (within current window) 324 | 325 | g:buffersaurus_sort_regime~ 326 | Default: "fl" *g:buffersaurus_sort_regime* 327 | Sets the default sort regime for results: 328 | "fl" : sort by filepath, then line number [default] 329 | "fa" : sort by filepath, then alphabetically by line text 330 | "a" : sort alphabetically by line text 331 | 332 | g:buffersaurus_move_wrap~ 333 | Default: 1 *g:buffersaurus_move_wrap* 334 | If true [default], then result navigation wraps (i.e., moving to a "next" 335 | result when already on the last result goes to the first result, while 336 | moving to a "previous" result when already on the first result goes to the 337 | last result). If false, then result navigation does not wrap. 338 | 339 | g:buffersaurus_set_search_register~ 340 | Default: 1 *g:buffersaurus_set_search_register* 341 | If true [default], then ":Bsgrep {pattern}" assigns {pattern} to the 342 | search register. 343 | 344 | g:buffersaurus_show_context~ 345 | Default: 0 *g:buffersaurus_show_context* 346 | If false, then show uncontexted lines by default; otherwise, show 347 | contexted lines. Use C or cc in the catalog_viewer to toggle this setting. 348 | 349 | g:buffersaurus_context_size~ 350 | Default: [4, 4] *g:buffersaurus_context_size* 351 | Must be a two-element list value. Determines the number of lines to show 352 | before and after matching indexed lines in results. First element gives 353 | the number of lines of context to show before matching line, while second 354 | elements gives the number of lines of context to show after the matching 355 | line. 356 | 357 | g:buffersaurus_autodismiss_on_select~ 358 | Default: 1 *g:buffersaurus_autodismiss_on_select* 359 | Indicates whether the catalog viewer should disappear when using the 360 | commands , o, s, i, t. 361 | 362 | g:buffersaurus_flash_jumped_line~ 363 | Default: 1 *g:buffersaurus_flash_jumped_line* 364 | If true [default], the indexed line will flash when jumping to it. Use 365 | P in the catalog viewer to toggle this setting. 366 | 367 | g:buffersaurus_default_single_file~ 368 | Default: {undefined} *g:buffersaurus_default_single_file* 369 | If false or undefined [default], the commands |:Bsgrep|, |:Bsterm| and |:Bstoc| 370 | will perform a scan of all opened buffers. 371 | If true only the current buffer will be considered. 372 | 373 | g:buffersaurus_element_term_map~ 374 | Default: {undefined} *g:buffersaurus_element_term_map* 375 | Custom |Dictionary| that maps custom keywords to search patterns. Used by 376 | the |:Bsterm| command. Those maps add up to the predefined PyClass and 377 | PyDef. 378 | 379 | g:buffersaurus_filetype_term_map~ 380 | Default: {undefined} *g:buffersaurus_filetype_term_map* 381 | Custom |Dictionary| that maps filetype values to search patterns. Used by 382 | the |:Bstoc| command. 383 | 384 | vim:tw=78:ts=8:ft=help:norl: 385 | -------------------------------------------------------------------------------- /plugin/buffersaurus.vim: -------------------------------------------------------------------------------- 1 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 2 | 3 | "" Buffersaurus 4 | "" 5 | "" Vim document buffer indexing and navigation utility 6 | "" 7 | "" Copyright 2010 Jeet Sukumaran. 8 | "" 9 | "" This program is free software; you can redistribute it and/or modify 10 | "" it under the terms of the GNU General Public License as published by 11 | "" the Free Software Foundation; either version 3 of the License, or 12 | "" (at your option) any later version. 13 | "" 14 | "" This program is distributed in the hope that it will be useful, 15 | "" but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | "" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | "" GNU General Public License 18 | "" for more details. 19 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 20 | 21 | " Reload and Compatibility Guard {{{1 22 | " ============================================================================ 23 | " Reload protection. 24 | if (exists('g:did_buffersaurus') && g:did_buffersaurus) || &cp || version < 700 25 | finish 26 | endif 27 | let g:did_buffersaurus = 1 28 | " avoid line continuation issues (see ':help user_41.txt') 29 | let s:save_cpo = &cpo 30 | set cpo&vim 31 | " 1}}} 32 | 33 | " Public Command and Key Maps {{{1 34 | " ============================================================================== 35 | command! -bang -nargs=* Bsgrep :call buffersaurus#IndexPatterns(, '', '') 36 | command! -bang -nargs=0 Bstoc :call buffersaurus#IndexTerms('', '', 'fl') 37 | command! -bang -nargs=1 -complete=customlist,buffersaurus#Complete_bsterm Bsterm :call buffersaurus#IndexTerms('', '', 'fl') 38 | command! -nargs=0 Bsopen :call buffersaurus#OpenLastActiveCatalog() 39 | command! -range -bang -nargs=0 Bsnext :call buffersaurus#GotoEntry("n") 40 | command! -range -bang -nargs=0 Bsprev :call buffersaurus#GotoEntry("p") 41 | command! -bang -nargs=0 Bsinfo :call buffersaurus#ShowCatalogStatus('') 42 | command! -nargs=0 Bsreplace :call buffersaurus#GlobalSearchAndReplace() 43 | 44 | " (development/debugging) " 45 | let g:buffersaurus_plugin_path = expand(':p') 46 | " command! -nargs=0 Bsreboot :let g:did_buffersaurus = 0 | :execute("so " . g:buffersaurus_plugin_path) 47 | 48 | nnoremap [ :Bsprev 49 | nnoremap ] :Bsnext 50 | nnoremap \| :Bsopen 51 | " 1}}} 52 | 53 | " Restore State {{{1 54 | " ============================================================================ 55 | " restore options 56 | let &cpo = s:save_cpo 57 | " 1}}} 58 | 59 | " vim:foldlevel=4: 60 | --------------------------------------------------------------------------------