Skip to content

Commit

Permalink
Version 2
Browse files Browse the repository at this point in the history
Added some new features: hotkey support. handle undo levels, auto refresh, and so on.
  • Loading branch information
mbbill authored and vim-scripts committed Sep 2, 2012
1 parent f196882 commit de6358f
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 30 deletions.
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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.
Expand All @@ -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/'
Expand All @@ -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>`
187 changes: 162 additions & 25 deletions plugin/undotree.vim
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -20,7 +20,7 @@ endif

" TODO remember split size.
if !exists('g:undotreeSplitSize')
let g:undotreeSplitSize = 24
let g:undotreeSplitSize = 28
endif


Expand All @@ -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)
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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).'] '.
Expand Down Expand Up @@ -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
Expand All @@ -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

"=================================================
Expand All @@ -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:
2 changes: 2 additions & 0 deletions syntax/undotree.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'

Expand Down

0 comments on commit de6358f

Please sign in to comment.