Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/libedit/keymacro.c
39477 views
1
/* $NetBSD: keymacro.c,v 1.25 2025/01/03 00:40:08 rillig Exp $ */
2
3
/*-
4
* Copyright (c) 1992, 1993
5
* The Regents of the University of California. All rights reserved.
6
*
7
* This code is derived from software contributed to Berkeley by
8
* Christos Zoulas of Cornell University.
9
*
10
* Redistribution and use in source and binary forms, with or without
11
* modification, are permitted provided that the following conditions
12
* are met:
13
* 1. Redistributions of source code must retain the above copyright
14
* notice, this list of conditions and the following disclaimer.
15
* 2. Redistributions in binary form must reproduce the above copyright
16
* notice, this list of conditions and the following disclaimer in the
17
* documentation and/or other materials provided with the distribution.
18
* 3. Neither the name of the University nor the names of its contributors
19
* may be used to endorse or promote products derived from this software
20
* without specific prior written permission.
21
*
22
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32
* SUCH DAMAGE.
33
*/
34
35
#include "config.h"
36
#if !defined(lint) && !defined(SCCSID)
37
#if 0
38
static char sccsid[] = "@(#)key.c 8.1 (Berkeley) 6/4/93";
39
#else
40
__RCSID("$NetBSD: keymacro.c,v 1.25 2025/01/03 00:40:08 rillig Exp $");
41
#endif
42
#endif /* not lint && not SCCSID */
43
44
/*
45
* keymacro.c: This module contains the procedures for maintaining
46
* the extended-key map.
47
*
48
* An extended-key (key) is a sequence of keystrokes introduced
49
* with a sequence introducer and consisting of an arbitrary
50
* number of characters. This module maintains a map (the
51
* el->el_keymacro.map)
52
* to convert these extended-key sequences into input strs
53
* (XK_STR) or editor functions (XK_CMD).
54
*
55
* Warning:
56
* If key is a substr of some other keys, then the longer
57
* keys are lost!! That is, if the keys "abcd" and "abcef"
58
* are in el->el_keymacro.map, adding the key "abc" will cause
59
* the first two definitions to be lost.
60
*
61
* Restrictions:
62
* -------------
63
* 1) It is not possible to have one key that is a
64
* substr of another.
65
*/
66
#include <stdlib.h>
67
#include <string.h>
68
69
#include "el.h"
70
#include "fcns.h"
71
72
/*
73
* The Nodes of the el->el_keymacro.map. The el->el_keymacro.map is a
74
* linked list of these node elements
75
*/
76
struct keymacro_node_t {
77
wchar_t ch; /* single character of key */
78
int type; /* node type */
79
keymacro_value_t val; /* command code or pointer to str, */
80
/* if this is a leaf */
81
struct keymacro_node_t *next; /* ptr to next char of this key */
82
struct keymacro_node_t *sibling;/* ptr to another key with same prefix*/
83
};
84
85
static int node_trav(EditLine *, keymacro_node_t *, wchar_t *,
86
keymacro_value_t *);
87
static int node__try(EditLine *, keymacro_node_t *,
88
const wchar_t *, keymacro_value_t *, int);
89
static keymacro_node_t *node__get(wint_t);
90
static void node__free(keymacro_node_t *);
91
static void node__put(EditLine *, keymacro_node_t *);
92
static int node__delete(EditLine *, keymacro_node_t **,
93
const wchar_t *);
94
static int node_lookup(EditLine *, const wchar_t *,
95
keymacro_node_t *, size_t);
96
static int node_enum(EditLine *, keymacro_node_t *, size_t);
97
98
#define KEY_BUFSIZ EL_BUFSIZ
99
100
101
/* keymacro_init():
102
* Initialize the key maps
103
*/
104
libedit_private int
105
keymacro_init(EditLine *el)
106
{
107
108
el->el_keymacro.buf = el_calloc(KEY_BUFSIZ,
109
sizeof(*el->el_keymacro.buf));
110
if (el->el_keymacro.buf == NULL)
111
return -1;
112
el->el_keymacro.map = NULL;
113
keymacro_reset(el);
114
return 0;
115
}
116
117
/* keymacro_end():
118
* Free the key maps
119
*/
120
libedit_private void
121
keymacro_end(EditLine *el)
122
{
123
124
el_free(el->el_keymacro.buf);
125
el->el_keymacro.buf = NULL;
126
node__free(el->el_keymacro.map);
127
}
128
129
130
/* keymacro_map_cmd():
131
* Associate cmd with a key value
132
*/
133
libedit_private keymacro_value_t *
134
keymacro_map_cmd(EditLine *el, int cmd)
135
{
136
137
el->el_keymacro.val.cmd = (el_action_t) cmd;
138
return &el->el_keymacro.val;
139
}
140
141
142
/* keymacro_map_str():
143
* Associate str with a key value
144
*/
145
libedit_private keymacro_value_t *
146
keymacro_map_str(EditLine *el, wchar_t *str)
147
{
148
149
el->el_keymacro.val.str = str;
150
return &el->el_keymacro.val;
151
}
152
153
154
/* keymacro_reset():
155
* Takes all nodes on el->el_keymacro.map and puts them on free list.
156
* Then initializes el->el_keymacro.map with arrow keys
157
* [Always bind the ansi arrow keys?]
158
*/
159
libedit_private void
160
keymacro_reset(EditLine *el)
161
{
162
163
node__put(el, el->el_keymacro.map);
164
el->el_keymacro.map = NULL;
165
return;
166
}
167
168
169
/* keymacro_get():
170
* Calls the recursive function with entry point el->el_keymacro.map
171
* Looks up *ch in map and then reads characters until a
172
* complete match is found or a mismatch occurs. Returns the
173
* type of the match found (XK_STR or XK_CMD).
174
* Returns NULL in val.str and XK_STR for no match.
175
* Returns XK_NOD for end of file or read error.
176
* The last character read is returned in *ch.
177
*/
178
libedit_private int
179
keymacro_get(EditLine *el, wchar_t *ch, keymacro_value_t *val)
180
{
181
182
return node_trav(el, el->el_keymacro.map, ch, val);
183
}
184
185
186
/* keymacro_add():
187
* Adds key to the el->el_keymacro.map and associates the value in
188
* val with it. If key is already is in el->el_keymacro.map, the new
189
* code is applied to the existing key. Ntype specifies if code is a
190
* command, an out str or a unix command.
191
*/
192
libedit_private void
193
keymacro_add(EditLine *el, const wchar_t *key, keymacro_value_t *val,
194
int ntype)
195
{
196
197
if (key[0] == '\0') {
198
(void) fprintf(el->el_errfile,
199
"keymacro_add: Null extended-key not allowed.\n");
200
return;
201
}
202
if (ntype == XK_CMD && val->cmd == ED_SEQUENCE_LEAD_IN) {
203
(void) fprintf(el->el_errfile,
204
"keymacro_add: sequence-lead-in command not allowed\n");
205
return;
206
}
207
if (el->el_keymacro.map == NULL)
208
/* tree is initially empty. Set up new node to match key[0] */
209
el->el_keymacro.map = node__get(key[0]);
210
/* it is properly initialized */
211
212
/* Now recurse through el->el_keymacro.map */
213
(void) node__try(el, el->el_keymacro.map, key, val, ntype);
214
return;
215
}
216
217
218
/* keymacro_clear():
219
*
220
*/
221
libedit_private void
222
keymacro_clear(EditLine *el, el_action_t *map, const wchar_t *in)
223
{
224
if (*in > N_KEYS) /* can't be in the map */
225
return;
226
if ((map[(unsigned char)*in] == ED_SEQUENCE_LEAD_IN) &&
227
((map == el->el_map.key &&
228
el->el_map.alt[(unsigned char)*in] != ED_SEQUENCE_LEAD_IN) ||
229
(map == el->el_map.alt &&
230
el->el_map.key[(unsigned char)*in] != ED_SEQUENCE_LEAD_IN)))
231
(void) keymacro_delete(el, in);
232
}
233
234
235
/* keymacro_delete():
236
* Delete the key and all longer keys staring with key, if
237
* they exists.
238
*/
239
libedit_private int
240
keymacro_delete(EditLine *el, const wchar_t *key)
241
{
242
243
if (key[0] == '\0') {
244
(void) fprintf(el->el_errfile,
245
"keymacro_delete: Null extended-key not allowed.\n");
246
return -1;
247
}
248
if (el->el_keymacro.map == NULL)
249
return 0;
250
251
(void) node__delete(el, &el->el_keymacro.map, key);
252
return 0;
253
}
254
255
256
/* keymacro_print():
257
* Print the binding associated with key key.
258
* Print entire el->el_keymacro.map if null
259
*/
260
libedit_private void
261
keymacro_print(EditLine *el, const wchar_t *key)
262
{
263
264
/* do nothing if el->el_keymacro.map is empty and null key specified */
265
if (el->el_keymacro.map == NULL && *key == 0)
266
return;
267
268
el->el_keymacro.buf[0] = '"';
269
if (node_lookup(el, key, el->el_keymacro.map, (size_t)1) <= -1)
270
/* key is not bound */
271
(void) fprintf(el->el_errfile, "Unbound extended key \"%ls"
272
"\"\n", key);
273
return;
274
}
275
276
277
/* node_trav():
278
* recursively traverses node in tree until match or mismatch is
279
* found. May read in more characters.
280
*/
281
static int
282
node_trav(EditLine *el, keymacro_node_t *ptr, wchar_t *ch,
283
keymacro_value_t *val)
284
{
285
286
if (ptr->ch == *ch) {
287
/* match found */
288
if (ptr->next) {
289
/* key not complete so get next char */
290
if (el_wgetc(el, ch) != 1)
291
return XK_NOD;
292
return node_trav(el, ptr->next, ch, val);
293
} else {
294
*val = ptr->val;
295
if (ptr->type != XK_CMD)
296
*ch = '\0';
297
return ptr->type;
298
}
299
} else {
300
/* no match found here */
301
if (ptr->sibling) {
302
/* try next sibling */
303
return node_trav(el, ptr->sibling, ch, val);
304
} else {
305
/* no next sibling -- mismatch */
306
val->str = NULL;
307
return XK_STR;
308
}
309
}
310
}
311
312
313
/* node__try():
314
* Find a node that matches *str or allocate a new one
315
*/
316
static int
317
node__try(EditLine *el, keymacro_node_t *ptr, const wchar_t *str,
318
keymacro_value_t *val, int ntype)
319
{
320
321
if (ptr->ch != *str) {
322
keymacro_node_t *xm;
323
324
for (xm = ptr; xm->sibling != NULL; xm = xm->sibling)
325
if (xm->sibling->ch == *str)
326
break;
327
if (xm->sibling == NULL)
328
xm->sibling = node__get(*str); /* setup new node */
329
ptr = xm->sibling;
330
}
331
if (*++str == '\0') {
332
/* we're there */
333
if (ptr->next != NULL) {
334
node__put(el, ptr->next);
335
/* lose longer keys with this prefix */
336
ptr->next = NULL;
337
}
338
switch (ptr->type) {
339
case XK_CMD:
340
case XK_NOD:
341
break;
342
case XK_STR:
343
if (ptr->val.str)
344
el_free(ptr->val.str);
345
break;
346
default:
347
EL_ABORT((el->el_errfile, "Bad XK_ type %d\n",
348
ptr->type));
349
}
350
351
switch (ptr->type = ntype) {
352
case XK_CMD:
353
ptr->val = *val;
354
break;
355
case XK_STR:
356
if ((ptr->val.str = wcsdup(val->str)) == NULL)
357
return -1;
358
break;
359
default:
360
EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", ntype));
361
}
362
} else {
363
/* still more chars to go */
364
if (ptr->next == NULL)
365
ptr->next = node__get(*str); /* setup new node */
366
(void) node__try(el, ptr->next, str, val, ntype);
367
}
368
return 0;
369
}
370
371
372
/* node__delete():
373
* Delete node that matches str
374
*/
375
static int
376
node__delete(EditLine *el, keymacro_node_t **inptr, const wchar_t *str)
377
{
378
keymacro_node_t *ptr;
379
keymacro_node_t *prev_ptr = NULL;
380
381
ptr = *inptr;
382
383
if (ptr->ch != *str) {
384
keymacro_node_t *xm;
385
386
for (xm = ptr; xm->sibling != NULL; xm = xm->sibling)
387
if (xm->sibling->ch == *str)
388
break;
389
if (xm->sibling == NULL)
390
return 0;
391
prev_ptr = xm;
392
ptr = xm->sibling;
393
}
394
if (*++str == '\0') {
395
/* we're there */
396
if (prev_ptr == NULL)
397
*inptr = ptr->sibling;
398
else
399
prev_ptr->sibling = ptr->sibling;
400
ptr->sibling = NULL;
401
node__put(el, ptr);
402
return 1;
403
} else if (ptr->next != NULL &&
404
node__delete(el, &ptr->next, str) == 1) {
405
if (ptr->next != NULL)
406
return 0;
407
if (prev_ptr == NULL)
408
*inptr = ptr->sibling;
409
else
410
prev_ptr->sibling = ptr->sibling;
411
ptr->sibling = NULL;
412
node__put(el, ptr);
413
return 1;
414
} else {
415
return 0;
416
}
417
}
418
419
420
/* node__put():
421
* Puts a tree of nodes onto free list using free(3).
422
*/
423
static void
424
node__put(EditLine *el, keymacro_node_t *ptr)
425
{
426
if (ptr == NULL)
427
return;
428
429
if (ptr->next != NULL) {
430
node__put(el, ptr->next);
431
ptr->next = NULL;
432
}
433
node__put(el, ptr->sibling);
434
435
switch (ptr->type) {
436
case XK_CMD:
437
case XK_NOD:
438
break;
439
case XK_STR:
440
if (ptr->val.str != NULL)
441
el_free(ptr->val.str);
442
break;
443
default:
444
EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", ptr->type));
445
}
446
el_free(ptr);
447
}
448
449
450
/* node__get():
451
* Returns pointer to a keymacro_node_t for ch.
452
*/
453
static keymacro_node_t *
454
node__get(wint_t ch)
455
{
456
keymacro_node_t *ptr;
457
458
ptr = el_malloc(sizeof(*ptr));
459
if (ptr == NULL)
460
return NULL;
461
ptr->ch = ch;
462
ptr->type = XK_NOD;
463
ptr->val.str = NULL;
464
ptr->next = NULL;
465
ptr->sibling = NULL;
466
return ptr;
467
}
468
469
static void
470
node__free(keymacro_node_t *k)
471
{
472
if (k == NULL)
473
return;
474
node__free(k->sibling);
475
node__free(k->next);
476
el_free(k);
477
}
478
479
/* node_lookup():
480
* look for the str starting at node ptr.
481
* Print if last node
482
*/
483
static int
484
node_lookup(EditLine *el, const wchar_t *str, keymacro_node_t *ptr,
485
size_t cnt)
486
{
487
ssize_t used;
488
489
if (ptr == NULL)
490
return -1; /* cannot have null ptr */
491
492
if (!str || *str == 0) {
493
/* no more chars in str. node_enum from here. */
494
(void) node_enum(el, ptr, cnt);
495
return 0;
496
} else {
497
/* If match put this char into el->el_keymacro.buf. Recurse */
498
if (ptr->ch == *str) {
499
/* match found */
500
used = ct_visual_char(el->el_keymacro.buf + cnt,
501
KEY_BUFSIZ - cnt, ptr->ch);
502
if (used == -1)
503
return -1; /* ran out of buffer space */
504
if (ptr->next != NULL)
505
/* not yet at leaf */
506
return (node_lookup(el, str + 1, ptr->next,
507
(size_t)used + cnt));
508
else {
509
/* next node is null so key should be complete */
510
if (str[1] == 0) {
511
size_t px = cnt + (size_t)used;
512
el->el_keymacro.buf[px] = '"';
513
el->el_keymacro.buf[px + 1] = '\0';
514
keymacro_kprint(el, el->el_keymacro.buf,
515
&ptr->val, ptr->type);
516
return 0;
517
} else
518
return -1;
519
/* mismatch -- str still has chars */
520
}
521
} else {
522
/* no match found try sibling */
523
if (ptr->sibling)
524
return (node_lookup(el, str, ptr->sibling,
525
cnt));
526
else
527
return -1;
528
}
529
}
530
}
531
532
533
/* node_enum():
534
* Traverse the node printing the characters it is bound in buffer
535
*/
536
static int
537
node_enum(EditLine *el, keymacro_node_t *ptr, size_t cnt)
538
{
539
ssize_t used;
540
541
if (cnt >= KEY_BUFSIZ - 5) { /* buffer too small */
542
el->el_keymacro.buf[++cnt] = '"';
543
el->el_keymacro.buf[++cnt] = '\0';
544
(void) fprintf(el->el_errfile,
545
"Some extended keys too long for internal print buffer");
546
(void) fprintf(el->el_errfile, " \"%ls...\"\n",
547
el->el_keymacro.buf);
548
return 0;
549
}
550
if (ptr == NULL) {
551
#ifdef DEBUG_EDIT
552
(void) fprintf(el->el_errfile,
553
"node_enum: BUG!! Null ptr passed\n!");
554
#endif
555
return -1;
556
}
557
/* put this char at end of str */
558
used = ct_visual_char(el->el_keymacro.buf + cnt, KEY_BUFSIZ - cnt,
559
ptr->ch);
560
if (ptr->next == NULL) {
561
/* print this key and function */
562
el->el_keymacro.buf[cnt + (size_t)used ] = '"';
563
el->el_keymacro.buf[cnt + (size_t)used + 1] = '\0';
564
keymacro_kprint(el, el->el_keymacro.buf, &ptr->val, ptr->type);
565
} else
566
(void) node_enum(el, ptr->next, cnt + (size_t)used);
567
568
/* go to sibling if there is one */
569
if (ptr->sibling)
570
(void) node_enum(el, ptr->sibling, cnt);
571
return 0;
572
}
573
574
575
/* keymacro_kprint():
576
* Print the specified key and its associated
577
* function specified by val
578
*/
579
libedit_private void
580
keymacro_kprint(EditLine *el, const wchar_t *key, keymacro_value_t *val,
581
int ntype)
582
{
583
el_bindings_t *fp;
584
char unparsbuf[EL_BUFSIZ];
585
static const char fmt[] = "%-15s-> %s\n";
586
587
if (val != NULL)
588
switch (ntype) {
589
case XK_STR:
590
(void) keymacro__decode_str(val->str, unparsbuf,
591
sizeof(unparsbuf),
592
ntype == XK_STR ? "\"\"" : "[]");
593
(void) fprintf(el->el_outfile, fmt,
594
ct_encode_string(key, &el->el_scratch), unparsbuf);
595
break;
596
case XK_CMD:
597
for (fp = el->el_map.help; fp->name; fp++)
598
if (val->cmd == fp->func) {
599
wcstombs(unparsbuf, fp->name, sizeof(unparsbuf));
600
unparsbuf[sizeof(unparsbuf) -1] = '\0';
601
(void) fprintf(el->el_outfile, fmt,
602
ct_encode_string(key, &el->el_scratch), unparsbuf);
603
break;
604
}
605
#ifdef DEBUG_KEY
606
if (fp->name == NULL)
607
(void) fprintf(el->el_outfile,
608
"BUG! Command not found.\n");
609
#endif
610
611
break;
612
default:
613
EL_ABORT((el->el_errfile, "Bad XK_ type %d\n", ntype));
614
}
615
else
616
(void) fprintf(el->el_outfile, fmt, ct_encode_string(key,
617
&el->el_scratch), "no input");
618
}
619
620
621
#define ADDC(c) \
622
if (b < eb) \
623
*b++ = c; \
624
else \
625
b++
626
/* keymacro__decode_str():
627
* Make a printable version of the ey
628
*/
629
libedit_private size_t
630
keymacro__decode_str(const wchar_t *str, char *buf, size_t len,
631
const char *sep)
632
{
633
char *b = buf, *eb = b + len;
634
const wchar_t *p;
635
636
b = buf;
637
if (sep[0] != '\0') {
638
ADDC(sep[0]);
639
}
640
if (*str == '\0') {
641
ADDC('^');
642
ADDC('@');
643
goto add_endsep;
644
}
645
for (p = str; *p != 0; p++) {
646
wchar_t dbuf[VISUAL_WIDTH_MAX];
647
wchar_t *p2 = dbuf;
648
ssize_t l = ct_visual_char(dbuf, VISUAL_WIDTH_MAX, *p);
649
while (l-- > 0) {
650
ssize_t n = ct_encode_char(b, (size_t)(eb - b), *p2++);
651
if (n == -1) /* ran out of space */
652
goto add_endsep;
653
else
654
b += n;
655
}
656
}
657
add_endsep:
658
if (sep[0] != '\0' && sep[1] != '\0') {
659
ADDC(sep[1]);
660
}
661
ADDC('\0');
662
if ((size_t)(b - buf) >= len)
663
buf[len - 1] = '\0';
664
return (size_t)(b - buf);
665
}
666
667