-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathpopper-echo.el
348 lines (298 loc) · 12.8 KB
/
popper-echo.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
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
;;; popper-echo.el --- Show a popup list in the echo area when cycling them -*- lexical-binding: t -*-
;; Copyright (C) 2023 Free Software Foundation, Inc.
;; Author: Karthik Chikmagalur <[email protected]>
;; Version: 0.45
;; Package-Requires: ((emacs "26.1"))
;; Keywords: convenience
;; URL: https://github.com/karthink/popper
;; This file is NOT part of GNU Emacs.
;; This file 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, 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.
;; For a full copy of the GNU General Public License
;; see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This library provides two minor-modes to preview the list of relevant popup
;; names when toggling or cycling popups. These popups can be accessed directly
;; using dispatch keybinds. See Popper for how to classify a buffer as a popup.
;;
;; `popper-echo-mode' displays the list of popups in the echo area when toggling
;; or cycling popups.
;;
;; `popper-tab-line-mode' displays the list of popups in the tab-line of the
;; active popup when toggling or cycling them. NOTE: This feature is
;; experimental.
;; CUSTOMIZATION:
;; `popper-echo-lines': The number of echo area/minibuffer lines to use when
;; showing a list of popups
;;
;; `popper-echo-dispatch-keys': A list of strings or characters representing the
;; keybindings to access popups shown in the echo area or tab-line.
;;
;; `popper-echo-dispatch-persist': A boolean to control whether the dispatch
;; keymap stays active after using a dispatch key.
;;
;; `popper-echo-transform-function': A function to transform the display of
;; these popups, such as by truncating buffer names, etc.
;;; Code:
(require 'popper)
(defcustom popper-echo-transform-function nil
"Function to transform buffer names.
This is called on buffer-names displayed by `popper-echo'.
This function should accept a string (the buffer name) and return
a transformed string."
:type '(choice (const :tag "Don't transform buffer-names" nil)
function)
:group 'popper)
(defcustom popper-echo-lines 2
"Number of minibuffer lines used to show popup buffer names by `popper-echo'.
This has no effect when popper-echo-mode is turned off."
:type 'integer
:group 'popper)
(defcustom popper-echo-dispatch-persist t
"Controls whether the `popper-echo' dispatch menu is persistent."
:type 'boolean
:group 'popper)
(defcustom popper-echo-dispatch-actions nil
"Controls whether `popper-echo' dispatch actions are bound.
When true, you can
- Kill popup buffers with k
- Raise popup buffers with ^
when using the dispatch menu by prefixing the dispatch keys with them.
NOTE: This feature is experimental."
:type 'boolean
:group 'popper)
(defcustom popper-echo-dispatch-keys '("M-0" "M-1" "M-2" "M-3" "M-4"
"M-5" "M-6" "M-7" "M-8" "M-9")
"List of keys used for dispatching to popup buffers.
The first element is bound to the currently open popup.
Each entry in the list can be a character or a string suitable
for the kbd macro. These keys are available when using
popper-echo-mode.
Examples:
\\='(?q ?w ?e ?r ?t ?y ?u ?i ?o ?p)
\\='(\"M-1\" \"M-2\" \"M-3\" \"M-4\" \"M-5\")
This variable has no effect when popper-echo-mode is turned
off."
:type '(repeat (choice character string))
:group 'popper)
(defface popper-echo-area-buried
'((t :inherit shadow))
"Echo area face for buried popups."
:group 'popper)
(defface popper-echo-area
'((t :inverse-video t
:weight bold))
"Echo area face for opened popup."
:group 'popper)
(defface popper-echo-dispatch-hint
'((t :inherit bold))
"Echo area face for popper dispatch key hints."
:group 'popper)
;;; Utility functions
(defun popper-echo--dispatch-toggle (i buf-list repeat)
"Return a function to switch to buffer I in list BUF-LIST.
This is used to create functions for switching between popups
quickly."
(lambda (&optional arg)
(interactive "P")
(when-let ((buf (nth i buf-list)))
(unless arg (popper-close-latest))
(display-buffer buf)
(popper--update-popups)
(when popper-echo-dispatch-persist
(with-current-buffer buf (funcall repeat))))))
(defun popper-echo--dispatch-kill (i buf-list repeat)
"Return a function to Kill buffer I in list BUF-LIST."
(lambda ()
(interactive)
(let* ((buf (nth i buf-list))
(win (get-buffer-window buf)))
(kill-buffer buf)
(popper--delete-popup win))
(popper--update-popups)
(when (and popper-echo-dispatch-persist
popper-open-popup-alist)
(with-current-buffer (cdar popper-open-popup-alist)
(funcall repeat)))))
(defun popper-echo--dispatch-raise (i buf-list repeat)
"Return a function to raise buffer I in list BUF-LIST.
Raising converts if from a popup to a regular buffer."
(lambda ()
(interactive)
(let* ((buf (nth i buf-list)))
(popper-toggle-type buf))
(popper--update-popups)
(when (and popper-echo-dispatch-persist
popper-open-popup-alist)
(with-current-buffer (cdar popper-open-popup-alist)
(funcall repeat)))))
(defun popper-echo--popup-info ()
"Return the popper group and list of buried popup buffers."
(let ((grp-symb (when popper-group-function
(funcall popper-group-function))))
(cons grp-symb
(thread-last (alist-get grp-symb popper-buried-popup-alist nil nil 'equal)
(mapcar #'cdr)
(cl-remove-if-not #'buffer-live-p)
(delete-dups)))))
(defun popper-echo--activate-keymap (buffers repeat)
"Activate a transient keymap to switch to or manipulate BUFFERS.
Each command in the keymap calls the function REPEAT afterwards."
(set-transient-map
(cl-loop with map = (make-sparse-keymap)
for i upto 9
for keybind in popper-echo-dispatch-keys
for rawkey = (cond ((characterp keybind) (char-to-string keybind))
(t keybind))
do
(define-key map (kbd rawkey) (popper-echo--dispatch-toggle i buffers repeat))
(define-key map (kbd (concat "k " rawkey))
(popper-echo--dispatch-kill i buffers repeat))
(define-key map (kbd (concat "^ " rawkey))
(popper-echo--dispatch-raise i buffers repeat))
finally return map)))
;;; Notify in echo area:
(defun popper-echo ()
"Show popup list in the echo area when cycling popups."
(pcase-let*
((message-log-max nil)
(`(,grp-symb . ,buried-popups) (popper-echo--popup-info))
(buried-popups (mapcar #'buffer-name buried-popups))
(group (and grp-symb
(concat "Group (" (truncate-string-to-width (format "%S" grp-symb) 20 nil nil t) "): ")))
(open-popup (buffer-name))
(dispatch-keys-extended
(append (cdr popper-echo-dispatch-keys)
(make-list (max 0 (- (length buried-popups)
(1- (length popper-echo-dispatch-keys))))
nil)))
(popup-strings
(apply #'concat
(cons
(if-let ((transform popper-echo-transform-function))
(funcall transform open-popup)
(propertize open-popup 'face 'popper-echo-area))
(cl-mapcar (lambda (key buf)
(concat
(propertize ", " 'face 'popper-echo-area-buried)
(propertize "[" 'face 'popper-echo-area-buried)
(and key
(concat
(propertize (if (characterp key)
(char-to-string key)
key)
'face 'popper-echo-dispatch-hint)
(propertize ":" 'face 'popper-echo-area-buried)))
(if-let ((transform popper-echo-transform-function))
(funcall transform buf)
(concat
(propertize buf 'face 'popper-echo-area-buried)))
(propertize "]" 'face 'popper-echo-area-buried)))
dispatch-keys-extended
buried-popups)))))
(let* ((max-width (- (* popper-echo-lines (frame-width)) (if group (length group) 11)))
(plen (length popup-strings))
(space-p (> max-width plen)))
(message "%s"
(concat
(or group "Popups: ")
(substring popup-strings 0 (if space-p plen max-width))
(unless space-p
(propertize "..." 'face 'popper-echo-area-buried)))))
(popper-echo--activate-keymap (cons open-popup buried-popups) #'popper-echo)))
(defvar popper-tab-line-mode "popper-echo")
;;;###autoload
(define-minor-mode popper-echo-mode
"Toggle Popper Echo mode.
Show popup names in cycling order in the echo area when
performing an action that involves showing a popup. These popups
can be accessed directly or acted upon by using quick keys (see
`popper-echo-dispatch-keys').
To define buffers as popups and customize popup display, see
`popper-mode'."
:global t
:lighter ""
:group 'popper
(if popper-echo-mode
(progn
(when popper-tab-line-mode
(message "`popper-echo-mode'. is incompatible with `popper-tab-line-mode' Disabling `popper-tab-line-mode'.")
(popper-tab-line-mode -1))
(add-hook 'popper-open-popup-hook 'popper-echo)
(unless popper-mode (popper-mode 1)))
(remove-hook 'popper-open-popup-hook 'popper-echo)))
;;; Notify using tab-line
(declare-function tab-line-mode "tab-line")
(declare-function tab-line-tab-name-format-default "tab-line")
(defvar tab-line-tab-name-format-function)
(defvar tab-line-tabs-function)
(defvar tab-line-mode)
(defun popper-tab-line--format (tab tabs)
(let ((name (tab-line-tab-name-format-default tab tabs))
(idx (cl-position tab tabs)))
(concat
(propertize
(char-to-string (+ idx #x2460)) ;; #x2776
'face (if (eq tab (current-buffer))
(if (mode-line-window-selected-p)
'tab-line-tab-current 'tab-line-tab)
'tab-line-tab-inactive))
name)))
(defun popper-tab-line--ensure ()
(pcase-let ((`(_ . ,buried-popups) (popper-echo--popup-info)))
(if (not buried-popups)
(tab-line-mode -1)
(unless tab-line-mode
(setq-local
tab-line-tabs-function
(lambda () (cl-sort (cons (current-buffer) (cdr (popper-echo--popup-info)))
#'string< :key #'buffer-name))
tab-line-tab-name-format-function #'popper-tab-line--format)
(when popper-echo-transform-function
(setq-local tab-line-tab-name-function
(lambda (buf _) (funcall popper-echo-transform-function
(buffer-name buf)))))
(tab-line-mode 1)))
(popper-echo--activate-keymap
(cl-sort (cons (current-buffer) buried-popups) #'string< :key #'buffer-name)
#'popper-tab-line--ensure)))
;;;###autoload
(define-minor-mode popper-tab-line-mode
"Toggle Popper Tab Line Mode.
Show popup names in cycling order in the tab-line of the popup
window when performing an action that involves showing a popup.
These popups can be accessed directly or acted upon by using
quick keys (see `popper-echo-dispatch-keys').
To define buffers as popups and customize popup display, see
`popper-mode'."
:global t
:lighter ""
:group 'popper
(if popper-tab-line-mode
(progn
(require 'tab-line)
(when popper-echo-mode
(message "`popper-tab-line-mode' is incompatible with `popper-echo-mode'. Disabling `popper-echo-mode'.")
(popper-echo-mode -1))
(add-hook 'popper-open-popup-hook #'popper-tab-line--ensure)
(unless popper-mode (popper-mode 1)))
(remove-hook 'popper-open-popup-hook #'popper-tab-line--ensure)
;; Clear tab-lines
(mapc
(pcase-lambda (`(_ . ,buf))
(when (buffer-live-p buf)
(with-current-buffer buf
(kill-local-variable 'tab-line-tabs-function)
(kill-local-variable 'tab-line-tab-name-format-function)
(unless global-tab-line-mode (tab-line-mode -1)))))
(mapcan #'cdr (cons (cons nil popper-open-popup-alist)
popper-buried-popup-alist)))
(force-mode-line-update)))
(provide 'popper-echo)
;;; popper-echo.el ends here