Fix missing slashes in short dir.
[profile.git] / .vim / plugin / undotree.vim
1 "=================================================
2 " File: undotree.vim
3 " Description: Manage your undo history in a graph.
4 " Author: Ming Bai <mbbill@gmail.com>
5 " License: BSD
6
7 " Avoid installing twice.
8 if exists('g:loaded_undotree')
9     finish
10 endif
11 let g:loaded_undotree = 0
12
13 " At least version 7.3 with 005 patch is needed for undo branches.
14 " Refer to https://github.com/mbbill/undotree/issues/4 for details.
15 " Thanks kien
16 if v:version < 703
17     "command! -n=0 -bar UndotreeToggle :echoerr "undotree.vim needs Vim version >= 7.3"
18     finish
19 endif
20 if (v:version == 703 && !has("patch005"))
21     "command! -n=0 -bar UndotreeToggle :echoerr "undotree.vim needs vim7.3 with patch005 applied."
22     finish
23 endif
24 let g:loaded_undotree = 1   " Signal plugin availability with a value of 1.
25
26 "=================================================
27 "Options:
28
29 " Window layout
30 " style 1
31 " +----------+------------------------+
32 " |          |                        |
33 " |          |                        |
34 " | undotree |                        |
35 " |          |                        |
36 " |          |                        |
37 " +----------+                        |
38 " |          |                        |
39 " |   diff   |                        |
40 " |          |                        |
41 " +----------+------------------------+
42 " Style 2
43 " +----------+------------------------+
44 " |          |                        |
45 " |          |                        |
46 " | undotree |                        |
47 " |          |                        |
48 " |          |                        |
49 " +----------+------------------------+
50 " |                                   |
51 " |   diff                            |
52 " |                                   |
53 " +-----------------------------------+
54 " Style 3
55 " +------------------------+----------+
56 " |                        |          |
57 " |                        |          |
58 " |                        | undotree |
59 " |                        |          |
60 " |                        |          |
61 " |                        +----------+
62 " |                        |          |
63 " |                        |   diff   |
64 " |                        |          |
65 " +------------------------+----------+
66 " Style 4
67 " +-----------------------++----------+
68 " |                        |          |
69 " |                        |          |
70 " |                        | undotree |
71 " |                        |          |
72 " |                        |          |
73 " +------------------------+----------+
74 " |                                   |
75 " |                            diff   |
76 " |                                   |
77 " +-----------------------------------+
78 if !exists('g:undotree_WindowLayout')
79     let g:undotree_WindowLayout = 1
80 endif
81
82 " undotree window width
83 if !exists('g:undotree_SplitWidth')
84     let g:undotree_SplitWidth = 30
85 endif
86
87 " diff window height
88 if !exists('g:undotree_DiffpanelHeight')
89     let g:undotree_DiffpanelHeight = 10
90 endif
91
92 " auto open diff window
93 if !exists('g:undotree_DiffAutoOpen')
94     let g:undotree_DiffAutoOpen = 1
95 endif
96
97 " if set, let undotree window get focus after being opened, otherwise
98 " focus will stay in current window.
99 if !exists('g:undotree_SetFocusWhenToggle')
100     let g:undotree_SetFocusWhenToggle = 0
101 endif
102
103 " tree node shape.
104 if !exists('g:undotree_TreeNodeShape')
105     let g:undotree_TreeNodeShape = '*'
106 endif
107
108 if !exists('g:undotree_DiffCommand')
109     let g:undotree_DiffCommand = "diff"
110 endif
111
112 " relative timestamp
113 if !exists('g:undotree_RelativeTimestamp')
114     let g:undotree_RelativeTimestamp = 1
115 endif
116
117 " Highlight changed text
118 if !exists('g:undotree_HighlightChangedText')
119     let g:undotree_HighlightChangedText = 1
120 endif
121
122 " Highlight linked syntax type.
123 " You may chose your favorite through ":hi" command
124 if !exists('g:undotree_HighlightSyntaxAdd')
125     let g:undotree_HighlightSyntaxAdd = "DiffAdd"
126 endif
127 if !exists('g:undotree_HighlightSyntaxChange')
128     let g:undotree_HighlightSyntaxChange = "DiffChange"
129 endif
130
131 " Deprecates the old style configuration.
132 if exists('g:undotree_SplitLocation')
133     echo "g:undotree_SplitLocation is deprecated,
134                 \ please use g:undotree_WindowLayout instead."
135 endif
136
137 "Custom key mappings: add this function to your vimrc.
138 "You can define whatever mapping as you like, this is a hook function which
139 "will be called after undotree window initialized.
140 "
141 "function g:Undotree_CustomMap()
142 "    map <buffer> <c-n> J
143 "    map <buffer> <c-p> K
144 "endfunction
145
146 "=================================================
147 " Help text
148 let s:helpmore = ['"    ===== Marks ===== ',
149             \'" >num< : current change',
150             \'" {num} : change to redo',
151             \'" [num] : the last change',
152             \'"   s   : saved changes',
153             \'"   S   : last saved change',
154             \'"   ===== Hotkeys =====']
155 let s:helpless = ['" Press ? for help.']
156
157 " Keymap
158 let s:keymap = []
159 " action, key, help.
160 let s:keymap += [['Help','?','Toggle quick help']]
161 let s:keymap += [['Close','q','Close this panel']]
162 let s:keymap += [['FocusTarget','<tab>','Set Focus to editor']]
163 let s:keymap += [['ClearHistory','C','Clear undo history']]
164 let s:keymap += [['TimestampToggle','T','Toggle relative timestamp']]
165 let s:keymap += [['DiffToggle','D','Toggle diff panel']]
166 let s:keymap += [['GoNextState','K','Revert to next state']]
167 let s:keymap += [['GoPreviousState','J','Revert to previous state']]
168 let s:keymap += [['GoNextSaved','>','Revert to next saved state']]
169 let s:keymap += [['GoPreviousSaved','<','Revert to previous saved state']]
170 let s:keymap += [['Redo','<c-r>','Redo']]
171 let s:keymap += [['Undo','u','Undo']]
172 let s:keymap += [['Enter','<2-LeftMouse>','Revert to current']]
173 let s:keymap += [['Enter','<cr>','Revert to current']]
174
175 function! s:new(obj)
176     let newobj = deepcopy(a:obj)
177     call newobj.Init()
178     return newobj
179 endfunction
180
181 " Get formatted time
182 function! s:gettime(time)
183     if a:time == 0
184         return "Original"
185     endif
186     if !g:undotree_RelativeTimestamp
187         let today = substitute(strftime("%c",localtime())," .*$",'','g')
188         if today == substitute(strftime("%c",a:time)," .*$",'','g')
189             return strftime("%H:%M:%S",a:time)
190         else
191             return strftime("%H:%M:%S %b%d %Y",a:time)
192         endif
193     else
194         let sec = localtime() - a:time
195         if sec < 0
196             let sec = 0
197         endif
198         if sec < 60
199             if sec == 1
200                 return '1 second ago'
201             else
202                 return sec.' seconds ago'
203             endif
204         endif
205         if sec < 3600
206             if (sec/60) == 1
207                 return '1 minute ago'
208             else
209                 return (sec/60).' minutes ago'
210             endif
211         endif
212         if sec < 86400 "3600*24
213             if (sec/3600) == 1
214                 return '1 hour ago'
215             else
216                 return (sec/3600).' hours ago'
217             endif
218         endif
219         return (sec/86400).' days ago'
220     endif
221 endfunction
222
223 function! s:exec(cmd)
224     call s:log("s:exec() ".a:cmd)
225     silent exe a:cmd
226 endfunction
227
228 " Don't trigger any events(like BufEnter which could cause redundant refresh)
229 function! s:exec_silent(cmd)
230     call s:log("s:exec_silent() ".a:cmd)
231     let ei_bak= &eventignore
232     set eventignore=all
233     silent exe a:cmd
234     let &eventignore = ei_bak
235 endfunction
236
237 " Return a unique id each time.
238 let s:cntr = 0
239 function! s:getUniqueID()
240     let s:cntr = s:cntr + 1
241     return s:cntr
242 endfunction
243
244 " Debug...
245 let s:debug = 0
246 let s:debugfile = $HOME.'/undotree_debug.log'
247 " If debug file exists, enable debug output.
248 if filewritable(s:debugfile)
249     let s:debug = 1
250     exec 'redir >> '. s:debugfile
251     silent echo "=======================================\n"
252     redir END
253 endif
254
255 function! s:log(msg)
256     if s:debug
257         exec 'redir >> ' . s:debugfile
258         silent echon strftime('%H:%M:%S') . ': ' . string(a:msg) . "\n"
259         redir END
260     endif
261 endfunction
262
263 "=================================================
264 "Base class for panels.
265 let s:panel = {}
266
267 function! s:panel.Init()
268     let self.bufname = "invalid"
269 endfunction
270
271 function! s:panel.SetFocus()
272     let winnr = bufwinnr(self.bufname)
273     " already focused.
274     if winnr == winnr()
275         return
276     endif
277     if winnr == -1
278         echoerr "Fatal: window does not exist!"
279         return
280     endif
281     call s:log("SetFocus() winnr:".winnr." bufname:".self.bufname)
282     " wincmd would cause cursor outside window.
283     call s:exec_silent("norm! ".winnr."\<c-w>\<c-w>")
284 endfunction
285
286 function! s:panel.IsVisible()
287     if bufwinnr(self.bufname) != -1
288         return 1
289     else
290         return 0
291     endif
292 endfunction
293
294 function! s:panel.Hide()
295     call s:log(self.bufname." Hide()")
296     if !self.IsVisible()
297         return
298     endif
299     call self.SetFocus()
300     call s:exec("quit")
301 endfunction
302
303 "=================================================
304 " undotree panel class.
305 " extended from panel.
306 "
307
308 " {rawtree}
309 "     |
310 "     | ConvertInput()               {seq2index}--> [seq1:index1]
311 "     v                                             [seq2:index2] ---+
312 "  {tree}                                               ...          |
313 "     |                                    [asciimeta]               |
314 "     | Render()                                |                    |
315 "     v                                         v                    |
316 " [asciitree] --> [" * | SEQ DDMMYY "] <==> [node1{seq,time,..}]     |
317 "                 [" |/             "]      [node2{seq,time,..}] <---+
318 "                         ...                       ...
319
320 let s:undotree = s:new(s:panel)
321
322 function! s:undotree.Init()
323     let self.bufname = "undotree_".s:getUniqueID()
324     " Increase to make it unique.
325     let self.width = g:undotree_SplitWidth
326     let self.opendiff = g:undotree_DiffAutoOpen
327     let self.targetid = -1
328     let self.targetBufnr = -1
329     let self.rawtree = {}  "data passed from undotree()
330     let self.tree = {}     "data converted to internal format.
331     let self.seq_last = -1
332     let self.save_last = -1
333     let self.save_last_bak = -1
334
335     " seqs
336     let self.seq_cur = -1
337     let self.seq_curhead = -1
338     let self.seq_newhead = -1
339     let self.seq_saved = {} "{saved value -> seq} pair
340
341     "backup, for mark
342     let self.seq_cur_bak = -1
343     let self.seq_curhead_bak = -1
344     let self.seq_newhead_bak = -1
345
346     let self.asciitree = []     "output data.
347     let self.asciimeta = []     "meta data behind ascii tree.
348     let self.seq2index = {}     "table used to convert seq to index.
349     let self.showHelp = 0
350 endfunction
351
352 function! s:undotree.BindKey()
353     if v:version > 703 || (v:version == 703 && has("patch1261"))
354         let map_options = ' <nowait> '
355     else
356         let map_options = ''
357     endif
358     let map_options = map_options.' <silent> <buffer> '
359     for i in s:keymap
360         silent exec 'nmap '.map_options.i[1].' <plug>Undotree'.i[0]
361         silent exec 'nnoremap '.map_options.'<plug>Undotree'.i[0]
362             \ .' :call <sid>undotreeAction("'.i[0].'")<cr>'
363     endfor
364     if exists('*g:Undotree_CustomMap')
365         call g:Undotree_CustomMap()
366     endif
367 endfunction
368
369 function! s:undotree.BindAu()
370     " Auto exit if it's the last window
371     augroup Undotree_Main
372         au!
373         au BufEnter <buffer> call s:exitIfLast()
374         au BufEnter,BufLeave <buffer> if exists('t:undotree') |
375                     \let t:undotree.width = winwidth(winnr()) | endif
376         au BufWinLeave <buffer> if exists('t:diffpanel') |
377                     \call t:diffpanel.Hide() | endif
378     augroup end
379 endfunction
380
381 function! s:undotree.Action(action)
382     call s:log("undotree.Action() ".a:action)
383     if !self.IsVisible() || bufname("%") != self.bufname
384         echoerr "Fatal: window does not exists."
385         return
386     endif
387     if !has_key(self,'Action'.a:action)
388         echoerr "Fatal: Action does not exists!"
389         return
390     endif
391     silent exec 'call self.Action'.a:action.'()'
392 endfunction
393
394 " Helper function, do action in target window, and then update itself.
395 function! s:undotree.ActionInTarget(cmd)
396     if !self.SetTargetFocus()
397         return
398     endif
399     " Target should be a normal buffer.
400     if (&bt == '') && (&modifiable == 1) && (mode() == 'n')
401         call s:exec(a:cmd)
402         call self.Update()
403     endif
404     " Update not always set current focus.
405     call self.SetFocus()
406 endfunction
407
408 function! s:undotree.ActionHelp()
409     let self.showHelp = !self.showHelp
410     call self.Draw()
411     call self.MarkSeqs()
412 endfunction
413
414 function! s:undotree.ActionFocusTarget()
415     call self.SetTargetFocus()
416 endfunction
417
418 function! s:undotree.ActionEnter()
419     let index = self.Screen2Index(line('.'))
420     if index < 0
421         return
422     endif
423     let seq = self.asciimeta[index].seq
424     if seq == -1
425         return
426     endif
427     if seq == 0
428         call self.ActionInTarget('norm 9999u')
429         return
430     endif
431     call self.ActionInTarget('u '.self.asciimeta[index].seq)
432 endfunction
433
434 function! s:undotree.ActionUndo()
435     call self.ActionInTarget('undo')
436 endfunction
437
438 function! s:undotree.ActionRedo()
439     call self.ActionInTarget("redo")
440 endfunction
441
442 function! s:undotree.ActionGoPreviousState()
443     call self.ActionInTarget('earlier')
444 endfunction
445
446 function! s:undotree.ActionGoNextState()
447     call self.ActionInTarget('later')
448 endfunction
449
450 function! s:undotree.ActionGoPreviousSaved()
451     call self.ActionInTarget('earlier 1f')
452 endfunction
453
454 function! s:undotree.ActionGoNextSaved()
455     call self.ActionInTarget('later 1f')
456 endfunction
457
458 function! s:undotree.ActionDiffToggle()
459     let self.opendiff = !self.opendiff
460     call t:diffpanel.Toggle()
461     call self.UpdateDiff()
462 endfunction
463
464 function! s:undotree.ActionTimestampToggle()
465     if !self.SetTargetFocus()
466         return
467     endif
468     let g:undotree_RelativeTimestamp = !g:undotree_RelativeTimestamp
469     let self.targetBufnr = -1 "force update
470     call self.Update()
471     " Update not always set current focus.
472     call self.SetFocus()
473 endfunction
474
475 function! s:undotree.ActionClearHistory()
476     if confirm("Are you sure to clear ALL undo history?","&Yes\n&No") != 1
477         return
478     endif
479     if !self.SetTargetFocus()
480         return
481     endif
482     let ul_bak = &undolevels
483     let &undolevels = -1
484     call s:exec("norm! a \<BS>\<Esc>")
485     let &undolevels = ul_bak
486     unlet ul_bak
487     let self.targetBufnr = -1 "force update
488     call self.Update()
489 endfunction
490
491 function! s:undotree.ActionClose()
492     call self.Toggle()
493 endfunction
494
495 function! s:undotree.UpdateDiff()
496     call s:log("undotree.UpdateDiff()")
497     if !t:diffpanel.IsVisible()
498         return
499     endif
500     call t:diffpanel.Update(self.seq_cur,self.targetBufnr,self.targetid)
501 endfunction
502
503 " May fail due to target window closed.
504 function! s:undotree.SetTargetFocus()
505     for winnr in range(1, winnr('$')) "winnr starts from 1
506         if getwinvar(winnr,'undotree_id') == self.targetid
507             if winnr() != winnr
508                 call s:exec("norm! ".winnr."\<c-w>\<c-w>")
509                 return 1
510             endif
511         endif
512     endfor
513     return 0
514 endfunction
515
516 function! s:undotree.Toggle()
517     call s:log(self.bufname." Toggle()")
518     if self.IsVisible()
519         call self.Hide()
520         call t:diffpanel.Hide()
521         call self.SetTargetFocus()
522     else
523         call self.Show()
524         if !g:undotree_SetFocusWhenToggle
525             call self.SetTargetFocus()
526         endif
527     endif
528 endfunction
529
530 function! s:undotree.GetStatusLine()
531     if self.seq_cur != -1
532         let seq_cur = self.seq_cur
533     else
534         let seq_cur = 'None'
535     endif
536     if self.seq_curhead != -1
537         let seq_curhead = self.seq_curhead
538     else
539         let seq_curhead = 'None'
540     endif
541     return 'current: '.seq_cur.' redo: '.seq_curhead
542 endfunction
543
544 function! s:undotree.Show()
545     call s:log("undotree.Show()")
546     if self.IsVisible()
547         return
548     endif
549
550     let self.targetid = w:undotree_id
551
552     " Create undotree window.
553     if g:undotree_WindowLayout == 1 || g:undotree_WindowLayout == 2
554         let cmd = "topleft vertical" .
555                     \self.width . ' new ' . self.bufname
556     else
557         let cmd = "botright vertical" .
558                     \self.width . ' new ' . self.bufname
559     endif
560     call s:exec("silent keepalt ".cmd)
561     call self.SetFocus()
562     setlocal winfixwidth
563     setlocal noswapfile
564     setlocal buftype=nowrite
565     setlocal bufhidden=delete
566     setlocal nowrap
567     setlocal foldcolumn=0
568     setlocal nobuflisted
569     setlocal nospell
570     setlocal nonumber
571     setlocal cursorline
572     setlocal nomodifiable
573     setlocal statusline=%!t:undotree.GetStatusLine()
574     setfiletype undotree
575
576     call self.BindKey()
577     call self.BindAu()
578
579     let ei_bak= &eventignore
580     set eventignore=all
581
582     call self.SetTargetFocus()
583     let self.targetBufnr = -1 "force update
584     call self.Update()
585
586     let &eventignore = ei_bak
587
588     if self.opendiff
589         call t:diffpanel.Show()
590         call self.UpdateDiff()
591     endif
592 endfunction
593
594 " called outside undotree window
595 function! s:undotree.Update()
596     if !self.IsVisible()
597         return
598     endif
599     " do nothing if we're in the undotree or diff panel
600     let bufname = bufname('%')
601     if bufname == self.bufname || bufname == t:diffpanel.bufname
602         return
603     endif
604     if (&bt != '') || (&modifiable == 0) || (mode() != 'n')
605         if self.targetBufnr == bufnr('%') && self.targetid == w:undotree_id
606             call s:log("undotree.Update() invalid buffer NOupdate")
607             return
608         endif
609         let emptybuf = 1 "This is not a valid buffer, could be help or something.
610         call s:log("undotree.Update() invalid buffer update")
611     else
612         let emptybuf = 0
613         "update undotree,set focus
614         if self.targetBufnr == bufnr('%')
615             let self.targetid = w:undotree_id
616             let newrawtree = undotree()
617             if self.rawtree == newrawtree
618                 return
619             endif
620
621             " same buffer, but seq changed.
622             if newrawtree.seq_last == self.seq_last
623                 call s:log("undotree.Update() update seqs")
624                 let self.rawtree = newrawtree
625                 call self.ConvertInput(0) "only update seqs.
626                 if (self.seq_cur == self.seq_cur_bak) &&
627                             \(self.seq_curhead == self.seq_curhead_bak)&&
628                             \(self.seq_newhead == self.seq_newhead_bak)&&
629                             \(self.save_last == self.save_last_bak)
630                     return
631                 endif
632                 call self.SetFocus()
633                 call self.MarkSeqs()
634                 call self.UpdateDiff()
635                 return
636             endif
637         endif
638     endif
639     call s:log("undotree.Update() update whole tree")
640
641     let self.targetBufnr = bufnr('%')
642     let self.targetid = w:undotree_id
643     if emptybuf " Show an empty undo tree instead of do nothing.
644         let self.rawtree = {'seq_last':0,'entries':[],'time_cur':0,'save_last':0,'synced':1,'save_cur':0,'seq_cur':0}
645     else
646         let self.rawtree = undotree()
647     endif
648     let self.seq_last = self.rawtree.seq_last
649     let self.seq_cur = -1
650     let self.seq_curhead = -1
651     let self.seq_newhead = -1
652     call self.ConvertInput(1) "update all.
653     call self.Render()
654     call self.SetFocus()
655     call self.Draw()
656     call self.MarkSeqs()
657     call self.UpdateDiff()
658 endfunction
659
660 function! s:undotree.AppendHelp()
661     call append(0,'') "empty line
662     if self.showHelp
663         for i in s:keymap
664             call append(0,'" '.i[1].' : '.i[2])
665         endfor
666         call append(0,s:helpmore)
667     else
668         call append(0,s:helpless)
669     endif
670 endfunction
671
672 function! s:undotree.Index2Screen(index)
673     " calculate line number according to the help text.
674     " index starts from zero and lineNr starts from 1
675     if self.showHelp
676         " 2 means 1 empty line + 1 index padding (index starts from zero)
677         let lineNr = a:index + len(s:keymap) + len(s:helpmore) + 2
678     else
679         let lineNr = a:index + len(s:helpless) + 2
680     endif
681     return lineNr
682 endfunction
683
684 " <0 if index is invalid. e.g. current line is in help text.
685 function! s:undotree.Screen2Index(line)
686     if self.showHelp
687         let index = a:line - len(s:keymap) - len(s:helpmore) - 2
688     else
689         let index = a:line - len(s:helpless) - 2
690     endif
691     return index
692 endfunction
693
694 " Current window must be undotree.
695 function! s:undotree.Draw()
696     " remember the current cursor position.
697     let savedview = winsaveview()
698
699     setlocal modifiable
700     " Delete text into blackhole register.
701     call s:exec('1,$ d _')
702     call append(0,self.asciitree)
703
704     call self.AppendHelp()
705
706     "remove the last empty line
707     call s:exec('$d _')
708
709     " restore previous cursor position.
710     call winrestview(savedview)
711
712     setlocal nomodifiable
713 endfunction
714
715 function! s:undotree.MarkSeqs()
716     call s:log("bak(cur,curhead,newhead): ".
717                 \self.seq_cur_bak.' '.
718                 \self.seq_curhead_bak.' '.
719                 \self.seq_newhead_bak)
720     call s:log("(cur,curhead,newhead): ".
721                 \self.seq_cur.' '.
722                 \self.seq_curhead.' '.
723                 \self.seq_newhead)
724     setlocal modifiable
725     " reset bak seq lines.
726     if self.seq_cur_bak != -1
727         let index = self.seq2index[self.seq_cur_bak]
728         call setline(self.Index2Screen(index),self.asciitree[index])
729     endif
730     if self.seq_curhead_bak != -1
731         let index = self.seq2index[self.seq_curhead_bak]
732         call setline(self.Index2Screen(index),self.asciitree[index])
733     endif
734     if self.seq_newhead_bak != -1
735         let index = self.seq2index[self.seq_newhead_bak]
736         call setline(self.Index2Screen(index),self.asciitree[index])
737     endif
738     " mark save seqs
739     for i in keys(self.seq_saved)
740         let index = self.seq2index[self.seq_saved[i]]
741         let lineNr = self.Index2Screen(index)
742         call setline(lineNr,substitute(self.asciitree[index],
743                     \' \d\+  \zs \ze','s',''))
744     endfor
745     let max_saved_num = max(keys(self.seq_saved))
746     if max_saved_num > 0
747         let lineNr = self.Index2Screen(self.seq2index[self.seq_saved[max_saved_num]])
748         call setline(lineNr,substitute(getline(lineNr),'s','S',''))
749     endif
750     " mark new seqs.
751     if self.seq_cur != -1
752         let index = self.seq2index[self.seq_cur]
753         let lineNr = self.Index2Screen(index)
754         call setline(lineNr,substitute(getline(lineNr),
755                     \'\zs \(\d\+\) \ze [sS ] ','>\1<',''))
756         " move cursor to that line.
757         call s:exec("normal! " . lineNr . "G")
758     endif
759     if self.seq_curhead != -1
760         let index = self.seq2index[self.seq_curhead]
761         let lineNr = self.Index2Screen(index)
762         call setline(lineNr,substitute(getline(lineNr),
763                     \'\zs \(\d\+\) \ze [sS ] ','{\1}',''))
764     endif
765     if self.seq_newhead != -1
766         let index = self.seq2index[self.seq_newhead]
767         let lineNr = self.Index2Screen(index)
768         call setline(lineNr,substitute(getline(lineNr),
769                     \'\zs \(\d\+\) \ze [sS ] ','[\1]',''))
770     endif
771     setlocal nomodifiable
772 endfunction
773
774 " tree node class
775 let s:node = {}
776
777 function! s:node.Init()
778     let self.seq = -1
779     let self.p = []
780     let self.time = -1
781 endfunction
782
783 function! s:undotree._parseNode(in,out)
784     " type(in) == type([]) && type(out) == type({})
785     if empty(a:in) "empty
786         return
787     endif
788     let curnode = a:out
789     for i in a:in
790         if has_key(i,'alt')
791             call self._parseNode(i.alt,curnode)
792         endif
793         let newnode = s:new(s:node)
794         let newnode.seq = i.seq
795         let newnode.time = i.time
796         if has_key(i,'newhead')
797             let self.seq_newhead = i.seq
798         endif
799         if has_key(i,'curhead')
800             let self.seq_curhead = i.seq
801             let self.seq_cur = curnode.seq
802         endif
803         if has_key(i,'save')
804             let self.seq_saved[i.save] = i.seq
805         endif
806         call extend(curnode.p,[newnode])
807         let curnode = newnode
808     endfor
809 endfunction
810
811 "Sample:
812 "let s:test={'seq_last': 4, 'entries': [{'seq': 3, 'alt': [{'seq': 1, 'time': 1345131443}, {'seq': 2, 'time': 1345131445}], 'time': 1345131490}, {'seq': 4, 'time': 1345131492, 'newhead': 1}], 'time_cur': 1345131493, 'save_last': 0, 'synced': 0, 'save_cur': 0, 'seq_cur': 4}
813
814 " updatetree: 0: no update, just assign seqs;  1: update and assign seqs.
815 function! s:undotree.ConvertInput(updatetree)
816     "reset seqs
817     let self.seq_cur_bak = self.seq_cur
818     let self.seq_curhead_bak = self.seq_curhead
819     let self.seq_newhead_bak = self.seq_newhead
820     let self.save_last_bak = self.save_last
821
822     let self.seq_cur = -1
823     let self.seq_curhead = -1
824     let self.seq_newhead = -1
825     let self.seq_saved = {}
826
827     "Generate root node
828     let root = s:new(s:node)
829     let root.seq = 0
830     let root.time = 0
831
832     call self._parseNode(self.rawtree.entries,root)
833
834     let self.save_last = self.rawtree.save_last
835     " Note: Normally, the current node should be the one that seq_cur points to,
836     " but in fact it's not. May be bug, bug anyway I found a workaround:
837     " first try to find the parent node of 'curhead', if not found, then use
838     " seq_cur.
839     if self.seq_cur == -1
840         let self.seq_cur = self.rawtree.seq_cur
841     endif
842     " undo history is cleared
843     if empty(self.rawtree.entries)
844         let self.seq_cur = 0
845     endif
846     if a:updatetree
847         let self.tree = root
848     endif
849 endfunction
850
851
852 "=================================================
853 " Ascii undo tree generator
854 "
855 " Example:
856 " 6 8  7
857 " |/   |
858 " 2    4
859 "  \   |
860 "   1  3  5
861 "    \ | /
862 "      0
863
864 " Tree sieve, p:fork, x:none
865 "
866 " x         8
867 " 8x        | 7
868 " 87         \ \
869 " x87       6 | |
870 " 687       |/ /
871 " p7x       | | 5
872 " p75       | 4 |
873 " p45       | 3 |
874 " p35       | |/
875 " pp        2 |
876 " 2p        1 |
877 " 1p        |/
878 " p         0
879 " 0
880 "
881 " Data sample:
882 "let example = {'seq':0,'p':[{'seq':1,'p':[{'seq':2,'p':[{'seq':6,'p':[]},{'seq':8,'p':[]}]}]},{'seq':3,'p':[{'seq':4,'p':[{'seq':7,'p':[]}]}]},{'seq':5,'p':[]}]}
883 "
884 " Convert self.tree -> self.asciitree
885 function! s:undotree.Render()
886     " We gonna modify self.tree so we'd better make a copy first.
887     " Can not make a copy because variable nested too deep, gosh.. okay,
888     " fine..
889     " let tree = deepcopy(self.tree)
890     let tree = self.tree
891     let slots = [tree]
892     let out = []
893     let outmeta = []
894     let seq2index = {}
895     let TYPE_E = type({})
896     let TYPE_P = type([])
897     let TYPE_X = type('x')
898     while slots != []
899         "find next node
900         let foundx = 0 " 1 if x element is found.
901         let index = 0 " Next element to be print.
902
903         " Find x element first.
904         for i in range(len(slots))
905             if type(slots[i]) == TYPE_X
906                 let foundx = 1
907                 let index = i
908                 break
909             endif
910         endfor
911
912         " Then, find the element with minimun seq.
913         let minseq = 99999999
914         let minnode = {}
915         if foundx == 0
916             "assume undo level isn't more than this... of course
917             for i in range(len(slots))
918                 if type(slots[i]) == TYPE_E
919                     if slots[i].seq < minseq
920                         let minseq = slots[i].seq
921                         let index = i
922                         let minnode = slots[i]
923                         continue
924                     endif
925                 endif
926                 if type(slots[i]) == TYPE_P
927                     for j in slots[i]
928                         if j.seq < minseq
929                             let minseq = j.seq
930                             let index = i
931                             let minnode = j
932                             continue
933                         endif
934                     endfor
935                 endif
936             endfor
937         endif
938
939         " output.
940         let onespace = " "
941         let newline = onespace
942         let newmeta = {}
943         let node = slots[index]
944         if type(node) == TYPE_X
945             let newmeta = s:new(s:node) "invalid node.
946             if index+1 != len(slots) " not the last one, append '\'
947                 for i in range(len(slots))
948                     if i < index
949                         let newline = newline.'| '
950                     endif
951                     if i > index
952                         let newline = newline.' \'
953                     endif
954                 endfor
955             endif
956             call remove(slots,index)
957         endif
958         if type(node) == TYPE_E
959             let newmeta = node
960             let seq2index[node.seq]=len(out)
961             for i in range(len(slots))
962                 if index == i
963                     let newline = newline.g:undotree_TreeNodeShape.' '
964                 else
965                     let newline = newline.'| '
966                 endif
967             endfor
968             let newline = newline.'   '.(node.seq).'    '.
969                         \'('.s:gettime(node.time).')'
970             " update the printed slot to its child.
971             if empty(node.p)
972                 let slots[index] = 'x'
973             endif
974             if len(node.p) == 1 "only one child.
975                 let slots[index] = node.p[0]
976             endif
977             if len(node.p) > 1 "insert p node
978                 let slots[index] = node.p
979             endif
980             let node.p = [] "cut reference.
981         endif
982         if type(node) == TYPE_P
983             let newmeta = s:new(s:node) "invalid node.
984             for k in range(len(slots))
985                 if k < index
986                     let newline = newline."| "
987                 endif
988                 if k == index
989                     let newline = newline."|/ "
990                 endif
991                 if k > index
992                     let newline = newline."/ "
993                 endif
994             endfor
995             call remove(slots,index)
996             if len(node) == 2
997                 if node[0].seq > node[1].seq
998                     call insert(slots,node[1],index)
999                     call insert(slots,node[0],index)
1000                 else
1001                     call insert(slots,node[0],index)
1002                     call insert(slots,node[1],index)
1003                 endif
1004             endif
1005             " split P to E+P if elements in p > 2
1006             if len(node) > 2
1007                 call remove(node,index(node,minnode))
1008                 call insert(slots,minnode,index)
1009                 call insert(slots,node,index)
1010             endif
1011         endif
1012         unlet node
1013         if newline != onespace
1014             let newline = substitute(newline,'\s*$','','g') "remove trailing space.
1015             call insert(out,newline,0)
1016             call insert(outmeta,newmeta,0)
1017         endif
1018     endwhile
1019     let self.asciitree = out
1020     let self.asciimeta = outmeta
1021     " revert index.
1022     let totallen = len(out)
1023     for i in keys(seq2index)
1024         let seq2index[i] = totallen - 1 - seq2index[i]
1025     endfor
1026     let self.seq2index = seq2index
1027 endfunction
1028
1029 "=================================================
1030 "diff panel
1031 let s:diffpanel = s:new(s:panel)
1032
1033 function! s:diffpanel.Update(seq,targetBufnr,targetid)
1034     call s:log('diffpanel.Update(),seq:'.a:seq.' bufname:'.bufname(a:targetBufnr))
1035     if !self.diffexecutable
1036         return
1037     endif
1038     let diffresult = []
1039     let self.changes.add = 0
1040     let self.changes.del = 0
1041
1042     if a:seq == 0
1043         let diffresult = []
1044     else
1045         if has_key(self.cache,a:targetBufnr.'_'.a:seq)
1046             call s:log("diff cache hit.")
1047             let diffresult = self.cache[a:targetBufnr.'_'.a:seq]
1048         else
1049             let ei_bak = &eventignore
1050             set eventignore=all
1051             let targetWinnr = -1
1052
1053             " Double check the target winnr and bufnr
1054             for winnr in range(1, winnr('$')) "winnr starts from 1
1055                 if (getwinvar(winnr,'undotree_id') == a:targetid)
1056                             \&& winbufnr(winnr) == a:targetBufnr
1057                     let targetWinnr = winnr
1058                 endif
1059             endfor
1060             if targetWinnr == -1
1061                 return
1062             endif
1063             call s:exec_silent(targetWinnr." wincmd w")
1064
1065             " remember and restore cursor and window position.
1066             let savedview = winsaveview()
1067
1068             let new = getbufline(a:targetBufnr,'^','$')
1069             silent undo
1070             let old = getbufline(a:targetBufnr,'^','$')
1071             silent redo
1072
1073             call winrestview(savedview)
1074
1075             " diff files.
1076             let tempfile1 = tempname()
1077             let tempfile2 = tempname()
1078             if writefile(old,tempfile1) == -1
1079                 echoerr "Can not write to temp file:".tempfile1
1080             endif
1081             if writefile(new,tempfile2) == -1
1082                 echoerr "Can not write to temp file:".tempfile2
1083             endif
1084             let diffresult = split(system(g:undotree_DiffCommand.' '.tempfile1.' '.tempfile2),"\n")
1085             call s:log("diffresult: ".string(diffresult))
1086             if delete(tempfile1) != 0
1087                 echoerr "Can not delete temp file:".tempfile1
1088             endif
1089             if delete(tempfile2) != 0
1090                 echoerr "Can not delete temp file:".tempfile2
1091             endif
1092             let &eventignore = ei_bak
1093             "Update cache
1094             let self.cache[a:targetBufnr.'_'.a:seq] = diffresult
1095         endif
1096     endif
1097
1098     call self.ParseDiff(diffresult)
1099
1100     call self.SetFocus()
1101
1102     setlocal modifiable
1103     call s:exec('1,$ d _')
1104
1105     call append(0,diffresult)
1106     call append(0,'- seq: '.a:seq.' -')
1107
1108     "remove the last empty line
1109     if getline("$") == ""
1110         call s:exec('$d _')
1111     endif
1112     call s:exec('norm! gg') "move cursor to line 1.
1113     setlocal nomodifiable
1114     call t:undotree.SetFocus()
1115 endfunction
1116
1117 function! s:diffpanel.ParseDiff(diffresult)
1118     " set target focus first.
1119     call t:undotree.SetTargetFocus()
1120
1121     if empty(a:diffresult)
1122         return
1123     endif
1124     " clear previous highlighted syntax
1125     " matchadd associates with windows.
1126     if exists("w:undotree_diffmatches")
1127         for i in w:undotree_diffmatches
1128             call matchdelete(i)
1129         endfor
1130     endif
1131     let w:undotree_diffmatches = []
1132     let lineNr = 0
1133     for line in a:diffresult
1134         let matchnum = matchstr(line,'^[0-9,\,]*[ac]\zs\d*\ze')
1135         if !empty(matchnum)
1136             let lineNr = str2nr(matchnum)
1137             let matchwhat = matchstr(line,'^[0-9,\,]*\zs[ac]\ze\d*')
1138             continue
1139         endif
1140         if matchstr(line,'^<.*$') != ''
1141             let self.changes.del += 1
1142         endif
1143         let matchtext = matchstr(line,'^>\zs .*$')
1144         if empty(matchtext)
1145             continue
1146         endif
1147         let self.changes.add += 1
1148         if g:undotree_HighlightChangedText
1149             if matchtext != ' '
1150                 let matchtext = '\%'.lineNr.'l\V'.escape(matchtext[1:],'"\') "remove beginning space.
1151                 call s:log("matchadd(".matchwhat.") ->  ".matchtext)
1152                 call add(w:undotree_diffmatches,matchadd((matchwhat ==# 'a' ? g:undotree_HighlightSyntaxAdd : g:undotree_HighlightSyntaxChange),matchtext))
1153             endif
1154         endif
1155
1156         let lineNr = lineNr+1
1157     endfor
1158 endfunction
1159
1160 function! s:diffpanel.GetStatusLine()
1161     let max = winwidth(0) - 4
1162     let sum = self.changes.add + self.changes.del
1163     if sum > max
1164         let add = self.changes.add * max / sum + 1
1165         let del = self.changes.del * max / sum + 1
1166     else
1167         let add = self.changes.add
1168         let del = self.changes.del
1169     endif
1170     return string(sum).' '.repeat('+',add).repeat('-',del)
1171 endfunction
1172
1173 function! s:diffpanel.Init()
1174     let self.bufname = "diffpanel_".s:getUniqueID()
1175     let self.cache = {}
1176     let self.changes = {'add':0, 'del':0}
1177     let self.diffexecutable = executable('diff')
1178     if !self.diffexecutable
1179         echoerr '"diff" is not executable.'
1180     endif
1181 endfunction
1182
1183 function! s:diffpanel.Toggle()
1184     call s:log(self.bufname." Toggle()")
1185     if self.IsVisible()
1186         call self.Hide()
1187     else
1188         call self.Show()
1189     endif
1190 endfunction
1191
1192 function! s:diffpanel.Show()
1193     call s:log("diffpanel.Show()")
1194     if self.IsVisible()
1195         return
1196     endif
1197     " Create diffpanel window.
1198     call t:undotree.SetFocus() "can not exist without undotree
1199     " remember and restore cursor and window position.
1200     let savedview = winsaveview()
1201
1202     let ei_bak= &eventignore
1203     set eventignore=all
1204
1205     if g:undotree_WindowLayout == 1 || g:undotree_WindowLayout == 3
1206         let cmd = 'belowright '.g:undotree_DiffpanelHeight.'new '.self.bufname
1207     else
1208         let cmd = 'botright '.g:undotree_DiffpanelHeight.'new '.self.bufname
1209     endif
1210     call s:exec_silent(cmd)
1211
1212     setlocal winfixwidth
1213     setlocal winfixheight
1214     setlocal noswapfile
1215     setlocal buftype=nowrite
1216     setlocal bufhidden=delete
1217     setlocal nowrap
1218     setlocal nobuflisted
1219     setlocal nospell
1220     setlocal nonumber
1221     setlocal nocursorline
1222     setlocal nomodifiable
1223     setlocal statusline=%!t:diffpanel.GetStatusLine()
1224
1225     let &eventignore = ei_bak
1226
1227     " syntax need filetype autocommand
1228     setfiletype diff
1229     setlocal foldcolumn=0
1230     setlocal nofoldenable
1231
1232     call self.BindAu()
1233     call t:undotree.SetFocus()
1234     call winrestview(savedview)
1235 endfunction
1236
1237 function! s:diffpanel.BindAu()
1238     " Auto exit if it's the last window or undotree closed.
1239     augroup Undotree_Diff
1240         au!
1241         au BufEnter <buffer> call s:exitIfLast()
1242         au BufEnter <buffer> if !t:undotree.IsVisible()
1243                     \|call t:diffpanel.Hide() |endif
1244     augroup end
1245 endfunction
1246
1247 function! s:diffpanel.CleanUpHighlight()
1248     call s:log("CleanUpHighlight()")
1249     " save current position
1250     let curwinnr = winnr()
1251     let savedview = winsaveview()
1252
1253     " clear w:undotree_diffmatches in all windows.
1254     let winnum = winnr('$')
1255     for i in range(1,winnum)
1256         call s:exec_silent("norm! ".i."\<c-w>\<c-w>")
1257         if exists("w:undotree_diffmatches")
1258             for j in w:undotree_diffmatches
1259                 call matchdelete(j)
1260             endfor
1261             let w:undotree_diffmatches = []
1262         endif
1263     endfor
1264
1265     "restore position
1266     call s:exec_silent("norm! ".curwinnr."\<c-w>\<c-w>")
1267     call winrestview(savedview)
1268 endfunction
1269
1270 function! s:diffpanel.Hide()
1271     call s:log(self.bufname." Hide()")
1272     if !self.IsVisible()
1273         return
1274     endif
1275     call self.SetFocus()
1276     call s:exec("quit")
1277     call self.CleanUpHighlight()
1278 endfunction
1279
1280 "=================================================
1281 " It will set the target of undotree window to the current editing buffer.
1282 function! s:undotreeAction(action)
1283     call s:log("undotreeAction()")
1284     if !exists('t:undotree')
1285         echoerr "Fatal: t:undotree does not exists!"
1286         return
1287     endif
1288     call t:undotree.Action(a:action)
1289 endfunction
1290
1291 function! s:exitIfLast()
1292     let num = 0
1293     if t:undotree.IsVisible()
1294         let num = num + 1
1295     endif
1296     if t:diffpanel.IsVisible()
1297         let num = num + 1
1298     endif
1299     if winnr('$') == num
1300         call t:undotree.Hide()
1301         call t:diffpanel.Hide()
1302     endif
1303 endfunction
1304
1305 "=================================================
1306 "called outside undotree window
1307 function! UndotreeUpdate()
1308     if !exists('t:undotree')
1309         return
1310     endif
1311     if !exists('w:undotree_id')
1312         let w:undotree_id = 'id_'.s:getUniqueID()
1313         call s:log("Unique window id assigned: ".w:undotree_id)
1314     endif
1315     " assume window layout won't change during updating.
1316     let thiswinnr = winnr()
1317     call t:undotree.Update()
1318     " focus moved
1319     if winnr() != thiswinnr
1320         call s:exec("norm! ".thiswinnr."\<c-w>\<c-w>")
1321     endif
1322 endfunction
1323
1324 function! UndotreeToggle()
1325     call s:log(">>> UndotreeToggle()")
1326     if !exists('w:undotree_id')
1327         let w:undotree_id = 'id_'.s:getUniqueID()
1328         call s:log("Unique window id assigned: ".w:undotree_id)
1329     endif
1330
1331     if !exists('t:undotree')
1332         let t:undotree = s:new(s:undotree)
1333         let t:diffpanel = s:new(s:diffpanel)
1334     endif
1335     call t:undotree.Toggle()
1336     call s:log("<<< UndotreeToggle() leave")
1337 endfunction
1338
1339 function! UndotreeIsVisible()
1340     return (exists('t:undotree') && t:undotree.IsVisible())
1341 endfunction
1342
1343 function! UndotreeHide()
1344     if UndotreeIsVisible()
1345         call UndotreeToggle()
1346     endif
1347 endfunction
1348
1349 function! UndotreeShow()
1350     if ! UndotreeIsVisible()
1351         call UndotreeToggle()
1352     else
1353         call t:undotree.SetFocus()
1354     endif
1355 endfunction
1356
1357 function! UndotreeFocus()
1358     if UndotreeIsVisible()
1359         call t:undotree.SetFocus()
1360     endif
1361 endfunction
1362
1363
1364 "let s:auEvents = "InsertEnter,InsertLeave,WinEnter,WinLeave,CursorMoved"
1365 let s:auEvents = "BufEnter,InsertLeave,CursorMoved,BufWritePost"
1366 augroup Undotree
1367     exec "au! ".s:auEvents." * call UndotreeUpdate()"
1368 augroup END
1369
1370 "=================================================
1371 " User commands.
1372 command! -n=0 -bar UndotreeToggle   :call UndotreeToggle()
1373 command! -n=0 -bar UndotreeHide     :call UndotreeHide()
1374 command! -n=0 -bar UndotreeShow     :call UndotreeShow()
1375 command! -n=0 -bar UndotreeFocus    :call UndotreeFocus()
1376
1377 " vim: set et fdm=marker sts=4 sw=4: