diff --git a/NEWS.txt b/NEWS.txt
index e037879..dc0ddc5 100644
--- a/NEWS.txt
+++ b/NEWS.txt
@@ -1,3 +1,15 @@
+<2023-12-08 Fri>: Version 2.5.0
+
+vundo-diff introduced, providing on-demand diff functionality.  Diff's
+are evaluated between the current node and either its parent node, or,
+if any, a marked node.  New key commands:
+
+  (m)ark   - mark a node for diff
+  (u)nmark - unmark any marked node
+  (d)iff   - diff between current and marked or parent node
+
+The (d)ebug command has been moved to (D)ebug.
+
 <2022-04-23 Sat>: Version 2.0.0
 
 Breaking change:
diff --git a/README.txt b/README.txt
index 26291aa..4ff5a29 100644
--- a/README.txt
+++ b/README.txt
@@ -72,9 +72,8 @@ Comparing to undo-tree:
 
 Vundo doesn’t need to be turned on all the time nor replace the undo
 commands like undo-tree does. Vundo displays the tree horizontally,
-whereas undo-tree displays a tree vertically. Vundo doesn’t have many
-advanced features that undo-tree does (like showing diff), and most
-probably will not add those features in the future.
+whereas undo-tree displays a tree vertically.  Diff is provided
+on-demand between any nodes, not just the node immediately prior.
 
 Tests:
 
@@ -89,6 +88,9 @@ to run those tests interactively, or use the following batch command:
 
 Changelog (full changelog in NEWS.txt):
 
+<2023-12-08 Fri>: Version 2.5.0: vundo-diff introduced, supporting
+on-demand diff to parent or any marked node.
+
 <2022-04-04 Mon>: Version 1.0.0
 
 <2022-03-29 Tue>: vundo--mode and vundo--mode-map are now vundo-mode
diff --git a/vundo-diff.el b/vundo-diff.el
new file mode 100644
index 0000000..ea847e1
--- /dev/null
+++ b/vundo-diff.el
@@ -0,0 +1,177 @@
+;;; vundo-diff.el --- buffer diff for vundo      -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2023 Free Software Foundation, Inc.
+;;
+;; Author: JD Smith <jdtsmith@gmail.com>
+;; Maintainer: Yuan Fu <casouri@gmail.com>
+;; URL: https://github.com/casouri/vundo
+;; Version: 0.1
+;; Package-Requires: ((emacs "28.1"))
+;;
+;; This file is part of GNU Emacs.
+;;
+;; GNU Emacs 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.
+;;
+;; GNU Emacs 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 <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; vundo-diff provides simple on-demand diff between arbitray undo
+;; states in the vundo tree.
+
+;;; Code:
+(require 'vundo)
+(require 'diff)
+(require 'diff-mode)
+(eval-when-compile (require 'cl-lib))
+
+(defface vundo-diff-highlight
+  '((((background light)) .
+     (:inherit vundo-highlight :foreground "DodgerBlue4"))
+    (((background dark)) .
+     (:inherit vundo-highlight  :foreground "DodgerBlue1")))
+  "Face for nodes marked for diff in the undo tree.")
+
+(defvar-local vundo-diff--marked-node nil)
+(defvar-local vundo-diff--highlight-overlay nil
+  "Overlay used to highlight the selected node.")
+
+(defun vundo-diff--cleanup-diff-buffer (orig-name buf current from to)
+  "Update diff headers in BUF.
+Headers are updated to indicate the diff in the contents of
+buffer named ORIG-NAME, between nodes FROM and TO, and given the
+CURRENT node."
+  (let ((inhibit-read-only t)
+        (info (cl-loop for x in (list from to)
+                       for idx = (vundo-m-idx x)
+                       for ts = (vundo--node-timestamp vundo--prev-mod-list x)
+                       for stat = (if (eq x current) "Current"
+                                    (if vundo-diff--marked-node "Marked" "Parent"))
+                       collect
+                       (list (format "[%d]" idx)
+                             (format "<%s> [mod %d] (%s)" orig-name idx stat)
+                             (when (consp ts) (format-time-string "%F %r" ts))))))
+    (with-current-buffer buf
+      (vundo-diff-mode)
+      (goto-char (point-min))
+      (insert (concat (propertize "vundo-diff: " 'font-lock-face 'diff-header)
+                      (propertize  orig-name 'font-lock-face
+                                   '(diff-file-header diff-header))
+                      "\n"))
+      (let* ((change-files
+              (cl-loop for (name fullname ts) in info
+                       for pat in '("---" "+++")
+                       if (re-search-forward
+                           (rx-to-string `(and bol ,pat (+ space)
+                                               (group (group (+ (not ?\t)))
+                                                      (* any))
+                                               eol))
+                           nil t)
+                       collect (cons (match-string-no-properties 2) name)
+                       and do (replace-match
+                               (if ts (concat fullname "\t" ts) fullname)
+                               t t nil 1)))
+             (lim (point)))
+        (when (eq (length change-files) 2)
+          (goto-char (point-min))
+          (dolist (c change-files) ; change the file names in the diff
+            (when (search-forward (car c) lim t)
+              (replace-match (cdr c)))))))))
+
+;;;###autoload
+(defun vundo-diff-mark (&optional node)
+  "Mark NODE for vundo diff.
+NODE defaults to the current node."
+  (interactive)
+  (let* ((mod-list vundo--prev-mod-list)
+         (node (or node (vundo--current-node mod-list))))
+    (setq vundo-diff--marked-node node)
+    (unless vundo-diff--highlight-overlay
+      (setq vundo-diff--highlight-overlay
+            (make-overlay (1- (vundo-m-point node)) (vundo-m-point node)))
+      (overlay-put vundo-diff--highlight-overlay
+                   'display (vundo--translate "●"))
+      (overlay-put vundo-diff--highlight-overlay
+                   'face 'vundo-diff-highlight)
+      (overlay-put vundo-diff--highlight-overlay 'priority 2))
+    (move-overlay vundo-diff--highlight-overlay
+                  (1- (vundo-m-point node))
+                  (vundo-m-point node))))
+
+;;;###autoload
+(defun vundo-diff-unmark ()
+  "Unmark the node marked for vundo diff."
+  (interactive)
+  (when vundo-diff--marked-node
+    (setq vundo-diff--marked-node nil)
+    (when vundo-diff--highlight-overlay
+      (delete-overlay vundo-diff--highlight-overlay)
+      (setq vundo-diff--highlight-overlay nil))))
+
+;;;###autoload
+(defun vundo-diff ()
+  "Perform diff between marked and current buffer state.
+Displays in a separate diff buffer with name based on
+the original buffer name."
+  (interactive)
+  (let* ((orig vundo--orig-buffer)
+	 (oname (buffer-name orig))
+	 (current (vundo--current-node vundo--prev-mod-list))
+	 (marked (or vundo-diff--marked-node (vundo-m-parent current)))
+	 (swapped (> (vundo-m-idx marked) (vundo-m-idx current)))
+	 mrkbuf)
+    (if (or (not current) (not marked) (eq current marked))
+	(message "vundo diff not available.")
+      (setq mrkbuf (get-buffer-create
+		    (make-temp-name (concat oname "-vundo-diff-marked"))))
+      (unwind-protect
+	  (progn
+            (vundo--check-for-command
+	     (vundo--move-to-node current marked orig vundo--prev-mod-list)
+	     (with-current-buffer mrkbuf
+	       (insert-buffer-substring-no-properties orig))
+	     (vundo--refresh-buffer orig (current-buffer) 'incremental)
+	     (vundo--move-to-node marked current orig vundo--prev-mod-list)
+	     (vundo--trim-undo-list orig current vundo--prev-mod-list)
+	     (vundo--refresh-buffer orig (current-buffer) 'incremental))
+	    (let* ((a (if swapped current marked))
+	           (b (if swapped marked current))
+		   (abuf (if swapped orig mrkbuf))
+		   (bbuf (if swapped mrkbuf orig))
+		   (dbuf (diff-no-select
+			  abuf bbuf nil t
+			  (get-buffer-create
+			   (concat "*vundo-diff-" oname "*")))))
+	      (vundo-diff--cleanup-diff-buffer oname dbuf current a b)
+	      (display-buffer dbuf)))
+	(kill-buffer mrkbuf)))))
+
+(defconst vundo-diff-font-lock-keywords
+  `((,(rx bol (or "---" "+++") (* nonl) "[mod " (group (+ num)) ?\]
+          (+ ?\s) ?\((group (or "Parent" "Current")) ?\))
+     (1 'diff-index t)
+     (2 'vundo-highlight t))
+    (,(rx bol (or "---" "+++") (* nonl) "[mod " (group (+ num)) ?\]
+          (+ ?\s) ?\((group "Marked") ?\))
+     (1 'diff-index t)
+     (2 'vundo-diff-highlight t)))
+  "Additional font-lock keyword to fontify Parent/Current/Marked.")
+
+(define-derived-mode vundo-diff-mode diff-mode "Vundo Diff"
+  :syntax-table nil
+  :abbrev-table nil
+  (setcar font-lock-defaults
+          (append diff-font-lock-keywords vundo-diff-font-lock-keywords)))
+
+(provide 'vundo-diff)
+
+;;; vundo-diff.el ends here
diff --git a/vundo.el b/vundo.el
index b99b585..2e5bba4 100644
--- a/vundo.el
+++ b/vundo.el
@@ -39,6 +39,11 @@
 ;;
 ;;   a   to go back to the last branching point
 ;;   e   to go forward to the end/tip of the branch
+;;   l   to go to the last saved node
+;;
+;;   m   to mark the current node for diff
+;;   u   to unmark the marked node
+;;   d   to show a diff between the marked (or parent) and current nodes
 ;;
 ;;   q   to quit, you can also type C-g
 ;;
@@ -88,9 +93,7 @@
 ;;
 ;; Vundo doesn’t need to be turned on all the time nor replace the undo
 ;; commands like undo-tree does. Vundo displays the tree horizontally,
-;; whereas undo-tree displays a tree vertically. Vundo doesn’t have many
-;; advanced features that undo-tree does (like showing diff), and most
-;; probably will not add those features in the future.
+;; whereas undo-tree displays a tree vertically.
 
 ;;; Developer:
 ;;
@@ -513,6 +516,41 @@ If FROM non-nil, build from FORM-th modification in MOD-LIST."
                        (vundo--sort-mod (cons mod children)
                                         'reverse))))))))))
 
+;;; Timestamps
+;; buffer-undo-list contains "timestamp entries" like (t . TIMESTAMP)
+;; which capture the file modification time of the saved file which
+;; an undo changed.  During tree draw, we collect the last of these, and
+;; indicated nodes which had been saved specially.
+
+(defvar-local vundo--last-saved-idx nil
+  "The last node index with a timestamp seen.
+This is set by ‘vundo--draw-tree’ and ‘vundo-save’, and used by
+‘vundo-goto-last-saved’ and ‘vundo--highlight-last-saved-node’.")
+
+(defvar-local vundo--orig-buffer nil
+  "Vundo buffer displays the undo tree for this buffer.")
+
+(defun vundo--mod-timestamp (mod-list idx)
+  "Return a timestamp if the mod in MOD-LIST at IDX has a timestamp."
+  ;; If the next mod’s timestamp is non-nil, this mod/node
+  ;; represents a saved state.
+  (let* ((next-mod-idx (1+ idx))
+         (next-mod (when (< next-mod-idx (length mod-list))
+                     (aref mod-list next-mod-idx))))
+    (and next-mod (vundo-m-timestamp next-mod))))
+
+(defun vundo--node-timestamp (mod-list node)
+  "Return a timestamp from MOD-LIST for NODE, if any.
+In addition to undo-based timestamps, this includes the modtime of the
+current buffer (if unmodified)."
+  (let* ((idx (vundo-m-idx node))
+	 (current (vundo--current-node mod-list)))
+    (or (vundo--mod-timestamp mod-list idx)
+	(and (eq node current) (eq idx vundo--last-saved-idx)
+             (with-current-buffer vundo--orig-buffer
+	       (and (buffer-file-name)
+                    (not (buffer-modified-p))
+                    (visited-file-modtime)))))))
 ;;; Draw tree
 
 (defun vundo--put-node-at-point (node)
@@ -553,15 +591,6 @@ Translate according to `vundo-glyph-alist'."
                   vundo-glyph-alist)))
               text 'string))
 
-(defun vundo--mod-timestamp (mod-list idx)
-  "Return a timestamp if the mod in MOD-LIST at IDX has a timestramp."
-  ;; If the next mod’s timestamp is non-nil, this mod/node
-  ;; represents a saved state.
-  (let* ((next-mod-idx (1+ idx))
-         (next-mod (when (< next-mod-idx (length mod-list))
-                     (aref mod-list next-mod-idx))))
-    (and next-mod (vundo-m-timestamp next-mod))))
-
 (defvar vundo--last-saved-idx)
 
 (defun vundo--draw-tree (mod-list orig-buffer-modified)
@@ -584,11 +613,11 @@ corresponding to the index of the last saved node."
              (only-child-p (and parent (eq (length siblings) 1)))
              (node-last-child-p (and parent (eq node (car (last siblings)))))
              (node-idx (vundo-m-idx node))
-             (saved-p (and vundo-highlight-saved-nodes
-                           (vundo--mod-timestamp mod-list node-idx)))
+             (mod-ts (vundo--mod-timestamp mod-list node-idx))
+             (saved-p (and vundo-highlight-saved-nodes mod-ts))
              (node-face (if saved-p 'vundo-saved 'vundo-node))
              (stem-face (if only-child-p 'vundo-stem 'vundo-branch-stem)))
-        (when (and saved-p (> node-idx last-saved-idx))
+        (when (and mod-ts (> node-idx last-saved-idx))
           (setq last-saved-idx node-idx))
         ;; Go to parent.
         (if parent (goto-char (vundo-m-point parent)))
@@ -647,8 +676,8 @@ corresponding to the index of the last saved node."
         (setq node-queue (append children node-queue))))
 
     ;; If the associated buffer is unmodified, the last node must be
-    ;; the last saved nodel even though it doesn’t have a next node
-    ;; with a timestamp to indicate that.
+    ;; the last saved node even though it doesn’t (yet) have a next
+    ;; node with a timestamp to indicate that.
     (setq vundo--last-saved-idx
           (if orig-buffer-modified
               (if (> last-saved-idx 0) last-saved-idx nil)
@@ -673,6 +702,9 @@ WINDOW is the window that was/is displaying the vundo buffer."
       (with-selected-window window
         (kill-buffer-and-window))))
 
+(declare-function vundo-diff "vundo-diff")
+(declare-function vundo-diff-mark "vundo-diff")
+(declare-function vundo-diff-unmark "vundo-diff")
 (defvar vundo-mode-map
   (let ((map (make-sparse-keymap)))
     (define-key map (kbd "f") #'vundo-forward)
@@ -689,8 +721,11 @@ WINDOW is the window that was/is displaying the vundo buffer."
     (define-key map (kbd "q") #'vundo-quit)
     (define-key map (kbd "C-g") #'vundo-quit)
     (define-key map (kbd "RET") #'vundo-confirm)
+    (define-key map (kbd "m") #'vundo-diff-mark)
+    (define-key map (kbd "u") #'vundo-diff-unmark)
+    (define-key map (kbd "d") #'vundo-diff)
     (define-key map (kbd "i") #'vundo--inspect)
-    (define-key map (kbd "d") #'vundo--debug)
+    (define-key map (kbd "D") #'vundo--debug)
 
     (define-key map [remap save-buffer] #'vundo-save)
     map)
@@ -716,18 +751,12 @@ WINDOW is the window that was/is displaying the vundo buffer."
   "Modification hash table generated by `vundo--update-mapping'.")
 (defvar-local vundo--prev-undo-list nil
   "Original buffer's `buffer-undo-list'.")
-(defvar-local vundo--orig-buffer nil
-  "Vundo buffer displays the undo tree for this buffer.")
 (defvar-local vundo--message nil
   "If non-nil, print information when moving between nodes.")
 (defvar-local vundo--roll-back-to-this nil
   "Vundo will roll back to this node.")
 (defvar-local vundo--highlight-overlay nil
   "Overlay used to highlight the selected node.")
-(defvar-local vundo--last-saved-idx nil
-  "The last node index with a timestamp seen.
-This is set by ‘vundo--draw-tree’ and ‘vundo-save’, and used by
-‘vundo-goto-last-saved’ and ‘vundo--highlight-last-saved-node’.")
 (defvar-local vundo--highlight-last-saved-overlay nil
   "Overlay used to highlight the last saved node.")
 
@@ -1269,9 +1298,9 @@ Accepts the same interactive arfument ARG as ‘save-buffer’."
   (vundo--check-for-command
    (with-current-buffer vundo--orig-buffer
      (save-buffer arg)))
-  (when vundo-highlight-saved-nodes
-    (let* ((cur-node (vundo--current-node vundo--prev-mod-list)))
-      (setq vundo--last-saved-idx (vundo-m-idx cur-node))
+  (let* ((cur-node (vundo--current-node vundo--prev-mod-list)))
+    (setq vundo--last-saved-idx (vundo-m-idx cur-node))
+    (when vundo-highlight-saved-nodes
       (vundo--highlight-last-saved-node cur-node))))
 
 ;;; Debug
@@ -1295,9 +1324,7 @@ TYPE is the type of buffer you want."
              (mapcar #'vundo-m-idx (vundo--eqv-list-of node))
              (and (vundo-m-children node)
                   (mapcar #'vundo-m-idx (vundo-m-children node)))
-             (if-let* ((vundo-highlight-saved-nodes)
-                       (ts (vundo--mod-timestamp vundo--prev-mod-list
-                                                 (vundo-m-idx node)))
+             (if-let* ((ts (vundo--node-timestamp vundo--prev-mod-list node))
                        ((consp ts)))
                  (format " Saved: %s" (format-time-string "%F %r" ts))
                ""))))