home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 5 Edit / 05-Edit.zip / vile-src.zip / vile-8.1 / bind.c < prev    next >
C/C++ Source or Header  |  1998-07-23  |  56KB  |  2,565 lines

  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.  * $Header: /usr/build/vile/vile/RCS/bind.c,v 1.183 1998/07/23 09:19:56 cmorgan Exp $
  7.  *
  8.  */
  9.  
  10. #ifdef _WIN32
  11. # include <windows.h>
  12. #endif
  13.  
  14. #include    "estruct.h"
  15. #include    "edef.h"
  16. #include    "nefunc.h"
  17.  
  18. #define BI_DATA NBST_DATA
  19. #include    "btree.h"
  20.  
  21. #define SHORT_CMD_LEN 4    /* command names longer than this are preferred
  22.                 over those shorter.  e.g. display "quit"
  23.                 instead of "q" if possible */
  24.  
  25. extern const int nametblsize;
  26.  
  27. static    KBIND *    kcode2kbind ( int code );
  28.  
  29. #if OPT_CASELESS
  30. #define Strcmp(s,d)      cs_strcmp(case_insensitive, s, d)
  31. #define StrNcmp(s,d,len) cs_strncmp(case_insensitive, s, d, len)
  32. #else
  33. #define Strcmp(s,d)      strcmp(s, d)
  34. #define StrNcmp(s,d,len) strncmp(s, d, len)
  35. #endif
  36.  
  37. #if OPT_REBIND
  38. #define    isSpecialCmd(k) \
  39.         ( (k == &f_cntl_a_func)\
  40.         ||(k == &f_cntl_x_func)\
  41.         ||(k == &f_poundc_func)\
  42.         ||(k == &f_unarg_func)\
  43.         ||(k == &f_esc_func)\
  44.         )
  45.  
  46. static    char *    kcod2prc (int c, char *seq);
  47. static    int    install_bind (int c, const CMDFUNC *kcmd, const CMDFUNC **oldfunc);
  48. static    int    key_to_bind ( const CMDFUNC *kcmd );
  49. static    int    rebind_key ( int c, const CMDFUNC *kcmd );
  50. static    int    strinc (const char *sourc, const char *sub);
  51. static    int    unbindchar ( int c );
  52. static    int    update_binding_list ( BUFFER *bp );
  53. static    void    makebindlist (LIST_ARGS);
  54. #endif    /* OPT_REBIND */
  55.  
  56. #if OPT_NAMEBST
  57. static int kbd_complete_bst( int case_insensitive, int c, char *buf, unsigned *pos);
  58. #else
  59. #define kbd_complete_bst(case_insensitive, c, buf, pos) \
  60.     kbd_complete(case_insensitive, c, buf, pos, \
  61.             (const char *)&nametbl[0], sizeof(nametbl[0]))
  62. #endif    /* OPT_NAMEBST */
  63.  
  64. #if OPT_EVAL || OPT_REBIND
  65. static    const NTAB * fnc2ntab ( const CMDFUNC *cfp );
  66. static    int    prc2kcod ( const char *kk );
  67. #endif
  68.  
  69. #if OPT_REBIND
  70. static    KBIND    *KeyBindings = kbindtbl;
  71. #endif
  72.  
  73. /*----------------------------------------------------------------------------*/
  74.  
  75. #if OPT_NAMEBST
  76. static BI_NODE*
  77. new_namebst (BI_DATA *a)
  78. {
  79.     BI_NODE *p = typecalloc(BI_NODE);
  80.     p->value = *a;
  81.     if (!(a->n_flags & NBST_READONLY))
  82.         BI_KEY(p) = strmalloc(a->bi_key);
  83.     return p;
  84. }
  85.  
  86. static void
  87. old_namebst (BI_NODE *a)
  88. {
  89.     if (!(a->value.n_flags & NBST_READONLY))
  90.         free(TYPECAST(char,BI_KEY(a)));
  91.     free(a);
  92. }
  93.  
  94. static void
  95. dpy_namebst (BI_NODE *a GCC_UNUSED, int level GCC_UNUSED)
  96. {
  97. #if OPT_TRACE
  98.     while (level-- > 0)
  99.         TRACE((". "));
  100.     TRACE(("%p -> %s (%d)\n", a, BI_KEY(a), a->balance));
  101. #endif
  102. }
  103.  
  104. static    BI_TREE namebst = {new_namebst, old_namebst, dpy_namebst};
  105. #endif /* OPT_NAMEBST */
  106.  
  107. /*----------------------------------------------------------------------------*/
  108.  
  109. int
  110. no_such_function(const char * fnp)
  111. {
  112.     mlforce("[No such function \"%s\"]", fnp != 0 ? fnp : "");
  113.     return FALSE;
  114. }
  115.  
  116. /* give me some help!!!! bring up a buffer and read the help file into it */
  117. /* ARGSUSED */
  118. int
  119. help(int f GCC_UNUSED, int n GCC_UNUSED)
  120. {
  121.     register BUFFER *bp;    /* buffer pointer to help */
  122.     char *fname;        /* ptr to file returned by flook() */
  123.     int alreadypopped;
  124.  
  125.     /* first check if we are already here */
  126.     bp = bfind(HELP_BufName, BFSCRTCH);
  127.     if (bp == NULL)
  128.         return FALSE;
  129.  
  130.     if (bp->b_active == FALSE) { /* never been used */
  131.         fname = flook(helpfile, FL_ANYWHERE|FL_READABLE);
  132.         if (fname == NULL) {
  133.             mlforce("[Sorry, can't find the help information]");
  134.             (void)zotbuf(bp);
  135.             return(FALSE);
  136.         }
  137.         alreadypopped = (bp->b_nwnd != 0);
  138.         /* and read the stuff in */
  139.         if (readin(fname, 0, bp, TRUE) == FALSE ||
  140.                 popupbuff(bp) == FALSE) {
  141.             (void)zotbuf(bp);
  142.             return(FALSE);
  143.         }
  144.         set_bname(bp, HELP_BufName);
  145.         set_rdonly(bp, fname, MDVIEW);
  146.  
  147.         make_local_b_val(bp,MDIGNCASE); /* easy to search, */
  148.         set_b_val(bp,MDIGNCASE,TRUE);
  149.         b_set_scratch(bp);
  150.         if (!alreadypopped)
  151.             shrinkwrap();
  152.     }
  153.  
  154.     if (!swbuffer(bp))
  155.         return FALSE;
  156.  
  157.     if (help_at >= 0) {
  158.         if (!gotoline(TRUE, help_at))
  159.             return FALSE;
  160.         mlwrite("[Type '1G' to return to start of help information]");
  161.         help_at = -1;  /* until zotbuf is called, we let normal
  162.                 DOT tracking keep our position */
  163.     }
  164.     return TRUE;
  165. }
  166.  
  167. #if OPT_REBIND
  168.  
  169. #if OPT_TERMCHRS
  170.  
  171.     /* patch: this table and the corresponding initializations should be
  172.      * generated by 'mktbls'.  In any case, the table must be sorted to use
  173.      * name-completion on it.
  174.      */
  175. static    const struct {
  176.         const char *name;
  177.         int    *value;
  178.         char    how_to;
  179.     } TermChrs[] = {
  180.         {"backspace",        &backspc,    's'},
  181.         {"interrupt",        &intrc,        's'},
  182.         {"line-kill",        &killc,        's'},
  183.         {"mini-edit",        &editc,        's'},
  184.         {"name-complete",    &name_cmpl,    0},
  185.         {"quote-next",        "ec,    0},
  186.         {"start-output",    &startc,    's'},
  187.         {"stop-output",        &stopc,        's'},
  188.         {"suspend",        &suspc,        's'},
  189.         {"test-completions",    &test_cmpl,    0},
  190.         {"word-kill",        &wkillc,    's'},
  191.         {0}
  192.     };
  193.  
  194. /*----------------------------------------------------------------------------*/
  195.  
  196. /* list the current chrs into the current buffer */
  197. /* ARGSUSED */
  198. static void
  199. makechrslist(int dum1 GCC_UNUSED, void *ptr GCC_UNUSED)
  200. {
  201.     register int i;
  202.     char    temp[NLINE];
  203.  
  204.     bprintf("--- Terminal Character Settings %*P\n", term.t_ncol-1, '-');
  205.     for (i = 0; TermChrs[i].name != 0; i++) {
  206.         bprintf("\n%s = %s",
  207.             TermChrs[i].name,
  208.             kcod2prc(*(TermChrs[i].value), temp));
  209.     }
  210. }
  211.  
  212. /*
  213.  * Find a special-character definition, given the name
  214.  */
  215. static int
  216. chr_lookup(const char *name)
  217. {
  218.     register int    j;
  219.     for (j = 0; TermChrs[j].name != 0; j++)
  220.         if (!strcmp(name, TermChrs[j].name))
  221.             return j;
  222.     return -1;
  223. }
  224.  
  225. /*
  226.  * The 'chr_complete()' and 'chr_eol()' functions are invoked from
  227.  * 'kbd_reply()' to setup the mode-name completion and query displays.
  228.  */
  229. static int
  230. chr_complete(int c, char *buf, unsigned *pos)
  231. {
  232.     return kbd_complete(FALSE, c, buf, pos, (const char *)&TermChrs[0],
  233.         sizeof(TermChrs[0]));
  234. }
  235.  
  236. static int
  237. /*ARGSUSED*/
  238. chr_eol(
  239.     const char * buffer GCC_UNUSED,
  240.     unsigned cpos GCC_UNUSED,
  241.     int c GCC_UNUSED,
  242.     int eolchar GCC_UNUSED)
  243. {
  244.     return isSpace(c);
  245. }
  246.  
  247. #if OPT_UPBUFF
  248. /* ARGSUSED */
  249. static int
  250. update_termchrs(BUFFER *bp GCC_UNUSED)
  251. {
  252.     return show_termchrs(FALSE,1);
  253. }
  254. #endif
  255.  
  256. /* ARGSUSED */
  257. int
  258. set_termchrs(int f GCC_UNUSED, int n GCC_UNUSED)
  259. {
  260.     register int s, j;
  261.     static    TBUFF *name;
  262.     int c;
  263.  
  264.     /* get the table-entry */
  265.     tb_scopy(&name, "");
  266.     if ((s = kbd_reply("Terminal setting: ", &name, chr_eol,
  267.         ' ', 0, chr_complete)) == TRUE) {
  268.  
  269.         j = chr_lookup(tb_values(name));
  270.         switch (TermChrs[j].how_to) {
  271.         case 's':
  272.         default:
  273.             c = key_to_bind((CMDFUNC *)0);
  274.             if (c < 0)
  275.                 return(FALSE);
  276.             *(TermChrs[j].value) = c;
  277.             break;
  278.         }
  279.         update_scratch(TERMINALCHARS_BufName, update_termchrs);
  280.     }
  281.     return s;
  282. }
  283.  
  284. /* ARGSUSED */
  285. int
  286. show_termchrs(int f GCC_UNUSED, int n GCC_UNUSED)
  287. {
  288.     return liststuff(TERMINALCHARS_BufName, FALSE, makechrslist, 0, (void *)0);
  289. }
  290. #endif /* OPT_TERMCHRS */
  291.  
  292. static void
  293. ostring(    /* output a string of output characters */
  294. char *s)    /* string to output */
  295. {
  296.     if (discmd)
  297.         kbd_puts(s);
  298. }
  299.  
  300. /* bindkey:    add a new key to the key binding table        */
  301.  
  302. /* ARGSUSED */
  303. int
  304. bindkey(int f GCC_UNUSED, int n GCC_UNUSED)
  305. {
  306.     register const CMDFUNC *kcmd; /* ptr to the requested function to bind to */
  307.     char cmd[NLINE];
  308.     char *fnp;
  309.  
  310.     /* prompt the user to type in a key to bind */
  311.     /* and get the function name to bind it to */
  312.     fnp = kbd_engl("Bind function whose full name is: ", cmd);
  313.  
  314.     if (fnp == NULL || (kcmd = engl2fnc(fnp)) == NULL) {
  315.         return no_such_function(fnp);
  316.     }
  317.  
  318.     return rebind_key(key_to_bind(kcmd), kcmd);
  319. }
  320.  
  321. /*
  322.  * Prompt-for and return the key-code to bind.
  323.  */
  324. static int
  325. key_to_bind(register const CMDFUNC *kcmd)
  326. {
  327.     char outseq[NLINE];    /* output buffer for keystroke sequence */
  328.     register int c;
  329.  
  330.     mlprompt("...to keyboard sequence (type it exactly): ");
  331.  
  332.     /* get the command sequence to bind */
  333.     if (clexec) {
  334.         char tok[NSTRING];
  335.         macarg(tok);    /* get the next token */
  336.         c = prc2kcod(tok);
  337.     } else {
  338.         /* perhaps we only want a single key, not a sequence */
  339.         /*     (see more comments below) */
  340.         if (isSpecialCmd(kcmd))
  341.             c = keystroke();
  342.         else
  343.             c = kbd_seq();
  344.     }
  345.  
  346.     if (c >= 0) {
  347.         /* change it to something we can print as well */
  348.         ostring(kcod2prc(c, outseq));
  349.         hst_append_s(outseq, FALSE);
  350.     } else {
  351.         mlforce("[Not a proper key-sequence]");
  352.     }
  353.     return c;
  354. }
  355.  
  356. /*
  357.  * Given a key-code and a command-function pointer, rebind the key-code to
  358.  * the command-function.
  359.  */
  360. static int
  361. rebind_key (
  362. register int    c,
  363. register const CMDFUNC *kcmd)
  364. {
  365.     static const CMDFUNC ignored = { INIT_UNION( unimpl ) },
  366.         *old = &ignored;
  367.     return install_bind (c, kcmd, &old);
  368. }
  369.  
  370. /*
  371.  * Prefix-keys can be only bound to one value. This procedure tests the
  372.  * argument 'kcmd' to see if it is a prefix key, and if so, unbinds the
  373.  * key, and sets the corresponding global variable to the new value.
  374.  * The calling procedure will then do the binding per se.
  375.  */
  376. static void
  377. reset_prefix (
  378. register int    c,
  379. register const CMDFUNC *kcmd)
  380. {
  381.     if (isSpecialCmd(kcmd)) {
  382.         register int j;
  383.         /* search for an existing binding for the prefix key */
  384.         if ((j = fnc2kcod(kcmd)) >= 0)
  385.             (void)unbindchar(j);
  386.         /* reset the appropriate global prefix variable */
  387.         if (kcmd == &f_cntl_a_func)
  388.             cntl_a = c;
  389.         if (kcmd == &f_cntl_x_func)
  390.             cntl_x = c;
  391.         if (kcmd == &f_poundc_func)
  392.             poundc = c;
  393.         if (kcmd == &f_unarg_func)
  394.             reptc = c;
  395.         if (kcmd == &f_esc_func)
  396.             abortc = c;
  397.     }
  398. }
  399.  
  400. /*
  401.  * Bind a command-function pointer to a given key-code (saving the old
  402.  * value of the function-pointer via an pointer given by the caller).
  403.  */
  404. static int
  405. install_bind (
  406. register int    c,
  407. register const CMDFUNC *kcmd,
  408. const CMDFUNC **oldfunc)
  409. {
  410.     register KBIND *kbp;    /* pointer into a binding table */
  411.  
  412.     if (c < 0)
  413.         return FALSE;    /* not a legal key-code */
  414.  
  415.     /* if the function is a prefix key, i.e. we're changing the definition
  416.         of a prefix key, then they typed a dummy function name, which
  417.         has been translated into a dummy function pointer */
  418.     *oldfunc = kcod2fnc(c);
  419.     reset_prefix(-1, *oldfunc);
  420.     reset_prefix(c, kcmd);
  421.  
  422.     if (!isspecial(c)) {
  423.         asciitbl[c] = TYPECAST(CMDFUNC,kcmd);
  424.     } else {
  425.         if ((kbp = kcode2kbind(c)) != 0) { /* change it in place */
  426.             kbp->k_cmd = kcmd;
  427.         } else {
  428.             if ((kbp = typealloc(KBIND)) == 0) {
  429.                 return no_memory("Key-Binding");
  430.             }
  431.             kbp->k_link = KeyBindings;
  432.             kbp->k_code = (short)c;    /* add keycode */
  433.             kbp->k_cmd  = kcmd;    /* and func pointer */
  434.             KeyBindings = kbp;
  435.         }
  436.     }
  437.     update_scratch(BINDINGLIST_BufName, update_binding_list);
  438.     return(TRUE);
  439. }
  440.  
  441. /* unbindkey:    delete a key from the key binding table    */
  442.  
  443. /* ARGSUSED */
  444. int
  445. unbindkey(int f GCC_UNUSED, int n GCC_UNUSED)
  446. {
  447.     register int c;        /* command key to unbind */
  448.     char outseq[NLINE];    /* output buffer for keystroke sequence */
  449.  
  450.     /* prompt the user to type in a key to unbind */
  451.     mlprompt("Unbind this key sequence: ");
  452.  
  453.     /* get the command sequence to unbind */
  454.     if (clexec) {
  455.         char tok[NSTRING];
  456.         macarg(tok);    /* get the next token */
  457.         c = prc2kcod(tok);
  458.         if (c < 0) {
  459.             mlforce("[Illegal key-sequence \"%s\"]",tok);
  460.             return FALSE;
  461.         }
  462.     } else {
  463.         c = kbd_seq();
  464.         if (c < 0) {
  465.             mlforce("[Not a bindable key-sequence]");
  466.             return(FALSE);
  467.         }
  468.     }
  469.  
  470.     /* change it to something we can print as well */
  471.     ostring(kcod2prc(c, outseq));
  472.  
  473.     /* if it isn't bound, bitch */
  474.     if (unbindchar(c) == FALSE) {
  475.         mlforce("[Key not bound]");
  476.         return(FALSE);
  477.     }
  478.     update_scratch(BINDINGLIST_BufName, update_binding_list);
  479.     return(TRUE);
  480. }
  481.  
  482. static int
  483. unbindchar(int c)        /* command key to unbind */
  484. {
  485.     register KBIND *kbp;    /* pointer into the command table */
  486.     register KBIND *skbp;    /* saved pointer into the command table */
  487.  
  488.     if (!isspecial(c))
  489.     {
  490.     if (asciitbl[c])
  491.     {
  492.         asciitbl[c] = 0;
  493.         return TRUE;
  494.     }
  495.  
  496.     return FALSE;
  497.     }
  498.  
  499.     /* check first entry in KeyBindings table */
  500.     kbp = skbp = KeyBindings;
  501.     if (kbp->k_code == c)
  502.     {
  503.     KeyBindings = kbp->k_link;
  504.     free(kbp);
  505.     return TRUE;
  506.     }
  507.  
  508.     /* check KeyBindings */
  509.     while (kbp != kbindtbl)
  510.     {
  511.     if (kbp->k_code == c)
  512.     {
  513.         /* relink previous */
  514.         skbp->k_link = kbp->k_link;
  515.         free(kbp);
  516.         return TRUE;
  517.     }
  518.  
  519.     skbp = kbp;
  520.     kbp = kbp->k_link;
  521.     }
  522.  
  523.     /* nope, check kbindtbl */
  524.     skbp = 0;
  525.     for (skbp = 0; kbp->k_cmd; kbp++)
  526.     {
  527.     if (!skbp && kbp->k_code == c)
  528.         skbp = kbp;
  529.     }
  530.  
  531.     /* not found */
  532.     if (!skbp)
  533.     return FALSE;
  534.  
  535.     --kbp; /* backup to the last legit entry */
  536.     if (skbp != kbp)
  537.     {
  538.     /* copy the last entry to the current one */
  539.     skbp->k_code = kbp->k_code;
  540.     skbp->k_cmd  = kbp->k_cmd;
  541.     }
  542.  
  543.     /* null out the last one */
  544.     kbp->k_code = 0;
  545.     kbp->k_cmd = 0;
  546.  
  547.     return TRUE;
  548. }
  549.  
  550. /* describe bindings bring up a fake buffer and list the key bindings
  551.            into it with view mode            */
  552.  
  553. /* remember whether we last did "apropos" or "describe-bindings" */
  554. static char *last_apropos_string;
  555. static CMDFLAGS last_whichcmds;
  556. static int append_to_binding_list;
  557.  
  558. /* ARGSUSED */
  559. static int
  560. update_binding_list(BUFFER *bp GCC_UNUSED)
  561. {
  562.     return liststuff(BINDINGLIST_BufName, append_to_binding_list,
  563.         makebindlist, (int)last_whichcmds, (void *)last_apropos_string);
  564. }
  565.  
  566. /* ARGSUSED */
  567. int
  568. desbind(int f GCC_UNUSED, int n GCC_UNUSED)
  569. {
  570.     last_apropos_string = (char *)0;
  571.     last_whichcmds = 0;
  572.  
  573.     return update_binding_list((BUFFER *)0);
  574. }
  575.  
  576. /* ARGSUSED */
  577. int
  578. desmotions(int f GCC_UNUSED, int n GCC_UNUSED)
  579. {
  580.     last_apropos_string = (char *)0;
  581.     last_whichcmds = MOTION;
  582.     return update_binding_list((BUFFER *)0);
  583. }
  584.  
  585. /* ARGSUSED */
  586. int
  587. desopers(int f GCC_UNUSED, int n GCC_UNUSED)
  588. {
  589.     last_apropos_string = (char *)0;
  590.     last_whichcmds = OPER;
  591.     return update_binding_list((BUFFER *)0);
  592. }
  593.  
  594. /* ARGSUSED */
  595. int
  596. desapro(int f GCC_UNUSED, int n GCC_UNUSED)    /* Apropos (List functions that match a substring) */
  597. {
  598.     register int    s;
  599.     static char mstring[NSTRING];    /* string to match cmd names to */
  600.  
  601.     s = mlreply("Apropos string: ", mstring, sizeof(mstring));
  602.     if (s != TRUE)
  603.         return(s);
  604.  
  605.     last_apropos_string = mstring;
  606.     last_whichcmds = 0;
  607.     return update_binding_list((BUFFER *)0);
  608. }
  609.  
  610. static char described_cmd[NLINE+1];    /* string to match cmd names to */
  611.  
  612. /* ARGSUSED */
  613. int
  614. desfunc(int f GCC_UNUSED, int n GCC_UNUSED)    /* describe-function */
  615. {
  616.     register int    s;
  617.     char *fnp;
  618.  
  619.     /* force an exact match by strinc() later on from makefuncdesc() */
  620.     described_cmd[0] = '^';
  621.  
  622.     fnp = kbd_engl("Describe function whose full name is: ",
  623.                             described_cmd+1);
  624.     if (fnp == NULL || engl2fnc(fnp) == NULL) {
  625.         s = no_such_function(fnp);
  626.     } else {
  627.         last_apropos_string = described_cmd;
  628.         last_whichcmds = 0;
  629.         append_to_binding_list = TRUE;
  630.         s = update_binding_list((BUFFER *)0);
  631.         append_to_binding_list = FALSE;
  632.     }
  633.     return s;
  634. }
  635.  
  636. /* ARGSUSED */
  637. int
  638. deskey(int f GCC_UNUSED, int n GCC_UNUSED)    /* describe the command for a certain key */
  639. {
  640.     register int c;        /* key to describe */
  641.     char outseq[NSTRING];    /* output buffer for command sequence */
  642.     const NTAB *nptr;    /* name table pointer */
  643.     int s;
  644.  
  645.     /* prompt the user to type us a key to describe */
  646.     mlprompt("Describe the function bound to this key sequence: ");
  647.  
  648.     /* get the command sequence to describe
  649.        change it to something we can print as well */
  650.  
  651.     /* check to see if we are executing a command line */
  652.     if (clexec) {
  653.         char tok[NSTRING];
  654.         macarg(tok);    /* get the next token */
  655.         c = prc2kcod(tok);
  656.         if (c < 0) {
  657.             mlforce("[Illegal key-sequence \"%s\"]",tok);
  658.             return(FALSE);
  659.         }
  660.     } else {
  661.         c = kbd_seq_nomap();
  662.         if (c < 0) {
  663.             mlforce("[Not a bindable key-sequence]");
  664.             return(FALSE);
  665.         }
  666.     }
  667.  
  668.     (void)kcod2prc(c, outseq);
  669.     hst_append_s(outseq, FALSE); /* cannot replay this, but can see it */
  670.  
  671.     /* find the right ->function */
  672.     if ((nptr = fnc2ntab(kcod2fnc(c))) == NULL) {
  673.         mlwrite("Key sequence '%s' is not bound to anything.",
  674.                     outseq);
  675.         return TRUE;
  676.     }
  677.  
  678.     /* describe it */
  679.     described_cmd[0] = '^';
  680.     (void)strcpy(described_cmd + 1, nptr->n_name);
  681.     last_apropos_string = described_cmd;
  682.     last_whichcmds = 0;
  683.     append_to_binding_list = TRUE;
  684.     s = update_binding_list((BUFFER *)0);
  685.     append_to_binding_list = FALSE;
  686.  
  687.     mlwrite("Key sequence '%s' is bound to function \"%s\"",
  688.                 outseq, nptr->n_name);
  689.  
  690.     return s;
  691. }
  692.  
  693. /* returns a name in double-quotes */
  694. static char *
  695. quoted(char *dst, const char *src)
  696. {
  697.     return strcat(strcat(strcpy(dst, "\""), src), "\"");
  698. }
  699.  
  700. /* returns the number of columns used by the given string */
  701. static unsigned
  702. converted_len(register char *buffer)
  703. {
  704.     register unsigned len = 0, c;
  705.     while ((c = *buffer++) != EOS) {
  706.         if (c == '\t')
  707.             len |= 7;
  708.         len++;
  709.     }
  710.     return len;
  711. }
  712.  
  713. /* force the buffer to a tab-stop if needed */
  714. static char *
  715. to_tabstop(char *buffer)
  716. {
  717.     register unsigned cpos = converted_len(buffer);
  718.     if (cpos & 7)
  719.         (void)strcat(buffer, "\t");
  720.     return skip_string(buffer);
  721. }
  722.  
  723. /* convert a key binding, padding to the next multiple of 8 columns */
  724. static void
  725. convert_kcode(int c, char *buffer)
  726. {
  727.     (void)kcod2prc(c, to_tabstop(buffer));
  728. }
  729.  
  730. #if OPT_NAMEBST
  731. struct bindlist_data {
  732.     int mask;        /* oper/motion mask */
  733.     int min;        /* minimum key length */
  734.     char *apropos;    /* key check */
  735. };
  736.  
  737. static int
  738. btree_walk(BI_NODE *node, int (*func)(BI_NODE *, const void *),
  739.        const void *data)
  740. {
  741.     if (node)
  742.     {
  743.     if (btree_walk(BI_LEFT(node), func, data))
  744.         return 1;
  745.  
  746.     if (BI_KEY(node))
  747.         func(node, data);
  748.  
  749.     if (btree_walk(BI_RIGHT(node), func, data))
  750.         return 1;
  751.     }
  752.  
  753.     return 0;
  754. }
  755.  
  756. static int
  757. clearflag_func(BI_NODE *n, const void *d GCC_UNUSED)
  758. {
  759.     n->value.n_flags &= ~NBST_DONE;
  760.     return 0;
  761. }
  762.  
  763. static int
  764. addsynonym_func(BI_NODE *node, const void *d)
  765. {
  766.     const CMDFUNC *func = (const CMDFUNC *)d;
  767.     static char outseq[NLINE];    /* output buffer for text */
  768.  
  769.     if (node->value.n_cmd == func &&
  770.     !(node->value.n_flags & NBST_DONE))
  771.     {
  772.     strcpy(outseq, "  or\t");
  773.     quoted(outseq+5, BI_KEY(node));
  774.     if (!addline(curbp, outseq, -1))
  775.         return 1;
  776.     }
  777.  
  778.     return 0;
  779. }
  780.  
  781. static int
  782. makebind_func(BI_NODE *node, const void *d)
  783. {
  784.     const struct bindlist_data *data = (const struct bindlist_data *)d;
  785.     static KBIND *kbp;        /* pointer into a key binding table */
  786.     static char outseq[NLINE];    /* output buffer for keystroke sequence */
  787.     const CMDFUNC *cmd = node->value.n_cmd;
  788.     register int i;
  789.  
  790.     /* has this been listed? */
  791.     if (node->value.n_flags & NBST_DONE)
  792.     return 0;
  793.  
  794.     /* are we interested in this type of command? */
  795.     if (data->mask && !(cmd->c_flags & data->mask))
  796.     return 0;
  797.  
  798.     /* try to avoid alphabetizing by the real short names */
  799.     if (data->min && (int) strlen(BI_KEY(node)) <= data->min)
  800.     return 0;
  801.  
  802.     /* if we are executing an apropos command
  803.        and current string doesn't include the search string */
  804.     if (data->apropos && !strinc(BI_KEY(node), data->apropos))
  805.     return 0;
  806.  
  807.     /* add in the command name */
  808.     quoted(outseq, BI_KEY(node));
  809.     while (converted_len(outseq) < 32)
  810.     strcat(outseq, "\t");
  811.  
  812.     /* look in the simple ascii binding table first */
  813.     for (i = 0; i < N_chars; i++)
  814.     if (asciitbl[i] == cmd)
  815.         convert_kcode(i, outseq);
  816.  
  817.     /* then look in the multi-key table */
  818. #if OPT_REBIND
  819.     for (kbp = KeyBindings; kbp != kbindtbl; kbp = kbp->k_link)
  820.     {
  821.     if (kbp->k_cmd == cmd)
  822.         convert_kcode(kbp->k_code, outseq);
  823.     }
  824. #endif
  825.     for (kbp = kbindtbl; kbp->k_cmd; kbp++)
  826.     if (kbp->k_cmd == cmd)
  827.         convert_kcode(kbp->k_code, outseq);
  828.  
  829.     /* dump the line */
  830.     if (!addline(curbp, outseq, -1))
  831.         return 1;
  832.  
  833.     node->value.n_flags |= NBST_DONE;
  834.  
  835.     /* add synonyms */
  836.     btree_walk(&namebst.head, addsynonym_func, cmd);
  837.  
  838. #if OPT_ONLINEHELP
  839.     if (cmd->c_help && *cmd->c_help)
  840.     lsprintf(outseq, "  (%s %s )",
  841.         (cmd->c_flags & MOTION) ? "motion: " :
  842.         (cmd->c_flags & OPER)   ? "operator: " : "",
  843.         cmd->c_help);
  844.     else
  845.         lsprintf(outseq, "  ( no help for this command )");
  846.  
  847.     if (!addline(curbp, outseq, -1))
  848.     return 1;
  849.  
  850.     if (cmd->c_flags & GLOBOK)
  851.     if (!addline(curbp, "  (may follow global command)", -1))
  852.         return 1;
  853. #endif
  854.     /* blank separator */
  855.     if (!addline(curbp, "", -1))
  856.     return 1;
  857.  
  858.     return 0;
  859. }
  860.  
  861. /* build a binding list (limited or full) */
  862. /* ARGSUSED */
  863. static void
  864. makebindlist(
  865. int whichmask,
  866. void *mstring)        /* match string if partial list, NULL to list all */
  867. {
  868.     struct bindlist_data data;
  869.  
  870.     data.mask = whichmask;
  871.     data.min = SHORT_CMD_LEN;
  872.     data.apropos = (char *)mstring;
  873.  
  874.     /* let us know this is in progress */
  875.     mlwrite("[Building binding list]");
  876.  
  877.     /* clear the NBST_DONE flag */
  878.     btree_walk(&namebst.head, clearflag_func, 0);
  879.  
  880.     /* create binding list */
  881.     if (btree_walk(&namebst.head, makebind_func, &data))
  882.     return;
  883.  
  884.     /* catch entries with no synonym > SHORT_CMD_LEN */
  885.     data.min = 0;
  886.     if (btree_walk(&namebst.head, makebind_func, &data))
  887.     return;
  888.  
  889.     mlerase();    /* clear the message line */
  890. }
  891. #else /* OPT_NAMEBST */
  892. /* fully describe a function into the current buffer, given a pointer to
  893.  * its name table entry */
  894. static int
  895. makefuncdesc(int j, char *listed)
  896. {
  897.     register KBIND *kbp;    /* pointer into a key binding table */
  898.     int i;
  899.     const CMDFUNC *cmd = nametbl[j].n_cmd;
  900.     char outseq[NLINE];    /* output buffer for keystroke sequence */
  901.  
  902.     /* add in the command name */
  903.     (void)quoted(outseq, nametbl[j].n_name);
  904.     while (converted_len(outseq) < 32)
  905.         (void)strcat(outseq, "\t");
  906.  
  907.     /* look in the simple ascii binding table first */
  908.     for (i = 0; i < N_chars; i++)
  909.         if (asciitbl[i] == cmd)
  910.             convert_kcode(i, outseq);
  911.  
  912.     /* then look in the multi-key table */
  913. #if OPT_REBIND
  914.     for (kbp = KeyBindings; kbp != kbindtbl; kbp = kbp->k_link) {
  915.         if (kbp->k_cmd == cmd)
  916.             convert_kcode(kbp->k_code, outseq);
  917.     }
  918. #endif
  919.     for (kbp = kbindtbl; kbp->k_cmd; kbp++)
  920.         if (kbp->k_cmd == cmd)
  921.             convert_kcode(kbp->k_code, outseq);
  922.  
  923.     /* dump the line */
  924.     if (!addline(curbp,outseq,-1))
  925.         return FALSE;
  926.  
  927.     /* then look for synonyms */
  928.     (void)strcpy(outseq, "  or\t");
  929.     for (i = 0; nametbl[i].n_name != 0; i++) {
  930.         /* if it's the one we're on, skip */
  931.         if (i == j)
  932.             continue;
  933.         /* if it's already been listed, skip */
  934.         if (listed[i])
  935.             continue;
  936.         /* if it's not a synonym, skip */
  937.         if (nametbl[i].n_cmd != cmd)
  938.             continue;
  939.         (void)quoted(outseq+5, nametbl[i].n_name);
  940.         if (!addline(curbp,outseq,-1))
  941.             return FALSE;
  942.     }
  943.  
  944. #if OPT_ONLINEHELP
  945.     if (cmd->c_help && cmd->c_help[0])
  946.         (void)lsprintf(outseq,"  (%s %s )",
  947.         (cmd->c_flags & MOTION) ? "motion: " :
  948.             (cmd->c_flags & OPER) ? "operator: " : "",
  949.         cmd->c_help);
  950.     else
  951.         (void)lsprintf(outseq,"  ( no help for this command )");
  952.     if (!addline(curbp,outseq,-1))
  953.         return FALSE;
  954.     if (cmd->c_flags & GLOBOK) {
  955.         if (!addline(curbp,"  (may follow global command)",-1))
  956.             return FALSE;
  957.     }
  958. #endif
  959.     /* blank separator */
  960.     if (!addline(curbp,"",-1))
  961.         return FALSE;
  962.  
  963.     return TRUE;
  964. }
  965.  
  966. /* build a binding list (limited or full) */
  967. /* ARGSUSED */
  968. static void
  969. makebindlist(
  970. int whichmask,
  971. void *mstring)        /* match string if partial list, NULL to list all */
  972. {
  973.     int pass;
  974.     int j;
  975.     int ok = TRUE;        /* reset if out-of-memory, etc. */
  976.     char *listed = typecallocn(char, (ALLOC_T)nametblsize);
  977.  
  978.     if (listed == 0) {
  979.         (void)no_memory(BINDINGLIST_BufName);
  980.         return;
  981.     }
  982.  
  983.     /* let us know this is in progress */
  984.     mlwrite("[Building binding list]");
  985.  
  986.     /* build the contents of this window, inserting it line by line */
  987.     for (pass = 0; pass < 2; pass++) {
  988.         for (j = 0; nametbl[j].n_name != 0; j++) {
  989.  
  990.         /* if we've already described this one, move on */
  991.         if (listed[j])
  992.             continue;
  993.  
  994.         /* are we interested in this type of command? */
  995.         if (whichmask && !(nametbl[j].n_cmd->c_flags & whichmask))
  996.             continue;
  997.  
  998.         /* try to avoid alphabetizing by the real short names */
  999.         if (pass == 0 && (int)strlen(nametbl[j].n_name) <= SHORT_CMD_LEN)
  1000.             continue;
  1001.  
  1002.         /* if we are executing an apropos command
  1003.            and current string doesn't include the search string */
  1004.         if (mstring
  1005.          && (strinc(nametbl[j].n_name, (char *)mstring) == FALSE))
  1006.             continue;
  1007.  
  1008.         ok = makefuncdesc(j, listed);
  1009.         if (!ok)
  1010.             break;
  1011.  
  1012.         listed[j] = TRUE; /* mark it as already listed */
  1013.         }
  1014.     }
  1015.  
  1016.     if (ok)
  1017.         mlerase();    /* clear the message line */
  1018.     free(listed);
  1019. }
  1020. #endif /* OPT_NAMEBST */
  1021.  
  1022. /* much like the "standard" strstr, but if the substring starts
  1023.     with a '^', we discard it and force an exact match.  */
  1024. static int
  1025. strinc(            /* does source include sub? */
  1026. const char *sourc,    /* string to search in */
  1027. const char *sub)    /* substring to look for */
  1028. {
  1029.     const char *sp;        /* ptr into source */
  1030.     const char *nxtsp;    /* next ptr into source */
  1031.     const char *tp;        /* ptr into substring */
  1032.     int exact = (*sub == '^');
  1033.  
  1034.     if (exact)
  1035.         sub++;
  1036.  
  1037.     /* for each character in the source string */
  1038.     sp = sourc;
  1039.     while (*sp) {
  1040.         tp = sub;
  1041.         nxtsp = sp;
  1042.  
  1043.         /* is the substring here? */
  1044.         while (*tp) {
  1045.             if (*nxtsp++ != *tp)
  1046.                 break;
  1047.             tp++;
  1048.         }
  1049.  
  1050.         if ((*tp == EOS) && (!exact || *nxtsp == EOS))
  1051.             return(TRUE);
  1052.  
  1053.         if (exact) /* we only get one chance */
  1054.             break;
  1055.  
  1056.         /* no, onward */
  1057.         sp++;
  1058.     }
  1059.     return(FALSE);
  1060. }
  1061.  
  1062. #endif /* OPT_REBIND */
  1063.  
  1064.  
  1065. /*    Look up the existence of a file along the normal or PATH
  1066.     environment variable. Look first in the HOME directory if
  1067.     asked and possible
  1068. */
  1069.  
  1070. char *
  1071. flook(
  1072. char *fname,        /* base file name to search for */
  1073. UINT hflag)        /* Look in the HOME environment variable first? */
  1074. {
  1075.     register char *home;    /* path to home directory */
  1076. #if ENVFUNC && OPT_PATHLOOKUP
  1077.     register const char *path; /* environmental PATH variable */
  1078. #endif
  1079.     static char fspec[NSTRING];    /* full path spec to search */
  1080. #if SYS_VMS
  1081.     register char *sp;    /* pointer into path spec */
  1082.     static TBUFF *myfiles;
  1083. #endif
  1084.     int    mode = (hflag & (FL_EXECABLE|FL_WRITEABLE|FL_READABLE));
  1085.  
  1086.     /* take care of special cases */
  1087.     if (!fname || !fname[0] || isSpace(fname[0]))
  1088.         return NULL;
  1089.     else if (isShellOrPipe(fname))
  1090.         return fname;
  1091.  
  1092.     if (hflag & FL_HERE) {
  1093.         if (ffaccess(fname, mode)) {
  1094.             return(fname);
  1095.         }
  1096.     }
  1097.  
  1098. #if ENVFUNC
  1099.  
  1100.     if (hflag & FL_HOME) {
  1101.         home = getenv("HOME");
  1102.         if (home != NULL) {
  1103.             /* try home dir file spec */
  1104.             if (ffaccess(pathcat(fspec,home,fname), mode)) {
  1105.                 return(fspec);
  1106.             }
  1107.         }
  1108.     }
  1109.  
  1110. #endif    /* ENVFUNC */
  1111.  
  1112.     if (hflag & FL_EXECDIR) { /* is it where we found the executable? */
  1113.         if (exec_pathname
  1114.          && exec_pathname[0] != EOS
  1115.          && ffaccess(pathcat(fspec, exec_pathname, fname), mode))
  1116.             return(fspec);
  1117.     }
  1118.  
  1119.     if (hflag & FL_TABLE) {
  1120.         /* then look it up via the table method */
  1121.         path = startup_path;
  1122.         while ((path = parse_pathlist(path, fspec)) != 0) {
  1123.             if (ffaccess(pathcat(fspec, fspec, fname), mode)) {
  1124.                 return(fspec);
  1125.             }
  1126.         }
  1127.     }
  1128.  
  1129.     if (hflag & FL_PATH) {
  1130.  
  1131. #if ENVFUNC
  1132. #if OPT_PATHLOOKUP
  1133.         /* then look along $PATH */
  1134. #if SYS_VMS
  1135.         /* On VAX/VMS, the PATH environment variable is only the
  1136.          * current-dir.  Fake up an acceptable alternative.
  1137.          */
  1138.         if (!tb_length(myfiles)) {
  1139.             char    mypath[NFILEN];
  1140.  
  1141.             (void)strcpy(mypath, prog_arg);
  1142.             if ((sp = vms_pathleaf(mypath)) == mypath)
  1143.                 (void)strcpy(mypath, current_directory(FALSE));
  1144.             else
  1145.                 *sp = EOS;
  1146.  
  1147.             if (!tb_init(&myfiles, EOS)
  1148.              || !tb_sappend(&myfiles, mypath)
  1149.              || !tb_sappend(&myfiles, ",SYS$SYSTEM:,SYS$LIBRARY:")
  1150.              || !tb_append(&myfiles, EOS))
  1151.             return NULL;
  1152.         }
  1153.         path = tb_values(myfiles);
  1154. #else    /* UNIX or MSDOS */
  1155.         path = getenv("PATH");    /* get the PATH variable */
  1156. #endif
  1157.         while ((path = parse_pathlist(path, fspec)) != 0) {
  1158.             if (ffaccess(pathcat(fspec, fspec, fname), mode)) {
  1159.                 return(fspec);
  1160.             }
  1161.         }
  1162. #endif    /* OPT_PATHLOOKUP */
  1163. #endif    /* ENVFUNC */
  1164.  
  1165.     }
  1166.  
  1167.     return NULL;    /* no such luck */
  1168. }
  1169.  
  1170. /* translate a keycode to its binding-string */
  1171. char *
  1172. kcod2pstr(
  1173. int c,        /* sequence to translate */
  1174. char *seq)    /* destination string for sequence */
  1175. {
  1176.     seq[0] = (char)kcod2escape_seq(c, &seq[1]);
  1177.     return seq;
  1178. }
  1179.  
  1180. /* Translate a 16-bit keycode to a string that will replay into the same
  1181.  * code.
  1182.  */
  1183. int
  1184. kcod2escape_seq (
  1185. int    c,
  1186. char *    ptr)
  1187. {
  1188.     char    *base = ptr;
  1189.  
  1190.     /* ...just for completeness */
  1191.     if (c & CTLA)        *ptr++ = (char)cntl_a;
  1192.     else if (c & CTLX)    *ptr++ = (char)cntl_x;
  1193.     else if (c & SPEC)    *ptr++ = (char)poundc;
  1194. #if SYS_WINNT
  1195. #define ALTPLUS   "Alt+"
  1196. #define CTRLPLUS  "Ctrl+"
  1197. #define SHIFTPLUS "Shift+"
  1198. #define W32INSERT "Insert"
  1199.     else if (c & W32_KEY)
  1200.     {
  1201.         if (c & W32_SHIFT)
  1202.         {
  1203.             strcpy(ptr, SHIFTPLUS);
  1204.             ptr += sizeof(SHIFTPLUS) - 1;
  1205.         }
  1206.         if (c & W32_CTRL)
  1207.         {
  1208.             strcpy(ptr, CTRLPLUS);
  1209.             ptr += sizeof(CTRLPLUS) - 1;
  1210.         }
  1211.         if (c & W32_ALT)
  1212.         {
  1213.             strcpy(ptr, ALTPLUS);
  1214.             ptr += sizeof(ALTPLUS) - 1;
  1215.         }
  1216.         c &= W32_NOMOD;
  1217.         if (c == VK_INSERT)
  1218.         {
  1219.             strcpy(ptr, W32INSERT);
  1220.             ptr += sizeof(W32INSERT) - 1;
  1221.         }
  1222.         else
  1223.             *ptr++ = c;    /* Pickup <modifier>+...<single_char> */
  1224.         *ptr = EOS;
  1225.         return (int)(ptr - base);
  1226.     }
  1227. #endif
  1228.     *ptr++ = (char)c;
  1229.     *ptr = EOS;
  1230.     return (int)(ptr - base);
  1231. }
  1232.  
  1233.  
  1234. /* translates a binding string into printable form */
  1235. #if OPT_REBIND
  1236. static char *
  1237. bytes2prc(char *dst, char *src, int n)
  1238. {
  1239.     char    *base = dst;
  1240.     register int    c;
  1241.     register const char *tmp;
  1242.  
  1243.     for ( ; n != 0; dst++, src++, n--) {
  1244.  
  1245.         c = *src;
  1246.  
  1247.         tmp = 0;
  1248.  
  1249.         if (c & HIGHBIT) {
  1250.             *dst++ = 'M';
  1251.             *dst++ = '-';
  1252.             c &= ~HIGHBIT;
  1253.         }
  1254.         if (c == ' ') {
  1255.             tmp = "<space>";
  1256.         } else if (isCntrl(c)) {
  1257.             *dst++ = '^';
  1258.             *dst = tocntrl(c);
  1259.         } else {
  1260.             *dst = (char)c;
  1261.         }
  1262.  
  1263.         if (tmp != 0) {
  1264.             while ((*dst++ = *tmp++) != EOS)
  1265.                 ;
  1266.             dst -= 2;    /* point back to last nonnull */
  1267.         }
  1268.  
  1269.         if (n > 1) {
  1270.             *++dst = '-';
  1271.         }
  1272.     }
  1273.     *dst = EOS;
  1274.     return base;
  1275. }
  1276.  
  1277. /* translate a 10-bit keycode to its printable name (like "M-j")  */
  1278. static char *
  1279. kcod2prc(
  1280. int c,        /* sequence to translate */
  1281. char *seq)    /* destination string for sequence */
  1282. {
  1283.     char    temp[NSTRING];
  1284.     (void)kcod2pstr(c,temp);
  1285. #if SYS_WINNT
  1286.     if (c & W32_KEY)
  1287.     {
  1288.         /* Translation is complete, by defn. */
  1289.  
  1290.         strcpy(seq, temp + 1);
  1291.         return (seq);
  1292.     }
  1293. #endif
  1294.     return bytes2prc(seq, temp + 1, (int)*temp);
  1295. }
  1296. #endif
  1297.  
  1298.  
  1299. /* kcode2kbind: translate a 10-bit key-binding to the table-pointer
  1300.  */
  1301. static KBIND *
  1302. kcode2kbind(register int code)
  1303. {
  1304.     register KBIND    *kbp;    /* pointer into a binding table */
  1305.  
  1306. #if OPT_REBIND
  1307.     for (kbp = KeyBindings; kbp != kbindtbl; kbp = kbp->k_link) {
  1308.         if (kbp->k_code == code)
  1309.             return kbp;
  1310.     }
  1311. #endif
  1312.     for (kbp = kbindtbl; kbp->k_cmd; kbp++) {
  1313.         if (kbp->k_code == code)
  1314.             return kbp;
  1315.     }
  1316.     return 0;
  1317. }
  1318.  
  1319. /* kcod2fnc:  translate a 10-bit keycode to a function pointer */
  1320. /*    (look a key binding up in the binding table)        */
  1321. const CMDFUNC *
  1322. kcod2fnc(
  1323. int c)    /* key to find what is bound to it */
  1324. {
  1325.     if (isspecial(c)) {
  1326.         register KBIND *kp = kcode2kbind(c);
  1327.         return (kp != 0) ? kp->k_cmd : 0;
  1328.     }
  1329.     return asciitbl[c];
  1330. }
  1331.  
  1332. /* fnc2kcod: translate a function pointer to a keycode */
  1333. int
  1334. fnc2kcod(const CMDFUNC *f)
  1335. {
  1336.     register KBIND *kbp;
  1337.     register int    c;
  1338.  
  1339.     for (c = 0; c < N_chars; c++)
  1340.         if (f == asciitbl[c])
  1341.             return c;
  1342.  
  1343. #if OPT_REBIND
  1344.     for (kbp = KeyBindings; kbp != kbindtbl; kbp = kbp->k_link) {
  1345.         if (kbp->k_cmd == f)
  1346.             return kbp->k_code;
  1347.     }
  1348. #endif
  1349.     for (kbp = kbindtbl; kbp->k_cmd != 0; kbp++) {
  1350.         if (kbp->k_cmd == f)
  1351.             return kbp->k_code;
  1352.     }
  1353.  
  1354.     return -1;    /* none found */
  1355. }
  1356.  
  1357. /* fnc2pstr: translate a function pointer to a pascal-string that a user
  1358.     could enter.  returns a pointer to a static array */
  1359. #if DISP_X11
  1360. char *
  1361. fnc2pstr(const CMDFUNC *f)
  1362. {
  1363.     register int    c;
  1364.     static char seq[10];
  1365.  
  1366.     c = fnc2kcod(f);
  1367.  
  1368.     if (c == -1)
  1369.         return NULL;
  1370.  
  1371.     return kcod2pstr(c, seq);
  1372. }
  1373. #endif
  1374.  
  1375. /* fnc2engl: translate a function pointer to the english name for
  1376.         that function
  1377. */
  1378. #if OPT_EVAL || OPT_REBIND
  1379. static const NTAB *
  1380. fnc2ntab(const CMDFUNC *cfp)
  1381. {
  1382.     register const NTAB *nptr;    /* pointer into the name table */
  1383.     register const NTAB *shortnptr = NULL; /* pointer into the name table */
  1384.  
  1385.     /* skim through the table, looking for a match */
  1386.     for (nptr = nametbl; nptr->n_cmd; nptr++) {
  1387.         if (nptr->n_cmd == cfp) {
  1388.             /* if it's a long name, return it */
  1389.             if ((int)strlen(nptr->n_name) > SHORT_CMD_LEN)
  1390.                 return nptr;
  1391.             /* remember the first short name, in case there's
  1392.                 no long name */
  1393.             if (!shortnptr)
  1394.                 shortnptr = nptr;
  1395.         }
  1396.     }
  1397.     if (shortnptr)
  1398.         return shortnptr;
  1399.  
  1400.     return NULL;
  1401. }
  1402.  
  1403. static char *
  1404. fnc2engl(const CMDFUNC *cfp)
  1405. {
  1406.     register const NTAB *nptr = fnc2ntab(cfp);
  1407.     return nptr ? nptr->n_name : 0;
  1408. }
  1409.  
  1410. #endif
  1411.  
  1412. /* engl2fnc: match name to a function in the names table
  1413.     translate english name to function pointer
  1414.           return any match or NULL if none
  1415.  */
  1416. #define BINARY_SEARCH_IS_BROKEN 0
  1417. #if OPT_NAMEBST
  1418. const CMDFUNC *
  1419. engl2fnc(const char *fname)    /* name to attempt to match */
  1420. {
  1421.     BI_NODE *n = btree_pmatch(BI_RIGHT(&namebst.head), TRUE, fname);
  1422.  
  1423.     if (n == NULL) return NULL;
  1424.     else return n->value.n_cmd;
  1425. }
  1426. #else
  1427. #if BINARY_SEARCH_IS_BROKEN  /* then use the old linear look-up */
  1428. const CMDFUNC *
  1429. engl2fnc(const char *fname)    /* name to attempt to match */
  1430. {
  1431.     register NTAB *nptr;    /* pointer to entry in name binding table */
  1432.     register SIZE_T len = strlen(fname);
  1433.  
  1434.     if (len != 0) {    /* scan through the table, returning any match */
  1435.         nptr = nametbl;
  1436.         while (nptr->n_cmd != NULL) {
  1437.             if (strncmp(fname, nptr->n_name, len) == 0)
  1438.                 return nptr->n_cmd;
  1439.             ++nptr;
  1440.         }
  1441.     }
  1442.     return NULL;
  1443. }
  1444. #else
  1445. /* this runs 10 times faster for 'nametbl[]' */
  1446. const CMDFUNC *
  1447. engl2fnc(const char *fname)    /* name to attempt to match */
  1448. {
  1449.     int lo, hi, cur;
  1450.     int r;
  1451.     register SIZE_T len = strlen(fname);
  1452.  
  1453.     if (len == 0)
  1454.         return NULL;
  1455.  
  1456.     /* scan through the table, returning any match */
  1457.     lo = 0;
  1458.     hi = nametblsize - 2;    /* don't want last entry -- it's NULL */
  1459.  
  1460.     while (lo <= hi) {
  1461.         cur = (lo + hi) >> 1;
  1462.         if ((r = strncmp(fname, nametbl[cur].n_name, len)) == 0) {
  1463.             /* Now find earliest matching entry */
  1464.             while (cur > lo
  1465.                 && strncmp(fname, nametbl[cur-1].n_name, len) == 0)
  1466.                 cur--;
  1467.             return nametbl[cur].n_cmd;
  1468.  
  1469.         } else if (r > 0) {
  1470.             lo = cur+1;
  1471.         } else {
  1472.             hi = cur-1;
  1473.         }
  1474.     }
  1475.     return NULL;
  1476. }
  1477. #endif    /* binary vs linear */
  1478. #endif  /* OPT_NAMEBST */
  1479.  
  1480. /* prc2kcod: translate printable code to 10 bit keycode */
  1481. #if OPT_EVAL || OPT_REBIND
  1482. static int
  1483. prc2kcod(
  1484. const char *kk)        /* name of key to translate to Command key form */
  1485. {
  1486.     register UINT c;    /* key sequence to return */
  1487.     register UINT pref = 0;    /* key prefixes */
  1488.     register int len = strlen(kk);
  1489.     register const UCHAR *k = (const UCHAR *)kk;
  1490.  
  1491.     if (len > 3 && *(k+2) == '-') {
  1492.         if (*k == '^') {
  1493.             if (isCntrl(cntl_a) && *(k+1) == toalpha(cntl_a))
  1494.                 pref = CTLA;
  1495.             if (isCntrl(cntl_x) && *(k+1) == toalpha(cntl_x))
  1496.                 pref = CTLX;
  1497.             if (isCntrl(poundc) && *(k+1) == toalpha(poundc))
  1498.                 pref = SPEC;
  1499.         } else if (!strncmp((const char *)k, "FN", (SIZE_T)2)) {
  1500.             pref = SPEC;
  1501.         }
  1502.         if (pref != 0)
  1503.             k += 3;
  1504.     } else if (len > 2 && !strncmp((const char *)k, "M-", (SIZE_T)2)) {
  1505.         pref = HIGHBIT;
  1506.         k += 2;
  1507.     } else if (len > 1) {
  1508.         if (*k == cntl_a)
  1509.             pref = CTLA;
  1510.         else if (*k == cntl_x)
  1511.             pref = CTLX;
  1512.         else if (*k == poundc)
  1513.             pref = SPEC;
  1514.         if (pref != 0) {
  1515.             k++;
  1516.             if (len > 2 && *k == '-')
  1517.                 k++;
  1518.         }
  1519.     }
  1520.  
  1521.     /* a control char? */
  1522.     if (*k == '^' && *(k+1) != EOS) {
  1523.         c = *(k+1);
  1524.         if (isLower(c)) c = toUpper(c);
  1525.         c = tocntrl(c);
  1526.         k += 2;
  1527.     } else {        /* any single char, control or not */
  1528.         c = *k++;
  1529.     }
  1530.  
  1531.     if (*k != EOS)        /* we should have eaten the whole thing */
  1532.         return -1;
  1533.  
  1534.     return (int)(pref|c);
  1535. }
  1536. #endif
  1537.  
  1538.  
  1539. #if OPT_EVAL
  1540. /* translate printable code (like "M-r") to english command name */
  1541. const char *
  1542. prc2engl(        /* string key name to binding name.... */
  1543. const char *skey)    /* name of key to get binding for */
  1544. {
  1545.     const char *bindname;
  1546.     int c;
  1547.  
  1548.     c = prc2kcod(skey);
  1549.     if (c < 0)
  1550.         return "ERROR";
  1551.  
  1552.     bindname = fnc2engl(kcod2fnc(c));
  1553.     if (bindname == NULL)
  1554.         bindname = "ERROR";
  1555.  
  1556.     return bindname;
  1557. }
  1558. #endif
  1559.  
  1560. /*
  1561.  * Get an english command name from the user
  1562.  */
  1563. char *
  1564. kbd_engl(
  1565. const char *prompt,    /* null pointer to splice calls */
  1566. char *buffer)
  1567. {
  1568.     if (kbd_engl_stat(prompt, buffer, 0) == TRUE)
  1569.         return buffer;
  1570.     return NULL;
  1571. }
  1572.  
  1573. /* sound the alarm! */
  1574. void
  1575. kbd_alarm(void)
  1576. {
  1577.     TRACE(("BEEP\n"))
  1578.  
  1579.     if (global_g_val(GMDERRORBELLS)) {
  1580.         TTbeep();
  1581.         TTflush();
  1582.     }
  1583.     warnings++;
  1584. }
  1585.  
  1586. /* put a character to the keyboard-prompt */
  1587. void
  1588. kbd_putc(int c)
  1589. {
  1590.     BUFFER *savebp;
  1591.     WINDOW *savewp;
  1592.     MARK savemk;
  1593.  
  1594.     beginDisplay();
  1595.     savebp = curbp;
  1596.     savewp = curwp;
  1597.     curbp = bminip;
  1598.     curwp = wminip;
  1599.     savemk = MK;
  1600.     MK = DOT;
  1601.     if ((kbd_expand <= 0) && isreturn(c)) {
  1602.         kbd_erase_to_end(0);
  1603.     } else {
  1604.         if ((kbd_expand < 0) && (c == '\t')) {
  1605.             (void)linsert(1,' ');
  1606.         } else {
  1607.             (void)linsert(1,c);
  1608.         }
  1609.         if (! is_header_line(DOT,curbp) && !is_at_end_of_line(DOT))
  1610.             forwchar(TRUE,1); /* END OF LINE HACK */
  1611. #ifdef DEBUG
  1612.         TRACE(("mini:%2d:%.*s\n", llength(DOT.l), llength(DOT.l), DOT.l->l_text));
  1613. #endif
  1614.     }
  1615.     curbp = savebp;
  1616.     curwp = savewp;
  1617.     MK = savemk;
  1618.     endofDisplay();
  1619. }
  1620.  
  1621. /* put a string to the keyboard-prompt */
  1622. void
  1623. kbd_puts(const char *s)
  1624. {
  1625.     while (*s)
  1626.         kbd_putc(*s++);
  1627. }
  1628.  
  1629. /* erase a character from the display by wiping it out */
  1630. void
  1631. kbd_erase(void)
  1632. {
  1633.     BUFFER *savebp;
  1634.     WINDOW *savewp;
  1635.     MARK savemk;
  1636.  
  1637.     if (!disinp)
  1638.         return;
  1639.  
  1640.     beginDisplay();
  1641.     savebp = curbp;
  1642.     savewp = curwp;
  1643.     curbp = bminip;
  1644.     curwp = wminip;
  1645.     savemk = MK;
  1646.     MK = DOT;
  1647.     if (DOT.o > 0) {
  1648.         DOT.o -= 1;
  1649.         ldelete(1, FALSE);
  1650.     }
  1651. #ifdef DEBUG
  1652.     TRACE(("MINI:%2d:%.*s\n", llength(DOT.l), llength(DOT.l), DOT.l->l_text));
  1653. #endif
  1654.     curbp = savebp;
  1655.     curwp = savewp;
  1656.     MK = savemk;
  1657.     endofDisplay();
  1658. }
  1659.  
  1660. void
  1661. kbd_erase_to_end(int column)
  1662. {
  1663.     BUFFER *savebp;
  1664.     WINDOW *savewp;
  1665.     MARK savemk;
  1666.  
  1667.     if (!disinp)
  1668.         return;
  1669.  
  1670.     beginDisplay();
  1671.     savebp = curbp;
  1672.     savewp = curwp;
  1673.     curbp = bminip;
  1674.     curwp = wminip;
  1675.     savemk = MK;
  1676.     MK = DOT;
  1677.     if (llength(DOT.l) > 0) {
  1678.         DOT.o = column;
  1679.         ldelete(llength(DOT.l) - DOT.o, FALSE);
  1680.         TRACE(("NULL:%2d:%.*s\n", llength(DOT.l), llength(DOT.l), DOT.l->l_text));
  1681.     }
  1682.     curbp = savebp;
  1683.     curwp = savewp;
  1684.     MK = savemk;
  1685.     endofDisplay();
  1686. }
  1687.  
  1688.  
  1689. #if OPT_CASELESS
  1690. static int
  1691. cs_strcmp(
  1692. int case_insensitive,
  1693. const char *s1,
  1694. const char *s2)
  1695. {
  1696.     if (case_insensitive)
  1697.         return stricmp(s1, s2);
  1698.     return strcmp(s1, s2);
  1699. }
  1700.  
  1701. static int
  1702. cs_strncmp(
  1703. int case_insensitive,
  1704. const char *s1,
  1705. const char *s2,
  1706. SIZE_T n)
  1707. {
  1708.     if (case_insensitive)
  1709.         return strnicmp(s1, s2, n);
  1710.     return strncmp(s1, s2, n);
  1711. }
  1712. #endif    /* OPT_CASELESS */
  1713.  
  1714. /* definitions for name-completion */
  1715. #define    NEXT_DATA(p)    ((p)+size_entry)
  1716. #define    PREV_DATA(p)    ((p)-size_entry)
  1717.  
  1718. #ifdef    lint
  1719. static    /*ARGSUSED*/
  1720. const char *    THIS_NAME(const char *p) { return 0; }
  1721. #else
  1722. #define    THIS_NAME(p)    (*TYPECAST(const char *const,p))
  1723. #endif
  1724. #define    NEXT_NAME(p)    THIS_NAME(NEXT_DATA(p))
  1725.  
  1726. /*
  1727.  * Scan down until we no longer match the current input, or reach the end of
  1728.  * the symbol table.
  1729.  */
  1730. /*ARGSUSED*/
  1731. static const char *
  1732. skip_partial(
  1733. int    case_insensitive GCC_UNUSED,
  1734. char    *buf,
  1735. SIZE_T    len,
  1736. const char *table,
  1737. SIZE_T    size_entry)
  1738. {
  1739.     register const char * next = NEXT_DATA(table);
  1740.     register const char *    sp;
  1741.  
  1742.     while ((sp = THIS_NAME(next)) != 0) {
  1743.         if (StrNcmp(buf, sp, len) != 0)
  1744.             break;
  1745.         next = NEXT_DATA(next);
  1746.     }
  1747.     return next;
  1748. }
  1749.  
  1750. /*
  1751.  * Shows a partial-match.  This is invoked in the symbol table at a partial
  1752.  * match, and the user wants to know what characters could be typed next.
  1753.  * If there is more than one possibility, they are shown in square-brackets.
  1754.  * If there is only one possibility, it is shown in curly-braces.
  1755.  */
  1756. static void
  1757. show_partial(
  1758. int    case_insensitive,
  1759. char    *buf,
  1760. SIZE_T    len,
  1761. const char *table,
  1762. SIZE_T    size_entry)
  1763. {
  1764.     register const char *next = skip_partial(case_insensitive, buf, len, table, size_entry);
  1765.     register const char *last = PREV_DATA(next);
  1766.     register int    c;
  1767.  
  1768.     if (THIS_NAME(table)[len] == THIS_NAME(last)[len]) {
  1769.         kbd_putc('{');
  1770.         while ((c = THIS_NAME(table)[len]) != 0) {
  1771.             if (c == THIS_NAME(last)[len]) {
  1772.                 kbd_putc(c);
  1773.                 len++;
  1774.             } else
  1775.                 break;
  1776.         }
  1777.         kbd_putc('}');
  1778.     }
  1779.     if (next != NEXT_DATA(table)) {
  1780.         c = TESTC;    /* shouldn't be in the table! */
  1781.         kbd_putc('[');
  1782.         while (table != next) {
  1783.             register const char *sp = THIS_NAME(table);
  1784.             if (c != sp[len]) {
  1785.                 c = sp[len];
  1786.                 kbd_putc(c ? c : '$');
  1787.             }
  1788.             table = NEXT_DATA(table);
  1789.         }
  1790.         kbd_putc(']');
  1791.     }
  1792.     kbd_flush();
  1793. }
  1794.  
  1795. #if OPT_POPUPCHOICE
  1796. /*
  1797.  * makecmpllist is called from liststuff to display the possible completions.
  1798.  */
  1799. struct compl_rec {
  1800.     char *buf;
  1801.     SIZE_T len;
  1802.     const char *table;
  1803.     SIZE_T size_entry;
  1804. };
  1805.  
  1806. #ifdef lint
  1807. #define c2ComplRec(c) ((struct compl_rec *)0)
  1808. #else
  1809. #define c2ComplRec(c) ((struct compl_rec *)c)
  1810. #endif
  1811.  
  1812. /*ARGSUSED*/
  1813. static void
  1814. makecmpllist(
  1815.     int case_insensitive,
  1816.     void *cinfop)
  1817. {
  1818.     char * buf        = c2ComplRec(cinfop)->buf;
  1819.     SIZE_T len        = c2ComplRec(cinfop)->len;
  1820.     const char * first    = c2ComplRec(cinfop)->table;
  1821.     SIZE_T size_entry    = c2ComplRec(cinfop)->size_entry;
  1822.     register const char *last = skip_partial(case_insensitive, buf, len, first, size_entry);
  1823.     register const char *p;
  1824.     SIZE_T maxlen;
  1825.     int slashcol;
  1826.     int cmpllen;
  1827.     int cmplcols;
  1828.     int cmplrows;
  1829.     int nentries;
  1830.     int i, j;
  1831.  
  1832.     for (p = NEXT_DATA(first), maxlen = strlen(THIS_NAME(first));
  1833.          p != last;
  1834.      p = NEXT_DATA(p)) {
  1835.     SIZE_T l = strlen(THIS_NAME(p));
  1836.     if (l > maxlen)
  1837.         maxlen = l;
  1838.     }
  1839.  
  1840.     slashcol = (int)(pathleaf(buf) - buf);
  1841.     if (slashcol != 0) {
  1842.         char b[NLINE];
  1843.         (void)strncpy(b, buf, (SIZE_T)slashcol);
  1844.         (void)strncpy(&b[slashcol], &(THIS_NAME(first))[slashcol],
  1845.             (len-slashcol));
  1846.         b[slashcol+(len-slashcol)] = EOS;
  1847.         bprintf("Completions prefixed by %s:\n", b);
  1848.     }
  1849.  
  1850.     cmplcols = term.t_ncol / (maxlen - slashcol + 1);
  1851.  
  1852.     if (cmplcols == 0)
  1853.     cmplcols = 1;
  1854.  
  1855.     nentries = (int)(last - first) / size_entry;
  1856.     cmplrows = nentries / cmplcols;
  1857.     cmpllen  = term.t_ncol / cmplcols;
  1858.     if (cmplrows * cmplcols < nentries)
  1859.     cmplrows++;
  1860.  
  1861.     for (i = 0; i < cmplrows; i++) {
  1862.     for (j = 0; j < cmplcols; j++) {
  1863.         int idx = cmplrows * j + i;
  1864.         if (idx < nentries) {
  1865.         const char *s = THIS_NAME(first+(idx*size_entry))+slashcol;
  1866.         if (j == cmplcols-1)
  1867.             bprintf("%s\n", s);
  1868.         else
  1869.             bprintf("%*s", cmpllen, s);
  1870.         }
  1871.         else {
  1872.         bprintf("\n");
  1873.         break;
  1874.         }
  1875.     }
  1876.     }
  1877. }
  1878.  
  1879. /*
  1880.  * Pop up a window and show the possible completions.
  1881.  */
  1882. static void
  1883. show_completions(
  1884. int    case_insensitive,
  1885. char    *buf,
  1886. SIZE_T    len,
  1887. const char *table,
  1888. SIZE_T    size_entry)
  1889. {
  1890.     struct compl_rec cinfo;
  1891.     BUFFER *bp;
  1892.     int alreadypopped = 0;
  1893.  
  1894.     /*
  1895.      * Find out if completions buffer exists; so we can take the time to
  1896.      * shrink/grow the window to the latest size.
  1897.      */
  1898.     if ((bp = find_b_name(COMPLETIONS_BufName)) != NULL) {
  1899.     alreadypopped = (bp->b_nwnd != 0);
  1900.     }
  1901.  
  1902.     cinfo.buf = buf;
  1903.     cinfo.len = len;
  1904.     cinfo.table = table;
  1905.     cinfo.size_entry = size_entry;
  1906.     liststuff(COMPLETIONS_BufName, FALSE, makecmpllist, case_insensitive, (void *) &cinfo);
  1907.  
  1908.     if (alreadypopped)
  1909.     shrinkwrap();
  1910.  
  1911.     (void)update(TRUE);
  1912. }
  1913.  
  1914. /*
  1915.  * Scroll the completions window wrapping around back to the beginning
  1916.  * of the buffer once it has been completely scrolled.  If the completions
  1917.  * buffer is missing for some reason, we will call show_completions to pop
  1918.  * it (back) up.
  1919.  */
  1920. static void
  1921. scroll_completions(
  1922.     int        case_insensitive,
  1923.     char    *buf,
  1924.     SIZE_T    len,
  1925.     const char    *table,
  1926.     SIZE_T    size_entry)
  1927. {
  1928.     BUFFER *bp = find_b_name(COMPLETIONS_BufName);
  1929.     if (bp == NULL)
  1930.     show_completions(case_insensitive, buf, len, table, size_entry);
  1931.     else {
  1932.     LINEPTR lp;
  1933.     swbuffer(bp);
  1934.     (void)gotoeos(FALSE, 1);
  1935.     lp = DOT.l;
  1936.     (void)forwhpage(FALSE, 1);
  1937.     if (lp == DOT.l)
  1938.         (void)gotobob(FALSE, 0);
  1939.     (void)update(TRUE);
  1940.     }
  1941. }
  1942.  
  1943. void
  1944. popdown_completions(void)
  1945. {
  1946.     BUFFER *bp;
  1947.     if ((bp = find_b_name(COMPLETIONS_BufName)) != NULL)
  1948.     zotwp(bp);
  1949. }
  1950. #endif /* OPT_POPUPCHOICE */
  1951.  
  1952. /*
  1953.  * Attempt to partial-complete the string, char at a time
  1954.  */
  1955. static SIZE_T
  1956. fill_partial(
  1957. int    case_insensitive GCC_UNUSED,
  1958. char    *buf,
  1959. SIZE_T    pos,
  1960. const char *first,
  1961. const char *last,
  1962. SIZE_T    size_entry)
  1963. {
  1964.     register const char *p;
  1965.     register SIZE_T    n = pos;
  1966.     const char *this_name = THIS_NAME(first);
  1967.  
  1968. #if 0 /* case insensitive reply correction doesn't work reliably yet */
  1969.     if (!clexec && case_insensitive) {
  1970.         int spos = pos;
  1971.  
  1972.         while (spos > 0 && buf[spos - 1] != SLASHC) {
  1973.             kbd_erase();
  1974.             spos--;
  1975.         }
  1976.         while (spos < pos) {
  1977.             kbd_putc(this_name[spos]);
  1978.             spos++;
  1979.         }
  1980.     }
  1981. #endif
  1982.  
  1983.     for_ever {
  1984.         buf[n] = this_name[n];    /* add the next char in */
  1985.         buf[n+1] = EOS;
  1986.  
  1987.         /* scan through the candidates */
  1988.         for (p = NEXT_DATA(first); p != last; p = NEXT_DATA(p)) {
  1989.             if (StrNcmp(&THIS_NAME(p)[n], &buf[n], 1) != 0) {
  1990.                 buf[n] = EOS;
  1991.                 if (n == pos
  1992. #if OPT_POPUPCHOICE
  1993. # if OPT_ENUM_MODES
  1994.                  && !global_g_val(GVAL_POPUP_CHOICES)
  1995. # else
  1996.                  && !global_g_val(GMDPOPUP_CHOICES)
  1997. # endif
  1998. #endif
  1999.                 )
  2000.                     kbd_alarm();
  2001.                 kbd_flush(); /* force out alarm or partial completion */
  2002.                 return n;
  2003.             }
  2004.         }
  2005.  
  2006.         if (!clexec)
  2007.             kbd_putc(buf[n]); /* add the character */
  2008.         n++;
  2009.     }
  2010. }
  2011.  
  2012. static    int    testcol;    /* records the column when TESTC is decoded */
  2013. #if OPT_POPUPCHOICE
  2014. /*
  2015.  * cmplcol is used to record the column number (on the message line) after
  2016.  * name completion.  Its value is used to decide whether or not to display
  2017.  * a completion list if the name completion character (tab) is pressed
  2018.  * twice in succession.  Once the completion list has been displayed, its
  2019.  * value will be changed to the additive inverse of the column number in
  2020.  * order to determine whether to scroll if tab is pressed yet again.  We
  2021.  * assume that 0 will never be a valid column number.  So long as we always
  2022.  * display some sort of prompt prior to reading from the message line, this
  2023.  * is a good assumption.
  2024.  */
  2025. static    int    cmplcol = 0;
  2026. #endif
  2027.  
  2028. /*
  2029.  * Initializes the name-completion logic
  2030.  */
  2031. void
  2032. kbd_init(void)
  2033. {
  2034.     testcol = -1;
  2035. }
  2036.  
  2037. /*
  2038.  * Returns the current length of the minibuffer
  2039.  */
  2040. int
  2041. kbd_length(void)
  2042. {
  2043.     if (wminip != 0
  2044.      && wminip->w_dot.l != 0
  2045.      && llength(wminip->w_dot.l) > 0)
  2046.         return llength(wminip->w_dot.l);
  2047.     return 0;
  2048. }
  2049.  
  2050. /*
  2051.  * Erases the display that was shown in response to TESTC
  2052.  */
  2053. void
  2054. kbd_unquery(void)
  2055. {
  2056.     beginDisplay();
  2057. #if OPT_POPUPCHOICE
  2058.     if (cmplcol != kbd_length() && -cmplcol != kbd_length())
  2059.         cmplcol = 0;
  2060. #endif
  2061.     if (testcol >= 0) {
  2062.         while (kbd_length() > testcol)
  2063.             kbd_erase();
  2064.         kbd_flush();
  2065.         testcol = -1;
  2066.     }
  2067.     endofDisplay();
  2068. }
  2069.  
  2070. /*
  2071.  * This is invoked to find the closest name to complete from the current buffer
  2072.  * contents.
  2073.  */
  2074. int
  2075. kbd_complete(
  2076. int    case_insensitive,
  2077. int    c,        /* TESTC, NAMEC or isreturn() */
  2078. char    *buf,
  2079. unsigned *pos,
  2080. const char *table,
  2081. SIZE_T    size_entry)
  2082. {
  2083.     register SIZE_T cpos = *pos;
  2084.     register const char *nbp; /* first ptr to entry in name binding table */
  2085.     int status = FALSE;
  2086. #if OPT_POPUPCHOICE
  2087. # if OPT_ENUM_MODES
  2088.     int gvalpopup_choices = global_g_val(GVAL_POPUP_CHOICES);
  2089. # else
  2090.     int gvalpopup_choices = global_g_val(GMDPOPUP_CHOICES);
  2091. # endif
  2092. #endif
  2093.  
  2094.     kbd_init();        /* nothing to erase */
  2095.     buf[cpos] = EOS;    /* terminate it for us */
  2096.     nbp = table;        /* scan for matches */
  2097.  
  2098.     while (THIS_NAME(nbp) != NULL) {
  2099.         if (StrNcmp(buf,  THIS_NAME(nbp), strlen(buf)) == 0) {
  2100.             testcol = kbd_length();
  2101.             /* a possible match! exact? no more than one? */
  2102. #if OPT_POPUPCHOICE
  2103.             if (!clexec && c == NAMEC && cmplcol == -kbd_length()) {
  2104.                 scroll_completions(case_insensitive, buf, cpos, nbp, size_entry);
  2105.                 return FALSE;
  2106.             }
  2107. #endif
  2108.             if (c == TESTC) {
  2109.                 show_partial(case_insensitive, buf, cpos, nbp, size_entry);
  2110.             }
  2111.             else if (Strcmp(buf,  THIS_NAME(nbp)) == 0 || /* exact? */
  2112.                 NEXT_NAME(nbp) == NULL ||
  2113.                 StrNcmp(buf, NEXT_NAME(nbp), strlen(buf)) != 0)
  2114.             {
  2115.                 /* exact or only one like it.  print it */
  2116.                 if (!clexec) {
  2117. #if 0 /* case insensitive reply correction doesn't work reliably yet */
  2118.                     if (case_insensitive) {
  2119.                         int spos = cpos;
  2120.  
  2121.                         while (spos > 0 && buf[spos - 1] != SLASHC) {
  2122.                             kbd_erase();
  2123.                             spos--;
  2124.                         }
  2125.                         kbd_puts(THIS_NAME(nbp) + spos);
  2126.                     }
  2127.                     else
  2128. #endif
  2129.                         kbd_puts(THIS_NAME(nbp) + cpos);
  2130.                     kbd_flush();
  2131.                     testcol = kbd_length();
  2132.                 }
  2133.                 if (c != NAMEC)  /* put it back */
  2134.                     unkeystroke(c);
  2135.                 /* return complete name */
  2136.                 (void)strncpy0(buf, THIS_NAME(nbp),
  2137.                         (SIZE_T)(NLINE - 1));
  2138.                 *pos = strlen(buf);
  2139. #if OPT_POPUPCHOICE
  2140.                 if (gvalpopup_choices != POPUP_CHOICES_OFF
  2141.                  && !clexec && (c == NAMEC))
  2142.                     status = FALSE;
  2143.                 else
  2144. #endif
  2145.                     status = TRUE;
  2146.             }
  2147.             else {
  2148.                 /* try for a partial match against the list */
  2149.                 *pos = fill_partial(case_insensitive, buf, cpos, nbp,
  2150.                     skip_partial(case_insensitive, buf, cpos, nbp, size_entry),
  2151.                     size_entry);
  2152.                 testcol = kbd_length();
  2153.             }
  2154. #if OPT_POPUPCHOICE
  2155. # if OPT_ENUM_MODES
  2156.             if (!clexec
  2157.              && gvalpopup_choices != POPUP_CHOICES_OFF
  2158.              && c == NAMEC
  2159.              && *pos == cpos) {
  2160.                 if (gvalpopup_choices == POPUP_CHOICES_IMMED
  2161.                  || cmplcol == kbd_length()) {
  2162.                     show_completions(case_insensitive, buf, cpos, nbp, size_entry);
  2163.                     cmplcol = -kbd_length();
  2164.                 }
  2165.                 else
  2166.                     cmplcol = kbd_length();
  2167.             }
  2168.             else
  2169.                 cmplcol = 0;
  2170. # else
  2171.             if (!clexec && gvalpopup_choices
  2172.              && c == NAMEC && *pos == cpos) {
  2173.                 show_completions(case_insensitive, buf, cpos, nbp, size_entry);
  2174.                 cmplcol = -kbd_length();
  2175.             }
  2176.             else
  2177.                 cmplcol = 0;
  2178. # endif
  2179. #endif
  2180.             return status;
  2181.         }
  2182.         nbp = NEXT_DATA(nbp);
  2183.     }
  2184.  
  2185. #if OPT_POPUPCHOICE
  2186.     cmplcol = 0;
  2187. #endif
  2188.     kbd_alarm();    /* no match */
  2189.     buf[*pos = cpos] = EOS;
  2190.     return FALSE;
  2191. }
  2192.  
  2193. /*
  2194.  * Test a buffer to see if it looks like a shift-command, which may have
  2195.  * repeated characters (but they must all be the same).
  2196.  */
  2197. static int
  2198. is_shift_cmd(
  2199. const char *buffer,
  2200. unsigned cpos)
  2201. {
  2202.     register int c = *buffer;
  2203.     if (isRepeatable(c)) {
  2204.         while (--cpos != 0)
  2205.             if (*(++buffer) != c)
  2206.                 return FALSE;
  2207.         return TRUE;
  2208.     }
  2209.     return FALSE;
  2210. }
  2211.  
  2212. /*
  2213.  * The following mess causes the command to terminate if:
  2214.  *
  2215.  *    we've got the eolchar
  2216.  *        -or-
  2217.  *    we're in the first few chars and we're switching from punctuation
  2218.  *    (i.e., delimiters) to non-punctuation (i.e., characters that are part
  2219.  *    of command-names), or vice-versa.  oh yeah -- '-' isn't punctuation
  2220.  *    today, and '!' isn't either, in one direction, at any rate.
  2221.  *    All this allows things like:
  2222.  *        : e#
  2223.  *        : e!%
  2224.  *        : !ls
  2225.  *        : q!
  2226.  *        : up-line
  2227.  *    to work properly.
  2228.  *
  2229.  *    If we pass this "if" with c != NAMEC, then c is ungotten below,
  2230.  *    so it can be picked up by the commands argument getter later.
  2231.  */
  2232.  
  2233. #define ismostpunct(c) (isPunct(c) && (c) != '-')
  2234.  
  2235. static int
  2236. eol_command(
  2237. const char * buffer,
  2238. unsigned cpos,
  2239. int    c,
  2240. int    eolchar)
  2241. {
  2242.     /*
  2243.      * Handle special case of repeated-character implying repeat-count
  2244.      */
  2245.     if (is_shift_cmd(buffer, cpos) && (c == *buffer))
  2246.         return TRUE;
  2247.  
  2248.     /*
  2249.      * Shell-commands aren't complete until the line is complete.
  2250.      */
  2251.     if ((cpos != 0) && isShellOrPipe(buffer))
  2252.         return isreturn(c);
  2253.  
  2254.     return    (c == eolchar)
  2255.       ||    (
  2256.           cpos != 0 &&  cpos < 3
  2257.           &&(
  2258.           (!ismostpunct(c)
  2259.         &&  ismostpunct(buffer[cpos-1])
  2260.           )
  2261.         || ((c != '!' && ismostpunct(c))
  2262.           && (buffer[cpos-1] == '!' || !ismostpunct(buffer[cpos-1]))
  2263.           )
  2264.         )
  2265.           );
  2266. }
  2267.  
  2268. /*
  2269.  * This procedure is invoked from 'kbd_string()' to setup the command-name
  2270.  * completion and query displays.
  2271.  */
  2272. static int
  2273. cmd_complete(
  2274. int    c,
  2275. char    *buf,
  2276. unsigned *pos)
  2277. {
  2278.     register int status;
  2279. #if OPT_HISTORY
  2280.     /*
  2281.      * If the user scrolled back in 'edithistory()', the text may be a
  2282.      * repeated-shift command, which won't match the command-table (e.g.,
  2283.      * ">>>").
  2284.      */
  2285.     if ((*pos > 1) && is_shift_cmd(buf, *pos)) {
  2286.         unsigned len = 1;
  2287.         char    tmp[NLINE];
  2288.         tmp[0] = *buf;
  2289.         tmp[1] = EOS;
  2290.         status = cmd_complete(c, tmp, &len);
  2291.     } else
  2292. #endif
  2293.      if ((*pos != 0) && isShellOrPipe(buf)) {
  2294. #if COMPLETE_FILES
  2295.         status = shell_complete(c, buf, pos);
  2296. #else
  2297.         status = isreturn(c);
  2298.         if (c != NAMEC)
  2299.             unkeystroke(c);
  2300. #endif
  2301.     } else {
  2302.         status = kbd_complete_bst(FALSE, c, buf, pos);
  2303.     }
  2304.     return status;
  2305. }
  2306.  
  2307. int
  2308. kbd_engl_stat(const char *prompt, char    *buffer, int stated)
  2309. {
  2310.     int    kbd_flags = KBD_EXPCMD|KBD_NULLOK|((NAMEC != ' ') ? 0 : KBD_MAYBEC);
  2311.     int    code;
  2312.     static    TBUFF *temp;
  2313.     ALLOC_T    len = NLINE;
  2314.  
  2315.     tb_scopy(&temp, "");
  2316. #if COMPLETE_FILES
  2317.     init_filec(FILECOMPLETION_BufName);
  2318. #endif
  2319.     kbd_flags |= stated;
  2320.     code = kbd_reply(
  2321.         prompt,        /* no-prompt => splice */
  2322.         &temp,        /* in/out buffer */
  2323.         eol_command,
  2324.         ' ',        /* eolchar */
  2325.         kbd_flags,    /* allow blank-return */
  2326.         cmd_complete);
  2327.     if (len > tb_length(temp))
  2328.         len = tb_length(temp);
  2329.     strncpy0(buffer, tb_values(temp), len);
  2330.     return code;
  2331. }
  2332.  
  2333. #if OPT_NAMEBST
  2334. int
  2335. insert_namebst(const char *name, const CMDFUNC *cmd, int ro)
  2336. {
  2337.     BI_DATA temp, *p;
  2338.  
  2339.     if ((p = btree_search(&namebst, name)) != 0)
  2340.     {
  2341.     if ((p->n_flags & NBST_READONLY) && !ro)
  2342.     {
  2343.         mlforce("[Cannot redefine %s]", name);
  2344.         return FALSE;
  2345.     }
  2346.  
  2347.     if (!delete_namebst(name, TRUE))
  2348.         return FALSE;
  2349.     }
  2350.  
  2351.     temp.bi_key     = name;
  2352.     temp.n_cmd      = cmd;
  2353.     temp.n_flags    = ro ? NBST_READONLY : 0;
  2354.  
  2355.     return (btree_insert(&namebst, &temp) != 0);
  2356. }
  2357.  
  2358. /*
  2359.  * Lookup a name in the binary-search tree, remove it if found
  2360.  */
  2361. int
  2362. delete_namebst(const char *name, int release)
  2363. {
  2364.     BI_DATA *p = btree_search(&namebst, name);
  2365.  
  2366.     /* not a named procedure */
  2367.     if (!p)
  2368.     return TRUE;
  2369.  
  2370.     if (p->n_flags & NBST_READONLY)
  2371.     {
  2372.     mlforce("BUG: btree entry %s is readonly", name);
  2373.     return FALSE;
  2374.     }
  2375.  
  2376.     /* we may have to free some stuff */
  2377.     if (p && release)
  2378.     {
  2379.     int i;
  2380.     int redo;
  2381.  
  2382.     /* remove ascii bindings */
  2383.     for (i = 0; i < N_chars; i++)
  2384.         if (asciitbl[i] == p->n_cmd)
  2385.         asciitbl[i] = 0;
  2386.  
  2387.     /* then look in the multi-key table */
  2388. #if OPT_REBIND
  2389.     do {
  2390.         register KBIND *kbp;
  2391.         redo = FALSE;
  2392.         for (kbp = KeyBindings; kbp != kbindtbl; kbp = kbp->k_link)
  2393.         if (kbp->k_cmd == p->n_cmd)
  2394.         {
  2395.             unbindchar(kbp->k_code);
  2396.             redo = TRUE;
  2397.             break;
  2398.         }
  2399.     } while (redo);
  2400. #endif
  2401.     do {
  2402.         register KBIND *kbp;
  2403.         redo = FALSE;
  2404.         for (kbp = kbindtbl; kbp->k_cmd; kbp++)
  2405.         if (kbp->k_cmd == p->n_cmd)
  2406.         {
  2407.             unbindchar(kbp->k_code);
  2408.             redo = TRUE;
  2409.             break;
  2410.         }
  2411.     } while (redo);
  2412.  
  2413.     /* free stuff */
  2414. #if OPT_PERL
  2415.     if (p->n_cmd->c_flags & CMD_PERL)
  2416.         perl_free_sub(CMD_U_PERL(p->n_cmd));
  2417. #endif
  2418.  
  2419.     free(TYPECAST(char,p->n_cmd->c_help));
  2420.     free(TYPECAST(char,p->n_cmd));
  2421.     }
  2422.  
  2423.     return btree_delete(&namebst, name);
  2424. }
  2425.  
  2426. /*
  2427.  * If we're renaming a procedure to another "procedure" name (i.e., bracketed),
  2428.  * rename it in the name-completion table.  Otherwise, simply remove it from the
  2429.  * name-completions.
  2430.  */
  2431. int
  2432. rename_namebst(const char *oldname, const char *newname)
  2433. {
  2434.     BI_DATA *prior;
  2435.     char name[NBUFN];
  2436.  
  2437.     /* not a named procedure */
  2438.     if (!(prior = btree_search(&namebst, oldname)))
  2439.     return TRUE;
  2440.  
  2441.     /* remove the entry if the new name is not a procedure (bracketed) */
  2442.     if (!is_scratchname(newname))
  2443.     return delete_namebst(oldname, TRUE);
  2444.  
  2445.     /* add the new name */
  2446.     strip_brackets(name, newname);
  2447.     if ((insert_namebst(name, prior->n_cmd,
  2448.                prior->n_flags & NBST_READONLY)) != TRUE)
  2449.     return FALSE;
  2450.  
  2451.     /* delete the old (but don't free the data) */
  2452.     return delete_namebst(oldname, FALSE);
  2453. }
  2454.  
  2455. int
  2456. search_namebst(const char *name)
  2457. {
  2458.     return (btree_search(&namebst, name) != 0);
  2459. }
  2460.  
  2461. /*
  2462.  * Build the initial name binary search tree.  Since the nametbl is sorted we
  2463.  * do this in a binary-search manner to get a balanced tree.
  2464.  */
  2465. void
  2466. build_namebst(const NTAB *nptr, int lo, int hi)
  2467. {
  2468.     for (; lo < hi; lo++)
  2469.         if (!insert_namebst(nptr[lo].n_name, nptr[lo].n_cmd, TRUE))
  2470.             tidy_exit(BADEXIT);
  2471. }
  2472.  
  2473. /*
  2474.  * This is invoked to find the closest name to complete from the current buffer
  2475.  * contents.
  2476.  */
  2477. static int
  2478. kbd_complete_bst(
  2479. int    case_insensitive GCC_UNUSED,
  2480. int    c,        /* TESTC, NAMEC or isreturn() */
  2481. char    *buf,
  2482. unsigned *pos)
  2483. {
  2484.     register unsigned cpos = *pos;
  2485.     int status = FALSE;
  2486.     const char **nptr;
  2487.  
  2488.     kbd_init();        /* nothing to erase */
  2489.     buf[cpos] = EOS;    /* terminate it for us */
  2490.  
  2491.     if ((nptr = btree_parray(&namebst, buf, cpos)) != 0) {
  2492.         status = kbd_complete(FALSE, c, buf, pos, (char *)nptr, sizeof(*nptr));
  2493.         free((char *)nptr);
  2494.     } else
  2495.         kbd_alarm();
  2496.     return status;
  2497. }
  2498. #endif /* OPT_NAMEBST */
  2499.  
  2500. #if OPT_MENUS
  2501. /* FIXME: reuse logic from makefuncdesc() */
  2502. char *give_accelerator ( char *bname )
  2503. {
  2504.     size_t             i, n;
  2505.     register KBIND     *kbp;
  2506.     const CMDFUNC     *cmd;
  2507.     static char     outseq[NLINE];
  2508.  
  2509.     for (n=0; nametbl[n].n_name != 0; n++)
  2510.     {
  2511.         if (!strcmp(nametbl[n].n_name, bname))
  2512.         {
  2513.             cmd = nametbl[n].n_cmd;
  2514.  
  2515.             outseq[0] = '\0';
  2516.  
  2517.             for (i = 0; i < N_chars; i++)
  2518.             {
  2519.                 if (asciitbl[i] == cmd)
  2520.                     convert_kcode(i, outseq);
  2521.             }
  2522.  
  2523.             for (kbp = KeyBindings; kbp != kbindtbl; kbp = kbp->k_link) {
  2524.                 if (kbp->k_cmd == cmd)
  2525.                     convert_kcode(kbp->k_code, outseq);
  2526.             }
  2527.  
  2528. #if 0
  2529.             for (kbp = kbindtbl; kbp->k_cmd; kbp++)
  2530.                 if (kbp->k_cmd == cmd)
  2531.                     convert_kcode(kbp->k_code, outseq);
  2532. #endif
  2533.  
  2534.             /* Replace \t by ' ' */
  2535.             for (i=0; i<strlen(outseq); i++)
  2536.             {
  2537.                 if (outseq[i] == '\t')
  2538.                     outseq[i] = ' ';
  2539.             }
  2540.  
  2541.             return outseq;
  2542.         }
  2543.     }
  2544.  
  2545.     return NULL;
  2546. }
  2547. #endif /* OPT_MENUS */
  2548.  
  2549. #if NO_LEAKS
  2550. void
  2551. bind_leaks(void)
  2552. {
  2553. #if OPT_REBIND
  2554.     while (KeyBindings != kbindtbl) {
  2555.         KBIND *kbp = KeyBindings;
  2556.         KeyBindings = kbp->k_link;
  2557.         free((char *)kbp);
  2558.     }
  2559. #endif
  2560. #if OPT_NAMEBST
  2561.     btree_freeup(&namebst);
  2562. #endif
  2563. }
  2564. #endif    /* NO_LEAKS */
  2565.