Permalink
Browse files

Version 2

Added some new features: hotkey support. handle undo levels, auto refresh, and so on.
  • Loading branch information...
1 parent f196882 commit de6358fd4fe68d169082aebbdb35b695357b6c83 @mbbill mbbill committed with Aug 19, 2012
Showing with 172 additions and 30 deletions.
  1. +8 −5 README.md
  2. +162 −25 plugin/undotree.vim
  3. +2 −0 syntax/undotree.vim
View
@@ -1,5 +1,4 @@
-### [Link to Vim.org]
-(....)
+### [Link to Vim.org](http://www.vim.org/scripts/script.php?script_id=4177)
### Description
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.
@@ -17,8 +16,10 @@ Now this plug-in will free you from those commands and bring back the power of u
1. TODO: Hotkey support.
1. TODO: Diff support.
+### [Download](https://github.com/mbbill/undotree/tags)
+
### Install
- 1. Unpack all scripts into *plugin* directory and that's all. There is no additional dependency.
+ 1. Unpack all scripts into *plugin* directory and that's all. This script is written purely in Vim script with no additional dependency.
### Usage
1. 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.
@@ -28,7 +29,9 @@ Now this plug-in will free you from those commands and bring back the power of u
1. Then you can try to do some modification, and the undo tree will automatically updated afterwards.
1. There are a bunch of hotkeys provided by vim to switch between the changes in history, like `u`, `<ctrl-r>`, `g+`, `g-` as well as the `:earlier` and `:later` commands.
1. 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, add the following line to your *vimrc* in order to make them stored together.
+ * 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.
+
+Add the following line to your *vimrc* in order to make them stored together.
if has("persistent_undo")
set undodir = '/path/to/what/you/want/'
@@ -46,4 +49,4 @@ Now this plug-in will free you from those commands and bring back the power of u
**BSD**
### Author
-Ming Bai <mbbill AT gmail DOT COM>
+Ming Bai `<mbbill AT gmail DOT COM>`
View
@@ -1,6 +1,6 @@
"=================================================
" File: undotree.vim
-" Description: Visualize your undo history.
+" Description: Manage your undo history in a graph.
" Author: Ming Bai <mbbill@gmail.com>
" License: BSD
@@ -20,7 +20,7 @@ endif
" TODO remember split size.
if !exists('g:undotreeSplitSize')
- let g:undotreeSplitSize = 24
+ let g:undotreeSplitSize = 28
endif
@@ -29,14 +29,15 @@ endif
let s:undobufNr = 0
" Keymap
-let s:keymap = {}
-let s:keymap['<cr>']='enter'
-let s:keymap['J']='godown'
-let s:keymap['K']='goup'
-let s:keymap['u']='undo'
-let s:keymap['<c-r>']='redo'
-let s:keymap['<c>']='clear'
-
+let s:keymap = []
+" action, key, help.
+let s:keymap += [['Enter','<cr>','Revert to current node']]
+let s:keymap += [['Enter','<2-LeftMouse>','Revert to current node']]
+let s:keymap += [['Godown','J','Revert to previous node']]
+let s:keymap += [['Goup','K','Revert to next node']]
+let s:keymap += [['Undo','u','Undo']]
+let s:keymap += [['Redo','<c-r>','Redo']]
+let s:keymap += [['Help','?','Toggle quick help']]
function! s:new(obj)
let newobj = deepcopy(a:obj)
@@ -70,15 +71,73 @@ function! s:undotree.Init()
let self.rawtree = {} "data passed from undotree()
let self.tree = {} "data converted to internal format.
let self.nodelist = {} "stores an ordered list of items points to self.tree
- let self.current = -1 "current node is the latest.
+ let self.currentseq = -1 "current node is the latest.
let self.asciitree = [] "output data.
let self.asciimeta = [] "meta data behind ascii tree.
+ let self.currentIndex = -1 "line of the current node in ascii tree.
+ let self.showHelp = 0
" Increase to make it unique.
let s:undobufNr = s:undobufNr + 1
endfunction
function! s:undotree.BindKey()
- silent! exec "nnoremap <buffer> "."<cr>"." :UndotreeAction "."
+ for i in s:keymap
+ silent! exec 'nnoremap <silent> <buffer> '.i[1].' :UndotreeAction '.i[0].'<cr>'
+ endfor
+endfunction
+
+function! s:undotree.Action(action)
+ if !self.IsVisible()
+ "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
+
+function! s:undotree.ActionHelp()
+ let self.showHelp = !self.showHelp
+ call self.Draw()
+ call self.SetFocus() " keep focus.
+endfunction
+
+" Helper function, do action in target window.
+function! s:undotree.ActionInTarget(cmd)
+ if !self.SetTargetFocus()
+ return
+ endif
+ silent! exec a:cmd
+ call self.SetFocus()
+endfunction
+
+function! s:undotree.ActionEnter()
+ let index = self.Screen2Index(line('.'))
+ if index < 0
+ return
+ endif
+ if (self.asciimeta[index].seq) == -1
+ 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.ActionGodown()
+ call self.ActionInTarget('earlier')
+endfunction
+
+function! s:undotree.ActionGoup()
+ call self.ActionInTarget('later')
endfunction
function! s:undotree.IsVisible()
@@ -89,13 +148,34 @@ function! s:undotree.IsVisible()
endif
endfunction
+function! s:undotree.IsTargetVisible()
+ if bufwinnr(self.targetBufname) != -1
+ return 1
+ else
+ return 0
+ endif
+endfunction
+
function! s:undotree.SetFocus()
let winnr = bufwinnr(self.bufname)
if winnr == -1
- echoerr "Fatal: can not create window!"
+ echoerr "Fatal: undotree window does not exist!"
+ return
+ else
+ exec winnr . " wincmd w"
return
endif
- exec winnr . " wincmd w"
+endfunction
+
+" May fail due to target window closed.
+function! s:undotree.SetTargetFocus()
+ let winnr = bufwinnr(self.targetBufname)
+ if winnr == -1
+ return 0
+ else
+ exec winnr . " wincmd w"
+ return 1
+ endif
endfunction
function! s:undotree.RestoreFocus()
@@ -124,7 +204,7 @@ function! s:undotree.Show()
setlocal nobuflisted
setlocal nospell
setlocal nonumber
- "setlocal cursorline
+ setlocal cursorline
setlocal nomodifiable
setfiletype undotree
call s:undotree.BindKey()
@@ -155,20 +235,71 @@ function! s:undotree.Update(bufname, rawtree)
endif
let self.targetBufname = a:bufname
let self.rawtree = a:rawtree
- let self.current = -1
+ let self.currentseq = -1
let self.nodelist = {}
+ let self.currentIndex = -1
call self.ConvertInput()
call self.Render()
call self.Draw()
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,'" ------------------')
+ call append(0,'" undotree quickhelp')
+ else
+ call append(0,'" Press ? for help.')
+ 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
+ let lineNr = a:index + len(s:keymap) + 3 + 1
+ else
+ let lineNr = a:index + 2 + 1
+ 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) - 3 - 1
+ else
+ let index = a:line - 2 - 1
+ endif
+ return index
+endfunction
+
function! s:undotree.Draw()
call self.SetFocus()
+
+ " remember the current cursor position.
+ let linePos = line('.') "Line number of cursor
+ normal! H
+ let topPos = line('.') "Line number of the first line in screen.
+
setlocal modifiable
silent normal! ggdG
call append(0,self.asciitree)
+
+ call self.AppendHelp()
+
"remove the last empty line
silent normal! Gdd
+
+ " restore previous cursor position.
+ exec "normal! " . topPos . "G"
+ normal! zt
+ exec "normal! " . linePos . "G"
+
+ exec "normal! " . self.Index2Screen(self.currentIndex) . "G"
setlocal nomodifiable
call self.RestoreFocus()
endfunction
@@ -205,7 +336,7 @@ function! s:undotree._parseNode(in,out)
let newnode.curhead = i.curhead
let curnode.curpos = 1
" See 'Note' below.
- let self.current = curnode.seq
+ let self.currentseq = curnode.seq
endif
"endif
let self.nodelist[newnode.seq] = newnode
@@ -232,9 +363,9 @@ function! s:undotree.ConvertInput()
" 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.current == -1
- let self.current = self.rawtree.seq_cur
- let self.nodelist[self.current].curpos = 1
+ if self.currentseq == -1
+ let self.currentseq = self.rawtree.seq_cur
+ let self.nodelist[self.currentseq].curpos = 1
endif
endfunction
@@ -357,6 +488,7 @@ function! s:undotree.Render()
if node.curpos
let newline = newline.'>'.(node.seq).'< '.
\s:gettime(node.time)
+ let self.currentIndex = len(out) + 1 "index from zero.
else
if node.newhead
let newline = newline.'['.(node.seq).'] '.
@@ -405,7 +537,7 @@ function! s:undotree.Render()
if len(node) > 2
call insert(slots,node[0],index)
call remove(node,0)
- call insert(slots,node,index)
+ call insert(slots,node,index+1)
endif
endif
unlet node
@@ -416,6 +548,7 @@ function! s:undotree.Render()
endwhile
let self.asciitree = out
let self.asciimeta = outmeta
+ let self.currentIndex = len(out) - self.currentIndex
endfunction
"=================================================
@@ -440,17 +573,21 @@ function! s:undotreeToggle()
endfunction
function! s:undotreeAction(action)
- echo a:action
+ if type(gettabvar(tabpagenr(),'undotree')) != type(s:undotree)
+ echoerr "Fatal: t:undotree does not exists!"
+ return
+ endif
+ call t:undotree.Action(a:action)
endfunction
-" internal commands, args:linenr, action
+" Internal commands, args:linenr, action
command! -n=1 -bar UndotreeAction :call s:undotreeAction(<f-args>)
command! -n=0 -bar UndotreeUpdate :call s:undotreeUpdate()
+" User commands.
command! -n=0 -bar UndotreeToggle :call s:undotreeToggle()
-" need a timer to reduce cpu consumption.
-autocmd InsertEnter,InsertLeave,WinEnter,WinLeave,CursorHold,CursorHoldI,CursorMoved * call s:undotreeUpdate()
+autocmd InsertEnter,InsertLeave,WinEnter,WinLeave,CursorMoved * call s:undotreeUpdate()
" vim: set et fdm=marker sts=4 sw=4:
View
@@ -17,6 +17,7 @@ syn match UndotreeSeq ' \zs\d\+\ze '
syn match UndotreeCurrent '>\d\+<'
syn match UndotreeNext '{\d\+}'
syn match UndotreeHead '\[\d\+]'
+syn match UndotreeHelp '^".*$'
hi link UndotreeNode Question
hi link UndotreeNodeCurrent CursorLineNr
@@ -27,6 +28,7 @@ hi link UndotreeSeq Comment
hi link UndotreeCurrent CursorLineNr
hi link UndotreeNext Type
hi link UndotreeHead Identifier
+hi link UndotreeHelp Comment
let b:undotree_syntax = 'undotree'

0 comments on commit de6358f

Please sign in to comment.