Undotree plugin.
authorIain Patterson <me@iain.cx>
Tue, 9 Feb 2016 17:13:57 +0000 (17:13 +0000)
committerIain Patterson <me@iain.cx>
Thu, 11 Feb 2016 13:09:16 +0000 (13:09 +0000)
.vim/doc/undotree.txt [new file with mode: 0644]
.vim/plugin/undotree.vim [new file with mode: 0644]
.vim/syntax/undotree.vim [new file with mode: 0644]

diff --git a/.vim/doc/undotree.txt b/.vim/doc/undotree.txt
new file mode 100644 (file)
index 0000000..3073723
--- /dev/null
@@ -0,0 +1,291 @@
+*undotree.txt*  Display your undo history in a graph
+
+Author:         Ming Bai <mbbill AT gmail DOT COM>
+Licence:        BSD
+Homepage:       https://github.com/mbbill/undotree/
+Version:        4.3
+
+==============================================================================
+CONTENTS                                                   *undotree-contents*
+
+    1. Intro ................................ |undotree-intro|
+    2. Usage ................................ |undotree-usage|
+    3. Configuration ........................ |undotree-config|
+        3.1  undotree_WindowLayout .......... |undotree_WindowLayout|
+        3.2  undotree_SplitWidth ............ |undotree_SplitWidth|
+        3.3  undotree_DiffpanelHeight ....... |undotree_DiffpanelHeight|
+        3.4  undotree_DiffAutoOpen .......... |undotree_DiffAutoOpen|
+        3.5  undotree_SetFocusWhenToggle .... |undotree_SetFocusWhenToggle|
+        3.6  undotree_TreeNodeShape ......... |undotree_TreeNodeShape|
+        3.7  undotree_DiffCommand ........... |undotree_DiffCommand|
+        3.8  undotree_RelativeTimestamp ..... |undotree_RelativeTimestamp|
+        3.9  undotree_HighlightChangedText .. |undotree_HighlightChangedText|
+        3.10 undotree_HighlightSyntaxAdd .... |undotree_HighlightSyntaxAdd|
+             undotree_HighlightSyntaxChange . |undotree_HighlightSyntaxChange|
+        3.11 Undotree_CustomMap ............. |Undotree_CustomMap|
+    4. Bugs ................................. |undotree-bugs|
+    5. Changelog ............................ |undotree-changelog|
+    6. License .............................. |undotree-license|
+
+==============================================================================
+1. Intro                                                      *undotree-intro*
+
+Vim 7.0 added a new feature named Undo branches. Basically it's a kind of
+ability to go back to the text after any change, even if they were undone. Vim
+stores undo history in a tree which you can browse and manipulate through a
+bunch of commands. But that was not enough straightforward and a bit hard to
+use. You may use :help |new-undo-branches| or :help |undo-tree| to get more
+detailed help. Now this plug-in will free you from those commands and bring
+back the power of undo tree.
+
+==============================================================================
+2. Usage                                                      *undotree-usage*
+
+Use :UndotreeToggle to toggle the undo-tree panel. You may want to map this
+command to whatever hotkey by adding the following line to your vimrc, take F5
+for example.
+>
+    nnoremap <F5> :UndotreeToggle<cr>
+<
+Then you can try to do some modification, and the undo tree will automatically
+updated afterwards.
+
+There are some hotkeys provided by vim to switch between the changes in
+history, like |u|, |<c-r>|, |g+|, |g-| as well as the |:earlier| and |:later|
+commands.
+
+You may also switch to undotree panel and use the hotkeys to switch between
+history versions. Press ? in undotree window for quick help of hotkeys.
+
+You can monitor the changed text in diff panel which is automatically updated
+when undo/redo happens.
+
+Persistent undo.
+It is highly recommend to enable the persistent undo. If you don't like your
+working directory be messed up with the undo file everywhere, you may add the
+following line to your vimrc in order to make them stored together.
+>
+    if has("persistent_undo")
+        set undodir='~/.undodir/'
+        set undofile
+    endif
+<
+==============================================================================
+3. Configuration                                             *undotree-config*
+
+------------------------------------------------------------------------------
+3.1 g:undotree_WindowLayout                            *undotree_WindowLayout*
+
+Set the undotree window layout.
+
+Style 1
+>
+    +----------+------------------------+
+    |          |                        |
+    |          |                        |
+    | undotree |                        |
+    |          |                        |
+    |          |                        |
+    +----------+                        |
+    |          |                        |
+    |   diff   |                        |
+    |          |                        |
+    +----------+------------------------+
+<
+Style 2
+>
+    +----------+------------------------+
+    |          |                        |
+    |          |                        |
+    | undotree |                        |
+    |          |                        |
+    |          |                        |
+    +----------+------------------------+
+    |                                   |
+    |   diff                            |
+    |                                   |
+    +-----------------------------------+
+<
+Style 3
+>
+    +------------------------+----------+
+    |                        |          |
+    |                        |          |
+    |                        | undotree |
+    |                        |          |
+    |                        |          |
+    |                        +----------+
+    |                        |          |
+    |                        |   diff   |
+    |                        |          |
+    +------------------------+----------+
+<
+Style 4
+>
+    +------------------------+----------+
+    |                        |          |
+    |                        |          |
+    |                        | undotree |
+    |                        |          |
+    |                        |          |
+    +------------------------+----------+
+    |                                   |
+    |                            diff   |
+    |                                   |
+    +-----------------------------------+
+<
+Default: 1
+
+------------------------------------------------------------------------------
+3.2 g:undotree_SplitWidth                                *undotree_SplitWidth*
+
+Set the undotree window width.
+
+Default: 30
+
+------------------------------------------------------------------------------
+3.3 g:undotree_DiffpanelHeight                      *undotree_DiffpanelHeight*
+
+Set the diff window height.
+
+Default: 10
+
+------------------------------------------------------------------------------
+3.4 g:undotree_DiffAutoOpen                            *undotree_DiffAutoOpen*
+
+Set this to 1 to auto open the diff window.
+
+Default: 1
+
+------------------------------------------------------------------------------
+3.5 g:undotree_SetFocusWhenToggle                *undotree_SetFocusWhenToggle*
+
+If set to 1, the undotree window will get focus after being opened, otherwise
+focus will stay in current window.
+
+Default: 0
+
+------------------------------------------------------------------------------
+3.6 g:undotree_TreeNodeShape                          *undotree_TreeNodeShape*
+
+Set the tree node shape.
+
+Default: '*'
+
+------------------------------------------------------------------------------
+3.7 g:undotree_DiffCommand                              *undotree_DiffCommand*
+
+Set the command used to get the diff output.
+
+Default: "diff"
+
+------------------------------------------------------------------------------
+3.8 g:undotree_RelativeTimestamp                  *undotree_RelativeTimestamp*
+
+Set to 1 to use relative timestamp.
+
+Default: 1
+
+------------------------------------------------------------------------------
+3.9 g:undotree_HighlightChangedText            *undotree_HighlightChangedText*
+
+Set to 1 to highlight the changed text.
+
+Default: 1
+
+------------------------------------------------------------------------------
+3.10 g:undotree_HighlightSyntaxAdd               *undotree_HighlightSyntaxAdd*
+     g:undotree_HighlightSyntaxChange         *undotree_HighlightSyntaxChange*
+
+Set the highlight linked syntax type.
+You may chose your favorite through ":hi" command.
+
+Default: "DiffAdd" and "DiffChange"
+
+------------------------------------------------------------------------------
+3.11 g:Undotree_CustomMap                                  *Undotree_CustomMap*
+
+There are two ways of changing the default key mappings:
+The first way is to define global mappings as the following example:
+>
+    nmap <buffer> J <plug>UndotreeGoNextState
+    nmap <buffer> K <plug>UndotreeGoPreviousState
+<
+A better approach is to define the callback function g:Undotree_CustomMap().
+The function will be called after the undotree windows is initialized, so the
+key mappings only works on the undotree windows.
+>
+    function g:Undotree_CustomMap()
+        nmap <buffer> J <plug>UndotreeGoNextState
+        nmap <buffer> K <plug>UndotreeGoPreviousState
+    endfunc
+<
+List of the commands available for redefinition.
+>
+    <plug>UndotreeHelp
+    <plug>UndotreeClose
+    <plug>UndotreeFocusTarget
+    <plug>UndotreeClearHistory
+    <plug>UndotreeTimestampToggle
+    <plug>UndotreeDiffToggle
+    <plug>UndotreeGoNextState
+    <plug>UndotreeGoPreviousState
+    <plug>UndotreeGoNextSaved
+    <plug>UndotreeGoPreviousSaved
+    <plug>UndotreeRedo
+    <plug>UndotreeUndo
+    <plug>UndotreeEnter
+    <plug>UndotreeEnter
+<
+==============================================================================
+4. Bugs                                                        *undotree-bugs*
+
+Post any issue and feature request here:
+https://github.com/mbbill/undotree/issues
+
+==============================================================================
+5. Changelog                                              *undotree-changelog*
+
+4.3 (2013-02-18)
+    - Several fixes and enhancements.
+
+4.2 (2012-11-24)
+    - Fixed some small issue.
+
+4.1 (2012-09-05)
+    - Enhanced tree style.
+    - Multi-window switching support.
+
+4.0 (2012-08-30)
+    - Live updated highlight for changed text.
+    - Customizable key mappings.
+    - Fixed some minor bugs.
+
+3.1 (2012-08-25)
+    - Add saved status.
+    - Add relative timestamp.
+    - Add ability of clear undo history.
+
+3.0 (2012-08-24)
+    - Add diff panel.
+    - Performance improvement.
+
+2.2 (2012-08-21)
+    - Stable version.
+
+2.1 (2012-08-20)
+    - Fixed some annoying issues.
+
+2.0 (2012-08-19)
+    - Hotkey support.
+    - Handle undo levels.
+    - Auto refresh.
+    - And so on.
+
+1.0 (2012-08-18)
+    - Initial upload
+
+==============================================================================
+6. License                                                  *undotree-license*
+
+BSD
diff --git a/.vim/plugin/undotree.vim b/.vim/plugin/undotree.vim
new file mode 100644 (file)
index 0000000..51d9b2c
--- /dev/null
@@ -0,0 +1,1377 @@
+"=================================================
+" File: undotree.vim
+" Description: Manage your undo history in a graph.
+" Author: Ming Bai <mbbill@gmail.com>
+" License: BSD
+
+" Avoid installing twice.
+if exists('g:loaded_undotree')
+    finish
+endif
+let g:loaded_undotree = 0
+
+" At least version 7.3 with 005 patch is needed for undo branches.
+" Refer to https://github.com/mbbill/undotree/issues/4 for details.
+" Thanks kien
+if v:version < 703
+    "command! -n=0 -bar UndotreeToggle :echoerr "undotree.vim needs Vim version >= 7.3"
+    finish
+endif
+if (v:version == 703 && !has("patch005"))
+    "command! -n=0 -bar UndotreeToggle :echoerr "undotree.vim needs vim7.3 with patch005 applied."
+    finish
+endif
+let g:loaded_undotree = 1   " Signal plugin availability with a value of 1.
+
+"=================================================
+"Options:
+
+" Window layout
+" style 1
+" +----------+------------------------+
+" |          |                        |
+" |          |                        |
+" | undotree |                        |
+" |          |                        |
+" |          |                        |
+" +----------+                        |
+" |          |                        |
+" |   diff   |                        |
+" |          |                        |
+" +----------+------------------------+
+" Style 2
+" +----------+------------------------+
+" |          |                        |
+" |          |                        |
+" | undotree |                        |
+" |          |                        |
+" |          |                        |
+" +----------+------------------------+
+" |                                   |
+" |   diff                            |
+" |                                   |
+" +-----------------------------------+
+" Style 3
+" +------------------------+----------+
+" |                        |          |
+" |                        |          |
+" |                        | undotree |
+" |                        |          |
+" |                        |          |
+" |                        +----------+
+" |                        |          |
+" |                        |   diff   |
+" |                        |          |
+" +------------------------+----------+
+" Style 4
+" +-----------------------++----------+
+" |                        |          |
+" |                        |          |
+" |                        | undotree |
+" |                        |          |
+" |                        |          |
+" +------------------------+----------+
+" |                                   |
+" |                            diff   |
+" |                                   |
+" +-----------------------------------+
+if !exists('g:undotree_WindowLayout')
+    let g:undotree_WindowLayout = 1
+endif
+
+" undotree window width
+if !exists('g:undotree_SplitWidth')
+    let g:undotree_SplitWidth = 30
+endif
+
+" diff window height
+if !exists('g:undotree_DiffpanelHeight')
+    let g:undotree_DiffpanelHeight = 10
+endif
+
+" auto open diff window
+if !exists('g:undotree_DiffAutoOpen')
+    let g:undotree_DiffAutoOpen = 1
+endif
+
+" if set, let undotree window get focus after being opened, otherwise
+" focus will stay in current window.
+if !exists('g:undotree_SetFocusWhenToggle')
+    let g:undotree_SetFocusWhenToggle = 0
+endif
+
+" tree node shape.
+if !exists('g:undotree_TreeNodeShape')
+    let g:undotree_TreeNodeShape = '*'
+endif
+
+if !exists('g:undotree_DiffCommand')
+    let g:undotree_DiffCommand = "diff"
+endif
+
+" relative timestamp
+if !exists('g:undotree_RelativeTimestamp')
+    let g:undotree_RelativeTimestamp = 1
+endif
+
+" Highlight changed text
+if !exists('g:undotree_HighlightChangedText')
+    let g:undotree_HighlightChangedText = 1
+endif
+
+" Highlight linked syntax type.
+" You may chose your favorite through ":hi" command
+if !exists('g:undotree_HighlightSyntaxAdd')
+    let g:undotree_HighlightSyntaxAdd = "DiffAdd"
+endif
+if !exists('g:undotree_HighlightSyntaxChange')
+    let g:undotree_HighlightSyntaxChange = "DiffChange"
+endif
+
+" Deprecates the old style configuration.
+if exists('g:undotree_SplitLocation')
+    echo "g:undotree_SplitLocation is deprecated,
+                \ please use g:undotree_WindowLayout instead."
+endif
+
+"Custom key mappings: add this function to your vimrc.
+"You can define whatever mapping as you like, this is a hook function which
+"will be called after undotree window initialized.
+"
+"function g:Undotree_CustomMap()
+"    map <buffer> <c-n> J
+"    map <buffer> <c-p> K
+"endfunction
+
+"=================================================
+" Help text
+let s:helpmore = ['"    ===== Marks ===== ',
+            \'" >num< : current change',
+            \'" {num} : change to redo',
+            \'" [num] : the last change',
+            \'"   s   : saved changes',
+            \'"   S   : last saved change',
+            \'"   ===== Hotkeys =====']
+let s:helpless = ['" Press ? for help.']
+
+" Keymap
+let s:keymap = []
+" action, key, help.
+let s:keymap += [['Help','?','Toggle quick help']]
+let s:keymap += [['Close','q','Close this panel']]
+let s:keymap += [['FocusTarget','<tab>','Set Focus to editor']]
+let s:keymap += [['ClearHistory','C','Clear undo history']]
+let s:keymap += [['TimestampToggle','T','Toggle relative timestamp']]
+let s:keymap += [['DiffToggle','D','Toggle diff panel']]
+let s:keymap += [['GoNextState','K','Revert to next state']]
+let s:keymap += [['GoPreviousState','J','Revert to previous state']]
+let s:keymap += [['GoNextSaved','>','Revert to next saved state']]
+let s:keymap += [['GoPreviousSaved','<','Revert to previous saved state']]
+let s:keymap += [['Redo','<c-r>','Redo']]
+let s:keymap += [['Undo','u','Undo']]
+let s:keymap += [['Enter','<2-LeftMouse>','Revert to current']]
+let s:keymap += [['Enter','<cr>','Revert to current']]
+
+function! s:new(obj)
+    let newobj = deepcopy(a:obj)
+    call newobj.Init()
+    return newobj
+endfunction
+
+" Get formatted time
+function! s:gettime(time)
+    if a:time == 0
+        return "Original"
+    endif
+    if !g:undotree_RelativeTimestamp
+        let today = substitute(strftime("%c",localtime())," .*$",'','g')
+        if today == substitute(strftime("%c",a:time)," .*$",'','g')
+            return strftime("%H:%M:%S",a:time)
+        else
+            return strftime("%H:%M:%S %b%d %Y",a:time)
+        endif
+    else
+        let sec = localtime() - a:time
+        if sec < 0
+            let sec = 0
+        endif
+        if sec < 60
+            if sec == 1
+                return '1 second ago'
+            else
+                return sec.' seconds ago'
+            endif
+        endif
+        if sec < 3600
+            if (sec/60) == 1
+                return '1 minute ago'
+            else
+                return (sec/60).' minutes ago'
+            endif
+        endif
+        if sec < 86400 "3600*24
+            if (sec/3600) == 1
+                return '1 hour ago'
+            else
+                return (sec/3600).' hours ago'
+            endif
+        endif
+        return (sec/86400).' days ago'
+    endif
+endfunction
+
+function! s:exec(cmd)
+    call s:log("s:exec() ".a:cmd)
+    silent exe a:cmd
+endfunction
+
+" Don't trigger any events(like BufEnter which could cause redundant refresh)
+function! s:exec_silent(cmd)
+    call s:log("s:exec_silent() ".a:cmd)
+    let ei_bak= &eventignore
+    set eventignore=all
+    silent exe a:cmd
+    let &eventignore = ei_bak
+endfunction
+
+" Return a unique id each time.
+let s:cntr = 0
+function! s:getUniqueID()
+    let s:cntr = s:cntr + 1
+    return s:cntr
+endfunction
+
+" Debug...
+let s:debug = 0
+let s:debugfile = $HOME.'/undotree_debug.log'
+" If debug file exists, enable debug output.
+if filewritable(s:debugfile)
+    let s:debug = 1
+    exec 'redir >> '. s:debugfile
+    silent echo "=======================================\n"
+    redir END
+endif
+
+function! s:log(msg)
+    if s:debug
+        exec 'redir >> ' . s:debugfile
+        silent echon strftime('%H:%M:%S') . ': ' . string(a:msg) . "\n"
+        redir END
+    endif
+endfunction
+
+"=================================================
+"Base class for panels.
+let s:panel = {}
+
+function! s:panel.Init()
+    let self.bufname = "invalid"
+endfunction
+
+function! s:panel.SetFocus()
+    let winnr = bufwinnr(self.bufname)
+    " already focused.
+    if winnr == winnr()
+        return
+    endif
+    if winnr == -1
+        echoerr "Fatal: window does not exist!"
+        return
+    endif
+    call s:log("SetFocus() winnr:".winnr." bufname:".self.bufname)
+    " wincmd would cause cursor outside window.
+    call s:exec_silent("norm! ".winnr."\<c-w>\<c-w>")
+endfunction
+
+function! s:panel.IsVisible()
+    if bufwinnr(self.bufname) != -1
+        return 1
+    else
+        return 0
+    endif
+endfunction
+
+function! s:panel.Hide()
+    call s:log(self.bufname." Hide()")
+    if !self.IsVisible()
+        return
+    endif
+    call self.SetFocus()
+    call s:exec("quit")
+endfunction
+
+"=================================================
+" undotree panel class.
+" extended from panel.
+"
+
+" {rawtree}
+"     |
+"     | ConvertInput()               {seq2index}--> [seq1:index1]
+"     v                                             [seq2:index2] ---+
+"  {tree}                                               ...          |
+"     |                                    [asciimeta]               |
+"     | Render()                                |                    |
+"     v                                         v                    |
+" [asciitree] --> [" * | SEQ DDMMYY "] <==> [node1{seq,time,..}]     |
+"                 [" |/             "]      [node2{seq,time,..}] <---+
+"                         ...                       ...
+
+let s:undotree = s:new(s:panel)
+
+function! s:undotree.Init()
+    let self.bufname = "undotree_".s:getUniqueID()
+    " Increase to make it unique.
+    let self.width = g:undotree_SplitWidth
+    let self.opendiff = g:undotree_DiffAutoOpen
+    let self.targetid = -1
+    let self.targetBufnr = -1
+    let self.rawtree = {}  "data passed from undotree()
+    let self.tree = {}     "data converted to internal format.
+    let self.seq_last = -1
+    let self.save_last = -1
+    let self.save_last_bak = -1
+
+    " seqs
+    let self.seq_cur = -1
+    let self.seq_curhead = -1
+    let self.seq_newhead = -1
+    let self.seq_saved = {} "{saved value -> seq} pair
+
+    "backup, for mark
+    let self.seq_cur_bak = -1
+    let self.seq_curhead_bak = -1
+    let self.seq_newhead_bak = -1
+
+    let self.asciitree = []     "output data.
+    let self.asciimeta = []     "meta data behind ascii tree.
+    let self.seq2index = {}     "table used to convert seq to index.
+    let self.showHelp = 0
+endfunction
+
+function! s:undotree.BindKey()
+    if v:version > 703 || (v:version == 703 && has("patch1261"))
+        let map_options = ' <nowait> '
+    else
+        let map_options = ''
+    endif
+    let map_options = map_options.' <silent> <buffer> '
+    for i in s:keymap
+        silent exec 'nmap '.map_options.i[1].' <plug>Undotree'.i[0]
+        silent exec 'nnoremap '.map_options.'<plug>Undotree'.i[0]
+            \ .' :call <sid>undotreeAction("'.i[0].'")<cr>'
+    endfor
+    if exists('*g:Undotree_CustomMap')
+        call g:Undotree_CustomMap()
+    endif
+endfunction
+
+function! s:undotree.BindAu()
+    " Auto exit if it's the last window
+    augroup Undotree_Main
+        au!
+        au BufEnter <buffer> call s:exitIfLast()
+        au BufEnter,BufLeave <buffer> if exists('t:undotree') |
+                    \let t:undotree.width = winwidth(winnr()) | endif
+        au BufWinLeave <buffer> if exists('t:diffpanel') |
+                    \call t:diffpanel.Hide() | endif
+    augroup end
+endfunction
+
+function! s:undotree.Action(action)
+    call s:log("undotree.Action() ".a:action)
+    if !self.IsVisible() || bufname("%") != self.bufname
+        echoerr "Fatal: window does not exists."
+        return
+    endif
+    if !has_key(self,'Action'.a:action)
+        echoerr "Fatal: Action does not exists!"
+        return
+    endif
+    silent exec 'call self.Action'.a:action.'()'
+endfunction
+
+" Helper function, do action in target window, and then update itself.
+function! s:undotree.ActionInTarget(cmd)
+    if !self.SetTargetFocus()
+        return
+    endif
+    " Target should be a normal buffer.
+    if (&bt == '') && (&modifiable == 1) && (mode() == 'n')
+        call s:exec(a:cmd)
+        call self.Update()
+    endif
+    " Update not always set current focus.
+    call self.SetFocus()
+endfunction
+
+function! s:undotree.ActionHelp()
+    let self.showHelp = !self.showHelp
+    call self.Draw()
+    call self.MarkSeqs()
+endfunction
+
+function! s:undotree.ActionFocusTarget()
+    call self.SetTargetFocus()
+endfunction
+
+function! s:undotree.ActionEnter()
+    let index = self.Screen2Index(line('.'))
+    if index < 0
+        return
+    endif
+    let seq = self.asciimeta[index].seq
+    if seq == -1
+        return
+    endif
+    if seq == 0
+        call self.ActionInTarget('norm 9999u')
+        return
+    endif
+    call self.ActionInTarget('u '.self.asciimeta[index].seq)
+endfunction
+
+function! s:undotree.ActionUndo()
+    call self.ActionInTarget('undo')
+endfunction
+
+function! s:undotree.ActionRedo()
+    call self.ActionInTarget("redo")
+endfunction
+
+function! s:undotree.ActionGoPreviousState()
+    call self.ActionInTarget('earlier')
+endfunction
+
+function! s:undotree.ActionGoNextState()
+    call self.ActionInTarget('later')
+endfunction
+
+function! s:undotree.ActionGoPreviousSaved()
+    call self.ActionInTarget('earlier 1f')
+endfunction
+
+function! s:undotree.ActionGoNextSaved()
+    call self.ActionInTarget('later 1f')
+endfunction
+
+function! s:undotree.ActionDiffToggle()
+    let self.opendiff = !self.opendiff
+    call t:diffpanel.Toggle()
+    call self.UpdateDiff()
+endfunction
+
+function! s:undotree.ActionTimestampToggle()
+    if !self.SetTargetFocus()
+        return
+    endif
+    let g:undotree_RelativeTimestamp = !g:undotree_RelativeTimestamp
+    let self.targetBufnr = -1 "force update
+    call self.Update()
+    " Update not always set current focus.
+    call self.SetFocus()
+endfunction
+
+function! s:undotree.ActionClearHistory()
+    if confirm("Are you sure to clear ALL undo history?","&Yes\n&No") != 1
+        return
+    endif
+    if !self.SetTargetFocus()
+        return
+    endif
+    let ul_bak = &undolevels
+    let &undolevels = -1
+    call s:exec("norm! a \<BS>\<Esc>")
+    let &undolevels = ul_bak
+    unlet ul_bak
+    let self.targetBufnr = -1 "force update
+    call self.Update()
+endfunction
+
+function! s:undotree.ActionClose()
+    call self.Toggle()
+endfunction
+
+function! s:undotree.UpdateDiff()
+    call s:log("undotree.UpdateDiff()")
+    if !t:diffpanel.IsVisible()
+        return
+    endif
+    call t:diffpanel.Update(self.seq_cur,self.targetBufnr,self.targetid)
+endfunction
+
+" May fail due to target window closed.
+function! s:undotree.SetTargetFocus()
+    for winnr in range(1, winnr('$')) "winnr starts from 1
+        if getwinvar(winnr,'undotree_id') == self.targetid
+            if winnr() != winnr
+                call s:exec("norm! ".winnr."\<c-w>\<c-w>")
+                return 1
+            endif
+        endif
+    endfor
+    return 0
+endfunction
+
+function! s:undotree.Toggle()
+    call s:log(self.bufname." Toggle()")
+    if self.IsVisible()
+        call self.Hide()
+        call t:diffpanel.Hide()
+        call self.SetTargetFocus()
+    else
+        call self.Show()
+        if !g:undotree_SetFocusWhenToggle
+            call self.SetTargetFocus()
+        endif
+    endif
+endfunction
+
+function! s:undotree.GetStatusLine()
+    if self.seq_cur != -1
+        let seq_cur = self.seq_cur
+    else
+        let seq_cur = 'None'
+    endif
+    if self.seq_curhead != -1
+        let seq_curhead = self.seq_curhead
+    else
+        let seq_curhead = 'None'
+    endif
+    return 'current: '.seq_cur.' redo: '.seq_curhead
+endfunction
+
+function! s:undotree.Show()
+    call s:log("undotree.Show()")
+    if self.IsVisible()
+        return
+    endif
+
+    let self.targetid = w:undotree_id
+
+    " Create undotree window.
+    if g:undotree_WindowLayout == 1 || g:undotree_WindowLayout == 2
+        let cmd = "topleft vertical" .
+                    \self.width . ' new ' . self.bufname
+    else
+        let cmd = "botright vertical" .
+                    \self.width . ' new ' . self.bufname
+    endif
+    call s:exec("silent keepalt ".cmd)
+    call self.SetFocus()
+    setlocal winfixwidth
+    setlocal noswapfile
+    setlocal buftype=nowrite
+    setlocal bufhidden=delete
+    setlocal nowrap
+    setlocal foldcolumn=0
+    setlocal nobuflisted
+    setlocal nospell
+    setlocal nonumber
+    setlocal cursorline
+    setlocal nomodifiable
+    setlocal statusline=%!t:undotree.GetStatusLine()
+    setfiletype undotree
+
+    call self.BindKey()
+    call self.BindAu()
+
+    let ei_bak= &eventignore
+    set eventignore=all
+
+    call self.SetTargetFocus()
+    let self.targetBufnr = -1 "force update
+    call self.Update()
+
+    let &eventignore = ei_bak
+
+    if self.opendiff
+        call t:diffpanel.Show()
+        call self.UpdateDiff()
+    endif
+endfunction
+
+" called outside undotree window
+function! s:undotree.Update()
+    if !self.IsVisible()
+        return
+    endif
+    " do nothing if we're in the undotree or diff panel
+    let bufname = bufname('%')
+    if bufname == self.bufname || bufname == t:diffpanel.bufname
+        return
+    endif
+    if (&bt != '') || (&modifiable == 0) || (mode() != 'n')
+        if self.targetBufnr == bufnr('%') && self.targetid == w:undotree_id
+            call s:log("undotree.Update() invalid buffer NOupdate")
+            return
+        endif
+        let emptybuf = 1 "This is not a valid buffer, could be help or something.
+        call s:log("undotree.Update() invalid buffer update")
+    else
+        let emptybuf = 0
+        "update undotree,set focus
+        if self.targetBufnr == bufnr('%')
+            let self.targetid = w:undotree_id
+            let newrawtree = undotree()
+            if self.rawtree == newrawtree
+                return
+            endif
+
+            " same buffer, but seq changed.
+            if newrawtree.seq_last == self.seq_last
+                call s:log("undotree.Update() update seqs")
+                let self.rawtree = newrawtree
+                call self.ConvertInput(0) "only update seqs.
+                if (self.seq_cur == self.seq_cur_bak) &&
+                            \(self.seq_curhead == self.seq_curhead_bak)&&
+                            \(self.seq_newhead == self.seq_newhead_bak)&&
+                            \(self.save_last == self.save_last_bak)
+                    return
+                endif
+                call self.SetFocus()
+                call self.MarkSeqs()
+                call self.UpdateDiff()
+                return
+            endif
+        endif
+    endif
+    call s:log("undotree.Update() update whole tree")
+
+    let self.targetBufnr = bufnr('%')
+    let self.targetid = w:undotree_id
+    if emptybuf " Show an empty undo tree instead of do nothing.
+        let self.rawtree = {'seq_last':0,'entries':[],'time_cur':0,'save_last':0,'synced':1,'save_cur':0,'seq_cur':0}
+    else
+        let self.rawtree = undotree()
+    endif
+    let self.seq_last = self.rawtree.seq_last
+    let self.seq_cur = -1
+    let self.seq_curhead = -1
+    let self.seq_newhead = -1
+    call self.ConvertInput(1) "update all.
+    call self.Render()
+    call self.SetFocus()
+    call self.Draw()
+    call self.MarkSeqs()
+    call self.UpdateDiff()
+endfunction
+
+function! s:undotree.AppendHelp()
+    call append(0,'') "empty line
+    if self.showHelp
+        for i in s:keymap
+            call append(0,'" '.i[1].' : '.i[2])
+        endfor
+        call append(0,s:helpmore)
+    else
+        call append(0,s:helpless)
+    endif
+endfunction
+
+function! s:undotree.Index2Screen(index)
+    " calculate line number according to the help text.
+    " index starts from zero and lineNr starts from 1
+    if self.showHelp
+        " 2 means 1 empty line + 1 index padding (index starts from zero)
+        let lineNr = a:index + len(s:keymap) + len(s:helpmore) + 2
+    else
+        let lineNr = a:index + len(s:helpless) + 2
+    endif
+    return lineNr
+endfunction
+
+" <0 if index is invalid. e.g. current line is in help text.
+function! s:undotree.Screen2Index(line)
+    if self.showHelp
+        let index = a:line - len(s:keymap) - len(s:helpmore) - 2
+    else
+        let index = a:line - len(s:helpless) - 2
+    endif
+    return index
+endfunction
+
+" Current window must be undotree.
+function! s:undotree.Draw()
+    " remember the current cursor position.
+    let savedview = winsaveview()
+
+    setlocal modifiable
+    " Delete text into blackhole register.
+    call s:exec('1,$ d _')
+    call append(0,self.asciitree)
+
+    call self.AppendHelp()
+
+    "remove the last empty line
+    call s:exec('$d _')
+
+    " restore previous cursor position.
+    call winrestview(savedview)
+
+    setlocal nomodifiable
+endfunction
+
+function! s:undotree.MarkSeqs()
+    call s:log("bak(cur,curhead,newhead): ".
+                \self.seq_cur_bak.' '.
+                \self.seq_curhead_bak.' '.
+                \self.seq_newhead_bak)
+    call s:log("(cur,curhead,newhead): ".
+                \self.seq_cur.' '.
+                \self.seq_curhead.' '.
+                \self.seq_newhead)
+    setlocal modifiable
+    " reset bak seq lines.
+    if self.seq_cur_bak != -1
+        let index = self.seq2index[self.seq_cur_bak]
+        call setline(self.Index2Screen(index),self.asciitree[index])
+    endif
+    if self.seq_curhead_bak != -1
+        let index = self.seq2index[self.seq_curhead_bak]
+        call setline(self.Index2Screen(index),self.asciitree[index])
+    endif
+    if self.seq_newhead_bak != -1
+        let index = self.seq2index[self.seq_newhead_bak]
+        call setline(self.Index2Screen(index),self.asciitree[index])
+    endif
+    " mark save seqs
+    for i in keys(self.seq_saved)
+        let index = self.seq2index[self.seq_saved[i]]
+        let lineNr = self.Index2Screen(index)
+        call setline(lineNr,substitute(self.asciitree[index],
+                    \' \d\+  \zs \ze','s',''))
+    endfor
+    let max_saved_num = max(keys(self.seq_saved))
+    if max_saved_num > 0
+        let lineNr = self.Index2Screen(self.seq2index[self.seq_saved[max_saved_num]])
+        call setline(lineNr,substitute(getline(lineNr),'s','S',''))
+    endif
+    " mark new seqs.
+    if self.seq_cur != -1
+        let index = self.seq2index[self.seq_cur]
+        let lineNr = self.Index2Screen(index)
+        call setline(lineNr,substitute(getline(lineNr),
+                    \'\zs \(\d\+\) \ze [sS ] ','>\1<',''))
+        " move cursor to that line.
+        call s:exec("normal! " . lineNr . "G")
+    endif
+    if self.seq_curhead != -1
+        let index = self.seq2index[self.seq_curhead]
+        let lineNr = self.Index2Screen(index)
+        call setline(lineNr,substitute(getline(lineNr),
+                    \'\zs \(\d\+\) \ze [sS ] ','{\1}',''))
+    endif
+    if self.seq_newhead != -1
+        let index = self.seq2index[self.seq_newhead]
+        let lineNr = self.Index2Screen(index)
+        call setline(lineNr,substitute(getline(lineNr),
+                    \'\zs \(\d\+\) \ze [sS ] ','[\1]',''))
+    endif
+    setlocal nomodifiable
+endfunction
+
+" tree node class
+let s:node = {}
+
+function! s:node.Init()
+    let self.seq = -1
+    let self.p = []
+    let self.time = -1
+endfunction
+
+function! s:undotree._parseNode(in,out)
+    " type(in) == type([]) && type(out) == type({})
+    if empty(a:in) "empty
+        return
+    endif
+    let curnode = a:out
+    for i in a:in
+        if has_key(i,'alt')
+            call self._parseNode(i.alt,curnode)
+        endif
+        let newnode = s:new(s:node)
+        let newnode.seq = i.seq
+        let newnode.time = i.time
+        if has_key(i,'newhead')
+            let self.seq_newhead = i.seq
+        endif
+        if has_key(i,'curhead')
+            let self.seq_curhead = i.seq
+            let self.seq_cur = curnode.seq
+        endif
+        if has_key(i,'save')
+            let self.seq_saved[i.save] = i.seq
+        endif
+        call extend(curnode.p,[newnode])
+        let curnode = newnode
+    endfor
+endfunction
+
+"Sample:
+"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}
+
+" updatetree: 0: no update, just assign seqs;  1: update and assign seqs.
+function! s:undotree.ConvertInput(updatetree)
+    "reset seqs
+    let self.seq_cur_bak = self.seq_cur
+    let self.seq_curhead_bak = self.seq_curhead
+    let self.seq_newhead_bak = self.seq_newhead
+    let self.save_last_bak = self.save_last
+
+    let self.seq_cur = -1
+    let self.seq_curhead = -1
+    let self.seq_newhead = -1
+    let self.seq_saved = {}
+
+    "Generate root node
+    let root = s:new(s:node)
+    let root.seq = 0
+    let root.time = 0
+
+    call self._parseNode(self.rawtree.entries,root)
+
+    let self.save_last = self.rawtree.save_last
+    " Note: Normally, the current node should be the one that seq_cur points to,
+    " but in fact it's not. May be bug, bug anyway I found a workaround:
+    " first try to find the parent node of 'curhead', if not found, then use
+    " seq_cur.
+    if self.seq_cur == -1
+        let self.seq_cur = self.rawtree.seq_cur
+    endif
+    " undo history is cleared
+    if empty(self.rawtree.entries)
+        let self.seq_cur = 0
+    endif
+    if a:updatetree
+        let self.tree = root
+    endif
+endfunction
+
+
+"=================================================
+" Ascii undo tree generator
+"
+" Example:
+" 6 8  7
+" |/   |
+" 2    4
+"  \   |
+"   1  3  5
+"    \ | /
+"      0
+
+" Tree sieve, p:fork, x:none
+"
+" x         8
+" 8x        | 7
+" 87         \ \
+" x87       6 | |
+" 687       |/ /
+" p7x       | | 5
+" p75       | 4 |
+" p45       | 3 |
+" p35       | |/
+" pp        2 |
+" 2p        1 |
+" 1p        |/
+" p         0
+" 0
+"
+" Data sample:
+"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':[]}]}
+"
+" Convert self.tree -> self.asciitree
+function! s:undotree.Render()
+    " We gonna modify self.tree so we'd better make a copy first.
+    " Can not make a copy because variable nested too deep, gosh.. okay,
+    " fine..
+    " let tree = deepcopy(self.tree)
+    let tree = self.tree
+    let slots = [tree]
+    let out = []
+    let outmeta = []
+    let seq2index = {}
+    let TYPE_E = type({})
+    let TYPE_P = type([])
+    let TYPE_X = type('x')
+    while slots != []
+        "find next node
+        let foundx = 0 " 1 if x element is found.
+        let index = 0 " Next element to be print.
+
+        " Find x element first.
+        for i in range(len(slots))
+            if type(slots[i]) == TYPE_X
+                let foundx = 1
+                let index = i
+                break
+            endif
+        endfor
+
+        " Then, find the element with minimun seq.
+        let minseq = 99999999
+        let minnode = {}
+        if foundx == 0
+            "assume undo level isn't more than this... of course
+            for i in range(len(slots))
+                if type(slots[i]) == TYPE_E
+                    if slots[i].seq < minseq
+                        let minseq = slots[i].seq
+                        let index = i
+                        let minnode = slots[i]
+                        continue
+                    endif
+                endif
+                if type(slots[i]) == TYPE_P
+                    for j in slots[i]
+                        if j.seq < minseq
+                            let minseq = j.seq
+                            let index = i
+                            let minnode = j
+                            continue
+                        endif
+                    endfor
+                endif
+            endfor
+        endif
+
+        " output.
+        let onespace = " "
+        let newline = onespace
+        let newmeta = {}
+        let node = slots[index]
+        if type(node) == TYPE_X
+            let newmeta = s:new(s:node) "invalid node.
+            if index+1 != len(slots) " not the last one, append '\'
+                for i in range(len(slots))
+                    if i < index
+                        let newline = newline.'| '
+                    endif
+                    if i > index
+                        let newline = newline.' \'
+                    endif
+                endfor
+            endif
+            call remove(slots,index)
+        endif
+        if type(node) == TYPE_E
+            let newmeta = node
+            let seq2index[node.seq]=len(out)
+            for i in range(len(slots))
+                if index == i
+                    let newline = newline.g:undotree_TreeNodeShape.' '
+                else
+                    let newline = newline.'| '
+                endif
+            endfor
+            let newline = newline.'   '.(node.seq).'    '.
+                        \'('.s:gettime(node.time).')'
+            " update the printed slot to its child.
+            if empty(node.p)
+                let slots[index] = 'x'
+            endif
+            if len(node.p) == 1 "only one child.
+                let slots[index] = node.p[0]
+            endif
+            if len(node.p) > 1 "insert p node
+                let slots[index] = node.p
+            endif
+            let node.p = [] "cut reference.
+        endif
+        if type(node) == TYPE_P
+            let newmeta = s:new(s:node) "invalid node.
+            for k in range(len(slots))
+                if k < index
+                    let newline = newline."| "
+                endif
+                if k == index
+                    let newline = newline."|/ "
+                endif
+                if k > index
+                    let newline = newline."/ "
+                endif
+            endfor
+            call remove(slots,index)
+            if len(node) == 2
+                if node[0].seq > node[1].seq
+                    call insert(slots,node[1],index)
+                    call insert(slots,node[0],index)
+                else
+                    call insert(slots,node[0],index)
+                    call insert(slots,node[1],index)
+                endif
+            endif
+            " split P to E+P if elements in p > 2
+            if len(node) > 2
+                call remove(node,index(node,minnode))
+                call insert(slots,minnode,index)
+                call insert(slots,node,index)
+            endif
+        endif
+        unlet node
+        if newline != onespace
+            let newline = substitute(newline,'\s*$','','g') "remove trailing space.
+            call insert(out,newline,0)
+            call insert(outmeta,newmeta,0)
+        endif
+    endwhile
+    let self.asciitree = out
+    let self.asciimeta = outmeta
+    " revert index.
+    let totallen = len(out)
+    for i in keys(seq2index)
+        let seq2index[i] = totallen - 1 - seq2index[i]
+    endfor
+    let self.seq2index = seq2index
+endfunction
+
+"=================================================
+"diff panel
+let s:diffpanel = s:new(s:panel)
+
+function! s:diffpanel.Update(seq,targetBufnr,targetid)
+    call s:log('diffpanel.Update(),seq:'.a:seq.' bufname:'.bufname(a:targetBufnr))
+    if !self.diffexecutable
+        return
+    endif
+    let diffresult = []
+    let self.changes.add = 0
+    let self.changes.del = 0
+
+    if a:seq == 0
+        let diffresult = []
+    else
+        if has_key(self.cache,a:targetBufnr.'_'.a:seq)
+            call s:log("diff cache hit.")
+            let diffresult = self.cache[a:targetBufnr.'_'.a:seq]
+        else
+            let ei_bak = &eventignore
+            set eventignore=all
+            let targetWinnr = -1
+
+            " Double check the target winnr and bufnr
+            for winnr in range(1, winnr('$')) "winnr starts from 1
+                if (getwinvar(winnr,'undotree_id') == a:targetid)
+                            \&& winbufnr(winnr) == a:targetBufnr
+                    let targetWinnr = winnr
+                endif
+            endfor
+            if targetWinnr == -1
+                return
+            endif
+            call s:exec_silent(targetWinnr." wincmd w")
+
+            " remember and restore cursor and window position.
+            let savedview = winsaveview()
+
+            let new = getbufline(a:targetBufnr,'^','$')
+            silent undo
+            let old = getbufline(a:targetBufnr,'^','$')
+            silent redo
+
+            call winrestview(savedview)
+
+            " diff files.
+            let tempfile1 = tempname()
+            let tempfile2 = tempname()
+            if writefile(old,tempfile1) == -1
+                echoerr "Can not write to temp file:".tempfile1
+            endif
+            if writefile(new,tempfile2) == -1
+                echoerr "Can not write to temp file:".tempfile2
+            endif
+            let diffresult = split(system(g:undotree_DiffCommand.' '.tempfile1.' '.tempfile2),"\n")
+            call s:log("diffresult: ".string(diffresult))
+            if delete(tempfile1) != 0
+                echoerr "Can not delete temp file:".tempfile1
+            endif
+            if delete(tempfile2) != 0
+                echoerr "Can not delete temp file:".tempfile2
+            endif
+            let &eventignore = ei_bak
+            "Update cache
+            let self.cache[a:targetBufnr.'_'.a:seq] = diffresult
+        endif
+    endif
+
+    call self.ParseDiff(diffresult)
+
+    call self.SetFocus()
+
+    setlocal modifiable
+    call s:exec('1,$ d _')
+
+    call append(0,diffresult)
+    call append(0,'- seq: '.a:seq.' -')
+
+    "remove the last empty line
+    if getline("$") == ""
+        call s:exec('$d _')
+    endif
+    call s:exec('norm! gg') "move cursor to line 1.
+    setlocal nomodifiable
+    call t:undotree.SetFocus()
+endfunction
+
+function! s:diffpanel.ParseDiff(diffresult)
+    " set target focus first.
+    call t:undotree.SetTargetFocus()
+
+    if empty(a:diffresult)
+        return
+    endif
+    " clear previous highlighted syntax
+    " matchadd associates with windows.
+    if exists("w:undotree_diffmatches")
+        for i in w:undotree_diffmatches
+            call matchdelete(i)
+        endfor
+    endif
+    let w:undotree_diffmatches = []
+    let lineNr = 0
+    for line in a:diffresult
+        let matchnum = matchstr(line,'^[0-9,\,]*[ac]\zs\d*\ze')
+        if !empty(matchnum)
+            let lineNr = str2nr(matchnum)
+            let matchwhat = matchstr(line,'^[0-9,\,]*\zs[ac]\ze\d*')
+            continue
+        endif
+        if matchstr(line,'^<.*$') != ''
+            let self.changes.del += 1
+        endif
+        let matchtext = matchstr(line,'^>\zs .*$')
+        if empty(matchtext)
+            continue
+        endif
+        let self.changes.add += 1
+        if g:undotree_HighlightChangedText
+            if matchtext != ' '
+                let matchtext = '\%'.lineNr.'l\V'.escape(matchtext[1:],'"\') "remove beginning space.
+                call s:log("matchadd(".matchwhat.") ->  ".matchtext)
+                call add(w:undotree_diffmatches,matchadd((matchwhat ==# 'a' ? g:undotree_HighlightSyntaxAdd : g:undotree_HighlightSyntaxChange),matchtext))
+            endif
+        endif
+
+        let lineNr = lineNr+1
+    endfor
+endfunction
+
+function! s:diffpanel.GetStatusLine()
+    let max = winwidth(0) - 4
+    let sum = self.changes.add + self.changes.del
+    if sum > max
+        let add = self.changes.add * max / sum + 1
+        let del = self.changes.del * max / sum + 1
+    else
+        let add = self.changes.add
+        let del = self.changes.del
+    endif
+    return string(sum).' '.repeat('+',add).repeat('-',del)
+endfunction
+
+function! s:diffpanel.Init()
+    let self.bufname = "diffpanel_".s:getUniqueID()
+    let self.cache = {}
+    let self.changes = {'add':0, 'del':0}
+    let self.diffexecutable = executable('diff')
+    if !self.diffexecutable
+        echoerr '"diff" is not executable.'
+    endif
+endfunction
+
+function! s:diffpanel.Toggle()
+    call s:log(self.bufname." Toggle()")
+    if self.IsVisible()
+        call self.Hide()
+    else
+        call self.Show()
+    endif
+endfunction
+
+function! s:diffpanel.Show()
+    call s:log("diffpanel.Show()")
+    if self.IsVisible()
+        return
+    endif
+    " Create diffpanel window.
+    call t:undotree.SetFocus() "can not exist without undotree
+    " remember and restore cursor and window position.
+    let savedview = winsaveview()
+
+    let ei_bak= &eventignore
+    set eventignore=all
+
+    if g:undotree_WindowLayout == 1 || g:undotree_WindowLayout == 3
+        let cmd = 'belowright '.g:undotree_DiffpanelHeight.'new '.self.bufname
+    else
+        let cmd = 'botright '.g:undotree_DiffpanelHeight.'new '.self.bufname
+    endif
+    call s:exec_silent(cmd)
+
+    setlocal winfixwidth
+    setlocal winfixheight
+    setlocal noswapfile
+    setlocal buftype=nowrite
+    setlocal bufhidden=delete
+    setlocal nowrap
+    setlocal nobuflisted
+    setlocal nospell
+    setlocal nonumber
+    setlocal nocursorline
+    setlocal nomodifiable
+    setlocal statusline=%!t:diffpanel.GetStatusLine()
+
+    let &eventignore = ei_bak
+
+    " syntax need filetype autocommand
+    setfiletype diff
+    setlocal foldcolumn=0
+    setlocal nofoldenable
+
+    call self.BindAu()
+    call t:undotree.SetFocus()
+    call winrestview(savedview)
+endfunction
+
+function! s:diffpanel.BindAu()
+    " Auto exit if it's the last window or undotree closed.
+    augroup Undotree_Diff
+        au!
+        au BufEnter <buffer> call s:exitIfLast()
+        au BufEnter <buffer> if !t:undotree.IsVisible()
+                    \|call t:diffpanel.Hide() |endif
+    augroup end
+endfunction
+
+function! s:diffpanel.CleanUpHighlight()
+    call s:log("CleanUpHighlight()")
+    " save current position
+    let curwinnr = winnr()
+    let savedview = winsaveview()
+
+    " clear w:undotree_diffmatches in all windows.
+    let winnum = winnr('$')
+    for i in range(1,winnum)
+        call s:exec_silent("norm! ".i."\<c-w>\<c-w>")
+        if exists("w:undotree_diffmatches")
+            for j in w:undotree_diffmatches
+                call matchdelete(j)
+            endfor
+            let w:undotree_diffmatches = []
+        endif
+    endfor
+
+    "restore position
+    call s:exec_silent("norm! ".curwinnr."\<c-w>\<c-w>")
+    call winrestview(savedview)
+endfunction
+
+function! s:diffpanel.Hide()
+    call s:log(self.bufname." Hide()")
+    if !self.IsVisible()
+        return
+    endif
+    call self.SetFocus()
+    call s:exec("quit")
+    call self.CleanUpHighlight()
+endfunction
+
+"=================================================
+" It will set the target of undotree window to the current editing buffer.
+function! s:undotreeAction(action)
+    call s:log("undotreeAction()")
+    if !exists('t:undotree')
+        echoerr "Fatal: t:undotree does not exists!"
+        return
+    endif
+    call t:undotree.Action(a:action)
+endfunction
+
+function! s:exitIfLast()
+    let num = 0
+    if t:undotree.IsVisible()
+        let num = num + 1
+    endif
+    if t:diffpanel.IsVisible()
+        let num = num + 1
+    endif
+    if winnr('$') == num
+        call t:undotree.Hide()
+        call t:diffpanel.Hide()
+    endif
+endfunction
+
+"=================================================
+"called outside undotree window
+function! UndotreeUpdate()
+    if !exists('t:undotree')
+        return
+    endif
+    if !exists('w:undotree_id')
+        let w:undotree_id = 'id_'.s:getUniqueID()
+        call s:log("Unique window id assigned: ".w:undotree_id)
+    endif
+    " assume window layout won't change during updating.
+    let thiswinnr = winnr()
+    call t:undotree.Update()
+    " focus moved
+    if winnr() != thiswinnr
+        call s:exec("norm! ".thiswinnr."\<c-w>\<c-w>")
+    endif
+endfunction
+
+function! UndotreeToggle()
+    call s:log(">>> UndotreeToggle()")
+    if !exists('w:undotree_id')
+        let w:undotree_id = 'id_'.s:getUniqueID()
+        call s:log("Unique window id assigned: ".w:undotree_id)
+    endif
+
+    if !exists('t:undotree')
+        let t:undotree = s:new(s:undotree)
+        let t:diffpanel = s:new(s:diffpanel)
+    endif
+    call t:undotree.Toggle()
+    call s:log("<<< UndotreeToggle() leave")
+endfunction
+
+function! UndotreeIsVisible()
+    return (exists('t:undotree') && t:undotree.IsVisible())
+endfunction
+
+function! UndotreeHide()
+    if UndotreeIsVisible()
+        call UndotreeToggle()
+    endif
+endfunction
+
+function! UndotreeShow()
+    if ! UndotreeIsVisible()
+        call UndotreeToggle()
+    else
+        call t:undotree.SetFocus()
+    endif
+endfunction
+
+function! UndotreeFocus()
+    if UndotreeIsVisible()
+        call t:undotree.SetFocus()
+    endif
+endfunction
+
+
+"let s:auEvents = "InsertEnter,InsertLeave,WinEnter,WinLeave,CursorMoved"
+let s:auEvents = "BufEnter,InsertLeave,CursorMoved,BufWritePost"
+augroup Undotree
+    exec "au! ".s:auEvents." * call UndotreeUpdate()"
+augroup END
+
+"=================================================
+" User commands.
+command! -n=0 -bar UndotreeToggle   :call UndotreeToggle()
+command! -n=0 -bar UndotreeHide     :call UndotreeHide()
+command! -n=0 -bar UndotreeShow     :call UndotreeShow()
+command! -n=0 -bar UndotreeFocus    :call UndotreeFocus()
+
+" vim: set et fdm=marker sts=4 sw=4:
diff --git a/.vim/syntax/undotree.vim b/.vim/syntax/undotree.vim
new file mode 100644 (file)
index 0000000..a900e6b
--- /dev/null
@@ -0,0 +1,37 @@
+"=================================================
+" File: undotree.vim
+" Description: undotree syntax
+" Author: Ming Bai <mbbill@gmail.com>
+" License: BSD
+
+syn match UndotreeNode ' \zs\*\ze '
+syn match UndotreeNodeCurrent '\zs\*\ze.*>\d\+<'
+syn match UndotreeTimeStamp '(.*)$'
+syn match UndotreeFirstNode 'Original'
+syn match UndotreeBranch '[|/\\]'
+syn match UndotreeSeq ' \zs\d\+\ze '
+syn match UndotreeCurrent '>\d\+<'
+syn match UndotreeNext '{\d\+}'
+syn match UndotreeHead '\[\d\+]'
+syn match UndotreeHelp '^".*$' contains=UndotreeHelpKey,UndotreeHelpTitle
+syn match UndotreeHelpKey '^" \zs.\{-}\ze:' contained
+syn match UndotreeHelpTitle '===.*===' contained
+syn match UndotreeSavedSmall ' \zss\ze '
+syn match UndotreeSavedBig ' \zsS\ze '
+
+hi def link UndotreeNode Question
+hi def link UndotreeNodeCurrent Statement
+hi def link UndotreeTimeStamp Function
+hi def link UndotreeFirstNode Function
+hi def link UndotreeBranch Constant
+hi def link UndotreeSeq Comment
+hi def link UndotreeCurrent Statement
+hi def link UndotreeNext Type
+hi def link UndotreeHead Identifier
+hi def link UndotreeHelp Comment
+hi def link UndotreeHelpKey Function
+hi def link UndotreeHelpTitle Type
+hi def link UndotreeSavedSmall WarningMsg
+hi def link UndotreeSavedBig MatchParen
+
+" vim: set et fdm=marker sts=4 sw=4: