zck
zck

Reputation: 2762

In emacs, how do I save without running save hooks?

I have various things set up in my 'before-save-hook. For example, I run 'delete-trailing-whitespace. This is what I want in almost all occasions.

But sometimes, I'm working on files that are shared with other people, and the file already has a bunch of trailing whitespace. If I save the file, I'll get a big diff that's pretty confusing, as my change is buried in dozens or hundreds of meaningless changes. Yes, everyone could just tell their diff tool to not show whitespace changes, but that's something that everyone has to do every time they look at the diff. I'd rather not even have the whitespace change.

Is there anything I can do to save the file without the whitespace changes, short of starting a new instance of Emacs with no init.el file, or with a modified init.el that doesn't have the hook?

Upvotes: 28

Views: 4972

Answers (6)

Olav Fosse
Olav Fosse

Reputation: 146

I wrote a command inspired by Nicholas Douma's solution.

(defun olav-save-buffer-as-is ()
  "Save file \"as is\", that is in read-only-mode."
  (interactive)
  (if buffer-read-only
      (save-buffer)
    (read-only-mode 1)
    (save-buffer)
    (read-only-mode 0)))

Upvotes: 5

Nicolas Douma
Nicolas Douma

Reputation: 311

Here is how I save without triggering delete-trailing-whitespace: C-x C-q C-x C-s C-x C-q: read-only, save, revert read-only

Upvotes: 31

Stefan
Stefan

Reputation: 28531

Here's another solution:

(defvar my-inhibit-dtw nil)
(defun my-delete-trailing-whitespace ()
  (unless my-inhibit-dtw (delete-trailing-whitespace)))
(add-hook 'before-save-hook 'my-delete-trailing-whitespace)

and then

(defun my-inhibit-dtw ()
  (interactive)
  (set (make-local-variable 'my-inhibit-dtw) t))

so you can M-x my-inhibit-dtw RET in the buffers where you don't want to trim whitespace.

Upvotes: 4

rafalcieslak
rafalcieslak

Reputation: 945

A simpler solution I came up with is that my fundamental-mode has no hooks installed, because I want it to be as plain as possible. Thus if I want to save a file without running hooks, I temporarily switch to fundamental-mode.

Upvotes: 16

sds
sds

Reputation: 60004

Based on a comment discussion with @Stefan, here are two possible (untested) solutions:

Use let:

(defun save-buffer-without-dtw ()
  (interactive)
  (let ((b (current-buffer)))   ; memorize the buffer
    (with-temp-buffer ; new temp buffer to bind the global value of before-save-hook
      (let ((before-save-hook (remove 'delete-trailing-whitespace before-save-hook))) 
        (with-current-buffer b  ; go back to the current buffer, before-save-hook is now buffer-local
          (let ((before-save-hook (remove 'delete-trailing-whitespace before-save-hook)))
            (save-buffer)))))))

Use unwind-protect:

(defun save-buffer-without-dtw ()
  (interactive)
  (let ((restore-global
         (memq 'delete-trailing-whitespace (default-value before-save-hook)))
        (restore-local
         (and (local-variable-p 'before-save-hook)
              (memq 'delete-trailing-whitespace before-save-hook))))
    (unwind-protect
         (progn
           (when restore-global
             (remove-hook 'before-save-hook 'delete-trailing-whitespace))
           (when restore-local
             (remove-hook 'before-save-hook 'delete-trailing-whitespace t))
           (save-buffer))
      (when restore-global
        (add-hook 'before-save-hook 'delete-trailing-whitespace))
      (when restore-local
        (add-hook 'before-save-hook 'delete-trailing-whitespace nil t)))))

The problem with the second solution is that the order of functions in the before-save-hook may change.

Upvotes: 5

zck
zck

Reputation: 2762

What we need to do is remove 'delete-trailing-whitespace from before-save-hook, save the buffer, then add it back.

This code will do that, but only remove and add it if it's there to begin with.

;; save the buffer, removing and readding the 'delete-trailing-whitespace function
;; to 'before-save-hook if it's there
(defun save-buffer-no-delete-trailing-whitespace ()
  (interactive)
  (let ((normally-should-delete-trailing-whitespace (memq 'delete-trailing-whitespace before-save-hook)))
    (when normally-should-delete-trailing-whitespace
      (remove-hook 'before-save-hook 'delete-trailing-whitespace))
    (save-buffer)
    (when normally-should-delete-trailing-whitespace
      (add-hook 'before-save-hook 'delete-trailing-whitespace))))
(global-set-key (kbd "C-c C-s") 'save-buffer-no-delete-trailing-whitespace)

It also binds the command to (kbd C-c C-s), for convenience.

Upvotes: 1

Related Questions