12

Q: How do I insert/modify text in a buffer without undo noticing?

Here's the use case. I have a comment block at the start of every file that, among other things, updates a timestamp for the most recent change to a file. I'd like to be able to modify that timestamp without the undo facilities noticing it.

The reason I want to short-circuit undo here is due to the following edge case, which comes up when editing/compiling LaTeX documents (and probably others, but this is the one that drives me nuts most frequently):

  1. Make a small change to the file to see how it will affect the compiled doc
  2. Save the file (which updates the timestamp)
  3. Run latex on the file
  4. Decide that the change is a bad one
  5. undo the changes

The problem at step (5) (undo) is that it does not undo the change made in step (1), but rather undoes the timestamp updating in step (2). That wouldn't bother me (I could just undo again) except that it also moves point all the way to the timestamp at the top of the file, which is almost always many, many lines away from the actual substantive change. It's very jarring and completely breaks my concentration.

I'm pitching the question with respect to a file I'm visiting, but it's more generally about modifying buffers.

So: how can I prevent undo from noticing a specific modification to a buffer?

Dan
  • 32,584
  • 6
  • 98
  • 168
  • 1
    This is not related to your undo question, but it might help with the "undo moves the point" issue: http://stackoverflow.com/a/20510025/1279369 – nanny Dec 08 '14 at 14:00
  • 2
    Wouldn't it be best if this date-stamp change were appended to the most recent item in undo history? That way `undo` would undo both. – Malabarba Dec 08 '14 at 14:30
  • I think what you really want is `atomic-change-group`. –  Dec 09 '14 at 20:21
  • @john: How would that help? The two changes to group are triggered by separate commands: a “small change” (e.g. an insertion) and the updating of the time stamp done while saving. – Gilles 'SO- stop being evil' Dec 09 '14 at 22:31
  • Note that for some reason none of these methods in existing answers worked for me, however this answer defines an `with-undo-collapse` macro that was very useful: http://emacs.stackexchange.com/a/7560/2418 – ideasman42 Nov 26 '16 at 05:18

3 Answers3

6

@stsquad's answer got me on the right path. Basically, the steps are:

  1. save buffer-undo-list
  2. disable undo
  3. do your thing
  4. re-enable undo and restore the buffer-undo-list

So here's a sketch of such a function:

(defun disable-undo-one-off ()
  (let ((undo buffer-undo-list))        ; save the undo list
    (buffer-disable-undo)               ; disable undo
    (do-some-stuff)                     ; do your thing
    (buffer-enable-undo)                ; re-enable undo
    (setq buffer-undo-list undo)))      ; restore the undo list

Edit: actually, it turns out that a simpler solution still is simply to let-bind buffer-undo-list so that changes to the list in the body of the let get clobbered when the original list is restored:

(defun disable-undo-one-off ()
  (let (buffer-undo-list)
    (do-some-stuff)))

The main limitation with this is that it seems to work for modifications that do not change the number of characters in the buffer (eg, changing "kittens" to "puppies", but not "cats"), because otherwise the rest of the undo list is now referring to the wrong points. Hence, this is only a partial solution.

Dan
  • 32,584
  • 6
  • 98
  • 168
  • You may want to wrap that in an unwind protect in-case you break out of the activity midway through your thing. I didn't realise you were doing this all in a function which makes keeping track of stuff easier. – stsquad Dec 08 '14 at 15:06
  • Can you also add in the solution for the time-stamp update to not update the undo history (and point history, if possible)? – Kaushal Modi Dec 08 '14 at 15:07
  • @stsquad: good idea, will try to do so later. Thanks again for the tip-off on the functions and list in question. – Dan Dec 08 '14 at 15:17
  • @kaushalmodi: clarifying question: meaning you want to see the actual timestamp update function? – Dan Dec 08 '14 at 15:19
  • @Dan Would like to see how to modify the timestamp update function to not update the undo or point history. – Kaushal Modi Dec 08 '14 at 15:25
5

An undo operation combines several elements from the undo list. A nil entry in the list marks the boundary between two change groups. By removing the nil at the start of the list which is automatically inserted by the toplevel loop¹, you can group the timestamp update with the last buffer change, which technically doesn't match your request but practically should solve the problem in your scenario.

(defun time-stamp-group-undo ()
  (if (eq nil (car buffer-undo-list))
      (setq buffer-undo-list (cdr buffer-undo-list)))
  (time-stamp))

(Warning: untested, the undo list might require more massaging.)

You can also play with the undo list in a different way, by modifying the position entry (an integer) for the timestamp update.

¹ self-insert-command does a similar removal to group insertions.

2

The problem with what you suggest is that the undo-tree is a list of deltas to get from where you are to where you want to be. While it is perfectly possible to disable undo tracking on a buffer I'm not sure what the effect of not actively recording changes would be. I currently have a toggle to turn undo on/off on a particular buffer as it doesn't make sense to have undo information on a growing log or refreshing page. However it nixes the changes on toggle:

(defun my-toggle-buffer-undo ()
  "Toggle undo tracking in current buffer."
  (interactive)
  (with-current-buffer (current-buffer)
    (if (eq buffer-undo-list t)
        (setq buffer-undo-list nil)
      (buffer-disable-undo (current-buffer)))))

(define-key my-toggle-map "u" 'my-toggle-buffer-undo)

buffer-disable-undo actually just sets buffer-undo-list to nil. Maybe if you saved the state of buffer-undo-list on you toggle you could restore it afterwards?

stsquad
  • 4,626
  • 28
  • 45