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