4312 lines
		
	
	
		
			165 KiB
		
	
	
	
		
			EmacsLisp
		
	
	
	
	
	
			
		
		
	
	
			4312 lines
		
	
	
		
			165 KiB
		
	
	
	
		
			EmacsLisp
		
	
	
	
	
	
| ;;; undo-tree.el --- Treat undo history as a tree  -*- lexical-binding: t; -*-
 | ||
| 
 | ||
| ;; Copyright (C) 2009-2013  Free Software Foundation, Inc
 | ||
| 
 | ||
| ;; Author: Toby Cubitt <toby-undo-tree@dr-qubit.org>
 | ||
| ;; Version: 0.6.4
 | ||
| ;; Keywords: convenience, files, undo, redo, history, tree
 | ||
| ;; URL: http://www.dr-qubit.org/emacs.php
 | ||
| ;; Repository: http://www.dr-qubit.org/git/undo-tree.git
 | ||
| 
 | ||
| ;; This file is part of Emacs.
 | ||
| ;;
 | ||
| ;; This file is free software: you can redistribute it and/or modify it under
 | ||
| ;; the terms of the GNU General Public License as published by the Free
 | ||
| ;; Software Foundation, either version 3 of the License, or (at your option)
 | ||
| ;; any later version.
 | ||
| ;;
 | ||
| ;; This program is distributed in the hope that it will be useful, but WITHOUT
 | ||
| ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | ||
| ;; FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 | ||
| ;; more details.
 | ||
| ;;
 | ||
| ;; You should have received a copy of the GNU General Public License along
 | ||
| ;; with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 | ||
| 
 | ||
| 
 | ||
| ;;; Commentary:
 | ||
| ;;
 | ||
| ;; Emacs has a powerful undo system. Unlike the standard undo/redo system in
 | ||
| ;; most software, it allows you to recover *any* past state of a buffer
 | ||
| ;; (whereas the standard undo/redo system can lose past states as soon as you
 | ||
| ;; redo). However, this power comes at a price: many people find Emacs' undo
 | ||
| ;; system confusing and difficult to use, spawning a number of packages that
 | ||
| ;; replace it with the less powerful but more intuitive undo/redo system.
 | ||
| ;;
 | ||
| ;; Both the loss of data with standard undo/redo, and the confusion of Emacs'
 | ||
| ;; undo, stem from trying to treat undo history as a linear sequence of
 | ||
| ;; changes. It's not. The `undo-tree-mode' provided by this package replaces
 | ||
| ;; Emacs' undo system with a system that treats undo history as what it is: a
 | ||
| ;; branching tree of changes. This simple idea allows the more intuitive
 | ||
| ;; behaviour of the standard undo/redo system to be combined with the power of
 | ||
| ;; never losing any history. An added side bonus is that undo history can in
 | ||
| ;; some cases be stored more efficiently, allowing more changes to accumulate
 | ||
| ;; before Emacs starts discarding history.
 | ||
| ;;
 | ||
| ;; The only downside to this more advanced yet simpler undo system is that it
 | ||
| ;; was inspired by Vim. But, after all, most successful religions steal the
 | ||
| ;; best ideas from their competitors!
 | ||
| ;;
 | ||
| ;;
 | ||
| ;; Installation
 | ||
| ;; ============
 | ||
| ;;
 | ||
| ;; This package has only been tested with Emacs versions 24 and CVS. It should
 | ||
| ;; work in Emacs versions 22 and 23 too, but will not work without
 | ||
| ;; modifications in earlier versions of Emacs.
 | ||
| ;;
 | ||
| ;; To install `undo-tree-mode', make sure this file is saved in a directory in
 | ||
| ;; your `load-path', and add the line:
 | ||
| ;;
 | ||
| ;;   (require 'undo-tree)
 | ||
| ;;
 | ||
| ;; to your .emacs file. Byte-compiling undo-tree.el is recommended (e.g. using
 | ||
| ;; "M-x byte-compile-file" from within emacs).
 | ||
| ;;
 | ||
| ;; If you want to replace the standard Emacs' undo system with the
 | ||
| ;; `undo-tree-mode' system in all buffers, you can enable it globally by
 | ||
| ;; adding:
 | ||
| ;;
 | ||
| ;;   (global-undo-tree-mode)
 | ||
| ;;
 | ||
| ;; to your .emacs file.
 | ||
| ;;
 | ||
| ;;
 | ||
| ;; Quick-Start
 | ||
| ;; ===========
 | ||
| ;;
 | ||
| ;; If you're the kind of person who likes to jump in the car and drive,
 | ||
| ;; without bothering to first figure out whether the button on the left dips
 | ||
| ;; the headlights or operates the ejector seat (after all, you'll soon figure
 | ||
| ;; it out when you push it), then here's the minimum you need to know:
 | ||
| ;;
 | ||
| ;; `undo-tree-mode' and `global-undo-tree-mode'
 | ||
| ;;   Enable undo-tree mode (either in the current buffer or globally).
 | ||
| ;;
 | ||
| ;; C-_  C-/  (`undo-tree-undo')
 | ||
| ;;   Undo changes.
 | ||
| ;;
 | ||
| ;; M-_  C-?  (`undo-tree-redo')
 | ||
| ;;   Redo changes.
 | ||
| ;;
 | ||
| ;; `undo-tree-switch-branch'
 | ||
| ;;   Switch undo-tree branch.
 | ||
| ;;   (What does this mean? Better press the button and see!)
 | ||
| ;;
 | ||
| ;; C-x u  (`undo-tree-visualize')
 | ||
| ;;   Visualize the undo tree.
 | ||
| ;;   (Better try pressing this button too!)
 | ||
| ;;
 | ||
| ;; C-x r u  (`undo-tree-save-state-to-register')
 | ||
| ;;   Save current buffer state to register.
 | ||
| ;;
 | ||
| ;; C-x r U  (`undo-tree-restore-state-from-register')
 | ||
| ;;   Restore buffer state from register.
 | ||
| ;;
 | ||
| ;;
 | ||
| ;;
 | ||
| ;; In the undo-tree visualizer:
 | ||
| ;;
 | ||
| ;; <up>  p  C-p  (`undo-tree-visualize-undo')
 | ||
| ;;   Undo changes.
 | ||
| ;;
 | ||
| ;; <down>  n  C-n  (`undo-tree-visualize-redo')
 | ||
| ;;   Redo changes.
 | ||
| ;;
 | ||
| ;; <left>  b  C-b  (`undo-tree-visualize-switch-branch-left')
 | ||
| ;;   Switch to previous undo-tree branch.
 | ||
| ;;
 | ||
| ;; <right>  f  C-f  (`undo-tree-visualize-switch-branch-right')
 | ||
| ;;   Switch to next undo-tree branch.
 | ||
| ;;
 | ||
| ;; C-<up>  M-{  (`undo-tree-visualize-undo-to-x')
 | ||
| ;;   Undo changes up to last branch point.
 | ||
| ;;
 | ||
| ;; C-<down>  M-}  (`undo-tree-visualize-redo-to-x')
 | ||
| ;;   Redo changes down to next branch point.
 | ||
| ;;
 | ||
| ;; <down>  n  C-n  (`undo-tree-visualize-redo')
 | ||
| ;;   Redo changes.
 | ||
| ;;
 | ||
| ;; <mouse-1>  (`undo-tree-visualizer-mouse-set')
 | ||
| ;;   Set state to node at mouse click.
 | ||
| ;;
 | ||
| ;; t  (`undo-tree-visualizer-toggle-timestamps')
 | ||
| ;;   Toggle display of time-stamps.
 | ||
| ;;
 | ||
| ;; d  (`undo-tree-visualizer-toggle-diff')
 | ||
| ;;   Toggle diff display.
 | ||
| ;;
 | ||
| ;; s  (`undo-tree-visualizer-selection-mode')
 | ||
| ;;   Toggle keyboard selection mode.
 | ||
| ;;
 | ||
| ;; q  (`undo-tree-visualizer-quit')
 | ||
| ;;   Quit undo-tree-visualizer.
 | ||
| ;;
 | ||
| ;; C-q  (`undo-tree-visualizer-abort')
 | ||
| ;;   Abort undo-tree-visualizer.
 | ||
| ;;
 | ||
| ;; ,  <
 | ||
| ;;   Scroll left.
 | ||
| ;;
 | ||
| ;; .  >
 | ||
| ;;   Scroll right.
 | ||
| ;;
 | ||
| ;; <pgup>  M-v
 | ||
| ;;   Scroll up.
 | ||
| ;;
 | ||
| ;; <pgdown>  C-v
 | ||
| ;;   Scroll down.
 | ||
| ;;
 | ||
| ;;
 | ||
| ;;
 | ||
| ;; In visualizer selection mode:
 | ||
| ;;
 | ||
| ;; <up>  p  C-p  (`undo-tree-visualizer-select-previous')
 | ||
| ;;   Select previous node.
 | ||
| ;;
 | ||
| ;; <down>  n  C-n  (`undo-tree-visualizer-select-next')
 | ||
| ;;   Select next node.
 | ||
| ;;
 | ||
| ;; <left>  b  C-b  (`undo-tree-visualizer-select-left')
 | ||
| ;;   Select left sibling node.
 | ||
| ;;
 | ||
| ;; <right>  f  C-f  (`undo-tree-visualizer-select-right')
 | ||
| ;;   Select right sibling node.
 | ||
| ;;
 | ||
| ;; <pgup>  M-v
 | ||
| ;;   Select node 10 above.
 | ||
| ;;
 | ||
| ;; <pgdown>  C-v
 | ||
| ;;   Select node 10 below.
 | ||
| ;;
 | ||
| ;; <enter>  (`undo-tree-visualizer-set')
 | ||
| ;;   Set state to selected node and exit selection mode.
 | ||
| ;;
 | ||
| ;; s  (`undo-tree-visualizer-mode')
 | ||
| ;;   Exit selection mode.
 | ||
| ;;
 | ||
| ;; t  (`undo-tree-visualizer-toggle-timestamps')
 | ||
| ;;   Toggle display of time-stamps.
 | ||
| ;;
 | ||
| ;; d  (`undo-tree-visualizer-toggle-diff')
 | ||
| ;;   Toggle diff display.
 | ||
| ;;
 | ||
| ;; q  (`undo-tree-visualizer-quit')
 | ||
| ;;   Quit undo-tree-visualizer.
 | ||
| ;;
 | ||
| ;; C-q  (`undo-tree-visualizer-abort')
 | ||
| ;;   Abort undo-tree-visualizer.
 | ||
| ;;
 | ||
| ;; ,  <
 | ||
| ;;   Scroll left.
 | ||
| ;;
 | ||
| ;; .  >
 | ||
| ;;   Scroll right.
 | ||
| ;;
 | ||
| ;;
 | ||
| ;;
 | ||
| ;; Persistent undo history:
 | ||
| ;;
 | ||
| ;; Note: Requires Emacs version 24.3 or higher.
 | ||
| ;;
 | ||
| ;; `undo-tree-auto-save-history' (variable)
 | ||
| ;;    automatically save and restore undo-tree history along with buffer
 | ||
| ;;    (disabled by default)
 | ||
| ;;
 | ||
| ;; `undo-tree-save-history' (command)
 | ||
| ;;    manually save undo history to file
 | ||
| ;;
 | ||
| ;; `undo-tree-load-history' (command)
 | ||
| ;;    manually load undo history from file
 | ||
| ;;
 | ||
| ;;
 | ||
| ;;
 | ||
| ;; Compressing undo history:
 | ||
| ;;
 | ||
| ;;   Undo history files cannot grow beyond the maximum undo tree size, which
 | ||
| ;;   is limited by `undo-limit', `undo-strong-limit' and
 | ||
| ;;   `undo-outer-limit'. Nevertheless, undo history files can grow quite
 | ||
| ;;   large. If you want to automatically compress undo history, add the
 | ||
| ;;   following advice to your .emacs file (replacing ".gz" with the filename
 | ||
| ;;   extension of your favourite compression algorithm):
 | ||
| ;;
 | ||
| ;;   (defadvice undo-tree-make-history-save-file-name
 | ||
| ;;     (after undo-tree activate)
 | ||
| ;;     (setq ad-return-value (concat ad-return-value ".gz")))
 | ||
| ;;
 | ||
| ;;
 | ||
| ;;
 | ||
| ;;
 | ||
| ;; Undo Systems
 | ||
| ;; ============
 | ||
| ;;
 | ||
| ;; To understand the different undo systems, it's easiest to consider an
 | ||
| ;; example. Imagine you make a few edits in a buffer. As you edit, you
 | ||
| ;; accumulate a history of changes, which we might visualize as a string of
 | ||
| ;; past buffer states, growing downwards:
 | ||
| ;;
 | ||
| ;;                                o  (initial buffer state)
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                o  (first edit)
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                o  (second edit)
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                x  (current buffer state)
 | ||
| ;;
 | ||
| ;;
 | ||
| ;; Now imagine that you undo the last two changes. We can visualize this as
 | ||
| ;; rewinding the current state back two steps:
 | ||
| ;;
 | ||
| ;;                                o  (initial buffer state)
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                x  (current buffer state)
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                o
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                o
 | ||
| ;;
 | ||
| ;;
 | ||
| ;; However, this isn't a good representation of what Emacs' undo system
 | ||
| ;; does. Instead, it treats the undos as *new* changes to the buffer, and adds
 | ||
| ;; them to the history:
 | ||
| ;;
 | ||
| ;;                                o  (initial buffer state)
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                o  (first edit)
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                o  (second edit)
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                x  (buffer state before undo)
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                o  (first undo)
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                x  (second undo)
 | ||
| ;;
 | ||
| ;;
 | ||
| ;; Actually, since the buffer returns to a previous state after an undo,
 | ||
| ;; perhaps a better way to visualize it is to imagine the string of changes
 | ||
| ;; turning back on itself:
 | ||
| ;;
 | ||
| ;;        (initial buffer state)  o
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                  (first edit)  o  x  (second undo)
 | ||
| ;;                                |  |
 | ||
| ;;                                |  |
 | ||
| ;;                 (second edit)  o  o  (first undo)
 | ||
| ;;                                | /
 | ||
| ;;                                |/
 | ||
| ;;                                o  (buffer state before undo)
 | ||
| ;;
 | ||
| ;; Treating undos as new changes might seem a strange thing to do. But the
 | ||
| ;; advantage becomes clear as soon as we imagine what happens when you edit
 | ||
| ;; the buffer again. Since you've undone a couple of changes, new edits will
 | ||
| ;; branch off from the buffer state that you've rewound to. Conceptually, it
 | ||
| ;; looks like this:
 | ||
| ;;
 | ||
| ;;                                o  (initial buffer state)
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                o
 | ||
| ;;                                |\
 | ||
| ;;                                | \
 | ||
| ;;                                o  x  (new edit)
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                o
 | ||
| ;;
 | ||
| ;; The standard undo/redo system only lets you go backwards and forwards
 | ||
| ;; linearly. So as soon as you make that new edit, it discards the old
 | ||
| ;; branch. Emacs' undo just keeps adding changes to the end of the string. So
 | ||
| ;; the undo history in the two systems now looks like this:
 | ||
| ;;
 | ||
| ;;            Undo/Redo:                      Emacs' undo
 | ||
| ;;
 | ||
| ;;               o                                o
 | ||
| ;;               |                                |
 | ||
| ;;               |                                |
 | ||
| ;;               o                                o  o
 | ||
| ;;               .\                               |  |\
 | ||
| ;;               . \                              |  | \
 | ||
| ;;               .  x  (new edit)                 o  o  |
 | ||
| ;;   (discarded  .                                | /   |
 | ||
| ;;     branch)   .                                |/    |
 | ||
| ;;               .                                o     |
 | ||
| ;;                                                      |
 | ||
| ;;                                                      |
 | ||
| ;;                                                      x  (new edit)
 | ||
| ;;
 | ||
| ;; Now, what if you change your mind about those undos, and decide you did
 | ||
| ;; like those other changes you'd made after all? With the standard undo/redo
 | ||
| ;; system, you're lost. There's no way to recover them, because that branch
 | ||
| ;; was discarded when you made the new edit.
 | ||
| ;;
 | ||
| ;; However, in Emacs' undo system, those old buffer states are still there in
 | ||
| ;; the undo history. You just have to rewind back through the new edit, and
 | ||
| ;; back through the changes made by the undos, until you reach them. Of
 | ||
| ;; course, since Emacs treats undos (even undos of undos!) as new changes,
 | ||
| ;; you're really weaving backwards and forwards through the history, all the
 | ||
| ;; time adding new changes to the end of the string as you go:
 | ||
| ;;
 | ||
| ;;                       o
 | ||
| ;;                       |
 | ||
| ;;                       |
 | ||
| ;;                       o  o     o  (undo new edit)
 | ||
| ;;                       |  |\    |\
 | ||
| ;;                       |  | \   | \
 | ||
| ;;                       o  o  |  |  o  (undo the undo)
 | ||
| ;;                       | /   |  |  |
 | ||
| ;;                       |/    |  |  |
 | ||
| ;;      (trying to get   o     |  |  x  (undo the undo)
 | ||
| ;;       to this state)        | /
 | ||
| ;;                             |/
 | ||
| ;;                             o
 | ||
| ;;
 | ||
| ;; So far, this is still reasonably intuitive to use. It doesn't behave so
 | ||
| ;; differently to standard undo/redo, except that by going back far enough you
 | ||
| ;; can access changes that would be lost in standard undo/redo.
 | ||
| ;;
 | ||
| ;; However, imagine that after undoing as just described, you decide you
 | ||
| ;; actually want to rewind right back to the initial state. If you're lucky,
 | ||
| ;; and haven't invoked any command since the last undo, you can just keep on
 | ||
| ;; undoing until you get back to the start:
 | ||
| ;;
 | ||
| ;;      (trying to get   o              x  (got there!)
 | ||
| ;;       to this state)  |              |
 | ||
| ;;                       |              |
 | ||
| ;;                       o  o     o     o  (keep undoing)
 | ||
| ;;                       |  |\    |\    |
 | ||
| ;;                       |  | \   | \   |
 | ||
| ;;                       o  o  |  |  o  o  (keep undoing)
 | ||
| ;;                       | /   |  |  | /
 | ||
| ;;                       |/    |  |  |/
 | ||
| ;;      (already undid   o     |  |  o  (got this far)
 | ||
| ;;       to this state)        | /
 | ||
| ;;                             |/
 | ||
| ;;                             o
 | ||
| ;;
 | ||
| ;; But if you're unlucky, and you happen to have moved the point (say) after
 | ||
| ;; getting to the state labelled "got this far", then you've "broken the undo
 | ||
| ;; chain". Hold on to something solid, because things are about to get
 | ||
| ;; hairy. If you try to undo now, Emacs thinks you're trying to undo the
 | ||
| ;; undos! So to get back to the initial state you now have to rewind through
 | ||
| ;; *all* the changes, including the undos you just did:
 | ||
| ;;
 | ||
| ;;      (trying to get   o                          x  (finally got there!)
 | ||
| ;;       to this state)  |                          |
 | ||
| ;;                       |                          |
 | ||
| ;;                       o  o     o     o     o     o
 | ||
| ;;                       |  |\    |\    |\    |\    |
 | ||
| ;;                       |  | \   | \   | \   | \   |
 | ||
| ;;                       o  o  |  |  o  o  o  |  o  o
 | ||
| ;;                       | /   |  |  | /   |  |  | /
 | ||
| ;;                       |/    |  |  |/    |  |  |/
 | ||
| ;;      (already undid   o     |  |  o<.   |  |  o
 | ||
| ;;       to this state)        | /     :   | /
 | ||
| ;;                             |/      :   |/
 | ||
| ;;                             o       :   o
 | ||
| ;;                                     :
 | ||
| ;;                             (got this far, but
 | ||
| ;;                              broke the undo chain)
 | ||
| ;;
 | ||
| ;; Confused?
 | ||
| ;;
 | ||
| ;; In practice you can just hold down the undo key until you reach the buffer
 | ||
| ;; state that you want. But whatever you do, don't move around in the buffer
 | ||
| ;; to *check* that you've got back to where you want! Because you'll break the
 | ||
| ;; undo chain, and then you'll have to traverse the entire string of undos
 | ||
| ;; again, just to get back to the point at which you broke the
 | ||
| ;; chain. Undo-in-region and commands such as `undo-only' help to make using
 | ||
| ;; Emacs' undo a little easier, but nonetheless it remains confusing for many
 | ||
| ;; people.
 | ||
| ;;
 | ||
| ;;
 | ||
| ;; So what does `undo-tree-mode' do? Remember the diagram we drew to represent
 | ||
| ;; the history we've been discussing (make a few edits, undo a couple of them,
 | ||
| ;; and edit again)? The diagram that conceptually represented our undo
 | ||
| ;; history, before we started discussing specific undo systems? It looked like
 | ||
| ;; this:
 | ||
| ;;
 | ||
| ;;                                o  (initial buffer state)
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                o
 | ||
| ;;                                |\
 | ||
| ;;                                | \
 | ||
| ;;                                o  x  (current state)
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                o
 | ||
| ;;
 | ||
| ;; Well, that's *exactly* what the undo history looks like to
 | ||
| ;; `undo-tree-mode'.  It doesn't discard the old branch (as standard undo/redo
 | ||
| ;; does), nor does it treat undos as new changes to be added to the end of a
 | ||
| ;; linear string of buffer states (as Emacs' undo does). It just keeps track
 | ||
| ;; of the tree of branching changes that make up the entire undo history.
 | ||
| ;;
 | ||
| ;; If you undo from this point, you'll rewind back up the tree to the previous
 | ||
| ;; state:
 | ||
| ;;
 | ||
| ;;                                o
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                x  (undo)
 | ||
| ;;                                |\
 | ||
| ;;                                | \
 | ||
| ;;                                o  o
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                o
 | ||
| ;;
 | ||
| ;; If you were to undo again, you'd rewind back to the initial state. If on
 | ||
| ;; the other hand you redo the change, you'll end up back at the bottom of the
 | ||
| ;; most recent branch:
 | ||
| ;;
 | ||
| ;;                                o  (undo takes you here)
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                o  (start here)
 | ||
| ;;                                |\
 | ||
| ;;                                | \
 | ||
| ;;                                o  x  (redo takes you here)
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                o
 | ||
| ;;
 | ||
| ;; So far, this is just like the standard undo/redo system. But what if you
 | ||
| ;; want to return to a buffer state located on a previous branch of the
 | ||
| ;; history? Since `undo-tree-mode' keeps the entire history, you simply need
 | ||
| ;; to tell it to switch to a different branch, and then redo the changes you
 | ||
| ;; want:
 | ||
| ;;
 | ||
| ;;                                o
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                                o  (start here, but switch
 | ||
| ;;                                |\  to the other branch)
 | ||
| ;;                                | \
 | ||
| ;;                        (redo)  o  o
 | ||
| ;;                                |
 | ||
| ;;                                |
 | ||
| ;;                        (redo)  x
 | ||
| ;;
 | ||
| ;; Now you're on the other branch, if you undo and redo changes you'll stay on
 | ||
| ;; that branch, moving up and down through the buffer states located on that
 | ||
| ;; branch. Until you decide to switch branches again, of course.
 | ||
| ;;
 | ||
| ;; Real undo trees might have multiple branches and sub-branches:
 | ||
| ;;
 | ||
| ;;                                o
 | ||
| ;;                            ____|______
 | ||
| ;;                           /           \
 | ||
| ;;                          o             o
 | ||
| ;;                      ____|__         __|
 | ||
| ;;                     /    |  \       /   \
 | ||
| ;;                    o     o   o     o     x
 | ||
| ;;                    |               |
 | ||
| ;;                   / \             / \
 | ||
| ;;                  o   o           o   o
 | ||
| ;;
 | ||
| ;; Trying to imagine what Emacs' undo would do as you move about such a tree
 | ||
| ;; will likely frazzle your brain circuits! But in `undo-tree-mode', you're
 | ||
| ;; just moving around this undo history tree. Most of the time, you'll
 | ||
| ;; probably only need to stay on the most recent branch, in which case it
 | ||
| ;; behaves like standard undo/redo, and is just as simple to understand. But
 | ||
| ;; if you ever need to recover a buffer state on a different branch, the
 | ||
| ;; possibility of switching between branches and accessing the full undo
 | ||
| ;; history is still there.
 | ||
| ;;
 | ||
| ;;
 | ||
| ;;
 | ||
| ;; The Undo-Tree Visualizer
 | ||
| ;; ========================
 | ||
| ;;
 | ||
| ;; Actually, it gets better. You don't have to imagine all these tree
 | ||
| ;; diagrams, because `undo-tree-mode' includes an undo-tree visualizer which
 | ||
| ;; draws them for you! In fact, it draws even better diagrams: it highlights
 | ||
| ;; the node representing the current buffer state, it highlights the current
 | ||
| ;; branch, and you can toggle the display of time-stamps (by hitting "t") and
 | ||
| ;; a diff of the undo changes (by hitting "d"). (There's one other tiny
 | ||
| ;; difference: the visualizer puts the most recent branch on the left rather
 | ||
| ;; than the right.)
 | ||
| ;;
 | ||
| ;; Bring up the undo tree visualizer whenever you want by hitting "C-x u".
 | ||
| ;;
 | ||
| ;; In the visualizer, the usual keys for moving up and down a buffer instead
 | ||
| ;; move up and down the undo history tree (e.g. the up and down arrow keys, or
 | ||
| ;; "C-n" and "C-p"). The state of the "parent" buffer (the buffer whose undo
 | ||
| ;; history you are visualizing) is updated as you move around the undo tree in
 | ||
| ;; the visualizer. If you reach a branch point in the visualizer, the usual
 | ||
| ;; keys for moving forward and backward in a buffer instead switch branch
 | ||
| ;; (e.g. the left and right arrow keys, or "C-f" and "C-b").
 | ||
| ;;
 | ||
| ;; Clicking with the mouse on any node in the visualizer will take you
 | ||
| ;; directly to that node, resetting the state of the parent buffer to the
 | ||
| ;; state represented by that node.
 | ||
| ;;
 | ||
| ;; You can also select nodes directly using the keyboard, by hitting "s" to
 | ||
| ;; toggle selection mode. The usual motion keys now allow you to move around
 | ||
| ;; the tree without changing the parent buffer. Hitting <enter> will reset the
 | ||
| ;; state of the parent buffer to the state represented by the currently
 | ||
| ;; selected node.
 | ||
| ;;
 | ||
| ;; It can be useful to see how long ago the parent buffer was in the state
 | ||
| ;; represented by a particular node in the visualizer. Hitting "t" in the
 | ||
| ;; visualizer toggles the display of time-stamps for all the nodes. (Note
 | ||
| ;; that, because of the way `undo-tree-mode' works, these time-stamps may be
 | ||
| ;; somewhat later than the true times, especially if it's been a long time
 | ||
| ;; since you last undid any changes.)
 | ||
| ;;
 | ||
| ;; To get some idea of what changes are represented by a given node in the
 | ||
| ;; tree, it can be useful to see a diff of the changes. Hit "d" in the
 | ||
| ;; visualizer to toggle a diff display. This normally displays a diff between
 | ||
| ;; the current state and the previous one, i.e. it shows you the changes that
 | ||
| ;; will be applied if you undo (move up the tree). However, the diff display
 | ||
| ;; really comes into its own in the visualizer's selection mode (see above),
 | ||
| ;; where it instead shows a diff between the current state and the currently
 | ||
| ;; selected state, i.e. it shows you the changes that will be applied if you
 | ||
| ;; reset to the selected state.
 | ||
| ;;
 | ||
| ;; (Note that the diff is generated by the Emacs `diff' command, and is
 | ||
| ;; displayed using `diff-mode'. See the corresponding customization groups if
 | ||
| ;; you want to customize the diff display.)
 | ||
| ;;
 | ||
| ;; Finally, hitting "q" will quit the visualizer, leaving the parent buffer in
 | ||
| ;; whatever state you ended at. Hitting "C-q" will abort the visualizer,
 | ||
| ;; returning the parent buffer to whatever state it was originally in when the
 | ||
| ;; visualizer was .
 | ||
| ;;
 | ||
| ;;
 | ||
| ;;
 | ||
| ;; Undo-in-Region
 | ||
| ;; ==============
 | ||
| ;;
 | ||
| ;; Emacs allows a very useful and powerful method of undoing only selected
 | ||
| ;; changes: when a region is active, only changes that affect the text within
 | ||
| ;; that region will be undone. With the standard Emacs undo system, changes
 | ||
| ;; produced by undoing-in-region naturally get added onto the end of the
 | ||
| ;; linear undo history:
 | ||
| ;;
 | ||
| ;;                       o
 | ||
| ;;                       |
 | ||
| ;;                       |  x  (second undo-in-region)
 | ||
| ;;                       o  |
 | ||
| ;;                       |  |
 | ||
| ;;                       |  o  (first undo-in-region)
 | ||
| ;;                       o  |
 | ||
| ;;                       | /
 | ||
| ;;                       |/
 | ||
| ;;                       o
 | ||
| ;;
 | ||
| ;; You can of course redo these undos-in-region as usual, by undoing the
 | ||
| ;; undos:
 | ||
| ;;
 | ||
| ;;                       o
 | ||
| ;;                       |
 | ||
| ;;                       |  o_
 | ||
| ;;                       o  | \
 | ||
| ;;                       |  |  |
 | ||
| ;;                       |  o  o  (undo the undo-in-region)
 | ||
| ;;                       o  |  |
 | ||
| ;;                       | /   |
 | ||
| ;;                       |/    |
 | ||
| ;;                       o     x  (undo the undo-in-region)
 | ||
| ;;
 | ||
| ;;
 | ||
| ;; In `undo-tree-mode', undo-in-region works similarly: when there's an active
 | ||
| ;; region, undoing only undoes changes that affect that region. However, the
 | ||
| ;; way these undos-in-region are recorded in the undo history is quite
 | ||
| ;; different. In `undo-tree-mode', undo-in-region creates a new branch in the
 | ||
| ;; undo history. The new branch consists of an undo step that undoes some of
 | ||
| ;; the changes that affect the current region, and another step that undoes
 | ||
| ;; the remaining changes needed to rejoin the previous undo history.
 | ||
| ;;
 | ||
| ;;      Previous undo history                Undo-in-region
 | ||
| ;;
 | ||
| ;;               o                                o
 | ||
| ;;               |                                |
 | ||
| ;;               |                                |
 | ||
| ;;               o                                o
 | ||
| ;;               |                                |\
 | ||
| ;;               |                                | \
 | ||
| ;;               o                                o  x  (undo-in-region)
 | ||
| ;;               |                                |  |
 | ||
| ;;               |                                |  |
 | ||
| ;;               x                                o  o
 | ||
| ;;
 | ||
| ;; As long as you don't change the active region after undoing-in-region,
 | ||
| ;; continuing to undo-in-region extends the new branch, pulling more changes
 | ||
| ;; that affect the current region into an undo step immediately above your
 | ||
| ;; current location in the undo tree, and pushing the point at which the new
 | ||
| ;; branch is attached further up the tree:
 | ||
| ;;
 | ||
| ;;      First undo-in-region                 Second undo-in-region
 | ||
| ;;
 | ||
| ;;               o                                o
 | ||
| ;;               |                                |\
 | ||
| ;;               |                                | \
 | ||
| ;;               o                                o  x  (undo-in-region)
 | ||
| ;;               |\                               |  |
 | ||
| ;;               | \                              |  |
 | ||
| ;;               o  x                             o  o
 | ||
| ;;               |  |                             |  |
 | ||
| ;;               |  |                             |  |
 | ||
| ;;               o  o                             o  o
 | ||
| ;;
 | ||
| ;; Redoing takes you back down the undo tree, as usual (as long as you haven't
 | ||
| ;; changed the active region after undoing-in-region, it doesn't matter if it
 | ||
| ;; is still active):
 | ||
| ;;
 | ||
| ;;                       o
 | ||
| ;;			 |\
 | ||
| ;;			 | \
 | ||
| ;;			 o  o
 | ||
| ;;			 |  |
 | ||
| ;;			 |  |
 | ||
| ;;			 o  o  (redo)
 | ||
| ;;			 |  |
 | ||
| ;;			 |  |
 | ||
| ;;			 o  x  (redo)
 | ||
| ;;
 | ||
| ;;
 | ||
| ;; What about redo-in-region? Obviously, this only makes sense if you have
 | ||
| ;; already undone some changes, so that there are some changes to redo!
 | ||
| ;; Redoing-in-region splits off a new branch of the undo history below your
 | ||
| ;; current location in the undo tree. This time, the new branch consists of a
 | ||
| ;; redo step that redoes some of the redo changes that affect the current
 | ||
| ;; region, followed by all the remaining redo changes.
 | ||
| ;;
 | ||
| ;;      Previous undo history                Redo-in-region
 | ||
| ;;
 | ||
| ;;               o                                o
 | ||
| ;;               |                                |
 | ||
| ;;               |                                |
 | ||
| ;;               x                                o
 | ||
| ;;               |                                |\
 | ||
| ;;               |                                | \
 | ||
| ;;               o                                o  x  (redo-in-region)
 | ||
| ;;               |                                |  |
 | ||
| ;;               |                                |  |
 | ||
| ;;               o                                o  o
 | ||
| ;;
 | ||
| ;; As long as you don't change the active region after redoing-in-region,
 | ||
| ;; continuing to redo-in-region extends the new branch, pulling more redo
 | ||
| ;; changes into a redo step immediately below your current location in the
 | ||
| ;; undo tree.
 | ||
| ;;
 | ||
| ;;      First redo-in-region                 Second redo-in-region
 | ||
| ;;
 | ||
| ;;          o                                     o
 | ||
| ;;          |                                     |
 | ||
| ;;          |                                     |
 | ||
| ;;          o                                     o
 | ||
| ;;          |\                                    |\
 | ||
| ;;          | \                                   | \
 | ||
| ;;          o  x  (redo-in-region)                o  o
 | ||
| ;;          |  |                                  |  |
 | ||
| ;;          |  |                                  |  |
 | ||
| ;;          o  o                                  o  x  (redo-in-region)
 | ||
| ;;                                                   |
 | ||
| ;;                                                   |
 | ||
| ;;                                                   o
 | ||
| ;;
 | ||
| ;; Note that undo-in-region and redo-in-region only ever add new changes to
 | ||
| ;; the undo tree, they *never* modify existing undo history. So you can always
 | ||
| ;; return to previous buffer states by switching to a previous branch of the
 | ||
| ;; tree.
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;;; Code:
 | ||
| 
 | ||
| (eval-when-compile (require 'cl))
 | ||
| (require 'diff)
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;;; =====================================================================
 | ||
| ;;;              Compatibility hacks for older Emacsen
 | ||
| 
 | ||
| ;; `characterp' isn't defined in Emacs versions < 23
 | ||
| (unless (fboundp 'characterp)
 | ||
|   (defalias 'characterp 'char-valid-p))
 | ||
| 
 | ||
| ;; `region-active-p' isn't defined in Emacs versions < 23
 | ||
| (unless (fboundp 'region-active-p)
 | ||
|   (defun region-active-p () (and transient-mark-mode mark-active)))
 | ||
| 
 | ||
| 
 | ||
| ;; `registerv' defstruct isn't defined in Emacs versions < 24
 | ||
| (unless (fboundp 'registerv-make)
 | ||
|   (defmacro registerv-make (data &rest _dummy) data))
 | ||
| 
 | ||
| (unless (fboundp 'registerv-data)
 | ||
|   (defmacro registerv-data (data) data))
 | ||
| 
 | ||
| 
 | ||
| ;; `diff-no-select' and `diff-file-local-copy' aren't defined in Emacs
 | ||
| ;; versions < 24 (copied and adapted from Emacs 24)
 | ||
| (unless (fboundp 'diff-no-select)
 | ||
|   (defun diff-no-select (old new &optional switches no-async buf)
 | ||
|     ;; Noninteractive helper for creating and reverting diff buffers
 | ||
|     (unless (bufferp new) (setq new (expand-file-name new)))
 | ||
|     (unless (bufferp old) (setq old (expand-file-name old)))
 | ||
|     (or switches (setq switches diff-switches)) ; If not specified, use default.
 | ||
|     (unless (listp switches) (setq switches (list switches)))
 | ||
|     (or buf (setq buf (get-buffer-create "*Diff*")))
 | ||
|     (let* ((old-alt (diff-file-local-copy old))
 | ||
| 	   (new-alt (diff-file-local-copy new))
 | ||
| 	   (command
 | ||
| 	    (mapconcat 'identity
 | ||
| 		       `(,diff-command
 | ||
| 			 ;; Use explicitly specified switches
 | ||
| 			 ,@switches
 | ||
| 			 ,@(mapcar #'shell-quote-argument
 | ||
| 				   (nconc
 | ||
| 				    (when (or old-alt new-alt)
 | ||
| 				      (list "-L" (if (stringp old)
 | ||
| 						     old (prin1-to-string old))
 | ||
| 					    "-L" (if (stringp new)
 | ||
| 						     new (prin1-to-string new))))
 | ||
| 				    (list (or old-alt old)
 | ||
| 					  (or new-alt new)))))
 | ||
| 		       " "))
 | ||
| 	   (thisdir default-directory))
 | ||
|       (with-current-buffer buf
 | ||
| 	(setq buffer-read-only t)
 | ||
| 	(buffer-disable-undo (current-buffer))
 | ||
| 	(let ((inhibit-read-only t))
 | ||
| 	  (erase-buffer))
 | ||
| 	(buffer-enable-undo (current-buffer))
 | ||
| 	(diff-mode)
 | ||
| 	(set (make-local-variable 'revert-buffer-function)
 | ||
| 	     (lambda (_ignore-auto _noconfirm)
 | ||
| 	       (diff-no-select old new switches no-async (current-buffer))))
 | ||
| 	(setq default-directory thisdir)
 | ||
| 	(let ((inhibit-read-only t))
 | ||
| 	  (insert command "\n"))
 | ||
| 	(if (and (not no-async) (fboundp 'start-process))
 | ||
| 	    (let ((proc (start-process "Diff" buf shell-file-name
 | ||
| 				       shell-command-switch command)))
 | ||
| 	      (set-process-filter proc 'diff-process-filter)
 | ||
| 	      (set-process-sentinel
 | ||
| 	       proc (lambda (proc _msg)
 | ||
| 		      (with-current-buffer (process-buffer proc)
 | ||
| 			(diff-sentinel (process-exit-status proc))
 | ||
| 			(if old-alt (delete-file old-alt))
 | ||
| 			(if new-alt (delete-file new-alt))))))
 | ||
| 	  ;; Async processes aren't available.
 | ||
| 	  (let ((inhibit-read-only t))
 | ||
| 	    (diff-sentinel
 | ||
| 	     (call-process shell-file-name nil buf nil
 | ||
| 			   shell-command-switch command))
 | ||
| 	    (if old-alt (delete-file old-alt))
 | ||
| 	    (if new-alt (delete-file new-alt)))))
 | ||
|       buf)))
 | ||
| 
 | ||
| (unless (fboundp 'diff-file-local-copy)
 | ||
|   (defun diff-file-local-copy (file-or-buf)
 | ||
|     (if (bufferp file-or-buf)
 | ||
| 	(with-current-buffer file-or-buf
 | ||
| 	  (let ((tempfile (make-temp-file "buffer-content-")))
 | ||
| 	    (write-region nil nil tempfile nil 'nomessage)
 | ||
| 	    tempfile))
 | ||
|       (file-local-copy file-or-buf))))
 | ||
| 
 | ||
| 
 | ||
| ;; `user-error' isn't defined in Emacs < 24.3
 | ||
| (unless (fboundp 'user-error)
 | ||
|   (defalias 'user-error 'error)
 | ||
|   ;; prevent debugger being called on user errors
 | ||
|   (add-to-list 'debug-ignored-errors "^No further undo information")
 | ||
|   (add-to-list 'debug-ignored-errors "^No further redo information")
 | ||
|   (add-to-list 'debug-ignored-errors "^No further redo information for region"))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;;; =====================================================================
 | ||
| ;;;              Global variables and customization options
 | ||
| 
 | ||
| (defvar buffer-undo-tree nil
 | ||
|   "Tree of undo entries in current buffer.")
 | ||
| (put 'buffer-undo-tree 'permanent-local t)
 | ||
| (make-variable-buffer-local 'buffer-undo-tree)
 | ||
| 
 | ||
| 
 | ||
| (defgroup undo-tree nil
 | ||
|   "Tree undo/redo."
 | ||
|   :group 'undo)
 | ||
| 
 | ||
| (defcustom undo-tree-mode-lighter " Undo-Tree"
 | ||
|   "Lighter displayed in mode line
 | ||
| when `undo-tree-mode' is enabled."
 | ||
|   :group 'undo-tree
 | ||
|   :type 'string)
 | ||
| 
 | ||
| 
 | ||
| (defcustom undo-tree-incompatible-major-modes '(term-mode)
 | ||
|   "List of major-modes in which `undo-tree-mode' should not be enabled.
 | ||
| \(See `turn-on-undo-tree-mode'.\)"
 | ||
|   :group 'undo-tree
 | ||
|   :type '(repeat symbol))
 | ||
| 
 | ||
| 
 | ||
| (defcustom undo-tree-enable-undo-in-region t
 | ||
|   "When non-nil, enable undo-in-region.
 | ||
| 
 | ||
| When undo-in-region is enabled, undoing or redoing when the
 | ||
| region is active (in `transient-mark-mode') or with a prefix
 | ||
| argument (not in `transient-mark-mode') only undoes changes
 | ||
| within the current region."
 | ||
|   :group 'undo-tree
 | ||
|   :type 'boolean)
 | ||
| 
 | ||
| 
 | ||
| (defcustom undo-tree-auto-save-history nil
 | ||
|   "When non-nil, `undo-tree-mode' will save undo history to file
 | ||
| when a buffer is saved to file.
 | ||
| 
 | ||
| It will automatically load undo history when a buffer is loaded
 | ||
| from file, if an undo save file exists.
 | ||
| 
 | ||
| By default, undo-tree history is saved to a file called
 | ||
| \".<buffer-file-name>.~undo-tree~\" in the same directory as the
 | ||
| file itself. To save under a different directory, customize
 | ||
| `undo-tree-history-directory-alist' (see the documentation for
 | ||
| that variable for details).
 | ||
| 
 | ||
| WARNING! `undo-tree-auto-save-history' will not work properly in
 | ||
| Emacs versions prior to 24.3, so it cannot be enabled via
 | ||
| the customization interface in versions earlier than that one. To
 | ||
| ignore this warning and enable it regardless, set
 | ||
| `undo-tree-auto-save-history' to a non-nil value outside of
 | ||
| customize."
 | ||
|   :group 'undo-tree
 | ||
|   :type (if (version-list-< (version-to-list emacs-version) '(24 3))
 | ||
| 	    '(choice (const :tag "<disabled>" nil))
 | ||
| 	  'boolean))
 | ||
| 
 | ||
| 
 | ||
| (defcustom undo-tree-history-directory-alist nil
 | ||
|   "Alist of filename patterns and undo history directory names.
 | ||
| Each element looks like (REGEXP . DIRECTORY).  Undo history for
 | ||
| files with names matching REGEXP will be saved in DIRECTORY.
 | ||
| DIRECTORY may be relative or absolute.  If it is absolute, so
 | ||
| that all matching files are backed up into the same directory,
 | ||
| the file names in this directory will be the full name of the
 | ||
| file backed up with all directory separators changed to `!' to
 | ||
| prevent clashes.  This will not work correctly if your filesystem
 | ||
| truncates the resulting name.
 | ||
| 
 | ||
| For the common case of all backups going into one directory, the
 | ||
| alist should contain a single element pairing \".\" with the
 | ||
| appropriate directory name.
 | ||
| 
 | ||
| If this variable is nil, or it fails to match a filename, the
 | ||
| backup is made in the original file's directory.
 | ||
| 
 | ||
| On MS-DOS filesystems without long names this variable is always
 | ||
| ignored."
 | ||
|   :group 'undo-tree
 | ||
|   :type '(repeat (cons (regexp :tag "Regexp matching filename")
 | ||
| 		       (directory :tag "Undo history directory name"))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defcustom undo-tree-visualizer-relative-timestamps t
 | ||
|   "When non-nil, display times relative to current time
 | ||
| when displaying time stamps in visualizer.
 | ||
| 
 | ||
| Otherwise, display absolute times."
 | ||
|   :group 'undo-tree
 | ||
|   :type 'boolean)
 | ||
| 
 | ||
| 
 | ||
| (defcustom undo-tree-visualizer-timestamps nil
 | ||
|   "When non-nil, display time-stamps by default
 | ||
| in undo-tree visualizer.
 | ||
| 
 | ||
| \\<undo-tree-visualizer-mode-map>You can always toggle time-stamps on and off \
 | ||
| using \\[undo-tree-visualizer-toggle-timestamps], regardless of the
 | ||
| setting of this variable."
 | ||
|   :group 'undo-tree
 | ||
|   :type 'boolean)
 | ||
| 
 | ||
| 
 | ||
| (defcustom undo-tree-visualizer-diff nil
 | ||
|   "When non-nil, display diff by default in undo-tree visualizer.
 | ||
| 
 | ||
| \\<undo-tree-visualizer-mode-map>You can always toggle the diff display \
 | ||
| using \\[undo-tree-visualizer-toggle-diff], regardless of the
 | ||
| setting of this variable."
 | ||
|   :group 'undo-tree
 | ||
|   :type 'boolean)
 | ||
| 
 | ||
| 
 | ||
| (defcustom undo-tree-visualizer-lazy-drawing 100
 | ||
|   "When non-nil, use lazy undo-tree drawing in visualizer.
 | ||
| 
 | ||
| Setting this to a number causes the visualizer to switch to lazy
 | ||
| drawing when the number of nodes in the tree is larger than this
 | ||
| value.
 | ||
| 
 | ||
| Lazy drawing means that only the visible portion of the tree will
 | ||
| be drawn initially, and the tree will be extended later as
 | ||
| needed. For the most part, the only visible effect of this is to
 | ||
| significantly speed up displaying the visualizer for very large
 | ||
| trees.
 | ||
| 
 | ||
| There is one potential negative effect of lazy drawing. Other
 | ||
| branches of the tree will only be drawn once the node from which
 | ||
| they branch off becomes visible. So it can happen that certain
 | ||
| portions of the tree that would be shown with lazy drawing
 | ||
| disabled, will not be drawn immediately when it is
 | ||
| enabled. However, this effect is quite rare in practice."
 | ||
|   :group 'undo-tree
 | ||
|   :type '(choice (const :tag "never" nil)
 | ||
| 		 (const :tag "always" t)
 | ||
| 		 (integer :tag "> size")))
 | ||
| 
 | ||
| 
 | ||
| (defface undo-tree-visualizer-default-face
 | ||
|   '((((class color)) :foreground "gray"))
 | ||
|   "Face used to draw undo-tree in visualizer."
 | ||
|   :group 'undo-tree)
 | ||
| 
 | ||
| (defface undo-tree-visualizer-current-face
 | ||
|   '((((class color)) :foreground "red"))
 | ||
|   "Face used to highlight current undo-tree node in visualizer."
 | ||
|   :group 'undo-tree)
 | ||
| 
 | ||
| (defface undo-tree-visualizer-active-branch-face
 | ||
|   '((((class color) (background dark))
 | ||
|      (:foreground "white" :weight bold))
 | ||
|     (((class color) (background light))
 | ||
|      (:foreground "black" :weight bold)))
 | ||
|   "Face used to highlight active undo-tree branch in visualizer."
 | ||
|   :group 'undo-tree)
 | ||
| 
 | ||
| (defface undo-tree-visualizer-register-face
 | ||
|   '((((class color)) :foreground "yellow"))
 | ||
|   "Face used to highlight undo-tree nodes saved to a register
 | ||
| in visualizer."
 | ||
|   :group 'undo-tree)
 | ||
| 
 | ||
| (defface undo-tree-visualizer-unmodified-face
 | ||
|   '((((class color)) :foreground "cyan"))
 | ||
|   "Face used to highlight nodes corresponding to unmodified buffers
 | ||
| in visualizer."
 | ||
|   :group 'undo-tree)
 | ||
| 
 | ||
| 
 | ||
| (defvar undo-tree-visualizer-parent-buffer nil
 | ||
|   "Parent buffer in visualizer.")
 | ||
| (put 'undo-tree-visualizer-parent-buffer 'permanent-local t)
 | ||
| (make-variable-buffer-local 'undo-tree-visualizer-parent-buffer)
 | ||
| 
 | ||
| ;; stores modification time of parent buffer's file, if any
 | ||
| (defvar undo-tree-visualizer-parent-mtime nil)
 | ||
| (put 'undo-tree-visualizer-parent-mtime 'permanent-local t)
 | ||
| (make-variable-buffer-local 'undo-tree-visualizer-parent-mtime)
 | ||
| 
 | ||
| ;; stores current horizontal spacing needed for drawing undo-tree
 | ||
| (defvar undo-tree-visualizer-spacing nil)
 | ||
| (put 'undo-tree-visualizer-spacing 'permanent-local t)
 | ||
| (make-variable-buffer-local 'undo-tree-visualizer-spacing)
 | ||
| 
 | ||
| ;; calculate horizontal spacing required for drawing tree with current
 | ||
| ;; settings
 | ||
| (defsubst undo-tree-visualizer-calculate-spacing ()
 | ||
|   (if undo-tree-visualizer-timestamps
 | ||
|       (if undo-tree-visualizer-relative-timestamps 9 13)
 | ||
|     3))
 | ||
| 
 | ||
| ;; holds node that was current when visualizer was invoked
 | ||
| (defvar undo-tree-visualizer-initial-node nil)
 | ||
| (put 'undo-tree-visualizer-initial-node 'permanent-local t)
 | ||
| (make-variable-buffer-local 'undo-tree-visualizer-initial-node)
 | ||
| 
 | ||
| ;; holds currently selected node in visualizer selection mode
 | ||
| (defvar undo-tree-visualizer-selected-node nil)
 | ||
| (put 'undo-tree-visualizer-selected-node 'permanent-local t)
 | ||
| (make-variable-buffer-local 'undo-tree-visualizer-selected)
 | ||
| 
 | ||
| ;; used to store nodes at edge of currently drawn portion of tree
 | ||
| (defvar undo-tree-visualizer-needs-extending-down nil)
 | ||
| (put 'undo-tree-visualizer-needs-extending-down 'permanent-local t)
 | ||
| (make-variable-buffer-local 'undo-tree-visualizer-needs-extending-down)
 | ||
| (defvar undo-tree-visualizer-needs-extending-up nil)
 | ||
| (put 'undo-tree-visualizer-needs-extending-up 'permanent-local t)
 | ||
| (make-variable-buffer-local 'undo-tree-visualizer-needs-extending-up)
 | ||
| 
 | ||
| ;; dynamically bound to t when undoing from visualizer, to inhibit
 | ||
| ;; `undo-tree-kill-visualizer' hook function in parent buffer
 | ||
| (defvar undo-tree-inhibit-kill-visualizer nil)
 | ||
| 
 | ||
| ;; can be let-bound to a face name, used in drawing functions
 | ||
| (defvar undo-tree-insert-face nil)
 | ||
| 
 | ||
| ;; visualizer buffer names
 | ||
| (defconst undo-tree-visualizer-buffer-name " *undo-tree*")
 | ||
| (defconst undo-tree-diff-buffer-name "*undo-tree Diff*")
 | ||
| 
 | ||
| ;; install history-auto-save hooks
 | ||
| (add-hook 'write-file-functions 'undo-tree-save-history-hook)
 | ||
| (add-hook 'find-file-hook 'undo-tree-load-history-hook)
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;;; =================================================================
 | ||
| ;;;                          Default keymaps
 | ||
| 
 | ||
| (defvar undo-tree-map nil
 | ||
|   "Keymap used in undo-tree-mode.")
 | ||
| 
 | ||
| (unless undo-tree-map
 | ||
|   (let ((map (make-sparse-keymap)))
 | ||
|     ;; remap `undo' and `undo-only' to `undo-tree-undo'
 | ||
|     (define-key map [remap undo] 'undo-tree-undo)
 | ||
|     (define-key map [remap undo-only] 'undo-tree-undo)
 | ||
|     ;; bind standard undo bindings (since these match redo counterparts)
 | ||
|     (define-key map (kbd "C-/") 'undo-tree-undo)
 | ||
|     (define-key map "\C-_" 'undo-tree-undo)
 | ||
|     ;; redo doesn't exist normally, so define our own keybindings
 | ||
|     (define-key map (kbd "C-?") 'undo-tree-redo)
 | ||
|     (define-key map (kbd "M-_") 'undo-tree-redo)
 | ||
|     ;; just in case something has defined `redo'...
 | ||
|     (define-key map [remap redo] 'undo-tree-redo)
 | ||
|     ;; we use "C-x u" for the undo-tree visualizer
 | ||
|     (define-key map (kbd "\C-x u") 'undo-tree-visualize)
 | ||
|     ;; bind register commands
 | ||
|     (define-key map (kbd "C-x r u") 'undo-tree-save-state-to-register)
 | ||
|     (define-key map (kbd "C-x r U") 'undo-tree-restore-state-from-register)
 | ||
|     ;; set keymap
 | ||
|     (setq undo-tree-map map)))
 | ||
| 
 | ||
| 
 | ||
| (defvar undo-tree-visualizer-mode-map nil
 | ||
|   "Keymap used in undo-tree visualizer.")
 | ||
| 
 | ||
| (unless undo-tree-visualizer-mode-map
 | ||
|   (let ((map (make-sparse-keymap)))
 | ||
|     ;; vertical motion keys undo/redo
 | ||
|     (define-key map [remap previous-line] 'undo-tree-visualize-undo)
 | ||
|     (define-key map [remap next-line] 'undo-tree-visualize-redo)
 | ||
|     (define-key map [up] 'undo-tree-visualize-undo)
 | ||
|     (define-key map "p" 'undo-tree-visualize-undo)
 | ||
|     (define-key map "\C-p" 'undo-tree-visualize-undo)
 | ||
|     (define-key map [down] 'undo-tree-visualize-redo)
 | ||
|     (define-key map "n" 'undo-tree-visualize-redo)
 | ||
|     (define-key map "\C-n" 'undo-tree-visualize-redo)
 | ||
|     ;; horizontal motion keys switch branch
 | ||
|     (define-key map [remap forward-char]
 | ||
|       'undo-tree-visualize-switch-branch-right)
 | ||
|     (define-key map [remap backward-char]
 | ||
|       'undo-tree-visualize-switch-branch-left)
 | ||
|     (define-key map [right] 'undo-tree-visualize-switch-branch-right)
 | ||
|     (define-key map "f" 'undo-tree-visualize-switch-branch-right)
 | ||
|     (define-key map "\C-f" 'undo-tree-visualize-switch-branch-right)
 | ||
|     (define-key map [left] 'undo-tree-visualize-switch-branch-left)
 | ||
|     (define-key map "b" 'undo-tree-visualize-switch-branch-left)
 | ||
|     (define-key map "\C-b" 'undo-tree-visualize-switch-branch-left)
 | ||
|     ;; paragraph motion keys undo/redo to significant points in tree
 | ||
|     (define-key map [remap backward-paragraph] 'undo-tree-visualize-undo-to-x)
 | ||
|     (define-key map [remap forward-paragraph] 'undo-tree-visualize-redo-to-x)
 | ||
|     (define-key map "\M-{" 'undo-tree-visualize-undo-to-x)
 | ||
|     (define-key map "\M-}" 'undo-tree-visualize-redo-to-x)
 | ||
|     (define-key map [C-up] 'undo-tree-visualize-undo-to-x)
 | ||
|     (define-key map [C-down] 'undo-tree-visualize-redo-to-x)
 | ||
|     ;; mouse sets buffer state to node at click
 | ||
|     (define-key map [mouse-1] 'undo-tree-visualizer-mouse-set)
 | ||
|     ;; toggle timestamps
 | ||
|     (define-key map "t" 'undo-tree-visualizer-toggle-timestamps)
 | ||
|     ;; toggle diff
 | ||
|     (define-key map "d" 'undo-tree-visualizer-toggle-diff)
 | ||
|     ;; toggle selection mode
 | ||
|     (define-key map "s" 'undo-tree-visualizer-selection-mode)
 | ||
|     ;; horizontal scrolling may be needed if the tree is very wide
 | ||
|     (define-key map "," 'undo-tree-visualizer-scroll-left)
 | ||
|     (define-key map "." 'undo-tree-visualizer-scroll-right)
 | ||
|     (define-key map "<" 'undo-tree-visualizer-scroll-left)
 | ||
|     (define-key map ">" 'undo-tree-visualizer-scroll-right)
 | ||
|     ;; vertical scrolling may be needed if the tree is very tall
 | ||
|     (define-key map [next] 'undo-tree-visualizer-scroll-up)
 | ||
|     (define-key map [prior] 'undo-tree-visualizer-scroll-down)
 | ||
|     ;; quit/abort visualizer
 | ||
|     (define-key map "q" 'undo-tree-visualizer-quit)
 | ||
|     (define-key map "\C-q" 'undo-tree-visualizer-abort)
 | ||
|     ;; set keymap
 | ||
|     (setq undo-tree-visualizer-mode-map map)))
 | ||
| 
 | ||
| 
 | ||
| (defvar undo-tree-visualizer-selection-mode-map nil
 | ||
|   "Keymap used in undo-tree visualizer selection mode.")
 | ||
| 
 | ||
| (unless undo-tree-visualizer-selection-mode-map
 | ||
|   (let ((map (make-sparse-keymap)))
 | ||
|     ;; vertical motion keys move up and down tree
 | ||
|     (define-key map [remap previous-line]
 | ||
|       'undo-tree-visualizer-select-previous)
 | ||
|     (define-key map [remap next-line]
 | ||
|       'undo-tree-visualizer-select-next)
 | ||
|     (define-key map [up] 'undo-tree-visualizer-select-previous)
 | ||
|     (define-key map "p" 'undo-tree-visualizer-select-previous)
 | ||
|     (define-key map "\C-p" 'undo-tree-visualizer-select-previous)
 | ||
|     (define-key map [down] 'undo-tree-visualizer-select-next)
 | ||
|     (define-key map "n" 'undo-tree-visualizer-select-next)
 | ||
|     (define-key map "\C-n" 'undo-tree-visualizer-select-next)
 | ||
|     ;; vertical scroll keys move up and down quickly
 | ||
|     (define-key map [next]
 | ||
|       (lambda () (interactive) (undo-tree-visualizer-select-next 10)))
 | ||
|     (define-key map [prior]
 | ||
|       (lambda () (interactive) (undo-tree-visualizer-select-previous 10)))
 | ||
|     ;; horizontal motion keys move to left and right siblings
 | ||
|     (define-key map [remap forward-char] 'undo-tree-visualizer-select-right)
 | ||
|     (define-key map [remap backward-char] 'undo-tree-visualizer-select-left)
 | ||
|     (define-key map [right] 'undo-tree-visualizer-select-right)
 | ||
|     (define-key map "f" 'undo-tree-visualizer-select-right)
 | ||
|     (define-key map "\C-f" 'undo-tree-visualizer-select-right)
 | ||
|     (define-key map [left] 'undo-tree-visualizer-select-left)
 | ||
|     (define-key map "b" 'undo-tree-visualizer-select-left)
 | ||
|     (define-key map "\C-b" 'undo-tree-visualizer-select-left)
 | ||
|     ;; horizontal scroll keys move left or right quickly
 | ||
|     (define-key map ","
 | ||
|       (lambda () (interactive) (undo-tree-visualizer-select-left 10)))
 | ||
|     (define-key map "."
 | ||
|       (lambda () (interactive) (undo-tree-visualizer-select-right 10)))
 | ||
|     (define-key map "<"
 | ||
|       (lambda () (interactive) (undo-tree-visualizer-select-left 10)))
 | ||
|     (define-key map ">"
 | ||
|       (lambda () (interactive) (undo-tree-visualizer-select-right 10)))
 | ||
|     ;; <enter> sets buffer state to node at point
 | ||
|     (define-key map "\r" 'undo-tree-visualizer-set)
 | ||
|     ;; mouse selects node at click
 | ||
|     (define-key map [mouse-1] 'undo-tree-visualizer-mouse-select)
 | ||
|     ;; toggle diff
 | ||
|     (define-key map "d" 'undo-tree-visualizer-selection-toggle-diff)
 | ||
|     ;; set keymap
 | ||
|     (setq undo-tree-visualizer-selection-mode-map map)))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;;; =====================================================================
 | ||
| ;;;                     Undo-tree data structure
 | ||
| 
 | ||
| (defstruct
 | ||
|   (undo-tree
 | ||
|    :named
 | ||
|    (:constructor nil)
 | ||
|    (:constructor make-undo-tree
 | ||
|                  (&aux
 | ||
|                   (root (undo-tree-make-node nil nil))
 | ||
|                   (current root)
 | ||
|                   (size 0)
 | ||
| 		  (count 0)
 | ||
| 		  (object-pool (make-hash-table :test 'eq :weakness 'value))))
 | ||
|    ;;(:copier nil)
 | ||
|    )
 | ||
|   root current size count object-pool)
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defstruct
 | ||
|   (undo-tree-node
 | ||
|    (:type vector)   ; create unnamed struct
 | ||
|    (:constructor nil)
 | ||
|    (:constructor undo-tree-make-node
 | ||
|                  (previous undo
 | ||
| 		  &optional redo
 | ||
|                   &aux
 | ||
|                   (timestamp (current-time))
 | ||
|                   (branch 0)))
 | ||
|    (:constructor undo-tree-make-node-backwards
 | ||
|                  (next-node undo
 | ||
| 		  &optional redo
 | ||
|                   &aux
 | ||
|                   (next (list next-node))
 | ||
|                   (timestamp (current-time))
 | ||
|                   (branch 0)))
 | ||
|    (:copier nil))
 | ||
|   previous next undo redo timestamp branch meta-data)
 | ||
| 
 | ||
| 
 | ||
| (defmacro undo-tree-node-p (n)
 | ||
|   (let ((len (length (undo-tree-make-node nil nil))))
 | ||
|     `(and (vectorp ,n) (= (length ,n) ,len))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defstruct
 | ||
|   (undo-tree-region-data
 | ||
|    (:type vector)   ; create unnamed struct
 | ||
|    (:constructor nil)
 | ||
|    (:constructor undo-tree-make-region-data
 | ||
| 		 (&optional undo-beginning undo-end
 | ||
| 			     redo-beginning redo-end))
 | ||
|    (:constructor undo-tree-make-undo-region-data
 | ||
| 		 (undo-beginning undo-end))
 | ||
|    (:constructor undo-tree-make-redo-region-data
 | ||
| 		 (redo-beginning redo-end))
 | ||
|    (:copier nil))
 | ||
|   undo-beginning undo-end redo-beginning redo-end)
 | ||
| 
 | ||
| 
 | ||
| (defmacro undo-tree-region-data-p (r)
 | ||
|   (let ((len (length (undo-tree-make-region-data))))
 | ||
|     `(and (vectorp ,r) (= (length ,r) ,len))))
 | ||
| 
 | ||
| (defmacro undo-tree-node-clear-region-data (node)
 | ||
|   `(setf (undo-tree-node-meta-data ,node)
 | ||
| 	 (delq nil
 | ||
| 	       (delq :region
 | ||
| 		     (plist-put (undo-tree-node-meta-data ,node)
 | ||
| 				:region nil)))))
 | ||
| 
 | ||
| 
 | ||
| (defmacro undo-tree-node-undo-beginning (node)
 | ||
|   `(let ((r (plist-get (undo-tree-node-meta-data ,node) :region)))
 | ||
|      (when (undo-tree-region-data-p r)
 | ||
|        (undo-tree-region-data-undo-beginning r))))
 | ||
| 
 | ||
| (defmacro undo-tree-node-undo-end (node)
 | ||
|   `(let ((r (plist-get (undo-tree-node-meta-data ,node) :region)))
 | ||
|      (when (undo-tree-region-data-p r)
 | ||
|        (undo-tree-region-data-undo-end r))))
 | ||
| 
 | ||
| (defmacro undo-tree-node-redo-beginning (node)
 | ||
|   `(let ((r (plist-get (undo-tree-node-meta-data ,node) :region)))
 | ||
|      (when (undo-tree-region-data-p r)
 | ||
|        (undo-tree-region-data-redo-beginning r))))
 | ||
| 
 | ||
| (defmacro undo-tree-node-redo-end (node)
 | ||
|   `(let ((r (plist-get (undo-tree-node-meta-data ,node) :region)))
 | ||
|      (when (undo-tree-region-data-p r)
 | ||
|        (undo-tree-region-data-redo-end r))))
 | ||
| 
 | ||
| 
 | ||
| (defsetf undo-tree-node-undo-beginning (node) (val)
 | ||
|   `(let ((r (plist-get (undo-tree-node-meta-data ,node) :region)))
 | ||
|      (unless (undo-tree-region-data-p r)
 | ||
|        (setf (undo-tree-node-meta-data ,node)
 | ||
| 	     (plist-put (undo-tree-node-meta-data ,node) :region
 | ||
| 			(setq r (undo-tree-make-region-data)))))
 | ||
|      (setf (undo-tree-region-data-undo-beginning r) ,val)))
 | ||
| 
 | ||
| (defsetf undo-tree-node-undo-end (node) (val)
 | ||
|   `(let ((r (plist-get (undo-tree-node-meta-data ,node) :region)))
 | ||
|      (unless (undo-tree-region-data-p r)
 | ||
|        (setf (undo-tree-node-meta-data ,node)
 | ||
| 	     (plist-put (undo-tree-node-meta-data ,node) :region
 | ||
| 			(setq r (undo-tree-make-region-data)))))
 | ||
|      (setf (undo-tree-region-data-undo-end r) ,val)))
 | ||
| 
 | ||
| (defsetf undo-tree-node-redo-beginning (node) (val)
 | ||
|   `(let ((r (plist-get (undo-tree-node-meta-data ,node) :region)))
 | ||
|      (unless (undo-tree-region-data-p r)
 | ||
|        (setf (undo-tree-node-meta-data ,node)
 | ||
| 	     (plist-put (undo-tree-node-meta-data ,node) :region
 | ||
| 			(setq r (undo-tree-make-region-data)))))
 | ||
|      (setf (undo-tree-region-data-redo-beginning r) ,val)))
 | ||
| 
 | ||
| (defsetf undo-tree-node-redo-end (node) (val)
 | ||
|   `(let ((r (plist-get (undo-tree-node-meta-data ,node) :region)))
 | ||
|      (unless (undo-tree-region-data-p r)
 | ||
|        (setf (undo-tree-node-meta-data ,node)
 | ||
| 	     (plist-put (undo-tree-node-meta-data ,node) :region
 | ||
| 			(setq r (undo-tree-make-region-data)))))
 | ||
|      (setf (undo-tree-region-data-redo-end r) ,val)))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defstruct
 | ||
|   (undo-tree-visualizer-data
 | ||
|    (:type vector)   ; create unnamed struct
 | ||
|    (:constructor nil)
 | ||
|    (:constructor undo-tree-make-visualizer-data
 | ||
| 		 (&optional lwidth cwidth rwidth marker))
 | ||
|    (:copier nil))
 | ||
|   lwidth cwidth rwidth marker)
 | ||
| 
 | ||
| 
 | ||
| (defmacro undo-tree-visualizer-data-p (v)
 | ||
|   (let ((len (length (undo-tree-make-visualizer-data))))
 | ||
|     `(and (vectorp ,v) (= (length ,v) ,len))))
 | ||
| 
 | ||
| (defun undo-tree-node-clear-visualizer-data (node)
 | ||
|   (let ((plist (undo-tree-node-meta-data node)))
 | ||
|     (if (eq (car plist) :visualizer)
 | ||
| 	(setf (undo-tree-node-meta-data node) (nthcdr 2 plist))
 | ||
|       (while (and plist (not (eq (cadr plist) :visualizer)))
 | ||
| 	(setq plist (cdr plist)))
 | ||
|       (if plist (setcdr plist (nthcdr 3 plist))))))
 | ||
| 
 | ||
| (defmacro undo-tree-node-lwidth (node)
 | ||
|   `(let ((v (plist-get (undo-tree-node-meta-data ,node) :visualizer)))
 | ||
|      (when (undo-tree-visualizer-data-p v)
 | ||
|        (undo-tree-visualizer-data-lwidth v))))
 | ||
| 
 | ||
| (defmacro undo-tree-node-cwidth (node)
 | ||
|   `(let ((v (plist-get (undo-tree-node-meta-data ,node) :visualizer)))
 | ||
|      (when (undo-tree-visualizer-data-p v)
 | ||
|        (undo-tree-visualizer-data-cwidth v))))
 | ||
| 
 | ||
| (defmacro undo-tree-node-rwidth (node)
 | ||
|   `(let ((v (plist-get (undo-tree-node-meta-data ,node) :visualizer)))
 | ||
|      (when (undo-tree-visualizer-data-p v)
 | ||
|        (undo-tree-visualizer-data-rwidth v))))
 | ||
| 
 | ||
| (defmacro undo-tree-node-marker (node)
 | ||
|   `(let ((v (plist-get (undo-tree-node-meta-data ,node) :visualizer)))
 | ||
|      (when (undo-tree-visualizer-data-p v)
 | ||
|        (undo-tree-visualizer-data-marker v))))
 | ||
| 
 | ||
| 
 | ||
| (defsetf undo-tree-node-lwidth (node) (val)
 | ||
|   `(let ((v (plist-get (undo-tree-node-meta-data ,node) :visualizer)))
 | ||
|      (unless (undo-tree-visualizer-data-p v)
 | ||
|        (setf (undo-tree-node-meta-data ,node)
 | ||
| 	     (plist-put (undo-tree-node-meta-data ,node) :visualizer
 | ||
| 			(setq v (undo-tree-make-visualizer-data)))))
 | ||
|      (setf (undo-tree-visualizer-data-lwidth v) ,val)))
 | ||
| 
 | ||
| (defsetf undo-tree-node-cwidth (node) (val)
 | ||
|   `(let ((v (plist-get (undo-tree-node-meta-data ,node) :visualizer)))
 | ||
|      (unless (undo-tree-visualizer-data-p v)
 | ||
|        (setf (undo-tree-node-meta-data ,node)
 | ||
| 	     (plist-put (undo-tree-node-meta-data ,node) :visualizer
 | ||
| 			(setq v (undo-tree-make-visualizer-data)))))
 | ||
|      (setf (undo-tree-visualizer-data-cwidth v) ,val)))
 | ||
| 
 | ||
| (defsetf undo-tree-node-rwidth (node) (val)
 | ||
|   `(let ((v (plist-get (undo-tree-node-meta-data ,node) :visualizer)))
 | ||
|      (unless (undo-tree-visualizer-data-p v)
 | ||
|        (setf (undo-tree-node-meta-data ,node)
 | ||
| 	     (plist-put (undo-tree-node-meta-data ,node) :visualizer
 | ||
| 			(setq v (undo-tree-make-visualizer-data)))))
 | ||
|      (setf (undo-tree-visualizer-data-rwidth v) ,val)))
 | ||
| 
 | ||
| (defsetf undo-tree-node-marker (node) (val)
 | ||
|   `(let ((v (plist-get (undo-tree-node-meta-data ,node) :visualizer)))
 | ||
|      (unless (undo-tree-visualizer-data-p v)
 | ||
|        (setf (undo-tree-node-meta-data ,node)
 | ||
| 	     (plist-put (undo-tree-node-meta-data ,node) :visualizer
 | ||
| 			(setq v (undo-tree-make-visualizer-data)))))
 | ||
|      (setf (undo-tree-visualizer-data-marker v) ,val)))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defstruct
 | ||
|   (undo-tree-register-data
 | ||
|    (:type vector)
 | ||
|    (:constructor nil)
 | ||
|    (:constructor undo-tree-make-register-data (buffer node)))
 | ||
|   buffer node)
 | ||
| 
 | ||
| (defun undo-tree-register-data-p (data)
 | ||
|   (and (vectorp data)
 | ||
|        (= (length data) 2)
 | ||
|        (undo-tree-node-p (undo-tree-register-data-node data))))
 | ||
| 
 | ||
| (defun undo-tree-register-data-print-func (data)
 | ||
|   (princ (format "an undo-tree state for buffer %s"
 | ||
| 		 (undo-tree-register-data-buffer data))))
 | ||
| 
 | ||
| (defmacro undo-tree-node-register (node)
 | ||
|   `(plist-get (undo-tree-node-meta-data ,node) :register))
 | ||
| 
 | ||
| (defsetf undo-tree-node-register (node) (val)
 | ||
|   `(setf (undo-tree-node-meta-data ,node)
 | ||
| 	 (plist-put (undo-tree-node-meta-data ,node) :register ,val)))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;;; =====================================================================
 | ||
| ;;;              Basic undo-tree data structure functions
 | ||
| 
 | ||
| (defun undo-tree-grow (undo)
 | ||
|   "Add an UNDO node to current branch of `buffer-undo-tree'."
 | ||
|   (let* ((current (undo-tree-current buffer-undo-tree))
 | ||
|          (new (undo-tree-make-node current undo)))
 | ||
|     (push new (undo-tree-node-next current))
 | ||
|     (setf (undo-tree-current buffer-undo-tree) new)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-grow-backwards (node undo &optional redo)
 | ||
|   "Add new node *above* undo-tree NODE, and return new node.
 | ||
| Note that this will overwrite NODE's \"previous\" link, so should
 | ||
| only be used on a detached NODE, never on nodes that are already
 | ||
| part of `buffer-undo-tree'."
 | ||
|   (let ((new (undo-tree-make-node-backwards node undo redo)))
 | ||
|     (setf (undo-tree-node-previous node) new)
 | ||
|     new))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-splice-node (node splice)
 | ||
|   "Splice NODE into undo tree, below node SPLICE.
 | ||
| Note that this will overwrite NODE's \"next\" and \"previous\"
 | ||
| links, so should only be used on a detached NODE, never on nodes
 | ||
| that are already part of `buffer-undo-tree'."
 | ||
|   (setf (undo-tree-node-next node) (undo-tree-node-next splice)
 | ||
| 	(undo-tree-node-branch node) (undo-tree-node-branch splice)
 | ||
| 	(undo-tree-node-previous node) splice
 | ||
| 	(undo-tree-node-next splice) (list node)
 | ||
| 	(undo-tree-node-branch splice) 0)
 | ||
|   (dolist (n (undo-tree-node-next node))
 | ||
|     (setf (undo-tree-node-previous n) node)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-snip-node (node)
 | ||
|   "Snip NODE out of undo tree."
 | ||
|   (let* ((parent (undo-tree-node-previous node))
 | ||
| 	 position p)
 | ||
|     ;; if NODE is only child, replace parent's next links with NODE's
 | ||
|     (if (= (length (undo-tree-node-next parent)) 0)
 | ||
| 	(setf (undo-tree-node-next parent) (undo-tree-node-next node)
 | ||
| 	      (undo-tree-node-branch parent) (undo-tree-node-branch node))
 | ||
|       ;; otherwise...
 | ||
|       (setq position (undo-tree-position node (undo-tree-node-next parent)))
 | ||
|       (cond
 | ||
|        ;; if active branch used do go via NODE, set parent's branch to active
 | ||
|        ;; branch of NODE
 | ||
|        ((= (undo-tree-node-branch parent) position)
 | ||
| 	(setf (undo-tree-node-branch parent)
 | ||
| 	      (+ position (undo-tree-node-branch node))))
 | ||
|        ;; if active branch didn't go via NODE, update parent's branch to point
 | ||
|        ;; to same node as before
 | ||
|        ((> (undo-tree-node-branch parent) position)
 | ||
| 	(incf (undo-tree-node-branch parent)
 | ||
| 	      (1- (length (undo-tree-node-next node))))))
 | ||
|       ;; replace NODE in parent's next list with NODE's entire next list
 | ||
|       (if (= position 0)
 | ||
| 	  (setf (undo-tree-node-next parent)
 | ||
| 		(nconc (undo-tree-node-next node)
 | ||
| 		       (cdr (undo-tree-node-next parent))))
 | ||
| 	(setq p (nthcdr (1- position) (undo-tree-node-next parent)))
 | ||
| 	(setcdr p (nconc (undo-tree-node-next node) (cddr p)))))
 | ||
|     ;; update previous links of NODE's children
 | ||
|     (dolist (n (undo-tree-node-next node))
 | ||
|       (setf (undo-tree-node-previous n) parent))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-mapc (--undo-tree-mapc-function-- node)
 | ||
|   ;; Apply FUNCTION to NODE and to each node below it.
 | ||
|   (let ((stack (list node))
 | ||
| 	n)
 | ||
|     (while stack
 | ||
|       (setq n (pop stack))
 | ||
|       (funcall --undo-tree-mapc-function-- n)
 | ||
|       (setq stack (append (undo-tree-node-next n) stack)))))
 | ||
| 
 | ||
| 
 | ||
| (defmacro undo-tree-num-branches ()
 | ||
|   "Return number of branches at current undo tree node."
 | ||
|   '(length (undo-tree-node-next (undo-tree-current buffer-undo-tree))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-position (node list)
 | ||
|   "Find the first occurrence of NODE in LIST.
 | ||
| Return the index of the matching item, or nil of not found.
 | ||
| Comparison is done with `eq'."
 | ||
|   (let ((i 0))
 | ||
|     (catch 'found
 | ||
|       (while (progn
 | ||
|                (when (eq node (car list)) (throw 'found i))
 | ||
|                (incf i)
 | ||
|                (setq list (cdr list))))
 | ||
|       nil)))
 | ||
| 
 | ||
| 
 | ||
| (defvar *undo-tree-id-counter* 0)
 | ||
| (make-variable-buffer-local '*undo-tree-id-counter*)
 | ||
| 
 | ||
| (defmacro undo-tree-generate-id ()
 | ||
|   ;; Generate a new, unique id (uninterned symbol).
 | ||
|   ;; The name is made by appending a number to "undo-tree-id".
 | ||
|   ;; (Copied from CL package `gensym'.)
 | ||
|   `(let ((num (prog1 *undo-tree-id-counter* (incf *undo-tree-id-counter*))))
 | ||
|      (make-symbol (format "undo-tree-id%d" num))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-decircle (undo-tree)
 | ||
|   ;; Nullify PREVIOUS links of UNDO-TREE nodes, to make UNDO-TREE data
 | ||
|   ;; structure non-circular.
 | ||
|   (undo-tree-mapc
 | ||
|    (lambda (node)
 | ||
|      (dolist (n (undo-tree-node-next node))
 | ||
|        (setf (undo-tree-node-previous n) nil)))
 | ||
|    (undo-tree-root undo-tree)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-recircle (undo-tree)
 | ||
|   ;; Recreate PREVIOUS links of UNDO-TREE nodes, to restore circular UNDO-TREE
 | ||
|   ;; data structure.
 | ||
|   (undo-tree-mapc
 | ||
|    (lambda (node)
 | ||
|      (dolist (n (undo-tree-node-next node))
 | ||
|        (setf (undo-tree-node-previous n) node)))
 | ||
|    (undo-tree-root undo-tree)))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;;; =====================================================================
 | ||
| ;;;             Undo list and undo changeset utility functions
 | ||
| 
 | ||
| (defmacro undo-list-marker-elt-p (elt)
 | ||
|   `(markerp (car-safe ,elt)))
 | ||
| 
 | ||
| (defmacro undo-list-GCd-marker-elt-p (elt)
 | ||
|   ;; Return t if ELT is a marker element whose marker has been moved to the
 | ||
|   ;; object-pool, so may potentially have been garbage-collected.
 | ||
|   ;; Note: Valid marker undo elements should be uniquely identified as cons
 | ||
|   ;; cells with a symbol in the car (replacing the marker), and a number in
 | ||
|   ;; the cdr. However, to guard against future changes to undo element
 | ||
|   ;; formats, we perform an additional redundant check on the symbol name.
 | ||
|   `(and (car-safe ,elt)
 | ||
| 	(symbolp (car ,elt))
 | ||
| 	(let ((str (symbol-name (car ,elt))))
 | ||
| 	  (and (> (length str) 12)
 | ||
| 	       (string= (substring str 0 12) "undo-tree-id")))
 | ||
| 	(numberp (cdr-safe ,elt))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-move-GC-elts-to-pool (elt)
 | ||
|   ;; Move elements that can be garbage-collected into `buffer-undo-tree'
 | ||
|   ;; object pool, substituting a unique id that can be used to retrieve them
 | ||
|   ;; later. (Only markers require this treatment currently.)
 | ||
|   (when (undo-list-marker-elt-p elt)
 | ||
|     (let ((id (undo-tree-generate-id)))
 | ||
|       (puthash id (car elt) (undo-tree-object-pool buffer-undo-tree))
 | ||
|       (setcar elt id))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-restore-GC-elts-from-pool (elt)
 | ||
|   ;; Replace object id's in ELT with corresponding objects from
 | ||
|   ;; `buffer-undo-tree' object pool and return modified ELT, or return nil if
 | ||
|   ;; any object in ELT has been garbage-collected.
 | ||
|   (if (undo-list-GCd-marker-elt-p elt)
 | ||
|       (when (setcar elt (gethash (car elt)
 | ||
| 				 (undo-tree-object-pool buffer-undo-tree)))
 | ||
| 	elt)
 | ||
|     elt))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-list-clean-GCd-elts (undo-list)
 | ||
|   ;; Remove object id's from UNDO-LIST that refer to elements that have been
 | ||
|   ;; garbage-collected. UNDO-LIST is modified by side-effect.
 | ||
|   (while (undo-list-GCd-marker-elt-p (car undo-list))
 | ||
|     (unless (gethash (caar undo-list)
 | ||
| 		     (undo-tree-object-pool buffer-undo-tree))
 | ||
|       (setq undo-list (cdr undo-list))))
 | ||
|   (let ((p undo-list))
 | ||
|     (while (cdr p)
 | ||
|       (when (and (undo-list-GCd-marker-elt-p (cadr p))
 | ||
| 		 (null (gethash (car (cadr p))
 | ||
| 				(undo-tree-object-pool buffer-undo-tree))))
 | ||
| 	(setcdr p (cddr p)))
 | ||
|       (setq p (cdr p))))
 | ||
|   undo-list)
 | ||
| 
 | ||
| 
 | ||
| (defun undo-list-pop-changeset (&optional discard-pos)
 | ||
|   ;; Pop changeset from `buffer-undo-list'. If DISCARD-POS is non-nil, discard
 | ||
|   ;; any position entries from changeset.
 | ||
| 
 | ||
|   ;; discard undo boundaries and (if DISCARD-POS is non-nil) position entries
 | ||
|   ;; at head of undo list
 | ||
|   (while (or (null (car buffer-undo-list))
 | ||
| 	     (and discard-pos (integerp (car buffer-undo-list))))
 | ||
|     (setq buffer-undo-list (cdr buffer-undo-list)))
 | ||
|   ;; pop elements up to next undo boundary, discarding position entries if
 | ||
|   ;; DISCARD-POS is non-nil
 | ||
|   (if (eq (car buffer-undo-list) 'undo-tree-canary)
 | ||
|       (push nil buffer-undo-list)
 | ||
|     (let* ((changeset (list (pop buffer-undo-list)))
 | ||
|            (p changeset))
 | ||
|       (while (progn
 | ||
| 	       (undo-tree-move-GC-elts-to-pool (car p))
 | ||
| 	       (while (and discard-pos (integerp (car buffer-undo-list)))
 | ||
| 		 (setq buffer-undo-list (cdr buffer-undo-list)))
 | ||
| 	       (and (car buffer-undo-list)
 | ||
| 		    (not (eq (car buffer-undo-list) 'undo-tree-canary))))
 | ||
|         (setcdr p (list (pop buffer-undo-list)))
 | ||
| 	(setq p (cdr p)))
 | ||
|       changeset)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-copy-list (undo-list)
 | ||
|   ;; Return a deep copy of first changeset in `undo-list'. Object id's are
 | ||
|   ;; replaced by corresponding objects from `buffer-undo-tree' object-pool.
 | ||
|   (when undo-list
 | ||
|     (let (copy p)
 | ||
|       ;; if first element contains an object id, replace it with object from
 | ||
|       ;; pool, discarding element entirely if it's been GC'd
 | ||
|       (while (null copy)
 | ||
| 	(setq copy
 | ||
| 	      (undo-tree-restore-GC-elts-from-pool (pop undo-list))))
 | ||
|       (setq copy (list copy)
 | ||
| 	    p copy)
 | ||
|       ;; copy remaining elements, replacing object id's with objects from
 | ||
|       ;; pool, or discarding them entirely if they've been GC'd
 | ||
|       (while undo-list
 | ||
| 	(when (setcdr p (undo-tree-restore-GC-elts-from-pool
 | ||
| 			 (undo-copy-list-1 (pop undo-list))))
 | ||
| 	  (setcdr p (list (cdr p)))
 | ||
| 	  (setq p (cdr p))))
 | ||
|       copy)))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defun undo-list-transfer-to-tree ()
 | ||
|   ;; Transfer entries accumulated in `buffer-undo-list' to `buffer-undo-tree'.
 | ||
| 
 | ||
|   ;; `undo-list-transfer-to-tree' should never be called when undo is disabled
 | ||
|   ;; (i.e. `buffer-undo-tree' is t)
 | ||
|   (assert (not (eq buffer-undo-tree t)))
 | ||
| 
 | ||
|   ;; if `buffer-undo-tree' is empty, create initial undo-tree
 | ||
|   (when (null buffer-undo-tree) (setq buffer-undo-tree (make-undo-tree)))
 | ||
|   ;; make sure there's a canary at end of `buffer-undo-list'
 | ||
|   (when (null buffer-undo-list)
 | ||
|     (setq buffer-undo-list '(nil undo-tree-canary)))
 | ||
| 
 | ||
|   (unless (or (eq (cadr buffer-undo-list) 'undo-tree-canary)
 | ||
| 	      (eq (car buffer-undo-list) 'undo-tree-canary))
 | ||
|     ;; create new node from first changeset in `buffer-undo-list', save old
 | ||
|     ;; `buffer-undo-tree' current node, and make new node the current node
 | ||
|     (let* ((node (undo-tree-make-node nil (undo-list-pop-changeset)))
 | ||
| 	   (splice (undo-tree-current buffer-undo-tree))
 | ||
| 	   (size (undo-list-byte-size (undo-tree-node-undo node)))
 | ||
| 	   (count 1))
 | ||
|       (setf (undo-tree-current buffer-undo-tree) node)
 | ||
|       ;; grow tree fragment backwards using `buffer-undo-list' changesets
 | ||
|       (while (and buffer-undo-list
 | ||
| 		  (not (eq (cadr buffer-undo-list) 'undo-tree-canary)))
 | ||
| 	(setq node
 | ||
| 	      (undo-tree-grow-backwards node (undo-list-pop-changeset)))
 | ||
| 	(incf size (undo-list-byte-size (undo-tree-node-undo node)))
 | ||
| 	(incf count))
 | ||
|       ;; if no undo history has been discarded from `buffer-undo-list' since
 | ||
|       ;; last transfer, splice new tree fragment onto end of old
 | ||
|       ;; `buffer-undo-tree' current node
 | ||
|       (if (or (eq (cadr buffer-undo-list) 'undo-tree-canary)
 | ||
| 	      (eq (car buffer-undo-list) 'undo-tree-canary))
 | ||
| 	  (progn
 | ||
| 	    (setf (undo-tree-node-previous node) splice)
 | ||
| 	    (push node (undo-tree-node-next splice))
 | ||
| 	    (setf (undo-tree-node-branch splice) 0)
 | ||
| 	    (incf (undo-tree-size buffer-undo-tree) size)
 | ||
| 	    (incf (undo-tree-count buffer-undo-tree) count))
 | ||
| 	;; if undo history has been discarded, replace entire
 | ||
| 	;; `buffer-undo-tree' with new tree fragment
 | ||
| 	(setq node (undo-tree-grow-backwards node nil))
 | ||
| 	(setf (undo-tree-root buffer-undo-tree) node)
 | ||
| 	(setq buffer-undo-list '(nil undo-tree-canary))
 | ||
| 	(setf (undo-tree-size buffer-undo-tree) size)
 | ||
| 	(setf (undo-tree-count buffer-undo-tree) count)
 | ||
| 	(setq buffer-undo-list '(nil undo-tree-canary))))
 | ||
|     ;; discard undo history if necessary
 | ||
|     (undo-tree-discard-history)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-list-byte-size (undo-list)
 | ||
|   ;; Return size (in bytes) of UNDO-LIST
 | ||
|   (let ((size 0) (p undo-list))
 | ||
|     (while p
 | ||
|       (incf size 8)  ; cons cells use up 8 bytes
 | ||
|       (when (and (consp (car p)) (stringp (caar p)))
 | ||
|         (incf size (string-bytes (caar p))))
 | ||
|       (setq p (cdr p)))
 | ||
|     size))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defun undo-list-rebuild-from-tree ()
 | ||
|   "Rebuild `buffer-undo-list' from information in `buffer-undo-tree'."
 | ||
|   (unless (eq buffer-undo-list t)
 | ||
|     (undo-list-transfer-to-tree)
 | ||
|     (setq buffer-undo-list nil)
 | ||
|     (when buffer-undo-tree
 | ||
|       (let ((stack (list (list (undo-tree-root buffer-undo-tree)))))
 | ||
| 	(push (sort (mapcar 'identity (undo-tree-node-next (caar stack)))
 | ||
| 		    (lambda (a b)
 | ||
| 		      (time-less-p (undo-tree-node-timestamp a)
 | ||
| 				   (undo-tree-node-timestamp b))))
 | ||
| 	      stack)
 | ||
| 	;; Traverse tree in depth-and-oldest-first order, but add undo records
 | ||
| 	;; on the way down, and redo records on the way up.
 | ||
| 	(while (or (car stack)
 | ||
| 		   (not (eq (car (nth 1 stack))
 | ||
| 			    (undo-tree-current buffer-undo-tree))))
 | ||
| 	  (if (car stack)
 | ||
| 	      (progn
 | ||
| 		(setq buffer-undo-list
 | ||
| 		      (append (undo-tree-node-undo (caar stack))
 | ||
| 			      buffer-undo-list))
 | ||
| 		(undo-boundary)
 | ||
| 		(push (sort (mapcar 'identity
 | ||
| 				    (undo-tree-node-next (caar stack)))
 | ||
| 			    (lambda (a b)
 | ||
| 			      (time-less-p (undo-tree-node-timestamp a)
 | ||
| 					   (undo-tree-node-timestamp b))))
 | ||
| 		      stack))
 | ||
| 	    (pop stack)
 | ||
| 	    (setq buffer-undo-list
 | ||
| 		  (append (undo-tree-node-redo (caar stack))
 | ||
| 			  buffer-undo-list))
 | ||
| 	    (undo-boundary)
 | ||
| 	    (pop (car stack))))))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;;; =====================================================================
 | ||
| ;;;                History discarding utility functions
 | ||
| 
 | ||
| (defun undo-tree-oldest-leaf (node)
 | ||
|   ;; Return oldest leaf node below NODE.
 | ||
|   (while (undo-tree-node-next node)
 | ||
|     (setq node
 | ||
|           (car (sort (mapcar 'identity (undo-tree-node-next node))
 | ||
|                      (lambda (a b)
 | ||
|                        (time-less-p (undo-tree-node-timestamp a)
 | ||
|                                     (undo-tree-node-timestamp b)))))))
 | ||
|   node)
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-discard-node (node)
 | ||
|   ;; Discard NODE from `buffer-undo-tree', and return next in line for
 | ||
|   ;; discarding.
 | ||
| 
 | ||
|   ;; don't discard current node
 | ||
|   (unless (eq node (undo-tree-current buffer-undo-tree))
 | ||
| 
 | ||
|     ;; discarding root node...
 | ||
|     (if (eq node (undo-tree-root buffer-undo-tree))
 | ||
|         (cond
 | ||
|          ;; should always discard branches before root
 | ||
|          ((> (length (undo-tree-node-next node)) 1)
 | ||
|           (error "Trying to discard undo-tree root which still\
 | ||
|  has multiple branches"))
 | ||
|          ;; don't discard root if current node is only child
 | ||
|          ((eq (car (undo-tree-node-next node))
 | ||
|               (undo-tree-current buffer-undo-tree))
 | ||
| 	  nil)
 | ||
| 	 ;; discard root
 | ||
|          (t
 | ||
| 	  ;; clear any register referring to root
 | ||
| 	  (let ((r (undo-tree-node-register node)))
 | ||
| 	    (when (and r (eq (get-register r) node))
 | ||
| 	      (set-register r nil)))
 | ||
|           ;; make child of root into new root
 | ||
|           (setq node (setf (undo-tree-root buffer-undo-tree)
 | ||
|                            (car (undo-tree-node-next node))))
 | ||
| 	  ;; update undo-tree size
 | ||
| 	  (decf (undo-tree-size buffer-undo-tree)
 | ||
| 		(+ (undo-list-byte-size (undo-tree-node-undo node))
 | ||
| 		   (undo-list-byte-size (undo-tree-node-redo node))))
 | ||
| 	  (decf (undo-tree-count buffer-undo-tree))
 | ||
| 	  ;; discard new root's undo data and PREVIOUS link
 | ||
| 	  (setf (undo-tree-node-undo node) nil
 | ||
| 		(undo-tree-node-redo node) nil
 | ||
| 		(undo-tree-node-previous node) nil)
 | ||
|           ;; if new root has branches, or new root is current node, next node
 | ||
|           ;; to discard is oldest leaf, otherwise it's new root
 | ||
|           (if (or (> (length (undo-tree-node-next node)) 1)
 | ||
|                   (eq (car (undo-tree-node-next node))
 | ||
|                       (undo-tree-current buffer-undo-tree)))
 | ||
|               (undo-tree-oldest-leaf node)
 | ||
|             node)))
 | ||
| 
 | ||
|       ;; discarding leaf node...
 | ||
|       (let* ((parent (undo-tree-node-previous node))
 | ||
|              (current (nth (undo-tree-node-branch parent)
 | ||
|                            (undo-tree-node-next parent))))
 | ||
| 	;; clear any register referring to the discarded node
 | ||
| 	(let ((r (undo-tree-node-register node)))
 | ||
| 	  (when (and r (eq (get-register r) node))
 | ||
| 	    (set-register r nil)))
 | ||
| 	;; update undo-tree size
 | ||
| 	(decf (undo-tree-size buffer-undo-tree)
 | ||
| 	      (+ (undo-list-byte-size (undo-tree-node-undo node))
 | ||
| 		 (undo-list-byte-size (undo-tree-node-redo node))))
 | ||
| 	(decf (undo-tree-count buffer-undo-tree))
 | ||
| 	;; discard leaf
 | ||
|         (setf (undo-tree-node-next parent)
 | ||
|                 (delq node (undo-tree-node-next parent))
 | ||
|               (undo-tree-node-branch parent)
 | ||
|                 (undo-tree-position current (undo-tree-node-next parent)))
 | ||
|         ;; if parent has branches, or parent is current node, next node to
 | ||
|         ;; discard is oldest leaf, otherwise it's the parent itself
 | ||
|         (if (or (eq parent (undo-tree-current buffer-undo-tree))
 | ||
|                 (and (undo-tree-node-next parent)
 | ||
|                      (or (not (eq parent (undo-tree-root buffer-undo-tree)))
 | ||
|                          (> (length (undo-tree-node-next parent)) 1))))
 | ||
|             (undo-tree-oldest-leaf parent)
 | ||
|           parent)))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-discard-history ()
 | ||
|   "Discard undo history until we're within memory usage limits
 | ||
| set by `undo-limit', `undo-strong-limit' and `undo-outer-limit'."
 | ||
| 
 | ||
|   (when (> (undo-tree-size buffer-undo-tree) undo-limit)
 | ||
|     ;; if there are no branches off root, first node to discard is root;
 | ||
|     ;; otherwise it's leaf node at botom of oldest branch
 | ||
|     (let ((node (if (> (length (undo-tree-node-next
 | ||
|                                 (undo-tree-root buffer-undo-tree))) 1)
 | ||
|                     (undo-tree-oldest-leaf (undo-tree-root buffer-undo-tree))
 | ||
|                   (undo-tree-root buffer-undo-tree))))
 | ||
| 
 | ||
|       ;; discard nodes until memory use is within `undo-strong-limit'
 | ||
|       (while (and node
 | ||
|                   (> (undo-tree-size buffer-undo-tree) undo-strong-limit))
 | ||
|         (setq node (undo-tree-discard-node node)))
 | ||
| 
 | ||
|       ;; discard nodes until next node to discard would bring memory use
 | ||
|       ;; within `undo-limit'
 | ||
|       (while (and node
 | ||
| 		  ;; check first if last discard has brought us within
 | ||
| 		  ;; `undo-limit', in case we can avoid more expensive
 | ||
| 		  ;; `undo-strong-limit' calculation
 | ||
| 		  ;; Note: this assumes undo-strong-limit > undo-limit;
 | ||
| 		  ;;       if not, effectively undo-strong-limit = undo-limit
 | ||
| 		  (> (undo-tree-size buffer-undo-tree) undo-limit)
 | ||
|                   (> (- (undo-tree-size buffer-undo-tree)
 | ||
| 			;; if next node to discard is root, the memory we
 | ||
| 			;; free-up comes from discarding changesets from its
 | ||
| 			;; only child...
 | ||
| 			(if (eq node (undo-tree-root buffer-undo-tree))
 | ||
| 			    (+ (undo-list-byte-size
 | ||
| 				(undo-tree-node-undo
 | ||
| 				 (car (undo-tree-node-next node))))
 | ||
| 			       (undo-list-byte-size
 | ||
| 				(undo-tree-node-redo
 | ||
| 				 (car (undo-tree-node-next node)))))
 | ||
| 			  ;; ...otherwise, it comes from discarding changesets
 | ||
| 			  ;; from along with the node itself
 | ||
| 			  (+ (undo-list-byte-size (undo-tree-node-undo node))
 | ||
| 			     (undo-list-byte-size (undo-tree-node-redo node)))
 | ||
| 			  ))
 | ||
|                      undo-limit))
 | ||
|         (setq node (undo-tree-discard-node node)))
 | ||
| 
 | ||
|       ;; if we're still over the `undo-outer-limit', discard entire history
 | ||
|       (when (> (undo-tree-size buffer-undo-tree) undo-outer-limit)
 | ||
|         ;; query first if `undo-ask-before-discard' is set
 | ||
|         (if undo-ask-before-discard
 | ||
|             (when (yes-or-no-p
 | ||
|                    (format
 | ||
|                     "Buffer `%s' undo info is %d bytes long;  discard it? "
 | ||
|                     (buffer-name) (undo-tree-size buffer-undo-tree)))
 | ||
|               (setq buffer-undo-tree nil))
 | ||
|           ;; otherwise, discard and display warning
 | ||
|           (display-warning
 | ||
|            '(undo discard-info)
 | ||
|            (concat
 | ||
|             (format "Buffer `%s' undo info was %d bytes long.\n"
 | ||
|                     (buffer-name) (undo-tree-size buffer-undo-tree))
 | ||
|             "The undo info was discarded because it exceeded\
 | ||
|  `undo-outer-limit'.
 | ||
| 
 | ||
| This is normal if you executed a command that made a huge change
 | ||
| to the buffer. In that case, to prevent similar problems in the
 | ||
| future, set `undo-outer-limit' to a value that is large enough to
 | ||
| cover the maximum size of normal changes you expect a single
 | ||
| command to make, but not so large that it might exceed the
 | ||
| maximum memory allotted to Emacs.
 | ||
| 
 | ||
| If you did not execute any such command, the situation is
 | ||
| probably due to a bug and you should report it.
 | ||
| 
 | ||
| You can disable the popping up of this buffer by adding the entry
 | ||
| \(undo discard-info) to the user option `warning-suppress-types',
 | ||
| which is defined in the `warnings' library.\n")
 | ||
|            :warning)
 | ||
|           (setq buffer-undo-tree nil)))
 | ||
|       )))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;;; =====================================================================
 | ||
| ;;;                   Visualizer utility functions
 | ||
| 
 | ||
| (defun undo-tree-compute-widths (node)
 | ||
|   "Recursively compute widths for nodes below NODE."
 | ||
|   (let ((stack (list node))
 | ||
|         res)
 | ||
|     (while stack
 | ||
|       ;; try to compute widths for node at top of stack
 | ||
|       (if (undo-tree-node-p
 | ||
|            (setq res (undo-tree-node-compute-widths (car stack))))
 | ||
|           ;; if computation fails, it returns a node whose widths still need
 | ||
|           ;; computing, which we push onto the stack
 | ||
|           (push res stack)
 | ||
|         ;; otherwise, store widths and remove it from stack
 | ||
|         (setf (undo-tree-node-lwidth (car stack)) (aref res 0)
 | ||
|               (undo-tree-node-cwidth (car stack)) (aref res 1)
 | ||
|               (undo-tree-node-rwidth (car stack)) (aref res 2))
 | ||
|         (pop stack)))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-node-compute-widths (node)
 | ||
|   ;; Compute NODE's left-, centre-, and right-subtree widths. Returns widths
 | ||
|   ;; (in a vector) if successful. Otherwise, returns a node whose widths need
 | ||
|   ;; calculating before NODE's can be calculated.
 | ||
|   (let ((num-children (length (undo-tree-node-next node)))
 | ||
|         (lwidth 0) (cwidth 0) (rwidth 0) p)
 | ||
|     (catch 'need-widths
 | ||
|       (cond
 | ||
|        ;; leaf nodes have 0 width
 | ||
|        ((= 0 num-children)
 | ||
|         (setf cwidth 1
 | ||
|               (undo-tree-node-lwidth node) 0
 | ||
|               (undo-tree-node-cwidth node) 1
 | ||
|               (undo-tree-node-rwidth node) 0))
 | ||
| 
 | ||
|        ;; odd number of children
 | ||
|        ((= (mod num-children 2) 1)
 | ||
|         (setq p (undo-tree-node-next node))
 | ||
|         ;; compute left-width
 | ||
|         (dotimes (i (/ num-children 2))
 | ||
|           (if (undo-tree-node-lwidth (car p))
 | ||
|               (incf lwidth (+ (undo-tree-node-lwidth (car p))
 | ||
|                               (undo-tree-node-cwidth (car p))
 | ||
|                               (undo-tree-node-rwidth (car p))))
 | ||
|             ;; if child's widths haven't been computed, return that child
 | ||
|             (throw 'need-widths (car p)))
 | ||
|           (setq p (cdr p)))
 | ||
|         (if (undo-tree-node-lwidth (car p))
 | ||
|             (incf lwidth (undo-tree-node-lwidth (car p)))
 | ||
|           (throw 'need-widths (car p)))
 | ||
|         ;; centre-width is inherited from middle child
 | ||
|         (setf cwidth (undo-tree-node-cwidth (car p)))
 | ||
|         ;; compute right-width
 | ||
|         (incf rwidth (undo-tree-node-rwidth (car p)))
 | ||
|         (setq p (cdr p))
 | ||
|         (dotimes (i (/ num-children 2))
 | ||
|           (if (undo-tree-node-lwidth (car p))
 | ||
|               (incf rwidth (+ (undo-tree-node-lwidth (car p))
 | ||
|                               (undo-tree-node-cwidth (car p))
 | ||
|                               (undo-tree-node-rwidth (car p))))
 | ||
|             (throw 'need-widths (car p)))
 | ||
|           (setq p (cdr p))))
 | ||
| 
 | ||
|        ;; even number of children
 | ||
|        (t
 | ||
|         (setq p (undo-tree-node-next node))
 | ||
|         ;; compute left-width
 | ||
|         (dotimes (i (/ num-children 2))
 | ||
|           (if (undo-tree-node-lwidth (car p))
 | ||
|               (incf lwidth (+ (undo-tree-node-lwidth (car p))
 | ||
|                               (undo-tree-node-cwidth (car p))
 | ||
|                               (undo-tree-node-rwidth (car p))))
 | ||
|             (throw 'need-widths (car p)))
 | ||
|           (setq p (cdr p)))
 | ||
|         ;; centre-width is 0 when number of children is even
 | ||
|         (setq cwidth 0)
 | ||
|         ;; compute right-width
 | ||
|         (dotimes (i (/ num-children 2))
 | ||
|           (if (undo-tree-node-lwidth (car p))
 | ||
|               (incf rwidth (+ (undo-tree-node-lwidth (car p))
 | ||
|                               (undo-tree-node-cwidth (car p))
 | ||
|                               (undo-tree-node-rwidth (car p))))
 | ||
|             (throw 'need-widths (car p)))
 | ||
|           (setq p (cdr p)))))
 | ||
| 
 | ||
|       ;; return left-, centre- and right-widths
 | ||
|       (vector lwidth cwidth rwidth))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-clear-visualizer-data (tree)
 | ||
|   ;; Clear visualizer data below NODE.
 | ||
|   (undo-tree-mapc
 | ||
|    (lambda (n) (undo-tree-node-clear-visualizer-data n))
 | ||
|    (undo-tree-root tree)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-node-unmodified-p (node &optional mtime)
 | ||
|   ;; Return non-nil if NODE corresponds to a buffer state that once upon a
 | ||
|   ;; time was unmodified. If a file modification time MTIME is specified,
 | ||
|   ;; return non-nil if the corresponding buffer state really is unmodified.
 | ||
|   (let (changeset ntime)
 | ||
|     (setq changeset
 | ||
| 	  (or (undo-tree-node-redo node)
 | ||
| 	      (and (setq changeset (car (undo-tree-node-next node)))
 | ||
| 		   (undo-tree-node-undo changeset)))
 | ||
| 	  ntime
 | ||
| 	  (catch 'found
 | ||
| 	    (dolist (elt changeset)
 | ||
| 	      (when (and (consp elt) (eq (car elt) t) (consp (cdr elt))
 | ||
| 			 (throw 'found (cdr elt)))))))
 | ||
|     (and ntime
 | ||
| 	 (or (null mtime)
 | ||
| 	     ;; high-precision timestamps
 | ||
| 	     (if (listp (cdr ntime))
 | ||
| 		 (equal ntime mtime)
 | ||
| 	       ;; old-style timestamps
 | ||
| 	       (and (= (car ntime) (car mtime))
 | ||
| 		    (= (cdr ntime) (cadr mtime))))))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;;; =====================================================================
 | ||
| ;;;                  Undo-in-region utility functions
 | ||
| 
 | ||
| ;; `undo-elt-in-region' uses this as a dynamically-scoped variable
 | ||
| (defvar undo-adjusted-markers nil)
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-pull-undo-in-region-branch (start end)
 | ||
|   ;; Pull out entries from undo changesets to create a new undo-in-region
 | ||
|   ;; branch, which undoes changeset entries lying between START and END first,
 | ||
|   ;; followed by remaining entries from the changesets, before rejoining the
 | ||
|   ;; existing undo tree history. Repeated calls will, if appropriate, extend
 | ||
|   ;; the current undo-in-region branch rather than creating a new one.
 | ||
| 
 | ||
|   ;; if we're just reverting the last redo-in-region, we don't need to
 | ||
|   ;; manipulate the undo tree at all
 | ||
|   (if (undo-tree-reverting-redo-in-region-p start end)
 | ||
|       t  ; return t to indicate success
 | ||
| 
 | ||
|     ;; We build the `region-changeset' and `delta-list' lists forwards, using
 | ||
|     ;; pointers `r' and `d' to the penultimate element of the list. So that we
 | ||
|     ;; don't have to treat the first element differently, we prepend a dummy
 | ||
|     ;; leading nil to the lists, and have the pointers point to that
 | ||
|     ;; initially.
 | ||
|     ;; Note: using '(nil) instead of (list nil) in the `let*' results in
 | ||
|     ;;       bizarre errors when the code is byte-compiled, where parts of the
 | ||
|     ;;       lists appear to survive across different calls to this function.
 | ||
|     ;;       An obscure byte-compiler bug, perhaps?
 | ||
|     (let* ((region-changeset (list nil))
 | ||
| 	   (r region-changeset)
 | ||
| 	   (delta-list (list nil))
 | ||
| 	   (d delta-list)
 | ||
| 	   (node (undo-tree-current buffer-undo-tree))
 | ||
| 	   (repeated-undo-in-region
 | ||
| 	    (undo-tree-repeated-undo-in-region-p start end))
 | ||
| 	   undo-adjusted-markers  ; `undo-elt-in-region' expects this
 | ||
| 	   fragment splice original-fragment original-splice original-current
 | ||
| 	   got-visible-elt undo-list elt)
 | ||
| 
 | ||
|       ;; --- initialisation ---
 | ||
|       (cond
 | ||
|        ;; if this is a repeated undo in the same region, start pulling changes
 | ||
|        ;; from NODE at which undo-in-region branch iss attached, and detatch
 | ||
|        ;; the branch, using it as initial FRAGMENT of branch being constructed
 | ||
|        (repeated-undo-in-region
 | ||
| 	(setq original-current node
 | ||
| 	      fragment (car (undo-tree-node-next node))
 | ||
| 	      splice node)
 | ||
| 	;; undo up to node at which undo-in-region branch is attached
 | ||
| 	;; (recognizable as first node with more than one branch)
 | ||
| 	(let ((mark-active nil))
 | ||
| 	  (while (= (length (undo-tree-node-next node)) 1)
 | ||
| 	    (undo-tree-undo-1)
 | ||
| 	    (setq fragment node
 | ||
| 		  node (undo-tree-current buffer-undo-tree))))
 | ||
| 	(when (eq splice node) (setq splice nil))
 | ||
| 	;; detatch undo-in-region branch
 | ||
| 	(setf (undo-tree-node-next node)
 | ||
| 	      (delq fragment (undo-tree-node-next node))
 | ||
| 	      (undo-tree-node-previous fragment) nil
 | ||
| 	      original-fragment fragment
 | ||
| 	      original-splice node))
 | ||
| 
 | ||
|        ;; if this is a new undo-in-region, initial FRAGMENT is a copy of all
 | ||
|        ;; nodes below the current one in the active branch
 | ||
|        ((undo-tree-node-next node)
 | ||
| 	(setq fragment (undo-tree-make-node nil nil)
 | ||
| 	      splice fragment)
 | ||
| 	(while (setq node (nth (undo-tree-node-branch node)
 | ||
| 			       (undo-tree-node-next node)))
 | ||
| 	  (push (undo-tree-make-node
 | ||
| 		 splice
 | ||
| 		 (undo-copy-list (undo-tree-node-undo node))
 | ||
| 		 (undo-copy-list (undo-tree-node-redo node)))
 | ||
| 		(undo-tree-node-next splice))
 | ||
| 	  (setq splice (car (undo-tree-node-next splice))))
 | ||
| 	(setq fragment (car (undo-tree-node-next fragment))
 | ||
| 	      splice nil
 | ||
| 	      node (undo-tree-current buffer-undo-tree))))
 | ||
| 
 | ||
| 
 | ||
|       ;; --- pull undo-in-region elements into branch ---
 | ||
|       ;; work backwards up tree, pulling out undo elements within region until
 | ||
|       ;; we've got one that undoes a visible change (insertion or deletion)
 | ||
|       (catch 'abort
 | ||
| 	(while (and (not got-visible-elt) node (undo-tree-node-undo node))
 | ||
| 	  ;; we cons a dummy nil element on the front of the changeset so that
 | ||
| 	  ;; we can conveniently remove the first (real) element from the
 | ||
| 	  ;; changeset if we need to; the leading nil is removed once we're
 | ||
| 	  ;; done with this changeset
 | ||
| 	  (setq undo-list (cons nil (undo-copy-list (undo-tree-node-undo node)))
 | ||
| 		elt (cadr undo-list))
 | ||
| 	  (if fragment
 | ||
| 	      (progn
 | ||
| 		(setq fragment (undo-tree-grow-backwards fragment undo-list))
 | ||
| 		(unless splice (setq splice fragment)))
 | ||
| 	    (setq fragment (undo-tree-make-node nil undo-list))
 | ||
| 	    (setq splice fragment))
 | ||
| 
 | ||
| 	  (while elt
 | ||
| 	    (cond
 | ||
| 	     ;; keep elements within region
 | ||
| 	     ((undo-elt-in-region elt start end)
 | ||
| 	      ;; set flag if kept element is visible (insertion or deletion)
 | ||
| 	      (when (and (consp elt)
 | ||
| 			 (or (stringp (car elt)) (integerp (car elt))))
 | ||
| 		(setq got-visible-elt t))
 | ||
| 	      ;; adjust buffer positions in elements previously undone before
 | ||
| 	      ;; kept element, as kept element will now be undone first
 | ||
| 	      (undo-tree-adjust-elements-to-elt splice elt)
 | ||
| 	      ;; move kept element to undo-in-region changeset, adjusting its
 | ||
| 	      ;; buffer position as it will now be undone first
 | ||
| 	      (setcdr r (list (undo-tree-apply-deltas elt (cdr delta-list))))
 | ||
| 	      (setq r (cdr r))
 | ||
| 	      (setcdr undo-list (cddr undo-list)))
 | ||
| 
 | ||
| 	     ;; discard "was unmodified" elements
 | ||
| 	     ;; FIXME: deal properly with these
 | ||
| 	     ((and (consp elt) (eq (car elt) t))
 | ||
| 	      (setcdr undo-list (cddr undo-list)))
 | ||
| 
 | ||
| 	     ;; if element crosses region, we can't pull any more elements
 | ||
| 	     ((undo-elt-crosses-region elt start end)
 | ||
| 	      ;; if we've found a visible element, it must be earlier in
 | ||
| 	      ;; current node's changeset; stop pulling elements (null
 | ||
| 	      ;; `undo-list' and non-nil `got-visible-elt' cause loop to exit)
 | ||
| 	      (if got-visible-elt
 | ||
| 		  (setq undo-list nil)
 | ||
| 		;; if we haven't found a visible element yet, pulling
 | ||
| 		;; undo-in-region branch has failed
 | ||
| 		(setq region-changeset nil)
 | ||
| 		(throw 'abort t)))
 | ||
| 
 | ||
| 	     ;; if rejecting element, add its delta (if any) to the list
 | ||
| 	     (t
 | ||
| 	      (let ((delta (undo-delta elt)))
 | ||
| 		(when (/= 0 (cdr delta))
 | ||
| 		  (setcdr d (list delta))
 | ||
| 		  (setq d (cdr d))))
 | ||
| 	      (setq undo-list (cdr undo-list))))
 | ||
| 
 | ||
| 	    ;; process next element of current changeset
 | ||
| 	    (setq elt (cadr undo-list)))
 | ||
| 
 | ||
| 	  ;; if there are remaining elements in changeset, remove dummy nil
 | ||
| 	  ;; from front
 | ||
| 	  (if (cadr (undo-tree-node-undo fragment))
 | ||
| 	      (pop (undo-tree-node-undo fragment))
 | ||
| 	    ;; otherwise, if we've kept all elements in changeset, discard
 | ||
| 	    ;; empty changeset
 | ||
| 	    (when (eq splice fragment) (setq splice nil))
 | ||
| 	    (setq fragment (car (undo-tree-node-next fragment))))
 | ||
| 	  ;; process changeset from next node up the tree
 | ||
| 	  (setq node (undo-tree-node-previous node))))
 | ||
| 
 | ||
|       ;; pop dummy nil from front of `region-changeset'
 | ||
|       (setq region-changeset (cdr region-changeset))
 | ||
| 
 | ||
| 
 | ||
|       ;; --- integrate branch into tree ---
 | ||
|       ;; if no undo-in-region elements were found, restore undo tree
 | ||
|       (if (null region-changeset)
 | ||
| 	  (when original-current
 | ||
| 	    (push original-fragment (undo-tree-node-next original-splice))
 | ||
| 	    (setf (undo-tree-node-branch original-splice) 0
 | ||
| 		  (undo-tree-node-previous original-fragment) original-splice)
 | ||
| 	    (let ((mark-active nil))
 | ||
| 	      (while (not (eq (undo-tree-current buffer-undo-tree)
 | ||
| 			      original-current))
 | ||
| 		(undo-tree-redo-1)))
 | ||
| 	    nil)  ; return nil to indicate failure
 | ||
| 
 | ||
| 	;; otherwise...
 | ||
| 	;; need to undo up to node where new branch will be attached, to
 | ||
| 	;; ensure redo entries are populated, and then redo back to where we
 | ||
| 	;; started
 | ||
| 	(let ((mark-active nil)
 | ||
| 	      (current (undo-tree-current buffer-undo-tree)))
 | ||
| 	  (while (not (eq (undo-tree-current buffer-undo-tree) node))
 | ||
| 	    (undo-tree-undo-1))
 | ||
| 	  (while (not (eq (undo-tree-current buffer-undo-tree) current))
 | ||
| 	    (undo-tree-redo-1)))
 | ||
| 
 | ||
| 	(cond
 | ||
| 	 ;; if there's no remaining fragment, just create undo-in-region node
 | ||
| 	 ;; and attach it to parent of last node from which elements were
 | ||
| 	 ;; pulled
 | ||
| 	 ((null fragment)
 | ||
| 	  (setq fragment (undo-tree-make-node node region-changeset))
 | ||
| 	  (push fragment (undo-tree-node-next node))
 | ||
| 	  (setf (undo-tree-node-branch node) 0)
 | ||
| 	  ;; set current node to undo-in-region node
 | ||
| 	  (setf (undo-tree-current buffer-undo-tree) fragment))
 | ||
| 
 | ||
| 	 ;; if no splice point has been set, add undo-in-region node to top of
 | ||
| 	 ;; fragment and attach it to parent of last node from which elements
 | ||
| 	 ;; were pulled
 | ||
| 	 ((null splice)
 | ||
| 	  (setq fragment (undo-tree-grow-backwards fragment region-changeset))
 | ||
| 	  (push fragment (undo-tree-node-next node))
 | ||
| 	  (setf (undo-tree-node-branch node) 0
 | ||
| 		(undo-tree-node-previous fragment) node)
 | ||
| 	  ;; set current node to undo-in-region node
 | ||
| 	  (setf (undo-tree-current buffer-undo-tree) fragment))
 | ||
| 
 | ||
| 	 ;; if fragment contains nodes, attach fragment to parent of last node
 | ||
| 	 ;; from which elements were pulled, and splice in undo-in-region node
 | ||
| 	 (t
 | ||
| 	  (setf (undo-tree-node-previous fragment) node)
 | ||
| 	  (push fragment (undo-tree-node-next node))
 | ||
| 	  (setf (undo-tree-node-branch node) 0)
 | ||
| 	  ;; if this is a repeated undo-in-region, then we've left the current
 | ||
| 	  ;; node at the original splice-point; we need to set the current
 | ||
| 	  ;; node to the equivalent node on the undo-in-region branch and redo
 | ||
| 	  ;; back to where we started
 | ||
| 	  (when repeated-undo-in-region
 | ||
| 	    (setf (undo-tree-current buffer-undo-tree)
 | ||
| 		  (undo-tree-node-previous original-fragment))
 | ||
| 	    (let ((mark-active nil))
 | ||
| 	      (while (not (eq (undo-tree-current buffer-undo-tree) splice))
 | ||
| 		(undo-tree-redo-1 nil 'preserve-undo))))
 | ||
| 	  ;; splice new undo-in-region node into fragment
 | ||
| 	  (setq node (undo-tree-make-node nil region-changeset))
 | ||
| 	  (undo-tree-splice-node node splice)
 | ||
| 	  ;; set current node to undo-in-region node
 | ||
| 	  (setf (undo-tree-current buffer-undo-tree) node)))
 | ||
| 
 | ||
| 	;; update undo-tree size
 | ||
| 	(setq node (undo-tree-node-previous fragment))
 | ||
| 	(while (progn
 | ||
| 		 (and (setq node (car (undo-tree-node-next node)))
 | ||
| 		      (not (eq node original-fragment))
 | ||
| 		      (incf (undo-tree-count buffer-undo-tree))
 | ||
| 		      (incf (undo-tree-size buffer-undo-tree)
 | ||
| 			    (+ (undo-list-byte-size (undo-tree-node-undo node))
 | ||
| 			       (undo-list-byte-size (undo-tree-node-redo node)))))))
 | ||
| 	t)  ; indicate undo-in-region branch was successfully pulled
 | ||
|       )))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-pull-redo-in-region-branch (start end)
 | ||
|   ;; Pull out entries from redo changesets to create a new redo-in-region
 | ||
|   ;; branch, which redoes changeset entries lying between START and END first,
 | ||
|   ;; followed by remaining entries from the changesets. Repeated calls will,
 | ||
|   ;; if appropriate, extend the current redo-in-region branch rather than
 | ||
|   ;; creating a new one.
 | ||
| 
 | ||
|   ;; if we're just reverting the last undo-in-region, we don't need to
 | ||
|   ;; manipulate the undo tree at all
 | ||
|   (if (undo-tree-reverting-undo-in-region-p start end)
 | ||
|       t  ; return t to indicate success
 | ||
| 
 | ||
|     ;; We build the `region-changeset' and `delta-list' lists forwards, using
 | ||
|     ;; pointers `r' and `d' to the penultimate element of the list. So that we
 | ||
|     ;; don't have to treat the first element differently, we prepend a dummy
 | ||
|     ;; leading nil to the lists, and have the pointers point to that
 | ||
|     ;; initially.
 | ||
|     ;; Note: using '(nil) instead of (list nil) in the `let*' causes bizarre
 | ||
|     ;;       errors when the code is byte-compiled, where parts of the lists
 | ||
|     ;;       appear to survive across different calls to this function.  An
 | ||
|     ;;       obscure byte-compiler bug, perhaps?
 | ||
|     (let* ((region-changeset (list nil))
 | ||
| 	   (r region-changeset)
 | ||
| 	   (delta-list (list nil))
 | ||
| 	   (d delta-list)
 | ||
| 	   (node (undo-tree-current buffer-undo-tree))
 | ||
| 	   (repeated-redo-in-region
 | ||
| 	    (undo-tree-repeated-redo-in-region-p start end))
 | ||
| 	   undo-adjusted-markers  ; `undo-elt-in-region' expects this
 | ||
| 	   fragment splice got-visible-elt redo-list elt)
 | ||
| 
 | ||
|       ;; --- inisitalisation ---
 | ||
|       (cond
 | ||
|        ;; if this is a repeated redo-in-region, detach fragment below current
 | ||
|        ;; node
 | ||
|        (repeated-redo-in-region
 | ||
| 	(when (setq fragment (car (undo-tree-node-next node)))
 | ||
| 	  (setf (undo-tree-node-previous fragment) nil
 | ||
| 		(undo-tree-node-next node)
 | ||
| 		(delq fragment (undo-tree-node-next node)))))
 | ||
|        ;; if this is a new redo-in-region, initial fragment is a copy of all
 | ||
|        ;; nodes below the current one in the active branch
 | ||
|        ((undo-tree-node-next node)
 | ||
| 	(setq fragment (undo-tree-make-node nil nil)
 | ||
| 	      splice fragment)
 | ||
| 	(while (setq node (nth (undo-tree-node-branch node)
 | ||
| 			       (undo-tree-node-next node)))
 | ||
| 	  (push (undo-tree-make-node
 | ||
| 		 splice nil
 | ||
| 		 (undo-copy-list (undo-tree-node-redo node)))
 | ||
| 		(undo-tree-node-next splice))
 | ||
| 	  (setq splice (car (undo-tree-node-next splice))))
 | ||
| 	(setq fragment (car (undo-tree-node-next fragment)))))
 | ||
| 
 | ||
| 
 | ||
|       ;; --- pull redo-in-region elements into branch ---
 | ||
|       ;; work down fragment, pulling out redo elements within region until
 | ||
|       ;; we've got one that redoes a visible change (insertion or deletion)
 | ||
|       (setq node fragment)
 | ||
|       (catch 'abort
 | ||
| 	(while (and (not got-visible-elt) node (undo-tree-node-redo node))
 | ||
| 	  ;; we cons a dummy nil element on the front of the changeset so that
 | ||
| 	  ;; we can conveniently remove the first (real) element from the
 | ||
| 	  ;; changeset if we need to; the leading nil is removed once we're
 | ||
| 	  ;; done with this changeset
 | ||
| 	  (setq redo-list (push nil (undo-tree-node-redo node))
 | ||
| 		elt (cadr redo-list))
 | ||
| 	  (while elt
 | ||
| 	    (cond
 | ||
| 	     ;; keep elements within region
 | ||
| 	     ((undo-elt-in-region elt start end)
 | ||
| 	      ;; set flag if kept element is visible (insertion or deletion)
 | ||
| 	      (when (and (consp elt)
 | ||
| 			 (or (stringp (car elt)) (integerp (car elt))))
 | ||
| 		(setq got-visible-elt t))
 | ||
| 	      ;; adjust buffer positions in elements previously redone before
 | ||
| 	      ;; kept element, as kept element will now be redone first
 | ||
| 	      (undo-tree-adjust-elements-to-elt fragment elt t)
 | ||
| 	      ;; move kept element to redo-in-region changeset, adjusting its
 | ||
| 	      ;; buffer position as it will now be redone first
 | ||
| 	      (setcdr r (list (undo-tree-apply-deltas elt (cdr delta-list) -1)))
 | ||
| 	      (setq r (cdr r))
 | ||
| 	      (setcdr redo-list (cddr redo-list)))
 | ||
| 
 | ||
| 	     ;; discard "was unmodified" elements
 | ||
| 	     ;; FIXME: deal properly with these
 | ||
| 	     ((and (consp elt) (eq (car elt) t))
 | ||
| 	      (setcdr redo-list (cddr redo-list)))
 | ||
| 
 | ||
| 	     ;; if element crosses region, we can't pull any more elements
 | ||
| 	     ((undo-elt-crosses-region elt start end)
 | ||
| 	      ;; if we've found a visible element, it must be earlier in
 | ||
| 	      ;; current node's changeset; stop pulling elements (null
 | ||
| 	      ;; `redo-list' and non-nil `got-visible-elt' cause loop to exit)
 | ||
| 	      (if got-visible-elt
 | ||
| 		  (setq redo-list nil)
 | ||
| 		;; if we haven't found a visible element yet, pulling
 | ||
| 		;; redo-in-region branch has failed
 | ||
| 		(setq region-changeset nil)
 | ||
| 		(throw 'abort t)))
 | ||
| 
 | ||
| 	     ;; if rejecting element, add its delta (if any) to the list
 | ||
| 	     (t
 | ||
| 	      (let ((delta (undo-delta elt)))
 | ||
| 		(when (/= 0 (cdr delta))
 | ||
| 		  (setcdr d (list delta))
 | ||
| 		  (setq d (cdr d))))
 | ||
| 	      (setq redo-list (cdr redo-list))))
 | ||
| 
 | ||
| 	    ;; process next element of current changeset
 | ||
| 	    (setq elt (cadr redo-list)))
 | ||
| 
 | ||
| 	  ;; if there are remaining elements in changeset, remove dummy nil
 | ||
| 	  ;; from front
 | ||
| 	  (if (cadr (undo-tree-node-redo node))
 | ||
| 	      (pop (undo-tree-node-undo node))
 | ||
| 	    ;; otherwise, if we've kept all elements in changeset, discard
 | ||
| 	    ;; empty changeset
 | ||
| 	    (if (eq fragment node)
 | ||
| 		(setq fragment (car (undo-tree-node-next fragment)))
 | ||
| 	      (undo-tree-snip-node node)))
 | ||
| 	  ;; process changeset from next node in fragment
 | ||
| 	  (setq node (car (undo-tree-node-next node)))))
 | ||
| 
 | ||
|       ;; pop dummy nil from front of `region-changeset'
 | ||
|       (setq region-changeset (cdr region-changeset))
 | ||
| 
 | ||
| 
 | ||
|       ;; --- integrate branch into tree ---
 | ||
|       (setq node (undo-tree-current buffer-undo-tree))
 | ||
|       ;; if no redo-in-region elements were found, restore undo tree
 | ||
|       (if (null (car region-changeset))
 | ||
| 	  (when (and repeated-redo-in-region fragment)
 | ||
| 	    (push fragment (undo-tree-node-next node))
 | ||
| 	    (setf (undo-tree-node-branch node) 0
 | ||
| 		  (undo-tree-node-previous fragment) node)
 | ||
| 	    nil)  ; return nil to indicate failure
 | ||
| 
 | ||
| 	;; otherwise, add redo-in-region node to top of fragment, and attach
 | ||
| 	;; it below current node
 | ||
| 	(setq fragment
 | ||
| 	      (if fragment
 | ||
| 		  (undo-tree-grow-backwards fragment nil region-changeset)
 | ||
| 		(undo-tree-make-node nil nil region-changeset)))
 | ||
| 	(push fragment (undo-tree-node-next node))
 | ||
| 	(setf (undo-tree-node-branch node) 0
 | ||
| 	      (undo-tree-node-previous fragment) node)
 | ||
| 	;; update undo-tree size
 | ||
| 	(unless repeated-redo-in-region
 | ||
| 	  (setq node fragment)
 | ||
| 	  (while (and (setq node (car (undo-tree-node-next node)))
 | ||
| 		      (incf (undo-tree-count buffer-undo-tree))
 | ||
| 		      (incf (undo-tree-size buffer-undo-tree)
 | ||
| 			    (undo-list-byte-size
 | ||
| 			     (undo-tree-node-redo node))))))
 | ||
| 	(incf (undo-tree-size buffer-undo-tree)
 | ||
| 	      (undo-list-byte-size (undo-tree-node-redo fragment)))
 | ||
| 	t)  ; indicate redo-in-region branch was successfully pulled
 | ||
|       )))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-adjust-elements-to-elt (node undo-elt &optional below)
 | ||
|   "Adjust buffer positions of undo elements, starting at NODE's
 | ||
| and going up the tree (or down the active branch if BELOW is
 | ||
| non-nil) and through the nodes' undo elements until we reach
 | ||
| UNDO-ELT.  UNDO-ELT must appear somewhere in the undo changeset
 | ||
| of either NODE itself or some node above it in the tree."
 | ||
|   (let ((delta (list (undo-delta undo-elt)))
 | ||
| 	(undo-list (undo-tree-node-undo node)))
 | ||
|     ;; adjust elements until we reach UNDO-ELT
 | ||
|     (while (and (car undo-list)
 | ||
| 		(not (eq (car undo-list) undo-elt)))
 | ||
|       (setcar undo-list
 | ||
| 	      (undo-tree-apply-deltas (car undo-list) delta -1))
 | ||
|       ;; move to next undo element in list, or to next node if we've run out
 | ||
|       ;; of elements
 | ||
|       (unless (car (setq undo-list (cdr undo-list)))
 | ||
| 	(if below
 | ||
| 	    (setq node (nth (undo-tree-node-branch node)
 | ||
| 			    (undo-tree-node-next node)))
 | ||
| 	  (setq node (undo-tree-node-previous node)))
 | ||
| 	(setq undo-list (undo-tree-node-undo node))))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-apply-deltas (undo-elt deltas &optional sgn)
 | ||
|   ;; Apply DELTAS in order to UNDO-ELT, multiplying deltas by SGN
 | ||
|   ;; (only useful value for SGN is -1).
 | ||
|   (let (position offset)
 | ||
|     (dolist (delta deltas)
 | ||
|       (setq position (car delta)
 | ||
| 	    offset (* (cdr delta) (or sgn 1)))
 | ||
|       (cond
 | ||
|        ;; POSITION
 | ||
|        ((integerp undo-elt)
 | ||
| 	(when (>= undo-elt position)
 | ||
| 	  (setq undo-elt (- undo-elt offset))))
 | ||
|        ;; nil (or any other atom)
 | ||
|        ((atom undo-elt))
 | ||
|        ;; (TEXT . POSITION)
 | ||
|        ((stringp (car undo-elt))
 | ||
| 	(let ((text-pos (abs (cdr undo-elt)))
 | ||
| 	      (point-at-end (< (cdr undo-elt) 0)))
 | ||
| 	  (if (>= text-pos position)
 | ||
| 	      (setcdr undo-elt (* (if point-at-end -1 1)
 | ||
| 				  (- text-pos offset))))))
 | ||
|        ;; (BEGIN . END)
 | ||
|        ((integerp (car undo-elt))
 | ||
| 	(when (>= (car undo-elt) position)
 | ||
| 	  (setcar undo-elt (- (car undo-elt) offset))
 | ||
| 	  (setcdr undo-elt (- (cdr undo-elt) offset))))
 | ||
|        ;; (nil PROPERTY VALUE BEG . END)
 | ||
|        ((null (car undo-elt))
 | ||
| 	(let ((tail (nthcdr 3 undo-elt)))
 | ||
| 	  (when (>= (car tail) position)
 | ||
| 	    (setcar tail (- (car tail) offset))
 | ||
| 	    (setcdr tail (- (cdr tail) offset)))))
 | ||
|        ))
 | ||
|     undo-elt))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-repeated-undo-in-region-p (start end)
 | ||
|   ;; Return non-nil if undo-in-region between START and END is a repeated
 | ||
|   ;; undo-in-region
 | ||
|   (let ((node (undo-tree-current buffer-undo-tree)))
 | ||
|     (and (setq node
 | ||
| 	       (nth (undo-tree-node-branch node) (undo-tree-node-next node)))
 | ||
| 	 (eq (undo-tree-node-undo-beginning node) start)
 | ||
| 	 (eq (undo-tree-node-undo-end node) end))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-repeated-redo-in-region-p (start end)
 | ||
|   ;; Return non-nil if undo-in-region between START and END is a repeated
 | ||
|   ;; undo-in-region
 | ||
|   (let ((node (undo-tree-current buffer-undo-tree)))
 | ||
|     (and (eq (undo-tree-node-redo-beginning node) start)
 | ||
| 	 (eq (undo-tree-node-redo-end node) end))))
 | ||
| 
 | ||
| 
 | ||
| ;; Return non-nil if undo-in-region between START and END is simply
 | ||
| ;; reverting the last redo-in-region
 | ||
| (defalias 'undo-tree-reverting-undo-in-region-p
 | ||
|   'undo-tree-repeated-undo-in-region-p)
 | ||
| 
 | ||
| 
 | ||
| ;; Return non-nil if redo-in-region between START and END is simply
 | ||
| ;; reverting the last undo-in-region
 | ||
| (defalias 'undo-tree-reverting-redo-in-region-p
 | ||
|   'undo-tree-repeated-redo-in-region-p)
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;;; =====================================================================
 | ||
| ;;;                        Undo-tree commands
 | ||
| 
 | ||
| ;;;###autoload
 | ||
| (define-minor-mode undo-tree-mode
 | ||
|   "Toggle undo-tree mode.
 | ||
| With no argument, this command toggles the mode.
 | ||
| A positive prefix argument turns the mode on.
 | ||
| A negative prefix argument turns it off.
 | ||
| 
 | ||
| Undo-tree-mode replaces Emacs' standard undo feature with a more
 | ||
| powerful yet easier to use version, that treats the undo history
 | ||
| as what it is: a tree.
 | ||
| 
 | ||
| The following keys are available in `undo-tree-mode':
 | ||
| 
 | ||
|   \\{undo-tree-map}
 | ||
| 
 | ||
| Within the undo-tree visualizer, the following keys are available:
 | ||
| 
 | ||
|   \\{undo-tree-visualizer-mode-map}"
 | ||
| 
 | ||
|   nil                       ; init value
 | ||
|   undo-tree-mode-lighter    ; lighter
 | ||
|   undo-tree-map             ; keymap
 | ||
| 
 | ||
|   ;; if disabling `undo-tree-mode', rebuild `buffer-undo-list' from tree so
 | ||
|   ;; Emacs undo can work
 | ||
|   (when (not undo-tree-mode)
 | ||
|     (undo-list-rebuild-from-tree)
 | ||
|     (setq buffer-undo-tree nil)))
 | ||
| 
 | ||
| 
 | ||
| (defun turn-on-undo-tree-mode (&optional print-message)
 | ||
|   "Enable `undo-tree-mode' in the current buffer, when appropriate.
 | ||
| Some major modes implement their own undo system, which should
 | ||
| not normally be overridden by `undo-tree-mode'. This command does
 | ||
| not enable `undo-tree-mode' in such buffers. If you want to force
 | ||
| `undo-tree-mode' to be enabled regardless, use (undo-tree-mode 1)
 | ||
| instead.
 | ||
| 
 | ||
| The heuristic used to detect major modes in which
 | ||
| `undo-tree-mode' should not be used is to check whether either
 | ||
| the `undo' command has been remapped, or the default undo
 | ||
| keybindings (C-/ and C-_) have been overridden somewhere other
 | ||
| than in the global map. In addition, `undo-tree-mode' will not be
 | ||
| enabled if the buffer's `major-mode' appears in
 | ||
| `undo-tree-incompatible-major-modes'."
 | ||
|   (interactive "p")
 | ||
|   (if (or (key-binding [remap undo])
 | ||
| 	  (undo-tree-overridden-undo-bindings-p)
 | ||
| 	  (memq major-mode undo-tree-incompatible-major-modes))
 | ||
|       (when print-message
 | ||
| 	(message "Buffer does not support undo-tree-mode;\
 | ||
|  undo-tree-mode NOT enabled"))
 | ||
|     (undo-tree-mode 1)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-overridden-undo-bindings-p ()
 | ||
|   "Returns t if default undo bindings are overridden, nil otherwise.
 | ||
| Checks if either of the default undo key bindings (\"C-/\" or
 | ||
| \"C-_\") are overridden in the current buffer by any keymap other
 | ||
| than the global one. (So global redefinitions of the default undo
 | ||
| key bindings do not count.)"
 | ||
|   (let ((binding1 (lookup-key (current-global-map) [?\C-/]))
 | ||
| 	(binding2 (lookup-key (current-global-map) [?\C-_])))
 | ||
|     (global-set-key [?\C-/] 'undo)
 | ||
|     (global-set-key [?\C-_] 'undo)
 | ||
|     (unwind-protect
 | ||
| 	(or (and (key-binding [?\C-/])
 | ||
| 		 (not (eq (key-binding [?\C-/]) 'undo)))
 | ||
| 	    (and (key-binding [?\C-_])
 | ||
| 		 (not (eq (key-binding [?\C-_]) 'undo))))
 | ||
|       (global-set-key [?\C-/] binding1)
 | ||
|       (global-set-key [?\C-_] binding2))))
 | ||
| 
 | ||
| 
 | ||
| ;;;###autoload
 | ||
| (define-globalized-minor-mode global-undo-tree-mode
 | ||
|   undo-tree-mode turn-on-undo-tree-mode)
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-undo (&optional arg)
 | ||
|   "Undo changes.
 | ||
| Repeat this command to undo more changes.
 | ||
| A numeric ARG serves as a repeat count.
 | ||
| 
 | ||
| In Transient Mark mode when the mark is active, only undo changes
 | ||
| within the current region. Similarly, when not in Transient Mark
 | ||
| mode, just \\[universal-argument] as an argument limits undo to
 | ||
| changes within the current region."
 | ||
|   (interactive "*P")
 | ||
|   ;; throw error if undo is disabled in buffer
 | ||
|   (when (eq buffer-undo-list t)
 | ||
|     (user-error "No undo information in this buffer"))
 | ||
|   (undo-tree-undo-1 arg)
 | ||
|   ;; inform user if at branch point
 | ||
|   (when (> (undo-tree-num-branches) 1) (message "Undo branch point!")))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-undo-1 (&optional arg preserve-redo preserve-timestamps)
 | ||
|   ;; Internal undo function. An active mark in `transient-mark-mode', or
 | ||
|   ;; non-nil ARG otherwise, enables undo-in-region. Non-nil PRESERVE-REDO
 | ||
|   ;; causes the existing redo record to be preserved, rather than replacing it
 | ||
|   ;; with the new one generated by undoing. Non-nil PRESERVE-TIMESTAMPS
 | ||
|   ;; disables updating of timestamps in visited undo-tree nodes. (This latter
 | ||
|   ;; should *only* be used when temporarily visiting another undo state and
 | ||
|   ;; immediately returning to the original state afterwards. Otherwise, it
 | ||
|   ;; could cause history-discarding errors.)
 | ||
|   (let ((undo-in-progress t)
 | ||
| 	(undo-in-region (and undo-tree-enable-undo-in-region
 | ||
| 			     (or (region-active-p)
 | ||
| 				 (and arg (not (numberp arg))))))
 | ||
| 	pos current)
 | ||
|     ;; transfer entries accumulated in `buffer-undo-list' to
 | ||
|     ;; `buffer-undo-tree'
 | ||
|     (undo-list-transfer-to-tree)
 | ||
| 
 | ||
|     (dotimes (i (or (and (numberp arg) (prefix-numeric-value arg)) 1))
 | ||
|       ;; check if at top of undo tree
 | ||
|       (unless (undo-tree-node-previous (undo-tree-current buffer-undo-tree))
 | ||
| 	(user-error "No further undo information"))
 | ||
| 
 | ||
|       ;; if region is active, or a non-numeric prefix argument was supplied,
 | ||
|       ;; try to pull out a new branch of changes affecting the region
 | ||
|       (when (and undo-in-region
 | ||
| 		 (not (undo-tree-pull-undo-in-region-branch
 | ||
| 		       (region-beginning) (region-end))))
 | ||
| 	(user-error "No further undo information for region"))
 | ||
| 
 | ||
|       ;; remove any GC'd elements from node's undo list
 | ||
|       (setq current (undo-tree-current buffer-undo-tree))
 | ||
|       (decf (undo-tree-size buffer-undo-tree)
 | ||
| 	    (undo-list-byte-size (undo-tree-node-undo current)))
 | ||
|       (setf (undo-tree-node-undo current)
 | ||
| 	    (undo-list-clean-GCd-elts (undo-tree-node-undo current)))
 | ||
|       (incf (undo-tree-size buffer-undo-tree)
 | ||
| 	    (undo-list-byte-size (undo-tree-node-undo current)))
 | ||
|       ;; undo one record from undo tree
 | ||
|       (when undo-in-region
 | ||
| 	(setq pos (set-marker (make-marker) (point)))
 | ||
| 	(set-marker-insertion-type pos t))
 | ||
|       (primitive-undo 1 (undo-tree-copy-list (undo-tree-node-undo current)))
 | ||
|       (undo-boundary)
 | ||
| 
 | ||
|       ;; if preserving old redo record, discard new redo entries that
 | ||
|       ;; `primitive-undo' has added to `buffer-undo-list', and remove any GC'd
 | ||
|       ;; elements from node's redo list
 | ||
|       (if preserve-redo
 | ||
| 	  (progn
 | ||
| 	    (undo-list-pop-changeset)
 | ||
| 	    (decf (undo-tree-size buffer-undo-tree)
 | ||
| 		  (undo-list-byte-size (undo-tree-node-redo current)))
 | ||
| 	    (setf (undo-tree-node-redo current)
 | ||
| 		  (undo-list-clean-GCd-elts (undo-tree-node-redo current)))
 | ||
| 	    (incf (undo-tree-size buffer-undo-tree)
 | ||
| 		  (undo-list-byte-size (undo-tree-node-redo current))))
 | ||
| 	;; otherwise, record redo entries that `primitive-undo' has added to
 | ||
| 	;; `buffer-undo-list' in current node's redo record, replacing
 | ||
| 	;; existing entry if one already exists
 | ||
| 	(decf (undo-tree-size buffer-undo-tree)
 | ||
| 	      (undo-list-byte-size (undo-tree-node-redo current)))
 | ||
| 	(setf (undo-tree-node-redo current)
 | ||
| 	      (undo-list-pop-changeset 'discard-pos))
 | ||
| 	(incf (undo-tree-size buffer-undo-tree)
 | ||
| 	      (undo-list-byte-size (undo-tree-node-redo current))))
 | ||
| 
 | ||
|       ;; rewind current node and update timestamp
 | ||
|       (setf (undo-tree-current buffer-undo-tree)
 | ||
| 	    (undo-tree-node-previous (undo-tree-current buffer-undo-tree)))
 | ||
|       (unless preserve-timestamps
 | ||
| 	(setf (undo-tree-node-timestamp (undo-tree-current buffer-undo-tree))
 | ||
| 	      (current-time)))
 | ||
| 
 | ||
|       ;; if undoing-in-region, record current node, region and direction so we
 | ||
|       ;; can tell if undo-in-region is repeated, and re-activate mark if in
 | ||
|       ;; `transient-mark-mode'; if not, erase any leftover data
 | ||
|       (if (not undo-in-region)
 | ||
| 	  (undo-tree-node-clear-region-data current)
 | ||
| 	(goto-char pos)
 | ||
| 	;; note: we deliberately want to store the region information in the
 | ||
| 	;; node *below* the now current one
 | ||
| 	(setf (undo-tree-node-undo-beginning current) (region-beginning)
 | ||
| 	      (undo-tree-node-undo-end current) (region-end))
 | ||
| 	(set-marker pos nil)))
 | ||
| 
 | ||
|     ;; undo deactivates mark unless undoing-in-region
 | ||
|     (setq deactivate-mark (not undo-in-region))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-redo (&optional arg)
 | ||
|   "Redo changes. A numeric ARG serves as a repeat count.
 | ||
| 
 | ||
| In Transient Mark mode when the mark is active, only redo changes
 | ||
| within the current region. Similarly, when not in Transient Mark
 | ||
| mode, just \\[universal-argument] as an argument limits redo to
 | ||
| changes within the current region."
 | ||
|   (interactive "*P")
 | ||
|   ;; throw error if undo is disabled in buffer
 | ||
|   (when (eq buffer-undo-list t)
 | ||
|     (user-error "No undo information in this buffer"))
 | ||
|   (undo-tree-redo-1 arg)
 | ||
|   ;; inform user if at branch point
 | ||
|   (when (> (undo-tree-num-branches) 1) (message "Undo branch point!")))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-redo-1 (&optional arg preserve-undo preserve-timestamps)
 | ||
|   ;; Internal redo function. An active mark in `transient-mark-mode', or
 | ||
|   ;; non-nil ARG otherwise, enables undo-in-region. Non-nil PRESERVE-UNDO
 | ||
|   ;; causes the existing redo record to be preserved, rather than replacing it
 | ||
|   ;; with the new one generated by undoing. Non-nil PRESERVE-TIMESTAMPS
 | ||
|   ;; disables updating of timestamps in visited undo-tree nodes. (This latter
 | ||
|   ;; should *only* be used when temporarily visiting another undo state and
 | ||
|   ;; immediately returning to the original state afterwards. Otherwise, it
 | ||
|   ;; could cause history-discarding errors.)
 | ||
|   (let ((undo-in-progress t)
 | ||
| 	(redo-in-region (and undo-tree-enable-undo-in-region
 | ||
| 			     (or (region-active-p)
 | ||
| 				 (and arg (not (numberp arg))))))
 | ||
| 	pos current)
 | ||
|     ;; transfer entries accumulated in `buffer-undo-list' to
 | ||
|     ;; `buffer-undo-tree'
 | ||
|     (undo-list-transfer-to-tree)
 | ||
| 
 | ||
|     (dotimes (i (or (and (numberp arg) (prefix-numeric-value arg)) 1))
 | ||
|       ;; check if at bottom of undo tree
 | ||
|       (when (null (undo-tree-node-next (undo-tree-current buffer-undo-tree)))
 | ||
| 	(user-error "No further redo information"))
 | ||
| 
 | ||
|       ;; if region is active, or a non-numeric prefix argument was supplied,
 | ||
|       ;; try to pull out a new branch of changes affecting the region
 | ||
|       (when (and redo-in-region
 | ||
| 		 (not (undo-tree-pull-redo-in-region-branch
 | ||
| 		       (region-beginning) (region-end))))
 | ||
| 	(user-error "No further redo information for region"))
 | ||
| 
 | ||
|       ;; get next node (but DON'T advance current node in tree yet, in case
 | ||
|       ;; redoing fails)
 | ||
|       (setq current (undo-tree-current buffer-undo-tree)
 | ||
| 	    current (nth (undo-tree-node-branch current)
 | ||
| 			 (undo-tree-node-next current)))
 | ||
|       ;; remove any GC'd elements from node's redo list
 | ||
|       (decf (undo-tree-size buffer-undo-tree)
 | ||
| 	    (undo-list-byte-size (undo-tree-node-redo current)))
 | ||
|       (setf (undo-tree-node-redo current)
 | ||
| 	    (undo-list-clean-GCd-elts (undo-tree-node-redo current)))
 | ||
|       (incf (undo-tree-size buffer-undo-tree)
 | ||
| 	    (undo-list-byte-size (undo-tree-node-redo current)))
 | ||
|       ;; redo one record from undo tree
 | ||
|       (when redo-in-region
 | ||
| 	(setq pos (set-marker (make-marker) (point)))
 | ||
| 	(set-marker-insertion-type pos t))
 | ||
|       (primitive-undo 1 (undo-tree-copy-list (undo-tree-node-redo current)))
 | ||
|       (undo-boundary)
 | ||
|       ;; advance current node in tree
 | ||
|       (setf (undo-tree-current buffer-undo-tree) current)
 | ||
| 
 | ||
|       ;; if preserving old undo record, discard new undo entries that
 | ||
|       ;; `primitive-undo' has added to `buffer-undo-list', and remove any GC'd
 | ||
|       ;; elements from node's redo list
 | ||
|       (if preserve-undo
 | ||
| 	  (progn
 | ||
| 	    (undo-list-pop-changeset)
 | ||
| 	    (decf (undo-tree-size buffer-undo-tree)
 | ||
| 		  (undo-list-byte-size (undo-tree-node-undo current)))
 | ||
| 	    (setf (undo-tree-node-undo current)
 | ||
| 		  (undo-list-clean-GCd-elts (undo-tree-node-undo current)))
 | ||
| 	    (incf (undo-tree-size buffer-undo-tree)
 | ||
| 		  (undo-list-byte-size (undo-tree-node-undo current))))
 | ||
| 	;; otherwise, record undo entries that `primitive-undo' has added to
 | ||
| 	;; `buffer-undo-list' in current node's undo record, replacing
 | ||
| 	;; existing entry if one already exists
 | ||
| 	(decf (undo-tree-size buffer-undo-tree)
 | ||
| 	      (undo-list-byte-size (undo-tree-node-undo current)))
 | ||
| 	(setf (undo-tree-node-undo current)
 | ||
| 	      (undo-list-pop-changeset 'discard-pos))
 | ||
| 	(incf (undo-tree-size buffer-undo-tree)
 | ||
| 	      (undo-list-byte-size (undo-tree-node-undo current))))
 | ||
| 
 | ||
|       ;; update timestamp
 | ||
|       (unless preserve-timestamps
 | ||
| 	(setf (undo-tree-node-timestamp current) (current-time)))
 | ||
| 
 | ||
|       ;; if redoing-in-region, record current node, region and direction so we
 | ||
|       ;; can tell if redo-in-region is repeated, and re-activate mark if in
 | ||
|       ;; `transient-mark-mode'
 | ||
|       (if (not redo-in-region)
 | ||
| 	  (undo-tree-node-clear-region-data current)
 | ||
| 	(goto-char pos)
 | ||
| 	(setf (undo-tree-node-redo-beginning current) (region-beginning)
 | ||
| 	      (undo-tree-node-redo-end current) (region-end))
 | ||
| 	(set-marker pos nil)))
 | ||
| 
 | ||
|     ;; redo deactivates the mark unless redoing-in-region
 | ||
|     (setq deactivate-mark (not redo-in-region))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-switch-branch (branch)
 | ||
|   "Switch to a different BRANCH of the undo tree.
 | ||
| This will affect which branch to descend when *redoing* changes
 | ||
| using `undo-tree-redo'."
 | ||
|   (interactive (list (or (and prefix-arg (prefix-numeric-value prefix-arg))
 | ||
|                          (and (not (eq buffer-undo-list t))
 | ||
| 			      (or (undo-list-transfer-to-tree) t)
 | ||
| 			      (let ((b (undo-tree-node-branch
 | ||
| 					(undo-tree-current
 | ||
| 					 buffer-undo-tree))))
 | ||
| 				(cond
 | ||
| 				 ;; switch to other branch if only 2
 | ||
| 				 ((= (undo-tree-num-branches) 2) (- 1 b))
 | ||
| 				 ;; prompt if more than 2
 | ||
| 				 ((> (undo-tree-num-branches) 2)
 | ||
| 				  (read-number
 | ||
| 				   (format "Branch (0-%d, on %d): "
 | ||
| 					   (1- (undo-tree-num-branches)) b)))
 | ||
| 				 ))))))
 | ||
|   ;; throw error if undo is disabled in buffer
 | ||
|   (when (eq buffer-undo-list t)
 | ||
|     (user-error "No undo information in this buffer"))
 | ||
|   ;; sanity check branch number
 | ||
|   (when (<= (undo-tree-num-branches) 1)
 | ||
|     (user-error "Not at undo branch point"))
 | ||
|   (when (or (< branch 0) (> branch (1- (undo-tree-num-branches))))
 | ||
|     (user-error "Invalid branch number"))
 | ||
|   ;; transfer entries accumulated in `buffer-undo-list' to `buffer-undo-tree'
 | ||
|   (undo-list-transfer-to-tree)
 | ||
|   ;; switch branch
 | ||
|   (setf (undo-tree-node-branch (undo-tree-current buffer-undo-tree))
 | ||
| 	branch)
 | ||
|   (message "Switched to branch %d" branch))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-set (node &optional preserve-timestamps)
 | ||
|   ;; Set buffer to state corresponding to NODE. Returns intersection point
 | ||
|   ;; between path back from current node and path back from selected NODE.
 | ||
|   ;; Non-nil PRESERVE-TIMESTAMPS disables updating of timestamps in visited
 | ||
|   ;; undo-tree nodes. (This should *only* be used when temporarily visiting
 | ||
|   ;; another undo state and immediately returning to the original state
 | ||
|   ;; afterwards. Otherwise, it could cause history-discarding errors.)
 | ||
|   (let ((path (make-hash-table :test 'eq))
 | ||
|         (n node))
 | ||
|     (puthash (undo-tree-root buffer-undo-tree) t path)
 | ||
|     ;; build list of nodes leading back from selected node to root, updating
 | ||
|     ;; branches as we go to point down to selected node
 | ||
|     (while (progn
 | ||
|              (puthash n t path)
 | ||
|              (when (undo-tree-node-previous n)
 | ||
|                (setf (undo-tree-node-branch (undo-tree-node-previous n))
 | ||
|                      (undo-tree-position
 | ||
|                       n (undo-tree-node-next (undo-tree-node-previous n))))
 | ||
|                (setq n (undo-tree-node-previous n)))))
 | ||
|     ;; work backwards from current node until we intersect path back from
 | ||
|     ;; selected node
 | ||
|     (setq n (undo-tree-current buffer-undo-tree))
 | ||
|     (while (not (gethash n path))
 | ||
|       (setq n (undo-tree-node-previous n)))
 | ||
|     ;; ascend tree until intersection node
 | ||
|     (while (not (eq (undo-tree-current buffer-undo-tree) n))
 | ||
|       (undo-tree-undo-1 nil nil preserve-timestamps))
 | ||
|     ;; descend tree until selected node
 | ||
|     (while (not (eq (undo-tree-current buffer-undo-tree) node))
 | ||
|       (undo-tree-redo-1 nil nil preserve-timestamps))
 | ||
|     n))  ; return intersection node
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-save-state-to-register (register)
 | ||
|   "Store current undo-tree state to REGISTER.
 | ||
| The saved state can be restored using
 | ||
| `undo-tree-restore-state-from-register'.
 | ||
| Argument is a character, naming the register."
 | ||
|   (interactive "cUndo-tree state to register: ")
 | ||
|   ;; throw error if undo is disabled in buffer
 | ||
|   (when (eq buffer-undo-list t)
 | ||
|     (user-error "No undo information in this buffer"))
 | ||
|   ;; transfer entries accumulated in `buffer-undo-list' to `buffer-undo-tree'
 | ||
|   (undo-list-transfer-to-tree)
 | ||
|   ;; save current node to REGISTER
 | ||
|   (set-register
 | ||
|    register (registerv-make
 | ||
| 	     (undo-tree-make-register-data
 | ||
| 	      (current-buffer) (undo-tree-current buffer-undo-tree))
 | ||
| 	     :print-func 'undo-tree-register-data-print-func))
 | ||
|   ;; record REGISTER in current node, for visualizer
 | ||
|   (setf (undo-tree-node-register (undo-tree-current buffer-undo-tree))
 | ||
| 	register))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-restore-state-from-register (register)
 | ||
|   "Restore undo-tree state from REGISTER.
 | ||
| The state must be saved using `undo-tree-save-state-to-register'.
 | ||
| Argument is a character, naming the register."
 | ||
|   (interactive "*cRestore undo-tree state from register: ")
 | ||
|   ;; throw error if undo is disabled in buffer, or if register doesn't contain
 | ||
|   ;; an undo-tree node
 | ||
|   (let ((data (registerv-data (get-register register))))
 | ||
|     (cond
 | ||
|      ((eq buffer-undo-list t)
 | ||
|       (user-error "No undo information in this buffer"))
 | ||
|      ((not (undo-tree-register-data-p data))
 | ||
|       (user-error "Register doesn't contain undo-tree state"))
 | ||
|      ((not (eq (current-buffer) (undo-tree-register-data-buffer data)))
 | ||
|       (user-error "Register contains undo-tree state for a different buffer")))
 | ||
|     ;; transfer entries accumulated in `buffer-undo-list' to `buffer-undo-tree'
 | ||
|     (undo-list-transfer-to-tree)
 | ||
|     ;; restore buffer state corresponding to saved node
 | ||
|     (undo-tree-set (undo-tree-register-data-node data))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;;; =====================================================================
 | ||
| ;;;                    Persistent storage commands
 | ||
| 
 | ||
| (defun undo-tree-make-history-save-file-name (file)
 | ||
|   "Create the undo history file name for FILE.
 | ||
| Normally this is the file's name with \".\" prepended and
 | ||
| \".~undo-tree~\" appended.
 | ||
| 
 | ||
| A match for FILE is sought in `undo-tree-history-directory-alist'
 | ||
| \(see the documentation of that variable for details\). If the
 | ||
| directory for the backup doesn't exist, it is created."
 | ||
|   (let* ((backup-directory-alist undo-tree-history-directory-alist)
 | ||
| 	 (name (make-backup-file-name-1 file)))
 | ||
|     (concat (file-name-directory name) "." (file-name-nondirectory name)
 | ||
| 	    ".~undo-tree~")))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-save-history (&optional filename overwrite)
 | ||
|   "Store undo-tree history to file.
 | ||
| 
 | ||
| If optional argument FILENAME is omitted, default save file is
 | ||
| \".<buffer-file-name>.~undo-tree\" if buffer is visiting a file.
 | ||
| Otherwise, prompt for one.
 | ||
| 
 | ||
| If OVERWRITE is non-nil, any existing file will be overwritten
 | ||
| without asking for confirmation."
 | ||
|   (interactive)
 | ||
|   (when (eq buffer-undo-list t)
 | ||
|     (user-error "No undo information in this buffer"))
 | ||
|   (undo-list-transfer-to-tree)
 | ||
|   (when (and buffer-undo-tree (not (eq buffer-undo-tree t)))
 | ||
|     (condition-case nil
 | ||
| 	(undo-tree-kill-visualizer)
 | ||
|       (error (undo-tree-clear-visualizer-data buffer-undo-tree)))
 | ||
|     (let ((buff (current-buffer))
 | ||
| 	  tree)
 | ||
|       ;; get filename
 | ||
|       (unless filename
 | ||
| 	(setq filename
 | ||
| 	      (if buffer-file-name
 | ||
| 		  (undo-tree-make-history-save-file-name buffer-file-name)
 | ||
| 		(expand-file-name (read-file-name "File to save in: ") nil))))
 | ||
|       (when (or (not (file-exists-p filename))
 | ||
| 		overwrite
 | ||
| 		(yes-or-no-p (format "Overwrite \"%s\"? " filename)))
 | ||
| 	(unwind-protect
 | ||
| 	    (progn
 | ||
| 	      ;; transform undo-tree into non-circular structure, and make
 | ||
| 	      ;; temporary copy
 | ||
| 	      (undo-tree-decircle buffer-undo-tree)
 | ||
| 	      (setq tree (copy-undo-tree buffer-undo-tree))
 | ||
| 	      ;; discard undo-tree object pool before saving
 | ||
| 	      (setf (undo-tree-object-pool tree) nil)
 | ||
| 	      ;; print undo-tree to file
 | ||
| 	      ;; NOTE: We use `with-temp-buffer' instead of `with-temp-file'
 | ||
| 	      ;;       to allow `auto-compression-mode' to take effect, in
 | ||
| 	      ;;       case user has overridden or advised the default
 | ||
| 	      ;;       `undo-tree-make-history-save-file-name' to add a
 | ||
| 	      ;;       compressed file extension.
 | ||
| 	      (with-auto-compression-mode
 | ||
| 		(with-temp-buffer
 | ||
| 		  (prin1 (sha1 buff) (current-buffer))
 | ||
| 		  (terpri (current-buffer))
 | ||
| 		  (let ((print-circle t)) (prin1 tree (current-buffer)))
 | ||
| 		  (write-region nil nil filename))))
 | ||
| 	  ;; restore circular undo-tree data structure
 | ||
| 	  (undo-tree-recircle buffer-undo-tree))
 | ||
| 	))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-load-history (&optional filename noerror)
 | ||
|   "Load undo-tree history from file.
 | ||
| 
 | ||
| If optional argument FILENAME is null, default load file is
 | ||
| \".<buffer-file-name>.~undo-tree\" if buffer is visiting a file.
 | ||
| Otherwise, prompt for one.
 | ||
| 
 | ||
| If optional argument NOERROR is non-nil, return nil instead of
 | ||
| signaling an error if file is not found."
 | ||
|   (interactive)
 | ||
|   ;; get filename
 | ||
|   (unless filename
 | ||
|     (setq filename
 | ||
| 	  (if buffer-file-name
 | ||
| 	      (undo-tree-make-history-save-file-name buffer-file-name)
 | ||
| 	    (expand-file-name (read-file-name "File to load from: ") nil))))
 | ||
| 
 | ||
|   ;; attempt to read undo-tree from FILENAME
 | ||
|   (catch 'load-error
 | ||
|     (unless (file-exists-p filename)
 | ||
|       (if noerror
 | ||
| 	  (throw 'load-error nil)
 | ||
| 	(error "File \"%s\" does not exist; could not load undo-tree history"
 | ||
| 	       filename)))
 | ||
|     (let (buff hash tree)
 | ||
|       (setq buff (current-buffer))
 | ||
|       (with-auto-compression-mode
 | ||
| 	(with-temp-buffer
 | ||
| 	  (insert-file-contents filename)
 | ||
| 	  (goto-char (point-min))
 | ||
| 	  (condition-case nil
 | ||
| 	      (setq hash (read (current-buffer)))
 | ||
| 	    (error
 | ||
| 	     (kill-buffer nil)
 | ||
| 	     (funcall (if noerror 'message 'user-error)
 | ||
| 		      "Error reading undo-tree history from \"%s\"" filename)
 | ||
| 	     (throw 'load-error nil)))
 | ||
| 	  (unless (string= (sha1 buff) hash)
 | ||
| 	    (kill-buffer nil)
 | ||
| 	    (funcall (if noerror 'message 'user-error)
 | ||
| 		     "Buffer has been modified; could not load undo-tree history")
 | ||
| 	    (throw 'load-error nil))
 | ||
| 	  (condition-case nil
 | ||
| 	      (setq tree (read (current-buffer)))
 | ||
| 	    (error
 | ||
| 	     (kill-buffer nil)
 | ||
| 	     (funcall (if noerror 'message 'error)
 | ||
| 		      "Error reading undo-tree history from \"%s\"" filename)
 | ||
| 	     (throw 'load-error nil)))
 | ||
| 	  (kill-buffer nil)))
 | ||
|       ;; initialise empty undo-tree object pool
 | ||
|       (setf (undo-tree-object-pool tree)
 | ||
| 	    (make-hash-table :test 'eq :weakness 'value))
 | ||
|       ;; restore circular undo-tree data structure
 | ||
|       (undo-tree-recircle tree)
 | ||
|       (setq buffer-undo-tree tree))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;; Versions of save/load functions for use in hooks
 | ||
| (defun undo-tree-save-history-hook ()
 | ||
|   (when (and undo-tree-mode undo-tree-auto-save-history
 | ||
| 	     (not (eq buffer-undo-list t)))
 | ||
|     (undo-tree-save-history nil t) nil))
 | ||
| 
 | ||
| (defun undo-tree-load-history-hook ()
 | ||
|   (when (and undo-tree-mode undo-tree-auto-save-history
 | ||
| 	     (not (eq buffer-undo-list t))
 | ||
| 	     (not revert-buffer-in-progress-p))
 | ||
|     (undo-tree-load-history nil t)))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;;; =====================================================================
 | ||
| ;;;                    Visualizer drawing functions
 | ||
| 
 | ||
| (defun undo-tree-visualize ()
 | ||
|   "Visualize the current buffer's undo tree."
 | ||
|   (interactive "*")
 | ||
|   (deactivate-mark)
 | ||
|   ;; throw error if undo is disabled in buffer
 | ||
|   (when (eq buffer-undo-list t)
 | ||
|     (user-error "No undo information in this buffer"))
 | ||
|   ;; transfer entries accumulated in `buffer-undo-list' to `buffer-undo-tree'
 | ||
|   (undo-list-transfer-to-tree)
 | ||
|   ;; add hook to kill visualizer buffer if original buffer is changed
 | ||
|   (add-hook 'before-change-functions 'undo-tree-kill-visualizer nil t)
 | ||
|   ;; prepare *undo-tree* buffer, then draw tree in it
 | ||
|   (let ((undo-tree buffer-undo-tree)
 | ||
|         (buff (current-buffer))
 | ||
| 	(display-buffer-mark-dedicated 'soft))
 | ||
|     (switch-to-buffer-other-window
 | ||
|      (get-buffer-create undo-tree-visualizer-buffer-name))
 | ||
|     (setq undo-tree-visualizer-parent-buffer buff)
 | ||
|     (setq undo-tree-visualizer-parent-mtime
 | ||
| 	  (and (buffer-file-name buff)
 | ||
| 	       (nth 5 (file-attributes (buffer-file-name buff)))))
 | ||
|     (setq undo-tree-visualizer-initial-node (undo-tree-current undo-tree))
 | ||
|     (setq undo-tree-visualizer-spacing
 | ||
| 	  (undo-tree-visualizer-calculate-spacing))
 | ||
|     (make-local-variable 'undo-tree-visualizer-timestamps)
 | ||
|     (make-local-variable 'undo-tree-visualizer-diff)
 | ||
|     (setq buffer-undo-tree undo-tree)
 | ||
|     (undo-tree-visualizer-mode)
 | ||
|     ;; FIXME; don't know why `undo-tree-visualizer-mode' clears this
 | ||
|     (setq buffer-undo-tree undo-tree)
 | ||
|     (set (make-local-variable 'undo-tree-visualizer-lazy-drawing)
 | ||
| 	 (or (eq undo-tree-visualizer-lazy-drawing t)
 | ||
| 	     (and (numberp undo-tree-visualizer-lazy-drawing)
 | ||
| 		  (>= (undo-tree-count undo-tree)
 | ||
| 		      undo-tree-visualizer-lazy-drawing))))
 | ||
|     (when undo-tree-visualizer-diff (undo-tree-visualizer-show-diff))
 | ||
|     (let ((inhibit-read-only t)) (undo-tree-draw-tree undo-tree))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-kill-visualizer (&rest _dummy)
 | ||
|   ;; Kill visualizer. Added to `before-change-functions' hook of original
 | ||
|   ;; buffer when visualizer is invoked.
 | ||
|   (unless (or undo-tree-inhibit-kill-visualizer
 | ||
| 	      (null (get-buffer undo-tree-visualizer-buffer-name)))
 | ||
|     (with-current-buffer undo-tree-visualizer-buffer-name
 | ||
|       (undo-tree-visualizer-quit))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-draw-tree (undo-tree)
 | ||
|   ;; Draw undo-tree in current buffer starting from NODE (or root if nil).
 | ||
|   (let ((node (if undo-tree-visualizer-lazy-drawing
 | ||
| 		  (undo-tree-current undo-tree)
 | ||
| 		(undo-tree-root undo-tree))))
 | ||
|     (erase-buffer)
 | ||
|     (undo-tree-clear-visualizer-data undo-tree)
 | ||
|     (undo-tree-compute-widths node)
 | ||
|     ;; lazy drawing starts vertically centred and displaced horizontally to
 | ||
|     ;; the left (window-width/4), since trees will typically grow right
 | ||
|     (if undo-tree-visualizer-lazy-drawing
 | ||
| 	(progn
 | ||
| 	  (undo-tree-move-down (/ (window-height) 2))
 | ||
| 	  (undo-tree-move-forward (max 2 (/ (window-width) 4)))) ; left margin
 | ||
|       ;; non-lazy drawing starts in centre at top of buffer
 | ||
|       (undo-tree-move-down 1)  ; top margin
 | ||
|       (undo-tree-move-forward
 | ||
|        (max (/ (window-width) 2)
 | ||
| 	    (+ (undo-tree-node-char-lwidth node)
 | ||
| 	       ;; add space for left part of left-most time-stamp
 | ||
| 	       (if undo-tree-visualizer-timestamps
 | ||
| 		   (/ (- undo-tree-visualizer-spacing 4) 2)
 | ||
| 		 0)
 | ||
| 	       2))))  ; left margin
 | ||
|     ;; link starting node to its representation in visualizer
 | ||
|     (setf (undo-tree-node-marker node) (make-marker))
 | ||
|     (set-marker-insertion-type (undo-tree-node-marker node) nil)
 | ||
|     (move-marker (undo-tree-node-marker node) (point))
 | ||
|     ;; draw undo-tree
 | ||
|     (let ((undo-tree-insert-face 'undo-tree-visualizer-default-face)
 | ||
| 	  node-list)
 | ||
|       (if (not undo-tree-visualizer-lazy-drawing)
 | ||
| 	  (undo-tree-extend-down node t)
 | ||
| 	(undo-tree-extend-down node)
 | ||
| 	(undo-tree-extend-up node)
 | ||
| 	(setq node-list undo-tree-visualizer-needs-extending-down
 | ||
| 	      undo-tree-visualizer-needs-extending-down nil)
 | ||
| 	(while node-list (undo-tree-extend-down (pop node-list)))))
 | ||
|     ;; highlight active branch
 | ||
|     (let ((undo-tree-insert-face 'undo-tree-visualizer-active-branch-face))
 | ||
|       (undo-tree-highlight-active-branch
 | ||
|        (or undo-tree-visualizer-needs-extending-up
 | ||
| 	   (undo-tree-root undo-tree))))
 | ||
|     ;; highlight current node
 | ||
|     (undo-tree-draw-node (undo-tree-current undo-tree) 'current)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-extend-down (node &optional bottom)
 | ||
|   ;; Extend tree downwards starting from NODE and point. If BOTTOM is t,
 | ||
|   ;; extend all the way down to the leaves. If BOTTOM is a node, extend down
 | ||
|   ;; as far as that node. If BOTTOM is an integer, extend down as far as that
 | ||
|   ;; line. Otherwise, only extend visible portion of tree. NODE is assumed to
 | ||
|   ;; already have a node marker. Returns non-nil if anything was actually
 | ||
|   ;; extended.
 | ||
|   (let ((extended nil)
 | ||
| 	(cur-stack (list node))
 | ||
| 	next-stack)
 | ||
|     ;; don't bother extending if BOTTOM specifies an already-drawn node
 | ||
|     (unless (and (undo-tree-node-p bottom) (undo-tree-node-marker bottom))
 | ||
|       ;; draw nodes layer by layer
 | ||
|       (while (or cur-stack
 | ||
| 		 (prog1 (setq cur-stack next-stack)
 | ||
| 		   (setq next-stack nil)))
 | ||
| 	(setq node (pop cur-stack))
 | ||
| 	;; if node is within range being drawn...
 | ||
| 	(if (or (eq bottom t)
 | ||
| 		(and (undo-tree-node-p bottom)
 | ||
| 		     (not (eq (undo-tree-node-previous node) bottom)))
 | ||
| 		(and (integerp bottom)
 | ||
| 		     (>= bottom (line-number-at-pos
 | ||
| 				 (undo-tree-node-marker node))))
 | ||
| 		(and (null bottom)
 | ||
| 		     (pos-visible-in-window-p (undo-tree-node-marker node)
 | ||
| 					      nil t)))
 | ||
| 	    ;; ...draw one layer of node's subtree (if not already drawn)
 | ||
| 	    (progn
 | ||
| 	      (unless (and (undo-tree-node-next node)
 | ||
| 			   (undo-tree-node-marker
 | ||
| 			    (nth (undo-tree-node-branch node)
 | ||
| 				 (undo-tree-node-next node))))
 | ||
| 		(goto-char (undo-tree-node-marker node))
 | ||
| 		(undo-tree-draw-subtree node)
 | ||
| 		(setq extended t))
 | ||
| 	      (setq next-stack
 | ||
| 		    (append (undo-tree-node-next node) next-stack)))
 | ||
| 	  ;; ...otherwise, postpone drawing until later
 | ||
| 	  (push node undo-tree-visualizer-needs-extending-down))))
 | ||
|     extended))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-extend-up (node &optional top)
 | ||
|   ;; Extend tree upwards starting from NODE. If TOP is t, extend all the way
 | ||
|   ;; to root. If TOP is a node, extend up as far as that node. If TOP is an
 | ||
|   ;; integer, extend up as far as that line. Otherwise, only extend visible
 | ||
|   ;; portion of tree. NODE is assumed to already have a node marker. Returns
 | ||
|   ;; non-nil if anything was actually extended.
 | ||
|   (let ((extended nil) parent)
 | ||
|     ;; don't bother extending if TOP specifies an already-drawn node
 | ||
|     (unless (and (undo-tree-node-p top) (undo-tree-node-marker top))
 | ||
|       (while node
 | ||
| 	(setq parent (undo-tree-node-previous node))
 | ||
| 	;; if we haven't reached root...
 | ||
| 	(if parent
 | ||
| 	    ;; ...and node is within range being drawn...
 | ||
| 	    (if (or (eq top t)
 | ||
| 		    (and (undo-tree-node-p top) (not (eq node top)))
 | ||
| 		    (and (integerp top)
 | ||
| 			 (< top (line-number-at-pos
 | ||
| 				 (undo-tree-node-marker node))))
 | ||
| 		    (and (null top)
 | ||
| 			 ;; NOTE: we check point in case window-start is outdated
 | ||
| 			 (< (min (line-number-at-pos (point))
 | ||
| 				 (line-number-at-pos (window-start)))
 | ||
| 			    (line-number-at-pos
 | ||
| 			     (undo-tree-node-marker node)))))
 | ||
| 		;; ...and it hasn't already been drawn
 | ||
| 		(when (not (undo-tree-node-marker parent))
 | ||
| 		  ;; link parent node to its representation in visualizer
 | ||
| 		  (undo-tree-compute-widths parent)
 | ||
| 		  (undo-tree-move-to-parent node)
 | ||
| 		  (setf (undo-tree-node-marker parent) (make-marker))
 | ||
| 		  (set-marker-insertion-type
 | ||
| 		   (undo-tree-node-marker parent) nil)
 | ||
| 		  (move-marker (undo-tree-node-marker parent) (point))
 | ||
| 		  ;; draw subtree beneath parent
 | ||
| 		  (setq undo-tree-visualizer-needs-extending-down
 | ||
| 			(nconc (delq node (undo-tree-draw-subtree parent))
 | ||
| 			       undo-tree-visualizer-needs-extending-down))
 | ||
| 		  (setq extended t))
 | ||
| 	      ;; ...otherwise, postpone drawing for later and exit
 | ||
| 	      (setq undo-tree-visualizer-needs-extending-up (when parent node)
 | ||
| 		    parent nil))
 | ||
| 
 | ||
| 	  ;; if we've reached root, stop extending and add top margin
 | ||
| 	  (setq undo-tree-visualizer-needs-extending-up nil)
 | ||
| 	  (goto-char (undo-tree-node-marker node))
 | ||
| 	  (undo-tree-move-up 1)  ; top margin
 | ||
| 	  (delete-region (point-min) (line-beginning-position)))
 | ||
| 	;; next iteration
 | ||
| 	(setq node parent)))
 | ||
|     extended))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-expand-down (from &optional to)
 | ||
|   ;; Expand tree downwards. FROM is the node to start expanding from. Stop
 | ||
|   ;; expanding at TO if specified. Otherwise, just expand visible portion of
 | ||
|   ;; tree and highlight active branch from FROM.
 | ||
|   (when undo-tree-visualizer-needs-extending-down
 | ||
|     (let ((inhibit-read-only t)
 | ||
| 	  node-list extended)
 | ||
|       ;; extend down as far as TO node
 | ||
|       (when to
 | ||
| 	(setq extended (undo-tree-extend-down from to))
 | ||
| 	(goto-char (undo-tree-node-marker to))
 | ||
| 	(redisplay t))  ; force redisplay to scroll buffer if necessary
 | ||
|       ;; extend visible portion of tree downwards
 | ||
|       (setq node-list undo-tree-visualizer-needs-extending-down
 | ||
| 	    undo-tree-visualizer-needs-extending-down nil)
 | ||
|       (when node-list
 | ||
| 	(dolist (n node-list)
 | ||
| 	  (when (undo-tree-extend-down n) (setq extended t)))
 | ||
| 	;; highlight active branch in newly-extended-down portion, if any
 | ||
| 	(when extended
 | ||
| 	  (let ((undo-tree-insert-face
 | ||
| 		 'undo-tree-visualizer-active-branch-face))
 | ||
| 	    (undo-tree-highlight-active-branch from)))))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-expand-up (from &optional to)
 | ||
|   ;; Expand tree upwards. FROM is the node to start expanding from, TO is the
 | ||
|   ;; node to stop expanding at. If TO node isn't specified, just expand visible
 | ||
|   ;; portion of tree and highlight active branch down to FROM.
 | ||
|   (when undo-tree-visualizer-needs-extending-up
 | ||
|     (let ((inhibit-read-only t)
 | ||
| 	  extended node-list)
 | ||
|       ;; extend up as far as TO node
 | ||
|       (when to
 | ||
| 	(setq extended (undo-tree-extend-up from to))
 | ||
| 	(goto-char (undo-tree-node-marker to))
 | ||
| 	;; simulate auto-scrolling if close to top of buffer
 | ||
| 	(when (<= (line-number-at-pos (point)) scroll-margin)
 | ||
| 	  (undo-tree-move-up (if (= scroll-conservatively 0)
 | ||
| 				 (/ (window-height) 2) 3))
 | ||
| 	  (when (undo-tree-extend-up to) (setq extended t))
 | ||
| 	  (goto-char (undo-tree-node-marker to))
 | ||
| 	  (unless (= scroll-conservatively 0) (recenter scroll-margin))))
 | ||
|       ;; extend visible portion of tree upwards
 | ||
|       (and undo-tree-visualizer-needs-extending-up
 | ||
| 	   (undo-tree-extend-up undo-tree-visualizer-needs-extending-up)
 | ||
| 	   (setq extended t))
 | ||
|       ;; extend visible portion of tree downwards
 | ||
|       (setq node-list undo-tree-visualizer-needs-extending-down
 | ||
| 	    undo-tree-visualizer-needs-extending-down nil)
 | ||
|       (dolist (n node-list) (undo-tree-extend-down n))
 | ||
|       ;; highlight active branch in newly-extended-up portion, if any
 | ||
|       (when extended
 | ||
| 	(let ((undo-tree-insert-face
 | ||
| 	       'undo-tree-visualizer-active-branch-face))
 | ||
| 	  (undo-tree-highlight-active-branch
 | ||
| 	   (or undo-tree-visualizer-needs-extending-up
 | ||
| 	       (undo-tree-root buffer-undo-tree))
 | ||
| 	   from))))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-highlight-active-branch (node &optional end)
 | ||
|   ;; Draw highlighted active branch below NODE in current buffer. Stop
 | ||
|   ;; highlighting at END node if specified.
 | ||
|   (let ((stack (list node)))
 | ||
|     ;; draw active branch
 | ||
|     (while stack
 | ||
|       (setq node (pop stack))
 | ||
|       (unless (or (eq node end)
 | ||
| 		  (memq node undo-tree-visualizer-needs-extending-down))
 | ||
| 	(goto-char (undo-tree-node-marker node))
 | ||
| 	(setq node (undo-tree-draw-subtree node 'active)
 | ||
| 	      stack (nconc stack node))))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-draw-node (node &optional current)
 | ||
|   ;; Draw symbol representing NODE in visualizer. If CURRENT is non-nil, node
 | ||
|   ;; is current node.
 | ||
|   (goto-char (undo-tree-node-marker node))
 | ||
|   (when undo-tree-visualizer-timestamps
 | ||
|     (undo-tree-move-backward (/ undo-tree-visualizer-spacing 2)))
 | ||
| 
 | ||
|   (let* ((undo-tree-insert-face (and undo-tree-insert-face
 | ||
| 				     (or (and (consp undo-tree-insert-face)
 | ||
| 					      undo-tree-insert-face)
 | ||
| 					 (list undo-tree-insert-face))))
 | ||
| 	 (register (undo-tree-node-register node))
 | ||
| 	 (unmodified (if undo-tree-visualizer-parent-mtime
 | ||
| 			 (undo-tree-node-unmodified-p
 | ||
| 			  node undo-tree-visualizer-parent-mtime)
 | ||
| 		       (undo-tree-node-unmodified-p node)))
 | ||
| 	node-string)
 | ||
|     ;; check node's register (if any) still stores appropriate undo-tree state
 | ||
|     (unless (and register
 | ||
| 		 (undo-tree-register-data-p
 | ||
| 		  (registerv-data (get-register register)))
 | ||
| 		 (eq node (undo-tree-register-data-node
 | ||
| 			   (registerv-data (get-register register)))))
 | ||
|       (setq register nil))
 | ||
|     ;; represent node by different symbols, depending on whether it's the
 | ||
|     ;; current node, is saved in a register, or corresponds to an unmodified
 | ||
|     ;; buffer
 | ||
|     (setq node-string
 | ||
| 	    (cond
 | ||
| 	     (undo-tree-visualizer-timestamps
 | ||
| 	        (undo-tree-timestamp-to-string
 | ||
| 	         (undo-tree-node-timestamp node)
 | ||
| 		 undo-tree-visualizer-relative-timestamps
 | ||
| 		 current register))
 | ||
| 	     (register (char-to-string register))
 | ||
| 	     (unmodified "s")
 | ||
| 	     (current "x")
 | ||
| 	     (t "o"))
 | ||
| 	  undo-tree-insert-face
 | ||
| 	    (nconc
 | ||
| 	     (cond
 | ||
| 	      (current    '(undo-tree-visualizer-current-face))
 | ||
| 	      (unmodified '(undo-tree-visualizer-unmodified-face))
 | ||
| 	      (register   '(undo-tree-visualizer-register-face)))
 | ||
| 	     undo-tree-insert-face))
 | ||
|     ;; draw node and link it to its representation in visualizer
 | ||
|     (undo-tree-insert node-string)
 | ||
|     (undo-tree-move-backward (if undo-tree-visualizer-timestamps
 | ||
| 				 (1+ (/ undo-tree-visualizer-spacing 2))
 | ||
| 			       1))
 | ||
|     (move-marker (undo-tree-node-marker node) (point))
 | ||
|     (put-text-property (point) (1+ (point)) 'undo-tree-node node)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-draw-subtree (node &optional active-branch)
 | ||
|   ;; Draw subtree rooted at NODE. The subtree will start from point.
 | ||
|   ;; If ACTIVE-BRANCH is non-nil, just draw active branch below NODE. Returns
 | ||
|   ;; list of nodes below NODE.
 | ||
|   (let ((num-children (length (undo-tree-node-next node)))
 | ||
|         node-list pos trunk-pos n)
 | ||
|     ;; draw node itself
 | ||
|     (undo-tree-draw-node node)
 | ||
| 
 | ||
|     (cond
 | ||
|      ;; if we're at a leaf node, we're done
 | ||
|      ((= num-children 0))
 | ||
| 
 | ||
|      ;; if node has only one child, draw it (not strictly necessary to deal
 | ||
|      ;; with this case separately, but as it's by far the most common case
 | ||
|      ;; this makes the code clearer and more efficient)
 | ||
|      ((= num-children 1)
 | ||
|       (undo-tree-move-down 1)
 | ||
|       (undo-tree-insert ?|)
 | ||
|       (undo-tree-move-backward 1)
 | ||
|       (undo-tree-move-down 1)
 | ||
|       (undo-tree-insert ?|)
 | ||
|       (undo-tree-move-backward 1)
 | ||
|       (undo-tree-move-down 1)
 | ||
|       (setq n (car (undo-tree-node-next node)))
 | ||
|       ;; link next node to its representation in visualizer
 | ||
|       (unless (markerp (undo-tree-node-marker n))
 | ||
|         (setf (undo-tree-node-marker n) (make-marker))
 | ||
|         (set-marker-insertion-type (undo-tree-node-marker n) nil))
 | ||
|       (move-marker (undo-tree-node-marker n) (point))
 | ||
|       ;; add next node to list of nodes to draw next
 | ||
|       (push n node-list))
 | ||
| 
 | ||
|      ;; if node has multiple children, draw branches
 | ||
|      (t
 | ||
|       (undo-tree-move-down 1)
 | ||
|       (undo-tree-insert ?|)
 | ||
|       (undo-tree-move-backward 1)
 | ||
|       (move-marker (setq trunk-pos (make-marker)) (point))
 | ||
|       ;; left subtrees
 | ||
|       (undo-tree-move-backward
 | ||
|        (- (undo-tree-node-char-lwidth node)
 | ||
|           (undo-tree-node-char-lwidth
 | ||
|            (car (undo-tree-node-next node)))))
 | ||
|       (move-marker (setq pos (make-marker)) (point))
 | ||
|       (setq n (cons nil (undo-tree-node-next node)))
 | ||
|       (dotimes (i (/ num-children 2))
 | ||
|         (setq n (cdr n))
 | ||
|         (when (or (null active-branch)
 | ||
|                   (eq (car n)
 | ||
|                       (nth (undo-tree-node-branch node)
 | ||
|                            (undo-tree-node-next node))))
 | ||
|           (undo-tree-move-forward 2)
 | ||
|           (undo-tree-insert ?_ (- trunk-pos pos 2))
 | ||
|           (goto-char pos)
 | ||
|           (undo-tree-move-forward 1)
 | ||
|           (undo-tree-move-down 1)
 | ||
|           (undo-tree-insert ?/)
 | ||
|           (undo-tree-move-backward 2)
 | ||
|           (undo-tree-move-down 1)
 | ||
|           ;; link node to its representation in visualizer
 | ||
|           (unless (markerp (undo-tree-node-marker (car n)))
 | ||
|             (setf (undo-tree-node-marker (car n)) (make-marker))
 | ||
|             (set-marker-insertion-type (undo-tree-node-marker (car n)) nil))
 | ||
|           (move-marker (undo-tree-node-marker (car n)) (point))
 | ||
|           ;; add node to list of nodes to draw next
 | ||
|           (push (car n) node-list))
 | ||
|         (goto-char pos)
 | ||
|         (undo-tree-move-forward
 | ||
|          (+ (undo-tree-node-char-rwidth (car n))
 | ||
|             (undo-tree-node-char-lwidth (cadr n))
 | ||
|             undo-tree-visualizer-spacing 1))
 | ||
|         (move-marker pos (point)))
 | ||
|       ;; middle subtree (only when number of children is odd)
 | ||
|       (when (= (mod num-children 2) 1)
 | ||
|         (setq n (cdr n))
 | ||
|         (when (or (null active-branch)
 | ||
|                   (eq (car n)
 | ||
|                       (nth (undo-tree-node-branch node)
 | ||
|                            (undo-tree-node-next node))))
 | ||
|           (undo-tree-move-down 1)
 | ||
|           (undo-tree-insert ?|)
 | ||
|           (undo-tree-move-backward 1)
 | ||
|           (undo-tree-move-down 1)
 | ||
|           ;; link node to its representation in visualizer
 | ||
|           (unless (markerp (undo-tree-node-marker (car n)))
 | ||
|             (setf (undo-tree-node-marker (car n)) (make-marker))
 | ||
|             (set-marker-insertion-type (undo-tree-node-marker (car n)) nil))
 | ||
|           (move-marker (undo-tree-node-marker (car n)) (point))
 | ||
|           ;; add node to list of nodes to draw next
 | ||
|           (push (car n) node-list))
 | ||
|         (goto-char pos)
 | ||
|         (undo-tree-move-forward
 | ||
|          (+ (undo-tree-node-char-rwidth (car n))
 | ||
|             (if (cadr n) (undo-tree-node-char-lwidth (cadr n)) 0)
 | ||
|             undo-tree-visualizer-spacing 1))
 | ||
|         (move-marker pos (point)))
 | ||
|       ;; right subtrees
 | ||
|       (move-marker trunk-pos (1+ trunk-pos))
 | ||
|       (dotimes (i (/ num-children 2))
 | ||
|         (setq n (cdr n))
 | ||
|         (when (or (null active-branch)
 | ||
|                   (eq (car n)
 | ||
|                       (nth (undo-tree-node-branch node)
 | ||
|                            (undo-tree-node-next node))))
 | ||
|           (goto-char trunk-pos)
 | ||
|           (undo-tree-insert ?_ (- pos trunk-pos 1))
 | ||
|           (goto-char pos)
 | ||
|           (undo-tree-move-backward 1)
 | ||
|           (undo-tree-move-down 1)
 | ||
|           (undo-tree-insert ?\\)
 | ||
|           (undo-tree-move-down 1)
 | ||
|           ;; link node to its representation in visualizer
 | ||
|           (unless (markerp (undo-tree-node-marker (car n)))
 | ||
|             (setf (undo-tree-node-marker (car n)) (make-marker))
 | ||
|             (set-marker-insertion-type (undo-tree-node-marker (car n)) nil))
 | ||
|           (move-marker (undo-tree-node-marker (car n)) (point))
 | ||
|           ;; add node to list of nodes to draw next
 | ||
|           (push (car n) node-list))
 | ||
|         (when (cdr n)
 | ||
|           (goto-char pos)
 | ||
|           (undo-tree-move-forward
 | ||
|            (+ (undo-tree-node-char-rwidth (car n))
 | ||
|               (if (cadr n) (undo-tree-node-char-lwidth (cadr n)) 0)
 | ||
|               undo-tree-visualizer-spacing 1))
 | ||
|           (move-marker pos (point))))
 | ||
|       ))
 | ||
|     ;; return list of nodes to draw next
 | ||
|     (nreverse node-list)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-node-char-lwidth (node)
 | ||
|   ;; Return left-width of NODE measured in characters.
 | ||
|   (if (= (length (undo-tree-node-next node)) 0) 0
 | ||
|     (- (* (+ undo-tree-visualizer-spacing 1) (undo-tree-node-lwidth node))
 | ||
|        (if (= (undo-tree-node-cwidth node) 0)
 | ||
|            (1+ (/ undo-tree-visualizer-spacing 2)) 0))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-node-char-rwidth (node)
 | ||
|   ;; Return right-width of NODE measured in characters.
 | ||
|   (if (= (length (undo-tree-node-next node)) 0) 0
 | ||
|     (- (* (+ undo-tree-visualizer-spacing 1) (undo-tree-node-rwidth node))
 | ||
|        (if (= (undo-tree-node-cwidth node) 0)
 | ||
|            (1+ (/ undo-tree-visualizer-spacing 2)) 0))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-insert (str &optional arg)
 | ||
|   ;; Insert character or string STR ARG times, overwriting, and using
 | ||
|   ;; `undo-tree-insert-face'.
 | ||
|   (unless arg (setq arg 1))
 | ||
|   (when (characterp str)
 | ||
|     (setq str (make-string arg str))
 | ||
|     (setq arg 1))
 | ||
|   (dotimes (i arg) (insert str))
 | ||
|   (setq arg (* arg (length str)))
 | ||
|   (undo-tree-move-forward arg)
 | ||
|   ;; make sure mark isn't active, otherwise `backward-delete-char' might
 | ||
|   ;; delete region instead of single char if transient-mark-mode is enabled
 | ||
|   (setq mark-active nil)
 | ||
|   (backward-delete-char arg)
 | ||
|   (when undo-tree-insert-face
 | ||
|     (put-text-property (- (point) arg) (point) 'face undo-tree-insert-face)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-move-down (&optional arg)
 | ||
|   ;; Move down, extending buffer if necessary.
 | ||
|   (let ((row (line-number-at-pos))
 | ||
|         (col (current-column))
 | ||
|         line)
 | ||
|     (unless arg (setq arg 1))
 | ||
|     (forward-line arg)
 | ||
|     (setq line (line-number-at-pos))
 | ||
|     ;; if buffer doesn't have enough lines, add some
 | ||
|     (when (/= line (+ row arg))
 | ||
|       (cond
 | ||
|        ((< arg 0)
 | ||
| 	(insert (make-string (- line row arg) ?\n))
 | ||
| 	(forward-line (+ arg (- row line))))
 | ||
|        (t (insert (make-string (- arg (- line row)) ?\n)))))
 | ||
|     (undo-tree-move-forward col)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-move-up (&optional arg)
 | ||
|   ;; Move up, extending buffer if necessary.
 | ||
|   (unless arg (setq arg 1))
 | ||
|   (undo-tree-move-down (- arg)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-move-forward (&optional arg)
 | ||
|   ;; Move forward, extending buffer if necessary.
 | ||
|   (unless arg (setq arg 1))
 | ||
|   (let (n)
 | ||
|     (cond
 | ||
|      ((>= arg 0)
 | ||
|       (setq n (- (line-end-position) (point)))
 | ||
|       (if (> n arg)
 | ||
| 	  (forward-char arg)
 | ||
| 	(end-of-line)
 | ||
| 	(insert (make-string (- arg n) ? ))))
 | ||
|      ((< arg 0)
 | ||
|       (setq arg (- arg))
 | ||
|       (setq n (- (point) (line-beginning-position)))
 | ||
|       (when (< (- n 2) arg)  ; -2 to create left-margin
 | ||
| 	;; no space left - shift entire buffer contents right!
 | ||
| 	(let ((pos (move-marker (make-marker) (point))))
 | ||
| 	  (set-marker-insertion-type pos t)
 | ||
| 	  (goto-char (point-min))
 | ||
| 	  (while (not (eobp))
 | ||
| 	    (insert-before-markers (make-string (- arg -2 n) ? ))
 | ||
| 	    (forward-line 1))
 | ||
| 	  (goto-char pos)))
 | ||
|       (backward-char arg)))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-move-backward (&optional arg)
 | ||
|   ;; Move backward, extending buffer if necessary.
 | ||
|   (unless arg (setq arg 1))
 | ||
|   (undo-tree-move-forward (- arg)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-move-to-parent (node)
 | ||
|   ;; Move to position of parent of NODE, extending buffer if necessary.
 | ||
|   (let* ((parent (undo-tree-node-previous node))
 | ||
| 	 (n (undo-tree-node-next parent))
 | ||
| 	 (l (length n)) p)
 | ||
|     (goto-char (undo-tree-node-marker node))
 | ||
|     (unless (= l 1)
 | ||
|       ;; move horizontally
 | ||
|       (setq p (undo-tree-position node n))
 | ||
|       (cond
 | ||
|        ;; node in centre subtree: no horizontal movement
 | ||
|        ((and (= (mod l 2) 1) (= p (/ l 2))))
 | ||
|        ;; node in left subtree: move right
 | ||
|        ((< p (/ l 2))
 | ||
| 	(setq n (nthcdr p n))
 | ||
| 	(undo-tree-move-forward
 | ||
| 	 (+ (undo-tree-node-char-rwidth (car n))
 | ||
| 	    (/ undo-tree-visualizer-spacing 2) 1))
 | ||
| 	(dotimes (i (- (/ l 2) p 1))
 | ||
| 	  (setq n (cdr n))
 | ||
| 	  (undo-tree-move-forward
 | ||
| 	   (+ (undo-tree-node-char-lwidth (car n))
 | ||
| 	      (undo-tree-node-char-rwidth (car n))
 | ||
| 	      undo-tree-visualizer-spacing 1)))
 | ||
| 	(when (= (mod l 2) 1)
 | ||
| 	  (setq n (cdr n))
 | ||
| 	  (undo-tree-move-forward
 | ||
| 	   (+ (undo-tree-node-char-lwidth (car n))
 | ||
| 	      (/ undo-tree-visualizer-spacing 2) 1))))
 | ||
|        (t ;; node in right subtree: move left
 | ||
| 	(setq n (nthcdr (/ l 2) n))
 | ||
| 	(when (= (mod l 2) 1)
 | ||
| 	  (undo-tree-move-backward
 | ||
| 	   (+ (undo-tree-node-char-rwidth (car n))
 | ||
| 	      (/ undo-tree-visualizer-spacing 2) 1))
 | ||
| 	  (setq n (cdr n)))
 | ||
| 	(dotimes (i (- p (/ l 2) (mod l 2)))
 | ||
| 	  (undo-tree-move-backward
 | ||
| 	   (+ (undo-tree-node-char-lwidth (car n))
 | ||
| 	      (undo-tree-node-char-rwidth (car n))
 | ||
| 	      undo-tree-visualizer-spacing 1))
 | ||
| 	  (setq n (cdr n)))
 | ||
| 	(undo-tree-move-backward
 | ||
| 	 (+ (undo-tree-node-char-lwidth (car n))
 | ||
| 	    (/ undo-tree-visualizer-spacing 2) 1)))))
 | ||
|     ;; move vertically
 | ||
|     (undo-tree-move-up 3)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-timestamp-to-string
 | ||
|   (timestamp &optional relative current register)
 | ||
|   ;; Convert TIMESTAMP to string (either absolute or RELATVE time), indicating
 | ||
|   ;; if it's the CURRENT node and/or has an associated REGISTER.
 | ||
|   (if relative
 | ||
|       ;; relative time
 | ||
|       (let ((time (floor (float-time
 | ||
| 			  (subtract-time (current-time) timestamp))))
 | ||
| 	    n)
 | ||
| 	(setq time
 | ||
| 	      ;; years
 | ||
| 	      (if (> (setq n (/ time 315360000)) 0)
 | ||
| 		  (if (> n 999) "-ages" (format "-%dy" n))
 | ||
| 		(setq time (% time 315360000))
 | ||
| 		;; days
 | ||
| 		(if (> (setq n (/ time 86400)) 0)
 | ||
| 		    (format "-%dd" n)
 | ||
| 		  (setq time (% time 86400))
 | ||
| 		  ;; hours
 | ||
| 		  (if (> (setq n (/ time 3600)) 0)
 | ||
| 		      (format "-%dh" n)
 | ||
| 		    (setq time (% time 3600))
 | ||
| 		    ;; mins
 | ||
| 		    (if (> (setq n (/ time 60)) 0)
 | ||
| 			(format "-%dm" n)
 | ||
| 		      ;; secs
 | ||
| 		      (format "-%ds" (% time 60)))))))
 | ||
| 	(setq time (concat
 | ||
| 		    (if current "*" " ")
 | ||
| 		    time
 | ||
| 		    (if register (concat "[" (char-to-string register) "]")
 | ||
| 		      "   ")))
 | ||
| 	(setq n (length time))
 | ||
| 	(if (< n 9)
 | ||
| 	    (concat (make-string (- 9 n) ? ) time)
 | ||
| 	  time))
 | ||
|     ;; absolute time
 | ||
|     (concat (if current " *" "  ")
 | ||
| 	    (format-time-string "%H:%M:%S" timestamp)
 | ||
| 	    (if register
 | ||
| 		(concat "[" (char-to-string register) "]")
 | ||
| 	      "   "))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;;; =====================================================================
 | ||
| ;;;                        Visualizer commands
 | ||
| 
 | ||
| (define-derived-mode
 | ||
|   undo-tree-visualizer-mode special-mode "undo-tree-visualizer"
 | ||
|   "Major mode used in undo-tree visualizer.
 | ||
| 
 | ||
| The undo-tree visualizer can only be invoked from a buffer in
 | ||
| which `undo-tree-mode' is enabled. The visualizer displays the
 | ||
| undo history tree graphically, and allows you to browse around
 | ||
| the undo history, undoing or redoing the corresponding changes in
 | ||
| the parent buffer.
 | ||
| 
 | ||
| Within the undo-tree visualizer, the following keys are available:
 | ||
| 
 | ||
|   \\{undo-tree-visualizer-mode-map}"
 | ||
|   :syntax-table nil
 | ||
|   :abbrev-table nil
 | ||
|   (setq truncate-lines t)
 | ||
|   (setq cursor-type nil)
 | ||
|   (setq undo-tree-visualizer-selected-node nil))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualize-undo (&optional arg)
 | ||
|   "Undo changes. A numeric ARG serves as a repeat count."
 | ||
|   (interactive "p")
 | ||
|   (let ((old (undo-tree-current buffer-undo-tree))
 | ||
| 	current)
 | ||
|     ;; unhighlight old current node
 | ||
|     (let ((undo-tree-insert-face 'undo-tree-visualizer-active-branch-face)
 | ||
| 	  (inhibit-read-only t))
 | ||
|       (undo-tree-draw-node old))
 | ||
|     ;; undo in parent buffer
 | ||
|     (switch-to-buffer-other-window undo-tree-visualizer-parent-buffer)
 | ||
|     (deactivate-mark)
 | ||
|     (unwind-protect
 | ||
| 	(let ((undo-tree-inhibit-kill-visualizer t)) (undo-tree-undo-1 arg))
 | ||
|       (setq current (undo-tree-current buffer-undo-tree))
 | ||
|       (switch-to-buffer-other-window undo-tree-visualizer-buffer-name)
 | ||
|       ;; when using lazy drawing, extend tree upwards as required
 | ||
|       (when undo-tree-visualizer-lazy-drawing
 | ||
| 	(undo-tree-expand-up old current))
 | ||
|       ;; highlight new current node
 | ||
|       (let ((inhibit-read-only t)) (undo-tree-draw-node current 'current))
 | ||
|       ;; update diff display, if any
 | ||
|       (when undo-tree-visualizer-diff (undo-tree-visualizer-update-diff)))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualize-redo (&optional arg)
 | ||
|   "Redo changes. A numeric ARG serves as a repeat count."
 | ||
|   (interactive "p")
 | ||
|   (let ((old (undo-tree-current buffer-undo-tree))
 | ||
| 	current)
 | ||
|     ;; unhighlight old current node
 | ||
|     (let ((undo-tree-insert-face 'undo-tree-visualizer-active-branch-face)
 | ||
| 	  (inhibit-read-only t))
 | ||
|       (undo-tree-draw-node (undo-tree-current buffer-undo-tree)))
 | ||
|     ;; redo in parent buffer
 | ||
|     (switch-to-buffer-other-window undo-tree-visualizer-parent-buffer)
 | ||
|     (deactivate-mark)
 | ||
|     (unwind-protect
 | ||
| 	(let ((undo-tree-inhibit-kill-visualizer t)) (undo-tree-redo-1 arg))
 | ||
|       (setq current (undo-tree-current buffer-undo-tree))
 | ||
|       (switch-to-buffer-other-window undo-tree-visualizer-buffer-name)
 | ||
|       ;; when using lazy drawing, extend tree downwards as required
 | ||
|       (when undo-tree-visualizer-lazy-drawing
 | ||
| 	(undo-tree-expand-down old current))
 | ||
|       ;; highlight new current node
 | ||
|       (let ((inhibit-read-only t)) (undo-tree-draw-node current 'current))
 | ||
|       ;; update diff display, if any
 | ||
|       (when undo-tree-visualizer-diff (undo-tree-visualizer-update-diff)))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualize-switch-branch-right (arg)
 | ||
|   "Switch to next branch of the undo tree.
 | ||
| This will affect which branch to descend when *redoing* changes
 | ||
| using `undo-tree-redo' or `undo-tree-visualizer-redo'."
 | ||
|   (interactive "p")
 | ||
|   ;; un-highlight old active branch below current node
 | ||
|   (goto-char (undo-tree-node-marker (undo-tree-current buffer-undo-tree)))
 | ||
|   (let ((undo-tree-insert-face 'undo-tree-visualizer-default-face)
 | ||
| 	(inhibit-read-only t))
 | ||
|     (undo-tree-highlight-active-branch (undo-tree-current buffer-undo-tree)))
 | ||
|   ;; increment branch
 | ||
|   (let ((branch (undo-tree-node-branch (undo-tree-current buffer-undo-tree))))
 | ||
|   (setf (undo-tree-node-branch (undo-tree-current buffer-undo-tree))
 | ||
|         (cond
 | ||
|          ((>= (+ branch arg) (undo-tree-num-branches))
 | ||
|           (1- (undo-tree-num-branches)))
 | ||
|          ((<= (+ branch arg) 0) 0)
 | ||
|          (t (+ branch arg))))
 | ||
|   (let ((inhibit-read-only t))
 | ||
|     ;; highlight new active branch below current node
 | ||
|     (goto-char (undo-tree-node-marker (undo-tree-current buffer-undo-tree)))
 | ||
|     (let ((undo-tree-insert-face 'undo-tree-visualizer-active-branch-face))
 | ||
|       (undo-tree-highlight-active-branch (undo-tree-current buffer-undo-tree)))
 | ||
|     ;; re-highlight current node
 | ||
|     (undo-tree-draw-node (undo-tree-current buffer-undo-tree) 'current))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualize-switch-branch-left (arg)
 | ||
|   "Switch to previous branch of the undo tree.
 | ||
| This will affect which branch to descend when *redoing* changes
 | ||
| using `undo-tree-redo' or `undo-tree-visualizer-redo'."
 | ||
|   (interactive "p")
 | ||
|   (undo-tree-visualize-switch-branch-right (- arg)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-quit ()
 | ||
|   "Quit the undo-tree visualizer."
 | ||
|   (interactive)
 | ||
|   (undo-tree-clear-visualizer-data buffer-undo-tree)
 | ||
|   ;; remove kill visualizer hook from parent buffer
 | ||
|   (unwind-protect
 | ||
|       (with-current-buffer undo-tree-visualizer-parent-buffer
 | ||
| 	(remove-hook 'before-change-functions 'undo-tree-kill-visualizer t))
 | ||
|     ;; kill diff buffer, if any
 | ||
|     (when undo-tree-visualizer-diff (undo-tree-visualizer-hide-diff))
 | ||
|     (let ((parent undo-tree-visualizer-parent-buffer)
 | ||
| 	  window)
 | ||
|       ;; kill visualizer buffer
 | ||
|       (kill-buffer nil)
 | ||
|       ;; switch back to parent buffer
 | ||
|       (unwind-protect
 | ||
| 	  (if (setq window (get-buffer-window parent))
 | ||
| 	      (select-window window)
 | ||
| 	    (switch-to-buffer parent))))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-abort ()
 | ||
|   "Quit the undo-tree visualizer and return buffer to original state."
 | ||
|   (interactive)
 | ||
|   (let ((node undo-tree-visualizer-initial-node))
 | ||
|     (undo-tree-visualizer-quit)
 | ||
|     (undo-tree-set node)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-set (&optional pos)
 | ||
|   "Set buffer to state corresponding to undo tree node
 | ||
| at POS, or point if POS is nil."
 | ||
|   (interactive)
 | ||
|   (unless pos (setq pos (point)))
 | ||
|   (let ((node (get-text-property pos 'undo-tree-node)))
 | ||
|     (when node
 | ||
|       ;; set parent buffer to state corresponding to node at POS
 | ||
|       (switch-to-buffer-other-window undo-tree-visualizer-parent-buffer)
 | ||
|       (let ((undo-tree-inhibit-kill-visualizer t)) (undo-tree-set node))
 | ||
|       (switch-to-buffer-other-window undo-tree-visualizer-buffer-name)
 | ||
|       ;; re-draw undo tree
 | ||
|       (let ((inhibit-read-only t)) (undo-tree-draw-tree buffer-undo-tree))
 | ||
|       (when undo-tree-visualizer-diff (undo-tree-visualizer-update-diff)))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-mouse-set (pos)
 | ||
|   "Set buffer to state corresponding to undo tree node
 | ||
| at mouse event POS."
 | ||
|   (interactive "@e")
 | ||
|   (undo-tree-visualizer-set (event-start (nth 1 pos))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualize-undo-to-x (&optional x)
 | ||
|   "Undo to last branch point, register, or saved state.
 | ||
| If X is the symbol `branch', undo to last branch point. If X is
 | ||
| the symbol `register', undo to last register. If X is the sumbol
 | ||
| `saved', undo to last saved state. If X is null, undo to first of
 | ||
| these that's encountered.
 | ||
| 
 | ||
| Interactively, a single \\[universal-argument] specifies
 | ||
| `branch', a double \\[universal-argument] \\[universal-argument]
 | ||
| specifies `saved', and a negative prefix argument specifies
 | ||
| `register'."
 | ||
|   (interactive "P")
 | ||
|   (when (and (called-interactively-p 'any) x)
 | ||
|     (setq x (prefix-numeric-value x)
 | ||
| 	  x (cond
 | ||
| 	     ((< x 0)  'register)
 | ||
| 	     ((<= x 4) 'branch)
 | ||
| 	     (t        'saved))))
 | ||
|   (let ((current (if undo-tree-visualizer-selection-mode
 | ||
| 		     undo-tree-visualizer-selected-node
 | ||
| 		   (undo-tree-current buffer-undo-tree)))
 | ||
| 	(diff undo-tree-visualizer-diff)
 | ||
| 	r)
 | ||
|     (undo-tree-visualizer-hide-diff)
 | ||
|     (while (and (undo-tree-node-previous current)
 | ||
| 		(or (if undo-tree-visualizer-selection-mode
 | ||
| 			(progn
 | ||
| 			  (undo-tree-visualizer-select-previous)
 | ||
| 			  (setq current undo-tree-visualizer-selected-node))
 | ||
| 		      (undo-tree-visualize-undo)
 | ||
| 		      (setq current (undo-tree-current buffer-undo-tree)))
 | ||
| 		    t)
 | ||
| 		         ;; branch point
 | ||
| 		(not (or (and (or (null x) (eq x 'branch))
 | ||
| 			      (> (undo-tree-num-branches) 1))
 | ||
| 			 ;; register
 | ||
| 			 (and (or (null x) (eq x 'register))
 | ||
| 			      (setq r (undo-tree-node-register current))
 | ||
| 			      (undo-tree-register-data-p
 | ||
| 			       (setq r (registerv-data (get-register r))))
 | ||
| 			      (eq current (undo-tree-register-data-node r)))
 | ||
| 			 ;; saved state
 | ||
| 			 (and (or (null x) (eq x 'saved))
 | ||
| 			      (undo-tree-node-unmodified-p current))
 | ||
| 			 ))))
 | ||
|     ;; update diff display, if any
 | ||
|     (when diff
 | ||
|       (undo-tree-visualizer-show-diff
 | ||
|        (when undo-tree-visualizer-selection-mode
 | ||
| 	 undo-tree-visualizer-selected-node)))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualize-redo-to-x (&optional x)
 | ||
|   "Redo to last branch point, register, or saved state.
 | ||
| If X is the symbol `branch', redo to last branch point. If X is
 | ||
| the symbol `register', redo to last register. If X is the sumbol
 | ||
| `saved', redo to last saved state. If X is null, redo to first of
 | ||
| these that's encountered.
 | ||
| 
 | ||
| Interactively, a single \\[universal-argument] specifies
 | ||
| `branch', a double \\[universal-argument] \\[universal-argument]
 | ||
| specifies `saved', and a negative prefix argument specifies
 | ||
| `register'."
 | ||
|   (interactive "P")
 | ||
|   (when (and (called-interactively-p 'any) x)
 | ||
|     (setq x (prefix-numeric-value x)
 | ||
| 	  x (cond
 | ||
| 	     ((< x 0)  'register)
 | ||
| 	     ((<= x 4) 'branch)
 | ||
| 	     (t        'saved))))
 | ||
|   (let ((current (if undo-tree-visualizer-selection-mode
 | ||
| 		     undo-tree-visualizer-selected-node
 | ||
| 		   (undo-tree-current buffer-undo-tree)))
 | ||
| 	(diff undo-tree-visualizer-diff)
 | ||
| 	r)
 | ||
|     (undo-tree-visualizer-hide-diff)
 | ||
|     (while (and (undo-tree-node-next current)
 | ||
| 		(or (if undo-tree-visualizer-selection-mode
 | ||
| 			(progn
 | ||
| 			  (undo-tree-visualizer-select-next)
 | ||
| 			  (setq current undo-tree-visualizer-selected-node))
 | ||
| 		      (undo-tree-visualize-redo)
 | ||
| 		      (setq current (undo-tree-current buffer-undo-tree)))
 | ||
| 		    t)
 | ||
| 		         ;; branch point
 | ||
| 		(not (or (and (or (null x) (eq x 'branch))
 | ||
| 			      (> (undo-tree-num-branches) 1))
 | ||
| 			 ;; register
 | ||
| 			 (and (or (null x) (eq x 'register))
 | ||
| 			      (setq r (undo-tree-node-register current))
 | ||
| 			      (undo-tree-register-data-p
 | ||
| 			       (setq r (registerv-data (get-register r))))
 | ||
| 			      (eq current (undo-tree-register-data-node r)))
 | ||
| 			 ;; saved state
 | ||
| 			 (and (or (null x) (eq x 'saved))
 | ||
| 			      (undo-tree-node-unmodified-p current))
 | ||
| 			 ))))
 | ||
|     ;; update diff display, if any
 | ||
|     (when diff
 | ||
|       (undo-tree-visualizer-show-diff
 | ||
|        (when undo-tree-visualizer-selection-mode
 | ||
| 	 undo-tree-visualizer-selected-node)))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-toggle-timestamps ()
 | ||
|   "Toggle display of time-stamps."
 | ||
|   (interactive)
 | ||
|   (setq undo-tree-visualizer-timestamps (not undo-tree-visualizer-timestamps))
 | ||
|   (setq undo-tree-visualizer-spacing (undo-tree-visualizer-calculate-spacing))
 | ||
|   ;; redraw tree
 | ||
|   (let ((inhibit-read-only t)) (undo-tree-draw-tree buffer-undo-tree)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-scroll-left (&optional arg)
 | ||
|   (interactive "p")
 | ||
|   (scroll-left (or arg 1) t))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-scroll-right (&optional arg)
 | ||
|   (interactive "p")
 | ||
|   (scroll-right (or arg 1) t))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-scroll-up (&optional arg)
 | ||
|   (interactive "P")
 | ||
|   (if (or (and (numberp arg) (< arg 0)) (eq arg '-))
 | ||
|       (undo-tree-visualizer-scroll-down arg)
 | ||
|     ;; scroll up and expand newly-visible portion of tree
 | ||
|     (unwind-protect
 | ||
| 	(scroll-up-command arg)
 | ||
|       (undo-tree-expand-down
 | ||
|        (nth (undo-tree-node-branch (undo-tree-current buffer-undo-tree))
 | ||
| 	    (undo-tree-node-next (undo-tree-current buffer-undo-tree)))))
 | ||
|     ;; signal error if at eob
 | ||
|     (when (and (not undo-tree-visualizer-needs-extending-down) (eobp))
 | ||
|       (scroll-up))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-scroll-down (&optional arg)
 | ||
|   (interactive "P")
 | ||
|   (if (or (and (numberp arg) (< arg 0)) (eq arg '-))
 | ||
|       (undo-tree-visualizer-scroll-up arg)
 | ||
|     ;; ensure there's enough room at top of buffer to scroll
 | ||
|     (let ((scroll-lines
 | ||
| 	   (or arg (- (window-height) next-screen-context-lines)))
 | ||
| 	  (window-line (1- (line-number-at-pos (window-start)))))
 | ||
|       (when (and undo-tree-visualizer-needs-extending-up
 | ||
| 		 (< window-line scroll-lines))
 | ||
| 	(let ((inhibit-read-only t))
 | ||
| 	  (goto-char (point-min))
 | ||
| 	  (undo-tree-move-up (- scroll-lines window-line)))))
 | ||
|     ;; scroll down and expand newly-visible portion of tree
 | ||
|     (unwind-protect
 | ||
| 	(scroll-down-command arg)
 | ||
|       (undo-tree-expand-up
 | ||
|        (undo-tree-node-previous (undo-tree-current buffer-undo-tree))))
 | ||
|     ;; signal error if at bob
 | ||
|     (when (and (not undo-tree-visualizer-needs-extending-down) (bobp))
 | ||
|       (scroll-down))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;;; =====================================================================
 | ||
| ;;;                    Visualizer selection mode
 | ||
| 
 | ||
| (define-minor-mode undo-tree-visualizer-selection-mode
 | ||
|   "Toggle mode to select nodes in undo-tree visualizer."
 | ||
|   :lighter "Select"
 | ||
|   :keymap undo-tree-visualizer-selection-mode-map
 | ||
|   :group undo-tree
 | ||
|   (cond
 | ||
|    ;; enable selection mode
 | ||
|    (undo-tree-visualizer-selection-mode
 | ||
|     (setq cursor-type 'box)
 | ||
|     (setq undo-tree-visualizer-selected-node
 | ||
| 	  (undo-tree-current buffer-undo-tree))
 | ||
|     ;; erase diff (if any), as initially selected node is identical to current
 | ||
|     (when undo-tree-visualizer-diff
 | ||
|       (let ((buff (get-buffer undo-tree-diff-buffer-name))
 | ||
| 	    (inhibit-read-only t))
 | ||
| 	(when buff (with-current-buffer buff (erase-buffer))))))
 | ||
|    (t ;; disable selection mode
 | ||
|     (setq cursor-type nil)
 | ||
|     (setq undo-tree-visualizer-selected-node nil)
 | ||
|     (goto-char (undo-tree-node-marker (undo-tree-current buffer-undo-tree)))
 | ||
|     (when undo-tree-visualizer-diff (undo-tree-visualizer-update-diff)))
 | ||
|    ))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-select-previous (&optional arg)
 | ||
|   "Move to previous node."
 | ||
|   (interactive "p")
 | ||
|   (let ((node undo-tree-visualizer-selected-node))
 | ||
|     (catch 'top
 | ||
|       (dotimes (i (or arg 1))
 | ||
| 	(unless (undo-tree-node-previous node) (throw 'top t))
 | ||
| 	(setq node (undo-tree-node-previous node))))
 | ||
|     ;; when using lazy drawing, extend tree upwards as required
 | ||
|     (when undo-tree-visualizer-lazy-drawing
 | ||
|       (undo-tree-expand-up undo-tree-visualizer-selected-node node))
 | ||
|     ;; update diff display, if any
 | ||
|     (when (and undo-tree-visualizer-diff
 | ||
| 	       (not (eq node undo-tree-visualizer-selected-node)))
 | ||
|       (undo-tree-visualizer-update-diff node))
 | ||
|     ;; move to selected node
 | ||
|     (goto-char (undo-tree-node-marker node))
 | ||
|     (setq undo-tree-visualizer-selected-node node)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-select-next (&optional arg)
 | ||
|   "Move to next node."
 | ||
|   (interactive "p")
 | ||
|   (let ((node undo-tree-visualizer-selected-node))
 | ||
|     (catch 'bottom
 | ||
|       (dotimes (i (or arg 1))
 | ||
| 	(unless (nth (undo-tree-node-branch node) (undo-tree-node-next node))
 | ||
| 	  (throw 'bottom t))
 | ||
| 	(setq node
 | ||
| 	      (nth (undo-tree-node-branch node) (undo-tree-node-next node)))))
 | ||
|     ;; when using lazy drawing, extend tree downwards as required
 | ||
|     (when undo-tree-visualizer-lazy-drawing
 | ||
|       (undo-tree-expand-down undo-tree-visualizer-selected-node node))
 | ||
|     ;; update diff display, if any
 | ||
|     (when (and undo-tree-visualizer-diff
 | ||
| 	       (not (eq node undo-tree-visualizer-selected-node)))
 | ||
|       (undo-tree-visualizer-update-diff node))
 | ||
|     ;; move to selected node
 | ||
|     (goto-char (undo-tree-node-marker node))
 | ||
|     (setq undo-tree-visualizer-selected-node node)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-select-right (&optional arg)
 | ||
|   "Move right to a sibling node."
 | ||
|   (interactive "p")
 | ||
|   (let ((node undo-tree-visualizer-selected-node)
 | ||
| 	end)
 | ||
|     (goto-char (undo-tree-node-marker undo-tree-visualizer-selected-node))
 | ||
|     (setq end (line-end-position))
 | ||
|     (catch 'end
 | ||
|       (dotimes (i arg)
 | ||
| 	(while (or (null node) (eq node undo-tree-visualizer-selected-node))
 | ||
| 	  (forward-char)
 | ||
| 	  (setq node (get-text-property (point) 'undo-tree-node))
 | ||
| 	  (when (= (point) end) (throw 'end t)))))
 | ||
|     (goto-char (undo-tree-node-marker
 | ||
| 		(or node undo-tree-visualizer-selected-node)))
 | ||
|     (when (and undo-tree-visualizer-diff node
 | ||
| 	       (not (eq node undo-tree-visualizer-selected-node)))
 | ||
|       (undo-tree-visualizer-update-diff node))
 | ||
|     (when node (setq undo-tree-visualizer-selected-node node))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-select-left (&optional arg)
 | ||
|   "Move left to a sibling node."
 | ||
|   (interactive "p")
 | ||
|   (let ((node (get-text-property (point) 'undo-tree-node))
 | ||
| 	beg)
 | ||
|     (goto-char (undo-tree-node-marker undo-tree-visualizer-selected-node))
 | ||
|     (setq beg (line-beginning-position))
 | ||
|     (catch 'beg
 | ||
|       (dotimes (i arg)
 | ||
| 	(while (or (null node) (eq node undo-tree-visualizer-selected-node))
 | ||
| 	  (backward-char)
 | ||
| 	  (setq node (get-text-property (point) 'undo-tree-node))
 | ||
| 	  (when (= (point) beg) (throw 'beg t)))))
 | ||
|     (goto-char (undo-tree-node-marker
 | ||
| 		(or node undo-tree-visualizer-selected-node)))
 | ||
|     (when (and undo-tree-visualizer-diff node
 | ||
| 	       (not (eq node undo-tree-visualizer-selected-node)))
 | ||
|       (undo-tree-visualizer-update-diff node))
 | ||
|     (when node (setq undo-tree-visualizer-selected-node node))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-select (pos)
 | ||
|   (let ((node (get-text-property pos 'undo-tree-node)))
 | ||
|     (when node
 | ||
|       ;; select node at POS
 | ||
|       (goto-char (undo-tree-node-marker node))
 | ||
|       ;; when using lazy drawing, extend tree up and down as required
 | ||
|       (when undo-tree-visualizer-lazy-drawing
 | ||
| 	(undo-tree-expand-up undo-tree-visualizer-selected-node node)
 | ||
| 	(undo-tree-expand-down undo-tree-visualizer-selected-node node))
 | ||
|       ;; update diff display, if any
 | ||
|       (when (and undo-tree-visualizer-diff
 | ||
| 		 (not (eq node undo-tree-visualizer-selected-node)))
 | ||
| 	(undo-tree-visualizer-update-diff node))
 | ||
|       ;; update selected node
 | ||
|       (setq undo-tree-visualizer-selected-node node)
 | ||
|       )))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-mouse-select (pos)
 | ||
|   "Select undo tree node at mouse event POS."
 | ||
|   (interactive "@e")
 | ||
|   (undo-tree-visualizer-select (event-start (nth 1 pos))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| ;;; =====================================================================
 | ||
| ;;;                      Visualizer diff display
 | ||
| 
 | ||
| (defun undo-tree-visualizer-toggle-diff ()
 | ||
|   "Toggle diff display in undo-tree visualizer."
 | ||
|   (interactive)
 | ||
|   (if undo-tree-visualizer-diff
 | ||
|       (undo-tree-visualizer-hide-diff)
 | ||
|     (undo-tree-visualizer-show-diff)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-selection-toggle-diff ()
 | ||
|   "Toggle diff display in undo-tree visualizer selection mode."
 | ||
|   (interactive)
 | ||
|   (if undo-tree-visualizer-diff
 | ||
|       (undo-tree-visualizer-hide-diff)
 | ||
|     (let ((node (get-text-property (point) 'undo-tree-node)))
 | ||
|       (when node (undo-tree-visualizer-show-diff node)))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-show-diff (&optional node)
 | ||
|   ;; show visualizer diff display
 | ||
|   (setq undo-tree-visualizer-diff t)
 | ||
|   (let ((buff (with-current-buffer undo-tree-visualizer-parent-buffer
 | ||
| 		(undo-tree-diff node)))
 | ||
| 	(display-buffer-mark-dedicated 'soft)
 | ||
| 	win)
 | ||
|     (setq win (split-window))
 | ||
|     (set-window-buffer win buff)
 | ||
|     (shrink-window-if-larger-than-buffer win)))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-hide-diff ()
 | ||
|   ;; hide visualizer diff display
 | ||
|   (setq undo-tree-visualizer-diff nil)
 | ||
|   (let ((win (get-buffer-window undo-tree-diff-buffer-name)))
 | ||
|     (when win (with-selected-window win (kill-buffer-and-window)))))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-diff (&optional node)
 | ||
|   ;; Create diff between NODE and current state (or previous state and current
 | ||
|   ;; state, if NODE is null). Returns buffer containing diff.
 | ||
|   (let (tmpfile buff)
 | ||
|     ;; generate diff
 | ||
|     (let ((undo-tree-inhibit-kill-visualizer t)
 | ||
| 	  (current (undo-tree-current buffer-undo-tree)))
 | ||
|       (undo-tree-set (or node (undo-tree-node-previous current) current)
 | ||
| 		     'preserve-timestamps)
 | ||
|       (setq tmpfile (diff-file-local-copy (current-buffer)))
 | ||
|       (undo-tree-set current 'preserve-timestamps))
 | ||
|     (setq buff (diff-no-select
 | ||
| 		tmpfile (current-buffer) nil 'noasync
 | ||
| 		(get-buffer-create undo-tree-diff-buffer-name)))
 | ||
|     ;; delete process messages and useless headers from diff buffer
 | ||
|     (let ((inhibit-read-only t))
 | ||
|       (with-current-buffer buff
 | ||
| 	(goto-char (point-min))
 | ||
| 	(delete-region (point) (1+ (line-end-position 3)))
 | ||
| 	(goto-char (point-max))
 | ||
| 	(forward-line -2)
 | ||
| 	(delete-region (point) (point-max))
 | ||
| 	(setq cursor-type nil)
 | ||
| 	(setq buffer-read-only t)))
 | ||
|     buff))
 | ||
| 
 | ||
| 
 | ||
| (defun undo-tree-visualizer-update-diff (&optional node)
 | ||
|   ;; update visualizer diff display to show diff between current state and
 | ||
|   ;; NODE (or previous state, if NODE is null)
 | ||
|   (with-current-buffer undo-tree-visualizer-parent-buffer
 | ||
|     (undo-tree-diff node))
 | ||
|   (let ((win (get-buffer-window undo-tree-diff-buffer-name)))
 | ||
|     (when win
 | ||
|       (balance-windows)
 | ||
|       (shrink-window-if-larger-than-buffer win))))
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| (provide 'undo-tree)
 | ||
| 
 | ||
| ;;; undo-tree.el ends here
 |