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