Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| ;;; undo-tree.el --- Treat undo history as a tree -*- lexical-binding: t; -*- | |
| ;; Copyright (C) 2009-2014 Free Software Foundation, Inc | |
| ;; Author: Toby Cubitt <toby-undo-tree@dr-qubit.org> | |
| ;; Maintainer: Toby Cubitt <toby-undo-tree@dr-qubit.org> | |
| ;; Version: 0.6.6 | |
| ;; 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 | |
| ;; | / | | | / | | | / | |
| ;; |/ | | |/ | | |/ | |
| ;; (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 invoked. | |
| ;; | |
| ;; | |
| ;; | |
| ;; 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 much the same way: when there's | |
| ;; an active region, undoing only undoes changes that affect that region. In | |
| ;; `undo-tree-mode', redoing when there's an active region similarly only | |
| ;; redoes changes that affect that region. | |
| ;; | |
| ;; However, the way these undo- and redo-in-region changes are recorded in the | |
| ;; undo history is quite different. The good news is, you don't need to | |
| ;; understand this to use undo- and redo-in-region in `undo-tree-mode' - just | |
| ;; go ahead and use them! They'll probably work as you expect. But if you're | |
| ;; masochistic enough to want to understand conceptually what's happening to | |
| ;; the undo tree as you undo- and redo-in-region, then read on... | |
| ;; | |
| ;; | |
| ;; 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_ o | | |
| ;; | \ | | | |
| ;; | x | 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, redo-in-region 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 first 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_ | |
| ;; | | \ | |
| ;; | | x (redo-in-region) | |
| ;; | | | | |
| ;; o o | | |
| ;; | | | | |
| ;; | | | | |
| ;; | | | | |
| ;; 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_ | |
| ;; | \ | \ | |
| ;; | x | o | |
| ;; | | | | | |
| ;; o | o | | |
| ;; | | | | | |
| ;; | | | x (redo-in-region) | |
| ;; | | | | | |
| ;; o o o 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))) | |
| (defvar undo-tree-old-undo-menu-item nil) | |
| (defun undo-tree-update-menu-bar () | |
| "Update `undo-tree-mode' Edit menu items." | |
| (if undo-tree-mode | |
| (progn | |
| ;; save old undo menu item, and install undo/redo menu items | |
| (setq undo-tree-old-undo-menu-item | |
| (cdr (assq 'undo (lookup-key global-map [menu-bar edit])))) | |
| (define-key (lookup-key global-map [menu-bar edit]) | |
| [undo] '(menu-item "Undo" undo-tree-undo | |
| :enable (and undo-tree-mode | |
| (not buffer-read-only) | |
| (not (eq t buffer-undo-list)) | |
| (not (eq nil buffer-undo-tree)) | |
| (undo-tree-node-previous | |
| (undo-tree-current buffer-undo-tree))) | |
| :help "Undo last operation")) | |
| (define-key-after (lookup-key global-map [menu-bar edit]) | |
| [redo] '(menu-item "Redo" undo-tree-redo | |
| :enable (and undo-tree-mode | |
| (not buffer-read-only) | |
| (not (eq t buffer-undo-list)) | |
| (not (eq nil buffer-undo-tree)) | |
| (undo-tree-node-next | |
| (undo-tree-current buffer-undo-tree))) | |
| :help "Redo last operation") | |
| 'undo)) | |
| ;; uninstall undo/redo menu items | |
| (define-key (lookup-key global-map [menu-bar edit]) | |
| [undo] undo-tree-old-undo-menu-item) | |
| (define-key (lookup-key global-map [menu-bar edit]) | |
| [redo] nil))) | |
| (add-hook 'menu-bar-update-hook 'undo-tree-update-menu-bar) | |
| ;;; ===================================================================== | |
| ;;; 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. | |
| (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 (and undo-list (null copy)) | |
| (setq copy | |
| (undo-tree-restore-GC-elts-from-pool (pop undo-list)))) | |
| (when copy | |
| (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") | |
| (unless undo-tree-mode | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| ;; 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") | |
| (unless undo-tree-mode | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| ;; 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))) | |
| )))))) | |
| (unless undo-tree-mode | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| ;; 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: ") | |
| (unless undo-tree-mode | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| ;; 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: ") | |
| (unless undo-tree-mode | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| ;; 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) | |
| (unless undo-tree-mode | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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) | |
| (unless undo-tree-mode | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| ;; 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 "*") | |
| (unless undo-tree-mode | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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) | |
| (setq undo-tree-visualizer-needs-extending-down nil | |
| undo-tree-visualizer-needs-extending-up nil) | |
| (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") | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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") | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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") | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| ;; 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) | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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) | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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 (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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") | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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") | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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") | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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) | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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") | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (scroll-left (or arg 1) t)) | |
| (defun undo-tree-visualizer-scroll-right (&optional arg) | |
| (interactive "p") | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (scroll-right (or arg 1) t)) | |
| (defun undo-tree-visualizer-scroll-up (&optional arg) | |
| (interactive "P") | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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") | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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") | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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") | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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") | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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") | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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") | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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) | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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) | |
| (unless (eq major-mode 'undo-tree-visualizer-mode) | |
| (user-error "Undo-tree mode not enabled in buffer")) | |
| (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 |