" perforce.vim: Interface with perforce SCM through p4. " Author: Hari Krishna " Last Change: 21-Oct-2003 @ 19:37 " Created: Sometime before 20-Apr-2001 " Requires: Vim-6.2, genutils.vim(1.9), multvals.vim(3.3) " Version: 2.0.39 " Licence: This program is free software; you can redistribute it and/or " modify it under the terms of the GNU General Public License. " See http://www.gnu.org/copyleft/gpl.txt " Acknowledgements: " Tom Slee (tslee at ianywhere dot com) for his idea of creating a status " bar with the p4 fstat information (see " http://www.vim.org/script.php?script_id=167). " Download From: " http://www.vim.org//script.php?script_id=240 " Usage: " For detailed help, see ":help perforce" or doc/perforce.txt. " " Summary Of Features: " See help for details and most up to date information. " " Command Line: " Short commands for most used perforce operations: " " PE (edit), PR (revert), PA (add), PD (diff), PD2 (diff2), " PP (print), PG (get), PO (opened), PH (help), " " Long commands each corresponding to one of the p4 operations: " " PAdd, PBranch, PBranches, PChange, PChanges, PClient, PClients, " PDelete, PDepot, PDepots, PDescribe, PDiff, PDiff2, PEdit, PFiles, " PFstat, PGet, PGroup, PGroups, PHave, PHelp, PIntegrate, PJob, PJobs, " PJobspec, PLabel, PLabels, PLabelsync, PLock, POpened, PPrint, " PReopen, PResolve, PRevert, PSubmit, PSync, PUnlock, PUser, PUsers, " PWhere " " Special commands which don't have an equivalent p4 command: " " E (command to open a file from a different codeline), " PRefreshActivePane (to refresh the current p4 window), " PRefreshFileStatus (to refresh the current file status), " PFSettings (to display and change the values of different settings). " " Commandline Mode Maps: " - to insert the current file from a different codeline. " - to get the current list item on the command line (in the " relevant p4 windows only) " " Normal Mode Maps: " O - open/edit current item (all list views), " <2-LeftMouse> or " - describe current item (all list views). " D - delete current item (in file list), " diff current file (in filelog). " S - sync to the current version (in filelog) . " o - list opened files in the current change list(in changes list). " C - describe current change list (in filelog or changes list). " open current change (in file list). " P - print properties of the current file (in file list). " R - revert current file (in file list). " S - submit current change (in changes list). " " Menus: " "Perforce" menu group in the main and Popup menus if enabled. " " Settings: " p4CmdPath, p4Port, p4User, p4Client, p4Password, p4DefaultOptions, " p4ClientRoot, p4EnableRuler, p4RulerWidth, p4EnableActiveStatus, " p4ASIgnoreDefPattern, p4ASIgnoreUsrPattern, p4OptimizeActiveStatus, " p4UseGUIDialogs, p4PromptToCheckout, p4DefaultListSize, " p4DefaultDiffOptions, p4EnableMenu, p4UseExpandedMenu, " p4EnablePopupMenu, p4UseExpandedPopupMenu, p4Presets, " p4MaxLinesInDialog, p4CheckOutDefault, p4SortSettings, p4TempDir, " p4SplitCommand, p4UseVimDiff2 " " " TODO: {{{ " - If there is a call to confirm() when it is in silent!, the message " doesn't show up. " - After pressing A to start autocheckout the cursor is wrongly placed. " " - I can have another scriptOrigin type that allows passing the " argumentString as it is from the handlers to PFIF(). " - Verify that the buffers/autocommands are not leaking. " TODO }}} " " BEGIN NOTES {{{ " - Now that we increase the level of escaping in the ParseOptions(), we " need to be careful in reparsing the options (by not using " scriptOrigin=2). When you CreateArgString() using these escaped " arguments as if they were typed in by user, they get sent to p4 as they " are, with incorrect number of back-slashes. " - When issuing sub-commands, we should remember to use the s:p4Options " that was passed to the main command, or the user will see incorrect " behavior or at the worst, errors. " - The p4FullCmd now can have double-quotes surrounding each of the " individual arguments, if the shell is cmd.exe or command.com, so while " manipulating it directly, we need to use "\?. " - With the new mode of scriptOrigin=2, the changes done to the s:p4* " variables will not get reflected in the s:p4WinName, unless there is " some other relevant processing done in PFIF. " - With the new mode of scriptOrigin=2, there is no reason to use " scriptOrigin=1 in most of the calls from handlers. " - The s:PFSetupBufAutoCommand and its cousines expect the buffer name to " be plain with no escaping, as they do their own escaping. " - Wherever we normally expect a depot name, we should use the s:p4Depot " instead of hardcoded 'depot'. We should also consider the client name " here. " - If checktime is called from inside an autocommand execution (which is " the case for auto checkout), the checktime may be delayed until the " execution is over. So we can't reset s:currentCommand immediately. I am " currently creating a CursorHold autocommand for this. " - We need to pass special characters such as , *, ?, [, (, &, |, ', $ " and " to p4 without getting interpreted by the shell. We may have to use " appropriate quotes around the characters when the shell treats them " specially. Windows+native is the least bothersome of all as it doesn't " treat most of the characters specially and the arguments can be " sorrounded in double-quotes and embedded double-quotes can be easily " passed in by just doubling them. " - I am aware of the following unique ways in which external commands are " executed (not sure if this is same for all of the variations possible: " ":[{range}][read|write]!cmd | filter" and "system()"): " For :! command " On Windoze+native: " cmd /c " On Windoze+sh: " sh -c "" " On Unix+sh: " sh -c () " - By the time we parse arguments, we protect all the back-slashes, which " means that we would never see a single-slash. " - Using back-slashes on Cygwin vim is unique and causes E303. This is " because it thinks it is on UNIX where it is not a special character, but " underlying Windows obviously treats it special and so it bails out. " - Using back-slashes on Windows+sh also seems to be different. Somewhere in " the execution line (most probably the path from CreateProcess() to sh, " as it doesn't happen in all other type sof interfaces) consumes one " level of bach-slashes. If it is even number it becomes half, and if it " is odd then the last unparied back-slash is left as it is. " - Some test cases for special character handling: " - PF fstat a\b " - PF fstat a\ b " - PF fstat a&b " - PF fstat a\&b " - PF fstat a\#b " - PF fstat a\|b " - Careful using PFIF(1) from within script, as it doesn't redirect the " call to the corresponding handler (if any). " END NOTES }}} if exists("loaded_perforce") finish endif if v:version < 602 echomsg "You need Vim 6.2 to run this version of perforce.vim." finish endif let loaded_perforce=1 " We need these scripts at the time of initialization itself. if !exists("loaded_multvals") runtime plugin/multvals.vim endif if !exists("loaded_genutils") runtime plugin/genutils.vim endif " Make sure line-continuations won't cause any problem. This will be restored " at the end let s:save_cpo = &cpo set cpo&vim " Call this any time to reconfigure the environment. This re-performs the same " initializations that the script does during the vim startup, without " loosing what is already configured. command! -nargs=0 PFInitialize :call Initialize() """ BEGIN: Initializations {{{ " Determine the script id. function! s:MyScriptId() map xx xx let s:sid = maparg("xx") unmap xx return substitute(s:sid, "xx$", "", "") endfunction let s:myScriptId = s:MyScriptId() delfunction s:MyScriptId " This is not needed anymore. function! s:Initialize() " {{{ " User Options {{{ if !exists("s:p4CmdPath") " The first-time only, initialize with defaults. let s:p4CmdPath = "p4" let s:clientRoot = "" let s:defaultListSize='100' let s:defaultDiffOptions='' let s:p4Client = $P4CLIENT if exists("$P4USER") && $P4USER != '' let s:p4User = $P4USER elseif OnMS() && exists("$USERNAME") let s:p4User = $USERNAME elseif exists("$LOGNAME") let s:p4User = $LOGNAME elseif exists("$USERNAME") " Happens if you are on cygwin too. let s:p4User = $USERNAME else let s:p4User = '' endif let s:p4Port = $P4PORT let s:p4Password = $P4PASSWD let s:p4Depot = 'depot' let s:p4Presets = "" let s:defaultOptions = "" let s:useGUIDialogs = 0 let s:promptToCheckout = 1 let s:maxLinesInDialog = 1 let s:activeStatusEnabled = 1 let s:ignoreDefPattern = '\c\%(\GetClientInfo() | au! P4ClientRoot else let s:clientRoot=fnamemodify(".", ":p") endif endif aug END aug P4Active au! if s:activeStatusEnabled au BufRead * call GetFileStatus(expand('') + 0, 0) endif aug END " User Options }}} """ The following are some shortcut commands. Some of them are enhanced such """ as the help window or the filelog window. " Command definitions {{{ command! -nargs=* -complete=file PP :call printHdlr(0, 0, ) command! -nargs=* -complete=file PPrint :call printHdlr(0, 0, ) command! -nargs=* -complete=file PD :call diffHdlr(0, 0, ) command! -nargs=* -complete=file PDiff :call diffHdlr(0, 0, ) command! -nargs=* -complete=file PEdit :call PFIF(0, 20, "edit", ) command! -nargs=* -complete=file PE :call PFIF(0, 20, "edit", ) command! -nargs=* -complete=file PReopen \ :call PFIF(0, 20, "reopen", ) command! -nargs=* -complete=file PAdd :call PFIF(0, 20, "add", ) command! -nargs=* -complete=file PA :call PFIF(0, 20, "add", ) command! -nargs=* -complete=file PDelete \ :call PFIF(0, 20, "delete", ) command! -nargs=* -complete=file PLock :call PFIF(0, 20, "lock", ) command! -nargs=* -complete=file PUnlock \ :call PFIF(0, 20, "unlock", ) command! -nargs=* -complete=file PRevert \ :call PFIF(0, 20, "revert", ) command! -nargs=* -complete=file PR :call PFIF(0, 20, "revert", ) command! -nargs=* -complete=file PSync :call PFIF(0, 20, "sync", ) command! -nargs=* -complete=file PG :call PFIF(0, 20, "get", ) command! -nargs=* -complete=file PGet :call PFIF(0, 20, "get", ) command! -nargs=* -complete=file POpened \ :call PFIF(0, 0, "opened", ) command! -nargs=* -complete=file PO :call PFIF(0, 0, "opened", ) command! -nargs=* -complete=file PHave :call PFIF(0, 0, "have", ) command! -nargs=* -complete=file PWhere :call PFIF(0, 0, "where", ) command! -nargs=* PDescribe :call describeHdlr(0, 0, ) command! -nargs=* -complete=file PFiles :call PFIF(0, 0, "files", ) command! -nargs=* -complete=file PLabelsync \ :call PFIF(0, 0, "labelsync", ) command! -nargs=* -complete=file PFilelog :call filelogHdlr(0, 0, ) command! -nargs=* -complete=file PIntegrate \ :call PFIF(0, 0, "integrate", ) command! -nargs=* -complete=file PD2 :call diff2Hdlr(0, 0, ) command! -nargs=* -complete=file PDiff2 :call diff2Hdlr(0, 0, ) command! -nargs=* -complete=file PFstat :call PFIF(0, 0, "fstat", ) command! -nargs=* PH :call helpHdlr(0, 0, ) command! -nargs=* PHelp :call helpHdlr(0, 0, ) """ Some list view commands. command! -nargs=* -complete=file PChanges :call changesHdlr(0, 0, ) command! -nargs=* PBranches :call PFIF(0, 0, "branches", ) command! -nargs=* -complete=file PLabels :call labelsHdlr(0, 0, ) command! -nargs=* PClients :call clientsHdlr(0, 0, ) command! -nargs=* PUsers :call PFIF(0, 0, "users", ) command! -nargs=* -complete=file PJobs :call PFIF(0, 0, "jobs", ) command! -nargs=* PDepots :call PFIF(0, 0, "depots", ) command! -nargs=* PGroups :call PFIF(0, 0, "groups", ) """ The following support some p4 operations that normally involve some """ interaction with the user (they are more than just shortcuts). command! -nargs=* -complete=file PChange :call changeHdlr(0, 0, ) command! -nargs=* PBranch :call PFIF(0, 0, "branch", ) command! -nargs=* PLabel :call PFIF(0, 0, "label", ) command! -nargs=* PClient :call PFIF(0, 0, "client", ) command! -nargs=* PUser :call PFIF(0, 0, "user", ) command! -nargs=* PJob :call PFIF(0, 0, "job", ) command! -nargs=* PJobspec :call PFIF(0, 0, "jobspec", ) command! -nargs=* PDepot :call PFIF(0, 0, "depot", ) command! -nargs=* PGroup :call PFIF(0, 0, "group", ) command! -nargs=* -complete=file PSubmit :call submitHdlr(0, 0, ) command! -nargs=* -complete=file PResolve :call resolveHdlr(0, 0, ) " Some built-in commands. command! -nargs=? -complete=file PVDiff :call PFIF(0, 0, "vdiff", ) command! -nargs=? -complete=file PVDiff2 \ :call PFIF(0, 0, "vdiff2", ) """ Other utility commands. command! -nargs=* -complete=file E :call PFOpenAltFile(0, ) command! -nargs=* -complete=file ES :call PFOpenAltFile(2, ) command! -nargs=* PSwitch :call PSwitch() command! -nargs=* PSwitchPortClientUser :call SwitchPortClientUser() command! -nargs=0 PRefreshActivePane :call PRefreshActivePane() command! -nargs=0 PRefreshFileStatus :call GetFileStatus(0, 1) command! -nargs=0 PToggleCkOut :call ToggleCheckOutPrompt(1) command! -nargs=0 PFSettings :call PFSettings() command! -nargs=0 PDiffOff :call CleanDiffOptions() command! -nargs=? PWipeoutBufs :call WipeoutP4Buffers() command! -nargs=* -complete=file -range=% PF \ ,call PFIF(0, -1, ) command! -nargs=* -complete=file PFRaw :call PFRaw(0, ) command! -nargs=* -complete=file -range=% PW \ :,call PW(0, ) command! -nargs=0 PLastMessage :call LastMessage() command! -nargs=1 PExecCmd :call PExecCmd() " Some generic mappings. if maparg('', 'c') == "" cnoremap =PFOpenAltFile(1) endif " New normal mode mappings. if (! exists("no_plugin_maps") || ! no_plugin_maps) && \ (! exists("no_perforce_maps") || ! no_execmap_maps) nnoremap prap :PRefreshActivePane nnoremap prfs :PRefreshFileStatus endif " Command definitions }}} " CreateMenu {{{ function! s:CreateMenu(sub, expanded) if ! a:expanded let fileGroup = '.' else let fileGroup = '.&File.' endif exec 'amenu ' . a:sub . '&Perforce' . fileGroup . '&Add :PA' exec 'amenu ' . a:sub . '&Perforce' . fileGroup . 'S&ync :PSync' exec 'amenu ' . a:sub . '&Perforce' . fileGroup . '&Edit :PE' exec 'amenu ' . a:sub . '&Perforce' . fileGroup . '-Sep1- :' exec 'amenu ' . a:sub . '&Perforce' . fileGroup . \ '&Delete :PDelete' exec 'amenu ' . a:sub . '&Perforce' . fileGroup . '&Revert :PR' exec 'amenu ' . a:sub . '&Perforce' . fileGroup . '-Sep2- :' exec 'amenu ' . a:sub . '&Perforce' . fileGroup . 'Loc&k :PLock' exec 'amenu ' . a:sub . '&Perforce' . fileGroup . \ 'U&nlock :PUnlock' exec 'amenu ' . a:sub . '&Perforce' . fileGroup . '-Sep3- :' exec 'amenu ' . a:sub . '&Perforce' . fileGroup . '&Diff :PD' exec 'amenu ' . a:sub . '&Perforce' . fileGroup . 'Diff&2 :PD2' exec 'amenu ' . a:sub . '&Perforce' . fileGroup . \ 'Revision\ &History :PFilelog' exec 'amenu ' . a:sub . '&Perforce' . fileGroup . 'Propert&ies ' . \ ':PFstat -C' exec 'amenu ' . a:sub . '&Perforce' . fileGroup . '&Print :PP' exec 'amenu ' . a:sub . '&Perforce' . fileGroup . '-Sep4- :' if a:expanded exec 'amenu ' . a:sub . '&Perforce.&File.' . \ 'Resol&ve.Accept\ &Their\ Changesresolve\ -at ' . \ ':PResolve -at' exec 'amenu ' . a:sub . '&Perforce.&File.' . \ 'Resol&ve.Accept\ &Your\ Changesresolve\ -ay :PResolve -ay' exec 'amenu ' . a:sub . '&Perforce.&File.' . \ 'Resol&ve.&Automatic\ Resolveresolve\ -am :PResolve -am' exec 'amenu ' . a:sub . '&Perforce.&File.' . \ 'Resol&ve.&Safe\ Resolveresolve\ -as :PResolve -as' exec 'amenu ' . a:sub . '&Perforce.&File.' . \ 'Resol&ve.&Force\ Resolveresolve\ -af :PResolve -af' exec 'amenu ' . a:sub . '&Perforce.&File.' . \ 'Resol&ve.S&how\ Integrationsresolve\ -n :PResolve -n' exec 'amenu ' . a:sub . '&Perforce.&File.-Sep5- :' exec 'amenu ' . a:sub . '&Perforce.&File.Sa&ve\ Current\ Spec ' . \':PExecCmd W' exec 'amenu ' . a:sub . '&Perforce.&File.Save\ and\ &Quit\ ' . \'Current\ Spec :PExecCmd WQ' endif if ! a:expanded exec 'amenu ' . a:sub . '&Perforce.&Opened\ Files :PO' exec 'amenu ' . a:sub . '&Perforce.&Refresh\ Active\ Pane ' . \ ':PRefreshActivePane' exec 'amenu ' . a:sub . '&Perforce.-Sep6- :' else exec 'amenu ' . a:sub . \ '&Perforce.&View.&BranchSpecs :PBranches' exec 'amenu ' . a:sub . '&Perforce.&View.&Changelist.' . \ '&Pending\ Changelists :PChanges -s pending' exec 'amenu ' . a:sub . '&Perforce.&View.&Changelist.' . \ '&Submitted\ Changelists :PChanges -s submitted' exec 'amenu ' . a:sub . \ '&Perforce.&View.Cl&ientSpecs :PClients' exec 'amenu ' . a:sub . '&Perforce.&View.&Jobs :PJobs' exec 'amenu ' . a:sub . '&Perforce.&View.&Labels :PLabels' exec 'amenu ' . a:sub . '&Perforce.&View.&Users :PUsers' exec 'amenu ' . a:sub . '&Perforce.&View.&Depots :PDepots' exec 'amenu ' . a:sub . '&Perforce.&View.&Opened\ Files :PO' exec 'amenu ' . a:sub . '&Perforce.&View.&Refresh\ Active\ Pane ' . \ ':PRefreshActivePane' endif if a:expanded exec 'amenu ' . a:sub . '&Perforce.&Settings.' . \ '&Switch\ Port\ Client\ User :call SwitchPortClientUser()' let nSets = MvNumberOfElements(s:p4Presets, ',') if nSets > 0 let index = 0 while index < nSets let nextSet = MvElementAt(s:p4Presets, ',', index) exec 'amenu ' . a:sub . '&Perforce.&Settings.&' . index . '\ ' \ . escape(nextSet, ' .') . ' :PSwitch ' . index . '' let index = index + 1 endwhile endif endif if ! a:expanded exec 'amenu ' . a:sub . \ '&Perforce.New\ &Submission\ Template :PSubmit' else exec 'amenu ' . a:sub . '&Perforce.&Changelist.&New :PChange' exec 'amenu ' . a:sub . '&Perforce.&Changelist.' . \ '&Edit\ Current\ Changelist :PExecCmd PItemOpen' exec 'amenu ' . a:sub . '&Perforce.&Changelist.' . \ 'Descri&be\ Current\ Changelist :PExecCmd PItemDescribe' exec 'amenu ' . a:sub . '&Perforce.&Changelist.' . \ '&Delete\ Current\ Changelist :PExecCmd PItemDelete' exec 'amenu ' . a:sub . '&Perforce.&Changelist.' . \ 'New\ &Submission\ Template :PSubmit' exec 'amenu ' . a:sub . '&Perforce.&Changelist.-Sep- :' exec 'amenu ' . a:sub . '&Perforce.&Changelist.' . \ 'View\ &Pending\ Changelists :PChanges -s pending' exec 'amenu ' . a:sub . '&Perforce.&Changelist.' . \ '&View\ Submitted\ Changelists :PChanges -s submitted' endif if ! a:expanded exec 'amenu ' . a:sub . '&Perforce.&Branch :PBranch' else exec 'amenu ' . a:sub . '&Perforce.&Branch.&New :PBranch' exec 'amenu ' . a:sub . '&Perforce.&Branch.' . \ '&Edit\ Current\ BranchSpec :PExecCmd PItemOpen' exec 'amenu ' . a:sub . '&Perforce.&Branch.' . \ 'Descri&be\ Current\ BranchSpec :PExecCmd PItemDescribe' exec 'amenu ' . a:sub . '&Perforce.&Branch.' . \ '&Delete\ Current\ BranchSpec :PExecCmd PItemDelete' exec 'amenu ' . a:sub . '&Perforce.&Branch.-Sep- :' exec 'amenu ' . a:sub . \ '&Perforce.&Branch.&View\ BranchSpecs :PBranches' endif if ! a:expanded exec 'amenu ' . a:sub . '&Perforce.&Label :PLabel' else exec 'amenu ' . a:sub . '&Perforce.&Label.&New :PLabel' exec 'amenu ' . a:sub . '&Perforce.&Label.' . \ '&Edit\ Current\ LabelSpec :PExecCmd PItemOpen' exec 'amenu ' . a:sub . '&Perforce.&Label.' . \ 'Descri&be\ Current\ LabelSpec :PExecCmd PItemDescribe' exec 'amenu ' . a:sub . '&Perforce.&Label.' . \ '&Delete\ Current\ LabelSpec :PExecCmd PItemDelete' exec 'amenu ' . a:sub . '&Perforce.&Label.-Sep1- :' exec 'amenu ' . a:sub . '&Perforce.&Label.' . \ '&Sync\ Client\ ' . s:p4Client . '\ to\ Current\ Label ' . \ ':PExecCmd PLabelsSyncClient' exec 'amenu ' . a:sub . '&Perforce.&Label.' . \ '&Replace\ Files\ in\ Current\ Label\ with\ Client\ ' . s:p4Client . \ '\ files ' . ':PExecCmd PLabelsSyncLabel' exec 'amenu ' . a:sub . '&Perforce.&Label.-Sep2- :' exec 'amenu ' . a:sub . \ '&Perforce.&Label.&View\ Labels :PLabels' endif if ! a:expanded exec 'amenu ' . a:sub . '&Perforce.Cl&ient :PClient' else exec 'amenu ' . a:sub . \ '&Perforce.Cl&ient.&New :PClient +P' exec 'amenu ' . a:sub . '&Perforce.Cl&ient.' . \ '&Edit\ Current\ ClientSpec :PExecCmd PItemOpen' exec 'amenu ' . a:sub . '&Perforce.Cl&ient.' . \ 'Descri&be\ Current\ ClientSpec :PExecCmd PItemDescribe' exec 'amenu ' . a:sub . '&Perforce.Cl&ient.' . \ '&Delete\ Current\ ClientSpec :PExecCmd PItemDelete' exec 'amenu ' . a:sub . '&Perforce.' . \ 'Cl&ient.&Edit\ ' . escape(s:p4Client, ' ') . ' :PClient' exec 'amenu ' . a:sub . '&Perforce.Cl&ient.-Sep- :' exec 'amenu ' . a:sub . '&Perforce.Cl&ient.&Switch\ to\ Current' . \ '\ Client :exec "PSwitch ' . s:p4Port . \ ' " . GetCurrentItem()' exec 'amenu ' . a:sub . \ '&Perforce.Cl&ient.&View\ ClientSpecs :PClients' endif if ! a:expanded exec 'amenu ' . a:sub . '&Perforce.&User :PUser' else exec 'amenu ' . a:sub . \ '&Perforce.&User.&New :PUser +P' exec 'amenu ' . a:sub . '&Perforce.&User.' . \ '&Edit\ Current\ UserSpec :PExecCmd PItemOpen' exec 'amenu ' . a:sub . '&Perforce.&User.' . \ 'Descri&be\ Current\ UserSpec :PExecCmd PItemDescribe' exec 'amenu ' . a:sub . '&Perforce.&User.' . \ '&Delete\ Current\ UserSpec :PExecCmd PItemDelete' exec 'amenu ' . a:sub . \ '&Perforce.&User.&Edit\ ' . escape(s:p4User, ' ') . ' :PUser' exec 'amenu ' . a:sub . '&Perforce.&User.-Sep- :' exec 'amenu ' . a:sub . '&Perforce.&User.&Switch\ to\ Current' . \ '\ User :exec "PSwitch ' . s:p4Port . ' ' . s:p4Client . \ ' " . GetCurrentItem()' exec 'amenu ' . a:sub . \ '&Perforce.&User.&View\ Users :PUsers' endif if ! a:expanded exec 'amenu ' . a:sub . '&Perforce.&Job :PJob' else exec 'amenu ' . a:sub . '&Perforce.&Job.&New :PJob' exec 'amenu ' . a:sub . '&Perforce.&Job.' . \ '&Edit\ Current\ JobSpec :PExecCmd PItemOpen' exec 'amenu ' . a:sub . '&Perforce.&Job.' . \ 'Descri&be\ Current\ JobSpec :PExecCmd PItemDescribe' exec 'amenu ' . a:sub . '&Perforce.&Job.' . \ '&Delete\ Current\ JobSpec :PExecCmd PItemDelete' exec 'amenu ' . a:sub . '&Perforce.&Job.-Sep1- :' exec 'amenu ' . a:sub . '&Perforce.&Job.&Edit\ Job&Spec ' . \ ':PJobspec' exec 'amenu ' . a:sub . '&Perforce.&Job.-Sep2- :' exec 'amenu ' . a:sub . '&Perforce.&Job.&View\ Jobs :PJobs' endif if a:expanded exec 'amenu ' . a:sub . '&Perforce.&Depot.&New :PDepot' exec 'amenu ' . a:sub . '&Perforce.&Depot.' . \ '&Edit\ Current\ DepotSpec :PExecCmd PItemOpen' exec 'amenu ' . a:sub . '&Perforce.&Depot.' . \ 'Descri&be\ Current\ DepotSpec :PExecCmd PItemDescribe' exec 'amenu ' . a:sub . '&Perforce.&Depot.' . \ '&Delete\ Current\ DepotSpec :PExecCmd PItemDelete' exec 'amenu ' . a:sub . '&Perforce.&Depot.-Sep- :' exec 'amenu ' . a:sub . \ '&Perforce.&Depot.&View\ Depots :PDepots' endif if ! a:expanded exec 'amenu ' . a:sub . \ '&Perforce.Open\ Current\ File\ From\ A¬her\ Branch :E' else exec 'amenu ' . a:sub . \ '&Perforce.&Tools.Open\ Current\ File\ From\ A¬her\ Branch ' . \ ':E' endif if ! a:expanded exec 'amenu ' . a:sub . '&Perforce.-Sep7- :' exec 'amenu ' . a:sub . '&Perforce.Sa&ve\ Current\ Spec ' . \':PExecCmd W' exec 'amenu ' . a:sub . '&Perforce.Save\ and\ &Quit\ ' . \'Current\ Spec :PExecCmd WQ' endif exec 'amenu ' . a:sub . '&Perforce.-Sep8- :' exec 'amenu ' . a:sub . '&Perforce.Re-Initial&ze :PFInitialize' if ! a:expanded exec 'amenu ' . a:sub . '&Perforce.&Help :PH' else exec 'amenu ' . a:sub . '&Perforce.&Help.&General :PH' exec 'amenu ' . a:sub . '&Perforce.&Help.&Simple :PH simple' exec 'amenu ' . a:sub . \ '&Perforce.&Help.&Commands :PH commands' exec 'amenu ' . a:sub . \ '&Perforce.&Help.&Environment :PH environment' exec 'amenu ' . a:sub . \ '&Perforce.&Help.&Filetypes :PH filetypes' exec 'amenu ' . a:sub . '&Perforce.&Help.&Jobview :PH jobview' exec 'amenu ' . a:sub . \ '&Perforce.&Help.&Revisions :PH revisions' exec 'amenu ' . a:sub . '&Perforce.&Help.&Usage :PH usage' exec 'amenu ' . a:sub . '&Perforce.&Help.&Views :PH views' endif endfunction " }}} " " Add menu entries if user wants. " silent! unmenu Perforce silent! unmenu! Perforce if s:menuEnabled call s:CreateMenu('', s:useExpandedMenu) endif silent! unmenu PopUp.Perforce silent! unmenu! PopUp.Perforce if s:popupMenuEnabled call s:CreateMenu('PopUp.', s:useExpandedPopupMenu) endif let v:errmsg='' " The above unmenu's could have set this variable. function! s:ToggleCheckOutPrompt(interactive) aug P4CheckOut au! if s:promptToCheckout let s:promptToCheckout = 0 else let s:promptToCheckout = 1 au FileChangedRO * nested :call CheckOutFile() endif aug END if a:interactive echomsg "PromptToCheckout is now " . ((s:promptToCheckout) ? "enabled." : \ "disabled.") endif endfunction let s:promptToCheckout = ! s:promptToCheckout call s:ToggleCheckOutPrompt(0) " Delete unnecessary stuff. delfunction s:CreateMenu endfunction " s:Initialize }}} " Do the actual initialization. call s:Initialize() """ BEGIN: One-time initialization of some script variables {{{ let s:lastMsg = '' let s:lastMsgGrp = 'None' " Indicates the current recursion level for executing p4 commands. let s:recLevel = 0 if OnMS() && match(&shell, '\') != -1 " When using cygwin bash with native vim, p4 gets confused by the PWD, which " is in cygwin style. let s:p4CommandPrefix = "unset PWD && " else let s:p4CommandPrefix = "" endif " Special characters in a filename that are not acceptable in a filename (as a " window title) on windows. let s:specialChars = '\([*:?"<>|]\)' let s:specialChars{'*'} = 'S' let s:specialChars{':'} = 'C' let s:specialChars{'?'} = 'Q' let s:specialChars{'"'} = 'D' let s:specialChars{'<'} = 'L' let s:specialChars{'>'} = 'G' let s:specialChars{'|'} = 'P' " " A lot of metadata on perforce command syntax and handling. " let s:p4KnownCmds = "add,admin,branch,branches,change,changes,client,clients," . \ "counter,counters,delete,depot,depots,describe,diff,diff2,dirs,edit," . \ "filelog,files,fix,fixes,flush,fstat,get,group,groups,have,help,info," . \ "integrate,integrated,job,jobs,jobspec,label,labels,labelsync,lock," . \ "logger,obliterate,opened,passwd,print,protect,rename,reopen,resolve," . \ "resolved,revert,review,reviews,set,submit,sync,triggers,typemap," . \ "unlock,user,users,verify,where," " Add some built-in commands to this list. let s:p4KnownCmds = s:p4KnownCmds . "vdiff,vdiff2," " Map between the option and the commands that reqire us to pass an argument " with this option. let s:p4OptCmdMap{'b'} = 'diff2,integrate' let s:p4OptCmdMap{'c'} = 'add,delete,edit,fix,fstat,integrate,lock,opened,' . \ 'reopen,revert,review,reviews,submit,unlock' let s:p4OptCmdMap{'e'} = 'jobs' let s:p4OptCmdMap{'j'} = 'fixes' let s:p4OptCmdMap{'l'} = 'labelsync' let s:p4OptCmdMap{'m'} = 'changes,filelog,jobs' let s:p4OptCmdMap{'o'} = 'print' let s:p4OptCmdMap{'s'} = 'changes,integrate' let s:p4OptCmdMap{'t'} = 'add,client,edit,label,reopen' let s:p4OptCmdMap{'O'} = 'passwd' let s:p4OptCmdMap{'P'} = 'passwd' let s:p4OptCmdMap{'S'} = 'set' " These built-in options require us to pass an argument. These options start " with a '+'. let s:biOptCmdMap{'c'} = 'diff' " NOTE: The current file is used as the default argument, only when the " command is not one of the s:askUserCmds and it is not one of " s:curFileNotDefCmds or s:nofileArgsCmds. " For these commands, we don't need to default to the current file, as these " commands can work without any arguments. let s:curFileNotDefCmds = 'change,changes,client,files,integrate,job,jobs,' . \ 'jobspec,labels,labelsync,opened,resolve,submit,user,' " For these commands, we need to ask user for the argument, as we can't assume " the current file is the default. let s:askUserCmds = 'admin,branch,counter,depot,fix,group,label,' " A subset of askUserCmds, that should use a more generic prompt. let s:genericPromptCmds = 'admin,counter,fix,' " Commands that essentially display a list of files. let s:filelistCmds = 'files,have,integrate,opened,' " Commands that work with a spec. let s:specCmds='branch,change,client,depot,group,job,jobspec,label,protect,' . \ 'submit,triggers,typemap,user,' " Out of the above specCmds, these are the only commands that don't " support '-o' option. Consequently we have to have our own template. let s:noOutputCmds='submit,' " The following are used only to create a specification, not to view them. " Consequently, they don't accept a '-d' option to delete the spec. let s:specOnlyCmds = 'jobspec,submit,' " These commands might change the fstat of files, requiring an update on some " or all the buffers loaded into vim. "let s:statusUpdReqCmds = 'add,delete,edit,get,lock,reopen,revert,sync,unlock,' "" For these commands we need to call :checktime, as the command might have "" changed the state of the file. "let s:checktimeReqCmds = 'edit,get,reopen,revert,sync,' " For these commands, we can even set 'autoread' along with doing a :checktime. let s:autoreadCmds = 'edit,get,reopen,revert,sync,' " These commands don't expect filename arguments, so no special processing for " file expansion. let s:nofileArgsCmds = 'branch,branches,change,client,clients,counters,depot,' . \ 'depots,describe,dirs,group,groups,help,info,job,jobspec,label,' . \ 'logger,passwd,protect,rename,review,triggers,typemap,user,users,' " For these commands, the output should not be set to perforce type. let s:ftNotPerforceCmds = 'diff,diff2,print,vdiff,vdiff2' " Allows navigation keys in the command window. let s:navigateCmds = 'help,' " These commands accept a '-m' argument to limit the list size. let s:limitListCmds = 'filelog,jobs,changes,' " These commands take the diff option -dx. let s:diffCmds = 'describe,diff,diff2,' " For the following commands, the default output mode is 20. let s:outputType20Cmds = 'add,delete,edit,get,lock,reopen,revert,sync,unlock,' " If there is a confirm message, then PFIF() will prompt user before " continuing with the run. let s:confirmMsgs{'revert'} = "Reverting file(s) will overwrite any edits to " . \ "the files(s)\n Do you want to continue?" let s:confirmMsgs{'submit'} = "This will commit the changelist to the depot." . \ "\n Do you want to continue?" " List of the global variable names of the user configurable settings. let s:settings = 'User,Client,Password,Port,ClientRoot,CmdPath,Presets,' . \ 'DefaultOptions,DefaultDiffOptions,EnableMenu,EnablePopupMenu,' . \ 'UseExpandedMenu,UseExpandedPopupMenu,EnableRuler,RulerWidth,' . \ 'DefaultListSize,EnableActiveStatus,OptimizeActiveStatus,' . \ 'ASIgnoreDefPattern,ASIgnoreUsrPattern,PromptToCheckout,' . \ 'CheckOutDefault,UseGUIDialogs,MaxLinesInDialog,SortSettings,' . \ 'TempDir,SplitCommand,UseVimDiff2,EnableFileChangedShell,' . \ 'HideOnBufHidden,Depot,Autoread' " Map of global variable name to the local variable that are different than " their global counterparts. let s:settingsMap{'EnableActiveStatus'} = 'activeStatusEnabled' let s:settingsMap{'EnableRuler'} = 'rulerEnabled' let s:settingsMap{'EnableMenu'} = 'menuEnabled' let s:settingsMap{'EnablePopupMenu'} = 'popupMenuEnabled' let s:settingsMap{'ASIgnoreDefPattern'} = 'ignoreDefPattern' let s:settingsMap{'ASIgnoreUsrPattern'} = 'ignoreUsrPattern' let s:helpWinName = 'P4\ help' let s:SPACE_AS_SEP = '\\\@]\+@[^>]\+> ([^)]\+)' . "')" let s:jobsExpr = "matchstr(getline(\".\"), '" . '^[^ ]\+\ze on ' . "')" let s:depotsExpr = "matchstr(getline(\".\"), '" . '^Depot \zs[^ ]\+\ze ' . \ "')" let s:openedExpr = "s:ConvertToLocalPath(s:GetCurrentDepotFile(line('.')))" let s:describeExpr = "s:DescribeGetCurrentItem()" let s:filesExpr = "s:ConvertToLocalPath(s:GetCurrentDepotFile(line('.')))" let s:haveExpr = "s:ConvertToLocalPath(s:GetCurrentDepotFile(line('.')))" let s:filelogExpr = "s:GetCurrentDepotFile(line('.'))" let s:groupsExpr = "expand('')" " If an explicit handler is defined, then it will override the default rule of " finding the command with the singular form. let s:filelogItemHandler = "s:printHdlr" let s:changesItemHandler = "s:changeHdlr" let s:openedItemHandler = "s:OpenFile" let s:describeItemHandler = "s:OpenFile" let s:filesItemHandler = "s:OpenFile" let s:haveItemHandler = "s:OpenFile" " Define handlers for built-in commands. These have no arguments, they will " use the existing parsed command-line vars. Set s:errCode on errors. let s:builtinCmdHandler{'vdiff'} = 's:VDiffHandler' let s:builtinCmdHandler{'vdiff2'} = 's:VDiff2Handler' let s:p4Contexts = "" let s:p4ContextSeparator = ":::" let s:p4ContextItemSeparator = ";;;" aug Perforce | aug END " Define autocommand group. call AddToFCShellPre(s:myScriptId . 'FileChangedShell') """ END: One-time initialization of some script variables }}} """ END: Initializations }}} """ BEGIN: Command specific functions {{{ function! s:printHdlr(scriptOrigin, outputType, ...) exec MakeArgumentString() exec "let retVal = s:PFIF(a:scriptOrigin + 1, a:outputType, 'print', " . \ argumentString . ")" if s:StartBufSetup(a:outputType) let undo = 0 " The first line printed by p4 for non-q operation causes vim to misjudge " the filetype. if getline(1) =~ '//[^#]\+#\d\+ - ' setlocal modifiable let firstLine = getline(1) silent! 1delete _ endif set ft= doautocmd filetypedetect BufNewFile " If automatic detection doesn't work... if &ft == "" let &ft=s:GuessFileTypeForCurrentWindow() endif if exists('firstLine') silent! 1put! =firstLine setlocal nomodifiable endif call s:EndBufSetup(a:outputType) endif return retVal endfunction function! s:describeHdlr(scriptOrigin, outputType, ...) exec MakeArgumentString() if !a:scriptOrigin exec "call s:ParseOptionsIF(1, line('$'), 1, 'describe', " . \ argumentString . ")" endif " If -s doesn't exist, and user doesn't intent to see a diff, then let us " add -s option. In any case he can press enter on the to see " it later. if ! MvContainsElement(s:p4CmdOptions, s:SPACE_AS_SEP, '-s', ' ') && \ ! MvContainsPattern(s:p4CmdOptions, s:SPACE_AS_SEP, '-d.', ' ') let s:p4CmdOptions = s:p4CmdOptions . ' -s' let s:p4WinName = s:MakeWindowName() " Adjust window name. endif exec "let retVal = s:PFIF(2, a:outputType, 'describe')" if s:StartBufSetup(a:outputType) && getline(1) !~ ' - no such changelist' call s:SetupFileBrowse(a:outputType) if MvContainsElement(s:p4CmdOptions, s:SPACE_AS_SEP, '-s', ' ') setlocal modifiable call append('$', "\t") setlocal nomodifiable else call s:SetupDiff() endif call s:EndBufSetup(a:outputType) endif return retVal endfunction function! s:diffHdlr(scriptOrigin, outputType, ...) exec MakeArgumentString() if !a:scriptOrigin exec "call s:ParseOptionsIF(1, line('$'), 1, 'diff', " . \ argumentString . ")" endif " If a change number is specified in the diff, we need to handle it " ourselves, as p4 doesn't understand this. let changeIdx = MvIndexOfPattern(s:p4CmdOptions, s:SPACE_AS_SEP, \ '+c\s\+\d\+', ' ') " Searches including change no. if changeIdx != -1 " If a change no. is specified. call s:PushP4Context() let s:p4Command = 'opened' let s:p4CmdOptions = '-c ' . MvElementAt(s:p4CmdOptions, s:SPACE_AS_SEP, \ changeIdx + 1, ' ') let retVal = s:PFIF(2, a:outputType, 'opened') if s:errCode != 0 return endif if getline(1) !~ 'ile(s) not opened on this client\.' setl modifiable call s:SilentSub('#.*', '%s///e') call s:PeekP4Context() " Bring in a copy. exec "PF -x - " . s:p4Options . " ++f diff" endif call s:PopP4Context() else exec "let retVal = s:PFIF(2, a:outputType, 'diff')" endif if s:StartBufSetup(a:outputType) call s:SetupDiff() call s:EndBufSetup(a:outputType) endif return retVal endfunction function! s:diff2Hdlr(scriptOrigin, outputType, ...) exec MakeArgumentString() if !a:scriptOrigin exec "call s:ParseOptionsIF(1, line('$'), 1, 'diff2', " . \ argumentString . ")" endif " The pattern takes care of ignoring protected spaces as separators. let nArgs = MvNumberOfElements(s:p4Arguments, s:SPACE_AS_SEP, ' ') if nArgs < 2 if nArgs == 0 let file = s:EscapeFileName(expand('%')) else let file = s:p4Arguments endif let ver1 = s:PromptFor(0, s:useGUIDialogs, "Version1? ", '') let ver2 = s:PromptFor(0, s:useGUIDialogs, "Version2? ", '') let s:p4Arguments = file . s:MakeRevStr(ver1) . " " . file . \ s:MakeRevStr(ver2) endif exec "let retVal = s:PFIF(2, a:outputType, 'diff2')" if s:StartBufSetup(a:outputType) call s:SetupDiff() call s:EndBufSetup(a:outputType) endif return retVal endfunction function! s:changeHdlrImpl(outputType) let _p4Arguments = '' " If argument(s) is not a number... if s:p4Arguments != '' && match(s:p4Arguments, '^\d\+$') == -1 let _p4Arguments = s:p4Arguments let s:p4Arguments = '' " Let a new changelist be created. endif let retVal = s:PFIF(2, a:outputType, 'change') if s:errCode == 0 && s:StartBufSetup(a:outputType) || \ s:commandMode == s:CM_FILTER let p4Options = '' if s:p4Options != '' let p4Options = CreateArgString(s:p4Options, s:SPACE_AS_SEP, ' ') . \ ', ' endif if _p4Arguments != '' if search('^Files:', 'w') && line('.') != line('$') call SaveHardPosition('PChangeImpl') + call s:PushP4Context() exec '.,$call s:PFIF(1, 0, ' . p4Options . '"++f", "opened", "-c", ' . \ '"default", ' . CreateArgString(_p4Arguments, ' ') . ')' call s:PopP4Context() if s:errCode == 0 call s:SilentSub('^', '.,$s//\t/e') call RestoreHardPosition('PChangeImpl') call s:SilentSub('#\d\+ - \(\S\+\) .*$', '.,$s//\t# \1/e') endif call RestoreHardPosition('PChangeImpl') call ResetHardPosition('PChangeImpl') endif endif call s:EndBufSetup(a:outputType) setl nomodified if _p4Arguments != '' && &cmdheight > 1 " The message about W and WQ must have gone by now. redraw | call s:LastMessage() endif endif return retVal endfunction function! s:changeHdlr(scriptOrigin, outputType, ...) exec MakeArgumentString() if !a:scriptOrigin exec "call s:ParseOptionsIF(1, line('$'), 1, 'change', " . \ argumentString . ")" endif let retVal = s:changeHdlrImpl(a:outputType) if s:StartBufSetup(a:outputType) let p4Options = '' if s:p4Options != '' let p4Options = CreateArgString(s:p4Options, s:SPACE_AS_SEP, ' ') . \ ', ' endif exec 'command! -buffer -nargs=* PChangeSubmit :call W(0, ' . \ p4Options . '"submit", )' call s:EndBufSetup(a:outputType) endif return retVal endfunction " Create a template for submit. function! s:submitHdlr(scriptOrigin, outputType, ...) exec MakeArgumentString() if !a:scriptOrigin exec "call s:ParseOptionsIF(1, line('$'), 1, 'submit', " . \ argumentString . ")" endif if MvContainsElement(s:p4CmdOptions, s:SPACE_AS_SEP, '-c', ' ') == 1 " Non-interactive. let retVal = s:PFIF(2, a:outputType, 'submit') else call s:PushP4Context() " This is done just to get the :W and :WQ commands defined properly and " open the window with a proper name. The actual job is done by the call " to s:changeHdlrImpl() which is then run in filter mode to avoid the " side-effects (such as :W and :WQ getting redefined etc.) let s:p4CmdOptions = '+y ' . s:p4CmdOptions " Don't confirm. let s:p4Options = '+t ' . s:p4Options " Testmode. call s:PFIF(2, 0, 'submit') let _newWindowCreated = s:newWindowCreated call s:PeekP4Context() let s:p4CmdOptions = '' " These must be specific to 'submit'. let s:p4Command = 'change' let s:commandMode = s:CM_FILTER | let s:filterRange = '.' let retVal = s:changeHdlrImpl(a:outputType) setlocal nomodified if s:errCode != 0 return endif let s:newWindowCreated = _newWindowCreated call s:PopP4Context() if s:StartBufSetup(a:outputType) let p4Options = '' if s:p4Options != '' let p4Options = CreateArgString(s:p4Options, s:SPACE_AS_SEP, ' ') . \ ', ' endif exec 'command! -buffer -nargs=* PSubmitPostpone :call W(0, ' . \ p4Options . '"change", )' set ft=perforce " Just to get the cursor placement right. call s:EndBufSetup(a:outputType) endif if s:errCode call s:EchoMessage("Error creating submission template.", 'Error') endif endif return s:errCode endfunction function! s:resolveHdlr(scriptOrigin, outputType, ...) exec MakeArgumentString() if !a:scriptOrigin exec "call s:ParseOptionsIF(1, line('$'), 1, 'resolve', " . \ argumentString . ")" endif if (match(s:p4CmdOptions, '-a[fmsty]\>') == -1) && \ (match(s:p4CmdOptions, '-n\>') == -1) return s:SyntaxError("Interactive resolve not implemented (yet).") endif exec "let retVal = s:PFIF(2, a:outputType, 'resolve')" return retVal endfunction function! s:filelogHdlr(scriptOrigin, outputType, ...) exec MakeArgumentString() exec "let retVal = s:PFIF(a:scriptOrigin + 1, a:outputType, 'filelog', " . \ argumentString . ")" if s:StartBufSetup(a:outputType) " No meaning for delete. silent! nunmap D silent! delcommand PItemDelete command! -range -buffer -nargs=0 PFilelogDiff \ :call s:FilelogDiff2(, ) vnoremap D :PFilelogDiff command! -buffer -nargs=0 PFilelogPrint :call PFIF(0, 0, 'print', \ GetCurrentItem()) nnoremap p :PFilelogPrint command! -buffer -nargs=0 PFilelogSync :call FilelogSyncToCurrentItem() nnoremap S :PFilelogSync command! -buffer -nargs=0 PFilelogDescribe \ :call FilelogDescribeChange() nnoremap C :PFilelogDescribe call s:EndBufSetup(a:outputType) endif endfunction function! s:clientsHdlr(scriptOrigin, outputType, ...) exec MakeArgumentString() exec "let retVal = s:PFIF(a:scriptOrigin + 1, a:outputType, 'clients', " . \ argumentString . ")" if s:StartBufSetup(a:outputType) command! -buffer -nargs=0 PClientsTemplate \ :call PFIF(1, 0, 'client', '+P', '-t', GetCurrentItem()) nnoremap P :PClientsTemplate call s:EndBufSetup(a:outputType) endif return retVal endfunction function! s:changesHdlr(scriptOrigin, outputType, ...) exec MakeArgumentString() exec "let retVal = s:PFIF(a:scriptOrigin + 1, a:outputType, 'changes', " . \ argumentString . ")" if s:StartBufSetup(a:outputType) command! -buffer -nargs=0 PItemDescribe \ :call PChangesDescribeCurrentItem() command! -buffer -nargs=0 PChangesSubmit \ :call ChangesSubmitChangeList() nnoremap S :PChangesSubmit command! -buffer -nargs=0 PChangesOpened \ :if getline('.') =~ " \\*pending\\* '" | \ call PFIF(1, 0, 'opened', '-c', GetCurrentItem()) | \ endif command! -buffer -nargs=0 PItemOpen \ :if getline('.') =~ " \\*pending\\* '" | \ call PFIF(0, 0, 'change', GetCurrentItem()) | \ else | \ call PFIF(0, 0, 'describe', '-dd', GetCurrentItem()) | \ endif nnoremap o :PChangesOpened call s:EndBufSetup(a:outputType) endif endfunction function! s:labelsHdlr(scriptOrigin, outputType, ...) exec MakeArgumentString() exec "let retVal = s:PFIF(a:scriptOrigin + 1, a:outputType, 'labels', " . \ argumentString . ")" if s:StartBufSetup(a:outputType) command! -buffer -nargs=0 PLabelsSyncClient \ :call LabelsSyncClientToLabel() nnoremap S :PLabelsSyncClient command! -buffer -nargs=0 PLabelsSyncLabel \ :call LabelsSyncLabelToClient() nnoremap C :PLabelsSyncLabel command! -buffer -nargs=0 PLabelsFiles :call PFIF(1, 0, 'files', '+p', \ '//...@'. GetCurrentItem()) nnoremap I :PLabelsFiles command! -buffer -nargs=0 PLabelsTemplate :call PFIF(1, 0, 'label', \ '+P', '-t', GetCurrentItem()) nnoremap P :PLabelsTemplate call s:EndBufSetup(a:outputType) endif return retVal endfunction function! s:helpHdlr(scriptOrigin, outputType, ...) call SaveWindowSettings2("PerforceHelp", 1) " If there is a help window already open, then we need to reuse it. exec MakeArgumentString() let helpWin = bufwinnr(s:helpWinName) exec "let retVal = s:PFIF(a:scriptOrigin + 1, a:outputType, 'help', " . \ argumentString . ")" if s:StartBufSetup(a:outputType) command! -buffer -nargs=0 PHelpSelect \ :call helpHdlr(0, 0, expand("")) nnoremap :PHelpSelect nnoremap K :PHelpSelect nnoremap <2-LeftMouse> :PHelpSelect call s:PFUnSetupBufAutoCommand(DeEscape(s:helpWinName), 'BufUnload') call AddNotifyWindowClose(s:helpWinName, s:myScriptId . "RestoreWindows") if helpWin == -1 " Resize only when it was not already visible. exec "resize " . 20 endif redraw | echo \ "Press /K/<2-LeftMouse> to drilldown on perforce help keywords." call s:EndBufSetup(a:outputType) endif return retVal endfunction " Built-in command handlers {{{ function! s:VDiffHandler() if MvNumberOfElements(s:p4Arguments, s:SPACE_AS_SEP, ' ') >= 2 return s:SyntaxError("Too many arguments passed to vdiff.") endif if s:p4Arguments != '' let fileName = s:p4Arguments let v:errmsg = "" exec s:splitCommand fileName if v:errmsg != "" return s:SyntaxError("There was an error openeing the file: " . fileName) endif else let fileName = expand('%') " This is already visible. endif let s:p4Command = 'print' let s:p4CmdOptions = s:p4CmdOptions . ' -q' let s:p4WinName = s:MakeTempName(fileName) let s:p4Arguments = fileName let _splitCommand = s:splitCommand let s:splitCommand = "vsplit" call s:PFIF(2, 0, 'print') let s:splitCommand = _splitCommand if s:errCode != 0 return endif diffthis wincmd l diffthis wincmd _ endfunction function! s:VDiff2Handler() if MvNumberOfElements(s:p4Arguments, s:SPACE_AS_SEP, ' ') != 2 return s:SyntaxError("vdiff2 command requires two arguments.") endif let firstFile = MvElementAt(s:p4Arguments, s:SPACE_AS_SEP, 0, ' ') let secondFile = MvElementAt(s:p4Arguments, s:SPACE_AS_SEP, 1, ' ') let tempFile1 = s:MakeTempName(firstFile) let tempFile2 = s:MakeTempName(secondFile) if firstFile == "" || secondFile == "" || (tempFile1 == tempFile2) return s:SyntaxError( \ "vdiff2 command requires two distinct files as arguments.") endif let s:p4Command = 'print' let s:p4CmdOptions = s:p4CmdOptions . ' -q' let s:p4WinName = tempFile1 let s:p4Arguments = firstFile call s:PFIF(2, 0, 'print') if s:errCode != 0 return endif let s:p4WinName = tempFile2 let s:p4Arguments = secondFile let _splitCommand = s:splitCommand wincmd K let s:splitCommand = "vsplit" call s:PFIF(2, 0, 'print') let s:splitCommand = _splitCommand if s:errCode != 0 return endif diffthis wincmd l diffthis wincmd _ endfunction " Returns a fileName in the temp directory that is unique for the branch and " revision specified in the fileName. function! s:MakeTempName(filePath) let depotPath = s:ConvertToDepotPath(a:filePath) if depotPath == '' return '' endif let tmpName = s:tempDir . '/' let branch = s:GetBranchName(depotPath) if branch != '' let tmpName = s:tempDir . branch . '-' endif let revSpec = s:GetRevisionSpecifier(depotPath) if revSpec != '' let tmpName = tmpName . substitute(strpart(revSpec, 1), '/', '_', 'g') . '-' endif return tmpName . fnamemodify(substitute(a:filePath, '\\*#\d\+$', '', ''), \ ':t') endfunction function! s:SyntaxError(msg) if s:errCode == 0 let s:errCode = 1 endif call s:ConfirmMessage(a:msg, "OK", 1, "Error") return s:errCode endfunction " Built-in command handlers }}} """ END: Command specific functions }}} """ BEGIN: Helper functions {{{ " Open a file from an alternative codeline. " If mode == 0, first file is opened and all other files are added to buffer " list. " If mode == 1, the files are not really opened, the list is just returned. " If mode == 2, it behaves the same as mode == 0, except that the file is " split opened. " If there are no arguments passed, user is prompted to enter. He can then " enter a codeline followed by a list of filenames. " If only one argument is passed, it is assumed to be the codeline and the " current filename is assumed (user is not prompted). function! s:PFOpenAltFile(mode, ...) " {{{ if a:0 < 2 if a:0 == 0 " Prompt for codeline string (codeline optionally followed by filenames). let codelineStr = s:PromptFor(0, s:useGUIDialogs, \ "Enter the alternative codeline string: ", '') if codelineStr == "" return "" endif if MvNumberOfElements(codelineStr, s:SPACE_AS_SEP, ' ') < 2 let codelineStr = codelineStr . ' ' . s:EscapeFileName(expand('%')) endif elseif a:0 == 1 let codelineStr = a:1 . ' ' . s:EscapeFileName(expand('%')) endif let argumentString = CreateArgString(codelineStr, s:SPACE_AS_SEP) else exec MakeArgumentString() endif exec "let altFileNames = s:PFGetAltFiles(" . argumentString . ")" if a:mode == 0 || a:mode == 2 let n = MvNumberOfElements(altFileNames, s:SPACE_AS_SEP, ' ') if n == 1 execute ((a:mode == 0) ? ":edit " : ":split ") . altFileNames else call MvIterCreate(altFileNames, s:SPACE_AS_SEP, "Perforce", ' ') while MvIterHasNext("Perforce") execute ":badd " . MvIterNext("Perforce") endwhile call MvIterDestroy("Perforce") execute ((a:mode == 0) ? ":edit " : ":split ") . \ MvElementAt(altFileNames, s:SPACE_AS_SEP, 0, ' ') endif else return altFileNames endif endfunction " }}} " Interactively change the port/client/user. function! s:SwitchPortClientUser() " {{{ let p4Port = s:PromptFor(0, s:useGUIDialogs, "Port: ", s:p4Port) let p4Client = s:PromptFor(0, s:useGUIDialogs, "Client: ", s:p4Client) let p4User = s:PromptFor(0, s:useGUIDialogs, "User: ", s:p4User) call s:PSwitch(p4Port, p4Client, p4User) endfunction " }}} " No args: Print presets and prompt user to select a preset. " Number: Select that numbered preset. " port [client] [user]: Set the specified settings. function! s:PSwitch(...) " {{{ let nSets = MvNumberOfElements(s:p4Presets, ',') if a:0 == 0 if nSets == 0 call s:EchoMessage("No sets to select from.", 'Error') return endif let selectedSetting = MvPromptForElement(s:p4Presets, ',', -1, \ "Select the setting: ", -1, s:useGUIDialogs) call s:PSwitchHelper(selectedSetting) return else if match(a:1, '^\d\+') == 0 let index = a:1 + 0 if index >= nSets call s:EchoMessage("Not that many sets.", 'Error') return endif let selectedSetting = MvElementAt(s:p4Presets, ',', index) call s:PSwitchHelper(selectedSetting) return else let g:p4Port = a:1 if a:0 > 1 let g:p4Client = a:2 endif if a:0 > 2 let g:p4User = a:3 endif endif let g:p4Password = "" call s:Initialize() call s:GetClientInfo() endif endfunction " }}} function! s:PSwitchHelper(settingStr) " {{{ if a:settingStr != "" let settingStr = substitute(a:settingStr, '\s\+', "','", 'g') let settingStr = substitute(settingStr, '^', "'", '') let settingStr = substitute(settingStr, '$', "'", '') exec 'call s:PSwitch(' . settingStr . ')' endif endfunction " }}} " Handler for opened command. function! s:OpenFile(scriptOrigin, outputType, fileName) " {{{ if filereadable(a:fileName) if a:outputType == 0 let curWin = winnr() let winnr = bufwinnr(a:fileName) if winnr != -1 exec winnr.'wincmd w' else wincmd p endif if curWin != winnr() && winnr() == GetPreviewWinnr() wincmd p " Don't use preview window. endif if winnr() == curWin split endif let bufNr = FindBufferForName(a:fileName) if bufNr != -1 exec "buffer" bufNr | " Preserves line number. else exec "edit " . a:fileName endif else " FIXME: Check if I can use the s:GotoWindow() function here. if ! s:p4HideOnBufHidden && exists('b:p4Command') pclose endif exec "pedit " . a:fileName endif else call s:printHdlr(0, a:outputType, a:fileName) endif endfunction " }}} function! s:DescribeGetCurrentItem() " {{{ if getline(".") == "\t" let b:p4FullCmd = MvRemovePattern(b:p4FullCmd, s:SPACE_AS_SEP, \ "[\"']\\?-s[\"']\\?", ' ') " -s possibly sorrounded by quotes. call s:PRefreshActivePane() call s:SetupDiff() return "" else return s:ConvertToLocalPath(s:GetCurrentDepotFile(line('.'))) endif endfunction " }}} function! s:getCommandItemHandler(outputType, command, args) " {{{ let itemHandler = "" if exists("s:{a:command}ItemHandler") let itemHandler = s:{a:command}ItemHandler elseif match(a:command, 'e\?s$') != -1 let handlerCmd = substitute(a:command, 'e\?s$', '', '') if exists('*s:{handlerCmd}Hdlr') let itemHandler = 's:' . handlerCmd . 'Hdlr' else let itemHandler = 's:PFIF' endif endif if itemHandler == 's:PFIF' return "call s:PFIF(1, " . a:outputType . ", '" . handlerCmd . "', " . \ a:args . ")" elseif itemHandler != '' return 'call ' . itemHandler . '(0, ' . a:outputType . ', ' . a:args . ')' endif return itemHandler endfunction " }}} function! s:OpenCurrentItem(outputType) " {{{ let curItem = s:GetCurrentItem() if curItem != "" let commandHandler = s:getCommandItemHandler(a:outputType, b:p4Command, \ "'" . curItem . "'") if commandHandler != "" exec commandHandler endif endif endfunction " }}} function! s:GetCurrentItem() " {{{ if exists("b:p4Command") && exists("s:{b:p4Command}Expr") exec "return " s:{b:p4Command}Expr endif return "" endfunction " }}} function! s:DeleteCurrentItem() " {{{ let curItem = s:GetCurrentItem() if curItem != "" let answer = s:ConfirmMessage("Are you sure you want to delete " . \ curItem . "?", "&Yes\n&No", 2, "Question") if answer == 1 let commandHandler = s:getCommandItemHandler(2, b:p4Command, \ "'-d', '" . curItem . "'") if commandHandler != "" exec commandHandler endif if v:shell_error == "" call s:PRefreshActivePane() endif endif endif endfunction " }}} function! s:LaunchCurrentFile() " {{{ if s:fileLauncher == '' call s:ConfirmMessage("There was no launcher command configured to launch ". \ "this item, use g:p4FileLauncher to configure." , "OK", 1, "Error") return endif let curItem = s:GetCurrentItem() if curItem != "" exec 'silent! !'.s:fileLauncher curItem endif endfunction " }}} function! s:FilelogDiff2(line1, line2) " {{{ let line1 = a:line1 let line2 = a:line2 if line1 == line2 if line2 < line("$") let line2 = line2 + 1 elseif line1 > 1 let line1 = line1 - 1 else return endif endif let file1 = s:GetCurrentDepotFile(line1) if file1 != "" let file2 = s:GetCurrentDepotFile(line2) if file2 != "" && file2 != file1 " file2 will be older than file1. exec "call s:PFIF(0, -1, \"" . (s:useVimDiff2 ? 'vdiff2' : 'diff2') . \ "\", file2, file1)" endif endif endfunction " }}} function! s:FilelogSyncToCurrentItem() " {{{ let curItem = s:GetCurrentItem() if curItem != "" let answer = s:ConfirmMessage("Do you want to sync to: " . curItem . " ?", \ "&Yes\n&No", 2, "Question") if answer == 1 call s:PFIF(1, -1, 'sync', curItem) endif endif endfunction " }}} function! s:ChangesSubmitChangeList() " {{{ let curItem = s:GetCurrentItem() if curItem != "" let answer = s:ConfirmMessage("Do you want to submit change list: " . \ curItem . " ?", "&Yes\n&No", 2, "Question") if answer == 1 call s:submitHdlr(0, 0, "+y", "-c", curItem) endif endif endfunction " }}} function! s:LabelsSyncClientToLabel() " {{{ let curItem = s:GetCurrentItem() if curItem != "" let answer = s:ConfirmMessage("Do you want to sync client to the label: " . \ curItem . " ?", "&Yes\n&No", 2, "Question") if answer == 1 exec "let retVal = s:PFIF(1, 1, 'sync', \ '//" . s:p4Depot . "/...@' . curItem)" return retVal endif endif endfunction " }}} function! s:LabelsSyncLabelToClient() " {{{ let curItem = s:GetCurrentItem() if curItem != "" let answer = s:ConfirmMessage("Do you want to sync label: " . curItem . \ " to client " . s:p4Client . " ?", "&Yes\n&No", 2, "Question") if answer == 1 exec "let retVal = s:PFIF(1, 1, 'labelsync', '-l', curItem)" return retVal endif endif endfunction " }}} function! s:FilelogDescribeChange() " {{{ let changeNo = matchstr(getline("."), ' change \zs\d\+\ze ') if changeNo != "" exec "call s:describeHdlr(0, 1, changeNo)" endif endfunction " }}} function! s:SetupFileBrowse(outputType) " {{{ " For now, assume that a new window is created and we are in the new window. exec "setlocal includeexpr=" . s:myScriptId . "ConvertToLocalPath(v:fname)" " No meaning for delete. silent! nunmap D silent! delcommand PItemDelete command! -buffer -nargs=0 PFileDiff :call diffHdlr(0, 1, \ ConvertToLocalPath(GetCurrentDepotFile(line(".")))) nnoremap D :PFileDiff command! -buffer -nargs=0 PFileProps :call PFIF(1, 0, 'fstat', '-C', \ GetCurrentDepotFile(line("."))) nnoremap P :PFileProps command! -buffer -nargs=0 PFileRevert :call PFIF(1, -1, 'revert', \ ConvertToLocalPath(GetCurrentDepotFile(line(".")))) nnoremap R :PFileRevert \| PRefreshActivePane command! -buffer -nargs=0 PFilePrint :call printHdlr(0, 0, \ GetCurrentDepotFile(line("."))) nnoremap p :PFilePrint command! -buffer -nargs=0 PFileGet :call PFIF(1, -1, 'sync', \ GetCurrentDepotFile(line("."))) command! -buffer -nargs=0 PFileSync :call PFIF(1, -1, 'sync', \ substitute(GetCurrentDepotFile(line(".")), '#.*', '', '')) nnoremap S :PFileSync command! -buffer -nargs=0 PFileChange :call changeHdlr(0, 0, \ GetCurrentChangeNumber(line("."))) nnoremap C :PFileChange command! -buffer -nargs=0 PFileLaunch :call LaunchCurrentFile() nnoremap A :PFileLaunch endfunction " }}} function! s:SetupDiff() " {{{ setlocal ft=diff command! -buffer -nargs=0 PDiffLink :call DiffOpenSrc(0) command! -buffer -nargs=0 PDiffPLink :call DiffOpenSrc(1) nnoremap O :PDiffLink nnoremap :PDiffPLink endfunction " }}} function! s:SetupSelectItem() " {{{ nnoremap D :PItemDelete nnoremap O :PItemOpen nnoremap :PItemDescribe nnoremap <2-LeftMouse> :PItemDescribe command! -buffer -nargs=0 PItemDescribe :call OpenCurrentItem(1) command! -buffer -nargs=0 PItemOpen :call OpenCurrentItem(0) command! -buffer -nargs=0 PItemDelete :call DeleteCurrentItem() cnoremap =GetCurrentItem() endfunction " }}} function! s:RestoreWindows(dummy) " {{{ call RestoreWindowSettings2("PerforceHelp") call s:PFExecBufClean(bufnr(s:helpWinName)) endfunction " }}} function! s:NavigateBack() " {{{ call s:Navigate('u') if line('$') == 1 && getline(1) == '' call s:NavigateForward() endif endfunction " }}} function! s:NavigateForward() " {{{ call s:Navigate("\") endfunction " }}} function! s:Navigate(key) " {{{ let _modifiable = &l:modifiable try setlocal modifiable " Use built-in markers as Vim takes care of remembering and restoring them " during the undo/redo. normal! mt silent! exec "normal" a:key if line("'t") > 0 && line("'t") <= line('$') normal! `t endif finally let &l:modifiable = _modifiable endtry endfunction " }}} function! s:GetCurrentChangeNumber(lineNo) " {{{ let line = getline(a:lineNo) let changeNo = matchstr(line, ' - \S\+ change \zs\S\+\ze (') if changeNo == 'default' let changeNo = '' endif return changeNo endfunction " }}} function! s:PChangesDescribeCurrentItem() " {{{ let currentChangeNo = s:GetCurrentItem() if currentChangeNo != "" call s:describeHdlr(0, 1, '-s', currentChangeNo) " For pending changelist, we have to run a separate opened command to get " the list of opened files. We don't need line, as it is " still not subbmitted. This works like p4win. if getline('.') =~ "^.* \\*pending\\* '.*$" wincmd p setlocal modifiable call setline(line('$'), "Affected files ...") call append(line('$'), "") call append(line('$'), "") exec '$call s:PW(0, "opened", "-c", currentChangeNo)' wincmd p endif endif endfunction " }}} function! s:PFSettings() " {{{ if s:sortSettings if exists("s:sortedSettings") let settings = s:sortedSettings else let settings = MvQSortElements(s:settings, ',', 'CmpByString', 1) let s:sortedSettings = settings endif else let settings = s:settings endif let selectedSetting = MvPromptForElement2(settings, ',', -1, \ "Select the setting: ", -1, 0, 3) if selectedSetting != "" let oldVal = '' if exists('s:p4{selectedSetting}') let oldVal = s:p4{selectedSetting} else if exists('s:settingsMap{selectedSetting}') let oldVal = s:{s:settingsMap{selectedSetting}} else let localVar = substitute(selectedSetting, '^\(\u\)', '\L\1', '') if exists('s:{localVar}') let oldVal = s:{localVar} else echoerr "Internal error detected, couldn't locate value for " . \ selectedSetting endif endif endif let newVal = input("Current value for " . selectedSetting . " is: " . \ oldVal . "\nEnter new value: ", oldVal) if newVal != oldVal exec "let g:p4" . selectedSetting . " = '" . newVal . "'" call s:Initialize() endif endif endfunction " }}} function! s:MakeRevStr(ver) " {{{ let verStr = '' if a:ver =~ '^[#@&]' let verStr = a:ver elseif a:ver =~ '^[-+]\?\d\+\>\|^none\>\|^head\>\|^have\>' let verStr = '#' . a:ver elseif a:ver != '' let verStr = '@' . a:ver endif return verStr endfunction " }}} function! s:GetDepotName(fileName) " {{{ if s:IsFileUnderDepot(a:fileName) return s:p4Depot elseif stridx(a:fileName, '//') == 0 return matchstr(a:fileName, '^//\zs[^/]\+\ze/') else return '' endif endfunction " }}} function! s:GetBranchName(fileName) " {{{ if s:IsFileUnderDepot(a:fileName) " TODO: Need to run where command at this phase. elseif stridx(a:fileName, '//') == 0 return matchstr(a:fileName, '^//[^/]\+/\zs[^/]\+\ze') else return '' endif endfunction " }}} function! s:GetRevisionSpecifier(fileName) " {{{ return matchstr(a:fileName, \ '^\(\%(\S\|\\\@' let context = 'local' let regPre = '[cad]' if search('^---$', 'bW') && line('.') > segStLine let segStLine = line('.') endif endif let stLine = matchstr(segHeader, regPre.'\zs\d\+\ze') call RestoreHardPosition('DiffOpenSrc') let offset = line('.') - segStLine - 1 elseif search('\([*-]\)\1\1 \d\+,\d\+ \1\{4}', 'bW') " context. if getline('.') =~ '^-' let context = 'local' else let context = 'depot' endif let stLine = matchstr(getline('.'), '^[*-]\{3} \zs\d\+\ze,') let segStLine = line('.') call RestoreHardPosition('DiffOpenSrc') let offset = line('.') - segStLine - 1 elseif search('^@@ -\=\d\+,\d\+ +\=\d\+,\d\+ @@$', 'bW') " unified let segStLine = line('.') let segHeader = getline('.') call RestoreHardPosition('DiffOpenSrc') let context = 'local' let sign = '+' if getline('.') =~ '^-' let context = 'depot' let sign = '-' endif let stLine = matchstr(segHeader, ' '.sign.'\zs\d\+\ze,\d\+') let _ma = &l:modifiable try setl modifiable " Count the number of lines that come from the other side (those lines " that start with an opposite sign). let _ss = @/ | let @/ = '^'.substitute('-+', sign, '', '') | \ let offOffset = matchstr(GetVimCmdOutput( segStLine.',.s//&/'), \ '\d\+\ze substitutions\? on \d\+ lines\?') + 0 | let @/ = _ss call RestoreHardPosition('DiffOpenSrc') let offset = line('.') - segStLine - 1 - offOffset if offOffset > 0 silent! undo call RestoreHardPosition('DiffOpenSrc') endif finally let &l:modifiable = _ma endtry endif let s:errCode = 0 if context == 'depot' && firstFile == '' return endif if context == 'local' let file = secondFile else let file = firstFile endif if s:IsDepotPath(file) call s:printHdlr(0, a:preview, file) let offset = offset + 1 " For print header. else call s:OpenFile(1, a:preview, file) endif if s:errCode == 0 if a:preview wincmd P endif exec (stLine + offset) if a:preview " Also works as a work-around for the buffer not getting scrolled. normal! z. wincmd p endif endif endif endfunction " }}} """ END: Helper functions }}} """ BEGIN: Middleware functions {{{ " Filter contents through p4. function! s:PW(scriptOrigin, ...) range exec MakeArgumentString() if a:scriptOrigin != 2 exec "call s:ParseOptions(a:firstline, a:lastline, '++f', " . \ argumentString . ")" else let s:commandMode = s:CM_FILTER endif setlocal modifiable let retVal = s:PFIF(2, 5, s:p4Command) return retVal endfunction " Generate raw output into a new window. function! s:PFRaw(outputType, ...) exec MakeArgumentString() exec "call s:ParseOptions(1, line('$'), " . argumentString . ")" let retVal = s:PFImpl(a:outputType, 1, 0, "") return retVal endfunction function! s:W(quitWhenDone, commandName, ...) exec MakeArgumentString() exec "call s:ParseOptionsIF(1, line('$'), 0, a:commandName, " . \ argumentString . ")" let s:p4CmdOptions = s:p4CmdOptions . ' -i' " We can't capture the return value using this syntax. 1,$call s:PW(2) if s:errCode == 0 setl nomodified if a:quitWhenDone quit endif else if search('^Change \d\+ created', 'w') let newChangeNo = matchstr(getline('.'), '\d\+') let _z = @z let _undolevels=&undolevels let _bufhidden=&l:bufhidden try silent! normal! 1G"zyG undo " Make the below changes such a way that they can't be undo. This in a " way, forces Vim to create an undo point, so that user can later " undo and see these changes, with proper change number and status " in place. Unfortunately this has the side effect of loosing the " previous undo history. set undolevels=-1 if search("^Change:\tnew$") call setline('.', "Change:\t" . newChangeNo) endif if search("^Status:\tnew$") call setline('.', "Status:\tpending") endif call s:PFUnSetupBufAutoCommand(expand('%'), 'BufUnload') setlocal bufhidden=hide setl nomodified let &undolevels=_undolevels silent! 0,$delete _ silent! put! =@z call s:PFSetupForSpec() finally let @z = _z let &undolevels=_undolevels let &l:bufhidden=_bufhidden endtry let b:p4FullCmd = s:CreateFullCmd('-o change ' . newChangeNo) endif endif endfunction function! s:ParseOptionsIF(fline, lline, scriptOrigin, commandName, ...) " range exec MakeArgumentString() " There are multiple possibilities here: " - scriptOrigin, in which case the commandName contains the name of the " command, but the varArgs also may contain it. " - commandOrigin, in which case the commandName may actually be the " name of the command, or it may be the first argument to p4 itself, in " any case we will let p4 handle the error cases. if MvContainsElement(s:p4KnownCmds, ',', a:commandName) && a:scriptOrigin exec "call s:ParseOptions(a:fline, a:lline, " . \ argumentString . ")" " Add a:commandName only if it doesn't already exist in the var args. " Handles cases like "PF help submit" and "PF -c change changeno#", " where the commandName need not be at the starting and there could be " more than one valid commandNames (help and submit). if s:p4Command != a:commandName exec "call s:ParseOptions(a:fline, a:lline, a:commandName, " \ . argumentString . ")" endif else exec "call s:ParseOptions(a:fline, a:lline, a:commandName, " . \ argumentString . ")" endif endfunction " PFIF {{{ " The commandName may not be the perforce command when it is not of script " origin (called directly from a command), but it should be always command " name, when it is script origin. " scriptOrigin: An integer indicating the origin of the call. " 0 - Originated directly from the user, so should redirect to the specific " command handler (if exists), after some basic processing. " 1 - Originated from the script, continue with the full processing. " 2 - Same as 1 but, avoid parsing arguments (they are already parsed by the " caller). function! s:PFIF(scriptOrigin, outputType, commandName, ...) range let output = '' " Used only when mode is s:CM_DISPLAY if a:scriptOrigin != 2 exec MakeArgumentString() exec "call s:ParseOptionsIF(a:firstline, a:lastline, " \ . "a:scriptOrigin, a:commandName, " . argumentString . ")" if s:commandMode == s:CM_DISPLAY let output = DeEscape(s:p4Arguments) let s:p4Arguments = '' let s:p4WinName = s:MakeWindowName() endif elseif s:commandMode == s:CM_DISPLAY let output = a:1 endif let modifyWindowName = 0 let outputType = a:outputType if a:outputType == -1 if MvContainsElement(s:outputType20Cmds, ',', s:p4Command) let outputType = 20 else let outputType = 0 endif endif let outputIdx = MvIndexOfPattern(s:p4Options, s:SPACE_AS_SEP, \ '+o\s\+\d\+', ' ') " Searches including output mode. if outputIdx != -1 let outputType = MvElementAt(s:p4Options, s:SPACE_AS_SEP, outputIdx + 1, \ ' ') + 0 endif if ! a:scriptOrigin if exists('*s:{s:p4Command}Hdlr') return s:{s:p4Command}Hdlr(1, outputType, a:commandName) endif endif let dontProcess = MvContainsElement(s:p4CmdOptions, s:SPACE_AS_SEP, '+p', ' ') " If there is a confirm message for this command, then first prompt user. let dontConfirm = MvContainsElement(s:p4CmdOptions, s:SPACE_AS_SEP, '+y', ' ') if exists('s:confirmMsgs{s:p4Command}') && ! dontConfirm let option = s:ConfirmMessage(s:confirmMsgs{s:p4Command}, "&Yes\n&No", 2, \ "Question") if option == 2 let s:errCode = 2 return '' endif endif if MvContainsElement(s:limitListCmds, ',', s:p4Command) && \ ! MvContainsElement(s:p4CmdOptions, s:SPACE_AS_SEP, '-m', ' ') && \ s:defaultListSize > -1 let s:p4CmdOptions = '-m ' . s:defaultListSize . ' ' . s:p4CmdOptions let modifyWindowName = 1 endif if MvContainsElement(s:diffCmds, ',', s:p4Command) && \ ! MvContainsPattern(s:p4CmdOptions, s:SPACE_AS_SEP, '-d[cdnsu]', ' ') \ && s:defaultDiffOptions != "" let s:p4CmdOptions = s:defaultDiffOptions . ' ' . s:p4CmdOptions let modifyWindowName = 1 endif " Process p4Arguments. if ! dontProcess && s:p4Arguments == "" && s:commandMode == s:CM_RUN if (MvContainsElement(s:askUserCmds, ',', s:p4Command) && \ ! MvContainsElement(s:p4CmdOptions, s:SPACE_AS_SEP, '-i', ' ')) || \ MvContainsElement(s:p4CmdOptions, s:SPACE_AS_SEP, '+P', ' ') if MvContainsElement(s:genericPromptCmds, ',', s:p4Command) let prompt = 'Enter arguments for ' . s:p4Command . ': ' else let prompt = "Enter the " . s:p4Command . " name: " endif let additionalArg = s:PromptFor(0, s:useGUIDialogs, prompt, '') if additionalArg == "" if MvContainsElement(s:genericPromptCmds, ',', s:p4Command) call s:EchoMessage('Arguments required for '. s:p4Command, 'Error') else call s:EchoMessage(substitute(s:p4Command, "^.", '\U&', '') . \ " name required.", 'Error') endif let s:errCode = 2 return '' endif let s:p4Arguments = additionalArg elseif ! dontProcess && \ ! MvContainsElement(s:curFileNotDefCmds, ',', s:p4Command) && \ ! MvContainsElement(s:nofileArgsCmds, ',', s:p4Command) let s:p4Arguments = s:EscapeFileName(expand('%')) let modifyWindowName = 1 endif elseif ! dontProcess && match(s:p4Arguments, '[#@&]') != -1 " If there is an argument without a filename, then assume it is the current " file. " Pattern is the start of line or whitespace followed by an unprotected " [#@&] with a revision/codeline specifier and then again followed by " end of line or whitespace. let s:p4Arguments = substitute(s:p4Arguments, \ '\%(^\|\%('.s:SPACE_AS_SEP.'\)\+\)\@<=' . \ '\\\@') == -1 \ && ! MvContainsElement(s:noOutputCmds, ',', s:p4Command) let s:p4CmdOptions = "-o " . s:p4CmdOptions endif " Go into specification mode only if the user intends to edit the output. if ((s:p4Command == 'submit' && \ ! MvContainsElement(s:p4CmdOptions, s:SPACE_AS_SEP, '-c', ' ')) || \ (! MvContainsElement(s:specOnlyCmds, ',', s:p4Command) && \ ! MvContainsElement(s:p4CmdOptions, s:SPACE_AS_SEP, '-d', ' '))) && \ outputType == 0 let specMode = 1 endif endif let navigateCmd = 0 if MvContainsElement(s:navigateCmds, ',', s:p4Command) let navigateCmd = 1 endif let retryCount = 0 " CAUTION: This is like a do..while loop, but not exactly the same, be " careful using continue, the counter will not get incremented. while 1 " Save the context, as the below call may result in a reentrant call to " this function. call s:PushP4Context() let retVal = s:PFImpl(outputType, (testMode != 2 ? ! navigateCmd : 2), \ testMode, output) call s:PopP4Context() " Everything else in this loop is for password support. if s:errCode == 0 break else let output = retVal if output == '' let output = getline(1) endif if output =~ 'Perforce password (P4PASSWD) invalid or unset.' let g:p4Password = inputsecret("Password required for user " . s:p4User \ . ": ", s:p4Password) if g:p4Password == s:p4Password unlet g:p4Password break endif "call s:PushP4Context() call s:Initialize() "call s:PopP4Context() else break endif endif let retryCount = retryCount + 1 if retryCount > 2 break endif endwhile " We are doing checktime now for all commands, right in PFImpl() itself. "if MvContainsElement(s:checktimeReqCmds, ',', s:p4Command) " checktime "endif if s:errCode != 0 return '' endif if s:StartBufSetup(outputType) " If this command has a handler for the individual items, then enable the " item selection commands. if s:getCommandItemHandler(0, s:p4Command, '') != "" call s:SetupSelectItem() endif if !MvContainsElement(s:ftNotPerforceCmds, ',', s:p4Command) setlocal ft=perforce endif if MvContainsElement(s:filelistCmds, ',', s:p4Command) call s:SetupFileBrowse(outputType) endif if s:newWindowCreated if specMode let argStr = '' if s:p4Options !~ s:EMPTY_STR let argStr = CreateArgString(s:p4Options, s:SPACE_AS_SEP) . ',' endif " It is not possible to have an s:p4Command which is in s:p4KnownCmds " and still not be the actual intended command. if MvContainsElement(s:p4KnownCmds, ',', s:p4Command) let argStr = argStr . "'" . s:p4Command . "', " else " FIXME: Why am I using b:p4Command instead of s:p4Command here ??? let argStr = argStr . "'" . b:p4Command . "', " endif if s:p4CmdOptions !~ s:EMPTY_STR let argStr = argStr . CreateArgString(s:p4CmdOptions, ' ') . ', ' endif exec 'command! -buffer -nargs=* W :call W(0, ' . argStr . \ ')' exec 'command! -buffer -nargs=* WQ :call W(1, ' . argStr . \ ')' call s:EchoMessage("When done, save " . s:p4Command . \ " spec by using :W or :WQ command. Undo on errors.", 'None') call s:PFSetupForSpec() else " Define q to quit the read-only perforce windows (David Fishburn) nnoremap q q endif endif if navigateCmd nnoremap :call NavigateBack() nnoremap :call NavigateBack() nnoremap :call NavigateForward() endif call s:EndBufSetup(outputType) endif return retVal endfunction " PFIF }}} """ START: Adopted from Tom's perforce plugin. {{{ "--------------------------------------------------------------------------- " Produce string for ruler output function! s:P4RulerStatus() if exists('b:p4RulerStr') && b:p4RulerStr != "" return b:p4RulerStr endif if !exists('b:p4FStatDone') || !b:p4FStatDone return '' endif "let b:p4RulerStr = '[p4 ' let b:p4RulerStr = '[' if exists('b:p4RulerErr') && b:p4RulerErr != '' let b:p4RulerStr = b:p4RulerStr . b:p4RulerErr elseif !exists('b:p4HaveRev') let b:p4RulerStr = '' elseif b:p4Action == '' if b:p4OtherOpen == '' let b:p4RulerStr = b:p4RulerStr . 'unopened' else let b:p4RulerStr = b:p4RulerStr . b:p4OtherOpen . ':' . b:p4OtherAction endif else if b:p4Change == 'default' let b:p4RulerStr = b:p4RulerStr . b:p4Action else let b:p4RulerStr = b:p4RulerStr . b:p4Action . ':' . b:p4Change endif endif if exists('b:p4HaveRev') && b:p4HaveRev != '' let b:p4RulerStr = b:p4RulerStr . ' #' . b:p4HaveRev . '/' . b:p4HeadRev endif if b:p4RulerStr != '' let b:p4RulerStr = b:p4RulerStr . ']' endif return b:p4RulerStr endfunction function! s:GetClientInfo() let infoStr = s:PFIF(0, 4, "info") if s:errCode != 0 return s:SyntaxError(v:errmsg) endif let s:clientRoot = CleanupFileName(s:StrExtract(infoStr, \ '\CClient root: \f\+', 13)) endfunction " Get/refresh filestatus for the specified buffer with optimizations. function! s:GetFileStatus(buf, refresh) " If it is not a normal buffer, then ignore it. if &buftype != '' return "" endif if ! type(a:buf) " If number. let bufNr = (a:buf == 0) ? bufnr('%') : a:buf else let bufNr = bufnr(a:buf) endif if bufNr == -1 || (!a:refresh && s:optimizeActiveStatus && \ getbufvar(bufNr, "p4FStatDone")) return "" endif " This is an optimization by restricting status to the files under the " client root only. if !s:IsFileUnderDepot(expand('#'.bufNr)) return "" endif return s:GetFileStatusImpl(bufNr) endfunction function! s:ResetFileStatusForBuffer(bufNr) call setbufvar(a:bufNr, 'p4FStatDone', 0) endfunction "--------------------------------------------------------------------------- " Obtain file status information " TODO: " By running fstat with the depot file would generate more information, but " that would mean I should know the branch mapping. Unless I have a way to " cache the branch mappings, running 'where' command everytime will be too " slow. function! s:GetFileStatusImpl(bufNr) if bufname(a:bufNr) == "" return "" endif let fileName = fnamemodify(bufname(a:bufNr), ':p') let bufNr = a:bufNr " If the filename matches with one of the ignore patterns, then don't do " status. if s:ignoreDefPattern != '' && match(fileName, s:ignoreDefPattern) != -1 return "" endif if s:ignoreUsrPattern != '' && match(fileName, s:ignoreUsrPattern) != -1 return "" endif call setbufvar(bufNr, 'p4RulerStr', '') " Let this be reconstructed. let fileStatusStr = s:PFIF(1, 4, 'fstat', fileName) call setbufvar(bufNr, 'p4FStatDone', '1') if s:errCode != 0 call setbufvar(bufNr, 'p4RulerErr', "") return "" endif if match(fileStatusStr, ' - file(s) not in client view\.') >= 0 call setbufvar(bufNr, 'p4RulerErr', "") " Required for optimizing out in future runs. call setbufvar(bufNr, 'p4HeadRev', '') return "" elseif match(fileStatusStr, ' - no such file(s).') >= 0 call setbufvar(bufNr, 'p4RulerErr', "") " Required for optimizing out in future runs. call setbufvar(bufNr, 'p4HeadRev', '') return "" else call setbufvar(bufNr, 'p4RulerErr', '') endif call setbufvar(bufNr, 'p4HeadRev', \ s:StrExtract(fileStatusStr, '\CheadRev [0-9]\+', 8)) call setbufvar(bufNr, 'p4DepotFile', \ s:StrExtract(fileStatusStr, '\CdepotFile \f\+', 10)) call setbufvar(bufNr, 'p4ClientFile', \ s:StrExtract(fileStatusStr, '\CclientFile \f\+', 11)) call setbufvar(bufNr, 'p4HaveRev', \ s:StrExtract(fileStatusStr, '\ChaveRev [0-9]\+', 8)) call setbufvar(bufNr, 'p4Action', \ s:StrExtract(fileStatusStr, '\Caction [^[:space:]]\+', 7)) call setbufvar(bufNr, 'p4OtherOpen', \ s:StrExtract(fileStatusStr, '\CotherOpen0 [^[:space:]@]\+', 11)) call setbufvar(bufNr, 'p4OtherAction', \ s:StrExtract(fileStatusStr, '\CotherAction0 [^[:space:]@]\+', 13)) call setbufvar(bufNr, 'p4Change', \ s:StrExtract(fileStatusStr, '\Cchange [^[:space:]]\+', 7)) return fileStatusStr endfunction function! s:StrExtract(str, pat, pos) let part = matchstr(a:str, a:pat) let part = strpart(part, a:pos) return part endfunction function! s:AdjustRevision(file, adjustment) let s:errCode = 0 let revNum = a:adjustment if revNum =~ '[-+]\d\+' let revNum = substitute(revNum, '^+', '', '') if getbufvar(a:file, 'p4HeadRev') == '' " If fstat is not done yet, do it now. call s:GetFileStatus(a:file, 1) if getbufvar(a:file, 'p4HeadRev') == '' call s:EchoMessage("Current revision is not available. " . \ "To be able to use negative revisions, see help on " . \ "'perforce-active-status'.", 'Error') let s:errCode = 1 return -1 endif endif let revNum = getbufvar(a:file, 'p4HaveRev') + revNum if revNum < 1 call s:EchoMessage("Not that many revisions available. Try again " . \ "using PRefreshFileStatus command.", 'Error') let s:errCode = 1 return -1 endif endif return revNum endfunction "--------------------------------------------------------------------------- " One of a set of functions that returns fields from the p4 fstat command function! s:IsCurrent() let revdiff = b:p4HeadRev - b:p4HaveRev if revdiff == 0 return 0 else return -1 endif endfunction function! s:CheckOutFile() if ! s:promptToCheckout || ! s:IsFileUnderDepot(expand("%")) return endif if filereadable(expand("%")) && ! filewritable(expand("%")) let option = s:ConfirmMessage("Readonly file, do you want to checkout " . \ "from perforce?", "&Yes\n&No", s:checkOutDefault, "Question") if option == 1 call s:PFIF(1, 21, 'edit') if ! s:errCode " You need to explicitly execute this autocommand to get the change " detected and for other events (such as BufRead) to get fired. This " was suggested by Bram. " The currentCommand by now must have got reset, so we need to " explicitly set it and finally reset it. let currentCommand = s:currentCommand try let s:currentCommand = 'edit' exec "doautocmd FileChangedShell " . expand('%') finally let s:currentCommand = currentCommand endtry endif endif endif endfunction function! s:FileChangedShell() if s:activeStatusEnabled call s:ResetFileStatusForBuffer(expand("") + 0) endif let autoread = -1 if MvContainsElement(s:autoreadCmds, ',', s:currentCommand) let autoread = s:autoread endif return autoread endfunction """ END: Adapted from Tom's perforce plugin. }}} """ END: Middleware functions }}} """ BEGIN: Infrastructure {{{ " Assumes that the arguments are already parsed and are ready to be used in " the script variables. " Low level interface with the p4 command. " outputType (string): " 0 - Execute p4 and place the output in a new window. " 1 - Same as above, but use preview window. " 2 - Execute p4 and show the output in a dialog for confirmation. " 3 - Execute p4 and echo the output. " 4 - Execute p4 and return the output. " 5 - Execute p4 no output expected. Essentially same as 4 when the current " commandMode doesn't produce any output, just for clarification. " 20 - Execute p4 and if the output is less than s:maxLinesInDialog number of " lines, display a dialog (mode 2), otherwise display in a new window " (mode 0) " 21 - Same as 20, use mode 1 instead of 0. " clearBuffer (boolean): If the buffer contents should be cleared before " adding the new output. " testMode (number): " 0 - Run normally. " 1 - debugging, display the command-line instead of the actual output.. " 2 - testing, ignore. " Returns the output if available. If there is any error, the error code will " be available in s:errCode variable. function! s:PFImpl(outputType, clearBuffer, testMode, output) " {{{ " FIXME: Work-around. let _newWindowCreated = s:newWindowCreated let s:newWindowCreated = 0 try " [-2f] let s:recLevel = s:recLevel + 1 let outputType = a:outputType let s:errCode = 0 let fullCmd = '' if s:commandMode != s:CM_DISPLAY let fullCmd = s:MakeP4Cmd() endif " save the name of the current file. let p4CurFileName = expand("%") " If the output has to be shown in a window, position cursor appropriately, " creating a new window if required. let v:errmsg = "" " Ignore outputType in this case. if s:commandMode != s:CM_PIPE && s:commandMode != s:CM_FILTER if outputType == 0 || outputType == 1 call s:GotoWindow(outputType, a:clearBuffer, p4CurFileName) endif endif let output = "" if ! a:testMode && s:errCode == 0 let s:currentCommand = '' " Make sure all the already existing changes are detected. We don't have " s:currentCommand set here, so the user will get an appropriate prompt. checktime let s:currentCommand = s:p4Command try if s:commandMode == s:CM_RUN " If we are placing the output in a new window, then we should avoid " system() for performance reasons, imagine doing a 'print' on a " huge file. " These two outputType's correspond to placing the output in a window. if outputType != 0 && outputType != 1 let output = s:System(fullCmd, a:outputType) else exec '.call s:Filter(fullCmd, a:outputType, 1)' let output = "" endif elseif s:commandMode == s:CM_FILTER exec s:filterRange . 'call s:Filter(fullCmd, a:outputType, 1)' elseif s:commandMode == s:CM_PIPE exec s:filterRange . 'call s:Filter(fullCmd, a:outputType, 2)' elseif s:commandMode == s:CM_DISPLAY let output = a:output endif " Detect any new changes to the loaded buffers. " CAUTION: This actually results in a reentrant call back to this " function, but our Push/Pop mechanism for the context should take " care of it. checktime finally let s:currentCommand = '' endtry elseif a:testMode != 2 let output = fullCmd endif if s:errCode == 0 if outputType == 20 || outputType == 21 let nLines = strlen(substitute(output, "[^\n]", "", "g")) if nLines > s:maxLinesInDialog " Open the window now. let outputType = outputType - 20 call s:GotoWindow(outputType, a:clearBuffer, p4CurFileName) else let outputType = 2 endif endif let v:errmsg = "" " If we have non-null output, then handling it is still pending. if output != '' " If the output has to be shown in a dialog, bring up a dialog with the " output, otherwise show it in the current window. if outputType == 0 || outputType == 1 silent! put! =output elseif outputType == 2 call s:ConfirmMessage(output, "OK", 1, "Info") elseif outputType == 3 echo output elseif outputType == 4 " Do nothing we will just return it. endif endif endif return output finally " [+2s] if s:newWindowCreated call s:PFSetupBuf(expand('%')) let b:p4Command = s:p4Command let b:p4Options = s:p4Options let b:p4FullCmd = fullCmd if outputType == 1 wincmd p endif endif if s:recLevel > 1 let s:newWindowCreated = _newWindowCreated endif let s:recLevel = s:recLevel - 1 endtry endfunction " }}} " External command execution {{{ let s:ST_WIN_CMD = 0 | let s:ST_WIN_SH = 1 | let s:ST_UNIX = 2 function! s:GetShellEnvType() " When 'shellslash' option is available, then the platform must be one of " those that support '\' as a pathsep. if exists('+shellslash') if stridx(&shell, 'cmd.exe') != -1 || \ stridx(&shell, 'command.com') != -1 return s:ST_WIN_CMD else return s:ST_WIN_SH endif else return s:ST_UNIX endif endfunction function! s:System(fullCmd, outputType) return s:ExecCmd(a:fullCmd, a:outputType, 0) endfunction function! s:Filter(fullCmd, outputType, mode) range " For command-line, we need to protect '%', '#' and '!' chars, even if they " are in quotes, to avoid getting expanded by Vim before invoking external " cmd. let fullCmd = Escape(a:fullCmd, '%#!') exec a:firstline.",".a:lastline. \ "call s:ExecCmd(fullCmd, a:outputType, a:mode)" endfunction function! s:ExecCmd(fullCmd, outputType, mode) range let shellEnvType = s:GetShellEnvType() let v:errmsg = '' let output = '' try " Assume the shellredir is set correctly to capture the error messages. if a:mode == 0 let output = system(a:fullCmd) elseif a:mode == 1 silent! exec a:firstline.",".a:lastline."!".a:fullCmd else silent! exec a:firstline.",".a:lastline."write !".a:fullCmd endif call s:CheckShellError(output, a:outputType) return output catch /^Vim\%((\a\+)\)\=:E/ " 48[2-5] let v:errmsg = substitute(v:exception, '^[^:]\+:', '', '') call s:CheckShellError(output, a:outputType) catch /^Vim:Interrupt$/ let s:errCode = 1 catch " Ignore. endtry endfunction " Creates the actual p4 command that can be executed using system(). function! s:MakeP4Cmd() let addOptions = s:defaultOptions . ' ' if s:p4Client != "" && \ !MvContainsElement(s:p4Options, s:SPACE_AS_SEP, '-c', ' ') let addOptions = addOptions . '-c ' . s:p4Client . ' ' endif if s:p4User != "" && \ !MvContainsElement(s:p4Options, s:SPACE_AS_SEP, '-u', ' ') let addOptions = addOptions . '-u ' . s:p4User . ' ' endif if s:p4Port != "" && \ !MvContainsElement(s:p4Options, s:SPACE_AS_SEP, '-p', ' ') let addOptions = addOptions . '-p ' . s:p4Port . ' ' endif if s:p4Password != "" && \ !MvContainsElement(s:p4Options, s:SPACE_AS_SEP, '-P', ' ') let addOptions = addOptions . '-P ' . s:p4Password endif return s:CreateFullCmd(s:MakeP4CmdString(addOptions)) endfunction " - For windoze+native, use double-quotes to sorround the arguments and for " embedded double-quotes, just double them. " - For windoze+sh, use single-quotes to sorround the aruments and for embedded " single-quotes, just replace them with '""'""' (if 'shq' or 'sxq' is a " double-quote) and just '"'"' otherwise. Embedded double-quotes also need " to be doubled. " - For Unix+sh, use single-quotes to sorround the arguments and for embedded " single-quotes, just replace them with '"'"'. function! s:CreateFullCmd(cmd) let fullCmd = a:cmd " I am only worried about passing arguments with spaces as they are to the " external commands, I currently don't care about back-slashes " (backslashes are normally expected only on windows when 'shellslash' " option is set, but even then the 'shell' is expected to take care of " them.). However, for cygwin bash, there is a loss of one level " of the back-slashes somewhere in the chain of execution (most probably " between CreateProcess() and cygwin?), so we need to double them. let shellEnvType = s:GetShellEnvType() if shellEnvType == s:ST_WIN_CMD let quoteChar = '"' " Escape the existing double-quotes (by doubling them). let fullCmd = substitute(fullCmd, '"', '""', 'g') else let quoteChar = "'" if shellEnvType == s:ST_WIN_SH " Escape the existing double-quotes (by doubling them). let fullCmd = substitute(fullCmd, '"', '""', 'g') endif " Take care of existing single-quotes (by exposing them, as you can't have " single-quotes inside a single-quoted string). if &shellquote == '"' || &shellxquote == '"' let squoteRepl = "'\"\"'\"\"'" else let squoteRepl = "'\"'\"'" endif let fullCmd = substitute(fullCmd, "'", squoteRepl, 'g') endif " Now sorround the arguments with quotes, considering the protected " spaces. let fullCmd = substitute(fullCmd, \ '\%(^\)\@' let fullCmd = substitute(fullCmd, '\\', '\\\\', 'g') endif let g:p4FullCmd = fullCmd " Debug. return fullCmd endfunction " Generates a command string as the user typed, using the script variables. function! s:MakeP4CmdString(p4DefOptions) let opts = '' if s:p4Options !~ s:EMPTY_STR let opts = s:p4Options . ' ' elseif exists('b:p4Options') && b:p4Options !~ s:EMPTY_STR let opts = b:p4Options . ' ' endif " If there are duplicates, perfore takes the first option, so let opts come " before p4DefOptions. let cmdStr = opts . a:p4DefOptions . ' ' . s:p4Command . ' ' . s:p4CmdOptions \ . ' ' . s:p4Arguments " Consolidate multiple consecutive spaces into one. let cmdStr = s:CleanSpaces(cmdStr) " Remove the protection from the characters that we treat specially (Note: # " and % are treated specially by Vim command-line itself, and the " back-slashes are removed even before we see them.) let cmdStr = UnEscape(cmdStr, '&') return cmdStr endfunction " In case of outputType == 4, it assumes the caller wants to see the output as " it is, so no error message is given. The caller is expected to check for " error code, though. function! s:CheckShellError(output, outputType) if (v:shell_error != 0 || v:errmsg != '') && a:outputType != 4 let output = "There was an error executing external p4 command.\n" if v:errmsg != '' let output = output . "\n" . "errmsg = " . v:errmsg endif " When commandMode == s:CM_RUN, the error message may already be there in " the current window. if a:output != '' let output = output . "\n" . a:output elseif a:output == "" && \ (s:commandMode == s:CM_RUN && line('$') == 1 && col('$') == 1) let output = output . "\n" . \ "Check if your 'shellredir' option captures error messages." endif call s:ConfirmMessage(output, "OK", 1, "Error") endif let s:errCode = v:shell_error return v:shell_error endfunction " External command execution }}} " Push/Pop/Peek context {{{ function! s:PushP4Context() let contextString = "" let contextString = MvAddElement(contextString, s:p4ContextItemSeparator, \ s:p4Options) let contextString = MvAddElement(contextString, s:p4ContextItemSeparator, \ s:p4Command) let contextString = MvAddElement(contextString, s:p4ContextItemSeparator, \ s:p4CmdOptions) let contextString = MvAddElement(contextString, s:p4ContextItemSeparator, \ s:p4Arguments) let contextString = MvAddElement(contextString, s:p4ContextItemSeparator, \ s:p4Pipe) let contextString = MvAddElement(contextString, s:p4ContextItemSeparator, \ s:p4WinName) let contextString = MvAddElement(contextString, s:p4ContextItemSeparator, \ s:commandMode) let contextString = MvAddElement(contextString, s:p4ContextItemSeparator, \ s:filterRange) "let contextString = MvAddElement(contextString, s:p4ContextItemSeparator, " \ s:newWindowCreated) let s:p4Contexts = MvAddElement(s:p4Contexts, s:p4ContextSeparator, \ contextString) endfunction function! s:PeekP4Context() call s:PopP4ContextImpl(1) endfunction function! s:PopP4Context() call s:PopP4ContextImpl(0) endfunction function! s:PopP4ContextImpl(peek) let nContexts = MvNumberOfElements(s:p4Contexts, s:p4ContextSeparator) if nContexts <= 0 echoerr "PopP4Context: Contexts stack is empty" return endif let contextString = MvElementAt(s:p4Contexts, s:p4ContextSeparator, \ nContexts - 1) if !a:peek let s:p4Contexts = MvRemoveElementAt(s:p4Contexts, s:p4ContextSeparator, \ nContexts - 1) endif call MvIterCreate(contextString, s:p4ContextItemSeparator, "PopP4Context") let s:p4Options = MvIterNext("PopP4Context") let s:p4Command = MvIterNext("PopP4Context") let s:p4CmdOptions = MvIterNext("PopP4Context") let s:p4Arguments = MvIterNext("PopP4Context") let s:p4Pipe = MvIterNext("PopP4Context") let s:p4WinName = MvIterNext("PopP4Context") let s:commandMode = MvIterNext("PopP4Context") let s:filterRange = MvIterNext("PopP4Context") "let s:newWindowCreated = MvIterNext("PopP4Context") call MvIterDestroy("PopP4Context") endfunction " Push/Pop/Peek context }}} """ BEGIN: Argument parsing {{{ function! s:ResetP4Vars() " Syntax is: " PF | " Ex: PF -c hari integrate -b branch -s let s:p4Options = "" let s:p4Command = "" let s:p4CmdOptions = "" let s:p4Arguments = "" let s:p4Pipe = "" let s:p4WinName = "" " commandMode: " run - Execute p4 using system() or its equivalent. " filter - Execute p4 as a filter for the current window contents. Use " commandPrefix to restrict the filter range. " display - Don't execute p4. The output is already passed in. let s:commandMode = "run" let s:filterRange = "" " The command that is currently being executed. Used to determine autoread. let s:currentCommand = '' let s:errCode = 0 " FIXME: Strictly speaking this should be part of the context, but " currently, the context gets generated by the ParseOptions() function, " which doesn't know about this. For now, this is being worked-around by " explicitly saving and restoring everytime in PFIF() function (before the " ParseOptions() can get a chance to overwrite it). let s:newWindowCreated = 0 endfunction call s:ResetP4Vars() " Let them get initialized the first time. " Parses the arguments into 4 parts, "options to p4", "p4 command", " "options to p4 command", "actual arguments". Also generates the window name. function! s:ParseOptions(fline, lline, ...) " range call s:ResetP4Vars() if a:0 == 0 return endif let s:filterRange = a:fline . ',' . a:lline let i = 1 let prevArg = "" let curArg = "" let s:pendingPipeArg = '' while i <= a:0 try " Just for the sake of loop variables. [-2f] if s:pendingPipeArg != '' let curArg = s:pendingPipeArg let s:pendingPipeArg = '' elseif s:p4Pipe == '' let curArg = a:{i} let pipeIndex = match(curArg, '\\\@' && a:nWindows == NumberOfWindows() let s:newWindowCreated = 0 endif endfunction function! s:MakeWindowName() let cmdStr = s:MakeP4CmdString('p4') let winName = cmdStr "let winName = DeEscape(winName) " HACK: Work-around for some weird handling of buffer names that have "..." " (the perforce wildcard) at the end of the filename or in the middle " followed by a space. The autocommand is not getting triggered to clean " the buffer. If we append another character to this, I observed that the " autocommand gets triggered. Using "/" instead of "'" would probably be " more appropriate, but this is causing unexpected FileChangedShell " autocommands on certain filenames (try "PF submit ../..." e.g.). "let winName = substitute(winName, '\.\.\%( \|$\)\@=', '&/', 'g') let winName = substitute(winName, '\.\.\%( \|$\)\@=', "&'", 'g') " The intention is to do the substitute only on systems like windoze that " don't allow all characters in the filename, but I can't generalize it " enough, so as a workaround I a just assuming any system supporting " 'shellslash' option to be a windoze like system. In addition, cygwin " vim thinks that it is on Unix and tries to allow all characters, but " since the underlying OS doesn't support it, we need the same treatment " here also. if exists('+shellslash') || has('win32unix') " Some characters are not allowed in a filename on windows so substitute " them with something else. let winName = substitute(winName, s:specialChars, \ '\="[" . s:specialChars{submatch(1)} . "]"', 'g') "let winName = substitute(winName, s:specialChars, '\\\1', 'g') endif " Finally escape some characters again. let winName = Escape(winName, " #%\t") if s:GetShellEnvType() == s:ST_UNIX let winName = substitute(winName, '\\\@$", '%s///e') setlocal nomodified setlocal nomodifiable setlocal foldcolumn=0 if s:p4HideOnBufHidden setlocal bufhidden=hide else setlocal bufhidden= call s:PFSetupBufAutoCommand(a:bufName, 'BufUnload', \ ':call PFExecBufClean(expand("") + 0)') endif endfunction function! s:PFSetupForSpec() setlocal modifiable set buftype= call s:PFSetupBufAutoCommand(expand('%'), 'BufWriteCmd', ':W') endfunction function! s:WipeoutP4Buffers(...) let testMode = 1 if a:0 > 0 && a:1 == '+y' let testMode = 0 endif let i = 1 let lastBuf = bufnr('$') let cleanedBufs = '' while i <= lastBuf if bufexists(i) && expand('#'.i) =~ '\TestParseOptions() function! s:TestParseOptions(commandName, ...) range exec MakeArgumentString() exec "call s:ParseOptionsIF(a:firstline, a:lastline," . \ " 0, a:commandName, " . argumentString . ")" echo "p4Options :" . s:p4Options . ":" echo "p4Command :" . s:p4Command . ":" echo "p4CmdOptions :" . s:p4CmdOptions . ":" echo "p4Arguments :" . s:p4Arguments . ":" echo "p4Pipe :" . s:p4Pipe . ":" echo "p4WinName :" . s:p4WinName . ":" echo "commandMode :" . s:commandMode . ":" echo "filterRange :" . s:filterRange . ":" echo "Cmd :" . s:CreateFullCmd(s:MakeP4CmdString('')) . ":" endfunction "function! s:TestPushPopContexts() " let s:p4Options = "options1" " let s:p4Command = "command1" " let s:p4CmdOptions = "cmdOptions1" " let s:p4Arguments = "arguments1" " let s:p4WinName = "winname1" " call s:PushP4Context() " " let s:p4Options = "options2" " let s:p4Command = "command2" " let s:p4CmdOptions = "cmdOptions2" " let s:p4Arguments = "arguments2" " let s:p4WinName = "winname2" " call s:PushP4Context() " " call s:ResetP4Vars() " echo "After reset: " . s:CreateFullCmd(s:MakeP4CmdString('')) " call s:PopP4Context() " echo "After pop1: " . s:CreateFullCmd(s:MakeP4CmdString('')) " call s:PopP4Context() " echo "After pop2: " . s:CreateFullCmd(s:MakeP4CmdString('')) "endfunction """ END: Testing }}} """ END: Infrastructure }}} " Restore cpo. let &cpo = s:save_cpo unlet s:save_cpo " vim6:fdm=marker