├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── autoload └── buffest.vim ├── doc └── buffest.txt ├── ftplugin ├── buffestloclist.vim ├── buffestqflist.vim └── buffestreg.vim ├── media └── demo.gif └── plugin └── buffest.vim /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | The idea behind this plugin is to make as light a layer on top of Vim as possible for editing common constructs in buffers. 4 | The plugin should do little more than read from a list/register, write it to a file, and read back from the file. 5 | It should rely on Vim to handle complexity whenever possible. 6 | Please keep your changes in line with this philosophy. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Post an issue with the changes you wish to make if it does not already exist. 11 | 2. Changes to the code should be simple, iterative, and consistent. 12 | 3. Run [vint](https://github.com/Kuniwak/vint) in the root directory and make any necessary changes. 13 | 4. Keep documentation easy to read, concise, and up-to-date. 14 | Update `README.md` with simple examples if necessary. 15 | Update `buffest.txt` with any new or missing related information that users need to know. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-buffest 2 | 3 | Easily edit vim registers/macros and lists as buffers. 4 | 5 | ![demo video](/media/demo.gif?raw=true) 6 | 7 | ## Examples 8 | 9 | **Opening registers** 10 | 11 | All of the named registers, as well as the `"`, `*`, and `+` registers, are supported. 12 | Some different ways to edit register `a` (case insensitive): 13 | 14 | ``` 15 | c@a 16 | :Regsplit a 17 | :Regvsplit a 18 | :Regtabedit a 19 | :Regedit a 20 | :Regpedit a 21 | ``` 22 | 23 | Repace `a` with `` to edit that register. 24 | Additionally, `c@@` opens the `"` register. 25 | 26 | **Opening the quickfix list** 27 | 28 | ``` 29 | c\q 30 | :Qflistsplit 31 | :Qflistvsplit 32 | :Qflisttabedit 33 | :Qflistedit 34 | ``` 35 | 36 | **Opening the location list** 37 | 38 | ``` 39 | c\l 40 | :Loclistsplit 41 | :Loclistvsplit 42 | :Loclisttabedit 43 | :Loclistedit 44 | ``` 45 | 46 | **Opening lists with specific fields shown** 47 | 48 | ``` 49 | :Qflistsplit filename lnum 50 | :Loclistsplit filename lnum 51 | ``` 52 | 53 | **Reloading and writing** 54 | 55 | Write a buffer to the target: 56 | 57 | ``` 58 | :w 59 | ``` 60 | 61 | Change the type of a register: 62 | 63 | ``` 64 | " Change to single-line mode 65 | v:w 66 | " Change to multi-line mode 67 | V:w 68 | " Change to block mode 69 | :w 70 | ``` 71 | 72 | Reload the contents of the buffer: 73 | 74 | ``` 75 | :e 76 | ``` 77 | 78 | Force reload the contents of the buffer: 79 | 80 | ``` 81 | :e! 82 | ``` 83 | 84 | **Remapping** 85 | 86 | This will change the default mappings: 87 | 88 | ``` 89 | nnoremap br BuffestRegsplit 90 | nnoremap bq BuffestQflistsplit 91 | nnoremap bl BuffestLoclistsplit 92 | ``` 93 | 94 | Other commands can be mapped in the same way. 95 | 96 | ## Contributing 97 | 98 | Before contributing please read [CONTRIBUTING.md](/CONTRIBUTING.md). 99 | -------------------------------------------------------------------------------- /autoload/buffest.vim: -------------------------------------------------------------------------------- 1 | " Variables {{{ 2 | 3 | let g:buffest_supported_registers = map(range(char2nr('a'),char2nr('z')),'nr2char(v:val)') + ['"', '*', '+'] 4 | 5 | let g:buffest_unsupported_register_error = 'buffest: E1: register not supported' 6 | 7 | let g:buffest_list_defaults = {'filename': '', 'module': '', 'lnum': '', 'pattern': '', 'col': 0, 'vcol': 0, 'nr': -1, 'text': '', 'type': '', 'valid': 1} 8 | 9 | let g:buffest_supported_listfields = keys(g:buffest_list_defaults) 10 | 11 | " prefix lists with a timestamp to prevent vim instances from sharing buffers 12 | let g:buffest_list_prefix = localtime() 13 | 14 | let s:tmpdir = '/' . $TMP . '/buffest/' 15 | if $TMP ==# '' 16 | let s:tmpdir = '/tmp/buffest/' 17 | endif 18 | 19 | " }}} 20 | 21 | " Initialization {{{ 22 | 23 | function! buffest#init_tmpdir() abort 24 | call mkdir(s:tmpdir, 'p') 25 | endfunction 26 | 27 | function! buffest#init_au() abort 28 | augroup buffestfiletype 29 | autocmd! 30 | exec 'autocmd BufNewFile,BufRead ' . s:tmpdir . '@* set filetype=buffestreg' 31 | exec 'autocmd BufNewFile,BufRead ' . s:tmpdir . '\,q set filetype=buffestqflist' 32 | exec 'autocmd BufNewFile,BufRead ' . s:tmpdir . '\,l#* set filetype=buffestloclist' 33 | augroup END 34 | endfunction 35 | 36 | function! buffest#init() abort 37 | call buffest#init_tmpdir() 38 | call buffest#init_au() 39 | endfunction 40 | 41 | " }}} 42 | 43 | " General utilities {{{ 44 | 45 | function! buffest#tmpname(name) abort 46 | return fnameescape(s:tmpdir.a:name) 47 | endfunction 48 | 49 | function! buffest#escape_regex(string) abort 50 | return escape(a:string, '\\^$*+?.()|[]{}') 51 | endfunction 52 | 53 | function! buffest#dict2sortedstring(dict, fields) abort 54 | let l:keyvalues = [] 55 | for l:field in a:fields 56 | let keyvalues += [string(l:field) . ': ' . string(a:dict[l:field])] 57 | endfor 58 | let l:string = '{' . join(keyvalues, ', ') . '}' 59 | return l:string 60 | endfunction 61 | 62 | function! buffest#uniq_unsorted(list) abort 63 | return filter(copy(a:list), 'index(a:list, v:val, v:key + 1) == -1') 64 | endfunction 65 | 66 | function! buffest#intersection(src, ref) abort 67 | return filter(a:src, 'index(a:ref, v:val) >= 0') 68 | endfunction 69 | 70 | " }}} 71 | 72 | " Registers {{{ 73 | 74 | " Register utilities {{{ 75 | 76 | function! buffest#valid_regname(regname) abort 77 | return index(g:buffest_supported_registers, a:regname) >= 0 78 | endfunction 79 | 80 | function! buffest#get_regname(filename) abort 81 | let l:pattern = '^' . buffest#escape_regex(s:tmpdir) . '@\zs.\?$' 82 | let [l:match, l:matchstart, l:matchend] = matchstrpos(a:filename, l:pattern) 83 | if l:matchstart < 0 84 | return v:null 85 | endif 86 | let l:regname = tolower(l:match ==# '' ? '"' : l:match) 87 | if !buffest#valid_regname(l:regname) 88 | return v:null 89 | endif 90 | return l:regname 91 | endfunction 92 | 93 | function! buffest#get_reg_type_label(filename) 94 | if &filetype ==# 'buffestreg' 95 | let l:mode = getregtype(buffest#get_regname(a:filename)) 96 | if char2nr(l:mode) == 22 97 | return 'BLOCKWISE' 98 | elseif l:mode ==# 'v' 99 | return 'CHARWISE' 100 | elseif l:mode ==# 'V' 101 | return 'LINEWISE' 102 | endif 103 | endif 104 | return '' 105 | endfunction 106 | 107 | function! buffest#reg_complete(...) abort 108 | return g:buffest_supported_registers 109 | endfunction 110 | 111 | function! buffest#reg_do(cmd, regname) abort 112 | let l:regname = tolower(a:regname) 113 | " escape character, cancel 114 | if l:regname ==# nr2char(27) 115 | return 116 | elseif !buffest#valid_regname(l:regname) 117 | throw g:buffest_unsupported_register_error 118 | endif 119 | exec a:cmd . ' ' . buffest#tmpname('@' . l:regname) 120 | endfunction 121 | 122 | " }}} 123 | 124 | " Reading/writing registers {{{ 125 | 126 | function! buffest#read_reg() abort 127 | let l:filename = expand('%:p') 128 | let l:regname = buffest#get_regname(l:filename) 129 | if l:regname == v:null 130 | return 131 | endif 132 | set binary 133 | call writefile(getreg(l:regname, 1, 1), l:filename, 'b') 134 | edit! 135 | endfunction 136 | 137 | function! buffest#write_reg() abort 138 | let l:filename = expand('%:p') 139 | let l:regname = buffest#get_regname(l:filename) 140 | if l:regname == v:null 141 | return 142 | endif 143 | 144 | let l:mode = visualmode() 145 | if l:mode ==# '' 146 | let l:mode = getregtype(l:regname) 147 | endif 148 | 149 | call setreg(l:regname, readfile(l:filename, 'b'), l:mode) 150 | endfunction 151 | 152 | " }}} 153 | 154 | " }}} 155 | 156 | " Lists {{{ 157 | 158 | " List utilities {{{ 159 | 160 | function! buffest#loclist_id() abort 161 | return g:buffest_list_prefix . '-' . string(bufnr('%')) 162 | endfunction 163 | 164 | " }}} 165 | 166 | " List fields {{{ 167 | 168 | function! buffest#listfield_complete(lead, ...) abort 169 | return filter(copy(g:buffest_supported_listfields), 'v:val =~# "^" . a:lead') 170 | endfunction 171 | 172 | function! buffest#filter_listfields(list) abort 173 | let l:list = buffest#uniq_unsorted(a:list) 174 | return buffest#intersection(l:list, g:buffest_supported_listfields) 175 | endfunction 176 | 177 | function! buffest#get_listfields() abort 178 | if exists('b:buffest_listfields') 179 | return b:buffest_listfields 180 | endif 181 | if &filetype ==# 'buffestqflist' 182 | return exists('s:qflist_fields') ? s:qflist_fields : v:null 183 | elseif &filetype ==# 'buffestloclist' 184 | return exists('s:loclist_fields') ? s:loclist_fields : v:null 185 | endif 186 | endfunction 187 | 188 | function! buffest#has_listfields() abort 189 | let l:listfields = buffest#get_listfields() 190 | return type(l:listfields) != v:null && len(l:listfields) 191 | endfunction 192 | 193 | function! buffest#has_listfield(field) abort 194 | return !buffest#has_listfields() || index(buffest#get_listfields(), a:field) >= 0 195 | endfunction 196 | 197 | function! buffest#save_listfields() abort 198 | if !exists('b:buffest_listfields') && buffest#has_listfields() 199 | let b:buffest_listfields = buffest#get_listfields() 200 | endif 201 | endfunction 202 | 203 | " }}} 204 | 205 | " Reading lists {{{ 206 | 207 | function! buffest#sanitize_listitem(item) abort 208 | let l:item = a:item 209 | " bufnr is not useful to edit for a human, it is converted to filename 210 | if has_key(l:item, 'bufnr') 211 | let l:item['filename'] = bufname(l:item['bufnr']) 212 | unlet l:item['bufnr'] 213 | endif 214 | " do not promote invalid items to valid 215 | if !buffest#has_listfield('valid') && !l:item['valid'] 216 | return v:null 217 | endif 218 | return l:item 219 | endfunction 220 | 221 | function! buffest#parse_listitem(item) abort 222 | let l:item = buffest#sanitize_listitem(a:item) 223 | if l:item is v:null 224 | " item has been sanitized, return nothing 225 | let l:line = v:null 226 | elseif !buffest#has_listfields() 227 | " return an unfiltered string representation of the line 228 | let l:line = string(l:item) 229 | else 230 | " return a filtered string representation of the line 231 | let l:line = buffest#dict2sortedstring(l:item, buffest#get_listfields()) 232 | endif 233 | return l:line 234 | endfunction 235 | 236 | function! buffest#read_list(list) abort 237 | if !len(a:list) 238 | let l:list = [g:buffest_list_defaults] 239 | else 240 | let l:list = a:list 241 | endif 242 | 243 | call buffest#save_listfields() 244 | 245 | let l:filelist = [] 246 | for l:item in l:list 247 | let l:line = buffest#parse_listitem(l:item) 248 | if l:line != v:null 249 | let l:filelist += [l:line] 250 | endif 251 | endfor 252 | call writefile(l:filelist, expand('%:p')) 253 | 254 | edit! 255 | endfunction 256 | 257 | " }}} 258 | 259 | " Writing lists {{{ 260 | 261 | function! buffest#get_writelist() abort 262 | let l:contents = [] 263 | for line in readfile(expand('%')) 264 | let l:contents += [eval(line)] 265 | endfor 266 | return l:contents 267 | endfunction 268 | 269 | " }}} 270 | 271 | " Quickfix list {{{ 272 | 273 | function! buffest#read_qflist() abort 274 | return buffest#read_list(getqflist()) 275 | endfunction 276 | 277 | function! buffest#write_qflist() abort 278 | call setqflist(buffest#get_writelist()) 279 | endfunction 280 | 281 | function! buffest#qflist_do(cmd, ...) abort 282 | let s:qflist_fields = buffest#filter_listfields(a:000) 283 | try 284 | exec a:cmd . ' ' . buffest#tmpname(',q') 285 | finally 286 | unlet s:qflist_fields 287 | endtry 288 | endfunction 289 | 290 | " }}} 291 | 292 | " Location list {{{ 293 | 294 | function! buffest#read_loclist() abort 295 | return buffest#read_list(getloclist('.')) 296 | endfunction 297 | 298 | function! buffest#write_loclist() abort 299 | call setloclist(winnr() + 1, buffest#get_writelist()) 300 | endfunction 301 | 302 | function! buffest#loclist_do(cmd, list_id, ...) abort 303 | let s:loclist_fields = buffest#filter_listfields(a:000) 304 | try 305 | exec a:cmd . ' ' . buffest#tmpname(',l#' . a:list_id) 306 | finally 307 | unlet s:loclist_fields 308 | endtry 309 | endfunction 310 | 311 | " }}} 312 | 313 | " }}} 314 | 315 | call buffest#init() 316 | 317 | " vim:set et sw=2 ts=2 fdm=marker: 318 | -------------------------------------------------------------------------------- /doc/buffest.txt: -------------------------------------------------------------------------------- 1 | *buffest.txt* Easily edit vim registers/macros and lists as buffers. 2 | 3 | Author: Roger Bongers 4 | 5 | *buffest* 6 | 7 | COMMANDS *buffest-commands* 8 | 9 | All commands can be mapped using Buffest and the name of the command. 10 | See |using-|. 11 | 12 | *:Regsplit* *:Regvsplit* *:Regtabedit* *:Regedit* *:Regpedit* 13 | 14 | Each of these commands takes one argument, the name of the register. 15 | Each command opens the register in the way indicated. 16 | Only the named registers, as well as {*}, {+}, and {"} are supported. 17 | 18 | *:Qflistsplit* *:Qflistvsplit* *:Qflisttabedit* *:Qflistedit* 19 | 20 | Each of these commands open the quickfix list in the way indicated. 21 | These commands support |buffest-list-fields|. 22 | 23 | *:Loclistsplit* *:Loclistvsplit* *:Loclisttabedit* *:Locflistedit* 24 | 25 | Each of these commands open the location list in the way indicated. 26 | These commands support |buffest-list-fields|. 27 | The window always splits using |:aboveleft| and sets the loclist of |winnr()| + 1. 28 | Buffest will attempt to differentiate location list buffers based on the buffer they are opened next to, if any. 29 | 30 | LIST FIELDS *buffest-list-fields* 31 | 32 | All list commands can optionally take in field arguments. 33 | If this list of fields is given, only those fields will be shown in output. 34 | The unlisted fields will be lost. 35 | 36 | The fields will be shown sorted. 37 | 38 | The supported fields are based on |getqflist()|, |setqflist()|, |setloclist()|, and |getloclist()|. 39 | The supported fields are as follows: 40 | 'filename', 'module', 'lnum', 'pattern', 'col', 'vcol', 'nr', 'text', 'type', and 'valid'. 41 | 42 | If 'valid' is excluded from the fields, invalid items will be filtered. 43 | 44 | Other fields excluded will result in the field reverting to its default value. 45 | Notably, if 'vcol' is excluded from the fields, all 'col' fields will be converted to byte index. 46 | 47 | USING BUFFERS *buffest-buffers* 48 | 49 | On re-opening a buffer it will automatically update from the register or list. 50 | 51 | Normal updating: > 52 | :e 53 | 54 | Force update: > 55 | :e! 56 | 57 | Buffers will write to the register or list when the buffer is written to. 58 | 59 | Write buffer: > 60 | :w 61 | 62 | Set register type: > 63 | V:w 64 | v:w 65 | :w 66 | 67 | Write and exit: > 68 | :wq 69 | Alternative: > 70 | ZZ 71 | 72 | MAPPINGS *buffest-mappings* 73 | 74 | All mappings can be overriden by setting the corresponding mapping. 75 | 76 | c@{regname} *buffest-c@* *c@* 77 | 78 | Edit the register in a split (BuffestRegsplit). 79 | 80 | c@@ *buffest-c@@* *c@@* 81 | 82 | Edit the {"} register in a split. 83 | Only set when the default register mapping is set. 84 | 85 | c\q *buffest-c\q* *c\q* 86 | 87 | Edit the quickfix list in a split (BuffestQflistsplit). 88 | 89 | c\l *buffest-c\l* *c\l* 90 | 91 | Edit the location list in a split (BuffestLoclistsplit). 92 | 93 | vim: ft=help 94 | -------------------------------------------------------------------------------- /ftplugin/buffestloclist.vim: -------------------------------------------------------------------------------- 1 | augroup buffestloclist 2 | autocmd! 3 | autocmd BufWritePost call buffest#write_loclist() 4 | autocmd BufEnter call buffest#read_loclist() 5 | augroup END 6 | 7 | setlocal bufhidden=delete 8 | setlocal nobuflisted 9 | 10 | " vim:set et sw=2 ts=2 fdm=marker: 11 | -------------------------------------------------------------------------------- /ftplugin/buffestqflist.vim: -------------------------------------------------------------------------------- 1 | augroup buffestqflist 2 | autocmd! 3 | autocmd BufWritePost call buffest#write_qflist() 4 | autocmd BufEnter call buffest#read_qflist() 5 | augroup END 6 | 7 | setlocal bufhidden=delete 8 | setlocal nobuflisted 9 | 10 | " vim:set et sw=2 ts=2 fdm=marker: 11 | -------------------------------------------------------------------------------- /ftplugin/buffestreg.vim: -------------------------------------------------------------------------------- 1 | augroup buffestreg 2 | autocmd! 3 | autocmd BufWritePost call buffest#write_reg() 4 | autocmd BufEnter call buffest#read_reg() 5 | augroup END 6 | 7 | setlocal bufhidden=delete 8 | setlocal nobuflisted 9 | 10 | " vim:set et sw=2 ts=2 fdm=marker: 11 | -------------------------------------------------------------------------------- /media/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbong/vim-buffest/6ed80444d687cbcb11dcbe83dd94ec9888329f1b/media/demo.gif -------------------------------------------------------------------------------- /plugin/buffest.vim: -------------------------------------------------------------------------------- 1 | " Commands {{{ 2 | 3 | command! -complete=customlist,buffest#reg_complete -nargs=1 4 | \ Regsplit call buffest#reg_do('split', ) 5 | command! -complete=customlist,buffest#reg_complete -nargs=1 6 | \ Regvsplit call buffest#reg_do('vsplit', ) 7 | command! -complete=customlist,buffest#reg_complete -nargs=1 8 | \ Regtabedit call buffest#reg_do('tabedit', ) 9 | command! -complete=customlist,buffest#reg_complete -nargs=1 10 | \ Regedit call buffest#reg_do('edit', ) 11 | command! -complete=customlist,buffest#reg_complete -nargs=1 12 | \ Regpedit call buffest#reg_do('pedit', ) 13 | 14 | command! -complete=customlist,buffest#listfield_complete -nargs=* 15 | \ Qflistsplit call buffest#qflist_do('split', ) 16 | command! -complete=customlist,buffest#listfield_complete -nargs=* 17 | \ Qflistvsplit call buffest#qflist_do('vsplit', ) 18 | command! -complete=customlist,buffest#listfield_complete -nargs=* 19 | \ Qflisttabedit call buffest#qflist_do('tabedit', ) 20 | command! -complete=customlist,buffest#listfield_complete -nargs=* 21 | \ Qflistedit call buffest#qflist_do('edit', ) 22 | 23 | command! -complete=customlist,buffest#listfield_complete -nargs=* 24 | \ Loclistsplit call buffest#loclist_do('aboveleft split', buffest#loclist_id(), ) 25 | command! -complete=customlist,buffest#listfield_complete -nargs=* 26 | \ Loclistvsplit call buffest#loclist_do('aboveleft vsplit', buffest#loclist_id(), ) 27 | command! -complete=customlist,buffest#listfield_complete -nargs=* 28 | \ Loclisttabedit call buffest#loclist_do('tabedit', '0', ) 29 | command! -complete=customlist,buffest#listfield_complete -nargs=* 30 | \ Loclistedit call buffest#loclist_do('edit', '0', ) 31 | 32 | " }}} 33 | 34 | " Bindings {{{ 35 | 36 | if !hasmapto('BuffestRegsplit') && mapcheck('c@', 'n') ==# '' 37 | " Only map this if the defaul mapping is used 38 | if mapcheck('c@@', 'n') ==# '' 39 | nnoremap c@@ :silent Regsplit " 40 | endif 41 | nmap c@ BuffestRegsplit 42 | endif 43 | 44 | nnoremap BuffestRegsplit :silent execute 'Regsplit '.nr2char(getchar()) 45 | 46 | if hasmapto('BuffestRegvsplit') 47 | nnoremap BuffestRegvsplit :silent execute 'Regvsplit '.nr2char(getchar()) 48 | endif 49 | 50 | if hasmapto('BuffestRegtabedit') 51 | nnoremap BuffestRegtabedit :silent execute 'Regtabedit '.nr2char(getchar()) 52 | endif 53 | 54 | if hasmapto('BuffestRegedit') 55 | nnoremap BuffestRegedit :silent execute 'Regedit '.nr2char(getchar()) 56 | endif 57 | 58 | if hasmapto('BuffestRegpedit') 59 | nnoremap BuffestRegpedit :silent execute 'Regpedit '.nr2char(getchar()) 60 | endif 61 | 62 | if !hasmapto('BuffestQflistsplit') && mapcheck('c\q', 'n') ==# '' 63 | nmap c\q BuffestQflistsplit 64 | endif 65 | 66 | nnoremap BuffestQflistsplit :silent Qflistsplit 67 | 68 | if hasmapto('BuffestQflistvsplit') 69 | nnoremap BuffestQflistvsplit :silent Qflistvsplit 70 | endif 71 | 72 | if hasmapto('BuffestQflisttabedit') 73 | nnoremap BuffestQflisttabedit :silent Qflisttabedit 74 | endif 75 | 76 | if hasmapto('BuffestQflistedit') 77 | nnoremap BuffestQflistedit :silent Qflistedit 78 | endif 79 | 80 | if !hasmapto('BuffestLoclistsplit') && mapcheck('c\l', 'n') ==# '' 81 | nmap c\l BuffestLoclistsplit 82 | endif 83 | 84 | nnoremap BuffestLoclistsplit :silent Loclistsplit 85 | 86 | if hasmapto('BuffestLoclistvsplit') 87 | nnoremap BuffestLoclistvsplit :silent Loclistvsplit 88 | endif 89 | 90 | if hasmapto('BuffestLoclisttabedit') 91 | nnoremap BuffestLoclisttabedit :silent Loclisttabedit 92 | endif 93 | 94 | if hasmapto('BuffestLoclistedit') 95 | nnoremap BuffestLoclistedit :silent Loclistedit 96 | endif 97 | 98 | " }}} 99 | 100 | " vim:set et sw=2 ts=2 fdm=marker: 101 | --------------------------------------------------------------------------------