Mercurial > vim-lawrencium
changeset 139:065625e1bb31
Split plugin file into multiple extensions.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Mon, 13 Jun 2016 09:32:34 -0700 |
parents | a2d823c82e5f |
children | 652a6f5df0f3 |
files | autoload/lawrencium.vim autoload/lawrencium/addremove.vim autoload/lawrencium/annotate.vim autoload/lawrencium/cat.vim autoload/lawrencium/commit.vim autoload/lawrencium/diff.vim autoload/lawrencium/hg.vim autoload/lawrencium/log.vim autoload/lawrencium/mq.vim autoload/lawrencium/record.vim autoload/lawrencium/revert.vim autoload/lawrencium/status.vim autoload/lawrencium/vimutils.vim plugin/lawrencium.vim |
diffstat | 14 files changed, 2398 insertions(+), 2345 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/lawrencium.vim Mon Jun 13 09:32:34 2016 -0700 @@ -0,0 +1,810 @@ + +" Path Utility {{{ + +" Strips the ending slash in a path. +function! lawrencium#stripslash(path) + return fnamemodify(a:path, ':s?[/\\]$??') +endfunction + +" Returns whether a path is absolute. +function! lawrencium#isabspath(path) + return a:path =~# '\v^(\w\:)?[/\\]' +endfunction + +" Normalizes the slashes in a path. +function! lawrencium#normalizepath(path) + if exists('+shellslash') && &shellslash + return substitute(a:path, '\v/', '\\', 'g') + elseif has('win32') + return substitute(a:path, '\v/', '\\', 'g') + else + return a:path + endif +endfunction + +" Shell-slashes the path (opposite of `normalizepath`). +function! lawrencium#shellslash(path) + if exists('+shellslash') && !&shellslash + return substitute(a:path, '\v\\', '/', 'g') + else + return a:path + endif +endfunction + +" Like tempname() but with some control over the filename. +function! lawrencium#tempname(name, ...) + let l:path = tempname() + let l:result = fnamemodify(l:path, ':h') . '/' . a:name . fnamemodify(l:path, ':t') + if a:0 > 0 + let l:result = l:result . a:1 + endif + return l:result +endfunction + +" Delete a temporary file if it exists. +function! lawrencium#clean_tempfile(path) + if filewritable(a:path) + call lawrencium#trace("Cleaning up temporary file: " . a:path) + call delete(a:path) + endif +endfunction + +" }}} + +" Logging {{{ + +" Prints a message if debug tracing is enabled. +function! lawrencium#trace(message, ...) + if g:lawrencium_trace || (a:0 && a:1) + let l:message = "lawrencium: " . a:message + echom l:message + endif +endfunction + +" Prints an error message with 'lawrencium error' prefixed to it. +function! lawrencium#error(message) + echom "lawrencium error: " . a:message +endfunction + +" Throw a Lawrencium exception message. +function! lawrencium#throw(message) + let v:errmsg = "lawrencium: " . a:message + throw v:errmsg +endfunction + +" }}} + +" Repositories {{{ + +" Finds the repository root given a path inside that repository. +" Throw an error if not repository is found. +function! lawrencium#find_repo_root(path) + let l:path = lawrencium#stripslash(a:path) + let l:previous_path = "" + while l:path != l:previous_path + if isdirectory(l:path . '/.hg') + return lawrencium#normalizepath(simplify(fnamemodify(l:path, ':p'))) + endif + let l:previous_path = l:path + let l:path = fnamemodify(l:path, ':h') + endwhile + call lawrencium#throw("No Mercurial repository found above: " . a:path) +endfunction + +" Given a Lawrencium path (e.g: 'lawrencium:///repo/root_dir//foo/bar/file.py//rev=34'), extract +" the repository root, relative file path and revision number/changeset ID. +" +" If a second argument exists, it must be: +" - `relative`: to make the file path relative to the repository root. +" - `absolute`: to make the file path absolute. +" +function! lawrencium#parse_lawrencium_path(lawrencium_path, ...) + let l:repo_path = lawrencium#shellslash(a:lawrencium_path) + let l:repo_path = substitute(l:repo_path, '\\ ', ' ', 'g') + if l:repo_path =~? '\v^lawrencium://' + let l:repo_path = strpart(l:repo_path, strlen('lawrencium://')) + endif + + let l:root_dir = '' + let l:at_idx = stridx(l:repo_path, '//') + if l:at_idx >= 0 + let l:root_dir = strpart(l:repo_path, 0, l:at_idx) + let l:repo_path = strpart(l:repo_path, l:at_idx + 2) + endif + + let l:value = '' + let l:action = '' + let l:actionidx = stridx(l:repo_path, '//') + if l:actionidx >= 0 + let l:action = strpart(l:repo_path, l:actionidx + 2) + let l:repo_path = strpart(l:repo_path, 0, l:actionidx) + + let l:equalidx = stridx(l:action, '=') + if l:equalidx >= 0 + let l:value = strpart(l:action, l:equalidx + 1) + let l:action = strpart(l:action, 0, l:equalidx) + endif + endif + + if a:0 > 0 + execute 'cd! ' . fnameescape(l:root_dir) + if a:1 == 'relative' + let l:repo_path = fnamemodify(l:repo_path, ':.') + elseif a:1 == 'absolute' + let l:repo_path = fnamemodify(l:repo_path, ':p') + endif + execute 'cd! -' + endif + + let l:result = { 'root': l:root_dir, 'path': l:repo_path, 'action': l:action, 'value': l:value } + return l:result +endfunction + +" Clean up all the 'HG:' lines from a commit message, and see if there's +" any message left (Mercurial does this automatically, usually, but +" apparently not when you feed it a log file...). +function! lawrencium#clean_commit_file(log_file) abort + let l:lines = readfile(a:log_file) + call filter(l:lines, "v:val !~# '\\v^HG:'") + if len(filter(copy(l:lines), "v:val !~# '\\v^\\s*$'")) == 0 + return 0 + endif + call writefile(l:lines, a:log_file) + return 1 +endfunction + +" }}} + +" Vim Utility {{{ + +" Finds a window whose displayed buffer has a given variable +" set to the given value. +function! lawrencium#find_buffer_window(varname, varvalue) abort + for wnr in range(1, winnr('$')) + let l:bnr = winbufnr(wnr) + if getbufvar(l:bnr, a:varname) == a:varvalue + return l:wnr + endif + endfor + return -1 +endfunction + +" Opens a buffer in a way that makes it easy to delete it later: +" - if the about-to-be previous buffer doesn't have a given variable, +" just open the new buffer. +" - if the about-to-be previous buffer has a given variable, open the +" new buffer with the `keepalt` option to make it so that the +" actual previous buffer (returned by things like `bufname('#')`) +" is the original buffer that was there before the first deletable +" buffer was opened. +function! lawrencium#edit_deletable_buffer(varname, varvalue, path) abort + let l:edit_cmd = 'edit ' + if getbufvar('%', a:varname) != '' + let l:edit_cmd = 'keepalt edit ' + endif + execute l:edit_cmd . fnameescape(a:path) + call setbufvar('%', a:varname, a:varvalue) +endfunction + +" Deletes all buffers that have a given variable set to a given value. +" For each buffer, if it is not shown in any window, it will be just deleted. +" If it is shown in a window, that window will be switched to the alternate +" buffer before the buffer is deleted, unless the `lawrencium_quit_on_delete` +" variable is set to `1`, in which case the window is closed too. +function! lawrencium#delete_dependency_buffers(varname, varvalue) abort + let l:cur_winnr = winnr() + for bnr in range(1, bufnr('$')) + if getbufvar(bnr, a:varname) == a:varvalue + " Delete this buffer if it is not shown in any window. + " Otherwise, display the alternate buffer before deleting + " it so the window is not closed. + let l:bwnr = bufwinnr(bnr) + if l:bwnr < 0 || getbufvar(bnr, 'lawrencium_quit_on_delete') == 1 + if bufloaded(l:bnr) + call lawrencium#trace("Deleting dependency buffer " . bnr) + execute "bdelete! " . bnr + else + call lawrencium#trace("Dependency buffer " . bnr . " is already unladed.") + endif + else + execute l:bwnr . "wincmd w" + " TODO: better handle case where there's no previous/alternate buffer? + let l:prev_bnr = bufnr('#') + if l:prev_bnr > 0 && bufloaded(l:prev_bnr) + execute "buffer " . l:prev_bnr + if bufloaded(l:bnr) + call lawrencium#trace("Deleting dependency buffer " . bnr . " after switching to " . l:prev_bnr . " in window " . l:bwnr) + execute "bdelete! " . bnr + else + call lawrencium#trace("Dependency buffer " . bnr . " is unladed after switching to " . l:prev_bnr) + endif + else + call lawrencium#trace("Deleting dependency buffer " . bnr . " and window.") + bdelete! + endif + endif + endif + endfor + if l:cur_winnr != winnr() + call lawrencium#trace("Returning to window " . l:cur_winnr) + execute l:cur_winnr . "wincmd w" + endif +endfunction + +" }}} + +" Mercurial Repository Object {{{ + +" Let's define a Mercurial repo 'class' using prototype-based object-oriented +" programming. +" +" The prototype dictionary. +let s:HgRepo = {} + +" Constructor. +function! s:HgRepo.New(path) abort + let l:newRepo = copy(self) + let l:newRepo.root_dir = lawrencium#find_repo_root(a:path) + call lawrencium#trace("Built new Mercurial repository object at : " . l:newRepo.root_dir) + return l:newRepo +endfunction + +" Gets a full path given a repo-relative path. +function! s:HgRepo.GetFullPath(path) abort + let l:root_dir = self.root_dir + if lawrencium#isabspath(a:path) + call lawrencium#throw("Expected relative path, got absolute path: " . a:path) + endif + return lawrencium#normalizepath(l:root_dir . a:path) +endfunction + +" Gets a repo-relative path given any path. +function! s:HgRepo.GetRelativePath(path) abort + execute 'lcd! ' . fnameescape(self.root_dir) + let l:relative_path = fnamemodify(a:path, ':.') + execute 'lcd! -' + return l:relative_path +endfunction + +" Gets, and optionally creates, a temp folder for some operation in the `.hg` +" directory. +function! s:HgRepo.GetTempDir(path, ...) abort + let l:tmp_dir = self.GetFullPath('.hg/lawrencium/' . a:path) + if !isdirectory(l:tmp_dir) + if a:0 > 0 && !a:1 + return '' + endif + call mkdir(l:tmp_dir, 'p') + endif + return l:tmp_dir +endfunction + +" Gets a list of files matching a root-relative pattern. +" If a flag is passed and is TRUE, a slash will be appended to all +" directories. +function! s:HgRepo.Glob(pattern, ...) abort + let l:root_dir = self.root_dir + if (a:pattern =~# '\v^[/\\]') + let l:root_dir = lawrencium#stripslash(l:root_dir) + endif + let l:matches = split(glob(l:root_dir . a:pattern), '\n') + if a:0 && a:1 + for l:idx in range(len(l:matches)) + if !filereadable(l:matches[l:idx]) + let l:matches[l:idx] = l:matches[l:idx] . '/' + endif + endfor + endif + let l:strip_len = len(l:root_dir) + call map(l:matches, 'v:val[l:strip_len : -1]') + return l:matches +endfunction + +" Gets a full Mercurial command. +function! s:HgRepo.GetCommand(command, ...) abort + " If there's only one argument, and it's a list, then use that as the + " argument list. + let l:arg_list = a:000 + if a:0 == 1 && type(a:1) == type([]) + let l:arg_list = a:1 + endif + let l:prev_shellslash = &shellslash + setlocal noshellslash + let l:hg_command = g:lawrencium_hg_executable . ' --repository ' . shellescape(lawrencium#stripslash(self.root_dir)) + let l:hg_command = l:hg_command . ' ' . a:command + for l:arg in l:arg_list + let l:hg_command = l:hg_command . ' ' . shellescape(l:arg) + endfor + if l:prev_shellslash + setlocal shellslash + endif + return l:hg_command +endfunction + +" Runs a Mercurial command in the repo. +function! s:HgRepo.RunCommand(command, ...) abort + let l:all_args = [1, a:command] + a:000 + return call(self['RunCommandEx'], l:all_args, self) +endfunction + +function! s:HgRepo.RunCommandEx(plain_mode, command, ...) abort + let l:prev_hgplain = $HGPLAIN + if a:plain_mode + let $HGPLAIN = 'true' + endif + let l:all_args = [a:command] + a:000 + let l:hg_command = call(self['GetCommand'], l:all_args, self) + call lawrencium#trace("Running Mercurial command: " . l:hg_command) + let l:cmd_out = system(l:hg_command) + if a:plain_mode + let $HGPLAIN = l:prev_hgplain + endif + return l:cmd_out +endfunction + +" Runs a Mercurial command in the repo and reads its output into the current +" buffer. +function! s:HgRepo.ReadCommandOutput(command, ...) abort + function! s:PutOutputIntoBuffer(command_line) + let l:was_buffer_empty = (line('$') == 1 && getline(1) == '') + execute '0read!' . escape(a:command_line, '%#\') + if l:was_buffer_empty " (Always true?) + " '0read' inserts before the cursor, leaving a blank line which + " needs to be deleted... but if there are folds in this thing, we + " must open them all first otherwise we could delete the whole + " contents of the last fold (since Vim may close them all by + " default). + normal! zRG"_dd + endif + endfunction + + let l:all_args = [a:command] + a:000 + let l:hg_command = call(self['GetCommand'], l:all_args, self) + call lawrencium#trace("Running Mercurial command: " . l:hg_command) + call s:PutOutputIntoBuffer(l:hg_command) +endfunction + +" Build a Lawrencium path for the given file and action. +" By default, the given path will be made relative to the repository root, +" unless '0' is passed as the 4th argument. +function! s:HgRepo.GetLawrenciumPath(path, action, value, ...) abort + let l:path = a:path + if a:0 == 0 || !a:1 + let l:path = self.GetRelativePath(a:path) + endif + let l:path = fnameescape(l:path) + let l:result = 'lawrencium://' . lawrencium#stripslash(self.root_dir) . '//' . l:path + if a:action !=? '' + let l:result = l:result . '//' . a:action + if a:value !=? '' + let l:result = l:result . '=' . a:value + endif + endif + return l:result +endfunction + +" Repo cache map. +let s:buffer_repos = {} + +" Get a cached repo. +function! lawrencium#hg_repo(...) abort + " Use the given path, or the mercurial directory of the current buffer. + if a:0 == 0 + if exists('b:mercurial_dir') + let l:path = b:mercurial_dir + else + let l:path = lawrencium#find_repo_root(expand('%:p')) + endif + else + let l:path = a:1 + endif + " Find a cache repo instance, or make a new one. + if has_key(s:buffer_repos, l:path) + return get(s:buffer_repos, l:path) + else + let l:repo = s:HgRepo.New(l:path) + let s:buffer_repos[l:path] = l:repo + return l:repo + endif +endfunction + +" }}} + +" Buffer Object {{{ + +" The prototype dictionary. +let s:Buffer = {} + +" Constructor. +function! s:Buffer.New(number) dict abort + let l:newBuffer = copy(self) + let l:newBuffer.nr = a:number + let l:newBuffer.var_backup = {} + let l:newBuffer.cmd_names = {} + let l:newBuffer.on_delete = [] + let l:newBuffer.on_winleave = [] + let l:newBuffer.on_unload = [] + execute 'augroup lawrencium_buffer_' . a:number + execute ' autocmd!' + execute ' autocmd BufDelete <buffer=' . a:number . '> call s:buffer_on_delete(' . a:number . ')' + execute 'augroup end' + call lawrencium#trace("Built new buffer object for buffer: " . a:number) + return l:newBuffer +endfunction + +function! s:Buffer.GetName(...) dict abort + let l:name = bufname(self.nr) + if a:0 > 0 + let l:name = fnamemodify(l:name, a:1) + endif + return l:name +endfunction + +function! s:Buffer.GetVar(var) dict abort + return getbufvar(self.nr, a:var) +endfunction + +function! s:Buffer.SetVar(var, value) dict abort + if !has_key(self.var_backup, a:var) + let self.var_backup[a:var] = getbufvar(self.nr, a:var) + endif + return setbufvar(self.nr, a:var, a:value) +endfunction + +function! s:Buffer.RestoreVars() dict abort + for key in keys(self.var_backup) + setbufvar(self.nr, key, self.var_backup[key]) + endfor +endfunction + +function! s:Buffer.DefineCommand(name, ...) dict abort + if a:0 == 0 + call lawrencium#throw("Not enough parameters for s:Buffer.DefineCommands()") + endif + if a:0 == 1 + let l:flags = '' + let l:cmd = a:1 + else + let l:flags = a:1 + let l:cmd = a:2 + endif + if has_key(self.cmd_names, a:name) + call lawrencium#throw("Command '".a:name."' is already defined in buffer ".self.nr) + endif + if bufnr('%') != self.nr + call lawrencium#throw("You must move to buffer ".self.nr."first before defining local commands") + endif + let self.cmd_names[a:name] = 1 + let l:real_flags = '' + if type(l:flags) == type('') + let l:real_flags = l:flags + endif + execute 'command -buffer '.l:real_flags.' '.a:name.' '.l:cmd +endfunction + +function! s:Buffer.DeleteCommand(name) dict abort + if !has_key(self.cmd_names, a:name) + call lawrencium#throw("Command '".a:name."' has not been defined in buffer ".self.nr) + endif + if bufnr('%') != self.nr + call lawrencium#throw("You must move to buffer ".self.nr."first before deleting local commands") + endif + execute 'delcommand '.a:name + call remove(self.cmd_names, a:name) +endfunction + +function! s:Buffer.DeleteCommands() dict abort + if bufnr('%') != self.nr + call lawrencium#throw("You must move to buffer ".self.nr."first before deleting local commands") + endif + for name in keys(self.cmd_names) + execute 'delcommand '.name + endfor + let self.cmd_names = {} +endfunction + +function! s:Buffer.MoveToFirstWindow() dict abort + let l:win_nr = bufwinnr(self.nr) + if l:win_nr < 0 + if a:0 > 0 && a:1 == 0 + return 0 + endif + call lawrencium#throw("No windows currently showing buffer ".self.nr) + endif + execute l:win_nr.'wincmd w' + return 1 +endfunction + +function! s:Buffer.OnDelete(cmd) dict abort + call lawrencium#trace("Adding BufDelete callback for buffer " . self.nr . ": " . a:cmd) + call add(self.on_delete, a:cmd) +endfunction + +function! s:Buffer.OnWinLeave(cmd) dict abort + if len(self.on_winleave) == 0 + call lawrencium#trace("Adding BufWinLeave auto-command on buffer " . self.nr) + execute 'augroup lawrencium_buffer_' . self.nr . '_winleave' + execute ' autocmd!' + execute ' autocmd BufWinLeave <buffer=' . self.nr . '> call s:buffer_on_winleave(' . self.nr .')' + execute 'augroup end' + endif + call lawrencium#trace("Adding BufWinLeave callback for buffer " . self.nr . ": " . a:cmd) + call add(self.on_winleave, a:cmd) +endfunction + +function! s:Buffer.OnUnload(cmd) dict abort + if len(self.on_unload) == 0 + call lawrencium#trace("Adding BufUnload auto-command on buffer " . self.nr) + execute 'augroup lawrencium_buffer_' . self.nr . '_unload' + execute ' autocmd!' + execute ' autocmd BufUnload <buffer=' . self.nr . '> call s:buffer_on_unload(' . self.nr . ')' + execute 'augroup end' + endif + call lawrencium#trace("Adding BufUnload callback for buffer " . self.nr . ": " . a:cmd) + call add(self.on_unload, a:cmd) +endfunction + +let s:buffer_objects = {} + +" Get a buffer instance for the specified buffer number, or the +" current buffer if nothing is specified. +function! lawrencium#buffer_obj(...) abort + let l:bufnr = a:0 ? a:1 : bufnr('%') + if !has_key(s:buffer_objects, l:bufnr) + let s:buffer_objects[l:bufnr] = s:Buffer.New(l:bufnr) + endif + return s:buffer_objects[l:bufnr] +endfunction + +" Execute all the "on delete" callbacks. +function! s:buffer_on_delete(number) abort + let l:bufobj = s:buffer_objects[a:number] + call lawrencium#trace("Calling BufDelete callbacks on buffer " . l:bufobj.nr) + for cmd in l:bufobj.on_delete + call lawrencium#trace(" [" . cmd . "]") + execute cmd + endfor + call lawrencium#trace("Deleted buffer object " . l:bufobj.nr) + call remove(s:buffer_objects, l:bufobj.nr) + execute 'augroup lawrencium_buffer_' . l:bufobj.nr + execute ' autocmd!' + execute 'augroup end' +endfunction + +" Execute all the "on winleave" callbacks. +function! s:buffer_on_winleave(number) abort + let l:bufobj = s:buffer_objects[a:number] + call lawrencium#trace("Calling BufWinLeave callbacks on buffer " . l:bufobj.nr) + for cmd in l:bufobj.on_winleave + call lawrencium#trace(" [" . cmd . "]") + execute cmd + endfor + execute 'augroup lawrencium_buffer_' . l:bufobj.nr . '_winleave' + execute ' autocmd!' + execute 'augroup end' +endfunction + +" Execute all the "on unload" callbacks. +function! s:buffer_on_unload(number) abort + let l:bufobj = s:buffer_objects[a:number] + call lawrencium#trace("Calling BufUnload callbacks on buffer " . l:bufobj.nr) + for cmd in l:bufobj.on_unload + call lawrencium#trace(" [" . cmd . "]") + execute cmd + endfor + execute 'augroup lawrencium_buffer_' . l:bufobj.nr . '_unload' + execute ' autocmd!' + execute 'augroup end' +endfunction + +" }}} + +" Buffer Commands Management {{{ + +" Store the commands for Lawrencium-enabled buffers so that we can add them in +" batch when we need to. +let s:main_commands = [] + +function! lawrencium#add_command(command) abort + let s:main_commands += [a:command] +endfunction + +function! lawrencium#define_commands() + for l:command in s:main_commands + execute 'command! -buffer ' . l:command + endfor +endfunction + +augroup lawrencium_main + autocmd! + autocmd User Lawrencium call lawrencium#define_commands() +augroup end + +" Sets up the current buffer with Lawrencium commands if it contains a file from a Mercurial repo. +" If the file is not in a Mercurial repo, just exit silently. +function! lawrencium#setup_buffer_commands() abort + call lawrencium#trace("Scanning buffer '" . bufname('%') . "' for Lawrencium setup...") + let l:do_setup = 1 + if exists('b:mercurial_dir') + if b:mercurial_dir =~# '\v^\s*$' + unlet b:mercurial_dir + else + let l:do_setup = 0 + endif + endif + try + let l:repo = lawrencium#hg_repo() + catch /^lawrencium\:/ + return + endtry + let b:mercurial_dir = l:repo.root_dir + if exists('b:mercurial_dir') && l:do_setup + call lawrencium#trace("Setting Mercurial commands for buffer '" . bufname('%')) + call lawrencium#trace(" with repo : " . expand(b:mercurial_dir)) + silent doautocmd User Lawrencium + endif +endfunction + +" }}} + +" Commands Auto-Complete {{{ + +" Auto-complete function for commands that take repo-relative file paths. +function! lawrencium#list_repo_files(ArgLead, CmdLine, CursorPos) abort + let l:matches = lawrencium#hg_repo().Glob(a:ArgLead . '*', 1) + call map(l:matches, 'lawrencium#normalizepath(v:val)') + return l:matches +endfunction + +" Auto-complete function for commands that take repo-relative directory paths. +function! lawrencium#list_repo_dirs(ArgLead, CmdLine, CursorPos) abort + let l:matches = lawrencium#hg_repo().Glob(a:ArgLead . '*/') + call map(l:matches, 'lawrencium#normalizepath(v:val)') + return l:matches +endfunction + +" }}} + +" Lawrencium Files {{{ + +" Generic read +let s:lawrencium_file_readers = {} +let s:lawrencium_file_customoptions = {} + +function! lawrencium#add_reader(action, callback, ...) abort + if has_key(s:lawrencium_file_readers, a:action) + call lawrencium#throw("Lawrencium file '".a:action."' has alredy been registered.") + endif + let s:lawrencium_file_readers[a:action] = function(a:callback) + if a:0 && a:1 + let s:lawrencium_file_customoptions[a:action] = 1 + endif +endfunction + +function! lawrencium#read_lawrencium_file(path) abort + call lawrencium#trace("Reading Lawrencium file: " . a:path) + let l:path_parts = lawrencium#parse_lawrencium_path(a:path) + if l:path_parts['root'] == '' + call lawrencium#throw("Can't get repository root from: " . a:path) + endif + if !has_key(s:lawrencium_file_readers, l:path_parts['action']) + call lawrencium#throw("No registered reader for action: " . l:path_parts['action']) + endif + + " Call the registered reader. + let l:repo = lawrencium#hg_repo(l:path_parts['root']) + let l:full_path = l:repo.root_dir . l:path_parts['path'] + let LawrenciumFileReader = s:lawrencium_file_readers[l:path_parts['action']] + call LawrenciumFileReader(l:repo, l:path_parts, l:full_path) + + " Setup the new buffer. + if !has_key(s:lawrencium_file_customoptions, l:path_parts['action']) + setlocal readonly + setlocal nomodified + setlocal bufhidden=delete + setlocal buftype=nofile + endif + goto + + " Remember the real Lawrencium path, because Vim can fuck up the slashes + " on Windows. + let b:lawrencium_path = a:path + + " Remember the repo it belongs to and make + " the Lawrencium commands available. + let b:mercurial_dir = l:repo.root_dir + call lawrencium#define_commands() + + return '' +endfunction + +function! lawrencium#write_lawrencium_file(path) abort + call lawrencium#trace("Writing Lawrencium file: " . a:path) +endfunction + +" }}} + +" Statusline {{{ + +" Prints a summary of the current repo (if any) that's appropriate for +" displaying on the status line. +function! lawrencium#statusline(...) + if !exists('b:mercurial_dir') + return '' + endif + let l:repo = lawrencium#hg_repo() + let l:prefix = (a:0 > 0 ? a:1 : '') + let l:suffix = (a:0 > 1 ? a:2 : '') + let l:branch = 'default' + let l:branch_file = l:repo.GetFullPath('.hg/branch') + if filereadable(l:branch_file) + let l:branch = readfile(l:branch_file)[0] + endif + let l:bookmarks = '' + let l:bookmarks_file = l:repo.GetFullPath('.hg/bookmarks.current') + if filereadable(l:bookmarks_file) + let l:bookmarks = join(readfile(l:bookmarks_file), ', ') + endif + let l:line = l:prefix . l:branch + if strlen(l:bookmarks) > 0 + let l:line = l:line . ' - ' . l:bookmarks + endif + let l:line = l:line . l:suffix + return l:line +endfunction + +" }}} + +" Miscellaneous User Functions {{{ + +" Rescans the current buffer for setting up Mercurial commands. +" Passing '1' as the parameter enables debug traces temporarily. +function! lawrencium#rescan(...) + if exists('b:mercurial_dir') + unlet b:mercurial_dir + endif + if a:0 && a:1 + let l:trace_backup = g:lawrencium_trace + let g:lawrencium_trace = 1 + endif + call lawrencium#setup_buffer_commands() + if a:0 && a:1 + let g:lawrencium_trace = l:trace_backup + endif +endfunction + +" Enables/disables the debug trace. +function! lawrencium#debugtrace(...) + let g:lawrencium_trace = (a:0 == 0 || (a:0 && a:1)) + echom "Lawrencium debug trace is now " . (g:lawrencium_trace ? "enabled." : "disabled.") +endfunction + +" }}} + +" Setup {{{ + +function! lawrencium#init() abort + let s:builtin_exts = [ + \'lawrencium#addremove', + \'lawrencium#annotate', + \'lawrencium#cat', + \'lawrencium#commit', + \'lawrencium#diff', + \'lawrencium#hg', + \'lawrencium#log', + \'lawrencium#mq', + \'lawrencium#record', + \'lawrencium#revert', + \'lawrencium#status', + \'lawrencium#vimutils' + \] + let s:user_exts = copy(g:lawrencium_extensions) + let s:exts = s:builtin_exts + s:user_exts + for ext in s:exts + call lawrencium#trace("Initializing Lawrencium extension " . ext) + execute ('call ' . ext . '#init()') + endfor +endfunction + +" }}} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/lawrencium/addremove.vim Mon Jun 13 09:32:34 2016 -0700 @@ -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 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/lawrencium/annotate.vim Mon Jun 13 09:32:34 2016 -0700 @@ -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 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/lawrencium/cat.vim Mon Jun 13 09:32:34 2016 -0700 @@ -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 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/lawrencium/commit.vim Mon Jun 13 09:32:34 2016 -0700 @@ -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 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/lawrencium/diff.vim Mon Jun 13 09:32:34 2016 -0700 @@ -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 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/lawrencium/hg.vim Mon Jun 13 09:32:34 2016 -0700 @@ -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 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/lawrencium/log.vim Mon Jun 13 09:32:34 2016 -0700 @@ -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 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/lawrencium/mq.vim Mon Jun 13 09:32:34 2016 -0700 @@ -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 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/lawrencium/record.vim Mon Jun 13 09:32:34 2016 -0700 @@ -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 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/lawrencium/revert.vim Mon Jun 13 09:32:34 2016 -0700 @@ -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 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/lawrencium/status.vim Mon Jun 13 09:32:34 2016 -0700 @@ -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 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/autoload/lawrencium/vimutils.vim Mon Jun 13 09:32:34 2016 -0700 @@ -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 + +" }}} +
--- a/plugin/lawrencium.vim Tue Apr 19 21:33:41 2016 -0700 +++ b/plugin/lawrencium.vim Mon Jun 13 09:32:34 2016 -0700 @@ -53,2362 +53,27 @@ 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 - -" }}} -