------------------------------------------------------------ revno: 229 committer: Toby S. Cubitt <ts...@cantab.net> branch nick: elpa timestamp: Thu 2012-05-24 11:49:41 +0300 message: updated undo-tree package to version 0.5.2 * undo-tree.el: add diff view feature in undo-tree visualizer. modified: packages/undo-tree/undo-tree.el
=== modified file 'packages/undo-tree/undo-tree.el' --- a/packages/undo-tree/undo-tree.el 2012-05-02 20:20:08 +0000 +++ b/packages/undo-tree/undo-tree.el 2012-05-24 08:49:41 +0000 @@ -3,7 +3,7 @@ ;; Copyright (C) 2009-2012 Free Software Foundation, Inc ;; Author: Toby Cubitt <toby-undo-t...@dr-qubit.org> -;; Version: 0.4 +;; Version: 0.5.2 ;; 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 @@ -103,6 +103,7 @@ ;; Restore buffer state from register. ;; ;; +;; ;; In the undo-tree visualizer: ;; ;; <up> p C-p (`undo-tree-visualize-undo') @@ -117,12 +118,24 @@ ;; <right> f C-f (`undo-tree-visualize-switch-branch-right') ;; Switch to next undo-tree branch. ;; +;; <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. ;; -;; q C-q (`undo-tree-visualizer-quit') +;; 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. ;; @@ -137,6 +150,53 @@ ;; ;; ;; +;; 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. +;; +;; +;; +;; ;; Undo Systems ;; ============ ;; @@ -436,9 +496,12 @@ ;; 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 (by hitting "t") you can toggle the display of -;; time-stamps. (There's one other tiny difference: the visualizer puts the -;; most recent branch on the left rather than the right.) +;; 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 @@ -446,10 +509,17 @@ ;; 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"). And 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. +;; (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 @@ -458,8 +528,24 @@ ;; 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. +;; 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 . ;; ;; ;; @@ -604,6 +690,47 @@ ;;; Change Log: ;; +;; Version 0.5.2 +;; * added `~' to end of default history save-file name +;; * avoid error in `undo-tree-save-history' when undo is disabled in buffer +;; or buffer has no undo information to save +;; +;; Version 0.5.1 +;; * remove now unnecessary compatibility hack for `called-interactively-p' +;; +;; Version 0.5 +;; * implemented diff display in visualizer, toggled on and off using +;; `undo-tree-visualizer-toggle-diff' +;; * added `undo-tree-visualizer-diff' customization option, to display diff +;; by default +;; * added `called-interactively-p', `registerv-make', `registerv-data', +;; `diff-no-select' and `diff-file-local-copy' compatibility hacks for +;; older Emacsen +;; * split out core of `undo-tree-undo' and `undo-tree-redo' into internal +;; `undo-tree-undo-1' and `undo-tree-redo-1' functions, which now take an +;; additional optional argument to preserve timestamps +;; * preserve timestamps when generating diff for visualizer diff view +;; * fixed bug in `undo-tree-visualizer-select-left' and +;; `undo-tree-visualizer-select-right' when using selection mode whilst +;; timestamps are displayed +;; * fixed bug in `undo-tree-draw-node' caused by new registerv structure, +;; which prevented registers from being displayed in visualizer +;; * added `undo-tree-visualizer-relative-timestamps' option to make +;; visualizer display timestamps relative to current time +;; * use a function `undo-tree-make-history-save-file-name' function to +;; generate history save filename, allowing save file to be customized by +;; overriding this function +;; * clear visualizer data / kill visualizer in `undo-tree-save-history' +;; before saving history to file, otherwise markers in visualizer meta-data +;; cause read errors in `undo-tree-load-history' +;; * make `undo-tree-visualizer-timestamps' into defcustom, to allow +;; timestamps to be displayed by default +;; * use `undo-tree-visualizer-selected-node' to store currently selected node +;; in visualizer selection mode, instead of relying on point location, to +;; avoid errors if point was moved manually +;; * added `undo-tree-visualizer-abort' command to quit visualizer and return +;; to original state, stored in `undo-tree-visualizer-initial-node' +;; ;; Version 0.4 ;; * implemented persistent history storage: `undo-tree-save-history' and ;; `undo-tree-load-history' save and restore an undo tree to file, enabling @@ -744,16 +871,101 @@ ;;; Code: (eval-when-compile (require 'cl)) - -;; `characterp' isn't defined in Emacs versions <= 22 +(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 <= 22 +;; `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)))) + + + ;;; ===================================================================== ;;; Global variables and customization options @@ -774,15 +986,6 @@ :group 'undo-tree :type 'string) -(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 @@ -806,32 +1009,65 @@ '(choice (const :tag "<disabled>" nil)) 'boolean)) + +(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-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) +(make-variable-buffer-local 'undo-tree-visualizer-timestamps) + + +(defcustom undo-tree-visualizer-diff nil + "When non-nil, display diff by default in undo-tree visualizer. + +\\<undo-tree-visualizer-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) +(make-variable-buffer-local 'undo-tree-visualizer-diff) + + (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-visualizer-spacing 3 - "Horizontal spacing in undo-tree visualization. -Must be a postivie odd integer." + +(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 '(integer - :match (lambda (w n) (and (integerp n) (> n 0) (= (mod n 2) 1))))) -(make-variable-buffer-local 'undo-tree-visualizer-spacing) - -(defvar undo-tree-map nil - "Keymap used in undo-tree-mode.") + :type 'boolean) (defface undo-tree-visualizer-default-face '((((class color)) :foreground "gray")) - "*Face used to draw undo-tree in visualizer." + "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." + "Face used to highlight current undo-tree node in visualizer." :group 'undo-tree) (defface undo-tree-visualizer-active-branch-face @@ -839,37 +1075,47 @@ (:foreground "white" :weight bold)) (((class color) (background light)) (:foreground "black" :weight bold))) - "*Face used to highlight active undo-tree branch -in visualizer." + "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 + "Face used to highlight undo-tree nodes saved to a register in visualizer." :group 'undo-tree) -(defvar undo-tree-visualizer-map nil - "Keymap used in undo-tree visualizer.") - -(defvar undo-tree-visualizer-selection-map nil - "Keymap used in undo-tree visualizer selection mode.") - (defvar undo-tree-visualizer-parent-buffer nil "Parent buffer in visualizer.") (make-variable-buffer-local 'undo-tree-visualizer-parent-buffer) -(defvar undo-tree-visualizer-timestamps nil - "Non-nil when visualizer is displaying time-stamps.") -(make-variable-buffer-local 'undo-tree-visualizer-timestamps) - -(defconst undo-tree-visualizer-buffer-name " *undo-tree*") +;; stores current horizontal spacing needed for drawing undo-tree +(defvar undo-tree-visualizer-spacing nil) +(make-variable-buffer-local 'undo-tree-visualizer-spacing) + +;; calculate horizontal spacing required for drawing undo-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) +(make-variable-buffer-local 'undo-tree-visualizer-initial-node) + +;; holds currently selected node in visualizer selection mode +(defvar undo-tree-visualizer-selected-node nil) +(make-variable-buffer-local 'undo-tree-visualizer-selected) ;; 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) + +(defconst undo-tree-visualizer-buffer-name " *undo-tree*") +(defconst undo-tree-diff-buffer-name "*undo-tree Diff*") + ;; prevent debugger being called on "No further redo information" (add-to-list 'debug-ignored-errors "^No further redo information") @@ -879,158 +1125,132 @@ ;;; ================================================================= ;;; Setup default keymaps +(defvar undo-tree-map nil + "Keymap used in undo-tree-mode.") + (unless undo-tree-map - (setq undo-tree-map (make-sparse-keymap)) - ;; remap `undo' and `undo-only' to `undo-tree-undo' - (define-key undo-tree-map [remap undo] 'undo-tree-undo) - (define-key undo-tree-map [remap undo-only] 'undo-tree-undo) - ;; bind standard undo bindings (since these match redo counterparts) - (define-key undo-tree-map (kbd "C-/") 'undo-tree-undo) - (define-key undo-tree-map "\C-_" 'undo-tree-undo) - ;; redo doesn't exist normally, so define our own keybindings - (define-key undo-tree-map (kbd "C-?") 'undo-tree-redo) - (define-key undo-tree-map (kbd "M-_") 'undo-tree-redo) - ;; just in case something has defined `redo'... - (define-key undo-tree-map [remap redo] 'undo-tree-redo) - ;; we use "C-x u" for the undo-tree visualizer - (define-key undo-tree-map (kbd "\C-x u") 'undo-tree-visualize) - ;; bind register commands - (define-key undo-tree-map (kbd "C-x r u") - 'undo-tree-save-state-to-register) - (define-key undo-tree-map (kbd "C-x r U") - 'undo-tree-restore-state-from-register)) - + (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-map nil + "Keymap used in undo-tree visualizer.") (unless undo-tree-visualizer-map - (setq undo-tree-visualizer-map (make-keymap)) - ;; vertical motion keys undo/redo - (define-key undo-tree-visualizer-map [remap previous-line] - 'undo-tree-visualize-undo) - (define-key undo-tree-visualizer-map [remap next-line] - 'undo-tree-visualize-redo) - (define-key undo-tree-visualizer-map [up] - 'undo-tree-visualize-undo) - (define-key undo-tree-visualizer-map "p" - 'undo-tree-visualize-undo) - (define-key undo-tree-visualizer-map "\C-p" - 'undo-tree-visualize-undo) - (define-key undo-tree-visualizer-map [down] - 'undo-tree-visualize-redo) - (define-key undo-tree-visualizer-map "n" - 'undo-tree-visualize-redo) - (define-key undo-tree-visualizer-map "\C-n" - 'undo-tree-visualize-redo) - ;; horizontal motion keys switch branch - (define-key undo-tree-visualizer-map [remap forward-char] - 'undo-tree-visualize-switch-branch-right) - (define-key undo-tree-visualizer-map [remap backward-char] - 'undo-tree-visualize-switch-branch-left) - (define-key undo-tree-visualizer-map [right] - 'undo-tree-visualize-switch-branch-right) - (define-key undo-tree-visualizer-map "f" - 'undo-tree-visualize-switch-branch-right) - (define-key undo-tree-visualizer-map "\C-f" - 'undo-tree-visualize-switch-branch-right) - (define-key undo-tree-visualizer-map [left] - 'undo-tree-visualize-switch-branch-left) - (define-key undo-tree-visualizer-map "b" - 'undo-tree-visualize-switch-branch-left) - (define-key undo-tree-visualizer-map "\C-b" - 'undo-tree-visualize-switch-branch-left) - ;; mouse sets buffer state to node at click - (define-key undo-tree-visualizer-map [mouse-1] - 'undo-tree-visualizer-mouse-set) - ;; toggle timestamps - (define-key undo-tree-visualizer-map "t" - 'undo-tree-visualizer-toggle-timestamps) - ;; selection mode - (define-key undo-tree-visualizer-map "s" - 'undo-tree-visualizer-selection-mode) - ;; horizontal scrolling may be needed if the tree is very wide - (define-key undo-tree-visualizer-map "," - 'undo-tree-visualizer-scroll-left) - (define-key undo-tree-visualizer-map "." - 'undo-tree-visualizer-scroll-right) - (define-key undo-tree-visualizer-map "<" - 'undo-tree-visualizer-scroll-left) - (define-key undo-tree-visualizer-map ">" - 'undo-tree-visualizer-scroll-right) - ;; vertical scrolling may be needed if the tree is very tall - (define-key undo-tree-visualizer-map [next] 'scroll-up) - (define-key undo-tree-visualizer-map [prior] 'scroll-down) - ;; quit visualizer - (define-key undo-tree-visualizer-map "q" - 'undo-tree-visualizer-quit) - (define-key undo-tree-visualizer-map "\C-q" - 'undo-tree-visualizer-quit)) - + (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) + ;; 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) + ;; 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] 'scroll-up) + (define-key map [prior] '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-map map))) + + +(defvar undo-tree-visualizer-selection-map nil + "Keymap used in undo-tree visualizer selection mode.") (unless undo-tree-visualizer-selection-map - (setq undo-tree-visualizer-selection-map (make-keymap)) - ;; vertical motion keys move up and down tree - (define-key undo-tree-visualizer-selection-map [remap previous-line] - 'undo-tree-visualizer-select-previous) - (define-key undo-tree-visualizer-selection-map [remap next-line] - 'undo-tree-visualizer-select-next) - (define-key undo-tree-visualizer-selection-map [up] - 'undo-tree-visualizer-select-previous) - (define-key undo-tree-visualizer-selection-map "p" - 'undo-tree-visualizer-select-previous) - (define-key undo-tree-visualizer-selection-map "\C-p" - 'undo-tree-visualizer-select-previous) - (define-key undo-tree-visualizer-selection-map [down] - 'undo-tree-visualizer-select-next) - (define-key undo-tree-visualizer-selection-map "n" - 'undo-tree-visualizer-select-next) - (define-key undo-tree-visualizer-selection-map "\C-n" - 'undo-tree-visualizer-select-next) - ;; vertical scroll keys move up and down quickly - (define-key undo-tree-visualizer-selection-map [next] - (lambda () (interactive) (undo-tree-visualizer-select-next 10))) - (define-key undo-tree-visualizer-selection-map [prior] - (lambda () (interactive) (undo-tree-visualizer-select-previous 10))) - ;; horizontal motion keys move to left and right siblings - (define-key undo-tree-visualizer-selection-map [remap forward-char] - 'undo-tree-visualizer-select-right) - (define-key undo-tree-visualizer-selection-map [remap backward-char] - 'undo-tree-visualizer-select-left) - (define-key undo-tree-visualizer-selection-map [right] - 'undo-tree-visualizer-select-right) - (define-key undo-tree-visualizer-selection-map "f" - 'undo-tree-visualizer-select-right) - (define-key undo-tree-visualizer-selection-map "\C-f" - 'undo-tree-visualizer-select-right) - (define-key undo-tree-visualizer-selection-map [left] - 'undo-tree-visualizer-select-left) - (define-key undo-tree-visualizer-selection-map "b" - 'undo-tree-visualizer-select-left) - (define-key undo-tree-visualizer-selection-map "\C-b" - 'undo-tree-visualizer-select-left) - ;; horizontal scroll keys move left or right quickly - (define-key undo-tree-visualizer-selection-map "," - (lambda () (interactive) (undo-tree-visualizer-select-left 10))) - (define-key undo-tree-visualizer-selection-map "." - (lambda () (interactive) (undo-tree-visualizer-select-right 10))) - (define-key undo-tree-visualizer-selection-map "<" - (lambda () (interactive) (undo-tree-visualizer-select-left 10))) - (define-key undo-tree-visualizer-selection-map ">" - (lambda () (interactive) (undo-tree-visualizer-select-right 10))) - ;; mouse or <enter> sets buffer state to node at point/click - (define-key undo-tree-visualizer-selection-map "\r" - 'undo-tree-visualizer-set) - (define-key undo-tree-visualizer-selection-map [mouse-1] - 'undo-tree-visualizer-mouse-set) - ;; toggle timestamps - (define-key undo-tree-visualizer-selection-map "t" - 'undo-tree-visualizer-toggle-timestamps) - ;; quit visualizer selection mode - (define-key undo-tree-visualizer-selection-map "s" - 'undo-tree-visualizer-mode) - ;; quit visualizer - (define-key undo-tree-visualizer-selection-map "q" - 'undo-tree-visualizer-quit) - (define-key undo-tree-visualizer-selection-map "\C-q" - 'undo-tree-visualizer-quit)) + (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))) + ;; mouse or <enter> sets buffer state to node at point/click + (define-key map "\r" 'undo-tree-visualizer-set) + (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-selection-toggle-diff) + ;; quit visualizer selection mode + (define-key map "s" 'undo-tree-visualizer-mode) + ;; quit visualizer + (define-key map "q" 'undo-tree-visualizer-quit) + (define-key map "\C-q" 'undo-tree-visualizer-abort) + ;; set keymap + (setq undo-tree-visualizer-selection-map map))) @@ -1889,7 +2109,7 @@ ;; (recognizable as first node with more than one branch) (let ((mark-active nil)) (while (= (length (undo-tree-node-next node)) 1) - (undo-tree-undo) + (undo-tree-undo-1) (setq fragment node node (undo-tree-current buffer-undo-tree)))) (when (eq splice node) (setq splice nil)) @@ -2006,7 +2226,7 @@ (let ((mark-active nil)) (while (not (eq (undo-tree-current buffer-undo-tree) original-current)) - (undo-tree-redo))) + (undo-tree-redo-1))) nil) ; return nil to indicate failure ;; otherwise... @@ -2016,9 +2236,9 @@ (let ((mark-active nil) (current (undo-tree-current buffer-undo-tree))) (while (not (eq (undo-tree-current buffer-undo-tree) node)) - (undo-tree-undo)) + (undo-tree-undo-1)) (while (not (eq (undo-tree-current buffer-undo-tree) current)) - (undo-tree-redo))) + (undo-tree-redo-1))) (cond ;; if there's no remaining fragment, just create undo-in-region node @@ -2057,7 +2277,7 @@ (undo-tree-node-previous original-fragment)) (let ((mark-active nil)) (while (not (eq (undo-tree-current buffer-undo-tree) splice)) - (undo-tree-redo nil 'preserve-undo)))) + (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) @@ -2428,7 +2648,7 @@ -(defun undo-tree-undo (&optional arg preserve-redo) +(defun undo-tree-undo (&optional arg) "Undo changes. Repeat this command to undo more changes. A numeric ARG serves as a repeat count. @@ -2436,15 +2656,24 @@ 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. - -A non-nil PRESERVE-REDO causes the existing redo record to be -preserved, rather than replacing it with the new one generated by -undoing." +changes within the current region." (interactive "*P") ;; throw error if undo is disabled in buffer (when (eq buffer-undo-list t) (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) @@ -2506,9 +2735,10 @@ ;; rewind current node and update timestamp (setf (undo-tree-current buffer-undo-tree) - (undo-tree-node-previous (undo-tree-current buffer-undo-tree)) - (undo-tree-node-timestamp (undo-tree-current buffer-undo-tree)) - (current-time)) + (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 @@ -2523,29 +2753,34 @@ (set-marker pos nil))) ;; undo deactivates mark unless undoing-in-region - (setq deactivate-mark (not undo-in-region)) - ;; inform user if at branch point - (when (and (called-interactively-p 'interactive) - (> (undo-tree-num-branches) 1)) - (message "Undo branch point!")))) - - - -(defun undo-tree-redo (&optional arg preserve-undo) + (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. - -A non-nil PRESERVE-UNDO causes the existing undo record to be -preserved, rather than replacing it with the new one generated by -redoing." +changes within the current region." (interactive "*P") ;; throw error if undo is disabled in buffer (when (eq buffer-undo-list t) (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) @@ -2610,7 +2845,8 @@ (undo-list-byte-size (undo-tree-node-undo current)))) ;; update timestamp - (setf (undo-tree-node-timestamp current) (current-time)) + (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 @@ -2623,11 +2859,7 @@ (set-marker pos nil))) ;; redo deactivates the mark unless redoing-in-region - (setq deactivate-mark (not redo-in-region)) - ;; inform user if at branch point - (when (and (called-interactively-p 'interactive) - (> (undo-tree-num-branches) 1)) - (message "Undo branch point!")))) + (setq deactivate-mark (not redo-in-region)))) @@ -2664,9 +2896,13 @@ (message "Switched to branch %d" branch)) -(defun undo-tree-set (node) +(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) @@ -2686,10 +2922,10 @@ (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)) + (undo-tree-undo-1)) ;; descend tree until selected node (while (not (eq (undo-tree-current buffer-undo-tree) node)) - (undo-tree-redo)) + (undo-tree-redo-1)) n)) ; return intersection node @@ -2738,10 +2974,9 @@ -(defmacro undo-tree-history-save-file (file) - `(concat (file-name-directory ,file) - "." (file-name-nondirectory ,file) - ".~undo-tree")) +(defun undo-tree-make-history-save-file-name () + (concat (file-name-directory (buffer-file-name)) + "." (file-name-nondirectory (buffer-file-name)) ".~undo-tree~")) (defun undo-tree-save-history (&optional filename overwrite) @@ -2755,24 +2990,28 @@ without asking for confirmation." (interactive) (undo-list-transfer-to-tree) - (let ((buff (current-buffer)) - (tree (copy-undo-tree buffer-undo-tree))) - ;; get filename - (unless filename - (setq filename - (if buffer-file-name - (undo-tree-history-save-file 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))) - ;; discard undo-tree object pool before saving - (setf (undo-tree-object-pool tree) nil) - ;; print undo-tree to file - (with-temp-file filename - (prin1 (sha1 buff) (current-buffer)) - (terpri (current-buffer)) - (let ((print-circle t)) (prin1 tree (current-buffer))))))) + (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 (copy-undo-tree buffer-undo-tree))) + ;; get filename + (unless filename + (setq filename + (if buffer-file-name + (undo-tree-make-history-save-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))) + ;; discard undo-tree object pool before saving + (setf (undo-tree-object-pool tree) nil) + ;; print undo-tree to file + (with-temp-file filename + (prin1 (sha1 buff) (current-buffer)) + (terpri (current-buffer)) + (let ((print-circle t)) (prin1 tree (current-buffer)))))))) @@ -2790,7 +3029,7 @@ (unless filename (setq filename (if buffer-file-name - (undo-tree-history-save-file buffer-file-name) + (undo-tree-make-history-save-file-name) (expand-file-name (read-file-name "File to load from: ") nil)))) ;; attempt to read undo-tree from FILENAME @@ -2861,9 +3100,13 @@ (display-buffer-mark-dedicated 'soft)) (switch-to-buffer-other-window (get-buffer-create undo-tree-visualizer-buffer-name)) - (undo-tree-visualizer-mode) (setq undo-tree-visualizer-parent-buffer buff) (setq buffer-undo-tree undo-tree) + (setq undo-tree-visualizer-initial-node (undo-tree-current undo-tree)) + (setq undo-tree-visualizer-spacing + (undo-tree-visualizer-calculate-spacing)) + (when undo-tree-visualizer-diff (undo-tree-visualizer-show-diff)) + (undo-tree-visualizer-mode) (let ((inhibit-read-only t)) (undo-tree-draw-tree undo-tree)))) @@ -2887,8 +3130,10 @@ (max (/ (window-width) 2) (+ (undo-tree-node-char-lwidth (undo-tree-root undo-tree)) ;; add space for left part of left-most time-stamp - (if undo-tree-visualizer-timestamps 4 0) - 2))) ; left margin + (if undo-tree-visualizer-timestamps + (/ (- undo-tree-visualizer-spacing 4) 2) + 0) + 2))) ; left margin ;; draw undo-tree (let ((undo-tree-insert-face 'undo-tree-visualizer-default-face) (stack (list (undo-tree-root undo-tree))) @@ -2931,26 +3176,27 @@ (defun undo-tree-draw-node (node &optional current) ;; Draw symbol representing NODE in visualizer. (goto-char (undo-tree-node-marker node)) - (when undo-tree-visualizer-timestamps (backward-char 5)) + (when undo-tree-visualizer-timestamps + (backward-char (/ undo-tree-visualizer-spacing 2))) (let ((register (undo-tree-node-register node)) node-string) - (unless (and register (eq node (get-register register))) + (unless (and register + (eq node (undo-tree-register-data-node + (registerv-data (get-register register))))) (setq register nil)) ;; represent node by differentl symbols, depending on whether it's the ;; current node or is saved in a register (setq node-string (cond (undo-tree-visualizer-timestamps - (undo-tree-timestamp-to-string (undo-tree-node-timestamp node))) + (undo-tree-timestamp-to-string + (undo-tree-node-timestamp node) + undo-tree-visualizer-relative-timestamps + current register)) (current "x") (register (char-to-string register)) (t "o"))) - (when undo-tree-visualizer-timestamps - (setq node-string - (concat (if current "*" " ") node-string - (if register (concat "(" (char-to-string register) ")") - " ")))) (cond (current @@ -2971,11 +3217,11 @@ (undo-tree-insert node-string))) (t (undo-tree-insert node-string))) - (backward-char (if undo-tree-visualizer-timestamps 7 1)) + (backward-char (if undo-tree-visualizer-timestamps + (1+ (/ undo-tree-visualizer-spacing 2)) + 1)) (move-marker (undo-tree-node-marker node) (point)) - (put-text-property (- (point) (if undo-tree-visualizer-timestamps 3 0)) - (+ (point) (if undo-tree-visualizer-timestamps 5 1)) - 'undo-tree-node node))) + (put-text-property (point) (1+ (point)) 'undo-tree-node node))) (defun undo-tree-draw-subtree (node &optional active-branch) @@ -3168,10 +3414,48 @@ (insert (make-string (- arg n) ? ))))) -(defun undo-tree-timestamp-to-string (timestamp) - ;; Convert TIMESTAMP to hh:mm:ss string. - (let ((time (decode-time timestamp))) - (format "%02d:%02d:%02d" (nth 2 time) (nth 1 time) (nth 0 time)))) +(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) "]") + " ")))) @@ -3197,7 +3481,9 @@ (use-local-map undo-tree-visualizer-map) (setq truncate-lines t) (setq cursor-type nil) - (setq buffer-read-only t)) + (setq buffer-read-only t) + (setq undo-tree-visualizer-selected-node nil) + (when undo-tree-visualizer-diff (undo-tree-visualizer-update-diff))) @@ -3213,7 +3499,8 @@ (let ((undo-tree-inhibit-kill-visualizer t)) (undo-tree-undo arg)) (switch-to-buffer-other-window undo-tree-visualizer-buffer-name) (let ((inhibit-read-only t)) - (undo-tree-draw-node (undo-tree-current buffer-undo-tree) 'current)))) + (undo-tree-draw-node (undo-tree-current buffer-undo-tree) 'current)) + (when undo-tree-visualizer-diff (undo-tree-visualizer-update-diff)))) (defun undo-tree-visualize-redo (&optional arg) @@ -3229,7 +3516,8 @@ (switch-to-buffer-other-window undo-tree-visualizer-buffer-name) (goto-char (undo-tree-node-marker (undo-tree-current buffer-undo-tree))) (let ((inhibit-read-only t)) - (undo-tree-draw-node (undo-tree-current buffer-undo-tree) 'current)))) + (undo-tree-draw-node (undo-tree-current buffer-undo-tree) 'current)) + (when undo-tree-visualizer-diff (undo-tree-visualizer-update-diff)))) (defun undo-tree-visualize-switch-branch-right (arg) @@ -3275,12 +3563,25 @@ (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) - (if (setq window (get-buffer-window parent)) - (select-window window) - (switch-to-buffer parent))))) + ;; switch back to parent buffer + (unwind-protect + (if (setq window (get-buffer-window parent)) + (select-window window) + (switch-to-buffer parent)))))) + + +(defun undo-tree-visualizer-abort () + "Quit the undo-tree visualizer and return buffer to original state." + (interactive) + (let ((node undo-tree-visualizer-initial-node)) + (undo-tree-visualizer-quit) + (undo-tree-set node))) (defun undo-tree-visualizer-set (&optional pos) @@ -3291,11 +3592,12 @@ (let ((node (get-text-property pos 'undo-tree-node))) (when node ;; set parent buffer to state corresponding to node at POS - (set-buffer undo-tree-visualizer-parent-buffer) + (switch-to-buffer-other-window undo-tree-visualizer-parent-buffer) (let ((undo-tree-inhibit-kill-visualizer t)) (undo-tree-set node)) - (set-buffer undo-tree-visualizer-buffer-name) + (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))))) + (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) @@ -3308,12 +3610,8 @@ (defun undo-tree-visualizer-toggle-timestamps () "Toggle display of time-stamps." (interactive) - (setq undo-tree-visualizer-spacing - (if (setq undo-tree-visualizer-timestamps - (not undo-tree-visualizer-timestamps)) - ;; need sufficient space if displaying timestamps - (max 13 (default-value 'undo-tree-visualizer-spacing)) - (default-value 'undo-tree-visualizer-spacing))) + (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))) @@ -3339,61 +3637,165 @@ (setq major-mode 'undo-tree-visualizer-selection-mode) (setq mode-name "undo-tree-visualizer-selection-mode") (use-local-map undo-tree-visualizer-selection-map) - (setq cursor-type 'box)) + (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)))))) (defun undo-tree-visualizer-select-previous (&optional arg) "Move to previous node." (interactive "p") - (let ((node (get-text-property (point) 'undo-tree-node))) + (let ((node undo-tree-visualizer-selected-node)) (catch 'top (dotimes (i arg) (unless (undo-tree-node-previous node) (throw 'top t)) (setq node (undo-tree-node-previous node)))) - (goto-char (undo-tree-node-marker node)))) + (goto-char (undo-tree-node-marker node)) + (when (and undo-tree-visualizer-diff + (not (eq node undo-tree-visualizer-selected-node))) + (undo-tree-visualizer-update-diff node)) + (setq undo-tree-visualizer-selected-node node))) (defun undo-tree-visualizer-select-next (&optional arg) "Move to next node." (interactive "p") - (let ((node (get-text-property (point) 'undo-tree-node))) + (let ((node undo-tree-visualizer-selected-node)) (catch 'bottom (dotimes (i arg) (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))))) - (goto-char (undo-tree-node-marker node)))) + (goto-char (undo-tree-node-marker node)) + (when (and undo-tree-visualizer-diff + (not (eq node undo-tree-visualizer-selected-node))) + (undo-tree-visualizer-update-diff node)) + (setq undo-tree-visualizer-selected-node node))) (defun undo-tree-visualizer-select-right (&optional arg) "Move right to a sibling node." (interactive "p") - (let ((pos (point)) - (end (line-end-position)) - node) + (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 (not node) + (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 (if node (undo-tree-node-marker node) pos)))) + (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)) + (setq undo-tree-visualizer-selected-node node))) (defun undo-tree-visualizer-select-left (&optional arg) "Move left to a sibling node." (interactive "p") - (let ((pos (point)) - (beg (line-beginning-position)) - node) + (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 (not node) + (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 (if node (undo-tree-node-marker node) pos)))) + (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)) + (setq undo-tree-visualizer-selected-node node))) + + + +;;; ===================================================================== +;;; Visualizer diff display + +(defun undo-tree-visualizer-toggle-diff () + "Toggle diff display in undo-tree visualizer." + (interactive) + (if undo-tree-visualizer-diff + (undo-tree-visualizer-hide-diff) + (undo-tree-visualizer-show-diff))) + + +(defun undo-tree-visualizer-selection-toggle-diff () + "Toggle diff display in undo-tree visualizer selection mode." + (interactive) + (if undo-tree-visualizer-diff + (undo-tree-visualizer-hide-diff) + (let ((node (get-text-property (point) 'undo-tree-node))) + (when node (undo-tree-visualizer-show-diff node))))) + + +(defun undo-tree-visualizer-show-diff (&optional node) + ;; show visualizer diff display + (setq undo-tree-visualizer-diff t) + (let ((buff (with-current-buffer undo-tree-visualizer-parent-buffer + (undo-tree-diff node))) + (display-buffer-mark-dedicated 'soft) + win) + (setq win (split-window)) + (set-window-buffer win buff) + (shrink-window-if-larger-than-buffer win))) + + +(defun undo-tree-visualizer-hide-diff () + ;; hide visualizer diff display + (setq undo-tree-visualizer-diff nil) + (let ((win (get-buffer-window undo-tree-diff-buffer-name))) + (when win (with-selected-window win (kill-buffer-and-window))))) + + +(defun undo-tree-diff (&optional node) + ;; Create diff between current state and NODE (or previous 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 + (current-buffer) tmpfile nil 'noasync + (get-buffer-create undo-tree-diff-buffer-name))) + ;; delete process messages and useless headers from diff buffer + (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))))