1 "=================================================
3 " Description: Manage your undo history in a graph.
4 " Author: Ming Bai <mbbill@gmail.com>
7 " Avoid installing twice.
8 if exists('g:loaded_undotree')
11 let g:loaded_undotree = 0
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.
17 "command! -n=0 -bar UndotreeToggle :echoerr "undotree.vim needs Vim version >= 7.3"
20 if (v:version == 703 && !has("patch005"))
21 "command! -n=0 -bar UndotreeToggle :echoerr "undotree.vim needs vim7.3 with patch005 applied."
24 let g:loaded_undotree = 1 " Signal plugin availability with a value of 1.
26 "=================================================
31 " +----------+------------------------+
41 " +----------+------------------------+
43 " +----------+------------------------+
49 " +----------+------------------------+
53 " +-----------------------------------+
55 " +------------------------+----------+
65 " +------------------------+----------+
67 " +-----------------------++----------+
73 " +------------------------+----------+
77 " +-----------------------------------+
78 if !exists('g:undotree_WindowLayout')
79 let g:undotree_WindowLayout = 1
82 " undotree window width
83 if !exists('g:undotree_SplitWidth')
84 let g:undotree_SplitWidth = 30
88 if !exists('g:undotree_DiffpanelHeight')
89 let g:undotree_DiffpanelHeight = 10
92 " auto open diff window
93 if !exists('g:undotree_DiffAutoOpen')
94 let g:undotree_DiffAutoOpen = 1
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
104 if !exists('g:undotree_TreeNodeShape')
105 let g:undotree_TreeNodeShape = '*'
108 if !exists('g:undotree_DiffCommand')
109 let g:undotree_DiffCommand = "diff"
113 if !exists('g:undotree_RelativeTimestamp')
114 let g:undotree_RelativeTimestamp = 1
117 " Highlight changed text
118 if !exists('g:undotree_HighlightChangedText')
119 let g:undotree_HighlightChangedText = 1
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"
127 if !exists('g:undotree_HighlightSyntaxChange')
128 let g:undotree_HighlightSyntaxChange = "DiffChange"
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."
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.
141 "function g:Undotree_CustomMap()
142 " map <buffer> <c-n> J
143 " map <buffer> <c-p> K
146 "=================================================
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.']
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']]
176 let newobj = deepcopy(a:obj)
182 function! s:gettime(time)
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)
191 return strftime("%H:%M:%S %b%d %Y",a:time)
194 let sec = localtime() - a:time
200 return '1 second ago'
202 return sec.' seconds ago'
207 return '1 minute ago'
209 return (sec/60).' minutes ago'
212 if sec < 86400 "3600*24
216 return (sec/3600).' hours ago'
219 return (sec/86400).' days ago'
223 function! s:exec(cmd)
224 call s:log("s:exec() ".a:cmd)
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
234 let &eventignore = ei_bak
237 " Return a unique id each time.
239 function! s:getUniqueID()
240 let s:cntr = s:cntr + 1
246 let s:debugfile = $HOME.'/undotree_debug.log'
247 " If debug file exists, enable debug output.
248 if filewritable(s:debugfile)
250 exec 'redir >> '. s:debugfile
251 silent echo "=======================================\n"
257 exec 'redir >> ' . s:debugfile
258 silent echon strftime('%H:%M:%S') . ': ' . string(a:msg) . "\n"
263 "=================================================
264 "Base class for panels.
267 function! s:panel.Init()
268 let self.bufname = "invalid"
271 function! s:panel.SetFocus()
272 let winnr = bufwinnr(self.bufname)
278 echoerr "Fatal: window does not exist!"
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>")
286 function! s:panel.IsVisible()
287 if bufwinnr(self.bufname) != -1
294 function! s:panel.Hide()
295 call s:log(self.bufname." Hide()")
303 "=================================================
304 " undotree panel class.
305 " extended from panel.
310 " | ConvertInput() {seq2index}--> [seq1:index1]
311 " v [seq2:index2] ---+
316 " [asciitree] --> [" * | SEQ DDMMYY "] <==> [node1{seq,time,..}] |
317 " [" |/ "] [node2{seq,time,..}] <---+
320 let s:undotree = s:new(s:panel)
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
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
342 let self.seq_cur_bak = -1
343 let self.seq_curhead_bak = -1
344 let self.seq_newhead_bak = -1
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
352 function! s:undotree.BindKey()
353 if v:version > 703 || (v:version == 703 && has("patch1261"))
354 let map_options = ' <nowait> '
358 let map_options = map_options.' <silent> <buffer> '
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>'
364 if exists('*g:Undotree_CustomMap')
365 call g:Undotree_CustomMap()
369 function! s:undotree.BindAu()
370 " Auto exit if it's the last window
371 augroup Undotree_Main
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
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."
387 if !has_key(self,'Action'.a:action)
388 echoerr "Fatal: Action does not exists!"
391 silent exec 'call self.Action'.a:action.'()'
394 " Helper function, do action in target window, and then update itself.
395 function! s:undotree.ActionInTarget(cmd)
396 if !self.SetTargetFocus()
399 " Target should be a normal buffer.
400 if (&bt == '') && (&modifiable == 1) && (mode() == 'n')
404 " Update not always set current focus.
408 function! s:undotree.ActionHelp()
409 let self.showHelp = !self.showHelp
414 function! s:undotree.ActionFocusTarget()
415 call self.SetTargetFocus()
418 function! s:undotree.ActionEnter()
419 let index = self.Screen2Index(line('.'))
423 let seq = self.asciimeta[index].seq
428 call self.ActionInTarget('norm 9999u')
431 call self.ActionInTarget('u '.self.asciimeta[index].seq)
434 function! s:undotree.ActionUndo()
435 call self.ActionInTarget('undo')
438 function! s:undotree.ActionRedo()
439 call self.ActionInTarget("redo")
442 function! s:undotree.ActionGoPreviousState()
443 call self.ActionInTarget('earlier')
446 function! s:undotree.ActionGoNextState()
447 call self.ActionInTarget('later')
450 function! s:undotree.ActionGoPreviousSaved()
451 call self.ActionInTarget('earlier 1f')
454 function! s:undotree.ActionGoNextSaved()
455 call self.ActionInTarget('later 1f')
458 function! s:undotree.ActionDiffToggle()
459 let self.opendiff = !self.opendiff
460 call t:diffpanel.Toggle()
461 call self.UpdateDiff()
464 function! s:undotree.ActionTimestampToggle()
465 if !self.SetTargetFocus()
468 let g:undotree_RelativeTimestamp = !g:undotree_RelativeTimestamp
469 let self.targetBufnr = -1 "force update
471 " Update not always set current focus.
475 function! s:undotree.ActionClearHistory()
476 if confirm("Are you sure to clear ALL undo history?","&Yes\n&No") != 1
479 if !self.SetTargetFocus()
482 let ul_bak = &undolevels
484 call s:exec("norm! a \<BS>\<Esc>")
485 let &undolevels = ul_bak
487 let self.targetBufnr = -1 "force update
491 function! s:undotree.ActionClose()
495 function! s:undotree.UpdateDiff()
496 call s:log("undotree.UpdateDiff()")
497 if !t:diffpanel.IsVisible()
500 call t:diffpanel.Update(self.seq_cur,self.targetBufnr,self.targetid)
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
508 call s:exec("norm! ".winnr."\<c-w>\<c-w>")
516 function! s:undotree.Toggle()
517 call s:log(self.bufname." Toggle()")
520 call t:diffpanel.Hide()
521 call self.SetTargetFocus()
524 if !g:undotree_SetFocusWhenToggle
525 call self.SetTargetFocus()
530 function! s:undotree.GetStatusLine()
531 if self.seq_cur != -1
532 let seq_cur = self.seq_cur
536 if self.seq_curhead != -1
537 let seq_curhead = self.seq_curhead
539 let seq_curhead = 'None'
541 return 'current: '.seq_cur.' redo: '.seq_curhead
544 function! s:undotree.Show()
545 call s:log("undotree.Show()")
550 let self.targetid = w:undotree_id
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
557 let cmd = "botright vertical" .
558 \self.width . ' new ' . self.bufname
560 call s:exec("silent keepalt ".cmd)
564 setlocal buftype=nowrite
565 setlocal bufhidden=delete
567 setlocal foldcolumn=0
572 setlocal nomodifiable
573 setlocal statusline=%!t:undotree.GetStatusLine()
579 let ei_bak= &eventignore
582 call self.SetTargetFocus()
583 let self.targetBufnr = -1 "force update
586 let &eventignore = ei_bak
589 call t:diffpanel.Show()
590 call self.UpdateDiff()
594 " called outside undotree window
595 function! s:undotree.Update()
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
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")
609 let emptybuf = 1 "This is not a valid buffer, could be help or something.
610 call s:log("undotree.Update() invalid buffer update")
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
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)
634 call self.UpdateDiff()
639 call s:log("undotree.Update() update whole tree")
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}
646 let self.rawtree = undotree()
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.
657 call self.UpdateDiff()
660 function! s:undotree.AppendHelp()
661 call append(0,'') "empty line
664 call append(0,'" '.i[1].' : '.i[2])
666 call append(0,s:helpmore)
668 call append(0,s:helpless)
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
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
679 let lineNr = a:index + len(s:helpless) + 2
684 " <0 if index is invalid. e.g. current line is in help text.
685 function! s:undotree.Screen2Index(line)
687 let index = a:line - len(s:keymap) - len(s:helpmore) - 2
689 let index = a:line - len(s:helpless) - 2
694 " Current window must be undotree.
695 function! s:undotree.Draw()
696 " remember the current cursor position.
697 let savedview = winsaveview()
700 " Delete text into blackhole register.
701 call s:exec('1,$ d _')
702 call append(0,self.asciitree)
704 call self.AppendHelp()
706 "remove the last empty line
709 " restore previous cursor position.
710 call winrestview(savedview)
712 setlocal nomodifiable
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): ".
722 \self.seq_curhead.' '.
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])
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])
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])
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',''))
745 let max_saved_num = max(keys(self.seq_saved))
747 let lineNr = self.Index2Screen(self.seq2index[self.seq_saved[max_saved_num]])
748 call setline(lineNr,substitute(getline(lineNr),'s','S',''))
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")
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}',''))
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]',''))
771 setlocal nomodifiable
777 function! s:node.Init()
783 function! s:undotree._parseNode(in,out)
784 " type(in) == type([]) && type(out) == type({})
785 if empty(a:in) "empty
791 call self._parseNode(i.alt,curnode)
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
799 if has_key(i,'curhead')
800 let self.seq_curhead = i.seq
801 let self.seq_cur = curnode.seq
804 let self.seq_saved[i.save] = i.seq
806 call extend(curnode.p,[newnode])
807 let curnode = newnode
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}
814 " updatetree: 0: no update, just assign seqs; 1: update and assign seqs.
815 function! s:undotree.ConvertInput(updatetree)
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
822 let self.seq_cur = -1
823 let self.seq_curhead = -1
824 let self.seq_newhead = -1
825 let self.seq_saved = {}
828 let root = s:new(s:node)
832 call self._parseNode(self.rawtree.entries,root)
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
839 if self.seq_cur == -1
840 let self.seq_cur = self.rawtree.seq_cur
842 " undo history is cleared
843 if empty(self.rawtree.entries)
852 "=================================================
853 " Ascii undo tree generator
864 " Tree sieve, p:fork, x:none
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':[]}]}
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,
889 " let tree = deepcopy(self.tree)
895 let TYPE_E = type({})
896 let TYPE_P = type([])
897 let TYPE_X = type('x')
900 let foundx = 0 " 1 if x element is found.
901 let index = 0 " Next element to be print.
903 " Find x element first.
904 for i in range(len(slots))
905 if type(slots[i]) == TYPE_X
912 " Then, find the element with minimun seq.
913 let minseq = 99999999
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
922 let minnode = slots[i]
926 if type(slots[i]) == TYPE_P
941 let newline = onespace
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))
949 let newline = newline.'| '
952 let newline = newline.' \'
956 call remove(slots,index)
958 if type(node) == TYPE_E
960 let seq2index[node.seq]=len(out)
961 for i in range(len(slots))
963 let newline = newline.g:undotree_TreeNodeShape.' '
965 let newline = newline.'| '
968 let newline = newline.' '.(node.seq).' '.
969 \'('.s:gettime(node.time).')'
970 " update the printed slot to its child.
972 let slots[index] = 'x'
974 if len(node.p) == 1 "only one child.
975 let slots[index] = node.p[0]
977 if len(node.p) > 1 "insert p node
978 let slots[index] = node.p
980 let node.p = [] "cut reference.
982 if type(node) == TYPE_P
983 let newmeta = s:new(s:node) "invalid node.
984 for k in range(len(slots))
986 let newline = newline."| "
989 let newline = newline."|/ "
992 let newline = newline."/ "
995 call remove(slots,index)
997 if node[0].seq > node[1].seq
998 call insert(slots,node[1],index)
999 call insert(slots,node[0],index)
1001 call insert(slots,node[0],index)
1002 call insert(slots,node[1],index)
1005 " split P to E+P if elements in p > 2
1007 call remove(node,index(node,minnode))
1008 call insert(slots,minnode,index)
1009 call insert(slots,node,index)
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)
1019 let self.asciitree = out
1020 let self.asciimeta = outmeta
1022 let totallen = len(out)
1023 for i in keys(seq2index)
1024 let seq2index[i] = totallen - 1 - seq2index[i]
1026 let self.seq2index = seq2index
1029 "=================================================
1031 let s:diffpanel = s:new(s:panel)
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
1039 let self.changes.add = 0
1040 let self.changes.del = 0
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]
1049 let ei_bak = &eventignore
1051 let targetWinnr = -1
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
1060 if targetWinnr == -1
1063 call s:exec_silent(targetWinnr." wincmd w")
1065 " remember and restore cursor and window position.
1066 let savedview = winsaveview()
1068 let new = getbufline(a:targetBufnr,'^','$')
1070 let old = getbufline(a:targetBufnr,'^','$')
1073 call winrestview(savedview)
1076 let tempfile1 = tempname()
1077 let tempfile2 = tempname()
1078 if writefile(old,tempfile1) == -1
1079 echoerr "Can not write to temp file:".tempfile1
1081 if writefile(new,tempfile2) == -1
1082 echoerr "Can not write to temp file:".tempfile2
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
1089 if delete(tempfile2) != 0
1090 echoerr "Can not delete temp file:".tempfile2
1092 let &eventignore = ei_bak
1094 let self.cache[a:targetBufnr.'_'.a:seq] = diffresult
1098 call self.ParseDiff(diffresult)
1100 call self.SetFocus()
1103 call s:exec('1,$ d _')
1105 call append(0,diffresult)
1106 call append(0,'- seq: '.a:seq.' -')
1108 "remove the last empty line
1109 if getline("$") == ""
1112 call s:exec('norm! gg') "move cursor to line 1.
1113 setlocal nomodifiable
1114 call t:undotree.SetFocus()
1117 function! s:diffpanel.ParseDiff(diffresult)
1118 " set target focus first.
1119 call t:undotree.SetTargetFocus()
1121 if empty(a:diffresult)
1124 " clear previous highlighted syntax
1125 " matchadd associates with windows.
1126 if exists("w:undotree_diffmatches")
1127 for i in w:undotree_diffmatches
1131 let w:undotree_diffmatches = []
1133 for line in a:diffresult
1134 let matchnum = matchstr(line,'^[0-9,\,]*[ac]\zs\d*\ze')
1136 let lineNr = str2nr(matchnum)
1137 let matchwhat = matchstr(line,'^[0-9,\,]*\zs[ac]\ze\d*')
1140 if matchstr(line,'^<.*$') != ''
1141 let self.changes.del += 1
1143 let matchtext = matchstr(line,'^>\zs .*$')
1147 let self.changes.add += 1
1148 if g:undotree_HighlightChangedText
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))
1156 let lineNr = lineNr+1
1160 function! s:diffpanel.GetStatusLine()
1161 let max = winwidth(0) - 4
1162 let sum = self.changes.add + self.changes.del
1164 let add = self.changes.add * max / sum + 1
1165 let del = self.changes.del * max / sum + 1
1167 let add = self.changes.add
1168 let del = self.changes.del
1170 return string(sum).' '.repeat('+',add).repeat('-',del)
1173 function! s:diffpanel.Init()
1174 let self.bufname = "diffpanel_".s:getUniqueID()
1176 let self.changes = {'add':0, 'del':0}
1177 let self.diffexecutable = executable('diff')
1178 if !self.diffexecutable
1179 echoerr '"diff" is not executable.'
1183 function! s:diffpanel.Toggle()
1184 call s:log(self.bufname." Toggle()")
1192 function! s:diffpanel.Show()
1193 call s:log("diffpanel.Show()")
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()
1202 let ei_bak= &eventignore
1205 if g:undotree_WindowLayout == 1 || g:undotree_WindowLayout == 3
1206 let cmd = 'belowright '.g:undotree_DiffpanelHeight.'new '.self.bufname
1208 let cmd = 'botright '.g:undotree_DiffpanelHeight.'new '.self.bufname
1210 call s:exec_silent(cmd)
1212 setlocal winfixwidth
1213 setlocal winfixheight
1215 setlocal buftype=nowrite
1216 setlocal bufhidden=delete
1218 setlocal nobuflisted
1221 setlocal nocursorline
1222 setlocal nomodifiable
1223 setlocal statusline=%!t:diffpanel.GetStatusLine()
1225 let &eventignore = ei_bak
1227 " syntax need filetype autocommand
1229 setlocal foldcolumn=0
1230 setlocal nofoldenable
1233 call t:undotree.SetFocus()
1234 call winrestview(savedview)
1237 function! s:diffpanel.BindAu()
1238 " Auto exit if it's the last window or undotree closed.
1239 augroup Undotree_Diff
1241 au BufEnter <buffer> call s:exitIfLast()
1242 au BufEnter <buffer> if !t:undotree.IsVisible()
1243 \|call t:diffpanel.Hide() |endif
1247 function! s:diffpanel.CleanUpHighlight()
1248 call s:log("CleanUpHighlight()")
1249 " save current position
1250 let curwinnr = winnr()
1251 let savedview = winsaveview()
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
1261 let w:undotree_diffmatches = []
1266 call s:exec_silent("norm! ".curwinnr."\<c-w>\<c-w>")
1267 call winrestview(savedview)
1270 function! s:diffpanel.Hide()
1271 call s:log(self.bufname." Hide()")
1272 if !self.IsVisible()
1275 call self.SetFocus()
1277 call self.CleanUpHighlight()
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!"
1288 call t:undotree.Action(a:action)
1291 function! s:exitIfLast()
1293 if t:undotree.IsVisible()
1296 if t:diffpanel.IsVisible()
1299 if winnr('$') == num
1300 call t:undotree.Hide()
1301 call t:diffpanel.Hide()
1305 "=================================================
1306 "called outside undotree window
1307 function! UndotreeUpdate()
1308 if !exists('t:undotree')
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)
1315 " assume window layout won't change during updating.
1316 let thiswinnr = winnr()
1317 call t:undotree.Update()
1319 if winnr() != thiswinnr
1320 call s:exec("norm! ".thiswinnr."\<c-w>\<c-w>")
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)
1331 if !exists('t:undotree')
1332 let t:undotree = s:new(s:undotree)
1333 let t:diffpanel = s:new(s:diffpanel)
1335 call t:undotree.Toggle()
1336 call s:log("<<< UndotreeToggle() leave")
1339 function! UndotreeIsVisible()
1340 return (exists('t:undotree') && t:undotree.IsVisible())
1343 function! UndotreeHide()
1344 if UndotreeIsVisible()
1345 call UndotreeToggle()
1349 function! UndotreeShow()
1350 if ! UndotreeIsVisible()
1351 call UndotreeToggle()
1353 call t:undotree.SetFocus()
1357 function! UndotreeFocus()
1358 if UndotreeIsVisible()
1359 call t:undotree.SetFocus()
1364 "let s:auEvents = "InsertEnter,InsertLeave,WinEnter,WinLeave,CursorMoved"
1365 let s:auEvents = "BufEnter,InsertLeave,CursorMoved,BufWritePost"
1367 exec "au! ".s:auEvents." * call UndotreeUpdate()"
1370 "=================================================
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()
1377 " vim: set et fdm=marker sts=4 sw=4: