Vim 7.4 won't let us quit windows from a script.
[profile.git] / .vim / plugin / gitv.vim
1 "AUTHOR:   Greg Sexton <gregsexton@gmail.com>
2 "WEBSITE:  http://www.gregsexton.org/portfolio/gitv/
3 "LICENSE:  Same terms as Vim itself (see :help license).
4 "NOTES:    Much of the credit for gitv goes to Tim Pope and the fugitive plugin
5 "          where this plugin either uses functionality directly or was inspired heavily.
6
7 if exists("g:loaded_gitv") || v:version < 700
8   finish
9 endif
10 let g:loaded_gitv = 1
11
12 let s:savecpo = &cpo
13 set cpo&vim
14
15 "configurable options:
16 "g:Gitv_CommitStep             - int
17 "g:Gitv_OpenHorizontal         - {0,1,'AUTO'}
18 "g:Gitv_GitExecutable          - string
19 "g:Gitv_WipeAllOnClose         - int
20 "g:Gitv_WrapLines              - {0,1}
21 "g:Gitv_TruncateCommitSubjects - {0,1}
22
23 if !exists("g:Gitv_CommitStep")
24     let g:Gitv_CommitStep = &lines
25 endif
26
27 if !exists('g:Gitv_GitExecutable')
28     let g:Gitv_GitExecutable = 'git'
29 endif
30
31 if !exists('g:Gitv_WipeAllOnClose')
32     let g:Gitv_WipeAllOnClose = 0 "default for safety
33 endif
34
35 if !exists('g:Gitv_WrapLines')
36     let g:Gitv_WrapLines = 0
37 endif
38
39 if !exists('g:Gitv_TruncateCommitSubjects')
40     let g:Gitv_TruncateCommitSubjects = 0
41 endif
42
43 "this counts up each time gitv is opened to ensure a unique file name
44 let g:Gitv_InstanceCounter = 0
45
46 let s:localUncommitedMsg = '*  Local uncommitted changes, not checked in to index.'
47 let s:localCommitedMsg   = '*  Local changes checked in to index but not committed.'
48
49 command! -nargs=* -bang Gitv call s:OpenGitv(shellescape(<q-args>), <bang>0)
50 cabbrev gitv Gitv
51
52 "Public API:"{{{
53 fu! Gitv_OpenGitCommand(command, windowCmd, ...) "{{{
54     "returns 1 if command succeeded with output
55     "optional arg is a flag, if present runs command verbatim
56
57     "this function is not limited to script scope as is useful for running other commands.
58     "e.g call Gitv_OpenGitCommand("diff --no-color", 'vnew') is useful for getting an overall git diff.
59
60     let [result, finalCmd] = s:RunGitCommand(a:command, a:0)
61
62     if type(result) == type(0)
63         return 0
64     endif
65     if type(result) == type("") && result == ""
66         echom "No output."
67         return 0
68     else
69         if a:windowCmd == ''
70             silent setlocal modifiable
71             silent setlocal noreadonly
72             1,$ d
73         else
74             let goBackTo       = winnr()
75             let dir            = s:GetRepoDir()
76             let workingDir     = fnamemodify(dir,':h')
77             let cd             = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
78             let bufferDir      = getcwd()
79             let tempSplitBelow = &splitbelow
80             let tempSplitRight = &splitright
81             try
82                 set nosplitbelow
83                 set nosplitright
84                 execute cd.'`=workingDir`'
85                 exec a:windowCmd
86                 let newWindow = winnr()
87             finally
88                 exec goBackTo . 'wincmd w'
89                 execute cd.'`=bufferDir`'
90                 if exists('newWindow')
91                     exec newWindow . 'wincmd w'
92                 endif
93                 exec 'set '. (tempSplitBelow ? '' : 'no') . 'splitbelow'
94                 exec 'set '. (tempSplitRight ? '' : 'no') . 'splitright'
95             endtry
96         endif
97         if !(&modifiable)
98             return 0
99         endif
100         let b:Git_Command = finalCmd
101         silent setlocal ft=git
102         silent setlocal buftype=nofile
103         silent setlocal nobuflisted
104         silent setlocal noswapfile
105         silent setlocal bufhidden=wipe
106         silent setlocal nonumber
107         if g:Gitv_WrapLines
108             silent setlocal wrap
109         else
110             silent setlocal nowrap
111         endif
112         silent setlocal fdm=syntax
113         silent setlocal foldlevel=0
114         nmap <buffer> <silent> q :q!<CR>
115         nmap <buffer> <silent> u :if exists('b:Git_Command')<bar>call Gitv_OpenGitCommand(b:Git_Command, '', 1)<bar>endif<cr>
116         call append(0, split(result, '\n')) "system converts eols to \n regardless of os.
117         silent setlocal nomodifiable
118         silent setlocal readonly
119         1
120         return 1
121     endif
122 endf "}}} }}}
123 "General Git Functions: "{{{
124 fu! s:RunGitCommand(command, verbatim) "{{{
125     "if verbatim returns result of system command, else
126     "switches to the buffer repository before running the command and switches back after.
127     if !a:verbatim
128         "switches to the buffer repository before running the command and switches back after.
129         let dir        = s:GetRepoDir()
130         let workingDir = fnamemodify(dir,':h')
131         if workingDir == ''
132             return 0
133         endif
134
135         let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
136         let bufferDir = getcwd()
137         try
138             execute cd.'`=workingDir`'
139             let finalCmd = g:Gitv_GitExecutable.' --git-dir="' .dir. '" ' . a:command
140             let result   = system(finalCmd)
141         finally
142             execute cd.'`=bufferDir`'
143         endtry
144     else
145         let result   = system(a:command)
146         let finalCmd = a:command
147     endif
148     return [result, finalCmd]
149 endfu "}}}
150 fu! s:GetRepoDir() "{{{
151     let dir = fugitive#buffer().repo().dir()
152     if dir == ''
153         echom "No git repository could be found."
154     endif
155     return dir
156 endfu "}}} }}}
157 "Open And Update Gitv:"{{{
158 fu! s:OpenGitv(extraArgs, fileMode) "{{{
159     let sanatizedArgs = a:extraArgs   == "''" ? '' : a:extraArgs
160     let sanatizedArgs = sanatizedArgs == '""' ? '' : sanatizedArgs
161     let g:Gitv_InstanceCounter += 1
162     if !s:IsCompatible() "this outputs specific errors
163         return
164     endif
165     try
166         if a:fileMode
167             call s:OpenFileMode(sanatizedArgs)
168         else
169             call s:OpenBrowserMode(sanatizedArgs)
170         endif
171     catch /not a git repository/
172         echom 'Not a git repository.'
173         return
174     endtry
175 endf "}}}
176 fu! s:IsCompatible() "{{{
177     if !exists('g:loaded_fugitive')
178         echoerr "gitv requires the fugitive plugin to be installed."
179     endif
180     return exists('g:loaded_fugitive')
181 endfu "}}}
182 fu! s:OpenBrowserMode(extraArgs) "{{{
183     "this throws an exception if not a git repo which is caught immediately
184     let fubuffer = fugitive#buffer()
185     silent Gtabedit HEAD
186
187     if s:IsHorizontal()
188         let direction = 'new gitv'.'-'.g:Gitv_InstanceCounter
189     else
190         let direction = 'vnew gitv'.'-'.g:Gitv_InstanceCounter
191     endif
192     if !s:LoadGitv(direction, 0, g:Gitv_CommitStep, a:extraArgs, '')
193         return 0
194     endif
195     call s:SetupBufferCommands(0)
196     "open the first commit
197     silent call s:OpenGitvCommit("Gedit", 0)
198 endf "}}}
199 fu! s:OpenFileMode(extraArgs) "{{{
200     let relPath = fugitive#buffer().path()
201     pclose!
202     if !s:LoadGitv(&previewheight . "new gitv".'-'.g:Gitv_InstanceCounter, 0, g:Gitv_CommitStep, a:extraArgs, relPath)
203         return 0
204     endif
205     set previewwindow
206     set winfixheight
207     let b:Gitv_FileMode = 1
208     let b:Gitv_FileModeRelPath = relPath
209     call s:SetupBufferCommands(1)
210 endf "}}}
211 fu! s:LoadGitv(direction, reload, commitCount, extraArgs, filePath) "{{{
212     if a:reload
213         let jumpTo = line('.') "this is for repositioning the cursor after reload
214     endif
215
216     if !s:ConstructAndExecuteCmd(a:direction, a:reload, a:commitCount, a:extraArgs, a:filePath)
217         return 0
218     endif
219     call s:SetupBuffer(a:commitCount, a:extraArgs, a:filePath)
220     exec exists('jumpTo') ? jumpTo : '1'
221     call s:SetupMappings() "redefines some of the mappings made by Gitv_OpenGitCommand
222     call s:ResizeWindow(a:filePath!='')
223
224     echom "Loaded up to " . a:commitCount . " commits."
225     return 1
226 endf "}}}
227 fu! s:ConstructAndExecuteCmd(direction, reload, commitCount, extraArgs, filePath) "{{{
228     if a:reload "run the same command again with any extra args
229         if exists('b:Git_Command')
230             "substitute in the potentially new commit count taking account of a potential filePath
231             let newcmd = b:Git_Command
232             if a:filePath != ''
233                 let newcmd = substitute(newcmd, " -- " . a:filePath . "$", "", "")
234             endif
235             let newcmd = substitute(newcmd, " -\\d\\+$", " -" . a:commitCount, "")
236             if a:filePath != ''
237                 let newcmd .= ' -- ' . a:filePath
238             endif
239             silent let res = Gitv_OpenGitCommand(newcmd, a:direction, 1)
240             return res
241         endif
242     else
243         let cmd  = "log " . a:extraArgs 
244         let cmd .= " --no-color --decorate --pretty=format:\"%d %s__SEP__%ar__SEP__%an__SEP__[%h]\" --graph -" 
245         let cmd .= a:commitCount
246         if a:filePath != ''
247             let cmd .= ' -- ' . a:filePath
248         endif
249         silent let res = Gitv_OpenGitCommand(cmd, a:direction)
250         return res
251     endif
252     return 0
253 endf "}}}
254 fu! s:SetupBuffer(commitCount, extraArgs, filePath) "{{{
255     silent set filetype=gitv
256     let b:Gitv_CommitCount = a:commitCount
257     let b:Gitv_ExtraArgs   = a:extraArgs
258     silent setlocal modifiable
259     silent setlocal noreadonly
260     silent %s/refs\/tags\//t:/ge
261     silent %s/refs\/remotes\//r:/ge
262     silent %s/refs\/heads\///ge
263     silent %call s:Align("__SEP__", a:filePath)
264     silent %s/\s\+$//e
265     call s:AddLoadMore()
266     call s:AddLocalNodes(a:filePath)
267     if a:filePath != ''
268         call append(0, '-- ['.a:filePath.'] --')
269     endif
270     silent setlocal nomodifiable
271     silent setlocal readonly
272     silent setlocal cursorline
273 endf "}}}
274 fu! s:AddLocalNodes(filePath) "{{{
275     let suffix = a:filePath == '' ? '' : ' -- '.a:filePath
276     let gitCmd = "diff --no-color --cached" . suffix
277     let [result, cmd] = s:RunGitCommand(gitCmd, 0)
278     if result != ""
279         call append(0, s:localCommitedMsg)
280     endif
281     let gitCmd = "diff --no-color" . suffix
282     let [result, cmd] = s:RunGitCommand(gitCmd, 0)
283     if result != ""
284         call append(0, s:localUncommitedMsg)
285     endif
286 endfu "}}}
287 fu! s:AddLoadMore() "{{{
288     call append(line('$'), '-- Load More --')
289 endfu "}}}
290 fu! s:SetupMappings() "{{{
291     "operations
292     nmap <buffer> <silent> <cr> :call <SID>OpenGitvCommit("Gedit", 0)<cr>
293     nmap <buffer> <silent> o :call <SID>OpenGitvCommit("Gsplit", 0)<cr>
294     nmap <buffer> <silent> O :call <SID>OpenGitvCommit("Gtabedit", 0)<cr>
295     nmap <buffer> <silent> s :call <SID>OpenGitvCommit("Gvsplit", 0)<cr>
296     "force opening the fugitive buffer for the commit
297     nmap <buffer> <silent> <c-cr> :call <SID>OpenGitvCommit("Gedit", 1)<cr>
298
299     nmap <buffer> <silent> q :call <SID>CloseGitv()<cr>
300     nmap <buffer> <silent> u :call <SID>LoadGitv('', 1, b:Gitv_CommitCount, b:Gitv_ExtraArgs, <SID>GetRelativeFilePath())<cr>
301     nmap <buffer> <silent> co :call <SID>CheckOutGitvCommit()<cr>
302
303     nmap <buffer> <silent> D :call <SID>DiffGitvCommit()<cr>
304     vmap <buffer> <silent> D :call <SID>DiffGitvCommit()<cr>
305
306     nmap <buffer> <silent> S :call <SID>StatGitvCommit()<cr>
307     vmap <buffer> <silent> S :call <SID>StatGitvCommit()<cr>
308
309     "movement
310     nmap <buffer> <silent> x :call <SID>JumpToBranch(0)<cr>
311     nmap <buffer> <silent> X :call <SID>JumpToBranch(1)<cr>
312     nmap <buffer> <silent> r :call <SID>JumpToRef(0)<cr>
313     nmap <buffer> <silent> R :call <SID>JumpToRef(1)<cr>
314     nmap <buffer> <silent> P :call <SID>JumpToHead()<cr>
315 endf "}}}
316 fu! s:SetupBufferCommands(fileMode) "{{{
317     silent command! -buffer -nargs=* -complete=customlist,s:fugitive_GitComplete Git call <sid>MoveIntoPreviewAndExecute("Git <args>",1)|normal u
318 endfu "}}}
319 fu! s:ResizeWindow(fileMode) "{{{
320     if a:fileMode "window height determined by &previewheight
321         return
322     endif
323     if !s:IsHorizontal()
324         "size window based on longest line
325         let longest = max(map(range(1, line('$')), "virtcol([v:val, '$'])"))
326         if longest > &columns/2
327             "potentially auto change to horizontal
328             if s:AutoHorizontal()
329                 "switching to horizontal
330                 let b:Gitv_AutoHorizontal=1
331                 wincmd K
332                 call s:ResizeWindow(a:fileMode)
333                 return
334             else
335                 let longest = &columns/2
336             endif
337         endif
338         exec "vertical resize " . longest
339     else
340         "size window based on num lines
341         call s:ResizeHorizontal()
342     endif
343 endf "}}} }}}
344 "Utilities:"{{{
345 fu! s:GetGitvSha(lineNumber) "{{{
346     let l = getline(a:lineNumber)
347     let sha = matchstr(l, "\\[\\zs[0-9a-f]\\{7}\\ze\\]$")
348     return sha
349 endf "}}}
350 fu! s:GetGitvRefs() "{{{
351     let l = getline('.')
352     let refstr = matchstr(l, "^\\(\\(|\\|\\/\\|\\\\\\|\\*\\)\\s\\?\\)*\\s\\+(\\zs.\\{-}\\ze)")
353     let refs = split(refstr, ', ')
354     return refs
355 endf "}}}
356 fu! s:RecordBufferExecAndWipe(cmd, wipe) "{{{
357     "this should be used to replace the buffer in a window
358     let buf = bufnr('%')
359     exec a:cmd
360     if a:wipe
361         "safe guard against wiping out buffer you're in
362         if bufnr('%') != buf && bufexists(buf)
363             exec 'bwipeout ' . buf
364         endif
365     endif
366 endfu "}}}
367 fu! s:MoveIntoPreviewAndExecute(cmd, tryToOpenNewWin) "{{{
368     if winnr("$") == 1 "is the only window
369         call s:AttemptToCreateAPreviewWindow(a:tryToOpenNewWin, a:cmd)
370         return
371     endif
372     let horiz      = s:IsHorizontal()
373     let filem      = s:IsFileMode()
374     let currentWin = winnr()
375
376     if horiz || filem
377         wincmd j
378     else
379         wincmd l
380     endif
381
382     if currentWin == winnr() "haven't moved anywhere
383         call s:AttemptToCreateAPreviewWindow(a:tryToOpenNewWin, a:cmd)
384         return
385     endif
386
387     silent exec a:cmd
388     if horiz || filem
389         wincmd k
390     else
391         wincmd h
392     endif
393 endfu "}}}
394 fu! s:AttemptToCreateAPreviewWindow(shouldAttempt, cmd) "{{{
395     if a:shouldAttempt
396         call s:CreateNewPreviewWindow()
397         call s:MoveIntoPreviewAndExecute(a:cmd, 0)
398     else
399         echoerr "No preview window detected."
400     endif
401 endfu "}}}
402 fu! s:CreateNewPreviewWindow() "{{{
403     "this should not be called by anything other than AttemptToCreateAPreviewWindow
404     let horiz      = s:IsHorizontal()
405     let filem      = s:IsFileMode()
406     if horiz || filem
407         Gsplit HEAD
408     else
409         Gvsplit HEAD
410     endif
411     wincmd x
412 endfu "}}}
413 fu! s:IsHorizontal() "{{{
414     "NOTE: this can only tell you if horizontal while cursor in browser window
415     let horizGlobal = exists('g:Gitv_OpenHorizontal') && g:Gitv_OpenHorizontal == 1
416     let horizBuffer = exists('b:Gitv_AutoHorizontal') && b:Gitv_AutoHorizontal == 1
417     return horizGlobal || horizBuffer
418 endf "}}}
419 fu! s:AutoHorizontal() "{{{
420     return exists('g:Gitv_OpenHorizontal') && 
421                 \ type(g:Gitv_OpenHorizontal) == type("") && 
422                 \ g:Gitv_OpenHorizontal ==? 'auto'
423 endf "}}}
424 fu! s:IsFileMode() "{{{
425     return exists('b:Gitv_FileMode') && b:Gitv_FileMode == 1
426 endf "}}}
427 fu! s:ResizeHorizontal() "{{{
428     let lines = line('$')
429     if lines > (&lines/2)-2
430         let lines = (&lines/2)-2
431     endif
432     exec "resize " . lines
433 endf "}}}
434 fu! s:GetRelativeFilePath() "{{{
435     return exists('b:Gitv_FileModeRelPath') ? b:Gitv_FileModeRelPath : ''
436 endf "}}}
437 fu! s:OpenRelativeFilePath(sha, geditForm) "{{{
438     let relPath = s:GetRelativeFilePath()
439     if relPath == ''
440         return
441     endif
442     let cmd = a:geditForm . " " . a:sha . ":" . relPath
443     let cmd = 'call s:RecordBufferExecAndWipe("'.cmd.'", '.(a:geditForm=='Gedit').')'
444     call s:MoveIntoPreviewAndExecute(cmd, 1)
445 endf "}}} }}}
446 "Mapped Functions:"{{{
447 "Operations: "{{{
448 fu! s:OpenGitvCommit(geditForm, forceOpenFugitive) "{{{
449     if getline('.') == "-- Load More --"
450         call s:LoadGitv('', 1, b:Gitv_CommitCount+g:Gitv_CommitStep, b:Gitv_ExtraArgs, s:GetRelativeFilePath())
451         return
452     endif
453     if s:IsFileMode() && getline('.') =~ "^-- \\[.*\\] --$"
454         call s:OpenWorkingCopy(a:geditForm)
455         return
456     endif
457     if getline('.') == s:localUncommitedMsg
458         call s:OpenWorkingDiff(a:geditForm, 0)
459         return
460     endif
461     if getline('.') == s:localCommitedMsg
462         call s:OpenWorkingDiff(a:geditForm, 1)
463         return
464     endif
465     let sha = s:GetGitvSha(line('.'))
466     if sha == ""
467         return
468     endif
469     if s:IsFileMode() && !a:forceOpenFugitive
470         call s:OpenRelativeFilePath(sha, a:geditForm)
471     else
472         let cmd = a:geditForm . " " . sha
473         let cmd = 'call s:RecordBufferExecAndWipe("'.cmd.'", '.(a:geditForm=='Gedit').')'
474         call s:MoveIntoPreviewAndExecute(cmd, 1)
475     endif
476 endf
477 fu! s:OpenWorkingCopy(geditForm)
478     let fp = s:GetRelativeFilePath()
479     let form = a:geditForm[1:] "strip off the leading 'G'
480     let cmd = form . " " . fugitive#buffer().repo().tree() . "/" . fp
481     let cmd = 'call s:RecordBufferExecAndWipe("'.cmd.'", '.(form=='edit').')'
482     call s:MoveIntoPreviewAndExecute(cmd, 1)
483 endfu
484 fu! s:OpenWorkingDiff(geditForm, staged)
485     let winCmd = a:geditForm[1:] == 'edit' ? '' : a:geditForm[1:]
486     if s:IsFileMode()
487         let fp = s:GetRelativeFilePath()
488         let suffix = ' -- '.fp
489         let g:Gitv_InstanceCounter += 1
490         let winCmd = 'new gitv'.'-'.g:Gitv_InstanceCounter
491     else
492         let suffix = ''
493     endif
494     if a:staged
495         let cmd = 'call Gitv_OpenGitCommand(\"diff --no-color --cached'.suffix.'\", \"'.winCmd.'\")'
496     else
497         let cmd = 'call Gitv_OpenGitCommand(\"diff --no-color'.suffix.'\", \"'.winCmd.'\")'
498     endif
499     let cmd = 'call s:RecordBufferExecAndWipe("'.cmd.'", '.(winCmd=='').')'
500     call s:MoveIntoPreviewAndExecute(cmd, 1)
501 endfu "}}}
502 fu! s:CheckOutGitvCommit() "{{{
503     let allrefs = s:GetGitvRefs()
504     let sha = s:GetGitvSha(line('.'))
505     if sha == ""
506         return
507     endif
508     let refs   = allrefs + [sha]
509     let refstr = join(refs, "\n")
510     let choice = confirm("Checkout commit:", refstr . "\nCancel")
511     if choice == 0
512         return
513     endif
514     let choice = get(refs, choice-1, "")
515     if choice == ""
516         return
517     endif
518     let choice = substitute(choice, "^t:", "", "")
519     let choice = substitute(choice, "^r:", "", "")
520     if s:IsFileMode()
521         let relPath = s:GetRelativeFilePath()
522         let choice .= " -- " . relPath
523     endif
524     exec "Git checkout " . choice
525 endf "}}}
526 fu! s:CloseGitv() "{{{
527     if s:IsFileMode()
528         q
529     else
530         if g:Gitv_WipeAllOnClose
531             silent windo setlocal bufhidden=wipe
532         endif
533         let moveLeft = tabpagenr() == tabpagenr('$') ? 0 : 1
534         tabc
535         if moveLeft && tabpagenr() != 1
536             tabp
537         endif
538     endif
539 endf "}}}
540 fu! s:DiffGitvCommit() range "{{{
541     if !s:IsFileMode()
542         echom "Diffing is not possible in browser mode."
543         return
544     endif
545     let shafirst = s:GetGitvSha(a:firstline)
546     let shalast  = s:GetGitvSha(a:lastline)
547     if shafirst == "" || shalast == ""
548         return
549     endif
550     if a:firstline != a:lastline
551         call s:OpenRelativeFilePath(shafirst, "Gedit")
552     endif
553     call s:MoveIntoPreviewAndExecute("Gdiff " . shalast, a:firstline != a:lastline)
554 endf "}}} 
555 fu! s:StatGitvCommit() range "{{{
556     let shafirst = s:GetGitvSha(a:firstline)
557     let shalast  = s:GetGitvSha(a:lastline)
558     if shafirst == "" || shalast == ""
559         return
560     endif
561     let cmd  = 'diff '.shafirst
562     if shafirst != shalast
563         let cmd .= ' '.shalast
564     endif
565     let cmd .= ' --stat'
566     let cmd = "call s:SetupStatBuffer('".cmd."')"
567     if s:IsFileMode()
568         exec cmd
569     else
570         call s:MoveIntoPreviewAndExecute(cmd, 1)
571     endif
572 endf
573 fu! s:SetupStatBuffer(cmd)
574     silent let res = Gitv_OpenGitCommand(a:cmd, s:IsFileMode()?'vnew':'')
575     if res
576         silent set filetype=gitv
577     endif
578 endfu "}}} }}}
579 "Movement: "{{{
580 fu! s:JumpToBranch(backward) "{{{
581     if a:backward
582         silent! ?|/\||\\?-1
583     else
584         silent! /|\\\||\//+1
585     endif
586 endf "}}}
587 fu! s:JumpToRef(backward) "{{{
588     if a:backward
589         silent! ?^\(\(|\|\/\|\\\|\*\)\s\=\)\+\s\+\zs(
590     else
591         silent! /^\(\(|\|\/\|\\\|\*\)\s\?\)\+\s\+\zs(/
592     endif
593 endf "}}}
594 fu! s:JumpToHead() "{{{
595     silent! /^\(\(|\|\/\|\\\|\*\)\s\?\)\+\s\+\zs(HEAD/
596 endf "}}}
597 "}}} }}}
598 "Align And Truncate Functions: "{{{
599 fu! s:Align(seperator, filePath) range "{{{
600     let lines = getline(a:firstline, a:lastline)
601     call map(lines, 'split(v:val, a:seperator)')
602
603     let newlines = copy(lines)
604     call filter(newlines, 'len(v:val)>1')
605     let maxLens = s:MaxLengths(newlines)
606
607     let newlines = []
608     for tokens in lines
609         if len(tokens)>1
610             let newline = []
611             for i in range(len(tokens))
612                 let token = tokens[i]
613                 call add(newline, token . repeat(' ', maxLens[i]-strlen(token)+1))
614             endfor
615             call add(newlines, newline)
616         else
617             call add(newlines, tokens)
618         endif
619     endfor
620
621     if g:Gitv_TruncateCommitSubjects
622         call s:TruncateLines(newlines, a:filePath)
623     endif
624
625     call map(newlines, "join(v:val)")
626     call setline(a:firstline, newlines)
627 endfu "}}}
628 fu! s:TruncateLines(lines, filePath) "{{{
629     "truncates the commit subject for any line > &columns
630     call map(a:lines, "s:TruncateHelp(v:val, a:filePath)")
631 endfu "}}}
632 fu! s:TruncateHelp(line, filePath) "{{{
633     let length = strlen(join(a:line))
634     let maxWidth = s:IsHorizontal() ? &columns : &columns/2
635     let maxWidth = a:filePath != '' ? winwidth(0) : maxWidth
636     if length > maxWidth
637         let delta = length - maxWidth
638         "offset = 3 for the elipsis and 1 for truncation
639         let offset = 3 + 1
640         if a:line[0][-(delta + offset + 1):] =~ "^\\s\\+$"
641             let extension = "   "
642         else
643             let extension = "..."
644         endif
645         let a:line[0] = a:line[0][:-(delta + offset)] . extension
646     endif
647     return a:line
648 endfu "}}}
649 fu! s:MaxLengths(colls) "{{{
650     "precondition: coll is a list of lists of strings -- should be rectangular
651     "returns a list of maximum string lengths
652     let lengths = []
653     for x in a:colls
654         for y in range(len(x))
655             let length = strlen(x[y])
656             if length > get(lengths, y, 0)
657                 if len(lengths)-1 < y
658                     call add(lengths, length)
659                 else
660                     let lengths[y] = length
661                 endif
662             endif
663         endfor
664     endfor
665     return lengths
666 endfu "}}} }}}
667 "Fugitive Functions: "{{{
668 "These functions are lifted directly from fugitive and modified only to work with gitv.
669 function! s:fugitive_sub(str,pat,rep) abort "{{{
670   return substitute(a:str,'\v\C'.a:pat,a:rep,'')
671 endfunction "}}}
672 function! s:fugitive_GitComplete(A,L,P) abort "{{{
673   if !exists('s:exec_path')
674     let s:exec_path = s:fugitive_sub(system(g:fugitive_git_executable.' --exec-path'),'\n$','')
675   endif
676   let cmds = map(split(glob(s:exec_path.'/git-*'),"\n"),'s:fugitive_sub(v:val[strlen(s:exec_path)+5 : -1],"\\.exe$","")')
677   if a:L =~ ' [[:alnum:]-]\+ '
678     return fugitive#buffer().repo().superglob(a:A)
679   elseif a:A == ''
680     return cmds
681   else
682     return filter(cmds,'v:val[0 : strlen(a:A)-1] ==# a:A')
683   endif
684 endfunction "}}} }}}
685
686 let &cpo = s:savecpo
687 unlet s:savecpo
688
689  " vim:fdm=marker