-
Notifications
You must be signed in to change notification settings - Fork 23
/
window-purpose.el
311 lines (256 loc) · 13.3 KB
/
window-purpose.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
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
;;; window-purpose.el --- Purpose-based window management for Emacs -*- lexical-binding: t -*-
;; Copyright (C) 2015-2021 Bar Magal & contributors
;; Author: Bar Magal
;; Package: purpose
;; Version: 1.8.1
;; Keywords: frames
;; Homepage: https://github.com/bmag/emacs-purpose
;; Package-Requires: ((emacs "24.4") (let-alist "1.0.3") (imenu-list "0.1"))
;; 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 <http://www.gnu.org/licenses/>.
;;; Commentary:
;; ---------------------------------------------------------------------
;; Full information can be found on GitHub:
;; https://github.com/bmag/emacs-purpose/wiki
;; ---------------------------------------------------------------------
;; Purpose is a package that introduces the concept of a "purpose" for
;; windows and buffers, and then helps you maintain a robust window
;; layout easily.
;; Installation and Setup:
;; Install Purpose from MELPA, or download it manually from GitHub. If
;; you download manually, add these lines to your init file:
;; (add-to-list 'load-path "/path/to/purpose")
;; (require 'window-purpose)
;; To activate Purpose at start-up, add this line to your init file:
;; (purpose-mode)
;; Purpose Configuration:
;; Customize `purpose-user-mode-purposes', `purpose-user-name-purposes',
;; `purpose-user-regexp-purposes' and
;; `purpose-use-default-configuration'.
;; Basic Usage:
;; 1. Load/Save window/frame layout (see `purpose-load-window-layout',
;; `purpose-save-window-layout', etc.)
;; 2. Use regular switch-buffer functions - they will not mess your
;; window layout (Purpose overrides them).
;; 3. If you don't want a window's purpose/buffer to change, dedicate
;; the window:
;; C-c , d: `purpose-toggle-window-purpose-dedicated'
;; C-c , D: `purpose-toggle-window-buffer-dedicated'
;; 4. To use a switch-buffer function that ignores Purpose, prefix it
;; with C-u. For example, [C-u C-x b] calls
;; `switch-buffer-without-purpose'.
;;; Code:
(require 'easymenu)
(require 'window-purpose-utils)
(require 'window-purpose-configuration)
(require 'window-purpose-core)
(require 'window-purpose-layout)
(require 'window-purpose-switch)
(require 'window-purpose-prefix-overload)
(require 'window-purpose-fixes)
(defconst purpose-version "1.8.1"
"Purpose's version.")
;;; Commands that work with both `ido' and `helm'
;; `helm' doesn't work well with `ido-find-file' (and other `ido' commands),
;; but `find-file' can't benefit from all of `ido''s features, so we create
;; commands that know when to use `ido-find-file' and when to use `find-file'.
;; the result: Purpose works well both with `ido' and `helm'!!!
(defmacro purpose-ido-caller (ido-fn other-fn)
"Create an interactive lambda to conditionally call an ido command.
The lambda calls IDO-FN interactively when `ido-mode' is on, otherwise
it calls OTHER-FN interactively.
Example:
(purpose-ido-caller #'ido-find-file #'find-file)"
(declare (indent nil) (debug (function-form function-form)))
`(lambda (&rest _args)
(interactive)
(call-interactively (if ido-mode ,ido-fn ,other-fn))))
(defalias 'purpose-friendly-find-file
(purpose-ido-caller #'ido-find-file #'find-file)
"Call `find-file' or `ido-find-file' intelligently.
If `ido-mode' is on, call `ido-find-file'. Otherwise, call `find-file'.
This allows Purpose to work well with both `ido' and `helm'.")
(defalias 'purpose-friendly-find-file-other-window
(purpose-ido-caller #'ido-find-file-other-window #'find-file-other-window)
"Call `find-file-other-window' or `ido-find-file-other-window'
intelligently.
If `ido-mode' is on, call `ido-find-file-other-window'. Otherwise, call
`find-file-other-window'.
This allows Purpose to work well with both `ido' and `helm'.")
(defalias 'purpose-friendly-find-file-other-frame
(purpose-ido-caller #'ido-find-file-other-frame #'find-file-other-frame)
"Call `find-file-other-frame' or `ido-find-file-other-frame'
intelligently.
If `ido-mode' is on, call `ido-find-file-other-frame'. Otherwise, call
`find-file-other-frame'.
This allows Purpose to work well with both `ido' and `helm'.")
(defalias 'purpose-friendly-switch-buffer
(purpose-ido-caller #'ido-switch-buffer #'switch-to-buffer)
"Call `switch-to-buffer' or `ido-switch-buffer' intelligently.
If `ido-mode' is on, call `ido-switch-buffer'. Otherwise, call
`switch-to-buffer'.
This allows Purpose to work well with both `ido' and `helm'.")
(defalias 'purpose-friendly-switch-buffer-other-window
(purpose-ido-caller #'ido-switch-buffer-other-window
#'switch-to-buffer-other-window)
"Call `switch-to-buffer-other-window' or
`ido-switch-buffer-other-window' intelligently.
If `ido-mode' is on, call `ido-switch-buffer-other-window'. Otherwise,
call `switch-to-buffer-other-window'.
This allows Purpose to work well with both `ido' and `helm'.")
(defalias 'purpose-friendly-switch-buffer-other-frame
(purpose-ido-caller #'ido-switch-buffer-other-frame
#'switch-to-buffer-other-frame)
"Call `switch-to-buffer-other-frame' or
`ido-switch-buffer-other-frame' intelligently.
If `ido-mode' is on, call `ido-switch-buffer-other-frame'. Otherwise,
call `switch-to-buffer-other-frame'.
This allows Purpose to work well with both `ido' and `helm'.")
;;; Commands for using Purpose-less behavior
(defalias 'find-file-without-purpose
(without-purpose-command #'find-file))
(defalias 'find-file-other-window-without-purpose
(without-purpose-command #'find-file-other-window))
(defalias 'find-file-other-frame-without-purpose
(without-purpose-command #'find-file-other-frame))
(defalias 'switch-buffer-without-purpose
(without-purpose-command #'switch-to-buffer))
(defalias 'switch-buffer-other-window-without-purpose
(without-purpose-command #'switch-to-buffer-other-window))
(defalias 'switch-buffer-other-frame-without-purpose
(without-purpose-command #'switch-to-buffer-other-frame))
;;; Overloaded commands: (C-u to get original Purpose-less behavior)
(define-purpose-prefix-overload purpose-find-file-overload
'(purpose-friendly-find-file find-file-without-purpose))
(define-purpose-prefix-overload purpose-find-file-other-window-overload
'(purpose-friendly-find-file-other-window find-file-other-window-without-purpose))
(define-purpose-prefix-overload purpose-find-file-other-frame-overload
'(purpose-friendly-find-file-other-frame find-file-other-frame-without-purpose))
(define-purpose-prefix-overload purpose-switch-buffer-overload
'(purpose-friendly-switch-buffer
switch-buffer-without-purpose
purpose-switch-buffer-with-purpose))
(define-purpose-prefix-overload purpose-switch-buffer-other-window-overload
'(purpose-friendly-switch-buffer-other-window
switch-buffer-other-window-without-purpose
purpose-switch-buffer-with-purpose-other-window))
(define-purpose-prefix-overload purpose-switch-buffer-other-frame-overload
'(purpose-friendly-switch-buffer-other-frame
switch-buffer-other-frame-without-purpose
purpose-switch-buffer-with-purpose-other-frame))
(define-purpose-prefix-overload purpose-delete-window-at
'(purpose-delete-window-at-bottom
purpose-delete-window-at-right
purpose-delete-window-at-top
purpose-delete-window-at-left))
(defvar purpose-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-x C-f") #'purpose-find-file-overload)
(define-key map (kbd "C-x 4 f") #'purpose-find-file-other-window-overload)
(define-key map (kbd "C-x 4 C-f") #'purpose-find-file-other-window-overload)
(define-key map (kbd "C-x 5 f") #'purpose-find-file-other-frame-overload)
(define-key map (kbd "C-x 5 C-f") #'purpose-find-file-other-frame-overload)
(define-key map (kbd "C-x b") #'purpose-switch-buffer-overload)
(define-key map (kbd "C-x 4 b") #'purpose-switch-buffer-other-window-overload)
(define-key map (kbd "C-x 5 b") #'purpose-switch-buffer-other-frame-overload)
;; Helpful for quitting temporary windows. Close in meaning to
;; `kill-buffer', so we map it to a close key ("C-x j" is close to
;; "C-x k")
(define-key map (kbd "C-x j") #'quit-window)
;; We use "C-c ," for compatibility with key-binding conventions
(define-key map (kbd "C-c ,") 'purpose-mode-prefix-map)
(define-prefix-command 'purpose-mode-prefix-map)
(cl-loop for (key . def)
in '(;; switch to any buffer
("o" . purpose-switch-buffer)
("[" . purpose-switch-buffer-other-frame)
("p" . purpose-switch-buffer-other-window)
;; switch to buffer with current buffer's purpose
("b" . purpose-switch-buffer-with-purpose)
("4 b" . purpose-switch-buffer-with-purpose-other-window)
("5 b" . purpose-switch-buffer-with-purpose-other-frame)
;; toggle window dedication (buffer, purpose)
("d" . purpose-toggle-window-purpose-dedicated)
("D" . purpose-toggle-window-buffer-dedicated)
;; delete windows
("w" . purpose-delete-window-at)
("1" . purpose-delete-non-dedicated-windows))
do (define-key purpose-mode-prefix-map key def))
map)
"Keymap for Purpose mode.")
(easy-menu-define purpose-menu purpose-mode-map "Purpose Mode"
'("Purpose"
["Toggle Buffer Dedication" purpose-toggle-window-buffer-dedicated
:help "Toggle current window's dedication to its current buffer"]
["Toggle Purpose Dedication" purpose-toggle-window-purpose-dedicated
:help "Toggle current window's dedication to its current purpose"]
["Change Window Purpose" purpose-set-window-purpose
:help "Select a purpose for the current window and change its buffer accordingly"]
["Delete Non-Dedicated Windows" purpose-delete-non-dedicated-windows
:help "Delete all windows that aren't dedicated to their buffer or purpose"]
["Load Window Layout" purpose-load-window-layout t]
["Save Window Layout" purpose-save-window-layout t]
["Load Frame Layout" purpose-load-frame-layout t]
["Save Frame Layout" purpose-save-frame-layout t]))
;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Menu-Keymaps.html#Menu-Keymaps
;; (defvar purpose-menu-bar-map (make-sparse-keymap "Purpose"))
(defun purpose--modeline-string ()
"Return the presentation of a window's purpose for display in the
modeline. The basic form of the string is \"[<purpose>]\". If the
window is purpose-dedicated, add a \"!\" before \"]\". If the window is
buffer-dedicated, add a \"#\" before \"]\".
Some examples:
\"[edit]\": window's purpose is 'edit, and it is not dedicated.
\"[edit!]\": window is dedicated to 'edit purpose.
\"[edit#]\": window's purpose is 'edit, and it is dedicated to its
current buffer.
\"[edit!#]\": window is dedicated to 'edit purpose and to its current buffer."
(format " [%s%s%s]"
(purpose-window-purpose)
(if (purpose-window-purpose-dedicated-p) "!" "")
(if (window-dedicated-p) "#" "")))
(defun purpose--add-advices ()
"Add all advices needed for Purpose to work.
This function is called when `purpose-mode' is activated."
(advice-add 'switch-to-buffer :around #'purpose-switch-to-buffer-advice)
(advice-add 'switch-to-buffer-other-window :around #'purpose-switch-to-buffer-other-window-advice)
(advice-add 'switch-to-buffer-other-frame :around #'purpose-switch-to-buffer-other-frame-advice)
(advice-add 'pop-to-buffer :around #'purpose-pop-to-buffer-advice)
(advice-add 'pop-to-buffer-same-window :around #'purpose-pop-to-buffer-same-window-advice)
(advice-add 'display-buffer :around #'purpose-display-buffer-advice))
(defun purpose--remove-advices ()
"Remove all advices needed for Purpose to work.
This function is called when `purpose-mode' is deactivated."
(advice-remove 'switch-to-buffer #'purpose-switch-to-buffer-advice)
(advice-remove 'switch-to-buffer-other-window #'purpose-switch-to-buffer-other-window-advice)
(advice-remove 'switch-to-buffer-other-frame #'purpose-switch-to-buffer-other-frame-advice)
(advice-remove 'pop-to-buffer #'purpose-pop-to-buffer-advice)
(advice-remove 'pop-to-buffer-same-window #'purpose-pop-to-buffer-same-window-advice)
(advice-remove 'display-buffer #'purpose-display-buffer-advice))
;;;###autoload
(define-minor-mode purpose-mode nil
:global t :lighter (:eval (purpose--modeline-string))
(if purpose-mode
(progn
(purpose--add-advices)
(setq display-buffer-overriding-action
'(purpose--action-function . nil))
(setq purpose--active-p t)
(unless purpose-fix-togglers-hook
(purpose-fix-install))
(run-hooks 'purpose-fix-togglers-hook))
(purpose--remove-advices)
(setq purpose--active-p nil)
(run-hooks 'purpose-fix-togglers-hook)))
(push '(purpose-dedicated . writable) window-persistent-parameters)
(provide 'window-purpose)
;;; window-purpose.el ends here