From 8a3638c931e01d5e29ce0561ed4a231508964d05 Mon Sep 17 00:00:00 2001 From: "Steffen (Daode) Nurpmeso" Date: Thu, 14 Apr 2016 23:19:26 +0200 Subject: [PATCH] MLE: add `bind' and `unbind' etc... Add the new commands `bind' and `unbind' which allow users to create key mappings of their desire. This changeset has been decoupled from ([termcap.2] Add WANT_KEY_BINDINGS..) in order to be able to move it to after the [topic/wysh] topic branch, which added support for sh(1)ell-style argument quoting, something that is desirable for `bind', since backslash escape sequences are a vivid part of the key-sequences they use. If a part of a key-sequence starts with a colon : this will indicate a termcap(5) or terminfo(5) capability name that will be looked up via our termcap sauce. User given expansion strings are checked against some special builtin editor functions, e.g., "mle-go-home", i.e., offer access to our internal functionality via a multiplexer, accessible like that. If an expansion string ends with an at-sign @ then the expansion will not be auto-committed, but will instead be subject to further user editing. Rewrite the MLE input handling to work with an input tree, relatively identical to our terminal library. Aaah: how nice it is to have an event-driven mainloop that multiplexes around select(2) and has I/O monitors, timers etc., then KeyEvent and maybe even KeySequenceEvent, and, practically, and especially in comparison: for free. But that can't be helped. While here: - Add some MLE functions that were missing: hist-srch-fwd, snarf-word-fwd, paste. - Drop the TSTP, TTIN and TTOU signal condomes that are provided by callers of n_tty_readline() (collect.c, lex_input.c), which always longjmp(3)d away from the TTY code even for those signals, therefore also causing line content resets, which i always have hated. So whereas we don't have SysV signal handling yet (see introductional comment / roman in signal.c), temporarily use SysV signal behaviour around the read(2) that is the core of n_tty_readline(), and simply restart, the MLE even automatically repaints the -- unchanged. -- line content. GNU readline(3) needs an ^L, but doesn't loose the content. The nothing-at-all implementation at least doesn't bail. Yay. It is very expensive, but which will change with v15. - Also the usual tweaks, here and there. P.S.: it is getting large. "Normally" the MLE should be splitted into at least three parts, already: visual update handling, raw data and (processed) input handling, key-binding. --- cmd_tab.h | 4 + collect.c | 71 +- lex_input.c | 34 - mk-conf.sh | 2 +- nail.1 | 657 ++++++++++++---- nail.h | 14 +- nail.rc | 8 + nailfuns.h | 9 + tty.c | 2498 +++++++++++++++++++++++++++++++++++++++++++++++++---------- 9 files changed, 2631 insertions(+), 666 deletions(-) diff --git a/cmd_tab.h b/cmd_tab.h index f02d9c7e..2c155bb9 100644 --- a/cmd_tab.h +++ b/cmd_tab.h @@ -396,6 +396,10 @@ { "history", &c_history, (H | I | M | V | RAWLIST), 0, 1 DS(N_("[], or select from editor history")) }, + { "bind", &c_bind, (M | STRLIST), 1, 1000 + DS(N_("For (base), [] or bind [<:data:>]"))}, + { "unbind", &c_unbind, (M | STRLIST), 2, 2 + DS(N_("Un`bind' (\"*\" for all)")) }, { "netrc", &c_netrc, (M | RAWLIST), 0, 1 DS(N_("[], or the .netrc cache")) }, diff --git a/collect.c b/collect.c index 21a82a17..8acea549 100644 --- a/collect.c +++ b/collect.c @@ -44,13 +44,9 @@ static sighandler_type _coll_saveint; /* Previous SIGINT value */ static sighandler_type _coll_savehup; /* Previous SIGHUP value */ -static sighandler_type _coll_savetstp; /* Previous SIGTSTP value */ -static sighandler_type _coll_savettou; /* Previous SIGTTOU value */ -static sighandler_type _coll_savettin; /* Previous SIGTTIN value */ static FILE *_coll_fp; /* File for saving away */ static int volatile _coll_hadintr; /* Have seen one SIGINT so far */ static sigjmp_buf _coll_jmp; /* To get back to work */ -static int _coll_jmp_p; /* whether to long jump */ static sigjmp_buf _coll_abort; /* To end collection with error */ static sigjmp_buf _coll_pipejmp; /* On broken pipe */ @@ -91,9 +87,6 @@ static void mespipe(char *cmd); * error is encountered writing the message temporary */ static int forward(char *ms, FILE *fp, int f); -/* Print (continue) when continued after ^Z */ -static void collstop(int s); - /* On interrupt, come here to save the partial message in ~/dead.letter. * Then jump out of the collection loop */ static void _collint(int s); @@ -335,7 +328,7 @@ exwrite(char const *name, FILE *fp, int f) NYD_ENTER; if (f) { - printf("\"%s\" ", name); + printf("%s ", n_shell_quote_cp(name, FAL0)); fflush(stdout); } if ((of = Fopen(name, "a")) == NULL) { @@ -511,29 +504,6 @@ jleave: } static void -collstop(int s) -{ - sighandler_type old_action; - sigset_t nset; - NYD_X; /* Signal handler */ - - old_action = safe_signal(s, SIG_DFL); - - sigemptyset(&nset); - sigaddset(&nset, s); - sigprocmask(SIG_UNBLOCK, &nset, NULL); - n_raise(s); - sigprocmask(SIG_BLOCK, &nset, NULL); - - safe_signal(s, old_action); - if (_coll_jmp_p) { - _coll_jmp_p = 0; - _coll_hadintr = 0; - siglongjmp(_coll_jmp, 1); - } -} - -static void _collint(int s) { NYD_X; /* Signal handler */ @@ -662,11 +632,6 @@ collect(struct header *hp, int printheaders, struct message *mp, safe_signal(SIGINT, &_collint); if ((_coll_savehup = safe_signal(SIGHUP, SIG_IGN)) != SIG_IGN) safe_signal(SIGHUP, collhup); - /* TODO We do a lot of redundant signal handling, especially - * TODO with the command line editor(s); try to merge this */ - _coll_savetstp = safe_signal(SIGTSTP, collstop); - _coll_savettou = safe_signal(SIGTTOU, collstop); - _coll_savettin = safe_signal(SIGTTIN, collstop); if (sigsetjmp(_coll_abort, 1)) goto jerr; if (sigsetjmp(_coll_jmp, 1)) @@ -791,19 +756,17 @@ collect(struct header *hp, int printheaders, struct message *mp, } else { rewind(_coll_fp); mesedit('e', hp); - goto jcont; + /* As mandated by the Mail Reference Manual, print "(continue)" */ +jcont: + printf(_("(continue)\n")); + fflush(stdout); } } } else { /* Come here for printing the after-signal message. Duplicate messages * won't be printed because the write is aborted if we get a SIGTTOU */ - if (_coll_hadintr) { + if (_coll_hadintr) n_err(_("\n(Interrupt -- one more to kill letter)\n")); - } else { -jcont: - printf(_("(continue)\n")); - fflush(stdout); - } } /* We're done with -M or -m */ @@ -823,9 +786,7 @@ jcont: * All commands which come here are forbidden when sourcing! */ assert(_coll_hadintr || !(pstate & PS_SOURCING)); for (;;) { - _coll_jmp_p = 1; cnt = n_lex_input("", FAL0, &linebuf, &linesize, NULL); - _coll_jmp_p = 0; if (cnt < 0) { assert(!(pstate & PS_SOURCING)); @@ -881,13 +842,14 @@ jputline: case '!': /* Shell escape, send the balance of line to sh -c */ c_shell(linebuf + 2); + goto jcont; break; case ':': /* FALLTHRU */ case '_': /* Escape to command mode, but be nice! */ _execute_command(hp, linebuf + 2, cnt - 2); - goto jcont; + break; case '.': /* Simulate end of file on input */ goto jout; @@ -906,13 +868,13 @@ jputline: grab_headers(hp, GTO | GSUBJECT | GCC | GBCC, (ok_blook(bsdcompat) && ok_blook(bsdorder))); while (hp->h_to == NULL); - goto jcont; + break; case 'H': /* Grab extra headers */ do grab_headers(hp, GEXTRA, 0); while (check_from_and_sender(hp->h_from, hp->h_sender) == NULL); - goto jcont; + break; case 't': /* Add to the To list */ hp->h_to = cat(hp->h_to, @@ -968,15 +930,15 @@ jputline: } if (*cp == '!') { insertcommand(_coll_fp, cp + 1); - break; + goto jcont; } if ((cp = file_expand(cp)) == NULL) break; if (is_dir(cp)) { - n_err(_("\"%s\": Directory\n"), cp); + n_err(_("%s: Directory\n"), n_shell_quote_cp(cp, FAL0)); break; } - printf(_("\"%s\" "), cp); + printf(_("%s "), n_shell_quote_cp(cp, FAL0)); fflush(stdout); if (_include_file(cp, &lc, &cc, FAL0, (c == 'R')) != 0) goto jerr; @@ -1029,11 +991,11 @@ jputline: * don't shift over */ if (forward(linebuf + 2, _coll_fp, c) < 0) goto jerr; - goto jcont; + break; case 'p': /* Print current state of the message without altering anything */ print_collf(_coll_fp, hp); - goto jcont; + break; case '|': /* Pipe message through command. Collect output as new message */ rewind(_coll_fp); @@ -1179,9 +1141,6 @@ jleave: pstate &= ~PS_RECURSED; safe_signal(SIGINT, _coll_saveint); safe_signal(SIGHUP, _coll_savehup); - safe_signal(SIGTSTP, _coll_savetstp); - safe_signal(SIGTTOU, _coll_savettou); - safe_signal(SIGTTIN, _coll_savettin); sigprocmask(SIG_SETMASK, &oset, NULL); NYD_LEAVE; return _coll_fp; diff --git a/lex_input.c b/lex_input.c index d0dac114..c17b0920 100644 --- a/lex_input.c +++ b/lex_input.c @@ -92,7 +92,6 @@ struct a_lex_input_stack{ char li_name[VFIELD_SIZE(0)]; /* Name of file or macro */ }; -static bool_t a_lex_reset_on_stop; /* do a reset() if stopped */ static sighandler_type a_lex_oldpipe; static struct a_lex_ghost *a_lex_ghosts; /* a_lex_cmd_tab[] after fun protos */ @@ -133,9 +132,6 @@ static int a_lex_evaluate(struct a_lex_eval_ctx *evp); /* Get first-fit, or NULL */ static struct a_lex_cmd const *a_lex__firstfit(char const *comm); -/* When we wake up after ^Z, reprint the prompt */ -static void a_lex_stop(int s); - /* Branch here on hangup signal and simulate "exit" */ static void a_lex_hangup(int s); @@ -751,29 +747,6 @@ jleave: } static void -a_lex_stop(int s){ - sighandler_type old_action; - sigset_t nset; - NYD_X; /* Signal handler */ - - old_action = safe_signal(s, SIG_DFL); - - sigemptyset(&nset); - sigaddset(&nset, s); - sigprocmask(SIG_UNBLOCK, &nset, NULL); - n_raise(s); - sigprocmask(SIG_BLOCK, &nset, NULL); - - safe_signal(s, old_action); - - if(a_lex_reset_on_stop){ - a_lex_reset_on_stop = 0; - n_TERMCAP_RESUME(TRU1); - siglongjmp(srbuf, 0); /* FIXME get rid */ - } -} - -static void a_lex_hangup(int s){ NYD_X; /* Signal handler */ UNUSED(s); @@ -1074,11 +1047,6 @@ n_commands(void){ /* FIXME */ safe_signal(SIGINT, &a_lex_onintr); if (safe_signal(SIGHUP, SIG_IGN) != SIG_IGN) safe_signal(SIGHUP, &a_lex_hangup); - /* TODO We do a lot of redundant signal handling, especially - * TODO with the command line editor(s); try to merge this */ - safe_signal(SIGTSTP, &a_lex_stop); - safe_signal(SIGTTOU, &a_lex_stop); - safe_signal(SIGTTIN, &a_lex_stop); } a_lex_oldpipe = safe_signal(SIGPIPE, SIG_IGN); safe_signal(SIGPIPE, a_lex_oldpipe); @@ -1148,7 +1116,6 @@ n_commands(void){ /* FIXME */ } } - a_lex_reset_on_stop = TRU1; exit_status = EXIT_OK; } @@ -1159,7 +1126,6 @@ jreadline: ev.le_new_content); ev.le_line_size = (ui32_t)ev.le_line.l; ev.le_line.l = (ui32_t)n; - a_lex_reset_on_stop = FAL0; if (n < 0) { /* FIXME did unstack() when PS_SOURCING, only break with PS_LOADING*/ diff --git a/mk-conf.sh b/mk-conf.sh index 8461ab9b..1fc111f5 100644 --- a/mk-conf.sh +++ b/mk-conf.sh @@ -1344,7 +1344,7 @@ int main(void){ struct termios tios; tcgetattr(0, &tios); - tcsetattr(0, TCSADRAIN | TCSAFLUSH, &tios); + tcsetattr(0, TCSANOW | TCSADRAIN | TCSAFLUSH, &tios); return 0; } ! diff --git a/nail.1 b/nail.1 index 1265a3a4..f895c79f 100644 --- a/nail.1 +++ b/nail.1 @@ -2173,32 +2173,12 @@ and terminal control to improve interactive usage experience. For the former one may either link against an external library .Pf ( Xr readline 3 ; behaviour of \*(UA may differ slightly), -or enable the builtin Mailx-Line-Editor (MLE), which should work in all -environments which comply to the ISO C standard -.St -isoC-amd1 , -and which will support wide glyphs if possible (the necessary -functionality had been removed from ISO C, but was included in -.St -xpg4 ) . +or enable the builtin Mailx-Line-Editor (MLE). Prevent usage of a line editor in interactive mode by setting the internal variable .Va line-editor-disable . . .Pp -\*(OP If the -.Ic history -feature is available then input from line editor prompts will be saved -in a history list that can be searched in and be expanded from. -Such saving can be prevented by prefixing input with any amount of -whitespace. -Aspects of history, like allowed content, maximum size etc., can be -configured with the variables -.Va history-file , -.Va history-gabby , -.Va history-gabby-persist -and -.Va history-size . -. -.Pp \*(OP Terminal control will be realized through one of the standard .Ux libraries, either the @@ -2209,7 +2189,7 @@ both of which will be initialized to work with the environment variable .Ev TERM . Terminal control will enhance or enable interactive usage aspects, e.g., .Sx "Coloured display" , -and extend behaviour of the MLE, which may learn the key-sequences of +and extend behaviour of the MLE, which may learn the byte-sequences of keys like the cursor and function keys, and which will automatically enter the so-called .Dq ca-mode @@ -2219,121 +2199,227 @@ The internal variable can be used to overwrite settings or to learn (correct(ed)) keycodes. Actual interaction with the chosen library can be disabled completely by setting the internal variable -.Va termcap-disable , -which may be necessary for proper operation on the actual terminal; +.Va termcap-disable ; .Va termcap will be queried regardless, even if the \*(OPal support for the libraries has not been enabled at configuration time. . .Pp -\*(OP The builtin \*(UA line editor MLE supports the following -operations; the notation -.Ql ^-LETTER -stands for the combination of the -.Ql control -key plus the mentioned character, e.g., -.Ql ^A -means -.Dq hold down control key and press the A key . -Especially without termcap support setting respective entries in +\*(OP If the +.Ic history +feature is available then input from line editor prompts will be saved +in a history list that can be searched in and be expanded from. +Such saving can be prevented by prefixing input with any amount of +whitespace. +Aspects of history, like allowed content, maximum size etc., can be +configured with the variables +.Va history-file , +.Va history-gabby , +.Va history-gabby-persist +and +.Va history-size . +. +.Pp +\*(OP The builtin \*(UA line editor MLE should work in all environments +which comply to the ISO C standard +.St -isoC-amd1 , +and will support wide glyphs if possible (the necessary functionality +had been removed from ISO C, but was included in +.St -xpg4 ) . +Especially if the \*(OPal terminal control support is missing setting +entries in the internal variable .Va termcap -will help shall the MLE misbehave. -The MLE also supports several -.Ic colour -mappings. +will help shall the MLE misbehave, see there for more. +The MLE can support a little bit of +.Ic colour . +. +.Pp +The MLE supports a set of editing and control commands. +By default (as) many (as possible) of these will be assigned to a set of +single-letter control codes, which should work on any terminal. +If the \*(OPal +.Ic bind +command is available then the MLE commands can also be accessed freely +by assigning the command name, which is shown in parenthesis in the list +below, to any desired key-sequence, and the MLE will instead and also use +.Ic bind +to establish its builtin key bindings +(more of them if the \*(OPal terminal control is available), +an action which can then be suppressed completely by setting +.Va line-editor-no-defaults . +The following uses the +.Xr sh 1 Ns +ell-style quote notation that is documented in the introductional +section of +.Sx COMMANDS ; +combinations not mentioned either cause job control signals or don't +generate a (unique) keycode: +. . .Pp .Bl -tag -compact -width "Ql _M" -.It Ql ^A -Go to the start of the line. -.It Ql ^B -Move the cursor backward one character. -.It Ql ^D +.It Ql \ecA +Go to the start of the line +.Pf ( Cd mle-go-home ) . +. +.It Ql \ecB +Move the cursor backward one character +.Pf ( Cd mle-go-bwd ) . +. +.It Ql \ecD Forward delete the character under the cursor; quits \*(UA if used on the empty line unless the .Va ignoreeof -option is set. -.It Ql ^E -Go to the end of the line. -.It Ql ^F -Move the cursor forward one character. +option is set +.Pf ( Cd mle-del-fwd ) . . -.It Ql ^G +.It Ql \ecE +Go to the end of the line +.Pf ( Cd mle-go-end ) . +. +.It Ql \ecF +Move the cursor forward one character +.Pf ( Cd mle-go-fwd ) . +. +.It Ql \ecG Cancel current operation, full reset. If there is an active history search or tabulator expansion then this command will first reset that, reverting to the former line content; -thus a second reset is needed for a full reset in this case. -In all cases \*(UA will reset a possibly used multibyte character input -state machine. +thus a second reset is needed for a full reset in this case +.Pf ( Cd mle-reset ) . . -.It Ql ^H -The same as -.Dq backspace : -backward delete one character. +.It Ql \ecH +Backspace: backward delete one character +.Pf ( Cd mle-del-bwd ) . . -.It Ql ^I -\*(NQ The same as -.Dq horizontal tabulator : +.It Ql \ecI +\*(NQ +Horizontal tabulator: try to expand the word before the cursor, also supporting \*(UA .Ic file -expansions. -. -.It Ql ^J +expansions +.Pf ( Cd mle-complete ) . +This is affected by +.Cd mle-quote-rndtrip . +. +.It Ql \ecJ +Newline: +commit the current line +.Pf ( Cd mle-commit ) . +. +.It Ql \ecK +Cut all characters from the cursor to the end of the line +.Pf ( Cd mle-snarf-end ) . +. +.It Ql \ecL +Repaint the line +.Pf ( Cd mle-repaint ) . +. +.It Ql \ecN +\*(OP Go to the next history entry +.Pf ( Cd mle-hist-fwd ) . +. +.It Ql \ecO +Invokes the command +.Ql Ic dt . +. +.It Ql \ecP +\*(OP Go to the previous history entry +.Pf ( Cd mle-hist-bwd ) . +. +.It Ql \ecQ +Toggle roundtrip mode shell quotes, where produced, +on and off +.Pf ( Cd mle-quote-rndtrip ) . +This setting is temporary, and will be forgotten once the command line +is committed. +. +.It Ql \ecR +\*(OP Complete the current line from (the remaining) older history entries +.Pf ( Cd mle-hist-srch-bwd ) . +. +.It Ql \ecS +\*(OP Complete the current line from (the remaining) newer history entries +.Pf ( Cd mle-hist-srch-fwd ) . +. +.It Ql \ecT +Paste the snarf buffer +.Pf ( Cd mle-paste ) . +. +.It Ql \ecU The same as -.Dq ENTER : -complete this line of input. -.It Ql ^K -Delete all characters from the cursor to the end of the line. -.It Ql ^L -Repaint the line. -.It Ql ^N -\*(OP Go to the next history entry. -.It Ql ^O -Execute the command -.Ic dp . -.It Ql ^P -\*(OP Go to the previous history entry. -.It Ql ^R -\*(OP Complete the current line from (the remaining older) history entries. -.It Ql ^U -The same as -.Ql ^A +.Ql \ecA followed by -.Ql ^K . -.It Ql ^V -Prompts for a Unicode character to be inserted. -.It Ql ^W -Delete the characters from the one preceding the cursor to the preceding -word boundary. -.It Ql ^X -Move the cursor forward one word boundary. -.It Ql ^Y -Move the cursor backward one word boundary. -.El +.Ql \ecK +.Pf ( Cd mle-snarf-line ) . +. +.It Ql \ecV +Prompts for a Unicode character (its hexadecimal number) to be inserted +.Pf ( Cd mle-prompt-char ) . +Note this command needs to be assigned to a single-letter control code +in order to become recognized and executed during input of +a key-sequence (only four single-letter control codes can be used for +that shortcut purpose); this control code is special-treated and can't +be part of any other sequence, because any occurrence will perform the +.Cd mle-prompt-char +function immediately. +. +.It Ql \ecW +Cut the characters from the one preceding the cursor to the preceding +word boundary +.Pf ( Cd mle-snarf-word-bwd ) . . -.Pp -If the keycodes are known then the left and right cursor keys will map to -.Ql ^B -and -.Ql ^F , -respectively, the up and down cursor keys will map to -.Ql ^P -and -.Ql ^N , -and the Home/End/PgUp/PgDown keys will call the -.Ic z -command with the respective arguments -.Ql 0 , -.Ql $ , -.Ql - -and -.Ql + -(i.e., perform scrolling through the header summary list). -Also the up and down cursor keys should invoke -.Ic dotmove -for up- and downwards movement if they are used while the -.Ql control -key is held. +.It Ql \ecX +Move the cursor forward one word boundary +.Pf ( Cd mle-go-word-fwd ) . +. +.It Ql \ecY +Move the cursor backward one word boundary +.Pf ( Cd mle-go-word-bwd ) . +. +.It Ql \ec[ +Escape: reset a possibly used multibyte character input state machine +and \*(OPally a lingering, incomplete key binding +.Pf ( Cd mle-cancel ) . +This command needs to be assigned to a single-letter control code in +order to become recognized and executed during input of a key-sequence +(only four single-letter control codes can be used for that shortcut +purpose). +This control code may also be part of a multi-byte sequence, but if +a sequence is active and the very control code is currently also an +expected input, then it will first be consumed by the active sequence. +. +.It Ql \ec\e +Invokes the command +.Ql Ic z Ns + . +. +.It Ql \ec] +Invokes the command +.Ql Ic z Ns $ . +. +.It Ql \ec^ +Invokes the command +.Ql Ic z Ns 0 . +. +.It Ql \ec_ +Cut the characters from the one after the cursor to the succeeding word +boundary +.Pf ( Cd mle-snarf-word-fwd ) . +. +.It Ql \ec? +Backspace: +.Cd mle-del-bwd . +. +.It \(en +.Cd mle-fullreset : +in difference to +.Cd mle-reset +this will immediately reset a possibly active search etc. +. +.It \(en +.Cd mle-bell : +ring the audible bell. +.El .\" }}} . .\" .Ss "Coloured display" {{{ @@ -2823,6 +2909,201 @@ This mark has no technical meaning in the mail system; it just causes messages to be marked in the header summary, and makes them specially addressable. . +. +.Mx +.It Ic bind +\*(OP\*(NQ The bind command extends the MLE (see +.Sx "On terminal control and line editor" ) +with freely configurable key bindings. +With one argument all bindings for the given context are shown, +specifying an asterisk +.Ql * +will show the bindings of all contexts; a more verbose listing will be +produced if either of +.Va debug +or +.Va verbose +are set. +With two or more arguments a binding is (re)established: +the first argument is the context to which the binding shall apply, +the second argument is a comma-separated list of the +.Dq keys +which form the binding, and any remaining arguments form the expansion. +To indicate that a binding shall not be auto-committed, but that the +expansion shall instead be furtherly editable by the user, an at-sign +.Ql @ +(that will be removed) can be placed last in the expansion, from which +leading and trailing whitespace will finally be removed. +. +.Pp +Contexts define when a binding applies, i.e., a binding won't be seen +unless the context for which it is defined for is currently active. +This is not true for the binding +.Ql base , +which always applies, but which will be searched secondarily to a more +specialized context and may thus have some or all of its key bindings +transparently replaced by equal bindings of more specialized contexts. +The available contexts are +.Ql base , +which always applies, and +.Ql compose , +which applies to compose-mode. +. +.Pp +.Dq Keys +which form the binding are specified as a comma-separated list of +byte-sequences, where each list entry corresponds to one key(press). +\*(OPally a list entry may, indicated by a leading colon character +.Ql \&: , +also refer to the name of a terminal capability; +several dozen names will be compiled into \*(UA and may be specified +either by their +.Xr terminfo 5 , +or, if existing, by their +.Xr termcap 5 +name, regardless of the actually used terminal control library. +It is however possible to use any capability, as long as the name is +resolvable by the control library or defined in the internal variable +.Va termcap . +Input sequences are not case-normalized, so that an exact match is +required to update or remove a binding. +Examples: +. +.Bd -literal -offset indent +bind base $'\eE',d mle-snarf-word-fwd # Esc(ape) +bind base $'\eE',$'\ec?' mle-snarf-word-bwd # Esc, Delete +bind compose :kf1 ~e +bind base $'\ecA',:khome,w 'echo An editable binding@' +bind base a,b,c rm -rf / @ # Another editable binding +.Ed +. +.Pp +Note that the entire comma-separated list is first parsed (over) as a +shell-token with whitespace as the field separator, before being parsed +and expanded for real with comma as the field separator, therefore +whitespace needs to be properly quoted: +shell-style quoting is documented in the introductional section of +.Sx COMMANDS . +Using Unicode reverse solidus escape sequences renders a binding +defunctional if the locale doesn't support Unicode (see +.Sx "Character sets" ) , +and using terminal capabilities does so if no terminal control support +is (currently) available. +. +.Pp +The following terminal capability names are builtin and can be used in +.Xr terminfo 5 +or (if available) the two-letter +.Xr termcap 5 +notation regardless of the actually used library. +See the respective manual for a list of capabilities. +The program +.Xr infocmp 1 +can be used to show all the capabilities of +.Ev $TERM +or the given terminal type; +using the +.Fl \&\&x +flag will also show supported (non-standard) extensions. +. +.Pp +.Bl -tag -compact -width kcuuf_or_kcuuf +.It Cd kbs Ns \0or Cd kb +Backspace. +.It Cd kdch1 Ns \0or Cd kD +Delete character. +.It Cd kDC Ns \0or Cd *4 +\(em shifted variant. +.It Cd kel Ns \0or Cd kE +Clear to end of line. +.It Cd kext Ns \0or Cd @9 +Exit. +.It Cd kich1 Ns \0or Cd kI +Insert character. +.It Cd kIC Ns \0or Cd #3 +\(em shifted variant. +.It Cd khome Ns \0or Cd kh +Home. +.It Cd kHOM Ns \0or Cd #2 +\(em shifted variant. +.It Cd kend Ns \0or Cd @7 +End. +.It Cd knp Ns \0or Cd kN +Next page. +.It Cd kpp Ns \0or Cd kP +Previous page. +.It Cd kcub1 Ns \0or Cd kl +Left cursor (with more modifiers: see below). +.It Cd kLFT Ns \0or Cd #4 +\(em shifted variant. +.It Cd kcuf1 Ns \0or Cd kr +Right cursor (ditto). +.It Cd kRIT Ns \0or Cd %i +\(em shifted variant. +.It Cd kcud1 Ns \0or Cd kd +Down cursor (ditto). +.It Cd kDN +\(em shifted variant (only terminfo). +.It Cd kcuu1 Ns \0or Cd ku +Up cursor (ditto). +.It Cd kUP +\(em shifted variant (only terminfo). +.It Cd kf0 Ns \0or Cd k0 +Function key 0. +Add one for each function key up to +.Cd kf9 +and +.Cd k9 , +respectively. +.It Cd kf10 Ns \0or Cd k; +Function key 10. +.It Cd kf11 Ns \0or Cd F1 +Function key 11. +Add one for each function key up to +.Cd kf19 +and +.Cd F9 , +respectively. +.El +. +.Pp +Some terminals support key-modifier combination extensions, e.g., +.Ql Alt+Shift+xy . +For example, the delete key, +.Cd kdch1 : +in its shifted variant, the name is mutated to +.Cd kDC , +then a number is appended for the states +.Ql Alt +.Pf ( Cd kDC3 ) , +.Ql Shift+Alt +.Pf ( Cd kDC4 ) , +.Ql Control +.Pf ( Cd kDC5 ) , +.Ql Shift+Control +.Pf ( Cd kDC6 ) , +.Ql Alt+Control +.Pf ( Cd kDC7 ) , +finally +.Ql Shift+Alt+Control +.Pf ( Cd kDC8 ) . +The same for the left cursor key, +.Cd kcub1 : +.Cd KLFT , KLFT3 , KLFT4 , KLFT5 , KLFT6 , KLFT7 , KLFT8 . +. +.Pp +Key bindings can be removed with the command +.Ic unbind . +It is advisable to use an initial escape or other control character (e.g., +.Ql \ecA ) +for bindings which describe user key combinations (as opposed to purely +terminal capability based ones), in order to avoid ambiguities whether +input belongs to key sequences or not; it also reduces search time. +Adjusting +.Va bind-timeout +may help shall keys and sequences be falsely recognized. +. +. .Mx .It Ic call Calls a macro that has been created via @@ -4609,6 +4890,17 @@ will discard all existing aliases. Takes a message list and marks each message as not having been answered. . .Mx +.It Ic unbind +Forget about a key +.Ic bind Ns +ing, specified by its context and input sequence, both of which may be +specified as a wildcard (asterisk, +.Ql * ) , +e.g., +.Ql unbind * * +will remove all bindings of all contexts. +. +.Mx .It Ic uncollapse Only applicable to threaded mode. Takes a message list and makes the message and all replies to it visible @@ -5582,6 +5874,18 @@ completes one operation (returns to the command prompt); if it is set then \*(UA will terminate if the last operation generated an error. . .Mx +.It Va bind-timeout +\*(OP Terminals generate multi-byte sequences for certain forms of +input, for example for function and other special keys. +Some terminals however do not write these multi-byte sequences as +a whole, but byte-by-byte, and the latter is what \*(UA actually reads. +This variable specifies the timeout in milliseconds that the MLE (see +.Sx "On terminal control and line editor" ) +waits for more bytes to arrive unless it considers a sequence +.Dq complete . +The default is 200. +. +.Mx .It Va bsdannounce \*(BO Causes automatic display of a header summary after executing a .Ic file @@ -6433,6 +6737,10 @@ Setting this option causes all saved message to be retained. for more). . .Mx +.It Va line-editor-no-defaults +\*(BO\*(OP Do not establish any default key binding. +. +.Mx .It Va markanswered \*(BO When a message is replied to and this variable is set, it is marked as having been answered. @@ -8121,49 +8429,31 @@ Numerics have to be notated as where the number is expected in normal decimal notation. Finally, booleans don't have any value but indicate a true or false state simply by being defined or not; this indeed means that \*(UA -doesn't support undefining a boolean that normally exists. -The following example defines that the terminal has 256 colours: +doesn't support undefining an existing boolean. +String capability values will undergo some expansions before use: +for one notations like +.Ql ^LETTER +stand for +.Ql control-LETTER , +and for clarification purposes +.Ql \eE +can be used to specify +.Ql escape +(the control notation +.Ql ^[ +could lead to misreadings when a left bracket follows, which it does for +the standard CSI sequence); +finally three letter octal sequences, as in +.Ql \e061 , +are supported. +To specify that a terminal supports 256-colours, and to define sequences +that home the cursor and produce an audible bell, one might write: . .Bd -literal -offset indent -set termcap="colors=256" +set termcap='Co#256,home=\eE[H,bel=^G' .Ed . .Pp -Keycodes can easily be detected with the command -.Xr cat 1 -by running it on an interactive terminal via -.Ql $ cat -(using the -.Fl \&\&v -command line option if available) and pressing some keys: here -.Ql ^[ -will stand for -.Ql escape -and -.Ql ^LETTER -for -.Ql control-LETTER -(actually a visualized numeric where -.Ql A -stands for 1 etc.; in fact: the numeric value of -.Ql LETTER -in the US-ASCII character set bitwise XORd with -.Ql 0x40 , -e.g., -.Ql $ echo $((0x41 ^ 0x40)) . -Note that -.Ql escape -and other control characters have to be notated as shell-style -escape sequences, e.g., -.Ql \e033 -for (US-ASCII) -.Ql escape -and -.Ql \e001 -for -.Ql ^A . -. -.Pp The following terminal capabilities are or may be meaningful for the operation of the builtin line editor or \*(UA in general: . @@ -8174,8 +8464,7 @@ operation of the builtin line editor or \*(UA in general: .Cd max_colors : numeric capability specifying the maximum number of colours. Note that \*(UA doesn't actually care about the terminal beside that, -but always emits ANSI/ISO 6429 escape sequences for producing the -colour and font attributes. +but always emits ANSI / ISO 6429 escape sequences. . .\" HAVE_TERMCAP .It Cd rmcup Ns \0or Cd te Ns \0/ Cd smcup Ns \0or Cd ti @@ -8244,14 +8533,17 @@ The default builtin fallback is .Cd cursor_right : move the cursor right one space (non-destructively). The default builtin fallback is -.Ql \e033[C , +.Ql \eE[C , which is used by most terminals. Less often occur -.Ql \e033OC +.Ql \eEC and -.Ql \e014 . +.Ql \eEOC . .El . +.Pp +Many more capabilities which describe key-sequences are documented for +.Ic bind . . .Mx .It Va termcap-disable @@ -9840,6 +10132,16 @@ and .\" .Sh "FAQ" {{{ .Sh "FAQ" . +In general it is a good idea to turn on +.Va debug +.Pf ( Fl d ) +and / or +.Va verbose +.Pf ( Fl v , +twice) if something doesn't work well. +Very often a diagnostic message can be produced that leads to the +problems' solution. +. .\" .Ss "\*(UA shortly hangs on startup" {{{ .Ss "\*(UA shortly hangs on startup" . @@ -9903,6 +10205,47 @@ use that special password instead of your real Google account password in .Sx "On URL syntax and credential lookup" ) . .El .\" }}} +. +.\" .Ss "Not \(dqdefunctional\(dq, but the editor key won't work" {{{ +.Ss "Not \(dqdefunctional\(dq, but the editor key won't work" +. +It can happen that the terminal library (see +.Sx "On terminal control and line editor", +.Ic bind , +.Va termcap ) +reports different codes than the terminal really sends, in which case +\*(UA will tell that a key binding is functional, but won't be able to +recognize it because the received data doesn't match anything expected. +The verbose listing of +.Ic bind Ns +ings will show the byte sequences that are expected. +. +.Pp +To overcome the situation, use, e.g., the program +.Xr cat 1 , +in conjunction with the +.Fl \&\&v +flag if available, to see the byte sequences which are actually produced +by keypresses, and use the variable +.Va termcap +to make \*(UA aware of them. +E.g., the terminal this is typed on produces some false sequences, here +an example showing the shifted home key: +. +.Bd -literal -offset indent +? set verbose +? bind* +# 1B 5B=[ 31=1 3B=; 32=2 48=H + bind base :kHOM z0 +? x +$ cat -v +^[[H +? \*(uA -v -Stermcap='kHOM=\eE[H' +? bind* +# 1B 5B=[ 48=H + bind base :kHOM z0 +.Ed +.\" }}} .\" }}} . . diff --git a/nail.h b/nail.h index 2b2fe8fd..df183ebf 100644 --- a/nail.h +++ b/nail.h @@ -891,6 +891,16 @@ enum n_file_lock_type{ FLT_WRITE }; +enum n_lexinput_flags{ + n_LEXINPUT_NONE, + n_LEXINPUT_CTX_BASE = 0, /* Input context (`bind' etc.): fallback */ + n_LEXINPUT_CTX_COMPOSE = 1, + n__LEXINPUT_CTX_MASK = 1, + n__LEXINPUT_CTX_MAX = n_LEXINPUT_CTX_COMPOSE + 1, + n_LEXINPUT_HIST_ADD = 1<<8, /* Add the result to history list */ + n_LEXINPUT_HIST_GABBY = 1<<9 /* Consider this history entry as gabby */ +}; + enum mimecontent { MIME_UNKNOWN, /* unknown content */ MIME_SUBHDR, /* inside a multipart subheader */ @@ -1379,7 +1389,7 @@ enum program_state { PS_t_FLAG = 1<<26, /* OPT_t_FLAG made persistant */ PS_TERMCAP_DISABLE = 1<<27, /* HAVE_TERMCAP: *termcap-disable* was set */ PS_TERMCAP_CA_MODE = 1<<28, /* HAVE_TERMCAP: ca_mode available & used */ - PS_HISTORY_LOADED = 1<<29 /* Command line editor history loaded */ + PS_LINE_EDITOR_INIT = 1<<29 /* MLE is initialized */ }; /* A large enum with all the boolean and value options a.k.a their keys. @@ -1429,6 +1439,7 @@ ok_b_autothread, ok_b_bang, ok_b_batch_exit_on_error, + ok_v_bind_timeout, /* {notempty=1,posnum=1} */ ok_b_bsdannounce, ok_b_bsdcompat, ok_b_bsdflags, @@ -1500,6 +1511,7 @@ ok_b_emptybox, ok_v_LISTER, /* {env=1,defval=VAL_LISTER} */ ok_v_LOGNAME, /* {rdonly=1,import=1} */ ok_b_line_editor_disable, + ok_b_line_editor_no_defaults, ok_v_MAIL, /* {env=1} */ ok_v_MAILRC, /* {import=1,defval=VAL_MAILRC} */ diff --git a/nail.rc b/nail.rc index b5f2ecbb..0068e317 100644 --- a/nail.rc +++ b/nail.rc @@ -181,6 +181,14 @@ retain from_ date from to cc subject message-id mail-followup-to reply-to ## Gimmicks +# More key bindings for the Mailx-Line-Editor (when in interactive mode) +#if terminal && $features =@ +mle && $features =@ +key-bindings +# bind base $'\e',d mle-snarf-word-fwd +# bind base $'\e',$'\c?' mle-snarf-word-bwd +# bind base $'\e',f mle-go-word-fwd +# bind base $'\e',b mle-go-word-bwd +#endif + # Coloured prompt for the Mailx-Line-Editor (when in interactive mode) #if terminal && $features =@ +mle && $features =@ +colour # colour 256 mle-position fg=202 diff --git a/nailfuns.h b/nailfuns.h index 6d52c804..5325f716 100644 --- a/nailfuns.h +++ b/nailfuns.h @@ -2300,6 +2300,15 @@ FL int c_history(void *v); # define c_history c_cmdnotsupp #endif +/* `bind' and `unbind' */ +#ifdef HAVE_KEY_BINDINGS +FL int c_bind(void *v); +FL int c_unbind(void *v); +#else +# define c_bind c_cmdnotsupp +# define c_unbind c_cmdnotsupp +#endif + /* * ui_str.c */ diff --git a/tty.c b/tty.c index 1afbdf19..6211742c 100644 --- a/tty.c +++ b/tty.c @@ -84,6 +84,8 @@ do{\ long entry;\ NYD_ENTER;\ \ + if(!(pstate & PS_LINE_EDITOR_INIT))\ + goto jleave;\ if(*argv == NULL)\ goto jlist;\ if(argv[1] != NULL)\ @@ -342,6 +344,8 @@ n_tty_init(void){ if(v != NULL) read_history(v); # endif + + pstate |= PS_LINE_EDITOR_INIT; NYD_LEAVE; } @@ -357,6 +361,8 @@ n_tty_destroy(void){ if(v != NULL) write_history(v); # endif + + DBG( pstate &= ~PS_LINE_EDITOR_INIT; ) NYD_LEAVE; } @@ -392,8 +398,7 @@ n_tty_signal(int sig){ n_TERMCAP_RESUME(TRU1); if(sig == SIGHUP) rl_reset_after_signal(); - break; - } + } break; } } @@ -404,6 +409,8 @@ FL int char *line; NYD_ENTER; + assert(pstate & PS_LINE_EDITOR_INIT); + if(n > 0){ a_tty_rl_buf = *linebuf; a_tty_rl_buflen = (int)n; @@ -442,7 +449,7 @@ n_tty_addhist(char const *s, bool_t isgabby){ UNUSED(isgabby); # ifdef HAVE_HISTORY - if(isgabby && !ok_blook(history_gabby)) + if(!(pstate & PS_LINE_EDITOR_INIT) || (isgabby && !ok_blook(history_gabby))) goto jleave; a_TTY_CHECK_ADDHIST(s, goto jleave); hold_all_sigs(); /* XXX too heavy */ @@ -504,20 +511,33 @@ jentry:{ /* * MLE: the Mailx-Line-Editor, our homebrew editor - * (inspired from NetBSD sh(1) / dash(1)s hetio.c). + * (inspired from NetBSDs sh(1) and dash(1)s hetio.c). * * Only used in interactive mode, simply use STDIN_FILENO as point of interest. + * TODO . This code should be splitted in funs/raw input/bind modules. * TODO . After I/O layer rewrite, also "output to STDIN_FILENO". * TODO . We work with wide characters, but not for buffer takeovers and * TODO cell2save()ings. This should be changed. For the former the buffer * TODO thus needs to be converted to wide first, and then simply be fed in. - * TODO . No BIDI support. * TODO . We repaint too much. To overcome this use the same approach that my * TODO terminal library uses, add a true "virtual screen line" that stores * TODO the actually visible content, keep a notion of "first modified slot" * TODO and "last modified slot" (including "unknown" and "any" specials), * TODO update that virtual instead, then synchronize what has truly changed. * TODO I.e., add an indirection layer. + * TODO . No BIDI support. + * TODO . `bind': we currently use only one lookup tree. + * TODO For absolute graceful behaviour in conjunction (with HAVE_TERMCAP) we + * TODO need a lower level tree, which possibly combines bytes into "symbolic + * TODO wchar_t values", into "keys" that is, as applicable, and an upper + * TODO layer which only works on "keys" in order to possibly combine them + * TODO into key sequences. We can reuse existent tree code for that. + * TODO We need an additional hashmap which maps termcap/terminfo names to + * TODO (their byte representations and) a dynamically assigned unique + * TODO "symbolic wchar_t value". This implies we may have incompatibilities + * TODO when __STDC_ISO_10646__ is not defined. Also we do need takeover- + * TODO bytes storage, but it can be a string_creat_auto in the line struct. + * TODO Until then we can run into ambiguities; in rare occasions. */ #ifdef HAVE_MLE /* To avoid memory leaks etc. with the current codebase that simply longjmp(3)s @@ -526,6 +546,30 @@ jentry:{ * convert that on-the-fly back to the plain char* result once we're done. * To simplify our live, use savestr() buffers for all other needed memory */ +# ifdef HAVE_KEY_BINDINGS +/* Default *bind-timeout* key-sequence continuation timeout, in tenths of + * a second. Must fit in 8-bit! Update the manual upon change! */ +# define a_TTY_BIND_TIMEOUT 2 +# define a_TTY_BIND_TIMEOUT_MAX SI8_MAX + +n_CTAV(a_TTY_BIND_TIMEOUT_MAX <= UI8_MAX); + +/* We have a chicken-and-egg problem with `bind' and our termcap layer, + * because we may not initialize the latter automatically to allow users to + * specify *termcap-disable* and let it mean exactly that. + * On the other hand users can be expected to use `bind' in resource file(s). + * Therefore bindings which involve termcap/terminfo sequences, and which are + * defined before PS_STARTED signals usability of termcap/terminfo, will be + * (partially) delayed until tty_init() is called. + * And we preallocate space for the expansion of the resolved capability */ +# define a_TTY_BIND_CAPNAME_MAX 15 +# define a_TTY_BIND_CAPEXP_ROUNDUP 16 + +n_CTAV(ISPOW2(a_TTY_BIND_CAPEXP_ROUNDUP)); +n_CTA(a_TTY_BIND_CAPEXP_ROUNDUP <= SI8_MAX / 2, "Variable must fit in 6-bit"); +n_CTA(a_TTY_BIND_CAPEXP_ROUNDUP >= 8, "Variable too small"); +# endif /* HAVE_KEY_BINDINGS */ + /* The maximum size (of a_tty_cell's) in a line */ # define a_TTY_LINE_MAX SI32_MAX @@ -545,6 +589,76 @@ n_CTA(a_TTY_LINE_MAX <= SI32_MAX, * that we place (if there is sufficient space) */ # define a_TTY_WIDTH_RIPOFF 5 +/* The implementation of the MLE functions always exists, and is based upon + * the a_TTY_BIND_FUN_* constants, so most of this enum is always necessary */ +enum a_tty_bind_flags{ +# ifdef HAVE_KEY_BINDINGS + a_TTY_BIND_RESOLVE = 1<<8, /* Term cap. yet needs to be resolved */ + a_TTY_BIND_DEFUNCT = 1<<9, /* Unicode/term cap. used but not avail. */ + a_TTY__BIND_MASK = a_TTY_BIND_RESOLVE | a_TTY_BIND_DEFUNCT, + /* MLE fun assigned to a one-byte-sequence: this may be used for special + * key-sequence bypass processing */ + a_TTY_BIND_MLE1CNTRL = 1<<10, + a_TTY_BIND_NOCOMMIT = 1<<11, /* Expansion shall be editable */ +# endif + + /* MLE internal commands */ + a_TTY_BIND_FUN_INTERNAL = 1<<15, + a_TTY__BIND_FUN_SHIFT = 16, + a_TTY__BIND_FUN_SHIFTMAX = 24, + a_TTY__BIND_FUN_MASK = ((1 << a_TTY__BIND_FUN_SHIFTMAX) - 1) & + ~((1 << a_TTY__BIND_FUN_SHIFT) - 1), +# define a_TTY_BIND_FUN_REDUCE(X) \ + (((ui32_t)(X) & a_TTY__BIND_FUN_MASK) >> a_TTY__BIND_FUN_SHIFT) +# define a_TTY_BIND_FUN_EXPAND(X) \ + (((ui32_t)(X) & (a_TTY__BIND_FUN_MASK >> a_TTY__BIND_FUN_SHIFT)) << \ + a_TTY__BIND_FUN_SHIFT) +# undef a_X +# define a_X(N,I)\ + a_TTY_BIND_FUN_ ## N = a_TTY_BIND_FUN_EXPAND(I), + + a_X(BELL, 0) + a_X(GO_BWD, 1) a_X(GO_FWD, 2) + a_X(GO_WORD_BWD, 3) a_X(GO_WORD_FWD, 4) + a_X(GO_HOME, 5) a_X(GO_END, 6) + a_X(DEL_BWD, 7) a_X(DEL_FWD, 8) + a_X(SNARF_WORD_BWD, 9) a_X(SNARF_WORD_FWD, 10) + a_X(SNARF_END, 11) a_X(SNARF_LINE, 12) + a_X(HIST_BWD, 13) a_X(HIST_FWD, 14) + a_X(HIST_SRCH_BWD, 15) a_X(HIST_SRCH_FWD, 16) + a_X(REPAINT, 17) + a_X(QUOTE_RNDTRIP, 18) + a_X(PROMPT_CHAR, 19) + a_X(COMPLETE, 20) + a_X(PASTE, 21) + + a_X(CANCEL, 22) + a_X(RESET, 23) + a_X(FULLRESET, 24) + a_X(COMMIT, 25) /* Must be last one! */ +# undef a_X + + a_TTY__BIND_LAST = 1<<25 +}; +# ifdef HAVE_KEY_BINDINGS +n_CTA((ui32_t)a_TTY_BIND_RESOLVE > (ui32_t)n__LEXINPUT_CTX_MAX, + "Bit carrier lower boundary must be raised to avoid value sharing"); +# endif +n_CTA(a_TTY_BIND_FUN_EXPAND(a_TTY_BIND_FUN_COMMIT) < + (1 << a_TTY__BIND_FUN_SHIFTMAX), + "Bit carrier range must be expanded to represent necessary bits"); +n_CTA(a_TTY__BIND_LAST >= (1 << a_TTY__BIND_FUN_SHIFTMAX), + "Bit carrier upper boundary must be raised to avoid value sharing"); +n_CTA(UICMP(64, a_TTY__BIND_LAST, <=, SI32_MAX), + "Flag bits excess storage datatype" /* And we need one bit free */); + +enum a_tty_fun_status{ + a_TTY_FUN_STATUS_OK, /* Worked, next character */ + a_TTY_FUN_STATUS_COMMIT, /* Line done */ + a_TTY_FUN_STATUS_RESTART, /* Complete restart, reset multibyte etc. */ + a_TTY_FUN_STATUS_END /* End, return EOF */ +}; + enum a_tty_visual_flags{ a_TTY_VF_NONE, a_TTY_VF_MOD_CURSOR = 1<<0, /* Cursor moved */ @@ -560,30 +674,126 @@ enum a_tty_visual_flags{ a_TTY__VF_LAST = a_TTY_VF_SYNC }; +# ifdef HAVE_KEY_BINDINGS +struct a_tty_bind_ctx{ + struct a_tty_bind_ctx *tbc_next; + char *tbc_seq; /* quence as given (poss. re-quoted), in .tb__buf */ + char *tbc_exp; /* ansion, in .tb__buf */ + /* The .tbc_seq'uence with any terminal capabilities resolved; in fact an + * array of structures, the first entry of which is {si32_t buf_len_iscap;} + * where the signed bit indicates whether the buffer is a resolved terminal + * capability instead of a (possibly multibyte) character. In .tbc__buf */ + char *tbc_cnv; + ui32_t tbc_seq_len; + ui32_t tbc_exp_len; + ui32_t tbc_cnv_len; + ui32_t tbc_flags; + char tbc__buf[VFIELD_SIZE(0)]; +}; + +struct a_tty_bind_ctx_map{ + enum n_lexinput_flags tbcm_ctx; + char const tbcm_name[12]; /* Name of `bind' context */ +}; +# endif /* HAVE_KEY_BINDINGS */ + +struct a_tty_bind_default_tuple{ + bool_t tbdt_iskey; /* Whether this is a control key; else termcap query */ + char tbdt_ckey; /* Control code */ + ui16_t tbdt_query; /* enum n_termcap_query (instead) */ + char tbdt_exp[12]; /* String or [0]=NUL/[1]=BIND_FUN_REDUCE() */ +}; +n_CTA(n__TERMCAP_QUERY_MAX <= UI16_MAX, + "Enumeration cannot be stored in datatype"); + +# ifdef HAVE_KEY_BINDINGS +struct a_tty_bind_parse_ctx{ + char const *tbpc_cmd; /* Command which parses */ + char const *tbpc_in_seq; /* In: key sequence */ + struct str tbpc_exp; /* In/Out: expansion (or NULL) */ + struct a_tty_bind_ctx *tbpc_tbcp; /* Out: if yet existent */ + struct a_tty_bind_ctx *tbpc_ltbcp; /* Out: the one before .tbpc_tbcp */ + char *tbpc_seq; /* Out: normalized sequence */ + char *tbpc_cnv; /* Out: sequence when read(2)ing it */ + ui32_t tbpc_seq_len; + ui32_t tbpc_cnv_len; + ui32_t tbpc_flags; /* n_lexinput_flags | a_tty_bind_flags */ + ui8_t tbpc__dummy[4]; +}; + +/* Input character tree */ +struct a_tty_bind_tree{ + struct a_tty_bind_tree *tbt_sibling; /* s at same level */ + struct a_tty_bind_tree *tbt_childs; /* Sequence continues.. here */ + struct a_tty_bind_tree *tbt_parent; + struct a_tty_bind_ctx *tbt_bind; /* NULL for intermediates */ + wchar_t tbt_char; /* acter this level represents */ + bool_t tbt_isseq; /* Belongs to multibyte sequence */ + bool_t tbt_isseq_trail; /* ..is trailing byte of it */ + ui8_t tbt__dummy[2]; +}; +# endif /* HAVE_KEY_BINDINGS */ + +struct a_tty_cell{ + wchar_t tc_wc; + ui16_t tc_count; /* ..of bytes */ + ui8_t tc_width; /* Visual width; TAB==UI8_MAX! */ + bool_t tc_novis; /* Don't display visually as such (control character) */ + char tc_cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */ +}; + struct a_tty_global{ + struct a_tty_line *tg_line; /* To be able to access it from signal hdl */ # ifdef HAVE_HISTORY struct a_tty_hist *tg_hist; struct a_tty_hist *tg_hist_tail; size_t tg_hist_size; size_t tg_hist_size_max; # endif +# ifdef HAVE_KEY_BINDINGS + ui32_t tg_bind_cnt; /* Overall number of bindings */ + bool_t tg_bind_isdirty; + bool_t tg_bind_isbuild; + char tg_bind_shcut_cancel[n__LEXINPUT_CTX_MAX][5]; + char tg_bind_shcut_prompt_char[n__LEXINPUT_CTX_MAX][5]; + struct a_tty_bind_ctx *tg_bind[n__LEXINPUT_CTX_MAX]; + struct a_tty_bind_tree *tg_bind_tree[n__LEXINPUT_CTX_MAX][HSHSIZE]; +# endif struct termios tg_tios_old; struct termios tg_tios_new; }; +n_CTA(n__LEXINPUT_CTX_MAX == 2, + "Value results in array sizes that results in bad structure layout"); -struct a_tty_cell{ - wchar_t tc_wc; - ui16_t tc_count; /* ..of bytes */ - ui8_t tc_width; /* Visual width; TAB==UI8_MAX! */ - bool_t tc_novis; /* Don't display visually as such (control character) */ - char tc_cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */ +# ifdef HAVE_HISTORY +struct a_tty_hist{ + struct a_tty_hist *th_older; + struct a_tty_hist *th_younger; +# ifdef HAVE_BYTE_ORDER_LITTLE + ui32_t th_isgabby : 1; +# endif + ui32_t th_len : 31; +# ifndef HAVE_BYTE_ORDER_LITTLE + ui32_t th_isgabby : 1; +# endif + char th_dat[VFIELD_SIZE(sizeof(ui32_t))]; }; +# endif struct a_tty_line{ /* Caller pointers */ char **tl_x_buf; size_t *tl_x_bufsize; /* Input processing */ +# ifdef HAVE_KEY_BINDINGS + wchar_t tl_bind_takeover; /* Leftover byte to consume next */ + ui8_t tl_bind_timeout; /* In-seq. inter-byte-timer, in 1/10th secs */ + ui8_t tl__bind_dummy[3]; + char (*tl_bind_shcut_cancel)[5]; /* Special _CANCEL shortcut control */ + char (*tl_bind_shcut_prompt_char)[5]; /* ..for _PROMPT_CHAR */ + struct a_tty_bind_tree *(*tl_bind_tree_hmap)[HSHSIZE]; /* Bind lookup tree */ + struct a_tty_bind_tree *tl_bind_tree; +# endif char const *tl_reenter_after_cmd; /* `bind' cmd to exec, then re-readline */ /* Line data / content handling */ ui32_t tl_count; /* ..of a_tty_cell's (<= a_TTY_LINE_MAX) */ @@ -595,7 +805,7 @@ struct a_tty_line{ struct str tl_defc; /* Current default content */ size_t tl_defc_cursor_byte; /* Desired position of cursor after takeover */ struct str tl_savec; /* Saved default content */ - struct str tl_yankbuf; /* Last yanked data */ + struct str tl_pastebuf; /* Last snarfed data */ # ifdef HAVE_HISTORY struct a_tty_hist *tl_hist; /* History cursor */ # endif @@ -609,7 +819,7 @@ struct a_tty_line{ * TODO then check what _really_ has changed, sync those changes only */ struct a_tty_cell const *tl_phy_start; /* First visible cell, left border */ ui32_t tl_phy_cursor; /* Physical cursor position */ - bool_t tl_quote_roundtrip; /* For _kht() expansion */ + bool_t tl_quote_rndtrip; /* For _kht() expansion */ ui8_t tl__dummy2[3]; ui32_t tl_prompt_length; /* Preclassified (TODO needed as a_tty_cell) */ ui32_t tl_prompt_width; @@ -621,26 +831,133 @@ struct a_tty_line{ # endif }; -# ifdef HAVE_HISTORY -struct a_tty_hist{ - struct a_tty_hist *th_older; - struct a_tty_hist *th_younger; -# ifdef HAVE_BYTE_ORDER_LITTLE - ui32_t th_isgabby : 1; -# endif - ui32_t th_len : 31; -# ifndef HAVE_BYTE_ORDER_LITTLE - ui32_t th_isgabby : 1; -# endif - char th_dat[VFIELD_SIZE(sizeof(ui32_t))]; +# ifdef HAVE_KEY_BINDINGS +/* C99: use [INDEX]={} */ +n_CTAV(n_LEXINPUT_CTX_BASE == 0); +n_CTAV(n_LEXINPUT_CTX_COMPOSE == 1); +static struct a_tty_bind_ctx_map const + a_tty_bind_ctx_maps[n__LEXINPUT_CTX_MAX] = { + {n_LEXINPUT_CTX_BASE, "base"}, + {n_LEXINPUT_CTX_COMPOSE, "compose"} +}; + +/* Special functions which our MLE provides internally. + * Update the manual upon change! */ +static char const a_tty_bind_fun_names[][24] = { +# undef a_X +# define a_X(I,N) \ + n_FIELD_INITI(a_TTY_BIND_FUN_REDUCE(a_TTY_BIND_FUN_ ## I)) "mle-" N "\0", + + a_X(BELL, "bell") + a_X(GO_BWD, "go-bwd") a_X(GO_FWD, "go-fwd") + a_X(GO_WORD_BWD, "go-word-bwd") a_X(GO_WORD_FWD, "go-word-fwd") + a_X(GO_HOME, "go-home") a_X(GO_END, "go-end") + a_X(DEL_BWD, "del-bwd") a_X(DEL_FWD, "del-fwd") + a_X(SNARF_WORD_BWD, "snarf-word-bwd") a_X(SNARF_WORD_FWD, "snarf-word-fwd") + a_X(SNARF_END, "snarf-end") a_X(SNARF_LINE, "snarf-line") + a_X(HIST_BWD, "hist-bwd") a_X(HIST_FWD, "hist-fwd") + a_X(HIST_SRCH_BWD, "hist-srch-bwd") a_X(HIST_SRCH_FWD, "hist-srch-fwd") + a_X(REPAINT, "repaint") + a_X(QUOTE_RNDTRIP, "quote-rndtrip") + a_X(PROMPT_CHAR, "prompt-char") + a_X(COMPLETE, "complete") + a_X(PASTE, "paste") + + a_X(CANCEL, "cancel") + a_X(RESET, "reset") + a_X(FULLRESET, "fullreset") + a_X(COMMIT, "commit") + +# undef a_X +}; +# endif /* HAVE_KEY_BINDINGS */ + +/* The default key bindings (unless disallowed). Update manual upon change! + * A logical subset of this table is also used if !HAVE_KEY_BINDINGS (more + * expensive than a switch() on control codes directly, but less redundant) */ +static struct a_tty_bind_default_tuple const a_tty_bind_default_tuples[] = { +# undef a_X +# define a_X(K,S) \ + {TRU1, K, 0, {'\0', (char)a_TTY_BIND_FUN_REDUCE(a_TTY_BIND_FUN_ ## S),}}, + + a_X('A', GO_HOME) + a_X('B', GO_BWD) + /* C: SIGINT */ + a_X('D', DEL_FWD) + a_X('E', GO_END) + a_X('F', GO_FWD) + a_X('G', RESET) + a_X('H', DEL_BWD) + a_X('I', COMPLETE) + a_X('J', COMMIT) + a_X('K', SNARF_END) + a_X('L', REPAINT) + /* M: same as J */ + a_X('N', HIST_FWD) + /* O: below */ + a_X('P', HIST_BWD) + a_X('Q', QUOTE_RNDTRIP) + a_X('R', HIST_SRCH_BWD) + a_X('S', HIST_SRCH_FWD) + a_X('T', PASTE) + a_X('U', SNARF_LINE) + a_X('V', PROMPT_CHAR) + a_X('W', SNARF_WORD_BWD) + a_X('X', GO_WORD_FWD) + a_X('Y', GO_WORD_BWD) + /* Z: SIGTSTP */ + + a_X('[', CANCEL) + /* \: below */ + /* ]: below */ + /* ^: below */ + a_X('_', SNARF_WORD_FWD) + + a_X('?', DEL_BWD) + +# undef a_X +# define a_X(K,S) {TRU1, K, 0, {S}}, + + a_X('O', "dt") + a_X('\\', "z+") + a_X(']', "z$") + a_X('^', "z0") + +# ifdef HAVE_KEY_BINDINGS +# undef a_X +# define a_X(Q,S) \ + {FAL0, '\0', n_TERMCAP_QUERY_ ## Q,\ + {'\0', (char)a_TTY_BIND_FUN_REDUCE(a_TTY_BIND_FUN_ ## S),}}, + + a_X(key_backspace, DEL_BWD) a_X(key_dc, DEL_FWD) + a_X(key_eol, SNARF_END) + a_X(key_home, GO_HOME) a_X(key_end, GO_END) + a_X(key_left, GO_BWD) a_X(key_right, GO_FWD) + a_X(key_sleft, GO_HOME) a_X(key_sright, GO_END) + a_X(key_up, HIST_BWD) a_X(key_down, HIST_FWD) + +# undef a_X +# define a_X(Q,S) {FAL0, '\0', n_TERMCAP_QUERY_ ## Q, {S}}, + + a_X(key_shome, "z0") a_X(key_send, "z$") + a_X(xkey_sup, "z0") a_X(xkey_sdown, "z$") + a_X(key_ppage, "z-") a_X(key_npage, "z+") + a_X(xkey_cup, "dotmove-") a_X(xkey_cdown, "dotmove+") + +# endif /* HAVE_KEY_BINDINGS */ +# undef a_X }; -# endif static struct a_tty_global a_tty; -/**/ +/* Change from canonical to raw, non-canonical mode, and way back */ static void a_tty_term_mode(bool_t raw); +/* Adjust an active raw mode to use / not use a timeout */ +# ifdef HAVE_KEY_BINDINGS +static void a_tty_term_rawmode_timeout(struct a_tty_line *tlp, bool_t enable); +# endif + /* 0-X (2), UI8_MAX == \t / TAB */ static ui8_t a_tty_wcwidth(wchar_t wc); @@ -651,7 +968,7 @@ static ssize_t a_tty_cell2dat(struct a_tty_line *tlp); static void a_tty_cell2save(struct a_tty_line *tlp); /* Save away data bytes of given range (max = non-inclusive) */ -static void a_tty_yank(struct a_tty_line *tlp, struct a_tty_cell *tcpmin, +static void a_tty_copy2paste(struct a_tty_line *tlp, struct a_tty_cell *tcpmin, struct a_tty_cell *tcpmax); /* Ask user for hexadecimal number, interpret as UTF-32 */ @@ -660,35 +977,73 @@ static wchar_t a_tty_vinuni(struct a_tty_line *tlp); /* Visual screen synchronization */ static bool_t a_tty_vi_refresh(struct a_tty_line *tlp); +static bool_t a_tty_vi__paint(struct a_tty_line *tlp); + /* Search for word boundary, starting at tl_cursor, in "dir"ection (<> 0). * Return <0 when moving is impossible (backward direction but in position 0, * forward direction but in outermost column), and relative distance to * tl_cursor otherwise */ static si32_t a_tty_wboundary(struct a_tty_line *tlp, si32_t dir); -/* Key actions */ +/* Most function implementations */ static void a_tty_khome(struct a_tty_line *tlp, bool_t dobell); static void a_tty_kend(struct a_tty_line *tlp); static void a_tty_kbs(struct a_tty_line *tlp); -static void a_tty_kkill(struct a_tty_line *tlp, bool_t dobell); +static void a_tty_ksnarf(struct a_tty_line *tlp, bool_t cplline, bool_t dobell); static si32_t a_tty_kdel(struct a_tty_line *tlp); static void a_tty_kleft(struct a_tty_line *tlp); static void a_tty_kright(struct a_tty_line *tlp); -static void a_tty_kbwddelw(struct a_tty_line *tlp); +static void a_tty_ksnarfw(struct a_tty_line *tlp, bool_t fwd); static void a_tty_kgow(struct a_tty_line *tlp, si32_t dir); -static void a_tty_kother(struct a_tty_line *tlp, wchar_t wc); +static bool_t a_tty_kother(struct a_tty_line *tlp, wchar_t wc); static ui32_t a_tty_kht(struct a_tty_line *tlp); # ifdef HAVE_HISTORY static ui32_t a_tty__khist_shared(struct a_tty_line *tlp, struct a_tty_hist *thp); -static ui32_t a_tty_khist(struct a_tty_line *tlp, bool_t backwd); -static ui32_t a_tty_krhist(struct a_tty_line *tlp); +static ui32_t a_tty_khist(struct a_tty_line *tlp, bool_t fwd); +static ui32_t a_tty_khist_search(struct a_tty_line *tlp, bool_t fwd); # endif +/* Handle a function */ +static enum a_tty_fun_status a_tty_fun(struct a_tty_line *tlp, + enum a_tty_bind_flags tbf, size_t *len); + /* Readline core */ static ssize_t a_tty_readline(struct a_tty_line *tlp, size_t len SMALLOC_DEBUG_ARGS); +# ifdef HAVE_KEY_BINDINGS +/* Find context or -1 */ +static enum n_lexinput_flags a_tty_bind_ctx_find(char const *name); + +/* Create (or replace, if allowed) a binding */ +static bool_t a_tty_bind_create(struct a_tty_bind_parse_ctx *tbpcp, + bool_t replace); + +/* Shared implementation to parse `bind' and `unbind' "key-sequence" and + * "expansion" command line arguments into something that we can work with */ +static bool_t a_tty_bind_parse(bool_t isbindcmd, + struct a_tty_bind_parse_ctx *tbpcp); + +/* Lazy resolve a termcap(5)/terminfo(5) (or *termcap*!) capability */ +static void a_tty_bind_resolve(struct a_tty_bind_ctx *tbcp); + +/* Delete an existing binding */ +static void a_tty_bind_del(struct a_tty_bind_parse_ctx *tbpcp); + +/* Life cycle of all input node trees */ +static void a_tty_bind_tree_build(void); +static void a_tty_bind_tree_teardown(void); + +static void a_tty__bind_tree_add(ui32_t hmap_idx, + struct a_tty_bind_tree *store[HSHSIZE], + struct a_tty_bind_ctx *tbcp); +static struct a_tty_bind_tree *a_tty__bind_tree_add_wc( + struct a_tty_bind_tree **treep, struct a_tty_bind_tree *parentp, + wchar_t wc, bool_t isseq); +static void a_tty__bind_tree_free(struct a_tty_bind_tree *tbtp); +# endif /* HAVE_KEY_BINDINGS */ + static void a_tty_term_mode(bool_t raw){ struct termios *tiosp; @@ -709,13 +1064,35 @@ a_tty_term_mode(bool_t raw){ tiosp = &a_tty.tg_tios_new; tiosp->c_cc[VMIN] = 1; tiosp->c_cc[VTIME] = 0; - tiosp->c_iflag &= ~(ISTRIP); + /* Enable ^\, ^Q and ^S to be used for key bindings */ + tiosp->c_cc[VQUIT] = tiosp->c_cc[VSTART] = tiosp->c_cc[VSTOP] = '\0'; + tiosp->c_iflag &= ~(ISTRIP | IGNCR); tiosp->c_lflag &= ~(ECHO /*| ECHOE | ECHONL */| ICANON | IEXTEN); jleave: tcsetattr(STDIN_FILENO, TCSADRAIN, tiosp); NYD2_LEAVE; } +# ifdef HAVE_KEY_BINDINGS +static void +a_tty_term_rawmode_timeout(struct a_tty_line *tlp, bool_t enable){ + NYD2_ENTER; + if(enable){ + ui8_t bt; + + a_tty.tg_tios_new.c_cc[VMIN] = 0; + if((bt = tlp->tl_bind_timeout) == 0) + bt = a_TTY_BIND_TIMEOUT; + a_tty.tg_tios_new.c_cc[VTIME] = bt; + }else{ + a_tty.tg_tios_new.c_cc[VMIN] = 1; + a_tty.tg_tios_new.c_cc[VTIME] = 0; + } + tcsetattr(STDIN_FILENO, TCSANOW, &a_tty.tg_tios_new); + NYD2_LEAVE; +} +# endif /* HAVE_KEY_BINDINGS */ + static ui8_t a_tty_wcwidth(wchar_t wc){ ui8_t rv; @@ -810,7 +1187,7 @@ jleave: } static void -a_tty_yank(struct a_tty_line *tlp, struct a_tty_cell *tcpmin, +a_tty_copy2paste(struct a_tty_line *tlp, struct a_tty_cell *tcpmin, struct a_tty_cell *tcpmax){ char *cp; struct a_tty_cell *tcp; @@ -821,7 +1198,7 @@ a_tty_yank(struct a_tty_line *tlp, struct a_tty_cell *tcpmin, for(tcp = tcpmin; tcp < tcpmax; ++tcp) l += tcp->tc_count; - tlp->tl_yankbuf.s = cp = salloc((tlp->tl_yankbuf.l = l) +1); + tlp->tl_pastebuf.s = cp = salloc((tlp->tl_pastebuf.l = l) +1); l = 0; for(tcp = tcpmin; tcp < tcpmax; cp += l, ++tcp) @@ -837,9 +1214,8 @@ a_tty_vinuni(struct a_tty_line *tlp){ wchar_t wc; NYD2_ENTER; - wc = 0; + wc = '\0'; - tlp->tl_vi_flags |= a_TTY_VF_MOD_DIRTY; if(!n_termcap_cmdx(n_TERMCAP_CMD_cr) || !n_termcap_cmd(n_TERMCAP_CMD_ce, 0, -1)) goto jleave; @@ -895,12 +1271,68 @@ jerr: wc = (wchar_t)u.l; jleave: + tlp->tl_vi_flags |= a_TTY_VF_MOD_DIRTY | (wc == '\0' ? a_TTY_VF_BELL : 0); NYD2_LEAVE; return wc; } static bool_t a_tty_vi_refresh(struct a_tty_line *tlp){ + bool_t rv; + NYD2_ENTER; + + if(tlp->tl_vi_flags & a_TTY_VF_BELL){ + tlp->tl_vi_flags |= a_TTY_VF_SYNC; + if(putchar('\a') == EOF) + goto jerr; + } + + if(tlp->tl_vi_flags & a_TTY_VF_REFRESH){ + /* kht may want to restore a cursor position after inserting some + * data somewhere */ + if(tlp->tl_defc_cursor_byte > 0){ + size_t i, j; + ssize_t k; + + a_tty_khome(tlp, FAL0); + + i = tlp->tl_defc_cursor_byte; + tlp->tl_defc_cursor_byte = 0; + for(j = 0; tlp->tl_cursor < tlp->tl_count; ++j){ + a_tty_kright(tlp); + if((k = tlp->tl_line.cells[j].tc_count) > i) + break; + i -= k; + } + } + + if(!a_tty_vi__paint(tlp)) + goto jerr; + } + + if(tlp->tl_vi_flags & a_TTY_VF_SYNC){ + tlp->tl_vi_flags &= ~a_TTY_VF_SYNC; + if(fflush(stdout)) + goto jerr; + } + + rv = TRU1; +jleave: + tlp->tl_vi_flags &= ~a_TTY_VF_ALL_MASK; + NYD2_LEAVE; + return rv; + +jerr: + clearerr(stdout); /* xxx I/O layer rewrite */ + n_err(_("Visual refresh failed! Is $TERM set correctly?\n" + " Setting *line-editor-disable* to get us through!\n")); + ok_bset(line_editor_disable, TRU1); + rv = FAL0; + goto jleave; +} + +static bool_t +a_tty_vi__paint(struct a_tty_line *tlp){ enum{ a_TRUE_RV = a_TTY__VF_LAST<<1, /* Return value bit */ a_HAVE_PROMPT = a_TTY__VF_LAST<<2, /* Have a prompt */ @@ -1373,23 +1805,29 @@ a_tty_kbs(struct a_tty_line *tlp){ } static void -a_tty_kkill(struct a_tty_line *tlp, bool_t dobell){ - ui32_t i; +a_tty_ksnarf(struct a_tty_line *tlp, bool_t cplline, bool_t dobell){ + ui32_t i, f; NYD2_ENTER; - if(LIKELY((i = tlp->tl_cursor) < tlp->tl_count)){ + f = a_TTY_VF_NONE; + i = tlp->tl_cursor; + + if(cplline && i > 0){ + tlp->tl_cursor = i = 0; + f = a_TTY_VF_MOD_CURSOR; + } + + if(LIKELY(i < tlp->tl_count)){ struct a_tty_cell *tcap; tcap = &tlp->tl_line.cells[0]; - a_tty_yank(tlp, &tcap[i], &tcap[tlp->tl_count]); + a_tty_copy2paste(tlp, &tcap[i], &tcap[tlp->tl_count]); tlp->tl_count = i; - i = a_TTY_VF_MOD_CONTENT; + f = a_TTY_VF_MOD_CONTENT; }else if(dobell) - i = a_TTY_VF_BELL; - else - i = a_TTY_VF_NONE; + f |= a_TTY_VF_BELL; - tlp->tl_vi_flags |= i; + tlp->tl_vi_flags |= f; NYD2_LEAVE; } @@ -1459,22 +1897,24 @@ a_tty_kright(struct a_tty_line *tlp){ } static void -a_tty_kbwddelw(struct a_tty_line *tlp){ +a_tty_ksnarfw(struct a_tty_line *tlp, bool_t fwd){ struct a_tty_cell *tcap; ui32_t cnt, cur, f; si32_t i; NYD2_ENTER; - if(UNLIKELY((i = a_tty_wboundary(tlp, -1)) <= 0)){ + if(UNLIKELY((i = a_tty_wboundary(tlp, (fwd ? +1 : -1))) <= 0)){ f = (i < 0) ? a_TTY_VF_BELL : a_TTY_VF_NONE; goto jleave; } cnt = tlp->tl_count - (ui32_t)i; - cur = tlp->tl_cursor - (ui32_t)i; + cur = tlp->tl_cursor; + if(!fwd) + cur -= (ui32_t)i; tcap = &tlp->tl_line.cells[cur]; - a_tty_yank(tlp, &tcap[0], &tcap[i]); + a_tty_copy2paste(tlp, &tcap[0], &tcap[i]); if((tlp->tl_count = cnt) != (tlp->tl_cursor = cur)){ cnt -= cur; @@ -1506,15 +1946,17 @@ a_tty_kgow(struct a_tty_line *tlp, si32_t dir){ NYD2_LEAVE; } -static void +static bool_t a_tty_kother(struct a_tty_line *tlp, wchar_t wc){ /* Append if at EOL, insert otherwise; * since we may move around character-wise, always use a fresh ps */ mbstate_t ps; struct a_tty_cell tc, *tcap; ui32_t f, cur, cnt; + bool_t rv; NYD2_ENTER; + rv = FAL0; f = a_TTY_VF_NONE; n_LCTA(a_TTY_LINE_MAX <= SI32_MAX, "a_TTY_LINE_MAX too large"); @@ -1523,7 +1965,7 @@ a_tty_kother(struct a_tty_line *tlp, wchar_t wc){ goto jleave; } - /* First init a cell and see wether we'll really handle this wc */ + /* First init a cell and see whether we'll really handle this wc */ memset(&ps, 0, sizeof ps); /* C99 */{ size_t l; @@ -1563,9 +2005,13 @@ jemb: memcpy(tcap, &tc, sizeof *tcap); f |= a_TTY_VF_MOD_CURSOR; + rv = TRU1; jleave: + if(!rv) + f |= a_TTY_VF_BELL; tlp->tl_vi_flags |= f; NYD2_LEAVE; + return rv; } static ui32_t @@ -1711,7 +2157,7 @@ jredo: --exp.l; exp.s[exp.l] = '\0'; jset: - exp.l = strlen(exp.s = n_shell_quote_cp(exp.s, tlp->tl_quote_roundtrip)); + exp.l = strlen(exp.s = n_shell_quote_cp(exp.s, tlp->tl_quote_rndtrip)); tlp->tl_defc_cursor_byte = bot.l + exp.l -1; if(wedid) goto jnope; @@ -1809,7 +2255,7 @@ jmulti:{ #ifdef HAVE_C90AMEND1 c2 = towlower(c2); #else - c2 = lowerconv((char)c2); + c2 = lowerconv(c2); #endif /* Query longest common prefix along the way */ @@ -1829,7 +2275,7 @@ jmulti:{ /* Prepare display */ input = sub; shoup = n_shell_quote(n_string_trunc(shoup, 0), &input, - tlp->tl_quote_roundtrip); + tlp->tl_quote_rndtrip); memset(&vic, 0, sizeof vic); vic.vic_indat = shoup->s_dat; vic.vic_inlen = shoup->s_len; @@ -1855,7 +2301,8 @@ jsep: fputs(n_string_cp(shoup), fp); lnlen += vic.vic_vi_width; - /* Support the known file name tagging TODO optional */ + /* Support the known file name tagging + * XXX *line-editor-completion-filetype* or so */ if(!lstat(fullpath, &sb)){ char c = '\0'; @@ -1907,7 +2354,7 @@ jsep: exp.l = (locolen += prefixlen); /* XXX Indicate that there is multiple choice */ - /* XXX f |= a_TTY_VF_BELL; -> *line-editor-audible-completion*? or so */ + /* XXX f |= a_TTY_VF_BELL; -> *line-editor-completion-bell*? or so */ wedid = FAL0; goto jset; } @@ -1946,7 +2393,7 @@ a_tty__khist_shared(struct a_tty_line *tlp, struct a_tty_hist *thp){ } static ui32_t -a_tty_khist(struct a_tty_line *tlp, bool_t backwd){ +a_tty_khist(struct a_tty_line *tlp, bool_t fwd){ struct a_tty_hist *thp; ui32_t rv; NYD2_ENTER; @@ -1955,7 +2402,7 @@ a_tty_khist(struct a_tty_line *tlp, bool_t backwd){ * also, disallow forward search, then, and, of course, bail unless we * do have any history at all */ if((thp = tlp->tl_hist) == NULL){ - if(!backwd) + if(fwd) goto jleave; if((thp = a_tty.tg_hist) == NULL) goto jleave; @@ -1963,7 +2410,7 @@ a_tty_khist(struct a_tty_line *tlp, bool_t backwd){ goto jleave; } - thp = backwd ? thp->th_older : thp->th_younger; + thp = fwd ? thp->th_younger : thp->th_older; jleave: rv = a_tty__khist_shared(tlp, thp); NYD2_LEAVE; @@ -1971,7 +2418,7 @@ jleave: } static ui32_t -a_tty_krhist(struct a_tty_line *tlp){ +a_tty_khist_search(struct a_tty_line *tlp, bool_t fwd){ struct str orig_savec; struct a_tty_hist *thp; ui32_t rv; @@ -1992,9 +2439,12 @@ a_tty_krhist(struct a_tty_line *tlp){ if((thp = tlp->tl_hist) == NULL){ if((thp = a_tty.tg_hist) == NULL) goto jleave; + /* We don't support wraparound, searching forward must always step */ + if(fwd) + thp = thp->th_younger; orig_savec.s = NULL; orig_savec.l = 0; /* silence CC */ - }else if((thp = thp->th_older) == NULL) + }else if((thp = (fwd ? thp->th_younger : thp->th_older)) == NULL) goto jleave; else orig_savec = tlp->tl_savec; @@ -2002,7 +2452,7 @@ a_tty_krhist(struct a_tty_line *tlp){ if(orig_savec.s == NULL) a_tty_cell2save(tlp); - for(; thp != NULL; thp = thp->th_older) + for(; thp != NULL; thp = (fwd ? thp->th_younger : thp->th_older)) if(is_prefix(tlp->tl_savec.s, thp->th_dat)) break; @@ -2015,427 +2465,1391 @@ jleave: } # endif /* HAVE_HISTORY */ +static enum a_tty_fun_status +a_tty_fun(struct a_tty_line *tlp, enum a_tty_bind_flags tbf, size_t *len){ + enum a_tty_fun_status rv; + NYD2_ENTER; + + rv = a_TTY_FUN_STATUS_OK; +# undef a_X +# define a_X(N) a_TTY_BIND_FUN_REDUCE(a_TTY_BIND_FUN_ ## N) + switch(a_TTY_BIND_FUN_REDUCE(tbf)){ + case a_X(BELL): + tlp->tl_vi_flags |= a_TTY_VF_BELL; + break; + case a_X(GO_BWD): + a_tty_kleft(tlp); + break; + case a_X(GO_FWD): + a_tty_kright(tlp); + break; + case a_X(GO_WORD_BWD): + a_tty_kgow(tlp, -1); + break; + case a_X(GO_WORD_FWD): + a_tty_kgow(tlp, +1); + break; + case a_X(GO_HOME): + a_tty_khome(tlp, TRU1); + break; + case a_X(GO_END): + a_tty_kend(tlp); + break; + case a_X(DEL_BWD): + a_tty_kbs(tlp); + break; + case a_X(DEL_FWD): + if(a_tty_kdel(tlp) < 0) + rv = a_TTY_FUN_STATUS_END; + break; + case a_X(SNARF_WORD_BWD): + a_tty_ksnarfw(tlp, FAL0); + break; + case a_X(SNARF_WORD_FWD): + a_tty_ksnarfw(tlp, TRU1); + break; + case a_X(SNARF_END): + a_tty_ksnarf(tlp, FAL0, TRU1); + break; + case a_X(SNARF_LINE): + a_tty_ksnarf(tlp, TRU1, (tlp->tl_count == 0)); + break; + + case a_X(HIST_FWD): +# ifdef HAVE_HISTORY + if(tlp->tl_hist != NULL){ + bool_t isfwd = TRU1; + + if(0){ +# endif + /* FALLTHRU */ + case a_X(HIST_BWD): +# ifdef HAVE_HISTORY + isfwd = FAL0; + } + if((*len = a_tty_khist(tlp, isfwd)) > 0){ + rv = a_TTY_FUN_STATUS_RESTART; + break; + } + goto jreset; +# endif + } + tlp->tl_vi_flags |= a_TTY_VF_BELL; + break; + + case a_X(HIST_SRCH_FWD):{ +# ifdef HAVE_HISTORY + bool_t isfwd = TRU1; + + if(0){ +# endif + /* FALLTHRU */ + case a_X(HIST_SRCH_BWD): +# ifdef HAVE_HISTORY + isfwd = FAL0; + } + if((*len = a_tty_khist_search(tlp, isfwd)) > 0){ + rv = a_TTY_FUN_STATUS_RESTART; + break; + } + goto jreset; +# else + tlp->tl_vi_flags |= a_TTY_VF_BELL; +# endif + } break; + + case a_X(REPAINT): + tlp->tl_vi_flags |= a_TTY_VF_MOD_DIRTY; + break; + case a_X(QUOTE_RNDTRIP): + tlp->tl_quote_rndtrip = !tlp->tl_quote_rndtrip; + break; + case a_X(PROMPT_CHAR):{ + wchar_t wc; + + if((wc = a_tty_vinuni(tlp)) > 0) + a_tty_kother(tlp, wc); + } break; + case a_X(COMPLETE): + if((*len = a_tty_kht(tlp)) > 0) + rv = a_TTY_FUN_STATUS_RESTART; + break; + + case a_X(PASTE): + if(tlp->tl_pastebuf.l > 0) + *len = (tlp->tl_defc = tlp->tl_pastebuf).l; + else + tlp->tl_vi_flags |= a_TTY_VF_BELL; + break; + + + case a_X(CANCEL): + /* Normally this just causes a restart and thus resets the state + * machine */ + if(tlp->tl_savec.l == 0 && tlp->tl_defc.l == 0){ + } +# ifdef HAVE_KEY_BINDINGS + tlp->tl_bind_takeover = '\0'; +# endif + tlp->tl_vi_flags |= a_TTY_VF_BELL; + rv = a_TTY_FUN_STATUS_RESTART; + break; + + case a_X(RESET): + if(tlp->tl_count == 0 && tlp->tl_savec.l == 0 && tlp->tl_defc.l == 0){ +# ifdef HAVE_KEY_BINDINGS + tlp->tl_bind_takeover = '\0'; +# endif + tlp->tl_vi_flags |= a_TTY_VF_MOD_DIRTY | a_TTY_VF_BELL; + break; + }else if(0){ + case a_X(FULLRESET): + tlp->tl_savec.s = tlp->tl_defc.s = NULL; + tlp->tl_savec.l = tlp->tl_defc.l = 0; + tlp->tl_defc_cursor_byte = 0; + tlp->tl_vi_flags |= a_TTY_VF_BELL; + } +jreset: +# ifdef HAVE_KEY_BINDINGS + tlp->tl_bind_takeover = '\0'; +# endif + tlp->tl_vi_flags |= a_TTY_VF_MOD_DIRTY; + tlp->tl_cursor = tlp->tl_count = 0; +# ifdef HAVE_HISTORY + tlp->tl_hist = NULL; +# endif + if((*len = tlp->tl_savec.l) != 0){ + tlp->tl_defc = tlp->tl_savec; + tlp->tl_savec.s = NULL; + tlp->tl_savec.l = 0; + }else + *len = tlp->tl_defc.l; + rv = a_TTY_FUN_STATUS_RESTART; + break; + + default: + case a_X(COMMIT): + rv = a_TTY_FUN_STATUS_COMMIT; + break; + } +# undef a_X + + NYD2_LEAVE; + return rv; +} + static ssize_t a_tty_readline(struct a_tty_line *tlp, size_t len SMALLOC_DEBUG_ARGS){ /* We want to save code, yet we may have to incorporate a lines' * default content and / or default input to switch back to after some * history movement; let "len > 0" mean "have to display some data - * buffer", and only otherwise read(2) it */ + * buffer" -> a_BUFMODE, and only otherwise read(2) it */ mbstate_t ps[2]; - char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp, cursor_maybe, cursor_store; - wchar_t wc; + char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp; ssize_t rv; + struct a_tty_bind_tree *tbtp; + wchar_t wc; + enum a_tty_bind_flags tbf; + enum {a_NONE, a_WAS_HERE = 1<<0, a_BUFMODE = 1<<1, a_MAYBEFUN = 1<<2, + a_TIMEOUT = 1<<3, a_TIMEOUT_EXPIRED = 1<<4, + a_TIMEOUT_MASK = a_TIMEOUT | a_TIMEOUT_EXPIRED, + a_READ_LOOP_MASK = ~(a_WAS_HERE | a_MAYBEFUN | a_TIMEOUT_MASK) + } flags; NYD_ENTER; + UNINIT(rv, 0); +# ifdef HAVE_KEY_BINDINGS + assert(tlp->tl_bind_takeover == '\0'); +# endif jrestart: memset(ps, 0, sizeof ps); + flags = a_NONE; + tbf = 0; tlp->tl_vi_flags |= a_TTY_VF_REFRESH | a_TTY_VF_SYNC; - for(cursor_maybe = cursor_store = 0;;){ +jinput_loop: + for(;;){ + if(len != 0) + flags |= a_BUFMODE; + /* Ensure we have valid pointers, and room for grow */ - a_tty_check_grow(tlp, (len == 0 ? 1 : (ui32_t)len) + a_tty_check_grow(tlp, ((flags & a_BUFMODE) ? (ui32_t)len : 1) SMALLOC_DEBUG_ARGSCALL); - /* Handle visual state flags, except in buffer take-over mode */ - if(len == 0){ - if(tlp->tl_vi_flags & a_TTY_VF_BELL){ - tlp->tl_vi_flags |= a_TTY_VF_SYNC; - putchar('\a'); - } - - if(tlp->tl_vi_flags & a_TTY_VF_REFRESH){ - /* kht may want to restore a cursor position after inserting some - * data somewhere */ - if(tlp->tl_defc_cursor_byte > 0){ - size_t i, j; - - a_tty_khome(tlp, FAL0); - - i = tlp->tl_defc_cursor_byte; - tlp->tl_defc_cursor_byte = 0; - for(j = 0; tlp->tl_cursor < tlp->tl_count; ++j){ - a_tty_kright(tlp); - if((len = tlp->tl_line.cells[j].tc_count) > i) - break; - i -= len; - } - len = 0; - } - - if(!a_tty_vi_refresh(tlp)){ - clearerr(stdout); /* xxx I/O layer rewrite */ - n_err(_("Visual refresh failed! Is $TERM set correctly?\n" - " Setting *line-editor-disable* to get us through!\n")); - ok_bset(line_editor_disable, TRU1); - rv = -1; - goto jleave; - } - } - - if(tlp->tl_vi_flags & a_TTY_VF_SYNC){ - tlp->tl_vi_flags &= ~a_TTY_VF_SYNC; - fflush(stdout); + /* Handle visual state flags, except in buffer mode */ + if(!(flags & a_BUFMODE) && (tlp->tl_vi_flags & a_TTY_VF_ALL_MASK)) + if(!a_tty_vi_refresh(tlp)){ + rv = -1; + goto jleave; } - tlp->tl_vi_flags &= ~a_TTY_VF_ALL_MASK; - } - /* Ready for messing around. - * Normal read(2)? Else buffer-takeover: speed this one up */ - if(len == 0){ + * Normal read(2)? Else buffer mode: speed this one up */ + if(!(flags & a_BUFMODE)){ cbufp = cbuf = cbuf_base; }else{ assert(tlp->tl_defc.l > 0 && tlp->tl_defc.s != NULL); + assert(tlp->tl_defc.l >= len); cbufp = cbuf = tlp->tl_defc.s + (tlp->tl_defc.l - len); cbufp += len; } /* Read in the next complete multibyte character */ - for(;;){ - if(len == 0){ - /* Let me at least once dream of iomon(itor), timer with one-shot, - * enwrapped with key_event and key_sequence_event, all driven by - * an event_loop */ - if((rv = read(STDIN_FILENO, cbufp, 1)) < 1){ - if(errno == EINTR) /* xxx #if !SA_RESTART ? */ + /* C99 */{ +# ifdef HAVE_KEY_BINDINGS + struct a_tty_bind_tree *xtbtp; + struct inseq{ + struct inseq *last; + struct inseq *next; + struct a_tty_bind_tree *tbtp; + } *isp_head, *isp; + + isp_head = isp = NULL; +# endif + + for(flags &= a_READ_LOOP_MASK;;){ +# ifdef HAVE_KEY_BINDINGS + if(!(flags & a_BUFMODE) && tlp->tl_bind_takeover != '\0'){ + wc = tlp->tl_bind_takeover; + tlp->tl_bind_takeover = '\0'; + }else +# endif + { + if(!(flags & a_BUFMODE)){ + /* Let me at least once dream of iomon(itor), timer with + * one-shot, enwrapped with key_event and key_sequence_event, + * all driven by an event_loop */ + /* TODO v15 Until we have SysV signal handling all through we + * TODO need to temporarily adjust our BSD signal handler with + * TODO a SysV one, here */ + n_sighdl_t otstp, ottin, ottou; + + otstp = n_signal(SIGTSTP, &n_tty_signal); + ottin = n_signal(SIGTTIN, &n_tty_signal); + ottou = n_signal(SIGTTOU, &n_tty_signal); +# ifdef HAVE_KEY_BINDINGS + flags &= ~a_TIMEOUT_MASK; + if(isp != NULL && (tbtp = isp->tbtp)->tbt_isseq && + !tbtp->tbt_isseq_trail){ + a_tty_term_rawmode_timeout(tlp, TRU1); + flags |= a_TIMEOUT; + } +# endif + + while((rv = read(STDIN_FILENO, cbufp, 1)) < 1){ + if(rv == -1){ + if(errno == EINTR){ + if((tlp->tl_vi_flags & a_TTY_VF_MOD_DIRTY) && + !a_tty_vi_refresh(tlp)) + break; + continue; + } + break; + } + +# ifdef HAVE_KEY_BINDINGS + /* Timeout expiration */ + if(rv == 0){ + assert(flags & a_TIMEOUT); + assert(isp != NULL); + a_tty_term_rawmode_timeout(tlp, FAL0); + + /* Something "atomic" broke. Maybe the current one can + * also be terminated already, by itself? xxx really? */ + if((tbtp = isp->tbtp)->tbt_bind != NULL){ + tlp->tl_bind_takeover = wc; + goto jhave_bind; + } + + /* Or, maybe there is a second path without a timeout; + * this should be covered by .tbt_isseq_trail, but then + * again a single-layer implementation cannot "know" */ + for(xtbtp = tbtp; (xtbtp = xtbtp->tbt_sibling) != NULL;) + if(xtbtp->tbt_char == tbtp->tbt_char){ + assert(!xtbtp->tbt_isseq); + break; + } + /* Lay down on read(2)? */ + if(xtbtp != NULL) + continue; + goto jtake_over; + } +# endif /* HAVE_KEY_BINDINGS */ + } + +# ifdef HAVE_KEY_BINDINGS + if(flags & a_TIMEOUT) + a_tty_term_rawmode_timeout(tlp, FAL0); +# endif + safe_signal(SIGTSTP, otstp); + safe_signal(SIGTTIN, ottin); + safe_signal(SIGTTOU, ottou); + if(rv < 0) + goto jleave; + + ++cbufp; + } + + rv = (ssize_t)mbrtowc(&wc, cbuf, PTR2SIZE(cbufp - cbuf), &ps[0]); + if(rv <= 0){ + /* Any error during buffer mode can only result in a hard + * reset; Otherwise, if it's a hard error, or if too many + * redundant shift sequences overflow our buffer: perform + * hard reset */ + if((flags & a_BUFMODE) || rv == -1 || + sizeof cbuf_base == PTR2SIZE(cbufp - cbuf)){ + a_tty_fun(tlp, a_TTY_BIND_FUN_FULLRESET, &len); + goto jrestart; + } + /* Otherwise, due to the way we deal with the buffer, we need + * to restore the mbstate_t from before this conversion */ + ps[0] = ps[1]; continue; - goto jleave; + } + cbufp = cbuf; + ps[1] = ps[0]; + } + + /* Normal read(2)ing is subject to detection of key-bindings */ +# ifdef HAVE_KEY_BINDINGS + if(!(flags & a_BUFMODE)){ + /* Check for special bypass functions before we try to embed + * this character into the tree */ + if(n_uasciichar(wc)){ + char c; + char const *cp; + + for(c = (char)wc, cp = &(*tlp->tl_bind_shcut_prompt_char)[0]; + *cp != '\0'; ++cp){ + if(c == *cp){ + wc = a_tty_vinuni(tlp); + break; + } + } + if(wc == '\0'){ + tlp->tl_vi_flags |= a_TTY_VF_BELL; + goto jinput_loop; + } + } + if(n_uasciichar(wc)) + flags |= a_MAYBEFUN; + else + flags &= ~a_MAYBEFUN; + + /* Search for this character in the bind tree */ + tbtp = (isp != NULL) ? isp->tbtp->tbt_childs + : (*tlp->tl_bind_tree_hmap)[wc % HSHSIZE]; + for(; tbtp != NULL; tbtp = tbtp->tbt_sibling){ + if(tbtp->tbt_char == wc){ + struct inseq *nisp; + + /* If this one cannot continue we're likely finished! */ + if(tbtp->tbt_childs == NULL){ + assert(tbtp->tbt_bind != NULL); + tbf = tbtp->tbt_bind->tbc_flags; + goto jmle_fun; + } + + /* This needs to read more characters */ + nisp = salloc(sizeof *nisp); + if((nisp->last = isp) == NULL) + isp_head = nisp; + else + isp->next = nisp; + nisp->next = NULL; + nisp->tbtp = tbtp; + isp = nisp; + flags &= ~a_WAS_HERE; + break; + } + } + if(tbtp != NULL) + continue; + + /* Was there a binding active, but couldn't be continued? */ + if(isp != NULL){ + /* A binding had a timeout, it didn't expire, but we saw + * something non-expected. Something "atomic" broke. + * Maybe there is a second path without a timeout, that + * continues like we've seen it. I.e., it may just have been + * the user, typing two fast. We definitely want to allow + * bindings like \e,d etc. to succeed: users are so used to + * them that a timeout cannot be the mechanism to catch up! + * A single-layer implementation cannot "know" */ + if((tbtp = isp->tbtp)->tbt_isseq && (isp->last == NULL || + !(xtbtp = isp->last->tbtp)->tbt_isseq || + xtbtp->tbt_isseq_trail)){ + for(xtbtp = (tbtp = isp->tbtp); + (xtbtp = xtbtp->tbt_sibling) != NULL;) + if(xtbtp->tbt_char == tbtp->tbt_char){ + assert(!xtbtp->tbt_isseq); + break; + } + if(xtbtp != NULL){ + isp->tbtp = xtbtp; + tlp->tl_bind_takeover = wc; + continue; + } + } + + /* Check for CANCEL shortcut now */ + if(flags & a_MAYBEFUN){ + char c; + char const *cp; + + for(c = (char)wc, cp = &(*tlp->tl_bind_shcut_cancel)[0]; + *cp != '\0'; ++cp) + if(c == *cp){ + tbf = a_TTY_BIND_FUN_INTERNAL |a_TTY_BIND_FUN_CANCEL; + goto jmle_fun; + } + } + + /* So: maybe the current sequence can be terminated here? */ + if((tbtp = isp->tbtp)->tbt_bind != NULL){ +jhave_bind: + tbf = tbtp->tbt_bind->tbc_flags; +jmle_fun: + if(tbf & a_TTY_BIND_FUN_INTERNAL){ + switch(a_tty_fun(tlp, tbf, &len)){ + case a_TTY_FUN_STATUS_OK: + goto jinput_loop; + case a_TTY_FUN_STATUS_COMMIT: + goto jdone; + case a_TTY_FUN_STATUS_RESTART: + goto jrestart; + case a_TTY_FUN_STATUS_END: + goto jleave; + } + assert(0); + }else if(tbtp->tbt_bind->tbc_flags & a_TTY_BIND_NOCOMMIT){ + struct a_tty_bind_ctx *tbcp; + + tbcp = tbtp->tbt_bind; + memcpy(tlp->tl_defc.s = salloc( + (tlp->tl_defc.l = len = tbcp->tbc_exp_len) +1), + tbcp->tbc_exp, tbcp->tbc_exp_len +1); + goto jrestart; + }else{ + tlp->tl_reenter_after_cmd = tbtp->tbt_bind->tbc_exp; + goto jdone; + } + } + } + + /* Otherwise take over all chars "as is" */ +jtake_over: + for(; isp_head != NULL; isp_head = isp_head->next) + if(a_tty_kother(tlp, isp_head->tbtp->tbt_char)){ + /* FIXME */ + } + /* And the current one too */ + goto jkother; + } +# endif /* HAVE_KEY_BINDINGS */ + + if((flags & a_BUFMODE) && (len -= (size_t)rv) == 0){ + /* Buffer mode completed */ + tlp->tl_defc.s = NULL; + tlp->tl_defc.l = 0; + flags &= ~a_BUFMODE; } - ++cbufp; + break; } - rv = (ssize_t)mbrtowc(&wc, cbuf, PTR2SIZE(cbufp - cbuf), &ps[0]); - if(rv <= 0){ - /* Any error during take-over can only result in a hard reset; - * Otherwise, if it's a hard error, or if too many redundant shift - * sequences overflow our buffer, also perform a hard reset */ - if(len != 0 || rv == -1 || - sizeof cbuf_base == PTR2SIZE(cbufp - cbuf)){ - tlp->tl_savec.s = tlp->tl_defc.s = NULL; - tlp->tl_savec.l = tlp->tl_defc.l = len = 0; - tlp->tl_defc_cursor_byte = 0; - tlp->tl_vi_flags |= a_TTY_VF_BELL; - goto jreset; +# ifndef HAVE_KEY_BINDINGS + /* Don't interpret control bytes during buffer mode. + * Otherwise, if it's a control byte check whether it is a MLE + * function. Remarks: initially a complete duplicate to be able to + * switch(), later converted to simply iterate over (an #ifdef'd + * subset of) the MLE default_tuple table in order to have "a SPOF" */ + if(cbuf == cbuf_base && n_uasciichar(wc) && cntrlchar((char)wc)){ + struct a_tty_bind_default_tuple const *tbdtp; + char c; + + for(c = (char)wc ^ 0x40, tbdtp = a_tty_bind_default_tuples; + PTRCMP(tbdtp, <, &a_tty_bind_default_tuples[ + NELEM(a_tty_bind_default_tuples)]); + ++tbdtp){ + /* Assert default_tuple table is properly subset'ed */ + assert(tbdtp->tbdt_iskey); + if(tbdtp->tbdt_ckey == c){ + if(tbdtp->tbdt_exp[0] == '\0'){ + enum a_tty_bind_flags tbf; + + tbf = a_TTY_BIND_FUN_EXPAND((ui8_t)tbdtp->tbdt_exp[1]); + switch(a_tty_fun(tlp, tbf, &len)){ + case a_TTY_FUN_STATUS_OK: + goto jinput_loop; + case a_TTY_FUN_STATUS_COMMIT: + goto jdone; + case a_TTY_FUN_STATUS_RESTART: + goto jrestart; + case a_TTY_FUN_STATUS_END: + goto jleave; + } + assert(0); + }else{ + tlp->tl_reenter_after_cmd = tbdtp->tbdt_exp; + goto jdone; + } + } } - /* Otherwise, due to the way we deal with the buffer, we need to - * restore the mbstate_t from before this conversion */ - ps[0] = ps[1]; - continue; } - ps[1] = ps[0]; +# endif /* !HAVE_KEY_BINDINGS */ - /* Buffer takeover completed? */ - if(len != 0 && (len -= (size_t)rv) == 0){ - tlp->tl_defc.s = NULL; - tlp->tl_defc.l = 0; +# ifdef HAVE_KEY_BINDINGS +jkother: +# endif + if(a_tty_kother(tlp, wc)){ + /* Don't clear the history during buffer mode.. */ +# ifdef HAVE_HISTORY + if(!(flags & a_BUFMODE) && cbuf == cbuf_base) + tlp->tl_hist = NULL; +# endif } - break; } + } - /* Don't interpret control bytes during buffer take-over */ - if(cbuf != cbuf_base) - goto jprint; - switch(wc){ - case 'A' ^ 0x40: /* cursor home */ - a_tty_khome(tlp, TRU1); - break; - case 'B' ^ 0x40: /* backward character */ -j_b: - a_tty_kleft(tlp); - break; - /* 'C': interrupt (CTRL-C) */ - case 'D' ^ 0x40: /* delete char forward if any, else EOF */ - if((rv = a_tty_kdel(tlp)) < 0) - goto jleave; - break; - case 'E' ^ 0x40: /* end of line */ - a_tty_kend(tlp); - break; - case 'F' ^ 0x40: /* forward character */ -j_f: - a_tty_kright(tlp); - break; - /* 'G' below */ - case 'H' ^ 0x40: /* backspace */ - case '\177': - a_tty_kbs(tlp); - break; - case 'I' ^ 0x40: /* horizontal tab */ - if((len = a_tty_kht(tlp)) > 0) - goto jrestart; + /* We have a completed input line, convert the struct cell data to its + * plain character equivalent */ +jdone: + rv = a_tty_cell2dat(tlp); +jleave: + putchar('\n'); + fflush(stdout); + NYD_LEAVE; + return rv; +} + +# ifdef HAVE_KEY_BINDINGS +static enum n_lexinput_flags +a_tty_bind_ctx_find(char const *name){ + enum n_lexinput_flags rv; + struct a_tty_bind_ctx_map const *tbcmp; + NYD2_ENTER; + + tbcmp = a_tty_bind_ctx_maps; + do if(!asccasecmp(tbcmp->tbcm_name, name)){ + rv = tbcmp->tbcm_ctx; + goto jleave; + }while(PTRCMP(++tbcmp, <, &a_tty_bind_ctx_maps[NELEM(a_tty_bind_ctx_maps)])); + + rv = (enum n_lexinput_flags)-1; +jleave: + NYD2_LEAVE; + return rv; +} + +static bool_t +a_tty_bind_create(struct a_tty_bind_parse_ctx *tbpcp, bool_t replace){ + struct a_tty_bind_ctx *tbcp; + bool_t rv; + NYD2_ENTER; + + rv = FAL0; + + if(!a_tty_bind_parse(TRU1, tbpcp)) + goto jleave; + + /* Since we use a single buffer for it all, need to replace as such */ + if(tbpcp->tbpc_tbcp != NULL){ + if(!replace) + goto jleave; + a_tty_bind_del(tbpcp); + }else if(a_tty.tg_bind_cnt == UI32_MAX){ + n_err(_("`bind': maximum number of bindings already established\n")); + goto jleave; + } + + /* C99 */{ + size_t i, j; + + tbcp = smalloc(sizeof(*tbcp) - + VFIELD_SIZEOF(struct a_tty_bind_ctx, tbc__buf) + + tbpcp->tbpc_seq_len + tbpcp->tbpc_exp.l + tbpcp->tbpc_cnv_len +3); + if(tbpcp->tbpc_ltbcp != NULL){ + tbcp->tbc_next = tbpcp->tbpc_ltbcp->tbc_next; + tbpcp->tbpc_ltbcp->tbc_next = tbcp; + }else{ + enum n_lexinput_flags lif = tbpcp->tbpc_flags & n__LEXINPUT_CTX_MASK; + + tbcp->tbc_next = a_tty.tg_bind[lif]; + a_tty.tg_bind[lif] = tbcp; + } + memcpy(tbcp->tbc_seq = &tbcp->tbc__buf[0], + tbpcp->tbpc_seq, i = (tbcp->tbc_seq_len = tbpcp->tbpc_seq_len) +1); + memcpy(tbcp->tbc_exp = &tbcp->tbc__buf[i], + tbpcp->tbpc_exp.s, j = (tbcp->tbc_exp_len = tbpcp->tbpc_exp.l) +1); + memcpy(tbcp->tbc_cnv = &tbcp->tbc__buf[i += j], + tbpcp->tbpc_cnv, (tbcp->tbc_cnv_len = tbpcp->tbpc_cnv_len) +1); + tbcp->tbc_flags = tbpcp->tbpc_flags; + } + + /* Directly resolve any termcap(5) symbol if we are already setup */ + if((pstate & PS_STARTED) && + (tbcp->tbc_flags & (a_TTY_BIND_RESOLVE | a_TTY_BIND_DEFUNCT)) == + a_TTY_BIND_RESOLVE) + a_tty_bind_resolve(tbcp); + + ++a_tty.tg_bind_cnt; + /* If this binding is usable invalidate the key input lookup trees */ + if(!(tbcp->tbc_flags & a_TTY_BIND_DEFUNCT)) + a_tty.tg_bind_isdirty = TRU1; + rv = TRU1; +jleave: + NYD2_LEAVE; + return rv; +} + +static bool_t +a_tty_bind_parse(bool_t isbindcmd, struct a_tty_bind_parse_ctx *tbpcp){ + enum{a_TRUE_RV = a_TTY__BIND_LAST<<1}; + + struct n_visual_info_ctx vic; + struct str shin_save, shin; + struct n_string shou, *shoup; + size_t i; + struct kse{ + struct kse *next; + char *seq_dat; + wc_t *cnv_dat; + ui32_t seq_len; + ui32_t cnv_len; /* High bit set if a termap to be resolved */ + ui32_t calc_cnv_len; /* Ditto, but aligned etc. */ + } *head, *tail; + ui32_t f; + NYD2_ENTER; + n_LCTA(UICMP(64, a_TRUE_RV, <, UI32_MAX), + "Flag bits excess storage datatype"); + + f = n_LEXINPUT_NONE; + shoup = n_string_creat_auto(&shou); + head = tail = NULL; + + /* Parse the key-sequence */ + for(shin.s = UNCONST(tbpcp->tbpc_in_seq), shin.l = UIZ_MAX;;){ + struct kse *ep; + enum n_shexp_state shs; + + shin_save = shin; + shs = n_shell_parse_token(shoup, &shin, + n_SHEXP_PARSE_TRUNC | n_SHEXP_PARSE_TRIMSPACE | + n_SHEXP_PARSE_IGNORE_EMPTY | n_SHEXP_PARSE_IFS_IS_COMMA); + if(shs & n_SHEXP_STATE_ERR_UNICODE){ + f |= a_TTY_BIND_DEFUNCT; + if(isbindcmd && (options & OPT_D_V)) + n_err(_("`%s': \\uNICODE not available in locale: %s\n"), + tbpcp->tbpc_cmd, tbpcp->tbpc_in_seq); + } + if((shs & n_SHEXP_STATE_ERR_MASK) & ~n_SHEXP_STATE_ERR_UNICODE){ + n_err(_("`%s': failed to parse key-sequence: %s\n"), + tbpcp->tbpc_cmd, tbpcp->tbpc_in_seq); + goto jleave; + } + if((shs & (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_STOP)) == + n_SHEXP_STATE_STOP) break; - case 'J' ^ 0x40: /* NL (\n) */ - goto jdone; - case 'G' ^ 0x40: /* full reset */ -jreset: - /* FALLTHRU */ - case 'U' ^ 0x40: /* ^U: ^A + ^K */ - a_tty_khome(tlp, FAL0); - /* FALLTHRU */ - case 'K' ^ 0x40: /* kill from cursor to end of line */ - a_tty_kkill(tlp, (wc == ('K' ^ 0x40) || tlp->tl_count == 0)); - /* (Handle full reset?) */ - if(wc == ('G' ^ 0x40)) { -# ifdef HAVE_HISTORY - tlp->tl_hist = NULL; -# endif - if((len = tlp->tl_savec.l) != 0){ - tlp->tl_defc = tlp->tl_savec; - tlp->tl_savec.s = NULL; - tlp->tl_savec.l = 0; - }else - len = tlp->tl_defc.l; + + ep = salloc(sizeof *ep); + if(head == NULL) + head = ep; + else + tail->next = ep; + tail = ep; + ep->next = NULL; + if(!(shs & n_SHEXP_STATE_ERR_UNICODE)){ + i = strlen(ep->seq_dat = n_shell_quote_cp(n_string_cp(shoup), TRU1)); + if(i >= SI32_MAX - 1) + goto jelen; + ep->seq_len = (ui32_t)i; + }else{ + /* Otherwise use the original buffer, _we_ can only quote it the wrong + * way (e.g., an initial $'\u3a' becomes '\u3a'), _then_ */ + if((i = shin_save.l - shin.l) >= SI32_MAX - 1) + goto jelen; + ep->seq_len = (ui32_t)i; + ep->seq_dat = savestrbuf(shin_save.s, i); + } + + memset(&vic, 0, sizeof vic); + vic.vic_inlen = shoup->s_len; + vic.vic_indat = shoup->s_dat; + if(!n_visual_info(&vic, + n_VISUAL_INFO_WOUT_CREATE | n_VISUAL_INFO_WOUT_SALLOC)){ + n_err(_("`%s': key-sequence seems to contain invalid " + "characters: %s: %s\n"), + tbpcp->tbpc_cmd, n_string_cp(shoup), tbpcp->tbpc_in_seq); + f |= a_TTY_BIND_DEFUNCT; + goto jleave; + }else if(vic.vic_woulen == 0 || + vic.vic_woulen >= (SI32_MAX - 2) / sizeof(wc_t)){ +jelen: + n_err(_("`%s': length of key-sequence unsupported: %s: %s\n"), + tbpcp->tbpc_cmd, n_string_cp(shoup), tbpcp->tbpc_in_seq); + f |= a_TTY_BIND_DEFUNCT; + goto jleave; + } + ep->cnv_dat = vic.vic_woudat; + ep->cnv_len = (ui32_t)vic.vic_woulen; + + /* A termcap(5)/terminfo(5) identifier? */ + if(ep->cnv_len > 1 && ep->cnv_dat[0] == ':'){ + i = --ep->cnv_len, ++ep->cnv_dat; +# ifndef HAVE_TERMCAP + if(options & OPT_D_V) + n_err(_("`%s': no termcap(5)/terminfo(5) support: %s: %s\n"), + tbpcp->tbpc_cmd, ep->seq_dat, tbpcp->tbpc_in_seq); + f |= a_TTY_BIND_DEFUNCT; +# endif + if(i > a_TTY_BIND_CAPNAME_MAX){ + n_err(_("`%s': termcap(5)/terminfo(5) name too long: %s: %s\n"), + tbpcp->tbpc_cmd, ep->seq_dat, tbpcp->tbpc_in_seq); + f |= a_TTY_BIND_DEFUNCT; } - goto jrestart; - case 'L' ^ 0x40: /* repaint line */ - tlp->tl_vi_flags |= a_TTY_VF_MOD_DIRTY; - break; - /* 'M': CR (\r) */ - case 'N' ^ 0x40: /* history next */ -j_n: -# ifdef HAVE_HISTORY - if(tlp->tl_hist == NULL) - goto jbell; - if((len = a_tty_khist(tlp, FAL0)) > 0) - goto jrestart; - wc = 'G' ^ 0x40; - goto jreset; -# else - goto jbell; -# endif - case 'O' ^ 0x40: - tlp->tl_reenter_after_cmd = "dp"; - goto jdone; - case 'P' ^ 0x40: /* history previous */ -j_p: -# ifdef HAVE_HISTORY - if((len = a_tty_khist(tlp, TRU1)) > 0) - goto jrestart; - wc = 'G' ^ 0x40; - goto jreset; -# else - goto jbell; -# endif - /* 'Q': no code */ - case 'R' ^ 0x40: /* reverse history search */ -# ifdef HAVE_HISTORY - if((len = a_tty_krhist(tlp)) > 0) - goto jrestart; - wc = 'G' ^ 0x40; - goto jreset; -# else - goto jbell; -# endif - /* 'S': no code */ - /* 'U' above */ - case 'V' ^ 0x40: /* insert (Unicode) character */ - if((wc = a_tty_vinuni(tlp)) > 0) - goto jprint; - goto jbell; - case 'W' ^ 0x40: /* backward delete "word" */ - a_tty_kbwddelw(tlp); - break; - case 'X' ^ 0x40: /* move cursor forward "word" */ - a_tty_kgow(tlp, +1); - break; - case 'Y' ^ 0x40: /* move cursor backward "word" */ - a_tty_kgow(tlp, -1); + while(i > 0) + /* (We store it as char[]) */ + if((ui32_t)ep->cnv_dat[--i] & ~0x7Fu){ + n_err(_("`%s': invalid termcap(5)/terminfo(5) name content: " + "%s: %s\n"), + tbpcp->tbpc_cmd, ep->seq_dat, tbpcp->tbpc_in_seq); + f |= a_TTY_BIND_DEFUNCT; + break; + } + ep->cnv_len |= SI32_MIN; /* Needs resolve */ + f |= a_TTY_BIND_RESOLVE; + } + + if(shs & n_SHEXP_STATE_STOP) break; - /* 'Z': suspend (CTRL-Z) */ - case 0x1B: - if(cursor_maybe++ != 0) - goto jreset; - continue; - default: - /* XXX Handle usual ^[[[ABCD1456] cursor keys: UGLY,"MAGIC",INFLEX */ - if(cursor_maybe > 0){ - if(++cursor_maybe == 2){ - if(wc == L'[') - continue; - cursor_maybe = 0; - }else if(cursor_maybe == 3){ - cursor_maybe = 0; - switch(wc){ - default: break; - case L'A': goto j_p; - case L'B': goto j_n; - case L'C': goto j_f; - case L'D': goto j_b; - case L'H': - cursor_store = '0'; - goto J_xterm_noapp; - case L'F': - cursor_store = '$'; - goto J_xterm_noapp; - case L'1': - case L'4': - case L'5': - case L'6': - cursor_store = ((wc == L'1') ? '0' : - (wc == L'4' ? '$' : (wc == L'5' ? '-' : '+'))); - cursor_maybe = 3; - continue; - } - a_tty_kother(tlp, L'['); - }else{ - cursor_maybe = 0; - if(wc == L'~') J_xterm_noapp:{ - char *cp = salloc(3); - - cp[0] = 'z'; - cp[1] = cursor_store; - cp[2] = '\0'; - tlp->tl_reenter_after_cmd = cp; - goto jdone; - }else if(cursor_store == '-' && (wc == L'A' || wc == L'B')){ - char const cmd[] = "dotmove"; - char *cp = salloc(sizeof(cmd) -1 + 1 +1); - - memcpy(cp, cmd, sizeof(cmd) -1); - cp[sizeof(cmd) -1] = (wc != L'A') ? '+' : cursor_store; - cp[sizeof(cmd)] = '\0'; - tlp->tl_reenter_after_cmd = cp; - goto jdone; - } - a_tty_kother(tlp, L'['); - a_tty_kother(tlp, (wchar_t)cursor_store); - cursor_store = 0; + } + + if(head == NULL){ +jeempty: + n_err(_("`%s': effectively empty key-sequence: %s\n"), + tbpcp->tbpc_cmd, tbpcp->tbpc_in_seq); + goto jleave; + } + + /* C99 */{ + struct a_tty_bind_ctx *ltbcp, *tbcp; + char *cpbase, *cp, *cnv; + size_t sl, cl; + + /* Unite the parsed sequence(s) into single string representations */ + for(sl = cl = 0, tail = head; tail != NULL; tail = tail->next){ + sl += tail->seq_len + 1; + + if(!isbindcmd) + continue; + + /* Preserve room for terminal capabilities to be resolved. + * Above we have ensured the buffer will fit in these calculations */ + if((i = tail->cnv_len) & SI32_MIN){ + /* For now + * struct{si32_t buf_len_iscap; si32_t cap_len; wc_t name[]+NUL;} + * later + * struct{si32_t buf_len_iscap; si32_t cap_len; char buf[]+NUL;} */ + n_LCTAV(ISPOW2(a_TTY_BIND_CAPEXP_ROUNDUP)); + n_LCTA(a_TTY_BIND_CAPEXP_ROUNDUP >= sizeof(wc_t), + "Aligning on this constant doesn't properly align wc_t"); + i &= SI32_MAX; + i *= sizeof(wc_t); + i += sizeof(si32_t); + if(i < a_TTY_BIND_CAPEXP_ROUNDUP) + i = (i + (a_TTY_BIND_CAPEXP_ROUNDUP - 1)) & + ~(a_TTY_BIND_CAPEXP_ROUNDUP - 1); + }else + /* struct{si32_t buf_len_iscap; wc_t buf[]+NUL;} */ + i *= sizeof(wc_t); + i += sizeof(si32_t) + sizeof(wc_t); /* (buf_len_iscap, NUL) */ + cl += i; + if(tail->cnv_len & SI32_MIN){ + tail->cnv_len &= SI32_MAX; + i |= SI32_MIN; + } + tail->calc_cnv_len = (ui32_t)i; + } + --sl; + + tbpcp->tbpc_seq_len = sl; + tbpcp->tbpc_cnv_len = cl; + /* C99 */{ + size_t j; + + j = i = sl + 1; /* Room for comma separator */ + if(isbindcmd){ + size_t const al = MAX(sizeof(si32_t), sizeof(wc_t)) - 1; + + i = (i + al) & ~al; + j = i; + i += cl; + } + tbpcp->tbpc_seq = cp = cpbase = salloc(i); + tbpcp->tbpc_cnv = cnv = &cpbase[j]; + } + + for(tail = head; tail != NULL; tail = tail->next){ + memcpy(cp, tail->seq_dat, tail->seq_len); + cp += tail->seq_len; + *cp++ = ','; + + if(isbindcmd){ + char * const save_cnv = cnv; + + ((si32_t*)cnv)[0] = (si32_t)(i = tail->calc_cnv_len); + cnv += sizeof(si32_t); + if(i & SI32_MIN){ + /* For now + * struct{si32_t buf_len_iscap; si32_t cap_len; wc_t name[];} + * later + * struct{si32_t buf_len_iscap; si32_t cap_len; char buf[];} */ + ((si32_t*)cnv)[0] = tail->cnv_len; + cnv += sizeof(si32_t); } + i = tail->cnv_len * sizeof(wc_t); + memcpy(cnv, tail->cnv_dat, i); + cnv += i; + *(wc_t*)cnv = '\0'; + + cnv = save_cnv + (tail->calc_cnv_len & SI32_MAX); } -jprint: - if(iswprint(wc)){ - a_tty_kother(tlp, wc); - /* Don't clear the history during takeover.. - * ..and also avoid fflush()ing unless we've worked entire buffer */ - if(len > 0) - continue; -# ifdef HAVE_HISTORY - if(cbuf == cbuf_base) - tlp->tl_hist = NULL; -# endif - }else{ -jbell: - tlp->tl_vi_flags |= a_TTY_VF_BELL; + } + *--cp = '\0'; + + /* Search for a yet existing identical mapping */ + for(ltbcp = NULL, tbcp = a_tty.tg_bind[tbpcp->tbpc_flags]; tbcp != NULL; + ltbcp = tbcp, tbcp = tbcp->tbc_next) + if(tbcp->tbc_seq_len == sl && !memcmp(tbcp->tbc_seq, cpbase, sl)){ + tbpcp->tbpc_tbcp = tbcp; + break; } - break; + tbpcp->tbpc_ltbcp = ltbcp; + tbpcp->tbpc_flags |= (f & a_TTY__BIND_MASK); + } + + /* Create single string expansion if so desired */ + if(isbindcmd){ + char *exp; + + exp = tbpcp->tbpc_exp.s; + + i = tbpcp->tbpc_exp.l; + if(i > 0 && exp[i - 1] == '@'){ + while(--i > 0){ + if(!blankspacechar(exp[i - 1])) + break; + } + if(i == 0) + goto jeempty; + + exp[tbpcp->tbpc_exp.l = i] = '\0'; + tbpcp->tbpc_flags |= a_TTY_BIND_NOCOMMIT; } - tlp->tl_vi_flags |= a_TTY_VF_SYNC; + + /* It may map to an internal MLE command! */ + for(i = 0; i < NELEM(a_tty_bind_fun_names); ++i) + if(!asccasecmp(exp, a_tty_bind_fun_names[i])){ + tbpcp->tbpc_flags |= a_TTY_BIND_FUN_EXPAND(i) | + a_TTY_BIND_FUN_INTERNAL | + (head->next == NULL ? a_TTY_BIND_MLE1CNTRL : 0); + if((options & OPT_D_V) && (tbpcp->tbpc_flags & a_TTY_BIND_NOCOMMIT)) + n_err(_("`%s': MLE commands can't be made editable via @: %s\n"), + tbpcp->tbpc_cmd, exp); + tbpcp->tbpc_flags &= ~a_TTY_BIND_NOCOMMIT; + break; + } } - /* We have a completed input line, convert the struct cell data to its - * plain character equivalent */ -jdone: - rv = a_tty_cell2dat(tlp); + f |= a_TRUE_RV; /* TODO because we only now true and false; DEFUNCT.. */ jleave: - putchar('\n'); - fflush(stdout); - NYD_LEAVE; - return rv; + n_string_gut(shoup); + NYD2_LEAVE; + return (f & a_TRUE_RV) != 0; +} + +static void +a_tty_bind_resolve(struct a_tty_bind_ctx *tbcp){ + char capname[a_TTY_BIND_CAPNAME_MAX +1]; + struct n_termcap_value tv; + size_t len; + bool_t isfirst; /* TODO For now: first char must be control! */ + char *cp, *next; + NYD2_ENTER; + + UNINIT(next, NULL); + for(cp = tbcp->tbc_cnv, isfirst = TRU1, len = tbcp->tbc_cnv_len; + len > 0; isfirst = FAL0, cp = next){ + /* C99 */{ + si32_t i, j; + + i = ((si32_t*)cp)[0]; + j = i & SI32_MAX; + next = &cp[j]; + len -= j; + if(i == j) + continue; + + /* struct{si32_t buf_len_iscap; si32_t cap_len; wc_t name[];} */ + cp += sizeof(si32_t); + i = ((si32_t*)cp)[0]; + cp += sizeof(si32_t); + for(j = 0; j < i; ++j) + capname[j] = ((wc_t*)cp)[j]; + capname[j] = '\0'; + } + + /* Use generic lookup mechanism if not a known query */ + /* C99 */{ + si32_t tq; + + tq = n_termcap_query_for_name(capname, n_TERMCAP_CAPTYPE_STRING); + if(tq == -1){ + tv.tv_data.tvd_string = capname; + tq = n__TERMCAP_QUERY_MAX; + } + + if(tq < 0 || !n_termcap_query(tq, &tv)){ + if(options & OPT_D_V) + n_err(_("`bind': unknown or unsupported capability: %s: %s\n"), + capname, tbcp->tbc_seq); + tbcp->tbc_flags |= a_TTY_BIND_DEFUNCT; + break; + } + } + + /* struct{si32_t buf_len_iscap; si32_t cap_len; char buf[]+NUL;} */ + /* C99 */{ + size_t i; + + i = strlen(tv.tv_data.tvd_string); + if(/*i > SI32_MAX ||*/ i >= PTR2SIZE(next - cp)){ + if(options & OPT_D_V) + n_err(_("`bind': capability expansion too long: %s: %s\n"), + capname, tbcp->tbc_seq); + tbcp->tbc_flags |= a_TTY_BIND_DEFUNCT; + break; + }else if(i == 0){ + if(options & OPT_D_V) + n_err(_("`bind': empty capability expansion: %s: %s\n"), + capname, tbcp->tbc_seq); + tbcp->tbc_flags |= a_TTY_BIND_DEFUNCT; + break; + }else if(isfirst && !cntrlchar(*tv.tv_data.tvd_string)){ + if(options & OPT_D_V) + n_err(_("`bind': capability expansion doesn't start with " + "control: %s: %s\n"), capname, tbcp->tbc_seq); + tbcp->tbc_flags |= a_TTY_BIND_DEFUNCT; + break; + } + ((si32_t*)cp)[-1] = (si32_t)i; + memcpy(cp, tv.tv_data.tvd_string, i); + cp[i] = '\0'; + } + } + NYD2_LEAVE; +} + +static void +a_tty_bind_del(struct a_tty_bind_parse_ctx *tbpcp){ + struct a_tty_bind_ctx *ltbcp, *tbcp; + NYD2_ENTER; + + tbcp = tbpcp->tbpc_tbcp; + + if((ltbcp = tbpcp->tbpc_ltbcp) != NULL) + ltbcp->tbc_next = tbcp->tbc_next; + else + a_tty.tg_bind[tbpcp->tbpc_flags] = tbcp->tbc_next; + free(tbcp); + + --a_tty.tg_bind_cnt; + a_tty.tg_bind_isdirty = TRU1; + NYD2_LEAVE; +} + +static void +a_tty_bind_tree_build(void){ + size_t i; + NYD2_ENTER; + + for(i = 0; i < n__LEXINPUT_CTX_MAX; ++i){ + struct a_tty_bind_ctx *tbcp; + n_LCTAV(n_LEXINPUT_CTX_BASE == 0); + + /* Somewhat wasteful, but easier to handle: simply clone the entire + * primary key onto the secondary one, then only modify it */ + for(tbcp = a_tty.tg_bind[n_LEXINPUT_CTX_BASE]; tbcp != NULL; + tbcp = tbcp->tbc_next) + if(!(tbcp->tbc_flags & a_TTY_BIND_DEFUNCT)) + a_tty__bind_tree_add(n_LEXINPUT_CTX_BASE, &a_tty.tg_bind_tree[i][0], + tbcp); + + if(i != n_LEXINPUT_CTX_BASE) + for(tbcp = a_tty.tg_bind[i]; tbcp != NULL; tbcp = tbcp->tbc_next) + if(!(tbcp->tbc_flags & a_TTY_BIND_DEFUNCT)) + a_tty__bind_tree_add(i, &a_tty.tg_bind_tree[i][0], tbcp); + } + + a_tty.tg_bind_isbuild = TRU1; + NYD2_LEAVE; +} + +static void +a_tty_bind_tree_teardown(void){ + size_t i, j; + NYD2_ENTER; + + memset(&a_tty.tg_bind_shcut_cancel[0], 0, + sizeof(a_tty.tg_bind_shcut_cancel)); + memset(&a_tty.tg_bind_shcut_prompt_char[0], 0, + sizeof(a_tty.tg_bind_shcut_prompt_char)); + + for(i = 0; i < n__LEXINPUT_CTX_MAX; ++i) + for(j = 0; j < HSHSIZE; ++j) + a_tty__bind_tree_free(a_tty.tg_bind_tree[i][j]); + memset(&a_tty.tg_bind_tree[0], 0, sizeof(a_tty.tg_bind_tree)); + + a_tty.tg_bind_isdirty = a_tty.tg_bind_isbuild = FAL0; + NYD2_LEAVE; +} + +static void +a_tty__bind_tree_add(ui32_t hmap_idx, struct a_tty_bind_tree *store[HSHSIZE], + struct a_tty_bind_ctx *tbcp){ + ui32_t cnvlen; + char const *cnvdat; + struct a_tty_bind_tree *ntbtp; + NYD2_ENTER; + UNUSED(hmap_idx); + + ntbtp = NULL; + + for(cnvdat = tbcp->tbc_cnv, cnvlen = tbcp->tbc_cnv_len; cnvlen > 0;){ + union {wchar_t const *wp; char const *cp;} u; + si32_t entlen; + + /* {si32_t buf_len_iscap;} */ + entlen = *(si32_t const*)cnvdat; + + if(entlen & SI32_MIN){ + /* struct{si32_t buf_len_iscap; si32_t cap_len; char buf[]+NUL;} + * Note that empty capabilities result in DEFUNCT */ + for(u.cp = (char const*)&((si32_t const*)cnvdat)[2]; + *u.cp != '\0'; ++u.cp) + ntbtp = a_tty__bind_tree_add_wc(store, ntbtp, *u.cp, TRU1); + ntbtp->tbt_isseq_trail = TRU1; + entlen &= SI32_MAX; + }else{ + /* struct{si32_t buf_len_iscap; wc_t buf[]+NUL;} */ + bool_t isseq; + + u.wp = (wchar_t const*)&((si32_t const*)cnvdat)[1]; + + /* May be a special shortcut function? */ + if(ntbtp == NULL && (tbcp->tbc_flags & a_TTY_BIND_MLE1CNTRL)){ + char *cp; + ui32_t ctx, fun; + + ctx = tbcp->tbc_flags & n__LEXINPUT_CTX_MAX; + fun = tbcp->tbc_flags & a_TTY__BIND_FUN_MASK; + + if(fun == a_TTY_BIND_FUN_CANCEL){ + for(cp = &a_tty.tg_bind_shcut_cancel[ctx][0]; + PTRCMP(cp, <, &a_tty.tg_bind_shcut_cancel[ctx] + [NELEM(a_tty.tg_bind_shcut_cancel[ctx]) - 1]); ++cp) + if(*cp == '\0'){ + *cp = (char)*u.wp; + break; + } + }else if(fun == a_TTY_BIND_FUN_PROMPT_CHAR){ + for(cp = &a_tty.tg_bind_shcut_prompt_char[ctx][0]; + PTRCMP(cp, <, &a_tty.tg_bind_shcut_prompt_char[ctx] + [NELEM(a_tty.tg_bind_shcut_prompt_char[ctx]) - 1]); + ++cp) + if(*cp == '\0'){ + *cp = (char)*u.wp; + break; + } + } + } + + isseq = (u.wp[1] != '\0'); + for(; *u.wp != '\0'; ++u.wp) + ntbtp = a_tty__bind_tree_add_wc(store, ntbtp, *u.wp, isseq); + if(isseq) + ntbtp->tbt_isseq_trail = TRU1; + } + + cnvlen -= entlen; + cnvdat += entlen; + } + + /* Should have been rendered defunctional at first instead */ + assert(ntbtp != NULL); + ntbtp->tbt_bind = tbcp; + NYD2_LEAVE; } +static struct a_tty_bind_tree * +a_tty__bind_tree_add_wc(struct a_tty_bind_tree **treep, + struct a_tty_bind_tree *parentp, wchar_t wc, bool_t isseq){ + struct a_tty_bind_tree *tbtp, *xtbtp; + NYD2_ENTER; + + if(parentp == NULL){ + treep += wc % HSHSIZE; + + /* Having no parent also means that the tree slot is possibly empty */ + for(tbtp = *treep; tbtp != NULL; + parentp = tbtp, tbtp = tbtp->tbt_sibling){ + if(tbtp->tbt_char != wc) + continue; + if(tbtp->tbt_isseq == isseq) + goto jleave; + /* isseq MUST be linked before !isseq, so record this "parent" + * sibling, but continue searching for now */ + if(!isseq) + parentp = tbtp; + /* Otherwise it is impossible that we'll find what we look for */ + else{ +#ifdef HAVE_DEBUG + while((tbtp = tbtp->tbt_sibling) != NULL) + assert(tbtp->tbt_char != wc); +#endif + break; + } + } + + tbtp = smalloc(sizeof *tbtp); + memset(tbtp, 0, sizeof *tbtp); + tbtp->tbt_char = wc; + tbtp->tbt_isseq = isseq; + + if(parentp == NULL){ + tbtp->tbt_sibling = *treep; + *treep = tbtp; + }else{ + tbtp->tbt_sibling = parentp->tbt_sibling; + parentp->tbt_sibling = tbtp; + } + }else{ + if((tbtp = *(treep = &parentp->tbt_childs)) != NULL){ + for(;; tbtp = xtbtp){ + if(tbtp->tbt_char == wc){ + if(tbtp->tbt_isseq == isseq) + goto jleave; + /* isseq MUST be linked before, so it is impossible that we'll + * find what we look for */ + if(isseq){ +#ifdef HAVE_DEBUG + while((tbtp = tbtp->tbt_sibling) != NULL) + assert(tbtp->tbt_char != wc); +#endif + tbtp = NULL; + break; + } + } + + if((xtbtp = tbtp->tbt_sibling) == NULL){ + treep = &tbtp->tbt_sibling; + break; + } + } + } + + xtbtp = smalloc(sizeof *xtbtp); + memset(xtbtp, 0, sizeof *xtbtp); + xtbtp->tbt_parent = parentp; + xtbtp->tbt_char = wc; + xtbtp->tbt_isseq = isseq; + tbtp = xtbtp; + *treep = tbtp; + } +jleave: + NYD2_LEAVE; + return tbtp; +} + +static void +a_tty__bind_tree_free(struct a_tty_bind_tree *tbtp){ + NYD2_ENTER; + while(tbtp != NULL){ + struct a_tty_bind_tree *tmp; + + if((tmp = tbtp->tbt_childs) != NULL) + a_tty__bind_tree_free(tmp); + + tmp = tbtp->tbt_sibling; + free(tbtp); + tbtp = tmp; + } + NYD2_LEAVE; +} +# endif /* HAVE_KEY_BINDINGS */ + FL void n_tty_init(void){ -# ifdef HAVE_HISTORY - long hs; - char const *v; - char *lbuf; - FILE *f; - size_t lsize, cnt, llen; -# endif NYD_ENTER; /* Load the history file */ # ifdef HAVE_HISTORY - a_TTY_HISTSIZE(hs); - a_tty.tg_hist_size = 0; - a_tty.tg_hist_size_max = (size_t)hs; - if(hs == 0) - goto jleave; + do/* for break */{ + long hs; + char const *v; + char *lbuf; + FILE *f; + size_t lsize, cnt, llen; + + a_TTY_HISTSIZE(hs); + a_tty.tg_hist_size = 0; + a_tty.tg_hist_size_max = (size_t)hs; + if(hs == 0) + break; - a_TTY_HISTFILE(v); - if(v == NULL) - goto jleave; + a_TTY_HISTFILE(v); + if(v == NULL) + break; - hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */ - f = fopen(v, "r"); /* TODO HISTFILE LOAD: use linebuf pool */ - if(f == NULL) - goto jdone; - (void)n_file_lock(fileno(f), FLT_READ, 0,0, 500); - - lbuf = NULL; - lsize = 0; - cnt = (size_t)fsize(f); - while(fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL){ - if(llen > 0 && lbuf[llen - 1] == '\n') - lbuf[--llen] = '\0'; - if(llen == 0 || lbuf[0] == '#') /* xxx comments? noone! */ - continue; - else{ - bool_t isgabby = (lbuf[0] == '*'); + hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */ + f = fopen(v, "r"); /* TODO HISTFILE LOAD: use linebuf pool */ + if(f == NULL) + goto jhist_done; + (void)n_file_lock(fileno(f), FLT_READ, 0,0, UIZ_MAX); + + lbuf = NULL; + lsize = 0; + cnt = (size_t)fsize(f); + while(fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL){ + if(llen > 0 && lbuf[llen - 1] == '\n') + lbuf[--llen] = '\0'; + if(llen == 0 || lbuf[0] == '#') /* xxx comments? noone! */ + continue; + else{ + bool_t isgabby; - n_tty_addhist(lbuf + isgabby, isgabby); + isgabby = (lbuf[0] == '*'); + n_tty_addhist(lbuf + isgabby, isgabby); + } } - } - if(lbuf != NULL) - free(lbuf); + if(lbuf != NULL) + free(lbuf); - fclose(f); -jdone: - rele_all_sigs(); /* XXX remove jumps */ -jleave: + fclose(f); +jhist_done: + rele_all_sigs(); /* XXX remove jumps */ + }while(0); # endif /* HAVE_HISTORY */ - pstate |= PS_HISTORY_LOADED; + + /* Force immediate resolve for anything which follows */ + pstate |= PS_LINE_EDITOR_INIT; + +# ifdef HAVE_KEY_BINDINGS + /* `bind's (and `unbind's) done from within resource files couldn't be + * performed for real since our termcap driver wasn't yet loaded, and we + * can't perform automatic init since the user may have disallowed so */ + /* C99 */{ + struct a_tty_bind_ctx *tbcp; + enum n_lexinput_flags lif; + + for(lif = 0; lif < n__LEXINPUT_CTX_MAX; ++lif) + for(tbcp = a_tty.tg_bind[lif]; tbcp != NULL; tbcp = tbcp->tbc_next) + if((tbcp->tbc_flags & (a_TTY_BIND_RESOLVE | a_TTY_BIND_DEFUNCT)) == + a_TTY_BIND_RESOLVE) + a_tty_bind_resolve(tbcp); + } + + /* And we want to (try to) install some default key bindings */ + if(!ok_blook(line_editor_no_defaults)){ + char buf[8]; + struct a_tty_bind_parse_ctx tbpc; + struct a_tty_bind_default_tuple const *tbdtp; + + buf[0] = '$', buf[1] = '\'', buf[2] = '\\', buf[3] = 'c', + buf[5] = '\'', buf[6] = '\0'; + for(tbdtp = a_tty_bind_default_tuples; + PTRCMP(tbdtp, <, + &a_tty_bind_default_tuples[NELEM(a_tty_bind_default_tuples)]); + ++tbdtp){ + memset(&tbpc, 0, sizeof tbpc); + tbpc.tbpc_cmd = "bind"; + if(tbdtp->tbdt_iskey){ + buf[4] = tbdtp->tbdt_ckey; + tbpc.tbpc_in_seq = buf; + }else + tbpc.tbpc_in_seq = savecatsep(":", '\0', + n_termcap_name_of_query(tbdtp->tbdt_query)); + tbpc.tbpc_exp.s = UNCONST(tbdtp->tbdt_exp[0] == '\0' + ? a_tty_bind_fun_names[(ui8_t)tbdtp->tbdt_exp[1]] + : tbdtp->tbdt_exp); + tbpc.tbpc_exp.l = strlen(tbpc.tbpc_exp.s); + tbpc.tbpc_flags = n_LEXINPUT_CTX_BASE; + /* ..but don't want to overwrite any user settings */ + a_tty_bind_create(&tbpc, FAL0); + } + } +# endif /* HAVE_KEY_BINDINGS */ + NYD_LEAVE; } FL void n_tty_destroy(void){ -# ifdef HAVE_HISTORY - long hs; - char const *v; - struct a_tty_hist *thp; - bool_t dogabby; - FILE *f; -# endif NYD_ENTER; # ifdef HAVE_HISTORY - a_TTY_HISTSIZE(hs); - if(hs == 0) - goto jleave; + do/* for break */{ + long hs; + char const *v; + struct a_tty_hist *thp; + bool_t dogabby; + FILE *f; + + a_TTY_HISTSIZE(hs); + if(hs == 0) + break; - a_TTY_HISTFILE(v); - if(v == NULL) - goto jleave; + a_TTY_HISTFILE(v); + if(v == NULL) + break; - dogabby = ok_blook(history_gabby_persist); + dogabby = ok_blook(history_gabby_persist); - if((thp = a_tty.tg_hist) != NULL) - for(; thp->th_older != NULL; thp = thp->th_older) - if((dogabby || !thp->th_isgabby) && --hs == 0) - break; + if((thp = a_tty.tg_hist) != NULL) + for(; thp->th_older != NULL; thp = thp->th_older) + if((dogabby || !thp->th_isgabby) && --hs == 0) + break; - hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */ - f = fopen(v, "w"); /* TODO temporary + rename?! */ - if(f == NULL) - goto jdone; - (void)n_file_lock(fileno(f), FLT_WRITE, 0,0, 500); - - for(; thp != NULL; thp = thp->th_younger){ - if(dogabby || !thp->th_isgabby){ - if(thp->th_isgabby) - putc('*', f); - fwrite(thp->th_dat, sizeof *thp->th_dat, thp->th_len, f); - putc('\n', f); + hold_all_sigs(); /* TODO too heavy, yet we may jump even here!? */ + f = fopen(v, "w"); /* TODO temporary + rename?! */ + if(f == NULL) + goto jhist_done; + (void)n_file_lock(fileno(f), FLT_WRITE, 0,0, UIZ_MAX); + + for(; thp != NULL; thp = thp->th_younger){ + if(dogabby || !thp->th_isgabby){ + if(thp->th_isgabby) + putc('*', f); + fwrite(thp->th_dat, sizeof *thp->th_dat, thp->th_len, f); + putc('\n', f); + } } - } - fclose(f); -jdone: - rele_all_sigs(); /* XXX remove jumps */ -jleave: + fclose(f); +jhist_done: + rele_all_sigs(); /* XXX remove jumps */ + }while(0); # endif /* HAVE_HISTORY */ + +# if defined HAVE_KEY_BINDINGS && defined HAVE_DEBUG + c_unbind(UNCONST("* *")); +# endif + +#ifdef HAVE_DEBUG + memset(&a_tty, 0, sizeof a_tty); +#endif + DBG( pstate &= ~PS_LINE_EDITOR_INIT; ) NYD_LEAVE; } @@ -2463,6 +3877,7 @@ n_tty_signal(int sig){ a_tty_sigs_up(); n_TERMCAP_RESUME(TRU1); a_tty_term_mode(TRU1); + a_tty.tg_line->tl_vi_flags |= a_TTY_VF_MOD_DIRTY; break; } } @@ -2476,8 +3891,16 @@ FL int # endif ui32_t plen, pwidth; ssize_t nn; + char const *orig_prompt; NYD_ENTER; + assert(pstate & PS_LINE_EDITOR_INIT); + + orig_prompt = prompt; +jredo: + /* TODO because of jumping away we cannot use srelax()ation: would be good */ + prompt = orig_prompt; + # ifdef HAVE_COLOUR n_colour_env_create(n_COLOUR_CTX_MLE, FAL0); # endif @@ -2557,9 +3980,36 @@ FL int } # endif /* HAVE_COLOUR */ -jredo: memset(&tl, 0, sizeof tl); +# ifdef HAVE_KEY_BINDINGS + /* C99 */{ + char const *cp = ok_vlook(bind_timeout); + + if(cp != NULL){ + ul_i ul; + + if((ul = strtoul(cp, NULL, 0)) > 0 && + /* Convert to tenths of a second, unfortunately */ + (ul = (ul + 99) / 100) <= a_TTY_BIND_TIMEOUT_MAX) + tl.tl_bind_timeout = (ui8_t)ul; + else if(options & OPT_D_V) + n_err(_("Ignoring invalid *bind-timeout*: %s\n"), cp); + } + } + + /* TODO honour other bind contexts, like "compose" */ + if(a_tty.tg_bind_isdirty) + a_tty_bind_tree_teardown(); + if(a_tty.tg_bind_cnt > 0 && !a_tty.tg_bind_isbuild) + a_tty_bind_tree_build(); + tl.tl_bind_tree_hmap = &a_tty.tg_bind_tree[n_LEXINPUT_CTX_BASE]; + tl.tl_bind_shcut_cancel = + &a_tty.tg_bind_shcut_cancel[n_LEXINPUT_CTX_BASE]; + tl.tl_bind_shcut_prompt_char = + &a_tty.tg_bind_shcut_prompt_char[n_LEXINPUT_CTX_BASE]; +# endif /* HAVE_KEY_BINDINGS */ + if((tl.tl_prompt = prompt) != NULL){ /* XXX not re-evaluated */ tl.tl_prompt_length = plen; tl.tl_prompt_width = pwidth; @@ -2577,21 +4027,23 @@ jredo: tl.tl_x_buf = linebuf; tl.tl_x_bufsize = linesize; + a_tty.tg_line = &tl; a_tty_sigs_up(); a_tty_term_mode(TRU1); nn = a_tty_readline(&tl, n SMALLOC_DEBUG_ARGSCALL); a_tty_term_mode(FAL0); a_tty_sigs_down(); + a_tty.tg_line = NULL; + +# ifdef HAVE_COLOUR + n_colour_env_gut(stdout); +# endif if(tl.tl_reenter_after_cmd != NULL){ - n = 0; + n = (nn <= 0) ? 0 : nn; n_source_command(tl.tl_reenter_after_cmd); goto jredo; } - -# ifdef HAVE_COLOUR - n_colour_env_gut(stdout); -# endif NYD_LEAVE; return (int)nn; } @@ -2619,7 +4071,7 @@ n_tty_addhist(char const *s, bool_t isgabby){ /* Eliminating duplicates is expensive, but simply inacceptable so * during the load of a potentially large history file! */ - if(pstate & PS_HISTORY_LOADED) + if(pstate & PS_LINE_EDITOR_INIT) for(thp = a_tty.tg_hist; thp != NULL; thp = thp->th_older) if(thp->th_len == l && !strcmp(thp->th_dat, s)){ hold_all_sigs(); /* TODO */ @@ -2640,7 +4092,7 @@ n_tty_addhist(char const *s, bool_t isgabby){ hold_all_sigs(); ++a_tty.tg_hist_size; - if((pstate & PS_HISTORY_LOADED) && + if((pstate & PS_LINE_EDITOR_INIT) && a_tty.tg_hist_size > a_tty.tg_hist_size_max){ --a_tty.tg_hist_size; if((thp = a_tty.tg_hist_tail) != NULL){ @@ -2735,6 +4187,218 @@ jentry:{ goto jleave; } # endif /* HAVE_HISTORY */ + +# ifdef HAVE_KEY_BINDINGS +FL int +c_bind(void *v){ + n_CMD_ARG_DESC_SUBCLASS_DEF(bind, 3, a_tty_bind_cad) { /* TODO cmd_tab.h */ + {n_CMD_ARG_DESC_STRING, 0}, + {n_CMD_ARG_DESC_WYSH | n_CMD_ARG_DESC_OPTION | + n_CMD_ARG_DESC_HONOUR_STOP, + n_SHEXP_PARSE_DRYRUN | n_SHEXP_PARSE_LOG}, + {n_CMD_ARG_DESC_WYSH | n_CMD_ARG_DESC_OPTION | n_CMD_ARG_DESC_GREEDY | + n_CMD_ARG_DESC_HONOUR_STOP, + n_SHEXP_PARSE_IGNORE_EMPTY} + } n_CMD_ARG_DESC_SUBCLASS_DEF_END; + struct n_cmd_arg_ctx cac; + struct a_tty_bind_ctx *tbcp; + enum n_lexinput_flags lif; + bool_t aster, show; + union {char const *cp; char *p; char c;} c; + NYD_ENTER; + + cac.cac_desc = n_CMD_ARG_DESC_SUBCLASS_CAST(&a_tty_bind_cad); + cac.cac_indat = v; + cac.cac_inlen = UIZ_MAX; + if(!n_cmd_arg_parse(&cac)){ + v = NULL; + goto jleave; + } + + c.cp = cac.cac_arg->ca_arg.ca_str.s; + if(cac.cac_no == 1) + show = TRU1; + else + show = !asccasecmp(cac.cac_arg->ca_next->ca_arg.ca_str.s, "show"); + aster = FAL0; + + if((lif = a_tty_bind_ctx_find(c.cp)) == (enum n_lexinput_flags)-1){ + if(!(aster = n_is_all_or_aster(c.cp)) || !show){ + n_err(_("`bind': invalid context: %s\n"), c.cp); + v = NULL; + goto jleave; + } + lif = 0; + } + + if(show){ + ui32_t lns; + FILE *fp; + + if((fp = Ftmp(NULL, "bind", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL){ + n_perr(_("tmpfile"), 0); + v = NULL; + goto jleave; + } + + lns = 0; + for(;;){ + for(tbcp = a_tty.tg_bind[lif]; tbcp != NULL; + ++lns, tbcp = tbcp->tbc_next){ + /* Print the bytes of resolved terminal capabilities, then */ + if((options & OPT_D_V) && + (tbcp->tbc_flags & (a_TTY_BIND_RESOLVE | a_TTY_BIND_DEFUNCT) + ) == a_TTY_BIND_RESOLVE){ + char cbuf[8]; + union {wchar_t const *wp; char const *cp;} u; + si32_t entlen; + ui32_t cnvlen; + char const *cnvdat, *bsep, *cbufp; + + putc('#', fp); + putc(' ', fp); + + cbuf[0] = '=', cbuf[2] = '\0'; + for(cnvdat = tbcp->tbc_cnv, cnvlen = tbcp->tbc_cnv_len; + cnvlen > 0;){ + if(cnvdat != tbcp->tbc_cnv) + putc(',', fp); + + /* {si32_t buf_len_iscap;} */ + entlen = *(si32_t const*)cnvdat; + if(entlen & SI32_MIN){ + /* struct{si32_t buf_len_iscap; si32_t cap_len; + * char buf[]+NUL;} */ + for(bsep = "", + u.cp = (char const*)&((si32_t const*)cnvdat)[2]; + (c.c = *u.cp) != '\0'; ++u.cp){ + if(asciichar(c.c) && !cntrlchar(c.c)) + cbuf[1] = c.c, cbufp = cbuf; + else + cbufp = ""; + fprintf(fp, "%s%02X%s", + bsep, (ui32_t)(ui8_t)c.c, cbufp); + bsep = " "; + } + entlen &= SI32_MAX; + }else + putc('-', fp); + + cnvlen -= entlen; + cnvdat += entlen; + } + + fputs("\n ", fp); + ++lns; + } + + fprintf(fp, "%sbind %s %s %s%s%s\n", + ((tbcp->tbc_flags & a_TTY_BIND_DEFUNCT) + /* I18N: `bind' sequence not working, either because it is + * I18N: using Unicode and that is not available in the locale, + * I18N: or a termcap(5)/terminfo(5) sequence won't work out */ + ? _("# ") : ""), + a_tty_bind_ctx_maps[lif].tbcm_name, tbcp->tbc_seq, + n_shell_quote_cp(tbcp->tbc_exp, TRU1), + (tbcp->tbc_flags & a_TTY_BIND_NOCOMMIT ? "@" : ""), + (!(options & OPT_D_VV) ? "" + : (tbcp->tbc_flags & a_TTY_BIND_FUN_INTERNAL + ? _(" # MLE internal") : "")) + ); + } + if(!aster || ++lif >= n__LEXINPUT_CTX_MAX) + break; + } + page_or_print(fp, lns); + + Fclose(fp); + }else{ + struct a_tty_bind_parse_ctx tbpc; + struct n_string store; + + memset(&tbpc, 0, sizeof tbpc); + tbpc.tbpc_cmd = a_tty_bind_cad.cad_name; + tbpc.tbpc_in_seq = cac.cac_arg->ca_next->ca_arg.ca_str.s; + tbpc.tbpc_exp.s = n_string_cp(n_cmd_arg_join_greedy(&cac, + n_string_creat_auto(&store))); + tbpc.tbpc_exp.l = store.s_len; + tbpc.tbpc_flags = lif; + if(!a_tty_bind_create(&tbpc, TRU1)) + v = NULL; + n_string_gut(&store); + } +jleave: + NYD_LEAVE; + return (v != NULL) ? EXIT_OK : EXIT_ERR; +} + +FL int +c_unbind(void *v){ + n_CMD_ARG_DESC_SUBCLASS_DEF(unbind, 2, a_tty_unbind_cad) {/* TODO cmd_tab.h*/ + {n_CMD_ARG_DESC_STRING, 0}, + {n_CMD_ARG_DESC_WYSH | n_CMD_ARG_DESC_HONOUR_STOP, + n_SHEXP_PARSE_DRYRUN | n_SHEXP_PARSE_LOG} + } n_CMD_ARG_DESC_SUBCLASS_DEF_END; + struct a_tty_bind_parse_ctx tbpc; + struct n_cmd_arg_ctx cac; + struct a_tty_bind_ctx *tbcp; + enum n_lexinput_flags lif; + bool_t aster; + union {char const *cp; char *p;} c; + NYD_ENTER; + + cac.cac_desc = n_CMD_ARG_DESC_SUBCLASS_CAST(&a_tty_unbind_cad); + cac.cac_indat = v; + cac.cac_inlen = UIZ_MAX; + if(!n_cmd_arg_parse(&cac)){ + v = NULL; + goto jleave; + } + + c.cp = cac.cac_arg->ca_arg.ca_str.s; + aster = FAL0; + + if((lif = a_tty_bind_ctx_find(c.cp)) == (enum n_lexinput_flags)-1){ + if(!(aster = n_is_all_or_aster(c.cp))){ + n_err(_("`unbind': invalid context: %s\n"), c.cp); + v = NULL; + goto jleave; + } + lif = 0; + } + + c.cp = cac.cac_arg->ca_next->ca_arg.ca_str.s; +jredo: + if(n_is_all_or_aster(c.cp)){ + while((tbcp = a_tty.tg_bind[lif]) != NULL){ + memset(&tbpc, 0, sizeof tbpc); + tbpc.tbpc_tbcp = tbcp; + tbpc.tbpc_flags = lif; + a_tty_bind_del(&tbpc); + } + }else{ + memset(&tbpc, 0, sizeof tbpc); + tbpc.tbpc_cmd = a_tty_unbind_cad.cad_name; + tbpc.tbpc_in_seq = c.cp; + tbpc.tbpc_flags = lif; + + if(UNLIKELY(!a_tty_bind_parse(FAL0, &tbpc))) + v = NULL; + else if(UNLIKELY((tbcp = tbpc.tbpc_tbcp) == NULL)){ + n_err(_("`unbind': no such `bind'ing: %s %s\n"), + a_tty_bind_ctx_maps[lif].tbcm_name, c.cp); + v = NULL; + }else + a_tty_bind_del(&tbpc); + } + + if(aster && ++lif < n__LEXINPUT_CTX_MAX) + goto jredo; +jleave: + NYD_LEAVE; + return (v != NULL) ? EXIT_OK : EXIT_ERR; +} +# endif /* HAVE_KEY_BINDINGS */ #endif /* HAVE_MLE */ /* -- 2.11.4.GIT