summary refs log tree commit diff stats
path: root/lisp/user-save.el
blob: 674abacb0918afa128b3d02c58a1e93455d151ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
;;; user-save.el --- Do things when explicitly saving files -*- lexical-binding: t; -*-

;; Copyright (C) 2021--2022 Case Duckworth <acdw@acdw.net>
;; URL: ...
;; Version: 0.1.0
;; Package-Requires: ((emacs "24.3"))
;; Keywords: files

;;; Commentary:

;; Because `super-save-mode' automatically saves every time we move away from a
;; buffer, it tends to run a lot of `before-save-hook's that don't need to be
;; run that often.  For that reason, I'm writing a mode where C-x C-s saves
;; /and/ runs all the "real" before-save-hooks, so that super-save won't
;; automatically do things like format the buffer all the time.

;;; Code:

(require 'cl-lib)

(defgroup user-save nil
  "Group for `user-save-mode' customizations."
  :group 'files
  :prefix "user-save-")

(defcustom user-save-hook-into-kill-emacs nil
  "Add a hook to perform `user-save' to `kill-emacs-hook'.
This option is only useful is `user-save-mode' is active when
Emacs is killed."
  :type 'boolean)

(defcustom user-save-inhibit-modes '(special-mode)
  "List of modes to inhibit `user-save-mode' from activation in."
  :type '(repeat symbol))

(defcustom user-save-inhibit-predicates '(user-save-non-file-buffer-p)
  "List of predicates to inhibit `user-save-mode' from activation.
Each predicate will be called with no arguments, and if it
returns t, will inhibit `user-save-mode' from activating."
  :type '(repeat function))

(defcustom user-save-before-save-hook nil
  "Hook to run before the user, not Emacs, saves the buffer."
  :type 'hook)

(defcustom user-save-after-save-hook nil
  "Hook to run after the user, not Emacs, saves the buffer."
  :type 'hook)

(defvar user-save-mode-map (let ((map (make-sparse-keymap)))
                             (define-key map (kbd "C-x C-s") #'user-save-buffer)
                             (define-key map (kbd "C-x s") #'user-save-some-buffers)
                             map)
  "Keymap for `user-save-mode'.
This map shadows the default map for `save-buffer'.")

(defun user-save-run-hooks (which &rest _)
  "Run the hooks in one of the user-save-hooks.
If WHICH is `'before', run `user-save-before-save-hook';
if it's `after', run `user-save-after-save-hook'.
This does /not/ also save the buffer."
  (with-demoted-errors "User-save-hook error: %S"
    (run-hooks (intern (format "user-save-%s-save-hook" which)))))

(defun user-save-non-file-buffer-p (&optional buffer-or-name)
  "Return whether BUFFER-OR-NAME is a non-file buffer.
BUFFER-OR-NAME, if omitted, defaults to the current buffer."
  (with-current-buffer (or buffer-or-name (current-buffer))
    (not (buffer-file-name))))

(defun user-save-buffer (&optional arg)
  "Save current buffer in visited file if modified.
This function is precisely the same as `save-buffer', but with
one modification: it also runs functions in `user-save-hook'.
This means that if you have some functionality in Emacs to
automatically save buffers periodically, but have hooks you want
to automatically run when the buffer saves that are
computationally expensive or just aren't something you want to
run all the time, put them in `user-save-hook'.

ARG is passed directly to `save-buffer'."
  (interactive '(called-interactively))
  (message "User-Saving the buffer...")
  (user-save-run-hooks 'before)
  (save-buffer arg)
  (user-save-run-hooks 'after)
  (message "User-Saving the buffer...Done."))

(defun user-save-some-buffers (&optional pred)
  "Save some buffers as though the user saved them.
This function does not ask the user about each buffer, but PRED
is used in almost the same way as `save-some-buffers': if it's
nil or t, it will save all file-visiting modified buffers; if
it's a zero-argument function, that will be called to determine
whether the buffer needs to be saved."
  ;; This could maybe be much better.
  (interactive "P")
  (unless pred (setq pred save-some-buffers-default-predicate))
  (dolist (buf (buffer-list))
    (with-current-buffer buf
      (when (and (buffer-modified-p)
                 (buffer-file-name)
                 (or (null pred)
                     (if (functionp pred) (funcall pred) pred)))
        (user-save-buffer)))))

;;;###autoload
(define-minor-mode user-save-mode
  "Mode to enable an an extra user-save hook."
  :lighter " US"
  :keymap user-save-mode-map)

;;;###autoload
(defun user-save-mode-disable ()
  "Turn off `user-save-mode' in the current buffer."
  (user-save-mode -1))

;;;###autoload
(defun user-save-mode-in-some-buffers ()
  "Enable `user-save-mode', but only in some buffers.
The mode will not be enabled in buffers derived from modes in
`user-save-inhibit-modes', those for which
`user-save-inhibit-predicates' return t, or in the minibuffer."
  (unless (or (minibufferp)
              (cl-some #'derived-mode-p user-save-inhibit-modes)
              (run-hook-with-args-until-failure 'user-save-inhibit-predicates))
    (user-save-mode +1)))

;;;###autoload
(define-globalized-minor-mode user-save-global-mode user-save-mode user-save-mode-in-some-buffers
  (if user-save-global-mode
      (when user-save-hook-into-kill-emacs
        (add-hook 'kill-emacs-hook #'user-save-some-buffers))
    (remove-hook 'kill-emacs-hook #'user-save-some-buffers)))

(provide 'user-save)
;;; user-save.el ends here