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.