From 258f3a24443b7ee061512fc1d749a37562a05dc6 Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Tue, 9 Feb 2016 17:13:57 +0000 Subject: [PATCH] Undotree plugin. --- .vim/doc/undotree.txt | 291 ++++++++++ .vim/plugin/undotree.vim | 1377 ++++++++++++++++++++++++++++++++++++++++++++++ .vim/syntax/undotree.vim | 37 ++ 3 files changed, 1705 insertions(+) create mode 100644 .vim/doc/undotree.txt create mode 100644 .vim/plugin/undotree.vim create mode 100644 .vim/syntax/undotree.vim diff --git a/.vim/doc/undotree.txt b/.vim/doc/undotree.txt new file mode 100644 index 0000000..3073723 --- /dev/null +++ b/.vim/doc/undotree.txt @@ -0,0 +1,291 @@ +*undotree.txt* Display your undo history in a graph + +Author: Ming Bai +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 :UndotreeToggle +< +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|, ||, |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 J UndotreeGoNextState + nmap K 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 J UndotreeGoNextState + nmap K UndotreeGoPreviousState + endfunc +< +List of the commands available for redefinition. +> + UndotreeHelp + UndotreeClose + UndotreeFocusTarget + UndotreeClearHistory + UndotreeTimestampToggle + UndotreeDiffToggle + UndotreeGoNextState + UndotreeGoPreviousState + UndotreeGoNextSaved + UndotreeGoPreviousSaved + UndotreeRedo + UndotreeUndo + UndotreeEnter + 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 index 0000000..51d9b2c --- /dev/null +++ b/.vim/plugin/undotree.vim @@ -0,0 +1,1377 @@ +"================================================= +" File: undotree.vim +" Description: Manage your undo history in a graph. +" Author: Ming Bai +" 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 J +" map 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','','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','','Redo']] +let s:keymap += [['Undo','u','Undo']] +let s:keymap += [['Enter','<2-LeftMouse>','Revert to current']] +let s:keymap += [['Enter','','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."\\") +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 = ' ' + else + let map_options = '' + endif + let map_options = map_options.' ' + for i in s:keymap + silent exec 'nmap '.map_options.i[1].' Undotree'.i[0] + silent exec 'nnoremap '.map_options.'Undotree'.i[0] + \ .' :call undotreeAction("'.i[0].'")' + 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 call s:exitIfLast() + au BufEnter,BufLeave if exists('t:undotree') | + \let t:undotree.width = winwidth(winnr()) | endif + au BufWinLeave 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 \\") + 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."\\") + 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 call s:exitIfLast() + au BufEnter 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."\\") + 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."\\") + 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."\\") + 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 index 0000000..a900e6b --- /dev/null +++ b/.vim/syntax/undotree.vim @@ -0,0 +1,37 @@ +"================================================= +" File: undotree.vim +" Description: undotree syntax +" Author: Ming Bai +" 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: -- 2.7.4