Mercurial > hg > mercurial-crew-with-dirclash
diff contrib/vim/hgcommand.vim @ 2593:0a30407fff72
merge with crew.
author | Vadim Gelfer <vadim.gelfer@gmail.com> |
---|---|
date | Tue, 11 Jul 2006 13:47:51 -0700 |
parents | 457846f400e8 |
children | f80057407c07 |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/contrib/vim/hgcommand.vim @@ -0,0 +1,1265 @@ +" vim600: set foldmethod=marker: +" +" Vim plugin to assist in working with HG-controlled files. +" +" Last Change: 2006/02/22 +" Version: 1.76 +" Maintainer: Mathieu Clabaut <mathieu.clabaut@gmail.com> +" License: This file is placed in the public domain. +" Credits: {{{1 +" Bob Hiestand <bob.hiestand@gmail.com> for the fabulous +" cvscommand.vim from which this script was directly created by +" means of sed commands and minor tweaks. + +" Section: Documentation {{{1 +" +" Provides functions to invoke various HG commands on the current file +" (either the current buffer, or, in the case of an directory buffer, the file +" on the current line). The output of the commands is captured in a new +" scratch window. For convenience, if the functions are invoked on a HG +" output window, the original file is used for the hg operation instead after +" the window is split. This is primarily useful when running HGCommit and +" you need to see the changes made, so that HGDiff is usable and shows up in +" another window. +" +" Command documentation {{{2 +" +" HGAdd Performs "hg add" on the current file. +" +" HGAnnotate Performs "hg annotate" on the current file. If an +" argument is given, the argument is used as a revision +" number to display. If not given an argument, it uses the +" most recent version of the file on the current branch. +" Additionally, if the current buffer is a HGAnnotate buffer +" already, the version number on the current line is used. +" +" If the 'HGCommandAnnotateParent' variable is set to a +" non-zero value, the version previous to the one on the +" current line is used instead. This allows one to navigate +" back to examine the previous version of a line. +" +" HGCommit[!] If called with arguments, this performs "hg commit" using +" the arguments as the log message. +" +" If '!' is used, an empty log message is committed. +" +" If called with no arguments, this is a two-step command. +" The first step opens a buffer to accept a log message. +" When that buffer is written, it is automatically closed and +" the file is committed using the information from that log +" message. The commit can be abandoned if the log message +" buffer is deleted or wiped before being written. +" +" HGDiff With no arguments, this performs "hg diff" on the current +" file. With one argument, "hg diff" is performed on the +" current file against the specified revision. With two +" arguments, hg diff is performed between the specified +" revisions of the current file. This command uses the +" 'HGCommandDiffOpt' variable to specify diff options. If +" that variable does not exist, then 'wbBc' is assumed. If +" you wish to have no options, then set it to the empty +" string. +" +" HGGotoOriginal Returns the current window to the source buffer if the +" current buffer is a HG output buffer. +" +" HGLog Performs "hg log" on the current file. +" +" HGRevert Replaces the modified version of the current file with the +" most recent version from the repository. +" +" HGReview Retrieves a particular version of the current file. If no +" argument is given, the most recent version of the file on +" the current branch is retrieved. The specified revision is +" retrieved into a new buffer. +" +" HGStatus Performs "hg status" on the current file. +" +" HGUpdate Performs "hg update" on the current file. +" +" HGVimDiff With no arguments, this prompts the user for a revision and +" then uses vimdiff to display the differences between the +" current file and the specified revision. If no revision is +" specified, the most recent version of the file on the +" current branch is used. With one argument, that argument +" is used as the revision as above. With two arguments, the +" differences between the two revisions is displayed using +" vimdiff. +" +" With either zero or one argument, the original buffer is used +" to perform the vimdiff. When the other buffer is closed, the +" original buffer will be returned to normal mode. +" +" Once vimdiff mode is started using the above methods, +" additional vimdiff buffers may be added by passing a single +" version argument to the command. There may be up to 4 +" vimdiff buffers total. +" +" Using the 2-argument form of the command resets the vimdiff +" to only those 2 versions. Additionally, invoking the +" command on a different file will close the previous vimdiff +" buffers. +" +" +" Mapping documentation: {{{2 +" +" By default, a mapping is defined for each command. User-provided mappings +" can be used instead by mapping to <Plug>CommandName, for instance: +" +" nnoremap ,ca <Plug>HGAdd +" +" The default mappings are as follow: +" +" <Leader>hga HGAdd +" <Leader>hgn HGAnnotate +" <Leader>hgc HGCommit +" <Leader>hgd HGDiff +" <Leader>hgg HGGotoOriginal +" <Leader>hgG HGGotoOriginal! +" <Leader>hgl HGLog +" <Leader>hgr HGReview +" <Leader>hgs HGStatus +" <Leader>hgu HGUpdate +" <Leader>hgv HGVimDiff +" +" Options documentation: {{{2 +" +" Several variables are checked by the script to determine behavior as follow: +" +" HGCommandAnnotateParent +" This variable, if set to a non-zero value, causes the zero-argument form +" of HGAnnotate when invoked on a HGAnnotate buffer to go to the version +" previous to that displayed on the current line. If not set, it defaults +" to 0. +" +" HGCommandCommitOnWrite +" This variable, if set to a non-zero value, causes the pending hg commit +" to take place immediately as soon as the log message buffer is written. +" If set to zero, only the HGCommit mapping will cause the pending commit +" to occur. If not set, it defaults to 1. +" +" HGCommandDeleteOnHide +" This variable, if set to a non-zero value, causes the temporary HG result +" buffers to automatically delete themselves when hidden. +" +" HGCommandDiffOpt +" This variable, if set, determines the options passed to the diff command +" of HG. If not set, it defaults to 'wbBc'. +" +" HGCommandDiffSplit +" This variable overrides the HGCommandSplit variable, but only for buffers +" created with HGVimDiff. +" +" HGCommandEdit +" This variable controls whether the original buffer is replaced ('edit') or +" split ('split'). If not set, it defaults to 'edit'. +" +" HGCommandEnableBufferSetup +" This variable, if set to a non-zero value, activates HG buffer management +" mode. This mode means that two buffer variables, 'HGRevision' and +" 'HGBranch', are set if the file is HG-controlled. This is useful for +" displaying version information in the status bar. +" +" HGCommandInteractive +" This variable, if set to a non-zero value, causes appropriate functions (for +" the moment, only HGReview) to query the user for a revision to use +" instead of the current revision if none is specified. +" +" HGCommandNameMarker +" This variable, if set, configures the special attention-getting characters +" that appear on either side of the hg buffer type in the buffer name. +" This has no effect unless 'HGCommandNameResultBuffers' is set to a true +" value. If not set, it defaults to '_'. +" +" HGCommandNameResultBuffers +" This variable, if set to a true value, causes the hg result buffers to be +" named in the old way ('<source file name> _<hg command>_'). If not set +" or set to a false value, the result buffer is nameless. +" +" HGCommandSplit +" This variable controls the orientation of the various window splits that +" may occur (such as with HGVimDiff, when using a HG command on a HG +" command buffer, or when the 'HGCommandEdit' variable is set to 'split'. +" If set to 'horizontal', the resulting windows will be on stacked on top of +" one another. If set to 'vertical', the resulting windows will be +" side-by-side. If not set, it defaults to 'horizontal' for all but +" HGVimDiff windows. +" +" Event documentation {{{2 +" For additional customization, hgcommand.vim uses User event autocommand +" hooks. Each event is in the HGCommand group, and different patterns +" match the various hooks. +" +" For instance, the following could be added to the vimrc to provide a 'q' +" mapping to quit a HG buffer: +" +" augroup HGCommand +" au HGCommand User HGBufferCreated silent! nmap <unique> <buffer> q :bwipeout<cr> +" augroup END +" +" The following hooks are available: +" +" HGBufferCreated This event is fired just after a hg command +" result buffer is created and filled with the +" result of a hg command. It is executed within +" the context of the new buffer. +" +" HGBufferSetup This event is fired just after HG buffer setup +" occurs, if enabled. +" +" HGPluginInit This event is fired when the HGCommand plugin +" first loads. +" +" HGPluginFinish This event is fired just after the HGCommand +" plugin loads. +" +" HGVimDiffFinish This event is fired just after the HGVimDiff +" command executes to allow customization of, +" for instance, window placement and focus. +" +" Section: Plugin header {{{1 + +" loaded_hgcommand is set to 1 when the initialization begins, and 2 when it +" completes. This allows various actions to only be taken by functions after +" system initialization. + +if exists("loaded_hgcommand") + finish +endif +let loaded_hgcommand = 1 + +if v:version < 602 + echohl WarningMsg|echomsg "HGCommand 1.69 or later requires VIM 6.2 or later"|echohl None + finish +endif + +" Section: Event group setup {{{1 + +augroup HGCommand +augroup END + +" Section: Plugin initialization {{{1 +silent do HGCommand User HGPluginInit + +" Section: Script variable initialization {{{1 + +let s:HGCommandEditFileRunning = 0 +unlet! s:vimDiffRestoreCmd +unlet! s:vimDiffSourceBuffer +unlet! s:vimDiffBufferCount +unlet! s:vimDiffScratchList + +" Section: Utility functions {{{1 + +" Function: s:HGResolveLink() {{{2 +" Fully resolve the given file name to remove shortcuts or symbolic links. + +function! s:HGResolveLink(fileName) + let resolved = resolve(a:fileName) + if resolved != a:fileName + let resolved = s:HGResolveLink(resolved) + endif + return resolved +endfunction + +" Function: s:HGChangeToCurrentFileDir() {{{2 +" Go to the directory in which the current HG-controlled file is located. +" If this is a HG command buffer, first switch to the original file. + +function! s:HGChangeToCurrentFileDir(fileName) + let oldCwd=getcwd() + let fileName=s:HGResolveLink(a:fileName) + let newCwd=fnamemodify(fileName, ':h') + if strlen(newCwd) > 0 + execute 'cd' escape(newCwd, ' ') + endif + return oldCwd +endfunction + +" Function: s:HGGetOption(name, default) {{{2 +" Grab a user-specified option to override the default provided. Options are +" searched in the window, buffer, then global spaces. + +function! s:HGGetOption(name, default) + if exists("s:" . a:name . "Override") + execute "return s:".a:name."Override" + elseif exists("w:" . a:name) + execute "return w:".a:name + elseif exists("b:" . a:name) + execute "return b:".a:name + elseif exists("g:" . a:name) + execute "return g:".a:name + else + return a:default + endif +endfunction + +" Function: s:HGEditFile(name, origBuffNR) {{{2 +" Wrapper around the 'edit' command to provide some helpful error text if the +" current buffer can't be abandoned. If name is provided, it is used; +" otherwise, a nameless scratch buffer is used. +" Returns: 0 if successful, -1 if an error occurs. + +function! s:HGEditFile(name, origBuffNR) + "Name parameter will be pasted into expression. + let name = escape(a:name, ' *?\') + + let editCommand = s:HGGetOption('HGCommandEdit', 'edit') + if editCommand != 'edit' + if s:HGGetOption('HGCommandSplit', 'horizontal') == 'horizontal' + if name == "" + let editCommand = 'rightbelow new' + else + let editCommand = 'rightbelow split ' . name + endif + else + if name == "" + let editCommand = 'vert rightbelow new' + else + let editCommand = 'vert rightbelow split ' . name + endif + endif + else + if name == "" + let editCommand = 'enew' + else + let editCommand = 'edit ' . name + endif + endif + + " Protect against useless buffer set-up + let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1 + try + execute editCommand + finally + let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1 + endtry + + let b:HGOrigBuffNR=a:origBuffNR + let b:HGCommandEdit='split' +endfunction + +" Function: s:HGCreateCommandBuffer(cmd, cmdName, statusText, filename) {{{2 +" Creates a new scratch buffer and captures the output from execution of the +" given command. The name of the scratch buffer is returned. + +function! s:HGCreateCommandBuffer(cmd, cmdName, statusText, origBuffNR) + let fileName=bufname(a:origBuffNR) + + let resultBufferName='' + + if s:HGGetOption("HGCommandNameResultBuffers", 0) + let nameMarker = s:HGGetOption("HGCommandNameMarker", '_') + if strlen(a:statusText) > 0 + let bufName=a:cmdName . ' -- ' . a:statusText + else + let bufName=a:cmdName + endif + let bufName=fileName . ' ' . nameMarker . bufName . nameMarker + let counter=0 + let resultBufferName = bufName + while buflisted(resultBufferName) + let counter=counter + 1 + let resultBufferName=bufName . ' (' . counter . ')' + endwhile + endif + + let hgCommand = s:HGGetOption("HGCommandHGExec", "hg") . " " . a:cmd + echomsg "DBG :".hgCommand + let hgOut = system(hgCommand) + " HACK: diff command does not return proper error codes + if v:shell_error && a:cmdName != 'hgdiff' + if strlen(hgOut) == 0 + echoerr "HG command failed" + else + echoerr "HG command failed: " . hgOut + endif + return -1 + endif + if strlen(hgOut) == 0 + " Handle case of no output. In this case, it is important to check the + " file status, especially since hg edit/unedit may change the attributes + " of the file with no visible output. + + echomsg "No output from HG command" + checktime + return -1 + endif + + if s:HGEditFile(resultBufferName, a:origBuffNR) == -1 + return -1 + endif + + set buftype=nofile + set noswapfile + set filetype= + + if s:HGGetOption("HGCommandDeleteOnHide", 0) + set bufhidden=delete + endif + + silent 0put=hgOut + + " The last command left a blank line at the end of the buffer. If the + " last line is folded (a side effect of the 'put') then the attempt to + " remove the blank line will kill the last fold. + " + " This could be fixed by explicitly detecting whether the last line is + " within a fold, but I prefer to simply unfold the result buffer altogether. + + if has('folding') + normal zR + endif + + $d + 1 + + " Define the environment and execute user-defined hooks. + + let b:HGSourceFile=fileName + let b:HGCommand=a:cmdName + if a:statusText != "" + let b:HGStatusText=a:statusText + endif + + silent do HGCommand User HGBufferCreated + return bufnr("%") +endfunction + +" Function: s:HGBufferCheck(hgBuffer) {{{2 +" Attempts to locate the original file to which HG operations were applied +" for a given buffer. + +function! s:HGBufferCheck(hgBuffer) + let origBuffer = getbufvar(a:hgBuffer, "HGOrigBuffNR") + if origBuffer + if bufexists(origBuffer) + return origBuffer + else + " Original buffer no longer exists. + return -1 + endif + else + " No original buffer + return a:hgBuffer + endif +endfunction + +" Function: s:HGCurrentBufferCheck() {{{2 +" Attempts to locate the original file to which HG operations were applied +" for the current buffer. + +function! s:HGCurrentBufferCheck() + return s:HGBufferCheck(bufnr("%")) +endfunction + +" Function: s:HGToggleDeleteOnHide() {{{2 +" Toggles on and off the delete-on-hide behavior of HG buffers + +function! s:HGToggleDeleteOnHide() + if exists("g:HGCommandDeleteOnHide") + unlet g:HGCommandDeleteOnHide + else + let g:HGCommandDeleteOnHide=1 + endif +endfunction + +" Function: s:HGDoCommand(hgcmd, cmdName, statusText) {{{2 +" General skeleton for HG function execution. +" Returns: name of the new command buffer containing the command results + +function! s:HGDoCommand(cmd, cmdName, statusText) + let hgBufferCheck=s:HGCurrentBufferCheck() + if hgBufferCheck == -1 + echo "Original buffer no longer exists, aborting." + return -1 + endif + + let fileName=bufname(hgBufferCheck) + if isdirectory(fileName) + let fileName=fileName . "/" . getline(".") + endif + let realFileName = fnamemodify(s:HGResolveLink(fileName), ':t') + let oldCwd=s:HGChangeToCurrentFileDir(fileName) + try + " TODO + "if !filereadable('HG/Root') + "throw fileName . ' is not a HG-controlled file.' + "endif + let fullCmd = a:cmd . ' "' . realFileName . '"' + "echomsg "DEBUG".fullCmd + let resultBuffer=s:HGCreateCommandBuffer(fullCmd, a:cmdName, a:statusText, hgBufferCheck) + return resultBuffer + catch + echoerr v:exception + return -1 + finally + execute 'cd' escape(oldCwd, ' ') + endtry +endfunction + + +" Function: s:HGGetStatusVars(revision, branch, repository) {{{2 +" +" Obtains a HG revision number and branch name. The 'revisionVar', +" 'branchVar'and 'repositoryVar' arguments, if non-empty, contain the names of variables to hold +" the corresponding results. +" +" Returns: string to be exec'd that sets the multiple return values. + +function! s:HGGetStatusVars(revisionVar, branchVar, repositoryVar) + let hgBufferCheck=s:HGCurrentBufferCheck() + if hgBufferCheck == -1 + return "" + endif + let fileName=bufname(hgBufferCheck) + let realFileName = fnamemodify(s:HGResolveLink(fileName), ':t') + let oldCwd=s:HGChangeToCurrentFileDir(fileName) + try + ""TODO + "if !filereadable('HG/Root') + "return "" + "endif + let hgCommand = s:HGGetOption("HGCommandHGExec", "hg") . " status -mardui " . fileName + let statustext=system(hgCommand) + if(v:shell_error) + return "" + endif + if match(statustext, '^[?I]') >= 0 + let revision="NEW" + elseif match(statustext, '^[R]') >= 0 + let revision="REMOVED" + elseif match(statustext, '^[D]') >= 0 + let revision="DELETED" + elseif match(statustext, '^[A]') >= 0 + let revision="ADDED" + endif + + let hgCommand = s:HGGetOption("HGCommandHGExec", "hg") . " parents -b " + let statustext=system(hgCommand) + if(v:shell_error) + return "" + endif + if exists('revision') + let returnExpression = "let " . a:revisionVar . "='" . revision . "'" + else + let revision=substitute(statustext, '^changeset:\s*\(\d\+\):.*\_$\_.*$', '\1', "") + let returnExpression = "let " . a:revisionVar . "='" . revision . "'" + endif + + if a:branchVar != "" && match(statustext, '^\_.*\_^branch:') >= 0 + let branch=substitute(statustext, '^\_.*\_^branch:\s*\(\S\+\)\n\_.*$', '\1', "") + let returnExpression=returnExpression . " | let " . a:branchVar . "='" . branch . "'" + endif + if a:repositoryVar != "" + let hgCommand = s:HGGetOption("HGCommandHGExec", "hg") . " root " + let roottext=system(hgCommand) + let repository=substitute(roottext,'^.*/\([^/\n\r]*\)\n\_.*$','\1','') + let returnExpression=returnExpression . " | let " . a:repositoryVar . "='" . repository . "'" + endif + + + + return returnExpression + finally + execute 'cd' escape(oldCwd, ' ') + endtry +endfunction + +" Function: s:HGSetupBuffer() {{{2 +" Attempts to set the b:HGBranch, b:HGRevision and b:HGRepository variables. + +function! s:HGSetupBuffer() + if (exists("b:HGBufferSetup") && b:HGBufferSetup) + " This buffer is already set up. + return + endif + + if !s:HGGetOption("HGCommandEnableBufferSetup", 0) + \ || @% == "" + \ || s:HGCommandEditFileRunning > 0 + \ || exists("b:HGOrigBuffNR") + unlet! b:HGRevision + unlet! b:HGBranch + unlet! b:HGRepository + return + endif + + if !filereadable(expand("%")) + return -1 + endif + + let revision="" + let branch="" + let repository="" + + exec s:HGGetStatusVars('revision', 'branch', 'repository') + "echomsg "DBG ".revision."#".branch."#".repository + if revision != "" + let b:HGRevision=revision + else + unlet! b:HGRevision + endif + if branch != "" + let b:HGBranch=branch + else + unlet! b:HGBranch + endif + if repository != "" + let b:HGRepository=repository + else + unlet! b:HGRepository + endif + silent do HGCommand User HGBufferSetup + let b:HGBufferSetup=1 +endfunction + +" Function: s:HGMarkOrigBufferForSetup(hgbuffer) {{{2 +" Resets the buffer setup state of the original buffer for a given HG buffer. +" Returns: The HG buffer number in a passthrough mode. + +function! s:HGMarkOrigBufferForSetup(hgBuffer) + checktime + if a:hgBuffer != -1 + let origBuffer = s:HGBufferCheck(a:hgBuffer) + "This should never not work, but I'm paranoid + if origBuffer != a:hgBuffer + call setbufvar(origBuffer, "HGBufferSetup", 0) + endif + endif + return a:hgBuffer +endfunction + +" Function: s:HGOverrideOption(option, [value]) {{{2 +" Provides a temporary override for the given HG option. If no value is +" passed, the override is disabled. + +function! s:HGOverrideOption(option, ...) + if a:0 == 0 + unlet! s:{a:option}Override + else + let s:{a:option}Override = a:1 + endif +endfunction + +" Function: s:HGWipeoutCommandBuffers() {{{2 +" Clears all current HG buffers of the specified type for a given source. + +function! s:HGWipeoutCommandBuffers(originalBuffer, hgCommand) + let buffer = 1 + while buffer <= bufnr('$') + if getbufvar(buffer, 'HGOrigBuffNR') == a:originalBuffer + if getbufvar(buffer, 'HGCommand') == a:hgCommand + execute 'bw' buffer + endif + endif + let buffer = buffer + 1 + endwhile +endfunction + +" Section: Public functions {{{1 + +" Function: HGGetRevision() {{{2 +" Global function for retrieving the current buffer's HG revision number. +" Returns: Revision number or an empty string if an error occurs. + +function! HGGetRevision() + let revision="" + exec s:HGGetStatusVars('revision', '', '') + return revision +endfunction + +" Function: HGDisableBufferSetup() {{{2 +" Global function for deactivating the buffer autovariables. + +function! HGDisableBufferSetup() + let g:HGCommandEnableBufferSetup=0 + silent! augroup! HGCommandPlugin +endfunction + +" Function: HGEnableBufferSetup() {{{2 +" Global function for activating the buffer autovariables. + +function! HGEnableBufferSetup() + let g:HGCommandEnableBufferSetup=1 + augroup HGCommandPlugin + au! + au BufEnter * call s:HGSetupBuffer() + augroup END + + " Only auto-load if the plugin is fully loaded. This gives other plugins a + " chance to run. + if g:loaded_hgcommand == 2 + call s:HGSetupBuffer() + endif +endfunction + +" Function: HGGetStatusLine() {{{2 +" Default (sample) status line entry for HG files. This is only useful if +" HG-managed buffer mode is on (see the HGCommandEnableBufferSetup variable +" for how to do this). + +function! HGGetStatusLine() + if exists('b:HGSourceFile') + " This is a result buffer + let value='[' . b:HGCommand . ' ' . b:HGSourceFile + if exists('b:HGStatusText') + let value=value . ' ' . b:HGStatusText + endif + let value = value . ']' + return value + endif + + if exists('b:HGRevision') + \ && b:HGRevision != '' + \ && exists('b:HGBranch') + \ && b:HGBranch != '' + \ && exists('b:HGRepository') + \ && b:HGRepository != '' + \ && exists('g:HGCommandEnableBufferSetup') + \ && g:HGCommandEnableBufferSetup + return '[HG ' . b:HGRepository . '/' . b:HGBranch .'/' . b:HGRevision . ']' + else + return '' + endif +endfunction + +" Section: HG command functions {{{1 + +" Function: s:HGAdd() {{{2 +function! s:HGAdd() + return s:HGMarkOrigBufferForSetup(s:HGDoCommand('add', 'hgadd', '')) +endfunction + +" Function: s:HGAnnotate(...) {{{2 +function! s:HGAnnotate(...) + if a:0 == 0 + if &filetype == "HGAnnotate" + " This is a HGAnnotate buffer. Perform annotation of the version + " indicated by the current line. + let revision = substitute(getline("."),'\(^[0-9]*\):.*','\1','') + if s:HGGetOption('HGCommandAnnotateParent', 0) != 0 && revision > 0 + let revision = revision - 1 + endif + else + let revision=HGGetRevision() + if revision == "" + echoerr "Unable to obtain HG version information." + return -1 + endif + endif + else + let revision=a:1 + endif + + if revision == "NEW" + echo "No annotatation available for new file." + return -1 + endif + + let resultBuffer=s:HGDoCommand('annotate -ndu -r ' . revision, 'hgannotate', revision) + echomsg "DBG: ".resultBuffer + if resultBuffer != -1 + set filetype=HGAnnotate + endif + + return resultBuffer +endfunction + +" Function: s:HGCommit() {{{2 +function! s:HGCommit(...) + " Handle the commit message being specified. If a message is supplied, it + " is used; if bang is supplied, an empty message is used; otherwise, the + " user is provided a buffer from which to edit the commit message. + if a:2 != "" || a:1 == "!" + return s:HGMarkOrigBufferForSetup(s:HGDoCommand('commit -m "' . a:2 . '"', 'hgcommit', '')) + endif + + let hgBufferCheck=s:HGCurrentBufferCheck() + if hgBufferCheck == -1 + echo "Original buffer no longer exists, aborting." + return -1 + endif + + " Protect against windows' backslashes in paths. They confuse exec'd + " commands. + + let shellSlashBak = &shellslash + try + set shellslash + + let messageFileName = tempname() + + let fileName=bufname(hgBufferCheck) + let realFilePath=s:HGResolveLink(fileName) + let newCwd=fnamemodify(realFilePath, ':h') + if strlen(newCwd) == 0 + " Account for autochdir being in effect, which will make this blank, but + " we know we'll be in the current directory for the original file. + let newCwd = getcwd() + endif + + let realFileName=fnamemodify(realFilePath, ':t') + + if s:HGEditFile(messageFileName, hgBufferCheck) == -1 + return + endif + + " Protect against case and backslash issues in Windows. + let autoPattern = '\c' . messageFileName + + " Ensure existance of group + augroup HGCommit + augroup END + + execute 'au HGCommit BufDelete' autoPattern 'call delete("' . messageFileName . '")' + execute 'au HGCommit BufDelete' autoPattern 'au! HGCommit * ' autoPattern + + " Create a commit mapping. The mapping must clear all autocommands in case + " it is invoked when HGCommandCommitOnWrite is active, as well as to not + " invoke the buffer deletion autocommand. + + execute 'nnoremap <silent> <buffer> <Plug>HGCommit '. + \ ':au! HGCommit * ' . autoPattern . '<CR>'. + \ ':g/^HG:/d<CR>'. + \ ':update<CR>'. + \ ':call <SID>HGFinishCommit("' . messageFileName . '",' . + \ '"' . newCwd . '",' . + \ '"' . realFileName . '",' . + \ hgBufferCheck . ')<CR>' + + silent 0put ='HG: ----------------------------------------------------------------------' + silent put =\"HG: Enter Log. Lines beginning with `HG:' are removed automatically\" + silent put ='HG: Type <leader>cc (or your own <Plug>HGCommit mapping)' + + if s:HGGetOption('HGCommandCommitOnWrite', 1) == 1 + execute 'au HGCommit BufWritePre' autoPattern 'g/^HG:/d' + execute 'au HGCommit BufWritePost' autoPattern 'call s:HGFinishCommit("' . messageFileName . '", "' . newCwd . '", "' . realFileName . '", ' . hgBufferCheck . ') | au! * ' autoPattern + silent put ='HG: or write this buffer' + endif + + silent put ='HG: to finish this commit operation' + silent put ='HG: ----------------------------------------------------------------------' + $ + let b:HGSourceFile=fileName + let b:HGCommand='HGCommit' + set filetype=hg + finally + let &shellslash = shellSlashBak + endtry + +endfunction + +" Function: s:HGDiff(...) {{{2 +function! s:HGDiff(...) + if a:0 == 1 + let revOptions = '-r' . a:1 + let caption = a:1 . ' -> current' + elseif a:0 == 2 + let revOptions = '-r' . a:1 . ' -r' . a:2 + let caption = a:1 . ' -> ' . a:2 + else + let revOptions = '' + let caption = '' + endif + + let hgdiffopt=s:HGGetOption('HGCommandDiffOpt', 'w') + + if hgdiffopt == "" + let diffoptionstring="" + else + let diffoptionstring=" -" . hgdiffopt . " " + endif + + let resultBuffer = s:HGDoCommand('diff ' . diffoptionstring . revOptions , 'hgdiff', caption) + if resultBuffer != -1 + set filetype=diff + endif + return resultBuffer +endfunction + + +" Function: s:HGGotoOriginal(["!]) {{{2 +function! s:HGGotoOriginal(...) + let origBuffNR = s:HGCurrentBufferCheck() + if origBuffNR > 0 + let origWinNR = bufwinnr(origBuffNR) + if origWinNR == -1 + execute 'buffer' origBuffNR + else + execute origWinNR . 'wincmd w' + endif + if a:0 == 1 + if a:1 == "!" + let buffnr = 1 + let buffmaxnr = bufnr("$") + while buffnr <= buffmaxnr + if getbufvar(buffnr, "HGOrigBuffNR") == origBuffNR + execute "bw" buffnr + endif + let buffnr = buffnr + 1 + endwhile + endif + endif + endif +endfunction + +" Function: s:HGFinishCommit(messageFile, targetDir, targetFile) {{{2 +function! s:HGFinishCommit(messageFile, targetDir, targetFile, origBuffNR) + if filereadable(a:messageFile) + let oldCwd=getcwd() + if strlen(a:targetDir) > 0 + execute 'cd' escape(a:targetDir, ' ') + endif + let resultBuffer=s:HGCreateCommandBuffer('commit -F "' . a:messageFile . '" "'. a:targetFile . '"', 'hgcommit', '', a:origBuffNR) + execute 'cd' escape(oldCwd, ' ') + execute 'bw' escape(a:messageFile, ' *?\') + silent execute 'call delete("' . a:messageFile . '")' + return s:HGMarkOrigBufferForSetup(resultBuffer) + else + echoerr "Can't read message file; no commit is possible." + return -1 + endif +endfunction + +" Function: s:HGLog() {{{2 +function! s:HGLog(...) + if a:0 == 0 + let versionOption = "" + let caption = '' + else + let versionOption=" -r" . a:1 + let caption = a:1 + endif + + let resultBuffer=s:HGDoCommand('log' . versionOption, 'hglog', caption) + if resultBuffer != "" + set filetype=rcslog + endif + return resultBuffer +endfunction + +" Function: s:HGRevert() {{{2 +function! s:HGRevert() + return s:HGMarkOrigBufferForSetup(s:HGDoCommand('revert', 'hgrevert', '')) +endfunction + +" Function: s:HGReview(...) {{{2 +function! s:HGReview(...) + if a:0 == 0 + let versiontag="" + if s:HGGetOption('HGCommandInteractive', 0) + let versiontag=input('Revision: ') + endif + if versiontag == "" + let versiontag="(current)" + let versionOption="" + else + let versionOption=" -r " . versiontag . " " + endif + else + let versiontag=a:1 + let versionOption=" -r " . versiontag . " " + endif + + let resultBuffer = s:HGDoCommand('cat' . versionOption, 'hgreview', versiontag) + if resultBuffer > 0 + let &filetype=getbufvar(b:HGOrigBuffNR, '&filetype') + endif + + return resultBuffer +endfunction + +" Function: s:HGStatus() {{{2 +function! s:HGStatus() + return s:HGDoCommand('status', 'hgstatus', '') +endfunction + + +" Function: s:HGUpdate() {{{2 +function! s:HGUpdate() + return s:HGMarkOrigBufferForSetup(s:HGDoCommand('update', 'update', '')) +endfunction + +" Function: s:HGVimDiff(...) {{{2 +function! s:HGVimDiff(...) + let originalBuffer = s:HGCurrentBufferCheck() + let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1 + try + " If there's already a VimDiff'ed window, restore it. + " There may only be one HGVimDiff original window at a time. + + if exists("s:vimDiffSourceBuffer") && s:vimDiffSourceBuffer != originalBuffer + " Clear the existing vimdiff setup by removing the result buffers. + call s:HGWipeoutCommandBuffers(s:vimDiffSourceBuffer, 'vimdiff') + endif + + " Split and diff + if(a:0 == 2) + " Reset the vimdiff system, as 2 explicit versions were provided. + if exists('s:vimDiffSourceBuffer') + call s:HGWipeoutCommandBuffers(s:vimDiffSourceBuffer, 'vimdiff') + endif + let resultBuffer = s:HGReview(a:1) + if resultBuffer < 0 + echomsg "Can't open HG revision " . a:1 + return resultBuffer + endif + let b:HGCommand = 'vimdiff' + diffthis + let s:vimDiffBufferCount = 1 + let s:vimDiffScratchList = '{'. resultBuffer . '}' + " If no split method is defined, cheat, and set it to vertical. + try + call s:HGOverrideOption('HGCommandSplit', s:HGGetOption('HGCommandDiffSplit', s:HGGetOption('HGCommandSplit', 'vertical'))) + let resultBuffer=s:HGReview(a:2) + finally + call s:HGOverrideOption('HGCommandSplit') + endtry + if resultBuffer < 0 + echomsg "Can't open HG revision " . a:1 + return resultBuffer + endif + let b:HGCommand = 'vimdiff' + diffthis + let s:vimDiffBufferCount = 2 + let s:vimDiffScratchList = s:vimDiffScratchList . '{'. resultBuffer . '}' + else + " Add new buffer + try + " Force splitting behavior, otherwise why use vimdiff? + call s:HGOverrideOption("HGCommandEdit", "split") + call s:HGOverrideOption("HGCommandSplit", s:HGGetOption('HGCommandDiffSplit', s:HGGetOption('HGCommandSplit', 'vertical'))) + if(a:0 == 0) + let resultBuffer=s:HGReview() + else + let resultBuffer=s:HGReview(a:1) + endif + finally + call s:HGOverrideOption("HGCommandEdit") + call s:HGOverrideOption("HGCommandSplit") + endtry + if resultBuffer < 0 + echomsg "Can't open current HG revision" + return resultBuffer + endif + let b:HGCommand = 'vimdiff' + diffthis + + if !exists('s:vimDiffBufferCount') + " New instance of vimdiff. + let s:vimDiffBufferCount = 2 + let s:vimDiffScratchList = '{' . resultBuffer . '}' + + " This could have been invoked on a HG result buffer, not the + " original buffer. + wincmd W + execute 'buffer' originalBuffer + " Store info for later original buffer restore + let s:vimDiffRestoreCmd = + \ "call setbufvar(".originalBuffer.", \"&diff\", ".getbufvar(originalBuffer, '&diff').")" + \ . "|call setbufvar(".originalBuffer.", \"&foldcolumn\", ".getbufvar(originalBuffer, '&foldcolumn').")" + \ . "|call setbufvar(".originalBuffer.", \"&foldenable\", ".getbufvar(originalBuffer, '&foldenable').")" + \ . "|call setbufvar(".originalBuffer.", \"&foldmethod\", '".getbufvar(originalBuffer, '&foldmethod')."')" + \ . "|call setbufvar(".originalBuffer.", \"&scrollbind\", ".getbufvar(originalBuffer, '&scrollbind').")" + \ . "|call setbufvar(".originalBuffer.", \"&wrap\", ".getbufvar(originalBuffer, '&wrap').")" + \ . "|if &foldmethod=='manual'|execute 'normal zE'|endif" + diffthis + wincmd w + else + " Adding a window to an existing vimdiff + let s:vimDiffBufferCount = s:vimDiffBufferCount + 1 + let s:vimDiffScratchList = s:vimDiffScratchList . '{' . resultBuffer . '}' + endif + endif + + let s:vimDiffSourceBuffer = originalBuffer + + " Avoid executing the modeline in the current buffer after the autocommand. + + let currentBuffer = bufnr('%') + let saveModeline = getbufvar(currentBuffer, '&modeline') + try + call setbufvar(currentBuffer, '&modeline', 0) + silent do HGCommand User HGVimDiffFinish + finally + call setbufvar(currentBuffer, '&modeline', saveModeline) + endtry + return resultBuffer + finally + let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1 + endtry +endfunction + +" Section: Command definitions {{{1 +" Section: Primary commands {{{2 +com! HGAdd call s:HGAdd() +com! -nargs=? HGAnnotate call s:HGAnnotate(<f-args>) +com! -bang -nargs=? HGCommit call s:HGCommit(<q-bang>, <q-args>) +com! -nargs=* HGDiff call s:HGDiff(<f-args>) +com! -bang HGGotoOriginal call s:HGGotoOriginal(<q-bang>) +com! -nargs=? HGLog call s:HGLog(<f-args>) +com! HGRevert call s:HGRevert() +com! -nargs=? HGReview call s:HGReview(<f-args>) +com! HGStatus call s:HGStatus() +com! HGUpdate call s:HGUpdate() +com! -nargs=* HGVimDiff call s:HGVimDiff(<f-args>) + +" Section: HG buffer management commands {{{2 +com! HGDisableBufferSetup call HGDisableBufferSetup() +com! HGEnableBufferSetup call HGEnableBufferSetup() + +" Allow reloading hgcommand.vim +com! HGReload unlet! loaded_hgcommand | runtime plugin/hgcommand.vim + +" Section: Plugin command mappings {{{1 +nnoremap <silent> <Plug>HGAdd :HGAdd<CR> +nnoremap <silent> <Plug>HGAnnotate :HGAnnotate<CR> +nnoremap <silent> <Plug>HGCommit :HGCommit<CR> +nnoremap <silent> <Plug>HGDiff :HGDiff<CR> +nnoremap <silent> <Plug>HGGotoOriginal :HGGotoOriginal<CR> +nnoremap <silent> <Plug>HGClearAndGotoOriginal :HGGotoOriginal!<CR> +nnoremap <silent> <Plug>HGLog :HGLog<CR> +nnoremap <silent> <Plug>HGRevert :HGRevert<CR> +nnoremap <silent> <Plug>HGReview :HGReview<CR> +nnoremap <silent> <Plug>HGStatus :HGStatus<CR> +nnoremap <silent> <Plug>HGUpdate :HGUpdate<CR> +nnoremap <silent> <Plug>HGVimDiff :HGVimDiff<CR> +nnoremap <silent> <Plug>HGWatchers :HGWatchers<CR> +nnoremap <silent> <Plug>HGWatchAdd :HGWatchAdd<CR> +nnoremap <silent> <Plug>HGWatchOn :HGWatchOn<CR> +nnoremap <silent> <Plug>HGWatchOff :HGWatchOff<CR> +nnoremap <silent> <Plug>HGWatchRemove :HGWatchRemove<CR> + +" Section: Default mappings {{{1 +if !hasmapto('<Plug>HGAdd') + nmap <unique> <Leader>hga <Plug>HGAdd +endif +if !hasmapto('<Plug>HGAnnotate') + nmap <unique> <Leader>hgn <Plug>HGAnnotate +endif +if !hasmapto('<Plug>HGClearAndGotoOriginal') + nmap <unique> <Leader>hgG <Plug>HGClearAndGotoOriginal +endif +if !hasmapto('<Plug>HGCommit') + nmap <unique> <Leader>hgc <Plug>HGCommit +endif +if !hasmapto('<Plug>HGDiff') + nmap <unique> <Leader>hgd <Plug>HGDiff +endif +if !hasmapto('<Plug>HGGotoOriginal') + nmap <unique> <Leader>hgg <Plug>HGGotoOriginal +endif +if !hasmapto('<Plug>HGLog') + nmap <unique> <Leader>hgl <Plug>HGLog +endif +if !hasmapto('<Plug>HGRevert') + nmap <unique> <Leader>hgq <Plug>HGRevert +endif +if !hasmapto('<Plug>HGReview') + nmap <unique> <Leader>hgr <Plug>HGReview +endif +if !hasmapto('<Plug>HGStatus') + nmap <unique> <Leader>hgs <Plug>HGStatus +endif +if !hasmapto('<Plug>HGUpdate') + nmap <unique> <Leader>hgu <Plug>HGUpdate +endif +if !hasmapto('<Plug>HGVimDiff') + nmap <unique> <Leader>hgv <Plug>HGVimDiff +endif + +" Section: Menu items {{{1 +silent! aunmenu Plugin.HG +amenu <silent> &Plugin.HG.&Add <Plug>HGAdd +amenu <silent> &Plugin.HG.A&nnotate <Plug>HGAnnotate +amenu <silent> &Plugin.HG.&Commit <Plug>HGCommit +amenu <silent> &Plugin.HG.&Diff <Plug>HGDiff +amenu <silent> &Plugin.HG.&Log <Plug>HGLog +amenu <silent> &Plugin.HG.Revert <Plug>HGRevert +amenu <silent> &Plugin.HG.&Review <Plug>HGReview +amenu <silent> &Plugin.HG.&Status <Plug>HGStatus +amenu <silent> &Plugin.HG.&Update <Plug>HGUpdate +amenu <silent> &Plugin.HG.&VimDiff <Plug>HGVimDiff +amenu <silent> &Plugin.HG.&Watchers <Plug>HGWatchers +amenu <silent> &Plugin.HG.WatchAdd <Plug>HGWatchAdd +amenu <silent> &Plugin.HG.WatchOn <Plug>HGWatchOn +amenu <silent> &Plugin.HG.WatchOff <Plug>HGWatchOff +amenu <silent> &Plugin.HG.WatchRemove <Plug>HGWatchRemove + +" Section: Autocommands to restore vimdiff state {{{1 +function! s:HGVimDiffRestore(vimDiffBuff) + let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1 + try + if exists("s:vimDiffSourceBuffer") + if a:vimDiffBuff == s:vimDiffSourceBuffer + " Original file is being removed. + unlet! s:vimDiffSourceBuffer + unlet! s:vimDiffBufferCount + unlet! s:vimDiffRestoreCmd + unlet! s:vimDiffScratchList + elseif match(s:vimDiffScratchList, '{' . a:vimDiffBuff . '}') >= 0 + let s:vimDiffScratchList = substitute(s:vimDiffScratchList, '{' . a:vimDiffBuff . '}', '', '') + let s:vimDiffBufferCount = s:vimDiffBufferCount - 1 + if s:vimDiffBufferCount == 1 && exists('s:vimDiffRestoreCmd') + " All scratch buffers are gone, reset the original. + " Only restore if the source buffer is still in Diff mode + + let sourceWinNR=bufwinnr(s:vimDiffSourceBuffer) + if sourceWinNR != -1 + " The buffer is visible in at least one window + let currentWinNR = winnr() + while winbufnr(sourceWinNR) != -1 + if winbufnr(sourceWinNR) == s:vimDiffSourceBuffer + execute sourceWinNR . 'wincmd w' + if getwinvar('', "&diff") + execute s:vimDiffRestoreCmd + endif + endif + let sourceWinNR = sourceWinNR + 1 + endwhile + execute currentWinNR . 'wincmd w' + else + " The buffer is hidden. It must be visible in order to set the + " diff option. + let currentBufNR = bufnr('') + execute "hide buffer" s:vimDiffSourceBuffer + if getwinvar('', "&diff") + execute s:vimDiffRestoreCmd + endif + execute "hide buffer" currentBufNR + endif + + unlet s:vimDiffRestoreCmd + unlet s:vimDiffSourceBuffer + unlet s:vimDiffBufferCount + unlet s:vimDiffScratchList + elseif s:vimDiffBufferCount == 0 + " All buffers are gone. + unlet s:vimDiffSourceBuffer + unlet s:vimDiffBufferCount + unlet s:vimDiffScratchList + endif + endif + endif + finally + let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1 + endtry +endfunction + +augroup HGVimDiffRestore + au! + au BufUnload * call s:HGVimDiffRestore(expand("<abuf>")) +augroup END + +" Section: Optional activation of buffer management {{{1 + +if s:HGGetOption('HGCommandEnableBufferSetup', 0) + call HGEnableBufferSetup() +endif + +" Section: Plugin completion {{{1 + +let loaded_hgcommand=2 +silent do HGCommand User HGPluginFinish +" vim:se expandtab sts=2 sw=2: