A => autoload/lawrencium.vim +810 -0
@@ 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
+
+" }}}
+
A => autoload/lawrencium/addremove.vim +23 -0
@@ 0,0 1,23 @@
+
+function! lawrencium#addremove#init() abort
+ call lawrencium#add_command("-bang -nargs=* -complete=customlist,lawrencium#list_repo_files Hgremove :call lawrencium#addremove#HgRemove(<bang>0, <f-args>)")
+endfunction
+
+function! lawrencium#addremove#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 = lawrencium#hg_repo()
+ call l:repo.RunCommand('rm', l:filenames)
+
+ " Re-edit the file to see the change.
+ edit
+endfunction
+
A => autoload/lawrencium/annotate.vim +154 -0
@@ 0,0 1,154 @@
+
+function! lawrencium#annotate#init() abort
+ call lawrencium#add_command("-bang -nargs=? -complete=customlist,lawrencium#list_repo_files Hgannotate :call lawrencium#annotate#HgAnnotate(<bang>0, 0, <f-args>)")
+ call lawrencium#add_command("-bang -nargs=? -complete=customlist,lawrencium#list_repo_files Hgwannotate :call lawrencium#annotate#HgAnnotate(<bang>0, 1, <f-args>)")
+
+ call lawrencium#add_reader('annotate', 'lawrencium#annotate#read')
+endfunction
+
+function! lawrencium#annotate#read(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
+
+function! lawrencium#annotate#HgAnnotate(bang, verbose, ...) abort
+ " Open the file to annotate if needed.
+ if a:0 > 0
+ call lawrencium#vimutils#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 = lawrencium#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 lawrencium#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 = lawrencium#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 = lawrencium#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 = lawrencium#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 = lawrencium#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
+
A => autoload/lawrencium/cat.vim +14 -0
@@ 0,0 1,14 @@
+
+function! lawrencium#cat#init() abort
+ call lawrencium#add_reader('rev', 'lawrencium#cat#read')
+endfunction
+
+function! lawrencium#cat#read(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
+
A => autoload/lawrencium/commit.vim +141 -0
@@ 0,0 1,141 @@
+
+function! lawrencium#commit#init() abort
+ call lawrencium#add_command("-bang -nargs=* -complete=customlist,lawrencium#list_repo_files Hgcommit :call lawrencium#commit#HgCommit(<bang>0, 0, 0, <f-args>)")
+ call lawrencium#add_command("-bang -nargs=* -complete=customlist,lawrencium#list_repo_files Hgvcommit :call lawrencium#commit#HgCommit(<bang>0, 1, 0, <f-args>)")
+endfunction
+
+function! lawrencium#commit#HgCommit(bang, vertical, callback, ...) abort
+ " Get the repo we'll be committing into.
+ let l:repo = lawrencium#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 lawrencium#define_commands()
+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 lawrencium#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 lawrencium#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 lawrencium#trace("Executing pre callback: ".b:lawrencium_commit_pre_callback)
+ execute b:lawrencium_commit_pre_callback
+ endif
+
+ call lawrencium#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 lawrencium#error("abort: Empty commit message")
+ return
+ endif
+
+ " Get the repo and commit with the given message.
+ let l:repo = lawrencium#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 lawrencium#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 lawrencium#trace("Executing post callback: ".b:lawrencium_commit_post_callback)
+ execute b:lawrencium_commit_post_callback
+ endif
+endfunction
+
A => autoload/lawrencium/diff.vim +292 -0
@@ 0,0 1,292 @@
+
+function! lawrencium#diff#init() abort
+ call lawrencium#add_command("-nargs=* Hgdiff :call lawrencium#diff#HgDiff('%:p', 0, <f-args>)")
+ call lawrencium#add_command("-nargs=* Hgvdiff :call lawrencium#diff#HgDiff('%:p', 1, <f-args>)")
+ call lawrencium#add_command("-nargs=* Hgtabdiff :call lawrencium#diff#HgDiff('%:p', 2, <f-args>)")
+
+ call lawrencium#add_command("-nargs=* Hgdiffsum :call lawrencium#diff#HgDiffSummary('%:p', 0, <f-args>)")
+ call lawrencium#add_command("-nargs=* Hgdiffsumsplit :call lawrencium#diff#HgDiffSummary('%:p', 1, <f-args>)")
+ call lawrencium#add_command("-nargs=* Hgvdiffsumsplit :call lawrencium#diff#HgDiffSummary('%:p', 2, <f-args>)")
+ call lawrencium#add_command("-nargs=* Hgtabdiffsum :call lawrencium#diff#HgDiffSummary('%:p', 3, <f-args>)")
+
+ call lawrencium#add_reader('diff', 'lawrencium#diff#read')
+endfunction
+
+function! lawrencium#diff#read(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
+
+function! lawrencium#diff#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 = lawrencium#hg_repo()
+ let l:path = expand(a:filename)
+ let l:diff_id = localtime()
+ call lawrencium#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! lawrencium#diff#HgDiffThis(diff_id)
+ call s:HgDiff_DiffThis(a: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 lawrencium#throw("Calling diffthis too late on a buffer!")
+ return
+ endif
+ call lawrencium#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 lawrencium#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 lawrencium#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 lawrencium#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 lawrencium#trace('Still ' . len(l:nrs) . ' diff windows open.')
+ endif
+endfunction
+
+function! lawrencium#diff#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 = lawrencium#hg_repo()
+ let l:path = expand(a:filename)
+ call lawrencium#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 = lawrencium#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 lawrencium#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 lawrencium#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 lawrencium#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 lawrencium#trace("Setting reuse ID '".l:reuse_id."' on buffer: ".bufnr('%'))
+ call setbufvar('%', l:reuse_id, 1)
+ endif
+endfunction
+
A => autoload/lawrencium/hg.vim +108 -0
@@ 0,0 1,108 @@
+
+function lawrencium#hg#init() abort
+ call lawrencium#add_command("-bang -complete=customlist,lawrencium#hg#CompleteHg -nargs=* Hg :call lawrencium#hg#Hg(<bang>0, <f-args>)")
+endfunction
+
+function! lawrencium#hg#Hg(bang, ...) abort
+ let l:repo = lawrencium#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 = lawrencium#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:h") . "/resources/hg_usage.vim"
+if filereadable(s:usage_file)
+ execute "source " . fnameescape(s:usage_file)
+else
+ call lawrencium#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! lawrencium#hg#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 lawrencium#list_repo_files(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
+
A => autoload/lawrencium/log.vim +203 -0
@@ 0,0 1,203 @@
+
+function! lawrencium#log#init() abort
+ call lawrencium#add_command("Hglogthis :call lawrencium#log#HgLog(0, '%:p')")
+ call lawrencium#add_command("Hgvlogthis :call lawrencium#log#HgLog(1, '%:p')")
+ call lawrencium#add_command("-nargs=* -complete=customlist,lawrencium#list_repo_files Hglog :call lawrencium#log#HgLog(0, <f-args>)")
+ call lawrencium#add_command("-nargs=* -complete=customlist,lawrencium#list_repo_files Hgvlog :call lawrencium#log#HgLog(1, <f-args>)")
+
+ call lawrencium#add_reader("log", "lawrencium#log#read")
+ call lawrencium#add_reader("logpatch", "lawrencium#log#read_patch")
+endfunction
+
+let s:log_style_file = expand("<sfile>:h:h") . "/resources/hg_log.style"
+
+function! lawrencium#log#read(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! lawrencium#log#read_patch(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
+
+function! lawrencium#log#HgLog(vertical, ...) abort
+ " Get the file or directory to get the log from.
+ " (empty string is for the whole repository)
+ let l:repo = lawrencium#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 = lawrencium#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 lawrencium#delete_dependency_buffers('lawrencium_diff_for', a:bufnr)
+ call lawrencium#delete_dependency_buffers('lawrencium_rev_for', a:bufnr)
+ endif
+endfunction
+
+function! s:HgLog_FileRevEdit()
+ let l:repo = lawrencium#hg_repo()
+ let l:bufobj = lawrencium#buffer_obj()
+ let l:rev = s:HgLog_GetSelectedRev()
+ let l:log_path = lawrencium#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 lawrencium#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 = lawrencium#hg_repo()
+ let l:bufobj = lawrencium#buffer_obj()
+ let l:log_path = lawrencium#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 lawrencium#diff#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 = lawrencium#hg_repo()
+ let l:bufobj = lawrencium#buffer_obj()
+ let l:log_path = lawrencium#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 lawrencium#diff#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 lawrencium#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 = lawrencium#normalizepath(
+ lawrencium#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 = lawrencium#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
+
A => autoload/lawrencium/mq.vim +126 -0
@@ 0,0 1,126 @@
+
+function! lawrencium#mq#init() abort
+ call lawrencium#add_command("Hgqseries call lawrencium#mq#HgQSeries()")
+
+ call lawrencium#add_reader('qseries', "lawrencium#mq#read")
+endfunction
+
+function! lawrencium#mq#read(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
+
+function! lawrencium#mq#HgQSeries() abort
+ " Open the MQ series in the preview window and jump to it.
+ let l:repo = lawrencium#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 lawrencium#define_commands()
+
+ " 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 = lawrencium#hg_repo()
+ let l:patchname = s:HgQSeries_GetCurrentPatchName()
+ if len(l:patchname) == 0
+ call lawrencium#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 = lawrencium#hg_repo()
+ let l:current_name = s:HgQSeries_GetCurrentPatchName()
+ if len(l:current_name) == 0
+ call lawrencium#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 = lawrencium#hg_repo()
+ let l:patchname = getbufvar('%', 'lawrencium_patchname_top')
+ if len(l:patchname) == 0
+ call lawrencium#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 = lawrencium#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 lawrencium#define_commands()
+endfunction
+
+function! s:HgQSeries_EditMessage_Execute(log_file) abort
+ if !filereadable(a:log_file)
+ call lawrencium#error("abort: Commit message not saved")
+ return
+ endif
+
+ " Clean all the 'HG:' lines.
+ let l:is_valid = lawrencium#clean_commit_file(a:log_file)
+ if !l:is_valid
+ call lawrencium#error("abort: Empty commit message")
+ return
+ endif
+
+ " Get the repo and edit the given patch.
+ let l:repo = lawrencium#hg_repo()
+ let l:hg_args = ['-s', '-l', a:log_file]
+ call l:repo.RunCommand('qref', l:hg_args)
+endfunction
+
A => autoload/lawrencium/record.vim +161 -0
@@ 0,0 1,161 @@
+
+function! lawrencium#record#init() abort
+ call lawrencium#add_command("Hgrecord call lawrencium#record#HgRecord(0)")
+ call lawrencium#add_command("Hgvrecord call lawrencium#record#HgRecord(1)")
+endfunction
+
+function! lawrencium#record#HgRecord(split) abort
+ let l:repo = lawrencium#hg_repo()
+ let l:orig_buf = lawrencium#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 lawrencium#diff#HgDiffThis(l:diff_id)
+ setlocal foldmethod=diff
+
+ " 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 = lawrencium#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 lawrencium#define_commands()
+
+ " Make it the other part of the diff.
+ call lawrencium#diff#HgDiffThis(l:diff_id)
+ setlocal foldmethod=diff
+ call l:rec_buf.SetVar('&filetype', l:orig_buf.GetVar('&filetype'))
+ call l:rec_buf.SetVar('&fileformat', l:orig_buf.GetVar('&fileformat'))
+
+ 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 lawrencium#error("abort: User requested aborting the record operation.")
+ return
+ endif
+
+ if !exists('b:lawrencium_record_for')
+ call lawrencium#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 = lawrencium#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 lawrencium#trace("Starting commit flow with callbacks: ".string(l:callbacks))
+ call lawrencium#commit#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 lawrencium#trace("Backuping original file: ".a:working_path)
+ silent call rename(a:working_path, a:working_path.'~working')
+ call lawrencium#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 lawrencium#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 lawrencium#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 = lawrencium#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 = lawrencium#buffer_obj(a:buf_nr)
+ call l:buf_obj.MoveToFirstWindow()
+ if !exists('b:lawrencium_record_for') || b:lawrencium_record_for != '%'
+ call lawrencium#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
+
A => autoload/lawrencium/revert.vim +23 -0
@@ 0,0 1,23 @@
+
+function! lawrencium#revert#init() abort
+ call lawrencium#add_command("-bang -nargs=* -complete=customlist,lawrencium#list_repo_files Hgrevert :call lawrencium#revert#HgRevert(<bang>0, <f-args>)")
+endfunction
+
+function! lawrencium#revert#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 = lawrencium#hg_repo()
+ call l:repo.RunCommand('revert', l:filenames)
+
+ " Re-edit the file to see the change.
+ edit
+endfunction
+
A => autoload/lawrencium/status.vim +288 -0
@@ 0,0 1,288 @@
+
+function! lawrencium#status#init() abort
+ call lawrencium#add_command("Hgstatus :call lawrencium#status#HgStatus()")
+
+ call lawrencium#add_reader('status', "lawrencium#status#read", 1)
+endfunction
+
+function! lawrencium#status#read(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
+
+function! lawrencium#status#HgStatus() abort
+ " Get the repo and the Lawrencium path for `hg status`.
+ let l:repo = lawrencium#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 lawrencium#trace("Switching back to status window ".l:win_nr)
+ if l:win_nr < 0
+ call lawrencium#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 lawrencium#read_lawrencium_file(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 lawrencium#error("No files to add or remove in selection or current line.")
+ return
+ endif
+
+ " Run `addremove` on those paths.
+ let l:repo = lawrencium#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 lawrencium#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 = lawrencium#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 lawrencium#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 lawrencium#commit#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 lawrencium#diff#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 lawrencium#diff#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 lawrencium#error("No files in selection or file to create patch.")
+ return
+ endif
+
+ " Run `Hg qnew` on those paths.
+ let l:repo = lawrencium#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 lawrencium#error("No files in selection or file to refresh the patch.")
+ return
+ endif
+
+ " Run `Hg qrefresh` on those paths.
+ let l:repo = lawrencium#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 = lawrencium#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
+
A => autoload/lawrencium/vimutils.vim +45 -0
@@ 0,0 1,45 @@
+
+function! lawrencium#vimutils#init() abort
+ call lawrencium#add_command("-bang -nargs=1 -complete=customlist,lawrencium#list_repo_files Hgedit :call lawrencium#vimutils#HgEdit(<bang>0, <f-args>)")
+
+ call lawrencium#add_command("-bang -nargs=? -complete=customlist,lawrencium#list_repo_dirs Hgcd :cd<bang> `=lawrencium#hg_repo().GetFullPath(<q-args>)`")
+ call lawrencium#add_command("-bang -nargs=? -complete=customlist,lawrencium#list_repo_dirs Hglcd :lcd<bang> `=lawrencium#hg_repo().GetFullPath(<q-args>)`")
+
+ call lawrencium#add_command("-bang -nargs=+ -complete=customlist,lawrencium#list_repo_files Hgvimgrep :call lawrencium#vimutils#HgVimGrep(<bang>0, <f-args>)")
+endfunction
+
+" Hgedit {{{
+
+function! lawrencium#vimutils#HgEdit(bang, filename) abort
+ let l:full_path = lawrencium#hg_repo().GetFullPath(a:filename)
+ if a:bang
+ execute "edit! " . fnameescape(l:full_path)
+ else
+ execute "edit " . fnameescape(l:full_path)
+ endif
+endfunction
+
+" }}}
+
+" Hgvimgrep {{{
+
+function! lawrencium#vimutils#HgVimGrep(bang, pattern, ...) abort
+ let l:repo = lawrencium#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
+
+" }}}
+
M plugin/lawrencium.vim +10 -2345
@@ 53,2362 53,27 @@ if !exists('g:lawrencium_record_start_in
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
+if !exists('g:lawrencium_extensions')
+ let g:lawrencium_extensions = []
+endif
" }}}
-" 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
+" Setup {{{
-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
+call lawrencium#init()
augroup lawrencium_detect
autocmd!
- autocmd BufNewFile,BufReadPost * call s:setup_buffer_commands()
- autocmd VimEnter * if expand('<amatch>')==''|call s:setup_buffer_commands()|endif
+ autocmd BufNewFile,BufReadPost * call lawrencium#setup_buffer_commands()
+ autocmd VimEnter * if expand('<amatch>')==''|call lawrencium#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>'))
+ autocmd!
+ autocmd BufReadCmd lawrencium://**//**//* exe lawrencium#read_lawrencium_file(expand('<amatch>'))
+ autocmd BufWriteCmd lawrencium://**//**//* exe lawrencium#write_lawrencium_file(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=diff
-
- " 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=diff
- call l:rec_buf.SetVar('&filetype', l:orig_buf.GetVar('&filetype'))
- call l:rec_buf.SetVar('&fileformat', l:orig_buf.GetVar('&fileformat'))
-
- 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
-
-" }}}
-