├── 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 :