-
Notifications
You must be signed in to change notification settings - Fork 137
/
meow-keypad.el
558 lines (509 loc) · 20.5 KB
/
meow-keypad.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
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
;;; meow-keypad.el --- Meow keypad mode -*- lexical-binding: t -*-
;; 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 GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;; Keypad state is a special state to simulate C-x and C-c key sequences.
;;
;; Useful commands:
;;
;; meow-keypad
;; Enter keypad state.
;;
;; meow-keypad-start
;; Enter keypad state, and simulate this key with Control modifier.
;;
;;; Code:
(require 'subr-x)
(require 'meow-var)
(require 'meow-util)
(require 'meow-helpers)
(require 'meow-beacon)
(defun meow--keypad-format-upcase (k)
"Return S-k for upcase K."
(let ((case-fold-search nil))
(if (and (stringp k)
(string-match-p "^[A-Z]$" k))
(format "S-%s" (downcase k))
k)))
(defun meow--keypad-format-key-1 (key)
"Return a display format for input KEY."
(cl-case (car key)
(meta (format "M-%s" (cdr key)))
(control (format "C-%s" (meow--keypad-format-upcase (cdr key))))
(both (format "C-M-%s" (meow--keypad-format-upcase (cdr key))))
(literal (cdr key))))
(defun meow--keypad-format-prefix ()
"Return a display format for current prefix."
(cond
((equal '(4) meow--prefix-arg)
"C-u ")
(meow--prefix-arg
(format "%s " meow--prefix-arg))
(t "")))
(defun meow--keypad-lookup-key (keys)
"Lookup the command which is bound at KEYS."
(let* ((keybind (if meow--keypad-base-keymap
(lookup-key meow--keypad-base-keymap keys)
(key-binding keys))))
(unless (and (meow--is-self-insertp keybind)
(not meow-keypad-self-insert-undefined))
keybind)))
(defun meow--keypad-has-sub-meta-keymap-p ()
"Check if there's a keymap belongs to Meta prefix.
A key sequences starts with ESC is accessible via Meta key."
(and (not meow--use-literal)
(not meow--use-both)
(not meow--use-meta)
(or (not meow--keypad-keys)
(let* ((key-str (meow--keypad-format-keys nil))
(keymap (meow--keypad-lookup-key (kbd key-str))))
(and (keymapp keymap)
(lookup-key keymap ""))))))
(defun meow--keypad-format-keys (&optional prompt)
"Return a display format for current input keys.
The message is prepended with an optional PROMPT."
(let ((result ""))
(setq result
(thread-first
(mapcar #'meow--keypad-format-key-1 meow--keypad-keys)
(reverse)
(string-join " ")))
(cond
(meow--use-both
(setq result
(if (string-empty-p result)
"C-M-"
(concat result " C-M-"))))
(meow--use-meta
(setq result
(if (string-empty-p result)
"M-"
(concat result " M-"))))
(meow--use-literal
(setq result (concat result " ○")))
(prompt
(setq result (concat result " C-"))))
result))
(defun meow--keypad-quit ()
"Quit keypad state."
(setq meow--keypad-keys nil
meow--use-literal nil
meow--use-meta nil
meow--use-both nil
meow--keypad-help nil)
(meow--keypad-clear-message)
(meow--exit-keypad-state)
;; Return t to indicate the keypad loop should be stopped
t)
(defun meow-keypad-quit ()
"Quit keypad state."
(interactive)
(setq this-command last-command)
(when meow-keypad-message
(message "KEYPAD exit"))
(meow--keypad-quit))
(defun meow--make-keymap-for-describe (keymap control)
"Parse the KEYMAP to make it suitable for describe.
Argument CONTROL, non-nils stands for current input is prefixed with Control."
(let ((km (make-keymap)))
(suppress-keymap km t)
(when (keymapp keymap)
(map-keymap
(lambda (key def)
(unless (member (event-basic-type key) '(127))
(when (if control (member 'control (event-modifiers key))
(not (member 'control (event-modifiers key))))
(define-key km (vector (meow--get-event-key key))
(funcall meow-keypad-get-title-function def)))))
keymap))
km))
(defun meow--keypad-get-keymap-for-describe ()
"Get a keymap for describe."
(let* ((input (thread-first
(mapcar #'meow--keypad-format-key-1 meow--keypad-keys)
(reverse)
(string-join " ")))
(meta-both-keymap (meow--keypad-lookup-key
(read-kbd-macro
(if (string-blank-p input)
"ESC"
(concat input " ESC"))))))
(cond
(meow--use-meta
(when meta-both-keymap
(meow--make-keymap-for-describe meta-both-keymap nil)))
(meow--use-both
(when meta-both-keymap
(meow--make-keymap-for-describe meta-both-keymap t)))
(meow--use-literal
(when-let* ((keymap (meow--keypad-lookup-key (read-kbd-macro input))))
(when (keymapp keymap)
(meow--make-keymap-for-describe keymap nil))))
;; For leader popup
;; meow-keypad-leader-dispatch can be string, keymap or nil
;; - string, dynamically find the keymap
;; - keymap, just use it
;; - nil, take the one in meow-keymap-alist
;; Leader keymap may contain meow-dispatch commands
;; translated names based on the commands they refer to
((null meow--keypad-keys)
(when-let* ((keymap (if (stringp meow-keypad-leader-dispatch)
(meow--keypad-lookup-key (read-kbd-macro meow-keypad-leader-dispatch))
(or meow-keypad-leader-dispatch
(alist-get 'leader meow-keymap-alist)))))
(let ((km (make-keymap)))
(suppress-keymap km t)
(map-keymap
(lambda (key def)
(when (and (not (member 'control (event-modifiers key)))
(not (member key (list meow-keypad-meta-prefix
meow-keypad-ctrl-meta-prefix
meow-keypad-literal-prefix)))
(not (alist-get key meow-keypad-start-keys)))
(let ((keys (vector (meow--get-event-key key))))
(unless (lookup-key km keys)
(define-key km keys (funcall meow-keypad-get-title-function def))))))
keymap)
km)))
(t
(when-let* ((keymap (meow--keypad-lookup-key (read-kbd-macro input))))
(when (keymapp keymap)
(let* ((km (make-keymap))
(has-sub-meta (meow--keypad-has-sub-meta-keymap-p))
(ignores (if has-sub-meta
(list meow-keypad-meta-prefix
meow-keypad-ctrl-meta-prefix
meow-keypad-literal-prefix
127)
(list meow-keypad-literal-prefix 127))))
(suppress-keymap km t)
(map-keymap
(lambda (key def)
(when (member 'control (event-modifiers key))
(unless (member (meow--event-key key) ignores)
(when def
(let ((k (vector (meow--get-event-key key))))
(unless (lookup-key km k)
(define-key km k (funcall meow-keypad-get-title-function def))))))))
keymap)
(map-keymap
(lambda (key def)
(unless (member 'control (event-modifiers key))
(unless (member key ignores)
(let ((k (vector (meow--get-event-key key))))
(unless (lookup-key km k)
(define-key km (vector (meow--get-event-key key)) (funcall meow-keypad-get-title-function def)))))))
keymap)
km)))))))
(defun meow--keypad-clear-message ()
"Clear displayed message by calling `meow-keypad-clear-describe-keymap-function'."
(when meow-keypad-clear-describe-keymap-function
(funcall meow-keypad-clear-describe-keymap-function)))
(defun meow--keypad-display-message ()
"Display a message for current input state."
(when meow-keypad-describe-keymap-function
(when (or
meow--keypad-keymap-description-activated
(setq meow--keypad-keymap-description-activated
(sit-for meow-keypad-describe-delay t)))
(let ((keymap (meow--keypad-get-keymap-for-describe)))
(funcall meow-keypad-describe-keymap-function keymap)))))
(defun meow--describe-keymap-format (pairs &optional width)
(let* ((fw (or width (frame-width)))
(cnt (length pairs))
(best-col-w nil)
(best-rows nil))
(cl-loop for col from 5 downto 2 do
(let* ((row (1+ (/ cnt col)))
(v-parts (seq-partition pairs row))
(rows (meow--transpose-lists v-parts))
(col-w (thread-last
v-parts
(mapcar
(lambda (col)
(cons (seq-max (or (mapcar (lambda (it) (length (car it))) col) '(0)))
(seq-max (or (mapcar (lambda (it) (length (cdr it))) col) '(0))))))))
;; col-w looks like:
;; ((3 . 2) (4 . 3))
(w (thread-last
col-w
;; 4 is for the width of arrow(3) between key and command
;; and the end tab or newline(1)
(mapcar (lambda (it) (+ (car it) (cdr it) 4)))
(meow--sum))))
(when (<= w fw)
(setq best-col-w col-w
best-rows rows)
(cl-return nil))))
(if best-rows
(thread-last
best-rows
(mapcar
(lambda (row)
(thread-last
row
(seq-map-indexed
(lambda (it idx)
(let* ((key-str (car it))
(def-str (cdr it))
(l-r (nth idx best-col-w))
(l (car l-r))
(r (cdr l-r))
(key (meow--string-pad key-str l 32 t))
(def (meow--string-pad def-str r 32)))
(format "%s%s%s"
key
(propertize " → " 'face 'font-lock-comment-face)
def))))
(meow--string-join " "))))
(meow--string-join "\n"))
(propertize "Frame is too narrow for KEYPAD popup" 'face 'meow-keypad-cannot-display))))
(defun meow-describe-keymap (keymap)
(when (and keymap (not defining-kbd-macro) (not meow--keypad-help))
(let* ((rst))
(map-keymap
(lambda (key def)
(let ((k (if (consp key)
(format "%s .. %s"
(key-description (list (car key)))
(key-description (list (cdr key))))
(key-description (list key)))))
(let (key-str def-str)
(cond
((and (commandp def) (symbolp def))
(setq key-str (propertize k 'face 'font-lock-constant-face)
def-str (propertize (symbol-name def) 'face 'font-lock-function-name-face)))
((symbolp def)
(setq key-str (propertize k 'face 'font-lock-constant-face)
def-str (propertize (concat "+" (symbol-name def)) 'face 'font-lock-keyword-face)))
((functionp def)
(setq key-str (propertize k 'face 'font-lock-constant-face)
def-str (propertize "?closure" 'face 'font-lock-function-name-face)))
(t
(setq key-str (propertize k 'face 'font-lock-constant-face)
def-str (propertize "+prefix" 'face 'font-lock-keyword-face))))
(push (cons key-str def-str) rst))))
keymap)
(setq rst (reverse rst))
(let ((msg (meow--describe-keymap-format rst)))
(let ((message-log-max)
(max-mini-window-height 1.0))
(save-window-excursion
(with-temp-message
(format "%s\n%s%s%s"
msg
meow-keypad-message-prefix
(let ((pre (meow--keypad-format-prefix)))
(if (string-blank-p pre)
""
(propertize pre 'face 'font-lock-comment-face)))
(propertize (meow--keypad-format-keys nil) 'face 'font-lock-string-face))
(sit-for 1000000 t))))))))
(defun meow-keypad-get-title (def)
"Return a symbol as title or DEF.
Returning DEF will result in a generated title."
(if-let* ((cmd (and (symbolp def)
(commandp def)
(get def 'meow-dispatch))))
(meow--keypad-lookup-key (kbd cmd))
def))
(defun meow-keypad-undo ()
"Pop the last input."
(interactive)
(setq this-command last-command)
(cond
(meow--use-both
(setq meow--use-both nil))
(meow--use-literal
(setq meow--use-literal nil))
(meow--use-meta
(setq meow--use-meta nil))
(t
(pop meow--keypad-keys)))
(if meow--keypad-keys
(progn
(meow--update-indicator)
(meow--keypad-display-message))
(when meow-keypad-message
(message "KEYPAD exit"))
(meow--keypad-quit)))
(defun meow--keypad-show-message ()
"Show message for current keypad input."
(let ((message-log-max))
(message "%s%s%s%s"
meow-keypad-message-prefix
(if meow--keypad-help "(describe key)" "")
(let ((pre (meow--keypad-format-prefix)))
(if (string-blank-p pre)
""
(propertize pre 'face 'font-lock-comment-face)))
(propertize (meow--keypad-format-keys nil) 'face 'font-lock-string-face))))
(defun meow--keypad-in-beacon-p ()
"Return whether keypad is started from BEACON state."
(and (meow--beacon-inside-secondary-selection)
meow--beacon-overlays))
(defun meow--keypad-execute (command)
"Execute the COMMAND.
If there are beacons, execute it at every beacon."
(if (meow--keypad-in-beacon-p)
(cond
((member command '(kmacro-start-macro kmacro-start-macro-or-insert-counter))
(call-interactively 'meow-beacon-start))
((member command '(kmacro-end-macro meow-end-kmacro))
(call-interactively 'meow-beacon-end-and-apply-kmacro))
((and (not defining-kbd-macro)
(not executing-kbd-macro)
meow-keypad-execute-on-beacons)
(call-interactively command)
(meow--beacon-apply-command command)))
(call-interactively command)))
(defun meow--keypad-try-execute ()
"Try execute command, return t when the translation progress can be ended.
If there is a command available on the current key binding,
try replacing the last modifier and try again."
(unless (or meow--use-literal
meow--use-meta
meow--use-both)
(let* ((key-str (meow--keypad-format-keys nil))
(cmd (meow--keypad-lookup-key (kbd key-str))))
(cond
((keymapp cmd)
(when meow-keypad-message (meow--keypad-show-message))
(meow--keypad-display-message)
nil)
((commandp cmd t)
(setq current-prefix-arg meow--prefix-arg
meow--prefix-arg nil)
(if meow--keypad-help
(progn
(meow--keypad-quit)
(describe-function cmd)
t)
(let ((meow--keypad-this-command cmd))
(meow--keypad-quit)
(setq real-this-command cmd
this-command cmd)
(meow--keypad-execute cmd)
t)))
((equal 'control (caar meow--keypad-keys))
(setcar meow--keypad-keys (cons 'literal (cdar meow--keypad-keys)))
(meow--keypad-try-execute))
(t
(setq meow--prefix-arg nil)
(message "%s is undefined" (meow--keypad-format-keys nil))
(meow--keypad-quit)
t)))))
(defun meow--keypad-handle-input-with-keymap (input-event)
"Handle INPUT-EVENT with `meow-keypad-state-keymap'.
Return t if handling is completed."
(if (numberp input-event)
(let* ((k (if (= 27 input-event)
[escape]
(kbd (single-key-description input-event))))
(cmd (lookup-key meow-keypad-state-keymap k)))
(if cmd
(call-interactively cmd)
(meow--keypad-handle-input-event input-event)))
(meow--keypad-quit)))
(defun meow--keypad-handle-input-event (input-event)
"Handle the INPUT-EVENT.
Add a parsed key and its modifier to current key sequence. Then invoke a
command when there's one available on current key sequence."
(meow--keypad-clear-message)
(when-let* ((key (single-key-description input-event)))
(let ((has-sub-meta (meow--keypad-has-sub-meta-keymap-p)))
(cond
(meow--use-literal
(push (cons 'literal key)
meow--keypad-keys)
(setq meow--use-literal nil))
(meow--use-both
(push (cons 'both key) meow--keypad-keys)
(setq meow--use-both nil))
(meow--use-meta
(push (cons 'meta key) meow--keypad-keys)
(setq meow--use-meta nil))
((and (equal input-event meow-keypad-meta-prefix)
(not meow--use-meta)
has-sub-meta)
(setq meow--use-meta t))
((and (equal input-event meow-keypad-ctrl-meta-prefix)
(not meow--use-both)
has-sub-meta)
(setq meow--use-both t))
((and (equal input-event meow-keypad-literal-prefix)
(not meow--use-literal)
meow--keypad-keys)
(setq meow--use-literal t))
(meow--keypad-keys
(push (cons 'control key) meow--keypad-keys))
((alist-get input-event meow-keypad-start-keys)
(push (cons 'control (meow--parse-input-event
(alist-get input-event meow-keypad-start-keys)))
meow--keypad-keys))
(t
(if-let* ((keymap (meow--get-leader-keymap)))
(setq meow--keypad-base-keymap keymap)
(setq meow--keypad-keys (meow--parse-string-to-keypad-keys meow-keypad-leader-dispatch)))
(push (cons 'literal key) meow--keypad-keys))))
;; Try execute if the input is valid.
(if (or meow--use-literal
meow--use-meta
meow--use-both)
(progn
(when meow-keypad-message (meow--keypad-show-message))
(meow--keypad-display-message)
nil)
(meow--keypad-try-execute))))
(defun meow-keypad ()
"Enter keypad state and convert inputs."
(interactive)
(meow-keypad-start-with nil))
(defun meow-keypad-start ()
"Enter keypad state with current input as initial key sequences."
(interactive)
(setq this-command last-command
meow--keypad-keys nil
meow--keypad-previous-state (meow--current-state)
meow--prefix-arg current-prefix-arg)
(meow--switch-state 'keypad)
(unwind-protect
(progn
(meow--keypad-handle-input-with-keymap last-input-event)
(while (not (meow--keypad-handle-input-with-keymap (read-key)))))
(when (bound-and-true-p meow-keypad-mode)
(meow--keypad-quit))))
(defun meow-keypad-start-with (input)
"Enter keypad state with INPUT.
A string INPUT, stands for initial keys.
When INPUT is nil, start without initial keys."
(setq this-command last-command
meow--keypad-keys (when input (meow--parse-string-to-keypad-keys input))
meow--keypad-previous-state (meow--current-state)
meow--prefix-arg current-prefix-arg)
(meow--switch-state 'keypad)
(unwind-protect
(progn
(meow--keypad-show-message)
(meow--keypad-display-message)
(while (not (meow--keypad-handle-input-with-keymap (read-key)))))
(when (bound-and-true-p meow-keypad-mode)
(meow--keypad-quit))))
(defun meow-keypad-describe-key ()
"Describe key via KEYPAD input."
(interactive)
(setq meow--keypad-help t)
(meow-keypad))
(provide 'meow-keypad)
;;; meow-keypad.el ends here