-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathvs-revbuf.el
228 lines (189 loc) · 8.09 KB
/
vs-revbuf.el
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
;;; vs-revbuf.el --- Revert buffers like Visual Studio -*- lexical-binding: t; -*-
;; Copyright (C) 2022-2025 Shen, Jen-Chieh
;; Created date 2022-03-08 19:54:08
;; Author: Shen, Jen-Chieh <[email protected]>
;; URL: https://github.com/emacs-vs/vs-revbuf
;; Version: 0.1.1
;; Package-Requires: ((emacs "27.1") (fextern "0.1.0"))
;; Keywords: convenience revert vs
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; Revert buffers like Visual Studio.
;;
;;; Code:
(require 'cl-lib)
(require 'subr-x)
(require 'fextern)
(defgroup vs-revbuf nil
"Revert buffers like Visual Studio."
:prefix "vs-revbuf-"
:group 'tool
:link '(url-link :tag "Repository" "https://github.com/emacs-vs/vs-revbuf"))
(defcustom vs-revbuf-ask-unsaved-changes-only nil
"Ask only when there are unsaved changes."
:type 'boolean
:group 'vs-revbuf)
(defcustom vs-revbuf-on-identical nil
"Revert buffers even buffer and file (on dist) have the same content."
:type 'boolean
:group 'vs-revbuf)
(defconst vs-revbuf--msg-edit-extern "
The file has been changed externally, and has no unsaved changes inside this editor.
Do you want to reload it? "
"Message when only edited externally.")
(defconst vs-revbuf--msg-edit-extern-and-unsaved "
The file has unsaved changes inside this editor and has been changed externally.
Do you want to reload it and lose the changes made in this source editor? "
"Message when edited externally and there are unsaved changes.")
(defconst vs-revbuf--msg-edit-moved "
The file has been moved (not found) externally, and has no unsaved changes inside this editor.
Do you want to kill it? "
"Message when buffer is modify, but file has moved externally.")
(defconst vs-revbuf--msg-edit-moved-and-unsaved "
The file has unsaved changes inside this editor and has been moved (not found) externally.
Do you want to kill it and lose the changes made in this source editor? "
"Message when buffer is modify, but file has moved externally.")
(defvar vs-revbuf--interactive-p nil
"Internal use only.")
;;
;; (@* "Externals" )
;;
(defvar flycheck-mode)
(defvar lsp-inhibit-lsp-hooks)
(defvar page-break-lines-mode)
(declare-function flycheck-mode "ext:flycheck.el")
(declare-function line-reminder-clear-reminder-lines-sign "ext:line-reminder.el")
(declare-function page-break-lines-mode "ext:page-break-lines.el")
;;
;; (@* "Util" )
;;
(defun vs-revbuf--invalid-buffer-p (&optional buffer)
"Return non-nil if BUFFER does't exist on disk but has a valid file path.
This occurs when file was opened but has moved to somewhere else externally."
(when-let ((bfn (buffer-file-name buffer))) (not (file-exists-p bfn))))
(defun vs-revbuf--invalid-buffer-list ()
"Return a list of invalid buffers."
(cl-remove-if-not #'vs-revbuf--invalid-buffer-p (buffer-list)))
(defun vs-revbuf--kill-buffer-no-confirm ()
"Kill modified/unmodified buffer without confirming."
(set-buffer-modified-p nil)
(let (kill-buffer-query-functions) (kill-this-buffer)))
;;
;; (@* "Core" )
;;
(defun vs-revbuf--buffer-identical ()
"Return t if the buffer is identical on the disk."
(let ((buffer-md5 (md5 (buffer-string)))
(file-md5 (md5 (fextern--file-content buffer-file-name))))
(string= buffer-md5 file-md5)))
;;;###autoload
(defun vs-revbuf-no-confirm ()
"Revert buffer without confirmation."
(interactive)
;; Record all the enabled mode that you want to remain enabled after
;; revert the file.
(let ((was-readonly buffer-read-only)
(was-flycheck (and (featurep 'flycheck) flycheck-mode))
(was-page-lines (and (featurep 'page-break-lines) page-break-lines-mode)))
;; Revert it!
(let ((lsp-inhibit-lsp-hooks t))
(ignore-errors (revert-buffer :ignore-auto :noconfirm :preserve-modes)))
(fextern-update-buffer-save-string)
(when (and (featurep 'line-reminder)
(or vs-revbuf--interactive-p
(called-interactively-p 'interactive)))
(line-reminder-clear-reminder-lines-sign))
;; Revert all the enabled modes
(when was-readonly (read-only-mode 1))
(when was-flycheck (flycheck-mode 1))
(when was-page-lines (page-break-lines-mode 1))))
(defun vs-revbuf--all-invalid-buffers ()
"Revert all invalid buffers."
(dolist (buf (vs-revbuf--invalid-buffer-list))
(with-current-buffer buf
(unless fextern-buffer-newly-created ; Execlude newly created buffer
;; If we hit here, the file has been moved externally...
(if (buffer-modified-p)
;; There is unsaved changes!
(when (yes-or-no-p (concat buffer-file-name "\n" vs-revbuf--msg-edit-moved-and-unsaved))
(vs-revbuf--kill-buffer-no-confirm))
;; No unsaved changes
(when (or vs-revbuf-ask-unsaved-changes-only
(yes-or-no-p (concat buffer-file-name "\n" vs-revbuf--msg-edit-moved)))
(vs-revbuf--kill-buffer-no-confirm)))))))
(defun vs-revbuf--all-valid-buffers ()
"Revert all valid buffers."
(dolist (buf (fextern--valid-buffer-list))
(with-current-buffer buf
(when (and
(or (not (buffer-modified-p)) vs-revbuf--interactive-p)
;; Handle identical condition
(or vs-revbuf-on-identical
(not (vs-revbuf--buffer-identical))))
(vs-revbuf-no-confirm)))))
(defun vs-revbuf-ask-all (bufs &optional index)
"Ask to revert all buffers decided by ANSWER.
This is called when only buffer changes externally and there are modification
still in this editor.
Optional argument INDEX is used to loop through BUFS."
(when-let*
((index (or index 0)) (buf (nth index bufs))
(path (buffer-file-name buf)))
(let* ((modified (buffer-modified-p buf))
(prompt (concat path "\n"
(if modified vs-revbuf--msg-edit-extern-and-unsaved
vs-revbuf--msg-edit-extern)))
(answer (if (and vs-revbuf-ask-unsaved-changes-only (not modified)) "Yes"
(completing-read prompt '("Yes" "Yes to All" "No" "No to All")
nil t nil nil "Yes"))))
(cl-incf index)
(pcase answer
("Yes"
(with-current-buffer buf (vs-revbuf-no-confirm))
(vs-revbuf-ask-all bufs index))
("Yes to All"
(vs-revbuf--all-valid-buffers)
(vs-revbuf--all-invalid-buffers))
("No" (vs-revbuf-ask-all bufs index))
("No to All"))))) ; Does nothing, exit
;;;###autoload
(defun vs-revbuf-all ()
"Refresh all open file buffers without confirmation."
(interactive)
(if-let ((bufs (fextern-buffers-edit-externally))
(vs-revbuf--interactive-p t))
(vs-revbuf-ask-all bufs)
(let ((vs-revbuf--interactive-p (called-interactively-p 'interactive)))
(vs-revbuf--all-valid-buffers)
(vs-revbuf--all-invalid-buffers))))
(defun vs-revbuf--focus-in (&rest _)
"Hook when focus in."
(when (and (frame-focus-state) (null (active-minibuffer-window)))
(vs-revbuf-all)))
(defun vs-revbuf-mode--enable ()
"Enable function `vs-revbuf-mode'."
(add-function :after after-focus-change-function #'vs-revbuf--focus-in
'((depth . 95))))
(defun vs-revbuf-mode--disable ()
"Disable function `vs-revbuf-mode'."
(remove-function after-focus-change-function #'vs-revbuf--focus-in))
;;;###autoload
(define-minor-mode vs-revbuf-mode
"Minor mode `vs-revbuf-mode'."
:global t
:require 'vs-revbuf-mode
:group 'vs-revbuf
(if vs-revbuf-mode (vs-revbuf-mode--enable) (vs-revbuf-mode--disable)))
(provide 'vs-revbuf)
;;; vs-revbuf.el ends here