vundo icon indicating copy to clipboard operation
vundo copied to clipboard

Live diffing (like undo-tree has)

Open Martinsos opened this issue 1 year ago • 4 comments

Hi, first of all thanks a lot for this package and effort that goes into it!

I have been using undo-tree for years but learned about undo-fu + vundo combo and decided to switch, and I love it, the only thing I have been missing is how undo-tree shows diff. What it does, if I am correct, is show diff between the node you are currently viewing and the node before it, therefore giving you an idea of the changes that this node introduces. I find this super helpful and it usually helps me figure out where I made specific change, so I can go back there, pick a piece of it maybe, then return, and similar. This is done "live" -> so as you move around the tree, the diff window is updated.

I know vundo has the option to show diff on demand, but that makes the workflow from above much harder, I would have to keep marking and unmarking nodes.

How hard would this be to add? Maybe I can help out? I am experienced programmer but not with elisp, however I could probably find my way around with enough time. Would this be a good match for vundo, both regarding DX and current architecture?

Also, do you have any suggestion on how I might hack this in the meantime? Maybe I could hook in into the move functions of vundo (with advice?) and try to automate marking, unmarking, and diffing to run on every move hm.

Thanks!

Martinsos avatar Oct 23 '24 09:10 Martinsos

Thanks for your kind words :)

Try this:

(defun vundo-live-diff-post-command ()
  "Post command hook function for live diffing."
  (when (and vundo-diff--marked-node
             (not (memq this-command
                        '(vundo-quit vundo-confirm))))
    (vundo-diff)))

(define-minor-mode vundo-live-diff-mode
  "Live diff when moving in vundo buffer."
  :lighter ""
  (if vundo-live-diff-mode
      (add-hook 'post-command-hook #'vundo-live-diff-post-command 0 t)
    (remove-hook 'post-command-hook #'vundo-live-diff-post-command t)))

(define-key vundo-mode-map (kbd "D") #'vundo-live-diff-mode)

casouri avatar Oct 24 '24 16:10 casouri

Thanks for this @casouri !

I went a step further and ended up with this:

;; Displays undo history as a tree and lets you move through it.
(use-package vundo
  :defer t
  :config
  (setq vundo-glyph-alist vundo-unicode-symbols)

  ;;;;;; Vundo Live Diff ;;;;;;
  ;; In vundo, you have to manually mark one node and call diff on another node to get their diff.
  ;; Here we extend vundo to have "live diff mode", that always shows diff between current node and its parent.
  ;; I turn it on by default. It can be toggled by pressing "f".
  (defun vundo-live-diff-post-command ()
    "Post command hook function for live diffing."
    (when (not (memq this-command '(vundo-quit vundo-confirm)))
      (progn
	(vundo-diff-mark (vundo-m-parent (vundo--current-node vundo--prev-mod-list)))
        (vundo-diff)
      )
    )
  )
  (define-minor-mode vundo-live-diff-mode
    "Shows live diff between the current node and its parent."
    :lighter nil
    (if vundo-live-diff-mode
      (add-hook 'post-command-hook #'vundo-live-diff-post-command 0 t)
      (remove-hook 'post-command-hook #'vundo-live-diff-post-command t)
    )
  )
  (evil-define-key 'normal vundo-mode-map (kbd "F") #'vundo-live-diff-mode)
  (add-hook 'vundo-mode-hook (lambda () (vundo-live-diff-mode 1)))
  ;;;;;/ Vundo Live Diff ;;;;;;
)

Now, diff opens immediately, and mark follows the current node so we always see the diff between it and its parent -> this is exactly the behaviour from node-tree that I wanted to replicate. And I can turn it off, of course, if I want.

Your code helped a lot, I never wrote minor mode and wouldn't think of it as a solution, and it also helped a lot the fact that the code in vundo(-diff).el is quite nicely written / documented.

Btw, a couple of weird errors I hit:

  1. First time I was testing it I was getting ; Error in post-command-hook (vundo-live-diff-post-command): (error "No possible route") error that would crash the minor mode. I was able to trigger it by moving over saved node, but not consistently, and later I wasn't able to replicate it at all, so maybe it was caused by my live evaling of parts of init.el, not sure.
  2. Later, I hit another situation where buffer content went bonkers, parts of text closer to start of the buffer started appearing at the middle of the buffer, all mixed. Here I am sure I didn't do any half evals, and I think I triggered it by moving really fast through the undo tree. Not sure if this is something already known, or I caused it by live-diff -> although I don't see how this code could have caused it. I also wasn't successful in replicating this again, but I haven't tried for long.

Martinsos avatar Oct 24 '24 20:10 Martinsos

As its author I'm biased of course, but I find vundo's diff more powerful. I can for example mark (m), then l to visit the last saved node (which may be dozens of nodes in the past), and d for the diff between them. Diff between adjacent nodes is pretty limiting.

That said, we could consider a toggle to turn on a live-diff-to-parent mode (v?).

jdtsmith avatar Jan 13 '25 22:01 jdtsmith

@jdtsmith I agree, it is more powerful! That said, I did get used to this specific workflow, where I go back through the undo tree till I get to the point that I want. And I am not always sure where I am going, which is it is important to me to understand what changes each node did, so I can say "aha, ok, yeah this I don't want, this neither, ok, ok, whoops I went too far, back, ok all right yes here, this is where I want to be". But I couldn't easily see what changes each node did while moving like that with vundo, while I could with undo-tree, so implementing this enables me to do this workflow in vundo also. So it is just a specific workflow that works for me, and I would guess likely for somebody else also. Myself, I have also defined a key for toggling it (although I mostly keep it on). I am on evil mode though so bindings I have are a bit different (I use "F" for toggling, as in "follow").

Martinsos avatar Jan 17 '25 11:01 Martinsos