comparison plugin/lawrencium.vim @ 61:ea794e48d4e2

Better way to handle buffer actions: - Added buffer object with on delete/windows leave callbacks. - Added helper functions to open and delete "dependency" buffers. - Using that new system to handle diffs opened by `Hgannotate` and `Hglog`.
author Ludovic Chabant <ludovic@chabant.com>
date Fri, 23 Nov 2012 13:43:08 -0800
parents 396da6e76952
children 136be8fa8710
comparison
equal deleted inserted replaced
60:137d5c895659 61:ea794e48d4e2
28 let g:lawrencium_trace = 0 28 let g:lawrencium_trace = 0
29 endif 29 endif
30 30
31 if !exists('g:lawrencium_define_mappings') 31 if !exists('g:lawrencium_define_mappings')
32 let g:lawrencium_define_mappings = 1 32 let g:lawrencium_define_mappings = 1
33 endif
34
35 if !exists('g:lawrencium_auto_close_buffers')
36 let g:lawrencium_auto_close_buffers = 1
33 endif 37 endif
34 38
35 if !exists('g:lawrencium_annotate_width_offset') 39 if !exists('g:lawrencium_annotate_width_offset')
36 let g:lawrencium_annotate_width_offset = 0 40 let g:lawrencium_annotate_width_offset = 0
37 endif 41 endif
156 execute 'cd! -' 160 execute 'cd! -'
157 endif 161 endif
158 162
159 let l:result = { 'root': l:root_dir, 'path': l:repo_path, 'action': l:action, 'value': l:value } 163 let l:result = { 'root': l:root_dir, 'path': l:repo_path, 'action': l:action, 'value': l:value }
160 return l:result 164 return l:result
165 endfunction
166
167 " Finds a window whose displayed buffer has a given variable
168 " set to the given value.
169 function! s:find_buffer_window(varname, varvalue) abort
170 for wnr in range(1, winnr('$'))
171 let l:bnr = winbufnr(wnr)
172 if getbufvar(l:bnr, a:varname) == a:varvalue
173 return l:wnr
174 endif
175 endfor
176 return -1
177 endfunction
178
179 " Opens a buffer in a way that makes it easy to delete it later:
180 " - if the about-to-be previous buffer doesn't have a given variable,
181 " just open the new buffer.
182 " - if the about-to-be previous buffer has a given variable, open the
183 " new buffer with the `keepalt` option to make it so that the
184 " actual previous buffer (returned by things like `bufname('#')`)
185 " is the original buffer that was there before the first deletable
186 " buffer was opened.
187 function! s:edit_deletable_buffer(varname, varvalue, path) abort
188 let l:edit_cmd = 'edit '
189 if getbufvar('%', a:varname) != ''
190 let l:edit_cmd = 'keepalt edit '
191 endif
192 execute l:edit_cmd . fnameescape(a:path)
193 call setbufvar('%', a:varname, a:varvalue)
194 endfunction
195
196 function! s:delete_dependency_buffers(varname, varvalue) abort
197 let l:cur_winnr = winnr()
198 for bnr in range(1, bufnr('$'))
199 if getbufvar(bnr, a:varname) == a:varvalue
200 " Delete this buffer if it is not shown in any window.
201 " Otherwise, display the alternate buffer before deleting
202 " it so the window is not closed.
203 let l:bwnr = bufwinnr(bnr)
204 if l:bwnr < 0 || getbufvar(bnr, 'lawrencium_quit_on_delete') == 1
205 if bufloaded(l:bnr)
206 call s:trace("Deleting dependency buffer " . bnr)
207 execute "bdelete! " . bnr
208 else
209 call s:trace("Dependency buffer " . bnr . " is already unladed.")
210 endif
211 else
212 execute l:bwnr . "wincmd w"
213 " TODO: better handle case where there's no previous/alternate buffer?
214 if bufnr('#') > 0 && bufloaded(bufnr('#'))
215 bprevious
216 if bufloaded(l:bnr)
217 call s:trace("Deleting dependency buffer " . bnr . " after buffer switching.")
218 execute "bdelete! " . bnr
219 else
220 call s:trace("Dependency buffer " . bnr . " is unladed after buffer switching.")
221 endif
222 else
223 call s:trace("Deleting dependency buffer " . bnr . " and window.")
224 bdelete!
225 endif
226 endif
227 endif
228 endfor
229 if l:cur_winnr != winnr()
230 execute l:cur_winnr . "wincmd w"
231 endif
161 endfunction 232 endfunction
162 233
163 " }}} 234 " }}}
164 235
165 " Mercurial Repository {{{ 236 " Mercurial Repository {{{
316 augroup lawrencium_detect 387 augroup lawrencium_detect
317 autocmd! 388 autocmd!
318 autocmd BufNewFile,BufReadPost * call s:setup_buffer_commands() 389 autocmd BufNewFile,BufReadPost * call s:setup_buffer_commands()
319 autocmd VimEnter * if expand('<amatch>')==''|call s:setup_buffer_commands()|endif 390 autocmd VimEnter * if expand('<amatch>')==''|call s:setup_buffer_commands()|endif
320 augroup end 391 augroup end
392
393 " }}}
394
395 " Buffer Object {{{
396
397 " The prototype dictionary.
398 let s:Buffer = {}
399
400 " Constructor.
401 function! s:Buffer.New(number) dict abort
402 let l:newBuffer = copy(self)
403 let l:newBuffer.nr = a:number
404 let l:newBuffer.var_backup = {}
405 let l:newBuffer.on_delete = []
406 let l:newBuffer.on_winleave = []
407 execute 'augroup lawrencium_buffer_' . a:number
408 execute ' autocmd!'
409 execute ' autocmd BufDelete <buffer=' . a:number . '> call s:buffer_on_delete(' . a:number . ')'
410 execute 'augroup end'
411 call s:trace("Built new buffer object for buffer: " . a:number)
412 return l:newBuffer
413 endfunction
414
415 function! s:Buffer.GetVar(var) dict abort
416 return getbufvar(self.nr, a:var)
417 endfunction
418
419 function! s:Buffer.SetVar(var, value) dict abort
420 if !has_key(self.var_backup, a:var)
421 self.var_backup[a:var] = getbufvar(self.nr, a:var)
422 endif
423 return setbufvar(self.nr, a:var, a:value)
424 endfunction
425
426 function! s:Buffer.RestoreVars() dict abort
427 for key in keys(self.var_backup)
428 setbufvar(self.nr, key, self.var_backup[key])
429 endfor
430 endfunction
431
432 function! s:Buffer.OnDelete(cmd) dict abort
433 call s:trace("Adding BufDelete callback for buffer " . self.nr . ": " . a:cmd)
434 call add(self.on_delete, a:cmd)
435 endfunction
436
437 function! s:Buffer.OnWinLeave(cmd) dict abort
438 if len(self.on_winleave) == 0
439 call s:trace("Adding BufWinLeave auto-command on buffer " . self.nr)
440 execute 'autocmd BufWinLeave <buffer=' . self.nr . '> call s:buffer_on_winleave(' . self.nr .')'
441 endif
442 call s:trace("Adding BufWinLeave callback for buffer " . self.nr . ": " . a:cmd)
443 call add(self.on_winleave, a:cmd)
444 endfunction
445
446 let s:buffer_objects = {}
447
448 " Get a buffer instance for the specified buffer number, or the
449 " current buffer if nothing is specified.
450 function! s:buffer_obj(...) abort
451 let l:bufnr = a:0 ? a:1 : bufnr('%')
452 if !has_key(s:buffer_objects, l:bufnr)
453 let s:buffer_objects[l:bufnr] = s:Buffer.New(l:bufnr)
454 endif
455 return s:buffer_objects[l:bufnr]
456 endfunction
457
458 " Execute all the "on delete" callbacks.
459 function! s:buffer_on_delete(number) abort
460 let l:bufobj = s:buffer_objects[a:number]
461 call s:trace("Calling BufDelete callbacks on buffer " . l:bufobj.nr)
462 for cmd in l:bufobj.on_delete
463 call s:trace(" [" . cmd . "]")
464 execute cmd
465 endfor
466 call s:trace("Deleted buffer object " . l:bufobj.nr)
467 call remove(s:buffer_objects, l:bufobj.nr)
468 execute 'augroup lawrencium_buffer_' . l:bufobj.nr
469 execute ' autocmd!'
470 execute 'augroup end'
471 endfunction
472
473 " Execute all the "on winleave" callbacks.
474 function! s:buffer_on_winleave(number) abort
475 let l:bufobj = s:buffer_objects[a:number]
476 call s:trace("Calling BufWinLeave callbacks on buffer " . l:bufobj.nr)
477 for cmd in l:bufobj.on_winleave
478 call s:trace(" [" . cmd . "]")
479 execute cmd
480 endfor
481 endfunction
321 482
322 " }}} 483 " }}}
323 484
324 " Buffer Commands Management {{{ 485 " Buffer Commands Management {{{
325 486
506 " Add some nice commands. 667 " Add some nice commands.
507 command! -buffer Hgstatusedit :call s:HgStatus_FileEdit() 668 command! -buffer Hgstatusedit :call s:HgStatus_FileEdit()
508 command! -buffer Hgstatusdiff :call s:HgStatus_Diff(0) 669 command! -buffer Hgstatusdiff :call s:HgStatus_Diff(0)
509 command! -buffer Hgstatusvdiff :call s:HgStatus_Diff(1) 670 command! -buffer Hgstatusvdiff :call s:HgStatus_Diff(1)
510 command! -buffer Hgstatusdiffsum :call s:HgStatus_DiffSummary(0) 671 command! -buffer Hgstatusdiffsum :call s:HgStatus_DiffSummary(0)
511 command! -buffer Hgstatusvdiffsum :call s:HgStatus_DiffSummary(1) 672 command! -buffer Hgstatusvdiffsum :call s:HgStatus_DiffSummary(1)
512 command! -buffer Hgstatusrefresh :call s:HgStatus_Refresh() 673 command! -buffer Hgstatusrefresh :call s:HgStatus_Refresh()
513 command! -buffer -range Hgstatusaddremove :call s:HgStatus_AddRemove(<line1>, <line2>) 674 command! -buffer -range Hgstatusaddremove :call s:HgStatus_AddRemove(<line1>, <line2>)
514 command! -buffer -range=% -bang Hgstatuscommit :call s:HgStatus_Commit(<line1>, <line2>, <bang>0, 0) 675 command! -buffer -range=% -bang Hgstatuscommit :call s:HgStatus_Commit(<line1>, <line2>, <bang>0, 0)
515 command! -buffer -range=% -bang Hgstatusvcommit :call s:HgStatus_Commit(<line1>, <line2>, <bang>0, 1) 676 command! -buffer -range=% -bang Hgstatusvcommit :call s:HgStatus_Commit(<line1>, <line2>, <bang>0, 1)
516 command! -buffer -range=% -nargs=+ Hgstatusqnew :call s:HgStatus_QNew(<line1>, <line2>, <f-args>) 677 command! -buffer -range=% -nargs=+ Hgstatusqnew :call s:HgStatus_QNew(<line1>, <line2>, <f-args>)
1068 nnoremap <buffer> <silent> <C-E> :Hglogrevedit<cr> 1229 nnoremap <buffer> <silent> <C-E> :Hglogrevedit<cr>
1069 endif 1230 endif
1070 endif 1231 endif
1071 1232
1072 " Clean up when the log buffer is deleted. 1233 " Clean up when the log buffer is deleted.
1073 execute 'autocmd BufDelete <buffer> call s:HgLog_Delete(' . a:is_file . ', "' . fnameescape(l:temp_file) . '")' 1234 let l:bufobj = s:buffer_obj()
1074 endfunction 1235 call l:bufobj.OnDelete('call s:HgLog_Delete(' . l:bufobj.nr . ', "' . fnameescape(l:temp_file) . '")')
1075 1236 endfunction
1076 function! s:HgLog_Delete(was_file, path) 1237
1077 let l:repo = s:hg_repo() 1238 function! s:HgLog_Delete(bufnr, path)
1078 let l:orignr = winnr() 1239 if g:lawrencium_auto_close_buffers
1079 let l:origedit = b:lawrencium_original_path 1240 call s:delete_dependency_buffers('lawrencium_diff_for', a:bufnr)
1080 let l:origroot = s:stripslash(b:mercurial_dir) 1241 call s:delete_dependency_buffers('lawrencium_rev_for', a:bufnr)
1081 let l:origpath = s:stripslash(b:lawrencium_logged_path) 1242 endif
1082 call s:trace("Cleaning up '" . a:path . "', opened from '" . l:origedit . "'")
1083 " Delete any other buffer opened by this log.
1084 " (buffers with Lawrencium paths that match this repo and filename)
1085 for nr in range(1, winnr('$'))
1086 let l:br = winbufnr(nr)
1087 let l:bpath = bufname(l:br)
1088 let l:bpath_comps = s:parse_lawrencium_path(l:bpath)
1089 if l:bpath_comps['root'] != ''
1090 let l:bpath_root = s:normalizepath(l:bpath_comps['root'])
1091 let l:bpath_path = s:normalizepath(s:stripslash(l:bpath_comps['path']))
1092 call s:trace("Comparing '".l:bpath_path."' and '".l:origpath."' for cleanup.")
1093 if l:bpath_root == l:origroot && l:bpath_path == l:origpath
1094 " Go to that window and switch to the previous buffer
1095 " from the buffer with the file revision.
1096 " Just switching away should delete the buffer since it
1097 " has `bufhidden=delete`.
1098 execute nr . 'wincmd w'
1099 let l:altbufname = s:shellslash(bufname('#'))
1100 if l:altbufname =~# '\v^lawrencium://'
1101 " This is a special Lawrencium buffer... it could be
1102 " a previously shown revision of the file opened with
1103 " this very `Hglog`, which we don't want to switch to.
1104 " Let's just default to editing the original file
1105 " again... not sure what else to do here...
1106 call s:trace("Reverting to editing: " . l:origedit)
1107 execute 'edit ' . l:origedit
1108 else
1109 bprevious
1110 endif
1111 endif
1112 endif
1113 endfor
1114 " Restore the current window if we switched away.
1115 let l:curnr = winnr()
1116 if l:curnr != l:orignr
1117 execute l:orignr . 'wincmd w'
1118 endif
1119
1120 " Delete the temp file if it was created somehow. 1243 " Delete the temp file if it was created somehow.
1121 call s:clean_tempfile(a:path) 1244 call s:clean_tempfile(a:path)
1122 endfunction 1245 endfunction
1123 1246
1124 function! s:HgLog_FileRevEdit(...) 1247 function! s:HgLog_FileRevEdit(...)
1128 else 1251 else
1129 " Revision should be parsed from the current line in the log. 1252 " Revision should be parsed from the current line in the log.
1130 let l:rev = s:HgLog_GetSelectedRev() 1253 let l:rev = s:HgLog_GetSelectedRev()
1131 endif 1254 endif
1132 let l:repo = s:hg_repo() 1255 let l:repo = s:hg_repo()
1256 let l:bufobj = s:buffer_obj()
1133 let l:path = l:repo.GetLawrenciumPath(b:lawrencium_logged_path, 'rev', l:rev) 1257 let l:path = l:repo.GetLawrenciumPath(b:lawrencium_logged_path, 'rev', l:rev)
1258
1259 " Go to the window we were in before going in the log window,
1260 " and open the revision there.
1134 wincmd p 1261 wincmd p
1135 execute 'edit ' . fnameescape(l:path) 1262 call s:edit_deletable_buffer('lawrencium_rev_for', l:bufobj.nr, l:path)
1136 endfunction 1263 endfunction
1137 1264
1138 function! s:HgLog_Diff(...) abort 1265 function! s:HgLog_Diff(...) abort
1139 if a:0 >= 2 1266 if a:0 >= 2
1140 let l:revs = a:1 . ',' . a:2 1267 let l:revs = a:1 . ',' . a:2
1142 let l:revs = a:1 1269 let l:revs = a:1
1143 else 1270 else
1144 let l:revs = s:HgLog_GetSelectedRev() 1271 let l:revs = s:HgLog_GetSelectedRev()
1145 endif 1272 endif
1146 let l:repo = s:hg_repo() 1273 let l:repo = s:hg_repo()
1274 let l:bufobj = s:buffer_obj()
1147 let l:path = l:repo.GetLawrenciumPath(b:lawrencium_logged_path, 'diff', l:revs) 1275 let l:path = l:repo.GetLawrenciumPath(b:lawrencium_logged_path, 'diff', l:revs)
1276
1277 " Go to the window we were in before going in the log window,
1278 " and open the diff there.
1148 wincmd p 1279 wincmd p
1149 execute 'edit ' . fnameescape(l:path) 1280 call s:edit_deletable_buffer('lawrencium_diff_for', l:bufobj.nr, l:path)
1150 endfunction 1281 endfunction
1151 1282
1152 function! s:HgLog_GetSelectedRev(...) abort 1283 function! s:HgLog_GetSelectedRev(...) abort
1153 if a:0 == 1 1284 if a:0 == 1
1154 let l:line = getline(a:1) 1285 let l:line = getline(a:1)
1191 execute 'edit ' . l:annotation_path 1322 execute 'edit ' . l:annotation_path
1192 setlocal nowrap nofoldenable 1323 setlocal nowrap nofoldenable
1193 setlocal filetype=hgannotate 1324 setlocal filetype=hgannotate
1194 else 1325 else
1195 " Store some info about the current buffer. 1326 " Store some info about the current buffer.
1196 let l:cur_bufnr = bufnr('%')
1197 let l:cur_topline = line('w0') + &scrolloff 1327 let l:cur_topline = line('w0') + &scrolloff
1198 let l:cur_line = line('.') 1328 let l:cur_line = line('.')
1199 let l:cur_wrap = &wrap 1329 let l:cur_wrap = &wrap
1200 let l:cur_foldenable = &foldenable 1330 let l:cur_foldenable = &foldenable
1201 1331
1206 execute 'keepalt leftabove vsplit ' . l:annotation_path 1336 execute 'keepalt leftabove vsplit ' . l:annotation_path
1207 setlocal nonumber 1337 setlocal nonumber
1208 setlocal scrollbind nowrap nofoldenable foldcolumn=0 1338 setlocal scrollbind nowrap nofoldenable foldcolumn=0
1209 setlocal filetype=hgannotate 1339 setlocal filetype=hgannotate
1210 1340
1211 " When the annotated window is closed, restore the settings we 1341 " When the annotated buffer is deleted, restore the settings we
1212 " changed on the current buffer. 1342 " changed on the current buffer, and go back to that buffer.
1213 execute 'autocmd BufDelete <buffer> call s:HgAnnotate_Delete(' . l:cur_bufnr . ', ' . l:cur_wrap . ', ' . l:cur_foldenable . ')' 1343 let l:annotate_buffer = s:buffer_obj()
1214 1344 call l:annotate_buffer.OnDelete('execute bufwinnr(' . l:bufnr . ') . "wincmd w"')
1345 call l:annotate_buffer.OnDelete('setlocal noscrollbind')
1346 if l:cur_wrap
1347 call l:annotate_buffer.OnDelete('setlocal wrap')
1348 endif
1349 if l:cur_foldenable
1350 call l:annotate_buffer.OnDelete('setlocal foldenable')
1351 endif
1352
1353 " Go to the line we were at in the source buffer when we
1354 " opened the annotation window.
1215 execute l:cur_topline 1355 execute l:cur_topline
1216 normal! zt 1356 normal! zt
1217 execute l:cur_line 1357 execute l:cur_line
1218 syncbind 1358 syncbind
1219 1359
1220 " Set the correct window width for the annotations. 1360 " Set the correct window width for the annotations.
1221 let l:column_count = strlen(matchstr(getline('.'), '[^:]*:')) + g:lawrencium_annotate_width_offset - 1 1361 let l:column_count = strlen(matchstr(getline('.'), '[^:]*:')) + g:lawrencium_annotate_width_offset - 1
1222 execute "vertical resize " . l:column_count 1362 execute "vertical resize " . l:column_count
1223 set winfixwidth 1363 setlocal winfixwidth
1224 endif 1364 endif
1225 1365
1226 " Make the annotate buffer a Lawrencium buffer. 1366 " Make the annotate buffer a Lawrencium buffer.
1227 let b:mercurial_dir = l:repo.root_dir 1367 let b:mercurial_dir = l:repo.root_dir
1228 let b:lawrencium_annotated_path = l:path 1368 let b:lawrencium_annotated_path = l:path
1232 " Add some other nice commands and mappings. 1372 " Add some other nice commands and mappings.
1233 command! -buffer Hgannotatediffsum :call s:HgAnnotate_DiffSummary() 1373 command! -buffer Hgannotatediffsum :call s:HgAnnotate_DiffSummary()
1234 if g:lawrencium_define_mappings 1374 if g:lawrencium_define_mappings
1235 nnoremap <buffer> <silent> <cr> :Hgannotatediffsum<cr> 1375 nnoremap <buffer> <silent> <cr> :Hgannotatediffsum<cr>
1236 endif 1376 endif
1237 endfunction 1377
1238 1378 " Clean up when the annotate buffer is deleted.
1239 function! s:HgAnnotate_Delete(orig_bufnr, orig_wrap, orig_foldenable) abort 1379 let l:bufobj = s:buffer_obj()
1240 execute 'call setwinvar(bufwinnr(' . a:orig_bufnr . '), "&scrollbind", 0)' 1380 call l:bufobj.OnDelete('call s:HgAnnotate_Delete(' . l:bufobj.nr . ')')
1241 if a:orig_wrap 1381 endfunction
1242 execute 'call setwinvar(bufwinnr(' . a:orig_bufnr . '), "&wrap", 1)' 1382
1243 endif 1383 function! s:HgAnnotate_Delete(bufnr) abort
1244 if a:orig_foldenable 1384 if g:lawrencium_auto_close_buffers
1245 execute 'call setwinvar(bufwinnr(' . a:orig_bufnr . '), "&foldenable", 1)' 1385 call s:delete_dependency_buffers('lawrencium_diff_for', a:bufnr)
1246 endif 1386 endif
1247 endfunction 1387 endfunction
1248 1388
1249 function! s:HgAnnotate_DiffSummary() abort 1389 function! s:HgAnnotate_DiffSummary() abort
1390 " Get the path for the diff of the revision specified under the cursor.
1250 let l:line = getline('.') 1391 let l:line = getline('.')
1251 let l:rev_hash = matchstr(l:line, '\v[a-f0-9]{12}') 1392 let l:rev_hash = matchstr(l:line, '\v[a-f0-9]{12}')
1393
1394 " Get the Lawrencium path for the diff, and the buffer object for the
1395 " annotation.
1252 let l:repo = s:hg_repo() 1396 let l:repo = s:hg_repo()
1253 let l:path = l:repo.GetLawrenciumPath(b:lawrencium_annotated_path, 'diff', l:rev_hash) 1397 let l:path = l:repo.GetLawrenciumPath(b:lawrencium_annotated_path, 'diff', l:rev_hash)
1254 execute b:lawrencium_annotated_bufnr . 'wincmd w' 1398 let l:annotate_buffer = s:buffer_obj()
1255 execute 'keepalt rightbelow split ' . fnameescape(l:path) 1399
1400 " Find a window already displaying diffs for this annotation.
1401 let l:diff_winnr = s:find_buffer_window('lawrencium_diff_for', l:annotate_buffer.nr)
1402 if l:diff_winnr == -1
1403 " Not found... go back to the main source buffer and open a bottom
1404 " split with the diff for the specified revision.
1405 execute bufwinnr(b:lawrencium_annotated_bufnr) . 'wincmd w'
1406 execute 'rightbelow split ' . fnameescape(l:path)
1407 let b:lawrencium_diff_for = l:annotate_buffer.nr
1408 let b:lawrencium_quit_on_delete = 1
1409 else
1410 " Found! Use that window to open the diff.
1411 execute l:diff_winnr . 'wincmd w'
1412 execute 'edit ' . fnameescape(l:path)
1413 let b:lawrencium_diff_for = l:annotate_buffer.nr
1414 endif
1256 endfunction 1415 endfunction
1257 1416
1258 call s:AddMainCommand("Hgannotate :call s:HgAnnotate()") 1417 call s:AddMainCommand("Hgannotate :call s:HgAnnotate()")
1259 1418
1260 " }}} 1419 " }}}