-
Notifications
You must be signed in to change notification settings - Fork 1
/
json-par-keymap.el
452 lines (369 loc) · 15.3 KB
/
json-par-keymap.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
;;; json-par-kemap.el --- Keymap helpers for JSON Par mode -*- lexical-binding: t -*-
;; Copyright (C) 2021 taku0
;;
;; Author: taku0 (http://github.com/taku0)
;; URL: https://github.com/taku0/json-par
;; 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:
;; Helper functions for keymaps for JSON Par mode.
;;; Code:
(require 'cl-lib)
(require 'json-par-lexer)
(defvar json-par-mode)
;; `skk-pre-command' clears partial kana inputs unless `this-command' is in
;; `skk-kana-cleanup-command-list'.
;;
;; We must defer to invoke `skk-pre-command' until commands defined by
;; `json-par-define-special-key-command' set `this-command'.
;;
;; We accomplish this by adding an advice to `skk-pre-command'.
;;
;; We also add a function to `skk-load-hook' to add advice when SKK is loaded
;; after JSON Par mode is enabled.
(defvar json-par--deferred-pre-command-hook-functions '(skk-pre-command)
"Functions in `pre-command-hooks' that should be deferred by JSON Par mode.
Those functions are invoked by commands defined by
`json-par-define-special-key-command'.")
(defvar json-par--hooks-to-install-advices-to-deferred-hook-functions
'(skk-load-hook)
"List of hooks to add `json-par--install-advice-to-deferred-hook-functions'.")
(defun json-par-define-special-key (keymap key command &optional special-p)
"Define KEY as wrapped version of COMMAND in KEYMAP.
COMMAND is wrapped with `json-par-define-special-key-command' with SPECIAL-P."
(define-key keymap key
(json-par-define-special-key-command command special-p)))
(defun json-par-define-special-key-command (command &optional special-p)
"Define a command that invokes COMMAND when SPECIAL-P return non-nil.
Otherwise, it calls `self-insert-command' interactively.
It has a name COMMAND-if-special.
If SPECIAL-P is nil, `json-par--special-p' is used."
(unless special-p
(setq special-p #'json-par--special-p))
(let ((function-name (intern (concat (symbol-name command) "-if-special"))))
(put function-name 'json-par--special-key-command t)
(defalias function-name
(lambda ()
(interactive)
(let ((actual-command
(if (funcall special-p)
command
(let ((json-par-mode nil))
(or (key-binding (this-single-command-keys))
#'undefined)))))
(setq this-command actual-command)
(dolist (fun json-par--deferred-pre-command-hook-functions)
(when (member fun pre-command-hook)
(funcall fun)))
(call-interactively actual-command)))
(concat "Call `" (symbol-name command) "' interactively if the buffer is "
"special state.\n"
"\n"
"Call `self-insert-command' interactively otherwise."))
function-name))
(defun json-par--special-p (&optional parser-state)
"Return non-nil if the current buffer is \"special state\".
The current buffer is \"special state\" if and only if:
- the region is active, or
- the point is not in a string or a comment.
JSON Par mode interprets ordinal keys as a command like the vi editor if the
buffer is special state. Otherwise, it inserts keys as is.
If PARSER-STATE is given, it is used instead of (syntax-ppss).
If PARSER-STATE is a number or a marker, use that position for (syntax-ppss)."
(or
(use-region-p)
(not (json-par--string-like-beginning-position parser-state))))
(defun json-par--special-but-not-number-p (&optional parser-state)
"Return non-nil if the current buffer is \"special state\" for number keys.
Return non-nil if and only if:
- the region is active, or
- the point is not in a string or a comment.
- the point is not in a number or just before/after a number.
If PARSER-STATE is given, it is used instead of (syntax-ppss).
If PARSER-STATE is a number or a marker, use that position for (syntax-ppss)."
(and
(json-par--special-p parser-state)
(not (json-par-token-number-p (json-par--current-atom parser-state)))))
(defun json-par--special-for-e-p (&optional parser-state)
"Return non-nil if the current buffer is \"special state\" for key `e'.
Return non-nil if and only if:
- the region is active, or
- the point is not in a string or a comment.
- the point is not in a number, just before a number, or the number contains
a character `e'.
If PARSER-STATE is given, it is used instead of (syntax-ppss).
If PARSER-STATE is a number or a marker, use that position for (syntax-ppss)."
(and
(json-par--special-p parser-state)
(let ((current-atom (json-par--current-atom parser-state)))
(or
(not (json-par-token-number-p current-atom))
(eq (json-par-token-start current-atom) (point))
(cl-find ?e (json-par-token-text-no-properties current-atom))
(cl-find ?E (json-par-token-text-no-properties current-atom))))))
(defvar-local json-par--prefix-prompt nil
"The prompt for the temporary keymap of `json-par-prefix-command'.")
(defvar-local json-par--prefix-help-form nil
"The help form for the temporary keymap of `json-par-prefix-command'.")
(defvar-local json-par--prefix-keymap nil
"The keymap installed by `json-par-prefix-command'.")
(defun json-par-prefix-command (arg prompt prefix-help-form keymap)
"Install a temporary keymap KEYMAP as a prefix command.
ARG is passed to the commands bound in the keymap.
PROMPT is shown as a message if given.
PREFIX-HELP-FORM is a help form for the keymap and shown if the user hits \"?\"
key or other help keys."
(setq prefix-arg arg)
(setq json-par--prefix-prompt prompt)
(when prompt
(message prompt
(if prefix-arg
(concat
" ("
(prin1-to-string prefix-arg)
")")
"")))
(setq json-par--prefix-help-form prefix-help-form)
(setq json-par--prefix-keymap keymap)
(set-transient-map keymap))
(defun json-par-prefix-universal-argument (arg)
"Start prefix argument while reading key after `json-par-prefix-command'.
See `universal-argument' and `universal-argument-more' for details and ARG."
(interactive "P")
(json-par-prefix-command
(cond
((null arg)
(list 4))
((consp arg)
(list (* 4 (car arg))))
((eq arg '-)
(list -4))
(t
arg))
json-par--prefix-prompt
json-par--prefix-help-form
json-par--prefix-keymap))
(defun json-par-prefix-digit-argument (arg event)
"Update prefix argument while reading key after `json-par-prefix-command'.
ARG is the current prefix argument.
EVENT is the event triggered this command.
See `digit-argument' for details."
(interactive
(list
current-prefix-arg
last-command-event))
;; This command may be called with n, C-n, M-n, or C-M-n, where n is a digit.
(let* ((base-key (event-basic-type event))
(number (- base-key ?0))
(new-arg (cond
((integerp arg)
(+
(* arg 10)
(if (< arg 0) (- number) number)))
((eq arg '-)
(if (zerop number)
'-
(- number)))
;; C-u C-2 is 2, not 8.
(t
number))))
(json-par-prefix-command
new-arg
json-par--prefix-prompt
json-par--prefix-help-form
json-par--prefix-keymap)))
(defun json-par-prefix-negative-argument (arg)
"Update prefix argument while reading key after `json-par-prefix-command'.
ARG is the current prefix argument.
See `negative-argument' for details."
(interactive "P")
(let* ((new-arg (cond
((integerp arg) (- arg))
;; C-- C-- C-- is '-
((eq arg '-) nil)
;; C-u C-- is '-, not -4
(t '-))))
(json-par-prefix-command
new-arg
json-par--prefix-prompt
json-par--prefix-help-form
json-par--prefix-keymap)))
(defun json-par-prefix-default ()
"Handle unknown event for `json-par-prefix-command'.
If the event is `C-g', call `keyboard-quit'.
If the event is `help-char', \"?\", or an element of `help-event-list', show a
help message if any.
Otherwise, signal `undefined' error."
(interactive)
(cond
;; Keyboard quit
((eq last-command-event ?\C-g)
(keyboard-quit))
;; Help
((and (or (eq last-command-event help-char)
(eq last-command-event ??)
(memq last-command-event help-event-list))
json-par--prefix-help-form)
(let ((help-form json-par--prefix-help-form))
(help-form-show)))
;; Others; undefined event
(t
(undefined))))
(cl-defun json-par--menu-item (command &key label help prefixes)
"Return a menu item for COMMAND suitable for `easy-menu-define'.
LABEL and HELP are used as the label and help if given.
If LABEL is omitted, the name of the COMMAND without the \"json-par-\" prefix
is used.
If HELP is omitted, the first line of the docstring is used.
If PREFIXES is given, it should be a list of pairs (PREFIX-COMMAND
. PREFIX-KEYMAP) and PREFIX-COMMAND is a prefix command like
`json-par-clone-prefix-command' and PREFIX-KEYMAP is a prefix keymap like
`json-par-clone-prefix-map'. It is used to show key sequence on the menu item."
(unless label
(setq label (mapconcat
(lambda (word)
(if (member word '("of" "or" "in" "to"))
word
(capitalize word)))
(split-string
(replace-regexp-in-string
"json-par-"
""
(symbol-name command))
"-")
" ")))
(unless help
(setq help (car (split-string (documentation command) "\n"))))
(let* ((special-key-command
(intern (concat (symbol-name command) "-if-special")))
(prefix-key-string (mapconcat
(lambda (prefix)
(let ((prefix-command (car prefix))
(prefix-keymap (cdr prefix)))
(concat
"\\["
(symbol-name prefix-command)
"]"
"\\<"
(symbol-name prefix-keymap)
">")))
prefixes
""))
(keys (concat prefix-key-string
"\\["
(symbol-name (if (fboundp special-key-command)
special-key-command
command))
"]")))
(vector label command :help help :keys keys)))
(defun json-par--format-command-help (keymap)
"Return description of KEYMAP.
It contains the key sequence, command name, and the first line of the docstring
of the command for each bindings in keymap.
`json-par-prefix-default' is excluded from the description."
(apply
#'concat
(mapcar
(lambda (pair)
(let ((key-sequence (car pair))
(binding (cdr pair)))
(if (eq binding 'json-par-prefix-default)
""
(format
"%s:\n %s\n\n"
(key-description key-sequence)
(cond
((null binding)
"nil")
((arrayp binding)
(concat "keyboard macro: " (key-description binding)))
((commandp binding)
(format "`%s'\n %s"
(if (symbolp binding)
(symbol-name binding)
"(lambda)")
(car (split-string
(or (documentation binding)
"(not documented)")
"\n")))))))))
(json-par--flatten-keymap keymap))))
(defun json-par--flatten-keymap (keymap &optional tail prefix seen-keymaps)
"Return a list of key sequences and commands in KEYMAP.
Return a list of cons (KEY-SEQUENCE . BINDING) where KEY-SEQUENCE is a vector of
input events and BINDING is one of nil, vector of input events (keyboard macro),
or command (symbol or lambda).
Nested keymap (prefix commands) are flatten.
TAIL, PREFIX, and SEEN-KEYMAPS are internal variable for recursion.
TAIL is the accumulator for the result; the list of cons are prepended to it.
PREFIX is the key sequence so far. It is a list in reversed order.
SEEN-KEYMAPS is a hash table to stop infinite recursion."
(unless seen-keymaps
(setq seen-keymaps (make-hash-table :test 'eq)))
(if (gethash keymap seen-keymaps)
tail
(puthash keymap t seen-keymaps)
(map-keymap
(lambda (event binding)
(setq tail (json-par--flatten-keymap-1
event
binding
tail
prefix
seen-keymaps)))
keymap)
tail))
(defun json-par--flatten-keymap-1 (event binding tail prefix seen-keymaps)
"Process a mapping from EVENT to BINDING and return the updated list TAIL.
See `json-par--flatten-keymap' for TAIL, PREFIX, and SEEN-KEYMAPS."
(cond
;; Terminal binding
((or (null binding)
(commandp binding)
(arrayp binding))
(let ((key-sequence (vconcat (reverse (cons event prefix)))))
(cons (cons key-sequence binding) tail)))
;; Nested keymap
((keymapp binding)
(json-par--flatten-keymap binding tail (cons event prefix) seen-keymaps))
;; Indirection
((symbolp binding)
(if (symbol-function binding)
(json-par--flatten-keymap-1
event
(symbol-function binding)
tail
prefix
seen-keymaps)
tail))
;; Others; ignored
(t tail)))
(defun json-par--supress-pre-command-hooks (fun &rest args)
"Call FUN with ARGS only if `this-command' would call it later."
(when (or (null json-par-mode)
(null (get this-command 'json-par--special-key-command)))
(apply fun args)))
(defun json-par--install-advice-to-deferred-hook-functions ()
"Add an advice around pre command hook functions to defer."
(dolist (fun json-par--deferred-pre-command-hook-functions)
(when (fboundp fun)
(advice-add fun :around #'json-par--supress-pre-command-hooks))))
(defun json-par--remove-advice-from-deferred-hook-functions ()
"Remove the advice from pre command hook functions to defer."
(dolist (fun json-par--deferred-pre-command-hook-functions)
(when (fboundp fun)
(advice-remove fun #'json-par--supress-pre-command-hooks))))
(defun json-par--add-install-advice-to-deferred-hook-functions-to-hooks ()
"Add `json-par--install-advice-to-deferred-hook-functions' to hooks."
(dolist (hook json-par--hooks-to-install-advices-to-deferred-hook-functions)
(add-hook hook #'json-par--install-advice-to-deferred-hook-functions)))
(provide 'json-par-keymap)
;;; json-par-keymap.el ends here