comparison plugin/lawrencium.vim @ 139:065625e1bb31

Split plugin file into multiple extensions.
author Ludovic Chabant <ludovic@chabant.com>
date Mon, 13 Jun 2016 09:32:34 -0700
parents a2d823c82e5f
children 78176bdfe0be
comparison
equal deleted inserted replaced
138:a2d823c82e5f 139:065625e1bb31
51 51
52 if !exists('g:lawrencium_record_start_in_working_buffer') 52 if !exists('g:lawrencium_record_start_in_working_buffer')
53 let g:lawrencium_record_start_in_working_buffer = 0 53 let g:lawrencium_record_start_in_working_buffer = 0
54 endif 54 endif
55 55
56 " }}} 56 if !exists('g:lawrencium_extensions')
57 57 let g:lawrencium_extensions = []
58 " Utility {{{ 58 endif
59
60 " Strips the ending slash in a path.
61 function! s:stripslash(path)
62 return fnamemodify(a:path, ':s?[/\\]$??')
63 endfunction
64
65 " Returns whether a path is absolute.
66 function! s:isabspath(path)
67 return a:path =~# '\v^(\w\:)?[/\\]'
68 endfunction
69
70 " Normalizes the slashes in a path.
71 function! s:normalizepath(path)
72 if exists('+shellslash') && &shellslash
73 return substitute(a:path, '\v/', '\\', 'g')
74 elseif has('win32')
75 return substitute(a:path, '\v/', '\\', 'g')
76 else
77 return a:path
78 endif
79 endfunction
80
81 " Shell-slashes the path (opposite of `normalizepath`).
82 function! s:shellslash(path)
83 if exists('+shellslash') && !&shellslash
84 return substitute(a:path, '\v\\', '/', 'g')
85 else
86 return a:path
87 endif
88 endfunction
89
90 " Like tempname() but with some control over the filename.
91 function! s:tempname(name, ...)
92 let l:path = tempname()
93 let l:result = fnamemodify(l:path, ':h') . '/' . a:name . fnamemodify(l:path, ':t')
94 if a:0 > 0
95 let l:result = l:result . a:1
96 endif
97 return l:result
98 endfunction
99
100 " Delete a temporary file if it exists.
101 function! s:clean_tempfile(path)
102 if filewritable(a:path)
103 call s:trace("Cleaning up temporary file: " . a:path)
104 call delete(a:path)
105 endif
106 endfunction
107
108 " Prints a message if debug tracing is enabled.
109 function! s:trace(message, ...)
110 if g:lawrencium_trace || (a:0 && a:1)
111 let l:message = "lawrencium: " . a:message
112 echom l:message
113 endif
114 endfunction
115
116 " Prints an error message with 'lawrencium error' prefixed to it.
117 function! s:error(message)
118 echom "lawrencium error: " . a:message
119 endfunction
120
121 " Throw a Lawrencium exception message.
122 function! s:throw(message)
123 let v:errmsg = "lawrencium: " . a:message
124 throw v:errmsg
125 endfunction
126
127 " Finds the repository root given a path inside that repository.
128 " Throw an error if not repository is found.
129 function! s:find_repo_root(path)
130 let l:path = s:stripslash(a:path)
131 let l:previous_path = ""
132 while l:path != l:previous_path
133 if isdirectory(l:path . '/.hg')
134 return s:normalizepath(simplify(fnamemodify(l:path, ':p')))
135 endif
136 let l:previous_path = l:path
137 let l:path = fnamemodify(l:path, ':h')
138 endwhile
139 call s:throw("No Mercurial repository found above: " . a:path)
140 endfunction
141
142 " Given a Lawrencium path (e.g: 'lawrencium:///repo/root_dir//foo/bar/file.py//rev=34'), extract
143 " the repository root, relative file path and revision number/changeset ID.
144 "
145 " If a second argument exists, it must be:
146 " - `relative`: to make the file path relative to the repository root.
147 " - `absolute`: to make the file path absolute.
148 "
149 function! s:parse_lawrencium_path(lawrencium_path, ...)
150 let l:repo_path = s:shellslash(a:lawrencium_path)
151 let l:repo_path = substitute(l:repo_path, '\\ ', ' ', 'g')
152 if l:repo_path =~? '\v^lawrencium://'
153 let l:repo_path = strpart(l:repo_path, strlen('lawrencium://'))
154 endif
155
156 let l:root_dir = ''
157 let l:at_idx = stridx(l:repo_path, '//')
158 if l:at_idx >= 0
159 let l:root_dir = strpart(l:repo_path, 0, l:at_idx)
160 let l:repo_path = strpart(l:repo_path, l:at_idx + 2)
161 endif
162
163 let l:value = ''
164 let l:action = ''
165 let l:actionidx = stridx(l:repo_path, '//')
166 if l:actionidx >= 0
167 let l:action = strpart(l:repo_path, l:actionidx + 2)
168 let l:repo_path = strpart(l:repo_path, 0, l:actionidx)
169
170 let l:equalidx = stridx(l:action, '=')
171 if l:equalidx >= 0
172 let l:value = strpart(l:action, l:equalidx + 1)
173 let l:action = strpart(l:action, 0, l:equalidx)
174 endif
175 endif
176
177 if a:0 > 0
178 execute 'cd! ' . fnameescape(l:root_dir)
179 if a:1 == 'relative'
180 let l:repo_path = fnamemodify(l:repo_path, ':.')
181 elseif a:1 == 'absolute'
182 let l:repo_path = fnamemodify(l:repo_path, ':p')
183 endif
184 execute 'cd! -'
185 endif
186
187 let l:result = { 'root': l:root_dir, 'path': l:repo_path, 'action': l:action, 'value': l:value }
188 return l:result
189 endfunction
190
191 " Finds a window whose displayed buffer has a given variable
192 " set to the given value.
193 function! s:find_buffer_window(varname, varvalue) abort
194 for wnr in range(1, winnr('$'))
195 let l:bnr = winbufnr(wnr)
196 if getbufvar(l:bnr, a:varname) == a:varvalue
197 return l:wnr
198 endif
199 endfor
200 return -1
201 endfunction
202
203 " Opens a buffer in a way that makes it easy to delete it later:
204 " - if the about-to-be previous buffer doesn't have a given variable,
205 " just open the new buffer.
206 " - if the about-to-be previous buffer has a given variable, open the
207 " new buffer with the `keepalt` option to make it so that the
208 " actual previous buffer (returned by things like `bufname('#')`)
209 " is the original buffer that was there before the first deletable
210 " buffer was opened.
211 function! s:edit_deletable_buffer(varname, varvalue, path) abort
212 let l:edit_cmd = 'edit '
213 if getbufvar('%', a:varname) != ''
214 let l:edit_cmd = 'keepalt edit '
215 endif
216 execute l:edit_cmd . fnameescape(a:path)
217 call setbufvar('%', a:varname, a:varvalue)
218 endfunction
219
220 " Deletes all buffers that have a given variable set to a given value.
221 " For each buffer, if it is not shown in any window, it will be just deleted.
222 " If it is shown in a window, that window will be switched to the alternate
223 " buffer before the buffer is deleted, unless the `lawrencium_quit_on_delete`
224 " variable is set to `1`, in which case the window is closed too.
225 function! s:delete_dependency_buffers(varname, varvalue) abort
226 let l:cur_winnr = winnr()
227 for bnr in range(1, bufnr('$'))
228 if getbufvar(bnr, a:varname) == a:varvalue
229 " Delete this buffer if it is not shown in any window.
230 " Otherwise, display the alternate buffer before deleting
231 " it so the window is not closed.
232 let l:bwnr = bufwinnr(bnr)
233 if l:bwnr < 0 || getbufvar(bnr, 'lawrencium_quit_on_delete') == 1
234 if bufloaded(l:bnr)
235 call s:trace("Deleting dependency buffer " . bnr)
236 execute "bdelete! " . bnr
237 else
238 call s:trace("Dependency buffer " . bnr . " is already unladed.")
239 endif
240 else
241 execute l:bwnr . "wincmd w"
242 " TODO: better handle case where there's no previous/alternate buffer?
243 let l:prev_bnr = bufnr('#')
244 if l:prev_bnr > 0 && bufloaded(l:prev_bnr)
245 execute "buffer " . l:prev_bnr
246 if bufloaded(l:bnr)
247 call s:trace("Deleting dependency buffer " . bnr . " after switching to " . l:prev_bnr . " in window " . l:bwnr)
248 execute "bdelete! " . bnr
249 else
250 call s:trace("Dependency buffer " . bnr . " is unladed after switching to " . l:prev_bnr)
251 endif
252 else
253 call s:trace("Deleting dependency buffer " . bnr . " and window.")
254 bdelete!
255 endif
256 endif
257 endif
258 endfor
259 if l:cur_winnr != winnr()
260 call s:trace("Returning to window " . l:cur_winnr)
261 execute l:cur_winnr . "wincmd w"
262 endif
263 endfunction
264
265 " Clean up all the 'HG:' lines from a commit message, and see if there's
266 " any message left (Mercurial does this automatically, usually, but
267 " apparently not when you feed it a log file...).
268 function! s:clean_commit_file(log_file) abort
269 let l:lines = readfile(a:log_file)
270 call filter(l:lines, "v:val !~# '\\v^HG:'")
271 if len(filter(copy(l:lines), "v:val !~# '\\v^\\s*$'")) == 0
272 return 0
273 endif
274 call writefile(l:lines, a:log_file)
275 return 1
276 endfunction
277 59
278 " }}} 60 " }}}
279 61
280 " Mercurial Repository Object {{{ 62 " Setup {{{
281 63
282 " Let's define a Mercurial repo 'class' using prototype-based object-oriented 64 call lawrencium#init()
283 " programming.
284 "
285 " The prototype dictionary.
286 let s:HgRepo = {}
287
288 " Constructor.
289 function! s:HgRepo.New(path) abort
290 let l:newRepo = copy(self)
291 let l:newRepo.root_dir = s:find_repo_root(a:path)
292 call s:trace("Built new Mercurial repository object at : " . l:newRepo.root_dir)
293 return l:newRepo
294 endfunction
295
296 " Gets a full path given a repo-relative path.
297 function! s:HgRepo.GetFullPath(path) abort
298 let l:root_dir = self.root_dir
299 if s:isabspath(a:path)
300 call s:throw("Expected relative path, got absolute path: " . a:path)
301 endif
302 return s:normalizepath(l:root_dir . a:path)
303 endfunction
304
305 " Gets a repo-relative path given any path.
306 function! s:HgRepo.GetRelativePath(path) abort
307 execute 'lcd! ' . fnameescape(self.root_dir)
308 let l:relative_path = fnamemodify(a:path, ':.')
309 execute 'lcd! -'
310 return l:relative_path
311 endfunction
312
313 " Gets, and optionally creates, a temp folder for some operation in the `.hg`
314 " directory.
315 function! s:HgRepo.GetTempDir(path, ...) abort
316 let l:tmp_dir = self.GetFullPath('.hg/lawrencium/' . a:path)
317 if !isdirectory(l:tmp_dir)
318 if a:0 > 0 && !a:1
319 return ''
320 endif
321 call mkdir(l:tmp_dir, 'p')
322 endif
323 return l:tmp_dir
324 endfunction
325
326 " Gets a list of files matching a root-relative pattern.
327 " If a flag is passed and is TRUE, a slash will be appended to all
328 " directories.
329 function! s:HgRepo.Glob(pattern, ...) abort
330 let l:root_dir = self.root_dir
331 if (a:pattern =~# '\v^[/\\]')
332 let l:root_dir = s:stripslash(l:root_dir)
333 endif
334 let l:matches = split(glob(l:root_dir . a:pattern), '\n')
335 if a:0 && a:1
336 for l:idx in range(len(l:matches))
337 if !filereadable(l:matches[l:idx])
338 let l:matches[l:idx] = l:matches[l:idx] . '/'
339 endif
340 endfor
341 endif
342 let l:strip_len = len(l:root_dir)
343 call map(l:matches, 'v:val[l:strip_len : -1]')
344 return l:matches
345 endfunction
346
347 " Gets a full Mercurial command.
348 function! s:HgRepo.GetCommand(command, ...) abort
349 " If there's only one argument, and it's a list, then use that as the
350 " argument list.
351 let l:arg_list = a:000
352 if a:0 == 1 && type(a:1) == type([])
353 let l:arg_list = a:1
354 endif
355 let l:prev_shellslash = &shellslash
356 setlocal noshellslash
357 let l:hg_command = g:lawrencium_hg_executable . ' --repository ' . shellescape(s:stripslash(self.root_dir))
358 let l:hg_command = l:hg_command . ' ' . a:command
359 for l:arg in l:arg_list
360 let l:hg_command = l:hg_command . ' ' . shellescape(l:arg)
361 endfor
362 if l:prev_shellslash
363 setlocal shellslash
364 endif
365 return l:hg_command
366 endfunction
367
368 " Runs a Mercurial command in the repo.
369 function! s:HgRepo.RunCommand(command, ...) abort
370 let l:all_args = [1, a:command] + a:000
371 return call(self['RunCommandEx'], l:all_args, self)
372 endfunction
373
374 function! s:HgRepo.RunCommandEx(plain_mode, command, ...) abort
375 let l:prev_hgplain = $HGPLAIN
376 if a:plain_mode
377 let $HGPLAIN = 'true'
378 endif
379 let l:all_args = [a:command] + a:000
380 let l:hg_command = call(self['GetCommand'], l:all_args, self)
381 call s:trace("Running Mercurial command: " . l:hg_command)
382 let l:cmd_out = system(l:hg_command)
383 if a:plain_mode
384 let $HGPLAIN = l:prev_hgplain
385 endif
386 return l:cmd_out
387 endfunction
388
389 " Runs a Mercurial command in the repo and reads its output into the current
390 " buffer.
391 function! s:HgRepo.ReadCommandOutput(command, ...) abort
392 function! s:PutOutputIntoBuffer(command_line)
393 let l:was_buffer_empty = (line('$') == 1 && getline(1) == '')
394 execute '0read!' . escape(a:command_line, '%#\')
395 if l:was_buffer_empty " (Always true?)
396 " '0read' inserts before the cursor, leaving a blank line which
397 " needs to be deleted... but if there are folds in this thing, we
398 " must open them all first otherwise we could delete the whole
399 " contents of the last fold (since Vim may close them all by
400 " default).
401 normal! zRG"_dd
402 endif
403 endfunction
404
405 let l:all_args = [a:command] + a:000
406 let l:hg_command = call(self['GetCommand'], l:all_args, self)
407 call s:trace("Running Mercurial command: " . l:hg_command)
408 call s:PutOutputIntoBuffer(l:hg_command)
409 endfunction
410
411 " Build a Lawrencium path for the given file and action.
412 " By default, the given path will be made relative to the repository root,
413 " unless '0' is passed as the 4th argument.
414 function! s:HgRepo.GetLawrenciumPath(path, action, value, ...) abort
415 let l:path = a:path
416 if a:0 == 0 || !a:1
417 let l:path = self.GetRelativePath(a:path)
418 endif
419 let l:path = fnameescape(l:path)
420 let l:result = 'lawrencium://' . s:stripslash(self.root_dir) . '//' . l:path
421 if a:action !=? ''
422 let l:result = l:result . '//' . a:action
423 if a:value !=? ''
424 let l:result = l:result . '=' . a:value
425 endif
426 endif
427 return l:result
428 endfunction
429
430 " Repo cache map.
431 let s:buffer_repos = {}
432
433 " Get a cached repo.
434 function! s:hg_repo(...) abort
435 " Use the given path, or the mercurial directory of the current buffer.
436 if a:0 == 0
437 if exists('b:mercurial_dir')
438 let l:path = b:mercurial_dir
439 else
440 let l:path = s:find_repo_root(expand('%:p'))
441 endif
442 else
443 let l:path = a:1
444 endif
445 " Find a cache repo instance, or make a new one.
446 if has_key(s:buffer_repos, l:path)
447 return get(s:buffer_repos, l:path)
448 else
449 let l:repo = s:HgRepo.New(l:path)
450 let s:buffer_repos[l:path] = l:repo
451 return l:repo
452 endif
453 endfunction
454
455 " Sets up the current buffer with Lawrencium commands if it contains a file from a Mercurial repo.
456 " If the file is not in a Mercurial repo, just exit silently.
457 function! s:setup_buffer_commands() abort
458 call s:trace("Scanning buffer '" . bufname('%') . "' for Lawrencium setup...")
459 let l:do_setup = 1
460 if exists('b:mercurial_dir')
461 if b:mercurial_dir =~# '\v^\s*$'
462 unlet b:mercurial_dir
463 else
464 let l:do_setup = 0
465 endif
466 endif
467 try
468 let l:repo = s:hg_repo()
469 catch /^lawrencium\:/
470 return
471 endtry
472 let b:mercurial_dir = l:repo.root_dir
473 if exists('b:mercurial_dir') && l:do_setup
474 call s:trace("Setting Mercurial commands for buffer '" . bufname('%'))
475 call s:trace(" with repo : " . expand(b:mercurial_dir))
476 silent doautocmd User Lawrencium
477 endif
478 endfunction
479 65
480 augroup lawrencium_detect 66 augroup lawrencium_detect
481 autocmd! 67 autocmd!
482 autocmd BufNewFile,BufReadPost * call s:setup_buffer_commands() 68 autocmd BufNewFile,BufReadPost * call lawrencium#setup_buffer_commands()
483 autocmd VimEnter * if expand('<amatch>')==''|call s:setup_buffer_commands()|endif 69 autocmd VimEnter * if expand('<amatch>')==''|call lawrencium#setup_buffer_commands()|endif
484 augroup end 70 augroup end
485 71
486 " }}}
487
488 " Buffer Object {{{
489
490 " The prototype dictionary.
491 let s:Buffer = {}
492
493 " Constructor.
494 function! s:Buffer.New(number) dict abort
495 let l:newBuffer = copy(self)
496 let l:newBuffer.nr = a:number
497 let l:newBuffer.var_backup = {}
498 let l:newBuffer.cmd_names = {}
499 let l:newBuffer.on_delete = []
500 let l:newBuffer.on_winleave = []
501 let l:newBuffer.on_unload = []
502 execute 'augroup lawrencium_buffer_' . a:number
503 execute ' autocmd!'
504 execute ' autocmd BufDelete <buffer=' . a:number . '> call s:buffer_on_delete(' . a:number . ')'
505 execute 'augroup end'
506 call s:trace("Built new buffer object for buffer: " . a:number)
507 return l:newBuffer
508 endfunction
509
510 function! s:Buffer.GetName(...) dict abort
511 let l:name = bufname(self.nr)
512 if a:0 > 0
513 let l:name = fnamemodify(l:name, a:1)
514 endif
515 return l:name
516 endfunction
517
518 function! s:Buffer.GetVar(var) dict abort
519 return getbufvar(self.nr, a:var)
520 endfunction
521
522 function! s:Buffer.SetVar(var, value) dict abort
523 if !has_key(self.var_backup, a:var)
524 let self.var_backup[a:var] = getbufvar(self.nr, a:var)
525 endif
526 return setbufvar(self.nr, a:var, a:value)
527 endfunction
528
529 function! s:Buffer.RestoreVars() dict abort
530 for key in keys(self.var_backup)
531 setbufvar(self.nr, key, self.var_backup[key])
532 endfor
533 endfunction
534
535 function! s:Buffer.DefineCommand(name, ...) dict abort
536 if a:0 == 0
537 call s:throw("Not enough parameters for s:Buffer.DefineCommands()")
538 endif
539 if a:0 == 1
540 let l:flags = ''
541 let l:cmd = a:1
542 else
543 let l:flags = a:1
544 let l:cmd = a:2
545 endif
546 if has_key(self.cmd_names, a:name)
547 call s:throw("Command '".a:name."' is already defined in buffer ".self.nr)
548 endif
549 if bufnr('%') != self.nr
550 call s:throw("You must move to buffer ".self.nr."first before defining local commands")
551 endif
552 let self.cmd_names[a:name] = 1
553 let l:real_flags = ''
554 if type(l:flags) == type('')
555 let l:real_flags = l:flags
556 endif
557 execute 'command -buffer '.l:real_flags.' '.a:name.' '.l:cmd
558 endfunction
559
560 function! s:Buffer.DeleteCommand(name) dict abort
561 if !has_key(self.cmd_names, a:name)
562 call s:throw("Command '".a:name."' has not been defined in buffer ".self.nr)
563 endif
564 if bufnr('%') != self.nr
565 call s:throw("You must move to buffer ".self.nr."first before deleting local commands")
566 endif
567 execute 'delcommand '.a:name
568 call remove(self.cmd_names, a:name)
569 endfunction
570
571 function! s:Buffer.DeleteCommands() dict abort
572 if bufnr('%') != self.nr
573 call s:throw("You must move to buffer ".self.nr."first before deleting local commands")
574 endif
575 for name in keys(self.cmd_names)
576 execute 'delcommand '.name
577 endfor
578 let self.cmd_names = {}
579 endfunction
580
581 function! s:Buffer.MoveToFirstWindow() dict abort
582 let l:win_nr = bufwinnr(self.nr)
583 if l:win_nr < 0
584 if a:0 > 0 && a:1 == 0
585 return 0
586 endif
587 call s:throw("No windows currently showing buffer ".self.nr)
588 endif
589 execute l:win_nr.'wincmd w'
590 return 1
591 endfunction
592
593 function! s:Buffer.OnDelete(cmd) dict abort
594 call s:trace("Adding BufDelete callback for buffer " . self.nr . ": " . a:cmd)
595 call add(self.on_delete, a:cmd)
596 endfunction
597
598 function! s:Buffer.OnWinLeave(cmd) dict abort
599 if len(self.on_winleave) == 0
600 call s:trace("Adding BufWinLeave auto-command on buffer " . self.nr)
601 execute 'augroup lawrencium_buffer_' . self.nr . '_winleave'
602 execute ' autocmd!'
603 execute ' autocmd BufWinLeave <buffer=' . self.nr . '> call s:buffer_on_winleave(' . self.nr .')'
604 execute 'augroup end'
605 endif
606 call s:trace("Adding BufWinLeave callback for buffer " . self.nr . ": " . a:cmd)
607 call add(self.on_winleave, a:cmd)
608 endfunction
609
610 function! s:Buffer.OnUnload(cmd) dict abort
611 if len(self.on_unload) == 0
612 call s:trace("Adding BufUnload auto-command on buffer " . self.nr)
613 execute 'augroup lawrencium_buffer_' . self.nr . '_unload'
614 execute ' autocmd!'
615 execute ' autocmd BufUnload <buffer=' . self.nr . '> call s:buffer_on_unload(' . self.nr . ')'
616 execute 'augroup end'
617 endif
618 call s:trace("Adding BufUnload callback for buffer " . self.nr . ": " . a:cmd)
619 call add(self.on_unload, a:cmd)
620 endfunction
621
622 let s:buffer_objects = {}
623
624 " Get a buffer instance for the specified buffer number, or the
625 " current buffer if nothing is specified.
626 function! s:buffer_obj(...) abort
627 let l:bufnr = a:0 ? a:1 : bufnr('%')
628 if !has_key(s:buffer_objects, l:bufnr)
629 let s:buffer_objects[l:bufnr] = s:Buffer.New(l:bufnr)
630 endif
631 return s:buffer_objects[l:bufnr]
632 endfunction
633
634 " Execute all the "on delete" callbacks.
635 function! s:buffer_on_delete(number) abort
636 let l:bufobj = s:buffer_objects[a:number]
637 call s:trace("Calling BufDelete callbacks on buffer " . l:bufobj.nr)
638 for cmd in l:bufobj.on_delete
639 call s:trace(" [" . cmd . "]")
640 execute cmd
641 endfor
642 call s:trace("Deleted buffer object " . l:bufobj.nr)
643 call remove(s:buffer_objects, l:bufobj.nr)
644 execute 'augroup lawrencium_buffer_' . l:bufobj.nr
645 execute ' autocmd!'
646 execute 'augroup end'
647 endfunction
648
649 " Execute all the "on winleave" callbacks.
650 function! s:buffer_on_winleave(number) abort
651 let l:bufobj = s:buffer_objects[a:number]
652 call s:trace("Calling BufWinLeave callbacks on buffer " . l:bufobj.nr)
653 for cmd in l:bufobj.on_winleave
654 call s:trace(" [" . cmd . "]")
655 execute cmd
656 endfor
657 execute 'augroup lawrencium_buffer_' . l:bufobj.nr . '_winleave'
658 execute ' autocmd!'
659 execute 'augroup end'
660 endfunction
661
662 " Execute all the "on unload" callbacks.
663 function! s:buffer_on_unload(number) abort
664 let l:bufobj = s:buffer_objects[a:number]
665 call s:trace("Calling BufUnload callbacks on buffer " . l:bufobj.nr)
666 for cmd in l:bufobj.on_unload
667 call s:trace(" [" . cmd . "]")
668 execute cmd
669 endfor
670 execute 'augroup lawrencium_buffer_' . l:bufobj.nr . '_unload'
671 execute ' autocmd!'
672 execute 'augroup end'
673 endfunction
674
675 " }}}
676
677 " Lawrencium Files {{{
678
679 " Read revision (`hg cat`)
680 function! s:read_lawrencium_rev(repo, path_parts, full_path) abort
681 let l:rev = a:path_parts['value']
682 if l:rev == ''
683 call a:repo.ReadCommandOutput('cat', a:full_path)
684 else
685 call a:repo.ReadCommandOutput('cat', '-r', l:rev, a:full_path)
686 endif
687 endfunction
688
689 " Status (`hg status`)
690 function! s:read_lawrencium_status(repo, path_parts, full_path) abort
691 if a:path_parts['path'] == ''
692 call a:repo.ReadCommandOutput('status')
693 else
694 call a:repo.ReadCommandOutput('status', a:full_path)
695 endif
696 setlocal nomodified
697 setlocal filetype=hgstatus
698 setlocal bufhidden=delete
699 setlocal buftype=nofile
700 endfunction
701
702 " Log (`hg log`)
703 let s:log_style_file = expand("<sfile>:h:h") . "/resources/hg_log.style"
704
705 function! s:read_lawrencium_log(repo, path_parts, full_path) abort
706 let l:log_opts = join(split(a:path_parts['value'], ','))
707 let l:log_cmd = "log " . l:log_opts
708
709 if a:path_parts['path'] == ''
710 call a:repo.ReadCommandOutput(l:log_cmd, '--style', s:log_style_file)
711 else
712 call a:repo.ReadCommandOutput(l:log_cmd, '--style', s:log_style_file, a:full_path)
713 endif
714 setlocal filetype=hglog
715 endfunction
716
717 function! s:read_lawrencium_logpatch(repo, path_parts, full_path) abort
718 let l:log_cmd = 'log --patch --verbose --rev ' . a:path_parts['value']
719
720 if a:path_parts['path'] == ''
721 call a:repo.ReadCommandOutput(l:log_cmd)
722 else
723 call a:repo.ReadCommandOutput(l:log_cmd, a:full_path)
724 endif
725 setlocal filetype=diff
726 endfunction
727
728 " Diff revisions (`hg diff`)
729 function! s:read_lawrencium_diff(repo, path_parts, full_path) abort
730 let l:diffargs = []
731 let l:commaidx = stridx(a:path_parts['value'], ',')
732 if l:commaidx > 0
733 let l:rev1 = strpart(a:path_parts['value'], 0, l:commaidx)
734 let l:rev2 = strpart(a:path_parts['value'], l:commaidx + 1)
735 if l:rev1 == '-'
736 let l:diffargs = [ '-r', l:rev2 ]
737 elseif l:rev2 == '-'
738 let l:diffargs = [ '-r', l:rev1 ]
739 else
740 let l:diffargs = [ '-r', l:rev1, '-r', l:rev2 ]
741 endif
742 elseif a:path_parts['value'] != ''
743 let l:diffargs = [ '-c', a:path_parts['value'] ]
744 else
745 let l:diffargs = []
746 endif
747 if a:path_parts['path'] != '' && a:path_parts['path'] != '.'
748 call add(l:diffargs, a:full_path)
749 endif
750 call a:repo.ReadCommandOutput('diff', l:diffargs)
751 setlocal filetype=diff
752 setlocal nofoldenable
753 endfunction
754
755 " Annotate file
756 function! s:read_lawrencium_annotate(repo, path_parts, full_path) abort
757 let l:cmd_args = ['-c', '-n', '-u', '-d', '-q']
758 if a:path_parts['value'] == 'v=1'
759 call insert(l:cmd_args, '-v', 0)
760 endif
761 call add(l:cmd_args, a:full_path)
762 call a:repo.ReadCommandOutput('annotate', l:cmd_args)
763 endfunction
764
765 " MQ series
766 function! s:read_lawrencium_qseries(repo, path_parts, full_path) abort
767 let l:names = split(a:repo.RunCommand('qseries'), '\n')
768 let l:head = split(a:repo.RunCommand('qapplied', '-s'), '\n')
769 let l:tail = split(a:repo.RunCommand('qunapplied', '-s'), '\n')
770
771 let l:idx = 0
772 let l:curbuffer = bufname('%')
773 for line in l:head
774 call setbufvar(l:curbuffer, 'lawrencium_patchname_' . (l:idx + 1), l:names[l:idx])
775 call append(l:idx, "*" . line)
776 let l:idx = l:idx + 1
777 endfor
778 for line in l:tail
779 call setbufvar(l:curbuffer, 'lawrencium_patchname_' . (l:idx + 1), l:names[l:idx])
780 call append(l:idx, line)
781 let l:idx = l:idx + 1
782 endfor
783 call setbufvar(l:curbuffer, 'lawrencium_patchname_top', l:names[len(l:head) - 1])
784 set filetype=hgqseries
785 endfunction
786
787 " Generic read
788 let s:lawrencium_file_readers = {
789 \'rev': function('s:read_lawrencium_rev'),
790 \'log': function('s:read_lawrencium_log'),
791 \'logpatch': function('s:read_lawrencium_logpatch'),
792 \'diff': function('s:read_lawrencium_diff'),
793 \'status': function('s:read_lawrencium_status'),
794 \'annotate': function('s:read_lawrencium_annotate'),
795 \'qseries': function('s:read_lawrencium_qseries')
796 \}
797 let s:lawrencium_file_customoptions = {
798 \'status': 1
799 \}
800
801 function! s:ReadLawrenciumFile(path) abort
802 call s:trace("Reading Lawrencium file: " . a:path)
803 let l:path_parts = s:parse_lawrencium_path(a:path)
804 if l:path_parts['root'] == ''
805 call s:throw("Can't get repository root from: " . a:path)
806 endif
807 if !has_key(s:lawrencium_file_readers, l:path_parts['action'])
808 call s:throw("No registered reader for action: " . l:path_parts['action'])
809 endif
810
811 " Call the registered reader.
812 let l:repo = s:hg_repo(l:path_parts['root'])
813 let l:full_path = l:repo.root_dir . l:path_parts['path']
814 let LawrenciumFileReader = s:lawrencium_file_readers[l:path_parts['action']]
815 call LawrenciumFileReader(l:repo, l:path_parts, l:full_path)
816
817 " Setup the new buffer.
818 if !has_key(s:lawrencium_file_customoptions, l:path_parts['action'])
819 setlocal readonly
820 setlocal nomodified
821 setlocal bufhidden=delete
822 setlocal buftype=nofile
823 endif
824 goto
825
826 " Remember the real Lawrencium path, because Vim can fuck up the slashes
827 " on Windows.
828 let b:lawrencium_path = a:path
829
830 " Remember the repo it belongs to and make
831 " the Lawrencium commands available.
832 let b:mercurial_dir = l:repo.root_dir
833 call s:DefineMainCommands()
834
835 return ''
836 endfunction
837
838 function! s:WriteLawrenciumFile(path) abort
839 call s:trace("Writing Lawrencium file: " . a:path)
840 endfunction
841
842 augroup lawrencium_files 72 augroup lawrencium_files
843 autocmd! 73 autocmd!
844 autocmd BufReadCmd lawrencium://**//**//* exe s:ReadLawrenciumFile(expand('<amatch>')) 74 autocmd BufReadCmd lawrencium://**//**//* exe lawrencium#read_lawrencium_file(expand('<amatch>'))
845 autocmd BufWriteCmd lawrencium://**//**//* exe s:WriteLawrenciumFile(expand('<amatch>')) 75 autocmd BufWriteCmd lawrencium://**//**//* exe lawrencium#write_lawrencium_file(expand('<amatch>'))
846 augroup END 76 augroup END
847 77
848 " }}} 78 " }}}
849 79
850 " Buffer Commands Management {{{
851
852 " Store the commands for Lawrencium-enabled buffers so that we can add them in
853 " batch when we need to.
854 let s:main_commands = []
855
856 function! s:AddMainCommand(command) abort
857 let s:main_commands += [a:command]
858 endfunction
859
860 function! s:DefineMainCommands()
861 for l:command in s:main_commands
862 execute 'command! -buffer ' . l:command
863 endfor
864 endfunction
865
866 augroup lawrencium_main
867 autocmd!
868 autocmd User Lawrencium call s:DefineMainCommands()
869 augroup end
870
871 " }}}
872
873 " Commands Auto-Complete {{{
874
875 " Auto-complete function for commands that take repo-relative file paths.
876 function! s:ListRepoFiles(ArgLead, CmdLine, CursorPos) abort
877 let l:matches = s:hg_repo().Glob(a:ArgLead . '*', 1)
878 call map(l:matches, 's:normalizepath(v:val)')
879 return l:matches
880 endfunction
881
882 " Auto-complete function for commands that take repo-relative directory paths.
883 function! s:ListRepoDirs(ArgLead, CmdLine, CursorPos) abort
884 let l:matches = s:hg_repo().Glob(a:ArgLead . '*/')
885 call map(l:matches, 's:normalizepath(v:val)')
886 return l:matches
887 endfunction
888
889 " }}}
890
891 " Hg {{{
892
893 function! s:Hg(bang, ...) abort
894 let l:repo = s:hg_repo()
895 if g:lawrencium_auto_cd
896 " Temporary set the current directory to the root of the repo
897 " to make auto-completed paths work magically.
898 execute 'cd! ' . fnameescape(l:repo.root_dir)
899 endif
900 let l:output = call(l:repo.RunCommandEx, [0] + a:000, l:repo)
901 if g:lawrencium_auto_cd
902 execute 'cd! -'
903 endif
904 silent doautocmd User HgCmdPost
905 if a:bang
906 " Open the output of the command in a temp file.
907 let l:temp_file = s:tempname('hg-output-', '.txt')
908 split
909 execute 'edit ' . fnameescape(l:temp_file)
910 call append(0, split(l:output, '\n'))
911 call cursor(1, 1)
912
913 " Make it a temp buffer
914 setlocal bufhidden=delete
915 setlocal buftype=nofile
916
917 " Try to find a nice syntax to set given the current command.
918 let l:command_name = s:GetHgCommandName(a:000)
919 if l:command_name != '' && exists('g:lawrencium_hg_commands_file_types')
920 let l:file_type = get(g:lawrencium_hg_commands_file_types, l:command_name, '')
921 if l:file_type != ''
922 execute 'setlocal ft=' . l:file_type
923 endif
924 endif
925 else
926 " Just print out the output of the command.
927 echo l:output
928 endif
929 endfunction
930
931 " Include the generated HG usage file.
932 let s:usage_file = expand("<sfile>:h:h") . "/resources/hg_usage.vim"
933 if filereadable(s:usage_file)
934 execute "source " . fnameescape(s:usage_file)
935 else
936 call s:error("Can't find the Mercurial usage file. Auto-completion will be disabled in Lawrencium.")
937 endif
938
939 " Include the command file type mappings.
940 let s:file_type_mappings = expand("<sfile>:h:h") . '/resources/hg_command_file_types.vim'
941 if filereadable(s:file_type_mappings)
942 execute "source " . fnameescape(s:file_type_mappings)
943 endif
944
945 function! s:CompleteHg(ArgLead, CmdLine, CursorPos)
946 " Don't do anything if the usage file was not sourced.
947 if !exists('g:lawrencium_hg_commands') || !exists('g:lawrencium_hg_options')
948 return []
949 endif
950
951 " a:ArgLead seems to be the number 0 when completing a minus '-'.
952 " Gotta find out why...
953 let l:arglead = a:ArgLead
954 if type(a:ArgLead) == type(0)
955 let l:arglead = '-'
956 endif
957
958 " Try completing a global option, before any command name.
959 if a:CmdLine =~# '\v^Hg(\s+\-[a-zA-Z0-9\-_]*)+$'
960 return filter(copy(g:lawrencium_hg_options), "v:val[0:strlen(l:arglead)-1] ==# l:arglead")
961 endif
962
963 " Try completing a command (note that there could be global options before
964 " the command name).
965 if a:CmdLine =~# '\v^Hg\s+(\-[a-zA-Z0-9\-_]+\s+)*[a-zA-Z]+$'
966 return filter(keys(g:lawrencium_hg_commands), "v:val[0:strlen(l:arglead)-1] ==# l:arglead")
967 endif
968
969 " Try completing a command's options.
970 let l:cmd = matchstr(a:CmdLine, '\v(^Hg\s+(\-[a-zA-Z0-9\-_]+\s+)*)@<=[a-zA-Z]+')
971 if strlen(l:cmd) > 0 && l:arglead[0] ==# '-'
972 if has_key(g:lawrencium_hg_commands, l:cmd)
973 " Return both command options and global options together.
974 let l:copts = filter(copy(g:lawrencium_hg_commands[l:cmd]), "v:val[0:strlen(l:arglead)-1] ==# l:arglead")
975 let l:gopts = filter(copy(g:lawrencium_hg_options), "v:val[0:strlen(l:arglead)-1] ==# l:arglead")
976 return l:copts + l:gopts
977 endif
978 endif
979
980 " Just auto-complete with filenames unless it's an option.
981 if l:arglead[0] ==# '-'
982 return []
983 else
984 return s:ListRepoFiles(a:ArgLead, a:CmdLine, a:CursorPos)
985 endfunction
986
987 function! s:GetHgCommandName(args) abort
988 for l:a in a:args
989 if stridx(l:a, '-') != 0
990 return l:a
991 endif
992 endfor
993 return ''
994 endfunction
995
996 call s:AddMainCommand("-bang -complete=customlist,s:CompleteHg -nargs=* Hg :call s:Hg(<bang>0, <f-args>)")
997
998 " }}}
999
1000 " Hgstatus {{{
1001
1002 function! s:HgStatus() abort
1003 " Get the repo and the Lawrencium path for `hg status`.
1004 let l:repo = s:hg_repo()
1005 let l:status_path = l:repo.GetLawrenciumPath('', 'status', '')
1006
1007 " Open the Lawrencium buffer in a new split window of the right size.
1008 if g:lawrencium_status_win_split_above
1009 execute "keepalt leftabove split " . fnameescape(l:status_path)
1010 else
1011 execute "keepalt rightbelow split " . fnameescape(l:status_path)
1012 endif
1013
1014 if (line('$') == 1 && getline(1) == '')
1015 " Buffer is empty, which means there are not changes...
1016 " Quit and display a message.
1017 " TODO: figure out why the first `echom` doesn't show when alone.
1018 bdelete
1019 echom "Nothing was modified."
1020 echom ""
1021 return
1022 endif
1023
1024 execute "setlocal winfixheight"
1025 if !g:lawrencium_status_win_split_even
1026 execute "setlocal winheight=" . (line('$') + 1)
1027 execute "resize " . (line('$') + 1)
1028 endif
1029
1030 " Add some nice commands.
1031 command! -buffer Hgstatusedit :call s:HgStatus_FileEdit(0)
1032 command! -buffer Hgstatusdiff :call s:HgStatus_Diff(0)
1033 command! -buffer Hgstatusvdiff :call s:HgStatus_Diff(1)
1034 command! -buffer Hgstatustabdiff :call s:HgStatus_Diff(2)
1035 command! -buffer Hgstatusdiffsum :call s:HgStatus_DiffSummary(1)
1036 command! -buffer Hgstatusvdiffsum :call s:HgStatus_DiffSummary(2)
1037 command! -buffer Hgstatustabdiffsum :call s:HgStatus_DiffSummary(3)
1038 command! -buffer Hgstatusrefresh :call s:HgStatus_Refresh()
1039 command! -buffer -range -bang Hgstatusrevert :call s:HgStatus_Revert(<line1>, <line2>, <bang>0)
1040 command! -buffer -range Hgstatusaddremove :call s:HgStatus_AddRemove(<line1>, <line2>)
1041 command! -buffer -range=% -bang Hgstatuscommit :call s:HgStatus_Commit(<line1>, <line2>, <bang>0, 0)
1042 command! -buffer -range=% -bang Hgstatusvcommit :call s:HgStatus_Commit(<line1>, <line2>, <bang>0, 1)
1043 command! -buffer -range=% -nargs=+ Hgstatusqnew :call s:HgStatus_QNew(<line1>, <line2>, <f-args>)
1044 command! -buffer -range=% Hgstatusqrefresh :call s:HgStatus_QRefresh(<line1>, <line2>)
1045
1046 " Add some handy mappings.
1047 if g:lawrencium_define_mappings
1048 nnoremap <buffer> <silent> <cr> :Hgstatusedit<cr>
1049 nnoremap <buffer> <silent> <C-N> :call search('^[MARC\!\?I ]\s.', 'We')<cr>
1050 nnoremap <buffer> <silent> <C-P> :call search('^[MARC\!\?I ]\s.', 'Wbe')<cr>
1051 nnoremap <buffer> <silent> <C-D> :Hgstatustabdiff<cr>
1052 nnoremap <buffer> <silent> <C-V> :Hgstatusvdiff<cr>
1053 nnoremap <buffer> <silent> <C-U> :Hgstatusdiffsum<cr>
1054 nnoremap <buffer> <silent> <C-H> :Hgstatusvdiffsum<cr>
1055 nnoremap <buffer> <silent> <C-A> :Hgstatusaddremove<cr>
1056 nnoremap <buffer> <silent> <C-S> :Hgstatuscommit<cr>
1057 nnoremap <buffer> <silent> <C-R> :Hgstatusrefresh<cr>
1058 nnoremap <buffer> <silent> q :bdelete!<cr>
1059
1060 vnoremap <buffer> <silent> <C-A> :Hgstatusaddremove<cr>
1061 vnoremap <buffer> <silent> <C-S> :Hgstatuscommit<cr>
1062 endif
1063 endfunction
1064
1065 function! s:HgStatus_Refresh(...) abort
1066 if a:0 > 0
1067 let l:win_nr = bufwinnr(a:1)
1068 call s:trace("Switching back to status window ".l:win_nr)
1069 if l:win_nr < 0
1070 call s:throw("Can't find the status window anymore!")
1071 endif
1072 execute l:win_nr . 'wincmd w'
1073 " Delete everything in the buffer, and re-read the status into it.
1074 " TODO: In theory I would only have to do `edit` like below when we're
1075 " already in the window, but for some reason Vim just goes bonkers and
1076 " weird shit happens. I have no idea why, hence the work-around here
1077 " to bypass the whole `BufReadCmd` auto-command altogether, and just
1078 " edit the buffer in place.
1079 normal! ggVGd
1080 call s:ReadLawrenciumFile(b:lawrencium_path)
1081 return
1082 endif
1083
1084 " Just re-edit the buffer, it will reload the contents by calling
1085 " the matching Mercurial command.
1086 edit
1087 endfunction
1088
1089 function! s:HgStatus_FileEdit(newtab) abort
1090 " Get the path of the file the cursor is on.
1091 let l:filename = s:HgStatus_GetSelectedFile()
1092
1093 let l:cleanupbufnr = -1
1094 if a:newtab == 0
1095 " If the file is already open in a window, jump to that window.
1096 " Otherwise, jump to the previous window and open it there.
1097 for nr in range(1, winnr('$'))
1098 let l:br = winbufnr(nr)
1099 let l:bpath = fnamemodify(bufname(l:br), ':p')
1100 if l:bpath ==# l:filename
1101 execute nr . 'wincmd w'
1102 return
1103 endif
1104 endfor
1105 wincmd p
1106 else
1107 " Just open a new tab so we can edit the file there.
1108 " We don't use `tabedit` because it messes up the current window
1109 " if it happens to be the same file.
1110 " We'll just have to clean up the default empty buffer created.
1111 tabnew
1112 let l:cleanupbufnr = bufnr('%')
1113 endif
1114 execute 'edit ' . fnameescape(l:filename)
1115 if l:cleanupbufnr >= 0
1116 execute 'bdelete ' . l:cleanupbufnr
1117 endif
1118 endfunction
1119
1120 function! s:HgStatus_AddRemove(linestart, lineend) abort
1121 " Get the selected filenames.
1122 let l:filenames = s:HgStatus_GetSelectedFiles(a:linestart, a:lineend, ['!', '?'])
1123 if len(l:filenames) == 0
1124 call s:error("No files to add or remove in selection or current line.")
1125 return
1126 endif
1127
1128 " Run `addremove` on those paths.
1129 let l:repo = s:hg_repo()
1130 call l:repo.RunCommand('addremove', l:filenames)
1131
1132 " Refresh the status window.
1133 call s:HgStatus_Refresh()
1134 endfunction
1135
1136 function! s:HgStatus_Revert(linestart, lineend, bang) abort
1137 " Get the selected filenames.
1138 let l:filenames = s:HgStatus_GetSelectedFiles(a:linestart, a:lineend, ['M', 'A', 'R'])
1139 if len(l:filenames) == 0
1140 call s:error("No files to revert in selection or current line.")
1141 return
1142 endif
1143
1144 " Run `revert` on those paths.
1145 " If the bang modifier is specified, revert with no backup.
1146 let l:repo = s:hg_repo()
1147 if a:bang
1148 call insert(l:filenames, '-C', 0)
1149 endif
1150 call l:repo.RunCommand('revert', l:filenames)
1151
1152 " Refresh the status window.
1153 call s:HgStatus_Refresh()
1154 endfunction
1155
1156 function! s:HgStatus_Commit(linestart, lineend, bang, vertical) abort
1157 " Get the selected filenames.
1158 let l:filenames = s:HgStatus_GetSelectedFiles(a:linestart, a:lineend, ['M', 'A', 'R'])
1159 if len(l:filenames) == 0
1160 call s:error("No files to commit in selection or file.")
1161 return
1162 endif
1163
1164 " Run `Hgcommit` on those paths.
1165 let l:buf_nr = bufnr('%')
1166 let l:callback = 'call s:HgStatus_Refresh('.l:buf_nr.')'
1167 call s:HgCommit(a:bang, a:vertical, l:callback, l:filenames)
1168 endfunction
1169
1170 function! s:HgStatus_Diff(split) abort
1171 " Open the file and run `Hgdiff` on it.
1172 " We also need to translate the split mode for it... if we already
1173 " opened the file in a new tab, `HgDiff` only needs to do a vertical
1174 " split (i.e. split=1).
1175 let l:newtab = 0
1176 let l:hgdiffsplit = a:split
1177 if a:split == 2
1178 let l:newtab = 1
1179 let l:hgdiffsplit = 1
1180 endif
1181 call s:HgStatus_FileEdit(l:newtab)
1182 call s:HgDiff('%:p', l:hgdiffsplit)
1183 endfunction
1184
1185 function! s:HgStatus_DiffSummary(split) abort
1186 " Get the path of the file the cursor is on.
1187 let l:path = s:HgStatus_GetSelectedFile()
1188 " Reuse the same diff summary window
1189 let l:reuse_id = 'lawrencium_diffsum_for_' . bufnr('%')
1190 let l:split_prev_win = (a:split < 3)
1191 let l:args = {'reuse_id': l:reuse_id, 'use_prev_win': l:split_prev_win,
1192 \'avoid_win': winnr(), 'split_mode': a:split}
1193 call s:HgDiffSummary(l:path, l:args)
1194 endfunction
1195
1196 function! s:HgStatus_QNew(linestart, lineend, patchname, ...) abort
1197 " Get the selected filenames.
1198 let l:filenames = s:HgStatus_GetSelectedFiles(a:linestart, a:lineend, ['M', 'A', 'R'])
1199 if len(l:filenames) == 0
1200 call s:error("No files in selection or file to create patch.")
1201 return
1202 endif
1203
1204 " Run `Hg qnew` on those paths.
1205 let l:repo = s:hg_repo()
1206 call insert(l:filenames, a:patchname, 0)
1207 if a:0 > 0
1208 call insert(l:filenames, '-m', 0)
1209 let l:message = '"' . join(a:000, ' ') . '"'
1210 call insert(l:filenames, l:message, 1)
1211 endif
1212 call l:repo.RunCommand('qnew', l:filenames)
1213
1214 " Refresh the status window.
1215 call s:HgStatus_Refresh()
1216 endfunction
1217
1218 function! s:HgStatus_QRefresh(linestart, lineend) abort
1219 " Get the selected filenames.
1220 let l:filenames = s:HgStatus_GetSelectedFiles(a:linestart, a:lineend, ['M', 'A', 'R'])
1221 if len(l:filenames) == 0
1222 call s:error("No files in selection or file to refresh the patch.")
1223 return
1224 endif
1225
1226 " Run `Hg qrefresh` on those paths.
1227 let l:repo = s:hg_repo()
1228 call insert(l:filenames, '-s', 0)
1229 call l:repo.RunCommand('qrefresh', l:filenames)
1230
1231 " Refresh the status window.
1232 call s:HgStatus_Refresh()
1233 endfunction
1234
1235
1236 function! s:HgStatus_GetSelectedFile() abort
1237 let l:filenames = s:HgStatus_GetSelectedFiles()
1238 return l:filenames[0]
1239 endfunction
1240
1241 function! s:HgStatus_GetSelectedFiles(...) abort
1242 if a:0 >= 2
1243 let l:lines = getline(a:1, a:2)
1244 else
1245 let l:lines = []
1246 call add(l:lines, getline('.'))
1247 endif
1248 let l:filenames = []
1249 let l:repo = s:hg_repo()
1250 for line in l:lines
1251 if a:0 >= 3
1252 let l:status = s:HgStatus_GetFileStatus(line)
1253 if index(a:3, l:status) < 0
1254 continue
1255 endif
1256 endif
1257 " Yay, awesome, Vim's regex syntax is fucked up like shit, especially for
1258 " look-aheads and look-behinds. See for yourself:
1259 let l:filename = matchstr(l:line, '\v(^[MARC\!\?I ]\s)@<=.*')
1260 let l:filename = l:repo.GetFullPath(l:filename)
1261 call add(l:filenames, l:filename)
1262 endfor
1263 return l:filenames
1264 endfunction
1265
1266 function! s:HgStatus_GetFileStatus(...) abort
1267 let l:line = a:0 ? a:1 : getline('.')
1268 return matchstr(l:line, '\v^[MARC\!\?I ]')
1269 endfunction
1270
1271 call s:AddMainCommand("Hgstatus :call s:HgStatus()")
1272
1273 " }}}
1274
1275 " Hgcd, Hglcd {{{
1276
1277 call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoDirs Hgcd :cd<bang> `=s:hg_repo().GetFullPath(<q-args>)`")
1278 call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoDirs Hglcd :lcd<bang> `=s:hg_repo().GetFullPath(<q-args>)`")
1279
1280 " }}}
1281
1282 " Hgedit {{{
1283
1284 function! s:HgEdit(bang, filename) abort
1285 let l:full_path = s:hg_repo().GetFullPath(a:filename)
1286 if a:bang
1287 execute "edit! " . fnameescape(l:full_path)
1288 else
1289 execute "edit " . fnameescape(l:full_path)
1290 endif
1291 endfunction
1292
1293 call s:AddMainCommand("-bang -nargs=1 -complete=customlist,s:ListRepoFiles Hgedit :call s:HgEdit(<bang>0, <f-args>)")
1294
1295 " }}}
1296
1297 " Hgvimgrep {{{
1298
1299 function! s:HgVimGrep(bang, pattern, ...) abort
1300 let l:repo = s:hg_repo()
1301 let l:file_paths = []
1302 if a:0 > 0
1303 for ff in a:000
1304 let l:full_ff = l:repo.GetFullPath(ff)
1305 call add(l:file_paths, l:full_ff)
1306 endfor
1307 else
1308 call add(l:file_paths, l:repo.root_dir . "**")
1309 endif
1310 if a:bang
1311 execute "vimgrep! " . a:pattern . " " . join(l:file_paths, " ")
1312 else
1313 execute "vimgrep " . a:pattern . " " . join(l:file_paths, " ")
1314 endif
1315 endfunction
1316
1317 call s:AddMainCommand("-bang -nargs=+ -complete=customlist,s:ListRepoFiles Hgvimgrep :call s:HgVimGrep(<bang>0, <f-args>)")
1318
1319 " }}}
1320
1321 " Hgdiff, Hgvdiff, Hgtabdiff {{{
1322
1323 function! s:HgDiff(filename, split, ...) abort
1324 " Default revisions to diff: the working directory (null string)
1325 " and the parent of the working directory (using Mercurial's revsets syntax).
1326 " Otherwise, use the 1 or 2 revisions specified as extra parameters.
1327 let l:rev1 = 'p1()'
1328 let l:rev2 = ''
1329 if a:0 == 1
1330 if type(a:1) == type([])
1331 if len(a:1) >= 2
1332 let l:rev1 = a:1[0]
1333 let l:rev2 = a:1[1]
1334 elseif len(a:1) == 1
1335 let l:rev1 = a:1[0]
1336 endif
1337 else
1338 let l:rev1 = a:1
1339 endif
1340 elseif a:0 == 2
1341 let l:rev1 = a:1
1342 let l:rev2 = a:2
1343 endif
1344
1345 " Get the current repo, and expand the given filename in case it contains
1346 " fancy filename modifiers.
1347 let l:repo = s:hg_repo()
1348 let l:path = expand(a:filename)
1349 let l:diff_id = localtime()
1350 call s:trace("Diff'ing '".l:rev1."' and '".l:rev2."' on file: ".l:path)
1351
1352 " Get the first file and open it.
1353 let l:cleanupbufnr = -1
1354 if l:rev1 == ''
1355 if a:split == 2
1356 " Don't use `tabedit` here because if `l:path` is the same as
1357 " the current path, it will also reload the buffer in the current
1358 " tab/window for some reason, which causes all state to be lost
1359 " (all folds get collapsed again, cursor is moved to start, etc.)
1360 tabnew
1361 let l:cleanupbufnr = bufnr('%')
1362 execute 'edit ' . fnameescape(l:path)
1363 else
1364 if bufexists(l:path)
1365 execute 'buffer ' . fnameescape(l:path)
1366 else
1367 execute 'edit ' . fnameescape(l:path)
1368 endif
1369 endif
1370 " Make it part of the diff group.
1371 call s:HgDiff_DiffThis(l:diff_id)
1372 else
1373 let l:rev_path = l:repo.GetLawrenciumPath(l:path, 'rev', l:rev1)
1374 if a:split == 2
1375 " See comments above about avoiding `tabedit`.
1376 tabnew
1377 let l:cleanupbufnr = bufnr('%')
1378 endif
1379 execute 'edit ' . fnameescape(l:rev_path)
1380 " Make it part of the diff group.
1381 call s:HgDiff_DiffThis(l:diff_id)
1382 endif
1383 if l:cleanupbufnr >= 0 && bufloaded(l:cleanupbufnr)
1384 execute 'bdelete ' . l:cleanupbufnr
1385 endif
1386
1387 " Get the second file and open it too.
1388 " Don't use `diffsplit` because it will set `&diff` before we get a chance
1389 " to save a bunch of local settings that we will want to restore later.
1390 let l:diffsplit = 'split'
1391 if a:split >= 1
1392 let l:diffsplit = 'vsplit'
1393 endif
1394 if l:rev2 == ''
1395 execute l:diffsplit . ' ' . fnameescape(l:path)
1396 else
1397 let l:rev_path = l:repo.GetLawrenciumPath(l:path, 'rev', l:rev2)
1398 execute l:diffsplit . ' ' . fnameescape(l:rev_path)
1399 endif
1400 call s:HgDiff_DiffThis(l:diff_id)
1401 endfunction
1402
1403 function! s:HgDiff_DiffThis(diff_id) abort
1404 " Store some commands to run when we exit diff mode.
1405 " It's needed because `diffoff` reverts those settings to their default
1406 " values, instead of their previous ones.
1407 if &diff
1408 call s:throw("Calling diffthis too late on a buffer!")
1409 return
1410 endif
1411 call s:trace('Enabling diff mode on ' . bufname('%'))
1412 let w:lawrencium_diffoff = {}
1413 let w:lawrencium_diffoff['&diff'] = 0
1414 let w:lawrencium_diffoff['&wrap'] = &l:wrap
1415 let w:lawrencium_diffoff['&scrollopt'] = &l:scrollopt
1416 let w:lawrencium_diffoff['&scrollbind'] = &l:scrollbind
1417 let w:lawrencium_diffoff['&cursorbind'] = &l:cursorbind
1418 let w:lawrencium_diffoff['&foldmethod'] = &l:foldmethod
1419 let w:lawrencium_diffoff['&foldcolumn'] = &l:foldcolumn
1420 let w:lawrencium_diffoff['&foldenable'] = &l:foldenable
1421 let w:lawrencium_diff_id = a:diff_id
1422 diffthis
1423 autocmd BufWinLeave <buffer> call s:HgDiff_CleanUp()
1424 endfunction
1425
1426 function! s:HgDiff_DiffOff(...) abort
1427 " Get the window name (given as a paramter, or current window).
1428 let l:nr = a:0 ? a:1 : winnr()
1429
1430 " Run the commands we saved in `HgDiff_DiffThis`, or just run `diffoff`.
1431 let l:backup = getwinvar(l:nr, 'lawrencium_diffoff')
1432 if type(l:backup) == type({}) && len(l:backup) > 0
1433 call s:trace('Disabling diff mode on ' . l:nr)
1434 for key in keys(l:backup)
1435 call setwinvar(l:nr, key, l:backup[key])
1436 endfor
1437 call setwinvar(l:nr, 'lawrencium_diffoff', {})
1438 else
1439 call s:trace('Disabling diff mode on ' . l:nr . ' (but no true restore)')
1440 diffoff
1441 endif
1442 endfunction
1443
1444 function! s:HgDiff_GetDiffWindows(diff_id) abort
1445 let l:result = []
1446 for nr in range(1, winnr('$'))
1447 if getwinvar(nr, '&diff') && getwinvar(nr, 'lawrencium_diff_id') == a:diff_id
1448 call add(l:result, nr)
1449 endif
1450 endfor
1451 return l:result
1452 endfunction
1453
1454 function! s:HgDiff_CleanUp() abort
1455 " If we're not leaving one of our diff window, do nothing.
1456 if !&diff || !exists('w:lawrencium_diff_id')
1457 return
1458 endif
1459
1460 " If there will be only one diff window left (plus the one we're leaving),
1461 " turn off diff in it and restore its local settings.
1462 let l:nrs = s:HgDiff_GetDiffWindows(w:lawrencium_diff_id)
1463 if len(l:nrs) <= 2
1464 call s:trace('Disabling diff mode in ' . len(l:nrs) . ' windows.')
1465 for nr in l:nrs
1466 if getwinvar(nr, '&diff')
1467 call s:HgDiff_DiffOff(nr)
1468 endif
1469 endfor
1470 else
1471 call s:trace('Still ' . len(l:nrs) . ' diff windows open.')
1472 endif
1473 endfunction
1474
1475 call s:AddMainCommand("-nargs=* Hgdiff :call s:HgDiff('%:p', 0, <f-args>)")
1476 call s:AddMainCommand("-nargs=* Hgvdiff :call s:HgDiff('%:p', 1, <f-args>)")
1477 call s:AddMainCommand("-nargs=* Hgtabdiff :call s:HgDiff('%:p', 2, <f-args>)")
1478
1479 " }}}
1480
1481 " Hgdiffsum, Hgdiffsumsplit, Hgvdiffsumsplit, Hgtabdiffsum {{{
1482
1483 function! s:HgDiffSummary(filename, present_args, ...) abort
1484 " Default revisions to diff: the working directory (null string)
1485 " and the parent of the working directory (using Mercurial's revsets syntax).
1486 " Otherwise, use the 1 or 2 revisions specified as extra parameters.
1487 let l:revs = ''
1488 if a:0 == 1
1489 if type(a:1) == type([])
1490 if len(a:1) >= 2
1491 let l:revs = a:1[0] . ',' . a:1[1]
1492 elseif len(a:1) == 1
1493 let l:revs = a:1[0]
1494 endif
1495 else
1496 let l:revs = a:1
1497 endif
1498 elseif a:0 >= 2
1499 let l:revs = a:1 . ',' . a:2
1500 endif
1501
1502 " Get the current repo, and expand the given filename in case it contains
1503 " fancy filename modifiers.
1504 let l:repo = s:hg_repo()
1505 let l:path = expand(a:filename)
1506 call s:trace("Diff'ing revisions: '".l:revs."' on file: ".l:path)
1507 let l:special = l:repo.GetLawrenciumPath(l:path, 'diff', l:revs)
1508
1509 " Build the correct edit command, and switch to the correct window, based
1510 " on the presentation arguments we got.
1511 if type(a:present_args) == type(0)
1512 " Just got a split mode.
1513 let l:valid_args = {'split_mode': a:present_args}
1514 else
1515 " Got complex args.
1516 let l:valid_args = a:present_args
1517 endif
1518
1519 " First, see if we should reuse an existing window based on some buffer
1520 " variable.
1521 let l:target_winnr = -1
1522 let l:split = get(l:valid_args, 'split_mode', 0)
1523 let l:reuse_id = get(l:valid_args, 'reuse_id', '')
1524 let l:avoid_id = get(l:valid_args, 'avoid_win', -1)
1525 if l:reuse_id != ''
1526 let l:target_winnr = s:find_buffer_window(l:reuse_id, 1)
1527 if l:target_winnr > 0 && l:split != 3
1528 " Unless we'll be opening in a new tab, don't split anymore, since
1529 " we found the exact window we wanted.
1530 let l:split = 0
1531 endif
1532 call s:trace("Looking for window with '".l:reuse_id."', found: ".l:target_winnr)
1533 endif
1534 " If we didn't find anything, see if we should use the current or previous
1535 " window.
1536 if l:target_winnr <= 0
1537 let l:use_prev_win = get(l:valid_args, 'use_prev_win', 0)
1538 if l:use_prev_win
1539 let l:target_winnr = winnr('#')
1540 call s:trace("Will use previous window: ".l:target_winnr)
1541 endif
1542 endif
1543 " And let's see if we have a window we should actually avoid.
1544 if l:avoid_id >= 0 &&
1545 \(l:target_winnr == l:avoid_id ||
1546 \(l:target_winnr <= 0 && winnr() == l:avoid_id))
1547 for wnr in range(1, winnr('$'))
1548 if wnr != l:avoid_id
1549 call s:trace("Avoiding using window ".l:avoid_id.
1550 \", now using: ".wnr)
1551 let l:target_winnr = wnr
1552 break
1553 endif
1554 endfor
1555 endif
1556 " Now let's see what kind of split we want to use, if any.
1557 let l:cmd = 'edit '
1558 if l:split == 1
1559 let l:cmd = 'rightbelow split '
1560 elseif l:split == 2
1561 let l:cmd = 'rightbelow vsplit '
1562 elseif l:split == 3
1563 let l:cmd = 'tabedit '
1564 endif
1565
1566 " All good now, proceed.
1567 if l:target_winnr > 0
1568 execute l:target_winnr . "wincmd w"
1569 endif
1570 execute 'keepalt ' . l:cmd . fnameescape(l:special)
1571
1572 " Set the reuse ID if we had one.
1573 if l:reuse_id != ''
1574 call s:trace("Setting reuse ID '".l:reuse_id."' on buffer: ".bufnr('%'))
1575 call setbufvar('%', l:reuse_id, 1)
1576 endif
1577 endfunction
1578
1579 call s:AddMainCommand("-nargs=* Hgdiffsum :call s:HgDiffSummary('%:p', 0, <f-args>)")
1580 call s:AddMainCommand("-nargs=* Hgdiffsumsplit :call s:HgDiffSummary('%:p', 1, <f-args>)")
1581 call s:AddMainCommand("-nargs=* Hgvdiffsumsplit :call s:HgDiffSummary('%:p', 2, <f-args>)")
1582 call s:AddMainCommand("-nargs=* Hgtabdiffsum :call s:HgDiffSummary('%:p', 3, <f-args>)")
1583
1584 " }}}
1585
1586 " Hgcommit {{{
1587
1588 function! s:HgCommit(bang, vertical, callback, ...) abort
1589 " Get the repo we'll be committing into.
1590 let l:repo = s:hg_repo()
1591
1592 " Get the list of files to commit.
1593 " It can either be several files passed as extra parameters, or an
1594 " actual list passed as the first extra parameter.
1595 let l:filenames = []
1596 if a:0
1597 let l:filenames = a:000
1598 if a:0 == 1 && type(a:1) == type([])
1599 let l:filenames = a:1
1600 endif
1601 endif
1602
1603 " Open a commit message file.
1604 let l:commit_path = s:tempname('hg-editor-', '.txt')
1605 let l:split = a:vertical ? 'vsplit' : 'split'
1606 execute l:split . ' ' . l:commit_path
1607 call append(0, ['', ''])
1608 call append(2, split(s:HgCommit_GenerateMessage(l:repo, l:filenames), '\n'))
1609 call cursor(1, 1)
1610
1611 " Setup the auto-command that will actually commit on write/exit,
1612 " and make the buffer delete itself on exit.
1613 let b:mercurial_dir = l:repo.root_dir
1614 let b:lawrencium_commit_files = l:filenames
1615 if type(a:callback) == type([])
1616 let b:lawrencium_commit_pre_callback = a:callback[0]
1617 let b:lawrencium_commit_post_callback = a:callback[1]
1618 let b:lawrencium_commit_abort_callback = a:callback[2]
1619 else
1620 let b:lawrencium_commit_pre_callback = 0
1621 let b:lawrencium_commit_post_callback = a:callback
1622 let b:lawrencium_commit_abort_callback = 0
1623 endif
1624 setlocal bufhidden=delete
1625 setlocal filetype=hgcommit
1626 if a:bang
1627 autocmd BufDelete <buffer> call s:HgCommit_Execute(expand('<afile>:p'), 0)
1628 else
1629 autocmd BufDelete <buffer> call s:HgCommit_Execute(expand('<afile>:p'), 1)
1630 endif
1631 " Make commands available.
1632 call s:DefineMainCommands()
1633 endfunction
1634
1635 let s:hg_status_messages = {
1636 \'M': 'modified',
1637 \'A': 'added',
1638 \'R': 'removed',
1639 \'C': 'clean',
1640 \'!': 'missing',
1641 \'?': 'not tracked',
1642 \'I': 'ignored',
1643 \' ': '',
1644 \}
1645
1646 function! s:HgCommit_GenerateMessage(repo, filenames) abort
1647 let l:msg = "HG: Enter commit message. Lines beginning with 'HG:' are removed.\n"
1648 let l:msg .= "HG: Leave message empty to abort commit.\n"
1649 let l:msg .= "HG: Write and quit buffer to proceed.\n"
1650 let l:msg .= "HG: --\n"
1651 let l:msg .= "HG: user: " . split(a:repo.RunCommand('showconfig ui.username'), '\n')[0] . "\n"
1652 let l:msg .= "HG: branch '" . split(a:repo.RunCommand('branch'), '\n')[0] . "'\n"
1653
1654 execute 'lcd ' . fnameescape(a:repo.root_dir)
1655 if len(a:filenames)
1656 let l:status_lines = split(a:repo.RunCommand('status', a:filenames), "\n")
1657 else
1658 let l:status_lines = split(a:repo.RunCommand('status'), "\n")
1659 endif
1660 for l:line in l:status_lines
1661 if l:line ==# ''
1662 continue
1663 endif
1664 let l:type = matchstr(l:line, '\v^[MARC\!\?I ]')
1665 let l:path = l:line[2:]
1666 let l:msg .= "HG: " . s:hg_status_messages[l:type] . ' ' . l:path . "\n"
1667 endfor
1668
1669 return l:msg
1670 endfunction
1671
1672 function! s:HgCommit_Execute(log_file, show_output) abort
1673 " Check if the user actually saved a commit message.
1674 if !filereadable(a:log_file)
1675 call s:error("abort: Commit message not saved")
1676 if exists('b:lawrencium_commit_abort_callback') &&
1677 \type(b:lawrencium_commit_abort_callback) == type("") &&
1678 \b:lawrencium_commit_abort_callback != ''
1679 call s:trace("Executing abort callback: ".b:lawrencium_commit_abort_callback)
1680 execute b:lawrencium_commit_abort_callback
1681 endif
1682 return
1683 endif
1684
1685 " Execute a pre-callback if there is one.
1686 if exists('b:lawrencium_commit_pre_callback') &&
1687 \type(b:lawrencium_commit_pre_callback) == type("") &&
1688 \b:lawrencium_commit_pre_callback != ''
1689 call s:trace("Executing pre callback: ".b:lawrencium_commit_pre_callback)
1690 execute b:lawrencium_commit_pre_callback
1691 endif
1692
1693 call s:trace("Committing with log file: " . a:log_file)
1694
1695 " Clean all the 'HG: ' lines.
1696 let l:is_valid = s:clean_commit_file(a:log_file)
1697 if !l:is_valid
1698 call s:error("abort: Empty commit message")
1699 return
1700 endif
1701
1702 " Get the repo and commit with the given message.
1703 let l:repo = s:hg_repo()
1704 let l:hg_args = ['-l', a:log_file]
1705 call extend(l:hg_args, b:lawrencium_commit_files)
1706 let l:output = l:repo.RunCommand('commit', l:hg_args)
1707 if a:show_output && l:output !~# '\v%^\s*%$'
1708 call s:trace("Output from hg commit:", 1)
1709 for l:output_line in split(l:output, '\n')
1710 echom l:output_line
1711 endfor
1712 endif
1713
1714 " Execute a post-callback if there is one.
1715 if exists('b:lawrencium_commit_post_callback') &&
1716 \type(b:lawrencium_commit_post_callback) == type("") &&
1717 \b:lawrencium_commit_post_callback != ''
1718 call s:trace("Executing post callback: ".b:lawrencium_commit_post_callback)
1719 execute b:lawrencium_commit_post_callback
1720 endif
1721 endfunction
1722
1723 call s:AddMainCommand("-bang -nargs=* -complete=customlist,s:ListRepoFiles Hgcommit :call s:HgCommit(<bang>0, 0, 0, <f-args>)")
1724 call s:AddMainCommand("-bang -nargs=* -complete=customlist,s:ListRepoFiles Hgvcommit :call s:HgCommit(<bang>0, 1, 0, <f-args>)")
1725
1726 " }}}
1727
1728 " Hgrevert {{{
1729
1730 function! s:HgRevert(bang, ...) abort
1731 " Get the files to revert.
1732 let l:filenames = a:000
1733 if a:0 == 0
1734 let l:filenames = [ expand('%:p') ]
1735 endif
1736 if a:bang
1737 call insert(l:filenames, '--no-backup', 0)
1738 endif
1739
1740 " Get the repo and run the command.
1741 let l:repo = s:hg_repo()
1742 call l:repo.RunCommand('revert', l:filenames)
1743
1744 " Re-edit the file to see the change.
1745 edit
1746 endfunction
1747
1748 call s:AddMainCommand("-bang -nargs=* -complete=customlist,s:ListRepoFiles Hgrevert :call s:HgRevert(<bang>0, <f-args>)")
1749
1750 " }}}
1751
1752 " Hgremove {{{
1753
1754 function! s:HgRemove(bang, ...) abort
1755 " Get the files to remove.
1756 let l:filenames = a:000
1757 if a:0 == 0
1758 let l:filenames = [ expand('%:p') ]
1759 endif
1760 if a:bang
1761 call insert(l:filenames, '--force', 0)
1762 endif
1763
1764 " Get the repo and run the command.
1765 let l:repo = s:hg_repo()
1766 call l:repo.RunCommand('rm', l:filenames)
1767
1768 " Re-edit the file to see the change.
1769 edit
1770 endfunction
1771
1772 call s:AddMainCommand("-bang -nargs=* -complete=customlist,s:ListRepoFiles Hgremove :call s:HgRemove(<bang>0, <f-args>)")
1773
1774 " }}}
1775
1776 " Hglog, Hglogthis {{{
1777
1778 function! s:HgLog(vertical, ...) abort
1779 " Get the file or directory to get the log from.
1780 " (empty string is for the whole repository)
1781 let l:repo = s:hg_repo()
1782 if a:0 > 0 && matchstr(a:1, '\v-*') == ""
1783 let l:path = l:repo.GetRelativePath(expand(a:1))
1784 else
1785 let l:path = ''
1786 endif
1787
1788 " Get the Lawrencium path for this `hg log`,
1789 " open it in a preview window and jump to it.
1790 if a:0 > 0 && l:path != ""
1791 let l:log_opts = join(a:000[1:-1], ',')
1792 else
1793 let l:log_opts = join(a:000, ',')
1794 endif
1795
1796 let l:log_path = l:repo.GetLawrenciumPath(l:path, 'log', l:log_opts)
1797 if a:vertical
1798 execute 'vertical pedit ' . fnameescape(l:log_path)
1799 else
1800 execute 'pedit ' . fnameescape(l:log_path)
1801 endif
1802 wincmd P
1803
1804 " Add some other nice commands and mappings.
1805 let l:is_file = (l:path != '' && filereadable(l:repo.GetFullPath(l:path)))
1806 command! -buffer -nargs=* Hglogdiffsum :call s:HgLog_DiffSummary(1, <f-args>)
1807 command! -buffer -nargs=* Hglogvdiffsum :call s:HgLog_DiffSummary(2, <f-args>)
1808 command! -buffer -nargs=* Hglogtabdiffsum :call s:HgLog_DiffSummary(3, <f-args>)
1809 command! -buffer -nargs=+ -complete=file Hglogexport :call s:HgLog_ExportPatch(<f-args>)
1810 if l:is_file
1811 command! -buffer Hglogrevedit :call s:HgLog_FileRevEdit()
1812 command! -buffer -nargs=* Hglogdiff :call s:HgLog_Diff(0, <f-args>)
1813 command! -buffer -nargs=* Hglogvdiff :call s:HgLog_Diff(1, <f-args>)
1814 command! -buffer -nargs=* Hglogtabdiff :call s:HgLog_Diff(2, <f-args>)
1815 endif
1816
1817 if g:lawrencium_define_mappings
1818 nnoremap <buffer> <silent> <C-U> :Hglogdiffsum<cr>
1819 nnoremap <buffer> <silent> <C-H> :Hglogvdiffsum<cr>
1820 nnoremap <buffer> <silent> <cr> :Hglogvdiffsum<cr>
1821 nnoremap <buffer> <silent> q :bdelete!<cr>
1822 if l:is_file
1823 nnoremap <buffer> <silent> <C-E> :Hglogrevedit<cr>
1824 nnoremap <buffer> <silent> <C-D> :Hglogtabdiff<cr>
1825 nnoremap <buffer> <silent> <C-V> :Hglogvdiff<cr>
1826 endif
1827 endif
1828
1829 " Clean up when the log buffer is deleted.
1830 let l:bufobj = s:buffer_obj()
1831 call l:bufobj.OnDelete('call s:HgLog_Delete(' . l:bufobj.nr . ')')
1832 endfunction
1833
1834 function! s:HgLog_Delete(bufnr)
1835 if g:lawrencium_auto_close_buffers
1836 call s:delete_dependency_buffers('lawrencium_diff_for', a:bufnr)
1837 call s:delete_dependency_buffers('lawrencium_rev_for', a:bufnr)
1838 endif
1839 endfunction
1840
1841 function! s:HgLog_FileRevEdit()
1842 let l:repo = s:hg_repo()
1843 let l:bufobj = s:buffer_obj()
1844 let l:rev = s:HgLog_GetSelectedRev()
1845 let l:log_path = s:parse_lawrencium_path(l:bufobj.GetName())
1846 let l:path = l:repo.GetLawrenciumPath(l:log_path['path'], 'rev', l:rev)
1847
1848 " Go to the window we were in before going in the log window,
1849 " and open the revision there.
1850 wincmd p
1851 call s:edit_deletable_buffer('lawrencium_rev_for', l:bufobj.nr, l:path)
1852 endfunction
1853
1854 function! s:HgLog_Diff(split, ...) abort
1855 let l:revs = []
1856 if a:0 >= 2
1857 let l:revs = [a:1, a:2]
1858 elseif a:0 == 1
1859 let l:revs = ['p1('.a:1.')', a:1]
1860 else
1861 let l:sel = s:HgLog_GetSelectedRev()
1862 let l:revs = ['p1('.l:sel.')', l:sel]
1863 endif
1864
1865 let l:repo = s:hg_repo()
1866 let l:bufobj = s:buffer_obj()
1867 let l:log_path = s:parse_lawrencium_path(l:bufobj.GetName())
1868 let l:path = l:repo.GetFullPath(l:log_path['path'])
1869
1870 " Go to the window we were in before going to the log window,
1871 " and open the split diff there.
1872 if a:split < 2
1873 wincmd p
1874 endif
1875 call s:HgDiff(l:path, a:split, l:revs)
1876 endfunction
1877
1878 function! s:HgLog_DiffSummary(split, ...) abort
1879 let l:revs = []
1880 if a:0 >= 2
1881 let l:revs = [a:1, a:2]
1882 elseif a:0 == 1
1883 let l:revs = [a:1]
1884 else
1885 let l:revs = [s:HgLog_GetSelectedRev()]
1886 endif
1887
1888 let l:repo = s:hg_repo()
1889 let l:bufobj = s:buffer_obj()
1890 let l:log_path = s:parse_lawrencium_path(l:bufobj.GetName())
1891 let l:path = l:repo.GetFullPath(l:log_path['path'])
1892
1893 " Go to the window we were in before going in the log window,
1894 " and split for the diff summary from there.
1895 let l:reuse_id = 'lawrencium_diffsum_for_' . bufnr('%')
1896 let l:split_prev_win = (a:split < 3)
1897 let l:args = {'reuse_id': l:reuse_id, 'use_prev_win': l:split_prev_win,
1898 \'split_mode': a:split}
1899 call s:HgDiffSummary(l:path, l:args, l:revs)
1900 endfunction
1901
1902 function! s:HgLog_GetSelectedRev(...) abort
1903 if a:0 == 1
1904 let l:line = getline(a:1)
1905 else
1906 let l:line = getline('.')
1907 endif
1908 " Behold, Vim's look-ahead regex syntax again! WTF.
1909 let l:rev = matchstr(l:line, '\v^(\d+)(\:)@=')
1910 if l:rev == ''
1911 call s:throw("Can't parse revision number from line: " . l:line)
1912 endif
1913 return l:rev
1914 endfunction
1915
1916 function! s:HgLog_ExportPatch(...) abort
1917 let l:patch_name = a:1
1918 if !empty($HG_EXPORT_PATCH_DIR)
1919 " Use the patch dir only if user has specified a relative path
1920 if has('win32')
1921 let l:is_patch_relative = (matchstr(l:patch_name, '\v^([a-zA-Z]:)?\\') == "")
1922 else
1923 let l:is_patch_relative = (matchstr(l:patch_name, '\v^/') == "")
1924 endif
1925 if l:is_patch_relative
1926 let l:patch_name = s:normalizepath(
1927 s:stripslash($HG_EXPORT_PATCH_DIR) . "/" . l:patch_name)
1928 endif
1929 endif
1930
1931 if a:0 == 2
1932 let l:rev = a:2
1933 else
1934 let l:rev = s:HgLog_GetSelectedRev()
1935 endif
1936
1937 let l:repo = s:hg_repo()
1938 let l:export_args = ['-o', l:patch_name, '-r', l:rev]
1939
1940 call l:repo.RunCommand('export', l:export_args)
1941
1942 echom "Created patch: " . l:patch_name
1943 endfunction
1944
1945 call s:AddMainCommand("Hglogthis :call s:HgLog(0, '%:p')")
1946 call s:AddMainCommand("Hgvlogthis :call s:HgLog(1, '%:p')")
1947 call s:AddMainCommand("-nargs=* -complete=customlist,s:ListRepoFiles Hglog :call s:HgLog(0, <f-args>)")
1948 call s:AddMainCommand("-nargs=* -complete=customlist,s:ListRepoFiles Hgvlog :call s:HgLog(1, <f-args>)")
1949
1950 " }}}
1951
1952 " Hgannotate, Hgwannotate {{{
1953
1954 function! s:HgAnnotate(bang, verbose, ...) abort
1955 " Open the file to annotate if needed.
1956 if a:0 > 0
1957 call s:HgEdit(a:bang, a:1)
1958 endif
1959
1960 " Get the Lawrencium path for the annotated file.
1961 let l:path = expand('%:p')
1962 let l:bufnr = bufnr('%')
1963 let l:repo = s:hg_repo()
1964 let l:value = a:verbose ? 'v=1' : ''
1965 let l:annotation_path = l:repo.GetLawrenciumPath(l:path, 'annotate', l:value)
1966
1967 " Check if we're trying to annotate something with local changes.
1968 let l:has_local_edits = 0
1969 let l:path_status = l:repo.RunCommand('status', l:path)
1970 if l:path_status != ''
1971 call s:trace("Found local edits for '" . l:path . "'. Will annotate parent revision.")
1972 let l:has_local_edits = 1
1973 endif
1974
1975 if l:has_local_edits
1976 " Just open the output of the command.
1977 echom "Local edits found, will show the annotations for the parent revision."
1978 execute 'edit ' . fnameescape(l:annotation_path)
1979 setlocal nowrap nofoldenable
1980 setlocal filetype=hgannotate
1981 else
1982 " Store some info about the current buffer.
1983 let l:cur_topline = line('w0') + &scrolloff
1984 let l:cur_line = line('.')
1985 let l:cur_wrap = &wrap
1986 let l:cur_foldenable = &foldenable
1987
1988 " Open the annotated file in a split buffer on the left, after
1989 " having disabled wrapping and folds on the current file.
1990 " Make both windows scroll-bound.
1991 setlocal scrollbind nowrap nofoldenable
1992 execute 'keepalt leftabove vsplit ' . fnameescape(l:annotation_path)
1993 setlocal nonumber
1994 setlocal scrollbind nowrap nofoldenable foldcolumn=0
1995 setlocal filetype=hgannotate
1996
1997 " When the annotated buffer is deleted, restore the settings we
1998 " changed on the current buffer, and go back to that buffer.
1999 let l:annotate_buffer = s:buffer_obj()
2000 call l:annotate_buffer.OnDelete('execute bufwinnr(' . l:bufnr . ') . "wincmd w"')
2001 call l:annotate_buffer.OnDelete('setlocal noscrollbind')
2002 if l:cur_wrap
2003 call l:annotate_buffer.OnDelete('setlocal wrap')
2004 endif
2005 if l:cur_foldenable
2006 call l:annotate_buffer.OnDelete('setlocal foldenable')
2007 endif
2008
2009 " Go to the line we were at in the source buffer when we
2010 " opened the annotation window.
2011 execute l:cur_topline
2012 normal! zt
2013 execute l:cur_line
2014 syncbind
2015
2016 " Set the correct window width for the annotations.
2017 if a:verbose
2018 let l:last_token = match(getline('.'), '\v\d{4}:\s')
2019 let l:token_end = 5
2020 else
2021 let l:last_token = match(getline('.'), '\v\d{2}:\s')
2022 let l:token_end = 3
2023 endif
2024 if l:last_token < 0
2025 echoerr "Can't find the end of the annotation columns."
2026 else
2027 let l:column_count = l:last_token + l:token_end + g:lawrencium_annotate_width_offset
2028 execute "vertical resize " . l:column_count
2029 setlocal winfixwidth
2030 endif
2031 endif
2032
2033 " Make the annotate buffer a Lawrencium buffer.
2034 let b:mercurial_dir = l:repo.root_dir
2035 let b:lawrencium_annotated_path = l:path
2036 let b:lawrencium_annotated_bufnr = l:bufnr
2037 call s:DefineMainCommands()
2038
2039 " Add some other nice commands and mappings.
2040 command! -buffer Hgannotatediffsum :call s:HgAnnotate_DiffSummary()
2041 command! -buffer Hgannotatelog :call s:HgAnnotate_DiffSummary(1)
2042 if g:lawrencium_define_mappings
2043 nnoremap <buffer> <silent> <cr> :Hgannotatediffsum<cr>
2044 nnoremap <buffer> <silent> <leader><cr> :Hgannotatelog<cr>
2045 endif
2046
2047 " Clean up when the annotate buffer is deleted.
2048 let l:bufobj = s:buffer_obj()
2049 call l:bufobj.OnDelete('call s:HgAnnotate_Delete(' . l:bufobj.nr . ')')
2050 endfunction
2051
2052 function! s:HgAnnotate_Delete(bufnr) abort
2053 if g:lawrencium_auto_close_buffers
2054 call s:delete_dependency_buffers('lawrencium_diff_for', a:bufnr)
2055 endif
2056 endfunction
2057
2058 function! s:HgAnnotate_DiffSummary(...) abort
2059 " Get the path for the diff of the revision specified under the cursor.
2060 let l:line = getline('.')
2061 let l:rev_hash = matchstr(l:line, '\v[a-f0-9]{12}')
2062 let l:log = (a:0 > 0 ? a:1 : 0)
2063
2064 " Get the Lawrencium path for the diff, and the buffer object for the
2065 " annotation.
2066 let l:repo = s:hg_repo()
2067 if l:log
2068 let l:path = l:repo.GetLawrenciumPath(b:lawrencium_annotated_path, 'logpatch', l:rev_hash)
2069 else
2070 let l:path = l:repo.GetLawrenciumPath(b:lawrencium_annotated_path, 'diff', l:rev_hash)
2071 endif
2072 let l:annotate_buffer = s:buffer_obj()
2073
2074 " Find a window already displaying diffs for this annotation.
2075 let l:diff_winnr = s:find_buffer_window('lawrencium_diff_for', l:annotate_buffer.nr)
2076 if l:diff_winnr == -1
2077 " Not found... go back to the main source buffer and open a bottom
2078 " split with the diff for the specified revision.
2079 execute bufwinnr(b:lawrencium_annotated_bufnr) . 'wincmd w'
2080 execute 'rightbelow split ' . fnameescape(l:path)
2081 let b:lawrencium_diff_for = l:annotate_buffer.nr
2082 let b:lawrencium_quit_on_delete = 1
2083 else
2084 " Found! Use that window to open the diff.
2085 execute l:diff_winnr . 'wincmd w'
2086 execute 'edit ' . fnameescape(l:path)
2087 let b:lawrencium_diff_for = l:annotate_buffer.nr
2088 endif
2089 endfunction
2090
2091 call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoFiles Hgannotate :call s:HgAnnotate(<bang>0, 0, <f-args>)")
2092 call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoFiles Hgwannotate :call s:HgAnnotate(<bang>0, 1, <f-args>)")
2093
2094 " }}}
2095
2096 " Hgqseries {{{
2097
2098 function! s:HgQSeries() abort
2099 " Open the MQ series in the preview window and jump to it.
2100 let l:repo = s:hg_repo()
2101 let l:path = l:repo.GetLawrenciumPath('', 'qseries', '')
2102 execute 'pedit ' . fnameescape(l:path)
2103 wincmd P
2104
2105 " Make the series buffer a Lawrencium buffer.
2106 let b:mercurial_dir = l:repo.root_dir
2107 call s:DefineMainCommands()
2108
2109 " Add some commands and mappings.
2110 command! -buffer Hgqseriesgoto :call s:HgQSeries_Goto()
2111 command! -buffer Hgqserieseditmessage :call s:HgQSeries_EditMessage()
2112 command! -buffer -nargs=+ Hgqseriesrename :call s:HgQSeries_Rename(<f-args>)
2113 if g:lawrencium_define_mappings
2114 nnoremap <buffer> <silent> <C-g> :Hgqseriesgoto<cr>
2115 nnoremap <buffer> <silent> <C-e> :Hgqserieseditmessage<cr>
2116 nnoremap <buffer> <silent> q :bdelete!<cr>
2117 endif
2118 endfunction
2119
2120 function! s:HgQSeries_GetCurrentPatchName() abort
2121 let l:pos = getpos('.')
2122 return getbufvar('%', 'lawrencium_patchname_' . l:pos[1])
2123 endfunction
2124
2125 function! s:HgQSeries_Goto() abort
2126 let l:repo = s:hg_repo()
2127 let l:patchname = s:HgQSeries_GetCurrentPatchName()
2128 if len(l:patchname) == 0
2129 call s:error("No patch to go to here.")
2130 return
2131 endif
2132 call l:repo.RunCommand('qgoto', l:patchname)
2133 edit
2134 endfunction
2135
2136 function! s:HgQSeries_Rename(...) abort
2137 let l:repo = s:hg_repo()
2138 let l:current_name = s:HgQSeries_GetCurrentPatchName()
2139 if len(l:current_name) == 0
2140 call s:error("No patch to rename here.")
2141 return
2142 endif
2143 let l:new_name = '"' . join(a:000, ' ') . '"'
2144 call l:repo.RunCommand('qrename', l:current_name, l:new_name)
2145 edit
2146 endfunction
2147
2148 function! s:HgQSeries_EditMessage() abort
2149 let l:repo = s:hg_repo()
2150 let l:patchname = getbufvar('%', 'lawrencium_patchname_top')
2151 if len(l:patchname) == 0
2152 call s:error("No patch to edit here.")
2153 return
2154 endif
2155 let l:current = split(l:repo.RunCommand('qheader', l:patchname), '\n')
2156
2157 " Open a temp file to write the commit message.
2158 let l:temp_file = s:tempname('hg-qrefedit-', '.txt')
2159 split
2160 execute 'edit ' . fnameescape(l:temp_file)
2161 call append(0, 'HG: Enter the new commit message for patch "' . l:patchname . '" here.\n')
2162 call append(0, '')
2163 call append(0, l:current)
2164 call cursor(1, 1)
2165
2166 " Make it a temp buffer that will actually change the commit message
2167 " when it is saved and closed.
2168 let b:mercurial_dir = l:repo.root_dir
2169 let b:lawrencium_patchname = l:patchname
2170 setlocal bufhidden=delete
2171 setlocal filetype=hgcommit
2172 autocmd BufDelete <buffer> call s:HgQSeries_EditMessage_Execute(expand('<afile>:p'))
2173
2174 call s:DefineMainCommands()
2175 endfunction
2176
2177 function! s:HgQSeries_EditMessage_Execute(log_file) abort
2178 if !filereadable(a:log_file)
2179 call s:error("abort: Commit message not saved")
2180 return
2181 endif
2182
2183 " Clean all the 'HG:' lines.
2184 let l:is_valid = s:clean_commit_file(a:log_file)
2185 if !l:is_valid
2186 call s:error("abort: Empty commit message")
2187 return
2188 endif
2189
2190 " Get the repo and edit the given patch.
2191 let l:repo = s:hg_repo()
2192 let l:hg_args = ['-s', '-l', a:log_file]
2193 call l:repo.RunCommand('qref', l:hg_args)
2194 endfunction
2195
2196 call s:AddMainCommand("Hgqseries call s:HgQSeries()")
2197
2198 " }}}
2199
2200 " Hgrecord {{{
2201
2202 function! s:HgRecord(split) abort
2203 let l:repo = s:hg_repo()
2204 let l:orig_buf = s:buffer_obj()
2205 let l:tmp_path = l:orig_buf.GetName(':p') . '~record'
2206 let l:diff_id = localtime()
2207
2208 " Start diffing on the current file, enable some commands.
2209 call l:orig_buf.DefineCommand('Hgrecordabort', ':call s:HgRecord_Abort()')
2210 call l:orig_buf.DefineCommand('Hgrecordcommit', ':call s:HgRecord_Execute()')
2211 call s:HgDiff_DiffThis(l:diff_id)
2212 setlocal foldmethod=diff
2213
2214 " Split the window and open the parent revision in the right or bottom
2215 " window. Keep the current buffer in the left or top window... we're going
2216 " to 'move' those changes into the parent revision.
2217 let l:cmd = 'keepalt rightbelow split '
2218 if a:split == 1
2219 let l:cmd = 'keepalt rightbelow vsplit '
2220 endif
2221 let l:rev_path = l:repo.GetLawrenciumPath(expand('%:p'), 'rev', '')
2222 execute l:cmd . fnameescape(l:rev_path)
2223
2224 " This new buffer with the parent revision is set as a Lawrencium buffer.
2225 " Let's save it to an actual file and reopen it like that (somehow we
2226 " could probably do it with `:saveas` instead but we'd need to reset a
2227 " bunch of other buffer settings, and Vim weirdly creates another backup
2228 " buffer when you do that).
2229 execute 'keepalt write! ' . fnameescape(l:tmp_path)
2230 execute 'keepalt edit! ' . fnameescape(l:tmp_path)
2231 setlocal bufhidden=delete
2232 let b:mercurial_dir = l:repo.root_dir
2233 let b:lawrencium_record_for = l:orig_buf.GetName(':p')
2234 let b:lawrencium_record_other_nr = l:orig_buf.nr
2235 let b:lawrencium_record_commit_split = !a:split
2236 call setbufvar(l:orig_buf.nr, 'lawrencium_record_for', '%')
2237 call setbufvar(l:orig_buf.nr, 'lawrencium_record_other_nr', bufnr('%'))
2238
2239 " Hookup the commit and abort commands.
2240 let l:rec_buf = s:buffer_obj()
2241 call l:rec_buf.OnDelete('call s:HgRecord_Execute()')
2242 call l:rec_buf.DefineCommand('Hgrecordcommit', ':quit')
2243 call l:rec_buf.DefineCommand('Hgrecordabort', ':call s:HgRecord_Abort()')
2244 call s:DefineMainCommands()
2245
2246 " Make it the other part of the diff.
2247 call s:HgDiff_DiffThis(l:diff_id)
2248 setlocal foldmethod=diff
2249 call l:rec_buf.SetVar('&filetype', l:orig_buf.GetVar('&filetype'))
2250 call l:rec_buf.SetVar('&fileformat', l:orig_buf.GetVar('&fileformat'))
2251
2252 if g:lawrencium_record_start_in_working_buffer
2253 wincmd p
2254 endif
2255 endfunction
2256
2257 function! s:HgRecord_Execute() abort
2258 if exists('b:lawrencium_record_abort')
2259 " Abort flag is set, let's just cleanup.
2260 let l:buf_nr = b:lawrencium_record_for == '%' ? bufnr('%') :
2261 \b:lawrencium_record_other_nr
2262 call s:HgRecord_CleanUp(l:buf_nr)
2263 call s:error("abort: User requested aborting the record operation.")
2264 return
2265 endif
2266
2267 if !exists('b:lawrencium_record_for')
2268 call s:throw("This doesn't seem like a record buffer, something's wrong!")
2269 endif
2270 if b:lawrencium_record_for == '%'
2271 " Switch to the 'recording' buffer's window.
2272 let l:buf_obj = s:buffer_obj(b:lawrencium_record_other_nr)
2273 call l:buf_obj.MoveToFirstWindow()
2274 endif
2275
2276 " Setup the commit operation.
2277 let l:split = b:lawrencium_record_commit_split
2278 let l:working_bufnr = b:lawrencium_record_other_nr
2279 let l:working_path = fnameescape(b:lawrencium_record_for)
2280 let l:record_path = fnameescape(expand('%:p'))
2281 let l:callbacks = [
2282 \'call s:HgRecord_PostExecutePre('.l:working_bufnr.', "'.
2283 \escape(l:working_path, '\').'", "'.
2284 \escape(l:record_path, '\').'")',
2285 \'call s:HgRecord_PostExecutePost('.l:working_bufnr.', "'.
2286 \escape(l:working_path, '\').'")',
2287 \'call s:HgRecord_PostExecuteAbort('.l:working_bufnr.', "'.
2288 \escape(l:record_path, '\').'")'
2289 \]
2290 call s:trace("Starting commit flow with callbacks: ".string(l:callbacks))
2291 call s:HgCommit(0, l:split, l:callbacks, b:lawrencium_record_for)
2292 endfunction
2293
2294 function! s:HgRecord_PostExecutePre(working_bufnr, working_path, record_path) abort
2295 " Just before committing, we switch the original file with the record
2296 " file... we'll restore things in the post-callback below.
2297 " We also switch on 'autoread' temporarily on the working buffer so that
2298 " we don't have an annoying popup in gVim.
2299 if has('dialog_gui')
2300 call setbufvar(a:working_bufnr, '&autoread', 1)
2301 endif
2302 call s:trace("Backuping original file: ".a:working_path)
2303 silent call rename(a:working_path, a:working_path.'~working')
2304 call s:trace("Committing recorded changes using: ".a:record_path)
2305 silent call rename(a:record_path, a:working_path)
2306 sleep 200m
2307 endfunction
2308
2309 function! s:HgRecord_PostExecutePost(working_bufnr, working_path) abort
2310 " Recover the back-up file from underneath the buffer.
2311 call s:trace("Recovering original file: ".a:working_path)
2312 silent call rename(a:working_path.'~working', a:working_path)
2313
2314 " Clean up!
2315 call s:HgRecord_CleanUp(a:working_bufnr)
2316
2317 " Restore default 'autoread'.
2318 if has('dialog_gui')
2319 set autoread<
2320 endif
2321 endfunction
2322
2323 function! s:HgRecord_PostExecuteAbort(working_bufnr, record_path) abort
2324 call s:HgRecord_CleanUp(a:working_bufnr)
2325 call s:trace("Delete discarded record file: ".a:record_path)
2326 silent call delete(a:record_path)
2327 endfunction
2328
2329 function! s:HgRecord_Abort() abort
2330 if b:lawrencium_record_for == '%'
2331 " We're in the working directory buffer. Switch to the 'recording'
2332 " buffer and quit.
2333 let l:buf_obj = s:buffer_obj(b:lawrencium_record_other_nr)
2334 call l:buf_obj.MoveToFirstWindow()
2335 endif
2336 " We're now in the 'recording' buffer... set the abort flag and quit,
2337 " which will run the execution (it will early out and clean things up).
2338 let b:lawrencium_record_abort = 1
2339 quit!
2340 endfunction
2341
2342 function! s:HgRecord_CleanUp(buf_nr) abort
2343 " Get in the original buffer and clean the local commands/variables.
2344 let l:buf_obj = s:buffer_obj(a:buf_nr)
2345 call l:buf_obj.MoveToFirstWindow()
2346 if !exists('b:lawrencium_record_for') || b:lawrencium_record_for != '%'
2347 call s:throw("Cleaning up something else than the original buffer ".
2348 \"for a record operation. That's suspiciously incorrect! ".
2349 \"Aborting.")
2350 endif
2351 call l:buf_obj.DeleteCommand('Hgrecordabort')
2352 call l:buf_obj.DeleteCommand('Hgrecordcommit')
2353 unlet b:lawrencium_record_for
2354 unlet b:lawrencium_record_other_nr
2355 endfunction
2356
2357 call s:AddMainCommand("Hgrecord call s:HgRecord(0)")
2358 call s:AddMainCommand("Hgvrecord call s:HgRecord(1)")
2359
2360 " }}}
2361
2362 " Autoload Functions {{{
2363
2364 " Prints a summary of the current repo (if any) that's appropriate for
2365 " displaying on the status line.
2366 function! lawrencium#statusline(...)
2367 if !exists('b:mercurial_dir')
2368 return ''
2369 endif
2370 let l:repo = s:hg_repo()
2371 let l:prefix = (a:0 > 0 ? a:1 : '')
2372 let l:suffix = (a:0 > 1 ? a:2 : '')
2373 let l:branch = 'default'
2374 let l:branch_file = l:repo.GetFullPath('.hg/branch')
2375 if filereadable(l:branch_file)
2376 let l:branch = readfile(l:branch_file)[0]
2377 endif
2378 let l:bookmarks = ''
2379 let l:bookmarks_file = l:repo.GetFullPath('.hg/bookmarks.current')
2380 if filereadable(l:bookmarks_file)
2381 let l:bookmarks = join(readfile(l:bookmarks_file), ', ')
2382 endif
2383 let l:line = l:prefix . l:branch
2384 if strlen(l:bookmarks) > 0
2385 let l:line = l:line . ' - ' . l:bookmarks
2386 endif
2387 let l:line = l:line . l:suffix
2388 return l:line
2389 endfunction
2390
2391 " Rescans the current buffer for setting up Mercurial commands.
2392 " Passing '1' as the parameter enables debug traces temporarily.
2393 function! lawrencium#rescan(...)
2394 if exists('b:mercurial_dir')
2395 unlet b:mercurial_dir
2396 endif
2397 if a:0 && a:1
2398 let l:trace_backup = g:lawrencium_trace
2399 let g:lawrencium_trace = 1
2400 endif
2401 call s:setup_buffer_commands()
2402 if a:0 && a:1
2403 let g:lawrencium_trace = l:trace_backup
2404 endif
2405 endfunction
2406
2407 " Enables/disables the debug trace.
2408 function! lawrencium#debugtrace(...)
2409 let g:lawrencium_trace = (a:0 == 0 || (a:0 && a:1))
2410 echom "Lawrencium debug trace is now " . (g:lawrencium_trace ? "enabled." : "disabled.")
2411 endfunction
2412
2413 " }}}
2414