Hello all, I have a few patches against mksh R55 which may be of interest to some people.
OpenBSD's ksh is now able to do limited programmable tab completions. The changes to the code are minimal so I ported the change to mksh. In order to complete the first argument to kill(1), for example, one defines an array 'complete_kill_1', which contains possible completions for the first argument, e.g.: set -A complete_kill_1 -- -9 -HUP -INT -TERM (example appropriated from OpenBSD ksh's man page). See the man page for more details. I also have a patch which makes the history synchronisation configurable via a shell option. At present, several concurrent mksh instances share history if they have the same history file, which I'm not used to, being a recent convert to mksh from bash. The shell option when set (which is the default) maintains the current behaviour, however when unset it prevents mksh from loading new commands from the history file This is similar to, but not the same as, bash, the difference being that mksh will write out completed commands to history as they are completed, and not at exit, as with bash. Regards, multiplex'd
diff -urN mksh-old/histrap.c mksh/histrap.c --- mksh-old/histrap.c 2017-04-08 02:07:43.000000000 +0100 +++ mksh/histrap.c 2017-05-10 12:25:19.463249647 +0100 @@ -595,7 +595,7 @@ changed = true; } - return (changed); + return Flag(FHISTSYNC) ? (changed) : false; } #endif @@ -928,7 +928,8 @@ news = base + (size_t)histfsize; if (*news == COMMAND) { hist_source->line--; - histload(hist_source, news, bytes); + if (Flag(FHISTSYNC)) + histload(hist_source, news, bytes); hist_source->line++; lno = hist_source->line; } else diff -urN mksh-old/main.c mksh/main.c --- mksh-old/main.c 2017-04-12 17:02:12.000000000 +0100 +++ mksh/main.c 2017-05-10 12:25:19.463249647 +0100 @@ -300,6 +300,13 @@ */ Flag(FXTRACEREC) = 1; +#if HAVE_PERSISTENT_HISTORY + /* + * Turn on history synchronisation by default. + */ + Flag(FHISTSYNC) = 1; +#endif + /* define built-in commands and see if we were called as one */ ktinit(APERM, &builtins, /* currently up to 54 builtins: 75% of 128 = 2^7 */ diff -urN mksh-old/mksh.1 mksh/mksh.1 --- mksh-old/mksh.1 2017-04-12 19:31:25.000000000 +0100 +++ mksh/mksh.1 2017-05-10 12:25:12.423249869 +0100 @@ -1878,7 +1878,11 @@ of several lines) are appended once they successfully compiled. Also, several invocations of the shell will share history if their .Ev HISTFILE -parameters all point to the same file. +parameters all point to the same file and the +.Ic histsync +shell option is set. The +.Ic histsync +option is set by default. .Pp .Sy Note : If @@ -4137,6 +4141,9 @@ .Sx Aliases above). Enabled by default for non-interactive shells. +.It Fl H \*(Ba Fl o Ic histsync +History is synchronised between concurrent shell sessions. +Enabled by default. .It Fl i \*(Ba Fl o Ic interactive The shell is an interactive shell. This option can only be used when the shell is invoked. diff -urN mksh-old/sh_flags.opt mksh/sh_flags.opt --- mksh-old/sh_flags.opt 2017-02-18 02:33:42.000000000 +0000 +++ mksh/sh_flags.opt 2017-05-10 12:25:19.463249647 +0100 @@ -63,6 +63,10 @@ >|!MKSH_NO_CMDLINE_EDITING FN("gmacs", FGMACS, OF_ANY +/* -H synchronise history between concurrent shell sessions */ +>H|HAVE_PERSISTENT_HISTORY +FN("histsync", FHISTSYNC, OF_ANY + /* ./. reading EOF does not exit */ >| FN("ignoreeof", FIGNOREEOF, OF_ANY
diff -urN mksh-old/edit.c mksh/edit.c --- mksh-old/edit.c 2017-04-12 17:46:47.000000000 +0100 +++ mksh/edit.c 2017-05-04 19:43:40.128899013 +0100 @@ -610,6 +610,81 @@ } static int +x_try_array(const char *buf, int buflen, const char *want, int wantlen, + int *nwords, char ***words) +{ + const char *cmd, *cp; + int cmdlen, n, i, slen; + char *name, *s; + struct tbl *v, *vp; + + *nwords = 0; + *words = NULL; + + /* Walk back to find start of command. */ + if (want == buf) + return 0; + for (cmd = want; cmd > buf; cmd--) { + if (strchr(";|&()`", cmd[-1]) != NULL) + break; + } + while (cmd < want && ksh_isspace((u_char)*cmd)) + cmd++; + cmdlen = 0; + while (cmd + cmdlen < want && !ksh_isspace((u_char)cmd[cmdlen])) + cmdlen++; + for (i = 0; i < cmdlen; i++) { + if (!ksh_isalnux((u_char)cmd[i]) && cmd[i] != '_') + return 0; + } + + /* Take a stab at argument count from here. */ + n = 1; + for (cp = cmd + cmdlen + 1; cp < want; cp++) { + if (!ksh_isspace((u_char)cp[-1]) && ksh_isspace((u_char)*cp)) + n++; + } + + /* Try to find the array. */ + if (asprintf(&name, "complete_%.*s_%d", cmdlen, cmd, n) < 0) + internal_errorf("unable to allocate memory"); + v = global(name); + free(name); + if (~v->flag & (ISSET|ARRAY)) { + if (asprintf(&name, "complete_%.*s", cmdlen, cmd) < 0) + internal_errorf("unable to allocate memory"); + v = global(name); + free(name); + if (~v->flag & (ISSET|ARRAY)) + return 0; + } + + /* Walk the array and build words list. */ + for (vp = v; vp; vp = vp->u.array) { + if (~vp->flag & ISSET) + continue; + + s = str_val(vp); + slen = strlen(s); + + if (slen < wantlen) + continue; + if (slen > wantlen) + slen = wantlen; + if (slen != 0 && strncmp(s, want, slen) != 0) + continue; + + *words = aresize(*words, ((*nwords) + 2) * (sizeof **words), + ATEMP); + strdupx((*words)[(*nwords)++], s, ATEMP); + } + if (*nwords != 0) + (*words)[*nwords] = NULL; + + return *nwords != 0; +} + +static int x_cf_glob(int *flagsp, const char *buf, int buflen, int pos, int *startp, int *endp, char ***wordsp) { @@ -676,9 +751,10 @@ * Expand (glob) it now. */ - nwords = is_command ? - x_command_glob(*flagsp, toglob, &words) : - x_file_glob(flagsp, toglob, &words); + if (is_command) + nwords = x_command_glob(*flagsp, toglob, &words); + else if (!x_try_array(buf, buflen, buf + *startp, len, &nwords, &words)) + nwords = x_file_glob(flagsp, toglob, &words); afree(toglob, ATEMP); } if (nwords == 0) { diff -urN mksh-old/mksh.1 mksh/mksh.1 --- mksh-old/mksh.1 2017-04-12 19:31:25.000000000 +0100 +++ mksh/mksh.1 2017-05-04 19:43:40.128899013 +0100 @@ -5590,6 +5590,24 @@ If there is no command or file name with the current partial word as its prefix, a bell character is output (usually causing a beep to be sounded). +.Pp +Custom completions may be configured by creating an array named +.Ql complete_command , +optionally suffixed with an argument number to complete only for a single +argument. +So defining an array named +.Ql complete_kill +provides possible completions for any argument to the +.Xr kill 1 +command, but +.Ql complete_kill_1 +only completes the first argument. +For example, the following command makes +.Nm +offer a selection of signal names for the first argument to +.Xr kill 1 : +.Pp +.Dl set -A complete_kill_1 -- -9 -HUP -INFO -KILL -TERM .It complete\-command: \*(haX\*(ha[ Automatically completes as much as is unique of the command name having the partial word up to the cursor as its prefix, as in the