1 /*	This file is for functions having to do with key bindings,
2  *	descriptions, help commands and startup file.
3  *
4  *	written 11-feb-86 by Daniel Lawrence
5  *
6  * $Id: bind.c,v 1.378 2018/10/24 00:14:57 tom Exp $
7  */
8 
9 #include	"estruct.h"
10 #include	"edef.h"
11 #include	"nefunc.h"
12 #include	"nefsms.h"
13 #include	"nename.h"
14 
15 #define BI_DATA NBST_DATA
16 #include	"btree.h"
17 
18 #define SHORT_CMD_LEN 4		/* command names longer than this are preferred
19 				   over those shorter.  e.g. display "quit"
20 				   instead of "q" if possible */
21 
22 extern const int nametbl_size;
23 
24 #define Strcmp(s,d)      cs_strcmp(case_insensitive, s, d)
25 #define StrNcmp(s,d,len) cs_strncmp(case_insensitive, s, d, len)
26 
27 #if OPT_REBIND
28 #define	isSpecialCmd(k) \
29 		( (k == &f_cntl_a_func)\
30 		||(k == &f_cntl_x_func)\
31 		||(k == &f_poundc_func)\
32 		||(k == &f_reptc_func)\
33 		||(k == &f_esc_func)\
34 		)
35 
36 static int key_to_bind(const CMDFUNC * kcmd);
37 static int update_binding_list(BUFFER *bp);
38 static void makebindlist(LIST_ARGS);
39 static void makebindkeyslist(LIST_ARGS);
40 #endif /* OPT_REBIND */
41 
42 #if OPT_NAMEBST
43 static int kbd_complete_bst(unsigned flags, int c, char *buf, size_t *pos);
44 #else
45 #define kbd_complete_bst(params) \
46 	kbd_complete(params, \
47 			(const char *)&nametbl[0], sizeof(nametbl[0]))
48 #endif /* OPT_NAMEBST */
49 
50 #if OPT_EVAL || OPT_REBIND
51 static int fnc2ntab(NTAB * result, const CMDFUNC * cfp);
52 static int prc2kcod(const char *kk);
53 #endif
54 
55 BINDINGS dft_bindings =
56 {
57     BINDINGLIST_BufName
58     ,asciitbl
59     ,kbindtbl
60 #if OPT_REBIND
61     ,kbindtbl
62 #endif
63 };
64 
65 static const CMDFUNC *ins_table[N_chars];
66 BINDINGS ins_bindings =
67 {
68     INS_BINDINGS_BufName
69     ,ins_table
70     ,kbindtbl
71 #if OPT_REBIND
72     ,kbindtbl
73 #endif
74 };
75 
76 static const CMDFUNC *cmd_table[N_chars];
77 BINDINGS cmd_bindings =
78 {
79     CMD_BINDINGS_BufName
80     ,cmd_table
81     ,kbindtbl
82 #if OPT_REBIND
83     ,kbindtbl
84 #endif
85 };
86 
87 static const CMDFUNC *sel_table[N_chars];
88 BINDINGS sel_bindings =
89 {
90     SEL_BINDINGS_BufName
91     ,sel_table
92     ,kbindtbl
93 #if OPT_REBIND
94     ,kbindtbl
95 #endif
96 };
97 
98 static struct {
99     const char *name;
100     BINDINGS *binding;
101 } bindings_by_name[] = {
102 
103     {
104 	"command", &cmd_bindings
105     },
106     {
107 	"default", &dft_bindings
108     },
109     {
110 	"insert", &ins_bindings
111     },
112     {
113 	"select", &sel_bindings
114     },
115 };
116 
117 static UINT which_current;
118 
119 #if OPT_REBIND
120 static BINDINGS *bindings_to_describe = &dft_bindings;
121 #endif
122 
123 static void kbd_puts(const char *s);
124 
125 /*----------------------------------------------------------------------------*/
126 
127 BINDINGS *
vl_get_binding(const char * name)128 vl_get_binding(const char *name)
129 {
130     BINDINGS *result = 0;
131     unsigned n;
132     for (n = 0; n < TABLESIZE(bindings_by_name); ++n) {
133 	if (!strncmp(bindings_by_name[n].name, name, strlen(name))) {
134 	    result = bindings_by_name[n].binding;
135 	    break;
136 	}
137     }
138     return result;
139 }
140 
141 /*----------------------------------------------------------------------------*/
142 
143 #if OPT_NAMEBST
144 static void
old_namebst(BI_NODE * a)145 old_namebst(BI_NODE * a)
146 {
147     beginDisplay();
148     if (!(a->value.n_flags & NBST_READONLY)) {
149 	CMDFUNC *cmd = TYPECAST(CMDFUNC, a->value.n_cmd);
150 	if (cmd != 0) {
151 	    if ((cmd->c_flags & CMD_TYPE) != CMD_FUNC) {
152 #if OPT_ONLINEHELP
153 		if (cmd->c_help)
154 		    free(TYPECAST(char, cmd->c_help));
155 #endif
156 #if OPT_MACRO_ARGS
157 		if (cmd->c_args) {
158 		    int n;
159 		    for (n = 0; cmd->c_args[n].pi_text != 0; ++n) {
160 			free(cmd->c_args[n].pi_text);
161 		    }
162 		    free(cmd->c_args);
163 		}
164 #endif
165 		free(cmd);
166 		free(TYPECAST(char, BI_KEY(a)));
167 	    }
168 	} else {
169 	    free(TYPECAST(char, BI_KEY(a)));
170 	}
171     }
172     free(a);
173     endofDisplay();
174 }
175 
176 static BI_NODE *
new_namebst(BI_DATA * a)177 new_namebst(BI_DATA * a)
178 {
179     BI_NODE *p;
180 
181     beginDisplay();
182     if ((p = typecalloc(BI_NODE)) != 0) {
183 	p->value = *a;
184 	if (!(a->n_flags & NBST_READONLY)) {
185 	    if ((BI_KEY(p) = strmalloc(a->bi_key)) == 0) {
186 		old_namebst(p);
187 		p = 0;
188 	    }
189 	}
190     }
191     endofDisplay();
192 
193     return p;
194 }
195 
196 static void
dpy_namebst(BI_NODE * a GCC_UNUSED,int level GCC_UNUSED)197 dpy_namebst(BI_NODE * a GCC_UNUSED, int level GCC_UNUSED)
198 {
199 #if OPT_TRACE
200     TRACE(("[%d]%s%p -> %s%s (%d)\n",
201 	   level, alloc_indent(level, '.'),
202 	   (void *) a,
203 	   (a->value.n_flags & NBST_READONLY)
204 	   ? "*"
205 	   : "",
206 	   BI_KEY(a), a->balance));
207 #endif
208 }
209 
210 static void
xcg_namebst(BI_NODE * a,BI_NODE * b)211 xcg_namebst(BI_NODE * a, BI_NODE * b)
212 {
213     BI_DATA temp = a->value;
214     a->value = b->value;
215     b->value = temp;
216 }
217 
218 #define BI_DATA0 {{0}, 0, {0,0,0}}
219 #define BI_TREE0 0, 0, BI_DATA0
220 
221 static BI_TREE namebst =
222 {new_namebst, old_namebst, dpy_namebst, xcg_namebst, BI_TREE0};
223 
224 static BI_TREE glbsbst =
225 {new_namebst, old_namebst, dpy_namebst, xcg_namebst, BI_TREE0};
226 
227 static BI_TREE redefns =
228 {new_namebst, old_namebst, dpy_namebst, xcg_namebst, BI_TREE0};
229 
230 /*----------------------------------------------------------------------------*/
231 /*
232  * Name-completion for global commands uses a different table (a subset) from
233  * the normal command-table.  Provide a way to select that table.
234  */
235 
236 static BI_TREE *
bst_pointer(UINT which)237 bst_pointer(UINT which)
238 {
239     BI_TREE *result;
240 
241     if (which == GLOBOK) {
242 	result = &glbsbst;
243     } else {
244 	result = &namebst;
245     }
246     return result;
247 }
248 
249 static BI_TREE *
bst_current(void)250 bst_current(void)
251 {
252     return bst_pointer(which_current);
253 }
254 #endif /* OPT_NAMEBST */
255 
256 /*----------------------------------------------------------------------------*/
257 
258 int
no_such_function(const char * fnp)259 no_such_function(const char *fnp)
260 {
261     mlforce("[No such function \"%s\"]", NONNULL(fnp));
262     return FALSE;
263 }
264 
265 /* give me some help!!!! bring up a buffer and read the help file into it */
266 /* ARGSUSED */
267 int
vl_help(int f GCC_UNUSED,int n GCC_UNUSED)268 vl_help(int f GCC_UNUSED, int n GCC_UNUSED)
269 {
270     BUFFER *bp;			/* buffer pointer to help */
271     char *hname;
272     int alreadypopped;
273 
274     TRACE((T_CALLED "vl_help()\n"));
275 
276     /* first check if we are already here */
277     bp = bfind(HELP_BufName, BFSCRTCH);
278     if (bp == NULL)
279 	returnCode(FALSE);
280 
281     if (bp->b_active == FALSE) {	/* never been used */
282 	hname = cfg_locate(helpfile, LOCATE_HELP);
283 	if (!hname) {
284 	    mlforce("[Sorry, can't find the help information]");
285 	    (void) zotbuf(bp);
286 	    returnCode(FALSE);
287 	}
288 	alreadypopped = (bp->b_nwnd != 0);
289 	/* and read the stuff in */
290 	if (readin(hname, 0, bp, TRUE) != TRUE ||
291 	    popupbuff(bp) == FALSE) {
292 	    (void) zotbuf(bp);
293 	    returnCode(FALSE);
294 	}
295 	set_bname(bp, HELP_BufName);
296 	set_rdonly(bp, hname, MDVIEW);
297 
298 	set_local_b_val(bp, MDIGNCASE, TRUE);	/* easy to search, */
299 	b_set_scratch(bp);
300 	b_set_recentlychanged(bp);
301 	if (!alreadypopped)
302 	    shrinkwrap();
303     }
304 
305     if (!swbuffer(bp))
306 	returnCode(FALSE);
307 
308     if (help_at >= 0) {
309 	if (!vl_gotoline(help_at))
310 	    returnCode(FALSE);
311 	mlwrite("[Type '1G' to return to start of help information]");
312 	help_at = -1;		/* until zotbuf is called, we let normal
313 				   DOT tracking keep our position */
314     }
315     returnCode(TRUE);
316 }
317 
318 /*
319  * translate a 10-bit key-binding to the table-pointer
320  */
321 static KBIND *
kcode2kbind(const BINDINGS * bs,int code)322 kcode2kbind(const BINDINGS * bs, int code)
323 {
324     KBIND *kbp;
325 
326     TRACE(("kcode2kbind(%s, %#x)\n", bs->bufname, code));
327 #if OPT_REBIND
328     for (kbp = bs->kb_extra; kbp != bs->kb_special; kbp = kbp->k_link) {
329 	if (kbp->k_code == code) {
330 	    TRACE(("...found in extra-bindings\n"));
331 	    return kbp;
332 	}
333     }
334 #endif
335     for (kbp = bs->kb_special; kbp->k_cmd; kbp++) {
336 	if (kbp->k_code == code) {
337 	    TRACE(("...found in special-bindings\n"));
338 	    return kbp;
339 	}
340     }
341     return 0;
342 }
343 
344 #if OPT_REBIND
345 
346 #if OPT_TERMCHRS
347 
348 	/* NOTE: this table and the corresponding initializations should be
349 	 * generated by 'mktbls'.  In any case, the table must be sorted to use
350 	 * name-completion on it.
351 	 */
352 static const struct {
353     const char *name;
354     int *value;
355     char how_to;
356 } TermChrs[] = {
357 		/* *INDENT-OFF* */
358 		{"backspace",		&backspc,	's'},
359 		{"interrupt",		&intrc,		's'},
360 		{"line-kill",		&killc,		's'},
361 		{"mini-edit",		&editc,		's'},
362 		{"name-complete",	&name_cmpl,	0},
363 		{"quote-next",		&quotec,	0},
364 		{"start-output",	&startc,	's'},
365 		{"stop-output",		&stopc,		's'},
366 		{"suspend",		&suspc,		's'},
367 		{"test-completions",	&test_cmpl,	0},
368 		{"word-kill",		&wkillc,	's'},
369 		{0,                     0,              0}
370 		/* *INDENT-ON* */
371 
372 };
373 
374 /*----------------------------------------------------------------------------*/
375 
376 /* list the current chrs into the current buffer */
377 /* ARGSUSED */
378 static void
makechrslist(int dum1 GCC_UNUSED,void * ptr GCC_UNUSED)379 makechrslist(int dum1 GCC_UNUSED, void *ptr GCC_UNUSED)
380 {
381     int i;
382     char temp[NLINE];
383 
384     bprintf("--- Terminal Character Settings ");
385     bpadc('-', term.cols - DOT.o);
386     bputc('\n');
387 
388     for (i = 0; TermChrs[i].name != 0; i++) {
389 	bprintf("\n%s = %s",
390 		TermChrs[i].name,
391 		kcod2prc(*(TermChrs[i].value), temp));
392     }
393 }
394 
395 /*
396  * Find a special-character definition, given the name
397  */
398 static int
chr_lookup(const char * name)399 chr_lookup(const char *name)
400 {
401     int j;
402     if (name != 0) {
403 	for (j = 0; TermChrs[j].name != 0; j++)
404 	    if (!strcmp(name, TermChrs[j].name))
405 		return j;
406     }
407     return -1;
408 }
409 
410 /*
411  * The 'chr_complete()' and 'chr_eol()' functions are invoked from
412  * 'kbd_reply()' to setup the mode-name completion and query displays.
413  */
414 static int
chr_complete(DONE_ARGS)415 chr_complete(DONE_ARGS)
416 {
417     return kbd_complete(PASS_DONE_ARGS, (const char *) &TermChrs[0],
418 			sizeof(TermChrs[0]));
419 }
420 
421 static int
422 /*ARGSUSED*/
chr_eol(EOL_ARGS)423 chr_eol(EOL_ARGS)
424 {
425     (void) buffer;
426     (void) cpos;
427     (void) eolchar;
428 
429     return isSpace(c);
430 }
431 
432 #if OPT_UPBUFF
433 /* ARGSUSED */
434 static int
update_termchrs(BUFFER * bp GCC_UNUSED)435 update_termchrs(BUFFER *bp GCC_UNUSED)
436 {
437     return show_termchrs(FALSE, 1);
438 }
439 #endif
440 
441 /* ARGSUSED */
442 int
set_termchrs(int f GCC_UNUSED,int n GCC_UNUSED)443 set_termchrs(int f GCC_UNUSED, int n GCC_UNUSED)
444 {
445     int s, j;
446     static TBUFF *name;
447     int c;
448 
449     /* get the table-entry */
450     tb_scopy(&name, "");
451     if ((s = kbd_reply("Terminal setting: ", &name, chr_eol,
452 		       ' ', 0, chr_complete)) == TRUE
453 	&& (j = chr_lookup(tb_values(name))) >= 0) {
454 	switch (TermChrs[j].how_to) {
455 	case 's':
456 	default:
457 	    c = key_to_bind((CMDFUNC *) 0);
458 	    if (c < 0)
459 		return (FALSE);
460 	    *(TermChrs[j].value) = c;
461 	    break;
462 	}
463 	update_scratch(TERMINALCHARS_BufName, update_termchrs);
464     }
465     return s;
466 }
467 
468 /* ARGSUSED */
469 int
show_termchrs(int f GCC_UNUSED,int n GCC_UNUSED)470 show_termchrs(int f GCC_UNUSED, int n GCC_UNUSED)
471 {
472     return liststuff(TERMINALCHARS_BufName, FALSE, makechrslist, 0, (void *) 0);
473 }
474 #endif /* OPT_TERMCHRS */
475 
476 static int
mac_token2kcod(int check)477 mac_token2kcod(int check)
478 {
479     static TBUFF *tok;
480     char *value;
481     int c;
482 
483     if ((value = mac_unquotedarg(&tok)) == 0) {
484 	c = -1;
485     } else {
486 	c = prc2kcod(value);
487 
488 	if (c < 0 && check)
489 	    mlforce("[Illegal key-sequence \"%s\"]", value);
490     }
491 
492     return c;
493 }
494 
495 /*
496  * Prompt-for and return the key-code to bind.
497  */
498 static int
key_to_bind(const CMDFUNC * kcmd)499 key_to_bind(const CMDFUNC * kcmd)
500 {
501     char outseq[NLINE];		/* output buffer for keystroke sequence */
502     int c;
503 
504     mlprompt("...to keyboard sequence (type it exactly): ");
505 
506     /* if running a command line, get a token rather than keystrokes */
507     if (clexec) {
508 	c = mac_token2kcod(FALSE);
509     } else {
510 	/* perhaps we only want a single key, not a sequence */
511 	/*      (see more comments below) */
512 	if (isSpecialCmd(kcmd)) {
513 	    c = keystroke();
514 	} else {
515 	    c = kbd_seq();
516 	}
517     }
518 
519     if (c >= 0) {
520 	/* change it to something we can print as well */
521 	outseq[0] = EOS;
522 	if (vl_msgs)
523 	    kbd_puts(kcod2prc(c, outseq));
524 	hst_append_s(outseq, FALSE, TRUE);
525     } else {
526 	mlforce("[Not a proper key-sequence]");
527     }
528     return c;
529 }
530 
531 static int
free_KBIND(KBIND ** oldp,KBIND * newp)532 free_KBIND(KBIND ** oldp, KBIND * newp)
533 {
534     *oldp = newp->k_link;
535 
536     beginDisplay();
537     free(newp);
538     endofDisplay();
539 
540     return TRUE;
541 }
542 
543 static int
unbindchar(BINDINGS * bs,int c)544 unbindchar(BINDINGS * bs, int c)
545 {
546     KBIND *kbp;			/* pointer into the command table */
547     KBIND *skbp;		/* saved pointer into the command table */
548 
549     /* if it's a simple character, it will be in the normal[] array */
550     if (is8Bits(c)) {
551 	if (bs->kb_normal[CharOf(c)]) {
552 	    bs->kb_normal[CharOf(c)] = 0;
553 	    return TRUE;
554 	}
555 	return FALSE;
556     }
557 
558     /* check first entry in kb_extra table */
559     kbp = skbp = bs->kb_extra;
560     if (kbp->k_code == c) {
561 	return free_KBIND(&(bs->kb_extra), kbp);
562     }
563 
564     /* check kb_extra codes */
565     while (kbp != bs->kb_special) {
566 	if (kbp->k_code == c) {
567 	    /* relink previous */
568 	    return free_KBIND(&(skbp->k_link), kbp);
569 	}
570 
571 	skbp = kbp;
572 	kbp = kbp->k_link;
573     }
574 
575     /* nope, check special codes */
576     for (skbp = 0; kbp->k_cmd; kbp++) {
577 	if (!skbp && kbp->k_code == c)
578 	    skbp = kbp;
579     }
580 
581     /* not found */
582     if (!skbp)
583 	return FALSE;
584 
585     --kbp;			/* backup to the last legit entry */
586     if (skbp != kbp) {
587 	/* copy the last entry to the current one */
588 	skbp->k_code = kbp->k_code;
589 	skbp->k_cmd = kbp->k_cmd;
590     }
591 
592     /* null out the last one */
593     kbp->k_code = 0;
594     kbp->k_cmd = 0;
595 
596     return TRUE;
597 }
598 
599 /*
600  * removes a key from the table of bindings
601  */
602 static int
unbind_any_key(BINDINGS * bs)603 unbind_any_key(BINDINGS * bs)
604 {
605     int c;			/* command key to unbind */
606     char outseq[NLINE];		/* output buffer for keystroke sequence */
607 
608     /* prompt the user to type in a key to unbind */
609     mlprompt("Unbind this key sequence: ");
610 
611     /* get the command sequence to unbind */
612     if (clexec) {
613 	if ((c = mac_token2kcod(TRUE)) < 0)
614 	    return FALSE;
615     } else {
616 	c = kbd_seq();
617 	if (c < 0) {
618 	    mlforce("[Not a bindable key-sequence]");
619 	    return (FALSE);
620 	}
621     }
622 
623     if (vl_msgs)
624 	kbd_puts(kcod2prc(c, outseq));
625 
626     if (unbindchar(bs, c) == FALSE) {
627 	mlforce("[Key not bound]");
628 	return (FALSE);
629     }
630     update_scratch(bs->bufname, update_binding_list);
631     return (TRUE);
632 }
633 
634 /* ARGSUSED */
635 int
unbindkey(int f GCC_UNUSED,int n GCC_UNUSED)636 unbindkey(int f GCC_UNUSED, int n GCC_UNUSED)
637 {
638     return unbind_any_key(&dft_bindings);
639 }
640 
641 /* ARGSUSED */
642 int
unbind_i_key(int f GCC_UNUSED,int n GCC_UNUSED)643 unbind_i_key(int f GCC_UNUSED, int n GCC_UNUSED)
644 {
645     return unbind_any_key(&ins_bindings);
646 }
647 
648 /* ARGSUSED */
649 int
unbind_c_key(int f GCC_UNUSED,int n GCC_UNUSED)650 unbind_c_key(int f GCC_UNUSED, int n GCC_UNUSED)
651 {
652     return unbind_any_key(&cmd_bindings);
653 }
654 
655 /* ARGSUSED */
656 int
unbind_s_key(int f GCC_UNUSED,int n GCC_UNUSED)657 unbind_s_key(int f GCC_UNUSED, int n GCC_UNUSED)
658 {
659     return unbind_any_key(&sel_bindings);
660 }
661 
662 /*
663  * Prefix-keys can be only bound to one value. This procedure tests the
664  * argument 'kcmd' to see if it is a prefix key, and if so, unbinds the
665  * key, and sets the corresponding global variable to the new value.
666  * The calling procedure will then do the binding per se.
667  */
668 static void
reset_prefix(int c,const CMDFUNC * kcmd,BINDINGS * bs)669 reset_prefix(int c, const CMDFUNC * kcmd, BINDINGS * bs)
670 {
671     if (isSpecialCmd(kcmd)) {
672 	int j;
673 	/* search for an existing binding for the prefix key */
674 	if ((j = fnc2kcod(kcmd)) >= 0)
675 	    (void) unbindchar(bs, j);
676 	/* reset the appropriate global prefix variable */
677 	if (kcmd == &f_cntl_a_func)
678 	    cntl_a = c;
679 	if (kcmd == &f_cntl_x_func)
680 	    cntl_x = c;
681 	if (kcmd == &f_poundc_func)
682 	    poundc = c;
683 	if (kcmd == &f_reptc_func)
684 	    reptc = c;
685 	if (kcmd == &f_esc_func)
686 	    esc_c = c;
687     }
688 }
689 
690 /*
691  * Bind a command-function pointer to a given key-code (saving the old
692  * value of the function-pointer via an pointer given by the caller).
693  */
694 static int
install_bind(int c,const CMDFUNC * kcmd,BINDINGS * bs)695 install_bind(int c, const CMDFUNC * kcmd, BINDINGS * bs)
696 {
697     KBIND *kbp;			/* pointer into a binding table */
698 
699     TRACE(("install_bind(%#x, %s(%s), %s)\n",
700 	   c, TRACE_CMDFUNC(kcmd), TRACE_BINDINGS(bs)));
701 
702     if (c < 0)
703 	return FALSE;		/* not a legal key-code */
704 
705     /* if the function is a prefix key, i.e. we're changing the definition
706        of a prefix key, then they typed a dummy function name, which
707        has been translated into a dummy function pointer */
708     reset_prefix(-1, kcod2fnc(bs, c), bs);
709     reset_prefix(c, kcmd, bs);
710 
711     if (is8Bits(c)) {
712 	bs->kb_normal[CharOf(c)] = TYPECAST(CMDFUNC, kcmd);
713     } else if ((kbp = kcode2kbind(bs, c)) != 0) {	/* change it in place */
714 	kbp->k_cmd = kcmd;
715     } else {
716 
717 	beginDisplay();
718 	kbp = typealloc(KBIND);
719 	endofDisplay();
720 
721 	if (kbp == 0) {
722 	    return no_memory("Key-Binding");
723 	}
724 	kbp->k_link = bs->kb_extra;
725 	kbp->k_code = c;	/* add keycode */
726 	kbp->k_cmd = kcmd;	/* and func pointer */
727 	bs->kb_extra = kbp;
728     }
729     update_scratch(bs->bufname, update_binding_list);
730     return (TRUE);
731 }
732 
733 /* ARGSUSED */
734 static int
bind_any_key(BINDINGS * bs)735 bind_any_key(BINDINGS * bs)
736 {
737     const CMDFUNC *kcmd;
738     char cmd[NLINE];
739     char *fnp;
740 
741     /* prompt the user to type in a key to bind */
742     /* and get the function name to bind it to */
743     fnp = kbd_engl("Bind function whose full name is: ", cmd, 0);
744 
745     if (fnp == NULL || (kcmd = engl2fnc(fnp)) == NULL) {
746 	return no_such_function(fnp);
747     }
748 
749     return install_bind(key_to_bind(kcmd), kcmd, bs);
750 }
751 
752 /*
753  * bindkey:	add a new key to the key binding table
754  */
755 
756 /* ARGSUSED */
757 int
bindkey(int f GCC_UNUSED,int n GCC_UNUSED)758 bindkey(int f GCC_UNUSED, int n GCC_UNUSED)
759 {
760     return bind_any_key(&dft_bindings);
761 }
762 
763 /* ARGSUSED */
764 int
bind_i_key(int f GCC_UNUSED,int n GCC_UNUSED)765 bind_i_key(int f GCC_UNUSED, int n GCC_UNUSED)
766 {
767     return bind_any_key(&ins_bindings);
768 }
769 
770 /* ARGSUSED */
771 int
bind_c_key(int f GCC_UNUSED,int n GCC_UNUSED)772 bind_c_key(int f GCC_UNUSED, int n GCC_UNUSED)
773 {
774     return bind_any_key(&cmd_bindings);
775 }
776 
777 /* ARGSUSED */
778 int
bind_s_key(int f GCC_UNUSED,int n GCC_UNUSED)779 bind_s_key(int f GCC_UNUSED, int n GCC_UNUSED)
780 {
781     return bind_any_key(&sel_bindings);
782 }
783 
784 /* describe bindings bring up a fake buffer and list the key bindings
785 		   into it with view mode			*/
786 
787 /* remember whether we last did "apropos" or "describe-bindings" */
788 static char *last_lookup_string;
789 static CMDFLAGS last_whichcmds;
790 static int last_whichmode;
791 static int append_to_binding_list;
792 
793 /* ARGSUSED */
794 static int
update_binding_list(BUFFER * bp GCC_UNUSED)795 update_binding_list(BUFFER *bp GCC_UNUSED)
796 {
797     return liststuff(bindings_to_describe->bufname, append_to_binding_list,
798 		     makebindlist, (int) last_whichcmds, (void *) last_lookup_string);
799 }
800 
801 static int
describe_any_bindings(char * lookup,CMDFLAGS whichcmd)802 describe_any_bindings(char *lookup, CMDFLAGS whichcmd)
803 {
804     last_lookup_string = lookup;
805     last_whichcmds = whichcmd;
806 
807     return update_binding_list((BUFFER *) 0);
808 }
809 
810 /* ARGSUSED */
811 int
desbind(int f GCC_UNUSED,int n GCC_UNUSED)812 desbind(int f GCC_UNUSED, int n GCC_UNUSED)
813 {
814     return describe_any_bindings((char *) 0, (CMDFLAGS) 0);
815 }
816 
817 /* ARGSUSED */
818 static int
update_bindkeys_list(BUFFER * bp GCC_UNUSED)819 update_bindkeys_list(BUFFER *bp GCC_UNUSED)
820 {
821     return liststuff(bindings_to_describe->bufname, append_to_binding_list,
822 		     makebindkeyslist, (int) last_whichcmds, (void *) last_lookup_string);
823 }
824 
825 static int
describe_all_bindings(char * lookup,CMDFLAGS whichcmd,int mode)826 describe_all_bindings(char *lookup, CMDFLAGS whichcmd, int mode)
827 {
828     last_lookup_string = lookup;
829     last_whichcmds = whichcmd;
830     last_whichmode = mode;
831 
832     return update_bindkeys_list((BUFFER *) 0);
833 }
834 
835 /* ARGSUSED */
836 int
desall_bind(int f GCC_UNUSED,int n GCC_UNUSED)837 desall_bind(int f GCC_UNUSED, int n GCC_UNUSED)
838 {
839     return describe_all_bindings((char *) 0, (CMDFLAGS) 0, (f ? n : -1));
840 }
841 
842 static int
describe_all_keys_bindings(BINDINGS * bs,int mode)843 describe_all_keys_bindings(BINDINGS * bs, int mode)
844 {
845     int code;
846     bindings_to_describe = bs;
847     code = describe_all_bindings((char *) 0, (CMDFLAGS) 0, mode);
848     bindings_to_describe = &dft_bindings;
849     return code;
850 }
851 
852 int
desall_i_bind(int f GCC_UNUSED,int n GCC_UNUSED)853 desall_i_bind(int f GCC_UNUSED, int n GCC_UNUSED)
854 {
855     return describe_all_keys_bindings(&ins_bindings, (f ? n : -1));
856 }
857 
858 int
desall_c_bind(int f GCC_UNUSED,int n GCC_UNUSED)859 desall_c_bind(int f GCC_UNUSED, int n GCC_UNUSED)
860 {
861     return describe_all_keys_bindings(&cmd_bindings, (f ? n : -1));
862 }
863 
864 int
desall_s_bind(int f GCC_UNUSED,int n GCC_UNUSED)865 desall_s_bind(int f GCC_UNUSED, int n GCC_UNUSED)
866 {
867     return describe_all_keys_bindings(&sel_bindings, (f ? n : -1));
868 }
869 
870 static int
describe_alternate_bindings(BINDINGS * bs)871 describe_alternate_bindings(BINDINGS * bs)
872 {
873     int code;
874     bindings_to_describe = bs;
875     code = describe_any_bindings((char *) 0, (CMDFLAGS) 0);
876     bindings_to_describe = &dft_bindings;
877     return code;
878 }
879 
880 int
des_i_bind(int f GCC_UNUSED,int n GCC_UNUSED)881 des_i_bind(int f GCC_UNUSED, int n GCC_UNUSED)
882 {
883     return describe_alternate_bindings(&ins_bindings);
884 }
885 
886 int
des_c_bind(int f GCC_UNUSED,int n GCC_UNUSED)887 des_c_bind(int f GCC_UNUSED, int n GCC_UNUSED)
888 {
889     return describe_alternate_bindings(&cmd_bindings);
890 }
891 
892 int
des_s_bind(int f GCC_UNUSED,int n GCC_UNUSED)893 des_s_bind(int f GCC_UNUSED, int n GCC_UNUSED)
894 {
895     return describe_alternate_bindings(&sel_bindings);
896 }
897 
898 /* ARGSUSED */
899 int
desmotions(int f GCC_UNUSED,int n GCC_UNUSED)900 desmotions(int f GCC_UNUSED, int n GCC_UNUSED)
901 {
902     return describe_any_bindings((char *) 0, MOTION);
903 }
904 
905 /* ARGSUSED */
906 int
desopers(int f GCC_UNUSED,int n GCC_UNUSED)907 desopers(int f GCC_UNUSED, int n GCC_UNUSED)
908 {
909     return describe_any_bindings((char *) 0, OPER);
910 }
911 
912 /* ARGSUSED */
913 int
desglobals(int f GCC_UNUSED,int n GCC_UNUSED)914 desglobals(int f GCC_UNUSED, int n GCC_UNUSED)
915 {
916     return describe_any_bindings((char *) 0, GLOBOK);
917 }
918 
919 /* lookup function by substring */
920 /* ARGSUSED */
921 int
dessubstr(int f GCC_UNUSED,int n GCC_UNUSED)922 dessubstr(int f GCC_UNUSED, int n GCC_UNUSED)
923 {
924     int s;
925     static char substring[NSTRING];
926 
927     s = mlreply("Apropos string: ", substring, (UINT) sizeof(substring));
928     if (s != TRUE)
929 	return (s);
930 
931     return describe_any_bindings(substring, (CMDFLAGS) 0);
932 }
933 
934 static char described_cmd[NLINE + 1];	/* string to match cmd names to */
935 
936 /* lookup up function by name */
937 /* ARGSUSED */
938 int
desfunc(int f GCC_UNUSED,int n GCC_UNUSED)939 desfunc(int f GCC_UNUSED, int n GCC_UNUSED)
940 {
941     int s;
942     char *fnp;
943 
944     /* force an exact match by ourstrstr() later on from makefuncdesc() */
945     described_cmd[0] = '^';
946 
947     fnp = kbd_engl("Describe function whose full name is: ",
948 		   described_cmd + 1, 0);
949     if (fnp == NULL || engl2fnc(fnp) == NULL) {
950 	s = no_such_function(fnp);
951     } else {
952 	append_to_binding_list = TRUE;
953 	s = describe_any_bindings(described_cmd, (CMDFLAGS) 0);
954 	append_to_binding_list = FALSE;
955     }
956     return s;
957 }
958 
959 /* FIXME: this table should be generated from cmdtbl, but excluding the
960  * mouse and text pseudo-keys.  Note that the table isn't really
961  * sorted alphabetically, so it would be hard to generate in a nice way.
962  */
963 static void
make_key_names(int iarg GCC_UNUSED,void * varg GCC_UNUSED)964 make_key_names(int iarg GCC_UNUSED, void *varg GCC_UNUSED)
965 {
966     static const struct {
967 	int code;
968 	const char *name;
969     } table[] = {
970 	/* *INDENT-OFF* */
971 	{ KEY_Up,	   "KEY_Up" },
972 	{ KEY_Down,	   "KEY_Down" },
973 	{ KEY_Right,	   "KEY_Right" },
974 	{ KEY_Left,	   "KEY_Left" },
975 	{ 0,		   0 },
976 	{ KEY_Delete,	   "KEY_Delete" },
977 	{ KEY_End,	   "KEY_End" },
978 	{ KEY_Find,	   "KEY_Find" },
979 	{ KEY_Help,	   "KEY_Help" },
980 	{ KEY_Home,	   "KEY_Home" },
981 	{ KEY_Insert,	   "KEY_Insert" },
982 	{ KEY_Menu,	   "KEY_Menu" },
983 	{ KEY_Next,	   "KEY_Next" },
984 	{ KEY_Prior,	   "KEY_Prior" },
985 	{ KEY_Select,	   "KEY_Select" },
986 	{ KEY_BackTab,	   "KEY_BackTab" },
987 	{ 0,		   0 },
988 	{ KEY_F1,	   "KEY_F1" },
989 	{ KEY_F2,	   "KEY_F2" },
990 	{ KEY_F3,	   "KEY_F3" },
991 	{ KEY_F4,	   "KEY_F4" },
992 	{ KEY_F5,	   "KEY_F5" },
993 	{ KEY_F6,	   "KEY_F6" },
994 	{ KEY_F7,	   "KEY_F7" },
995 	{ KEY_F8,	   "KEY_F8" },
996 	{ KEY_F9,	   "KEY_F9" },
997 	{ KEY_F10,	   "KEY_F10" },
998 	{ KEY_F11,	   "KEY_F11" },
999 	{ KEY_F12,	   "KEY_F12" },
1000 	{ KEY_F13,	   "KEY_F13" },
1001 	{ KEY_F14,	   "KEY_F14" },
1002 	{ KEY_F15,	   "KEY_F15" },
1003 	{ KEY_F16,	   "KEY_F16" },
1004 	{ KEY_F17,	   "KEY_F17" },
1005 	{ KEY_F18,	   "KEY_F18" },
1006 	{ KEY_F19,	   "KEY_F19" },
1007 	{ KEY_F20,	   "KEY_F20" },
1008 	{ KEY_F21,	   "KEY_F21" },
1009 	{ KEY_F22,	   "KEY_F22" },
1010 	{ KEY_F23,	   "KEY_F23" },
1011 	{ KEY_F24,	   "KEY_F24" },
1012 	{ KEY_F25,	   "KEY_F25" },
1013 	{ KEY_F26,	   "KEY_F26" },
1014 	{ KEY_F27,	   "KEY_F27" },
1015 	{ KEY_F28,	   "KEY_F28" },
1016 	{ KEY_F29,	   "KEY_F29" },
1017 	{ KEY_F30,	   "KEY_F30" },
1018 	{ KEY_F31,	   "KEY_F31" },
1019 	{ KEY_F32,	   "KEY_F32" },
1020 	{ KEY_F33,	   "KEY_F33" },
1021 	{ KEY_F34,	   "KEY_F34" },
1022 	{ KEY_F35,	   "KEY_F35" },
1023 	{ 0,		   0 },
1024 	{ KEY_KP_F1,	   "KEY_KP_F1" },
1025 	{ KEY_KP_F2,	   "KEY_KP_F2" },
1026 	{ KEY_KP_F3,	   "KEY_KP_F3" },
1027 	{ KEY_KP_F4,	   "KEY_KP_F4" },
1028 	/* *INDENT-ON* */
1029 
1030     };
1031     unsigned n;
1032     char temp[80];
1033 
1034     bprintf("Coding for function and cursor-keys (your terminal may not support all):\n");
1035     for (n = 0; n < TABLESIZE(table); n++) {
1036 	bputc('\n');
1037 	if (table[n].code != 0)
1038 	    bprintf("%s\t%s", kcod2prc(table[n].code, temp), table[n].name);
1039     }
1040 }
1041 
1042 int
des_keynames(int f GCC_UNUSED,int n GCC_UNUSED)1043 des_keynames(int f GCC_UNUSED, int n GCC_UNUSED)
1044 {
1045     return liststuff(KEY_NAMES_BufName, FALSE, make_key_names, 0, (void *) 0);
1046 }
1047 
1048 /* lookup up function by key */
1049 /* ARGSUSED */
1050 static int
prompt_describe_key(BINDINGS * bs)1051 prompt_describe_key(BINDINGS * bs)
1052 {
1053     int c;			/* character to describe */
1054     char outseq[NSTRING];	/* output buffer for command sequence */
1055     NTAB temp;			/* name table pointer */
1056     int s;
1057 
1058     /* prompt the user to type us a key to describe */
1059     mlprompt("Describe the function bound to this key sequence: ");
1060 
1061     /* check to see if we are executing a command line */
1062     if (clexec) {
1063 	if ((c = mac_token2kcod(TRUE)) < 0)
1064 	    return (FALSE);
1065     } else {
1066 	c = kbd_seq_nomap();
1067 	if (c < 0) {
1068 	    mlforce("[Not a bindable key-sequence]");
1069 	    return (FALSE);
1070 	}
1071     }
1072 
1073     (void) kcod2prc(c, outseq);
1074     hst_append_s(outseq, FALSE, TRUE);	/* cannot replay this, but can see it */
1075 
1076     /* find the function bound to the key */
1077     if (!fnc2ntab(&temp, kcod2fnc(bs, c))) {
1078 	mlwrite("Key sequence '%s' is not bound to anything.",
1079 		outseq);
1080 	return TRUE;
1081     }
1082 
1083     /* describe it */
1084     described_cmd[0] = '^';
1085     (void) vl_strncpy(described_cmd + 1, temp.n_name, sizeof(described_cmd));
1086     append_to_binding_list = TRUE;
1087     s = describe_any_bindings(described_cmd, (CMDFLAGS) 0);
1088     append_to_binding_list = FALSE;
1089 
1090     mlwrite("Key sequence '%s' is bound to function \"%s\"",
1091 	    outseq, temp.n_name);
1092 
1093     return s;
1094 }
1095 
1096 int
deskey(int f GCC_UNUSED,int n GCC_UNUSED)1097 deskey(int f GCC_UNUSED, int n GCC_UNUSED)
1098 {
1099     return prompt_describe_key(&dft_bindings);
1100 }
1101 
1102 int
des_i_key(int f GCC_UNUSED,int n GCC_UNUSED)1103 des_i_key(int f GCC_UNUSED, int n GCC_UNUSED)
1104 {
1105     return prompt_describe_key(&ins_bindings);
1106 }
1107 
1108 int
des_c_key(int f GCC_UNUSED,int n GCC_UNUSED)1109 des_c_key(int f GCC_UNUSED, int n GCC_UNUSED)
1110 {
1111     return prompt_describe_key(&cmd_bindings);
1112 }
1113 
1114 int
des_s_key(int f GCC_UNUSED,int n GCC_UNUSED)1115 des_s_key(int f GCC_UNUSED, int n GCC_UNUSED)
1116 {
1117     return prompt_describe_key(&sel_bindings);
1118 }
1119 
1120 /* returns a name in double-quotes */
1121 static char *
quoted(char * dst,const char * src)1122 quoted(char *dst, const char *src)
1123 {
1124     char *result = 0;
1125     if (dst != 0) {
1126 	result = strcat(strcat(strcpy(dst, "\""), src), "\"");
1127     } else {
1128 	char temp[NLINE];
1129 	bputsn_xcolor(quoted(temp, src), -1, XCOLOR_STRING);
1130     }
1131     return result;
1132 }
1133 
1134 /* returns the number of columns used by the given string */
1135 static unsigned
converted_len(char * buffer)1136 converted_len(char *buffer)
1137 {
1138     unsigned len = 0, c;
1139     while ((c = CharOf(*buffer++)) != EOS) {
1140 	if (c == '\t')
1141 	    len |= 7;
1142 	len++;
1143     }
1144     return len;
1145 }
1146 
1147 static void
quote_and_pad(char * dst,const char * src)1148 quote_and_pad(char *dst, const char *src)
1149 {
1150     quoted(dst, src);
1151     if (dst != 0) {
1152 	if (converted_len(dst) >= 32)
1153 	    strcat(dst, "\t");
1154 	else
1155 	    while (converted_len(dst) < 32)
1156 		strcat(dst, "\t");
1157     } else {
1158 	do {
1159 	    bputc('\t');
1160 	} while (getccol(FALSE) < 32);
1161     }
1162 }
1163 
1164 #if OPT_MENUS || OPT_NAMEBST
1165 /* force the buffer to a tab-stop if needed */
1166 static char *
to_tabstop(char * buffer)1167 to_tabstop(char *buffer)
1168 {
1169     size_t len = strlen(buffer);
1170     unsigned cpos = converted_len(buffer);
1171     if (cpos & 7 || (len != 0 && !isBlank(buffer[len - 1])))
1172 	(void) strcat(buffer, "\t");
1173     return skip_string(buffer);
1174 }
1175 
1176 /* convert a key binding, padding to the next multiple of 8 columns */
1177 static void
convert_kcode(int c,char * buffer)1178 convert_kcode(int c, char *buffer)
1179 {
1180     if (buffer != 0) {
1181 	(void) kcod2prc(c, to_tabstop(buffer));
1182     } else {
1183 	char temp[NLINE];
1184 	if (llength(DOT.l) > 0
1185 	    && !isBlank(lvalue(DOT.l)[llength(DOT.l) - 1])) {
1186 	    bputc('\t');
1187 	}
1188 	(void) kcod2prc(c, temp);
1189 	bputsn_xcolor(temp, -1, XCOLOR_STRING);
1190     }
1191 }
1192 
1193 static void
convert_cmdfunc(BINDINGS * bs,const CMDFUNC * cmd,char * outseq)1194 convert_cmdfunc(BINDINGS * bs, const CMDFUNC * cmd, char *outseq)
1195 {
1196     int i;
1197     KBIND *kbp;
1198 
1199     /* look in the simple ascii binding table first */
1200     for (i = 0; i < N_chars; i++)
1201 	if (bs->kb_normal[i] == cmd)
1202 	    convert_kcode(i, outseq);
1203 
1204     /* then look in the multi-key table */
1205 #if OPT_REBIND
1206     for (kbp = bs->kb_extra; kbp != bs->kb_special; kbp = kbp->k_link) {
1207 	if (kbp->k_cmd == cmd)
1208 	    convert_kcode(kbp->k_code, outseq);
1209     }
1210 #endif
1211     for (kbp = bs->kb_special; kbp->k_cmd; kbp++)
1212 	if (kbp->k_cmd == cmd)
1213 	    convert_kcode(kbp->k_code, outseq);
1214 }
1215 #endif /* OPT_MENUS || OPT_NAMEBST */
1216 
1217 static int
add_newline(void)1218 add_newline(void)
1219 {
1220     int rc = TRUE;
1221     if (buf_head(curbp) != DOT.l)
1222 	rc = bputc('\n');
1223     return rc;
1224 }
1225 
1226 static int
add_line(const char * text)1227 add_line(const char *text)
1228 {
1229     int rc = add_newline();
1230     if (rc)
1231 	rc = bputsn(text, -1);
1232     return rc;
1233 }
1234 
1235 #if OPT_ONLINEHELP
1236 static int
show_onlinehelp(const CMDFUNC * cmd)1237 show_onlinehelp(const CMDFUNC * cmd)
1238 {
1239     char outseq[NLINE];		/* output buffer for text */
1240     const char *text = cmd->c_help;
1241     CMDFLAGS flags = cmd->c_flags;
1242 
1243     if (text && *text) {
1244 	if (*text == SQUOTE)
1245 	    text++;
1246 	(void) lsprintf(outseq, "  (%s %s )",
1247 			(cmd->c_flags & MOTION) ? " motion: " :
1248 			(cmd->c_flags & OPER) ? " operator: " : "",
1249 			text);
1250     } else {
1251 	(void) lsprintf(outseq, "  ( no help for this command )");
1252     }
1253     if (!add_line(outseq))
1254 	return FALSE;
1255 #if OPT_PROCEDURES
1256     if ((cmd->c_flags & CMD_TYPE) == CMD_PROC)
1257 	flags &= ~(UNDO | REDO);
1258 #endif
1259 #if OPT_PERL
1260     if ((cmd->c_flags & CMD_TYPE) == CMD_PERL)
1261 	flags &= ~(UNDO | REDO);
1262 #endif
1263     if (flags & (RANGE | UNDO | REDO | GLOBOK)) {
1264 	const char *gaps = "";
1265 	char *next;
1266 
1267 	next = lsprintf(outseq, "  ( ");
1268 	if (cmd->c_flags & (UNDO | REDO)) {
1269 	    {
1270 		next = lsprintf(next, "undoable");
1271 		gaps = ", ";
1272 	    }
1273 	}
1274 	if (cmd->c_flags & RANGE) {
1275 	    next = lsprintf(next, "%saccepts range", gaps);
1276 	    gaps = ", ";
1277 	}
1278 	if (cmd->c_flags & GLOBOK) {
1279 	    next = lsprintf(next, "%smay follow global command", gaps);
1280 	}
1281 	(void) lsprintf(next, " )");
1282 	if (!add_line(outseq))
1283 	    return FALSE;
1284     }
1285 #if OPT_MACRO_ARGS
1286     if (cmd->c_args != 0) {
1287 	int i;
1288 	for (i = 0; cmd->c_args[i].pi_type != PT_UNKNOWN; i++) {
1289 	    (void) lsprintf(outseq, "  ( $%d = %s )", i + 1,
1290 			    (cmd->c_args[i].pi_text != 0)
1291 			    ? cmd->c_args[i].pi_text
1292 			    : choice_to_name(&fsm_paramtypes_blist,
1293 					     cmd->c_args[i].pi_type));
1294 	    if (!add_line(outseq))
1295 		return FALSE;
1296 	}
1297     }
1298 #endif
1299     return TRUE;
1300 }
1301 #endif
1302 
1303 #if OPT_NAMEBST
1304 struct bindlist_data {
1305     UINT mask;			/* oper/motion mask */
1306     int min;			/* minimum key length */
1307     char *apropos;		/* key check */
1308     BINDINGS *bs;
1309 };
1310 
1311 static int
btree_walk(BI_NODE * node,int (* func)(BI_NODE *,const void *),const void * data)1312 btree_walk(BI_NODE * node, int (*func) (BI_NODE *, const void *),
1313 	   const void *data)
1314 {
1315     if (node) {
1316 	if (btree_walk(BI_LEFT(node), func, data))
1317 	    return 1;
1318 
1319 	if (BI_KEY(node))
1320 	    func(node, data);
1321 
1322 	if (btree_walk(BI_RIGHT(node), func, data))
1323 	    return 1;
1324     }
1325 
1326     return 0;
1327 }
1328 
1329 static int
clearflag_func(BI_NODE * n,const void * d GCC_UNUSED)1330 clearflag_func(BI_NODE * n, const void *d GCC_UNUSED)
1331 {
1332     clr_typed_flags(n->value.n_flags, UCHAR, NBST_DONE);
1333     return 0;
1334 }
1335 
1336 #define isShortCmd(s) ((int) strlen(s) <= SHORT_CMD_LEN)
1337 
1338 static int
makebind_func(BI_NODE * node,const void * d)1339 makebind_func(BI_NODE * node, const void *d)
1340 {
1341     const struct bindlist_data *data = (const struct bindlist_data *) d;
1342     const CMDFUNC *cmd = node->value.n_cmd;
1343     int n;
1344 
1345     /* has this been listed? */
1346     if (node->value.n_flags & NBST_DONE)
1347 	return 0;
1348 
1349     /* are we interested in this type of command? */
1350     if (data->mask && !(cmd->c_flags & data->mask))
1351 	return 0;
1352 
1353     /* try to avoid alphabetizing by the real short names */
1354     if (data->min && isShortCmd(BI_KEY(node)))
1355 	return 0;
1356 
1357     /* failed lookup by substring? */
1358     if (data->apropos && !ourstrstr(BI_KEY(node), data->apropos, TRUE))
1359 	return 0;
1360 
1361     /* add in the command name */
1362     quote_and_pad(0, BI_KEY(node));
1363     convert_cmdfunc(data->bs, cmd, 0);
1364 
1365     node->value.n_flags |= NBST_DONE;
1366 
1367     if (cmd->c_alias != 0) {
1368 	const char *top = BI_KEY(node);
1369 	const char *name;
1370 	for (n = 0; (name = cmd->c_alias[n]) != 0; ++n) {
1371 	    if (data->min) {
1372 		if ((isShortCmd(name)
1373 		     || ((strcmp(name, top) > 0)
1374 			 && !isShortCmd(top)))
1375 		    && add_newline()
1376 		    && bputsn("  or\t", -1)) {
1377 		    quoted(0, name);
1378 		}
1379 	    } else {
1380 		if ((isShortCmd(name)
1381 		     && isShortCmd(top)
1382 		     && strcmp(name, top) > 0)
1383 		    && add_newline()
1384 		    && bputsn("  or\t", -1)) {
1385 		    quoted(0, name);
1386 		}
1387 	    }
1388 	}
1389     }
1390 #if OPT_ONLINEHELP
1391     if (!show_onlinehelp(cmd))
1392 	return FALSE;
1393 #endif
1394     /* blank separator */
1395     if (!add_line(""))
1396 	return 1;
1397 
1398     return 0;
1399 }
1400 
1401 /* build a binding list (limited or full) */
1402 /* ARGSUSED */
1403 static void
makebindlist(int whichmask,void * mstring)1404 makebindlist(int whichmask, void *mstring)
1405 {
1406     BI_TREE *my_bst = bst_current();
1407     struct bindlist_data data;
1408 
1409     data.mask = (UINT) whichmask;
1410     data.min = SHORT_CMD_LEN;
1411     data.apropos = (char *) mstring;
1412     data.bs = bindings_to_describe;
1413 
1414     /* let us know this is in progress */
1415     mlwrite("[Building binding list]");
1416 
1417     /* clear the NBST_DONE flag */
1418     btree_walk(&(my_bst->head), clearflag_func, 0);
1419 
1420     /* create binding list */
1421     if (btree_walk(&(my_bst->head), makebind_func, &data))
1422 	return;
1423 
1424     /* catch entries with no synonym > SHORT_CMD_LEN */
1425     data.min = 0;
1426     if (btree_walk(&(my_bst->head), makebind_func, &data))
1427 	return;
1428 
1429     mlerase();			/* clear the message line */
1430 }
1431 
1432 /* build a binding list (limited or full) */
1433 /* ARGSUSED */
1434 static void
makebindkeyslist(int whichmask,void * mstring)1435 makebindkeyslist(int whichmask, void *mstring)
1436 {
1437     char outseq[NSTRING];	/* output buffer for command sequence */
1438     NTAB temp;			/* name table pointer */
1439     int ch;
1440     int state;
1441 
1442     (void) mstring;
1443 
1444     /* let us know this is in progress */
1445     mlwrite("[Building binding list]");
1446     for (state = 0; state < 3; ++state) {
1447 	int modify = 0;
1448 	int found = 0;
1449 	const char *what = "?";
1450 	switch (state) {
1451 	case 0:
1452 	    what = "Normal keys";
1453 	    break;
1454 	case 1:
1455 	    what = "^A-keys";
1456 	    modify = CTLA;
1457 	    break;
1458 	case 2:
1459 	    what = "^X-keys";
1460 	    modify = CTLX;
1461 	    break;
1462 	}
1463 	bprintf("%s-- %s", state ? "\n\n" : "", what);
1464 	for (ch = 0; ch < N_chars; ++ch) {
1465 	    (void) kcod2prc(ch + modify, outseq);
1466 	    if (fnc2ntab(&temp, kcod2fnc(bindings_to_describe, ch + modify))) {
1467 		if (whichmask && !(temp.n_cmd->c_flags & (CMDFLAGS) whichmask))
1468 		    continue;
1469 		bputc('\n');
1470 		bprintf("%d\t%s\t", ch, outseq);
1471 		quoted(0, temp.n_name);
1472 		++found;
1473 	    } else if (last_whichmode > 1) {
1474 		bputc('\n');
1475 		bprintf("%d\t%s", ch, outseq);
1476 	    }
1477 	}
1478 	if (found) {
1479 	    bprintf("\n-- %s total: %d", what, found);
1480 	}
1481     }
1482     mlerase();			/* clear the message line */
1483 }
1484 #else /* OPT_NAMEBST */
1485 /* fully describe a function into the current buffer, given a pointer to
1486  * its name table entry */
1487 static int
makefuncdesc(int j,char * listed)1488 makefuncdesc(int j, char *listed)
1489 {
1490     int i;
1491     const CMDFUNC *cmd = nametbl[j].n_cmd;
1492     char outseq[NLINE];		/* output buffer for keystroke sequence */
1493 
1494     /* add in the command name */
1495     quote_and_pad(outseq, nametbl[j].n_name);
1496 
1497     /* dump the line */
1498     if (!add_line(outseq))
1499 	return FALSE;
1500 
1501     /* then look for synonyms */
1502     (void) strcpy(outseq, "  or\t");
1503     for (i = 0; nametbl[i].n_name != 0; i++) {
1504 	/* if it's the one we're on, skip */
1505 	if (i == j)
1506 	    continue;
1507 	/* if it's already been listed, skip */
1508 	if (listed[i])
1509 	    continue;
1510 	/* if it's not a synonym, skip */
1511 	if (nametbl[i].n_cmd != cmd)
1512 	    continue;
1513 	(void) quoted(outseq + 5, nametbl[i].n_name);
1514 	if (!add_line(outseq))
1515 	    return FALSE;
1516     }
1517 
1518 #if OPT_ONLINEHELP
1519     if (!show_onlinehelp(cmd))
1520 	return FALSE;
1521 #endif
1522     /* blank separator */
1523     if (!add_line(""))
1524 	return FALSE;
1525 
1526     return TRUE;
1527 }
1528 
1529 /* build a binding list (limited or full) */
1530 /* ARGSUSED */
1531 static void
makebindlist(int whichmask,void * mstring)1532 makebindlist(int whichmask, void *mstring)
1533 {
1534     int pass;
1535     int j;
1536     int ok = TRUE;		/* reset if out-of-memory, etc. */
1537     char *listed;
1538 
1539     beginDisplay();
1540     listed = typecallocn(char, (size_t) nametbl_size);
1541     endofDisplay();
1542 
1543     if (listed == 0) {
1544 	(void) no_memory(bindings_to_describe->bufname);
1545 	return;
1546     }
1547 
1548     /* let us know this is in progress */
1549     mlwrite("[Building binding list]");
1550 
1551     /* build the contents of this window, inserting it line by line */
1552     for (pass = 0; pass < 2; pass++) {
1553 	for (j = 0; nametbl[j].n_name != 0; j++) {
1554 
1555 	    /* if we've already described this one, move on */
1556 	    if (listed[j])
1557 		continue;
1558 
1559 	    /* are we interested in this type of command? */
1560 	    if (whichmask && !(nametbl[j].n_cmd->c_flags & whichmask))
1561 		continue;
1562 
1563 	    /* try to avoid alphabetizing by the real short names */
1564 	    if (pass == 0 && (int) strlen(nametbl[j].n_name) <= SHORT_CMD_LEN)
1565 		continue;
1566 
1567 	    /* if we are executing an apropos command
1568 	       and current string doesn't include the search string */
1569 	    if (mstring
1570 		&& !ourstrstr(nametbl[j].n_name, (char *) mstring, TRUE))
1571 		continue;
1572 
1573 	    ok = makefuncdesc(j, listed);
1574 	    if (!ok)
1575 		break;
1576 
1577 	    listed[j] = TRUE;	/* mark it as already listed */
1578 	}
1579     }
1580 
1581     if (ok)
1582 	mlerase();		/* clear the message line */
1583 
1584     beginDisplay();
1585     free(listed);
1586     endofDisplay();
1587 }
1588 #endif /* OPT_NAMEBST */
1589 
1590 #endif /* OPT_REBIND */
1591 
1592 #if OPT_REBIND || OPT_EVAL
1593 /* much like the "standard" strstr, but if the substring starts
1594 	with a '^', we discard it and force an exact match.
1595 	returns 1-based offset of match, or 0.  */
1596 int
ourstrstr(const char * haystack,const char * needle,int anchor)1597 ourstrstr(const char *haystack, const char *needle, int anchor)
1598 {
1599     if (anchor && *needle == '^') {
1600 	return strcmp(needle + 1, haystack) ? 0 : 1;
1601     } else {
1602 	size_t nl = strlen(needle);
1603 	size_t hl = strlen(haystack);
1604 	const char *hp = haystack;
1605 	while (hl >= nl && *hp) {
1606 	    if (!strncmp(hp, needle, nl))
1607 		return (int) (hp - haystack + 1);
1608 	    hp++;
1609 	    hl--;
1610 	}
1611 	return 0;
1612     }
1613 }
1614 #endif /* OPT_REBIND || OPT_EVAL */
1615 
1616 #if SYS_UNIX
1617 /*
1618  * Only source a startup file on Unix if it (and the directory in which it
1619  * resides) happens to be in a directory owned by the current user (or root),
1620  * and neither is writable by other users.
1621  */
1622 static int
is_our_file(char * fname,int check_owner)1623 is_our_file(char *fname, int check_owner)
1624 {
1625     int status = FALSE;
1626     struct stat sb;
1627 
1628     if (stat(fname, &sb) == 0) {
1629 	if ((sb.st_mode & 002) != 0) {
1630 	    TPRINTF(("Ignoring world-writable \"%s\"\n", fname));
1631 	} else if ((sb.st_mode & 020) != 0) {
1632 	    TPRINTF(("Ignoring group-writable \"%s\"\n", fname));
1633 	} else if (check_owner
1634 		   && (sb.st_uid != getuid() && sb.st_uid != 0)) {
1635 	    TPRINTF(("\"%s\" is not our %s\n",
1636 		     fname, S_ISDIR(sb.st_mode) ? "directory" : "file"));
1637 	} else {
1638 	    status = TRUE;
1639 	}
1640     } else {
1641 	TPRINTF(("\"%s\": %s\n", fname, strerror(errno)));
1642     }
1643     return status;
1644 }
1645 #endif
1646 
1647 static int
check_file_access(char * fname,UINT mode)1648 check_file_access(char *fname, UINT mode)
1649 {
1650     int result = ffaccess(fname, mode);
1651 
1652     TRACE((T_CALLED "check_file_access(%s, %#x) = %d\n", fname, mode, result));
1653 #if SYS_UNIX
1654 #define CHK_OWNER (FL_HOME | FL_CDIR)
1655     if (result) {
1656 	int doit = FALSE;
1657 	int owner;
1658 #ifdef GVAL_CHK_ACCESS
1659 	int check;
1660 #endif
1661 
1662 	/*
1663 	 * Suppress ownership check for absolute pathnames.
1664 	 */
1665 	if (is_abs_pathname(fname))
1666 	    mode &= ~CHK_OWNER;
1667 
1668 	/*
1669 	 * Check ownership for current- and home-directories.
1670 	 */
1671 	owner = (mode < (CHK_OWNER * 2));
1672 
1673 	if (mode & FL_EXECABLE) {
1674 	    doit = TRUE;
1675 	}
1676 #ifdef GVAL_CHK_ACCESS
1677 	else if ((check = global_g_val(GVAL_CHK_ACCESS)) != 0) {
1678 	    /*
1679 	     * The values for check-access mode are single bits.  But we treat
1680 	     * those as an ordered list.  Hence, setting "home" implies we
1681 	     * also check "current", since that is the first item in the list.
1682 	     */
1683 	    if ((mode & FL_ALWAYS) && !((UINT) check & FL_ALWAYS)) {
1684 		doit = FALSE;
1685 	    } else if ((mode
1686 			& ((UINT) check * 2 - 1)
1687 			& ~(FL_EXECABLE | FL_WRITEABLE | FL_READABLE)) != 0) {
1688 		doit = TRUE;
1689 	    }
1690 	}
1691 #endif
1692 
1693 	if (doit && !(mode & FL_INSECURE)) {
1694 	    char *dname = (char *) malloc(NFILEN + strlen(fname) + 10);
1695 	    char *leaf;
1696 
1697 	    if (dname != 0) {
1698 		leaf = pathleaf(lengthen_path(strcpy(dname, fname)));
1699 		if ((leaf - 1) != dname)
1700 		    *--leaf = EOS;
1701 		if (!is_our_file(dname, owner) || !is_our_file(fname, owner)) {
1702 		    mlforce("[Skipping '%s' (insecure permissions)]", fname);
1703 		    result = ABORT;
1704 		}
1705 		free(dname);
1706 	    }
1707 	}
1708     }
1709 #endif
1710 
1711     returnCode(result);
1712 }
1713 
1714 static char *
locate_fname(char * dir_name,char * fname,UINT mode)1715 locate_fname(char *dir_name, char *fname, UINT mode)
1716 {
1717     static char fullpath[NFILEN];	/* expanded path */
1718 
1719     if (!isEmpty(dir_name)
1720 	&& check_file_access(pathcat(fullpath, dir_name, fname), mode) == TRUE)
1721 	return (fullpath);
1722 
1723     return 0;
1724 }
1725 
1726 static char *
locate_file_in_list(char * list,char * fname,UINT mode)1727 locate_file_in_list(char *list, char *fname, UINT mode)
1728 {
1729     const char *cp;
1730     char *sp;
1731     char dir_name[NFILEN];
1732     int first = TRUE;
1733 
1734     if ((cp = list) != 0) {
1735 	while ((cp = parse_pathlist(cp, dir_name, &first)) != 0) {
1736 	    if ((sp = locate_fname(dir_name, fname, mode)) != 0)
1737 		return sp;
1738 	}
1739     }
1740     return 0;
1741 }
1742 
1743 #if OPT_PATHLOOKUP
1744 #if SYS_VMS
1745 static char *
PATH_value(void)1746 PATH_value(void)
1747 {
1748     /* On VAX/VMS, the PATH environment variable is only the
1749      * current-dir.  Fake up an acceptable alternative.
1750      */
1751 
1752     static TBUFF *myfiles;
1753     char *tmp;
1754 
1755     if (!tb_length(myfiles)) {
1756 	char mypath[NFILEN];
1757 
1758 	(void) vl_strncpy(mypath, NONNULL(prog_arg), sizeof(mypath));
1759 	if ((tmp = vms_pathleaf(mypath)) == mypath) {
1760 	    (void) vl_strncpy(mypath, current_directory(FALSE), sizeof(mypath));
1761 	} else {
1762 	    *tmp = EOS;
1763 	}
1764 
1765 	if (!tb_init(&myfiles, EOS)
1766 	    || !tb_sappend(&myfiles, mypath)
1767 	    || !tb_sappend(&myfiles,
1768 			   ",SYS$SYSTEM:,SYS$LIBRARY:")
1769 	    || !tb_append(&myfiles, EOS))
1770 	    return NULL;
1771     }
1772     return tb_values(myfiles);
1773 }
1774 #else /* UNIX or MSDOS */
1775 #define PATH_value() getenv("PATH")
1776 #endif
1777 #endif /* OPT_PATHLOOKUP */
1778 
1779 /* config files that vile is interested in may live in various places,
1780  * and which may be constrained to various r/w/x modes.  the files may
1781  * be
1782  *  in the current directory,
1783  *  in the HOME directory,
1784  *  along a special "path" variable called "VILE_STARTUP_PATH",
1785  *  they may be in the directory where we found the vile executable itself,
1786  *  or they may be along the user's PATH.
1787  * in addition, they may be readable, writeable, or executable.
1788  */
1789 
1790 char *
cfg_locate(char * fname,UINT which)1791 cfg_locate(char *fname, UINT which)
1792 {
1793     char *sp;
1794     UINT mode = (which & (FL_ALWAYS |
1795 			  FL_EXECABLE |
1796 			  FL_WRITEABLE |
1797 			  FL_READABLE |
1798 			  FL_INSECURE));
1799 
1800 #define FL_BIT(name) ((which & FL_##name) ? " " #name : "")
1801     TRACE((T_CALLED "cfg_locate('%s',%s%s%s%s%s%s%s%s%s%s%s)\n", NonNull(fname),
1802 	   FL_BIT(EXECABLE),
1803 	   FL_BIT(WRITEABLE),
1804 	   FL_BIT(READABLE),
1805 	   FL_BIT(CDIR),
1806 	   FL_BIT(HOME),
1807 	   FL_BIT(EXECDIR),
1808 	   FL_BIT(STARTPATH),
1809 	   FL_BIT(PATH),
1810 	   FL_BIT(LIBDIR),
1811 	   FL_BIT(ALWAYS),
1812 	   FL_BIT(INSECURE)));
1813 
1814     /* take care of special cases */
1815     if (!fname || !fname[0] || isSpace(fname[0]))
1816 	returnString(NULL);
1817     else if (isShellOrPipe(fname))
1818 	returnString(fname);
1819 
1820     /* look in the current directory */
1821     if (which & FL_CDIR) {
1822 	if (check_file_access(fname, FL_CDIR | mode) == TRUE) {
1823 	    returnString(fname);
1824 	}
1825     }
1826 
1827     if ((which & FL_HOME)	/* look in the home directory */
1828 	&&((sp = locate_fname(home_dir(), fname, FL_HOME | mode)) != 0))
1829 	returnString(sp);
1830 
1831     if ((which & FL_EXECDIR)	/* look in vile's bin directory */
1832 	&&((sp = locate_fname(exec_pathname, fname, FL_EXECDIR | mode)) != 0))
1833 	returnString(sp);
1834 
1835     if ((which & FL_STARTPATH)	/* look along "VILE_STARTUP_PATH" */
1836 	&&(sp = locate_file_in_list(startup_path,
1837 				    fname,
1838 				    FL_STARTPATH | mode)) != 0)
1839 	returnString(sp);
1840 
1841     if (which & FL_PATH) {	/* look along "PATH" */
1842 #if OPT_PATHLOOKUP
1843 	if ((sp = locate_file_in_list(PATH_value(),
1844 				      fname,
1845 				      FL_PATH | mode)) != 0)
1846 	    returnString(sp);
1847 #endif /* OPT_PATHLOOKUP */
1848 
1849     }
1850 
1851     if ((which & FL_LIBDIR)	/* look along "VILE_LIBDIR_PATH" */
1852 	&&(sp = locate_file_in_list(libdir_path,
1853 				    fname,
1854 				    FL_LIBDIR | mode)) != 0)
1855 	returnString(sp);
1856 
1857     returnString(NULL);
1858 }
1859 
1860 #if OPT_SHOW_WHICH
1861 static int
ask_which_file(char * fname)1862 ask_which_file(char *fname)
1863 {
1864     static TBUFF *last;
1865 
1866     return mlreply_file("Which file: ", &last,
1867 			FILEC_READ | FILEC_PROMPT, fname);
1868 }
1869 
1870 static void
list_one_fname(char * fname,UINT mode)1871 list_one_fname(char *fname, UINT mode)
1872 {
1873     int tag = check_file_access(fname, mode);
1874 
1875     bprintf("\n%s\t", ((tag == ABORT)
1876 		       ? "?"
1877 		       : ((tag == TRUE)
1878 			  ? "*"
1879 			  : "")));
1880 
1881 #if OPT_HYPERTEXT
1882     if (tag == TRUE) {
1883 	REGION myRegion;
1884 	MARK myDot;
1885 	TBUFF *myBuff = 0;
1886 	TBUFF *myFile = 0;
1887 
1888 	memset(&myRegion, 0, sizeof(myRegion));
1889 	myRegion.r_orig = DOT;
1890 	bprintf("%s", fname);
1891 	myRegion.r_end = DOT;
1892 
1893 	tb_scopy(&myBuff, "view ");
1894 	path_quote(&myFile, fname);
1895 	bsl_to_sl_inplace(tb_values(myFile));
1896 	tb_sappend0(&myBuff, tb_values(myFile));
1897 
1898 	myDot = DOT;
1899 	attributeregion_in_region(&myRegion,
1900 				  rgn_EXACT,
1901 				  VAREV,
1902 				  tb_values(myBuff));
1903 	tb_free(&myBuff);
1904 	tb_free(&myFile);
1905 	DOT = myDot;
1906     } else
1907 #endif
1908 	bprintf("%s", fname);
1909 }
1910 
1911 static void
list_which_fname(char * dir_name,char * fname,UINT mode)1912 list_which_fname(char *dir_name, char *fname, UINT mode)
1913 {
1914     char fullpath[NFILEN];	/* expanded path */
1915 
1916     if (!isEmpty(dir_name)) {
1917 	list_one_fname(SL_TO_BSL(pathcat(fullpath, dir_name, fname)), mode);
1918     }
1919 }
1920 
1921 static void
list_which_file_in_list(char * list,char * fname,UINT mode)1922 list_which_file_in_list(char *list, char *fname, UINT mode)
1923 {
1924     const char *cp;
1925     char dir_name[NFILEN];
1926     int first = TRUE;
1927 
1928     if ((cp = list) != 0) {
1929 	while ((cp = parse_pathlist(cp, dir_name, &first)) != 0) {
1930 	    list_which_fname(dir_name, fname, mode);
1931 	}
1932     }
1933 }
1934 
1935 static void
list_which(LIST_ARGS)1936 list_which(LIST_ARGS)
1937 {
1938     UINT uflag = (UINT) flag;
1939     char *fname = (char *) ptr;
1940     UINT mode = (uflag & (FL_EXECABLE | FL_WRITEABLE | FL_READABLE));
1941 
1942     /* take care of special cases */
1943     if (!fname || !fname[0] || isSpace(fname[0]))
1944 	return;
1945     else if (isShellOrPipe(fname))
1946 	return;
1947 
1948     bprintf("Show which %s-paths are tested for:\n\t%s\n(\"*\" marks found-files)\n",
1949 	    (mode & FL_EXECABLE) ? "executable" : "source",
1950 	    fname);
1951 
1952     /* look in the current directory */
1953     if (uflag & FL_CDIR) {
1954 	bprintf("\n$cwd");
1955 	list_one_fname(fname, FL_CDIR | mode);
1956     }
1957 
1958     if (uflag & FL_HOME) {	/* look in the home directory */
1959 	bprintf("\n$HOME");
1960 	list_which_fname(home_dir(), fname, FL_HOME | mode);
1961     }
1962 
1963     if (uflag & FL_EXECDIR) {	/* look in vile's bin directory */
1964 	bprintf("\n$exec-path");
1965 	list_which_fname(exec_pathname, fname, FL_EXECDIR | mode);
1966     }
1967 
1968     if (uflag & FL_STARTPATH) {	/* look along "VILE_STARTUP_PATH" */
1969 	bprintf("\n$startup-path");
1970 	list_which_file_in_list(startup_path, fname, FL_STARTPATH | mode);
1971     }
1972 
1973     if (uflag & FL_PATH) {	/* look along "PATH" */
1974 #if OPT_PATHLOOKUP
1975 	bprintf("\n$PATH");
1976 	list_which_file_in_list(PATH_value(), fname, FL_PATH | mode);
1977 #endif /* OPT_PATHLOOKUP */
1978 
1979     }
1980 
1981     if (uflag & FL_LIBDIR) {	/* look along "VILE_LIBDIR_PATH" */
1982 	bprintf("\n$libdir-path");
1983 	list_which_file_in_list(libdir_path, fname, FL_LIBDIR | mode);
1984     }
1985 }
1986 
1987 static int
show_which_file(char * fname,UINT mode,int f,int n)1988 show_which_file(char *fname, UINT mode, int f, int n)
1989 {
1990     char *result;
1991     if ((result = cfg_locate(fname, mode)) != 0) {
1992 	mlwrite("%s", result);
1993     }
1994     if (f)
1995 	liststuff(WHICH_BufName, n > 2, list_which, (int) mode, fname);
1996     return (result != 0);
1997 }
1998 
1999 int
which_source(int f,int n)2000 which_source(int f, int n)
2001 {
2002     int s;
2003     char fname[NFILEN];
2004 
2005     if ((s = ask_which_file(fname)) == TRUE) {
2006 	s = show_which_file(fname, LOCATE_SOURCE, f, n);
2007     }
2008     return s;
2009 }
2010 
2011 int
which_exec(int f,int n)2012 which_exec(int f, int n)
2013 {
2014     int s;
2015     char fname[NFILEN];
2016 
2017     if ((s = ask_which_file(fname)) == TRUE) {
2018 	s = show_which_file(fname, LOCATE_EXEC, f, n);
2019     }
2020     return s;
2021 }
2022 #endif
2023 
2024 /*
2025  * Translate a keycode to its binding-string.
2026  * seq[0] gets the length of the result.
2027  */
2028 char *
kcod2pstr(int c,char * seq,int limit)2029 kcod2pstr(int c, char *seq, int limit)
2030 {
2031     seq[0] = (char) kcod2escape_seq(c, &seq[1], (size_t) limit - 1);
2032     return seq;
2033 }
2034 
2035 #if OPT_KEY_MODIFY
2036 static const struct {
2037     const char *name;
2038     int code;
2039 } key_modifiers[] = {
2040 
2041     {
2042 	"Alt+", mod_ALT
2043     },
2044     {
2045 	"Ctrl+", mod_CTRL
2046     },
2047     {
2048 	"Shift+", mod_SHIFT
2049     },
2050 };
2051 #endif
2052 
2053 #define ADD_KCODE(src) \
2054 	if (((size_t) (ptr - base) + (len = strlen(src)) + need + 2) < limit) { \
2055 	    strcpy(ptr, src); \
2056 	    ptr += len; \
2057 	}
2058 
2059 /* Translate a 16-bit keycode to a string that will replay into the same
2060  * code.
2061  */
2062 int
kcod2escape_seq(int c,char * ptr,size_t limit)2063 kcod2escape_seq(int c, char *ptr, size_t limit)
2064 {
2065     char *base = ptr;
2066     char temp[20];
2067     size_t need;
2068 
2069 #if OPT_MULTIBYTE
2070     int ch = (c & ((1 << MaxCBits) - 1));
2071     const char *s;
2072 
2073     if ((ch >= 256)
2074 	&& (cmd_encoding >= enc_UTF8
2075 	    || (cmd_encoding <= enc_AUTO && okCTYPE2(vl_wide_enc)))
2076 	&& (s = vl_mb_to_utf8(ch)) != 0) {
2077 	vl_strncpy(temp, s, sizeof(temp));
2078     } else
2079 #endif
2080     {
2081 	temp[0] = (char) c;
2082 	temp[1] = EOS;
2083     }
2084     need = strlen(temp);
2085 
2086     if (base != 0 && limit > (4 + need)) {
2087 	if ((UINT) c & CTLA)
2088 	    *ptr++ = (char) cntl_a;
2089 	else if ((UINT) c & CTLX)
2090 	    *ptr++ = (char) cntl_x;
2091 
2092 #if OPT_KEY_MODIFY
2093 	if ((UINT) c & mod_KEY) {
2094 	    size_t len;
2095 	    unsigned n;
2096 	    for (n = 0; n < TABLESIZE(key_modifiers); ++n) {
2097 		if (c & key_modifiers[n].code) {
2098 		    ADD_KCODE(key_modifiers[n].name);
2099 		}
2100 	    }
2101 #if SYS_WINNT
2102 #define W32INSERT "Insert"
2103 #define W32DELETE "Delete"
2104 	    c &= mod_NOMOD;
2105 	    if (c == KEY_Insert) {
2106 		ADD_KCODE(W32INSERT);
2107 		c = EOS;
2108 	    } else if (c == KEY_Delete) {
2109 		ADD_KCODE(W32DELETE);
2110 		c = EOS;
2111 	    }
2112 #endif
2113 	}
2114 #endif
2115 	if ((UINT) c & SPEC)
2116 	    *ptr++ = (char) poundc;
2117 	strcpy(ptr, temp);
2118 	ptr += need;
2119     }
2120     return (int) (ptr - base);
2121 }
2122 
2123 /* translates a binding string into printable form */
2124 #if OPT_REBIND
2125 static char *
bytes2prc(char * dst,char * src,int n)2126 bytes2prc(char *dst, char *src, int n)
2127 {
2128     char *base = dst;
2129     int c;
2130     const char *tmp;
2131 
2132     for (; n != 0; dst++, src++, n--) {
2133 
2134 	c = *src;
2135 
2136 	tmp = 0;
2137 
2138 	if (c & HIGHBIT) {
2139 	    *dst++ = 'M';
2140 	    *dst++ = '-';
2141 	    c &= ~HIGHBIT;
2142 	}
2143 	if (c == ' ') {
2144 	    tmp = "<space>";
2145 	} else if (isCntrl(c)) {
2146 	    *dst++ = '^';
2147 	    *dst = (char) tocntrl(c);
2148 	} else {
2149 	    *dst = (char) c;
2150 	}
2151 
2152 	if (tmp != 0) {
2153 	    while ((*dst++ = *tmp++) != EOS) {
2154 		;
2155 	    }
2156 	    dst -= 2;		/* point back to last nonnull */
2157 	}
2158 
2159 	if (n > 1) {
2160 	    *++dst = '-';
2161 	}
2162     }
2163     *dst = EOS;
2164     return base;
2165 }
2166 
2167 /* translate a 10-bit keycode to its printable name (like "M-j")  */
2168 char *
kcod2prc(int c,char * seq)2169 kcod2prc(int c, char *seq)
2170 {
2171     char temp[NSTRING];
2172     int length;
2173 
2174     length = kcod2pstr(c, temp, (int) sizeof(temp))[0];
2175 #if OPT_KEY_MODIFY
2176     if (((UINT) c & mod_KEY) != 0 && (length != 0)) {
2177 	(void) strcpy(seq, temp + 1);
2178 	if (length < (int) (1 + strlen(temp + 1) + (size_t) (CharOf(c) == 0))) {
2179 	    (void) bytes2prc(seq + length - 1, temp + length, 1);
2180 	}
2181     } else
2182 #endif
2183 	(void) bytes2prc(seq, temp + 1, length);
2184     TRACE2(("kcod2prc(%#x) ->%s\n", c, seq));
2185     return seq;
2186 }
2187 #endif
2188 
2189 static int
cmdfunc2keycode(BINDINGS * bs,const CMDFUNC * f)2190 cmdfunc2keycode(BINDINGS * bs, const CMDFUNC * f)
2191 {
2192     KBIND *kbp;
2193     int c;
2194 
2195     for (c = 0; c < N_chars; c++)
2196 	if (f == bs->kb_normal[c])
2197 	    return c;
2198 
2199 #if OPT_REBIND
2200     for (kbp = bs->kb_extra; kbp != bs->kb_special; kbp = kbp->k_link) {
2201 	if (kbp->k_cmd == f)
2202 	    return kbp->k_code;
2203     }
2204 #endif
2205     for (kbp = bs->kb_special; kbp->k_cmd != 0; kbp++) {
2206 	if (kbp->k_cmd == f)
2207 	    return kbp->k_code;
2208     }
2209 
2210     return -1;			/* none found */
2211 }
2212 
2213 /* kcod2fnc:  translate a 10-bit keycode to a function pointer */
2214 /*	(look a key binding up in the binding table)		*/
2215 const CMDFUNC *
kcod2fnc(const BINDINGS * bs,int c)2216 kcod2fnc(const BINDINGS * bs, int c)
2217 {
2218     const CMDFUNC *result = 0;
2219 
2220     if (bs != 0) {
2221 	if (!is8Bits(c)) {
2222 	    KBIND *kp = kcode2kbind(bs, c);
2223 	    result = (kp != 0) ? kp->k_cmd : 0;
2224 	} else {
2225 	    result = bs->kb_normal[CharOf(c)];
2226 	}
2227     }
2228     return result;
2229 }
2230 
2231 /* fnc2kcod: translate a function pointer to a keycode */
2232 int
fnc2kcod(const CMDFUNC * f)2233 fnc2kcod(const CMDFUNC * f)
2234 {
2235     return cmdfunc2keycode(&dft_bindings, f);
2236 }
2237 
2238 /* fnc2kins: translate a function pointer to an insert binding keycode */
2239 int
fnc2kins(const CMDFUNC * f)2240 fnc2kins(const CMDFUNC * f)
2241 {
2242     return cmdfunc2keycode(&ins_bindings, f);
2243 }
2244 
2245 /* fnc2pstr: translate a function pointer to a pascal-string that a user
2246 	could enter.  returns a pointer to a static array */
2247 #if DISP_X11
2248 char *
fnc2pstr(const CMDFUNC * f)2249 fnc2pstr(const CMDFUNC * f)
2250 {
2251     int c;
2252     static char seq[40];
2253 
2254     c = fnc2kcod(f);
2255 
2256     if (c == -1)
2257 	return NULL;
2258 
2259     return kcod2pstr(c, seq, (int) sizeof(seq));
2260 }
2261 #endif
2262 
2263 #if OPT_EVAL || OPT_REBIND
2264 
2265 #if OPT_NAMEBST
2266 /*
2267  * Search for a matching CMDFUNC pointer, return the corresponding name.  We
2268  * use this rather than extracting from the buffer name, since it may be
2269  * ambiguous if the macro's name is longer than NBUFN-2.
2270  */
2271 static int
match_cmdfunc(BI_NODE * node,const void * d)2272 match_cmdfunc(BI_NODE * node, const void *d)
2273 {
2274     NTAB *result = (NTAB *) TYPECAST(void *, d);
2275 
2276     if (node->value.n_cmd == result->n_cmd) {
2277 	result->n_name = (char *) TYPECAST(void *, node->value.bi_key);
2278 	return 1;
2279     }
2280     return 0;
2281 }
2282 #endif
2283 
2284 /*
2285  * Fill-in an NTAB with the data for the command.
2286  */
2287 static int
fnc2ntab(NTAB * result,const CMDFUNC * cfp)2288 fnc2ntab(NTAB * result, const CMDFUNC * cfp)
2289 {
2290     int found = FALSE;
2291 
2292     if (cfp != NULL) {
2293 #if OPT_NAMEBST
2294 	result->n_name = 0;
2295 	result->n_cmd = cfp;
2296 	btree_walk(&(bst_current()->head), match_cmdfunc, result);
2297 	found = (result->n_name != 0);
2298 #else
2299 	switch (cfp->c_flags & CMD_TYPE) {
2300 	    const NTAB *nptr;
2301 
2302 	case CMD_FUNC:		/* normal CmdFunc */
2303 	    for (nptr = nametbl; nptr->n_cmd; nptr++) {
2304 		if (nptr->n_cmd == cfp) {
2305 		    /* if it's a long name, return it */
2306 		    if ((int) strlen(nptr->n_name) > SHORT_CMD_LEN) {
2307 			found = TRUE;
2308 			*result = *nptr;
2309 			break;
2310 		    }
2311 		    /* remember the first short name, in case there's
2312 		       no long name */
2313 		    if (!result) {
2314 			*result = *nptr;
2315 			found = SORTOFTRUE;
2316 		    }
2317 		}
2318 	    }
2319 	    break;
2320 
2321 #if OPT_PROCEDURES
2322 	case CMD_PROC:		/* named procedure */
2323 	    result->n_name = CMD_U_BUFF(cfp)->b_bname;	/* not very good */
2324 	    result->n_cmd = cfp;
2325 	    found = TRUE;
2326 	    break;
2327 #endif
2328 
2329 #if OPT_PERL
2330 	case CMD_PERL:		/* perl subroutine */
2331 	    result->n_name = "Perl subroutine";		/* FIXME - what name? */
2332 	    result->n_cmd = cfp;
2333 	    found = TRUE;
2334 	    break;
2335 #endif
2336 	}
2337 #endif /* OPT_NAMEBST */
2338 
2339 	TRACE(("fnc2ntab(%s)\n", found ? result->n_name : ""));
2340     }
2341     return found;
2342 }
2343 
2344 /* fnc2engl: translate a function pointer to the english name for
2345 		that function.  prefer long names to short ones.
2346 */
2347 const char *
fnc2engl(const CMDFUNC * cfp)2348 fnc2engl(const CMDFUNC * cfp)
2349 {
2350     NTAB temp;
2351     return fnc2ntab(&temp, cfp) ? temp.n_name : 0;
2352 }
2353 
2354 #endif
2355 
2356 /* engl2fnc: match name to a function in the names table
2357 	translate english name to function pointer
2358 		 return any match or NULL if none
2359  */
2360 const CMDFUNC *
engl2fnc(const char * fname)2361 engl2fnc(const char *fname)
2362 {
2363 #if OPT_NAMEBST
2364     BI_NODE *n = btree_pmatch(BI_RIGHT(&(bst_current()->head)), TRUE, fname);
2365 
2366     if (n != NULL)
2367 	return n->value.n_cmd;
2368 #else
2369     /* this runs 10 times faster for 'nametbl[]' */
2370     int lo, hi, cur;
2371     int r;
2372     size_t len = strlen(fname);
2373 
2374     if (len != 0) {
2375 
2376 	/* scan through the table, returning any match */
2377 	lo = 0;
2378 	hi = nametbl_size - 2;	/* don't want last entry -- it's NULL */
2379 
2380 	while (lo <= hi) {
2381 	    cur = (lo + hi) >> 1;
2382 	    if ((r = strncmp(fname, nametbl[cur].n_name, len)) == 0) {
2383 		/* Now find earliest matching entry */
2384 		while (cur > lo
2385 		       && strncmp(fname, nametbl[cur - 1].n_name, len) == 0)
2386 		    cur--;
2387 		return nametbl[cur].n_cmd;
2388 
2389 	    } else if (r > 0) {
2390 		lo = cur + 1;
2391 	    } else {
2392 		hi = cur - 1;
2393 	    }
2394 	}
2395     }
2396 #endif /* OPT_NAMEBST */
2397     return NULL;
2398 }
2399 
2400 /*
2401  * This parser allows too many combinations; we will check for illegal
2402  * combinations after getting all of the information.  Still, it will not check
2403  * for odd things such as
2404  *	#-^X-a
2405  * since it is treated the same as
2406  *	^X-#-a
2407  */
2408 #if OPT_EVAL || OPT_REBIND
2409 static const char *
decode_prefix(const char * kk,UINT * prefix)2410 decode_prefix(const char *kk, UINT * prefix)
2411 {
2412     UCHAR ch;
2413     int found;
2414 
2415     do {
2416 	UINT bits = 0;
2417 	size_t len = strlen(kk);
2418 
2419 	if (len > 1) {
2420 	    ch = (UCHAR) (kk[0]);
2421 	    if (ch == cntl_a)
2422 		bits = CTLA;
2423 	    else if (ch == cntl_x)
2424 		bits = CTLX;
2425 	    else if (ch == poundc)
2426 		bits = SPEC;
2427 	    if (bits != 0) {
2428 		kk++;
2429 		if (len > 2 && *kk == '-')
2430 		    kk++;
2431 	    }
2432 	}
2433 
2434 	if (bits == 0 && len > 3 && *(kk + 2) == '-') {
2435 	    if (*kk == '^') {
2436 		ch = (UCHAR) (kk[1]);
2437 		if (isCntrl(cntl_a) && ch == (UCHAR) toalpha(cntl_a))
2438 		    bits = CTLA;
2439 		if (isCntrl(cntl_x) && ch == (UCHAR) toalpha(cntl_x))
2440 		    bits = CTLX;
2441 		if (isCntrl(poundc) && ch == (UCHAR) toalpha(poundc))
2442 		    bits = SPEC;
2443 	    } else if (!strncmp(kk, "FN", (size_t) 2)) {
2444 		bits = SPEC;
2445 	    }
2446 	    if (bits != 0)
2447 		kk += 3;
2448 	}
2449 
2450 	found = FALSE;
2451 	if (bits != 0) {
2452 	    found = TRUE;
2453 	    *prefix |= bits;
2454 	}
2455     } while (found);
2456     return kk;
2457 }
2458 
2459 /* prc2kcod: translate printable code to 10 bit keycode */
2460 static int
prc2kcod(const char * kk)2461 prc2kcod(const char *kk)
2462 {
2463     UINT kcod;			/* code to return */
2464     UINT pref = 0;		/* key prefixes */
2465 
2466     kk = decode_prefix(kk, &pref);
2467     if ((pref & CTLA) && (pref & CTLX))
2468 	return -1;
2469 
2470 #if OPT_KEY_MODIFY
2471     {
2472 	int found = FALSE;
2473 	UINT last, len, n;
2474 	char *s;
2475 	char temp[NSTRING];
2476 
2477 	while ((s = strchr(kk, '+')) != 0) {
2478 	    if ((len = (UINT) (s - kk) + 1) >= sizeof(temp))
2479 		break;
2480 	    if (s == kk || s[1] == '\n' || s[1] == '\0')
2481 		break;
2482 	    mklower(vl_strncpy(temp, kk, (size_t) len + 1));
2483 	    if (isLower(temp[0]))
2484 		temp[0] = (char) toUpper(temp[0]);
2485 	    for (n = 0; n < TABLESIZE(key_modifiers); ++n) {
2486 		if (!strncmp(key_modifiers[n].name, temp, (size_t) len)) {
2487 		    found = TRUE;
2488 		    pref |= ((UINT) key_modifiers[n].code | mod_KEY);
2489 		    break;
2490 		}
2491 	    }
2492 	    kk += len;
2493 	}
2494 	/*
2495 	 * SPEC would be set after a modifier, e.g.,
2496 	 *      control+#6
2497 	 *      control+FN-6
2498 	 * but it would be too confusing to allow
2499 	 *      control+^A-x
2500 	 *      FN-control+6
2501 	 */
2502 	if (found) {
2503 	    last = pref;
2504 	    pref = 0;
2505 	    kk = decode_prefix(kk, &pref);
2506 	    if ((pref & (CTLA | CTLX)) != 0
2507 		|| (last & pref & SPEC) != 0)
2508 		return -1;
2509 	    pref |= last;
2510 	}
2511     }
2512 #endif
2513 
2514     if (strlen(kk) > 2 && !strncmp(kk, "M-", (size_t) 2)) {
2515 	pref |= HIGHBIT;
2516 	kk += 2;
2517     }
2518 
2519     if (*kk == '^' && kk[1] != EOS) {	/* control character */
2520 	kcod = (UCHAR) kk[1];
2521 	if (isLower(kcod))
2522 	    kcod = (UINT) toUpper(kcod);
2523 	kcod = tocntrl(kcod);
2524 	kk += 2;
2525     } else {			/* any single char, control or not */
2526 	kcod = (UCHAR) (*kk++);
2527     }
2528 
2529     if (*kk != EOS)		/* we should have eaten the whole thing */
2530 	return -1;
2531 
2532     return (int) (pref | kcod);
2533 }
2534 #endif
2535 
2536 #if OPT_EVAL
2537 /* translate printable code (like "M-r") to english command name */
2538 const char *
prc2engl(const char * kk)2539 prc2engl(const char *kk)
2540 {
2541     const char *englname;
2542     int kcod;
2543 
2544     kcod = prc2kcod(kk);
2545     if (kcod < 0)
2546 	return error_val;
2547 
2548     englname = fnc2engl(DefaultKeyBinding(kcod));
2549     if (englname == NULL)
2550 	englname = error_val;
2551 
2552     return englname;
2553 }
2554 #endif
2555 
2556 /*
2557  * Get an english command name from the user.  If 'prompt' is a null pointer,
2558  * we will splice calls.
2559  */
2560 char *
kbd_engl(const char * prompt,char * buffer,UINT which)2561 kbd_engl(const char *prompt, char *buffer, UINT which)
2562 {
2563     char *result = NULL;
2564     if (kbd_engl_stat(prompt, buffer, which, 0) == TRUE)
2565 	result = buffer;
2566     return result;
2567 }
2568 
2569 /* sound the alarm! */
2570 #if OPT_TRACE
2571 void
trace_alarm(const char * file,int line)2572 trace_alarm(const char *file, int line)
2573 #else
2574 void
2575 kbd_alarm(void)
2576 #endif
2577 {
2578     TRACE(("BEEP (%s @%d)\n", file, line));
2579     TPRINTF(("BEEP\n"));
2580 
2581     if (!no_errs
2582 	&& !clhide
2583 	&& !quiet
2584 	&& global_g_val(GMDERRORBELLS)) {
2585 	term.beep();
2586 	term.flush();
2587     }
2588     warnings++;
2589 }
2590 
2591 void
kbd_start(KBD_DATA * data)2592 kbd_start(KBD_DATA * data)
2593 {
2594     beginDisplay();
2595 
2596 #if OPT_MULTIBYTE
2597     make_local_b_val(bminip, VAL_FILE_ENCODING);
2598     if (cmd_encoding == enc_AUTO) {
2599 	set_b_val(bminip, VAL_FILE_ENCODING, b_val(curbp, VAL_FILE_ENCODING));
2600     } else {
2601 	set_b_val(bminip, VAL_FILE_ENCODING, cmd_encoding);
2602     }
2603 #endif
2604 
2605     data->save_bp = curbp;
2606     data->save_wp = curwp;
2607     data->save_mk = MK;
2608 
2609     curbp = bminip;
2610     curwp = wminip;
2611     MK = DOT;
2612 }
2613 
2614 void
kbd_finish(KBD_DATA * data)2615 kbd_finish(KBD_DATA * data)
2616 {
2617     curbp = data->save_bp;
2618     curwp = data->save_wp;
2619     MK = data->save_mk;
2620 
2621     endofDisplay();
2622 }
2623 
2624 /* put a character to the keyboard-prompt */
2625 int
kbd_putc(int c)2626 kbd_putc(int c)
2627 {
2628     KBD_DATA save;
2629     int status;
2630 
2631     kbd_start(&save);
2632     if ((kbd_expand <= 0) && isreturn(c)) {
2633 	status = kbd_erase_to_end(0);
2634     } else {
2635 	if ((kbd_expand < 0) && (c == '\t')) {
2636 	    status = lins_bytes(1, ' ');
2637 	} else {
2638 	    status = lins_bytes(1, c);
2639 	}
2640 #ifdef VILE_DEBUG
2641 	TRACE(("mini:%2d:%s\n", llength(DOT.l), lp_visible(DOT.l)));
2642 #endif
2643     }
2644     kbd_finish(&save);
2645 
2646     return status;
2647 }
2648 
2649 /* put a string to the keyboard-prompt */
2650 static void
kbd_puts(const char * s)2651 kbd_puts(const char *s)
2652 {
2653     while (*s)
2654 	kbd_putc(*s++);
2655 }
2656 
2657 /* erase a character from the display by wiping it out */
2658 void
kbd_erase(void)2659 kbd_erase(void)
2660 {
2661     if (vl_echo && !clhide) {
2662 	KBD_DATA save;
2663 
2664 	kbd_start(&save);
2665 	if (DOT.o > 0) {
2666 	    backchar_to_bol(TRUE, 1);
2667 	    ldel_chars((B_COUNT) 1, FALSE);
2668 	}
2669 #ifdef VILE_DEBUG
2670 	TRACE(("MINI:%2d:%s\n", llength(DOT.l), lp_visible(DOT.l)));
2671 #endif
2672 	kbd_finish(&save);
2673     }
2674 }
2675 
2676 int
kbd_erase_to_end(int column)2677 kbd_erase_to_end(int column)
2678 {
2679     if (vl_echo && !clhide) {
2680 	KBD_DATA save;
2681 
2682 	kbd_start(&save);
2683 	if (llength(DOT.l) > 0) {
2684 	    DOT.o = column;
2685 	    if (llength(DOT.l) > DOT.o)
2686 		ldel_bytes((B_COUNT) (llength(DOT.l) - DOT.o), FALSE);
2687 	    TRACE(("NULL:%2d:%s\n", llength(DOT.l), lp_visible(DOT.l)));
2688 	}
2689 	kbd_finish(&save);
2690     }
2691 
2692     return TRUE;
2693 }
2694 
2695 int
cs_strcmp(int case_insensitive,const char * s1,const char * s2)2696 cs_strcmp(
2697 	     int case_insensitive,
2698 	     const char *s1,
2699 	     const char *s2)
2700 {
2701     if (case_insensitive)
2702 	return stricmp(s1, s2);
2703     return strcmp(s1, s2);
2704 }
2705 
2706 int
cs_strncmp(int case_insensitive,const char * s1,const char * s2,size_t n)2707 cs_strncmp(
2708 	      int case_insensitive,
2709 	      const char *s1,
2710 	      const char *s2,
2711 	      size_t n)
2712 {
2713     if (case_insensitive)
2714 	return strnicmp(s1, s2, n);
2715     return strncmp(s1, s2, n);
2716 }
2717 
2718 /* definitions for name-completion */
2719 #define	NEXT_DATA(p)	((p)+size_entry)
2720 #define	PREV_DATA(p)	((p)-size_entry)
2721 
2722 #define	THIS_NAME(p)	(*TYPECAST(const char *const,p))
2723 #define	NEXT_NAME(p)	THIS_NAME(NEXT_DATA(p))
2724 
2725 /*
2726  * Scan down until we no longer match the current input, or reach the end of
2727  * the symbol table.
2728  */
2729 /*ARGSUSED*/
2730 static const char *
skip_partial(int case_insensitive,char * buf,size_t len,const char * table,size_t size_entry)2731 skip_partial(
2732 		int case_insensitive,
2733 		char *buf,
2734 		size_t len,
2735 		const char *table,
2736 		size_t size_entry)
2737 {
2738     const char *next = NEXT_DATA(table);
2739     const char *sp;
2740 
2741     while ((sp = THIS_NAME(next)) != 0) {
2742 	if (StrNcmp(buf, sp, len) != 0)
2743 	    break;
2744 	next = NEXT_DATA(next);
2745     }
2746     return next;
2747 }
2748 
2749 /*
2750  * Shows a partial-match.  This is invoked in the symbol table at a partial
2751  * match, and the user wants to know what characters could be typed next.
2752  * If there is more than one possibility, they are shown in square-brackets.
2753  * If there is only one possibility, it is shown in curly-braces.
2754  */
2755 static void
show_partial(int case_insensitive,char * buf,size_t len,const char * table,size_t size_entry)2756 show_partial(
2757 		int case_insensitive,
2758 		char *buf,
2759 		size_t len,
2760 		const char *table,
2761 		size_t size_entry)
2762 {
2763     const char *next = skip_partial(case_insensitive, buf, len, table, size_entry);
2764     const char *last = PREV_DATA(next);
2765     int c;
2766 
2767     if (THIS_NAME(table)[len] == THIS_NAME(last)[len]) {
2768 	kbd_putc('{');
2769 	while ((c = THIS_NAME(table)[len]) != 0) {
2770 	    if (c == THIS_NAME(last)[len]) {
2771 		kbd_putc(c);
2772 		len++;
2773 	    } else
2774 		break;
2775 	}
2776 	kbd_putc('}');
2777     }
2778     if (next != NEXT_DATA(table)) {
2779 	c = TESTC;		/* shouldn't be in the table! */
2780 	kbd_putc('[');
2781 	while (table != next) {
2782 	    const char *sp = THIS_NAME(table);
2783 	    if (c != sp[len]) {
2784 		c = sp[len];
2785 		kbd_putc(c ? c : '$');
2786 	    }
2787 	    table = NEXT_DATA(table);
2788 	}
2789 	kbd_putc(']');
2790     }
2791     kbd_flush();
2792 }
2793 
2794 #if OPT_POPUPCHOICE
2795 /*
2796  * makecmpllist is called from liststuff to display the possible completions.
2797  */
2798 struct compl_rec {
2799     char *buf;
2800     size_t len;
2801     const char *table;
2802     size_t size_entry;
2803 };
2804 
2805 #ifdef lint
2806 #define c2ComplRec(c) ((struct compl_rec *)0)
2807 #else
2808 #define c2ComplRec(c) ((struct compl_rec *)c)
2809 #endif
2810 
2811 /*ARGSUSED*/
2812 static void
makecmpllist(int case_insensitive,void * cinfop)2813 makecmpllist(int case_insensitive, void *cinfop)
2814 {
2815     char *buf = c2ComplRec(cinfop)->buf;
2816     size_t len = c2ComplRec(cinfop)->len;
2817     const char *first = c2ComplRec(cinfop)->table;
2818     size_t size_entry = c2ComplRec(cinfop)->size_entry;
2819     const char *last = skip_partial(case_insensitive, buf, len, first, size_entry);
2820     const char *p;
2821     size_t maxlen;
2822     size_t slashcol;
2823     int cmpllen;
2824     int cmplcols;
2825     int cmplrows;
2826     int nentries;
2827     int i, j;
2828 
2829     for (p = NEXT_DATA(first), maxlen = strlen(THIS_NAME(first));
2830 	 p != last;
2831 	 p = NEXT_DATA(p)) {
2832 	size_t l = strlen(THIS_NAME(p));
2833 	if (l > maxlen)
2834 	    maxlen = l;
2835     }
2836 
2837     slashcol = (size_t) (pathleaf(buf) - buf);
2838     if (slashcol != 0) {
2839 	char b[NLINE];
2840 	(void) strncpy(b, buf, slashcol);
2841 	(void) strncpy(&b[slashcol], &(THIS_NAME(first))[slashcol],
2842 		       (len - slashcol));
2843 	b[slashcol + (len - slashcol)] = EOS;
2844 	bprintf("Completions prefixed by %s:\n", b);
2845     }
2846 
2847     cmplcols = term.cols / (int) (maxlen - slashcol + 1);
2848 
2849     if (cmplcols == 0)
2850 	cmplcols = 1;
2851 
2852     nentries = (int) ((size_t) (last - first) / size_entry);
2853     cmplrows = nentries / cmplcols;
2854     cmpllen = term.cols / cmplcols;
2855     if (cmplrows * cmplcols < nentries)
2856 	cmplrows++;
2857 
2858     for (i = 0; i < cmplrows; i++) {
2859 	for (j = 0; j < cmplcols; j++) {
2860 	    int idx = cmplrows * j + i;
2861 	    if (idx < nentries) {
2862 		const char *s = (THIS_NAME(first
2863 					   + ((size_t) idx * size_entry))
2864 				 + slashcol);
2865 		if (j == cmplcols - 1)
2866 		    bprintf("%s\n", s);
2867 		else
2868 		    bprintf("%*s", cmpllen, s);
2869 	    } else {
2870 		bprintf("\n");
2871 		break;
2872 	    }
2873 	}
2874     }
2875 }
2876 
2877 /*
2878  * Pop up a window and show the possible completions.
2879  */
2880 static void
show_completions(int case_insensitive,char * buf,size_t len,const char * table,size_t size_entry)2881 show_completions(
2882 		    int case_insensitive,
2883 		    char *buf,
2884 		    size_t len,
2885 		    const char *table,
2886 		    size_t size_entry)
2887 {
2888     struct compl_rec cinfo;
2889     BUFFER *bp;
2890     int alreadypopped = 0;
2891 
2892     /*
2893      * Find out if completions buffer exists; so we can take the time to
2894      * shrink/grow the window to the latest size.
2895      */
2896     if ((bp = find_b_name(COMPLETIONS_BufName)) != NULL) {
2897 	alreadypopped = (bp->b_nwnd != 0);
2898     }
2899 
2900     cinfo.buf = buf;
2901     cinfo.len = len;
2902     cinfo.table = table;
2903     cinfo.size_entry = size_entry;
2904     liststuff(COMPLETIONS_BufName, FALSE, makecmpllist, case_insensitive,
2905 	      (void *) &cinfo);
2906 
2907     if (alreadypopped)
2908 	shrinkwrap();
2909 
2910     (void) update(TRUE);
2911 }
2912 
2913 /*
2914  * Scroll the completions window wrapping around back to the beginning
2915  * of the buffer once it has been completely scrolled.  If the completions
2916  * buffer is missing for some reason, we will call show_completions to pop
2917  * it (back) up.
2918  */
2919 static void
scroll_completions(int case_insensitive,char * buf,size_t len,const char * table,size_t size_entry)2920 scroll_completions(
2921 		      int case_insensitive,
2922 		      char *buf,
2923 		      size_t len,
2924 		      const char *table,
2925 		      size_t size_entry)
2926 {
2927     BUFFER *bp;
2928 
2929     if ((bp = find_b_name(COMPLETIONS_BufName)) == NULL)
2930 	show_completions(case_insensitive, buf, len, table, size_entry);
2931     else {
2932 	LINE *lp;
2933 	swbuffer(bp);
2934 	(void) gotoeos(FALSE, 1);
2935 	lp = DOT.l;
2936 	(void) forwhpage(FALSE, 1);
2937 	if (lp == DOT.l)
2938 	    (void) gotobob(FALSE, 0);
2939 	(void) update(TRUE);
2940     }
2941 }
2942 
2943 void
popdown_completions(const char * old_bname,WINDOW * old_wp)2944 popdown_completions(const char *old_bname, WINDOW *old_wp)
2945 {
2946     BUFFER *bp;
2947     if ((bp = find_b_name(COMPLETIONS_BufName)) != NULL) {
2948 	zotwp(bp);
2949 	if (strcmp(old_bname, curbp->b_bname)) {
2950 	    if (!strcmp(old_wp->w_bufp->b_bname, old_bname)) {
2951 		set_curwp(old_wp);
2952 	    } else if ((bp = find_b_name(old_bname)) != NULL) {
2953 		swbuffer(bp);
2954 	    }
2955 	}
2956     }
2957 }
2958 #endif /* OPT_POPUPCHOICE */
2959 
2960 /*
2961  * Attempt to partial-complete the string, char at a time
2962  */
2963 static size_t
fill_partial(int case_insensitive,char * buf,size_t pos,const char * first,const char * last,size_t size_entry)2964 fill_partial(
2965 		int case_insensitive,
2966 		char *buf,
2967 		size_t pos,
2968 		const char *first,
2969 		const char *last,
2970 		size_t size_entry)
2971 {
2972     const char *p;
2973     size_t n = pos;
2974     const char *this_name = THIS_NAME(first);
2975 
2976     TRACE(("fill_partial(%d:%.*s) first=%s, last=%s\n",
2977 	   (int) pos, (int) pos,
2978 	   TRACE_NULL(buf),
2979 	   TRACE_NULL(THIS_NAME(first)),
2980 	   TRACE_NULL(THIS_NAME(last))));
2981 
2982     assert(buf != 0);
2983 #if 0				/* case insensitive reply correction doesn't work reliably yet */
2984     if (!clexec && case_insensitive) {
2985 	int spos = pos;
2986 
2987 	while (spos > 0 && buf[spos - 1] != SLASHC) {
2988 	    kbd_erase();
2989 	    spos--;
2990 	}
2991 	while (spos < pos) {
2992 	    kbd_putc(this_name[spos]);
2993 	    spos++;
2994 	}
2995     }
2996 #endif
2997 
2998     if (first == last
2999 	|| (first = NEXT_DATA(first)) == last) {
3000 	TRACE(("...empty list\n"));
3001 	return n;
3002     }
3003 
3004     for_ever {
3005 	buf[n] = this_name[n];	/* add the next char in */
3006 	buf[n + 1] = EOS;
3007 	TRACE(("...added(%d:%c)\n", (int) n, buf[n]));
3008 
3009 	/* scan through the candidates */
3010 	for (p = first; p != last; p = NEXT_DATA(p)) {
3011 	    if (StrNcmp(&THIS_NAME(p)[n], &buf[n], (size_t) 1) != 0) {
3012 		buf[n] = EOS;
3013 		if (n == pos
3014 #if OPT_POPUPCHOICE
3015 # if OPT_ENUM_MODES
3016 		    && !global_g_val(GVAL_POPUP_CHOICES)
3017 # else
3018 		    && !global_g_val(GMDPOPUP_CHOICES)
3019 # endif
3020 #endif
3021 		    )
3022 		    kbd_alarm();
3023 		kbd_flush();	/* force out alarm or partial completion */
3024 		TRACE(("...fill_partial %d\n", (int) n));
3025 		return n;
3026 	    }
3027 	}
3028 
3029 	if (!clexec)
3030 	    kbd_putc(buf[n]);	/* add the character */
3031 	n++;
3032     }
3033 }
3034 
3035 static int testcol;		/* records the column when TESTC is decoded */
3036 #if OPT_POPUPCHOICE
3037 /*
3038  * cmplcol is used to record the column number (on the message line) after
3039  * name completion.  Its value is used to decide whether or not to display
3040  * a completion list if the name completion character (tab) is pressed
3041  * twice in succession.  Once the completion list has been displayed, its
3042  * value will be changed to the additive inverse of the column number in
3043  * order to determine whether to scroll if tab is pressed yet again.  We
3044  * assume that 0 will never be a valid column number.  So long as we always
3045  * display some sort of prompt prior to reading from the message line, this
3046  * is a good assumption.
3047  */
3048 static int cmplcol = 0;
3049 #endif
3050 
3051 /*
3052  * Initializes the name-completion logic
3053  */
3054 void
kbd_init(void)3055 kbd_init(void)
3056 {
3057     testcol = -1;
3058 }
3059 
3060 /*
3061  * Returns the current length of the minibuffer
3062  */
3063 int
kbd_length(void)3064 kbd_length(void)
3065 {
3066     if (wminip != 0
3067 	&& wminip->w_dot.l != 0
3068 	&& llength(wminip->w_dot.l) > 0)
3069 	return llength(wminip->w_dot.l);
3070     return 0;
3071 }
3072 
3073 /*
3074  * Erases the display that was shown in response to TESTC
3075  */
3076 void
kbd_unquery(void)3077 kbd_unquery(void)
3078 {
3079     beginDisplay();
3080 #if OPT_POPUPCHOICE
3081     if (cmplcol != kbd_length() && -cmplcol != kbd_length())
3082 	cmplcol = 0;
3083 #endif
3084     if (testcol >= 0) {
3085 	while (kbd_length() > testcol)
3086 	    kbd_erase();
3087 	kbd_flush();
3088 	testcol = -1;
3089     }
3090     endofDisplay();
3091 }
3092 
3093 /*
3094  * This is invoked to find the closest name to complete from the current buffer
3095  * contents.
3096  */
3097 int
kbd_complete(DONE_ARGS,const char * table,size_t size_entry)3098 kbd_complete(DONE_ARGS, const char *table, size_t size_entry)
3099 {
3100     int case_insensitive = (flags & KBD_CASELESS) != 0;
3101     size_t cpos = *pos;
3102     const char *nbp;		/* first ptr to entry in name binding table */
3103     int status = FALSE;
3104 #if OPT_POPUPCHOICE
3105 # if OPT_ENUM_MODES
3106     int gvalpopup_choices = global_g_val(GVAL_POPUP_CHOICES);
3107 # else
3108     int gvalpopup_choices = global_g_val(GMDPOPUP_CHOICES);
3109 # endif
3110 #endif
3111 
3112     kbd_init();			/* nothing to erase */
3113     buf[cpos] = EOS;		/* terminate it for us */
3114     nbp = table;		/* scan for matches */
3115 
3116     if (nbp == 0)
3117 	return FALSE;
3118 
3119     while (THIS_NAME(nbp) != NULL) {
3120 	if (StrNcmp(buf, THIS_NAME(nbp), strlen(buf)) == 0) {
3121 	    testcol = kbd_length();
3122 	    /* a possible match! exact? no more than one? */
3123 #if OPT_POPUPCHOICE
3124 	    if (!clexec && c == NAMEC && cmplcol == -kbd_length()) {
3125 		scroll_completions(case_insensitive, buf, cpos, nbp, size_entry);
3126 		return FALSE;
3127 	    }
3128 #endif
3129 	    if (c == TESTC) {
3130 		show_partial(case_insensitive, buf, cpos, nbp, size_entry);
3131 	    } else if (Strcmp(buf, THIS_NAME(nbp)) == 0 ||	/* exact? */
3132 		       NEXT_NAME(nbp) == NULL ||
3133 		       StrNcmp(buf, NEXT_NAME(nbp), strlen(buf)) != 0) {
3134 		/* exact or only one like it.  print it */
3135 		if (!clexec) {
3136 #if 0				/* case insensitive reply correction doesn't work reliably yet */
3137 		    if (case_insensitive) {
3138 			int spos = cpos;
3139 
3140 			while (spos > 0 && buf[spos - 1] != SLASHC) {
3141 			    kbd_erase();
3142 			    spos--;
3143 			}
3144 			kbd_puts(THIS_NAME(nbp) + spos);
3145 		    } else
3146 #endif
3147 			kbd_puts(THIS_NAME(nbp) + cpos);
3148 		    kbd_flush();
3149 		    testcol = kbd_length();
3150 		}
3151 		if (c != NAMEC)	/* put it back */
3152 		    unkeystroke(c);
3153 		/* return complete name */
3154 		(void) strncpy0(buf, THIS_NAME(nbp),
3155 				(size_t) (NLINE - 1));
3156 		*pos = strlen(buf);
3157 #if OPT_POPUPCHOICE
3158 		if (gvalpopup_choices != POPUP_CHOICES_OFF
3159 		    && !clexec && (c == NAMEC))
3160 		    status = FALSE;
3161 		else
3162 #endif
3163 		    status = TRUE;
3164 	    } else {
3165 		/* try for a partial match against the list */
3166 		*pos = fill_partial(case_insensitive, buf, cpos, nbp,
3167 				    skip_partial(case_insensitive, buf,
3168 						 cpos, nbp, size_entry),
3169 				    size_entry);
3170 		testcol = kbd_length();
3171 	    }
3172 #if OPT_POPUPCHOICE
3173 # if OPT_ENUM_MODES
3174 	    if (!clexec
3175 		&& gvalpopup_choices != POPUP_CHOICES_OFF
3176 		&& c == NAMEC
3177 		&& *pos == cpos) {
3178 		if (gvalpopup_choices == POPUP_CHOICES_IMMED
3179 		    || cmplcol == kbd_length()) {
3180 		    show_completions(case_insensitive, buf, cpos, nbp, size_entry);
3181 		    cmplcol = -kbd_length();
3182 		} else
3183 		    cmplcol = kbd_length();
3184 	    } else
3185 		cmplcol = 0;
3186 # else
3187 	    if (!clexec && gvalpopup_choices
3188 		&& c == NAMEC && *pos == cpos) {
3189 		show_completions(case_insensitive, buf, cpos, nbp, size_entry);
3190 		cmplcol = -kbd_length();
3191 	    } else
3192 		cmplcol = 0;
3193 # endif
3194 #endif
3195 	    return status;
3196 	}
3197 	nbp = NEXT_DATA(nbp);
3198     }
3199 
3200 #if OPT_POPUPCHOICE
3201     cmplcol = 0;
3202 #endif
3203     buf[*pos = cpos] = EOS;
3204     if ((flags & (KBD_MAYBEC | KBD_MAYBEC2)) != 0) {
3205 	status = (cpos != 0);
3206     } else {
3207 	if (clexec) {
3208 #if OPT_MODELINE
3209 	    if (in_modeline < 2)
3210 #endif
3211 		mlwarn("[No match for '%s']", buf);
3212 	} else {
3213 	    kbd_alarm();
3214 	}
3215 	status = FALSE;
3216     }
3217     return status;
3218 }
3219 
3220 /*
3221  * Test a buffer to see if it looks like a shift-command, which may have
3222  * repeated characters (but they must all be the same).
3223  */
3224 static int
is_shift_cmd(const char * buffer,size_t cpos)3225 is_shift_cmd(const char *buffer, size_t cpos)
3226 {
3227     if (buffer != 0) {
3228 	int c = *buffer;
3229 	if (isRepeatable(c)) {
3230 	    while (--cpos != 0)
3231 		if (*(++buffer) != c)
3232 		    return FALSE;
3233 	    return TRUE;
3234 	}
3235     }
3236     return FALSE;
3237 }
3238 
3239 /*
3240  * The following mess causes the command to terminate if:
3241  *
3242  *	we've got the eolchar
3243  *		-or-
3244  *	we're in the first few chars and we're switching from punctuation
3245  *	(i.e., delimiters) to non-punctuation (i.e., characters that are part
3246  *	of command-names), or vice-versa.  oh yeah -- '-' isn't punctuation
3247  *	today, and '!' isn't either, in one direction, at any rate.
3248  *	All this allows things like:
3249  *		: e#
3250  *		: e!%
3251  *		: !ls
3252  *		: q!
3253  *		: up-line
3254  *	to work properly.
3255  *
3256  *	If we pass this "if" with c != NAMEC, then c is ungotten below,
3257  *	so it can be picked up by the commands argument getter later.
3258  */
3259 
3260 #define ismostpunct(c) (isPunct(c) && (c) != '-' && (c) != '_')
3261 
3262 int
eol_command(EOL_ARGS)3263 eol_command(EOL_ARGS)
3264 {
3265     int result = FALSE;
3266 
3267     if (is_shift_cmd(buffer, cpos) && (c == *buffer)) {
3268 	/*
3269 	 * Handle special case of repeated-character implying repeat-count
3270 	 */
3271 	result = TRUE;
3272     } else if ((cpos != 0) && isShellOrPipe(buffer)) {
3273 	/*
3274 	 * Shell-commands aren't complete until the line is complete.
3275 	 */
3276 	result = isreturn(c);
3277     } else if (c == eolchar) {
3278 	result = TRUE;
3279     } else if (cpos != 0 && cpos < 3
3280 	       && ((!ismostpunct(c)
3281 		    && ismostpunct(buffer[cpos - 1]))
3282 		   || ((c != '!' && ismostpunct(c))
3283 		       && (buffer[cpos - 1] == '!'
3284 			   || !ismostpunct(buffer[cpos - 1]))
3285 		   )
3286 	       )) {
3287 	result = TRUE;
3288     }
3289     return result;
3290 }
3291 
3292 /*
3293  * This procedure is invoked from 'kbd_string()' to setup the command-name
3294  * completion and query displays.
3295  */
3296 int
cmd_complete(DONE_ARGS)3297 cmd_complete(DONE_ARGS)
3298 {
3299     int status;
3300     if (buf != 0 && pos != 0) {
3301 #if OPT_HISTORY
3302 	/*
3303 	 * If the user scrolled back in 'edithistory()', the text may be a
3304 	 * repeated-shift command, which won't match the command-table (e.g.,
3305 	 * ">>>").
3306 	 */
3307 	if ((*pos > 1) && is_shift_cmd(buf, *pos)) {
3308 	    size_t len = 1;
3309 	    char tmp[NLINE];
3310 	    tmp[0] = *buf;
3311 	    tmp[1] = EOS;
3312 	    status = cmd_complete(0, c, tmp, &len);
3313 	} else
3314 #endif
3315 	if ((*pos != 0) && isShellOrPipe(buf)) {
3316 #if COMPLETE_FILES
3317 	    status = shell_complete(PASS_DONE_ARGS);
3318 #else
3319 	    status = isreturn(c);
3320 	    if (c != NAMEC)
3321 		unkeystroke(c);
3322 #endif
3323 	} else {
3324 	    status = kbd_complete_bst(PASS_DONE_ARGS);
3325 	}
3326     } else {
3327 	status = ABORT;
3328     }
3329     return status;
3330 }
3331 
3332 int
kbd_engl_stat(const char * prompt,char * buffer,UINT which,int stated)3333 kbd_engl_stat(const char *prompt, char *buffer, UINT which, int stated)
3334 {
3335     KBD_OPTIONS kbd_flags = KBD_EXPCMD | KBD_NULLOK | ((NAMEC != ' ') ? 0 : KBD_MAYBEC);
3336     int code;
3337     static TBUFF *temp;
3338     size_t len = NLINE;
3339 
3340     which_current = which;
3341 
3342     tb_scopy(&temp, "");
3343     init_filec(FILECOMPLETION_BufName);
3344     kbd_flags |= (KBD_OPTIONS) stated;
3345     code = kbd_reply(
3346 			prompt,	/* no-prompt => splice */
3347 			&temp,	/* in/out buffer */
3348 			eol_command,
3349 			' ',	/* eolchar */
3350 			kbd_flags,	/* allow blank-return */
3351 			cmd_complete);
3352     if (len > tb_length(temp))
3353 	len = tb_length(temp);
3354     strncpy0(buffer, tb_values(temp), len);
3355 
3356     which_current = 0;
3357     return code;
3358 }
3359 
3360 #if OPT_NAMEBST
3361 int
insert_namebst(const char * name,const CMDFUNC * cmd,int ro,UINT which)3362 insert_namebst(const char *name, const CMDFUNC * cmd, int ro, UINT which)
3363 {
3364     int result;
3365 
3366     TRACE2((T_CALLED "insert_namebst(%s,%s)\n", name, ro ? "ro" : "rw"));
3367     if (name != 0) {
3368 	BI_TREE *my_bst = bst_pointer(which);
3369 	BI_DATA temp, *p;
3370 
3371 	if ((p = btree_search(my_bst, name)) != 0) {
3372 	    if ((p->n_flags & NBST_READONLY)) {
3373 		if (btree_insert(&redefns, p) == 0) {
3374 		    returnCode(FALSE);
3375 		} else {
3376 		    if (!btree_delete(my_bst, name)) {
3377 			returnCode(FALSE);
3378 		    }
3379 		}
3380 		mlwrite("[Redefining builtin '%s']", name);
3381 	    } else {
3382 		if (!delete_namebst(name, TRUE, TRUE, which)) {
3383 		    returnCode(FALSE);
3384 		}
3385 	    }
3386 	}
3387 
3388 	temp.bi_key = name;
3389 	temp.n_cmd = cmd;
3390 	temp.n_flags = (UCHAR) (ro ? NBST_READONLY : 0);
3391 
3392 	result = (btree_insert(my_bst, &temp) != 0);
3393     } else {
3394 	result = FALSE;
3395     }
3396     return2Code(result);
3397 }
3398 
3399 static void
remove_cmdfunc_ref(BINDINGS * bs,const CMDFUNC * cmd)3400 remove_cmdfunc_ref(BINDINGS * bs, const CMDFUNC * cmd)
3401 {
3402 #if OPT_REBIND
3403     int i;
3404     int redo;
3405     KBIND *kbp;
3406 
3407     /* remove ascii bindings */
3408     for (i = 0; i < N_chars; i++)
3409 	if (bs->kb_normal[i] == cmd)
3410 	    bs->kb_normal[i] = 0;
3411 
3412     /* then look in the multi-key table */
3413     do {
3414 	redo = FALSE;
3415 	for (kbp = bs->kb_extra; kbp != bs->kb_special; kbp = kbp->k_link)
3416 	    if (kbp->k_cmd == cmd) {
3417 		unbindchar(bs, kbp->k_code);
3418 		redo = TRUE;
3419 		break;
3420 	    }
3421     } while (redo);
3422 
3423     do {
3424 	redo = FALSE;
3425 	for (kbp = bs->kb_special; kbp->k_cmd; kbp++)
3426 	    if (kbp->k_cmd == cmd) {
3427 		unbindchar(bs, kbp->k_code);
3428 		redo = TRUE;
3429 		break;
3430 	    }
3431     } while (redo);
3432 #endif
3433 }
3434 
3435 /*
3436  * Lookup a name in the binary-search tree, remove it if found
3437  */
3438 int
delete_namebst(const char * name,int release,int redefining,UINT which)3439 delete_namebst(const char *name, int release, int redefining, UINT which)
3440 {
3441     BI_TREE *my_bst = bst_pointer(which);
3442     BI_DATA *p = btree_search(my_bst, name);
3443     int code = TRUE;
3444 
3445     TRACE((T_CALLED "delete_namebst(%s,%d) %p\n", name, release, (void *) p));
3446     /* not a named procedure */
3447     if ((p = btree_search(my_bst, name)) != 0) {
3448 
3449 	/* we may have to free some stuff */
3450 	if (p && release) {
3451 	    remove_cmdfunc_ref(&dft_bindings, p->n_cmd);
3452 	    remove_cmdfunc_ref(&ins_bindings, p->n_cmd);
3453 	    remove_cmdfunc_ref(&cmd_bindings, p->n_cmd);
3454 
3455 	    /* free stuff */
3456 #if OPT_PERL
3457 	    if (p->n_cmd->c_flags & CMD_PERL)
3458 		perl_free_sub(CMD_U_PERL(p->n_cmd));
3459 #endif
3460 
3461 	    if (!(p->n_flags & NBST_READONLY)) {
3462 		beginDisplay();
3463 		free(TYPECAST(char, p->n_cmd->c_help));
3464 		free(TYPECAST(char, p->n_cmd));
3465 		endofDisplay();
3466 	    }
3467 	    p->n_cmd = 0;	/* ...so old_namebst won't free this too */
3468 	}
3469 
3470 	if ((code = btree_delete(my_bst, name)) == TRUE) {
3471 	    if (!redefining) {
3472 		if ((p = btree_search(&redefns, name)) != 0) {
3473 		    mlwrite("[Restoring builtin '%s']", name);
3474 		    code = (btree_insert(my_bst, p) != 0);
3475 		    (void) btree_delete(&redefns, p->bi_key);
3476 		}
3477 	    }
3478 	}
3479     }
3480     returnCode(code);
3481 }
3482 
3483 /*
3484  * If we're renaming a procedure to another "procedure" name (i.e., bracketed),
3485  * rename it in the name-completion table.  Otherwise, simply remove it from the
3486  * name-completions.
3487  */
3488 int
rename_namebst(const char * oldname,const char * newname,UINT which)3489 rename_namebst(const char *oldname, const char *newname, UINT which)
3490 {
3491     BI_DATA *prior;
3492     char name[NBUFN];
3493 
3494     /* not a named procedure */
3495     if ((prior = btree_search(bst_pointer(which), oldname)) == 0)
3496 	return TRUE;
3497 
3498     /* remove the entry if the new name is not a procedure (bracketed) */
3499     if (!is_scratchname(newname))
3500 	return delete_namebst(oldname, TRUE, FALSE, which);
3501 
3502     /* add the new name */
3503     strip_brackets(name, newname);
3504     if ((insert_namebst(name,
3505 			prior->n_cmd,
3506 			prior->n_flags & NBST_READONLY,
3507 			which)) != TRUE)
3508 	return FALSE;
3509 
3510     /* delete the old (but don't free the data) */
3511     return delete_namebst(oldname, FALSE, FALSE, which);
3512 }
3513 
3514 int
search_namebst(const char * name,UINT which)3515 search_namebst(const char *name, UINT which)
3516 {
3517     return (btree_search(bst_pointer(which), name) != 0);
3518 }
3519 
3520 /*
3521  * Build the initial name binary search tree.  Since the nametbl is sorted we
3522  * do this in a binary-search manner to get a balanced tree.
3523  */
3524 void
build_namebst(const NTAB * nptr,int lo,int hi,UINT which)3525 build_namebst(const NTAB * nptr, int lo, int hi, UINT which)
3526 {
3527     for (; lo < hi; lo++) {
3528 	if (!insert_namebst(nptr[lo].n_name, nptr[lo].n_cmd, TRUE, which))
3529 	    tidy_exit(BADEXIT);
3530     }
3531 }
3532 
3533 /*
3534  * This is invoked to find the closest name to complete from the current buffer
3535  * contents.
3536  */
3537 static int
kbd_complete_bst(unsigned flags,int c,char * buf,size_t * pos)3538 kbd_complete_bst(unsigned flags,
3539 		 int c,		/* TESTC, NAMEC or isreturn() */
3540 		 char *buf,
3541 		 size_t *pos)
3542 {
3543     size_t cpos = *pos;
3544     int status = FALSE;
3545     const char **nptr;
3546 
3547     (void) flags;
3548 
3549     kbd_init();			/* nothing to erase */
3550     buf[cpos] = EOS;		/* terminate it for us */
3551 
3552     if ((nptr = btree_parray(bst_current(), buf, cpos)) != 0) {
3553 	status = kbd_complete(0, c, buf, pos, (const char *) nptr,
3554 			      sizeof(*nptr));
3555 	beginDisplay();
3556 	free(TYPECAST(char, nptr));
3557 	endofDisplay();
3558     } else
3559 	kbd_alarm();
3560 
3561     return status;
3562 }
3563 #endif /* OPT_NAMEBST */
3564 
3565 #if OPT_MENUS
3566 /* FIXME: reuse logic from makefuncdesc() */
3567 char *
give_accelerator(char * bname)3568 give_accelerator(char *bname)
3569 {
3570     size_t i;
3571     int n;
3572     const CMDFUNC *cmd;
3573     static char outseq[NLINE];
3574 
3575     if ((n = blist_match(&blist_nametbl, bname)) >= 0) {
3576 	cmd = nametbl[n].n_cmd;
3577 
3578 	outseq[0] = '\0';
3579 	convert_cmdfunc(&dft_bindings, cmd, outseq);
3580 
3581 	/* Replace \t by ' ' */
3582 	for (i = 0; i < strlen(outseq); i++) {
3583 	    if (outseq[i] == '\t')
3584 		outseq[i] = ' ';
3585 	}
3586 
3587 	return outseq;
3588     }
3589 
3590     return NULL;
3591 }
3592 #endif /* OPT_MENUS */
3593 
3594 #if SYS_WINNT
3595 /*
3596  * At least one user wants the ability to swap ALT+Insert and CTRL+Insert
3597  * (mappings that copy info to the clipboard).
3598  *
3599  * If [win]vile ever gives end users the ability to map commands using
3600  * ALT, CTRL, and SHIFT prefixes, then swapcbrdkeys() will likely be
3601  * removed.
3602  */
3603 int
swapcbrdkeys(int f GCC_UNUSED,int n GCC_UNUSED)3604 swapcbrdkeys(int f GCC_UNUSED, int n GCC_UNUSED)
3605 {
3606     const CMDFUNC *cmd;
3607     int rc = FALSE;
3608 
3609     if ((cmd = engl2fnc("copy-unnamed-reg-to-clipboard")) != NULL)
3610 	if (install_bind(mod_KEY | KEY_Insert | mod_CTRL, cmd, &dft_bindings))
3611 	    if ((cmd = engl2fnc("copy-to-clipboard-til")) != NULL)
3612 		if (install_bind(mod_KEY | KEY_Insert | mod_ALT, cmd, &dft_bindings))
3613 		    rc = TRUE;
3614 
3615     if (!rc)
3616 	mlforce("[swap failed]");
3617     return (rc);
3618 }
3619 #endif
3620 
3621 #if NO_LEAKS
3622 
3623 #if OPT_REBIND
3624 static void
free_all_bindings(BINDINGS * bs)3625 free_all_bindings(BINDINGS * bs)
3626 {
3627     while (bs->kb_extra != bs->kb_special) {
3628 	KBIND *kbp = bs->kb_extra;
3629 	bs->kb_extra = kbp->k_link;
3630 	free((char *) kbp);
3631     }
3632 }
3633 
3634 static void
free_ext_bindings(BINDINGS * bs)3635 free_ext_bindings(BINDINGS * bs)
3636 {
3637     free_all_bindings(bs);
3638     FreeAndNull(bs->kb_special);
3639 }
3640 #endif
3641 void
bind_leaks(void)3642 bind_leaks(void)
3643 {
3644     btree_printf(&namebst);
3645 #if OPT_REBIND
3646     free_all_bindings(&dft_bindings);
3647     free_ext_bindings(&ins_bindings);
3648     free_ext_bindings(&cmd_bindings);
3649     free_ext_bindings(&sel_bindings);
3650 #endif
3651 #if OPT_NAMEBST
3652     btree_freeup(&redefns);
3653     btree_freeup(&namebst);
3654     btree_freeup(&glbsbst);
3655 #endif
3656 }
3657 #endif /* NO_LEAKS */
3658