"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.10/tty.c" (25 Mar 2018, 142713 Bytes) of package /linux/misc/s-nail-14.9.10.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 last Fossies "Diffs" side-by-side code changes report: 14.9.7_vs_14.9.8.

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