1
0
Эх сурвалжийг харах

nvim tweaks, gundo -> mundo

raylu 9 жил өмнө
parent
commit
4df897f224

+ 1 - 0
.gitignore

@@ -1 +1,2 @@
 vim/.netrwhist
 vim/.netrwhist
+vim/autoload/mundo/__pycache__/

+ 0 - 577
vim/autoload/gundo.py

@@ -1,577 +0,0 @@
-# ============================================================================
-# File:        gundo.py
-# Description: vim global plugin to visualize your undo tree
-# Maintainer:  Steve Losh <steve@stevelosh.com>
-# License:     GPLv2+ -- look it up.
-# Notes:       Much of this code was thiefed from Mercurial, and the rest was
-#              heavily inspired by scratch.vim and histwin.vim.
-#
-# ============================================================================
-
-import difflib
-import itertools
-import sys
-import time
-import vim
-
-
-# Mercurial's graphlog code --------------------------------------------------------
-def asciiedges(seen, rev, parents):
-    """adds edge info to changelog DAG walk suitable for ascii()"""
-    if rev not in seen:
-        seen.append(rev)
-    nodeidx = seen.index(rev)
-
-    knownparents = []
-    newparents = []
-    for parent in parents:
-        if parent in seen:
-            knownparents.append(parent)
-        else:
-            newparents.append(parent)
-
-    ncols = len(seen)
-    seen[nodeidx:nodeidx + 1] = newparents
-    edges = [(nodeidx, seen.index(p)) for p in knownparents]
-
-    if len(newparents) > 0:
-        edges.append((nodeidx, nodeidx))
-    if len(newparents) > 1:
-        edges.append((nodeidx, nodeidx + 1))
-
-    nmorecols = len(seen) - ncols
-    return nodeidx, edges, ncols, nmorecols
-
-def get_nodeline_edges_tail(
-        node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail):
-    if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0:
-        # Still going in the same non-vertical direction.
-        if n_columns_diff == -1:
-            start = max(node_index + 1, p_node_index)
-            tail = ["|", " "] * (start - node_index - 1)
-            tail.extend(["/", " "] * (n_columns - start))
-            return tail
-        else:
-            return ["\\", " "] * (n_columns - node_index - 1)
-    else:
-        return ["|", " "] * (n_columns - node_index - 1)
-
-def draw_edges(edges, nodeline, interline):
-    for (start, end) in edges:
-        if start == end + 1:
-            interline[2 * end + 1] = "/"
-        elif start == end - 1:
-            interline[2 * start + 1] = "\\"
-        elif start == end:
-            interline[2 * start] = "|"
-        else:
-            nodeline[2 * end] = "+"
-            if start > end:
-                (start, end) = (end, start)
-            for i in range(2 * start + 1, 2 * end):
-                if nodeline[i] != "+":
-                    nodeline[i] = "-"
-
-def fix_long_right_edges(edges):
-    for (i, (start, end)) in enumerate(edges):
-        if end > start:
-            edges[i] = (start, end + 1)
-
-def ascii(buf, state, type, char, text, coldata):
-    """prints an ASCII graph of the DAG
-
-    takes the following arguments (one call per node in the graph):
-
-      - Somewhere to keep the needed state in (init to asciistate())
-      - Column of the current node in the set of ongoing edges.
-      - Type indicator of node data == ASCIIDATA.
-      - Payload: (char, lines):
-        - Character to use as node's symbol.
-        - List of lines to display as the node's text.
-      - Edges; a list of (col, next_col) indicating the edges between
-        the current node and its parents.
-      - Number of columns (ongoing edges) in the current revision.
-      - The difference between the number of columns (ongoing edges)
-        in the next revision and the number of columns (ongoing edges)
-        in the current revision. That is: -1 means one column removed;
-        0 means no columns added or removed; 1 means one column added.
-    """
-
-    idx, edges, ncols, coldiff = coldata
-    assert -2 < coldiff < 2
-    if coldiff == -1:
-        # Transform
-        #
-        #     | | |        | | |
-        #     o | |  into  o---+
-        #     |X /         |/ /
-        #     | |          | |
-        fix_long_right_edges(edges)
-
-    # add_padding_line says whether to rewrite
-    #
-    #     | | | |        | | | |
-    #     | o---+  into  | o---+
-    #     |  / /         |   | |  # <--- padding line
-    #     o | |          |  / /
-    #                    o | |
-    add_padding_line = (len(text) > 2 and coldiff == -1 and
-                        [x for (x, y) in edges if x + 1 < y])
-
-    # fix_nodeline_tail says whether to rewrite
-    #
-    #     | | o | |        | | o | |
-    #     | | |/ /         | | |/ /
-    #     | o | |    into  | o / /   # <--- fixed nodeline tail
-    #     | |/ /           | |/ /
-    #     o | |            o | |
-    fix_nodeline_tail = len(text) <= 2 and not add_padding_line
-
-    # nodeline is the line containing the node character (typically o)
-    nodeline = ["|", " "] * idx
-    nodeline.extend([char, " "])
-
-    nodeline.extend(
-        get_nodeline_edges_tail(idx, state[1], ncols, coldiff,
-                                state[0], fix_nodeline_tail))
-
-    # shift_interline is the line containing the non-vertical
-    # edges between this entry and the next
-    shift_interline = ["|", " "] * idx
-    if coldiff == -1:
-        n_spaces = 1
-        edge_ch = "/"
-    elif coldiff == 0:
-        n_spaces = 2
-        edge_ch = "|"
-    else:
-        n_spaces = 3
-        edge_ch = "\\"
-    shift_interline.extend(n_spaces * [" "])
-    shift_interline.extend([edge_ch, " "] * (ncols - idx - 1))
-
-    # draw edges from the current node to its parents
-    draw_edges(edges, nodeline, shift_interline)
-
-    # lines is the list of all graph lines to print
-    lines = [nodeline]
-    if add_padding_line:
-        lines.append(get_padding_line(idx, ncols, edges))
-    lines.append(shift_interline)
-
-    # make sure that there are as many graph lines as there are
-    # log strings
-    while len(text) < len(lines):
-        text.append("")
-    if len(lines) < len(text):
-        extra_interline = ["|", " "] * (ncols + coldiff)
-        while len(lines) < len(text):
-            lines.append(extra_interline)
-
-    # print lines
-    indentation_level = max(ncols, ncols + coldiff)
-    for (line, logstr) in zip(lines, text):
-        ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr)
-        buf.write(ln.rstrip() + '\n')
-
-    # ... and start over
-    state[0] = coldiff
-    state[1] = idx
-
-def generate(dag, edgefn, current):
-    seen, state = [], [0, 0]
-    buf = Buffer()
-    for node, parents in list(dag):
-        if node.time:
-            age_label = age(int(node.time))
-        else:
-            age_label = 'Original'
-        line = '[%s] %s' % (node.n, age_label)
-        if node.n == current:
-            char = '@'
-        else:
-            char = 'o'
-        ascii(buf, state, 'C', char, [line], edgefn(seen, node, parents))
-    return buf.b
-
-
-# Mercurial age function -----------------------------------------------------------
-agescales = [("year", 3600 * 24 * 365),
-             ("month", 3600 * 24 * 30),
-             ("week", 3600 * 24 * 7),
-             ("day", 3600 * 24),
-             ("hour", 3600),
-             ("minute", 60),
-             ("second", 1)]
-
-def age(ts):
-    '''turn a timestamp into an age string.'''
-
-    def plural(t, c):
-        if c == 1:
-            return t
-        return t + "s"
-    def fmt(t, c):
-        return "%d %s" % (c, plural(t, c))
-
-    now = time.time()
-    then = ts
-    if then > now:
-        return 'in the future'
-
-    delta = max(1, int(now - then))
-    if delta > agescales[0][1] * 2:
-        return time.strftime('%Y-%m-%d', time.gmtime(float(ts)))
-
-    for t, s in agescales:
-        n = delta // s
-        if n >= 2 or s == 1:
-            return '%s ago' % fmt(t, n)
-
-
-# Python Vim utility functions -----------------------------------------------------
-normal = lambda s: vim.command('normal %s' % s)
-
-MISSING_BUFFER = "Cannot find Gundo's target buffer (%s)"
-MISSING_WINDOW = "Cannot find window (%s) for Gundo's target buffer (%s)"
-
-def _check_sanity():
-    '''Check to make sure we're not crazy.
-
-    Does the following things:
-
-        * Make sure the target buffer still exists.
-    '''
-    b = int(vim.eval('g:gundo_target_n'))
-
-    if not vim.eval('bufloaded(%d)' % b):
-        vim.command('echo "%s"' % (MISSING_BUFFER % b))
-        return False
-
-    w = int(vim.eval('bufwinnr(%d)' % b))
-    if w == -1:
-        vim.command('echo "%s"' % (MISSING_WINDOW % (w, b)))
-        return False
-
-    return True
-
-def _goto_window_for_buffer(b):
-    w = int(vim.eval('bufwinnr(%d)' % int(b)))
-    vim.command('%dwincmd w' % w)
-
-def _goto_window_for_buffer_name(bn):
-    b = vim.eval('bufnr("%s")' % bn)
-    return _goto_window_for_buffer(b)
-
-def _undo_to(n):
-    n = int(n)
-    if n == 0:
-        vim.command('silent earlier %s' % (int(vim.eval('&undolevels')) + 1))
-    else:
-        vim.command('silent undo %d' % n)
-
-
-INLINE_HELP = '''\
-" Gundo for %s (%d)
-" j/k  - move between undo states
-" p    - preview diff of selected and current states
-" <cr> - revert to selected state
-
-'''
-
-
-# Python undo tree data structures and functions -----------------------------------
-class Buffer(object):
-    def __init__(self):
-        self.b = ''
-
-    def write(self, s):
-        self.b += s
-
-class Node(object):
-    def __init__(self, n, parent, time, curhead):
-        self.n = int(n)
-        self.parent = parent
-        self.children = []
-        self.curhead = curhead
-        self.time = time
-
-def _make_nodes(alts, nodes, parent=None):
-    p = parent
-
-    for alt in alts:
-        curhead = 'curhead' in alt
-        node = Node(n=alt['seq'], parent=p, time=alt['time'], curhead=curhead)
-        nodes.append(node)
-        if alt.get('alt'):
-            _make_nodes(alt['alt'], nodes, p)
-        p = node
-
-def make_nodes():
-    ut = vim.eval('undotree()')
-    entries = ut['entries']
-
-    root = Node(0, None, False, 0)
-    nodes = []
-    _make_nodes(entries, nodes, root)
-    nodes.append(root)
-    nmap = dict((node.n, node) for node in nodes)
-    return nodes, nmap
-
-def changenr(nodes):
-    _curhead_l = list(itertools.dropwhile(lambda n: not n.curhead, nodes))
-    if _curhead_l:
-        current = _curhead_l[0].parent.n
-    else:
-        current = int(vim.eval('changenr()'))
-    return current
-
-
-# Gundo rendering ------------------------------------------------------------------
-
-# Rendering utility functions
-def _fmt_time(t):
-    return time.strftime('%Y-%m-%d %I:%M:%S %p', time.localtime(float(t)))
-
-def _output_preview_text(lines):
-    _goto_window_for_buffer_name('__Gundo_Preview__')
-    vim.command('setlocal modifiable')
-    vim.current.buffer[:] = lines
-    vim.command('setlocal nomodifiable')
-
-def _generate_preview_diff(current, node_before, node_after):
-    _goto_window_for_buffer(vim.eval('g:gundo_target_n'))
-
-    if not node_after.n:    # we're at the original file
-        before_lines = []
-
-        _undo_to(0)
-        after_lines = vim.current.buffer[:]
-
-        before_name = 'n/a'
-        before_time = ''
-        after_name = 'Original'
-        after_time = ''
-    elif not node_before.n: # we're at a pseudo-root state
-        _undo_to(0)
-        before_lines = vim.current.buffer[:]
-
-        _undo_to(node_after.n)
-        after_lines = vim.current.buffer[:]
-
-        before_name = 'Original'
-        before_time = ''
-        after_name = node_after.n
-        after_time = _fmt_time(node_after.time)
-    else:
-        _undo_to(node_before.n)
-        before_lines = vim.current.buffer[:]
-
-        _undo_to(node_after.n)
-        after_lines = vim.current.buffer[:]
-
-        before_name = node_before.n
-        before_time = _fmt_time(node_before.time)
-        after_name = node_after.n
-        after_time = _fmt_time(node_after.time)
-
-    _undo_to(current)
-
-    return list(difflib.unified_diff(before_lines, after_lines,
-                                     before_name, after_name,
-                                     before_time, after_time))
-
-def _generate_change_preview_diff(current, node_before, node_after):
-    _goto_window_for_buffer(vim.eval('g:gundo_target_n'))
-
-    _undo_to(node_before.n)
-    before_lines = vim.current.buffer[:]
-
-    _undo_to(node_after.n)
-    after_lines = vim.current.buffer[:]
-
-    before_name = node_before.n or 'Original'
-    before_time = node_before.time and _fmt_time(node_before.time) or ''
-    after_name = node_after.n or 'Original'
-    after_time = node_after.time and _fmt_time(node_after.time) or ''
-
-    _undo_to(current)
-
-    return list(difflib.unified_diff(before_lines, after_lines,
-                                     before_name, after_name,
-                                     before_time, after_time))
-
-def GundoRenderGraph():
-    if not _check_sanity():
-        return
-
-    nodes, nmap = make_nodes()
-
-    for node in nodes:
-        node.children = [n for n in nodes if n.parent == node]
-
-    def walk_nodes(nodes):
-        for node in nodes:
-            if node.parent:
-                yield (node, [node.parent])
-            else:
-                yield (node, [])
-
-    dag = sorted(nodes, key=lambda n: int(n.n), reverse=True)
-    current = changenr(nodes)
-
-    result = generate(walk_nodes(dag), asciiedges, current).rstrip().splitlines()
-    result = [' ' + l for l in result]
-
-    target = (vim.eval('g:gundo_target_f'), int(vim.eval('g:gundo_target_n')))
-
-    if int(vim.eval('g:gundo_help')):
-        header = (INLINE_HELP % target).splitlines()
-    else:
-        header = []
-
-    vim.command('call s:GundoOpenGraph()')
-    vim.command('setlocal modifiable')
-    vim.current.buffer[:] = (header + result)
-    vim.command('setlocal nomodifiable')
-
-    i = 1
-    for line in result:
-        try:
-            line.split('[')[0].index('@')
-            i += 1
-            break
-        except ValueError:
-            pass
-        i += 1
-    vim.command('%d' % (i+len(header)-1))
-
-def GundoRenderPreview():
-    if not _check_sanity():
-        return
-
-    target_state = vim.eval('s:GundoGetTargetState()')
-
-    # Check that there's an undo state. There may not be if we're talking about
-    # a buffer with no changes yet.
-    if target_state == None:
-        _goto_window_for_buffer_name('__Gundo__')
-        return
-    else:
-        target_state = int(target_state)
-
-    _goto_window_for_buffer(vim.eval('g:gundo_target_n'))
-
-    nodes, nmap = make_nodes()
-    current = changenr(nodes)
-
-    node_after = nmap[target_state]
-    node_before = node_after.parent
-
-    vim.command('call s:GundoOpenPreview()')
-    _output_preview_text(_generate_preview_diff(current, node_before, node_after))
-
-    _goto_window_for_buffer_name('__Gundo__')
-
-def GundoRenderChangePreview():
-    if not _check_sanity():
-        return
-
-    target_state = vim.eval('s:GundoGetTargetState()')
-
-    # Check that there's an undo state. There may not be if we're talking about
-    # a buffer with no changes yet.
-    if target_state == None:
-        _goto_window_for_buffer_name('__Gundo__')
-        return
-    else:
-        target_state = int(target_state)
-
-    _goto_window_for_buffer(vim.eval('g:gundo_target_n'))
-
-    nodes, nmap = make_nodes()
-    current = changenr(nodes)
-
-    node_after = nmap[target_state]
-    node_before = nmap[current]
-
-    vim.command('call s:GundoOpenPreview()')
-    _output_preview_text(_generate_change_preview_diff(current, node_before, node_after))
-
-    _goto_window_for_buffer_name('__Gundo__')
-
-
-# Gundo undo/redo
-def GundoRevert():
-    if not _check_sanity():
-        return
-
-    target_n = int(vim.eval('s:GundoGetTargetState()'))
-    back = vim.eval('g:gundo_target_n')
-
-    _goto_window_for_buffer(back)
-    _undo_to(target_n)
-
-    vim.command('GundoRenderGraph')
-    _goto_window_for_buffer(back)
-
-    if int(vim.eval('g:gundo_close_on_revert')):
-        vim.command('GundoToggle')
-
-def GundoPlayTo():
-    if not _check_sanity():
-        return
-
-    target_n = int(vim.eval('s:GundoGetTargetState()'))
-    back = int(vim.eval('g:gundo_target_n'))
-
-    vim.command('echo "%s"' % back)
-
-    _goto_window_for_buffer(back)
-    normal('zR')
-
-    nodes, nmap = make_nodes()
-
-    start = nmap[changenr(nodes)]
-    end = nmap[target_n]
-
-    def _walk_branch(origin, dest):
-        rev = origin.n < dest.n
-
-        nodes = []
-        if origin.n > dest.n:
-            current, final = origin, dest
-        else:
-            current, final = dest, origin
-
-        while current.n >= final.n:
-            if current.n == final.n:
-                break
-            nodes.append(current)
-            current = current.parent
-        else:
-            return None
-        nodes.append(current)
-
-        if rev:
-            return reversed(nodes)
-        else:
-            return nodes
-
-    branch = _walk_branch(start, end)
-
-    if not branch:
-        vim.command('unsilent echo "No path to that node from here!"')
-        return
-
-    for node in branch:
-        _undo_to(node.n)
-        vim.command('GundoRenderGraph')
-        normal('zz')
-        _goto_window_for_buffer(back)
-        vim.command('redraw')
-        vim.command('sleep 60m')
-
-def initPythonModule():
-    if sys.version_info[:2] < (2, 4):
-        vim.command('let s:has_supported_python = 0')

+ 0 - 449
vim/autoload/gundo.vim

@@ -1,449 +0,0 @@
-" ============================================================================
-" File:        gundo.vim
-" Description: vim global plugin to visualize your undo tree
-" Maintainer:  Steve Losh <steve@stevelosh.com>
-" License:     GPLv2+ -- look it up.
-" Notes:       Much of this code was thiefed from Mercurial, and the rest was
-"              heavily inspired by scratch.vim and histwin.vim.
-"
-" ============================================================================
-
-
-"{{{ Init
-
-if v:version < '703'"{{{
-    function! s:GundoDidNotLoad()
-        echohl WarningMsg|echomsg "Gundo unavailable: requires Vim 7.3+"|echohl None
-    endfunction
-    command! -nargs=0 GundoToggle call s:GundoDidNotLoad()
-    finish
-endif"}}}
-
-if !exists('g:gundo_width')"{{{
-    let g:gundo_width = 45
-endif"}}}
-if !exists('g:gundo_preview_height')"{{{
-    let g:gundo_preview_height = 15
-endif"}}}
-if !exists('g:gundo_preview_bottom')"{{{
-    let g:gundo_preview_bottom = 0
-endif"}}}
-if !exists('g:gundo_right')"{{{
-    let g:gundo_right = 0
-endif"}}}
-if !exists('g:gundo_help')"{{{
-    let g:gundo_help = 1
-endif"}}}
-if !exists("g:gundo_map_move_older")"{{{
-    let g:gundo_map_move_older = 'j'
-endif"}}}
-if !exists("g:gundo_map_move_newer")"{{{
-    let g:gundo_map_move_newer = 'k'
-endif"}}}
-if !exists("g:gundo_close_on_revert")"{{{
-    let g:gundo_close_on_revert = 0
-endif"}}}
-if !exists("g:gundo_prefer_python3")"{{{
-    let g:gundo_prefer_python3 = 0
-endif"}}}
-
-let s:has_supported_python = 0
-if g:gundo_prefer_python3 && has('python3')"{{{
-    let s:has_supported_python = 2
-elseif has('python')"
-    let s:has_supported_python = 1
-endif
-
-if !s:has_supported_python
-    function! s:GundoDidNotLoad()
-        echohl WarningMsg|echomsg "Gundo requires Vim to be compiled with Python 2.4+"|echohl None
-    endfunction
-    command! -nargs=0 GundoToggle call s:GundoDidNotLoad()
-    finish
-endif"}}}
-
-let s:plugin_path = escape(expand('<sfile>:p:h'), '\')
-"}}}
-
-"{{{ Gundo utility functions
-
-function! s:GundoGetTargetState()"{{{
-    let target_line = matchstr(getline("."), '\v\[[0-9]+\]')
-    return matchstr(target_line, '\v[0-9]+')
-endfunction"}}}
-
-function! s:GundoGoToWindowForBufferName(name)"{{{
-    if bufwinnr(bufnr(a:name)) != -1
-        exe bufwinnr(bufnr(a:name)) . "wincmd w"
-        return 1
-    else
-        return 0
-    endif
-endfunction"}}}
-
-function! s:GundoIsVisible()"{{{
-    if bufwinnr(bufnr("__Gundo__")) != -1 || bufwinnr(bufnr("__Gundo_Preview__")) != -1
-        return 1
-    else
-        return 0
-    endif
-endfunction"}}}
-
-function! s:GundoInlineHelpLength()"{{{
-    if g:gundo_help
-        return 6
-    else
-        return 0
-    endif
-endfunction"}}}
-
-"}}}
-
-"{{{ Gundo buffer settings
-
-function! s:GundoMapGraph()"{{{
-    exec 'nnoremap <script> <silent> <buffer> ' . g:gundo_map_move_older . " :call <sid>GundoMove(1)<CR>"
-    exec 'nnoremap <script> <silent> <buffer> ' . g:gundo_map_move_newer . " :call <sid>GundoMove(-1)<CR>"
-    nnoremap <script> <silent> <buffer> <CR>          :call <sid>GundoRevert()<CR>
-    nnoremap <script> <silent> <buffer> o             :call <sid>GundoRevert()<CR>
-    nnoremap <script> <silent> <buffer> <down>        :call <sid>GundoMove(1)<CR>
-    nnoremap <script> <silent> <buffer> <up>          :call <sid>GundoMove(-1)<CR>
-    nnoremap <script> <silent> <buffer> gg            gg:call <sid>GundoMove(1)<CR>
-    nnoremap <script> <silent> <buffer> P             :call <sid>GundoPlayTo()<CR>
-    nnoremap <script> <silent> <buffer> p             :call <sid>GundoRenderChangePreview()<CR>
-    nnoremap <script> <silent> <buffer> q             :call <sid>GundoClose()<CR>
-    cabbrev  <script> <silent> <buffer> q             call <sid>GundoClose()
-    cabbrev  <script> <silent> <buffer> quit          call <sid>GundoClose()
-    nnoremap <script> <silent> <buffer> <2-LeftMouse> :call <sid>GundoMouseDoubleClick()<CR>
-endfunction"}}}
-
-function! s:GundoMapPreview()"{{{
-    nnoremap <script> <silent> <buffer> q     :call <sid>GundoClose()<CR>
-    cabbrev  <script> <silent> <buffer> q     call <sid>GundoClose()
-    cabbrev  <script> <silent> <buffer> quit  call <sid>GundoClose()
-endfunction"}}}
-
-function! s:GundoSettingsGraph()"{{{
-    setlocal buftype=nofile
-    setlocal bufhidden=hide
-    setlocal noswapfile
-    setlocal nobuflisted
-    setlocal nomodifiable
-    setlocal filetype=gundo
-    setlocal nolist
-    setlocal nonumber
-    setlocal norelativenumber
-    setlocal nowrap
-    call s:GundoSyntaxGraph()
-    call s:GundoMapGraph()
-endfunction"}}}
-
-function! s:GundoSettingsPreview()"{{{
-    setlocal buftype=nofile
-    setlocal bufhidden=hide
-    setlocal noswapfile
-    setlocal nobuflisted
-    setlocal nomodifiable
-    setlocal filetype=diff
-    setlocal nonumber
-    setlocal norelativenumber
-    setlocal nowrap
-    setlocal foldlevel=20
-    setlocal foldmethod=diff
-    call s:GundoMapPreview()
-endfunction"}}}
-
-function! s:GundoSyntaxGraph()"{{{
-    let b:current_syntax = 'gundo'
-
-    syn match GundoCurrentLocation '@'
-    syn match GundoHelp '\v^".*$'
-    syn match GundoNumberField '\v\[[0-9]+\]'
-    syn match GundoNumber '\v[0-9]+' contained containedin=GundoNumberField
-
-    hi def link GundoCurrentLocation Keyword
-    hi def link GundoHelp Comment
-    hi def link GundoNumberField Comment
-    hi def link GundoNumber Identifier
-endfunction"}}}
-
-"}}}
-
-"{{{ Gundo buffer/window management
-
-function! s:GundoResizeBuffers(backto)"{{{
-    call s:GundoGoToWindowForBufferName('__Gundo__')
-    exe "vertical resize " . g:gundo_width
-
-    call s:GundoGoToWindowForBufferName('__Gundo_Preview__')
-    exe "resize " . g:gundo_preview_height
-
-    exe a:backto . "wincmd w"
-endfunction"}}}
-
-function! s:GundoOpenGraph()"{{{
-    let existing_gundo_buffer = bufnr("__Gundo__")
-
-    if existing_gundo_buffer == -1
-        call s:GundoGoToWindowForBufferName('__Gundo_Preview__')
-        exe "new __Gundo__"
-        if g:gundo_preview_bottom
-            if g:gundo_right
-                wincmd L
-            else
-                wincmd H
-            endif
-        endif
-        call s:GundoResizeBuffers(winnr())
-    else
-        let existing_gundo_window = bufwinnr(existing_gundo_buffer)
-
-        if existing_gundo_window != -1
-            if winnr() != existing_gundo_window
-                exe existing_gundo_window . "wincmd w"
-            endif
-        else
-            call s:GundoGoToWindowForBufferName('__Gundo_Preview__')
-            if g:gundo_preview_bottom
-                if g:gundo_right
-                    exe "botright vsplit +buffer" . existing_gundo_buffer
-                else
-                    exe "topleft vsplit +buffer" . existing_gundo_buffer
-                endif
-            else
-                exe "split +buffer" . existing_gundo_buffer
-            endif
-            call s:GundoResizeBuffers(winnr())
-        endif
-    endif
-    if exists("g:gundo_tree_statusline")
-        let &l:statusline = g:gundo_tree_statusline
-    endif
-endfunction"}}}
-
-function! s:GundoOpenPreview()"{{{
-    let existing_preview_buffer = bufnr("__Gundo_Preview__")
-
-    if existing_preview_buffer == -1
-        if g:gundo_preview_bottom
-            exe "botright new __Gundo_Preview__"
-        else
-            if g:gundo_right
-                exe "botright vnew __Gundo_Preview__"
-            else
-                exe "topleft vnew __Gundo_Preview__"
-            endif
-        endif
-    else
-        let existing_preview_window = bufwinnr(existing_preview_buffer)
-
-        if existing_preview_window != -1
-            if winnr() != existing_preview_window
-                exe existing_preview_window . "wincmd w"
-            endif
-        else
-            if g:gundo_preview_bottom
-                exe "botright split +buffer" . existing_preview_buffer
-            else
-                if g:gundo_right
-                    exe "botright vsplit +buffer" . existing_preview_buffer
-                else
-                    exe "topleft vsplit +buffer" . existing_preview_buffer
-                endif
-            endif
-        endif
-    endif
-    if exists("g:gundo_preview_statusline")
-        let &l:statusline = g:gundo_preview_statusline
-    endif
-endfunction"}}}
-
-function! s:GundoClose()"{{{
-    if s:GundoGoToWindowForBufferName('__Gundo__')
-        quit
-    endif
-
-    if s:GundoGoToWindowForBufferName('__Gundo_Preview__')
-        quit
-    endif
-
-    exe bufwinnr(g:gundo_target_n) . "wincmd w"
-endfunction"}}}
-
-function! s:GundoOpen()"{{{
-    if !exists('g:gundo_py_loaded')
-        if s:has_supported_python == 2 && g:gundo_prefer_python3
-            exe 'py3file ' . s:plugin_path . '/gundo.py'
-            python3 initPythonModule()
-        else
-            exe 'pyfile ' . s:plugin_path . '/gundo.py'
-            python initPythonModule()
-        endif
-
-        if !s:has_supported_python
-            function! s:GundoDidNotLoad()
-                echohl WarningMsg|echomsg "Gundo unavailable: requires Vim 7.3+"|echohl None
-            endfunction
-            command! -nargs=0 GundoToggle call s:GundoDidNotLoad()
-            call s:GundoDidNotLoad()
-            return
-        endif"
-
-        let g:gundo_py_loaded = 1
-    endif
-
-    " Save `splitbelow` value and set it to default to avoid problems with
-    " positioning new windows.
-    let saved_splitbelow = &splitbelow
-    let &splitbelow = 0
-
-    call s:GundoOpenPreview()
-    exe bufwinnr(g:gundo_target_n) . "wincmd w"
-
-    call s:GundoRenderGraph()
-    call s:GundoRenderPreview()
-
-    " Restore `splitbelow` value.
-    let &splitbelow = saved_splitbelow
-endfunction"}}}
-
-function! s:GundoToggle()"{{{
-    if s:GundoIsVisible()
-        call s:GundoClose()
-    else
-        let g:gundo_target_n = bufnr('')
-        let g:gundo_target_f = @%
-        call s:GundoOpen()
-    endif
-endfunction"}}}
-
-function! s:GundoShow()"{{{
-    call s:GundoOpen()
-endfunction"}}}
-
-function! s:GundoHide()"{{{
-    call s:GundoClose()
-endfunction"}}}
-
-"}}}
-
-"{{{ Gundo mouse handling
-
-function! s:GundoMouseDoubleClick()"{{{
-    let start_line = getline('.')
-
-    if stridx(start_line, '[') == -1
-        return
-    else
-        call s:GundoRevert()
-    endif
-endfunction"}}}
-
-"}}}
-
-"{{{ Gundo movement
-
-function! s:GundoMove(direction) range"{{{
-    let start_line = getline('.')
-    if v:count1 == 0
-        let move_count = 1
-    else
-        let move_count = v:count1
-    endif
-    let distance = 2 * move_count
-
-    " If we're in between two nodes we move by one less to get back on track.
-    if stridx(start_line, '[') == -1
-        let distance = distance - 1
-    endif
-
-    let target_n = line('.') + (distance * a:direction)
-
-    " Bound the movement to the graph.
-    if target_n <= s:GundoInlineHelpLength() - 1
-        call cursor(s:GundoInlineHelpLength(), 0)
-    else
-        call cursor(target_n, 0)
-    endif
-
-    let line = getline('.')
-
-    " Move to the node, whether it's an @ or an o
-    let idx1 = stridx(line, '@')
-    let idx2 = stridx(line, 'o')
-    if idx1 != -1
-        call cursor(0, idx1 + 1)
-    else
-        call cursor(0, idx2 + 1)
-    endif
-
-    call s:GundoRenderPreview()
-endfunction"}}}
-
-"}}}
-
-"{{{ Gundo rendering
-
-function! s:GundoRenderGraph()"{{{
-    if s:has_supported_python == 2 && g:gundo_prefer_python3
-        python3 GundoRenderGraph()
-    else
-        python GundoRenderGraph()
-    endif
-endfunction"}}}
-
-function! s:GundoRenderPreview()"{{{
-    if s:has_supported_python == 2 && g:gundo_prefer_python3
-        python3 GundoRenderPreview()
-    else
-        python GundoRenderPreview()
-    endif
-endfunction"}}}
-
-function! s:GundoRenderChangePreview()"{{{
-    if s:has_supported_python == 2 && g:gundo_prefer_python3
-        python3 GundoRenderChangePreview()
-    else
-        python GundoRenderChangePreview()
-    endif
-endfunction"}}}
-
-"}}}
-
-"{{{ Gundo undo/redo
-
-function! s:GundoRevert()"{{{
-    if s:has_supported_python == 2 && g:gundo_prefer_python3
-        python3 GundoRevert()
-    else
-        python GundoRevert()
-    endif
-endfunction"}}}
-
-function! s:GundoPlayTo()"{{{
-    if s:has_supported_python == 2 && g:gundo_prefer_python3
-        python3 GundoPlayTo()
-    else
-        python GundoPlayTo()
-    endif
-endfunction"}}}
-
-"}}}
-
-"{{{ Misc
-
-function! gundo#GundoToggle()"{{{
-    call s:GundoToggle()
-endfunction"}}}
-
-function! gundo#GundoRenderGraph()"{{{
-    call s:GundoRenderGraph()
-endfunction"}}}
-
-augroup GundoAug
-    autocmd!
-    autocmd BufNewFile __Gundo__ call s:GundoSettingsGraph()
-    autocmd BufNewFile __Gundo_Preview__ call s:GundoSettingsPreview()
-augroup END
-
-"}}}

+ 484 - 0
vim/autoload/mundo.py

@@ -0,0 +1,484 @@
+# vim: set fdm=marker ts=4 sw=4 et:
+# ============================================================================
+# File:        mundo.py
+# Description: vim global plugin to visualize your undo tree
+# Maintainer:  Hyeon Kim <simnalamburt@gmail.com>
+# License:     GPLv2+ -- look it up.
+# Notes:       Much of this code was thieved from Mercurial, and the rest was
+#              heavily inspired by scratch.vim and histwin.vim.
+#
+# ============================================================================
+
+import re
+import sys
+import tempfile
+import vim
+
+from mundo.node import Nodes
+import mundo.util as util
+import mundo.graphlog as graphlog
+
+# Python Vim utility functions -----------------------------------------------------#{{{
+
+MISSING_BUFFER = "Cannot find Mundo's target buffer (%s)"
+MISSING_WINDOW = "Cannot find window (%s) for Mundo's target buffer (%s)"
+
+def _check_sanity():
+    '''Check to make sure we're not crazy.
+
+    Does the following things:
+
+        * Make sure the target buffer still exists.
+    '''
+    global nodesData
+    if not nodesData:
+        nodesData = Nodes()
+    b = int(vim.eval('g:mundo_target_n'))
+
+    if not vim.eval('bufloaded(%d)' % int(b)):
+        vim.command('echo "%s"' % (MISSING_BUFFER % b))
+        return False
+
+    w = int(vim.eval('bufwinnr(%d)' % int(b)))
+    if w == -1:
+        vim.command('echo "%s"' % (MISSING_WINDOW % (w, b)))
+        return False
+
+    return True
+
+INLINE_HELP = '''\
+" Mundo (%d) - Press ? for Help:
+" %s/%s  - Next/Prev undo state.
+" J/K  - Next/Prev write state.
+" i    - Toggle 'inline diff' mode.
+" /    - Find changes that match string.
+" n/N  - Next/Prev undo that matches search.
+" P    - Play current state to selected undo.
+" d    - Vert diff of undo with current state.
+" p    - Diff of selected undo and current state.
+" r    - Diff of selected undo and prior undo.
+" q    - Quit!
+" <cr> - Revert to selected state.
+
+'''
+
+#}}}
+
+nodesData = Nodes()
+
+# from profilehooks import profile
+# @profile(immediate=True)
+def MundoRenderGraph(force=False):
+    if not _check_sanity():
+        return
+
+    first_visible_line = int(vim.eval("line('w0')"))
+    last_visible_line = int(vim.eval("line('w$')"))
+
+    verbose = vim.eval('g:mundo_verbose_graph') == "1"
+    target = (int(vim.eval('g:mundo_target_n')),
+                vim.eval('g:mundo_map_move_older'),
+                vim.eval('g:mundo_map_move_newer'))
+
+    if int(vim.eval('g:mundo_help')):
+        header = (INLINE_HELP % target).splitlines()
+    else:
+        header = [(INLINE_HELP % target).splitlines()[0], '\n']
+
+    show_inline_undo = int(vim.eval("g:mundo_inline_undo")) == 1
+    mundo_last_visible_line = int(vim.eval("g:mundo_last_visible_line"))
+    mundo_first_visible_line = int(vim.eval("g:mundo_first_visible_line"))
+
+    if not force and not nodesData.is_outdated() and (
+                not show_inline_undo or 
+                (
+                    mundo_first_visible_line == first_visible_line and
+                    mundo_last_visible_line == last_visible_line
+                )
+            ):
+        return
+
+    result = graphlog.generate(
+            verbose,
+            len(header)+1,
+            first_visible_line,
+            last_visible_line,
+            show_inline_undo,
+            nodesData
+    )
+    vim.command("let g:mundo_last_visible_line=%s"%last_visible_line)
+    vim.command("let g:mundo_first_visible_line=%s"%first_visible_line)
+
+    output = []
+    # right align the dag and flip over the y axis:
+    flip_dag = int(vim.eval("g:mundo_mirror_graph")) == 1
+    dag_width = 1
+    maxwidth = int(vim.eval("g:mundo_width"))
+    for line in result:
+        if len(line[0]) > dag_width:
+            dag_width = len(line[0])
+    for line in result:
+        if flip_dag:
+            dag_line = (line[0][::-1]).replace("/","\\")
+            output.append("%*s %s"% (dag_width,dag_line,line[1]))
+        else:
+            output.append("%-*s %s"% (dag_width,line[0],line[1]))
+
+    vim.command('call s:MundoOpenGraph()')
+    vim.command('setlocal modifiable')
+    lines = (header + output)
+    lines = [line.rstrip('\n') for line in lines]
+    vim.current.buffer[:] = lines
+    vim.command('setlocal nomodifiable')
+
+    i = 1
+    for line in output:
+        try:
+            line.split('[')[0].index('@')
+            i += 1
+            break
+        except ValueError:
+            pass
+        i += 1
+    vim.command('%d' % (i+len(header)-1))
+
+def MundoRenderPreview():
+    if not _check_sanity():
+        return
+
+    target_state = MundoGetTargetState()
+    # Check that there's an undo state. There may not be if we're talking about
+    # a buffer with no changes yet.
+    if target_state == None:
+        util._goto_window_for_buffer_name('__Mundo__')
+        return
+    else:
+        target_state = int(target_state)
+
+    util._goto_window_for_buffer(vim.eval('g:mundo_target_n'))
+
+    nodes, nmap = nodesData.make_nodes()
+
+    node_after = nmap[target_state]
+    node_before = node_after.parent
+
+    vim.command('call s:MundoOpenPreview()')
+    util._output_preview_text(nodesData.preview_diff(node_before, node_after))
+
+    util._goto_window_for_buffer_name('__Mundo__')
+
+def MundoGetTargetState():
+    """ Get the current undo number that mundo is at.  """
+    util._goto_window_for_buffer_name('__Mundo__')
+    target_line = vim.eval("getline('.')")
+    matches = re.match('^.* \[([0-9]+)\] .*$',target_line)
+    if matches:
+        return int(matches.group(1))
+    return 0
+
+def GetNextLine(direction,move_count,write,start="line('.')"):
+    start_line_no = int(vim.eval(start))
+    start_line = vim.eval("getline(%d)" % start_line_no)
+    mundo_verbose_graph = vim.eval('g:mundo_verbose_graph')
+    if mundo_verbose_graph != "0":
+        distance = 2
+
+        # If we're in between two nodes we move by one less to get back on track.
+        if start_line.find('[') == -1:
+            distance = distance - 1
+    else:
+      distance = 1
+      nextline = vim.eval("getline(%d)" % (start_line_no+direction))
+      idx1 = nextline.find('@')
+      idx2 = nextline.find('o')
+      idx3 = nextline.find('w')
+      # if the next line is not a revision - then go down one more.
+      if (idx1+idx2+idx3) == -3:
+          distance = distance + 1
+
+    next_line = start_line_no + distance*direction
+    if move_count > 1:
+        return GetNextLine(direction,move_count-1,write,str(next_line))
+    elif write:
+        newline = vim.eval("getline(%d)" % (next_line))
+        if newline.find('w ') == -1:
+            # make sure that if we can't go up/down anymore that we quit out.
+            if direction < 0 and next_line == 1:
+                return next_line
+            if direction > 0 and next_line >= len(vim.current.window.buffer):
+                return next_line
+            return GetNextLine(direction,1,write,str(next_line))
+    return next_line
+
+def MundoMove(direction,move_count=1,relative=True,write=False):
+    """
+    Move within the undo graph in the direction specified (or to the specific
+    undo node specified).
+
+    Parameters:
+
+      direction  - -1/1 (up/down). when 'relative' if False, the undo node to
+                   move to.
+      move_count - how many times to perform the operation (irrelevent for
+                   relative == False).
+      relative   - whether to move up/down, or to jump to a specific undo node.
+
+      write      - If True, move to the next written undo.
+    """
+    if relative:
+        target_n = GetNextLine(direction,move_count,write)
+    else:
+        updown = 1
+        if MundoGetTargetState() < direction:
+            updown = -1
+        target_n = GetNextLine(updown,abs(MundoGetTargetState()-direction),write)
+
+    # Bound the movement to the graph.
+    help_lines = 3
+    if int(vim.eval('g:mundo_help')):
+        help_lines = len(INLINE_HELP.split('\n'))
+    if target_n <= help_lines:
+        vim.command("call cursor(%d, 0)" % help_lines)
+    else:
+        vim.command("call cursor(%d, 0)" % target_n)
+
+    line = vim.eval("getline('.')")
+
+    # Move to the node, whether it's an @, o, or w
+    idx1 = line.find('@ ')
+    idx2 = line.find('o ')
+    idx3 = line.find('w ')
+    idxs = []
+    if idx1 != -1:
+        idxs.append(idx1)
+    if idx2 != -1:
+        idxs.append(idx2)
+    if idx3 != -1:
+        idxs.append(idx3)
+    minidx = min(idxs)
+    if idx1 == minidx:
+        vim.command("call cursor(0, %d + 1)" % idx1)
+    elif idx2 == minidx:
+        vim.command("call cursor(0, %d + 1)" % idx2)
+    else:
+        vim.command("call cursor(0, %d + 1)" % idx3)
+
+    if vim.eval('g:mundo_auto_preview') == '1':
+        MundoRenderPreview()
+
+def MundoSearch():
+    search = vim.eval("input('/')");
+    vim.command('let @/="%s"'% search.replace("\\","\\\\").replace('"','\\"'))
+    MundoNextMatch()
+
+def MundoPrevMatch():
+    MundoMatch(-1)
+
+def MundoNextMatch():
+    MundoMatch(1)
+
+def MundoMatch(down):
+    """ Jump to the next node that matches the current pattern.  If there is a
+    next node, search from the next node to the end of the list of changes. Stop
+    on a match. """
+    if not _check_sanity():
+        return
+
+    # save the current window number (should be the navigation window)
+    # then generate the undo nodes, and then go back to the current window.
+    util._goto_window_for_buffer(vim.eval('g:mundo_target_n'))
+
+    nodes, nmap = nodesData.make_nodes()
+    total = len(nodes) - 1
+
+    util._goto_window_for_buffer_name('__Mundo__')
+    curline = int(vim.eval("line('.')"))
+    mundo_node = MundoGetTargetState()
+
+    found_version = -1
+    if total > 0:
+        therange = range(mundo_node-1,-1,-1)
+        if down < 0:
+            therange = range(mundo_node+1,total+1)
+        for version in therange:
+            util._goto_window_for_buffer_name('__Mundo__')
+            undochanges = nodesData.preview_diff(nmap[version].parent, nmap[version])
+            # Look thru all of the changes, ignore the first two b/c those are the
+            # diff timestamp fields (not relevent):
+            for change in undochanges[3:]:
+                match_index = vim.eval('match("%s",@/)'% change.replace("\\","\\\\").replace('"','\\"'))
+                # only consider the matches that are actual additions or
+                # subtractions
+                if int(match_index) >= 0 and (change.startswith('-') or change.startswith('+')):
+                    found_version = version
+                    break
+            # found something, lets get out of here:
+            if found_version != -1:
+                break
+    util._goto_window_for_buffer_name('__Mundo__')
+    if found_version >= 0:
+        MundoMove(found_version,1,False)
+
+def MundoRenderPatchdiff():
+    """ Call MundoRenderChangePreview and display a vert diffpatch with the
+    current file. """
+    if MundoRenderChangePreview():
+        # if there are no lines, do nothing (show a warning).
+        util._goto_window_for_buffer_name('__Mundo_Preview__')
+        if vim.current.buffer[:] == ['']:
+            # restore the cursor position before exiting.
+            util._goto_window_for_buffer_name('__Mundo__')
+            vim.command('unsilent echo "No difference between current file and undo number!"')
+            return False
+
+        # quit out of mundo main screen
+        util._goto_window_for_buffer_name('__Mundo__')
+        vim.command('quit')
+
+        # save the __Mundo_Preview__ buffer to a temp file.
+        util._goto_window_for_buffer_name('__Mundo_Preview__')
+        (handle,filename) = tempfile.mkstemp()
+        vim.command('silent! w %s' % (filename))
+        # exit the __Mundo_Preview__ window
+        vim.command('bdelete')
+        # diff the temp file
+        vim.command('silent! keepalt vert diffpatch %s' % (filename))
+        vim.command('set buftype=nofile bufhidden=delete')
+        return True
+    return False
+
+def MundoGetChangesForLine():
+    if not _check_sanity():
+        return False
+
+    target_state = MundoGetTargetState()
+
+    # Check that there's an undo state. There may not be if we're talking about
+    # a buffer with no changes yet.
+    if target_state == None:
+        util._goto_window_for_buffer_name('__Mundo__')
+        return False
+    else:
+        target_state = int(target_state)
+
+    util._goto_window_for_buffer(vim.eval('g:mundo_target_n'))
+
+    nodes, nmap = nodesData.make_nodes()
+
+    node_after = nmap[target_state]
+    node_before = nmap[nodesData.current()]
+    return nodesData.change_preview_diff(node_before, node_after)
+
+def MundoRenderChangePreview():
+    """ Render the selected undo level with the current file.
+    Return True on success, False on failure. """
+    if not _check_sanity():
+        return
+
+    vim.command('call s:MundoOpenPreview()')
+    util._output_preview_text(MundoGetChangesForLine())
+
+    util._goto_window_for_buffer_name('__Mundo__')
+
+    return True
+
+def MundoRenderToggleInlineDiff():
+    show_inline = int(vim.eval('g:mundo_inline_undo'))
+    if show_inline == 0:
+        vim.command("let g:mundo_inline_undo=1")
+    else:
+        vim.command("let g:mundo_inline_undo=0")
+    line = int(vim.eval("line('.')"))
+    nodesData.clear_oneline_diffs()
+    MundoRenderGraph(True)
+    vim.command("call cursor(%d,0)" % line)
+
+def MundoToggleHelp():
+    show_help = int(vim.eval('g:mundo_help'))
+    if show_help == 0:
+        vim.command("let g:mundo_help=1")
+    else:
+        vim.command("let g:mundo_help=0")
+    line = int(vim.eval("line('.')"))
+    column = int(vim.eval("col('.')"))
+    old_line_count = int(vim.eval("line('$')"))
+    MundoRenderGraph(True)
+    new_line_count = int(vim.eval("line('$')"))
+    vim.command("call cursor(%d, %d)" % (line + new_line_count - old_line_count, column))
+
+# Mundo undo/redo
+def MundoRevert():
+    if not _check_sanity():
+        return
+
+    target_n = MundoGetTargetState()
+    back = vim.eval('g:mundo_target_n')
+
+    util._goto_window_for_buffer(back)
+    util._undo_to(target_n)
+
+    vim.command('MundoRenderGraph')
+    if int(vim.eval('g:mundo_return_on_revert')):
+        util._goto_window_for_buffer(back)
+
+    if int(vim.eval('g:mundo_close_on_revert')):
+        vim.command('MundoToggle')
+
+def MundoPlayTo():
+    if not _check_sanity():
+        return
+
+    target_n = MundoGetTargetState()
+    back = int(vim.eval('g:mundo_target_n'))
+    delay = int(vim.eval('g:mundo_playback_delay'))
+
+    vim.command('echo "%s"' % back)
+
+    util._goto_window_for_buffer(back)
+    util.normal('zR')
+
+    nodes, nmap = nodesData.make_nodes()
+
+    start = nmap[nodesData.current()]
+    end = nmap[target_n]
+
+    def _walk_branch(origin, dest):
+        rev = origin.n < dest.n
+
+        nodes = []
+        if origin.n > dest.n:
+            current, final = origin, dest
+        else:
+            current, final = dest, origin
+
+        while current.n >= final.n:
+            if current.n == final.n:
+                break
+            nodes.append(current)
+            current = current.parent
+        else:
+            return None
+        nodes.append(current)
+
+        if rev:
+            return reversed(nodes)
+        else:
+            return nodes
+
+    branch = _walk_branch(start, end)
+
+    if not branch:
+        vim.command('unsilent echo "No path to that node from here!"')
+        return
+
+    for node in branch:
+        util._undo_to(node.n)
+        vim.command('MundoRenderGraph')
+        util.normal('zz')
+        util._goto_window_for_buffer(back)
+        vim.command('redraw')
+        vim.command('sleep %dm' % delay)
+
+def initPythonModule():
+    if sys.version_info[:2] < (2, 4):
+        vim.command('let s:has_supported_python = 0')

+ 410 - 0
vim/autoload/mundo.vim

@@ -0,0 +1,410 @@
+" ============================================================================
+" File:        mundo.vim
+" Description: vim global plugin to visualize your undo tree
+" Maintainer:  Hyeon Kim <simnalamburt@gmail.com>
+" License:     GPLv2+ -- look it up.
+" Notes:       Much of this code was thiefed from Mercurial, and the rest was
+"              heavily inspired by scratch.vim and histwin.vim.
+"
+" ============================================================================
+
+
+"{{{ Init
+let s:save_cpo = &cpo
+set cpo&vim
+if v:version < '703'"{{{
+    function! s:MundoDidNotLoad()
+        echohl WarningMsg|echomsg "Mundo unavailable: requires Vim 7.3+"|echohl None
+    endfunction
+    command! -nargs=0 MundoToggle call s:MundoDidNotLoad()
+    finish
+endif"}}}
+
+call mundo#util#init()
+
+
+let s:has_supported_python = 0
+if g:mundo_prefer_python3 && has('python3')"{{{
+    let s:has_supported_python = 2
+elseif has('python')"
+    let s:has_supported_python = 1
+endif
+
+if !s:has_supported_python
+    function! s:MundoDidNotLoad()
+        echohl WarningMsg|echomsg "Mundo requires Vim to be compiled with Python 2.4+"|echohl None
+    endfunction
+    command! -nargs=0 MundoToggle call s:MundoDidNotLoad()
+    finish
+endif"}}}
+
+
+let s:plugin_path = escape(expand('<sfile>:p:h'), '\')
+"}}}
+
+"{{{ Mundo utility functions
+
+function! s:MundoGoToWindowForBufferName(name)"{{{
+    if bufwinnr(bufnr(a:name)) != -1
+        exe bufwinnr(bufnr(a:name)) . "wincmd w"
+        return 1
+    else
+        return 0
+    endif
+endfunction"}}}
+
+function! s:MundoIsVisible()"{{{
+    if bufwinnr(bufnr("__Mundo__")) != -1 || bufwinnr(bufnr("__Mundo_Preview__")) != -1
+        return 1
+    else
+        return 0
+    endif
+endfunction"}}}
+
+function! s:MundoInlineHelpLength()"{{{
+    if g:mundo_help
+        return 10
+    else
+        return 0
+    endif
+endfunction"}}}
+
+"}}}
+
+"{{{ Mundo buffer settings
+
+function! s:MundoMapGraph()"{{{
+    exec 'nnoremap <script> <silent> <buffer> ' . g:mundo_map_move_older . " :<C-u>call <sid>MundoPython('MundoMove(1,'. v:count .')')<CR>"
+    exec 'nnoremap <script> <silent> <buffer> ' . g:mundo_map_move_newer . " :<C-u>call <sid>MundoPython('MundoMove(-1,'. v:count .')')<CR>"
+    nnoremap <script> <silent> <buffer> <CR>          :call <sid>MundoPython('MundoRevert()')<CR>
+    nnoremap <script> <silent> <buffer> o             :call <sid>MundoPython('MundoRevert()')<CR>
+    nnoremap <script> <silent> <buffer> <down>        :<C-u>call <sid>MundoPython('MundoMove(1,'. v:count .')')<CR>
+    nnoremap <script> <silent> <buffer> <up>          :<C-u>call <sid>MundoPython('MundoMove(-1,'. v:count .')')<CR>
+    nnoremap <script> <silent> <buffer> J             :<C-u>call <sid>MundoPython('MundoMove(1,'. v:count .',True,True)')<CR>
+    nnoremap <script> <silent> <buffer> K             :<C-u>call <sid>MundoPython('MundoMove(-1,'. v:count .',True,True)')<CR>
+    nnoremap <script> <silent> <buffer> gg            gg:<C-u>call <sid>MundoPython('MundoMove(1,'. v:count .')')<CR>
+    nnoremap <script> <silent> <buffer> P             :call <sid>MundoPython('MundoPlayTo()')<CR>
+    nnoremap <script> <silent> <buffer> d             :call <sid>MundoPython('MundoRenderPatchdiff()')<CR>
+    nnoremap <script> <silent> <buffer> i             :call <sid>MundoPython('MundoRenderToggleInlineDiff()')<CR>
+    nnoremap <script> <silent> <buffer> /             :call <sid>MundoPython('MundoSearch()')<CR>
+    nnoremap <script> <silent> <buffer> n             :call <sid>MundoPython('MundoNextMatch()')<CR>
+    nnoremap <script> <silent> <buffer> N             :call <sid>MundoPython('MundoPrevMatch()')<CR>
+    nnoremap <script> <silent> <buffer> p             :call <sid>MundoPython('MundoRenderChangePreview()')<CR>
+    nnoremap <script> <silent> <buffer> r             :call <sid>MundoPython('MundoRenderPreview()')<CR>
+    nnoremap <script> <silent> <buffer> ?             :call <sid>MundoPython('MundoToggleHelp()')<CR>
+    nnoremap <script> <silent> <buffer> q             :call <sid>MundoClose()<CR>
+    cabbrev  <script> <silent> <buffer> q             call <sid>MundoClose()
+    cabbrev  <script> <silent> <buffer> quit          call <sid>MundoClose()
+    nnoremap <script> <silent> <buffer> <2-LeftMouse> :call <sid>MundoMouseDoubleClick()<CR>
+endfunction"}}}
+
+function! s:MundoMapPreview()"{{{
+    nnoremap <script> <silent> <buffer> q     :call <sid>MundoClose()<CR>
+    cabbrev  <script> <silent> <buffer> q     call <sid>MundoClose()
+    cabbrev  <script> <silent> <buffer> quit  call <sid>MundoClose()
+endfunction"}}}
+
+function! s:MundoSettingsGraph()"{{{
+    setlocal buftype=nofile
+    setlocal bufhidden=hide
+    setlocal noswapfile
+    setlocal nobuflisted
+    setlocal nomodifiable
+    setlocal filetype=mundo
+    setlocal nolist
+    setlocal nonumber
+    setlocal norelativenumber
+    setlocal nowrap
+    call s:MundoSyntaxGraph()
+    call s:MundoMapGraph()
+endfunction"}}}
+
+function! s:MundoSettingsPreview()"{{{
+    setlocal buftype=nofile
+    setlocal bufhidden=hide
+    setlocal noswapfile
+    setlocal nobuflisted
+    setlocal nomodifiable
+    setlocal filetype=diff
+    setlocal nonumber
+    setlocal norelativenumber
+    setlocal nowrap
+    setlocal foldlevel=20
+    setlocal foldmethod=diff
+    call s:MundoMapPreview()
+endfunction"}}}
+
+function! s:MundoSyntaxGraph()"{{{
+    let b:current_syntax = 'mundo'
+
+    syn match MundoCurrentLocation '@'
+    syn match MundoHelp '\v^".*$'
+    syn match MundoNumberField '\v\[[0-9]+\]'
+    syn match MundoNumber '\v[0-9]+' contained containedin=MundoNumberField
+    syn region MundoDiff start=/\v<ago> / end=/$/
+    syn match MundoDiffAdd '\v\+[^+-]+\+' contained containedin=MundoDiff
+    syn match MundoDiffDelete '\v-[^+-]+-' contained containedin=MundoDiff
+
+    hi def link MundoCurrentLocation Keyword
+    hi def link MundoHelp Comment
+    hi def link MundoNumberField Comment
+    hi def link MundoNumber Identifier
+    hi def link MundoDiffAdd DiffAdd
+    hi def link MundoDiffDelete DiffDelete
+endfunction"}}}
+
+"}}}
+
+"{{{ Mundo buffer/window management
+
+function! s:MundoResizeBuffers(backto)"{{{
+    call s:MundoGoToWindowForBufferName('__Mundo__')
+    exe "vertical resize " . g:mundo_width
+
+    call s:MundoGoToWindowForBufferName('__Mundo_Preview__')
+    exe "resize " . g:mundo_preview_height
+
+    exe a:backto . "wincmd w"
+endfunction"}}}
+
+function! s:MundoOpenGraph()"{{{
+    let existing_mundo_buffer = bufnr("__Mundo__")
+
+    if existing_mundo_buffer == -1
+        call s:MundoGoToWindowForBufferName('__Mundo_Preview__')
+        exe "new __Mundo__"
+        set fdm=manual
+        if g:mundo_preview_bottom
+            if g:mundo_right
+                wincmd L
+            else
+                wincmd H
+            endif
+        endif
+        call s:MundoResizeBuffers(winnr())
+    else
+        let existing_mundo_window = bufwinnr(existing_mundo_buffer)
+
+        if existing_mundo_window != -1
+            if winnr() != existing_mundo_window
+                exe existing_mundo_window . "wincmd w"
+            endif
+        else
+            call s:MundoGoToWindowForBufferName('__Mundo_Preview__')
+            if g:mundo_preview_bottom
+                if g:mundo_right
+                    exe "botright vsplit +buffer" . existing_mundo_buffer
+                else
+                    exe "topleft vsplit +buffer" . existing_mundo_buffer
+                endif
+            else
+                exe "split +buffer" . existing_mundo_buffer
+            endif
+            call s:MundoResizeBuffers(winnr())
+        endif
+    endif
+    if exists("g:mundo_tree_statusline")
+        let &l:statusline = g:mundo_tree_statusline
+    endif
+endfunction"}}}
+
+function! s:MundoOpenPreview()"{{{
+    let existing_preview_buffer = bufnr("__Mundo_Preview__")
+
+    if existing_preview_buffer == -1
+        if g:mundo_preview_bottom
+            exe "botright keepalt new __Mundo_Preview__"
+        else
+            if g:mundo_right
+                exe "botright keepalt vnew __Mundo_Preview__"
+            else
+                exe "topleft keepalt vnew __Mundo_Preview__"
+            endif
+        endif
+    else
+        let existing_preview_window = bufwinnr(existing_preview_buffer)
+
+        if existing_preview_window != -1
+            if winnr() != existing_preview_window
+                exe existing_preview_window . "wincmd w"
+            endif
+        else
+            if g:mundo_preview_bottom
+                exe "botright keepalt split +buffer" . existing_preview_buffer
+            else
+                if g:mundo_right
+                    exe "botright keepalt vsplit +buffer" . existing_preview_buffer
+                else
+                    exe "topleft keepalt vsplit +buffer" . existing_preview_buffer
+                endif
+            endif
+        endif
+    endif
+    if exists("g:mundo_preview_statusline")
+        let &l:statusline = g:mundo_preview_statusline
+    endif
+endfunction"}}}
+
+function! s:MundoClose()"{{{
+    if s:MundoGoToWindowForBufferName('__Mundo__')
+        quit
+    endif
+
+    if s:MundoGoToWindowForBufferName('__Mundo_Preview__')
+        quit
+    endif
+
+    exe bufwinnr(g:mundo_target_n) . "wincmd w"
+endfunction"}}}
+
+function! s:MundoOpen()"{{{
+    if !exists('g:mundo_py_loaded')
+        if s:has_supported_python == 2 && g:mundo_prefer_python3
+            exe 'py3file ' . escape(s:plugin_path, ' ') . '/mundo.py'
+            python3 initPythonModule()
+        else
+            exe 'pyfile ' . escape(s:plugin_path, ' ') . '/mundo.py'
+            python initPythonModule()
+        endif
+
+        if !s:has_supported_python
+            function! s:MundoDidNotLoad()
+                echohl WarningMsg|echomsg "Mundo unavailable: requires Vim 7.3+"|echohl None
+            endfunction
+            command! -nargs=0 MundoToggle call s:MundoDidNotLoad()
+            call s:MundoDidNotLoad()
+            return
+        endif
+
+        let g:mundo_py_loaded = 1
+    endif
+
+    " Save `splitbelow` value and set it to default to avoid problems with
+    " positioning new windows.
+    let saved_splitbelow = &splitbelow
+    let &splitbelow = 0
+
+    call s:MundoOpenPreview()
+    exe bufwinnr(g:mundo_target_n) . "wincmd w"
+    call s:MundoOpenGraph()
+
+    call s:MundoPython('MundoRenderGraph()')
+    call s:MundoPython('MundoRenderPreview()')
+
+    " Restore `splitbelow` value.
+    let &splitbelow = saved_splitbelow
+endfunction"}}}
+
+" This has to be outside of a function otherwise it just picks up the CWD
+let s:mundo_path = escape( expand( '<sfile>:p:h' ), '\' )
+
+function! s:MundoToggle()"{{{
+    if g:mundo_python_path_setup == 0
+        let g:mundo_python_path_setup = 1
+        call s:MundoPython('sys.path.insert(1, "'. s:mundo_path .'")')
+        call s:MundoPython('sys.path.insert(1, "'. s:mundo_path .'/mundo")')
+    end
+    if s:MundoIsVisible()
+        call s:MundoClose()
+    else
+        let g:mundo_target_n = bufnr('')
+        let g:mundo_target_f = @%
+        call s:MundoOpen()
+    endif
+endfunction"}}}
+
+function! s:MundoShow()"{{{
+    if !s:MundoIsVisible()
+        let g:mundo_target_n = bufnr('')
+        let g:mundo_target_f = @%
+        call s:MundoOpen()
+    endif
+endfunction"}}}
+
+function! s:MundoHide()"{{{
+    if s:MundoIsVisible()
+        call s:MundoClose()
+    endif
+endfunction"}}}
+
+"}}}
+
+"{{{ Mundo mouse handling
+
+function! s:MundoMouseDoubleClick()"{{{
+    let start_line = getline('.')
+
+    if stridx(start_line, '[') == -1
+        return
+    else
+        call <sid>MundoPython('MundoRevert()')
+    endif
+endfunction"}}}
+
+"}}}
+
+"{{{ Mundo rendering
+
+function! s:MundoPython(fn)"{{{
+    if s:has_supported_python == 2 && g:mundo_prefer_python3
+        exec "python3 ". a:fn
+    else
+        exec "python ". a:fn
+    endif
+endfunction"}}}
+
+"}}}
+
+"{{{ Misc
+
+function! mundo#MundoToggle()"{{{
+    call s:MundoToggle()
+endfunction"}}}
+
+function! mundo#MundoShow()"{{{
+    call s:MundoShow()
+endfunction"}}}
+
+function! mundo#MundoHide()"{{{
+    call s:MundoHide()
+endfunction"}}}
+
+function! mundo#MundoRenderGraph()"{{{
+    call s:MundoPython('MundoRenderGraph()')
+endfunction"}}}
+
+" automatically reload Mundo buffer if open
+function! s:MundoRefresh()"{{{
+  " abort when there were no changes
+
+  let mundoWin    = bufwinnr('__Mundo__')
+  let mundoPreWin = bufwinnr('__Mundo_Preview__')
+  let currentWin  = bufwinnr('%')
+
+  " abort if Mundo is closed or is current window
+  if (mundoWin == -1) || (mundoPreWin == -1) || (mundoPreWin == currentWin)
+    return
+  endif
+
+  let winView = winsaveview()
+  :MundoRenderGraph
+
+  " switch back to previous window
+  execute currentWin .'wincmd w'
+  call winrestview(winView)
+endfunction"}}}
+
+augroup MundoAug
+    autocmd!
+    autocmd BufNewFile __Mundo__ call s:MundoSettingsGraph()
+    autocmd BufNewFile __Mundo_Preview__ call s:MundoSettingsPreview()
+    autocmd CursorHold * call s:MundoRefresh()
+    autocmd CursorMoved * call s:MundoRefresh()
+    autocmd BufEnter * let b:mundoChangedtick = 0
+augroup END
+
+"}}}
+
+
+let &cpo = s:save_cpo
+unlet s:save_cpo

+ 0 - 0
vim/autoload/mundo/__init__.py


+ 105 - 0
vim/autoload/mundo/diff.py

@@ -0,0 +1,105 @@
+import difflib
+import itertools
+
+# one line diff functions.
+def one_line_diff_str(before,after,mx=15,pre=2):
+    """
+    Return a summary of the differences between two strings, concatenated.
+
+    Parameters:
+
+      before - string before.
+      after  - after string.
+      mx     - the max number of strings.
+      pre    - number of characters to show before diff (context)
+
+    Returns a string no longer than 'mx'.
+    """
+    old = one_line_diff(before,after)
+    result = ''
+    firstEl = True
+    # TODO instead of using +addition+ and -subtraction- it'd be nice to be able
+    # to highlight the change w/o requiring the +/- chars.
+    for v in old:
+        # if the first element doesn't have a change, then don't include it.
+        v = escape_returns(v)
+        if firstEl:
+            firstEl = False
+            # add in pre character context:
+            if not (v.startswith('+') or v.startswith('-')) and result == '':
+                v = v[-pre:]
+        # when we're going to be bigger than our max limit, lets ensure that the
+        # trailing +/- appears in the text:
+        if len(result) + len(v) > mx:
+            if v.startswith('+') or v.startswith('-'):
+                result += v[:mx - len(result) - 1]
+                result += v[0]
+            break
+        result += v
+    return result
+
+def escape_returns(result):
+    return result.replace('\n','\\n').replace('\r','\\r').replace('\t','\\t')
+
+def one_line_diff(before, after):
+    """
+    Return a summary of the differences between two arbitrary strings.
+
+    Returns a list of strings, summarizing all the changes.
+    """
+    a, b, result = [], [], []
+    for line in itertools.chain(itertools.islice(
+        difflib.unified_diff(before.splitlines(),
+                             after.splitlines()), 2, None), ['@@']):
+        if line.startswith('@@'):
+            result.extend(one_line_diff_raw('\n'.join(a), '\n'.join(b)))
+            a, b = [], []
+            continue
+        if not line.startswith('+'):
+            a.append(line[1:])
+        if not line.startswith('-'):
+            b.append(line[1:])
+    if after.endswith('\n') and not before.endswith('\n'):
+        if result:
+            result[-1] = result[-1][:-1] + '\n+'
+        else:
+            result = ['+\n+']
+    return result
+
+def one_line_diff_raw(before,after):
+  s = difflib.SequenceMatcher(None,before,after)
+  results = []
+  for tag, i1, i2, j1, j2 in s.get_opcodes():
+    #print ("%7s a[%d:%d] (%s) b[%d:%d] (%s)" % (tag, i1, i2, before[i1:i2], j1, j2, after[j1:j2]))
+    if tag == 'equal':
+      _append_result(results,{
+        'equal': after[j1:j2]
+      })
+    if tag == 'insert':
+      _append_result(results,{
+        'plus': after[j1:j2]
+      })
+    elif tag == 'delete':
+      _append_result(results,{
+        'minus': before[i1:i2]
+      })
+    elif tag == 'replace':
+      _append_result(results,{
+        'minus': before[j1:j2],
+        'plus': after[j1:j2]
+      })
+  final_results = []
+  # finally, create a human readable string of information.
+  for v in results:
+    if 'minus' in v and 'plus' in v and len(v['minus']) > 0 and len(v['plus']) > 0:
+      final_results.append("-%s-+%s+"% (v['minus'],v['plus']))
+    elif 'minus' in v and len(v['minus']) > 0:
+      final_results.append("-%s-"% (v['minus']))
+    elif 'plus' in v and len(v['plus']) > 0:
+      final_results.append("+%s+"% (v['plus']))
+    elif 'equal' in v:
+      final_results.append("%s"% (v['equal']))
+  return final_results
+
+def _append_result(results,val):
+  results.append(val)

+ 253 - 0
vim/autoload/mundo/graphlog.py

@@ -0,0 +1,253 @@
+import time
+import util
+
+# Mercurial's graphlog code -------------------------------------------------------
+def asciiedges(seen, rev, parents):
+    """adds edge info to changelog DAG walk suitable for ascii()"""
+    if rev not in seen:
+        seen.append(rev)
+    nodeidx = seen.index(rev)
+
+    knownparents = []
+    newparents = []
+    for parent in parents:
+        if parent in seen:
+            knownparents.append(parent)
+        else:
+            newparents.append(parent)
+
+    ncols = len(seen)
+    seen[nodeidx:nodeidx + 1] = newparents
+    edges = [(nodeidx, seen.index(p)) for p in knownparents]
+
+    if len(newparents) > 0:
+        edges.append((nodeidx, nodeidx))
+    if len(newparents) > 1:
+        edges.append((nodeidx, nodeidx + 1))
+
+    nmorecols = len(seen) - ncols
+    return nodeidx, edges, ncols, nmorecols
+
+def get_nodeline_edges_tail(
+        node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail):
+    if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0:
+        # Still going in the same non-vertical direction.
+        if n_columns_diff == -1:
+            start = max(node_index + 1, p_node_index)
+            tail = ["|", " "] * (start - node_index - 1)
+            tail.extend(["/", " "] * (n_columns - start))
+            return tail
+        else:
+            return ["\\", " "] * (n_columns - node_index - 1)
+    else:
+        return ["|", " "] * (n_columns - node_index - 1)
+
+def draw_edges(edges, nodeline, interline):
+    for (start, end) in edges:
+        if start == end + 1:
+            interline[2 * end + 1] = "/"
+        elif start == end - 1:
+            interline[2 * start + 1] = "\\"
+        elif start == end:
+            interline[2 * start] = "|"
+        else:
+            nodeline[2 * end] = "+"
+            if start > end:
+                (start, end) = (end, start)
+            for i in range(2 * start + 1, 2 * end):
+                if nodeline[i] != "+":
+                    nodeline[i] = "-"
+
+def fix_long_right_edges(edges):
+    for (i, (start, end)) in enumerate(edges):
+        if end > start:
+            edges[i] = (start, end + 1)
+
+def ascii(state, type, char, text, coldata, verbose):
+    """prints an ASCII graph of the DAG
+
+    takes the following arguments (one call per node in the graph):
+
+      - Somewhere to keep the needed state in (init to asciistate())
+      - Column of the current node in the set of ongoing edges.
+      - Type indicator of node data == ASCIIDATA.
+      - Payload: (char, lines):
+        - Character to use as node's symbol.
+        - List of lines to display as the node's text.
+      - Edges; a list of (col, next_col) indicating the edges between
+        the current node and its parents.
+      - Number of columns (ongoing edges) in the current revision.
+      - The difference between the number of columns (ongoing edges)
+        in the next revision and the number of columns (ongoing edges)
+        in the current revision. That is: -1 means one column removed;
+        0 means no columns added or removed; 1 means one column added.
+      - Verbosity: if enabled then the graph prints an extra '|'
+        between each line of information.
+
+    Returns a string representing the output.
+    """
+
+    idx, edges, ncols, coldiff = coldata
+    assert -2 < coldiff < 2
+    if coldiff == -1:
+        # Transform
+        #
+        #     | | |        | | |
+        #     o | |  into  o---+
+        #     |X /         |/ /
+        #     | |          | |
+        fix_long_right_edges(edges)
+
+    # add_padding_line says whether to rewrite
+    #
+    #     | | | |        | | | |
+    #     | o---+  into  | o---+
+    #     |  / /         |   | |  # <--- padding line
+    #     o | |          |  / /
+    #                    o | |
+    add_padding_line = (len(text) > 2 and coldiff == -1 and
+                        [x for (x, y) in edges if x + 1 < y] and
+                        verbose)
+
+    # fix_nodeline_tail says whether to rewrite
+    #
+    #     | | o | |        | | o | |
+    #     | | |/ /         | | |/ /
+    #     | o | |    into  | o / /   # <--- fixed nodeline tail
+    #     | |/ /           | |/ /
+    #     o | |            o | |
+    fix_nodeline_tail = len(text) <= 2 and not add_padding_line
+
+    # nodeline is the line containing the node character (typically o)
+    nodeline = ["|", " "] * idx
+    nodeline.extend([char, " "])
+
+    nodeline.extend(
+        get_nodeline_edges_tail(idx, state[1], ncols, coldiff,
+                                state[0], fix_nodeline_tail))
+
+    # shift_interline is the line containing the non-vertical
+    # edges between this entry and the next
+    shift_interline = ["|", " "] * idx
+    if coldiff == -1:
+        n_spaces = 1
+        edge_ch = "/"
+    elif coldiff == 0:
+        n_spaces = 2
+        edge_ch = "|"
+    else:
+        n_spaces = 3
+        edge_ch = "\\"
+    shift_interline.extend(n_spaces * [" "])
+    shift_interline.extend([edge_ch, " "] * (ncols - idx - 1))
+
+    # draw edges from the current node to its parents
+    draw_edges(edges, nodeline, shift_interline)
+
+    # lines is the list of all graph lines to print
+    lines = [nodeline]
+    if add_padding_line:
+        lines.append(get_padding_line(idx, ncols, edges))
+    lines.append(shift_interline)
+
+    # make sure that there are as many graph lines as there are
+    # log strings
+    if any("/" in s for s in lines) or verbose:
+        while len(text) < len(lines):
+            text.append('')
+    if len(lines) < len(text):
+        extra_interline = ["|", " "] * (ncols + coldiff)
+        while len(lines) < len(text):
+            lines.append(extra_interline)
+
+    indentation_level = max(ncols, ncols + coldiff)
+    result = []
+    for (line, logstr) in zip(lines, text):
+        graph = "%-*s" % (2 * indentation_level, "".join(line))
+        if not graph.isspace():
+            result.append([graph, logstr])
+
+    # ... and start over
+    state[0] = coldiff
+    state[1] = idx
+    return result
+
+def generate(verbose, num_header_lines, first_visible_line, last_visible_line, inline_graph, nodesData):
+    """
+    Generate an array of the graph, and text describing the node of the graph.
+    """
+    seen, state = [], [0, 0]
+    result = []
+    current = nodesData.current()
+
+    nodes, nmap = nodesData.make_nodes()
+
+    for node in nodes:
+        node.children = [n for n in nodes if n.parent == node]
+
+    def walk_nodes(nodes):
+        for node in nodes:
+            if node.parent:
+                yield (node, [node.parent])
+            else:
+                yield (node, [])
+
+    dag = sorted(nodes, key=lambda n: int(n.n), reverse=True)
+    dag = walk_nodes(dag)
+
+    line_number = num_header_lines
+    for idx, part in list(enumerate(dag)):
+        node, parents = part
+        if node.time:
+            age_label = age(int(node.time))
+        else:
+            age_label = 'Original'
+        line = '[%s] %s' % (node.n, age_label)
+        if node.n == current:
+            char = '@'
+        elif node.saved:
+            char = 'w'
+        else:
+            char = 'o'
+        show_inine_diff = inline_graph and line_number >= first_visible_line and line_number <= last_visible_line
+        preview_diff = nodesData.preview_diff(node.parent, node,False,show_inine_diff)
+        line = '[%s] %-10s %s' % (node.n, age_label, preview_diff)
+        new_lines = ascii(state, 'C', char, [line], asciiedges(seen, node, parents), verbose)
+        line_number += len(new_lines)
+        result.extend(new_lines)
+    util._undo_to(current)
+    return result
+
+# Mercurial age function -----------------------------------------------------------
+agescales = [("yr", 3600 * 24 * 365),
+             ("mon", 3600 * 24 * 30),
+             ("wk", 3600 * 24 * 7),
+             ("dy", 3600 * 24),
+             ("hr", 3600),
+             ("min", 60)]
+
+def age(ts):
+    '''turn a timestamp into an age string.'''
+
+    def plural(t, c):
+        if c == 1:
+            return t
+        return t + "s"
+    def fmt(t, c):
+        return "%d %s" % (int(c), plural(t, c))
+
+    now = time.time()
+    then = ts
+    if then > now:
+        return 'in the future'
+
+    delta = max(1, int(now - then))
+    if delta > agescales[0][1] * 2:
+        return time.strftime('%Y-%m-%d', time.gmtime(float(ts)))
+
+    for t, s in agescales:
+        n = delta // s
+        if n >= 2 or s == 1:
+            return '%s ago' % fmt(t, n)
+
+    return "<1 min ago"

+ 196 - 0
vim/autoload/mundo/node.py

@@ -0,0 +1,196 @@
+import diff
+import difflib
+import itertools
+import time
+import util
+
+# Python undo tree data structures and functions ----------------------------------
+class Node(object):
+    def __init__(self, n, parent, time, curhead, saved):
+        self.n = int(n)
+        self.parent = parent
+        self.children = []
+        self.curhead = curhead
+        self.saved = saved
+        self.time = time
+
+    def __repr__(self):
+        return "[n=%s,parent=%s,time=%s,curhead=%s,saved=%s]" % \
+            (self.n,self.parent,self.time,self.curhead,self.saved)
+
+class Nodes(object):
+    def __init__(self):
+        self._clear_cache()
+
+    def _clear_cache(self):
+        self.nodes_made = None
+        self.target_f = None
+        self.changedtick = None
+        self.lines = {}
+        self.clear_oneline_diffs()
+
+    def clear_oneline_diffs(self):
+        self.diffs = {}
+        self.diff_has_oneline = {}
+
+    def _check_version_location(self):
+        util._goto_window_for_buffer(util.vim().eval('g:mundo_target_n'))
+        target_f = util.vim().eval('g:mundo_target_f')
+        if target_f != self.target_f:
+            self._clear_cache()
+
+    def _make_nodes(self,alts, nodes, parent=None):
+        p = parent
+
+        for alt in alts:
+            if alt:
+                curhead = 'curhead' in alt
+                saved = 'save' in alt
+                node = Node(n=alt['seq'], parent=p, time=alt['time'], curhead=curhead, saved=saved)
+                nodes.append(node)
+                if alt.get('alt'):
+                    self._make_nodes(alt['alt'], nodes, p)
+                p = node
+
+    def is_outdated(self):
+        util._goto_window_for_buffer(util.vim().eval('g:mundo_target_n'))
+        current_changedtick = util.vim().eval('b:changedtick')
+        return self.changedtick != current_changedtick
+
+    def make_nodes(self):
+        # If the current changedtick is unchanged, we don't need to do
+        # anything:
+        if not self.is_outdated():
+            return self.nodes_made
+
+        self._check_version_location()
+        target_f = util.vim().eval('g:mundo_target_f')
+        ut = util.vim().eval('undotree()')
+        entries = ut['entries']
+        seq_last = ut['seq_last']
+        current_changedtick = util.vim().eval('b:changedtick')
+
+        root = Node(0, None, False, 0, 0)
+        nodes = []
+        # TODO only compute new values (not all values)
+        self._make_nodes(entries, nodes, root)
+        nodes.append(root)
+        nmap = dict((node.n, node) for node in nodes)
+
+        # cache values for later use
+        self.target_f = target_f
+        self.seq_last = seq_last
+        self.nodes_made = (nodes, nmap)
+        self.changedtick = current_changedtick
+
+        return self.nodes_made
+
+    def current(self):
+        """ Return the number of the current change. """
+        self._check_version_location()
+        nodes, nmap = self.make_nodes()
+        _curhead_l = list(itertools.dropwhile(lambda n: not n.curhead, nodes))
+        if _curhead_l:
+            current = _curhead_l[0].parent.n
+        else:
+            current = int(util.vim().eval('changenr()'))
+        return current
+
+    def _fmt_time(self,t):
+        return time.strftime('%Y-%m-%d %I:%M:%S %p', time.localtime(float(t)))
+
+    def _get_lines(self,node):
+        n = 0
+        if node:
+            n = node.n
+        if n not in self.lines:
+            util._undo_to(n)
+            self.lines[n] = util.vim().current.buffer[:]
+        return self.lines[n]
+
+    def change_preview_diff(self,before,after):
+        self._check_version_location()
+        key = "%s-%s-cpd"%(before.n,after.n)
+        if key in self.diffs:
+            return self.diffs[key]
+
+        util._goto_window_for_buffer(util.vim().eval('g:mundo_target_n'))
+        before_lines = self._get_lines(before)
+        after_lines = self._get_lines(after)
+
+        before_name = str(before.n or 'Original')
+        before_time = before.time and self._fmt_time(before.time) or ''
+        after_name = str(after.n or 'Original')
+        after_time = after.time and self._fmt_time(after.time) or ''
+
+        util._undo_to(self.current())
+
+        self.diffs[key] = list(difflib.unified_diff(before_lines, after_lines,
+                                         before_name, after_name,
+                                         before_time, after_time))
+        return self.diffs[key]
+
+    def preview_diff(self, before, after, unified=True, inline=False):
+        """
+        Generate a diff comparing two versions of a file.
+
+        Parameters:
+
+          current - ?
+          before
+          after
+          unified - If True, generate a unified diff
+          inline - Generate a one line summary line.
+        """
+        self._check_version_location()
+        bn = 0
+        an = 0
+        if not after.n:    # we're at the original file
+            pass
+        elif not before.n: # we're at a pseudo-root state
+            an = after.n
+        else:
+            bn = before.n
+            an = after.n
+        key = "%s-%s-pd-%s"%(bn,an,unified)
+        needs_oneline = inline and key not in self.diff_has_oneline
+        if key in self.diffs and not needs_oneline:
+            return self.diffs[key]
+
+        if not after.n:    # we're at the original file
+            before_lines = []
+            after_lines = self._get_lines(None)
+
+            before_name = 'n/a'
+            before_time = ''
+            after_name = 'Original'
+            after_time = ''
+        elif not before.n: # we're at a pseudo-root state
+            before_lines = self._get_lines(None)
+            after_lines = self._get_lines(after)
+
+            before_name = 'Original'
+            before_time = ''
+            after_name = str(after.n)
+            after_time = self._fmt_time(after.time)
+        else:
+            before_lines = self._get_lines(before)
+            after_lines = self._get_lines(after)
+
+            before_name = str(before.n)
+            before_time = self._fmt_time(before.time)
+            after_name = str(after.n)
+            after_time = self._fmt_time(after.time)
+
+        if unified:
+            self.diffs[key] = list(difflib.unified_diff(before_lines, after_lines,
+                                             before_name, after_name,
+                                             before_time, after_time))
+        elif inline:
+            maxwidth = int(util.vim().eval("winwidth(0)"))
+            self.diffs[key] = diff.one_line_diff_str('\n'.join(before_lines),'\n'.join(after_lines),maxwidth)
+            self.diff_has_oneline[key] = True
+        else:
+            self.diffs[key] = ""
+
+        return self.diffs[key]

+ 34 - 0
vim/autoload/mundo/util.py

@@ -0,0 +1,34 @@
+# import vim
+
+normal = lambda s: vim().command('normal %s' % s)
+normal_silent = lambda s: vim().command('silent! normal %s' % s)
+
+def vim():
+    """ call Vim.
+    
+    This is wrapped so that it can easily be mocked.
+    """
+    import vim
+    return vim
+
+def _goto_window_for_buffer(b):
+    w = int(vim().eval('bufwinnr(%d)' % int(b)))
+    vim().command('%dwincmd w' % int(w))
+
+def _goto_window_for_buffer_name(bn):
+    b = vim().eval('bufnr("%s")' % bn)
+    return _goto_window_for_buffer(b)
+
+# Rendering utility functions
+def _output_preview_text(lines):
+    _goto_window_for_buffer_name('__Mundo_Preview__')
+    vim().command('setlocal modifiable')
+    vim().current.buffer[:] = [line.rstrip() for line in lines]
+    vim().command('setlocal nomodifiable')
+
+def _undo_to(n):
+    n = int(n)
+    if n == 0:
+        vim().command('silent earlier %s' % (int(vim().eval('&undolevels')) + 1))
+    else:
+        vim().command('silent undo %d' % int(n))

+ 124 - 0
vim/autoload/mundo/util.vim

@@ -0,0 +1,124 @@
+let s:save_cpo = &cpo
+set cpo&vim
+
+if exists('g:Mundo_PluginLoaded')
+    finish
+endif
+
+function! mundo#util#set_default(var, val, ...)  "{{{
+    if !exists(a:var)
+        let {a:var} = a:val
+    endif
+    let old_var = get(a:000, 0, '')
+    if exists(old_var)
+        echohl WarningMsg
+        echomsg "{".old_var."}is deprecated! Please change your setting to {"
+                    \.split(old_var,':')[0]
+                    \.':'
+                    \.substitute(split(old_var,':')[1],'gundo_','mundo_','g')
+                    \.'}'
+        echohl None
+    endif
+endfunction"}}}
+
+call mundo#util#set_default(
+            \ 'g:mundo_python_path_setup', 0,
+            \ 'g:gundo_python_path_setup')
+
+call mundo#util#set_default(
+            \ 'g:mundo_first_visible_line', 0,
+            \ 'g:gundo_first_visible_line')
+
+call mundo#util#set_default(
+            \ 'g:mundo_last_visible_line', 0,
+            \ 'g:gundo_last_visible_line')
+
+call mundo#util#set_default(
+            \ 'g:mundo_width', 45,
+            \ 'g:gundo_width')
+
+call mundo#util#set_default(
+            \ 'g:mundo_preview_height', 15,
+            \ 'g:gundo_preview_height')
+
+call mundo#util#set_default(
+            \ 'g:mundo_preview_bottom', 0,
+            \ 'g:gundo_preview_bottom')
+
+call mundo#util#set_default(
+            \ 'g:mundo_right', 0,
+            \ 'g:gundo_right')
+
+call mundo#util#set_default(
+            \ 'g:mundo_help', 0,
+            \ 'g:gundo_help')
+
+call mundo#util#set_default(
+            \ 'g:mundo_map_move_older', 'j',
+            \ 'g:gundo_map_move_older')
+
+call mundo#util#set_default(
+            \ 'g:mundo_map_move_newer', 'k',
+            \ 'g:gundo_map_move_newer')
+
+call mundo#util#set_default(
+            \ 'g:mundo_close_on_revert', 0,
+            \ 'g:gundo_close_on_revert')
+
+call mundo#util#set_default(
+            \ 'g:mundo_prefer_python3', 0,
+            \ 'g:gundo_prefer_python3')
+
+call mundo#util#set_default(
+            \ 'g:mundo_auto_preview', 1,
+            \ 'g:gundo_auto_preview')
+
+call mundo#util#set_default(
+            \ 'g:mundo_verbose_graph', 1,
+            \ 'g:gundo_verbose_graph')
+
+call mundo#util#set_default(
+            \ 'g:mundo_playback_delay', 60,
+            \ 'g:gundo_playback_delay')
+
+call mundo#util#set_default(
+            \ 'g:mundo_mirror_graph', 0,
+            \ 'g:gundo_mirror_graph')
+
+call mundo#util#set_default(
+            \ 'g:mundo_inline_undo', 0,
+            \ 'g:gundo_inline_undo')
+
+call mundo#util#set_default(
+            \ 'g:mundo_return_on_revert', 1,
+            \ 'g:gundo_return_on_revert')
+
+function! mundo#util#init() abort
+
+endfunction
+
+func! mundo#util#MundoToggle()
+    echohl WarningMsg
+    echomsg "GundoToggle commands are deprecated. Please change to their corresponding MundoToggle command"
+    echohl None
+endf
+func! mundo#util#MundoShow()
+    echohl WarningMsg
+    echomsg "GundoShow commands are deprecated. Please change to their corresponding MundoShow command"
+    echohl None
+endf
+func! mundo#util#MundoHide()
+    echohl WarningMsg
+    echomsg "GundoHide commands are deprecated. Please change to their corresponding MundoHide command"
+    echohl None
+endf
+func! mundo#util#MundoRenderGraph()
+    echohl WarningMsg
+    echomsg "GundoRenderGraph commands are deprecated. Please change to their corresponding MundoRenderGraph command"
+    echohl None
+endf
+
+let g:Mundo_PluginLoaded = 1
+
+let &cpo = s:save_cpo
+unlet s:save_cpo

+ 150 - 74
vim/doc/gundo.txt → vim/doc/mundo.txt

@@ -1,32 +1,38 @@
-*gundo.txt*   Graph your undo tree so you can actually USE it.
+*mundo.txt*   Graph your undo tree so you can actually USE it.
 
 
 Making Vim's undo tree usable by humans.
 Making Vim's undo tree usable by humans.
 
 
 ==============================================================================
 ==============================================================================
-CONTENTS                                                      *Gundo-contents*
-
-    1. Intro .......................... |GundoIntro|
-    2. Usage .......................... |GundoUsage|
-    3. Configuration .................. |GundoConfig|
-        3.1 gundo_width ............... |gundo_width|
-        3.2 gundo_preview_height ...... |gundo_preview_height|
-        3.3 gundo_preview_bottom ...... |gundo_preview_bottom|
-        3.4 gundo_right ............... |gundo_right|
-        3.5 gundo_help ................ |gundo_help|
-        3.6 gundo_disable ............. |gundo_disable|
-        3.7 gundo_map_move_older ...... |gundo_map_move_older|
-            gundo_map_move_newer ...... |gundo_map_move_newer|
-        3.8 gundo_close_on_revert ..... |gundo_close_on_revert|
-        3.9 gundo_preview_statusline .. |gundo_preview_statusline|
-            gundo_tree_statusline ..... |gundo_tree_statusline|
-    4. License ........................ |GundoLicense|
-    5. Bugs ........................... |GundoBugs|
-    6. Contributing ................... |GundoContributing|
-    7. Changelog ...................... |GundoChangelog|
-    8. Credits ........................ |GundoCredits|
+CONTENTS                                                      *Mundo-contents*
+
+    1. Intro ........................... |MundoIntro|
+    2. Usage ........................... |MundoUsage|
+    3. Configuration ................... |MundoConfig|
+        3.1  mundo_width ............... |mundo_width|
+        3.2  mundo_preview_height ...... |mundo_preview_height|
+        3.3  mundo_preview_bottom ...... |mundo_preview_bottom|
+        3.4  mundo_right ............... |mundo_right|
+        3.5  mundo_help ................ |mundo_help|
+        3.6  mundo_disable ............. |mundo_disable|
+        3.7  mundo_map_move_older ...... |mundo_map_move_older|
+             mundo_map_move_newer ...... |mundo_map_move_newer|
+        3.8  mundo_close_on_revert ..... |mundo_close_on_revert|
+        3.9  mundo_preview_statusline .. |mundo_preview_statusline|
+             mundo_tree_statusline ..... |mundo_tree_statusline|
+        3.10 mundo_auto_preview ........ |mundo_auto_preview|
+        3.11 mundo_verbose_graph ....... |mundo_verbose_graph|
+        3.12 mundo_playback_delay ...... |mundo_playback_delay|
+        3.13 mundo_mirror_graph ........ |mundo_mirror_graph|
+        3.14 mundo_inline_undo ......... |mundo_inline_undo|
+        3.15 mundo_return_on_revert .... |mundo_return_on_revert|
+    4. License ......................... |MundoLicense|
+    5. Bugs ............................ |MundoBugs|
+    6. Contributing .................... |MundoContributing|
+    7. Changelog ....................... |MundoChangelog|
+    8. Credits ......................... |MundoCredits|
 
 
 ==============================================================================
 ==============================================================================
-1. Intro                                                          *GundoIntro*
+1. Intro                                                          *MundoIntro*
 
 
 You know that Vim lets you undo changes like any text editor. What you might
 You know that Vim lets you undo changes like any text editor. What you might
 not know is that it doesn't just keep a list of your changes -- it keeps
 not know is that it doesn't just keep a list of your changes -- it keeps
@@ -40,17 +46,17 @@ The problem is that trying to do this in the real world is painful. Vim gives
 you an |:undolist| command that shows you the leaves of the tree. Good luck
 you an |:undolist| command that shows you the leaves of the tree. Good luck
 finding the change you want in that list.
 finding the change you want in that list.
 
 
-Gundo is a plugin to make browsing this ridiculously powerful undo tree less
+Mundo is a plugin to make browsing this ridiculously powerful undo tree less
 painful.
 painful.
 
 
 ==============================================================================
 ==============================================================================
-2. Usage                                                          *GundoUsage*
+2. Usage                                                          *MundoUsage*
 
 
 We'll get to the technical details later, but if you're a human the first
 We'll get to the technical details later, but if you're a human the first
 thing you need to do is add a mapping to your |:vimrc| to toggle the undo
 thing you need to do is add a mapping to your |:vimrc| to toggle the undo
 graph: >
 graph: >
 
 
-    nnoremap <F5> :GundoToggle<CR>
+    nnoremap <F5> :MundoToggle<CR>
 
 
 Change the mapped key to suit your taste. We'll stick with F5 because that's
 Change the mapped key to suit your taste. We'll stick with F5 because that's
 what the author uses.
 what the author uses.
@@ -60,7 +66,7 @@ look something like this: >
 
 
       Undo graph                          File
       Undo graph                          File
     +-----------------------------------+------------------------------------+
     +-----------------------------------+------------------------------------+
-    | " Gundo for something.txt [1]     |one                                 |
+    | " Mundo for something.txt [1]     |one                                 |
     | " j/k  - move between undo states |two                                 |
     | " j/k  - move between undo states |two                                 |
     | " <cr> - revert to that state     |three                               |
     | " <cr> - revert to that state     |three                               |
     |                                   |five                                |
     |                                   |five                                |
@@ -70,7 +76,7 @@ look something like this: >
     | | |                               |                                    |
     | | |                               |                                    |
     | o |  [3] 4 hours ago              |                                    |
     | o |  [3] 4 hours ago              |                                    |
     | | |                               |                                    |
     | | |                               |                                    |
-    | o |  [2] 4 hours ago              |                                    |
+    | w |  [2] 4 hours ago              |                                    |
     | |/                                |                                    |
     | |/                                |                                    |
     | o  [1] 4 hours ago                |                                    |
     | o  [1] 4 hours ago                |                                    |
     | |                                 |                                    |
     | |                                 |                                    |
@@ -86,10 +92,11 @@ look something like this: >
     +-----------------------------------+------------------------------------+
     +-----------------------------------+------------------------------------+
       Preview pane
       Preview pane
 
 
-Your current position in the undo tree is marked with an '@' character. Other
-nodes are marked with an 'o' character.
+Your current position in the undo tree is marked with an '@' character. Undo
+positions that were saved to disk are marked with a 'w'. Other nodes are marked
+with an 'o' character.
 
 
-When you toggle open the graph Gundo will put your cursor on your current
+When you toggle open the graph Mundo will put your cursor on your current
 position in the tree. You can move up and down the graph with the j and
 position in the tree. You can move up and down the graph with the j and
 k keys.
 k keys.
 
 
@@ -116,31 +123,31 @@ Pressing q while in the undo graph will close it.  You can also just press your
 toggle mapping key.
 toggle mapping key.
 
 
 ==============================================================================
 ==============================================================================
-3. Configuration                                                 *GundoConfig*
+3. Configuration                                                 *MundoConfig*
 
 
-You can tweak the behavior of Gundo by setting a few variables in your :vimrc
+You can tweak the behavior of Mundo by setting a few variables in your :vimrc
 file. For example: >
 file. For example: >
 
 
-    let g:gundo_width = 60
-    let g:gundo_preview_height = 40
-    let g:gundo_right = 1
+    let g:mundo_width = 60
+    let g:mundo_preview_height = 40
+    let g:mundo_right = 1
 
 
 ------------------------------------------------------------------------------
 ------------------------------------------------------------------------------
-3.1 g:gundo_width                                                *gundo_width*
+3.1 g:mundo_width                                                *mundo_width*
 
 
-Set the horizontal width of the Gundo graph (and preview).
+Set the horizontal width of the Mundo graph (and preview).
 
 
 Default: 45
 Default: 45
 
 
 ------------------------------------------------------------------------------
 ------------------------------------------------------------------------------
-3.2 g:gundo_preview_height                              *gundo_preview_height*
+3.2 g:mundo_preview_height                              *mundo_preview_height*
 
 
-Set the vertical height of the Gundo preview.
+Set the vertical height of the Mundo preview.
 
 
 Default: 15
 Default: 15
 
 
 ------------------------------------------------------------------------------
 ------------------------------------------------------------------------------
-3.3 g:gundo_preview_bottom                              *gundo_preview_bottom*
+3.3 g:mundo_preview_bottom                              *mundo_preview_bottom*
 
 
 Force the preview window below current windows instead of below the graph.
 Force the preview window below current windows instead of below the graph.
 This gives the preview window more space to show the unified diff.
 This gives the preview window more space to show the unified diff.
@@ -157,78 +164,147 @@ Example:
 Default: 0
 Default: 0
 
 
 ------------------------------------------------------------------------------
 ------------------------------------------------------------------------------
-3.4 g:gundo_right                                                *gundo_right*
+3.4 g:mundo_right                                                *mundo_right*
 
 
-Set this to 1 to make the Gundo graph (and preview) open on the right side
+Set this to 1 to make the Mundo graph (and preview) open on the right side
 instead of the left.
 instead of the left.
 
 
 Default: 0 (off, open on the left side)
 Default: 0 (off, open on the left side)
 
 
 ------------------------------------------------------------------------------
 ------------------------------------------------------------------------------
-3.5 g:gundo_help                                                  *gundo_help*
+3.5 g:mundo_help                                                  *mundo_help*
 
 
-Set this to 0 to disable the help text in the Gundo graph window.
+Set this to 0 to disable the help text in the Mundo graph window.
 
 
 Default: 1 (show the help)
 Default: 1 (show the help)
 
 
 ------------------------------------------------------------------------------
 ------------------------------------------------------------------------------
-3.6 g:gundo_disable                                            *gundo_disable*
+3.6 g:mundo_disable                                            *mundo_disable*
 
 
-Set this to 1 to disable Gundo entirely.
+Set this to 1 to disable Mundo entirely.
 
 
 Useful if you use the same ~/.vim folder on multiple machines, and some of
 Useful if you use the same ~/.vim folder on multiple machines, and some of
 them may not have Python support.
 them may not have Python support.
 
 
-Default: 0 (Gundo is enabled as usual)
+Default: 0 (Mundo is enabled as usual)
 
 
 ------------------------------------------------------------------------------
 ------------------------------------------------------------------------------
-3.7 g:gundo_map_move_older, g:gundo_map_move_newer      *gundo_map_move_older*
-                                                        *gundo_map_move_newer*
+3.7 g:mundo_map_move_older, g:mundo_map_move_newer      *mundo_map_move_older*
+                                                        *mundo_map_move_newer*
 
 
 These options let you change the keys that navigate the undo graph. This is
 These options let you change the keys that navigate the undo graph. This is
 useful if you use a Dvorak keyboard and have changed your movement keys.
 useful if you use a Dvorak keyboard and have changed your movement keys.
 
 
-Default: gundo_map_move_older = "j"
-         gundo_map_move_newer = "k"
+Default: mundo_map_move_older = "j"
+         mundo_map_move_newer = "k"
 
 
 ------------------------------------------------------------------------------
 ------------------------------------------------------------------------------
-3.8 g:gundo_close_on_revert                            *gundo_close_on_revert*
+3.8 g:mundo_close_on_revert                            *mundo_close_on_revert*
 
 
-Set this to 1 to automatically close the Gundo windows when reverting.
+Set this to 1 to automatically close the Mundo windows when reverting.
 
 
 Default: 0 (windows do not automatically close)
 Default: 0 (windows do not automatically close)
 
 
 ------------------------------------------------------------------------------
 ------------------------------------------------------------------------------
-3.9 g:gundo_preview_statusline                      *gundo_preview_statusline*
-    g:gundo_tree_statusline                            *gundo_tree_statusline*
+3.9 g:mundo_preview_statusline                      *mundo_preview_statusline*
+    g:mundo_tree_statusline                            *mundo_tree_statusline*
 
 
-Set these to a string to display it as the status line for each Gundo window.
+Set these to a string to display it as the status line for each Mundo window.
 
 
 Default: unset (windows use the default statusline)
 Default: unset (windows use the default statusline)
 
 
+------------------------------------------------------------------------------
+3.10 g:mundo_auto_preview                                 *mundo_auto_preview*
+
+Set this to 0 to disable automatically rendering preview diffs as you move
+through the undo tree (you can still render a specific diff with r).  This can
+be useful on large files and undo trees to speed up Mundo.
+
+Default: 1 (automatically preview diffs)
+
+------------------------------------------------------------------------------
+3.11 g:mundo_verbose_graph                               *mundo_verbose_graph*
+
+Set this to 0 to create shorter graphs: the 'o' characters will only be used
+when multiple branches exist, and extra lines of '|' are suppressed making for
+a graph half as long.
+
+Default: 1 (verbose graphs)
+
+------------------------------------------------------------------------------
+3.12 g:mundo_playback_delay                             *mundo_playback_delay*
+
+This is the delay in milliseconds between each change when running 'play to'
+mode. Set this to a higher number for a slower playback or to a lower number
+for a faster playback.
+
+Default: 60
+
+------------------------------------------------------------------------------
+3.13 g:mundo_mirror_graph                                *mundo_mirror_graph*
+
+Set this to 0 to align the graph to the left; set to 1 to align to the right.
+
+Default: 1 (mirror graph)
+
+------------------------------------------------------------------------------
+3.14 g:mundo_inline_graph                                *mundo_inline_undo*
+
+When enabled, a small one line diff is displayed to the right of the graph undo.
+Although not as detailed as a full diff provided in the preview window, it
+provides a quick summary of the diff w/o having to navigate.
+
+Default: 0 (no inline graph)
+
+------------------------------------------------------------------------------
+3.15 g:mundo_return_on_revert                         *mundo_return_on_revert*
+
+Set this to 0 to keep focus in the Mundo window after a revert.
+
+Default: 1
+
 ==============================================================================
 ==============================================================================
-4. License                                                      *GundoLicense*
+4. License                                                      *MundoLicense*
 
 
 GPLv2+. Look it up.
 GPLv2+. Look it up.
 
 
 ==============================================================================
 ==============================================================================
-5. Bugs                                                            *GundoBugs*
+5. Bugs                                                            *MundoBugs*
 
 
 If you find a bug please post it on the issue tracker:
 If you find a bug please post it on the issue tracker:
-http://bitbucket.org/sjl/gundo.vim/issues?status=new&status=open
+
+https://github.com/simnalamburt/vim-mundo/issues
 
 
 ==============================================================================
 ==============================================================================
-6. Contributing                                            *GundoContributing*
+6. Contributing                                            *MundoContributing*
 
 
 Think you can make this plugin better? Awesome. Fork it on BitBucket or GitHub
 Think you can make this plugin better? Awesome. Fork it on BitBucket or GitHub
 and send a pull request.
 and send a pull request.
 
 
-BitBucket: http://bitbucket.org/sjl/gundo.vim/
-GitHub: http://github.com/sjl/gundo.vim/
+GitHub: https://github.com/simnalamburt/vim-mundo
 
 
 ==============================================================================
 ==============================================================================
-7. Changelog                                                  *GundoChangelog*
-
+7. Changelog                                                  *MundoChangelog*
+
+v3.0.0
+    * Rename plugin
+    * Add one line diff in __Mundo__ window.
+    * Adds g:mundo_mirror_graph setting.
+    * Adds g:mundo_return_on_revert setting.
+    * Adds ability to toggle help on/off in __Mundo__ window.
+    * Adds J and K commands (navigate between written undos)
+    * Adds /, n and N commands (search undos)
+    * Adds g:mundo_verbose_graph setting.
+    * Adds g:mundo_mirror_graph setting.
+    * Show written undos in the Mundo graph.
+    * Adds live reload of Mundo as you edit.
+v2.5.0
+    * Fix the help window to take custom mappings into account.
+    * Add g:mundo_playback_delay option.
+v2.4.0
+    * Add auto preview option.
+    * Add 'r' mapping to preview current state.
+    * Add public mundo#MundoShow() and mundo#MundoHide() functions.
 v2.3.0
 v2.3.0
     * Add statusline configuration.
     * Add statusline configuration.
 v2.2.2
 v2.2.2
@@ -236,23 +312,23 @@ v2.2.2
 v2.2.1
 v2.2.1
     * Refactoring and performance improvements.
     * Refactoring and performance improvements.
 v2.2.0
 v2.2.0
-    * Add the g:gundo_close_on_revert setting.
+    * Add the g:mundo_close_on_revert setting.
     * Fix a bug with the splitbelow setting.
     * Fix a bug with the splitbelow setting.
 v2.1.1
 v2.1.1
     * Fix a bug with the movement key mappings.
     * Fix a bug with the movement key mappings.
 v2.1.0
 v2.1.0
     * Warnings about having an incompatible Vim and/or Python installation
     * Warnings about having an incompatible Vim and/or Python installation
-      are now deferred until the first time you try to use Gundo, instead
+      are now deferred until the first time you try to use Mundo, instead
       of being displayed on launch.
       of being displayed on launch.
     * The <j> and <k> mappings are now configurable with
     * The <j> and <k> mappings are now configurable with
-      g:gundo_map_move_older and g:gundo_map_move_newer.
-    * The o, <Up> and <Down> keys are now mapped in the Gundo pane.
-    * Improve and add several unit tests for Gundo.
+      g:mundo_map_move_older and g:mundo_map_move_newer.
+    * The o, <Up> and <Down> keys are now mapped in the Mundo pane.
+    * Improve and add several unit tests for Mundo.
 v2.0.0
 v2.0.0
-    * Make GundoToggle close the Gundo windows if they're visible but not the
+    * Make MundoToggle close the Mundo windows if they're visible but not the
       current window, instead of moving to them.
       current window, instead of moving to them.
-    * Add the g:gundo_help setting.
-    * Add the g:gundo_disable setting.
+    * Add the g:mundo_help setting.
+    * Add the g:mundo_disable setting.
     * Add the 'p' mapping to preview the result of reverting to the selected
     * Add the 'p' mapping to preview the result of reverting to the selected
       state.
       state.
     * Fix movement commands with counts in the graph.
     * Fix movement commands with counts in the graph.
@@ -260,7 +336,7 @@ v1.0.0
     * Initial stable release.
     * Initial stable release.
 
 
 ==============================================================================
 ==============================================================================
-8. Credits                                                      *GundoCredits*
+8. Credits                                                      *MundoCredits*
 
 
 The graphing code was all taken from Mercurial, hence the GPLv2+ license.
 The graphing code was all taken from Mercurial, hence the GPLv2+ license.
 
 

+ 1 - 0
vim/init.vim

@@ -0,0 +1 @@
+../vimrc

+ 0 - 24
vim/plugin/gundo.vim

@@ -1,24 +0,0 @@
-" ============================================================================
-" File:        gundo.vim
-" Description: vim global plugin to visualize your undo tree
-" Maintainer:  Steve Losh <steve@stevelosh.com>
-" License:     GPLv2+ -- look it up.
-" Notes:       Much of this code was thiefed from Mercurial, and the rest was
-"              heavily inspired by scratch.vim and histwin.vim.
-"
-" ============================================================================
-
-
-"{{{ Init
-if !exists('g:gundo_debug') && (exists('g:gundo_disable') || exists('loaded_gundo') || &cp)"{{{
-    finish
-endif
-let loaded_gundo = 1"}}}
-"}}}
-
-"{{{ Misc
-command! -nargs=0 GundoToggle call gundo#GundoToggle()
-command! -nargs=0 GundoShow call gundo#GundoShow()
-command! -nargs=0 GundoHide call gundo#GundoHide()
-command! -nargs=0 GundoRenderGraph call gundo#GundoRenderGraph()
-"}}}

+ 28 - 0
vim/plugin/mundo.vim

@@ -0,0 +1,28 @@
+" ============================================================================
+" File:        mundo.vim
+" Description: vim global plugin to visualize your undo tree
+" Maintainer:  Hyeon Kim <simnalamburt@gmail.com>
+" License:     GPLv2+ -- look it up.
+" Notes:       Much of this code was thiefed from Mercurial, and the rest was
+"              heavily inspired by scratch.vim and histwin.vim.
+"
+" ============================================================================
+
+
+"{{{ Init
+if !exists('g:mundo_debug') && (exists('g:mundo_disable') && g:mundo_disable == 1 || exists('loaded_mundo') || &cp)"{{{
+    finish
+endif
+let loaded_mundo = 1"}}}
+"}}}
+
+"{{{ Misc
+command! -nargs=0 MundoToggle call mundo#MundoToggle()
+command! -nargs=0 MundoShow call mundo#MundoShow()
+command! -nargs=0 MundoHide call mundo#MundoHide()
+command! -nargs=0 MundoRenderGraph call mundo#MundoRenderGraph()
+command! -nargs=0 GundoToggle call mundo#util#MundoToggle()
+command! -nargs=0 GundoShow call mundo#util#MundoShow()
+command! -nargs=0 GundoHide call mundo#util#MundoHide()
+command! -nargs=0 GundoRenderGraph call mundo#util#MundoRenderGraph()
+"}}}

+ 4 - 1
vimrc

@@ -50,7 +50,9 @@ nmap <C-k> :lprevious<cr>
 vmap P "_dP
 vmap P "_dP
 inoremap <C-d> <C-t>
 inoremap <C-d> <C-t>
 inoremap <C-a> <C-d>
 inoremap <C-a> <C-d>
-nnoremap U :GundoToggle<CR>
+
+let g:mundo_prefer_python3 = 1
+nnoremap U :MundoToggle<CR>
 
 
 let g:syntastic_python_checkers = ['pyflakes']
 let g:syntastic_python_checkers = ['pyflakes']
 let g:syntastic_always_populate_loc_list = 1
 let g:syntastic_always_populate_loc_list = 1
@@ -74,6 +76,7 @@ au BufNewFile,BufRead *.ccss set ft=clevercss
 
 
 autocmd FileType c,cpp,perl,php,java,glsl set cindent foldmethod=syntax
 autocmd FileType c,cpp,perl,php,java,glsl set cindent foldmethod=syntax
 autocmd FileType python set autoindent foldmethod=indent
 autocmd FileType python set autoindent foldmethod=indent
+autocmd FileType ruby set foldmethod=indent foldnestmax=4
 autocmd FileType sh set autoindent foldmethod=syntax
 autocmd FileType sh set autoindent foldmethod=syntax
 autocmd FileType go set foldmethod=syntax foldlevel=0 foldnestmax=1
 autocmd FileType go set foldmethod=syntax foldlevel=0 foldnestmax=1
 autocmd FileType javascript,less,html set foldnestmax=3 formatoptions-=o
 autocmd FileType javascript,less,html set foldnestmax=3 formatoptions-=o