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", "ec, 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