Mercurial > vim-lawrencium
view plugin/lawrencium.vim @ 126:47209552ec46
Shellescaped all command arguments in HgRepo.GetCommand,
so that the commands work properly with ugly file names,
in my case containing parentheses.
Wrapping revision arguments in quotes is no longer necessary,
so removed all of that as well.
author | namark <nshan.nnnn@gmail.com> |
---|---|
date | Wed, 02 Dec 2015 22:45:12 +0400 |
parents | a9136d95cf47 |
children | 96e4423e729e |
line wrap: on
line source
" lawrencium.vim - A Mercurial wrapper " Maintainer: Ludovic Chabant <http://ludovic.chabant.com> " Version: 0.4.0 " Globals {{{ if !exists('g:lawrencium_debug') let g:lawrencium_debug = 0 endif if (exists('g:loaded_lawrencium') || &cp) && !g:lawrencium_debug finish endif if (exists('g:loaded_lawrencium') && g:lawrencium_debug) echom "Reloaded Lawrencium." endif let g:loaded_lawrencium = 1 if !exists('g:lawrencium_hg_executable') let g:lawrencium_hg_executable = 'hg' endif if !exists('g:lawrencium_auto_cd') let g:lawrencium_auto_cd = 1 endif if !exists('g:lawrencium_trace') let g:lawrencium_trace = 0 endif if !exists('g:lawrencium_define_mappings') let g:lawrencium_define_mappings = 1 endif if !exists('g:lawrencium_auto_close_buffers') let g:lawrencium_auto_close_buffers = 1 endif if !exists('g:lawrencium_annotate_width_offset') let g:lawrencium_annotate_width_offset = 0 endif if !exists('g:lawrencium_status_win_split_above') let g:lawrencium_status_win_split_above = 0 endif if !exists('g:lawrencium_status_win_split_even') let g:lawrencium_status_win_split_even = 0 endif if !exists('g:lawrencium_record_start_in_working_buffer') let g:lawrencium_record_start_in_working_buffer = 0 endif " }}} " Utility {{{ " Strips the ending slash in a path. function! s:stripslash(path) return fnamemodify(a:path, ':s?[/\\]$??') endfunction " Returns whether a path is absolute. function! s:isabspath(path) return a:path =~# '\v^(\w\:)?[/\\]' endfunction " Normalizes the slashes in a path. function! s:normalizepath(path) if exists('+shellslash') && &shellslash return substitute(a:path, '\v/', '\\', 'g') elseif has('win32') return substitute(a:path, '\v/', '\\', 'g') else return a:path endif endfunction " Shell-slashes the path (opposite of `normalizepath`). function! s:shellslash(path) if exists('+shellslash') && !&shellslash return substitute(a:path, '\v\\', '/', 'g') else return a:path endif endfunction " Like tempname() but with some control over the filename. function! s:tempname(name, ...) let l:path = tempname() let l:result = fnamemodify(l:path, ':h') . '/' . a:name . fnamemodify(l:path, ':t') if a:0 > 0 let l:result = l:result . a:1 endif return l:result endfunction " Delete a temporary file if it exists. function! s:clean_tempfile(path) if filewritable(a:path) call s:trace("Cleaning up temporary file: " . a:path) call delete(a:path) endif endfunction " Prints a message if debug tracing is enabled. function! s:trace(message, ...) if g:lawrencium_trace || (a:0 && a:1) let l:message = "lawrencium: " . a:message echom l:message endif endfunction " Prints an error message with 'lawrencium error' prefixed to it. function! s:error(message) echom "lawrencium error: " . a:message endfunction " Throw a Lawrencium exception message. function! s:throw(message) let v:errmsg = "lawrencium: " . a:message throw v:errmsg endfunction " Finds the repository root given a path inside that repository. " Throw an error if not repository is found. function! s:find_repo_root(path) let l:path = s:stripslash(a:path) let l:previous_path = "" while l:path != l:previous_path if isdirectory(l:path . '/.hg') return s:normalizepath(simplify(fnamemodify(l:path, ':p'))) endif let l:previous_path = l:path let l:path = fnamemodify(l:path, ':h') endwhile call s:throw("No Mercurial repository found above: " . a:path) endfunction " Given a Lawrencium path (e.g: 'lawrencium:///repo/root_dir//foo/bar/file.py//rev=34'), extract " the repository root, relative file path and revision number/changeset ID. " " If a second argument exists, it must be: " - `relative`: to make the file path relative to the repository root. " - `absolute`: to make the file path absolute. " function! s:parse_lawrencium_path(lawrencium_path, ...) let l:repo_path = s:shellslash(a:lawrencium_path) let l:repo_path = substitute(l:repo_path, '\\ ', ' ', 'g') if l:repo_path =~? '\v^lawrencium://' let l:repo_path = strpart(l:repo_path, strlen('lawrencium://')) endif let l:root_dir = '' let l:at_idx = stridx(l:repo_path, '//') if l:at_idx >= 0 let l:root_dir = strpart(l:repo_path, 0, l:at_idx) let l:repo_path = strpart(l:repo_path, l:at_idx + 2) endif let l:value = '' let l:action = '' let l:actionidx = stridx(l:repo_path, '//') if l:actionidx >= 0 let l:action = strpart(l:repo_path, l:actionidx + 2) let l:repo_path = strpart(l:repo_path, 0, l:actionidx) let l:equalidx = stridx(l:action, '=') if l:equalidx >= 0 let l:value = strpart(l:action, l:equalidx + 1) let l:action = strpart(l:action, 0, l:equalidx) endif endif if a:0 > 0 execute 'cd! ' . fnameescape(l:root_dir) if a:1 == 'relative' let l:repo_path = fnamemodify(l:repo_path, ':.') elseif a:1 == 'absolute' let l:repo_path = fnamemodify(l:repo_path, ':p') endif execute 'cd! -' endif let l:result = { 'root': l:root_dir, 'path': l:repo_path, 'action': l:action, 'value': l:value } return l:result endfunction " Finds a window whose displayed buffer has a given variable " set to the given value. function! s:find_buffer_window(varname, varvalue) abort for wnr in range(1, winnr('$')) let l:bnr = winbufnr(wnr) if getbufvar(l:bnr, a:varname) == a:varvalue return l:wnr endif endfor return -1 endfunction " Opens a buffer in a way that makes it easy to delete it later: " - if the about-to-be previous buffer doesn't have a given variable, " just open the new buffer. " - if the about-to-be previous buffer has a given variable, open the " new buffer with the `keepalt` option to make it so that the " actual previous buffer (returned by things like `bufname('#')`) " is the original buffer that was there before the first deletable " buffer was opened. function! s:edit_deletable_buffer(varname, varvalue, path) abort let l:edit_cmd = 'edit ' if getbufvar('%', a:varname) != '' let l:edit_cmd = 'keepalt edit ' endif execute l:edit_cmd . fnameescape(a:path) call setbufvar('%', a:varname, a:varvalue) endfunction " Deletes all buffers that have a given variable set to a given value. " For each buffer, if it is not shown in any window, it will be just deleted. " If it is shown in a window, that window will be switched to the alternate " buffer before the buffer is deleted, unless the `lawrencium_quit_on_delete` " variable is set to `1`, in which case the window is closed too. function! s:delete_dependency_buffers(varname, varvalue) abort let l:cur_winnr = winnr() for bnr in range(1, bufnr('$')) if getbufvar(bnr, a:varname) == a:varvalue " Delete this buffer if it is not shown in any window. " Otherwise, display the alternate buffer before deleting " it so the window is not closed. let l:bwnr = bufwinnr(bnr) if l:bwnr < 0 || getbufvar(bnr, 'lawrencium_quit_on_delete') == 1 if bufloaded(l:bnr) call s:trace("Deleting dependency buffer " . bnr) execute "bdelete! " . bnr else call s:trace("Dependency buffer " . bnr . " is already unladed.") endif else execute l:bwnr . "wincmd w" " TODO: better handle case where there's no previous/alternate buffer? let l:prev_bnr = bufnr('#') if l:prev_bnr > 0 && bufloaded(l:prev_bnr) execute "buffer " . l:prev_bnr if bufloaded(l:bnr) call s:trace("Deleting dependency buffer " . bnr . " after switching to " . l:prev_bnr . " in window " . l:bwnr) execute "bdelete! " . bnr else call s:trace("Dependency buffer " . bnr . " is unladed after switching to " . l:prev_bnr) endif else call s:trace("Deleting dependency buffer " . bnr . " and window.") bdelete! endif endif endif endfor if l:cur_winnr != winnr() call s:trace("Returning to window " . l:cur_winnr) execute l:cur_winnr . "wincmd w" endif endfunction " Clean up all the 'HG:' lines from a commit message, and see if there's " any message left (Mercurial does this automatically, usually, but " apparently not when you feed it a log file...). function! s:clean_commit_file(log_file) abort let l:lines = readfile(a:log_file) call filter(l:lines, "v:val !~# '\\v^HG:'") if len(filter(copy(l:lines), "v:val !~# '\\v^\\s*$'")) == 0 return 0 endif call writefile(l:lines, a:log_file) return 1 endfunction " }}} " Mercurial Repository Object {{{ " Let's define a Mercurial repo 'class' using prototype-based object-oriented " programming. " " The prototype dictionary. let s:HgRepo = {} " Constructor. function! s:HgRepo.New(path) abort let l:newRepo = copy(self) let l:newRepo.root_dir = s:find_repo_root(a:path) call s:trace("Built new Mercurial repository object at : " . l:newRepo.root_dir) return l:newRepo endfunction " Gets a full path given a repo-relative path. function! s:HgRepo.GetFullPath(path) abort let l:root_dir = self.root_dir if s:isabspath(a:path) call s:throw("Expected relative path, got absolute path: " . a:path) endif return s:normalizepath(l:root_dir . a:path) endfunction " Gets a repo-relative path given any path. function! s:HgRepo.GetRelativePath(path) abort execute 'lcd! ' . fnameescape(self.root_dir) let l:relative_path = fnamemodify(a:path, ':.') execute 'lcd! -' return l:relative_path endfunction " Gets, and optionally creates, a temp folder for some operation in the `.hg` " directory. function! s:HgRepo.GetTempDir(path, ...) abort let l:tmp_dir = self.GetFullPath('.hg/lawrencium/' . a:path) if !isdirectory(l:tmp_dir) if a:0 > 0 && !a:1 return '' endif call mkdir(l:tmp_dir, 'p') endif return l:tmp_dir endfunction " Gets a list of files matching a root-relative pattern. " If a flag is passed and is TRUE, a slash will be appended to all " directories. function! s:HgRepo.Glob(pattern, ...) abort let l:root_dir = self.root_dir if (a:pattern =~# '\v^[/\\]') let l:root_dir = s:stripslash(l:root_dir) endif let l:matches = split(glob(l:root_dir . a:pattern), '\n') if a:0 && a:1 for l:idx in range(len(l:matches)) if !filereadable(l:matches[l:idx]) let l:matches[l:idx] = l:matches[l:idx] . '/' endif endfor endif let l:strip_len = len(l:root_dir) call map(l:matches, 'v:val[l:strip_len : -1]') return l:matches endfunction " Gets a full Mercurial command. function! s:HgRepo.GetCommand(command, ...) abort " If there's only one argument, and it's a list, then use that as the " argument list. let l:arg_list = a:000 if a:0 == 1 && type(a:1) == type([]) let l:arg_list = a:1 endif let l: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 return l:hg_command endfunction " Runs a Mercurial command in the repo. function! s:HgRepo.RunCommand(command, ...) abort 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) return system(l:hg_command) endfunction " Runs a Mercurial command in the repo and reads its output into the current " buffer. function! s:HgRepo.ReadCommandOutput(command, ...) abort function! s:PutOutputIntoBuffer(command_line) let l:was_buffer_empty = (line('$') == 1 && getline(1) == '') execute '0read!' . escape(a:command_line, '%#\') if l:was_buffer_empty " (Always true?) " '0read' inserts before the cursor, leaving a blank line which " needs to be deleted... but if there are folds in this thing, we " must open them all first otherwise we could delete the whole " contents of the last fold (since Vim may close them all by " default). normal! zRG"_dd endif endfunction let l:all_args = [a:command] + a:000 let l:hg_command = call(self['GetCommand'], l:all_args, self) call s:trace("Running Mercurial command: " . l:hg_command) call s:PutOutputIntoBuffer(l:hg_command) endfunction " Build a Lawrencium path for the given file and action. " By default, the given path will be made relative to the repository root, " unless '0' is passed as the 4th argument. function! s:HgRepo.GetLawrenciumPath(path, action, value, ...) abort let l:path = a:path if a:0 == 0 || !a:1 let l:path = self.GetRelativePath(a:path) endif let l:path = fnameescape(l:path) let l:result = 'lawrencium://' . s:stripslash(self.root_dir) . '//' . l:path if a:action !=? '' let l:result = l:result . '//' . a:action if a:value !=? '' let l:result = l:result . '=' . a:value endif endif return l:result endfunction " Repo cache map. let s:buffer_repos = {} " Get a cached repo. function! s:hg_repo(...) abort " Use the given path, or the mercurial directory of the current buffer. if a:0 == 0 if exists('b:mercurial_dir') let l:path = b:mercurial_dir else let l:path = s:find_repo_root(expand('%:p')) endif else let l:path = a:1 endif " Find a cache repo instance, or make a new one. if has_key(s:buffer_repos, l:path) return get(s:buffer_repos, l:path) else let l:repo = s:HgRepo.New(l:path) let s:buffer_repos[l:path] = l:repo return l:repo endif endfunction " Sets up the current buffer with Lawrencium commands if it contains a file from a Mercurial repo. " If the file is not in a Mercurial repo, just exit silently. function! s:setup_buffer_commands() abort call s:trace("Scanning buffer '" . bufname('%') . "' for Lawrencium setup...") let l:do_setup = 1 if exists('b:mercurial_dir') if b:mercurial_dir =~# '\v^\s*$' unlet b:mercurial_dir else let l:do_setup = 0 endif endif try let l:repo = s:hg_repo() catch /^lawrencium\:/ return endtry let b:mercurial_dir = l:repo.root_dir if exists('b:mercurial_dir') && l:do_setup call s:trace("Setting Mercurial commands for buffer '" . bufname('%')) call s:trace(" with repo : " . expand(b:mercurial_dir)) silent doautocmd User Lawrencium endif endfunction augroup lawrencium_detect autocmd! autocmd BufNewFile,BufReadPost * call s:setup_buffer_commands() autocmd VimEnter * if expand('<amatch>')==''|call s:setup_buffer_commands()|endif augroup end " }}} " Buffer Object {{{ " The prototype dictionary. let s:Buffer = {} " Constructor. function! s:Buffer.New(number) dict abort let l:newBuffer = copy(self) let l:newBuffer.nr = a:number let l:newBuffer.var_backup = {} let l:newBuffer.cmd_names = {} let l:newBuffer.on_delete = [] let l:newBuffer.on_winleave = [] let l:newBuffer.on_unload = [] execute 'augroup lawrencium_buffer_' . a:number execute ' autocmd!' execute ' autocmd BufDelete <buffer=' . a:number . '> call s:buffer_on_delete(' . a:number . ')' execute 'augroup end' call s:trace("Built new buffer object for buffer: " . a:number) return l:newBuffer endfunction function! s:Buffer.GetName(...) dict abort let l:name = bufname(self.nr) if a:0 > 0 let l:name = fnamemodify(l:name, a:1) endif return l:name endfunction function! s:Buffer.GetVar(var) dict abort return getbufvar(self.nr, a:var) endfunction function! s:Buffer.SetVar(var, value) dict abort if !has_key(self.var_backup, a:var) let self.var_backup[a:var] = getbufvar(self.nr, a:var) endif return setbufvar(self.nr, a:var, a:value) endfunction function! s:Buffer.RestoreVars() dict abort for key in keys(self.var_backup) setbufvar(self.nr, key, self.var_backup[key]) endfor endfunction function! s:Buffer.DefineCommand(name, ...) dict abort if a:0 == 0 call s:throw("Not enough parameters for s:Buffer.DefineCommands()") endif if a:0 == 1 let l:flags = '' let l:cmd = a:1 else let l:flags = a:1 let l:cmd = a:2 endif if has_key(self.cmd_names, a:name) call s:throw("Command '".a:name."' is already defined in buffer ".self.nr) endif if bufnr('%') != self.nr call s:throw("You must move to buffer ".self.nr."first before defining local commands") endif let self.cmd_names[a:name] = 1 let l:real_flags = '' if type(l:flags) == type('') let l:real_flags = l:flags endif execute 'command -buffer '.l:real_flags.' '.a:name.' '.l:cmd endfunction function! s:Buffer.DeleteCommand(name) dict abort if !has_key(self.cmd_names, a:name) call s:throw("Command '".a:name."' has not been defined in buffer ".self.nr) endif if bufnr('%') != self.nr call s:throw("You must move to buffer ".self.nr."first before deleting local commands") endif execute 'delcommand '.a:name call remove(self.cmd_names, a:name) endfunction function! s:Buffer.DeleteCommands() dict abort if bufnr('%') != self.nr call s:throw("You must move to buffer ".self.nr."first before deleting local commands") endif for name in keys(self.cmd_names) execute 'delcommand '.name endfor let self.cmd_names = {} endfunction function! s:Buffer.MoveToFirstWindow() dict abort let l:win_nr = bufwinnr(self.nr) if l:win_nr < 0 if a:0 > 0 && a:1 == 0 return 0 endif call s:throw("No windows currently showing buffer ".self.nr) endif execute l:win_nr.'wincmd w' return 1 endfunction function! s:Buffer.OnDelete(cmd) dict abort call s:trace("Adding BufDelete callback for buffer " . self.nr . ": " . a:cmd) call add(self.on_delete, a:cmd) endfunction function! s:Buffer.OnWinLeave(cmd) dict abort if len(self.on_winleave) == 0 call s:trace("Adding BufWinLeave auto-command on buffer " . self.nr) execute 'augroup lawrencium_buffer_' . self.nr . '_winleave' execute ' autocmd!' execute ' autocmd BufWinLeave <buffer=' . self.nr . '> call s:buffer_on_winleave(' . self.nr .')' execute 'augroup end' endif call s:trace("Adding BufWinLeave callback for buffer " . self.nr . ": " . a:cmd) call add(self.on_winleave, a:cmd) endfunction function! s:Buffer.OnUnload(cmd) dict abort if len(self.on_unload) == 0 call s:trace("Adding BufUnload auto-command on buffer " . self.nr) execute 'augroup lawrencium_buffer_' . self.nr . '_unload' execute ' autocmd!' execute ' autocmd BufUnload <buffer=' . self.nr . '> call s:buffer_on_unload(' . self.nr . ')' execute 'augroup end' endif call s:trace("Adding BufUnload callback for buffer " . self.nr . ": " . a:cmd) call add(self.on_unload, a:cmd) endfunction let s:buffer_objects = {} " Get a buffer instance for the specified buffer number, or the " current buffer if nothing is specified. function! s:buffer_obj(...) abort let l:bufnr = a:0 ? a:1 : bufnr('%') if !has_key(s:buffer_objects, l:bufnr) let s:buffer_objects[l:bufnr] = s:Buffer.New(l:bufnr) endif return s:buffer_objects[l:bufnr] endfunction " Execute all the "on delete" callbacks. function! s:buffer_on_delete(number) abort let l:bufobj = s:buffer_objects[a:number] call s:trace("Calling BufDelete callbacks on buffer " . l:bufobj.nr) for cmd in l:bufobj.on_delete call s:trace(" [" . cmd . "]") execute cmd endfor call s:trace("Deleted buffer object " . l:bufobj.nr) call remove(s:buffer_objects, l:bufobj.nr) execute 'augroup lawrencium_buffer_' . l:bufobj.nr execute ' autocmd!' execute 'augroup end' endfunction " Execute all the "on winleave" callbacks. function! s:buffer_on_winleave(number) abort let l:bufobj = s:buffer_objects[a:number] call s:trace("Calling BufWinLeave callbacks on buffer " . l:bufobj.nr) for cmd in l:bufobj.on_winleave call s:trace(" [" . cmd . "]") execute cmd endfor execute 'augroup lawrencium_buffer_' . l:bufobj.nr . '_winleave' execute ' autocmd!' execute 'augroup end' endfunction " Execute all the "on unload" callbacks. function! s:buffer_on_unload(number) abort let l:bufobj = s:buffer_objects[a:number] call s:trace("Calling BufUnload callbacks on buffer " . l:bufobj.nr) for cmd in l:bufobj.on_unload call s:trace(" [" . cmd . "]") execute cmd endfor execute 'augroup lawrencium_buffer_' . l:bufobj.nr . '_unload' execute ' autocmd!' execute 'augroup end' endfunction " }}} " Lawrencium Files {{{ " Read revision (`hg cat`) function! s:read_lawrencium_rev(repo, path_parts, full_path) abort let l:rev = a:path_parts['value'] if l:rev == '' call a:repo.ReadCommandOutput('cat', a:full_path) else call a:repo.ReadCommandOutput('cat', '-r', l:rev, a:full_path) endif endfunction " Status (`hg status`) function! s:read_lawrencium_status(repo, path_parts, full_path) abort if a:path_parts['path'] == '' call a:repo.ReadCommandOutput('status') else call a:repo.ReadCommandOutput('status', a:full_path) endif setlocal nomodified setlocal filetype=hgstatus setlocal bufhidden=delete setlocal buftype=nofile endfunction " Log (`hg log`) let s:log_style_file = expand("<sfile>:h:h") . "/resources/hg_log.style" function! s:read_lawrencium_log(repo, path_parts, full_path) abort let l:log_opts = join(split(a:path_parts['value'], ',')) let l:log_cmd = "log " . l:log_opts if a:path_parts['path'] == '' call a:repo.ReadCommandOutput(l:log_cmd, '--style', s:log_style_file) else call a:repo.ReadCommandOutput(l:log_cmd, '--style', s:log_style_file, a:full_path) endif setlocal filetype=hglog endfunction " 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'), \'diff': function('s:read_lawrencium_diff'), \'status': function('s:read_lawrencium_status'), \'annotate': function('s:read_lawrencium_annotate'), \'qseries': function('s:read_lawrencium_qseries') \} let s:lawrencium_file_customoptions = { \'status': 1 \} function! s:ReadLawrenciumFile(path) abort call s:trace("Reading Lawrencium file: " . a:path) let l:path_parts = s:parse_lawrencium_path(a:path) if l:path_parts['root'] == '' call s:throw("Can't get repository root from: " . a:path) endif if !has_key(s:lawrencium_file_readers, l:path_parts['action']) call s:throw("No registered reader for action: " . l:path_parts['action']) endif " Call the registered reader. let l:repo = s:hg_repo(l:path_parts['root']) let l:full_path = l:repo.root_dir . l:path_parts['path'] let LawrenciumFileReader = s:lawrencium_file_readers[l:path_parts['action']] call LawrenciumFileReader(l:repo, l:path_parts, l:full_path) " Setup the new buffer. if !has_key(s:lawrencium_file_customoptions, l:path_parts['action']) setlocal readonly setlocal nomodified setlocal bufhidden=delete setlocal buftype=nofile endif goto " Remember the real Lawrencium path, because Vim can fuck up the slashes " on Windows. let b:lawrencium_path = a:path " Remember the repo it belongs to and make " the Lawrencium commands available. let b:mercurial_dir = l:repo.root_dir call s:DefineMainCommands() return '' endfunction function! s:WriteLawrenciumFile(path) abort call s:trace("Writing Lawrencium file: " . a:path) endfunction augroup lawrencium_files autocmd! autocmd BufReadCmd lawrencium://**//**//* exe s:ReadLawrenciumFile(expand('<amatch>')) autocmd BufWriteCmd lawrencium://**//**//* exe s:WriteLawrenciumFile(expand('<amatch>')) augroup END " }}} " Buffer Commands Management {{{ " Store the commands for Lawrencium-enabled buffers so that we can add them in " batch when we need to. let s:main_commands = [] function! s:AddMainCommand(command) abort let s:main_commands += [a:command] endfunction function! s:DefineMainCommands() for l:command in s:main_commands execute 'command! -buffer ' . l:command endfor endfunction augroup lawrencium_main autocmd! autocmd User Lawrencium call s:DefineMainCommands() augroup end " }}} " Commands Auto-Complete {{{ " Auto-complete function for commands that take repo-relative file paths. function! s:ListRepoFiles(ArgLead, CmdLine, CursorPos) abort let l:matches = s:hg_repo().Glob(a:ArgLead . '*', 1) call map(l:matches, 's:normalizepath(v:val)') return l:matches endfunction " Auto-complete function for commands that take repo-relative directory paths. function! s:ListRepoDirs(ArgLead, CmdLine, CursorPos) abort let l:matches = s:hg_repo().Glob(a:ArgLead . '*/') call map(l:matches, 's:normalizepath(v:val)') return l:matches endfunction " }}} " Hg {{{ function! s:Hg(bang, ...) abort let l:repo = s:hg_repo() if g:lawrencium_auto_cd " Temporary set the current directory to the root of the repo " to make auto-completed paths work magically. execute 'cd! ' . fnameescape(l:repo.root_dir) endif let l:output = call(l:repo.RunCommand, 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_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() if g:lawrencium_define_mappings nnoremap <buffer> <silent> <cr> :Hgannotatediffsum<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}') " Get the Lawrencium path for the diff, and the buffer object for the " annotation. let l:repo = s:hg_repo() let l:path = l:repo.GetLawrenciumPath(b:lawrencium_annotated_path, 'diff', l:rev_hash) let l:annotate_buffer = s:buffer_obj() " Find a window already displaying diffs for this annotation. let l:diff_winnr = s:find_buffer_window('lawrencium_diff_for', l:annotate_buffer.nr) if l:diff_winnr == -1 " Not found... go back to the main source buffer and open a bottom " split with the diff for the specified revision. execute bufwinnr(b:lawrencium_annotated_bufnr) . 'wincmd w' execute 'rightbelow split ' . fnameescape(l:path) let b:lawrencium_diff_for = l:annotate_buffer.nr let b:lawrencium_quit_on_delete = 1 else " Found! Use that window to open the diff. execute l:diff_winnr . 'wincmd w' execute 'edit ' . fnameescape(l:path) let b:lawrencium_diff_for = l:annotate_buffer.nr endif endfunction call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoFiles Hgannotate :call s:HgAnnotate(<bang>0, 0, <f-args>)") call s:AddMainCommand("-bang -nargs=? -complete=customlist,s:ListRepoFiles Hgwannotate :call s:HgAnnotate(<bang>0, 1, <f-args>)") " }}} " Hgqseries {{{ function! s:HgQSeries() abort " Open the MQ series in the preview window and jump to it. let l:repo = s:hg_repo() let l:path = l:repo.GetLawrenciumPath('', 'qseries', '') execute 'pedit ' . fnameescape(l:path) wincmd P " Make the series buffer a Lawrencium buffer. let b:mercurial_dir = l:repo.root_dir call s:DefineMainCommands() " Add some commands and mappings. command! -buffer Hgqseriesgoto :call s:HgQSeries_Goto() command! -buffer Hgqserieseditmessage :call s:HgQSeries_EditMessage() command! -buffer -nargs=+ Hgqseriesrename :call s:HgQSeries_Rename(<f-args>) if g:lawrencium_define_mappings nnoremap <buffer> <silent> <C-g> :Hgqseriesgoto<cr> nnoremap <buffer> <silent> <C-e> :Hgqserieseditmessage<cr> nnoremap <buffer> <silent> q :bdelete!<cr> endif endfunction function! s:HgQSeries_GetCurrentPatchName() abort let l:pos = getpos('.') return getbufvar('%', 'lawrencium_patchname_' . l:pos[1]) endfunction function! s:HgQSeries_Goto() abort let l:repo = s:hg_repo() let l:patchname = s:HgQSeries_GetCurrentPatchName() if len(l:patchname) == 0 call s:error("No patch to go to here.") return endif call l:repo.RunCommand('qgoto', l:patchname) edit endfunction function! s:HgQSeries_Rename(...) abort let l:repo = s:hg_repo() let l:current_name = s:HgQSeries_GetCurrentPatchName() if len(l:current_name) == 0 call s:error("No patch to rename here.") return endif let l:new_name = '"' . join(a:000, ' ') . '"' call l:repo.RunCommand('qrename', l:current_name, l:new_name) edit endfunction function! s:HgQSeries_EditMessage() abort let l:repo = s:hg_repo() let l:patchname = getbufvar('%', 'lawrencium_patchname_top') if len(l:patchname) == 0 call s:error("No patch to edit here.") return endif let l:current = split(l:repo.RunCommand('qheader', l:patchname), '\n') " Open a temp file to write the commit message. let l:temp_file = s:tempname('hg-qrefedit-', '.txt') split execute 'edit ' . fnameescape(l:temp_file) call append(0, 'HG: Enter the new commit message for patch "' . l:patchname . '" here.\n') call append(0, '') call append(0, l:current) call cursor(1, 1) " Make it a temp buffer that will actually change the commit message " when it is saved and closed. let b:mercurial_dir = l:repo.root_dir let b:lawrencium_patchname = l:patchname setlocal bufhidden=delete setlocal filetype=hgcommit autocmd BufDelete <buffer> call s:HgQSeries_EditMessage_Execute(expand('<afile>:p')) call s:DefineMainCommands() endfunction function! s:HgQSeries_EditMessage_Execute(log_file) abort if !filereadable(a:log_file) call s:error("abort: Commit message not saved") return endif " Clean all the 'HG:' lines. let l:is_valid = s:clean_commit_file(a:log_file) if !l:is_valid call s:error("abort: Empty commit message") return endif " Get the repo and edit the given patch. let l:repo = s:hg_repo() let l:hg_args = ['-s', '-l', a:log_file] call l:repo.RunCommand('qref', l:hg_args) endfunction call s:AddMainCommand("Hgqseries call s:HgQSeries()") " }}} " Hgrecord {{{ function! s:HgRecord(split) abort let l:repo = s:hg_repo() let l:orig_buf = s:buffer_obj() let l:tmp_path = l:orig_buf.GetName(':p') . '~record' let l:diff_id = localtime() " Start diffing on the current file, enable some commands. call l:orig_buf.DefineCommand('Hgrecordabort', ':call s:HgRecord_Abort()') call l:orig_buf.DefineCommand('Hgrecordcommit', ':call s:HgRecord_Execute()') call s:HgDiff_DiffThis(l:diff_id) setlocal foldmethod=marker " Split the window and open the parent revision in the right or bottom " window. Keep the current buffer in the left or top window... we're going " to 'move' those changes into the parent revision. let l:cmd = 'keepalt rightbelow split ' if a:split == 1 let l:cmd = 'keepalt rightbelow vsplit ' endif let l:rev_path = l:repo.GetLawrenciumPath(expand('%'), 'rev', '') execute l:cmd . fnameescape(l:rev_path) " This new buffer with the parent revision is set as a Lawrencium buffer. " Let's save it to an actual file and reopen it like that (somehow we " could probably do it with `:saveas` instead but we'd need to reset a " bunch of other buffer settings, and Vim weirdly creates another backup " buffer when you do that). execute 'keepalt write! ' . fnameescape(l:tmp_path) execute 'keepalt edit! ' . fnameescape(l:tmp_path) setlocal bufhidden=delete let b:mercurial_dir = l:repo.root_dir let b:lawrencium_record_for = l:orig_buf.GetName(':p') let b:lawrencium_record_other_nr = l:orig_buf.nr let b:lawrencium_record_commit_split = !a:split call setbufvar(l:orig_buf.nr, 'lawrencium_record_for', '%') call setbufvar(l:orig_buf.nr, 'lawrencium_record_other_nr', bufnr('%')) " Hookup the commit and abort commands. let l:rec_buf = s:buffer_obj() call l:rec_buf.OnDelete('call s:HgRecord_Execute()') call l:rec_buf.DefineCommand('Hgrecordcommit', ':quit') call l:rec_buf.DefineCommand('Hgrecordabort', ':call s:HgRecord_Abort()') call s:DefineMainCommands() " Make it the other part of the diff. call s:HgDiff_DiffThis(l:diff_id) setlocal foldmethod=marker call l:rec_buf.SetVar('&filetype', l:orig_buf.GetVar('&filetype')) if g:lawrencium_record_start_in_working_buffer wincmd p endif endfunction function! s:HgRecord_Execute() abort if exists('b:lawrencium_record_abort') " Abort flag is set, let's just cleanup. let l:buf_nr = b:lawrencium_record_for == '%' ? bufnr('%') : \b:lawrencium_record_other_nr call s:HgRecord_CleanUp(l:buf_nr) call s:error("abort: User requested aborting the record operation.") return endif if !exists('b:lawrencium_record_for') call s:throw("This doesn't seem like a record buffer, something's wrong!") endif if b:lawrencium_record_for == '%' " Switch to the 'recording' buffer's window. let l:buf_obj = s:buffer_obj(b:lawrencium_record_other_nr) call l:buf_obj.MoveToFirstWindow() endif " Setup the commit operation. let l:split = b:lawrencium_record_commit_split let l:working_bufnr = b:lawrencium_record_other_nr let l:working_path = fnameescape(b:lawrencium_record_for) let l:record_path = fnameescape(expand('%:p')) let l:callbacks = [ \'call s:HgRecord_PostExecutePre('.l:working_bufnr.', "'. \escape(l:working_path, '\').'", "'. \escape(l:record_path, '\').'")', \'call s:HgRecord_PostExecutePost('.l:working_bufnr.', "'. \escape(l:working_path, '\').'")', \'call s:HgRecord_PostExecuteAbort('.l:working_bufnr.', "'. \escape(l:record_path, '\').'")' \] call s:trace("Starting commit flow with callbacks: ".string(l:callbacks)) call s:HgCommit(0, l:split, l:callbacks, b:lawrencium_record_for) endfunction function! s:HgRecord_PostExecutePre(working_bufnr, working_path, record_path) abort " Just before committing, we switch the original file with the record " file... we'll restore things in the post-callback below. " We also switch on 'autoread' temporarily on the working buffer so that " we don't have an annoying popup in gVim. if has('dialog_gui') call setbufvar(a:working_bufnr, '&autoread', 1) endif call s:trace("Backuping original file: ".a:working_path) silent call rename(a:working_path, a:working_path.'~working') call s:trace("Committing recorded changes using: ".a:record_path) silent call rename(a:record_path, a:working_path) sleep 200m endfunction function! s:HgRecord_PostExecutePost(working_bufnr, working_path) abort " Recover the back-up file from underneath the buffer. call s:trace("Recovering original file: ".a:working_path) silent call rename(a:working_path.'~working', a:working_path) " Clean up! call s:HgRecord_CleanUp(a:working_bufnr) " Restore default 'autoread'. if has('dialog_gui') set autoread< endif endfunction function! s:HgRecord_PostExecuteAbort(working_bufnr, record_path) abort call s:HgRecord_CleanUp(a:working_bufnr) call s:trace("Delete discarded record file: ".a:record_path) silent call delete(a:record_path) endfunction function! s:HgRecord_Abort() abort if b:lawrencium_record_for == '%' " We're in the working directory buffer. Switch to the 'recording' " buffer and quit. let l:buf_obj = s:buffer_obj(b:lawrencium_record_other_nr) call l:buf_obj.MoveToFirstWindow() endif " We're now in the 'recording' buffer... set the abort flag and quit, " which will run the execution (it will early out and clean things up). let b:lawrencium_record_abort = 1 quit! endfunction function! s:HgRecord_CleanUp(buf_nr) abort " Get in the original buffer and clean the local commands/variables. let l:buf_obj = s:buffer_obj(a:buf_nr) call l:buf_obj.MoveToFirstWindow() if !exists('b:lawrencium_record_for') || b:lawrencium_record_for != '%' call s:throw("Cleaning up something else than the original buffer ". \"for a record operation. That's suspiciously incorrect! ". \"Aborting.") endif call l:buf_obj.DeleteCommand('Hgrecordabort') call l:buf_obj.DeleteCommand('Hgrecordcommit') unlet b:lawrencium_record_for unlet b:lawrencium_record_other_nr endfunction call s:AddMainCommand("Hgrecord call s:HgRecord(0)") call s:AddMainCommand("Hgvrecord call s:HgRecord(1)") " }}} " Autoload Functions {{{ " Prints a summary of the current repo (if any) that's appropriate for " displaying on the status line. function! lawrencium#statusline(...) if !exists('b:mercurial_dir') return '' endif let l:repo = s:hg_repo() let l:prefix = (a:0 > 0 ? a:1 : '') let l:suffix = (a:0 > 1 ? a:2 : '') let l:branch = 'default' let l:branch_file = l:repo.GetFullPath('.hg/branch') if filereadable(l:branch_file) let l:branch = readfile(l:branch_file)[0] endif let l:bookmarks = '' let l:bookmarks_file = l:repo.GetFullPath('.hg/bookmarks.current') if filereadable(l:bookmarks_file) let l:bookmarks = join(readfile(l:bookmarks_file), ', ') endif let l:line = l:prefix . l:branch if strlen(l:bookmarks) > 0 let l:line = l:line . ' - ' . l:bookmarks endif let l:line = l:line . l:suffix return l:line endfunction " Rescans the current buffer for setting up Mercurial commands. " Passing '1' as the parameter enables debug traces temporarily. function! lawrencium#rescan(...) if exists('b:mercurial_dir') unlet b:mercurial_dir endif if a:0 && a:1 let l:trace_backup = g:lawrencium_trace let g:lawrencium_trace = 1 endif call s:setup_buffer_commands() if a:0 && a:1 let g:lawrencium_trace = l:trace_backup endif endfunction " Enables/disables the debug trace. function! lawrencium#debugtrace(...) let g:lawrencium_trace = (a:0 == 0 || (a:0 && a:1)) echom "Lawrencium debug trace is now " . (g:lawrencium_trace ? "enabled." : "disabled.") endfunction " }}}