-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathframes-only-mode.el
More file actions
320 lines (241 loc) · 12 KB
/
Copy pathframes-only-mode.el
File metadata and controls
320 lines (241 loc) · 12 KB
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
;;; frames-only-mode.el --- Use frames instead of Emacs windows -*- lexical-binding: t; -*-
;; Copyright (C) 2015 Free Software Foundation, Inc.
;; Author: David Shepherd <davidshepherd7@gmail.com>
;; Version: 1.0.0
;; Package-Requires: ((emacs "28.1"))
;; Keywords: frames, windows
;; URL: https://github.com/davidshepherd7/frames-only-mode
;;; Commentary:
;;; Code:
;;; Options:
(defgroup frames-only '()
"Use frames instead of emacs windows."
:group 'frames)
(defcustom frames-only-mode-kill-frame-when-buffer-killed-buffer-list
'("*RefTeX Select*" "*Help*" "*Popup Help*" "*Completions*")
"Buffers for which the containing frame should be killed when the
buffer is killed.
Each entry in the list is either a string representing a buffer
name or a pair like `(regexp . \"\\\\*foo.*\\\\.bz\\\\*\")' representing a regexp
which matches buffer names.
"
:type '(repeat (choice
string
(cons (const regexp) string)))
:group 'frames-only)
(defcustom frames-only-mode-use-windows-for-completion t
"Use Emacs windows for display of completions.
This is useful because a new completion frame would steal
window manager focus.
Completion windows are always split horizontally (helm style).
To disable completion popups entirely use the variable
`completion-auto-help' for default Emacs completion or
`ido-completion-buffer' for ido-based completion."
:type 'boolean
:group 'frames-only)
(defcustom frames-only-mode-use-window-functions
(list #'calendar #'report-emacs-bug 'checkdoc-show-diagnostics 'checkdoc 'org-compile-file 'corfu-popupinfo--show)
"List of functions inside which new emacs windows should be
created instead of frames.
\(i.e. pop-up-frames is let bound to nil, the default value)."
;; Type is symbol, not function, because we sometimes want to add functions to
;; this list without requiring the library.
:type '(repeat symbol)
:group 'frames-only)
(defcustom frames-only-mode-configuration-variables
(list
;; Make new frames instead of new windows, the main setting
(list 'pop-up-frames 'graphic-only)
;; Focus follows mouse (for emacs windows) off to prevent crazy things
;; happening when I click on e.g. compilation error links. Would do nothing
;; interesting anyway if everything is working because there are no windows
;; within frames.
(list 'mouse-autoselect-window nil)
(list 'focus-follows-mouse nil)
;; kill frames when a buffer is buried, makes most things play nice with
;; frames
(list 'frame-auto-hide-function 'delete-frame)
;; org windows: Use frames not emacs windows
(list 'org-agenda-window-setup 'other-frame)
(list 'org-src-window-setup 'other-frame)
;; Use a single frame for ediff (without this you end up with an entire
;; frame for the control buffer, this doesn't work well at all with tiling
;; window managers).
(list 'ediff-window-setup-function 'ediff-setup-windows-plain)
;; When using ido-switch-buffer: open buffers in the current frame
;; rather than raising any existing frame (the default behaviour is
;; to only do this with emacs windows but switch to existing
;; frames).
(list 'ido-default-buffer-method 'selected-window)
;; Don't auto popup a magit diff buffer when commiting, can still
;; get it if needed with C-c C-d. The variable name for this
;; changed in magit version 2.30 (~November 2015) so we are not
;; compatible with older versions.
(list 'magit-commit-show-diff nil)
;; We've configured magit to always open a new frame, so we need to delete
;; the frame on quitting otherwise each use of magit-status leaves an
;; additional frame lying around.
(list 'magit-bury-buffer-function #'frames-only-mode-magit-bury-buffer-function)
;; Don't pop an errors buffer, it's really annoying, instead
;; format a message in the minibuffer
(list 'flycheck-display-errors-function #'frames-only-mode-flycheck-display-errors))
"List of configuration variables set by frames-only-mode.
Each entry should be of the form `(list variable-symbol value)'.
If you find any settings that you think will be useful to others using this
mode please open an issue at https://github.com/davidshepherd7/frames-only-mode/issues
to let me know."
:type '(repeat (list symbol sexp))
:group 'frames-only)
(defun frames-only-mode-revertable-set (var-vals)
"Like set but return a closure to revert the change.
In accordance with
https://www.gnu.org/software/emacs/manual/html_node/elisp/Hooks-for-Loading.html
we even set variables that are not currently bound, but we unbind
them again on revert."
;; Transform to list of (var value initial-value) and call helper function. I
;; wish we had a back-portable thread-last macro...
(let ((var-val-initials (seq-map (lambda (s) (append s
(list (when (boundp (car s))
(symbol-value (car s)))
(boundp (car s)))))
var-vals)))
(frames-only-mode--revertable-set-helper var-val-initials)))
(defun frames-only-mode--revertable-set-helper (var-value-initials)
"Internal function."
(let ((revert-done nil)
(revert-var-fn
(lambda (s)
(when (equal (symbol-value (car s)) (cadr s))
;; core emacs doesn't have caddr, why? :(
(if (cadr (cdr (cdr s)))
(set (car s) (cadr (cdr s)))
(makunbound (car s)))))))
;; Set each var
(seq-map (lambda (s) (set (car s) (cadr s))) var-value-initials)
;; Return a function to revert the changes
(lambda ()
"Revert the variable values set by revertable-set"
(when (not revert-done)
(seq-map revert-var-fn var-value-initials)
(setq revert-done t)))))
(defvar frames-only-mode--revert-fn #'ignore
"Storage for function to revert changes to variables made by ‘frames-only-mode’.")
;;; Other helpers
(defun frames-only-mode-advice-use-windows (fun &rest args)
"Create new emacs windows instead of frames within this FUN."
(let ((pop-up-frames nil))
(apply fun args)))
(defun frames-only-mode-abort-recursive-edit ()
"Close any sub-windows and abort recursive edit.
This is useful for closing temporary windows created by some commands."
(interactive)
;; Biggest window is probably the "main" one, select it and delete the rest.
(select-window (get-largest-window))
(delete-other-windows)
(abort-recursive-edit))
(defun frames-only-should-kill-frame-for-buffer (b-name)
(seq-some
(lambda (it) (cond
((stringp it) (equal b-name it))
((and (consp it) (eq (car it) 'regexp)) (string-match-p (cdr it) b-name))
(t nil)))
frames-only-mode-kill-frame-when-buffer-killed-buffer-list))
(defun frames-only-mode-kill-frame-if-current-buffer-matches ()
"Kill frames as well when certain buffers are closed.
Only if there is only a single window in the frame, helps stop some
packages spamming frames."
(when (and (one-window-p)
(frames-only-should-kill-frame-for-buffer (buffer-name)))
(delete-frame)))
(defun frames-only-mode-advice-use-windows-for-completion (orig-fun &rest args)
"Advise a completion function to not use frames."
(let ((pop-up-frames (not frames-only-mode-use-windows-for-completion))
(split-width-threshold 9999))
(apply orig-fun args)))
(defun frames-only-mode-advice-delete-frame-on-bury (orig-fun &rest args)
"Delete the frame when burying certain buffers.
Only if there are no other windows in the frame, and if the
buffer is in frames-only-mode-kill-frame-when-buffer-killed-buffer-list."
;; Store the buffer name now because we can't get it after burying the buffer
(let ((buffer-to-bury (buffer-name)))
(apply orig-fun args)
(when (and (one-window-p)
(frames-only-should-kill-frame-for-buffer buffer-to-bury))
(delete-frame))))
(defun frames-only-mode-bury-completions ()
(when (get-buffer "*Completions*")
(bury-buffer "*Completions*")))
(defun frames-only-mode-flycheck-display-errors (errors)
(message "%s" (mapcar 'flycheck-error-format-message-and-id errors)))
;; Quiet the compiler: will always be loaded by the time this function is used.
(declare-function magit-restore-window-configuration "magit-mode")
(defun frames-only-mode-magit-bury-buffer-function (kill-buffer)
"By default magit leaves open frames after quitting magit-status
buffers in some cases.
This modification makes it always kill the frame after quitting a
magit status buffer."
(let ((current-buffer-is-magit-status (derived-mode-p 'magit-status-mode))
(current-frame (selected-frame)))
(magit-restore-window-configuration kill-buffer)
(when (and current-buffer-is-magit-status
(equal (selected-frame) current-frame))
(delete-frame))))
(defvar frames-only-mode-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-]") #'frames-only-mode-abort-recursive-edit)
;; Additional keys rebinding common window commands are added/removed by TODO
map)
"Keymap for ‘frames-only-mode’.")
;;;###autoload
(defun frames-only-mode-remap-common-window-split-keybindings ()
"Make frames-only-mode remap common window splitting keybinds.
Only modifies `frames-only-mode-mode-map', no effect when the mode is
disabled.
If you have much customisation of your keybinds then you should
probably do this manually instead.
"
(interactive)
(define-key frames-only-mode-mode-map [remap split-window-below] #'make-frame-command)
(define-key frames-only-mode-mode-map [remap split-window-right] #'make-frame-command)
;; C-x 4 is other-window functions, C-x 5 is other frame. remap doesn't work
;; for this case.
(define-key frames-only-mode-mode-map (kbd "C-x 4") #'ctl-x-5-prefix)
)
;;;###autoload
(define-minor-mode frames-only-mode
"Use frames instead of emacs windows."
:global t
:group 'frames-only
:keymap frames-only-mode-mode-map
;; Apply most of the configuration changes
(if frames-only-mode
(setq frames-only-mode--revert-fn (frames-only-mode-revertable-set frames-only-mode-configuration-variables))
(funcall frames-only-mode--revert-fn))
;; Disable in some functions as specified by customisation
(if frames-only-mode
(mapc (lambda (fun) (advice-add fun :around #'frames-only-mode-advice-use-windows)) frames-only-mode-use-window-functions)
(mapc (lambda (fun) (advice-remove fun #'frames-only-mode-advice-use-windows)) frames-only-mode-use-window-functions))
;; Hacks to make other things play nice by killing the frame when certain
;; buffers are closed.
(if frames-only-mode
(progn
(add-hook 'kill-buffer-hook #'frames-only-mode-kill-frame-if-current-buffer-matches)
(advice-add #'bury-buffer :around #'frames-only-mode-advice-delete-frame-on-bury))
(remove-hook 'kill-buffer-hook #'frames-only-mode-kill-frame-if-current-buffer-matches)
(advice-remove #'bury-buffer #'frames-only-mode-advice-delete-frame-on-bury))
;; Advise completion popup functions to use windows instead of frames if
;; the custom setting is true.
(if frames-only-mode
(progn
(advice-add #'minibuffer-completion-help :around #'frames-only-mode-advice-use-windows-for-completion)
(advice-add 'ido-completion-help :around #'frames-only-mode-advice-use-windows-for-completion)
(advice-add 'pcomplete :around #'frames-only-mode-advice-use-windows-for-completion))
(advice-remove #'minibuffer-completion-help #'frames-only-mode-advice-use-windows-for-completion)
(advice-remove 'ido-completion-help #'frames-only-mode-advice-use-windows-for-completion)
(advice-remove 'pcomplete #'frames-only-mode-advice-use-windows-for-completion))
;; Make sure completions buffer is buried after we are done with the minibuffer
(if frames-only-mode
(add-hook 'minibuffer-exit-hook #'frames-only-mode-bury-completions)
(remove-hook 'minibuffer-exit-hook #'frames-only-mode-bury-completions)))
(provide 'frames-only-mode)
;;; frames-only-mode.el ends here