xref: /openbsd/usr.bin/mg/extend.c (revision e5dd7070)
1 /*	$OpenBSD: extend.c,v 1.71 2019/07/18 15:52:11 lum Exp $	*/
2 /* This file is in the public domain. */
3 
4 /*
5  *	Extended (M-x) commands, rebinding, and	startup file processing.
6  */
7 
8 #include <sys/queue.h>
9 #include <sys/types.h>
10 #include <regex.h>
11 #include <ctype.h>
12 #include <limits.h>
13 #include <signal.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 
18 #include "chrdef.h"
19 #include "def.h"
20 #include "funmap.h"
21 #include "kbd.h"
22 #include "key.h"
23 #include "macro.h"
24 
25 static int	 remap(KEYMAP *, int, PF, KEYMAP *);
26 static KEYMAP	*reallocmap(KEYMAP *);
27 static void	 fixmap(KEYMAP *, KEYMAP *, KEYMAP *);
28 static int	 dobind(KEYMAP *, const char *, int);
29 static char	*parsetoken(char *);
30 static int	 bindkey(KEYMAP **, const char *, KCHAR *, int);
31 
32 /*
33  * Insert a string, mainly for use from macros (created by selfinsert).
34  */
35 /* ARGSUSED */
36 int
37 insert(int f, int n)
38 {
39 	char	 buf[BUFSIZE], *bufp, *cp;
40 	int	 count, c;
41 
42 	if (inmacro) {
43 		while (--n >= 0) {
44 			for (count = 0; count < maclcur->l_used; count++) {
45 				if ((((c = maclcur->l_text[count]) == '\n')
46 				    ? lnewline() : linsert(1, c)) != TRUE)
47 					return (FALSE);
48 			}
49 		}
50 		maclcur = maclcur->l_fp;
51 		return (TRUE);
52 	}
53 	if (n == 1)
54 		/* CFINS means selfinsert can tack on the end */
55 		thisflag |= CFINS;
56 
57 	if ((bufp = eread("Insert: ", buf, sizeof(buf), EFNEW)) == NULL)
58 		return (ABORT);
59 	else if (bufp[0] == '\0')
60 		return (FALSE);
61 	while (--n >= 0) {
62 		cp = buf;
63 		while (*cp) {
64 			if (((*cp == '\n') ? lnewline() : linsert(1, *cp))
65 			    != TRUE)
66 				return (FALSE);
67 			cp++;
68 		}
69 	}
70 	return (TRUE);
71 }
72 
73 /*
74  * Bind a key to a function.  Cases range from the trivial (replacing an
75  * existing binding) to the extremely complex (creating a new prefix in a
76  * map_element that already has one, so the map_element must be split,
77  * but the keymap doesn't have enough room for another map_element, so
78  * the keymap is reallocated).	No attempt is made to reclaim space no
79  * longer used, if this is a problem flags must be added to indicate
80  * malloced versus static storage in both keymaps and map_elements.
81  * Structure assignments would come in real handy, but K&R based compilers
82  * don't have them.  Care is taken so running out of memory will leave
83  * the keymap in a usable state.
84  * Parameters are:
85  * curmap:  	pointer to the map being changed
86  * c:		character being changed
87  * funct: 	function being changed to
88  * pref_map: 	if funct==NULL, map to bind to or NULL for new
89  */
90 static int
91 remap(KEYMAP *curmap, int c, PF funct, KEYMAP *pref_map)
92 {
93 	int		 i, n1, n2, nold;
94 	KEYMAP		*mp, *newmap;
95 	PF		*pfp;
96 	struct map_element	*mep;
97 
98 	if (ele >= &curmap->map_element[curmap->map_num] || c < ele->k_base) {
99 		if (ele > &curmap->map_element[0] && (funct != NULL ||
100 		    (ele - 1)->k_prefmap == NULL))
101 			n1 = c - (ele - 1)->k_num;
102 		else
103 			n1 = HUGE;
104 		if (ele < &curmap->map_element[curmap->map_num] &&
105 		    (funct != NULL || ele->k_prefmap == NULL))
106 			n2 = ele->k_base - c;
107 		else
108 			n2 = HUGE;
109 		if (n1 <= MAPELEDEF && n1 <= n2) {
110 			ele--;
111 			if ((pfp = calloc(c - ele->k_base + 1,
112 			    sizeof(PF))) == NULL)
113 				return (dobeep_msg("Out of memory"));
114 
115 			nold = ele->k_num - ele->k_base + 1;
116 			for (i = 0; i < nold; i++)
117 				pfp[i] = ele->k_funcp[i];
118 			while (--n1)
119 				pfp[i++] = curmap->map_default;
120 			pfp[i] = funct;
121 			ele->k_num = c;
122 			ele->k_funcp = pfp;
123 		} else if (n2 <= MAPELEDEF) {
124 			if ((pfp = calloc(ele->k_num - c + 1,
125 			    sizeof(PF))) == NULL)
126 				return (dobeep_msg("Out of memory"));
127 
128 			nold = ele->k_num - ele->k_base + 1;
129 			for (i = 0; i < nold; i++)
130 				pfp[i + n2] = ele->k_funcp[i];
131 			while (--n2)
132 				pfp[n2] = curmap->map_default;
133 			pfp[0] = funct;
134 			ele->k_base = c;
135 			ele->k_funcp = pfp;
136 		} else {
137 			if (curmap->map_num >= curmap->map_max) {
138 				if ((newmap = reallocmap(curmap)) == NULL)
139 					return (FALSE);
140 				curmap = newmap;
141 			}
142 			if ((pfp = malloc(sizeof(PF))) == NULL)
143 				return (dobeep_msg("Out of memory"));
144 
145 			pfp[0] = funct;
146 			for (mep = &curmap->map_element[curmap->map_num];
147 			    mep > ele; mep--) {
148 				mep->k_base = (mep - 1)->k_base;
149 				mep->k_num = (mep - 1)->k_num;
150 				mep->k_funcp = (mep - 1)->k_funcp;
151 				mep->k_prefmap = (mep - 1)->k_prefmap;
152 			}
153 			ele->k_base = c;
154 			ele->k_num = c;
155 			ele->k_funcp = pfp;
156 			ele->k_prefmap = NULL;
157 			curmap->map_num++;
158 		}
159 		if (funct == NULL) {
160 			if (pref_map != NULL)
161 				ele->k_prefmap = pref_map;
162 			else {
163 				if ((mp = malloc(sizeof(KEYMAP) +
164 				    (MAPINIT - 1) * sizeof(struct map_element))) == NULL) {
165 					(void)dobeep_msg("Out of memory");
166 					ele->k_funcp[c - ele->k_base] =
167 					    curmap->map_default;
168 					return (FALSE);
169 				}
170 				mp->map_num = 0;
171 				mp->map_max = MAPINIT;
172 				mp->map_default = rescan;
173 				ele->k_prefmap = mp;
174 			}
175 		}
176 	} else {
177 		n1 = c - ele->k_base;
178 		if (ele->k_funcp[n1] == funct && (funct != NULL ||
179 		    pref_map == NULL || pref_map == ele->k_prefmap))
180 			/* no change */
181 			return (TRUE);
182 		if (funct != NULL || ele->k_prefmap == NULL) {
183 			if (ele->k_funcp[n1] == NULL)
184 				ele->k_prefmap = NULL;
185 			/* easy case */
186 			ele->k_funcp[n1] = funct;
187 			if (funct == NULL) {
188 				if (pref_map != NULL)
189 					ele->k_prefmap = pref_map;
190 				else {
191 					if ((mp = malloc(sizeof(KEYMAP) +
192 					    (MAPINIT - 1) *
193 					    sizeof(struct map_element))) == NULL) {
194 						(void)dobeep_msg("Out of memory");
195 						ele->k_funcp[c - ele->k_base] =
196 						    curmap->map_default;
197 						return (FALSE);
198 					}
199 					mp->map_num = 0;
200 					mp->map_max = MAPINIT;
201 					mp->map_default = rescan;
202 					ele->k_prefmap = mp;
203 				}
204 			}
205 		} else {
206 			/*
207 			 * This case is the splits.
208 			 * Determine which side of the break c goes on
209 			 * 0 = after break; 1 = before break
210 			 */
211 			n2 = 1;
212 			for (i = 0; n2 && i < n1; i++)
213 				n2 &= ele->k_funcp[i] != NULL;
214 			if (curmap->map_num >= curmap->map_max) {
215 				if ((newmap = reallocmap(curmap)) == NULL)
216 					return (FALSE);
217 				curmap = newmap;
218 			}
219 			if ((pfp = calloc(ele->k_num - c + !n2,
220 			    sizeof(PF))) == NULL)
221 				return (dobeep_msg("Out of memory"));
222 
223 			ele->k_funcp[n1] = NULL;
224 			for (i = n1 + n2; i <= ele->k_num - ele->k_base; i++)
225 				pfp[i - n1 - n2] = ele->k_funcp[i];
226 			for (mep = &curmap->map_element[curmap->map_num];
227 			    mep > ele; mep--) {
228 				mep->k_base = (mep - 1)->k_base;
229 				mep->k_num = (mep - 1)->k_num;
230 				mep->k_funcp = (mep - 1)->k_funcp;
231 				mep->k_prefmap = (mep - 1)->k_prefmap;
232 			}
233 			ele->k_num = c - !n2;
234 			(ele + 1)->k_base = c + n2;
235 			(ele + 1)->k_funcp = pfp;
236 			ele += !n2;
237 			ele->k_prefmap = NULL;
238 			curmap->map_num++;
239 			if (pref_map == NULL) {
240 				if ((mp = malloc(sizeof(KEYMAP) + (MAPINIT - 1)
241 				    * sizeof(struct map_element))) == NULL) {
242 					(void)dobeep_msg("Out of memory");
243 					ele->k_funcp[c - ele->k_base] =
244 					    curmap->map_default;
245 					return (FALSE);
246 				}
247 				mp->map_num = 0;
248 				mp->map_max = MAPINIT;
249 				mp->map_default = rescan;
250 				ele->k_prefmap = mp;
251 			} else
252 				ele->k_prefmap = pref_map;
253 		}
254 	}
255 	return (TRUE);
256 }
257 
258 /*
259  * Reallocate a keymap. Returns NULL (without trashing the current map)
260  * on failure.
261  */
262 static KEYMAP *
263 reallocmap(KEYMAP *curmap)
264 {
265 	struct maps_s	*mps;
266 	KEYMAP	*mp;
267 	int	 i;
268 
269 	if (curmap->map_max > SHRT_MAX - MAPGROW) {
270 		(void)dobeep_msg("keymap too large");
271 		return (NULL);
272 	}
273 	if ((mp = malloc(sizeof(KEYMAP) + (curmap->map_max + (MAPGROW - 1)) *
274 	    sizeof(struct map_element))) == NULL) {
275 		(void)dobeep_msg("Out of memory");
276 		return (NULL);
277 	}
278 	mp->map_num = curmap->map_num;
279 	mp->map_max = curmap->map_max + MAPGROW;
280 	mp->map_default = curmap->map_default;
281 	for (i = curmap->map_num; i--;) {
282 		mp->map_element[i].k_base = curmap->map_element[i].k_base;
283 		mp->map_element[i].k_num = curmap->map_element[i].k_num;
284 		mp->map_element[i].k_funcp = curmap->map_element[i].k_funcp;
285 		mp->map_element[i].k_prefmap = curmap->map_element[i].k_prefmap;
286 	}
287 	for (mps = maps; mps != NULL; mps = mps->p_next) {
288 		if (mps->p_map == curmap)
289 			mps->p_map = mp;
290 		else
291 			fixmap(curmap, mp, mps->p_map);
292 	}
293 	ele = &mp->map_element[ele - &curmap->map_element[0]];
294 	return (mp);
295 }
296 
297 /*
298  * Fix references to a reallocated keymap (recursive).
299  */
300 static void
301 fixmap(KEYMAP *curmap, KEYMAP *mp, KEYMAP *mt)
302 {
303 	int	 i;
304 
305 	for (i = mt->map_num; i--;) {
306 		if (mt->map_element[i].k_prefmap != NULL) {
307 			if (mt->map_element[i].k_prefmap == curmap)
308 				mt->map_element[i].k_prefmap = mp;
309 			else
310 				fixmap(curmap, mp, mt->map_element[i].k_prefmap);
311 		}
312 	}
313 }
314 
315 /*
316  * Do the input for local-set-key, global-set-key  and define-key
317  * then call remap to do the work.
318  */
319 static int
320 dobind(KEYMAP *curmap, const char *p, int unbind)
321 {
322 	KEYMAP	*pref_map = NULL;
323 	PF	 funct;
324 	char	 bprompt[80], *bufp, *pep;
325 	int	 c, s, n;
326 
327 	if (macrodef) {
328 		/*
329 		 * Keystrokes aren't collected. Not hard, but pretty useless.
330 		 * Would not work for function keys in any case.
331 		 */
332 		return (dobeep_msg("Can't rebind key in macro"));
333 	}
334 	if (inmacro) {
335 		for (s = 0; s < maclcur->l_used - 1; s++) {
336 			if (doscan(curmap, c = CHARMASK(maclcur->l_text[s]), &curmap)
337 			    != NULL) {
338 				if (remap(curmap, c, NULL, NULL)
339 				    != TRUE)
340 					return (FALSE);
341 			}
342 		}
343 		(void)doscan(curmap, c = maclcur->l_text[s], NULL);
344 		maclcur = maclcur->l_fp;
345 	} else {
346 		n = strlcpy(bprompt, p, sizeof(bprompt));
347 		if (n >= sizeof(bprompt))
348 			n = sizeof(bprompt) - 1;
349 		pep = bprompt + n;
350 		for (;;) {
351 			ewprintf("%s", bprompt);
352 			pep[-1] = ' ';
353 			pep = getkeyname(pep, sizeof(bprompt) -
354 			    (pep - bprompt), c = getkey(FALSE));
355 			if (doscan(curmap, c, &curmap) != NULL)
356 				break;
357 			*pep++ = '-';
358 			*pep = '\0';
359 		}
360 	}
361 	if (unbind)
362 		funct = rescan;
363 	else {
364 		if ((bufp = eread("%s to command: ", bprompt, sizeof(bprompt),
365 		    EFFUNC | EFNEW, bprompt)) == NULL)
366 			return (ABORT);
367 		else if (bufp[0] == '\0')
368 			return (FALSE);
369 		if (((funct = name_function(bprompt)) == NULL) ?
370 		    (pref_map = name_map(bprompt)) == NULL : funct == NULL)
371 			return (dobeep_msg("[No match]"));
372 
373 	}
374 	return (remap(curmap, c, funct, pref_map));
375 }
376 
377 /*
378  * bindkey: bind key sequence to a function in the specified map.  Used by
379  * excline so it can bind function keys.  To close to release to change
380  * calling sequence, should just pass KEYMAP *curmap rather than
381  * KEYMAP **mapp.
382  */
383 static int
384 bindkey(KEYMAP **mapp, const char *fname, KCHAR *keys, int kcount)
385 {
386 	KEYMAP	*curmap = *mapp;
387 	KEYMAP	*pref_map = NULL;
388 	PF	 funct;
389 	int	 c;
390 
391 	if (fname == NULL)
392 		funct = rescan;
393 	else if (((funct = name_function(fname)) == NULL) ?
394 	    (pref_map = name_map(fname)) == NULL : funct == NULL) {
395 		dobeep();
396 		ewprintf("[No match: %s]", fname);
397 		return (FALSE);
398 	}
399 	while (--kcount) {
400 		if (doscan(curmap, c = *keys++, &curmap) != NULL) {
401 			if (remap(curmap, c, NULL, NULL) != TRUE)
402 				return (FALSE);
403 			/*
404 			 * XXX - Bizzarreness. remap creates an empty KEYMAP
405 			 *       that the last key is supposed to point to.
406 			 */
407 			curmap = ele->k_prefmap;
408 		}
409 	}
410 	(void)doscan(curmap, c = *keys, NULL);
411 	return (remap(curmap, c, funct, pref_map));
412 }
413 
414 /*
415  * Wrapper for bindkey() that converts escapes.
416  */
417 int
418 dobindkey(KEYMAP *map, const char *func, const char *str)
419 {
420 	int	 i;
421 
422 	for (i = 0; *str && i < MAXKEY; i++) {
423 		/* XXX - convert numbers w/ strol()? */
424 		if (*str == '^' && *(str + 1) !=  '\0') {
425 			key.k_chars[i] = CCHR(toupper((unsigned char)*++str));
426 		} else if (*str == '\\' && *(str + 1) != '\0') {
427 			switch (*++str) {
428 			case '^':
429 				key.k_chars[i] = '^';
430 				break;
431 			case 't':
432 			case 'T':
433 				key.k_chars[i] = '\t';
434 				break;
435 			case 'n':
436 			case 'N':
437 				key.k_chars[i] = '\n';
438 				break;
439 			case 'r':
440 			case 'R':
441 				key.k_chars[i] = '\r';
442 				break;
443 			case 'e':
444 			case 'E':
445 				key.k_chars[i] = CCHR('[');
446 				break;
447 			case '\\':
448 				key.k_chars[i] = '\\';
449 				break;
450 			}
451 		} else
452 			key.k_chars[i] = *str;
453 		str++;
454 	}
455 	key.k_count = i;
456 	return (bindkey(&map, func, key.k_chars, key.k_count));
457 }
458 
459 /*
460  * This function modifies the fundamental keyboard map.
461  */
462 /* ARGSUSED */
463 int
464 bindtokey(int f, int n)
465 {
466 	return (dobind(fundamental_map, "Global set key: ", FALSE));
467 }
468 
469 /*
470  * This function modifies the current mode's keyboard map.
471  */
472 /* ARGSUSED */
473 int
474 localbind(int f, int n)
475 {
476 	return (dobind(curbp->b_modes[curbp->b_nmodes]->p_map,
477 	    "Local set key: ", FALSE));
478 }
479 
480 /*
481  * This function redefines a key in any keymap.
482  */
483 /* ARGSUSED */
484 int
485 redefine_key(int f, int n)
486 {
487 	static char	 buf[48];
488 	char		 tmp[32], *bufp;
489 	KEYMAP		*mp;
490 
491 	(void)strlcpy(buf, "Define key map: ", sizeof(buf));
492 	if ((bufp = eread("%s", tmp, sizeof(tmp), EFNEW, buf)) == NULL)
493 		return (ABORT);
494 	else if (bufp[0] == '\0')
495 		return (FALSE);
496 	(void)strlcat(buf, tmp, sizeof(buf));
497 	if ((mp = name_map(tmp)) == NULL)
498 		return (dobeep_msgs("Unknown map ", tmp));
499 
500 	if (strlcat(buf, "key: ", sizeof(buf)) >= sizeof(buf))
501 		return (FALSE);
502 
503 	return (dobind(mp, buf, FALSE));
504 }
505 
506 /* ARGSUSED */
507 int
508 unbindtokey(int f, int n)
509 {
510 	return (dobind(fundamental_map, "Global unset key: ", TRUE));
511 }
512 
513 /* ARGSUSED */
514 int
515 localunbind(int f, int n)
516 {
517 	return (dobind(curbp->b_modes[curbp->b_nmodes]->p_map,
518 	    "Local unset key: ", TRUE));
519 }
520 
521 /*
522  * Extended command. Call the message line routine to read in the command
523  * name and apply autocompletion to it. When it comes back, look the name
524  * up in the symbol table and run the command if it is found.  Print an
525  * error if there is anything wrong.
526  */
527 int
528 extend(int f, int n)
529 {
530 	PF	 funct;
531 	char	 xname[NXNAME], *bufp;
532 
533 	if (!(f & FFARG))
534 		bufp = eread("M-x ", xname, NXNAME, EFNEW | EFFUNC);
535 	else
536 		bufp = eread("%d M-x ", xname, NXNAME, EFNEW | EFFUNC, n);
537 	if (bufp == NULL)
538 		return (ABORT);
539 	else if (bufp[0] == '\0')
540 		return (FALSE);
541 	if ((funct = name_function(bufp)) != NULL) {
542 		if (macrodef) {
543 			struct line	*lp = maclcur;
544 			macro[macrocount - 1].m_funct = funct;
545 			maclcur = lp->l_bp;
546 			maclcur->l_fp = lp->l_fp;
547 			free(lp);
548 		}
549 		return ((*funct)(f, n));
550 	}
551 	return (dobeep_msg("[No match]"));
552 }
553 
554 /*
555  * Define the commands needed to do startup-file processing.
556  * This code is mostly a kludge just so we can get startup-file processing.
557  *
558  * If you're serious about having this code, you should rewrite it.
559  * To wit:
560  *	It has lots of funny things in it to make the startup-file look
561  *	like a GNU startup file; mostly dealing with parens and semicolons.
562  *	This should all vanish.
563  *
564  * We define eval-expression because it's easy.	 It can make
565  * *-set-key or define-key set an arbitrary key sequence, so it isn't
566  * useless.
567  */
568 
569 /*
570  * evalexpr - get one line from the user, and run it.
571  */
572 /* ARGSUSED */
573 int
574 evalexpr(int f, int n)
575 {
576 	char	 exbuf[BUFSIZE], *bufp;
577 
578 	if ((bufp = eread("Eval: ", exbuf, sizeof(exbuf),
579 	    EFNEW | EFCR)) == NULL)
580 		return (ABORT);
581 	else if (bufp[0] == '\0')
582 		return (FALSE);
583 	return (excline(exbuf));
584 }
585 
586 /*
587  * evalbuffer - evaluate the current buffer as line commands. Useful for
588  * testing startup files.
589  */
590 /* ARGSUSED */
591 int
592 evalbuffer(int f, int n)
593 {
594 	struct line		*lp;
595 	struct buffer		*bp = curbp;
596 	int		 s;
597 	static char	 excbuf[BUFSIZE];
598 
599 	for (lp = bfirstlp(bp); lp != bp->b_headp; lp = lforw(lp)) {
600 		if (llength(lp) >= BUFSIZE)
601 			return (FALSE);
602 		(void)strncpy(excbuf, ltext(lp), llength(lp));
603 
604 		/* make sure it's terminated */
605 		excbuf[llength(lp)] = '\0';
606 		if ((s = excline(excbuf)) != TRUE) {
607 			(void) clearvars();
608 			return (s);
609 		}
610 	}
611 	(void) clearvars();
612 	return (TRUE);
613 }
614 
615 /*
616  * evalfile - go get a file and evaluate it as line commands. You can
617  *	go get your own startup file if need be.
618  */
619 /* ARGSUSED */
620 int
621 evalfile(int f, int n)
622 {
623 	char	 fname[NFILEN], *bufp;
624 
625 	if ((bufp = eread("Load file: ", fname, NFILEN,
626 	    EFNEW | EFCR)) == NULL)
627 		return (ABORT);
628 	else if (bufp[0] == '\0')
629 		return (FALSE);
630 	return (load(fname));
631 }
632 
633 /*
634  * load - go load the file name we got passed.
635  */
636 int
637 load(const char *fname)
638 {
639 	int	 s = TRUE, line, ret;
640 	int	 nbytes = 0;
641 	char	 excbuf[BUFSIZE], fncpy[NFILEN];
642 	FILE    *ffp;
643 
644 	if ((fname = adjustname(fname, TRUE)) == NULL)
645 		/* just to be careful */
646 		return (FALSE);
647 
648 	ret = ffropen(&ffp, fname, NULL);
649 	if (ret != FIOSUC) {
650 		if (ret == FIODIR)
651 			(void)ffclose(ffp, NULL);
652 		return (FALSE);
653 	}
654 
655 	/* keep a note of fname incase of errors in loaded file. */
656 	(void)strlcpy(fncpy, fname, sizeof(fncpy));
657 	line = 0;
658 	while ((s = ffgetline(ffp, excbuf, sizeof(excbuf) - 1, &nbytes))
659 	    == FIOSUC) {
660 		line++;
661 		excbuf[nbytes] = '\0';
662 		if (excline(excbuf) != TRUE) {
663 			s = FIOERR;
664 			dobeep();
665 			ewprintf("Error loading file %s at line %d", fncpy, line);
666 			break;
667 		}
668 	}
669 	(void)ffclose(ffp, NULL);
670 	excbuf[nbytes] = '\0';
671 	if (s != FIOEOF || (nbytes && excline(excbuf) != TRUE))
672 		return (FALSE);
673 	return (TRUE);
674 }
675 
676 /*
677  * excline - run a line from a load file or eval-expression.
678  */
679 int
680 excline(char *line)
681 {
682 	PF	 fp;
683 	struct line	*lp, *np;
684 	int	 status, c, f, n;
685 	char	*funcp, *tmp;
686 	char	*argp = NULL;
687 	long	 nl;
688 	int	 bind;
689 	KEYMAP	*curmap;
690 #define BINDARG		0  /* this arg is key to bind (local/global set key) */
691 #define	BINDNO		1  /* not binding or non-quoted BINDARG */
692 #define BINDNEXT	2  /* next arg " (define-key) */
693 #define BINDDO		3  /* already found key to bind */
694 #define BINDEXT		1  /* space for trailing \0 */
695 
696 	lp = NULL;
697 
698 	if (macrodef || inmacro)
699 		return (dobeep_msg("Not now!"));
700 
701 	f = 0;
702 	n = 1;
703 	funcp = skipwhite(line);
704 	if (*funcp == '\0')
705 		return (TRUE);	/* No error on blank lines */
706 	if (*funcp == '(')
707 		return (foundparen(funcp));
708 	line = parsetoken(funcp);
709 	if (*line != '\0') {
710 		*line++ = '\0';
711 		line = skipwhite(line);
712 		if (ISDIGIT(*line) || *line == '-') {
713 			argp = line;
714 			line = parsetoken(line);
715 		}
716 	}
717 	if (argp != NULL) {
718 		f = FFARG;
719 		nl = strtol(argp, &tmp, 10);
720 		if (*tmp != '\0')
721 			return (FALSE);
722 		if (nl >= INT_MAX || nl <= INT_MIN)
723 			return (FALSE);
724 		n = (int)nl;
725 	}
726 	if ((fp = name_function(funcp)) == NULL)
727 		return (dobeep_msgs("Unknown function: ", funcp));
728 
729 	if (fp == bindtokey || fp == unbindtokey) {
730 		bind = BINDARG;
731 		curmap = fundamental_map;
732 	} else if (fp == localbind || fp == localunbind) {
733 		bind = BINDARG;
734 		curmap = curbp->b_modes[curbp->b_nmodes]->p_map;
735 	} else if (fp == redefine_key)
736 		bind = BINDNEXT;
737 	else
738 		bind = BINDNO;
739 	/* Pack away all the args now... */
740 	if ((np = lalloc(0)) == FALSE)
741 		return (FALSE);
742 	np->l_fp = np->l_bp = maclcur = np;
743 	while (*line != '\0') {
744 		argp = skipwhite(line);
745 		if (*argp == '\0')
746 			break;
747 		line = parsetoken(argp);
748 		if (*argp != '"') {
749 			if (*argp == '\'')
750 				++argp;
751 			if ((lp = lalloc((int) (line - argp) + BINDEXT)) ==
752 			    NULL) {
753 				status = FALSE;
754 				goto cleanup;
755 			}
756 			bcopy(argp, ltext(lp), (int)(line - argp));
757 			/* don't count BINDEXT */
758 			lp->l_used--;
759 			if (bind == BINDARG)
760 				bind = BINDNO;
761 		} else {
762 			/* quoted strings are special */
763 			++argp;
764 			if (bind != BINDARG) {
765 				lp = lalloc((int)(line - argp) + BINDEXT);
766 				if (lp == NULL) {
767 					status = FALSE;
768 					goto cleanup;
769 				}
770 				lp->l_used = 0;
771 			} else
772 				key.k_count = 0;
773 			while (*argp != '"' && *argp != '\0') {
774 				if (*argp != '\\')
775 					c = *argp++;
776 				else {
777 					switch (*++argp) {
778 					case 't':
779 					case 'T':
780 						c = CCHR('I');
781 						break;
782 					case 'n':
783 					case 'N':
784 						c = CCHR('J');
785 						break;
786 					case 'r':
787 					case 'R':
788 						c = CCHR('M');
789 						break;
790 					case 'e':
791 					case 'E':
792 						c = CCHR('[');
793 						break;
794 					case '^':
795 						/*
796 						 * split into two statements
797 						 * due to bug in OSK cpp
798 						 */
799 						c = CHARMASK(*++argp);
800 						c = ISLOWER(c) ?
801 						    CCHR(TOUPPER(c)) : CCHR(c);
802 						break;
803 					case '0':
804 					case '1':
805 					case '2':
806 					case '3':
807 					case '4':
808 					case '5':
809 					case '6':
810 					case '7':
811 						c = *argp - '0';
812 						if (argp[1] <= '7' &&
813 						    argp[1] >= '0') {
814 							c <<= 3;
815 							c += *++argp - '0';
816 							if (argp[1] <= '7' &&
817 							    argp[1] >= '0') {
818 								c <<= 3;
819 								c += *++argp
820 								    - '0';
821 							}
822 						}
823 						break;
824 					case 'f':
825 					case 'F':
826 						c = *++argp - '0';
827 						if (ISDIGIT(argp[1])) {
828 							c *= 10;
829 							c += *++argp - '0';
830 						}
831 						c += KFIRST;
832 						break;
833 					default:
834 						c = CHARMASK(*argp);
835 						break;
836 					}
837 					argp++;
838 				}
839 				if (bind == BINDARG)
840 					key.k_chars[key.k_count++] = c;
841 				else
842 					lp->l_text[lp->l_used++] = c;
843 			}
844 			if (*line)
845 				line++;
846 		}
847 		switch (bind) {
848 		case BINDARG:
849 			bind = BINDDO;
850 			break;
851 		case BINDNEXT:
852 			lp->l_text[lp->l_used] = '\0';
853 			if ((curmap = name_map(lp->l_text)) == NULL) {
854 				(void)dobeep_msgs("No such mode: ", lp->l_text);
855 				status = FALSE;
856 				free(lp);
857 				goto cleanup;
858 			}
859 			free(lp);
860 			bind = BINDARG;
861 			break;
862 		default:
863 			lp->l_fp = np->l_fp;
864 			lp->l_bp = np;
865 			np->l_fp = lp;
866 			np = lp;
867 		}
868 	}
869 	switch (bind) {
870 	default:
871 		(void)dobeep_msg("Bad args to set key");
872 		status = FALSE;
873 		break;
874 	case BINDDO:
875 		if (fp != unbindtokey && fp != localunbind) {
876 			lp->l_text[lp->l_used] = '\0';
877 			status = bindkey(&curmap, lp->l_text, key.k_chars,
878 			    key.k_count);
879 		} else
880 			status = bindkey(&curmap, NULL, key.k_chars,
881 			    key.k_count);
882 		break;
883 	case BINDNO:
884 		inmacro = TRUE;
885 		maclcur = maclcur->l_fp;
886 		status = (*fp)(f, n);
887 		inmacro = FALSE;
888 	}
889 cleanup:
890 	lp = maclcur->l_fp;
891 	while (lp != maclcur) {
892 		np = lp->l_fp;
893 		free(lp);
894 		lp = np;
895 	}
896 	free(lp);
897 	maclhead = NULL;
898 	macrodef = FALSE;
899 	return (status);
900 }
901 
902 /*
903  * a pair of utility functions for the above
904  */
905 char *
906 skipwhite(char *s)
907 {
908 	while (*s == ' ' || *s == '\t')
909 		s++;
910 	if ((*s == ';') || (*s == '#'))
911 		*s = '\0';
912 	return (s);
913 }
914 
915 static char *
916 parsetoken(char *s)
917 {
918 	if (*s != '"') {
919 		while (*s && *s != ' ' && *s != '\t' && *s != ')' && *s != '(')
920 			s++;
921 		if (*s == ';')
922 			*s = '\0';
923 	} else
924 		do {
925 			/*
926 			 * Strings get special treatment.
927 			 * Beware: You can \ out the end of the string!
928 			 */
929 			if (*s == '\\')
930 				++s;
931 		} while (*++s != '"' && *s != '\0');
932 	return (s);
933 }
934