diff autoload/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
children 4d5f4233b04e
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/autoload/lawrencium.vim	Mon Jun 13 09:32:34 2016 -0700
@@ -0,0 +1,810 @@
+
+" Path Utility {{{
+
+" Strips the ending slash in a path.
+function! lawrencium#stripslash(path)
+    return fnamemodify(a:path, ':s?[/\\]$??')
+endfunction
+
+" Returns whether a path is absolute.
+function! lawrencium#isabspath(path)
+    return a:path =~# '\v^(\w\:)?[/\\]'
+endfunction
+
+" Normalizes the slashes in a path.
+function! lawrencium#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! lawrencium#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! lawrencium#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! lawrencium#clean_tempfile(path)
+    if filewritable(a:path)
+        call lawrencium#trace("Cleaning up temporary file: " . a:path)
+        call delete(a:path)
+    endif
+endfunction
+
+" }}}
+
+" Logging {{{
+
+" Prints a message if debug tracing is enabled.
+function! lawrencium#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! lawrencium#error(message)
+    echom "lawrencium error: " . a:message
+endfunction
+
+" Throw a Lawrencium exception message.
+function! lawrencium#throw(message)
+    let v:errmsg = "lawrencium: " . a:message
+    throw v:errmsg
+endfunction
+
+" }}}
+
+" Repositories {{{
+
+" Finds the repository root given a path inside that repository.
+" Throw an error if not repository is found.
+function! lawrencium#find_repo_root(path)
+    let l:path = lawrencium#stripslash(a:path)
+    let l:previous_path = ""
+    while l:path != l:previous_path
+        if isdirectory(l:path . '/.hg')
+            return lawrencium#normalizepath(simplify(fnamemodify(l:path, ':p')))
+        endif
+        let l:previous_path = l:path
+        let l:path = fnamemodify(l:path, ':h')
+    endwhile
+    call lawrencium#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! lawrencium#parse_lawrencium_path(lawrencium_path, ...)
+    let l:repo_path = lawrencium#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
+
+" 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! lawrencium#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
+
+" }}}
+
+" Vim Utility {{{
+
+" Finds a window whose displayed buffer has a given variable
+" set to the given value.
+function! lawrencium#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! lawrencium#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! lawrencium#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 lawrencium#trace("Deleting dependency buffer " . bnr)
+                    execute "bdelete! " . bnr
+                else
+                    call lawrencium#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 lawrencium#trace("Deleting dependency buffer " . bnr . " after switching to " . l:prev_bnr . " in window " . l:bwnr)
+                        execute "bdelete! " . bnr
+                    else
+                        call lawrencium#trace("Dependency buffer " . bnr . " is unladed after switching to " . l:prev_bnr)
+                    endif
+                else
+                    call lawrencium#trace("Deleting dependency buffer " . bnr . " and window.")
+                    bdelete!
+                endif
+            endif
+        endif
+    endfor
+    if l:cur_winnr != winnr()
+        call lawrencium#trace("Returning to window " . l:cur_winnr)
+        execute l:cur_winnr . "wincmd w"
+    endif
+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 = lawrencium#find_repo_root(a:path)
+    call lawrencium#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 lawrencium#isabspath(a:path)
+        call lawrencium#throw("Expected relative path, got absolute path: " . a:path)
+    endif
+    return lawrencium#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 = lawrencium#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(lawrencium#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 lawrencium#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 lawrencium#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://' . lawrencium#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! lawrencium#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 = lawrencium#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
+
+" }}}
+
+" 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 lawrencium#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 lawrencium#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 lawrencium#throw("Command '".a:name."' is already defined in buffer ".self.nr)
+    endif
+    if bufnr('%') != self.nr
+        call lawrencium#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 lawrencium#throw("Command '".a:name."' has not been defined in buffer ".self.nr)
+    endif
+    if bufnr('%') != self.nr
+        call lawrencium#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 lawrencium#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 lawrencium#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 lawrencium#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 lawrencium#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 lawrencium#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 lawrencium#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 lawrencium#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! lawrencium#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 lawrencium#trace("Calling BufDelete callbacks on buffer " . l:bufobj.nr)
+    for cmd in l:bufobj.on_delete
+        call lawrencium#trace(" [" . cmd . "]")
+        execute cmd
+    endfor
+    call lawrencium#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 lawrencium#trace("Calling BufWinLeave callbacks on buffer " . l:bufobj.nr)
+    for cmd in l:bufobj.on_winleave
+        call lawrencium#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 lawrencium#trace("Calling BufUnload callbacks on buffer " . l:bufobj.nr)
+    for cmd in l:bufobj.on_unload
+        call lawrencium#trace(" [" . cmd . "]")
+        execute cmd
+    endfor
+    execute 'augroup lawrencium_buffer_' . l:bufobj.nr . '_unload'
+    execute '  autocmd!'
+    execute 'augroup end'
+endfunction
+
+" }}}
+
+" 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! lawrencium#add_command(command) abort
+    let s:main_commands += [a:command]
+endfunction
+
+function! lawrencium#define_commands()
+    for l:command in s:main_commands
+        execute 'command! -buffer ' . l:command
+    endfor
+endfunction
+
+augroup lawrencium_main
+    autocmd!
+    autocmd User Lawrencium call lawrencium#define_commands()
+augroup end
+
+" 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! lawrencium#setup_buffer_commands() abort
+    call lawrencium#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 = lawrencium#hg_repo()
+    catch /^lawrencium\:/
+        return
+    endtry
+    let b:mercurial_dir = l:repo.root_dir
+    if exists('b:mercurial_dir') && l:do_setup
+        call lawrencium#trace("Setting Mercurial commands for buffer '" . bufname('%'))
+        call lawrencium#trace("  with repo : " . expand(b:mercurial_dir))
+        silent doautocmd User Lawrencium
+    endif
+endfunction
+
+" }}}
+
+" Commands Auto-Complete {{{
+
+" Auto-complete function for commands that take repo-relative file paths.
+function! lawrencium#list_repo_files(ArgLead, CmdLine, CursorPos) abort
+    let l:matches = lawrencium#hg_repo().Glob(a:ArgLead . '*', 1)
+    call map(l:matches, 'lawrencium#normalizepath(v:val)')
+    return l:matches
+endfunction
+
+" Auto-complete function for commands that take repo-relative directory paths.
+function! lawrencium#list_repo_dirs(ArgLead, CmdLine, CursorPos) abort
+    let l:matches = lawrencium#hg_repo().Glob(a:ArgLead . '*/')
+    call map(l:matches, 'lawrencium#normalizepath(v:val)')
+    return l:matches
+endfunction
+
+" }}}
+
+" Lawrencium Files {{{
+
+" Generic read
+let s:lawrencium_file_readers = {}
+let s:lawrencium_file_customoptions = {}
+
+function! lawrencium#add_reader(action, callback, ...) abort
+    if has_key(s:lawrencium_file_readers, a:action)
+        call lawrencium#throw("Lawrencium file '".a:action."' has alredy been registered.")
+    endif
+    let s:lawrencium_file_readers[a:action] = function(a:callback)
+    if a:0 && a:1
+        let s:lawrencium_file_customoptions[a:action] = 1
+    endif
+endfunction
+
+function! lawrencium#read_lawrencium_file(path) abort
+    call lawrencium#trace("Reading Lawrencium file: " . a:path)
+    let l:path_parts = lawrencium#parse_lawrencium_path(a:path)
+    if l:path_parts['root'] == ''
+        call lawrencium#throw("Can't get repository root from: " . a:path)
+    endif
+    if !has_key(s:lawrencium_file_readers, l:path_parts['action'])
+        call lawrencium#throw("No registered reader for action: " . l:path_parts['action'])
+    endif
+
+    " Call the registered reader.
+    let l:repo = lawrencium#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 lawrencium#define_commands()
+
+    return ''
+endfunction
+
+function! lawrencium#write_lawrencium_file(path) abort
+    call lawrencium#trace("Writing Lawrencium file: " . a:path)
+endfunction
+
+" }}}
+
+" Statusline {{{
+
+" 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 = lawrencium#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
+
+" }}}
+
+" Miscellaneous User Functions {{{
+
+" 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 lawrencium#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
+
+" }}}
+
+" Setup {{{
+
+function! lawrencium#init() abort
+    let s:builtin_exts = [
+                \'lawrencium#addremove',
+                \'lawrencium#annotate',
+                \'lawrencium#cat',
+                \'lawrencium#commit',
+                \'lawrencium#diff',
+                \'lawrencium#hg',
+                \'lawrencium#log',
+                \'lawrencium#mq',
+                \'lawrencium#record',
+                \'lawrencium#revert',
+                \'lawrencium#status',
+                \'lawrencium#vimutils'
+                \]
+    let s:user_exts = copy(g:lawrencium_extensions)
+    let s:exts = s:builtin_exts + s:user_exts
+    for ext in s:exts
+        call lawrencium#trace("Initializing Lawrencium extension " . ext)
+        execute ('call ' . ext . '#init()')
+    endfor
+endfunction
+
+" }}}
+