"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.22/src/mx/tty-mle.c" (24 Feb 2021, 138523 Bytes) of package /linux/misc/s-nail-14.9.22.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-mle.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.9.21_vs_14.9.22.

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