├── .gitignore ├── LICENSE ├── README.md ├── autoload └── mrw.vim ├── doc └── mrw.txt ├── plugin └── mrw.vim └── syntax └── mrw.vim /.gitignore: -------------------------------------------------------------------------------- 1 | .mrw.* 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Naruhiko Nishino 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # vim-mrw 3 | 4 | This plugin provides to switch a file to edit **M**ost **R**ecently **W**ritten files. 5 | 6 | ## Usage 7 | Please see doc. 8 | 9 | ## License 10 | Distributed under MIT License. See LICENSE. 11 | 12 | -------------------------------------------------------------------------------- /autoload/mrw.vim: -------------------------------------------------------------------------------- 1 | 2 | let s:REVERSE = '-reverse' 3 | let s:FILENAME_ONLY = '-filename-only' 4 | let s:DIRECTORY_ONLY = '-directory-only' 5 | let s:SORTBY = '-sortby=' 6 | let s:NUM = '-N=' 7 | let s:FILTER = '-filter=' 8 | let s:SORTBY_TIME = s:SORTBY .. 'time' 9 | let s:SORTBY_FILENAME = s:SORTBY .. 'filename' 10 | let s:SORTBY_DIRECTORY = s:SORTBY .. 'directory' 11 | let s:DELIMITER = ' | ' 12 | 13 | function! mrw#init() abort 14 | let s:args = get(s:, 'args', s:parse_arguments('')) 15 | let g:mrw_limit = get(g:, 'mrw_limit', 300) 16 | let g:mrw_cache_path = expand(get(g:, 'mrw_cache_path', '~/.mrw')) 17 | command! -nargs=? -complete=customlist,mrw#comp MRW :call mrw#exec() 18 | augroup mrw 19 | autocmd! 20 | autocmd BufWritePost * :call mrw#bufwritepost() 21 | autocmd BufEnter mrw://output :call mrw#bufenter() 22 | augroup END 23 | endfunction 24 | 25 | function! mrw#exec(q_args) abort 26 | let s:args = s:parse_arguments(a:q_args) 27 | silent! edit mrw://output 28 | endfunction 29 | 30 | function! mrw#bufenter() abort 31 | try 32 | setfiletype mrw 33 | setlocal buftype=nofile bufhidden=hide nobuflisted 34 | nnoremap :call mrw#select() 35 | nnoremap :call mrw#select() 36 | 37 | let xs = mrw#read_cachefile() 38 | call filter(xs, { i,x -> x['path'] =~ s:args['filter_text'] }) 39 | if empty(xs) 40 | throw 'No most recently written' 41 | endif 42 | 43 | " make lines 44 | let sorted = [] 45 | if s:args['sortby_filename'] 46 | let sorted = sort(xs, { i1,i2 -> s:strcmp(fnamemodify(i1['path'], ':t'), fnamemodify(i2['path'], ':t')) }) 47 | elseif s:args['sortby_directory'] 48 | let sorted = sort(xs, { i1,i2 -> s:strcmp(fnamemodify(i1['path'], ':h'), fnamemodify(i2['path'], ':h')) }) 49 | else 50 | " It's -sortby=time 51 | let sorted = sort(xs, { i1,i2 -> getftime(i2['path']) - getftime(i1['path']) }) 52 | endif 53 | 54 | if s:args['is_reverse'] 55 | let sorted = reverse(sorted) 56 | endif 57 | 58 | try 59 | let &l:statusline = '[MRW]' 60 | setlocal modifiable noreadonly 61 | silent! call deletebufline(bufnr(), 1, '$') 62 | let lnum = 1 63 | for x in sorted[:((s:args['num'] ? s:args['num'] : g:mrw_limit) - 1)] 64 | let curr_path = x['path'] 65 | let curr_lnum = x['lnum'] 66 | let curr_col = x['col'] 67 | if s:args['is_fname_only'] 68 | let line = printf('%s(%d,%d)', fnamemodify(curr_path, ':p'), curr_lnum, curr_col) 69 | else 70 | " calculate the width of first and second column 71 | let first_max = 0 72 | let second_max = 0 73 | for x in sorted[:((s:args['num'] ? s:args['num'] : g:mrw_limit) - 1)] 74 | let ftime = strftime('%Y/%m/%d %T', getftime(x['path'])) 75 | if first_max < strdisplaywidth(ftime) 76 | let first_max = strdisplaywidth(ftime) 77 | endif 78 | let fname = printf('%s(%d,%d)', fnamemodify(x['path'], ':t'), x['lnum'], x['col']) 79 | if second_max < strdisplaywidth(fname) 80 | let second_max = strdisplaywidth(fname) 81 | endif 82 | endfor 83 | let line = join([ 84 | \ s:padding_right_space(strftime('%Y/%m/%d %T', getftime(curr_path)), first_max), 85 | \ s:padding_right_space(printf('%s(%d,%d)', fnamemodify(curr_path, ':t'), curr_lnum, curr_col), second_max), 86 | \ fnamemodify(curr_path, ':h'), 87 | \ ], s:DELIMITER) 88 | endif 89 | call setbufline(bufnr(), lnum, line) 90 | redraw 91 | let lnum += 1 92 | endfor 93 | finally 94 | setlocal nomodifiable readonly 95 | endtry 96 | catch 97 | echohl Error 98 | echo '[mrw]' v:exception 99 | echohl None 100 | endtry 101 | endfunction 102 | 103 | function! mrw#bufwritepost() abort 104 | let mrw_cache_path = s:fix_path(g:mrw_cache_path) 105 | let fullpath = s:fix_path(expand('')) 106 | if fullpath != mrw_cache_path 107 | let p = v:false 108 | let lnum = line('.') 109 | let col = col('.') 110 | if filereadable(mrw_cache_path) 111 | if filereadable(fullpath) 112 | let head = readfile(mrw_cache_path, '', 1) 113 | if 0 < len(head) 114 | let x = s:line2dict(head[0]) 115 | if ((fullpath != s:fix_path(x['path'])) || (lnum != x['lnum']) || (col != x['col'])) 116 | let p = v:true 117 | endif 118 | else 119 | let p = v:true 120 | endif 121 | endif 122 | else 123 | let p = v:true 124 | endif 125 | if p 126 | let xs = [json_encode({ 'path': fullpath, 'lnum': lnum, 'col': col, })] + map(mrw#read_cachefile(fullpath), { i,x -> json_encode(x) }) 127 | call writefile(xs, mrw_cache_path) 128 | endif 129 | endif 130 | endfunction 131 | 132 | function! mrw#comp(ArgLead, CmdLine, CursorPos) abort 133 | let xs = [] 134 | let args = s:parse_arguments(a:CmdLine) 135 | let sortby = v:false 136 | for x in [(s:SORTBY_TIME), (s:SORTBY_FILENAME), (s:SORTBY_DIRECTORY)] 137 | if -1 != stridx(a:CmdLine, x) 138 | let sortby = v:true 139 | break 140 | endif 141 | endfor 142 | for x in (sortby ? [] : [(s:SORTBY_TIME), (s:SORTBY_FILENAME), (s:SORTBY_DIRECTORY)]) 143 | \ + (args['is_reverse'] ? [] : [(s:REVERSE)]) 144 | \ + (args['is_fname_only'] ? [] : [(s:FILENAME_ONLY)]) 145 | \ + (args['num'] ? [] : [(s:NUM)]) 146 | \ + (args['filter_text'] ? [] : [(s:FILTER)]) 147 | if -1 == match(a:CmdLine, x) 148 | let xs += [x] 149 | endif 150 | endfor 151 | return filter(xs, { i,x -> -1 != match(x, a:ArgLead) }) 152 | endfunction 153 | 154 | function! mrw#select() abort 155 | let line = getbufline(bufnr(), line('.'), line('.'))[0] 156 | if !empty(line) 157 | let xs = split(line, s:DELIMITER) 158 | if 1 == len(xs) 159 | let m = matchlist(s:fix_path(trim(xs[0])), '^\(.\{-\}\)(\(\d\+\),\(\d\+\))$') 160 | else 161 | let m = matchlist(s:fix_path(trim(xs[2]) .. '/' .. trim(xs[1])), '^\(.\{-\}\)(\(\d\+\),\(\d\+\))$') 162 | endif 163 | if !empty(m) 164 | call s:open_file(m[1], str2nr(m[2]), str2nr(m[3])) 165 | endif 166 | endif 167 | endfunction 168 | 169 | function! mrw#read_cachefile(fullpath = '') abort 170 | if filereadable(g:mrw_cache_path) 171 | let lines = readfile(g:mrw_cache_path, '', g:mrw_limit) 172 | let xs = [] 173 | for i in range(0, len(lines) - 1) 174 | let x = s:line2dict(lines[i]) 175 | if (a:fullpath != x['path']) && filereadable(x['path']) 176 | let xs += [x] 177 | endif 178 | endfor 179 | return xs 180 | else 181 | return [] 182 | endif 183 | endfunction 184 | 185 | 186 | 187 | function! s:parse_arguments(cmdline) abort 188 | let is_fname_only = -1 != index(split(a:cmdline, '\s\+'), s:FILENAME_ONLY) 189 | let sortby_filename = -1 != index(split(a:cmdline, '\s\+'), s:SORTBY_FILENAME) 190 | let sortby_directory = -1 != index(split(a:cmdline, '\s\+'), s:SORTBY_DIRECTORY) 191 | let is_reverse = -1 != index(split(a:cmdline, '\s\+'), s:REVERSE) 192 | let num = get(filter(map(split(a:cmdline, '\s\+'), { i,x -> str2nr(matchstr(x, '^' .. s:NUM .. '\zs\d\+$')) }), { i,x -> 0 < x }), 0, 0) 193 | let filter_text = get(filter(map(split(a:cmdline, '\s\+'), { i,x -> matchstr(x, '^' .. s:FILTER .. '\zs[^ ]\+$') }), { i,x -> !empty(x) }), 0, '') 194 | return { 195 | \ 'is_fname_only': is_fname_only, 196 | \ 'sortby_filename': sortby_filename, 197 | \ 'sortby_directory': sortby_directory, 198 | \ 'is_reverse': is_reverse, 199 | \ 'num': num, 200 | \ 'filter_text': filter_text, 201 | \ } 202 | endfunction 203 | 204 | function! s:fix_path(path) abort 205 | return fnamemodify(resolve(a:path), ':p:gs?\\?/?') 206 | endfunction 207 | 208 | function! s:strict_bufnr(path) abort 209 | let bnr = bufnr(a:path) 210 | let fname1 = fnamemodify(a:path, ':t') 211 | let fname2 = fnamemodify(bufname(bnr), ':t') 212 | if (-1 == bnr) || (fname1 != fname2) 213 | return -1 214 | else 215 | return bnr 216 | endif 217 | endfunction 218 | 219 | function! s:open_file(path, lnum, col) abort 220 | let bnr = s:strict_bufnr(a:path) 221 | if -1 == bnr 222 | execute printf('edit %s', fnameescape(a:path)) 223 | else 224 | execute printf('buffer %d', bnr) 225 | endif 226 | call cursor(a:lnum, a:col) 227 | endfunction 228 | 229 | function! s:strcmp(x, y) abort 230 | return (a:x == a:y) ? 0 : ((a:x < a:y) ? -1 : 1) 231 | endfunction 232 | 233 | function! s:padding_right_space(text, width) 234 | return a:text .. repeat(' ', a:width - strdisplaywidth(a:text)) 235 | endfunction 236 | 237 | function! s:line2dict(line) abort 238 | if a:line =~# '^{' 239 | return json_decode(a:line) 240 | else 241 | return { 'path': a:line, 'lnum': 1, 'col': 1, } 242 | endif 243 | endfunction 244 | 245 | -------------------------------------------------------------------------------- /doc/mrw.txt: -------------------------------------------------------------------------------- 1 | *mrw.txt* switch a file to edit most recently written files. 2 | 3 | Author : rbtnn 4 | LICENSE: MIT license (see LICENSE.txt) 5 | 6 | CONTENTS *mrw-contents* 7 | 8 | Concepts |mrw-concepts| 9 | Commands |mrw-commands| 10 | Variables |mrw-variables| 11 | 12 | 13 | 14 | ============================================================================== 15 | Concepts *mrw-concepts* 16 | 17 | * This plugin supports Vim and Neovim. 18 | * This plugin provides only one command. 19 | 20 | 21 | 22 | ============================================================================== 23 | Commands *mrw-commands* 24 | 25 | *:MRW* 26 | :MRW [-sortby={sortby-value}] [-reverse] [-filename-only] [-N={number}] 27 | [-filter={text}] 28 | Show most recently written files. Then you can edit a selected 29 | file. 30 | 31 | -sortby={sortby-value} *:MRW-sortby* 32 | {sortby-value} is a value of following: 33 | 34 | 'time' (default) Sort by last modification time of 35 | the file. 36 | 'filename' Sort by file name of the path. 37 | 'directory' Sort by directory of the path. 38 | 39 | -reverse *:MRW-reverse* 40 | If `-reverse` is specified, the items are reversed. 41 | 42 | -filename-only *:MRW-filename-only* 43 | If `-filename-only` is specified, the filename column is 44 | only displayed. 45 | 46 | -N={number} *:MRW-N* 47 | If `-N` is specified, the {number} items are only displayed. 48 | 49 | -filter={text} *:MRW-filter* 50 | If `-filter` is specified, the items filtered by {text} are 51 | only displayed. {text} must not be contained Space(0x20). 52 | 53 | 54 | 55 | ============================================================================== 56 | Variables *mrw-variables* 57 | 58 | 59 | g:mrw_limit *g:mrw_limit* 60 | The number of saved items. 61 | (default: `300`) 62 | 63 | g:mrw_cache_path *g:mrw_cache_path* 64 | The cache file for this plugin. 65 | (default: `~/.mrw`) 66 | 67 | 68 | 69 | ============================================================================== 70 | vim:tw=78:ts=8:ft=help:norl:noet:fen:fdl=0: 71 | -------------------------------------------------------------------------------- /plugin/mrw.vim: -------------------------------------------------------------------------------- 1 | 2 | let g:loaded_mrw = 1 3 | 4 | call mrw#init() 5 | -------------------------------------------------------------------------------- /syntax/mrw.vim: -------------------------------------------------------------------------------- 1 | 2 | if exists("b:current_syntax") 3 | finish 4 | endif 5 | 6 | syntax match mrwDelimiter '|' contained 7 | syntax match mrwTime '^[^|]\+ ' contained 8 | syntax match mrwFileName '[^|]\+(\d\+,\d\+)' contained contains=mrwLnumAndCol 9 | syntax match mrwLnumAndCol '(\d\+,\d\+)' contained 10 | syntax match mrwDirectory ' [^|]\+$' contained 11 | 12 | syntax match mrwLine1 '^[^|]\+ | [^|]\+ | [^|]\+$' contains=mrwFileName,mrwDirectory,mrwTime,mrwDelimiter 13 | syntax match mrwLine2 '^[^|]\+(\d\+,\d\+)$' contains=mrwFileName 14 | syntax match mrwLine3 '^>.*$' 15 | 16 | highlight default link mrwTime LineNr 17 | highlight default link mrwFileName Normal 18 | highlight default link mrwLnumAndCol Comment 19 | highlight default link mrwDirectory Directory 20 | highlight default link mrwDelimiter NonText 21 | highlight default link mrwLine3 Directory 22 | 23 | --------------------------------------------------------------------------------