reddit / emacs
Save all buffers in a project.el project context
divinedominion · 2022-09-08 07:43
I whipped up this function to save all buffers _in the current project_, e.g. before executing a compile/test command:
(defun ct/project-save-all-buffers (&optional proj arg)
"Save all file-visiting buffers in PROJ without asking.
Falls back to `project-current' if PROJ is not specified."
(let* ((proj (or proj (project-current)))
(buffers (project-buffers (project-current))))
(dolist (buf buffers)
;; Act on base buffer of indirect buffers, if needed.
(with-current-buffer (or (buffer-base-buffer buf) buf)
(when (and (buffer-file-name buf) ; Ignore all non-file-visiting buffers.
(buffer-modified-p buf)) ; Ignore all unchanged buffers.
(let ((buffer-save-without-query t)) ; Save silently.
(save-buffer arg)))))))
The filter for file-visiting buffers and getting the base buffer for indirect buffers was added thanks to /u/whhone sharing a function in: https://www.reddit.com/r/emacs/comments/u09yf9/ignore_save_if_buffer_isnt_visiting_a_file/
Alternatives that are simpler: override asking to save before compiling:
(let ((compilation-ask-about-save nil))
(my compilation command here))
But that would save *all* modified buffers unconditionally. Similarly, you could call:
(save-some-buffers t) ; Save without asking
(my compilation command here)
What if you don't want to save changes to, say, some .org file that you edited in the meantime?
I prefer to be asked for unrelated files, so I added my function above and call it in this sequence:
(ct/project-save-all-buffers)
(my compilation command here)
Feedback welcome, and enjoy the function if you need it! Didn't find something like it for some reason.
--------------------------
Edit 2022-09-09:
Thanks to /u/akirakom I've checked out how magit uses `save-some-buffers` and passes a predicate function as the 2nd argument. That sounds like the best approach -- narrowing down the scope via the predicate, and using the existing `save-some-buffers` logic to not ask whether to save.
(defun ct/project-save-all-buffers (&optional arg proj)
"Save all file-visiting buffers in PROJ, without asking if ARG is non-nil.
Falls back to `project-current' if PROJ is not specified."
(let* ((proj (or proj (project-current)))
(buffers (project-buffers (project-current))))
(save-some-buffers
arg (lambda ()
"Return non-nil when the current buffer should be considered."
(let ((buffer (or (buffer-base-buffer (current-buffer))
(current-buffer))))
(and (member (current-buffer) buffers) ; Ignore non-project buffers
(buffer-file-name buffer) ; Ignore all non-file-visiting buffers .
(buffer-modified-p buffer) ; Ignore all unchanged buffers. Maybe redundant with save-some-buffers.
(file-writable-p (buffer-file-name buffer))))))))
eleven_cupfuls · 2022-09-08 17:07
This is great! I have a longstanding todo in my config for this. I will definitely borrow or adapt your function.
akirakom · 2022-09-08 18:54
Please have a look at magit-save-repository-buffers. It shows an example usage of save-some-buffers with an optional predicate.
divinedominion · 2022-09-09 07:33
Thanks for the pointer!
Here's the function for everyone:
(defun magit-save-repository-buffers (&optional arg)
"Save file-visiting buffers belonging to the current repository.
After any buffer where `buffer-save-without-query' is non-nil
is saved without asking, the user is asked about each modified
buffer which visits a file in the current repository. Optional
argument (the prefix) non-nil means save all with no questions."
(interactive "P")
(when-let ((topdir (magit-rev-parse-safe "--show-toplevel")))
(let ((remote (file-remote-p default-directory))
(save-some-buffers-action-alist
`((?Y (lambda (buffer)
(with-current-buffer buffer
(setq buffer-save-without-query t)
(save-buffer)))
"to save the current buffer and remember choice")
(?N (lambda (buffer)
(with-current-buffer buffer
(setq magit-inhibit-refresh-save t)))
"to skip the current buffer and remember choice")
,@save-some-buffers-action-alist)))
(save-some-buffers
arg (lambda ()
(and buffer-file-name
;; - Check whether refreshing is disabled.
(not magit-inhibit-refresh-save)
;; - Check whether the visited file is either on the
;; same remote as the repository, or both are on
;; the local system.
(equal (file-remote-p buffer-file-name) remote)
;; Delayed checks that are more expensive for remote
;; repositories, due to the required network access.
;; - Check whether the file is inside the repository.
(equal (magit-rev-parse-safe "--show-toplevel") topdir)
;; - Check whether the file is actually writable.
(file-writable-p buffer-file-name)))))))
divinedominion · 2022-09-09 08:11
Tried magit's approach and that looks quite good!
I've (non-idiomatically?) chosen to treat `buffer-file-name` as a function and passing it the current buffer explicitly instead of treating it as a variable. Feels less magical to me.
I do wonder, though:
**Is the** `project-buffers` **based filtering any good? Should I be using** `project-files` **and check for** `buffer-file-name` **instead?**
I picked the buffer-based approach so I don't have to worry about canonical/expanded file paths, but buffer containment also is a bit odd.
(defun ct/project-save-all-buffers (&optional arg proj)
"Save all file-visiting buffers in PROJ, without asking if ARG is non-nil.
Falls back to `project-current' if PROJ is not specified."
(let* ((proj (or proj (project-current)))
(buffers (project-buffers (project-current))))
(save-some-buffers
arg (lambda ()
"Return non-nil when the current buffer should be considered."
(let ((buffer (or (buffer-base-buffer (current-buffer))
(current-buffer))))
(and (member (current-buffer) buffers) ; Ignore non-project buffers
(buffer-file-name buffer) ; Ignore all non-file-visiting buffers.
(buffer-modified-p buffer) ; Ignore all unchanged buffers. Maybe redundant with save-some-buffers.
(file-writable-p (buffer-file-name buffer))))))))
akirakom · 2022-09-09 08:28
>Is the project-buffers based filtering any good? Should I be using project-files and check for buffer-file-name instead?
I would assert on `buffer-file-name` against `(project-root PR)` so you don't have to scan the buffer list twice. I also don't find `buffer-base-buffer` necessary, because you only have to save base buffers.
link0ff · 2022-09-09 16:23
Also it's possible to customize `save-some-buffers-default-predicate` to `save-some-buffers-root`.
eleven_cupfuls · 2022-09-10 01:38
The thing is that this does not achieve your original goal (also my goal), to save only the buffers in the project. `save-some-buffers` doesn't consult the predicate to decide what to _save_, but what to _query about_. And it saves everything it doesn't query about.
It's annoying, because `compile` has customization points in `compilation-save-buffers-predicate` and `compilation-ask-about-save` but `save-some-buffers` still saves everything else anyways.
divinedominion · 2022-09-10 06:41
Ah, maybe then I should be tinkering with the compilation settings first -- what are yours? -- because with my config, it looked the same: auto-save all the project stuff, ask about the rest. But that's because my compile settings still try to save everything first.
eleven_cupfuls · 2022-09-10 18:10
Oh, perhaps we actually have different goals then. What I would ultimately like is, when I compile from a buffer in a project:
- that project's buffers are saved without querying
- no _other_ buffers are saved, again without querying
For customization, I have `compilation-save-buffers-predicate` set to the function `ignore`, which accomplishes the first point, but all other buffers are saved too.
divinedominion · 2022-09-11 11:08
I wouldn't mind what you describe at all! That's the end goal, so to speak. But: That sounded more complicated, so I settled to solve a simpler problem at first.