11

Whenever I call fill-paragraph, the buffer is always marked as modified, even if the command had no effect (i.e. if the paragraph was already filled). It also creates an empty undoable action (easily detected with undo-tree-mode). Other commands which have the potential to make changes, such as the indentation commands, don't mark the buffer as modified or create an undoable action if nothing was changed. Is there any way to make fill-paragraph mark the buffer modified and create an undoable action only if it actually changed something?

nicael
  • 1,598
  • 2
  • 13
  • 20
Lily Chung
  • 375
  • 1
  • 8
  • I don't think this is correct--`M-q` doesn't mark the buffer changed by default, at least from my testing. What mode are you using? I'd guess the mode is overwriting `fill-paragraph` in some way. – shosti Sep 24 '14 at 16:51
  • @shosti I'm using Fundamental mode. The paragraph has to be more than one line long (when properly filled). – Lily Chung Sep 24 '14 at 17:33
  • Ah OK I see it now. – shosti Sep 24 '14 at 17:34

3 Answers3

10

The problem is that fill-paragraph (or rather, fill-region-as-paragraph) will remove and re-insert newlines while it breaks down your paragraph. It won't modify the buffer if there is only one line. The no-op in the undo list you witness is just fill-paragraph removing and re-inserting newlines.

It is non-trivial to avoid this. The following is a pretty bad hack, and highly inefficient for large buffers, but maybe it works for you. The command mimics fill-paragraph (M-q) with the identical behavior, except it stores the contents of the buffer before calling it, and afterwards, if the contents stayed the same, it will restore the modification state and undo list from before the change. To do this, it needs a copy (two, actually) of the buffer contents, so really, this is quite inefficient. :-)

(defun my/fill-paragraph (&optional justify region)
  (interactive (progn
                 (barf-if-buffer-read-only)
                 (list (if current-prefix-arg 'full) t)))
  (let ((old-text (buffer-string))
        (old-modified (buffer-modified-p))
        (old-undo-list buffer-undo-list))
    (fill-paragraph justify region)
    (when (equal old-text (buffer-string))
      (setq buffer-undo-list old-undo-list)
      (set-buffer-modified-p old-modified))))

You can bind that to M-q.

Jorgen Schäfer
  • 3,899
  • 2
  • 17
  • 19
  • 1
    Yes, this has long been a pain. ;-) I wonder (I don't recall) whether a fix for this has been requested before. Seems like it would have been. – Drew Sep 24 '14 at 17:03
  • Hmmm. I wonder if there's a better solution which doesn't have to check the entire buffer- maybe it could check only the selected paragraph somehow? – Lily Chung Sep 26 '14 at 00:44
  • `fill-paragraph` does some distinction between various cases, i.e. behaves differently depending on an active region, existing fill-paragraph functions, etc. You'd have to replicate that behavior to figure out which parts of the buffer are actually going to be changed. Possible, but tricky. :-) – Jorgen Schäfer Sep 26 '14 at 08:18
  • @Drew There was a long discussion about it on the mailing list last year: [bug#13949: 24.3.50; 'fill-paragraph' should not always put the buffer as modified](https://lists.gnu.org/archive/html/bug-gnu-emacs/2013-03/msg00466.html) – dkim Oct 08 '14 at 02:18
  • @dkim: Yes, I remember now. And nothing ever came of it... – Drew Oct 08 '14 at 02:26
2

Note that this is fixed for newer Emacsen (v.26 upwards).

clemera
  • 3,401
  • 13
  • 40
1

Late answer, but here's a simple version that doesn't modify the buffer if the text doesn't change.

(defun my-fill-paragraph (&optional justify region)
  "Fill paragraph, but don't modify the buffer if filling doesn't
change the text.  See `fill-paragraph' for details."
  (interactive (progn
                 (barf-if-buffer-read-only)
                 (list (if current-prefix-arg 'full) t)))
  (if (buffer-modified-p)
      ;; if modified: use standard fill-paragraph
      (fill-paragraph justify region)
    ;; if unmodified: get a candidate filled version
    (save-excursion
      (let* ((col fill-column)
             (beg (progn (forward-paragraph -1)
                         (skip-syntax-forward " >")
                         (point)))
             (end (progn (forward-paragraph 1)
                         (skip-syntax-backward " >")
                         (point)))
             (old (buffer-substring-no-properties beg end))
             (new (with-temp-buffer
                    (setq fill-column col)
                    (insert old)
                    (fill-paragraph justify region)
                    (buffer-string))))
        ;; don't modify unless the old and new versions differ
        (unless (string-equal old new)
          (delete-region beg end)
          (insert new))))))

It adapts some of the ideas in @JorgenSchäfer's answer, but works only with the current paragraph, and only in a simple, whitespace-separated way (see comments on @JorgenSchäfer's answer about complications under the hood).

This is about the only use case that's relevant for my own purposes (ie, interactive use with "normal" prose, no active region), so I'm posting it in case anyone wants to use it or improve it for more complicated use cases.

Dan
  • 32,584
  • 6
  • 98
  • 168