view plugin/lawrencium.vim @ 133:add9f0ed0b49

Save/restore `&foldenable` when diffing files.
author Ludovic Chabant <ludovic@chabant.com>
date Tue, 19 Apr 2016 21:12:37 -0700
parents c04855b6f318
children 0e005903aae4
line wrap: on
line source

" lawrencium.vim - A Mercurial wrapper
" Maintainer:   Ludovic Chabant <http://ludovic.chabant.com>
" Version:      0.4.0


" Globals {{{

if !exists('g:lawrencium_debug')
    let g:lawrencium_debug = 0
endif

if (exists('g:loaded_lawrencium') || &cp) && !g:lawrencium_debug
    finish
endif
if (exists('g:loaded_lawrencium') && g:lawrencium_debug)
    echom "Reloaded Lawrencium."
endif
let g:loaded_lawrencium = 1

if !exists('g:lawrencium_hg_executable')
    let g:lawrencium_hg_executable = 'hg'
endif

if !exists('g:lawrencium_auto_cd')
    let g:lawrencium_auto_cd = 1
endif

if !exists('g:lawrencium_trace')
    let g:lawrencium_trace = 0
endif

if !exists('g:lawrencium_define_mappings')
    let g:lawrencium_define_mappings = 1
endif

if !exists('g:lawrencium_auto_close_buffers')
    let g:lawrencium_auto_close_buffers = 1
endif

if !exists('g:lawrencium_annotate_width_offset')
    let g:lawrencium_annotate_width_offset = 0
endif

if !exists('g:lawrencium_status_win_split_above')
    let g:lawrencium_status_win_split_above = 0
endif

if !exists('g:lawrencium_status_win_split_even')
    let g:lawrencium_status_win_split_even = 0
endif

if !exists('g:lawrencium_record_start_in_working_buffer')
    let g:lawrencium_record_start_in_working_buffer = 0
endif

" }}}

" Utility {{{

" Strips the ending slash in a path.
function! s:stripslash(path)
    return fnamemodify(a:path, ':s?[/\\]$??')
endfunction

" Returns whether a path is absolute.
function! s:isabspath(path)
    return a:path =~# '\v^(\w\:)?[/\\]'
endfunction

" Normalizes the slashes in a path.
function! s:normalizepath(path)
    if exists('+shellslash') && &shellslash
        return substitute(a:path, '\v/', '\\', 'g')
    elseif has('win32')
        return substitute(a:path, '\v/', '\\', 'g')
    else
        return a:path
    endif
endfunction

" Shell-slashes the path (opposite of `normalizepath`).
function! s:shellslash(path)
  if exists('+shellslash') && !&shellslash
    return substitute(a:path, '\v\\', '/', 'g')
  else
    return a:path
  endif
endfunction

" Like tempname() but with some control over the filename.
function! s:tempname(name, ...)
    let l:path = tempname()
    let l:result = fnamemodify(l:path, ':h') . '/' . a:name . fnamemodify(l:path, ':t')
    if a:0 > 0
        let l:result = l:result . a:1
    endif
    return l:result
endfunction

" Delete a temporary file if it exists.
function! s:clean_tempfile(path)
    if filewritable(a:path)
        call s:trace("Cleaning up temporary file: " . a:path)
        call delete(a:path)
    endif
endfunction

" Prints a message if debug tracing is enabled.
function! s:trace(message, ...)
   if g:lawrencium_trace || (a:0 && a:1)
       let l:message = "lawrencium: " . a:message
       echom l:message
   endif
endfunction

" Prints an error message with 'lawrencium error' prefixed to it.
function! s:error(message)
    echom "lawrencium error: " . a:message
endfunction

" Throw a Lawrencium exception message.
function! s:throw(message)
    let v:errmsg = "lawrencium: " . a:message
    throw v:errmsg
endfunction

" Finds the repository root given a path inside that repository.
" Throw an error if not repository is found.
function! s:find_repo_root(path)
    let l:path = s:stripslash(a:path)
    let l:previous_path = ""
    while l:path != l:previous_path
        if isdirectory(l:path . '/.hg')
            return s:normalizepath(simplify(fnamemodify(l:path, ':p')))
        endif
        let l:previous_path = l:path
        let l:path = fnamemodify(l:path, ':h')
    endwhile
    call s:throw("No Mercurial repository found above: " . a:path)
endfunction

" Given a Lawrencium path (e.g: 'lawrencium:///repo/root_dir//foo/bar/file.py//rev=34'), extract
" the repository root, relative file path and revision number/changeset ID.
"
" If a second argument exists, it must be:
" - `relative`: to make the file path relative to the repository root.
" - `absolute`: to make the file path absolute.
"
function! s:parse_lawrencium_path(lawrencium_path, ...)
    let l:repo_path = s:shellslash(a:lawrencium_path)
    let l:repo_path = substitute(l:repo_path, '\\ ', ' ', 'g')
    if l:repo_path =~? '\v^lawrencium://'
        let l:repo_path = strpart(l:repo_path, strlen('lawrencium://'))
    endif

    let l:root_dir = ''
    let l:at_idx = stridx(l:repo_path, '//')
    if l:at_idx >= 0
        let l:root_dir = strpart(l:repo_path, 0, l:at_idx)
        let l:repo_path = strpart(l:repo_path, l:at_idx + 2)
    endif

    let l:value = ''
    let l:action = ''
    let l:actionidx = stridx(l:repo_path, '//')
    if l:actionidx >= 0
        let l:action = strpart(l:repo_path, l:actionidx + 2)
        let l:repo_path = strpart(l:repo_path, 0, l:actionidx)

        let l:equalidx = stridx(l:action, '=')
        if l:equalidx >= 0
            let l:value = strpart(l:action, l:equalidx + 1)
            let l:action = strpart(l:action, 0, l:equalidx)
        endif
    endif

    if a:0 > 0
        execute 'cd! ' . fnameescape(l:root_dir)
        if a:1 == 'relative'
            let l:repo_path = fnamemodify(l:repo_path, ':.')
        elseif a:1 == 'absolute'
            let l:repo_path = fnamemodify(l:repo_path, ':p')
        endif
        execute 'cd! -'
    endif
    
    let l:result = { 'root': l:root_dir, 'path': l:repo_path, 'action': l:action, 'value': l:value }
    return l:result
endfunction

" Finds a window whose displayed buffer has a given variable
" set to the given value.
function! s:find_buffer_window(varname, varvalue) abort
    for wnr in range(1, winnr('$'))
        let l:bnr = winbufnr(wnr)
        if getbufvar(l:bnr, a:varname) == a:varvalue
            return l:wnr
        endif
    endfor
    return -1
endfunction

" Opens a buffer in a way that makes it easy to delete it later:
" - if the about-to-be previous buffer doesn't have a given variable,
"   just open the new buffer.
" - if the about-to-be previous buffer has a given variable, open the
"   new buffer with the `keepalt` option to make it so that the
"   actual previous buffer (returned by things like `bufname('#')`)
"   is the original buffer that was there before the first deletable
"   buffer was opened.
function! s:edit_deletable_buffer(varname, varvalue, path) abort
    let l:edit_cmd = 'edit '
    if getbufvar('%', a:varname) != ''
        let l:edit_cmd = 'keepalt edit '
    endif
    execute l:edit_cmd . fnameescape(a:path)
    call setbufvar('%', a:varname, a:varvalue)
endfunction

" Deletes all buffers that have a given variable set to a given value.
" For each buffer, if it is not shown in any window, it will be just deleted.
" If it is shown in a window, that window will be switched to the alternate
" buffer before the buffer is deleted, unless the `lawrencium_quit_on_delete`
" variable is set to `1`, in which case the window is closed too.
function! s:delete_dependency_buffers(varname, varvalue) abort
    let l:cur_winnr = winnr()
    for bnr in range(1, bufnr('$'))
        if getbufvar(bnr, a:varname) == a:varvalue
            " Delete this buffer if it is not shown in any window.
            " Otherwise, display the alternate buffer before deleting
            " it so the window is not closed.
            let l:bwnr = bufwinnr(bnr)
            if l:bwnr < 0 || getbufvar(bnr, 'lawrencium_quit_on_delete') == 1
                if bufloaded(l:bnr)
                    call s:trace("Deleting dependency buffer " . bnr)
                    execute "bdelete! " . bnr
                else
                    call s:trace("Dependency buffer " . bnr . " is already unladed.")
                endif
            else
                execute l:bwnr . "wincmd w"
                " TODO: better handle case where there's no previous/alternate buffer?
                let l:prev_bnr = bufnr('#')
                if l:prev_bnr > 0 && bufloaded(l:prev_bnr)
                    execute "buffer " . l:prev_bnr
                    if bufloaded(l:bnr)
                        call s:trace("Deleting dependency buffer " . bnr . " after switching to " . l:prev_bnr . " in window " . l:bwnr)
                        execute "bdelete! " . bnr
                    else
                        call s:trace("Dependency buffer " . bnr . " is unladed after switching to " . l:prev_bnr)
                    endif
                else
                    call s:trace("Deleting dependency buffer " . bnr . " and window.")
                    bdelete!
                endif
            endif
        endif
    endfor
    if l:cur_winnr != winnr()
        call s:trace("Returning to window " . l:cur_winnr)
        execute l:cur_winnr . "wincmd w"
    endif
endfunction

" Clean up all the 'HG:' lines from a commit message, and see if there's
" any message left (Mercurial does this automatically, usually, but
" apparently not when you feed it a log file...).
function! s:clean_commit_file(log_file) abort
    let l:lines = readfile(a:log_file)
    call filter(l:lines, "v:val !~# '\\v^HG:'")
    if len(filter(copy(l:lines), "v:val !~# '\\v^\\s*$'")) == 0
        return 0
    endif
    call writefile(l:lines, a:log_file)
    return 1
endfunction

" }}}

" Mercurial Repository Object {{{

" Let's define a Mercurial repo 'class' using prototype-based object-oriented
" programming.
"
" The prototype dictionary.
let s:HgRepo = {}

" Constructor.
function! s:HgRepo.New(path) abort
    let l:newRepo = copy(self)
    let l:newRepo.root_dir = s:find_repo_root(a:path)
    call s:trace("Built new Mercurial repository object at : " . l:newRepo.root_dir)
    return l:newRepo
endfunction

" Gets a full path given a repo-relative path.
function! s:HgRepo.GetFullPath(path) abort
    let l:root_dir = self.root_dir
    if s:isabspath(a:path)
        call s:throw("Expected relative path, got absolute path: " . a:path)
    endif
    return s:normalizepath(l:root_dir . a:path)
endfunction

" Gets a repo-relative path given any path.
function! s:HgRepo.GetRelativePath(path) abort
    execute 'lcd! ' . fnameescape(self.root_dir)
    let l:relative_path = fnamemodify(a:path, ':.')
    execute 'lcd! -'
    return l:relative_path
endfunction

" Gets, and optionally creates, a temp folder for some operation in the `.hg`
" directory.
function! s:HgRepo.GetTempDir(path, ...) abort
    let l:tmp_dir = self.GetFullPath('.hg/lawrencium/' . a:path)
    if !isdirectory(l:tmp_dir)
        if a:0 > 0 && !a:1
            return ''
        endif
        call mkdir(l:tmp_dir, 'p')
    endif
    return l:tmp_dir
endfunction

" Gets a list of files matching a root-relative pattern.
" If a flag is passed and is TRUE, a slash will be appended to all
" directories.
function! s:HgRepo.Glob(pattern, ...) abort
    let l:root_dir = self.root_dir
    if (a:pattern =~# '\v^[/\\]')
        let l:root_dir = s:stripslash(l:root_dir)
    endif
    let l:matches = split(glob(l:root_dir . a:pattern), '\n')
    if a:0 && a:1
        for l:idx in range(len(l:matches))
            if !filereadable(l:matches[l:idx])
                let l:matches[l:idx] = l:matches[l:idx] . '/'
            endif
        endfor
    endif
    let l:strip_len = len(l:root_dir)
    call map(l:matches, 'v:val[l:strip_len : -1]')
    return l:matches
endfunction

" Gets a full Mercurial command.
function! s:HgRepo.GetCommand(command, ...) abort
    " If there's only one argument, and it's a list, then use that as the
    " argument list.
    let l:arg_list = a:000
    if a:0 == 1 && type(a:1) == type([])
        let l:arg_list = a:1
    endif
    let l:prev_shellslash = &shellslash
    setlocal noshellslash
    let l:hg_command = g:lawrencium_hg_executable . ' --repository ' . shellescape(s:stripslash(self.root_dir))
    let l:hg_command = l:hg_command . ' ' . a:command
    for l:arg in l:arg_list
		let l:hg_command = l:hg_command . ' ' . shellescape(l:arg)
    endfor
    if l:prev_shellslash
        setlocal shellslash
    endif
    return l:hg_command
endfunction

" Runs a Mercurial command in the repo.
function! s:HgRepo.RunCommand(command, ...) abort
    let l:all_args = [1, a:command] + a:000
    return call(self['RunCommandEx'], l:all_args, self)
endfunction

function! s:HgRepo.RunCommandEx(plain_mode, command, ...) abort
    let l:prev_hgplain = $HGPLAIN
    if a:plain_mode
        let $HGPLAIN = 'true'
    endif
    let l:all_args = [a:command] + a:000
    let l:hg_command = call(self['GetCommand'], l:all_args, self)
    call s:trace("Running Mercurial command: " . l:hg_command)
    let l:cmd_out = system(l:hg_command)
    if a:plain_mode
        let $HGPLAIN = l:prev_hgplain
    endif
    return l:cmd_out
endfunction

" Runs a Mercurial command in the repo and reads its output into the current
" buffer.
function! s:HgRepo.ReadCommandOutput(command, ...) abort
    function! s:PutOutputIntoBuffer(command_line)
        let l:was_buffer_empty = (line('$') == 1 && getline(1) == '')
        execute '0read!' . escape(a:command_line, '%#\')
        if l:was_buffer_empty  " (Always true?)
            " '0read' inserts before the cursor, leaving a blank line which
            " needs to be deleted... but if there are folds in this thing, we
            " must open them all first otherwise we could delete the whole
            " contents of the last fold (since Vim may close them all by
            " default).
            normal! zRG"_dd
        endif
    endfunction

    let l:all_args = [a:command] + a:000
    let l:hg_command = call(self['GetCommand'], l:all_args, self)
    call s:trace("Running Mercurial command: " . l:hg_command)
    call s:PutOutputIntoBuffer(l:hg_command)
endfunction

" Build a Lawrencium path for the given file and action.
" By default, the given path will be made relative to the repository root,
" unless '0' is passed as the 4th argument.
function! s:HgRepo.GetLawrenciumPath(path, action, value, ...) abort
    let l:path = a:path
    if a:0 == 0 || !a:1
        let l:path = self.GetRelativePath(a:path)
    endif
    let l:path = fnameescape(l:path)
    let l:result = 'lawrencium://' . s:stripslash(self.root_dir) . '//' . l:path
    if a:action !=? ''
        let l:result  = l:result . '//' . a:action
        if a:value !=? ''
            let l:result = l:result . '=' . a:value
        endif
    endif
    return l:result
endfunction

" Repo cache map.
let s:buffer_repos = {}

" Get a cached repo.
function! s:hg_repo(...) abort
    " Use the given path, or the mercurial directory of the current buffer.
    if a:0 == 0
        if exists('b:mercurial_dir')
            let l:path = b:mercurial_dir
        else
            let l:path = s:find_repo_root(expand('%:p'))
        endif
    else
        let l:path = a:1
    endif
    " Find a cache repo instance, or make a new one.
    if has_key(s:buffer_repos, l:path)
        return get(s:buffer_repos, l:path)
    else
        let l:repo = s:HgRepo.New(l:path)
        let s:buffer_repos[l:path] = l:repo
        return l:repo
    endif
endfunction

" Sets up the current buffer with Lawrencium commands if it contains a file from a Mercurial repo.
" If the file is not in a Mercurial repo, just exit silently.
function! s:setup_buffer_commands() abort
    call s:trace("Scanning buffer '" . bufname('%') . "' for Lawrencium setup...")
    let l:do_setup = 1
    if exists('b:mercurial_dir')
        if b:mercurial_dir =~# '\v^\s*$'
            unlet b:mercurial_dir
        else
            let l:do_setup = 0
        endif
    endif
    try
        let l:repo = s:hg_repo()
    catch /^lawrencium\:/
        return
    endtry
    let b:mercurial_dir = l:repo.root_dir
    if exists('b:mercurial_dir') && l:do_setup
        call s:trace("Setting Mercurial commands for buffer '" . bufname('%'))
        call s:trace("  with repo : " . expand(b:mercurial_dir))
        silent doautocmd User Lawrencium
    endif
endfunction

augroup lawrencium_detect
    autocmd!
    autocmd BufNewFile,BufReadPost *     call s:setup_buffer_commands()
    autocmd VimEnter               *     if expand('<amatch>')==''|call s:setup_buffer_commands()|endif
augroup end

" }}}

" Buffer Object {{{

" The prototype dictionary.
let s:Buffer = {}

" Constructor.
function! s:Buffer.New(number) dict abort
    let l:newBuffer = copy(self)
    let l:newBuffer.nr = a:number
    let l:newBuffer.var_backup = {}
    let l:newBuffer.cmd_names = {}
    let l:newBuffer.on_delete = []
    let l:newBuffer.on_winleave = []
    let l:newBuffer.on_unload = []
    execute 'augroup lawrencium_buffer_' . a:number
    execute '  autocmd!'
    execute '  autocmd BufDelete <buffer=' . a:number . '> call s:buffer_on_delete(' . a:number . ')'
    execute 'augroup end'
    call s:trace("Built new buffer object for buffer: " . a:number)
    return l:newBuffer
endfunction

function! s:Buffer.GetName(...) dict abort
    let l:name = bufname(self.nr)
    if a:0 > 0
        let l:name = fnamemodify(l:name, a:1)
    endif
    return l:name
endfunction

function! s:Buffer.GetVar(var) dict abort
    return getbufvar(self.nr, a:var)
endfunction

function! s:Buffer.SetVar(var, value) dict abort
    if !has_key(self.var_backup, a:var)
        let self.var_backup[a:var] = getbufvar(self.nr, a:var)
    endif
    return setbufvar(self.nr, a:var, a:value)
endfunction

function! s:Buffer.RestoreVars() dict abort
    for key in keys(self.var_backup)
        setbufvar(self.nr, key, self.var_backup[key])
    endfor
endfunction

function! s:Buffer.DefineCommand(name, ...) dict abort
    if a:0 == 0
        call s:throw("Not enough parameters for s:Buffer.DefineCommands()")
    endif
    if a:0 == 1
        let l:flags = ''
        let l:cmd = a:1
    else
        let l:flags = a:1
        let l:cmd = a:2
    endif
    if has_key(self.cmd_names, a:name)
        call s:throw("Command '".a:name."' is already defined in buffer ".self.nr)
    endif
    if bufnr('%') != self.nr
        call s:throw("You must move to buffer ".self.nr."first before defining local commands")
    endif
    let self.cmd_names[a:name] = 1
    let l:real_flags = ''
    if type(l:flags) == type('')
        let l:real_flags = l:flags
    endif
    execute 'command -buffer '.l:real_flags.' '.a:name.' '.l:cmd
endfunction

function! s:Buffer.DeleteCommand(name) dict abort
    if !has_key(self.cmd_names, a:name)
        call s:throw("Command '".a:name."' has not been defined in buffer ".self.nr)
    endif
    if bufnr('%') != self.nr
        call s:throw("You must move to buffer ".self.nr."first before deleting local commands")
    endif
    execute 'delcommand '.a:name
    call remove(self.cmd_names, a:name)
endfunction

function! s:Buffer.DeleteCommands() dict abort
    if bufnr('%') != self.nr
        call s:throw("You must move to buffer ".self.nr."first before deleting local commands")
    endif
    for name in keys(self.cmd_names)
        execute 'delcommand '.name
    endfor
    let self.cmd_names = {}
endfunction

function! s:Buffer.MoveToFirstWindow() dict abort
    let l:win_nr = bufwinnr(self.nr)
    if l:win_nr < 0
        if a:0 > 0 && a:1 == 0
            return 0
        endif
        call s:throw("No windows currently showing buffer ".self.nr)
    endif
    execute l:win_nr.'wincmd w'
    return 1
endfunction

function! s:Buffer.OnDelete(cmd) dict abort
    call s:trace("Adding BufDelete callback for buffer " . self.nr . ": " . a:cmd)
    call add(self.on_delete, a:cmd)
endfunction

function! s:Buffer.OnWinLeave(cmd) dict abort
    if len(self.on_winleave) == 0
        call s:trace("Adding BufWinLeave auto-command on buffer " . self.nr)
        execute 'augroup lawrencium_buffer_' . self.nr . '_winleave'
        execute '  autocmd!'
        execute '  autocmd BufWinLeave <buffer=' . self.nr . '> call s:buffer_on_winleave(' . self.nr .')'
        execute 'augroup end'
    endif
    call s:trace("Adding BufWinLeave callback for buffer " . self.nr . ": " . a:cmd)
    call add(self.on_winleave, a:cmd)
endfunction

function! s:Buffer.OnUnload(cmd) dict abort
    if len(self.on_unload) == 0
        call s:trace("Adding BufUnload auto-command on buffer " . self.nr)
        execute 'augroup lawrencium_buffer_' . self.nr . '_unload'
        execute '  autocmd!'
        execute '  autocmd BufUnload <buffer=' . self.nr . '> call s:buffer_on_unload(' . self.nr . ')'
        execute 'augroup end'
    endif
    call s:trace("Adding BufUnload callback for buffer " . self.nr . ": " . a:cmd)
    call add(self.on_unload, a:cmd)
endfunction

let s:buffer_objects = {}

" Get a buffer instance for the specified buffer number, or the
" current buffer if nothing is specified.
function! s:buffer_obj(...) abort
    let l:bufnr = a:0 ? a:1 : bufnr('%')
    if !has_key(s:buffer_objects, l:bufnr)
        let s:buffer_objects[l:bufnr] = s:Buffer.New(l:bufnr)
    endif
    return s:buffer_objects[l:bufnr]
endfunction

" Execute all the "on delete" callbacks.
function! s:buffer_on_delete(number) abort
    let l:bufobj = s:buffer_objects[a:number]
    call s:trace("Calling BufDelete callbacks on buffer " . l:bufobj.nr)
    for cmd in l:bufobj.on_delete
        call s:trace(" [" . cmd . "]")
        execute cmd
    endfor
    call s:trace("Deleted buffer object " . l:bufobj.nr)
    call remove(s:buffer_objects, l:bufobj.nr)
    execute 'augroup lawrencium_buffer_' . l:bufobj.nr
    execute '  autocmd!'
    execute 'augroup end'
endfunction

" Execute all the "on winleave" callbacks.
function! s:buffer_on_winleave(number) abort
    let l:bufobj = s:buffer_objects[a:number]
    call s:trace("Calling BufWinLeave callbacks on buffer " . l:bufobj.nr)
    for cmd in l:bufobj.on_winleave
        call s:trace(" [" . cmd . "]")
        execute cmd
    endfor
    execute 'augroup lawrencium_buffer_' . l:bufobj.nr . '_winleave'
    execute '  autocmd!'
    execute 'augroup end'
endfunction

" Execute all the "on unload" callbacks.
function! s:buffer_on_unload(number) abort
    let l:bufobj = s:buffer_objects[a:number]
    call s:trace("Calling BufUnload callbacks on buffer " . l:bufobj.nr)
    for cmd in l:bufobj.on_unload
        call s:trace(" [" . cmd . "]")
        execute cmd
    endfor
    execute 'augroup lawrencium_buffer_' . l:bufobj.nr . '_unload'
    execute '  autocmd!'
    execute 'augroup end'
endfunction

" }}}

" Lawrencium Files {{{

" Read revision (`hg cat`)
function! s:read_lawrencium_rev(repo, path_parts, full_path) abort
    let l:rev = a:path_parts['value']
    if l:rev == ''
        call a:repo.ReadCommandOutput('cat', a:full_path)
    else
        call a:repo.ReadCommandOutput('cat', '-r', l:rev, a:full_path)
    endif
endfunction

" Status (`hg status`)
function! s:read_lawrencium_status(repo, path_parts, full_path) abort
    if a:path_parts['path'] == ''
        call a:repo.ReadCommandOutput('status')
    else
        call a:repo.ReadCommandOutput('status', a:full_path)
    endif
    setlocal nomodified
    setlocal filetype=hgstatus
    setlocal bufhidden=delete
    setlocal buftype=nofile
endfunction

" Log (`hg log`)
let s:log_style_file = expand("<sfile>:h:h") . "/resources/hg_log.style"

function! s:read_lawrencium_log(repo, path_parts, full_path) abort
    let l:log_opts = join(split(a:path_parts['value'], ','))
    let l:log_cmd = "log " . l:log_opts

    if a:path_parts['path'] == ''
        call a:repo.ReadCommandOutput(l:log_cmd, '--style', s:log_style_file)
    else
        call a:repo.ReadCommandOutput(l:log_cmd, '--style', s:log_style_file, a:full_path)
    endif
    setlocal filetype=hglog
endfunction

function! s:read_lawrencium_logpatch(repo, path_parts, full_path) abort
    let l:log_cmd = 'log --patch --verbose --rev ' . a:path_parts['value']

    if a:path_parts['path'] == ''
        call a:repo.ReadCommandOutput(l:log_cmd)
    else
        call a:repo.ReadCommandOutput(l:log_cmd, a:full_path)
    endif
    setlocal filetype=diff
endfunction

" Diff revisions (`hg diff`)
function! s:read_lawrencium_diff(repo, path_parts, full_path) abort
    let l:diffargs = []
    let l:commaidx = stridx(a:path_parts['value'], ',')
    if l:commaidx > 0
        let l:rev1 = strpart(a:path_parts['value'], 0, l:commaidx)
        let l:rev2 = strpart(a:path_parts['value'], l:commaidx + 1)
        if l:rev1 == '-'
            let l:diffargs = [ '-r', l:rev2 ]
        elseif l:rev2 == '-'
            let l:diffargs = [ '-r', l:rev1 ]
        else
            let l:diffargs = [ '-r', l:rev1, '-r', l:rev2 ]
        endif
    elseif a:path_parts['value'] != ''
        let l:diffargs = [ '-c', a:path_parts['value'] ]
    else
        let l:diffargs = []
    endif
    if a:path_parts['path'] != '' && a:path_parts['path'] != '.'
        call add(l:diffargs, a:full_path)
    endif
    call a:repo.ReadCommandOutput('diff', l:diffargs)
    setlocal filetype=diff
    setlocal nofoldenable
endfunction

" Annotate file
function! s:read_lawrencium_annotate(repo, path_parts, full_path) abort
    let l:cmd_args = ['-c', '-n', '-u', '-d', '-q']
    if a:path_parts['value'] == 'v=1'
        call insert(l:cmd_args, '-v', 0)
    endif
    call add(l:cmd_args, a:full_path)
    call a:repo.ReadCommandOutput('annotate', l:cmd_args)
endfunction

" MQ series
function! s:read_lawrencium_qseries(repo, path_parts, full_path) abort
    let l:names = split(a:repo.RunCommand('qseries'), '\n')
    let l:head = split(a:repo.RunCommand('qapplied', '-s'), '\n')
    let l:tail = split(a:repo.RunCommand('qunapplied', '-s'), '\n')

    let l:idx = 0
    let l:curbuffer = bufname('%')
    for line in l:head
        call setbufvar(l:curbuffer, 'lawrencium_patchname_' . (l:idx + 1), l:names[l:idx])
        call append(l:idx, "*" . line)
        let l:idx = l:idx + 1
    endfor
    for line in l:tail
        call setbufvar(l:curbuffer, 'lawrencium_patchname_' . (l:idx + 1), l:names[l:idx])
        call append(l:idx, line)
        let l:idx = l:idx + 1
    endfor
    call setbufvar(l:curbuffer, 'lawrencium_patchname_top', l:names[len(l:head) - 1])
    set filetype=hgqseries
endfunction

" Generic read
let s:lawrencium_file_readers = {
            \'rev': function('s:read_lawrencium_rev'),
            \'log': function('s:read_lawrencium_log'),
            \'logpatch': function('s:read_lawrencium_logpatch'),
            \'diff': function('s:read_lawrencium_diff'),
            \'status': function('s:read_lawrencium_status'),
            \'annotate': function('s:read_lawrencium_annotate'),
            \'qseries': function('s:read_lawrencium_qseries')
            \}
let s:lawrencium_file_customoptions = {
            \'status': 1
            \}

function! s:ReadLawrenciumFile(path) abort
    call s:trace("Reading Lawrencium file: " . a:path)
    let l:path_parts = s:parse_lawrencium_path(a:path)
    if l:path_parts['root'] == ''
        call s:throw("Can't get repository root from: " . a:path)
    endif
    if !has_key(s:lawrencium_file_readers, l:path_parts['action'])
        call s:throw("No registered reader for action: " . l:path_parts['action'])
    endif

    " Call the registered reader.
    let l:repo = s:hg_repo(l:path_parts['root'])
    let l:full_path = l:repo.root_dir . l:path_parts['path']
    let LawrenciumFileReader = s:lawrencium_file_readers[l:path_parts['action']]
    call LawrenciumFileReader(l:repo, l:path_parts, l:full_path)

    " Setup the new buffer.
    if !has_key(s:lawrencium_file_customoptions, l:path_parts['action'])
        setlocal readonly
        setlocal nomodified
        setlocal bufhidden=delete
        setlocal buftype=nofile
    endif
    goto

    " Remember the real Lawrencium path, because Vim can fuck up the slashes
    " on Windows.
    let b:lawrencium_path = a:path

    " Remember the repo it belongs to and make
    " the Lawrencium commands available.
    let b:mercurial_dir = l:repo.root_dir
    call s:DefineMainCommands()

    return ''
endfunction

function! s:WriteLawrenciumFile(path) abort
    call s:trace("Writing Lawrencium file: " . a:path)
endfunction

augroup lawrencium_files
  autocmd!
  autocmd BufReadCmd  lawrencium://**//**//* exe s:ReadLawrenciumFile(expand('<amatch>'))
  autocmd BufWriteCmd lawrencium://**//**//* exe s:WriteLawrenciumFile(expand('<amatch>'))
augroup END

" }}}

" Buffer Commands Management {{{

" Store the commands for Lawrencium-enabled buffers so that we can add them in
" batch when we need to.
let s:main_commands = []

function! s:AddMainCommand(command) abort
    let s:main_commands += [a:command]
endfunction

function! s:DefineMainCommands()
    for l:command in s:main_commands
        execute 'command! -buffer ' . l:command
    endfor
endfunction

augroup lawrencium_main
    autocmd!
    autocmd User Lawrencium call s:DefineMainCommands()
augroup end

" }}}

" Commands Auto-Complete {{{

" Auto-complete function for commands that take repo-relative file paths.
function! s:ListRepoFiles(ArgLead, CmdLine, CursorPos) abort
    let l:matches = s:hg_repo().Glob(a:ArgLead . '*', 1)
    call map(l:matches, 's:normalizepath(v:val)')
    return l:matches
endfunction

" Auto-complete function for commands that take repo-relative directory paths.
function! s:ListRepoDirs(ArgLead, CmdLine, CursorPos) abort
    let l:matches = s:hg_repo().Glob(a:ArgLead . '*/')
    call map(l:matches, 's:normalizepath(v:val)')
    return l:matches
endfunction

" }}}

" Hg {{{

function! s:Hg(bang, ...) abort
    let l:repo = s:hg_repo()
    if g:lawrencium_auto_cd
        " Temporary set the current directory to the root of the repo
        " to make auto-completed paths work magically.
        execute 'cd! ' . fnameescape(l:repo.root_dir)
    endif
    let l:output = call(l:repo.RunCommandEx, [0] + a:000, l:repo)
    if g:lawrencium_auto_cd
        execute 'cd! -'
    endif
    silent doautocmd User HgCmdPost
    if a:bang
        " Open the output of the command in a temp file.
        let l:temp_file = s:tempname('hg-output-', '.txt')
        split
        execute 'edit ' . fnameescape(l:temp_file)
        call append(0, split(l:output, '\n'))
        call cursor(1, 1)

        " Make it a temp buffer
        setlocal bufhidden=delete
        setlocal buftype=nofile

        " Try to find a nice syntax to set given the current command.
        let l:command_name = s:GetHgCommandName(a:000)
        if l:command_name != '' && exists('g:lawrencium_hg_commands_file_types')
            let l:file_type = get(g:lawrencium_hg_commands_file_types, l:command_name, '')
            if l:file_type != ''
                execute 'setlocal ft=' . l:file_type
            endif
        endif
    else
        " Just print out the output of the command.
        echo l:output
    endif
endfunction

" Include the generated HG usage file.
let s:usage_file = expand("<sfile>:h:h") . "/resources/hg_usage.vim"
if filereadable(s:usage_file)
    execute "source " . fnameescape(s:usage_file)
else
    call s:error("Can't find the Mercurial usage file. Auto-completion will be disabled in Lawrencium.")
endif

" Include the command file type mappings.
let s:file_type_mappings = expand("<sfile>:h:h") . '/resources/hg_command_file_types.vim'
if filereadable(s:file_type_mappings)
    execute "source " . fnameescape(s:file_type_mappings)
endif

function! s:CompleteHg(ArgLead, CmdLine, CursorPos)
    " Don't do anything if the usage file was not sourced.
    if !exists('g:lawrencium_hg_commands') || !exists('g:lawrencium_hg_options')
        return []
    endif

    " a:ArgLead seems to be the number 0 when completing a minus '-'.
    " Gotta find out why...
    let l:arglead = a:ArgLead
    if type(a:ArgLead) == type(0)
        let l:arglead = '-'
    endif

    " Try completing a global option, before any command name.
    if a:CmdLine =~# '\v^Hg(\s+\-[a-zA-Z0-9\-_]*)+$'
        return filter(copy(g:lawrencium_hg_options), "v:val[0:strlen(l:arglead)-1] ==# l:arglead")
    endif

    " Try completing a command (note that there could be global options before
    " the command name).
    if a:CmdLine =~# '\v^Hg\s+(\-[a-zA-Z0-9\-_]+\s+)*[a-zA-Z]+$'
        return filter(keys(g:lawrencium_hg_commands), "v:val[0:strlen(l:arglead)-1] ==# l:arglead")
    endif
    
    " Try completing a command's options.
    let l:cmd = matchstr(a:CmdLine, '\v(^Hg\s+(\-[a-zA-Z0-9\-_]+\s+)*)@<=[a-zA-Z]+')
    if strlen(l:cmd) > 0 && l:arglead[0] ==# '-'
        if has_key(g:lawrencium_hg_commands, l:cmd)
            " Return both command options and global options together.
            let l:copts = filter(copy(g:lawrencium_hg_commands[l:cmd]), "v:val[0:strlen(l:arglead)-1] ==# l:arglead")
            let l:gopts = filter(copy(g:lawrencium_hg_options), "v:val[0:strlen(l:arglead)-1] ==# l:arglead")
            return l:copts + l:gopts
        endif
    endif
    
    " Just auto-complete with filenames unless it's an option.
    if l:arglead[0] ==# '-'
        return []
    else
        return s:ListRepoFiles(a:ArgLead, a:CmdLine, a:CursorPos)
endfunction

function! s:GetHgCommandName(args) abort
    for l:a in a:args
        if stridx(l:a, '-') != 0
            return l:a
        endif
    endfor
    return ''
endfunction

call s:AddMainCommand("-bang -complete=customlist,s:CompleteHg -nargs=* Hg :call s:Hg(<bang>0, <f-args>)")

" }}}

" Hgstatus {{{

function! s:HgStatus() abort
    " Get the repo and the Lawrencium path for `hg status`.
    let l:repo = s:hg_repo()
    let l:status_path = l:repo.GetLawrenciumPath('', 'status', '')

    " Open the Lawrencium buffer in a new split window of the right size.
    if g:lawrencium_status_win_split_above
      execute "keepalt leftabove split " . fnameescape(l:status_path)
    else
      execute "keepalt rightbelow split " . fnameescape(l:status_path)
    endif
    
    if (line('$') == 1 && getline(1) == '')
        " Buffer is empty, which means there are not changes...
        " Quit and display a message.
        " TODO: figure out why the first `echom` doesn't show when alone.
        bdelete
        echom "Nothing was modified."
        echom ""
        return
    endif

    execute "setlocal winfixheight"
    if !g:lawrencium_status_win_split_even
      execute "setlocal winheight=" . (line('$') + 1)
      execute "resize " . (line('$') + 1)
    endif

    " Add some nice commands.
    command! -buffer          Hgstatusedit          :call s:HgStatus_FileEdit(0)
    command! -buffer          Hgstatusdiff          :call s:HgStatus_Diff(0)
    command! -buffer          Hgstatusvdiff         :call s:HgStatus_Diff(1)
    command! -buffer          Hgstatustabdiff       :call s:HgStatus_Diff(2)
    command! -buffer          Hgstatusdiffsum       :call s:HgStatus_DiffSummary(1)
    command! -buffer          Hgstatusvdiffsum      :call s:HgStatus_DiffSummary(2)
    command! -buffer          Hgstatustabdiffsum    :call s:HgStatus_DiffSummary(3)
    command! -buffer          Hgstatusrefresh       :call s:HgStatus_Refresh()
    command! -buffer -range -bang Hgstatusrevert    :call s:HgStatus_Revert(<line1>, <line2>, <bang>0)
    command! -buffer -range   Hgstatusaddremove     :call s:HgStatus_AddRemove(<line1>, <line2>)
    command! -buffer -range=% -bang Hgstatuscommit  :call s:HgStatus_Commit(<line1>, <line2>, <bang>0, 0)
    command! -buffer -range=% -bang Hgstatusvcommit :call s:HgStatus_Commit(<line1>, <line2>, <bang>0, 1)
    command! -buffer -range=% -nargs=+ Hgstatusqnew :call s:HgStatus_QNew(<line1>, <line2>, <f-args>)
    command! -buffer -range=% Hgstatusqrefresh      :call s:HgStatus_QRefresh(<line1>, <line2>)

    " Add some handy mappings.
    if g:lawrencium_define_mappings
        nnoremap <buffer> <silent> <cr>  :Hgstatusedit<cr>
        nnoremap <buffer> <silent> <C-N> :call search('^[MARC\!\?I ]\s.', 'We')<cr>
        nnoremap <buffer> <silent> <C-P> :call search('^[MARC\!\?I ]\s.', 'Wbe')<cr>
        nnoremap <buffer> <silent> <C-D> :Hgstatustabdiff<cr>
        nnoremap <buffer> <silent> <C-V> :Hgstatusvdiff<cr>
        nnoremap <buffer> <silent> <C-U> :Hgstatusdiffsum<cr>
        nnoremap <buffer> <silent> <C-H> :Hgstatusvdiffsum<cr>
        nnoremap <buffer> <silent> <C-A> :Hgstatusaddremove<cr>
        nnoremap <buffer> <silent> <C-S> :Hgstatuscommit<cr>
        nnoremap <buffer> <silent> <C-R> :Hgstatusrefresh<cr>
        nnoremap <buffer> <silent> q     :bdelete!<cr>

        vnoremap <buffer> <silent> <C-A> :Hgstatusaddremove<cr>
        vnoremap <buffer> <silent> <C-S> :Hgstatuscommit<cr>
    endif
endfunction

function! s:HgStatus_Refresh(...) abort
    if a:0 > 0
        let l:win_nr = bufwinnr(a:1)
        call s:trace("Switching back to status window ".l:win_nr)
        if l:win_nr < 0
            call s:throw("Can't find the status window anymore!")
        endif
        execute l:win_nr . 'wincmd w'
        " Delete everything in the buffer, and re-read the status into it.
        " TODO: In theory I would only have to do `edit` like below when we're
        " already in the window, but for some reason Vim just goes bonkers and
        " weird shit happens. I have no idea why, hence the work-around here
        " to bypass the whole `BufReadCmd` auto-command altogether, and just
        " edit the buffer in place.
        normal! ggVGd
        call s:ReadLawrenciumFile(b:lawrencium_path)
        return
    endif

    " Just re-edit the buffer, it will reload the contents by calling
    " the matching Mercurial command.
    edit
endfunction

function! s:HgStatus_FileEdit(newtab) abort
    " Get the path of the file the cursor is on.
    let l:filename = s:HgStatus_GetSelectedFile()

    let l:cleanupbufnr = -1
    if a:newtab == 0
        " If the file is already open in a window, jump to that window.
        " Otherwise, jump to the previous window and open it there.
        for nr in range(1, winnr('$'))
            let l:br = winbufnr(nr)
            let l:bpath = fnamemodify(bufname(l:br), ':p')
            if l:bpath ==# l:filename
                execute nr . 'wincmd w'
                return
            endif
        endfor
        wincmd p
    else
        " Just open a new tab so we can edit the file there.
        " We don't use `tabedit` because it messes up the current window
        " if it happens to be the same file.
        " We'll just have to clean up the default empty buffer created.
        tabnew
        let l:cleanupbufnr = bufnr('%')
    endif
    execute 'edit ' . fnameescape(l:filename)
    if l:cleanupbufnr >= 0
        execute 'bdelete ' . l:cleanupbufnr
    endif
endfunction

function! s:HgStatus_AddRemove(linestart, lineend) abort
    " Get the selected filenames.
    let l:filenames = s:HgStatus_GetSelectedFiles(a:linestart, a:lineend, ['!', '?'])
    if len(l:filenames) == 0
        call s:error("No files to add or remove in selection or current line.")
        return
    endif

    " Run `addremove` on those paths.
    let l:repo = s:hg_repo()
    call l:repo.RunCommand('addremove', l:filenames)

    " Refresh the status window.
    call s:HgStatus_Refresh()
endfunction

function! s:HgStatus_Revert(linestart, lineend, bang) abort
    " Get the selected filenames.
    let l:filenames = s:HgStatus_GetSelectedFiles(a:linestart, a:lineend, ['M', 'A', 'R'])
    if len(l:filenames) == 0
        call s:error("No files to revert in selection or current line.")
        return
    endif

    " Run `revert` on those paths.
    " If the bang modifier is specified, revert with no backup.
    let l:repo = s:hg_repo()
    if a:bang
        call insert(l:filenames, '-C', 0)
    endif
    call l:repo.RunCommand('revert', l:filenames)

    " Refresh the status window.
    call s:HgStatus_Refresh()
endfunction

function! s:HgStatus_Commit(linestart, lineend, bang, vertical) abort
    " Get the selected filenames.
    let l:filenames = s:HgStatus_GetSelectedFiles(a:linestart, a:lineend, ['M', 'A', 'R'])
    if len(l:filenames) == 0
        call s:error("No files to commit in selection or file.")
        return
    endif

    " Run `Hgcommit` on those paths.
    let l:buf_nr = bufnr('%')
    let l:callback = 'call s:HgStatus_Refresh('.l:buf_nr.')'
    call s:HgCommit(a:bang, a:vertical, l:callback, l:filenames)
endfunction

function! s:HgStatus_Diff(split) abort
    " Open the file and run `Hgdiff` on it.
    " We also need to translate the split mode for it... if we already
    " opened the file in a new tab, `HgDiff` only needs to do a vertical
    " split (i.e. split=1).
    let l:newtab = 0
    let l:hgdiffsplit = a:split
    if a:split == 2
        let l:newtab = 1
        let l:hgdiffsplit = 1
    endif
    call s:HgStatus_FileEdit(l:newtab)
    call s:HgDiff('%:p', l:hgdiffsplit)
endfunction

function! s:HgStatus_DiffSummary(split) abort
    " Get the path of the file the cursor is on.
    let l:path = s:HgStatus_GetSelectedFile()
    " Reuse the same diff summary window
    let l:reuse_id = 'lawrencium_diffsum_for_' . bufnr('%')
    let l:split_prev_win = (a:split < 3)
    let l:args = {'reuse_id': l:reuse_id, 'use_prev_win': l:split_prev_win,
                \'avoid_win': winnr(), 'split_mode': a:split}
    call s:HgDiffSummary(l:path, l:args)
endfunction

function! s:HgStatus_QNew(linestart, lineend, patchname, ...) abort
    " Get the selected filenames.
    let l:filenames = s:HgStatus_GetSelectedFiles(a:linestart, a:lineend, ['M', 'A', 'R'])
    if len(l:filenames) == 0
        call s:error("No files in selection or file to create patch.")
        return
    endif

    " Run `Hg qnew` on those paths.
    let l:repo = s:hg_repo()
    call insert(l:filenames, a:patchname, 0)
    if a:0 > 0
        call insert(l:filenames, '-m', 0)
        let l:message = '"' . join(a:000, ' ') . '"'
        call insert(l:filenames, l:message, 1)
    endif
    call l:repo.RunCommand('qnew', l:filenames)

    " Refresh the status window.
    call s:HgStatus_Refresh()
endfunction

function! s:HgStatus_QRefresh(linestart, lineend) abort
    " Get the selected filenames.
    let l:filenames = s:HgStatus_GetSelectedFiles(a:linestart, a:lineend, ['M', 'A', 'R'])
    if len(l:filenames) == 0
        call s:error("No files in selection or file to refresh the patch.")
        return
    endif

    " Run `Hg qrefresh` on those paths.
    let l:repo = s:hg_repo()
    call insert(l:filenames, '-s', 0)
    call l:repo.RunCommand('qrefresh', l:filenames)

    " Refresh the status window.
    call s:HgStatus_Refresh()
endfunction


function! s:HgStatus_GetSelectedFile() abort
    let l:filenames = s:HgStatus_GetSelectedFiles()
    return l:filenames[0]
endfunction

function! s:HgStatus_GetSelectedFiles(...) abort
    if a:0 >= 2
        let l:lines = getline(a:1, a:2)
    else
        let l:lines = []
        call add(l:lines, getline('.'))
    endif
    let l:filenames = []
    let l:repo = s:hg_repo()
    for line in l:lines
        if a:0 >= 3
            let l:status = s:HgStatus_GetFileStatus(line)
            if index(a:3, l:status) < 0
                continue
            endif
        endif
        " Yay, awesome, Vim's regex syntax is fucked up like shit, especially for
        " look-aheads and look-behinds. See for yourself:
        let l:filename = matchstr(l:line, '\v(^[MARC\!\?I ]\s)@<=.*')
        let l:filename = l:repo.GetFullPath(l:filename)
        call add(l:filenames, l:filename)
    endfor
    return l:filenames
endfunction

function! s:HgStatus_GetFileStatus(...) abort
    let l:line = a:0 ? a:1 : getline('.')
    return matchstr(l:line, '\v^[MARC\!\?I ]')
endfunction

call s:AddMainCommand("Hgstatus :call s:HgStatus()")

" }}}

" Hgcd, Hglcd {{{

call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoDirs Hgcd :cd<bang> `=s:hg_repo().GetFullPath(<q-args>)`")
call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoDirs Hglcd :lcd<bang> `=s:hg_repo().GetFullPath(<q-args>)`")

" }}}

" Hgedit {{{

function! s:HgEdit(bang, filename) abort
    let l:full_path = s:hg_repo().GetFullPath(a:filename)
    if a:bang
        execute "edit! " . fnameescape(l:full_path)
    else
        execute "edit " . fnameescape(l:full_path)
    endif
endfunction

call s:AddMainCommand("-bang -nargs=1 -complete=customlist,s:ListRepoFiles Hgedit :call s:HgEdit(<bang>0, <f-args>)")

" }}}

" Hgvimgrep {{{

function! s:HgVimGrep(bang, pattern, ...) abort
    let l:repo = s:hg_repo()
    let l:file_paths = []
    if a:0 > 0
        for ff in a:000
            let l:full_ff = l:repo.GetFullPath(ff)
            call add(l:file_paths, l:full_ff)
        endfor
    else
        call add(l:file_paths, l:repo.root_dir . "**")
    endif
    if a:bang
        execute "vimgrep! " . a:pattern . " " . join(l:file_paths, " ")
    else
        execute "vimgrep " . a:pattern . " " . join(l:file_paths, " ")
    endif
endfunction

call s:AddMainCommand("-bang -nargs=+ -complete=customlist,s:ListRepoFiles Hgvimgrep :call s:HgVimGrep(<bang>0, <f-args>)")

" }}}

" Hgdiff, Hgvdiff, Hgtabdiff {{{

function! s:HgDiff(filename, split, ...) abort
    " Default revisions to diff: the working directory (null string) 
    " and the parent of the working directory (using Mercurial's revsets syntax).
    " Otherwise, use the 1 or 2 revisions specified as extra parameters.
    let l:rev1 = 'p1()'
    let l:rev2 = ''
    if a:0 == 1
        if type(a:1) == type([])
            if len(a:1) >= 2
                let l:rev1 = a:1[0]
                let l:rev2 = a:1[1]
            elseif len(a:1) == 1
                let l:rev1 = a:1[0]
            endif
        else
            let l:rev1 = a:1
        endif
    elseif a:0 == 2
        let l:rev1 = a:1
        let l:rev2 = a:2
    endif

    " Get the current repo, and expand the given filename in case it contains
    " fancy filename modifiers.
    let l:repo = s:hg_repo()
    let l:path = expand(a:filename)
    let l:diff_id = localtime()
    call s:trace("Diff'ing '".l:rev1."' and '".l:rev2."' on file: ".l:path)

    " Get the first file and open it.
    let l:cleanupbufnr = -1
    if l:rev1 == ''
        if a:split == 2
            " Don't use `tabedit` here because if `l:path` is the same as
            " the current path, it will also reload the buffer in the current
            " tab/window for some reason, which causes all state to be lost
            " (all folds get collapsed again, cursor is moved to start, etc.)
            tabnew
            let l:cleanupbufnr = bufnr('%')
            execute 'edit ' . fnameescape(l:path)
        else
            if bufexists(l:path)
                execute 'buffer ' . fnameescape(l:path)
            else
                execute 'edit ' . fnameescape(l:path)
            endif
        endif
        " Make it part of the diff group.
        call s:HgDiff_DiffThis(l:diff_id)
    else
        let l:rev_path = l:repo.GetLawrenciumPath(l:path, 'rev', l:rev1)
        if a:split == 2
            " See comments above about avoiding `tabedit`.
            tabnew
            let l:cleanupbufnr = bufnr('%')
        endif
        execute 'edit ' . fnameescape(l:rev_path)
        " Make it part of the diff group.
        call s:HgDiff_DiffThis(l:diff_id)
    endif
    if l:cleanupbufnr >= 0 && bufloaded(l:cleanupbufnr)
        execute 'bdelete ' . l:cleanupbufnr
    endif

    " Get the second file and open it too.
    " Don't use `diffsplit` because it will set `&diff` before we get a chance
    " to save a bunch of local settings that we will want to restore later.
    let l:diffsplit = 'split'
    if a:split >= 1
        let l:diffsplit = 'vsplit'
    endif
    if l:rev2 == ''
        execute l:diffsplit . ' ' . fnameescape(l:path)
    else
        let l:rev_path = l:repo.GetLawrenciumPath(l:path, 'rev', l:rev2)
        execute l:diffsplit . ' ' . fnameescape(l:rev_path)
    endif
    call s:HgDiff_DiffThis(l:diff_id)
endfunction

function! s:HgDiff_DiffThis(diff_id) abort
    " Store some commands to run when we exit diff mode.
    " It's needed because `diffoff` reverts those settings to their default
    " values, instead of their previous ones.
    if &diff
        call s:throw("Calling diffthis too late on a buffer!")
        return
    endif
    call s:trace('Enabling diff mode on ' . bufname('%'))
    let w:lawrencium_diffoff = {}
    let w:lawrencium_diffoff['&diff'] = 0
    let w:lawrencium_diffoff['&wrap'] = &l:wrap
    let w:lawrencium_diffoff['&scrollopt'] = &l:scrollopt
    let w:lawrencium_diffoff['&scrollbind'] = &l:scrollbind
    let w:lawrencium_diffoff['&cursorbind'] = &l:cursorbind
    let w:lawrencium_diffoff['&foldmethod'] = &l:foldmethod
    let w:lawrencium_diffoff['&foldcolumn'] = &l:foldcolumn
    let w:lawrencium_diffoff['&foldenable'] = &l:foldenable
    let w:lawrencium_diff_id = a:diff_id
    diffthis
    autocmd BufWinLeave <buffer> call s:HgDiff_CleanUp()
endfunction

function! s:HgDiff_DiffOff(...) abort
    " Get the window name (given as a paramter, or current window).
    let l:nr = a:0 ? a:1 : winnr()

    " Run the commands we saved in `HgDiff_DiffThis`, or just run `diffoff`.
    let l:backup = getwinvar(l:nr, 'lawrencium_diffoff')
    if type(l:backup) == type({}) && len(l:backup) > 0
        call s:trace('Disabling diff mode on ' . l:nr)
        for key in keys(l:backup)
            call setwinvar(l:nr, key, l:backup[key])
        endfor
        call setwinvar(l:nr, 'lawrencium_diffoff', {})
    else
        call s:trace('Disabling diff mode on ' . l:nr . ' (but no true restore)')
        diffoff
    endif
endfunction

function! s:HgDiff_GetDiffWindows(diff_id) abort
    let l:result = []
    for nr in range(1, winnr('$'))
        if getwinvar(nr, '&diff') && getwinvar(nr, 'lawrencium_diff_id') == a:diff_id
            call add(l:result, nr)
        endif
    endfor
    return l:result
endfunction

function! s:HgDiff_CleanUp() abort
    " If we're not leaving one of our diff window, do nothing.
    if !&diff || !exists('w:lawrencium_diff_id')
        return
    endif

    " If there will be only one diff window left (plus the one we're leaving),
    " turn off diff in it and restore its local settings.
    let l:nrs = s:HgDiff_GetDiffWindows(w:lawrencium_diff_id)
    if len(l:nrs) <= 2
        call s:trace('Disabling diff mode in ' . len(l:nrs) . ' windows.')
        for nr in l:nrs
            if getwinvar(nr, '&diff')
                call s:HgDiff_DiffOff(nr)
            endif
        endfor
    else
        call s:trace('Still ' . len(l:nrs) . ' diff windows open.')
    endif
endfunction

call s:AddMainCommand("-nargs=* Hgdiff :call s:HgDiff('%:p', 0, <f-args>)")
call s:AddMainCommand("-nargs=* Hgvdiff :call s:HgDiff('%:p', 1, <f-args>)")
call s:AddMainCommand("-nargs=* Hgtabdiff :call s:HgDiff('%:p', 2, <f-args>)")

" }}}

" Hgdiffsum, Hgdiffsumsplit, Hgvdiffsumsplit, Hgtabdiffsum {{{

function! s:HgDiffSummary(filename, present_args, ...) abort
    " Default revisions to diff: the working directory (null string) 
    " and the parent of the working directory (using Mercurial's revsets syntax).
    " Otherwise, use the 1 or 2 revisions specified as extra parameters.
    let l:revs = ''
    if a:0 == 1
        if type(a:1) == type([])
            if len(a:1) >= 2
                let l:revs = a:1[0] . ',' . a:1[1]
            elseif len(a:1) == 1
                let l:revs = a:1[0]
            endif
        else
            let l:revs = a:1
        endif
    elseif a:0 >= 2
        let l:revs = a:1 . ',' . a:2
    endif

    " Get the current repo, and expand the given filename in case it contains
    " fancy filename modifiers.
    let l:repo = s:hg_repo()
    let l:path = expand(a:filename)
    call s:trace("Diff'ing revisions: '".l:revs."' on file: ".l:path)
    let l:special = l:repo.GetLawrenciumPath(l:path, 'diff', l:revs)

    " Build the correct edit command, and switch to the correct window, based
    " on the presentation arguments we got.
    if type(a:present_args) == type(0)
        " Just got a split mode.
        let l:valid_args = {'split_mode': a:present_args}
    else
        " Got complex args.
        let l:valid_args = a:present_args
    endif

    " First, see if we should reuse an existing window based on some buffer
    " variable.
    let l:target_winnr = -1
    let l:split = get(l:valid_args, 'split_mode', 0)
    let l:reuse_id = get(l:valid_args, 'reuse_id', '')
    let l:avoid_id = get(l:valid_args, 'avoid_win', -1)
    if l:reuse_id != ''
        let l:target_winnr = s:find_buffer_window(l:reuse_id, 1)
        if l:target_winnr > 0 && l:split != 3
            " Unless we'll be opening in a new tab, don't split anymore, since
            " we found the exact window we wanted.
            let l:split = 0
        endif
        call s:trace("Looking for window with '".l:reuse_id."', found: ".l:target_winnr)
    endif
    " If we didn't find anything, see if we should use the current or previous
    " window.
    if l:target_winnr <= 0
        let l:use_prev_win = get(l:valid_args, 'use_prev_win', 0)
        if l:use_prev_win
            let l:target_winnr = winnr('#')
            call s:trace("Will use previous window: ".l:target_winnr)
        endif
    endif
    " And let's see if we have a window we should actually avoid.
    if l:avoid_id >= 0 && 
                \(l:target_winnr == l:avoid_id ||
                \(l:target_winnr <= 0 && winnr() == l:avoid_id))
        for wnr in range(1, winnr('$'))
            if wnr != l:avoid_id
                call s:trace("Avoiding using window ".l:avoid_id.
                            \", now using: ".wnr)
                let l:target_winnr = wnr
                break
            endif
        endfor
    endif
    " Now let's see what kind of split we want to use, if any.
    let l:cmd = 'edit '
    if l:split == 1
        let l:cmd = 'rightbelow split '
    elseif l:split == 2
        let l:cmd = 'rightbelow vsplit '
    elseif l:split == 3
        let l:cmd = 'tabedit '
    endif
    
    " All good now, proceed.
    if l:target_winnr > 0
        execute l:target_winnr . "wincmd w"
    endif
    execute 'keepalt ' . l:cmd . fnameescape(l:special)

    " Set the reuse ID if we had one.
    if l:reuse_id != ''
        call s:trace("Setting reuse ID '".l:reuse_id."' on buffer: ".bufnr('%'))
        call setbufvar('%', l:reuse_id, 1)
    endif
endfunction

call s:AddMainCommand("-nargs=* Hgdiffsum       :call s:HgDiffSummary('%:p', 0, <f-args>)")
call s:AddMainCommand("-nargs=* Hgdiffsumsplit  :call s:HgDiffSummary('%:p', 1, <f-args>)")
call s:AddMainCommand("-nargs=* Hgvdiffsumsplit :call s:HgDiffSummary('%:p', 2, <f-args>)")
call s:AddMainCommand("-nargs=* Hgtabdiffsum    :call s:HgDiffSummary('%:p', 3, <f-args>)")

" }}}

" Hgcommit {{{

function! s:HgCommit(bang, vertical, callback, ...) abort
    " Get the repo we'll be committing into.
    let l:repo = s:hg_repo()

    " Get the list of files to commit.
    " It can either be several files passed as extra parameters, or an
    " actual list passed as the first extra parameter.
    let l:filenames = []
    if a:0
        let l:filenames = a:000
        if a:0 == 1 && type(a:1) == type([])
            let l:filenames = a:1
        endif
    endif

    " Open a commit message file.
    let l:commit_path = s:tempname('hg-editor-', '.txt')
    let l:split = a:vertical ? 'vsplit' : 'split'
    execute l:split . ' ' . l:commit_path
    call append(0, ['', ''])
    call append(2, split(s:HgCommit_GenerateMessage(l:repo, l:filenames), '\n'))
    call cursor(1, 1)

    " Setup the auto-command that will actually commit on write/exit,
    " and make the buffer delete itself on exit.
    let b:mercurial_dir = l:repo.root_dir
    let b:lawrencium_commit_files = l:filenames
    if type(a:callback) == type([])
        let b:lawrencium_commit_pre_callback = a:callback[0]
        let b:lawrencium_commit_post_callback = a:callback[1]
        let b:lawrencium_commit_abort_callback = a:callback[2]
    else
        let b:lawrencium_commit_pre_callback = 0
        let b:lawrencium_commit_post_callback = a:callback
        let b:lawrencium_commit_abort_callback = 0
    endif
    setlocal bufhidden=delete
    setlocal filetype=hgcommit
    if a:bang
        autocmd BufDelete <buffer> call s:HgCommit_Execute(expand('<afile>:p'), 0)
    else
        autocmd BufDelete <buffer> call s:HgCommit_Execute(expand('<afile>:p'), 1)
    endif
    " Make commands available.
    call s:DefineMainCommands()
endfunction

let s:hg_status_messages = { 
    \'M': 'modified',
    \'A': 'added',
    \'R': 'removed',
    \'C': 'clean',
    \'!': 'missing',
    \'?': 'not tracked',
    \'I': 'ignored',
    \' ': '',
    \}

function! s:HgCommit_GenerateMessage(repo, filenames) abort
    let l:msg  = "HG: Enter commit message. Lines beginning with 'HG:' are removed.\n"
    let l:msg .= "HG: Leave message empty to abort commit.\n"
    let l:msg .= "HG: Write and quit buffer to proceed.\n"
    let l:msg .= "HG: --\n"
    let l:msg .= "HG: user: " . split(a:repo.RunCommand('showconfig ui.username'), '\n')[0] . "\n"
    let l:msg .= "HG: branch '" . split(a:repo.RunCommand('branch'), '\n')[0] . "'\n"

    execute 'lcd ' . fnameescape(a:repo.root_dir)
    if len(a:filenames)
        let l:status_lines = split(a:repo.RunCommand('status', a:filenames), "\n")
    else
        let l:status_lines = split(a:repo.RunCommand('status'), "\n")
    endif
    for l:line in l:status_lines
        if l:line ==# ''
            continue
        endif
        let l:type = matchstr(l:line, '\v^[MARC\!\?I ]')
        let l:path = l:line[2:]
        let l:msg .= "HG: " . s:hg_status_messages[l:type] . ' ' . l:path . "\n"
    endfor

    return l:msg
endfunction

function! s:HgCommit_Execute(log_file, show_output) abort
    " Check if the user actually saved a commit message.
    if !filereadable(a:log_file)
        call s:error("abort: Commit message not saved")
        if exists('b:lawrencium_commit_abort_callback') &&
                    \type(b:lawrencium_commit_abort_callback) == type("") &&
                    \b:lawrencium_commit_abort_callback != ''
            call s:trace("Executing abort callback: ".b:lawrencium_commit_abort_callback)
            execute b:lawrencium_commit_abort_callback
        endif
        return
    endif

    " Execute a pre-callback if there is one.
    if exists('b:lawrencium_commit_pre_callback') &&
                \type(b:lawrencium_commit_pre_callback) == type("") &&
                \b:lawrencium_commit_pre_callback != ''
        call s:trace("Executing pre callback: ".b:lawrencium_commit_pre_callback)
        execute b:lawrencium_commit_pre_callback
    endif

    call s:trace("Committing with log file: " . a:log_file)

    " Clean all the 'HG: ' lines.
    let l:is_valid = s:clean_commit_file(a:log_file)
    if !l:is_valid
        call s:error("abort: Empty commit message")
        return
    endif

    " Get the repo and commit with the given message.
    let l:repo = s:hg_repo()
    let l:hg_args = ['-l', a:log_file]
    call extend(l:hg_args, b:lawrencium_commit_files)
    let l:output = l:repo.RunCommand('commit', l:hg_args)
    if a:show_output && l:output !~# '\v%^\s*%$'
        call s:trace("Output from hg commit:", 1)
        for l:output_line in split(l:output, '\n')
            echom l:output_line
        endfor
    endif

    " Execute a post-callback if there is one.
    if exists('b:lawrencium_commit_post_callback') &&
                \type(b:lawrencium_commit_post_callback) == type("") &&
                \b:lawrencium_commit_post_callback != ''
        call s:trace("Executing post callback: ".b:lawrencium_commit_post_callback)
        execute b:lawrencium_commit_post_callback
    endif
endfunction

call s:AddMainCommand("-bang -nargs=* -complete=customlist,s:ListRepoFiles Hgcommit :call s:HgCommit(<bang>0, 0, 0, <f-args>)")
call s:AddMainCommand("-bang -nargs=* -complete=customlist,s:ListRepoFiles Hgvcommit :call s:HgCommit(<bang>0, 1, 0, <f-args>)")

" }}}

" Hgrevert {{{

function! s:HgRevert(bang, ...) abort
    " Get the files to revert.
    let l:filenames = a:000
    if a:0 == 0
        let l:filenames = [ expand('%:p') ]
    endif
    if a:bang
        call insert(l:filenames, '--no-backup', 0)
    endif

    " Get the repo and run the command.
    let l:repo = s:hg_repo()
    call l:repo.RunCommand('revert', l:filenames)

    " Re-edit the file to see the change.
    edit
endfunction

call s:AddMainCommand("-bang -nargs=* -complete=customlist,s:ListRepoFiles Hgrevert :call s:HgRevert(<bang>0, <f-args>)")

" }}}

" Hgremove {{{

function! s:HgRemove(bang, ...) abort
    " Get the files to remove.
    let l:filenames = a:000
    if a:0 == 0
        let l:filenames = [ expand('%:p') ]
    endif
    if a:bang
        call insert(l:filenames, '--force', 0)
    endif

    " Get the repo and run the command.
    let l:repo = s:hg_repo()
    call l:repo.RunCommand('rm', l:filenames)

    " Re-edit the file to see the change.
    edit
endfunction

call s:AddMainCommand("-bang -nargs=* -complete=customlist,s:ListRepoFiles Hgremove :call s:HgRemove(<bang>0, <f-args>)")

" }}}

" Hglog, Hglogthis {{{

function! s:HgLog(vertical, ...) abort
    " Get the file or directory to get the log from.
    " (empty string is for the whole repository)
    let l:repo = s:hg_repo()
    if a:0 > 0 && matchstr(a:1, '\v-*') == ""
        let l:path = l:repo.GetRelativePath(expand(a:1))
    else
        let l:path = ''
    endif

    " Get the Lawrencium path for this `hg log`,
    " open it in a preview window and jump to it.
    if a:0 > 0 && l:path != ""
      let l:log_opts = join(a:000[1:-1], ',')
    else
      let l:log_opts = join(a:000, ',')
    endif

    let l:log_path = l:repo.GetLawrenciumPath(l:path, 'log', l:log_opts)
    if a:vertical
        execute 'vertical pedit ' . fnameescape(l:log_path)
    else
        execute 'pedit ' . fnameescape(l:log_path)
    endif
    wincmd P

    " Add some other nice commands and mappings.
    let l:is_file = (l:path != '' && filereadable(l:repo.GetFullPath(l:path)))
    command! -buffer -nargs=* Hglogdiffsum    :call s:HgLog_DiffSummary(1, <f-args>)
    command! -buffer -nargs=* Hglogvdiffsum   :call s:HgLog_DiffSummary(2, <f-args>)
    command! -buffer -nargs=* Hglogtabdiffsum :call s:HgLog_DiffSummary(3, <f-args>)
    command! -buffer -nargs=+ -complete=file Hglogexport :call s:HgLog_ExportPatch(<f-args>)
    if l:is_file
        command! -buffer Hglogrevedit          :call s:HgLog_FileRevEdit()
        command! -buffer -nargs=* Hglogdiff    :call s:HgLog_Diff(0, <f-args>)
        command! -buffer -nargs=* Hglogvdiff   :call s:HgLog_Diff(1, <f-args>)
        command! -buffer -nargs=* Hglogtabdiff :call s:HgLog_Diff(2, <f-args>)
    endif

    if g:lawrencium_define_mappings
        nnoremap <buffer> <silent> <C-U> :Hglogdiffsum<cr>
        nnoremap <buffer> <silent> <C-H> :Hglogvdiffsum<cr>
        nnoremap <buffer> <silent> <cr>  :Hglogvdiffsum<cr>
        nnoremap <buffer> <silent> q     :bdelete!<cr>
        if l:is_file
            nnoremap <buffer> <silent> <C-E>  :Hglogrevedit<cr>
            nnoremap <buffer> <silent> <C-D>  :Hglogtabdiff<cr>
            nnoremap <buffer> <silent> <C-V>  :Hglogvdiff<cr>
        endif
    endif

    " Clean up when the log buffer is deleted.
    let l:bufobj = s:buffer_obj()
    call l:bufobj.OnDelete('call s:HgLog_Delete(' . l:bufobj.nr . ')')
endfunction

function! s:HgLog_Delete(bufnr)
    if g:lawrencium_auto_close_buffers
        call s:delete_dependency_buffers('lawrencium_diff_for', a:bufnr)
        call s:delete_dependency_buffers('lawrencium_rev_for', a:bufnr)
    endif
endfunction

function! s:HgLog_FileRevEdit()
    let l:repo = s:hg_repo()
    let l:bufobj = s:buffer_obj()
    let l:rev = s:HgLog_GetSelectedRev()
    let l:log_path = s:parse_lawrencium_path(l:bufobj.GetName())
    let l:path = l:repo.GetLawrenciumPath(l:log_path['path'], 'rev', l:rev)

    " Go to the window we were in before going in the log window,
    " and open the revision there.
    wincmd p
    call s:edit_deletable_buffer('lawrencium_rev_for', l:bufobj.nr, l:path)
endfunction

function! s:HgLog_Diff(split, ...) abort
    let l:revs = []
    if a:0 >= 2
        let l:revs = [a:1, a:2]
    elseif a:0 == 1
        let l:revs = ['p1('.a:1.')', a:1]
    else
        let l:sel = s:HgLog_GetSelectedRev()
        let l:revs = ['p1('.l:sel.')', l:sel]
    endif

    let l:repo = s:hg_repo()
    let l:bufobj = s:buffer_obj()
    let l:log_path = s:parse_lawrencium_path(l:bufobj.GetName())
    let l:path = l:repo.GetFullPath(l:log_path['path'])

    " Go to the window we were in before going to the log window,
    " and open the split diff there.
    if a:split < 2
        wincmd p
    endif
    call s:HgDiff(l:path, a:split, l:revs)
endfunction

function! s:HgLog_DiffSummary(split, ...) abort
    let l:revs = []
    if a:0 >= 2
        let l:revs = [a:1, a:2]
    elseif a:0 == 1
        let l:revs = [a:1]
    else
        let l:revs = [s:HgLog_GetSelectedRev()]
    endif

    let l:repo = s:hg_repo()
    let l:bufobj = s:buffer_obj()
    let l:log_path = s:parse_lawrencium_path(l:bufobj.GetName())
    let l:path = l:repo.GetFullPath(l:log_path['path'])

    " Go to the window we were in before going in the log window,
    " and split for the diff summary from there.
    let l:reuse_id = 'lawrencium_diffsum_for_' . bufnr('%')
    let l:split_prev_win = (a:split < 3)
    let l:args = {'reuse_id': l:reuse_id, 'use_prev_win': l:split_prev_win,
                \'split_mode': a:split}
    call s:HgDiffSummary(l:path, l:args, l:revs)
endfunction

function! s:HgLog_GetSelectedRev(...) abort
    if a:0 == 1
        let l:line = getline(a:1)
    else
        let l:line = getline('.')
    endif
    " Behold, Vim's look-ahead regex syntax again! WTF.
    let l:rev = matchstr(l:line, '\v^(\d+)(\:)@=')
    if l:rev == ''
        call s:throw("Can't parse revision number from line: " . l:line)
    endif
    return l:rev
endfunction

function! s:HgLog_ExportPatch(...) abort
    let l:patch_name = a:1
    if !empty($HG_EXPORT_PATCH_DIR)
        " Use the patch dir only if user has specified a relative path
        if has('win32')
            let l:is_patch_relative = (matchstr(l:patch_name, '\v^([a-zA-Z]:)?\\') == "")
        else
            let l:is_patch_relative = (matchstr(l:patch_name, '\v^/') == "")
        endif
        if l:is_patch_relative
            let l:patch_name = s:normalizepath(
                s:stripslash($HG_EXPORT_PATCH_DIR) . "/" . l:patch_name)
        endif
    endif

    if a:0 == 2
        let l:rev = a:2
    else
        let l:rev = s:HgLog_GetSelectedRev()
    endif

    let l:repo = s:hg_repo()
    let l:export_args = ['-o', l:patch_name, '-r', l:rev]

    call l:repo.RunCommand('export', l:export_args)

    echom "Created patch: " . l:patch_name
endfunction

call s:AddMainCommand("Hglogthis  :call s:HgLog(0, '%:p')")
call s:AddMainCommand("Hgvlogthis :call s:HgLog(1, '%:p')")
call s:AddMainCommand("-nargs=* -complete=customlist,s:ListRepoFiles Hglog  :call s:HgLog(0, <f-args>)")
call s:AddMainCommand("-nargs=* -complete=customlist,s:ListRepoFiles Hgvlog  :call s:HgLog(1, <f-args>)")

" }}}

" Hgannotate, Hgwannotate {{{

function! s:HgAnnotate(bang, verbose, ...) abort
    " Open the file to annotate if needed.
    if a:0 > 0
        call s:HgEdit(a:bang, a:1)
    endif

    " Get the Lawrencium path for the annotated file.
    let l:path = expand('%:p')
    let l:bufnr = bufnr('%')
    let l:repo = s:hg_repo()
    let l:value = a:verbose ? 'v=1' : ''
    let l:annotation_path = l:repo.GetLawrenciumPath(l:path, 'annotate', l:value)
    
    " Check if we're trying to annotate something with local changes.
    let l:has_local_edits = 0
    let l:path_status = l:repo.RunCommand('status', l:path)
    if l:path_status != ''
        call s:trace("Found local edits for '" . l:path . "'. Will annotate parent revision.")
        let l:has_local_edits = 1
    endif
    
    if l:has_local_edits
        " Just open the output of the command.
        echom "Local edits found, will show the annotations for the parent revision."
        execute 'edit ' . fnameescape(l:annotation_path)
        setlocal nowrap nofoldenable
        setlocal filetype=hgannotate
    else
        " Store some info about the current buffer.
        let l:cur_topline = line('w0') + &scrolloff
        let l:cur_line = line('.')
        let l:cur_wrap = &wrap
        let l:cur_foldenable = &foldenable

        " Open the annotated file in a split buffer on the left, after
        " having disabled wrapping and folds on the current file.
        " Make both windows scroll-bound.
        setlocal scrollbind nowrap nofoldenable
        execute 'keepalt leftabove vsplit ' . fnameescape(l:annotation_path)
        setlocal nonumber
        setlocal scrollbind nowrap nofoldenable foldcolumn=0
        setlocal filetype=hgannotate

        " When the annotated buffer is deleted, restore the settings we
        " changed on the current buffer, and go back to that buffer.
        let l:annotate_buffer = s:buffer_obj()
        call l:annotate_buffer.OnDelete('execute bufwinnr(' . l:bufnr . ') . "wincmd w"')
        call l:annotate_buffer.OnDelete('setlocal noscrollbind')
        if l:cur_wrap
            call l:annotate_buffer.OnDelete('setlocal wrap')
        endif
        if l:cur_foldenable
            call l:annotate_buffer.OnDelete('setlocal foldenable')
        endif

        " Go to the line we were at in the source buffer when we
        " opened the annotation window.
        execute l:cur_topline
        normal! zt
        execute l:cur_line
        syncbind

        " Set the correct window width for the annotations.
        if a:verbose
            let l:last_token = match(getline('.'), '\v\d{4}:\s')
            let l:token_end = 5
        else
            let l:last_token = match(getline('.'), '\v\d{2}:\s')
            let l:token_end = 3
        endif
        if l:last_token < 0
            echoerr "Can't find the end of the annotation columns."
        else
            let l:column_count = l:last_token + l:token_end + g:lawrencium_annotate_width_offset
            execute "vertical resize " . l:column_count
            setlocal winfixwidth
        endif
    endif

    " Make the annotate buffer a Lawrencium buffer.
    let b:mercurial_dir = l:repo.root_dir
    let b:lawrencium_annotated_path = l:path
    let b:lawrencium_annotated_bufnr = l:bufnr
    call s:DefineMainCommands()

    " Add some other nice commands and mappings.
    command! -buffer Hgannotatediffsum :call s:HgAnnotate_DiffSummary()
    command! -buffer Hgannotatelog     :call s:HgAnnotate_DiffSummary(1)
    if g:lawrencium_define_mappings
        nnoremap <buffer> <silent> <cr> :Hgannotatediffsum<cr>
        nnoremap <buffer> <silent> <leader><cr> :Hgannotatelog<cr>
    endif

    " Clean up when the annotate buffer is deleted.
    let l:bufobj = s:buffer_obj()
    call l:bufobj.OnDelete('call s:HgAnnotate_Delete(' . l:bufobj.nr . ')')
endfunction

function! s:HgAnnotate_Delete(bufnr) abort
    if g:lawrencium_auto_close_buffers
        call s:delete_dependency_buffers('lawrencium_diff_for', a:bufnr)
    endif
endfunction

function! s:HgAnnotate_DiffSummary(...) abort
    " Get the path for the diff of the revision specified under the cursor.
    let l:line = getline('.')
    let l:rev_hash = matchstr(l:line, '\v[a-f0-9]{12}')
    let l:log = (a:0 > 0 ? a:1 : 0)

    " Get the Lawrencium path for the diff, and the buffer object for the
    " annotation.
    let l:repo = s:hg_repo()
    if l:log
      let l:path = l:repo.GetLawrenciumPath(b:lawrencium_annotated_path, 'logpatch', l:rev_hash)
    else
      let l:path = l:repo.GetLawrenciumPath(b:lawrencium_annotated_path, 'diff', l:rev_hash)
    endif
    let l:annotate_buffer = s:buffer_obj()

    " Find a window already displaying diffs for this annotation.
    let l:diff_winnr = s:find_buffer_window('lawrencium_diff_for', l:annotate_buffer.nr)
    if l:diff_winnr == -1
        " Not found... go back to the main source buffer and open a bottom 
        " split with the diff for the specified revision.
        execute bufwinnr(b:lawrencium_annotated_bufnr) . 'wincmd w'
        execute 'rightbelow split ' . fnameescape(l:path)
        let b:lawrencium_diff_for = l:annotate_buffer.nr
        let b:lawrencium_quit_on_delete = 1
    else
        " Found! Use that window to open the diff.
        execute l:diff_winnr . 'wincmd w'
        execute 'edit ' . fnameescape(l:path)
        let b:lawrencium_diff_for = l:annotate_buffer.nr
    endif
endfunction

call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoFiles Hgannotate :call s:HgAnnotate(<bang>0, 0, <f-args>)")
call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoFiles Hgwannotate :call s:HgAnnotate(<bang>0, 1, <f-args>)")

" }}}

" Hgqseries {{{

function! s:HgQSeries() abort
    " Open the MQ series in the preview window and jump to it.
    let l:repo = s:hg_repo()
    let l:path = l:repo.GetLawrenciumPath('', 'qseries', '')
    execute 'pedit ' . fnameescape(l:path)
    wincmd P

    " Make the series buffer a Lawrencium buffer.
    let b:mercurial_dir = l:repo.root_dir
    call s:DefineMainCommands()

    " Add some commands and mappings.
    command! -buffer Hgqseriesgoto                  :call s:HgQSeries_Goto()
    command! -buffer Hgqserieseditmessage           :call s:HgQSeries_EditMessage()
    command! -buffer -nargs=+ Hgqseriesrename       :call s:HgQSeries_Rename(<f-args>)
    if g:lawrencium_define_mappings
        nnoremap <buffer> <silent> <C-g> :Hgqseriesgoto<cr>
        nnoremap <buffer> <silent> <C-e> :Hgqserieseditmessage<cr>
        nnoremap <buffer> <silent> q     :bdelete!<cr>
    endif
endfunction

function! s:HgQSeries_GetCurrentPatchName() abort
    let l:pos = getpos('.')
    return getbufvar('%', 'lawrencium_patchname_' . l:pos[1])
endfunction

function! s:HgQSeries_Goto() abort
    let l:repo = s:hg_repo()
    let l:patchname = s:HgQSeries_GetCurrentPatchName()
    if len(l:patchname) == 0
        call s:error("No patch to go to here.")
        return
    endif
    call l:repo.RunCommand('qgoto', l:patchname)
    edit
endfunction

function! s:HgQSeries_Rename(...) abort
    let l:repo = s:hg_repo()
    let l:current_name = s:HgQSeries_GetCurrentPatchName()
    if len(l:current_name) == 0
        call s:error("No patch to rename here.")
        return
    endif
    let l:new_name = '"' . join(a:000, ' ') . '"'
    call l:repo.RunCommand('qrename', l:current_name, l:new_name)
    edit
endfunction

function! s:HgQSeries_EditMessage() abort
    let l:repo = s:hg_repo()
    let l:patchname = getbufvar('%', 'lawrencium_patchname_top')
    if len(l:patchname) == 0
        call s:error("No patch to edit here.")
        return
    endif
    let l:current = split(l:repo.RunCommand('qheader', l:patchname), '\n')

    " Open a temp file to write the commit message.
    let l:temp_file = s:tempname('hg-qrefedit-', '.txt')
    split
    execute 'edit ' . fnameescape(l:temp_file)
    call append(0, 'HG: Enter the new commit message for patch "' . l:patchname . '" here.\n')
    call append(0, '')
    call append(0, l:current)
    call cursor(1, 1)

    " Make it a temp buffer that will actually change the commit message
    " when it is saved and closed.
    let b:mercurial_dir = l:repo.root_dir
    let b:lawrencium_patchname = l:patchname
    setlocal bufhidden=delete
    setlocal filetype=hgcommit
    autocmd BufDelete <buffer> call s:HgQSeries_EditMessage_Execute(expand('<afile>:p'))

    call s:DefineMainCommands()
endfunction

function! s:HgQSeries_EditMessage_Execute(log_file) abort
    if !filereadable(a:log_file)
        call s:error("abort: Commit message not saved")
        return
    endif

    " Clean all the 'HG:' lines.
    let l:is_valid = s:clean_commit_file(a:log_file)
    if !l:is_valid
        call s:error("abort: Empty commit message")
        return
    endif

    " Get the repo and edit the given patch.
    let l:repo = s:hg_repo()
    let l:hg_args = ['-s', '-l', a:log_file]
    call l:repo.RunCommand('qref', l:hg_args)
endfunction

call s:AddMainCommand("Hgqseries call s:HgQSeries()")

" }}}

" Hgrecord {{{

function! s:HgRecord(split) abort
    let l:repo = s:hg_repo()
    let l:orig_buf = s:buffer_obj()
    let l:tmp_path = l:orig_buf.GetName(':p') . '~record'
    let l:diff_id = localtime()

    " Start diffing on the current file, enable some commands.
    call l:orig_buf.DefineCommand('Hgrecordabort', ':call s:HgRecord_Abort()')
    call l:orig_buf.DefineCommand('Hgrecordcommit', ':call s:HgRecord_Execute()')
    call s:HgDiff_DiffThis(l:diff_id)
    setlocal foldmethod=marker

    " Split the window and open the parent revision in the right or bottom
    " window. Keep the current buffer in the left or top window... we're going
    " to 'move' those changes into the parent revision.
    let l:cmd = 'keepalt rightbelow split '
    if a:split == 1
        let l:cmd = 'keepalt rightbelow vsplit '
    endif
    let l:rev_path = l:repo.GetLawrenciumPath(expand('%:p'), 'rev', '')
    execute l:cmd . fnameescape(l:rev_path)

    " This new buffer with the parent revision is set as a Lawrencium buffer.
    " Let's save it to an actual file and reopen it like that (somehow we
    " could probably do it with `:saveas` instead but we'd need to reset a
    " bunch of other buffer settings, and Vim weirdly creates another backup
    " buffer when you do that).
    execute 'keepalt write! ' . fnameescape(l:tmp_path)
    execute 'keepalt edit! ' . fnameescape(l:tmp_path)
    setlocal bufhidden=delete
    let b:mercurial_dir = l:repo.root_dir
    let b:lawrencium_record_for = l:orig_buf.GetName(':p')
    let b:lawrencium_record_other_nr = l:orig_buf.nr
    let b:lawrencium_record_commit_split = !a:split
    call setbufvar(l:orig_buf.nr, 'lawrencium_record_for', '%')
    call setbufvar(l:orig_buf.nr, 'lawrencium_record_other_nr', bufnr('%'))

    " Hookup the commit and abort commands.
    let l:rec_buf = s:buffer_obj()
    call l:rec_buf.OnDelete('call s:HgRecord_Execute()')
    call l:rec_buf.DefineCommand('Hgrecordcommit', ':quit')
    call l:rec_buf.DefineCommand('Hgrecordabort', ':call s:HgRecord_Abort()')
    call s:DefineMainCommands()

    " Make it the other part of the diff.
    call s:HgDiff_DiffThis(l:diff_id)
    setlocal foldmethod=marker
    call l:rec_buf.SetVar('&filetype', l:orig_buf.GetVar('&filetype'))

    if g:lawrencium_record_start_in_working_buffer
        wincmd p
    endif
endfunction

function! s:HgRecord_Execute() abort
    if exists('b:lawrencium_record_abort')
        " Abort flag is set, let's just cleanup.
        let l:buf_nr = b:lawrencium_record_for == '%' ? bufnr('%') :
                    \b:lawrencium_record_other_nr
        call s:HgRecord_CleanUp(l:buf_nr)
        call s:error("abort: User requested aborting the record operation.")
        return
    endif

    if !exists('b:lawrencium_record_for')
        call s:throw("This doesn't seem like a record buffer, something's wrong!")
    endif
    if b:lawrencium_record_for == '%'
        " Switch to the 'recording' buffer's window.
        let l:buf_obj = s:buffer_obj(b:lawrencium_record_other_nr)
        call l:buf_obj.MoveToFirstWindow()
    endif

    " Setup the commit operation.
    let l:split = b:lawrencium_record_commit_split
    let l:working_bufnr = b:lawrencium_record_other_nr
    let l:working_path = fnameescape(b:lawrencium_record_for)
    let l:record_path = fnameescape(expand('%:p'))
    let l:callbacks = [
                \'call s:HgRecord_PostExecutePre('.l:working_bufnr.', "'.
                    \escape(l:working_path, '\').'", "'.
                    \escape(l:record_path, '\').'")',
                \'call s:HgRecord_PostExecutePost('.l:working_bufnr.', "'.
                    \escape(l:working_path, '\').'")',
                \'call s:HgRecord_PostExecuteAbort('.l:working_bufnr.', "'.
                    \escape(l:record_path, '\').'")'
                \]
    call s:trace("Starting commit flow with callbacks: ".string(l:callbacks))
    call s:HgCommit(0, l:split, l:callbacks, b:lawrencium_record_for)
endfunction

function! s:HgRecord_PostExecutePre(working_bufnr, working_path, record_path) abort
    " Just before committing, we switch the original file with the record
    " file... we'll restore things in the post-callback below.
    " We also switch on 'autoread' temporarily on the working buffer so that
    " we don't have an annoying popup in gVim.
    if has('dialog_gui')
        call setbufvar(a:working_bufnr, '&autoread', 1)
    endif
    call s:trace("Backuping original file: ".a:working_path)
    silent call rename(a:working_path, a:working_path.'~working')
    call s:trace("Committing recorded changes using: ".a:record_path)
    silent call rename(a:record_path, a:working_path)
    sleep 200m
endfunction

function! s:HgRecord_PostExecutePost(working_bufnr, working_path) abort
    " Recover the back-up file from underneath the buffer.
    call s:trace("Recovering original file: ".a:working_path)
    silent call rename(a:working_path.'~working', a:working_path)

    " Clean up!
    call s:HgRecord_CleanUp(a:working_bufnr)

    " Restore default 'autoread'.
    if has('dialog_gui')
        set autoread<
    endif
endfunction

function! s:HgRecord_PostExecuteAbort(working_bufnr, record_path) abort
    call s:HgRecord_CleanUp(a:working_bufnr)
    call s:trace("Delete discarded record file: ".a:record_path)
    silent call delete(a:record_path)
endfunction

function! s:HgRecord_Abort() abort
    if b:lawrencium_record_for == '%'
        " We're in the working directory buffer. Switch to the 'recording'
        " buffer and quit.
        let l:buf_obj = s:buffer_obj(b:lawrencium_record_other_nr)
        call l:buf_obj.MoveToFirstWindow()
    endif
    " We're now in the 'recording' buffer... set the abort flag and quit,
    " which will run the execution (it will early out and clean things up).
    let b:lawrencium_record_abort = 1
    quit!
endfunction

function! s:HgRecord_CleanUp(buf_nr) abort
    " Get in the original buffer and clean the local commands/variables.
    let l:buf_obj = s:buffer_obj(a:buf_nr)
    call l:buf_obj.MoveToFirstWindow()
    if !exists('b:lawrencium_record_for') || b:lawrencium_record_for != '%'
        call s:throw("Cleaning up something else than the original buffer ".
                \"for a record operation. That's suspiciously incorrect! ".
                \"Aborting.")
    endif
    call l:buf_obj.DeleteCommand('Hgrecordabort')
    call l:buf_obj.DeleteCommand('Hgrecordcommit')
    unlet b:lawrencium_record_for
    unlet b:lawrencium_record_other_nr
endfunction

call s:AddMainCommand("Hgrecord call s:HgRecord(0)")
call s:AddMainCommand("Hgvrecord call s:HgRecord(1)")

" }}}

" Autoload Functions {{{

" Prints a summary of the current repo (if any) that's appropriate for
" displaying on the status line.
function! lawrencium#statusline(...)
    if !exists('b:mercurial_dir')
        return ''
    endif
    let l:repo = s:hg_repo()
    let l:prefix = (a:0 > 0 ? a:1 : '')
    let l:suffix = (a:0 > 1 ? a:2 : '')
    let l:branch = 'default'
    let l:branch_file = l:repo.GetFullPath('.hg/branch')
    if filereadable(l:branch_file)
        let l:branch = readfile(l:branch_file)[0]
    endif
    let l:bookmarks = ''
    let l:bookmarks_file = l:repo.GetFullPath('.hg/bookmarks.current')
    if filereadable(l:bookmarks_file)
        let l:bookmarks = join(readfile(l:bookmarks_file), ', ')
    endif
    let l:line = l:prefix . l:branch
    if strlen(l:bookmarks) > 0
        let l:line = l:line . ' - ' . l:bookmarks
    endif
    let l:line = l:line . l:suffix
    return l:line
endfunction

" Rescans the current buffer for setting up Mercurial commands.
" Passing '1' as the parameter enables debug traces temporarily.
function! lawrencium#rescan(...)
    if exists('b:mercurial_dir')
        unlet b:mercurial_dir
    endif
    if a:0 && a:1
        let l:trace_backup = g:lawrencium_trace
        let g:lawrencium_trace = 1
    endif
    call s:setup_buffer_commands()
    if a:0 && a:1
        let g:lawrencium_trace = l:trace_backup
    endif
endfunction

" Enables/disables the debug trace.
function! lawrencium#debugtrace(...)
    let g:lawrencium_trace = (a:0 == 0 || (a:0 && a:1))
    echom "Lawrencium debug trace is now " . (g:lawrencium_trace ? "enabled." : "disabled.")
endfunction

" }}}