"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.11/tty.c" (8 Aug 2018, 144608 Bytes) of package /linux/misc/s-nail-14.9.11.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.10_vs_14.9.11.

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