"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.7/tty.c" (16 Feb 2018, 142805 Bytes) of package /linux/misc/s-nail-14.9.7.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "tty.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.9.6_vs_14.9.7.

    1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
    2  *@ TTY (command line) editing interaction.
    3  *@ Because we have (had) multiple line-editor implementations, including our
    4  *@ own M(ailx) L(ine) E(ditor), change the file layout a bit and place those
    5  *@ one after the other below the other externals.
    6  *
    7  * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
    8  *
    9  * Permission to use, copy, modify, and/or distribute this software for any
   10  * purpose with or without fee is hereby granted, provided that the above
   11  * copyright notice and this permission notice appear in all copies.
   12  *
   13  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   14  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
   15  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
   16  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
   17  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
   18  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
   19  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   20  */
   21 #undef n_FILE
   22 #define n_FILE tty
   23 
   24 #ifndef HAVE_AMALGAMATION
   25 # include "nail.h"
   26 #endif
   27 
   28 #if defined HAVE_MLE || defined HAVE_TERMCAP
   29 # define a_TTY_SIGNALS
   30 #endif
   31 
   32 #ifdef a_TTY_SIGNALS
   33 static sighandler_type a_tty_oint, a_tty_oquit, a_tty_oterm,
   34    a_tty_ohup,
   35    a_tty_otstp, a_tty_ottin, a_tty_ottou;
   36 #endif
   37 
   38 #ifdef a_TTY_SIGNALS
   39 static void a_tty_sigs_up(void), a_tty_sigs_down(void);
   40 #endif
   41 
   42 /* Prototype here, implementation is specific to chosen editor */
   43 static void a_tty_signal(int sig);
   44 
   45 #ifdef a_TTY_SIGNALS
   46 static void
   47 a_tty_sigs_up(void){
   48    sigset_t nset, oset;
   49    NYD2_ENTER;
   50 
   51    sigfillset(&nset);
   52 
   53    sigprocmask(SIG_BLOCK, &nset, &oset);
   54    a_tty_oint = safe_signal(SIGINT, &a_tty_signal);
   55    a_tty_oquit = safe_signal(SIGQUIT, &a_tty_signal);
   56    a_tty_oterm = safe_signal(SIGTERM, &a_tty_signal);
   57    a_tty_ohup = safe_signal(SIGHUP, &a_tty_signal);
   58    a_tty_otstp = safe_signal(SIGTSTP, &a_tty_signal);
   59    a_tty_ottin = safe_signal(SIGTTIN, &a_tty_signal);
   60    a_tty_ottou = safe_signal(SIGTTOU, &a_tty_signal);
   61    sigprocmask(SIG_SETMASK, &oset, NULL);
   62    NYD2_LEAVE;
   63 }
   64 
   65 static void
   66 a_tty_sigs_down(void){
   67    sigset_t nset, oset;
   68    NYD2_ENTER;
   69 
   70    sigfillset(&nset);
   71 
   72    sigprocmask(SIG_BLOCK, &nset, &oset);
   73    safe_signal(SIGINT, a_tty_oint);
   74    safe_signal(SIGQUIT, a_tty_oquit);
   75    safe_signal(SIGTERM, a_tty_oterm);
   76    safe_signal(SIGHUP, a_tty_ohup);
   77    safe_signal(SIGTSTP, a_tty_otstp);
   78    safe_signal(SIGTTIN, a_tty_ottin);
   79    safe_signal(SIGTTOU, a_tty_ottou);
   80    sigprocmask(SIG_SETMASK, &oset, NULL);
   81    NYD2_LEAVE;
   82 }
   83 #endif /* a_TTY_SIGNALS */
   84 
   85 static sigjmp_buf a_tty__actjmp; /* TODO someday, we won't need it no more */
   86 static void
   87 a_tty__acthdl(int s) /* TODO someday, we won't need it no more */
   88 {
   89    NYD_X; /* Signal handler */
   90    siglongjmp(a_tty__actjmp, s);
   91 }
   92 
   93 FL bool_t
   94 getapproval(char const * volatile prompt, bool_t noninteract_default)
   95 {
   96    sighandler_type volatile oint, ohup;
   97    bool_t volatile rv;
   98    int volatile sig;
   99    NYD_ENTER;
  100 
  101    if(!(n_psonce & n_PSO_INTERACTIVE) || (n_pstate & n_PS_ROBOT)){
  102       sig = 0;
  103       rv = noninteract_default;
  104       goto jleave;
  105    }
  106    rv = FAL0;
  107 
  108    /* C99 */{
  109       char const *quest = noninteract_default
  110             ? _("[yes]/no? ") : _("[no]/yes? ");
  111 
  112       if (prompt == NULL)
  113          prompt = _("Continue");
  114       prompt = savecatsep(prompt, ' ', quest);
  115    }
  116 
  117    oint = safe_signal(SIGINT, SIG_IGN);
  118    ohup = safe_signal(SIGHUP, SIG_IGN);
  119    if ((sig = sigsetjmp(a_tty__actjmp, 1)) != 0)
  120       goto jrestore;
  121    safe_signal(SIGINT, &a_tty__acthdl);
  122    safe_signal(SIGHUP, &a_tty__acthdl);
  123 
  124    if (n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, prompt,
  125          &termios_state.ts_linebuf, &termios_state.ts_linesize, NULL,NULL) >= 0)
  126       rv = (boolify(termios_state.ts_linebuf, UIZ_MAX,
  127             noninteract_default) > 0);
  128 jrestore:
  129    safe_signal(SIGHUP, ohup);
  130    safe_signal(SIGINT, oint);
  131 jleave:
  132    NYD_LEAVE;
  133    if (sig != 0)
  134       n_raise(sig);
  135    return rv;
  136 }
  137 
  138 #ifdef HAVE_SOCKETS
  139 FL char *
  140 getuser(char const * volatile query) /* TODO v15-compat obsolete */
  141 {
  142    sighandler_type volatile oint, ohup;
  143    char * volatile user = NULL;
  144    int volatile sig;
  145    NYD_ENTER;
  146 
  147    if (query == NULL)
  148       query = _("User: ");
  149 
  150    oint = safe_signal(SIGINT, SIG_IGN);
  151    ohup = safe_signal(SIGHUP, SIG_IGN);
  152    if ((sig = sigsetjmp(a_tty__actjmp, 1)) != 0)
  153       goto jrestore;
  154    safe_signal(SIGINT, &a_tty__acthdl);
  155    safe_signal(SIGHUP, &a_tty__acthdl);
  156 
  157    if (n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, query,
  158          &termios_state.ts_linebuf, &termios_state.ts_linesize, NULL,NULL) >= 0)
  159       user = termios_state.ts_linebuf;
  160 
  161 jrestore:
  162    safe_signal(SIGHUP, ohup);
  163    safe_signal(SIGINT, oint);
  164 
  165    NYD_LEAVE;
  166    if (sig != 0)
  167       n_raise(sig);
  168    return user;
  169 }
  170 
  171 FL char *
  172 getpassword(char const *query)/* TODO v15: use _only_ n_tty_fp! */
  173 {
  174    sighandler_type volatile oint, ohup;
  175    struct termios tios;
  176    char * volatile pass;
  177    int volatile sig;
  178    NYD_ENTER;
  179 
  180    pass = NULL;
  181    if(!(n_psonce & n_PSO_TTYIN))
  182       goto j_leave;
  183 
  184    if (query == NULL)
  185       query = _("Password: ");
  186    fputs(query, n_tty_fp);
  187    fflush(n_tty_fp);
  188 
  189    /* FIXME everywhere: tcsetattr() generates SIGTTOU when we're not in
  190     * FIXME foreground pgrp, and can fail with EINTR!! also affects
  191     * FIXME termios_state_reset() */
  192    tcgetattr(STDIN_FILENO, &termios_state.ts_tios);
  193    memcpy(&tios, &termios_state.ts_tios, sizeof tios);
  194    termios_state.ts_needs_reset = TRU1;
  195    tios.c_iflag &= ~(ISTRIP);
  196    tios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
  197 
  198    oint = safe_signal(SIGINT, SIG_IGN);
  199    ohup = safe_signal(SIGHUP, SIG_IGN);
  200    if ((sig = sigsetjmp(a_tty__actjmp, 1)) != 0)
  201       goto jrestore;
  202    safe_signal(SIGINT, &a_tty__acthdl);
  203    safe_signal(SIGHUP, &a_tty__acthdl);
  204 
  205    tcsetattr(STDIN_FILENO, TCSAFLUSH, &tios);
  206    if (readline_restart(n_stdin, &termios_state.ts_linebuf,
  207          &termios_state.ts_linesize, 0) >= 0)
  208       pass = termios_state.ts_linebuf;
  209 jrestore:
  210    termios_state_reset();
  211    putc('\n', n_tty_fp);
  212 
  213    safe_signal(SIGHUP, ohup);
  214    safe_signal(SIGINT, oint);
  215    NYD_LEAVE;
  216    if (sig != 0)
  217       n_raise(sig);
  218 j_leave:
  219    return pass;
  220 }
  221 #endif /* HAVE_SOCKETS */
  222 
  223 FL ui32_t
  224 n_tty_create_prompt(struct n_string *store, char const *xprompt,
  225       enum n_go_input_flags gif){
  226    struct n_visual_info_ctx vic;
  227    struct str in, out;
  228    ui32_t pwidth;
  229    char const *cp;
  230    NYD2_ENTER;
  231 
  232    /* Prompt creation indicates that prompt printing is directly ahead, so take
  233     * this opportunity of UI-in-a-known-state and advertise the error ring */
  234 #ifdef HAVE_ERRORS
  235    if((n_psonce & (n_PSO_INTERACTIVE | n_PSO_ERRORS_NOTED)
  236          ) == n_PSO_INTERACTIVE && (n_pstate & n_PS_ERRORS_PROMPT)){
  237       n_psonce |= n_PSO_ERRORS_NOTED;
  238       fprintf(n_stdout, _("There are new messages in the error message ring "
  239          "(denoted by %s)\n"
  240          "  The `errors' command manages this message ring\n"),
  241          V_(n_error));
  242    }
  243 #endif
  244 
  245 jredo:
  246    n_string_trunc(store, 0);
  247 
  248    if(gif & n_GO_INPUT_PROMPT_NONE){
  249       pwidth = 0;
  250       goto jleave;
  251    }
  252 #ifdef HAVE_ERRORS
  253    if(n_pstate & n_PS_ERRORS_PROMPT){
  254       n_pstate &= ~n_PS_ERRORS_PROMPT;
  255       store = n_string_push_cp(store, V_(n_error));
  256       store = n_string_push_c(store, '#');
  257       store = n_string_push_c(store, ' ');
  258    }
  259 #endif
  260 
  261    cp = (gif & n_GO_INPUT_PROMPT_EVAL)
  262          ? (gif & n_GO_INPUT_NL_FOLLOW ? ok_vlook(prompt2) : ok_vlook(prompt))
  263          : xprompt;
  264    if(cp != NULL && *cp != '\0'){
  265       enum n_shexp_state shs;
  266 
  267       store = n_string_push_cp(store, cp);
  268       in.s = n_string_cp(store);
  269       in.l = store->s_len;
  270       out = in;
  271       store = n_string_drop_ownership(store);
  272 
  273       shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG |
  274             n_SHEXP_PARSE_IGNORE_EMPTY | n_SHEXP_PARSE_QUOTE_AUTO_FIXED |
  275             n_SHEXP_PARSE_QUOTE_AUTO_DSQ), store, &in, NULL);
  276       if((shs & n_SHEXP_STATE_ERR_MASK) || !(shs & n_SHEXP_STATE_STOP)){
  277          store = n_string_clear(store);
  278          store = n_string_take_ownership(store, out.s, out.l +1, out.l);
  279 jeeval:
  280          n_err(_("*prompt2?* evaluation failed, actively unsetting it\n"));
  281          if(gif & n_GO_INPUT_NL_FOLLOW)
  282             ok_vclear(prompt2);
  283          else
  284             ok_vclear(prompt);
  285          goto jredo;
  286       }
  287 
  288       if(!store->s_auto)
  289          n_free(out.s);
  290    }
  291 
  292    /* Make all printable TODO not know, we want to pass through ESC/CSI! */
  293 #if 0
  294    in.s = n_string_cp(store);
  295    in.l = store->s_len;
  296    makeprint(&in, &out);
  297    store = n_string_assign_buf(store, out.s, out.l);
  298    n_free(out.s);
  299 #endif
  300 
  301    /* We need the visual width.. */
  302    memset(&vic, 0, sizeof vic);
  303    vic.vic_indat = n_string_cp(store);
  304    vic.vic_inlen = store->s_len;
  305    for(pwidth = 0; vic.vic_inlen > 0;){
  306       /* but \[ .. \] is not taken into account */
  307       if(vic.vic_indat[0] == '\\' && vic.vic_inlen > 1 &&
  308             vic.vic_indat[1] == '['){
  309          size_t i;
  310 
  311          i = PTR2SIZE(vic.vic_indat - store->s_dat);
  312          store = n_string_cut(store, i, 2);
  313          cp = &n_string_cp(store)[i];
  314          i = store->s_len - i;
  315          for(;; ++cp, --i){
  316             if(i < 2){
  317                n_err(_("Open \\[ sequence not closed in *prompt2?*\n"));
  318                goto jeeval;
  319             }
  320             if(cp[0] == '\\' && cp[1] == ']')
  321                break;
  322          }
  323          i = PTR2SIZE(cp - store->s_dat);
  324          store = n_string_cut(store, i, 2);
  325          vic.vic_indat = &n_string_cp(store)[i];
  326          vic.vic_inlen = store->s_len - i;
  327       }else if(!n_visual_info(&vic, n_VISUAL_INFO_WIDTH_QUERY |
  328             n_VISUAL_INFO_ONE_CHAR)){
  329          n_err(_("Character set error in evaluation of *prompt2?*\n"));
  330          goto jeeval;
  331       }else{
  332          pwidth += (ui32_t)vic.vic_vi_width;
  333          vic.vic_indat = vic.vic_oudat;
  334          vic.vic_inlen = vic.vic_oulen;
  335       }
  336    }
  337 
  338    /* And there may be colour support, too */
  339 #ifdef HAVE_COLOUR
  340    if(n_COLOUR_IS_ACTIVE()){
  341       struct str const *psp, *rsp;
  342       struct n_colour_pen *ccp;
  343 
  344       if((ccp = n_colour_pen_create(n_COLOUR_ID_MLE_PROMPT, NULL)) != NULL &&
  345             (psp = n_colour_pen_to_str(ccp)) != NULL &&
  346             (rsp = n_colour_reset_to_str()) != NULL){
  347          store = n_string_unshift_buf(store, psp->s, psp->l);
  348          /*store =*/ n_string_push_buf(store, rsp->s, rsp->l);
  349       }
  350    }
  351 #endif /* HAVE_COLOUR */
  352 
  353 jleave:
  354    NYD2_LEAVE;
  355    return pwidth;
  356 }
  357 
  358 /*
  359  * MLE: the Mailx-Line-Editor, our homebrew editor
  360  * (inspired from NetBSDs sh(1) and dash(1)s hetio.c).
  361  *
  362  * Only used in interactive mode.
  363  * TODO . This code should be splitted in funs/raw input/bind modules.
  364  * TODO . We work with wide characters, but not for buffer takeovers and
  365  * TODO   cell2save()ings.  This should be changed.  For the former the buffer
  366  * TODO   thus needs to be converted to wide first, and then simply be fed in.
  367  * TODO . We repaint too much.  To overcome this use the same approach that my
  368  * TODO   terminal library uses, add a true "virtual screen line" that stores
  369  * TODO   the actually visible content, keep a notion of "first modified slot"
  370  * TODO   and "last modified slot" (including "unknown" and "any" specials),
  371  * TODO   update that virtual instead, then synchronize what has truly changed.
  372  * TODO   I.e., add an indirection layer.
  373  * TODO . No BIDI support.
  374  * TODO . `bind': we currently use only one lookup tree.
  375  * TODO   For absolute graceful behaviour (in conjunction with HAVE_TERMCAP) we
  376  * TODO   need a lower level tree, which possibly combines bytes into "symbolic
  377  * TODO   wchar_t values", into "keys" that is, as applicable, and an upper
  378  * TODO   layer which only works on "keys" in order to possibly combine them
  379  * TODO   into key sequences.  We can reuse existent tree code for that.
  380  * TODO   We need an additional hashmap which maps termcap/terminfo names to
  381  * TODO   (their byte representations and) a dynamically assigned unique
  382  * TODO   "symbolic wchar_t value".  This implies we may have incompatibilities
  383  * TODO   when __STDC_ISO_10646__ is not defined.  Also we do need takeover-
  384  * TODO   bytes storage, but it can be a string_creat_auto in the line struct.
  385  * TODO   Until then we can run into ambiguities; in rare occasions.
  386  */
  387 #ifdef HAVE_MLE
  388 /* To avoid memory leaks etc. with the current codebase that simply longjmp(3)s
  389  * we're forced to use the very same buffer--the one that is passed through to
  390  * us from the outside--to store anything we need, i.e., a "struct cell[]", and
  391  * convert that on-the-fly back to the plain char* result once we're done.
  392  * To simplify our live, use savestr() buffers for all other needed memory */
  393 
  394 # ifdef HAVE_KEY_BINDINGS
  395    /* Default *bind-timeout* key-sequence continuation timeout, in tenths of
  396     * a second.  Must fit in 8-bit!  Update the manual upon change! */
  397 #  define a_TTY_BIND_TIMEOUT 2
  398 #  define a_TTY_BIND_TIMEOUT_MAX SI8_MAX
  399 
  400 n_CTAV(a_TTY_BIND_TIMEOUT_MAX <= UI8_MAX);
  401 
  402    /* We have a chicken-and-egg problem with `bind' and our termcap layer,
  403     * because we may not initialize the latter automatically to allow users to
  404     * specify *termcap-disable* and let it mean exactly that.
  405     * On the other hand users can be expected to use `bind' in resources.
  406     * Therefore bindings which involve termcap/terminfo sequences, and which
  407     * are defined before n_PSO_STARTED signals usability of termcap/terminfo,
  408     * will be (partially) delayed until tty_init() is called.
  409     * And we preallocate space for the expansion of the resolved capability */
  410 #  define a_TTY_BIND_CAPNAME_MAX 15
  411 #  define a_TTY_BIND_CAPEXP_ROUNDUP 16
  412 
  413 n_CTAV(n_ISPOW2(a_TTY_BIND_CAPEXP_ROUNDUP));
  414 n_CTA(a_TTY_BIND_CAPEXP_ROUNDUP <= SI8_MAX / 2, "Variable must fit in 6-bit");
  415 n_CTA(a_TTY_BIND_CAPEXP_ROUNDUP >= 8, "Variable too small");
  416 # endif /* HAVE_KEY_BINDINGS */
  417 
  418 # ifdef HAVE_HISTORY
  419    /* The first line of the history file is used as a marker after >v14.9.6 */
  420 #  define a_TTY_HIST_MARKER "@s-mailx history v2"
  421 # endif
  422 
  423 /* The maximum size (of a_tty_cell's) in a line */
  424 # define a_TTY_LINE_MAX SI32_MAX
  425 
  426 /* (Some more CTAs around) */
  427 n_CTA(a_TTY_LINE_MAX <= SI32_MAX,
  428    "a_TTY_LINE_MAX larger than SI32_MAX, but the MLE uses 32-bit arithmetic");
  429 
  430 /* When shall the visual screen be scrolled, in % of usable screen width */
  431 # define a_TTY_SCROLL_MARGIN_LEFT 15
  432 # define a_TTY_SCROLL_MARGIN_RIGHT 10
  433 
  434 /* fexpand() flags for expand-on-tab */
  435 # define a_TTY_TAB_FEXP_FL \
  436    (FEXP_NOPROTO | FEXP_FULL | FEXP_SILENT | FEXP_MULTIOK)
  437 
  438 /* Columns to ripoff: outermost may not be touched, plus position indicator.
  439  * Must thus be at least 1, but should be >= 1+4 to dig the position indicator
  440  * that we place (if there is sufficient space) */
  441 # define a_TTY_WIDTH_RIPOFF 5
  442 
  443 /* The implementation of the MLE functions always exists, and is based upon
  444  * the a_TTY_BIND_FUN_* constants, so most of this enum is always necessary */
  445 enum a_tty_bind_flags{
  446 # ifdef HAVE_KEY_BINDINGS
  447    a_TTY_BIND_RESOLVE = 1u<<8,   /* Term cap. yet needs to be resolved */
  448    a_TTY_BIND_DEFUNCT = 1u<<9,   /* Unicode/term cap. used but not avail. */
  449    a_TTY__BIND_MASK = a_TTY_BIND_RESOLVE | a_TTY_BIND_DEFUNCT,
  450    /* MLE fun assigned to a one-byte-sequence: this may be used for special
  451     * key-sequence bypass processing */
  452    a_TTY_BIND_MLE1CNTRL = 1u<<10,
  453    a_TTY_BIND_NOCOMMIT = 1u<<11, /* Expansion shall be editable */
  454 # endif
  455 
  456    /* MLE internal commands */
  457    a_TTY_BIND_FUN_INTERNAL = 1u<<15,
  458    a_TTY__BIND_FUN_SHIFT = 16u,
  459    a_TTY__BIND_FUN_SHIFTMAX = 24u,
  460    a_TTY__BIND_FUN_MASK = ((1u << a_TTY__BIND_FUN_SHIFTMAX) - 1) &
  461          ~((1u << a_TTY__BIND_FUN_SHIFT) - 1),
  462 # define a_TTY_BIND_FUN_REDUCE(X) \
  463    (((ui32_t)(X) & a_TTY__BIND_FUN_MASK) >> a_TTY__BIND_FUN_SHIFT)
  464 # define a_TTY_BIND_FUN_EXPAND(X) \
  465    (((ui32_t)(X) & (a_TTY__BIND_FUN_MASK >> a_TTY__BIND_FUN_SHIFT)) << \
  466       a_TTY__BIND_FUN_SHIFT)
  467 # undef a_X
  468 # define a_X(N,I)\
  469    a_TTY_BIND_FUN_ ## N = a_TTY_BIND_FUN_EXPAND(I),
  470 
  471    a_X(BELL,  0)
  472    a_X(GO_BWD,  1) a_X(GO_FWD,  2)
  473    a_X(GO_WORD_BWD,  3) a_X(GO_WORD_FWD,  4)
  474    a_X(GO_HOME,  5) a_X(GO_END,  6)
  475    a_X(DEL_BWD,  7) a_X(DEL_FWD,   8)
  476    a_X(SNARF_WORD_BWD,  9) a_X(SNARF_WORD_FWD, 10)
  477    a_X(SNARF_END, 11) a_X(SNARF_LINE, 12)
  478    a_X(HIST_BWD, 13) a_X(HIST_FWD, 14)
  479    a_X(HIST_SRCH_BWD, 15) a_X(HIST_SRCH_FWD, 16)
  480    a_X(REPAINT, 17)
  481    a_X(QUOTE_RNDTRIP, 18)
  482    a_X(PROMPT_CHAR, 19)
  483    a_X(COMPLETE, 20)
  484    a_X(PASTE, 21)
  485 
  486    a_X(CANCEL, 22)
  487    a_X(RESET, 23)
  488    a_X(FULLRESET, 24)
  489    a_X(COMMIT, 25) /* Must be last one! */
  490 # undef a_X
  491 
  492    a_TTY__BIND_LAST = 1<<25
  493 };
  494 # ifdef HAVE_KEY_BINDINGS
  495 n_CTA((ui32_t)a_TTY_BIND_RESOLVE >= (ui32_t)n__GO_INPUT_CTX_MAX1,
  496    "Bit carrier lower boundary must be raised to avoid value sharing");
  497 # endif
  498 n_CTA(a_TTY_BIND_FUN_EXPAND(a_TTY_BIND_FUN_COMMIT) <
  499       (1 << a_TTY__BIND_FUN_SHIFTMAX),
  500    "Bit carrier range must be expanded to represent necessary bits");
  501 n_CTA(a_TTY__BIND_LAST >= (1u << a_TTY__BIND_FUN_SHIFTMAX),
  502    "Bit carrier upper boundary must be raised to avoid value sharing");
  503 n_CTA(UICMP(64, a_TTY__BIND_LAST, <=, SI32_MAX),
  504    "Flag bits excess storage datatype" /* And we need one bit free */);
  505 
  506 enum a_tty_fun_status{
  507    a_TTY_FUN_STATUS_OK,       /* Worked, next character */
  508    a_TTY_FUN_STATUS_COMMIT,   /* Line done */
  509    a_TTY_FUN_STATUS_RESTART,  /* Complete restart, reset multibyte etc. */
  510    a_TTY_FUN_STATUS_END       /* End, return EOF */
  511 };
  512 
  513 # ifdef HAVE_HISTORY
  514 enum a_tty_hist_flags{
  515    a_TTY_HIST_CTX_DEFAULT = n_GO_INPUT_CTX_DEFAULT,
  516    a_TTY_HIST_CTX_COMPOSE = n_GO_INPUT_CTX_COMPOSE,
  517    a_TTY_HIST_CTX_MASK = n__GO_INPUT_CTX_MASK,
  518    /* Cannot use enum n_go_input_flags for the rest, need to stay in 8-bit */
  519    a_TTY_HIST_GABBY = 1u<<7,
  520    a_TTY_HIST__MAX = a_TTY_HIST_GABBY
  521 };
  522 n_CTA(a_TTY_HIST_CTX_MASK < a_TTY_HIST_GABBY, "Enumeration value overlap");
  523 # endif
  524 
  525 enum a_tty_visual_flags{
  526    a_TTY_VF_NONE,
  527    a_TTY_VF_MOD_CURSOR = 1u<<0,  /* Cursor moved */
  528    a_TTY_VF_MOD_CONTENT = 1u<<1, /* Content modified */
  529    a_TTY_VF_MOD_DIRTY = 1u<<2,   /* Needs complete repaint */
  530    a_TTY_VF_MOD_SINGLE = 1u<<3,  /* TODO Drop when indirection as above comes */
  531    a_TTY_VF_REFRESH = a_TTY_VF_MOD_DIRTY | a_TTY_VF_MOD_CURSOR |
  532          a_TTY_VF_MOD_CONTENT | a_TTY_VF_MOD_SINGLE,
  533    a_TTY_VF_BELL = 1u<<8,        /* Ring the bell */
  534    a_TTY_VF_SYNC = 1u<<9,        /* Flush/Sync I/O channel */
  535 
  536    a_TTY_VF_ALL_MASK = a_TTY_VF_REFRESH | a_TTY_VF_BELL | a_TTY_VF_SYNC,
  537    a_TTY__VF_LAST = a_TTY_VF_SYNC
  538 };
  539 
  540 # ifdef HAVE_KEY_BINDINGS
  541 struct a_tty_bind_ctx{
  542    struct a_tty_bind_ctx *tbc_next;
  543    char *tbc_seq;       /* quence as given (poss. re-quoted), in .tb__buf */
  544    char *tbc_exp;       /* ansion, in .tb__buf */
  545    /* The .tbc_seq'uence with any terminal capabilities resolved; in fact an
  546     * array of structures, the first entry of which is {si32_t buf_len_iscap;}
  547     * where the signed bit indicates whether the buffer is a resolved terminal
  548     * capability instead of a (possibly multibyte) character.  In .tbc__buf */
  549    char *tbc_cnv;
  550    ui32_t tbc_seq_len;
  551    ui32_t tbc_exp_len;
  552    ui32_t tbc_cnv_len;
  553    ui32_t tbc_flags;
  554    char tbc__buf[n_VFIELD_SIZE(0)];
  555 };
  556 
  557 struct a_tty_bind_ctx_map{
  558    enum n_go_input_flags tbcm_ctx;
  559    char const tbcm_name[12];  /* Name of `bind' context */
  560 };
  561 # endif /* HAVE_KEY_BINDINGS */
  562 
  563 struct a_tty_bind_builtin_tuple{
  564    bool_t tbbt_iskey;   /* Whether this is a control key; else termcap query */
  565    char tbbt_ckey;      /* Control code */
  566    ui16_t tbbt_query;   /* enum n_termcap_query (instead) */
  567    char tbbt_exp[12];   /* String or [0]=NUL/[1]=BIND_FUN_REDUCE() */
  568 };
  569 n_CTA(n__TERMCAP_QUERY_MAX1 <= UI16_MAX,
  570    "Enumeration cannot be stored in datatype");
  571 
  572 # ifdef HAVE_KEY_BINDINGS
  573 struct a_tty_bind_parse_ctx{
  574    char const *tbpc_cmd;      /* Command which parses */
  575    char const *tbpc_in_seq;   /* In: key sequence */
  576    struct str tbpc_exp;       /* In/Out: expansion (or NULL) */
  577    struct a_tty_bind_ctx *tbpc_tbcp;  /* Out: if yet existent */
  578    struct a_tty_bind_ctx *tbpc_ltbcp; /* Out: the one before .tbpc_tbcp */
  579    char *tbpc_seq;            /* Out: normalized sequence */
  580    char *tbpc_cnv;            /* Out: sequence when read(2)ing it */
  581    ui32_t tbpc_seq_len;
  582    ui32_t tbpc_cnv_len;
  583    ui32_t tbpc_cnv_align_mask; /* For creating a_tty_bind_ctx.tbc_cnv */
  584    ui32_t tbpc_flags;         /* n_go_input_flags | a_tty_bind_flags */
  585 };
  586 
  587 /* Input character tree */
  588 struct a_tty_bind_tree{
  589    struct a_tty_bind_tree *tbt_sibling; /* s at same level */
  590    struct a_tty_bind_tree *tbt_childs; /* Sequence continues.. here */
  591    struct a_tty_bind_tree *tbt_parent;
  592    struct a_tty_bind_ctx *tbt_bind;    /* NULL for intermediates */
  593    wchar_t tbt_char;                   /* acter this level represents */
  594    bool_t tbt_isseq;                   /* Belongs to multibyte sequence */
  595    bool_t tbt_isseq_trail;             /* ..is trailing byte of it */
  596    ui8_t tbt__dummy[2];
  597 };
  598 # endif /* HAVE_KEY_BINDINGS */
  599 
  600 struct a_tty_cell{
  601    wchar_t tc_wc;
  602    ui16_t tc_count;  /* ..of bytes */
  603    ui8_t tc_width;   /* Visual width; TAB==UI8_MAX! */
  604    bool_t tc_novis;  /* Don't display visually as such (control character) */
  605    char tc_cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */
  606 };
  607 
  608 struct a_tty_global{
  609    struct a_tty_line *tg_line;   /* To be able to access it from signal hdl */
  610 # ifdef HAVE_HISTORY
  611    struct a_tty_hist *tg_hist;
  612    struct a_tty_hist *tg_hist_tail;
  613    size_t tg_hist_size;
  614    size_t tg_hist_size_max;
  615 # endif
  616 # ifdef HAVE_KEY_BINDINGS
  617    ui32_t tg_bind_cnt;           /* Overall number of bindings */
  618    bool_t tg_bind_isdirty;
  619    bool_t tg_bind_isbuild;
  620 #  define a_TTY_SHCUT_MAX (3 +1) /* Note: update manual on change! */
  621    ui8_t tg_bind__dummy[2];
  622    char tg_bind_shcut_cancel[n__GO_INPUT_CTX_MAX1][a_TTY_SHCUT_MAX];
  623    char tg_bind_shcut_prompt_char[n__GO_INPUT_CTX_MAX1][a_TTY_SHCUT_MAX];
  624    struct a_tty_bind_ctx *tg_bind[n__GO_INPUT_CTX_MAX1];
  625    struct a_tty_bind_tree *tg_bind_tree[n__GO_INPUT_CTX_MAX1][HSHSIZE];
  626 # endif
  627    struct termios tg_tios_old;
  628    struct termios tg_tios_new;
  629 };
  630 # ifdef HAVE_KEY_BINDINGS
  631 n_CTA(n__GO_INPUT_CTX_MAX1 == 3 && a_TTY_SHCUT_MAX == 4 &&
  632    n_SIZEOF_FIELD(struct a_tty_global, tg_bind__dummy) == 2,
  633    "Value results in array sizes that results in bad structure layout");
  634 n_CTA(a_TTY_SHCUT_MAX > 1,
  635    "Users need at least one shortcut, plus NUL terminator");
  636 # endif
  637 
  638 # ifdef HAVE_HISTORY
  639 struct a_tty_hist{
  640    struct a_tty_hist *th_older;
  641    struct a_tty_hist *th_younger;
  642    ui32_t th_len;
  643    ui8_t th_flags;                  /* enum a_tty_hist_flags */
  644    char th_dat[n_VFIELD_SIZE(3)];
  645 };
  646 n_CTA(UI8_MAX >= a_TTY_HIST__MAX, "Value exceeds datatype storage");
  647 # endif
  648 
  649 struct a_tty_line{
  650    /* Caller pointers */
  651    char **tl_x_buf;
  652    size_t *tl_x_bufsize;
  653    /* Input processing */
  654 # ifdef HAVE_KEY_BINDINGS
  655    wchar_t tl_bind_takeover;     /* Leftover byte to consume next */
  656    ui8_t tl_bind_timeout;        /* In-seq. inter-byte-timer, in 1/10th secs */
  657    ui8_t tl__bind_dummy[3];
  658    char (*tl_bind_shcut_cancel)[a_TTY_SHCUT_MAX]; /* Special _CANCEL control */
  659    char (*tl_bind_shcut_prompt_char)[a_TTY_SHCUT_MAX]; /* ..for _PROMPT_CHAR */
  660    struct a_tty_bind_tree *(*tl_bind_tree_hmap)[HSHSIZE]; /* Bind lookup tree */
  661    struct a_tty_bind_tree *tl_bind_tree;
  662 # endif
  663    /* Line data / content handling */
  664    ui32_t tl_count;              /* ..of a_tty_cell's (<= a_TTY_LINE_MAX) */
  665    ui32_t tl_cursor;             /* Current a_tty_cell insertion point */
  666    union{
  667       char *cbuf;                /* *.tl_x_buf */
  668       struct a_tty_cell *cells;
  669    } tl_line;
  670    struct str tl_defc;           /* Current default content */
  671    size_t tl_defc_cursor_byte;   /* Desired position of cursor after takeover */
  672    struct str tl_savec;          /* Saved default content */
  673    struct str tl_pastebuf;       /* Last snarfed data */
  674 # ifdef HAVE_HISTORY
  675    struct a_tty_hist *tl_hist;   /* History cursor */
  676 # endif
  677    ui32_t tl_count_max;          /* ..before buffer needs to grow */
  678    /* Visual data representation handling */
  679    ui32_t tl_vi_flags;           /* enum a_tty_visual_flags */
  680    ui32_t tl_lst_count;          /* .tl_count after last sync */
  681    ui32_t tl_lst_cursor;         /* .tl_cursor after last sync */
  682    /* TODO Add another indirection layer by adding a tl_phy_line of
  683     * TODO a_tty_cell objects, incorporate changes in visual layer,
  684     * TODO then check what _really_ has changed, sync those changes only */
  685    struct a_tty_cell const *tl_phy_start; /* First visible cell, left border */
  686    ui32_t tl_phy_cursor;         /* Physical cursor position */
  687    bool_t tl_quote_rndtrip;      /* For _kht() expansion */
  688    ui8_t tl__dummy2[3 + 4];
  689    ui32_t tl_goinflags;          /* enum n_go_input_flags */
  690    ui32_t tl_prompt_length;      /* Preclassified (TODO needed as a_tty_cell) */
  691    ui32_t tl_prompt_width;
  692    char const *tl_prompt;        /* Preformatted prompt (including colours) */
  693    /* .tl_pos_buf is a hack */
  694 # ifdef HAVE_COLOUR
  695    char *tl_pos_buf;             /* mle-position colour-on, [4], reset seq. */
  696    char *tl_pos;                 /* Address of the [4] */
  697 # endif
  698 };
  699 
  700 # ifdef HAVE_KEY_BINDINGS
  701 /* C99: use [INDEX]={} */
  702 n_CTAV(n_GO_INPUT_CTX_BASE == 0);
  703 n_CTAV(n_GO_INPUT_CTX_DEFAULT == 1);
  704 n_CTAV(n_GO_INPUT_CTX_COMPOSE == 2);
  705 static struct a_tty_bind_ctx_map const
  706       a_tty_bind_ctx_maps[n__GO_INPUT_CTX_MAX1] = {
  707    {n_GO_INPUT_CTX_BASE, "base"},
  708    {n_GO_INPUT_CTX_DEFAULT, "default"},
  709    {n_GO_INPUT_CTX_COMPOSE, "compose"}
  710 };
  711 
  712 /* Special functions which our MLE provides internally.
  713  * Update the manual upon change! */
  714 static char const a_tty_bind_fun_names[][24] = {
  715 #  undef a_X
  716 #  define a_X(I,N) \
  717    n_FIELD_INITI(a_TTY_BIND_FUN_REDUCE(a_TTY_BIND_FUN_ ## I)) "mle-" N "\0",
  718 
  719    a_X(BELL, "bell")
  720    a_X(GO_BWD, "go-bwd") a_X(GO_FWD, "go-fwd")
  721    a_X(GO_WORD_BWD, "go-word-bwd") a_X(GO_WORD_FWD, "go-word-fwd")
  722    a_X(GO_HOME, "go-home") a_X(GO_END, "go-end")
  723    a_X(DEL_BWD, "del-bwd") a_X(DEL_FWD, "del-fwd")
  724    a_X(SNARF_WORD_BWD, "snarf-word-bwd") a_X(SNARF_WORD_FWD, "snarf-word-fwd")
  725    a_X(SNARF_END, "snarf-end") a_X(SNARF_LINE, "snarf-line")
  726    a_X(HIST_BWD, "hist-bwd") a_X(HIST_FWD, "hist-fwd")
  727    a_X(HIST_SRCH_BWD, "hist-srch-bwd") a_X(HIST_SRCH_FWD, "hist-srch-fwd")
  728    a_X(REPAINT, "repaint")
  729    a_X(QUOTE_RNDTRIP, "quote-rndtrip")
  730    a_X(PROMPT_CHAR, "prompt-char")
  731    a_X(COMPLETE, "complete")
  732    a_X(PASTE, "paste")
  733 
  734    a_X(CANCEL, "cancel")
  735    a_X(RESET, "reset")
  736    a_X(FULLRESET, "fullreset")
  737    a_X(COMMIT, "commit")
  738 
  739 #  undef a_X
  740 };
  741 # endif /* HAVE_KEY_BINDINGS */
  742 
  743 /* The default key bindings (unless disallowed).  Update manual upon change!
  744  * A logical subset of this table is also used if !HAVE_KEY_BINDINGS (more
  745  * expensive than a switch() on control codes directly, but less redundant).
  746  * The table for the "base" context */
  747 static struct a_tty_bind_builtin_tuple const a_tty_bind_base_tuples[] = {
  748 # undef a_X
  749 # define a_X(K,S) \
  750    {TRU1, K, 0, {'\0', (char)a_TTY_BIND_FUN_REDUCE(a_TTY_BIND_FUN_ ## S),}},
  751 
  752    a_X('A', GO_HOME)
  753    a_X('B', GO_BWD)
  754    /* C: SIGINT */
  755    a_X('D', DEL_FWD)
  756    a_X('E', GO_END)
  757    a_X('F', GO_FWD)
  758    a_X('G', RESET)
  759    a_X('H', DEL_BWD)
  760    a_X('I', COMPLETE)
  761    a_X('J', COMMIT)
  762    a_X('K', SNARF_END)
  763    a_X('L', REPAINT)
  764    /* M: same as J */
  765    a_X('N', HIST_FWD)
  766    /* O: below */
  767    a_X('P', HIST_BWD)
  768    a_X('Q', QUOTE_RNDTRIP)
  769    a_X('R', HIST_SRCH_BWD)
  770    a_X('S', HIST_SRCH_FWD)
  771    a_X('T', PASTE)
  772    a_X('U', SNARF_LINE)
  773    a_X('V', PROMPT_CHAR)
  774    a_X('W', SNARF_WORD_BWD)
  775    a_X('X', GO_WORD_FWD)
  776    a_X('Y', GO_WORD_BWD)
  777    /* Z: SIGTSTP */
  778 
  779    a_X('[', CANCEL)
  780    /* \: below */
  781    /* ]: below */
  782    /* ^: below */
  783    a_X('_', SNARF_WORD_FWD)
  784 
  785    a_X('?', DEL_BWD)
  786 
  787 # undef a_X
  788 # define a_X(K,S) {TRU1, K, 0, {S}},
  789 
  790    /* The remains only if we have `bind' functionality available */
  791 # ifdef HAVE_KEY_BINDINGS
  792 #  undef a_X
  793 #  define a_X(Q,S) \
  794    {FAL0, '\0', n_TERMCAP_QUERY_ ## Q,\
  795       {'\0', (char)a_TTY_BIND_FUN_REDUCE(a_TTY_BIND_FUN_ ## S),}},
  796 
  797    a_X(key_backspace, DEL_BWD) a_X(key_dc, DEL_FWD)
  798    a_X(key_eol, SNARF_END)
  799    a_X(key_home, GO_HOME) a_X(key_end, GO_END)
  800    a_X(key_left, GO_BWD) a_X(key_right, GO_FWD)
  801    a_X(key_sleft, GO_HOME) a_X(key_sright, GO_END)
  802    a_X(key_up, HIST_BWD) a_X(key_down, HIST_FWD)
  803 # endif /* HAVE_KEY_BINDINGS */
  804 };
  805 
  806 /* The table for the "default" context */
  807 static struct a_tty_bind_builtin_tuple const a_tty_bind_default_tuples[] = {
  808 # undef a_X
  809 # define a_X(K,S) \
  810    {TRU1, K, 0, {'\0', (char)a_TTY_BIND_FUN_REDUCE(a_TTY_BIND_FUN_ ## S),}},
  811 
  812 # undef a_X
  813 # define a_X(K,S) {TRU1, K, 0, {S}},
  814 
  815    a_X('O', "dt")
  816 
  817    a_X('\\', "z+")
  818    a_X(']', "z$")
  819    a_X('^', "z0")
  820 
  821    /* The remains only if we have `bind' functionality available */
  822 # ifdef HAVE_KEY_BINDINGS
  823 #  undef a_X
  824 #  define a_X(Q,S) {FAL0, '\0', n_TERMCAP_QUERY_ ## Q, {S}},
  825 
  826    a_X(key_shome, "z0") a_X(key_send, "z$")
  827    a_X(xkey_sup, "z0") a_X(xkey_sdown, "z$")
  828    a_X(key_ppage, "z-") a_X(key_npage, "z+")
  829    a_X(xkey_cup, "dotmove-") a_X(xkey_cdown, "dotmove+")
  830 # endif /* HAVE_KEY_BINDINGS */
  831 };
  832 # undef a_X
  833 
  834 static struct a_tty_global a_tty;
  835 
  836 /* Change from canonical to raw, non-canonical mode, and way back */
  837 static void a_tty_term_mode(bool_t raw);
  838 
  839 # ifdef HAVE_HISTORY
  840 /* Load and save the history file, respectively */
  841 static bool_t a_tty_hist_load(void);
  842 static bool_t a_tty_hist_save(void);
  843 
  844 /* Initialize .tg_hist_size_max and return desired history file, or NULL */
  845 static char const *a_tty_hist__query_config(void);
  846 # endif
  847 
  848 /* Adjust an active raw mode to use / not use a timeout */
  849 # ifdef HAVE_KEY_BINDINGS
  850 static void a_tty_term_rawmode_timeout(struct a_tty_line *tlp, bool_t enable);
  851 # endif
  852 
  853 /* 0-X (2), UI8_MAX == \t / HT */
  854 static ui8_t a_tty_wcwidth(wchar_t wc);
  855 
  856 /* Memory / cell / word generics */
  857 static void a_tty_check_grow(struct a_tty_line *tlp, ui32_t no
  858                n_MEMORY_DEBUG_ARGS);
  859 static ssize_t a_tty_cell2dat(struct a_tty_line *tlp);
  860 static void a_tty_cell2save(struct a_tty_line *tlp);
  861 
  862 /* Save away data bytes of given range (max = non-inclusive) */
  863 static void a_tty_copy2paste(struct a_tty_line *tlp, struct a_tty_cell *tcpmin,
  864                struct a_tty_cell *tcpmax);
  865 
  866 /* Ask user for hexadecimal number, interpret as UTF-32 */
  867 static wchar_t a_tty_vinuni(struct a_tty_line *tlp);
  868 
  869 /* Visual screen synchronization */
  870 static bool_t a_tty_vi_refresh(struct a_tty_line *tlp);
  871 
  872 static bool_t a_tty_vi__paint(struct a_tty_line *tlp);
  873 
  874 /* Search for word boundary, starting at tl_cursor, in "dir"ection (<> 0).
  875  * Return <0 when moving is impossible (backward direction but in position 0,
  876  * forward direction but in outermost column), and relative distance to
  877  * tl_cursor otherwise */
  878 static si32_t a_tty_wboundary(struct a_tty_line *tlp, si32_t dir);
  879 
  880 /* Most function implementations */
  881 static void a_tty_khome(struct a_tty_line *tlp, bool_t dobell);
  882 static void a_tty_kend(struct a_tty_line *tlp);
  883 static void a_tty_kbs(struct a_tty_line *tlp);
  884 static void a_tty_ksnarf(struct a_tty_line *tlp, bool_t cplline, bool_t dobell);
  885 static si32_t a_tty_kdel(struct a_tty_line *tlp);
  886 static void a_tty_kleft(struct a_tty_line *tlp);
  887 static void a_tty_kright(struct a_tty_line *tlp);
  888 static void a_tty_ksnarfw(struct a_tty_line *tlp, bool_t fwd);
  889 static void a_tty_kgow(struct a_tty_line *tlp, si32_t dir);
  890 static bool_t a_tty_kother(struct a_tty_line *tlp, wchar_t wc);
  891 static ui32_t a_tty_kht(struct a_tty_line *tlp);
  892 
  893 # ifdef HAVE_HISTORY
  894 /* Return UI32_MAX on "exhaustion" */
  895 static ui32_t a_tty_khist(struct a_tty_line *tlp, bool_t fwd);
  896 static ui32_t a_tty_khist_search(struct a_tty_line *tlp, bool_t fwd);
  897 
  898 static ui32_t a_tty__khist_shared(struct a_tty_line *tlp,
  899                   struct a_tty_hist *thp);
  900 # endif
  901 
  902 /* Handle a function */
  903 static enum a_tty_fun_status a_tty_fun(struct a_tty_line *tlp,
  904                               enum a_tty_bind_flags tbf, size_t *len);
  905 
  906 /* Readline core */
  907 static ssize_t a_tty_readline(struct a_tty_line *tlp, size_t len,
  908                   bool_t *histok_or_null n_MEMORY_DEBUG_ARGS);
  909 
  910 # ifdef HAVE_KEY_BINDINGS
  911 /* Find context or -1 */
  912 static enum n_go_input_flags a_tty_bind_ctx_find(char const *name);
  913 
  914 /* Create (or replace, if allowed) a binding */
  915 static bool_t a_tty_bind_create(struct a_tty_bind_parse_ctx *tbpcp,
  916                bool_t replace);
  917 
  918 /* Shared implementation to parse `bind' and `unbind' "key-sequence" and
  919  * "expansion" command line arguments into something that we can work with */
  920 static bool_t a_tty_bind_parse(bool_t isbindcmd,
  921                struct a_tty_bind_parse_ctx *tbpcp);
  922 
  923 /* Lazy resolve a termcap(5)/terminfo(5) (or *termcap*!) capability */
  924 static void a_tty_bind_resolve(struct a_tty_bind_ctx *tbcp);
  925 
  926 /* Delete an existing binding */
  927 static void a_tty_bind_del(struct a_tty_bind_parse_ctx *tbpcp);
  928 
  929 /* Life cycle of all input node trees */
  930 static void a_tty_bind_tree_build(void);
  931 static void a_tty_bind_tree_teardown(void);
  932 
  933 static void a_tty__bind_tree_add(ui32_t hmap_idx,
  934                struct a_tty_bind_tree *store[HSHSIZE],
  935                struct a_tty_bind_ctx *tbcp);
  936 static struct a_tty_bind_tree *a_tty__bind_tree_add_wc(
  937                struct a_tty_bind_tree **treep, struct a_tty_bind_tree *parentp,
  938                wchar_t wc, bool_t isseq);
  939 static void a_tty__bind_tree_free(struct a_tty_bind_tree *tbtp);
  940 # endif /* HAVE_KEY_BINDINGS */
  941 
  942 static void
  943 a_tty_signal(int sig){
  944    /* Prototype at top */
  945    sigset_t nset, oset;
  946    NYD_X; /* Signal handler */
  947 
  948    n_COLOUR( n_colour_env_gut(); ) /* TODO NO SIMPLE SUSPENSION POSSIBLE.. */
  949    a_tty_term_mode(FAL0);
  950    n_TERMCAP_SUSPEND(TRU1);
  951    a_tty_sigs_down();
  952 
  953    sigemptyset(&nset);
  954    sigaddset(&nset, sig);
  955    sigprocmask(SIG_UNBLOCK, &nset, &oset);
  956    n_raise(sig);
  957    /* When we come here we'll continue editing, so reestablish */
  958    sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
  959 
  960    /* TODO THEREFORE NEED TO _GUT() .. _CREATE() ENTIRE ENVS!! */
  961    n_COLOUR( n_colour_env_create(n_COLOUR_CTX_MLE, n_tty_fp, FAL0); )
  962    a_tty_sigs_up();
  963    n_TERMCAP_RESUME(TRU1);
  964    a_tty_term_mode(TRU1);
  965    a_tty.tg_line->tl_vi_flags |= a_TTY_VF_MOD_DIRTY;
  966 }
  967 
  968 static void
  969 a_tty_term_mode(bool_t raw){
  970    struct termios *tiosp;
  971    NYD2_ENTER;
  972 
  973    tiosp = &a_tty.tg_tios_old;
  974    if(!raw)
  975       goto jleave;
  976 
  977    /* Always requery the attributes, in case we've been moved from background
  978     * to foreground or however else in between sessions */
  979    /* XXX Always enforce ECHO and ICANON in the OLD attributes - do so as long
  980     * XXX as we don't properly deal with TTIN and TTOU etc. */
  981    tcgetattr(STDIN_FILENO, tiosp); /* TODO v15: use _only_ n_tty_fp! */
  982    tiosp->c_lflag |= ECHO | ICANON;
  983 
  984    memcpy(&a_tty.tg_tios_new, tiosp, sizeof *tiosp);
  985    tiosp = &a_tty.tg_tios_new;
  986    tiosp->c_cc[VMIN] = 1;
  987    tiosp->c_cc[VTIME] = 0;
  988    /* Enable ^\, ^Q and ^S to be used for key bindings */
  989    tiosp->c_cc[VQUIT] = tiosp->c_cc[VSTART] = tiosp->c_cc[VSTOP] = '\0';
  990    tiosp->c_iflag &= ~(ISTRIP | IGNCR);
  991    tiosp->c_lflag &= ~(ECHO /*| ECHOE | ECHONL */| ICANON | IEXTEN);
  992 jleave:
  993    tcsetattr(STDIN_FILENO, TCSADRAIN, tiosp);
  994    NYD2_LEAVE;
  995 }
  996 
  997 # ifdef HAVE_HISTORY
  998 static bool_t
  999 a_tty_hist_load(void){
 1000    ui8_t version;
 1001    size_t lsize, cnt, llen;
 1002    char *lbuf, esc, *cp;
 1003    FILE *f;
 1004    char const *v;
 1005    bool_t rv;
 1006    NYD_ENTER;
 1007 
 1008    rv = TRU1;
 1009 
 1010    if((v = a_tty_hist__query_config()) == NULL ||
 1011          a_tty.tg_hist_size_max == 0)
 1012       goto jleave;
 1013 
 1014    hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
 1015    f = fopen(v, "r"); /* TODO HISTFILE LOAD: use linebuf pool */
 1016    if(f == NULL){
 1017       int e;
 1018 
 1019       e = errno;
 1020       n_err(_("Cannot read *history-file*=%s: %s\n"),
 1021          n_shexp_quote_cp(v, FAL0), n_err_to_doc(e));
 1022       rv = FAL0;
 1023       goto jrele;
 1024    }
 1025    (void)n_file_lock(fileno(f), FLT_READ, 0,0, UIZ_MAX);
 1026 
 1027    /* Clear old history */
 1028    /* C99 */{
 1029       struct a_tty_hist *thp;
 1030 
 1031       while((thp = a_tty.tg_hist) != NULL){
 1032          a_tty.tg_hist = thp->th_older;
 1033          n_free(thp);
 1034       }
 1035       a_tty.tg_hist_tail = NULL;
 1036       a_tty.tg_hist_size = 0;
 1037    }
 1038 
 1039    assert(!(n_pstate & n_PS_ROOT));
 1040    n_pstate |= n_PS_ROOT; /* Allow calling addhist() */
 1041    lbuf = NULL;
 1042    n_UNINIT(esc, '\0');
 1043    lsize = 0;
 1044    cnt = (size_t)fsize(f);
 1045    version = 0;
 1046 
 1047    while(fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL){
 1048       cp = lbuf;
 1049       /* Hand-edited history files may have this, probably */
 1050       while(llen > 0 && spacechar(cp[0])){
 1051          ++cp;
 1052          --llen;
 1053       }
 1054       if(llen > 0 && cp[llen - 1] == '\n')
 1055          cp[--llen] = '\0';
 1056 
 1057       /* Skip empty and comment lines */
 1058       if(llen == 0 || cp[0] == '#')
 1059          continue;
 1060 
 1061       if(n_UNLIKELY(version == 0)){
 1062          switch((version = strcmp(cp, a_TTY_HIST_MARKER) ? 1 : 2)){
 1063          case 1:
 1064             if((esc = ok_vlook(escape)[0]) == '\0')
 1065                esc = n_ESCAPE[0];
 1066             break;
 1067          default:
 1068             continue;
 1069          }
 1070       }
 1071 
 1072       /* C99 */{
 1073          enum n_go_input_flags gif;
 1074 
 1075          if(version == 2){
 1076             if(llen <= 2){
 1077                /* XXX n_err(_("Skipped invalid *history-file* entry: %s\n"),
 1078                 * XXX  n_shexp_quote_cp(cp));*/
 1079                continue;
 1080             }
 1081             switch(*cp++){
 1082             default:
 1083             case 'd':
 1084                gif = n_GO_INPUT_CTX_DEFAULT; /* == a_TTY_HIST_CTX_DEFAULT */
 1085                break;
 1086             case 'c':
 1087                gif = n_GO_INPUT_CTX_COMPOSE; /* == a_TTY_HIST_CTX_COMPOSE */
 1088                break;
 1089             }
 1090 
 1091             if(*cp++ == '*')
 1092                gif |= n_GO_INPUT_HIST_GABBY;
 1093 
 1094             while(*cp == ' ')
 1095                ++cp;
 1096          }else{
 1097             gif = n_GO_INPUT_CTX_DEFAULT;
 1098             if(cp[0] == '*'){
 1099                ++cp;
 1100                gif |= n_GO_INPUT_HIST_GABBY;
 1101             }
 1102          }
 1103 
 1104          n_tty_addhist(cp, gif);
 1105       }
 1106    }
 1107 
 1108    if(lbuf != NULL)
 1109       n_free(lbuf);
 1110    n_pstate &= ~n_PS_ROOT;
 1111 
 1112    fclose(f);
 1113 jrele:
 1114    rele_all_sigs(); /* XXX remove jumps */
 1115 jleave:
 1116    NYD_LEAVE;
 1117    return rv;
 1118 }
 1119 
 1120 static bool_t
 1121 a_tty_hist_save(void){
 1122    size_t i;
 1123    struct a_tty_hist *thp;
 1124    FILE *f;
 1125    char const *v;
 1126    bool_t rv, dogabby;
 1127    NYD_ENTER;
 1128 
 1129    rv = TRU1;
 1130 
 1131    if((v = a_tty_hist__query_config()) == NULL ||
 1132          a_tty.tg_hist_size_max == 0)
 1133       goto jleave;
 1134 
 1135    dogabby = ok_blook(history_gabby_persist);
 1136 
 1137    if((thp = a_tty.tg_hist) != NULL)
 1138       for(i = a_tty.tg_hist_size_max; thp->th_older != NULL;
 1139             thp = thp->th_older)
 1140          if((dogabby || !(thp->th_flags & a_TTY_HIST_GABBY)) && --i == 0)
 1141             break;
 1142 
 1143    hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */
 1144    if((f = fopen(v, "w")) == NULL){ /* TODO temporary + rename?! */
 1145       int e;
 1146 
 1147       e = errno;
 1148       n_err(_("Cannot write *history-file*=%s: %s\n"),
 1149          n_shexp_quote_cp(v, FAL0), n_err_to_doc(e));
 1150       rv = FAL0;
 1151       goto jrele;
 1152    }
 1153    (void)n_file_lock(fileno(f), FLT_WRITE, 0,0, UIZ_MAX);
 1154 
 1155    if(fwrite(a_TTY_HIST_MARKER "\n", sizeof *a_TTY_HIST_MARKER,
 1156          sizeof(a_TTY_HIST_MARKER "\n") -1, f) !=
 1157          sizeof(a_TTY_HIST_MARKER "\n") -1)
 1158       goto jioerr;
 1159    else for(; thp != NULL; thp = thp->th_younger){
 1160       if(dogabby || !(thp->th_flags & a_TTY_HIST_GABBY)){
 1161          char c;
 1162 
 1163          switch(thp->th_flags & a_TTY_HIST_CTX_MASK){
 1164          default:
 1165          case a_TTY_HIST_CTX_DEFAULT:
 1166             c = 'd';
 1167             break;
 1168          case a_TTY_HIST_CTX_COMPOSE:
 1169             c = 'c';
 1170             break;
 1171          }
 1172          if(putc(c, f) == EOF)
 1173             goto jioerr;
 1174 
 1175          if((thp->th_flags & a_TTY_HIST_GABBY) && putc('*', f) == EOF)
 1176             goto jioerr;
 1177 
 1178          if(putc(' ', f) == EOF ||
 1179                fwrite(thp->th_dat, sizeof *thp->th_dat, thp->th_len, f) !=
 1180                   sizeof(*thp->th_dat) * thp->th_len ||
 1181                putc('\n', f) == EOF){
 1182 jioerr:
 1183             n_err(_("I/O error while writing *history-file* %s\n"),
 1184                n_shexp_quote_cp(v, FAL0));
 1185             rv = FAL0;
 1186             break;
 1187          }
 1188       }
 1189    }
 1190 
 1191    fclose(f);
 1192 jrele:
 1193    rele_all_sigs(); /* XXX remove jumps */
 1194 jleave:
 1195    NYD_LEAVE;
 1196    return rv;
 1197 }
 1198 
 1199 static char const *
 1200 a_tty_hist__query_config(void){
 1201    char const *rv, *cp;
 1202    NYD2_ENTER;
 1203 
 1204    if((cp = ok_vlook(NAIL_HISTSIZE)) != NULL)
 1205       n_OBSOLETE(_("please use *history-size* instead of *NAIL_HISTSIZE*"));
 1206    if((rv = ok_vlook(history_size)) == NULL)
 1207       rv = cp;
 1208    if(rv == NULL)
 1209       a_tty.tg_hist_size_max = UIZ_MAX;
 1210    else
 1211       (void)n_idec_uiz_cp(&a_tty.tg_hist_size_max, rv, 10, NULL);
 1212 
 1213    if((cp = ok_vlook(NAIL_HISTFILE)) != NULL)
 1214       n_OBSOLETE(_("please use *history-file* instead of *NAIL_HISTFILE*"));
 1215    if((rv = ok_vlook(history_file)) == NULL)
 1216       rv = cp;
 1217    if(rv != NULL)
 1218       rv = fexpand(rv, FEXP_LOCAL | FEXP_NSHELL);
 1219    NYD2_LEAVE;
 1220    return rv;
 1221 }
 1222 # endif /* HAVE_HISTORY */
 1223 
 1224 # ifdef HAVE_KEY_BINDINGS
 1225 static void
 1226 a_tty_term_rawmode_timeout(struct a_tty_line *tlp, bool_t enable){
 1227    NYD2_ENTER;
 1228    if(enable){
 1229       ui8_t bt;
 1230 
 1231       a_tty.tg_tios_new.c_cc[VMIN] = 0;
 1232       if((bt = tlp->tl_bind_timeout) == 0)
 1233          bt = a_TTY_BIND_TIMEOUT;
 1234       a_tty.tg_tios_new.c_cc[VTIME] = bt;
 1235    }else{
 1236       a_tty.tg_tios_new.c_cc[VMIN] = 1;
 1237       a_tty.tg_tios_new.c_cc[VTIME] = 0;
 1238    }
 1239    tcsetattr(STDIN_FILENO, TCSANOW, &a_tty.tg_tios_new);
 1240    NYD2_LEAVE;
 1241 }
 1242 # endif /* HAVE_KEY_BINDINGS */
 1243 
 1244 static ui8_t
 1245 a_tty_wcwidth(wchar_t wc){
 1246    ui8_t rv;
 1247    NYD2_ENTER;
 1248 
 1249    /* Special case the reverse solidus at first */
 1250    if(wc == '\t')
 1251       rv = UI8_MAX;
 1252    else{
 1253       int i;
 1254 
 1255 # ifdef HAVE_WCWIDTH
 1256       rv = ((i = wcwidth(wc)) > 0) ? (ui8_t)i : 0;
 1257 # else
 1258       rv = iswprint(wc) ? 1 + (wc >= 0x1100u) : 0; /* TODO use S-CText */
 1259 # endif
 1260    }
 1261    NYD2_LEAVE;
 1262    return rv;
 1263 }
 1264 
 1265 static void
 1266 a_tty_check_grow(struct a_tty_line *tlp, ui32_t no n_MEMORY_DEBUG_ARGS){
 1267    ui32_t cmax;
 1268    NYD2_ENTER;
 1269 
 1270    if(n_UNLIKELY((cmax = tlp->tl_count + no) > tlp->tl_count_max)){
 1271       size_t i;
 1272 
 1273       i = cmax * sizeof(struct a_tty_cell) + 2 * sizeof(struct a_tty_cell);
 1274       if(n_LIKELY(i >= *tlp->tl_x_bufsize)){
 1275          hold_all_sigs(); /* XXX v15 drop */
 1276          i <<= 1;
 1277          tlp->tl_line.cbuf =
 1278          *tlp->tl_x_buf = (n_realloc)(*tlp->tl_x_buf, i
 1279                n_MEMORY_DEBUG_ARGSCALL);
 1280          rele_all_sigs(); /* XXX v15 drop */
 1281       }
 1282       tlp->tl_count_max = cmax;
 1283       *tlp->tl_x_bufsize = i;
 1284    }
 1285    NYD2_LEAVE;
 1286 }
 1287 
 1288 static ssize_t
 1289 a_tty_cell2dat(struct a_tty_line *tlp){
 1290    size_t len, i;
 1291    NYD2_ENTER;
 1292 
 1293    len = 0;
 1294 
 1295    if(n_LIKELY((i = tlp->tl_count) > 0)){
 1296       struct a_tty_cell const *tcap;
 1297 
 1298       tcap = tlp->tl_line.cells;
 1299       do{
 1300          memcpy(tlp->tl_line.cbuf + len, tcap->tc_cbuf, tcap->tc_count);
 1301          len += tcap->tc_count;
 1302       }while(++tcap, --i > 0);
 1303    }
 1304 
 1305    tlp->tl_line.cbuf[len] = '\0';
 1306    NYD2_LEAVE;
 1307    return (ssize_t)len;
 1308 }
 1309 
 1310 static void
 1311 a_tty_cell2save(struct a_tty_line *tlp){
 1312    size_t len, i;
 1313    struct a_tty_cell *tcap;
 1314    NYD2_ENTER;
 1315 
 1316    tlp->tl_savec.s = NULL;
 1317    tlp->tl_savec.l = 0;
 1318 
 1319    if(n_UNLIKELY(tlp->tl_count == 0))
 1320       goto jleave;
 1321 
 1322    for(tcap = tlp->tl_line.cells, len = 0, i = tlp->tl_count; i > 0;
 1323          ++tcap, --i)
 1324       len += tcap->tc_count;
 1325 
 1326    tlp->tl_savec.s = n_autorec_alloc((tlp->tl_savec.l = len) +1);
 1327 
 1328    for(tcap = tlp->tl_line.cells, len = 0, i = tlp->tl_count; i > 0;
 1329          ++tcap, --i){
 1330       memcpy(tlp->tl_savec.s + len, tcap->tc_cbuf, tcap->tc_count);
 1331       len += tcap->tc_count;
 1332    }
 1333    tlp->tl_savec.s[len] = '\0';
 1334 jleave:
 1335    NYD2_LEAVE;
 1336 }
 1337 
 1338 static void
 1339 a_tty_copy2paste(struct a_tty_line *tlp, struct a_tty_cell *tcpmin,
 1340       struct a_tty_cell *tcpmax){
 1341    char *cp;
 1342    struct a_tty_cell *tcp;
 1343    size_t l;
 1344    NYD2_ENTER;
 1345 
 1346    l = 0;
 1347    for(tcp = tcpmin; tcp < tcpmax; ++tcp)
 1348       l += tcp->tc_count;
 1349 
 1350    tlp->tl_pastebuf.s = cp = n_autorec_alloc((tlp->tl_pastebuf.l = l) +1);
 1351 
 1352    for(tcp = tcpmin; tcp < tcpmax; cp += l, ++tcp)
 1353       memcpy(cp, tcp->tc_cbuf, l = tcp->tc_count);
 1354    *cp = '\0';
 1355    NYD2_LEAVE;
 1356 }
 1357 
 1358 static wchar_t
 1359 a_tty_vinuni(struct a_tty_line *tlp){
 1360    char buf[16];
 1361    uiz_t i;
 1362    wchar_t wc;
 1363    NYD2_ENTER;
 1364 
 1365    wc = '\0';
 1366 
 1367    if(!n_termcap_cmdx(n_TERMCAP_CMD_cr) ||
 1368          !n_termcap_cmd(n_TERMCAP_CMD_ce, 0, -1))
 1369       goto jleave;
 1370 
 1371    /* C99 */{
 1372       struct str const *cpre, *csuf;
 1373 
 1374       cpre = csuf = NULL;
 1375 #ifdef HAVE_COLOUR
 1376       if(n_COLOUR_IS_ACTIVE()){
 1377          struct n_colour_pen *cpen;
 1378 
 1379          cpen = n_colour_pen_create(n_COLOUR_ID_MLE_PROMPT, NULL);
 1380          if((cpre = n_colour_pen_to_str(cpen)) != NULL)
 1381             csuf = n_colour_reset_to_str();
 1382       }
 1383 #endif
 1384       fprintf(n_tty_fp, _("%sPlease enter Unicode code point:%s "),
 1385          (cpre != NULL ? cpre->s : n_empty),
 1386          (csuf != NULL ? csuf->s : n_empty));
 1387    }
 1388    fflush(n_tty_fp);
 1389 
 1390    buf[sizeof(buf) -1] = '\0';
 1391    for(i = 0;;){
 1392       if(read(STDIN_FILENO, &buf[i], 1) != 1){
 1393          if(n_err_no == n_ERR_INTR) /* xxx #if !SA_RESTART ? */
 1394             continue;
 1395          goto jleave;
 1396       }
 1397       if(buf[i] == '\n')
 1398          break;
 1399       if(!hexchar(buf[i])){
 1400          char const emsg[] = "[0-9a-fA-F]";
 1401 
 1402          n_LCTA(sizeof emsg <= sizeof(buf), "Preallocated buffer too small");
 1403          memcpy(buf, emsg, sizeof emsg);
 1404          goto jerr;
 1405       }
 1406 
 1407       putc(buf[i], n_tty_fp);
 1408       fflush(n_tty_fp);
 1409       if(++i == sizeof buf)
 1410          goto jerr;
 1411    }
 1412    buf[i] = '\0';
 1413 
 1414    if((n_idec_uiz_cp(&i, buf, 16, NULL
 1415             ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 1416          ) != n_IDEC_STATE_CONSUMED || i > 0x10FFFF/* XXX magic; CText */){
 1417 jerr:
 1418       n_err(_("\nInvalid input: %s\n"), buf);
 1419       goto jleave;
 1420    }
 1421 
 1422    wc = (wchar_t)i;
 1423 jleave:
 1424    tlp->tl_vi_flags |= a_TTY_VF_MOD_DIRTY | (wc == '\0' ? a_TTY_VF_BELL : 0);
 1425    NYD2_LEAVE;
 1426    return wc;
 1427 }
 1428 
 1429 static bool_t
 1430 a_tty_vi_refresh(struct a_tty_line *tlp){
 1431    bool_t rv;
 1432    NYD2_ENTER;
 1433 
 1434    if(tlp->tl_vi_flags & a_TTY_VF_BELL){
 1435       tlp->tl_vi_flags |= a_TTY_VF_SYNC;
 1436       if(putc('\a', n_tty_fp) == EOF)
 1437          goto jerr;
 1438    }
 1439 
 1440    if(tlp->tl_vi_flags & a_TTY_VF_REFRESH){
 1441       /* kht may want to restore a cursor position after inserting some
 1442        * data somewhere */
 1443       if(tlp->tl_defc_cursor_byte > 0){
 1444          size_t i, j;
 1445          ssize_t k;
 1446 
 1447          a_tty_khome(tlp, FAL0);
 1448 
 1449          i = tlp->tl_defc_cursor_byte;
 1450          tlp->tl_defc_cursor_byte = 0;
 1451          for(j = 0; tlp->tl_cursor < tlp->tl_count; ++j){
 1452             a_tty_kright(tlp);
 1453             if((k = tlp->tl_line.cells[j].tc_count) > i)
 1454                break;
 1455             i -= k;
 1456          }
 1457       }
 1458 
 1459       if(!a_tty_vi__paint(tlp))
 1460          goto jerr;
 1461    }
 1462 
 1463    if(tlp->tl_vi_flags & a_TTY_VF_SYNC){
 1464       tlp->tl_vi_flags &= ~a_TTY_VF_SYNC;
 1465       if(fflush(n_tty_fp))
 1466          goto jerr;
 1467    }
 1468 
 1469    rv = TRU1;
 1470 jleave:
 1471    tlp->tl_vi_flags &= ~a_TTY_VF_ALL_MASK;
 1472    NYD2_LEAVE;
 1473    return rv;
 1474 
 1475 jerr:
 1476    clearerr(n_tty_fp); /* xxx I/O layer rewrite */
 1477    n_err(_("Visual refresh failed!  Is $TERM set correctly?\n"
 1478       "  Setting *line-editor-disable* to get us through!\n"));
 1479    ok_bset(line_editor_disable);
 1480    rv = FAL0;
 1481    goto jleave;
 1482 }
 1483 
 1484 static bool_t
 1485 a_tty_vi__paint(struct a_tty_line *tlp){
 1486    enum{
 1487       a_TRUE_RV = a_TTY__VF_LAST<<1,         /* Return value bit */
 1488       a_HAVE_PROMPT = a_TTY__VF_LAST<<2,     /* Have a prompt */
 1489       a_SHOW_PROMPT = a_TTY__VF_LAST<<3,     /* Shall print the prompt */
 1490       a_MOVE_CURSOR = a_TTY__VF_LAST<<4,     /* Move visual cursor for user! */
 1491       a_LEFT_MIN = a_TTY__VF_LAST<<5,        /* On left boundary */
 1492       a_RIGHT_MAX = a_TTY__VF_LAST<<6,
 1493       a_HAVE_POSITION = a_TTY__VF_LAST<<7,   /* Print the position indicator */
 1494 
 1495       /* We carry some flags over invocations (not worth a specific field) */
 1496       a_VISIBLE_PROMPT = a_TTY__VF_LAST<<8,  /* The prompt is on the screen */
 1497       a_PERSIST_MASK = a_VISIBLE_PROMPT,
 1498       a__LAST = a_PERSIST_MASK
 1499    };
 1500 
 1501    ui32_t f, w, phy_wid_base, phy_wid, phy_base, phy_cur, cnt,
 1502       DBG(lstcur COMMA) cur,
 1503       vi_left, /*vi_right,*/ phy_nxtcur;
 1504    struct a_tty_cell const *tccp, *tcp_left, *tcp_right, *tcxp;
 1505    NYD2_ENTER;
 1506    n_LCTA(UICMP(64, a__LAST, <, UI32_MAX), "Flag bits excess storage datatype");
 1507 
 1508    f = tlp->tl_vi_flags;
 1509    tlp->tl_vi_flags = (f & ~(a_TTY_VF_REFRESH | a_PERSIST_MASK)) |
 1510          a_TTY_VF_SYNC;
 1511    f |= a_TRUE_RV;
 1512    if((w = tlp->tl_prompt_width) > 0)
 1513       f |= a_HAVE_PROMPT;
 1514    f |= a_HAVE_POSITION;
 1515 
 1516    /* XXX We don't have a OnTerminalResize event (see main.c) yet, so we need
 1517     * XXX to reevaluate our circumstances over and over again */
 1518    /* Don't display prompt or position indicator on very small screens */
 1519    if((phy_wid_base = (ui32_t)n_scrnwidth) <= a_TTY_WIDTH_RIPOFF)
 1520       f &= ~(a_HAVE_PROMPT | a_HAVE_POSITION);
 1521    else{
 1522       phy_wid_base -= a_TTY_WIDTH_RIPOFF;
 1523 
 1524       /* Disable the prompt if the screen is too small; due to lack of some
 1525        * indicator simply add a second ripoff */
 1526       if((f & a_HAVE_PROMPT) && w + a_TTY_WIDTH_RIPOFF >= phy_wid_base)
 1527          f &= ~a_HAVE_PROMPT;
 1528    }
 1529 
 1530    phy_wid = phy_wid_base;
 1531    phy_base = 0;
 1532    phy_cur = tlp->tl_phy_cursor;
 1533    cnt = tlp->tl_count;
 1534    DBG( lstcur = tlp->tl_lst_cursor; )
 1535 
 1536    /* XXX Assume dirty screen if shrunk */
 1537    if(cnt < tlp->tl_lst_count)
 1538       f |= a_TTY_VF_MOD_DIRTY;
 1539 
 1540    /* TODO Without HAVE_TERMCAP, it would likely be much cheaper to simply
 1541     * TODO always "cr + paint + ce + ch", since ce is simulated via spaces.. */
 1542 
 1543    /* Quickshot: if the line is empty, possibly print prompt and out */
 1544    if(cnt == 0){
 1545       /* In that special case dirty anything if it seems better */
 1546       if((f & a_TTY_VF_MOD_CONTENT) || tlp->tl_lst_count > 0)
 1547          f |= a_TTY_VF_MOD_DIRTY;
 1548 
 1549       if((f & a_TTY_VF_MOD_DIRTY) && phy_cur != 0){
 1550          if(!n_termcap_cmdx(n_TERMCAP_CMD_cr))
 1551             goto jerr;
 1552          phy_cur = 0;
 1553       }
 1554 
 1555       if((f & (a_TTY_VF_MOD_DIRTY | a_HAVE_PROMPT)) ==
 1556             (a_TTY_VF_MOD_DIRTY | a_HAVE_PROMPT)){
 1557          if(fputs(tlp->tl_prompt, n_tty_fp) == EOF)
 1558             goto jerr;
 1559          phy_cur = tlp->tl_prompt_width + 1;
 1560       }
 1561 
 1562       /* May need to clear former line content */
 1563       if((f & a_TTY_VF_MOD_DIRTY) &&
 1564             !n_termcap_cmd(n_TERMCAP_CMD_ce, phy_cur, -1))
 1565          goto jerr;
 1566 
 1567       tlp->tl_phy_start = tlp->tl_line.cells;
 1568       goto jleave;
 1569    }
 1570 
 1571    /* Try to get an idea of the visual window */
 1572 
 1573    /* Find the left visual boundary */
 1574    phy_wid = (phy_wid >> 1) + (phy_wid >> 2);
 1575    if((cur = tlp->tl_cursor) == cnt)
 1576       --cur;
 1577 
 1578    w = (tcp_left = tccp = tlp->tl_line.cells + cur)->tc_width;
 1579    if(w == UI8_MAX) /* TODO yet HT == SPACE */
 1580       w = 1;
 1581    while(tcp_left > tlp->tl_line.cells){
 1582       ui16_t cw = tcp_left[-1].tc_width;
 1583 
 1584       if(cw == UI8_MAX) /* TODO yet HT == SPACE */
 1585          cw = 1;
 1586       if(w + cw >= phy_wid)
 1587          break;
 1588       w += cw;
 1589       --tcp_left;
 1590    }
 1591    vi_left = w;
 1592 
 1593    /* If the left hand side of our visual viewpoint consumes less than half
 1594     * of the screen width, show the prompt */
 1595    if(tcp_left == tlp->tl_line.cells)
 1596       f |= a_LEFT_MIN;
 1597 
 1598    if((f & (a_LEFT_MIN | a_HAVE_PROMPT)) == (a_LEFT_MIN | a_HAVE_PROMPT) &&
 1599          w + tlp->tl_prompt_width < phy_wid){
 1600       phy_base = tlp->tl_prompt_width;
 1601       f |= a_SHOW_PROMPT;
 1602    }
 1603 
 1604    /* Then search for right boundary.  We always leave the rightmost column
 1605     * empty because some terminals [cw]ould wrap the line if we write into
 1606     * that.  XXX terminfo(5)/termcap(5) have the semi_auto_right_margin/sam/YE
 1607     * XXX capability to indicate this, but we don't look at that */
 1608    phy_wid = phy_wid_base - phy_base;
 1609    tcp_right = tlp->tl_line.cells + cnt;
 1610 
 1611    while(&tccp[1] < tcp_right){
 1612       ui16_t cw = tccp[1].tc_width;
 1613       ui32_t i;
 1614 
 1615       if(cw == UI8_MAX) /* TODO yet HT == SPACE */
 1616          cw = 1;
 1617       i = w + cw;
 1618       if(i > phy_wid)
 1619          break;
 1620       w = i;
 1621       ++tccp;
 1622    }
 1623    /*vi_right = w - vi_left;*/
 1624 
 1625    /* If the complete line including prompt fits on the screen, show prompt */
 1626    if(--tcp_right == tccp){
 1627       f |= a_RIGHT_MAX;
 1628 
 1629       /* Since we did brute-force walk also for the left boundary we may end up
 1630        * in a situation were anything effectively fits on the screen, including
 1631        * the prompt that is, but were we don't recognize this since we
 1632        * restricted the search to fit in some visual viewpoint.  Therefore try
 1633        * again to extend the left boundary to overcome that */
 1634       if(!(f & a_LEFT_MIN)){
 1635          struct a_tty_cell const *tc1p = tlp->tl_line.cells;
 1636          ui32_t vil1 = vi_left;
 1637 
 1638          assert(!(f & a_SHOW_PROMPT));
 1639          w += tlp->tl_prompt_width;
 1640          for(tcxp = tcp_left;;){
 1641             ui32_t i = tcxp[-1].tc_width;
 1642 
 1643             if(i == UI8_MAX) /* TODO yet HT == SPACE */
 1644                i = 1;
 1645             vil1 += i;
 1646             i += w;
 1647             if(i > phy_wid)
 1648                break;
 1649             w = i;
 1650             if(--tcxp == tc1p){
 1651                tcp_left = tc1p;
 1652                /*vi_left = vil1;*/
 1653                f |= a_LEFT_MIN;
 1654                break;
 1655             }
 1656          }
 1657          /*w -= tlp->tl_prompt_width;*/
 1658       }
 1659    }
 1660    tcp_right = tccp;
 1661    tccp = tlp->tl_line.cells + cur;
 1662 
 1663    if((f & (a_LEFT_MIN | a_RIGHT_MAX | a_HAVE_PROMPT | a_SHOW_PROMPT)) ==
 1664             (a_LEFT_MIN | a_RIGHT_MAX | a_HAVE_PROMPT) &&
 1665          w + tlp->tl_prompt_width <= phy_wid){
 1666       phy_wid -= (phy_base = tlp->tl_prompt_width);
 1667       f |= a_SHOW_PROMPT;
 1668    }
 1669 
 1670    /* Try to avoid repainting the complete line - this is possible if the
 1671     * cursor "did not leave the screen" and the prompt status hasn't changed.
 1672     * I.e., after clamping virtual viewpoint, compare relation to physical */
 1673    if((f & (a_TTY_VF_MOD_SINGLE/*FIXME*/ |
 1674             a_TTY_VF_MOD_CONTENT/* xxx */ | a_TTY_VF_MOD_DIRTY)) ||
 1675          (tcxp = tlp->tl_phy_start) == NULL ||
 1676          tcxp > tccp || tcxp <= tcp_right)
 1677          f |= a_TTY_VF_MOD_DIRTY;
 1678    else{
 1679          f |= a_TTY_VF_MOD_DIRTY;
 1680 #if 0
 1681       struct a_tty_cell const *tcyp;
 1682       si32_t cur_displace;
 1683       ui32_t phy_lmargin, phy_rmargin, fx, phy_displace;
 1684 
 1685       phy_lmargin = (fx = phy_wid) / 100;
 1686       phy_rmargin = fx - (phy_lmargin * a_TTY_SCROLL_MARGIN_RIGHT);
 1687       phy_lmargin *= a_TTY_SCROLL_MARGIN_LEFT;
 1688       fx = (f & (a_SHOW_PROMPT | a_VISIBLE_PROMPT));
 1689 
 1690       if(fx == 0 || fx == (a_SHOW_PROMPT | a_VISIBLE_PROMPT)){
 1691       }
 1692 #endif
 1693    }
 1694    goto jpaint;
 1695 
 1696    /* We know what we have to paint, start synchronizing */
 1697 jpaint:
 1698    assert(phy_cur == tlp->tl_phy_cursor);
 1699    assert(phy_wid == phy_wid_base - phy_base);
 1700    assert(cnt == tlp->tl_count);
 1701    assert(cnt > 0);
 1702    assert(lstcur == tlp->tl_lst_cursor);
 1703    assert(tccp == tlp->tl_line.cells + cur);
 1704 
 1705    phy_nxtcur = phy_base; /* FIXME only if repaint cpl. */
 1706 
 1707    /* Quickshot: is it only cursor movement within the visible screen? */
 1708    if((f & a_TTY_VF_REFRESH) == a_TTY_VF_MOD_CURSOR){
 1709       f |= a_MOVE_CURSOR;
 1710       goto jcursor;
 1711    }
 1712 
 1713    /* To be able to apply some quick jump offs, clear line if possible */
 1714    if(f & a_TTY_VF_MOD_DIRTY){
 1715       /* Force complete clearance and cursor reinitialization */
 1716       if(!n_termcap_cmdx(n_TERMCAP_CMD_cr) ||
 1717             !n_termcap_cmd(n_TERMCAP_CMD_ce, 0, -1))
 1718          goto jerr;
 1719       tlp->tl_phy_start = tcp_left;
 1720       phy_cur = 0;
 1721    }
 1722 
 1723    if((f & (a_TTY_VF_MOD_DIRTY | a_SHOW_PROMPT)) && phy_cur != 0){
 1724       if(!n_termcap_cmdx(n_TERMCAP_CMD_cr))
 1725          goto jerr;
 1726       phy_cur = 0;
 1727    }
 1728 
 1729    if(f & a_SHOW_PROMPT){
 1730       assert(phy_base == tlp->tl_prompt_width);
 1731       if(fputs(tlp->tl_prompt, n_tty_fp) == EOF)
 1732          goto jerr;
 1733       phy_cur = phy_nxtcur;
 1734       f |= a_VISIBLE_PROMPT;
 1735    }else
 1736       f &= ~a_VISIBLE_PROMPT;
 1737 
 1738 /* FIXME reposition cursor for paint */
 1739    for(w = phy_nxtcur; tcp_left <= tcp_right; ++tcp_left){
 1740       ui16_t cw;
 1741 
 1742       cw = tcp_left->tc_width;
 1743 
 1744       if(n_LIKELY(!tcp_left->tc_novis)){
 1745          if(fwrite(tcp_left->tc_cbuf, sizeof *tcp_left->tc_cbuf,
 1746                tcp_left->tc_count, n_tty_fp) != tcp_left->tc_count)
 1747             goto jerr;
 1748       }else{ /* XXX Shouldn't be here <-> CText, ui_str.c */
 1749          char wbuf[8]; /* XXX magic */
 1750 
 1751          if(n_psonce & n_PSO_UNICODE){
 1752             ui32_t wc;
 1753 
 1754             wc = (ui32_t)tcp_left->tc_wc;
 1755             if((wc & ~0x1Fu) == 0)
 1756                wc |= 0x2400;
 1757             else if(wc == 0x7F)
 1758                wc = 0x2421;
 1759             else
 1760                wc = 0x2426;
 1761             n_utf32_to_utf8(wc, wbuf);
 1762          }else
 1763             wbuf[0] = '?', wbuf[1] = '\0';
 1764 
 1765          if(fputs(wbuf, n_tty_fp) == EOF)
 1766             goto jerr;
 1767          cw = 1;
 1768       }
 1769 
 1770       if(cw == UI8_MAX) /* TODO yet HT == SPACE */
 1771          cw = 1;
 1772       w += cw;
 1773       if(tcp_left == tccp)
 1774          phy_nxtcur = w;
 1775       phy_cur += cw;
 1776    }
 1777 
 1778    /* Write something position marker alike if it does not fit on screen */
 1779    if((f & a_HAVE_POSITION) &&
 1780          ((f & (a_LEFT_MIN | a_RIGHT_MAX)) != (a_LEFT_MIN | a_RIGHT_MAX) ||
 1781           ((f & a_HAVE_PROMPT) && !(f & a_SHOW_PROMPT)))){
 1782 # ifdef HAVE_COLOUR
 1783       char *posbuf = tlp->tl_pos_buf, *pos = tlp->tl_pos;
 1784 # else
 1785       char posbuf[5], *pos = posbuf;
 1786 
 1787       pos[4] = '\0';
 1788 # endif
 1789 
 1790       if(phy_cur != (w = phy_wid_base) &&
 1791             !n_termcap_cmd(n_TERMCAP_CMD_ch, phy_cur = w, 0))
 1792          goto jerr;
 1793 
 1794       *pos++ = '|';
 1795       if((f & a_LEFT_MIN) && (!(f & a_HAVE_PROMPT) || (f & a_SHOW_PROMPT)))
 1796          memcpy(pos, "^.+", 3);
 1797       else if(f & a_RIGHT_MAX)
 1798          memcpy(pos, ".+$", 3);
 1799       else{
 1800          /* Theoretical line length limit a_TTY_LINE_MAX, choose next power of
 1801           * ten (10 ** 10) to represent 100 percent, since we don't have a macro
 1802           * that generates a constant, and i don't trust the standard "u type
 1803           * suffix automatically scales" calculate the large number */
 1804          static char const itoa[] = "0123456789";
 1805 
 1806          ui64_t const fact100 = (ui64_t)0x3B9ACA00u * 10u, fact = fact100 / 100;
 1807          ui32_t i = (ui32_t)(((fact100 / cnt) * tlp->tl_cursor) / fact);
 1808          n_LCTA(a_TTY_LINE_MAX <= SI32_MAX, "a_TTY_LINE_MAX too large");
 1809 
 1810          if(i < 10)
 1811             pos[0] = ' ', pos[1] = itoa[i];
 1812          else
 1813             pos[1] = itoa[i % 10], pos[0] = itoa[i / 10];
 1814          pos[2] = '%';
 1815       }
 1816 
 1817       if(fputs(posbuf, n_tty_fp) == EOF)
 1818          goto jerr;
 1819       phy_cur += 4;
 1820    }
 1821 
 1822    /* Users are used to see the cursor right of the point of interest, so we
 1823     * need some further adjustments unless in special conditions.  Be aware
 1824     * that we may have adjusted cur at the beginning, too */
 1825    if((cur = tlp->tl_cursor) == 0)
 1826       phy_nxtcur = phy_base;
 1827    else if(cur != cnt){
 1828       ui16_t cw = tccp->tc_width;
 1829 
 1830       if(cw == UI8_MAX) /* TODO yet HT == SPACE */
 1831          cw = 1;
 1832       phy_nxtcur -= cw;
 1833    }
 1834 
 1835 jcursor:
 1836    if(((f & a_MOVE_CURSOR) || phy_nxtcur != phy_cur) &&
 1837          !n_termcap_cmd(n_TERMCAP_CMD_ch, phy_cur = phy_nxtcur, 0))
 1838       goto jerr;
 1839 
 1840 jleave:
 1841    tlp->tl_vi_flags |= (f & a_PERSIST_MASK);
 1842    tlp->tl_lst_count = tlp->tl_count;
 1843    tlp->tl_lst_cursor = tlp->tl_cursor;
 1844    tlp->tl_phy_cursor = phy_cur;
 1845 
 1846    NYD2_LEAVE;
 1847    return ((f & a_TRUE_RV) != 0);
 1848 jerr:
 1849    f &= ~a_TRUE_RV;
 1850    goto jleave;
 1851 }
 1852 
 1853 static si32_t
 1854 a_tty_wboundary(struct a_tty_line *tlp, si32_t dir){/* TODO shell token-wise */
 1855    bool_t anynon;
 1856    struct a_tty_cell *tcap;
 1857    ui32_t cur, cnt;
 1858    si32_t rv;
 1859    NYD2_ENTER;
 1860 
 1861    assert(dir == 1 || dir == -1);
 1862 
 1863    rv = -1;
 1864    cnt = tlp->tl_count;
 1865    cur = tlp->tl_cursor;
 1866 
 1867    if(dir < 0){
 1868       if(cur == 0)
 1869          goto jleave;
 1870    }else if(cur + 1 >= cnt)
 1871       goto jleave;
 1872    else
 1873       --cnt, --cur; /* xxx Unsigned wrapping may occur (twice), then */
 1874 
 1875    for(rv = 0, tcap = tlp->tl_line.cells, anynon = FAL0;;){
 1876       wchar_t wc;
 1877 
 1878       wc = tcap[cur += (ui32_t)dir].tc_wc;
 1879       if(/*TODO not everywhere iswblank(wc)*/ wc == L' ' || wc == L'\t' ||
 1880             iswpunct(wc)){
 1881          if(anynon)
 1882             break;
 1883       }else
 1884          anynon = TRU1;
 1885 
 1886       ++rv;
 1887 
 1888       if(dir < 0){
 1889          if(cur == 0)
 1890             break;
 1891       }else if(cur + 1 >= cnt){
 1892          ++rv;
 1893          break;
 1894       }
 1895    }
 1896 jleave:
 1897    NYD2_LEAVE;
 1898    return rv;
 1899 }
 1900 
 1901 static void
 1902 a_tty_khome(struct a_tty_line *tlp, bool_t dobell){
 1903    ui32_t f;
 1904    NYD2_ENTER;
 1905 
 1906    if(n_LIKELY(tlp->tl_cursor > 0)){
 1907       tlp->tl_cursor = 0;
 1908       f = a_TTY_VF_MOD_CURSOR;
 1909    }else if(dobell)
 1910       f = a_TTY_VF_BELL;
 1911    else
 1912       f = a_TTY_VF_NONE;
 1913 
 1914    tlp->tl_vi_flags |= f;
 1915    NYD2_LEAVE;
 1916 }
 1917 
 1918 static void
 1919 a_tty_kend(struct a_tty_line *tlp){
 1920    ui32_t f;
 1921    NYD2_ENTER;
 1922 
 1923    if(n_LIKELY(tlp->tl_cursor < tlp->tl_count)){
 1924       tlp->tl_cursor = tlp->tl_count;
 1925       f = a_TTY_VF_MOD_CURSOR;
 1926    }else
 1927       f = a_TTY_VF_BELL;
 1928 
 1929    tlp->tl_vi_flags |= f;
 1930    NYD2_LEAVE;
 1931 }
 1932 
 1933 static void
 1934 a_tty_kbs(struct a_tty_line *tlp){
 1935    ui32_t f, cur, cnt;
 1936    NYD2_ENTER;
 1937 
 1938    cur = tlp->tl_cursor;
 1939    cnt = tlp->tl_count;
 1940 
 1941    if(n_LIKELY(cur > 0)){
 1942       tlp->tl_cursor = --cur;
 1943       tlp->tl_count = --cnt;
 1944 
 1945       if((cnt -= cur) > 0){
 1946          struct a_tty_cell *tcap;
 1947 
 1948          tcap = tlp->tl_line.cells + cur;
 1949          memmove(tcap, &tcap[1], cnt *= sizeof(*tcap));
 1950       }
 1951       f = a_TTY_VF_MOD_CURSOR | a_TTY_VF_MOD_CONTENT;
 1952    }else
 1953       f = a_TTY_VF_BELL;
 1954 
 1955    tlp->tl_vi_flags |= f;
 1956    NYD2_LEAVE;
 1957 }
 1958 
 1959 static void
 1960 a_tty_ksnarf(struct a_tty_line *tlp, bool_t cplline, bool_t dobell){
 1961    ui32_t i, f;
 1962    NYD2_ENTER;
 1963 
 1964    f = a_TTY_VF_NONE;
 1965    i = tlp->tl_cursor;
 1966 
 1967    if(cplline && i > 0){
 1968       tlp->tl_cursor = i = 0;
 1969       f = a_TTY_VF_MOD_CURSOR;
 1970    }
 1971 
 1972    if(n_LIKELY(i < tlp->tl_count)){
 1973       struct a_tty_cell *tcap;
 1974 
 1975       tcap = &tlp->tl_line.cells[0];
 1976       a_tty_copy2paste(tlp, &tcap[i], &tcap[tlp->tl_count]);
 1977       tlp->tl_count = i;
 1978       f = a_TTY_VF_MOD_CONTENT;
 1979    }else if(dobell)
 1980       f |= a_TTY_VF_BELL;
 1981 
 1982    tlp->tl_vi_flags |= f;
 1983    NYD2_LEAVE;
 1984 }
 1985 
 1986 static si32_t
 1987 a_tty_kdel(struct a_tty_line *tlp){
 1988    ui32_t cur, cnt, f;
 1989    si32_t i;
 1990    NYD2_ENTER;
 1991 
 1992    cur = tlp->tl_cursor;
 1993    cnt = tlp->tl_count;
 1994    i = (si32_t)(cnt - cur);
 1995 
 1996    if(n_LIKELY(i > 0)){
 1997       tlp->tl_count = --cnt;
 1998 
 1999       if(n_LIKELY(--i > 0)){
 2000          struct a_tty_cell *tcap;
 2001 
 2002          tcap = &tlp->tl_line.cells[cur];
 2003          memmove(tcap, &tcap[1], (ui32_t)i * sizeof(*tcap));
 2004       }
 2005       f = a_TTY_VF_MOD_CONTENT;
 2006    }else if(cnt == 0 && !ok_blook(ignoreeof)){
 2007       putc('^', n_tty_fp);
 2008       putc('D', n_tty_fp);
 2009       i = -1;
 2010       f = a_TTY_VF_NONE;
 2011    }else{
 2012       i = 0;
 2013       f = a_TTY_VF_BELL;
 2014    }
 2015 
 2016    tlp->tl_vi_flags |= f;
 2017    NYD2_LEAVE;
 2018    return i;
 2019 }
 2020 
 2021 static void
 2022 a_tty_kleft(struct a_tty_line *tlp){
 2023    ui32_t f;
 2024    NYD2_ENTER;
 2025 
 2026    if(n_LIKELY(tlp->tl_cursor > 0)){
 2027       --tlp->tl_cursor;
 2028       f = a_TTY_VF_MOD_CURSOR;
 2029    }else
 2030       f = a_TTY_VF_BELL;
 2031 
 2032    tlp->tl_vi_flags |= f;
 2033    NYD2_LEAVE;
 2034 }
 2035 
 2036 static void
 2037 a_tty_kright(struct a_tty_line *tlp){
 2038    ui32_t i;
 2039    NYD2_ENTER;
 2040 
 2041    if(n_LIKELY((i = tlp->tl_cursor + 1) <= tlp->tl_count)){
 2042       tlp->tl_cursor = i;
 2043       i = a_TTY_VF_MOD_CURSOR;
 2044    }else
 2045       i = a_TTY_VF_BELL;
 2046 
 2047    tlp->tl_vi_flags |= i;
 2048    NYD2_LEAVE;
 2049 }
 2050 
 2051 static void
 2052 a_tty_ksnarfw(struct a_tty_line *tlp, bool_t fwd){
 2053    struct a_tty_cell *tcap;
 2054    ui32_t cnt, cur, f;
 2055    si32_t i;
 2056    NYD2_ENTER;
 2057 
 2058    if(n_UNLIKELY((i = a_tty_wboundary(tlp, (fwd ? +1 : -1))) <= 0)){
 2059       f = (i < 0) ? a_TTY_VF_BELL : a_TTY_VF_NONE;
 2060       goto jleave;
 2061    }
 2062 
 2063    cnt = tlp->tl_count - (ui32_t)i;
 2064    cur = tlp->tl_cursor;
 2065    if(!fwd)
 2066       cur -= (ui32_t)i;
 2067    tcap = &tlp->tl_line.cells[cur];
 2068 
 2069    a_tty_copy2paste(tlp, &tcap[0], &tcap[i]);
 2070 
 2071    if((tlp->tl_count = cnt) != (tlp->tl_cursor = cur)){
 2072       cnt -= cur;
 2073       memmove(&tcap[0], &tcap[i], cnt * sizeof(*tcap)); /* FIXME*/
 2074    }
 2075 
 2076    f = a_TTY_VF_MOD_CURSOR | a_TTY_VF_MOD_CONTENT;
 2077 jleave:
 2078    tlp->tl_vi_flags |= f;
 2079    NYD2_LEAVE;
 2080 }
 2081 
 2082 static void
 2083 a_tty_kgow(struct a_tty_line *tlp, si32_t dir){
 2084    ui32_t f;
 2085    si32_t i;
 2086    NYD2_ENTER;
 2087 
 2088    if(n_UNLIKELY((i = a_tty_wboundary(tlp, dir)) <= 0))
 2089       f = (i < 0) ? a_TTY_VF_BELL : a_TTY_VF_NONE;
 2090    else{
 2091       if(dir < 0)
 2092          i = -i;
 2093       tlp->tl_cursor += (ui32_t)i;
 2094       f = a_TTY_VF_MOD_CURSOR;
 2095    }
 2096 
 2097    tlp->tl_vi_flags |= f;
 2098    NYD2_LEAVE;
 2099 }
 2100 
 2101 static bool_t
 2102 a_tty_kother(struct a_tty_line *tlp, wchar_t wc){
 2103    /* Append if at EOL, insert otherwise;
 2104     * since we may move around character-wise, always use a fresh ps */
 2105    mbstate_t ps;
 2106    struct a_tty_cell tc, *tcap;
 2107    ui32_t f, cur, cnt;
 2108    bool_t rv;
 2109    NYD2_ENTER;
 2110 
 2111    rv = FAL0;
 2112    f = a_TTY_VF_NONE;
 2113 
 2114    n_LCTA(a_TTY_LINE_MAX <= SI32_MAX, "a_TTY_LINE_MAX too large");
 2115    if(tlp->tl_count + 1 >= a_TTY_LINE_MAX){
 2116       n_err(_("Stop here, we can't extend line beyond size limit\n"));
 2117       goto jleave;
 2118    }
 2119 
 2120    /* First init a cell and see whether we'll really handle this wc */
 2121    memset(&ps, 0, sizeof ps);
 2122    /* C99 */{
 2123       size_t l;
 2124 
 2125       l = wcrtomb(tc.tc_cbuf, tc.tc_wc = wc, &ps);
 2126       if(n_UNLIKELY(l > MB_LEN_MAX)){
 2127 jemb:
 2128          n_err(_("wcrtomb(3) error: too many multibyte character bytes\n"));
 2129          goto jleave;
 2130       }
 2131       tc.tc_count = (ui16_t)l;
 2132 
 2133       if(n_UNLIKELY((n_psonce & n_PSO_ENC_MBSTATE) != 0)){
 2134          l = wcrtomb(&tc.tc_cbuf[l], L'\0', &ps);
 2135          if(n_LIKELY(l == 1))
 2136             /* Only NUL terminator */;
 2137          else if(n_LIKELY(--l < MB_LEN_MAX))
 2138             tc.tc_count += (ui16_t)l;
 2139          else
 2140             goto jemb;
 2141       }
 2142    }
 2143 
 2144    /* Yes, we will!  Place it in the array */
 2145    tc.tc_novis = (iswprint(wc) == 0);
 2146    tc.tc_width = a_tty_wcwidth(wc);
 2147    /* TODO if(tc.tc_novis && tc.tc_width > 0) */
 2148 
 2149    cur = tlp->tl_cursor++;
 2150    cnt = tlp->tl_count++ - cur;
 2151    tcap = &tlp->tl_line.cells[cur];
 2152    if(cnt >= 1){
 2153       memmove(&tcap[1], tcap, cnt * sizeof(*tcap));
 2154       f = a_TTY_VF_MOD_CONTENT;
 2155    }else
 2156       f = a_TTY_VF_MOD_SINGLE;
 2157    memcpy(tcap, &tc, sizeof *tcap);
 2158 
 2159    f |= a_TTY_VF_MOD_CURSOR;
 2160    rv = TRU1;
 2161 jleave:
 2162    if(!rv)
 2163       f |= a_TTY_VF_BELL;
 2164    tlp->tl_vi_flags |= f;
 2165    NYD2_LEAVE;
 2166    return rv;
 2167 }
 2168 
 2169 static ui32_t
 2170 a_tty_kht(struct a_tty_line *tlp){
 2171    ui8_t (*mempool)[n_MEMORY_POOL_TYPE_SIZEOF], *mempool_persist;
 2172    struct stat sb;
 2173    struct str orig, bot, topp, sub, exp, preexp;
 2174    struct n_string shou, *shoup;
 2175    struct a_tty_cell *ctop, *cx;
 2176    bool_t wedid, set_savec;
 2177    ui32_t rv, f;
 2178    NYD2_ENTER;
 2179 
 2180    /* Get plain line data; if this is the first expansion/xy, update the
 2181     * very original content so that ^G gets the origin back */
 2182    orig = tlp->tl_savec;
 2183    a_tty_cell2save(tlp);
 2184    exp = tlp->tl_savec;
 2185    if(orig.s != NULL){
 2186       /*tlp->tl_savec = orig;*/
 2187       set_savec = FAL0;
 2188    }else
 2189       set_savec = TRU1;
 2190    orig = exp;
 2191 
 2192    mempool_persist = n_go_data->gdc_mempool;
 2193    n_memory_pool_push(mempool = n_lofi_alloc(sizeof *mempool));
 2194 
 2195    shoup = n_string_creat_auto(&shou);
 2196    f = a_TTY_VF_NONE;
 2197 
 2198    /* C99 */{
 2199       size_t max;
 2200       struct a_tty_cell *cword;
 2201 
 2202       /* Find the word to be expanded */
 2203       cword = tlp->tl_line.cells;
 2204       ctop = &cword[tlp->tl_cursor];
 2205       cx = &cword[tlp->tl_count];
 2206 
 2207       /* topp: separate data right of cursor */
 2208       if(cx > ctop){
 2209          for(rv = 0; ctop < cx; ++ctop)
 2210             rv += ctop->tc_count;
 2211          topp.l = rv;
 2212          topp.s = orig.s + orig.l - rv;
 2213          ctop = cword + tlp->tl_cursor;
 2214       }else
 2215          topp.s = NULL, topp.l = 0;
 2216 
 2217       /* Find the shell token that corresponds to the cursor position */
 2218       max = 0;
 2219       if(ctop > cword){
 2220          for(; cword < ctop; ++cword)
 2221             max += cword->tc_count;
 2222       }
 2223       bot = sub = orig;
 2224       bot.l = 0;
 2225       sub.l = max;
 2226 
 2227       if(max > 0){
 2228          for(;;){
 2229             enum n_shexp_state shs;
 2230 
 2231             exp = sub;
 2232             shs = n_shexp_parse_token((n_SHEXP_PARSE_DRYRUN |
 2233                   n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_IGNORE_EMPTY |
 2234                   n_SHEXP_PARSE_QUOTE_AUTO_CLOSE), NULL, &sub, NULL);
 2235             if(sub.l != 0){
 2236                size_t x;
 2237 
 2238                assert(max >= sub.l);
 2239                x = max - sub.l;
 2240                bot.l += x;
 2241                max -= x;
 2242                continue;
 2243             }
 2244             if(shs & n_SHEXP_STATE_ERR_MASK){
 2245                n_err(_("Invalid completion pattern: %.*s\n"),
 2246                   (int)exp.l, exp.s);
 2247                f |= a_TTY_VF_BELL;
 2248                goto jnope;
 2249             }
 2250 
 2251             /* All WS?  Trailing WS that has been "jumped over"? */
 2252             if(exp.l == 0 || (shs & n_SHEXP_STATE_WS_TRAIL))
 2253                break;
 2254 
 2255             n_shexp_parse_token((n_SHEXP_PARSE_TRIM_SPACE |
 2256                   n_SHEXP_PARSE_IGNORE_EMPTY | n_SHEXP_PARSE_QUOTE_AUTO_CLOSE),
 2257                   shoup, &exp, NULL);
 2258             break;
 2259          }
 2260 
 2261          sub.s = n_string_cp(shoup);
 2262          sub.l = shoup->s_len;
 2263       }
 2264    }
 2265 
 2266    /* Leave room for "implicit asterisk" expansion, as below */
 2267    if(sub.l == 0){
 2268       sub.s = n_UNCONST(n_star);
 2269       sub.l = sizeof(n_star) -1;
 2270    }
 2271 
 2272    preexp.s = n_UNCONST(n_empty);
 2273    preexp.l = sizeof(n_empty) -1;
 2274    wedid = FAL0;
 2275 jredo:
 2276    /* TODO Super-Heavy-Metal: block all sigs, avoid leaks on jump */
 2277    hold_all_sigs();
 2278    exp.s = fexpand(sub.s, a_TTY_TAB_FEXP_FL);
 2279    rele_all_sigs();
 2280 
 2281    if(exp.s == NULL || (exp.l = strlen(exp.s)) == 0){
 2282       /* No.  But maybe the users' desire was to complete only a part of the
 2283        * shell token of interest!  TODO This can be improved, we would need to
 2284        * TODO have shexp_parse to create a DOM structure of parsed snippets, so
 2285        * TODO that we can tell for each snippet which quote is active and
 2286        * TODO whether we may cross its boundary and/or apply expansion for it */
 2287       if(wedid == TRU1){
 2288          size_t i, li;
 2289 
 2290          wedid = TRUM1;
 2291          for(li = UIZ_MAX, i = sub.l; i-- > 0;){
 2292             char c;
 2293 
 2294             if((c = sub.s[i]) == '/' || c == '+' /* *folder*! */)
 2295                li = i;
 2296             /* Do stop once some "magic" characters are seen XXX magic set */
 2297             else if(c == '<' || c == '>' || c == '=' || c == ':')
 2298                break;
 2299          }
 2300          if(li != UIZ_MAX){
 2301             preexp = sub;
 2302             preexp.l = li;
 2303             sub.l -= li;
 2304             sub.s += li;
 2305             goto jredo;
 2306          }
 2307       }
 2308       goto jnope;
 2309    }
 2310 
 2311    if(wedid == TRUM1 && preexp.l > 0)
 2312       preexp.s = savestrbuf(preexp.s, preexp.l);
 2313 
 2314    /* May be multi-return! */
 2315    if(n_pstate & n_PS_EXPAND_MULTIRESULT)
 2316       goto jmulti;
 2317 
 2318    /* xxx That is not really true since the limit counts characters not bytes */
 2319    n_LCTA(a_TTY_LINE_MAX <= SI32_MAX, "a_TTY_LINE_MAX too large");
 2320    if(exp.l >= a_TTY_LINE_MAX - 1 || a_TTY_LINE_MAX - 1 - exp.l < preexp.l){
 2321       n_err(_("Tabulator expansion would extend beyond line size limit\n"));
 2322       f |= a_TTY_VF_BELL;
 2323       goto jnope;
 2324    }
 2325 
 2326    /* If the expansion equals the original string, assume the user wants what
 2327     * is usually known as tab completion, append `*' and restart */
 2328    if(!wedid && exp.l == sub.l && !memcmp(exp.s, sub.s, exp.l)){
 2329       if(sub.s[sub.l - 1] == '*')
 2330          goto jnope;
 2331 
 2332       wedid = TRU1;
 2333       shoup = n_string_push_c(shoup, '*');
 2334       sub.s = n_string_cp(shoup);
 2335       sub.l = shoup->s_len;
 2336       goto jredo;
 2337    }
 2338 
 2339    if(exp.s[exp.l - 1] != '/'){
 2340       if(!stat(exp.s, &sb) && S_ISDIR(sb.st_mode)){
 2341          shoup = n_string_assign_buf(shoup, exp.s, exp.l);
 2342          shoup = n_string_push_c(shoup, '/');
 2343          exp.s = n_string_cp(shoup);
 2344          goto jset;
 2345       }
 2346    }
 2347    exp.s[exp.l] = '\0';
 2348 
 2349 jset:
 2350    exp.l = strlen(exp.s = n_shexp_quote_cp(exp.s, tlp->tl_quote_rndtrip));
 2351    tlp->tl_defc_cursor_byte = bot.l + preexp.l + exp.l -1;
 2352 
 2353    orig.l = bot.l + preexp.l + exp.l + topp.l;
 2354    orig.s = n_autorec_alloc_from_pool(mempool_persist, orig.l + 5 +1);
 2355    if((rv = (ui32_t)bot.l) > 0)
 2356       memcpy(orig.s, bot.s, rv);
 2357    if(preexp.l > 0){
 2358       memcpy(&orig.s[rv], preexp.s, preexp.l);
 2359       rv += preexp.l;
 2360    }
 2361    memcpy(&orig.s[rv], exp.s, exp.l);
 2362    rv += exp.l;
 2363    if(topp.l > 0){
 2364       memcpy(&orig.s[rv], topp.s, topp.l);
 2365       rv += topp.l;
 2366    }
 2367    orig.s[rv] = '\0';
 2368 
 2369    tlp->tl_defc = orig;
 2370    tlp->tl_count = tlp->tl_cursor = 0;
 2371    f |= a_TTY_VF_MOD_DIRTY;
 2372 jleave:
 2373    n_memory_pool_pop(mempool);
 2374    n_lofi_free(mempool);
 2375    tlp->tl_vi_flags |= f;
 2376    NYD2_LEAVE;
 2377    return rv;
 2378 
 2379 jmulti:{
 2380       struct n_visual_info_ctx vic;
 2381       struct str input;
 2382       wc_t c2, c1;
 2383       bool_t isfirst;
 2384       char const *lococp;
 2385       size_t locolen, scrwid, lnlen, lncnt, prefixlen;
 2386       FILE *fp;
 2387 
 2388       if((fp = Ftmp(NULL, "tabex", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL){
 2389          n_perr(_("tmpfile"), 0);
 2390          fp = n_tty_fp;
 2391       }
 2392 
 2393       /* How long is the result string for real?  Search the NUL NUL
 2394        * terminator.  While here, detect the longest entry to perform an
 2395        * initial allocation of our accumulator string */
 2396       locolen = preexp.l;
 2397       do{
 2398          size_t i;
 2399 
 2400          i = strlen(&exp.s[++exp.l]);
 2401          locolen = n_MAX(locolen, i);
 2402          exp.l += i;
 2403       }while(exp.s[exp.l + 1] != '\0');
 2404 
 2405       shoup = n_string_reserve(n_string_trunc(shoup, 0),
 2406             locolen + (locolen >> 1));
 2407 
 2408       /* Iterate (once again) over all results */
 2409       scrwid = n_SCRNWIDTH_FOR_LISTS;
 2410       lnlen = lncnt = 0;
 2411       n_UNINIT(prefixlen, 0);
 2412       n_UNINIT(lococp, NULL);
 2413       n_UNINIT(c1, '\0');
 2414       for(isfirst = TRU1; exp.l > 0; isfirst = FAL0, c1 = c2){
 2415          size_t i;
 2416          char const *fullpath;
 2417 
 2418          /* Next result */
 2419          sub = exp;
 2420          sub.l = i = strlen(sub.s);
 2421          assert(exp.l >= i);
 2422          if((exp.l -= i) > 0)
 2423             --exp.l;
 2424          exp.s += ++i;
 2425 
 2426          /* Separate dirname and basename */
 2427          fullpath = sub.s;
 2428          if(isfirst){
 2429             char const *cp;
 2430 
 2431             if((cp = strrchr(fullpath, '/')) != NULL)
 2432                prefixlen = PTR2SIZE(++cp - fullpath);
 2433             else
 2434                prefixlen = 0;
 2435          }
 2436          if(prefixlen > 0 && prefixlen < sub.l){
 2437             sub.l -= prefixlen;
 2438             sub.s += prefixlen;
 2439          }
 2440 
 2441          /* We want case-insensitive sort-order */
 2442          memset(&vic, 0, sizeof vic);
 2443          vic.vic_indat = sub.s;
 2444          vic.vic_inlen = sub.l;
 2445          c2 = n_visual_info(&vic, n_VISUAL_INFO_ONE_CHAR) ? vic.vic_waccu
 2446                : (ui8_t)*sub.s;
 2447 #ifdef HAVE_C90AMEND1
 2448          c2 = towlower(c2);
 2449 #else
 2450          c2 = lowerconv(c2);
 2451 #endif
 2452 
 2453          /* Query longest common prefix along the way */
 2454          if(isfirst){
 2455             c1 = c2;
 2456             lococp = sub.s;
 2457             locolen = sub.l;
 2458          }else if(locolen > 0){
 2459             for(i = 0; i < locolen; ++i)
 2460                if(lococp[i] != sub.s[i]){
 2461                   i = field_detect_clip(i, lococp, i);
 2462                   locolen = i;
 2463                   break;
 2464                }
 2465          }
 2466 
 2467          /* Prepare display */
 2468          input = sub;
 2469          shoup = n_shexp_quote(n_string_trunc(shoup, 0), &input,
 2470                tlp->tl_quote_rndtrip);
 2471          memset(&vic, 0, sizeof vic);
 2472          vic.vic_indat = shoup->s_dat;
 2473          vic.vic_inlen = shoup->s_len;
 2474          if(!n_visual_info(&vic,
 2475                n_VISUAL_INFO_SKIP_ERRORS | n_VISUAL_INFO_WIDTH_QUERY))
 2476             vic.vic_vi_width = shoup->s_len;
 2477 
 2478          /* Put on screen.  Indent follow lines of same sort slot.
 2479           * Leave enough room for filename tagging */
 2480          if((c1 = (c1 != c2))){
 2481 #ifdef HAVE_C90AMEND1
 2482             c1 = (iswalnum(c2) != 0);
 2483 #else
 2484             c1 = (alnumchar(c2) != 0);
 2485 #endif
 2486          }
 2487          if(isfirst || c1 ||
 2488                scrwid < lnlen || scrwid - lnlen <= vic.vic_vi_width + 2){
 2489             putc('\n', fp);
 2490             if(scrwid < lnlen)
 2491                ++lncnt;
 2492             ++lncnt, lnlen = 0;
 2493             if(!isfirst && !c1)
 2494                goto jsep;
 2495          }else if(lnlen > 0){
 2496 jsep:
 2497             fputs("  ", fp);
 2498             lnlen += 2;
 2499          }
 2500          fputs(n_string_cp(shoup), fp);
 2501          lnlen += vic.vic_vi_width;
 2502 
 2503          /* Support the known filename tagging
 2504           * XXX *line-editor-completion-filetype* or so */
 2505          if(!lstat(fullpath, &sb)){
 2506             char c = '\0';
 2507 
 2508             if(S_ISDIR(sb.st_mode))
 2509                c = '/';
 2510             else if(S_ISLNK(sb.st_mode))
 2511                c = '@';
 2512 # ifdef S_ISFIFO
 2513             else if(S_ISFIFO(sb.st_mode))
 2514                c = '|';
 2515 # endif
 2516 # ifdef S_ISSOCK
 2517             else if(S_ISSOCK(sb.st_mode))
 2518                c = '=';
 2519 # endif
 2520 # ifdef S_ISCHR
 2521             else if(S_ISCHR(sb.st_mode))
 2522                c = '%';
 2523 # endif
 2524 # ifdef S_ISBLK
 2525             else if(S_ISBLK(sb.st_mode))
 2526                c = '#';
 2527 # endif
 2528 
 2529             if(c != '\0'){
 2530                putc(c, fp);
 2531                ++lnlen;
 2532             }
 2533          }
 2534       }
 2535       putc('\n', fp);
 2536       ++lncnt;
 2537 
 2538       page_or_print(fp, lncnt);
 2539       if(fp != n_tty_fp)
 2540          Fclose(fp);
 2541 
 2542       n_string_gut(shoup);
 2543 
 2544       /* A common prefix of 0 means we cannot provide the user any auto
 2545        * completed characters for the multiple possible results.
 2546        * Otherwise we can, so extend the visual line content by the common
 2547        * prefix (in a reversible way) */
 2548       f |= a_TTY_VF_BELL; /* XXX -> *line-editor-completion-bell*? or so */
 2549       if(locolen > 0){
 2550          (exp.s = n_UNCONST(lococp))[locolen] = '\0';
 2551          exp.s -= prefixlen;
 2552          exp.l = (locolen += prefixlen);
 2553          goto jset;
 2554       }
 2555    }
 2556 
 2557 jnope:
 2558    /* If we've provided a default content, but failed to expand, there is
 2559     * nothing we can "revert to": drop that default again */
 2560    if(set_savec){
 2561       tlp->tl_savec.s = NULL;
 2562       tlp->tl_savec.l = 0;
 2563    }
 2564    f &= a_TTY_VF_BELL; /* XXX -> *line-editor-completion-bell*? or so */
 2565    rv = 0;
 2566    goto jleave;
 2567 }
 2568 
 2569 # ifdef HAVE_HISTORY
 2570 static ui32_t
 2571 a_tty__khist_shared(struct a_tty_line *tlp, struct a_tty_hist *thp){
 2572    ui32_t f, rv;
 2573    NYD2_ENTER;
 2574 
 2575    if(n_LIKELY((tlp->tl_hist = thp) != NULL)){
 2576       char *cp;
 2577       size_t i;
 2578 
 2579       i = thp->th_len;
 2580       if(tlp->tl_goinflags & n_GO_INPUT_CTX_COMPOSE){
 2581          ++i;
 2582          if(!(thp->th_flags & a_TTY_HIST_CTX_COMPOSE))
 2583             ++i;
 2584       }
 2585       tlp->tl_defc.s = cp = n_autorec_alloc(i +1);
 2586       if(tlp->tl_goinflags & n_GO_INPUT_CTX_COMPOSE){
 2587          *cp++ = ok_vlook(escape)[0]; /* Do not care whether \0 == disabled! */
 2588          if(!(thp->th_flags & a_TTY_HIST_CTX_COMPOSE))
 2589             *cp++ = ':';
 2590       }
 2591       memcpy(cp, thp->th_dat, thp->th_len +1);
 2592       rv = tlp->tl_defc.l = i;
 2593 
 2594       f = (tlp->tl_count > 0) ? a_TTY_VF_MOD_DIRTY : a_TTY_VF_NONE;
 2595       tlp->tl_count = tlp->tl_cursor = 0;
 2596    }else{
 2597       f = a_TTY_VF_BELL;
 2598       rv = UI32_MAX;
 2599    }
 2600 
 2601    tlp->tl_vi_flags |= f;
 2602    NYD2_LEAVE;
 2603    return rv;
 2604 }
 2605 
 2606 static ui32_t
 2607 a_tty_khist(struct a_tty_line *tlp, bool_t fwd){
 2608    struct a_tty_hist *thp;
 2609    ui32_t rv;
 2610    NYD2_ENTER;
 2611 
 2612    /* If we're not in history mode yet, save line content */
 2613    if((thp = tlp->tl_hist) == NULL){
 2614       a_tty_cell2save(tlp);
 2615       if((thp = a_tty.tg_hist) == NULL)
 2616          goto jleave;
 2617       if(fwd)
 2618          while(thp->th_older != NULL)
 2619             thp = thp->th_older;
 2620       goto jleave;
 2621    }
 2622 
 2623    while((thp = fwd ? thp->th_younger : thp->th_older) != NULL){
 2624       /* Applicable to input context?  Compose mode swallows anything */
 2625       if((tlp->tl_goinflags & n__GO_INPUT_CTX_MASK) == n_GO_INPUT_CTX_COMPOSE)
 2626          break;
 2627       if((thp->th_flags & a_TTY_HIST_CTX_MASK) != a_TTY_HIST_CTX_COMPOSE)
 2628          break;
 2629    }
 2630 jleave:
 2631    rv = a_tty__khist_shared(tlp, thp);
 2632    NYD2_LEAVE;
 2633    return rv;
 2634 }
 2635 
 2636 static ui32_t
 2637 a_tty_khist_search(struct a_tty_line *tlp, bool_t fwd){
 2638    struct str orig_savec;
 2639    ui32_t xoff, rv;
 2640    struct a_tty_hist *thp;
 2641    NYD2_ENTER;
 2642 
 2643    thp = NULL;
 2644 
 2645    /* We cannot complete an empty line */
 2646    if(n_UNLIKELY(tlp->tl_count == 0)){
 2647       /* XXX The upcoming hard reset would restore a set savec buffer,
 2648        * XXX so forcefully reset that.  A cleaner solution would be to
 2649        * XXX reset it whenever a restore is no longer desired */
 2650       tlp->tl_savec.s = NULL;
 2651       tlp->tl_savec.l = 0;
 2652       goto jleave;
 2653    }
 2654 
 2655    /* xxx It is a bit of a hack, but let's just hard-code the knowledge that
 2656     * xxx in compose mode the first character is *escape* and must be skipped
 2657     * xxx when searching the history.  Alternatively we would need to have
 2658     * xxx context-specific history search hooks which would do the search,
 2659     * xxx which is overkill for our sole special case compose mode */
 2660    if((tlp->tl_goinflags & n__GO_INPUT_CTX_MASK) == n_GO_INPUT_CTX_COMPOSE)
 2661       xoff = 1;
 2662    else
 2663       xoff = 0;
 2664 
 2665    if((thp = tlp->tl_hist) == NULL){
 2666       a_tty_cell2save(tlp);
 2667       if((thp = a_tty.tg_hist) == NULL) /* TODO Should end "doing nothing"! */
 2668          goto jleave;
 2669       if(fwd)
 2670          while(thp->th_older != NULL)
 2671             thp = thp->th_older;
 2672       orig_savec.s = NULL;
 2673       orig_savec.l = 0; /* silence CC */
 2674    }else{
 2675       while((thp = fwd ? thp->th_younger : thp->th_older) != NULL){
 2676          /* Applicable to input context?  Compose mode swallows anything */
 2677          if(xoff != 0)
 2678             break;
 2679          if((thp->th_flags & a_TTY_HIST_CTX_MASK) != a_TTY_HIST_CTX_COMPOSE)
 2680             break;
 2681       }
 2682       if(thp == NULL)
 2683          goto jleave;
 2684 
 2685       orig_savec = tlp->tl_savec;
 2686    }
 2687 
 2688    if(xoff >= tlp->tl_savec.l){
 2689       thp = NULL;
 2690       goto jleave;
 2691    }
 2692 
 2693    if(orig_savec.s == NULL)
 2694       a_tty_cell2save(tlp);
 2695 
 2696    for(; thp != NULL; thp = fwd ? thp->th_younger : thp->th_older){
 2697       /* Applicable to input context?  Compose mode swallows anything */
 2698       if(xoff != 1 && (thp->th_flags & a_TTY_HIST_CTX_MASK) ==
 2699             a_TTY_HIST_CTX_COMPOSE)
 2700          continue;
 2701       if(is_prefix(&tlp->tl_savec.s[xoff], thp->th_dat))
 2702          break;
 2703    }
 2704 
 2705    if(orig_savec.s != NULL)
 2706       tlp->tl_savec = orig_savec;
 2707 jleave:
 2708    rv = a_tty__khist_shared(tlp, thp);
 2709    NYD2_LEAVE;
 2710    return rv;
 2711 }
 2712 # endif /* HAVE_HISTORY */
 2713 
 2714 static enum a_tty_fun_status
 2715 a_tty_fun(struct a_tty_line *tlp, enum a_tty_bind_flags tbf, size_t *len){
 2716    enum a_tty_fun_status rv;
 2717    NYD2_ENTER;
 2718 
 2719    rv = a_TTY_FUN_STATUS_OK;
 2720 # undef a_X
 2721 # define a_X(N) a_TTY_BIND_FUN_REDUCE(a_TTY_BIND_FUN_ ## N)
 2722    switch(a_TTY_BIND_FUN_REDUCE(tbf)){
 2723    case a_X(BELL):
 2724       tlp->tl_vi_flags |= a_TTY_VF_BELL;
 2725       break;
 2726    case a_X(GO_BWD):
 2727       a_tty_kleft(tlp);
 2728       break;
 2729    case a_X(GO_FWD):
 2730       a_tty_kright(tlp);
 2731       break;
 2732    case a_X(GO_WORD_BWD):
 2733       a_tty_kgow(tlp, -1);
 2734       break;
 2735    case a_X(GO_WORD_FWD):
 2736       a_tty_kgow(tlp, +1);
 2737       break;
 2738    case a_X(GO_HOME):
 2739       a_tty_khome(tlp, TRU1);
 2740       break;
 2741    case a_X(GO_END):
 2742       a_tty_kend(tlp);
 2743       break;
 2744    case a_X(DEL_BWD):
 2745       a_tty_kbs(tlp);
 2746       break;
 2747    case a_X(DEL_FWD):
 2748       if(a_tty_kdel(tlp) < 0)
 2749          rv = a_TTY_FUN_STATUS_END;
 2750       break;
 2751    case a_X(SNARF_WORD_BWD):
 2752       a_tty_ksnarfw(tlp, FAL0);
 2753       break;
 2754    case a_X(SNARF_WORD_FWD):
 2755       a_tty_ksnarfw(tlp, TRU1);
 2756       break;
 2757    case a_X(SNARF_END):
 2758       a_tty_ksnarf(tlp, FAL0, TRU1);
 2759       break;
 2760    case a_X(SNARF_LINE):
 2761       a_tty_ksnarf(tlp, TRU1, (tlp->tl_count == 0));
 2762       break;
 2763 
 2764    case a_X(HIST_FWD):{
 2765 # ifdef HAVE_HISTORY
 2766          bool_t isfwd = TRU1;
 2767 
 2768          if(0){
 2769 # endif
 2770       /* FALLTHRU */
 2771    case a_X(HIST_BWD):
 2772 # ifdef HAVE_HISTORY
 2773             isfwd = FAL0;
 2774          }
 2775          if((*len = a_tty_khist(tlp, isfwd)) != UI32_MAX){
 2776             rv = a_TTY_FUN_STATUS_RESTART;
 2777             break;
 2778          }
 2779          goto jreset;
 2780 # endif
 2781       }
 2782       tlp->tl_vi_flags |= a_TTY_VF_BELL;
 2783       break;
 2784 
 2785    case a_X(HIST_SRCH_FWD):{
 2786 # ifdef HAVE_HISTORY
 2787       bool_t isfwd = TRU1;
 2788 
 2789       if(0){
 2790 # endif
 2791       /* FALLTHRU */
 2792    case a_X(HIST_SRCH_BWD):
 2793 # ifdef HAVE_HISTORY
 2794          isfwd = FAL0;
 2795       }
 2796       if((*len = a_tty_khist_search(tlp, isfwd)) != UI32_MAX){
 2797          rv = a_TTY_FUN_STATUS_RESTART;
 2798          break;
 2799       }
 2800       goto jreset;
 2801 # else
 2802       tlp->tl_vi_flags |= a_TTY_VF_BELL;
 2803 # endif
 2804       }break;
 2805 
 2806    case a_X(REPAINT):
 2807       tlp->tl_vi_flags |= a_TTY_VF_MOD_DIRTY;
 2808       break;
 2809    case a_X(QUOTE_RNDTRIP):
 2810       tlp->tl_quote_rndtrip = !tlp->tl_quote_rndtrip;
 2811       break;
 2812    case a_X(PROMPT_CHAR):{
 2813       wchar_t wc;
 2814 
 2815       if((wc = a_tty_vinuni(tlp)) > 0)
 2816          a_tty_kother(tlp, wc);
 2817       }break;
 2818    case a_X(COMPLETE):
 2819       if((*len = a_tty_kht(tlp)) > 0)
 2820          rv = a_TTY_FUN_STATUS_RESTART;
 2821       break;
 2822 
 2823    case a_X(PASTE):
 2824       if(tlp->tl_pastebuf.l > 0)
 2825          *len = (tlp->tl_defc = tlp->tl_pastebuf).l;
 2826       else
 2827          tlp->tl_vi_flags |= a_TTY_VF_BELL;
 2828       break;
 2829 
 2830 
 2831    case a_X(CANCEL):
 2832       /* Normally this just causes a restart and thus resets the state
 2833        * machine  */
 2834       if(tlp->tl_savec.l == 0 && tlp->tl_defc.l == 0){
 2835       }
 2836 # ifdef HAVE_KEY_BINDINGS
 2837       tlp->tl_bind_takeover = '\0';
 2838 # endif
 2839       tlp->tl_vi_flags |= a_TTY_VF_BELL;
 2840       rv = a_TTY_FUN_STATUS_RESTART;
 2841       break;
 2842 
 2843    case a_X(RESET):
 2844       if(tlp->tl_count == 0 && tlp->tl_savec.l == 0 && tlp->tl_defc.l == 0){
 2845 # ifdef HAVE_KEY_BINDINGS
 2846          tlp->tl_bind_takeover = '\0';
 2847 # endif
 2848          tlp->tl_vi_flags |= a_TTY_VF_MOD_DIRTY | a_TTY_VF_BELL;
 2849          break;
 2850       }else if(0){
 2851    case a_X(FULLRESET):
 2852          tlp->tl_savec.s = tlp->tl_defc.s = NULL;
 2853          tlp->tl_savec.l = tlp->tl_defc.l = 0;
 2854          tlp->tl_defc_cursor_byte = 0;
 2855          tlp->tl_vi_flags |= a_TTY_VF_BELL;
 2856       }
 2857 jreset:
 2858 # ifdef HAVE_KEY_BINDINGS
 2859       tlp->tl_bind_takeover = '\0';
 2860 # endif
 2861       tlp->tl_vi_flags |= a_TTY_VF_MOD_DIRTY;
 2862       tlp->tl_cursor = tlp->tl_count = 0;
 2863 # ifdef HAVE_HISTORY
 2864       tlp->tl_hist = NULL;
 2865 # endif
 2866       if((*len = tlp->tl_savec.l) != 0){
 2867          tlp->tl_defc = tlp->tl_savec;
 2868          tlp->tl_savec.s = NULL;
 2869          tlp->tl_savec.l = 0;
 2870       }else
 2871          *len = tlp->tl_defc.l;
 2872       rv = a_TTY_FUN_STATUS_RESTART;
 2873       break;
 2874 
 2875    default:
 2876    case a_X(COMMIT):
 2877       rv = a_TTY_FUN_STATUS_COMMIT;
 2878       break;
 2879    }
 2880 # undef a_X
 2881 
 2882    NYD2_LEAVE;
 2883    return rv;
 2884 }
 2885 
 2886 static ssize_t
 2887 a_tty_readline(struct a_tty_line *tlp, size_t len, bool_t *histok_or_null
 2888       n_MEMORY_DEBUG_ARGS){
 2889    /* We want to save code, yet we may have to incorporate a lines'
 2890     * default content and / or default input to switch back to after some
 2891     * history movement; let "len > 0" mean "have to display some data
 2892     * buffer" -> a_BUFMODE, and only otherwise read(2) it */
 2893    mbstate_t ps[2];
 2894    char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp;
 2895    ssize_t rv;
 2896    struct a_tty_bind_tree *tbtp;
 2897    wchar_t wc;
 2898    enum a_tty_bind_flags tbf;
 2899    enum {a_NONE, a_WAS_HERE = 1<<0, a_BUFMODE = 1<<1, a_MAYBEFUN = 1<<2,
 2900       a_TIMEOUT = 1<<3, a_TIMEOUT_EXPIRED = 1<<4,
 2901          a_TIMEOUT_MASK = a_TIMEOUT | a_TIMEOUT_EXPIRED,
 2902       a_READ_LOOP_MASK = ~(a_WAS_HERE | a_MAYBEFUN | a_TIMEOUT_MASK)
 2903    } flags;
 2904    NYD_ENTER;
 2905 
 2906    n_UNINIT(rv, 0);
 2907 # ifdef HAVE_KEY_BINDINGS
 2908    assert(tlp->tl_bind_takeover == '\0');
 2909 # endif
 2910 jrestart:
 2911    memset(ps, 0, sizeof ps);
 2912    flags = a_NONE;
 2913    tlp->tl_vi_flags |= a_TTY_VF_REFRESH | a_TTY_VF_SYNC;
 2914 
 2915 jinput_loop:
 2916    for(;;){
 2917       if(len != 0)
 2918          flags |= a_BUFMODE;
 2919 
 2920       /* Ensure we have valid pointers, and room for grow */
 2921       a_tty_check_grow(tlp, ((flags & a_BUFMODE) ? (ui32_t)len : 1)
 2922          n_MEMORY_DEBUG_ARGSCALL);
 2923 
 2924       /* Handle visual state flags, except in buffer mode */
 2925       if(!(flags & a_BUFMODE) && (tlp->tl_vi_flags & a_TTY_VF_ALL_MASK))
 2926          if(!a_tty_vi_refresh(tlp)){
 2927             rv = -1;
 2928             goto jleave;
 2929          }
 2930 
 2931       /* Ready for messing around.
 2932        * Normal read(2)?  Else buffer mode: speed this one up */
 2933       if(!(flags & a_BUFMODE)){
 2934          cbufp =
 2935          cbuf = cbuf_base;
 2936       }else{
 2937          assert(tlp->tl_defc.l > 0 && tlp->tl_defc.s != NULL);
 2938          assert(tlp->tl_defc.l >= len);
 2939          cbufp =
 2940          cbuf = tlp->tl_defc.s + (tlp->tl_defc.l - len);
 2941          cbufp += len;
 2942       }
 2943 
 2944       /* Read in the next complete multibyte character */
 2945       /* C99 */{
 2946 # ifdef HAVE_KEY_BINDINGS
 2947          struct a_tty_bind_tree *xtbtp;
 2948          struct inseq{
 2949             struct inseq *last;
 2950             struct inseq *next;
 2951             struct a_tty_bind_tree *tbtp;
 2952          } *isp_head, *isp;
 2953 
 2954          isp_head = isp = NULL;
 2955 # endif
 2956 
 2957          for(flags &= a_READ_LOOP_MASK;;){
 2958 # ifdef HAVE_KEY_BINDINGS
 2959             if(!(flags & a_BUFMODE) && tlp->tl_bind_takeover != '\0'){
 2960                wc = tlp->tl_bind_takeover;
 2961                tlp->tl_bind_takeover = '\0';
 2962             }else
 2963 # endif
 2964             {
 2965                if(!(flags & a_BUFMODE)){
 2966                   /* Let me at least once dream of iomon(itor), timer with
 2967                    * one-shot, enwrapped with key_event and key_sequence_event,
 2968                    * all driven by an event_loop */
 2969                   /* TODO v15 Until we have SysV signal handling all through we
 2970                    * TODO need to temporarily adjust our BSD signal handler with
 2971                    * TODO a SysV one, here */
 2972                   n_sighdl_t otstp, ottin, ottou;
 2973 
 2974                   otstp = n_signal(SIGTSTP, &a_tty_signal);
 2975                   ottin = n_signal(SIGTTIN, &a_tty_signal);
 2976                   ottou = n_signal(SIGTTOU, &a_tty_signal);
 2977 # ifdef HAVE_KEY_BINDINGS
 2978                   flags &= ~a_TIMEOUT_MASK;
 2979                   if(isp != NULL && (tbtp = isp->tbtp)->tbt_isseq &&
 2980                         !tbtp->tbt_isseq_trail){
 2981                      a_tty_term_rawmode_timeout(tlp, TRU1);
 2982                      flags |= a_TIMEOUT;
 2983                   }
 2984 # endif
 2985 
 2986                   while((rv = read(STDIN_FILENO, cbufp, 1)) < 1){
 2987                      if(rv == -1){
 2988                         if(n_err_no == n_ERR_INTR){
 2989                            if((tlp->tl_vi_flags & a_TTY_VF_MOD_DIRTY) &&
 2990                                  !a_tty_vi_refresh(tlp))
 2991                               break;
 2992                            continue;
 2993                         }
 2994                         break;
 2995                      }
 2996 
 2997 # ifdef HAVE_KEY_BINDINGS
 2998                      /* Timeout expiration */
 2999                      if(rv == 0){
 3000                         assert(flags & a_TIMEOUT);
 3001                         assert(isp != NULL);
 3002                         a_tty_term_rawmode_timeout(tlp, FAL0);
 3003 
 3004                         /* Something "atomic" broke.  Maybe the current one can
 3005                          * also be terminated already, by itself? xxx really? */
 3006                         if((tbtp = isp->tbtp)->tbt_bind != NULL){
 3007                            tlp->tl_bind_takeover = wc;
 3008                            goto jhave_bind;
 3009                         }
 3010 
 3011                         /* Or, maybe there is a second path without a timeout;
 3012                          * this should be covered by .tbt_isseq_trail, but then
 3013                          * again a single-layer implementation cannot "know" */
 3014                         for(xtbtp = tbtp; (xtbtp = xtbtp->tbt_sibling) != NULL;)
 3015                            if(xtbtp->tbt_char == tbtp->tbt_char){
 3016                               assert(!xtbtp->tbt_isseq);
 3017                               break;
 3018                            }
 3019                         /* Lay down on read(2)? */
 3020                         if(xtbtp != NULL)
 3021                            continue;
 3022                         goto jtake_over;
 3023                      }
 3024 # endif /* HAVE_KEY_BINDINGS */
 3025                   }
 3026 
 3027 # ifdef HAVE_KEY_BINDINGS
 3028                   if(flags & a_TIMEOUT)
 3029                      a_tty_term_rawmode_timeout(tlp, FAL0);
 3030 # endif
 3031                   safe_signal(SIGTSTP, otstp);
 3032                   safe_signal(SIGTTIN, ottin);
 3033                   safe_signal(SIGTTOU, ottou);
 3034                   if(rv < 0)
 3035                      goto jleave;
 3036 
 3037                   ++cbufp;
 3038                }
 3039 
 3040                rv = (ssize_t)mbrtowc(&wc, cbuf, PTR2SIZE(cbufp - cbuf), &ps[0]);
 3041                if(rv <= 0){
 3042                   /* Any error during buffer mode can only result in a hard
 3043                    * reset;  Otherwise, if it's a hard error, or if too many
 3044                    * redundant shift sequences overflow our buffer: perform
 3045                    * hard reset */
 3046                   if((flags & a_BUFMODE) || rv == -1 ||
 3047                         sizeof cbuf_base == PTR2SIZE(cbufp - cbuf)){
 3048                      a_tty_fun(tlp, a_TTY_BIND_FUN_FULLRESET, &len);
 3049                      goto jrestart;
 3050                   }
 3051                   /* Otherwise, due to the way we deal with the buffer, we need
 3052                    * to restore the mbstate_t from before this conversion */
 3053                   ps[0] = ps[1];
 3054                   continue;
 3055                }
 3056                cbufp = cbuf;
 3057                ps[1] = ps[0];
 3058             }
 3059 
 3060             /* Normal read(2)ing is subject to detection of key-bindings */
 3061 # ifdef HAVE_KEY_BINDINGS
 3062             if(!(flags & a_BUFMODE)){
 3063                /* Check for special bypass functions before we try to embed
 3064                 * this character into the tree */
 3065                if(n_uasciichar(wc)){
 3066                   char c;
 3067                   char const *cp;
 3068 
 3069                   for(c = (char)wc, cp = &(*tlp->tl_bind_shcut_prompt_char)[0];
 3070                         *cp != '\0'; ++cp){
 3071                      if(c == *cp){
 3072                         wc = a_tty_vinuni(tlp);
 3073                         break;
 3074                      }
 3075                   }
 3076                   if(wc == '\0'){
 3077                      tlp->tl_vi_flags |= a_TTY_VF_BELL;
 3078                      goto jinput_loop;
 3079                   }
 3080                }
 3081                if(n_uasciichar(wc))
 3082                   flags |= a_MAYBEFUN;
 3083                else
 3084                   flags &= ~a_MAYBEFUN;
 3085 
 3086                /* Search for this character in the bind tree */
 3087                tbtp = (isp != NULL) ? isp->tbtp->tbt_childs
 3088                      : (*tlp->tl_bind_tree_hmap)[wc % HSHSIZE];
 3089                for(; tbtp != NULL; tbtp = tbtp->tbt_sibling){
 3090                   if(tbtp->tbt_char == wc){
 3091                      struct inseq *nisp;
 3092 
 3093                      /* If this one cannot continue we're likely finished! */
 3094                      if(tbtp->tbt_childs == NULL){
 3095                         assert(tbtp->tbt_bind != NULL);
 3096                         tbf = tbtp->tbt_bind->tbc_flags;
 3097                         goto jmle_fun;
 3098                      }
 3099 
 3100                      /* This needs to read more characters */
 3101                      nisp = n_autorec_alloc(sizeof *nisp);
 3102                      if((nisp->last = isp) == NULL)
 3103                         isp_head = nisp;
 3104                      else
 3105                         isp->next = nisp;
 3106                      nisp->next = NULL;
 3107                      nisp->tbtp = tbtp;
 3108                      isp = nisp;
 3109                      flags &= ~a_WAS_HERE;
 3110                      break;
 3111                   }
 3112                }
 3113                if(tbtp != NULL)
 3114                   continue;
 3115 
 3116                /* Was there a binding active, but couldn't be continued? */
 3117                if(isp != NULL){
 3118                   /* A binding had a timeout, it didn't expire, but we saw
 3119                    * something non-expected.  Something "atomic" broke.
 3120                    * Maybe there is a second path without a timeout, that
 3121                    * continues like we've seen it.  I.e., it may just have been
 3122                    * the user, typing too fast.  We definitely want to allow
 3123                    * bindings like \e,d etc. to succeed: users are so used to
 3124                    * them that a timeout cannot be the mechanism to catch up!
 3125                    * A single-layer implementation cannot "know" */
 3126                   if((tbtp = isp->tbtp)->tbt_isseq && (isp->last == NULL ||
 3127                         !(xtbtp = isp->last->tbtp)->tbt_isseq ||
 3128                         xtbtp->tbt_isseq_trail)){
 3129                      for(xtbtp = (tbtp = isp->tbtp);
 3130                            (xtbtp = xtbtp->tbt_sibling) != NULL;)
 3131                         if(xtbtp->tbt_char == tbtp->tbt_char){
 3132                            assert(!xtbtp->tbt_isseq);
 3133                            break;
 3134                         }
 3135                      if(xtbtp != NULL){
 3136                         isp->tbtp = xtbtp;
 3137                         tlp->tl_bind_takeover = wc;
 3138                         continue;
 3139                      }
 3140                   }
 3141 
 3142                   /* Check for CANCEL shortcut now */
 3143                   if(flags & a_MAYBEFUN){
 3144                      char c;
 3145                      char const *cp;
 3146 
 3147                      for(c = (char)wc, cp = &(*tlp->tl_bind_shcut_cancel)[0];
 3148                            *cp != '\0'; ++cp)
 3149                         if(c == *cp){
 3150                            tbf = a_TTY_BIND_FUN_INTERNAL |a_TTY_BIND_FUN_CANCEL;
 3151                            goto jmle_fun;
 3152                         }
 3153                   }
 3154 
 3155                   /* So: maybe the current sequence can be terminated here? */
 3156                   if((tbtp = isp->tbtp)->tbt_bind != NULL){
 3157 jhave_bind:
 3158                      tbf = tbtp->tbt_bind->tbc_flags;
 3159 jmle_fun:
 3160                      if(tbf & a_TTY_BIND_FUN_INTERNAL){
 3161                         switch(a_tty_fun(tlp, tbf, &len)){
 3162                         case a_TTY_FUN_STATUS_OK:
 3163                            goto jinput_loop;
 3164                         case a_TTY_FUN_STATUS_COMMIT:
 3165                            goto jdone;
 3166                         case a_TTY_FUN_STATUS_RESTART:
 3167                            goto jrestart;
 3168                         case a_TTY_FUN_STATUS_END:
 3169                            rv = -1;
 3170                            goto jleave;
 3171                         }
 3172                         assert(0);
 3173                      }else if(tbtp->tbt_bind->tbc_flags & a_TTY_BIND_NOCOMMIT){
 3174                         struct a_tty_bind_ctx *tbcp;
 3175 
 3176                         tbcp = tbtp->tbt_bind;
 3177                         memcpy(tlp->tl_defc.s = n_autorec_alloc(
 3178                               (tlp->tl_defc.l = len = tbcp->tbc_exp_len) +1),
 3179                            tbcp->tbc_exp, tbcp->tbc_exp_len +1);
 3180                         goto jrestart;
 3181                      }else{
 3182                         cbufp = tbtp->tbt_bind->tbc_exp;
 3183                         goto jinject_input;
 3184                      }
 3185                   }
 3186                }
 3187 
 3188                /* Otherwise take over all chars "as is" */
 3189 jtake_over:
 3190                for(; isp_head != NULL; isp_head = isp_head->next)
 3191                   if(a_tty_kother(tlp, isp_head->tbtp->tbt_char)){
 3192                      /* FIXME */
 3193                   }
 3194                /* And the current one too */
 3195                goto jkother;
 3196             }
 3197 # endif /* HAVE_KEY_BINDINGS */
 3198 
 3199             if((flags & a_BUFMODE) && (len -= (size_t)rv) == 0){
 3200                /* Buffer mode completed */
 3201                tlp->tl_defc.s = NULL;
 3202                tlp->tl_defc.l = 0;
 3203                flags &= ~a_BUFMODE;
 3204             }
 3205             break;
 3206          }
 3207 
 3208 # ifndef HAVE_KEY_BINDINGS
 3209          /* Don't interpret control bytes during buffer mode.
 3210           * Otherwise, if it's a control byte check whether it is a MLE
 3211           * function.  Remarks: initially a complete duplicate to be able to
 3212           * switch(), later converted to simply iterate over (an #ifdef'd
 3213           * subset of) the MLE base_tuple table in order to have "a SPOF" */
 3214          if(cbuf == cbuf_base && n_uasciichar(wc) && cntrlchar((char)wc)){
 3215             struct a_tty_bind_builtin_tuple const *tbbtp, *tbbtp_max;
 3216             char c;
 3217 
 3218             c = (char)wc ^ 0x40;
 3219             tbbtp = a_tty_bind_base_tuples;
 3220             tbbtp_max = &tbbtp[n_NELEM(a_tty_bind_base_tuples)];
 3221 jbuiltin_redo:
 3222             for(; tbbtp < tbbtp_max; ++tbbtp){
 3223                /* Assert default_tuple table is properly subset'ed */
 3224                assert(tbbtp->tbdt_iskey);
 3225                if(tbbtp->tbbt_ckey == c){
 3226                   if(tbbtp->tbbt_exp[0] == '\0'){
 3227                      tbf = a_TTY_BIND_FUN_EXPAND((ui8_t)tbbtp->tbbt_exp[1]);
 3228                      switch(a_tty_fun(tlp, tbf, &len)){
 3229                      case a_TTY_FUN_STATUS_OK:
 3230                         goto jinput_loop;
 3231                      case a_TTY_FUN_STATUS_COMMIT:
 3232                         goto jdone;
 3233                      case a_TTY_FUN_STATUS_RESTART:
 3234                         goto jrestart;
 3235                      case a_TTY_FUN_STATUS_END:
 3236                         rv = -1;
 3237                         goto jleave;
 3238                      }
 3239                      assert(0);
 3240                   }else{
 3241                      cbufp = tbbtp->tbbt_exp;
 3242                      goto jinject_input;
 3243                   }
 3244                }
 3245             }
 3246             if(tbbtp ==
 3247                   &a_tty_bind_base_tuples[n_NELEM(a_tty_bind_base_tuples)]){
 3248                tbbtp = a_tty_bind_default_tuples;
 3249                tbbtp_max = &tbbtp[n_NELEM(a_tty_bind_default_tuples)];
 3250                goto jbuiltin_redo;
 3251             }
 3252          }
 3253 #  endif /* !HAVE_KEY_BINDINGS */
 3254 
 3255 # ifdef HAVE_KEY_BINDINGS
 3256 jkother:
 3257 # endif
 3258          if(a_tty_kother(tlp, wc)){
 3259             /* Don't clear the history during buffer mode.. */
 3260 # ifdef HAVE_HISTORY
 3261             if(!(flags & a_BUFMODE) && cbuf == cbuf_base)
 3262                tlp->tl_hist = NULL;
 3263 # endif
 3264          }
 3265       }
 3266    }
 3267 
 3268    /* We have a completed input line, convert the struct cell data to its
 3269     * plain character equivalent */
 3270 jdone:
 3271    rv = a_tty_cell2dat(tlp);
 3272 jleave:
 3273    putc('\n', n_tty_fp);
 3274    fflush(n_tty_fp);
 3275    NYD_LEAVE;
 3276    return rv;
 3277 
 3278 jinject_input:{
 3279    size_t i;
 3280 
 3281    hold_all_sigs(); /* XXX v15 drop */
 3282    i = a_tty_cell2dat(tlp);
 3283    n_go_input_inject(n_GO_INPUT_INJECT_NONE, tlp->tl_line.cbuf, i);
 3284    i = strlen(cbufp) +1;
 3285    if(i >= *tlp->tl_x_bufsize){
 3286       *tlp->tl_x_buf = (n_realloc)(*tlp->tl_x_buf, i n_MEMORY_DEBUG_ARGSCALL);
 3287       *tlp->tl_x_bufsize = i;
 3288    }
 3289    memcpy(*tlp->tl_x_buf, cbufp, i);
 3290    rele_all_sigs(); /* XXX v15 drop */
 3291    if(histok_or_null != NULL)
 3292       *histok_or_null = FAL0;
 3293    rv = (ssize_t)--i;
 3294    }
 3295    goto jleave;
 3296 }
 3297 
 3298 # ifdef HAVE_KEY_BINDINGS
 3299 static enum n_go_input_flags
 3300 a_tty_bind_ctx_find(char const *name){
 3301    enum n_go_input_flags rv;
 3302    struct a_tty_bind_ctx_map const *tbcmp;
 3303    NYD2_ENTER;
 3304 
 3305    tbcmp = a_tty_bind_ctx_maps;
 3306    do if(!asccasecmp(tbcmp->tbcm_name, name)){
 3307       rv = tbcmp->tbcm_ctx;
 3308       goto jleave;
 3309    }while(PTRCMP(++tbcmp, <,
 3310       &a_tty_bind_ctx_maps[n_NELEM(a_tty_bind_ctx_maps)]));
 3311 
 3312    rv = (enum n_go_input_flags)-1;
 3313 jleave:
 3314    NYD2_LEAVE;
 3315    return rv;
 3316 }
 3317 
 3318 static bool_t
 3319 a_tty_bind_create(struct a_tty_bind_parse_ctx *tbpcp, bool_t replace){
 3320    struct a_tty_bind_ctx *tbcp;
 3321    bool_t rv;
 3322    NYD2_ENTER;
 3323 
 3324    rv = FAL0;
 3325 
 3326    if(!a_tty_bind_parse(TRU1, tbpcp))
 3327       goto jleave;
 3328 
 3329    /* Since we use a single buffer for it all, need to replace as such */
 3330    if(tbpcp->tbpc_tbcp != NULL){
 3331       if(!replace)
 3332          goto jleave;
 3333       a_tty_bind_del(tbpcp);
 3334    }else if(a_tty.tg_bind_cnt == UI32_MAX){
 3335       n_err(_("`bind': maximum number of bindings already established\n"));
 3336       goto jleave;
 3337    }
 3338 
 3339    /* C99 */{
 3340       size_t i, j;
 3341 
 3342       tbcp = n_alloc(n_VSTRUCT_SIZEOF(struct a_tty_bind_ctx, tbc__buf) +
 3343             tbpcp->tbpc_seq_len + tbpcp->tbpc_exp.l +
 3344             n_MAX(sizeof(si32_t), sizeof(wc_t)) + tbpcp->tbpc_cnv_len +3);
 3345       if(tbpcp->tbpc_ltbcp != NULL){
 3346          tbcp->tbc_next = tbpcp->tbpc_ltbcp->tbc_next;
 3347          tbpcp->tbpc_ltbcp->tbc_next = tbcp;
 3348       }else{
 3349          enum n_go_input_flags gif;
 3350 
 3351          gif = tbpcp->tbpc_flags & n__GO_INPUT_CTX_MASK;
 3352          tbcp->tbc_next = a_tty.tg_bind[gif];
 3353          a_tty.tg_bind[gif] = tbcp;
 3354       }
 3355       memcpy(tbcp->tbc_seq = &tbcp->tbc__buf[0],
 3356          tbpcp->tbpc_seq, i = (tbcp->tbc_seq_len = tbpcp->tbpc_seq_len) +1);
 3357       memcpy(tbcp->tbc_exp = &tbcp->tbc__buf[i],
 3358          tbpcp->tbpc_exp.s, j = (tbcp->tbc_exp_len = tbpcp->tbpc_exp.l) +1);
 3359       i += j;
 3360       i = (i + tbpcp->tbpc_cnv_align_mask) & ~tbpcp->tbpc_cnv_align_mask;
 3361       memcpy(tbcp->tbc_cnv = &tbcp->tbc__buf[i],
 3362          tbpcp->tbpc_cnv, (tbcp->tbc_cnv_len = tbpcp->tbpc_cnv_len) +1);
 3363       tbcp->tbc_flags = tbpcp->tbpc_flags;
 3364    }
 3365 
 3366    /* Directly resolve any termcap(5) symbol if we are already setup */
 3367    if((n_psonce & n_PSO_STARTED) &&
 3368          (tbcp->tbc_flags & (a_TTY_BIND_RESOLVE | a_TTY_BIND_DEFUNCT)) ==
 3369           a_TTY_BIND_RESOLVE)
 3370       a_tty_bind_resolve(tbcp);
 3371 
 3372    ++a_tty.tg_bind_cnt;
 3373    /* If this binding is usable invalidate the key input lookup trees */
 3374    if(!(tbcp->tbc_flags & a_TTY_BIND_DEFUNCT))
 3375       a_tty.tg_bind_isdirty = TRU1;
 3376    rv = TRU1;
 3377 jleave:
 3378    NYD2_LEAVE;
 3379    return rv;
 3380 }
 3381 
 3382 static bool_t
 3383 a_tty_bind_parse(bool_t isbindcmd, struct a_tty_bind_parse_ctx *tbpcp){
 3384    enum{a_TRUE_RV = a_TTY__BIND_LAST<<1};
 3385 
 3386    struct n_visual_info_ctx vic;
 3387    struct str shin_save, shin;
 3388    struct n_string shou, *shoup;
 3389    size_t i;
 3390    struct kse{
 3391       struct kse *next;
 3392       char *seq_dat;
 3393       wc_t *cnv_dat;
 3394       ui32_t seq_len;
 3395       ui32_t cnv_len;      /* High bit set if a termap to be resolved */
 3396       ui32_t calc_cnv_len; /* Ditto, but aligned etc. */
 3397       ui8_t kse__dummy[4];
 3398    } *head, *tail;
 3399    ui32_t f;
 3400    NYD2_ENTER;
 3401    n_LCTA(UICMP(64, a_TRUE_RV, <, UI32_MAX),
 3402       "Flag bits excess storage datatype");
 3403 
 3404    f = n_GO_INPUT_NONE;
 3405    shoup = n_string_creat_auto(&shou);
 3406    head = tail = NULL;
 3407 
 3408    /* Parse the key-sequence */
 3409    for(shin.s = n_UNCONST(tbpcp->tbpc_in_seq), shin.l = UIZ_MAX;;){
 3410       struct kse *ep;
 3411       enum n_shexp_state shs;
 3412 
 3413       shin_save = shin;
 3414       shs = n_shexp_parse_token((n_SHEXP_PARSE_TRUNC |
 3415             n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_IGNORE_EMPTY |
 3416             n_SHEXP_PARSE_IFS_IS_COMMA), shoup, &shin, NULL);
 3417       if(shs & n_SHEXP_STATE_ERR_UNICODE){
 3418          f |= a_TTY_BIND_DEFUNCT;
 3419          if(isbindcmd && (n_poption & n_PO_D_V))
 3420             n_err(_("`%s': \\uNICODE not available in locale: %s\n"),
 3421                tbpcp->tbpc_cmd, tbpcp->tbpc_in_seq);
 3422       }
 3423       if((shs & n_SHEXP_STATE_ERR_MASK) & ~n_SHEXP_STATE_ERR_UNICODE){
 3424          n_err(_("`%s': failed to parse key-sequence: %s\n"),
 3425             tbpcp->tbpc_cmd, tbpcp->tbpc_in_seq);
 3426          goto jleave;
 3427       }
 3428       if((shs & (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_STOP)) ==
 3429             n_SHEXP_STATE_STOP)
 3430          break;
 3431 
 3432       ep = n_autorec_alloc(sizeof *ep);
 3433       if(head == NULL)
 3434          head = ep;
 3435       else
 3436          tail->next = ep;
 3437       tail = ep;
 3438       ep->next = NULL;
 3439       if(!(shs & n_SHEXP_STATE_ERR_UNICODE)){
 3440          i = strlen(ep->seq_dat = n_shexp_quote_cp(n_string_cp(shoup), TRU1));
 3441          if(i >= SI32_MAX - 1)
 3442             goto jelen;
 3443          ep->seq_len = (ui32_t)i;
 3444       }else{
 3445          /* Otherwise use the original buffer, _we_ can only quote it the wrong
 3446           * way (e.g., an initial $'\u3a' becomes '\u3a'), _then_ */
 3447          if((i = shin_save.l - shin.l) >= SI32_MAX - 1)
 3448             goto jelen;
 3449          ep->seq_len = (ui32_t)i;
 3450          ep->seq_dat = savestrbuf(shin_save.s, i);
 3451       }
 3452 
 3453       memset(&vic, 0, sizeof vic);
 3454       vic.vic_inlen = shoup->s_len;
 3455       vic.vic_indat = shoup->s_dat;
 3456       if(!n_visual_info(&vic,
 3457             n_VISUAL_INFO_WOUT_CREATE | n_VISUAL_INFO_WOUT_SALLOC)){
 3458          n_err(_("`%s': key-sequence seems to contain invalid "
 3459             "characters: %s: %s\n"),
 3460             tbpcp->tbpc_cmd, n_string_cp(shoup), tbpcp->tbpc_in_seq);
 3461          f |= a_TTY_BIND_DEFUNCT;
 3462          goto jleave;
 3463       }else if(vic.vic_woulen == 0 ||
 3464             vic.vic_woulen >= (SI32_MAX - 2) / sizeof(wc_t)){
 3465 jelen:
 3466          n_err(_("`%s': length of key-sequence unsupported: %s: %s\n"),
 3467             tbpcp->tbpc_cmd, n_string_cp(shoup), tbpcp->tbpc_in_seq);
 3468          f |= a_TTY_BIND_DEFUNCT;
 3469          goto jleave;
 3470       }
 3471       ep->cnv_dat = vic.vic_woudat;
 3472       ep->cnv_len = (ui32_t)vic.vic_woulen;
 3473 
 3474       /* A termcap(5)/terminfo(5) identifier? */
 3475       if(ep->cnv_len > 1 && ep->cnv_dat[0] == ':'){
 3476          i = --ep->cnv_len, ++ep->cnv_dat;
 3477 #  if 0 /* ndef HAVE_TERMCAP xxx User can, via *termcap*! */
 3478          if(n_poption & n_PO_D_V)
 3479             n_err(_("`%s': no termcap(5)/terminfo(5) support: %s: %s\n"),
 3480                tbpcp->tbpc_cmd, ep->seq_dat, tbpcp->tbpc_in_seq);
 3481          f |= a_TTY_BIND_DEFUNCT;
 3482 #  endif
 3483          if(i > a_TTY_BIND_CAPNAME_MAX){
 3484             n_err(_("`%s': termcap(5)/terminfo(5) name too long: %s: %s\n"),
 3485                tbpcp->tbpc_cmd, ep->seq_dat, tbpcp->tbpc_in_seq);
 3486             f |= a_TTY_BIND_DEFUNCT;
 3487          }
 3488          while(i > 0)
 3489             /* (We store it as char[]) */
 3490             if((ui32_t)ep->cnv_dat[--i] & ~0x7Fu){
 3491                n_err(_("`%s': invalid termcap(5)/terminfo(5) name content: "
 3492                   "%s: %s\n"),
 3493                   tbpcp->tbpc_cmd, ep->seq_dat, tbpcp->tbpc_in_seq);
 3494                f |= a_TTY_BIND_DEFUNCT;
 3495                break;
 3496             }
 3497          ep->cnv_len |= SI32_MIN; /* Needs resolve */
 3498          f |= a_TTY_BIND_RESOLVE;
 3499       }
 3500 
 3501       if(shs & n_SHEXP_STATE_STOP)
 3502          break;
 3503    }
 3504 
 3505    if(head == NULL){
 3506 jeempty:
 3507       n_err(_("`%s': effectively empty key-sequence: %s\n"),
 3508          tbpcp->tbpc_cmd, tbpcp->tbpc_in_seq);
 3509       goto jleave;
 3510    }
 3511 
 3512    if(isbindcmd) /* (Or always, just "1st time init") */
 3513       tbpcp->tbpc_cnv_align_mask = n_MAX(sizeof(si32_t), sizeof(wc_t)) - 1;
 3514 
 3515    /* C99 */{
 3516       struct a_tty_bind_ctx *ltbcp, *tbcp;
 3517       char *cpbase, *cp, *cnv;
 3518       size_t sl, cl;
 3519 
 3520       /* Unite the parsed sequence(s) into single string representations */
 3521       for(sl = cl = 0, tail = head; tail != NULL; tail = tail->next){
 3522          sl += tail->seq_len + 1;
 3523 
 3524          if(!isbindcmd)
 3525             continue;
 3526 
 3527          /* Preserve room for terminal capabilities to be resolved.
 3528           * Above we have ensured the buffer will fit in these calculations */
 3529          if((i = tail->cnv_len) & SI32_MIN){
 3530             /* For now
 3531              * struct{si32_t buf_len_iscap; si32_t cap_len; wc_t name[]+NUL;}
 3532              * later
 3533              * struct{si32_t buf_len_iscap; si32_t cap_len; char buf[]+NUL;} */
 3534             n_LCTAV(n_ISPOW2(a_TTY_BIND_CAPEXP_ROUNDUP));
 3535             n_LCTA(a_TTY_BIND_CAPEXP_ROUNDUP >= sizeof(wc_t),
 3536                "Aligning on this constant does not properly align wc_t");
 3537             i &= SI32_MAX;
 3538             i *= sizeof(wc_t);
 3539             i += sizeof(si32_t);
 3540             if(i < a_TTY_BIND_CAPEXP_ROUNDUP)
 3541                i = (i + (a_TTY_BIND_CAPEXP_ROUNDUP - 1)) &
 3542                      ~(a_TTY_BIND_CAPEXP_ROUNDUP - 1);
 3543          }else
 3544             /* struct{si32_t buf_len_iscap; wc_t buf[]+NUL;} */
 3545             i *= sizeof(wc_t);
 3546          i += sizeof(si32_t) + sizeof(wc_t); /* (buf_len_iscap, NUL) */
 3547          cl += i;
 3548          if(tail->cnv_len & SI32_MIN){
 3549             tail->cnv_len &= SI32_MAX;
 3550             i |= SI32_MIN;
 3551          }
 3552          tail->calc_cnv_len = (ui32_t)i;
 3553       }
 3554       --sl;
 3555 
 3556       tbpcp->tbpc_seq_len = sl;
 3557       tbpcp->tbpc_cnv_len = cl;
 3558       /* C99 */{
 3559          size_t j;
 3560 
 3561          j = i = sl + 1; /* Room for comma separator */
 3562          if(isbindcmd){
 3563             i = (i + tbpcp->tbpc_cnv_align_mask) & ~tbpcp->tbpc_cnv_align_mask;
 3564             j = i;
 3565             i += cl;
 3566          }
 3567          tbpcp->tbpc_seq = cp = cpbase = n_autorec_alloc(i);
 3568          tbpcp->tbpc_cnv = cnv = &cpbase[j];
 3569       }
 3570 
 3571       for(tail = head; tail != NULL; tail = tail->next){
 3572          memcpy(cp, tail->seq_dat, tail->seq_len);
 3573          cp += tail->seq_len;
 3574          *cp++ = ',';
 3575 
 3576          if(isbindcmd){
 3577             char * const save_cnv = cnv;
 3578 
 3579             n_UNALIGN(si32_t*,cnv)[0] = (si32_t)(i = tail->calc_cnv_len);
 3580             cnv += sizeof(si32_t);
 3581             if(i & SI32_MIN){
 3582                /* For now
 3583                 * struct{si32_t buf_len_iscap; si32_t cap_len; wc_t name[];}
 3584                 * later
 3585                 * struct{si32_t buf_len_iscap; si32_t cap_len; char buf[];} */
 3586                n_UNALIGN(si32_t*,cnv)[0] = tail->cnv_len;
 3587                cnv += sizeof(si32_t);
 3588             }
 3589             i = tail->cnv_len * sizeof(wc_t);
 3590             memcpy(cnv, tail->cnv_dat, i);
 3591             cnv += i;
 3592             *n_UNALIGN(wc_t*,cnv) = '\0';
 3593 
 3594             cnv = save_cnv + (tail->calc_cnv_len & SI32_MAX);
 3595          }
 3596       }
 3597       *--cp = '\0';
 3598 
 3599       /* Search for a yet existing identical mapping */
 3600       /* C99 */{
 3601          enum n_go_input_flags gif;
 3602 
 3603          gif = tbpcp->tbpc_flags & n__GO_INPUT_CTX_MASK;
 3604 
 3605          for(ltbcp = NULL, tbcp = a_tty.tg_bind[gif]; tbcp != NULL;
 3606                ltbcp = tbcp, tbcp = tbcp->tbc_next)
 3607             if(tbcp->tbc_seq_len == sl && !memcmp(tbcp->tbc_seq, cpbase, sl)){
 3608                tbpcp->tbpc_tbcp = tbcp;
 3609                break;
 3610             }
 3611       }
 3612       tbpcp->tbpc_ltbcp = ltbcp;
 3613       tbpcp->tbpc_flags |= (f & a_TTY__BIND_MASK);
 3614    }
 3615 
 3616    /* Create single string expansion if so desired */
 3617    if(isbindcmd){
 3618       char *exp;
 3619 
 3620       exp = tbpcp->tbpc_exp.s;
 3621 
 3622       i = tbpcp->tbpc_exp.l;
 3623       if(i > 0 && exp[i - 1] == '@'){
 3624          while(--i > 0){
 3625             if(!blankspacechar(exp[i - 1]))
 3626                break;
 3627          }
 3628          if(i == 0)
 3629             goto jeempty;
 3630 
 3631          exp[tbpcp->tbpc_exp.l = i] = '\0';
 3632          tbpcp->tbpc_flags |= a_TTY_BIND_NOCOMMIT;
 3633       }
 3634 
 3635       /* Reverse solidus cannot be placed last in expansion to avoid (at the
 3636        * time of this writing) possible problems with newline escaping.
 3637        * Don't care about (un)even number thereof */
 3638       if(i > 0 && exp[i - 1] == '\\'){
 3639          n_err(_("`%s': reverse solidus cannot be last in expansion: %s\n"),
 3640             tbpcp->tbpc_cmd, tbpcp->tbpc_in_seq);
 3641          goto jleave;
 3642       }
 3643 
 3644       /* It may map to an internal MLE command! */
 3645       for(i = 0; i < n_NELEM(a_tty_bind_fun_names); ++i)
 3646          if(!asccasecmp(exp, a_tty_bind_fun_names[i])){
 3647             tbpcp->tbpc_flags |= a_TTY_BIND_FUN_EXPAND(i) |
 3648                   a_TTY_BIND_FUN_INTERNAL |
 3649                   (head->next == NULL ? a_TTY_BIND_MLE1CNTRL : 0);
 3650             if((n_poption & n_PO_D_V) &&
 3651                   (tbpcp->tbpc_flags & a_TTY_BIND_NOCOMMIT))
 3652                n_err(_("`%s': MLE commands can't be made editable via @: %s\n"),
 3653                   tbpcp->tbpc_cmd, exp);
 3654             tbpcp->tbpc_flags &= ~a_TTY_BIND_NOCOMMIT;
 3655             break;
 3656          }
 3657    }
 3658 
 3659   f |= a_TRUE_RV; /* TODO because we only now true and false; DEFUNCT.. */
 3660 jleave:
 3661    n_string_gut(shoup);
 3662    NYD2_LEAVE;
 3663    return (f & a_TRUE_RV) != 0;
 3664 }
 3665 
 3666 static void
 3667 a_tty_bind_resolve(struct a_tty_bind_ctx *tbcp){
 3668    char capname[a_TTY_BIND_CAPNAME_MAX +1];
 3669    struct n_termcap_value tv;
 3670    size_t len;
 3671    bool_t isfirst; /* TODO For now: first char must be control! */
 3672    char *cp, *next;
 3673    NYD2_ENTER;
 3674 
 3675    n_UNINIT(next, NULL);
 3676    for(cp = tbcp->tbc_cnv, isfirst = TRU1, len = tbcp->tbc_cnv_len;
 3677          len > 0; isfirst = FAL0, cp = next){
 3678       /* C99 */{
 3679          si32_t i, j;
 3680 
 3681          i = n_UNALIGN(si32_t*,cp)[0];
 3682          j = i & SI32_MAX;
 3683          next = &cp[j];
 3684          len -= j;
 3685          if(i == j)
 3686             continue;
 3687 
 3688          /* struct{si32_t buf_len_iscap; si32_t cap_len; wc_t name[];} */
 3689          cp += sizeof(si32_t);
 3690          i = n_UNALIGN(si32_t*,cp)[0];
 3691          cp += sizeof(si32_t);
 3692          for(j = 0; j < i; ++j)
 3693             capname[j] = n_UNALIGN(wc_t*,cp)[j];
 3694          capname[j] = '\0';
 3695       }
 3696 
 3697       /* Use generic lookup mechanism if not a known query */
 3698       /* C99 */{
 3699          si32_t tq;
 3700 
 3701          tq = n_termcap_query_for_name(capname, n_TERMCAP_CAPTYPE_STRING);
 3702          if(tq == -1){
 3703             tv.tv_data.tvd_string = capname;
 3704             tq = n__TERMCAP_QUERY_MAX1;
 3705          }
 3706 
 3707          if(tq < 0 || !n_termcap_query(tq, &tv)){
 3708             if(n_poption & n_PO_D_V)
 3709                n_err(_("`bind': unknown or unsupported capability: %s: %s\n"),
 3710                   capname, tbcp->tbc_seq);
 3711             tbcp->tbc_flags |= a_TTY_BIND_DEFUNCT;
 3712             break;
 3713          }
 3714       }
 3715 
 3716       /* struct{si32_t buf_len_iscap; si32_t cap_len; char buf[]+NUL;} */
 3717       /* C99 */{
 3718          size_t i;
 3719 
 3720          i = strlen(tv.tv_data.tvd_string);
 3721          if(/*i > SI32_MAX ||*/ i >= PTR2SIZE(next - cp)){
 3722             if(n_poption & n_PO_D_V)
 3723                n_err(_("`bind': capability expansion too long: %s: %s\n"),
 3724                   capname, tbcp->tbc_seq);
 3725             tbcp->tbc_flags |= a_TTY_BIND_DEFUNCT;
 3726             break;
 3727          }else if(i == 0){
 3728             if(n_poption & n_PO_D_V)
 3729                n_err(_("`bind': empty capability expansion: %s: %s\n"),
 3730                   capname, tbcp->tbc_seq);
 3731             tbcp->tbc_flags |= a_TTY_BIND_DEFUNCT;
 3732             break;
 3733          }else if(isfirst && !cntrlchar(*tv.tv_data.tvd_string)){
 3734             if(n_poption & n_PO_D_V)
 3735                n_err(_("`bind': capability expansion does not start with "
 3736                   "control: %s: %s\n"), capname, tbcp->tbc_seq);
 3737             tbcp->tbc_flags |= a_TTY_BIND_DEFUNCT;
 3738             break;
 3739          }
 3740          n_UNALIGN(si32_t*,cp)[-1] = (si32_t)i;
 3741          memcpy(cp, tv.tv_data.tvd_string, i);
 3742          cp[i] = '\0';
 3743       }
 3744    }
 3745    NYD2_LEAVE;
 3746 }
 3747 
 3748 static void
 3749 a_tty_bind_del(struct a_tty_bind_parse_ctx *tbpcp){
 3750    struct a_tty_bind_ctx *ltbcp, *tbcp;
 3751    NYD2_ENTER;
 3752 
 3753    tbcp = tbpcp->tbpc_tbcp;
 3754 
 3755    if((ltbcp = tbpcp->tbpc_ltbcp) != NULL)
 3756       ltbcp->tbc_next = tbcp->tbc_next;
 3757    else
 3758       a_tty.tg_bind[tbpcp->tbpc_flags & n__GO_INPUT_CTX_MASK] = tbcp->tbc_next;
 3759    n_free(tbcp);
 3760 
 3761    --a_tty.tg_bind_cnt;
 3762    a_tty.tg_bind_isdirty = TRU1;
 3763    NYD2_LEAVE;
 3764 }
 3765 
 3766 static void
 3767 a_tty_bind_tree_build(void){
 3768    size_t i;
 3769    NYD2_ENTER;
 3770 
 3771    for(i = 0; i < n__GO_INPUT_CTX_MAX1; ++i){
 3772       struct a_tty_bind_ctx *tbcp;
 3773       n_LCTAV(n_GO_INPUT_CTX_BASE == 0);
 3774 
 3775       /* Somewhat wasteful, but easier to handle: simply clone the entire
 3776        * primary key onto the secondary one, then only modify it */
 3777       for(tbcp = a_tty.tg_bind[n_GO_INPUT_CTX_BASE]; tbcp != NULL;
 3778             tbcp = tbcp->tbc_next)
 3779          if(!(tbcp->tbc_flags & a_TTY_BIND_DEFUNCT))
 3780             a_tty__bind_tree_add(n_GO_INPUT_CTX_BASE, &a_tty.tg_bind_tree[i][0],
 3781                tbcp);
 3782 
 3783       if(i != n_GO_INPUT_CTX_BASE)
 3784          for(tbcp = a_tty.tg_bind[i]; tbcp != NULL; tbcp = tbcp->tbc_next)
 3785             if(!(tbcp->tbc_flags & a_TTY_BIND_DEFUNCT))
 3786                a_tty__bind_tree_add(i, &a_tty.tg_bind_tree[i][0], tbcp);
 3787    }
 3788 
 3789    a_tty.tg_bind_isbuild = TRU1;
 3790    NYD2_LEAVE;
 3791 }
 3792 
 3793 static void
 3794 a_tty_bind_tree_teardown(void){
 3795    size_t i, j;
 3796    NYD2_ENTER;
 3797 
 3798    memset(&a_tty.tg_bind_shcut_cancel[0], 0,
 3799       sizeof(a_tty.tg_bind_shcut_cancel));
 3800    memset(&a_tty.tg_bind_shcut_prompt_char[0], 0,
 3801       sizeof(a_tty.tg_bind_shcut_prompt_char));
 3802 
 3803    for(i = 0; i < n__GO_INPUT_CTX_MAX1; ++i)
 3804       for(j = 0; j < HSHSIZE; ++j)
 3805          a_tty__bind_tree_free(a_tty.tg_bind_tree[i][j]);
 3806    memset(&a_tty.tg_bind_tree[0], 0, sizeof(a_tty.tg_bind_tree));
 3807 
 3808    a_tty.tg_bind_isdirty = a_tty.tg_bind_isbuild = FAL0;
 3809    NYD2_LEAVE;
 3810 }
 3811 
 3812 static void
 3813 a_tty__bind_tree_add(ui32_t hmap_idx, struct a_tty_bind_tree *store[HSHSIZE],
 3814       struct a_tty_bind_ctx *tbcp){
 3815    ui32_t cnvlen;
 3816    char const *cnvdat;
 3817    struct a_tty_bind_tree *ntbtp;
 3818    NYD2_ENTER;
 3819    n_UNUSED(hmap_idx);
 3820 
 3821    ntbtp = NULL;
 3822 
 3823    for(cnvdat = tbcp->tbc_cnv, cnvlen = tbcp->tbc_cnv_len; cnvlen > 0;){
 3824       union {wchar_t const *wp; char const *cp;} u;
 3825       si32_t entlen;
 3826 
 3827       /* {si32_t buf_len_iscap;} */
 3828       entlen = *n_UNALIGN(si32_t const*,cnvdat);
 3829 
 3830       if(entlen & SI32_MIN){
 3831          /* struct{si32_t buf_len_iscap; si32_t cap_len; char buf[]+NUL;}
 3832           * Note that empty capabilities result in DEFUNCT */
 3833          for(u.cp = (char const*)&n_UNALIGN(si32_t const*,cnvdat)[2];
 3834                *u.cp != '\0'; ++u.cp)
 3835             ntbtp = a_tty__bind_tree_add_wc(store, ntbtp, *u.cp, TRU1);
 3836          assert(ntbtp != NULL);
 3837          ntbtp->tbt_isseq_trail = TRU1;
 3838          entlen &= SI32_MAX;
 3839       }else{
 3840          /* struct{si32_t buf_len_iscap; wc_t buf[]+NUL;} */
 3841          bool_t isseq;
 3842 
 3843          u.wp = (wchar_t const*)&n_UNALIGN(si32_t const*,cnvdat)[1];
 3844 
 3845          /* May be a special shortcut function? */
 3846          if(ntbtp == NULL && (tbcp->tbc_flags & a_TTY_BIND_MLE1CNTRL)){
 3847             char *cp;
 3848             ui32_t ctx, fun;
 3849 
 3850             ctx = tbcp->tbc_flags & n__GO_INPUT_CTX_MASK;
 3851             fun = tbcp->tbc_flags & a_TTY__BIND_FUN_MASK;
 3852 
 3853             if(fun == a_TTY_BIND_FUN_CANCEL){
 3854                for(cp = &a_tty.tg_bind_shcut_cancel[ctx][0];
 3855                      PTRCMP(cp, <, &a_tty.tg_bind_shcut_cancel[ctx]
 3856                         [n_NELEM(a_tty.tg_bind_shcut_cancel[ctx]) - 1]); ++cp)
 3857                   if(*cp == '\0'){
 3858                      *cp = (char)*u.wp;
 3859                      break;
 3860                   }
 3861             }else if(fun == a_TTY_BIND_FUN_PROMPT_CHAR){
 3862                for(cp = &a_tty.tg_bind_shcut_prompt_char[ctx][0];
 3863                      PTRCMP(cp, <, &a_tty.tg_bind_shcut_prompt_char[ctx]
 3864                         [n_NELEM(a_tty.tg_bind_shcut_prompt_char[ctx]) - 1]);
 3865                      ++cp)
 3866                   if(*cp == '\0'){
 3867                      *cp = (char)*u.wp;
 3868                      break;
 3869                   }
 3870             }
 3871          }
 3872 
 3873          isseq = (u.wp[1] != '\0');
 3874          for(; *u.wp != '\0'; ++u.wp)
 3875             ntbtp = a_tty__bind_tree_add_wc(store, ntbtp, *u.wp, isseq);
 3876          if(isseq){
 3877             assert(ntbtp != NULL);
 3878             ntbtp->tbt_isseq_trail = TRU1;
 3879          }
 3880       }
 3881 
 3882       cnvlen -= entlen;
 3883       cnvdat += entlen;
 3884    }
 3885 
 3886    /* Should have been rendered defunctional at first instead */
 3887    assert(ntbtp != NULL);
 3888    ntbtp->tbt_bind = tbcp;
 3889    NYD2_LEAVE;
 3890 }
 3891 
 3892 static struct a_tty_bind_tree *
 3893 a_tty__bind_tree_add_wc(struct a_tty_bind_tree **treep,
 3894       struct a_tty_bind_tree *parentp, wchar_t wc, bool_t isseq){
 3895    struct a_tty_bind_tree *tbtp, *xtbtp;
 3896    NYD2_ENTER;
 3897 
 3898    if(parentp == NULL){
 3899       treep += wc % HSHSIZE;
 3900 
 3901       /* Having no parent also means that the tree slot is possibly empty */
 3902       for(tbtp = *treep; tbtp != NULL;
 3903             parentp = tbtp, tbtp = tbtp->tbt_sibling){
 3904          if(tbtp->tbt_char != wc)
 3905             continue;
 3906          if(tbtp->tbt_isseq == isseq)
 3907             goto jleave;
 3908          /* isseq MUST be linked before !isseq, so record this "parent"
 3909           * sibling, but continue searching for now.
 3910           * Otherwise it is impossible that we'll find what we look for */
 3911          if(isseq){
 3912 #ifdef HAVE_DEBUG
 3913             while((tbtp = tbtp->tbt_sibling) != NULL)
 3914                assert(tbtp->tbt_char != wc);
 3915 #endif
 3916             break;
 3917          }
 3918       }
 3919 
 3920       tbtp = n_alloc(sizeof *tbtp);
 3921       memset(tbtp, 0, sizeof *tbtp);
 3922       tbtp->tbt_char = wc;
 3923       tbtp->tbt_isseq = isseq;
 3924 
 3925       if(parentp == NULL){
 3926          tbtp->tbt_sibling = *treep;
 3927          *treep = tbtp;
 3928       }else{
 3929          tbtp->tbt_sibling = parentp->tbt_sibling;
 3930          parentp->tbt_sibling = tbtp;
 3931       }
 3932    }else{
 3933       if((tbtp = *(treep = &parentp->tbt_childs)) != NULL){
 3934          for(;; tbtp = xtbtp){
 3935             if(tbtp->tbt_char == wc){
 3936                if(tbtp->tbt_isseq == isseq)
 3937                   goto jleave;
 3938                /* isseq MUST be linked before, so it is impossible that we'll
 3939                 * find what we look for */
 3940                if(isseq){
 3941 #ifdef HAVE_DEBUG
 3942                   while((tbtp = tbtp->tbt_sibling) != NULL)
 3943                      assert(tbtp->tbt_char != wc);
 3944 #endif
 3945                   tbtp = NULL;
 3946                   break;
 3947                }
 3948             }
 3949 
 3950             if((xtbtp = tbtp->tbt_sibling) == NULL){
 3951                treep = &tbtp->tbt_sibling;
 3952                break;
 3953             }
 3954          }
 3955       }
 3956 
 3957       xtbtp = n_alloc(sizeof *xtbtp);
 3958       memset(xtbtp, 0, sizeof *xtbtp);
 3959       xtbtp->tbt_parent = parentp;
 3960       xtbtp->tbt_char = wc;
 3961       xtbtp->tbt_isseq = isseq;
 3962       tbtp = xtbtp;
 3963       *treep = tbtp;
 3964    }
 3965 jleave:
 3966    NYD2_LEAVE;
 3967    return tbtp;
 3968 }
 3969 
 3970 static void
 3971 a_tty__bind_tree_free(struct a_tty_bind_tree *tbtp){
 3972    NYD2_ENTER;
 3973    while(tbtp != NULL){
 3974       struct a_tty_bind_tree *tmp;
 3975 
 3976       if((tmp = tbtp->tbt_childs) != NULL)
 3977          a_tty__bind_tree_free(tmp);
 3978 
 3979       tmp = tbtp->tbt_sibling;
 3980       n_free(tbtp);
 3981       tbtp = tmp;
 3982    }
 3983    NYD2_LEAVE;
 3984 }
 3985 # endif /* HAVE_KEY_BINDINGS */
 3986 
 3987 FL void
 3988 n_tty_init(void){
 3989    NYD_ENTER;
 3990 
 3991    if(ok_blook(line_editor_disable))
 3992       goto jleave;
 3993 
 3994    /* Load the history file */
 3995 # ifdef HAVE_HISTORY
 3996    a_tty_hist_load();
 3997 # endif
 3998 
 3999    /* Force immediate resolve for anything which follows */
 4000    n_psonce |= n_PSO_LINE_EDITOR_INIT;
 4001 
 4002 # ifdef HAVE_KEY_BINDINGS
 4003    /* `bind's (and `unbind's) done from within resource files couldn't be
 4004     * performed for real since our termcap driver wasn't yet loaded, and we
 4005     * can't perform automatic init since the user may have disallowed so */
 4006    /* C99 */{ /* TODO outsource into own file */
 4007       struct a_tty_bind_ctx *tbcp;
 4008       enum n_go_input_flags gif;
 4009 
 4010       for(gif = 0; gif < n__GO_INPUT_CTX_MAX1; ++gif)
 4011          for(tbcp = a_tty.tg_bind[gif]; tbcp != NULL; tbcp = tbcp->tbc_next)
 4012             if((tbcp->tbc_flags & (a_TTY_BIND_RESOLVE | a_TTY_BIND_DEFUNCT)) ==
 4013                   a_TTY_BIND_RESOLVE)
 4014                a_tty_bind_resolve(tbcp);
 4015    }
 4016 
 4017    /* And we want to (try to) install some default key bindings */
 4018    if(!ok_blook(line_editor_no_defaults)){
 4019       char buf[8];
 4020       struct a_tty_bind_parse_ctx tbpc;
 4021       struct a_tty_bind_builtin_tuple const *tbbtp, *tbbtp_max;
 4022       ui32_t flags;
 4023 
 4024       buf[0] = '$', buf[1] = '\'', buf[2] = '\\', buf[3] = 'c',
 4025          buf[5] = '\'', buf[6] = '\0';
 4026 
 4027       tbbtp = a_tty_bind_base_tuples;
 4028       tbbtp_max = &tbbtp[n_NELEM(a_tty_bind_base_tuples)];
 4029       flags = n_GO_INPUT_CTX_BASE;
 4030 jbuiltin_redo:
 4031       for(; tbbtp < tbbtp_max; ++tbbtp){
 4032          memset(&tbpc, 0, sizeof tbpc);
 4033          tbpc.tbpc_cmd = "bind";
 4034          if(tbbtp->tbbt_iskey){
 4035             buf[4] = tbbtp->tbbt_ckey;
 4036             tbpc.tbpc_in_seq = buf;
 4037          }else
 4038             tbpc.tbpc_in_seq = savecatsep(":", '\0',
 4039                n_termcap_name_of_query(tbbtp->tbbt_query));
 4040          tbpc.tbpc_exp.s = n_UNCONST(tbbtp->tbbt_exp[0] == '\0'
 4041                ? a_tty_bind_fun_names[(ui8_t)tbbtp->tbbt_exp[1]]
 4042                : tbbtp->tbbt_exp);
 4043          tbpc.tbpc_exp.l = strlen(tbpc.tbpc_exp.s);
 4044          tbpc.tbpc_flags = flags;
 4045          /* ..but don't want to overwrite any user settings */
 4046          a_tty_bind_create(&tbpc, FAL0);
 4047       }
 4048       if(flags == n_GO_INPUT_CTX_BASE){
 4049          tbbtp = a_tty_bind_default_tuples;
 4050          tbbtp_max = &tbbtp[n_NELEM(a_tty_bind_default_tuples)];
 4051          flags = n_GO_INPUT_CTX_DEFAULT;
 4052          goto jbuiltin_redo;
 4053       }
 4054    }
 4055 # endif /* HAVE_KEY_BINDINGS */
 4056 
 4057 jleave:
 4058    NYD_LEAVE;
 4059 }
 4060 
 4061 FL void
 4062 n_tty_destroy(bool_t xit_fastpath){
 4063    NYD_ENTER;
 4064 
 4065    if(!(n_psonce & n_PSO_LINE_EDITOR_INIT))
 4066       goto jleave;
 4067 
 4068    /* Write the history file */
 4069 # ifdef HAVE_HISTORY
 4070    if(!xit_fastpath)
 4071       a_tty_hist_save();
 4072 # endif
 4073 
 4074 # if defined HAVE_KEY_BINDINGS && defined HAVE_DEBUG
 4075    n_go_command(n_GO_INPUT_NONE, "unbind * *");
 4076 # endif
 4077 
 4078 # ifdef HAVE_DEBUG
 4079    memset(&a_tty, 0, sizeof a_tty);
 4080 
 4081    n_psonce &= ~n_PSO_LINE_EDITOR_INIT;
 4082 # endif
 4083 jleave:
 4084    NYD_LEAVE;
 4085 }
 4086 
 4087 FL int
 4088 (n_tty_readline)(enum n_go_input_flags gif, char const *prompt,
 4089       char **linebuf, size_t *linesize, size_t n, bool_t *histok_or_null
 4090       n_MEMORY_DEBUG_ARGS){
 4091    struct a_tty_line tl;
 4092    struct n_string xprompt;
 4093 # ifdef HAVE_COLOUR
 4094    char *posbuf, *pos;
 4095 # endif
 4096    ssize_t nn;
 4097    NYD_ENTER;
 4098 
 4099    assert(!ok_blook(line_editor_disable));
 4100    if(!(n_psonce & n_PSO_LINE_EDITOR_INIT))
 4101       n_tty_init();
 4102    assert(n_psonce & n_PSO_LINE_EDITOR_INIT);
 4103 
 4104 # ifdef HAVE_COLOUR
 4105    n_colour_env_create(n_COLOUR_CTX_MLE, n_tty_fp, FAL0);
 4106 
 4107    /* .tl_pos_buf is a hack */
 4108    posbuf = pos = NULL;
 4109 
 4110    if(n_COLOUR_IS_ACTIVE()){
 4111       char const *ccol;
 4112       struct n_colour_pen *ccp;
 4113       struct str const *sp;
 4114 
 4115       if((ccp = n_colour_pen_create(n_COLOUR_ID_MLE_POSITION, NULL)) != NULL &&
 4116             (sp = n_colour_pen_to_str(ccp)) != NULL){
 4117          ccol = sp->s;
 4118          if((sp = n_colour_reset_to_str()) != NULL){
 4119             size_t l1, l2;
 4120 
 4121             l1 = strlen(ccol);
 4122             l2 = strlen(sp->s);
 4123             posbuf = n_autorec_alloc(l1 + 4 + l2 +1);
 4124             memcpy(posbuf, ccol, l1);
 4125             pos = &posbuf[l1];
 4126             memcpy(&pos[4], sp->s, ++l2);
 4127          }
 4128       }
 4129    }
 4130 
 4131    if(posbuf == NULL){
 4132       posbuf = pos = n_autorec_alloc(4 +1);
 4133       pos[4] = '\0';
 4134    }
 4135 # endif /* HAVE_COLOUR */
 4136 
 4137    memset(&tl, 0, sizeof tl);
 4138    tl.tl_goinflags = gif;
 4139 
 4140 # ifdef HAVE_KEY_BINDINGS
 4141    /* C99 */{
 4142       char const *cp;
 4143 
 4144       if((cp = ok_vlook(bind_timeout)) != NULL){
 4145          ui64_t uib;
 4146 
 4147          n_idec_ui64_cp(&uib, cp, 0, NULL);
 4148 
 4149          if(uib > 0 &&
 4150                /* Convert to tenths of a second, unfortunately */
 4151                (uib = (uib + 99) / 100) <= a_TTY_BIND_TIMEOUT_MAX)
 4152             tl.tl_bind_timeout = (ui8_t)uib;
 4153          else if(n_poption & n_PO_D_V)
 4154             n_err(_("Ignoring invalid *bind-timeout*: %s\n"), cp);
 4155       }
 4156    }
 4157 
 4158    if(a_tty.tg_bind_isdirty)
 4159       a_tty_bind_tree_teardown();
 4160    if(a_tty.tg_bind_cnt > 0 && !a_tty.tg_bind_isbuild)
 4161       a_tty_bind_tree_build();
 4162    tl.tl_bind_tree_hmap = &a_tty.tg_bind_tree[gif & n__GO_INPUT_CTX_MASK];
 4163    tl.tl_bind_shcut_cancel =
 4164          &a_tty.tg_bind_shcut_cancel[gif & n__GO_INPUT_CTX_MASK];
 4165    tl.tl_bind_shcut_prompt_char =
 4166          &a_tty.tg_bind_shcut_prompt_char[gif & n__GO_INPUT_CTX_MASK];
 4167 # endif /* HAVE_KEY_BINDINGS */
 4168 
 4169 # ifdef HAVE_COLOUR
 4170    tl.tl_pos_buf = posbuf;
 4171    tl.tl_pos = pos;
 4172 # endif
 4173 
 4174    if(!(gif & n_GO_INPUT_PROMPT_NONE)){
 4175       n_string_creat_auto(&xprompt);
 4176 
 4177       if((tl.tl_prompt_width = n_tty_create_prompt(&xprompt, prompt, gif)
 4178                ) > 0){
 4179          tl.tl_prompt = n_string_cp_const(&xprompt);
 4180          tl.tl_prompt_length = (ui32_t)xprompt.s_len;
 4181       }
 4182    }
 4183 
 4184    tl.tl_line.cbuf = *linebuf;
 4185    if(n != 0){
 4186       tl.tl_defc.s = savestrbuf(*linebuf, n);
 4187       tl.tl_defc.l = n;
 4188    }
 4189    tl.tl_x_buf = linebuf;
 4190    tl.tl_x_bufsize = linesize;
 4191 
 4192    a_tty.tg_line = &tl;
 4193    a_tty_sigs_up();
 4194    n_TERMCAP_RESUME(FAL0);
 4195    a_tty_term_mode(TRU1);
 4196    nn = a_tty_readline(&tl, n, histok_or_null n_MEMORY_DEBUG_ARGSCALL);
 4197    n_COLOUR( n_colour_env_gut(); )
 4198    a_tty_term_mode(FAL0);
 4199    n_TERMCAP_SUSPEND(FAL0);
 4200    a_tty_sigs_down();
 4201    a_tty.tg_line = NULL;
 4202 
 4203    NYD_LEAVE;
 4204    return (int)nn;
 4205 }
 4206 
 4207 FL void
 4208 n_tty_addhist(char const *s, enum n_go_input_flags gif){
 4209 # ifdef HAVE_HISTORY
 4210    /* Super-Heavy-Metal: block all sigs, avoid leaks+ on jump */
 4211    ui32_t l;
 4212    struct a_tty_hist *thp, *othp, *ythp;
 4213 # endif
 4214    NYD_ENTER;
 4215    n_UNUSED(s);
 4216    n_UNUSED(gif);
 4217 
 4218 # ifdef HAVE_HISTORY
 4219    if(*s == '\0' ||
 4220          (!(n_psonce & n_PSO_LINE_EDITOR_INIT) && !(n_pstate & n_PS_ROOT)) ||
 4221          a_tty.tg_hist_size_max == 0 ||
 4222          ok_blook(line_editor_disable) ||
 4223          ((gif & n_GO_INPUT_HIST_GABBY) && !ok_blook(history_gabby)))
 4224       goto j_leave;
 4225 
 4226    l = (ui32_t)strlen(s);
 4227 
 4228    /* Eliminating duplicates is expensive, but simply inacceptable so
 4229     * during the load of a potentially large history file! */
 4230    if(n_psonce & n_PSO_LINE_EDITOR_INIT)
 4231       for(thp = a_tty.tg_hist; thp != NULL; thp = thp->th_older)
 4232          if(thp->th_len == l && !strcmp(thp->th_dat, s)){
 4233             hold_all_sigs(); /* TODO */
 4234             thp->th_flags = (gif & a_TTY_HIST_CTX_MASK) |
 4235                   (gif & n_GO_INPUT_HIST_GABBY ? a_TTY_HIST_GABBY : 0);
 4236             othp = thp->th_older;
 4237             ythp = thp->th_younger;
 4238             if(othp != NULL)
 4239                othp->th_younger = ythp;
 4240             else
 4241                a_tty.tg_hist_tail = ythp;
 4242             if(ythp != NULL)
 4243                ythp->th_older = othp;
 4244             else
 4245                a_tty.tg_hist = othp;
 4246             goto jleave;
 4247          }
 4248    hold_all_sigs();
 4249 
 4250    ++a_tty.tg_hist_size;
 4251    if((n_psonce & n_PSO_LINE_EDITOR_INIT) &&
 4252          a_tty.tg_hist_size > a_tty.tg_hist_size_max){
 4253       --a_tty.tg_hist_size;
 4254       if((thp = a_tty.tg_hist_tail) != NULL){
 4255          if((a_tty.tg_hist_tail = thp->th_younger) == NULL)
 4256             a_tty.tg_hist = NULL;
 4257          else
 4258             a_tty.tg_hist_tail->th_older = NULL;
 4259          n_free(thp);
 4260       }
 4261    }
 4262 
 4263    thp = n_alloc(n_VSTRUCT_SIZEOF(struct a_tty_hist, th_dat) + l +1);
 4264    thp->th_len = l;
 4265    thp->th_flags = (gif & a_TTY_HIST_CTX_MASK) |
 4266          (gif & n_GO_INPUT_HIST_GABBY ? a_TTY_HIST_GABBY : 0);
 4267    memcpy(thp->th_dat, s, l +1);
 4268 jleave:
 4269    if((thp->th_older = a_tty.tg_hist) != NULL)
 4270       a_tty.tg_hist->th_younger = thp;
 4271    else
 4272       a_tty.tg_hist_tail = thp;
 4273    thp->th_younger = NULL;
 4274    a_tty.tg_hist = thp;
 4275 
 4276    rele_all_sigs();
 4277 j_leave:
 4278 # endif /* HAVE_HISTORY */
 4279    NYD_LEAVE;
 4280 }
 4281 
 4282 # ifdef HAVE_HISTORY
 4283 FL int
 4284 c_history(void *v){
 4285    siz_t entry;
 4286    struct a_tty_hist *thp;
 4287    char **argv;
 4288    NYD_ENTER;
 4289 
 4290    if(ok_blook(line_editor_disable)){
 4291       n_err(_("history: *line-editor-disable* is set\n"));
 4292       goto jerr;
 4293    }
 4294 
 4295    if(!(n_psonce & n_PSO_LINE_EDITOR_INIT)){
 4296       n_tty_init();
 4297       assert(n_psonce & n_PSO_LINE_EDITOR_INIT);
 4298    }
 4299 
 4300    if(*(argv = v) == NULL)
 4301       goto jlist;
 4302    if(argv[1] != NULL)
 4303       goto jerr;
 4304    if(!asccasecmp(*argv, "show"))
 4305       goto jlist;
 4306    if(!asccasecmp(*argv, "clear"))
 4307       goto jclear;
 4308 
 4309    if(!asccasecmp(*argv, "load")){
 4310       if(!a_tty_hist_load())
 4311          v = NULL;
 4312       goto jleave;
 4313    }
 4314    if(!asccasecmp(*argv, "save")){
 4315       if(!a_tty_hist_save())
 4316          v = NULL;
 4317       goto jleave;
 4318    }
 4319 
 4320    if((n_idec_siz_cp(&entry, *argv, 10, NULL
 4321             ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
 4322          ) == n_IDEC_STATE_CONSUMED)
 4323       goto jentry;
 4324 jerr:
 4325    n_err(_("Synopsis: history: %s\n"),
 4326       /* Same string as in cmd-tab.h, still hoping...) */
 4327       _("<show (default)|load|save|clear> or select history <NO>"));
 4328    v = NULL;
 4329 jleave:
 4330    NYD_LEAVE;
 4331    return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
 4332 
 4333 jlist:{
 4334    size_t no, l, b;
 4335    FILE *fp;
 4336 
 4337    if(a_tty.tg_hist == NULL)
 4338       goto jleave;
 4339 
 4340    if((fp = Ftmp(NULL, "hist", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL){
 4341       n_perr(_("tmpfile"), 0);
 4342       v = NULL;
 4343       goto jleave;
 4344    }
 4345 
 4346    no = a_tty.tg_hist_size;
 4347    l = b = 0;
 4348 
 4349    for(thp = a_tty.tg_hist; thp != NULL;
 4350          --no, ++l, thp = thp->th_older){
 4351       char c1, c2;
 4352 
 4353       b += thp->th_len;
 4354 
 4355       switch(thp->th_flags & a_TTY_HIST_CTX_MASK){
 4356       default:
 4357       case a_TTY_HIST_CTX_DEFAULT:
 4358          c1 = 'd';
 4359          break;
 4360       case a_TTY_HIST_CTX_COMPOSE:
 4361          c1 = 'c';
 4362          break;
 4363       }
 4364       c2 = (thp->th_flags & a_TTY_HIST_GABBY) ? '*' : ' ';
 4365 
 4366       if(n_poption & n_PO_D_V)
 4367          fprintf(fp, "# Length +%" PRIu32 ", total %" PRIuZ "\n",
 4368             thp->th_len, b);
 4369       fprintf(fp, "%c%c%4" PRIuZ "\t%s\n", c1, c2, no, thp->th_dat);
 4370    }
 4371 
 4372    page_or_print(fp, l);
 4373    Fclose(fp);
 4374    }
 4375    goto jleave;
 4376 
 4377 jclear:
 4378    while((thp = a_tty.tg_hist) != NULL){
 4379       a_tty.tg_hist = thp->th_older;
 4380       n_free(thp);
 4381    }
 4382    a_tty.tg_hist_tail = NULL;
 4383    a_tty.tg_hist_size = 0;
 4384    goto jleave;
 4385 
 4386 jentry:{
 4387    siz_t ep;
 4388 
 4389    ep = (entry < 0) ? -entry : entry;
 4390 
 4391    if(ep != 0 && UICMP(z, ep, <=, a_tty.tg_hist_size)){
 4392       if(ep != entry)
 4393          --ep;
 4394       else
 4395          ep = (siz_t)a_tty.tg_hist_size - ep;
 4396       for(thp = a_tty.tg_hist;; thp = thp->th_older){
 4397          assert(thp != NULL);
 4398          if(ep-- == 0){
 4399             n_go_input_inject((n_GO_INPUT_INJECT_COMMIT |
 4400                n_GO_INPUT_INJECT_HISTORY), v = thp->th_dat, thp->th_len);
 4401             break;
 4402          }
 4403       }
 4404    }else{
 4405       n_err(_("`history': no such entry: %" PRIdZ "\n"), entry);
 4406       v = NULL;
 4407    }
 4408    }
 4409    goto jleave;
 4410 }
 4411 # endif /* HAVE_HISTORY */
 4412 
 4413 # ifdef HAVE_KEY_BINDINGS
 4414 FL int
 4415 c_bind(void *v){
 4416    struct a_tty_bind_ctx *tbcp;
 4417    enum n_go_input_flags gif;
 4418    bool_t aster, show;
 4419    union {char const *cp; char *p; char c;} c;
 4420    struct n_cmd_arg_ctx *cacp;
 4421    NYD_ENTER;
 4422 
 4423    cacp = v;
 4424 
 4425    c.cp = cacp->cac_arg->ca_arg.ca_str.s;
 4426    if(cacp->cac_no == 1)
 4427       show = TRU1;
 4428    else
 4429       show = !asccasecmp(cacp->cac_arg->ca_next->ca_arg.ca_str.s, "show");
 4430    aster = FAL0;
 4431 
 4432    if((gif = a_tty_bind_ctx_find(c.cp)) == (enum n_go_input_flags)-1){
 4433       if(!(aster = n_is_all_or_aster(c.cp)) || !show){
 4434          n_err(_("`bind': invalid context: %s\n"), c.cp);
 4435          v = NULL;
 4436          goto jleave;
 4437       }
 4438       gif = 0;
 4439    }
 4440 
 4441    if(show){
 4442       ui32_t lns;
 4443       FILE *fp;
 4444 
 4445       if((fp = Ftmp(NULL, "bind", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL){
 4446          n_perr(_("tmpfile"), 0);
 4447          v = NULL;
 4448          goto jleave;
 4449       }
 4450 
 4451       lns = 0;
 4452       for(;;){
 4453          for(tbcp = a_tty.tg_bind[gif]; tbcp != NULL;
 4454                ++lns, tbcp = tbcp->tbc_next){
 4455             /* Print the bytes of resolved terminal capabilities, then */
 4456             if((n_poption & n_PO_D_V) &&
 4457                   (tbcp->tbc_flags & (a_TTY_BIND_RESOLVE | a_TTY_BIND_DEFUNCT)
 4458                   ) == a_TTY_BIND_RESOLVE){
 4459                char cbuf[8];
 4460                union {wchar_t const *wp; char const *cp;} u;
 4461                si32_t entlen;
 4462                ui32_t cnvlen;
 4463                char const *cnvdat, *bsep, *cbufp;
 4464 
 4465                putc('#', fp);
 4466                putc(' ', fp);
 4467 
 4468                cbuf[0] = '=', cbuf[2] = '\0';
 4469                for(cnvdat = tbcp->tbc_cnv, cnvlen = tbcp->tbc_cnv_len;
 4470                      cnvlen > 0;){
 4471                   if(cnvdat != tbcp->tbc_cnv)
 4472                      putc(',', fp);
 4473 
 4474                   /* {si32_t buf_len_iscap;} */
 4475                   entlen = *n_UNALIGN(si32_t const*,cnvdat);
 4476                   if(entlen & SI32_MIN){
 4477                      /* struct{si32_t buf_len_iscap; si32_t cap_len;
 4478                       * char buf[]+NUL;} */
 4479                      for(bsep = n_empty,
 4480                               u.cp = (char const*)
 4481                                     &n_UNALIGN(si32_t const*,cnvdat)[2];
 4482                            (c.c = *u.cp) != '\0'; ++u.cp){
 4483                         if(asciichar(c.c) && !cntrlchar(c.c))
 4484                            cbuf[1] = c.c, cbufp = cbuf;
 4485                         else
 4486                            cbufp = n_empty;
 4487                         fprintf(fp, "%s%02X%s",
 4488                            bsep, (ui32_t)(ui8_t)c.c, cbufp);
 4489                         bsep = " ";
 4490                      }
 4491                      entlen &= SI32_MAX;
 4492                   }else
 4493                      putc('-', fp);
 4494 
 4495                   cnvlen -= entlen;
 4496                   cnvdat += entlen;
 4497                }
 4498 
 4499                fputs("\n  ", fp);
 4500                ++lns;
 4501             }
 4502 
 4503             fprintf(fp, "%sbind %s %s %s%s%s\n",
 4504                ((tbcp->tbc_flags & a_TTY_BIND_DEFUNCT)
 4505                /* I18N: `bind' sequence not working, either because it is
 4506                 * I18N: using Unicode and that is not available in the locale,
 4507                 * I18N: or a termcap(5)/terminfo(5) sequence won't work out */
 4508                   ? _("# <Defunctional> ") : n_empty),
 4509                a_tty_bind_ctx_maps[gif].tbcm_name, tbcp->tbc_seq,
 4510                n_shexp_quote_cp(tbcp->tbc_exp, TRU1),
 4511                (tbcp->tbc_flags & a_TTY_BIND_NOCOMMIT ? n_at : n_empty),
 4512                (!(n_poption & n_PO_D_VV) ? n_empty
 4513                   : (tbcp->tbc_flags & a_TTY_BIND_FUN_INTERNAL
 4514                      ? _(" # MLE internal") : n_empty))
 4515                );
 4516          }
 4517          if(!aster || ++gif >= n__GO_INPUT_CTX_MAX1)
 4518             break;
 4519       }
 4520       page_or_print(fp, lns);
 4521 
 4522       Fclose(fp);
 4523    }else{
 4524       struct a_tty_bind_parse_ctx tbpc;
 4525       struct n_cmd_arg *cap;
 4526 
 4527       memset(&tbpc, 0, sizeof tbpc);
 4528       tbpc.tbpc_cmd = cacp->cac_desc->cad_name;
 4529       tbpc.tbpc_in_seq = (cap = cacp->cac_arg->ca_next)->ca_arg.ca_str.s;
 4530       if((cap = cap->ca_next) != NULL){
 4531          tbpc.tbpc_exp.s = cap->ca_arg.ca_str.s;
 4532          tbpc.tbpc_exp.l = cap->ca_arg.ca_str.l;
 4533       }
 4534       tbpc.tbpc_flags = gif;
 4535       if(!a_tty_bind_create(&tbpc, TRU1))
 4536          v = NULL;
 4537    }
 4538 jleave:
 4539    NYD_LEAVE;
 4540    return (v != NULL) ? n_EXIT_OK : n_EXIT_ERR;
 4541 }
 4542 
 4543 FL int
 4544 c_unbind(void *v){
 4545    struct a_tty_bind_parse_ctx tbpc;
 4546    struct a_tty_bind_ctx *tbcp;
 4547    enum n_go_input_flags gif;
 4548    bool_t aster;
 4549    union {char const *cp; char *p;} c;
 4550    struct n_cmd_arg_ctx *cacp;
 4551    NYD_ENTER;
 4552 
 4553    cacp = v;
 4554    c.cp = cacp->cac_arg->ca_arg.ca_str.s;
 4555    aster = FAL0;
 4556 
 4557    if((gif = a_tty_bind_ctx_find(c.cp)) == (enum n_go_input_flags)-1){
 4558       if(!(aster = n_is_all_or_aster(c.cp))){
 4559          n_err(_("`unbind': invalid context: %s\n"), c.cp);
 4560          v = NULL;
 4561          goto jleave;
 4562       }
 4563       gif = 0;
 4564    }
 4565 
 4566    c.cp = cacp->cac_arg->ca_next->ca_arg.ca_str.s;
 4567 jredo:
 4568    if(n_is_all_or_aster(c.cp)){
 4569       while((tbcp = a_tty.tg_bind[gif]) != NULL){
 4570          memset(&tbpc, 0, sizeof tbpc);
 4571          tbpc.tbpc_tbcp = tbcp;
 4572          tbpc.tbpc_flags = gif;
 4573          a_tty_bind_del(&tbpc);
 4574       }
 4575    }else{
 4576       memset(&tbpc, 0, sizeof tbpc);
 4577       tbpc.tbpc_cmd = cacp->cac_desc->cad_name;
 4578       tbpc.tbpc_in_seq = c.cp;
 4579       tbpc.tbpc_flags = gif;
 4580 
 4581       if(n_UNLIKELY(!a_tty_bind_parse(FAL0, &tbpc)))
 4582          v = NULL;
 4583       else if(n_UNLIKELY((tbcp = tbpc.tbpc_tbcp) == NULL)){
 4584          n_err(_("`unbind': no such `bind'ing: %s  %s\n"),
 4585             a_tty_bind_ctx_maps[gif].tbcm_name, c.cp);
 4586          v = NULL;
 4587       }else
 4588          a_tty_bind_del(&tbpc);
 4589    }
 4590 
 4591    if(aster && ++gif < n__GO_INPUT_CTX_MAX1)
 4592       goto jredo;
 4593 jleave:
 4594    NYD_LEAVE;
 4595    return (v != NULL) ? n_EXIT_OK : n_EXIT_ERR;
 4596 }
 4597 # endif /* HAVE_KEY_BINDINGS */
 4598 
 4599 #else /* HAVE_MLE */
 4600 /*
 4601  * The really-nothing-at-all implementation
 4602  */
 4603 
 4604 static void
 4605 a_tty_signal(int sig){
 4606    /* Prototype at top */
 4607 # ifdef HAVE_TERMCAP
 4608    sigset_t nset, oset;
 4609 # endif
 4610    NYD_X; /* Signal handler */
 4611    n_UNUSED(sig);
 4612 
 4613 # ifdef HAVE_TERMCAP
 4614    n_TERMCAP_SUSPEND(TRU1);
 4615    a_tty_sigs_down();
 4616 
 4617    sigemptyset(&nset);
 4618    sigaddset(&nset, sig);
 4619    sigprocmask(SIG_UNBLOCK, &nset, &oset);
 4620    n_raise(sig);
 4621    /* When we come here we'll continue editing, so reestablish */
 4622    sigprocmask(SIG_BLOCK, &oset, (sigset_t*)NULL);
 4623 
 4624    a_tty_sigs_up();
 4625    n_TERMCAP_RESUME(TRU1);
 4626 # endif /* HAVE_TERMCAP */
 4627 }
 4628 
 4629 # if 0
 4630 FL void
 4631 n_tty_init(void){
 4632    NYD_ENTER;
 4633    NYD_LEAVE;
 4634 }
 4635 
 4636 FL void
 4637 n_tty_destroy(bool_t xit_fastpath){
 4638    NYD_ENTER;
 4639    n_UNUSED(xit_fastpath);
 4640    NYD_LEAVE;
 4641 }
 4642 # endif /* 0 */
 4643 
 4644 FL int
 4645 (n_tty_readline)(enum n_go_input_flags gif, char const *prompt,
 4646       char **linebuf, size_t *linesize, size_t n, bool_t *histok_or_null
 4647       n_MEMORY_DEBUG_ARGS){
 4648    struct n_string xprompt;
 4649    int rv;
 4650    NYD_ENTER;
 4651    n_UNUSED(histok_or_null);
 4652 
 4653    if(!(gif & n_GO_INPUT_PROMPT_NONE)){
 4654       if(n_tty_create_prompt(n_string_creat_auto(&xprompt), prompt, gif) > 0){
 4655          fwrite(xprompt.s_dat, 1, xprompt.s_len, n_tty_fp);
 4656          fflush(n_tty_fp);
 4657       }
 4658    }
 4659 
 4660 # ifdef HAVE_TERMCAP
 4661    a_tty_sigs_up();
 4662    n_TERMCAP_RESUME(FAL0);
 4663 # endif
 4664    rv = (readline_restart)(n_stdin, linebuf, linesize, n
 4665          n_MEMORY_DEBUG_ARGSCALL);
 4666 # ifdef HAVE_TERMCAP
 4667    n_TERMCAP_SUSPEND(FAL0);
 4668    a_tty_sigs_down();
 4669 # endif
 4670    NYD_LEAVE;
 4671    return rv;
 4672 }
 4673 
 4674 FL void
 4675 n_tty_addhist(char const *s, enum n_go_input_flags gif){
 4676    NYD_ENTER;
 4677    n_UNUSED(s);
 4678    n_UNUSED(gif);
 4679    NYD_LEAVE;
 4680 }
 4681 #endif /* nothing at all */
 4682 
 4683 #undef a_TTY_SIGNALS
 4684 /* s-it-mode */