lobase

Linux port of OpenBSDs userland.
Log | Files | Refs | README

commit 8a36ce0ce894e41b6bf0d8305a297d617b89be67
parent 3ebfe629b6192a641321e4ae951835578d8d3ce8
Author: Duncaen <mail@duncano.de>
Date:   Wed, 24 May 2017 01:55:51 +0200

bin/ksh: update to OPENBSD_6_1

Diffstat:
bin/ksh/edit.c | 108++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
bin/ksh/emacs.c | 7++++---
bin/ksh/exec.c | 35+++++++++++++++++++++--------------
bin/ksh/history.c | 30+++++++++++++++++++++++-------
bin/ksh/ksh.1 | 277+++++++++++++++++++++++++++++++++++++++++++------------------------------------
bin/ksh/main.c | 4++--
bin/ksh/misc.c | 2+-
bin/ksh/sh.1 | 37+++++++++++++++++++++++++------------
bin/ksh/sh.h | 11++++++-----
bin/ksh/var.c | 17+++++++++++------
bin/ksh/vi.c | 416+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
11 files changed, 596 insertions(+), 348 deletions(-)

diff --git a/bin/ksh/edit.c b/bin/ksh/edit.c @@ -1,4 +1,4 @@ -/* $OpenBSD: edit.c,v 1.53 2016/03/17 23:33:23 mmcc Exp $ */ +/* $OpenBSD: edit.c,v 1.57 2016/09/08 12:12:40 nicm Exp $ */ /* * Command line editing - common code @@ -15,6 +15,7 @@ #include <errno.h> #include <libgen.h> #include <stdlib.h> +#include <stdio.h> #include <string.h> #include <unistd.h> @@ -298,9 +299,7 @@ static void glob_path(int flags, const char *pat, XPtrV *wp, void x_print_expansions(int nwords, char *const *words, int is_command) { - int use_copy = 0; int prefix_len; - XPtrV l; /* Check if all matches are in the same directory (in this * case, we want to omit the directory name) @@ -319,25 +318,29 @@ x_print_expansions(int nwords, char *const *words, int is_command) break; /* All in same directory? */ if (i == nwords) { + XPtrV l; + while (prefix_len > 0 && words[0][prefix_len - 1] != '/') prefix_len--; - use_copy = 1; XPinit(l, nwords + 1); for (i = 0; i < nwords; i++) XPput(l, words[i] + prefix_len); XPput(l, NULL); + + /* Enumerate expansions */ + x_putc('\r'); + x_putc('\n'); + pr_list((char **) XPptrv(l)); + + XPfree(l); /* not x_free_words() */ + return; } } - /* - * Enumerate expansions - */ + /* Enumerate expansions */ x_putc('\r'); x_putc('\n'); - pr_list(use_copy ? (char **) XPptrv(l) : words); - - if (use_copy) - XPfree(l); /* not x_free_words() */ + pr_list(words); } /* @@ -576,13 +579,88 @@ x_locate_word(const char *buf, int buflen, int pos, int *startp, return end - start; } +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 && isspace((u_char)*cmd)) + cmd++; + cmdlen = 0; + while (cmd + cmdlen < want && !isspace((u_char)cmd[cmdlen])) + cmdlen++; + for (i = 0; i < cmdlen; i++) { + if (!isalnum((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 (!isspace((u_char)cp[-1]) && isspace((u_char)*cp)) + n++; + } + + /* Try to find the array. */ + if (asprintf(&name, "complete_%.*s_%d", cmdlen, cmd, n) < 0) + internal_errorf(1, "unable to allocate memory"); + v = global(name); + free(name); + if (~v->flag & (ISSET|ARRAY)) { + if (asprintf(&name, "complete_%.*s", cmdlen, cmd) < 0) + internal_errorf(1, "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 = areallocarray(*words, (*nwords) + 2, sizeof **words, + ATEMP); + (*words)[(*nwords)++] = str_save(s, ATEMP); + } + if (*nwords != 0) + (*words)[*nwords] = NULL; + + return *nwords != 0; +} + int x_cf_glob(int flags, const char *buf, int buflen, int pos, int *startp, int *endp, char ***wordsp, int *is_commandp) { int len; int nwords; - char **words; + char **words = NULL; int is_command; len = x_locate_word(buf, buflen, pos, startp, &is_command); @@ -595,8 +673,10 @@ x_cf_glob(int flags, const char *buf, int buflen, int pos, int *startp, if (len == 0 && is_command) return 0; - nwords = (is_command ? x_command_glob : x_file_glob)(flags, - buf + *startp, len, &words); + if (is_command) + nwords = x_command_glob(flags, buf + *startp, len, &words); + else if (!x_try_array(buf, buflen, buf + *startp, len, &nwords, &words)) + nwords = x_file_glob(flags, buf + *startp, len, &words); if (nwords == 0) { *wordsp = NULL; return 0; diff --git a/bin/ksh/emacs.c b/bin/ksh/emacs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: emacs.c,v 1.65 2016/01/26 17:39:31 mmcc Exp $ */ +/* $OpenBSD: emacs.c,v 1.66 2016/08/09 11:04:46 schwarze Exp $ */ /* * Emacs-like command line editing and history @@ -893,9 +893,10 @@ x_search_hist(int c) if ((c = x_e_getc()) < 0) return KSTD; f = kb_find_hist_func(c); - if (c == CTRL('[')) + if (c == CTRL('[')) { + x_e_ungetc(c); break; - else if (f == x_search_hist) + } else if (f == x_search_hist) offset = x_search(pat, 0, offset); else if (f == x_del_back) { if (p == pat) { diff --git a/bin/ksh/exec.c b/bin/ksh/exec.c @@ -1,4 +1,4 @@ -/* $OpenBSD: exec.c,v 1.64 2015/12/30 09:07:00 tedu Exp $ */ +/* $OpenBSD: exec.c,v 1.68 2016/12/11 17:49:19 millert Exp $ */ /* * execute command tree @@ -40,9 +40,12 @@ static void dbteste_error(Test_env *, int, const char *); */ int execute(struct op *volatile t, - volatile int flags, volatile int *xerrok) /* if XEXEC don't fork */ + volatile int flags, /* if XEXEC don't fork */ + volatile int *xerrok) /* inform recursive callers in -e mode that + * short-circuit && or || shouldn't be treated + * as an error */ { - int i, dummy = 0; + int i, dummy = 0, save_xerrok = 0; volatile int rv = 0; int pv[2]; char ** volatile ap; @@ -240,16 +243,14 @@ execute(struct op *volatile t, rv = execute(t->right, flags & XERROK, xerrok); else { flags |= XERROK; - if (xerrok) - *xerrok = 1; + *xerrok = 1; } break; case TBANG: rv = !execute(t->right, XERROK, xerrok); flags |= XERROK; - if (xerrok) - *xerrok = 1; + *xerrok = 1; break; case TDBRACKET: @@ -289,10 +290,15 @@ execute(struct op *volatile t, } rv = 0; /* in case of a continue */ if (t->type == TFOR) { + save_xerrok = *xerrok; while (*ap != NULL) { setstr(global(t->str), *ap++, KSH_UNWIND_ERROR); + /* undo xerrok in all iterations except the + * last */ + *xerrok = save_xerrok; rv = execute(t->left, flags & XERROK, xerrok); } + /* ripple xerrok set at final iteration */ } else { /* TSELECT */ for (;;) { if (!(cp = do_selectargs(ap, is_first))) { @@ -339,11 +345,13 @@ execute(struct op *volatile t, case TCASE: cp = evalstr(t->str, DOTILDE); - for (t = t->left; t != NULL && t->type == TPAT; t = t->right) - for (ap = t->vars; *ap; ap++) - if ((s = evalstr(*ap, DOTILDE|DOPAT)) && - gmatch(cp, s, false)) - goto Found; + for (t = t->left; t != NULL && t->type == TPAT; t = t->right) { + for (ap = t->vars; *ap; ap++) { + if ((s = evalstr(*ap, DOTILDE|DOPAT)) && + gmatch(cp, s, false)) + goto Found; + } + } break; Found: rv = execute(t->left, flags & XERROK, xerrok); @@ -381,8 +389,7 @@ execute(struct op *volatile t, quitenv(NULL); /* restores IO */ if ((flags&XEXEC)) unwind(LEXIT); /* exit child */ - if (rv != 0 && !(flags & XERROK) && - (xerrok == NULL || !*xerrok)) { + if (rv != 0 && !(flags & XERROK) && !*xerrok) { trapsig(SIGERR_); if (Flag(FERREXIT)) unwind(LERROR); diff --git a/bin/ksh/history.c b/bin/ksh/history.c @@ -1,4 +1,4 @@ -/* $OpenBSD: history.c,v 1.56 2015/12/30 09:07:00 tedu Exp $ */ +/* $OpenBSD: history.c,v 1.58 2016/08/24 16:09:40 millert Exp $ */ /* * command history @@ -14,6 +14,7 @@ */ #include <sys/stat.h> +#include <sys/uio.h> #include <errno.h> #include <fcntl.h> @@ -60,9 +61,15 @@ c_fc(char **wp) struct temp *tf = NULL; char *p, *editor = NULL; int gflag = 0, lflag = 0, nflag = 0, sflag = 0, rflag = 0; - int optc; + int optc, ret; char *first = NULL, *last = NULL; char **hfirst, **hlast, **hp; + static int depth; + + if (depth != 0) { + bi_errorf("history function called recursively"); + return 1; + } if (!Flag(FTALKING_I)) { bi_errorf("history functions not available"); @@ -145,7 +152,10 @@ c_fc(char **wp) hist_get_newest(false); if (!hp) return 1; - return hist_replace(hp, pat, rep, gflag); + depth++; + ret = hist_replace(hp, pat, rep, gflag); + depth--; + return ret; } if (editor && (lflag || nflag)) { @@ -229,7 +239,6 @@ c_fc(char **wp) /* XXX: source should not get trashed by this.. */ { Source *sold = source; - int ret; ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_", 0); source = sold; @@ -265,7 +274,10 @@ c_fc(char **wp) shf_close(shf); *xp = '\0'; strip_nuls(Xstring(xs, xp), Xlength(xs, xp)); - return hist_execute(Xstring(xs, xp)); + depth++; + ret = hist_execute(Xstring(xs, xp)); + depth--; + return ret; } } @@ -890,6 +902,7 @@ writehistfile(int lno, char *cmd) unsigned char *new; int bytes; unsigned char hdr[5]; + struct iovec iov[2]; (void) flock(histfd, LOCK_EX); sizenow = lseek(histfd, 0L, SEEK_END); @@ -930,8 +943,11 @@ writehistfile(int lno, char *cmd) hdr[2] = (lno>>16)&0xff; hdr[3] = (lno>>8)&0xff; hdr[4] = lno&0xff; - (void) write(histfd, hdr, 5); - (void) write(histfd, cmd, strlen(cmd)+1); + iov[0].iov_base = hdr; + iov[0].iov_len = 5; + iov[1].iov_base = cmd; + iov[1].iov_len = strlen(cmd) + 1; + (void) writev(histfd, iov, 2); hsize = lseek(histfd, 0L, SEEK_END); (void) flock(histfd, LOCK_UN); return; diff --git a/bin/ksh/ksh.1 b/bin/ksh/ksh.1 @@ -1,8 +1,8 @@ -.\" $OpenBSD: ksh.1,v 1.179 2016/04/27 12:46:23 naddy Exp $ +.\" $OpenBSD: ksh.1,v 1.187 2017/02/19 22:09:18 schwarze Exp $ .\" .\" Public Domain .\" -.Dd $Mdocdate: April 27 2016 $ +.Dd $Mdocdate: February 19 2017 $ .Dt KSH 1 .Os .Sh NAME @@ -124,10 +124,10 @@ option of the built-in command can't be used. .It Redirections that create files can't be used (i.e.\& -.Ql > , -.Ql >| , -.Ql >> , -.Ql <> ) . +.Sq Cm > , +.Sq Cm >| , +.Sq Cm >> , +.Sq Cm <> ) . .El .It Fl s The shell reads commands from standard input; all non-option arguments @@ -219,12 +219,12 @@ Aside from delimiting words, spaces and tabs are ignored, while newlines usually delimit commands. The meta-characters are used in building the following .Em tokens : -.Ql < , -.Ql <& , -.Ql << , -.Ql > , -.Ql >& , -.Ql >> , +.Sq Cm < , +.Sq Cm <& , +.Sq Cm << , +.Sq Cm > , +.Sq Cm >& , +.Sq Cm >> , etc. are used to specify redirections (see .Sx Input/output redirection below); @@ -497,12 +497,12 @@ Note that and .Ql } are reserved words, not meta-characters. -.It Xo case Ar word No in +.It Xo Ic case Ar word Cm in .Oo Op \&( .Ar pattern .Op | Ar pattern .No ... ) -.Ar list No ;;\ \& Oc ... esac +.Ar list No ;;\ \& Oc ... Cm esac .Xc The .Ic case @@ -527,9 +527,9 @@ Both the word and the patterns are subject to parameter, command, and arithmetic substitution, as well as tilde substitution. For historical reasons, open and close braces may be used instead of -.Ic in +.Cm in and -.Ic esac +.Cm esac e.g.\& .Ic case $foo { *) echo bar; } . The exit status of a @@ -539,9 +539,9 @@ statement is that of the executed if no .Ar list is executed, the exit status is zero. -.It Xo for Ar name -.Oo in Ar word No ... Oc ; -.No do Ar list ; No done +.It Xo Ic for Ar name +.Oo Cm in Ar word No ... Oc ; +.Cm do Ar list ; Cm done .Xc For each .Ar word @@ -551,14 +551,14 @@ is set to the word and .Ar list is executed. If -.Ic in +.Cm in is not used to specify a word list, the positional parameters ($1, $2, etc.)\& are used instead. For historical reasons, open and close braces may be used instead of -.Ic do +.Cm do and -.Ic done +.Cm done e.g.\& .Ic for i; { echo $i; } . The exit status of a @@ -568,13 +568,12 @@ statement is the last exit status of if .Ar list is never executed, the exit status is zero. -.It Xo if Ar list ; -.No then Ar list ; -.Oo elif Ar list ; -.No then Ar list ; Oc -.No ... -.Oo else Ar list ; Oc -.No fi +.It Xo Ic if Ar list ; +.Cm then Ar list ; +.Oo Cm elif Ar list ; +.Cm then Ar list ; Oc ... +.Oo Cm else Ar list ; Oc +.Cm fi .Xc If the exit status of the first .Ar list @@ -583,16 +582,16 @@ is zero, the second is executed; otherwise, the .Ar list following the -.Ic elif , +.Cm elif , if any, is executed with similar consequences. If all the lists following the .Ic if and -.Ic elif Ns s +.Cm elif Ns s fail (i.e. exit with non-zero status), the .Ar list following the -.Ic else +.Cm else is executed. The exit status of an .Ic if @@ -601,9 +600,9 @@ statement is that of non-conditional that is executed; if no non-conditional .Ar list is executed, the exit status is zero. -.It Xo select Ar name -.Oo in Ar word No ... Oc ; -.No do Ar list ; No done +.It Xo Ic select Ar name +.Oo Cm in Ar word No ... Oc ; +.Cm do Ar list ; Cm done .Xc The .Ic select @@ -613,7 +612,8 @@ An enumerated list of the specified .Ar word Ns (s) is printed on standard error, followed by a prompt .Po -.Ev PS3: normally +.Ev PS3 : +normally .Sq #?\ \& .Pc . A number corresponding to one of the enumerated words is then read from @@ -645,9 +645,9 @@ If is omitted, the positional parameters are used (i.e. $1, $2, etc.). For historical reasons, open and close braces may be used instead of -.Ic do +.Cm do and -.Ic done +.Cm done e.g.\& .Ic select i; { echo $i; } . The exit status of a @@ -655,18 +655,18 @@ The exit status of a statement is zero if a .Ic break statement is used to exit the loop, non-zero otherwise. -.It Xo until Ar list ; -.No do Ar list ; -.No done +.It Xo Ic until Ar list ; +.Cm do Ar list ; +.Cm done .Xc This works like .Ic while , except that the body is executed only while the exit status of the first .Ar list is non-zero. -.It Xo while Ar list ; -.No do Ar list ; -.No done +.It Xo Ic while Ar list ; +.Cm do Ar list ; +.Cm done .Xc A .Ic while @@ -679,7 +679,7 @@ The exit status of a statement is the last exit status of the .Ar list in the body of the loop; if the body is not executed, the exit status is zero. -.It Xo function Ar name +.It Xo Ic function Ar name .No { Ar list ; No } .Xc Defines the function @@ -704,21 +704,21 @@ The reserved word is described in the .Sx Command execution section. -.It (( Ar expression No )) +.It Ic (( Ar expression Cm )) The arithmetic expression .Ar expression is evaluated; equivalent to -.Dq let expression +.Ic let Ar expression (see .Sx Arithmetic expressions and the .Ic let command, below). -.It Bq Bq Ar \ \&expression\ \& +.It Ic [[ Ar expression Cm ]] Similar to the .Ic test and -.Ic \&[ ... \&] +.Ic \&[ No ... Cm \&] commands (described later), with the following exceptions: .Bl -bullet -offset indent .It @@ -836,19 +836,31 @@ when a quoted word is found, or when an alias word that is currently being expanded is found. .Pp The following command aliases are defined automatically by the shell: -.Bd -literal -offset indent -autoload='typeset -fu' -functions='typeset -f' -hash='alias -t' -history='fc -l' -integer='typeset -i' -local='typeset' -login='exec login' -nohup='nohup ' -r='fc -s' -stop='kill -STOP' -type='whence -v' -.Ed +.Pp +.Bl -item -compact -offset indent +.It +.Ic autoload Ns ='typeset -fu' +.It +.Ic functions Ns ='typeset -f' +.It +.Ic hash Ns ='alias -t' +.It +.Ic history Ns ='fc -l' +.It +.Ic integer Ns ='typeset -i' +.It +.Ic local Ns ='typeset' +.It +.Ic login Ns ='exec login' +.It +.Ic nohup Ns ='nohup ' +.It +.Ic r Ns ='fc -s' +.It +.Ic stop Ns ='kill -STOP' +.It +.Ic type Ns ='whence -v' +.El .Pp Tracked aliases allow the shell to remember where it found a particular command. @@ -1007,12 +1019,6 @@ has the same effect as .Ic $(cat foo) , but it is carried out more efficiently because no process is started. .Pp -.Sy Note : -.Pf $( Ar command ) -expressions are currently parsed by finding the matching parenthesis, -regardless of quoting. -This should be fixed soon. -.Pp Arithmetic substitutions are replaced by the value of the specified expression. For example, the command .Ic echo $((2+3*4)) @@ -1414,10 +1420,7 @@ If .Ev HISTFILE isn't set, no history file is used. This is different from the original Korn shell, which uses -.Pa $HOME/.sh_history ; -in the future, -.Nm ksh -may also use a default history file. +.Pa $HOME/.sh_history . .It Ev HISTSIZE The number of commands normally stored for history. The default is 500. @@ -1553,7 +1556,7 @@ The current date, in the format .Dq Day Month Date for example .Dq Wed Nov 03 . -.It Li \eD{ Ns Ar format Ns Li } +.It Li \eD Ns Brq Ar format The current date, with .Ar format converted by @@ -1701,12 +1704,10 @@ is used to produce values. If the variable .Ev RANDOM is assigned a value, the value is used as the seed to -.Xr srand 3 +.Xr srand_deterministic 3 and subsequent references of .Ev RANDOM -will use -.Xr rand 3 -to produce values, resulting in a predictable sequence. +produce a predictable sequence. .It Ev REPLY Default parameter for the .Ic read @@ -1988,7 +1989,7 @@ input is initially set to be from .Pa /dev/null , and commands for which any of the following redirections have been specified: .Bl -tag -width Ds -.It > Ar file +.It Cm > Ar file Standard output is redirected to .Ar file . If @@ -2004,30 +2005,30 @@ for reading and then truncate it when it opens it for writing, before .Ar cmd gets a chance to actually read .Ar foo . -.It >| Ar file +.It Cm >| Ar file Same as -.Ic > , +.Cm > , except the file is truncated, even if the .Ic noclobber option is set. -.It >> Ar file +.It Cm >> Ar file Same as -.Ic > , +.Cm > , except if .Ar file exists it is appended to instead of being truncated. Also, the file is opened in append mode, so writes always go to the end of the file (see .Xr open 2 ) . -.It < Ar file +.It Cm < Ar file Standard input is redirected from .Ar file , which is opened for reading. -.It <> Ar file +.It Cm <> Ar file Same as -.Ic < , +.Cm < , except the file is opened for reading and writing. -.It << Ar marker +.It Cm << Ar marker After reading the command line containing this kind of redirection (called a .Dq here document ) , the shell copies lines from the command source into a temporary file until a @@ -2051,11 +2052,11 @@ and .Ql \enewline . If multiple here documents are used on the same command line, they are saved in order. -.It <<- Ar marker +.It Cm <<- Ar marker Same as -.Ic << , +.Cm << , except leading tabs are stripped from lines in the here document. -.It <& Ar fd +.It Cm <& Ar fd Standard input is duplicated from file descriptor .Ar fd . .Ar fd @@ -2066,9 +2067,9 @@ indicating the file descriptor associated with the output of the current co-process; or the character .Ql - , indicating standard input is to be closed. -.It >& Ar fd +.It Cm >& Ar fd Same as -.Ic <& , +.Cm <& , except the operation is done on standard output. .El .Pp @@ -2278,19 +2279,19 @@ operator, is an asynchronous process that the shell can both write to (using and read from (using .Ic read -p ) . The input and output of the co-process can also be manipulated using -.Ic >&p +.Cm >&p and -.Ic <&p +.Cm <&p redirections, respectively. Once a co-process has been started, another can't be started until the co-process exits, or until the co-process's input has been redirected using an -.Ic exec Ar n Ns Ic >&p +.Ic exec Ar n Ns Cm >&p redirection. If a co-process's input is redirected in this way, the next co-process to be started will share the output with the first co-process, unless the output of the initial co-process has been redirected using an -.Ic exec Ar n Ns Ic <&p +.Ic exec Ar n Ns Cm <&p redirection. .Pp Some notes concerning co-processes: @@ -2431,19 +2432,6 @@ inside a function interferes with using .Ic getopts outside the function). .El -.Pp -In the future, the following differences will also be added: -.Bl -bullet -.It -A separate trap/signal environment will be used during the execution of -functions. -This will mean that traps set inside a function will not affect the -shell's traps and signals that are not ignored in the shell (but may be -trapped) will have their default effect in a function. -.It -The EXIT trap, if set in a function, will be executed after the function -returns. -.El .Ss POSIX mode The shell is intended to be POSIX compliant; however, in some cases, POSIX behaviour is contrary either to @@ -2472,9 +2460,6 @@ output. In POSIX mode, only signal names are listed (in a single line); in non-POSIX mode, signal numbers, names, and descriptions are printed (in columns). -In the future, a new option -.Pq Fl v No perhaps -will be added to distinguish the two behaviours. .It .Ic echo options. @@ -2694,11 +2679,6 @@ regular commands .Ic print , suspend , test , .Ic ulimit , whence .Pp -In the future, the additional -.Nm -special and regular commands may be treated -differently from the POSIX special and regular commands. -.Pp Once the type of command has been determined, any command-line parameter assignments are performed and exported for the duration of the command. .Pp @@ -2809,9 +2789,7 @@ for more information. .Ar ... .Xc The specified editing command is bound to the given -.Ar string , -which should consist of a control character -optionally preceded by one of the two prefix characters. +.Ar string . Future input of the .Ar string will cause the editing command to be immediately invoked. @@ -2822,10 +2800,23 @@ flag is given, the specified input will afterwards be immediately replaced by the given .Ar substitute string, which may contain editing commands. +Control characters may be written using caret notation. +For example, ^X represents Control-X. .Pp -Control characters may be written using caret notation -i.e. ^X represents Control-X. -Multi-character sequences are supported. +If a certain character occurs as the first character of any bound +multi-character +.Ar string +sequence, that character becomes a command prefix character. +Any character sequence that starts with a command prefix character +but that is not bound to a command or substitute +is implicitly considered as bound to the +.Sq error +command. +By default, two command prefix characters exist: +Escape +.Pq ^[ +and Control-X +.Pq ^X . .Pp The following default bindings show how the arrow keys on an ANSI terminal or xterm are bound @@ -3516,8 +3507,12 @@ prompt. Only used if job control is enabled .Pq Fl m . .It Fl C | Ic noclobber -Prevent > redirection from overwriting existing files. -Instead, >| must be used to force an overwrite. +Prevent +.Cm > +redirection from overwriting existing files. +Instead, +.Cm >| +must be used to force an overwrite. .It Fl e | Ic errexit Exit (after executing the .Dv ERR @@ -3627,8 +3622,8 @@ See above for a description of what this means. Do not kill running jobs with a .Dv SIGHUP signal when a login shell exits. -Currently set by default, but this will -change in the future to be compatible with the original Korn shell (which +Currently set by default; +this is different from the original Korn shell (which doesn't have this option, but does send the .Dv SIGHUP signal). @@ -4278,6 +4273,7 @@ kilobytes on the amount of locked (wired) physical memory. Impose a limit of .Ar n kilobytes on the amount of physical memory used. +This limit is not enforced. .It Fl n Ar n Impose a limit of .Ar n @@ -4705,6 +4701,24 @@ is appended. 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: ^X^[ 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 @@ -5561,3 +5575,14 @@ The .Pa CONTRIBUTORS file in the source distribution contains a more complete list of people and their part in the shell's development. +.Sh BUGS +.Pf $( Ar command ) +expressions are currently parsed by finding the closest matching (unquoted) +parenthesis. +Thus constructs inside +.Pf $( Ar command ) +may produce an error. +For example, the parenthesis in +.Ql x);; +is interpreted as the closing parenthesis in +.Ql $(case x in x);; *);; esac) . diff --git a/bin/ksh/main.c b/bin/ksh/main.c @@ -1,4 +1,4 @@ -/* $OpenBSD: main.c,v 1.79 2016/03/04 15:11:06 deraadt Exp $ */ +/* $OpenBSD: main.c,v 1.82 2016/10/17 17:44:47 schwarze Exp $ */ /* * startup, main loop, environments and error handling @@ -84,7 +84,7 @@ static const char initsubs[] = "${PS2=> } ${PS3=#? } ${PS4=+ }"; static const char *initcoms [] = { "typeset", "-r", "KSH_VERSION", NULL, "typeset", "-x", "SHELL", "PATH", "HOME", NULL, - "typeset", "-i", "PPID", NULL, + "typeset", "-ir", "PPID", NULL, "typeset", "-i", "OPTIND=1", NULL, "eval", "typeset -i RANDOM MAILCHECK=\"${MAILCHECK-600}\" SECONDS=\"${SECONDS-0}\" TMOUT=\"${TMOUT-0}\"", NULL, "alias", diff --git a/bin/ksh/misc.c b/bin/ksh/misc.c @@ -115,7 +115,7 @@ Xcheck_grow_(XString *xsp, char *xp, int more) return xsp->beg + (xp - old_beg); } -const struct option options[] = { +const struct ksh_option options[] = { /* Special cases (see parse_args()): -A, -o, -s. * Options are sorted by their longnames - the order of these * entries MUST match the order of sh_flag F* enumerations in sh.h. diff --git a/bin/ksh/sh.1 b/bin/ksh/sh.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: sh.1,v 1.134 2016/07/18 18:24:21 jmc Exp $ +.\" $OpenBSD: sh.1,v 1.141 2017/03/16 20:06:37 jmc Exp $ .\" .\" Copyright (c) 2015 Jason McIntyre <jmc@openbsd.org> .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: July 18 2016 $ +.Dd $Mdocdate: March 16 2017 $ .Dt SH 1 .Os .Sh NAME @@ -42,9 +42,9 @@ This version of is actually .Nm ksh in disguise. -As such, it will also accept options documented in +As such, it also supports the features described in .Xr ksh 1 . -This manual page describes only features +This manual page describes only the parts relevant to a POSIX compliant .Nm . If portability is a concern, @@ -121,8 +121,6 @@ Do not expand file name patterns. When a utility is first executed, hash (record) its location so that future invocations do not need to search for it. -Builtins are not hashed, regardless of whether this option is set or not. -This option is set by default for non-interactive shells. .It Fl i Enable behaviour convenient for an interactive shell. This option is set by default @@ -510,6 +508,14 @@ is a colon, .Ev OPTARG is set to the unsupported option, otherwise an error message is displayed. +.It Ic hash Op Fl r | Ar utility +Add +.Ar utility +to the hash list +or remove +.Pq Fl r +all utilities from the hash list. +Without arguments, show the utilities currently hashed. .It Ic jobs Oo Fl l | p Oc Op Ar id ... Display the status of all jobs in the current shell environment, or those selected by @@ -739,8 +745,6 @@ no change occurs. .It Ic times Display accumulated process times for the shell (user and system) and all child processes (user and system). -.It Ic true -Return a true (zero) value. .It Ic trap Op Ar action signal ... Perform .Ar action @@ -776,6 +780,18 @@ otherwise should be a signal name (without the SIG prefix) or number. +.It Ic true +Return a true (zero) value. +.It Ic type Ar command ... +For each +.Ar command , +show how the shell would interpret it. +.It Ic ulimit Op Fl f Ar n +Limit the maximum size of a file that can be created to +.Ar n +blocks. +Without arguments, +display the current file size limit. .It Ic umask Oo Fl S Oc Op Ar mask Set the file mode creation mask to .Ar mask . @@ -1646,7 +1662,7 @@ where commands are executed in the order given. The exit status of a sequential list is that of the last command executed. The format for a sequential list is: .Pp -.D1 Ar command\ \& ; Op Ar command ... +.D1 Ar command No \&; Op Ar command ... .Pp A series of one or more commands separated by .Sq & @@ -2197,9 +2213,6 @@ This implementation of .Nm does not provide notification when these files are created. .It -Command substitution occurring within double quotes -is subject to pathname expansion but should not be. -.It The built-in .Ic newgrp is unsupported. diff --git a/bin/ksh/sh.h b/bin/ksh/sh.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sh.h,v 1.57 2016/03/04 15:11:06 deraadt Exp $ */ +/* $OpenBSD: sh.h,v 1.58 2016/09/08 15:50:50 millert Exp $ */ /* * Public Domain Bourne/Korn shell @@ -119,12 +119,12 @@ extern struct env *genv; #define OF_INTERNAL 0x08 /* set internally by shell */ #define OF_ANY (OF_CMDLINE | OF_SET | OF_SPECIAL | OF_INTERNAL) -struct option { +struct ksh_option { const char *name; /* long name of option */ char c; /* character flag (if any) */ short flags; /* OF_* */ }; -extern const struct option options[]; +extern const struct ksh_option options[]; /* * flags (the order of these enums MUST match the order in misc.c(options[])) @@ -367,8 +367,9 @@ extern int x_cols; /* tty columns */ #define KSH_SYSTEM_PROFILE "/etc/profile" /* Used by v_evaluate() and setstr() to control action when error occurs */ -#define KSH_UNWIND_ERROR 0 /* unwind the stack (longjmp) */ -#define KSH_RETURN_ERROR 1 /* return 1/0 for success/failure */ +#define KSH_UNWIND_ERROR 0x0 /* unwind the stack (longjmp) */ +#define KSH_RETURN_ERROR 0x1 /* return 1/0 for success/failure */ +#define KSH_IGNORE_RDONLY 0x4 /* ignore the read-only flag */ #include "shf.h" #include "table.h" diff --git a/bin/ksh/var.c b/bin/ksh/var.c @@ -1,4 +1,4 @@ -/* $OpenBSD: var.c,v 1.55 2015/12/30 09:07:00 tedu Exp $ */ +/* $OpenBSD: var.c,v 1.57 2016/09/08 15:50:50 millert Exp $ */ #include <sys/stat.h> @@ -356,8 +356,8 @@ int setstr(struct tbl *vq, const char *s, int error_ok) { const char *fs = NULL; - int no_ro_check = error_ok & 0x4; - error_ok &= ~0x4; + int no_ro_check = error_ok & KSH_IGNORE_RDONLY; + error_ok &= ~KSH_IGNORE_RDONLY; if ((vq->flag & RDONLY) && !no_ro_check) { warningf(true, "%s: is read only", vq->name); if (!error_ok) @@ -661,6 +661,7 @@ typeset(const char *var, int set, int clr, int field, int base) */ for (t = vpbase; t; t = t->u.array) { int fake_assign; + int error_ok = KSH_RETURN_ERROR; char *s = NULL; char *free_me = NULL; @@ -683,6 +684,10 @@ typeset(const char *var, int set, int clr, int field, int base) t->type = 0; t->flag &= ~ALLOC; } + if (!(t->flag & RDONLY) && (set & RDONLY)) { + /* allow var to be initialized read-only */ + error_ok |= KSH_IGNORE_RDONLY; + } t->flag = (t->flag | set) & ~clr; /* Don't change base if assignment is to be done, * in case assignment fails. @@ -692,7 +697,7 @@ typeset(const char *var, int set, int clr, int field, int base) if (set & (LJUST|RJUST|ZEROFIL)) t->u2.field = field; if (fake_assign) { - if (!setstr(t, s, KSH_RETURN_ERROR)) { + if (!setstr(t, s, error_ok)) { /* Somewhat arbitrary action here: * zap contents of variable, but keep * the flag settings. @@ -717,13 +722,13 @@ typeset(const char *var, int set, int clr, int field, int base) if (val != NULL) { if (vp->flag&INTEGER) { /* do not zero base before assignment */ - setstr(vp, val, KSH_UNWIND_ERROR | 0x4); + setstr(vp, val, KSH_UNWIND_ERROR | KSH_IGNORE_RDONLY); /* Done after assignment to override default */ if (base > 0) vp->type = base; } else /* setstr can't fail (readonly check already done) */ - setstr(vp, val, KSH_RETURN_ERROR | 0x4); + setstr(vp, val, KSH_RETURN_ERROR | KSH_IGNORE_RDONLY); } /* only x[0] is ever exported, so use vpbase */ diff --git a/bin/ksh/vi.c b/bin/ksh/vi.c @@ -1,4 +1,4 @@ -/* $OpenBSD: vi.c,v 1.39 2015/12/22 08:39:26 mmcc Exp $ */ +/* $OpenBSD: vi.c,v 1.44 2016/10/17 18:39:43 schwarze Exp $ */ /* * vi command editing @@ -12,6 +12,7 @@ #include <sys/stat.h> /* completion */ #include <ctype.h> +#include <stdlib.h> #include <string.h> #include "sh.h" @@ -21,11 +22,11 @@ #define CTRL(c) (c & 0x1f) struct edstate { - int winleft; - char *cbuf; - int cbufsize; - int linelen; - int cursor; + char *cbuf; /* main buffer to build the command line */ + int cbufsize; /* number of bytes allocated for cbuf */ + int linelen; /* current number of bytes in cbuf */ + int winleft; /* first byte# in cbuf to be displayed */ + int cursor; /* byte# in cbuf having the cursor */ }; @@ -68,6 +69,7 @@ static void vi_pprompt(int); static void vi_error(void); static void vi_macro_reset(void); static int x_vi_putbuf(const char *, size_t); +static int isu8cont(unsigned char); #define C_ 0x1 /* a valid command that isn't a M_, E_, U_ */ #define M_ 0x2 /* movement command (h, l, etc.) */ @@ -148,7 +150,7 @@ static void restore_edstate(struct edstate *old, struct edstate *new); static void free_edstate(struct edstate *old); static struct edstate ebuf; -static struct edstate undobuf = { 0, undocbuf, CMDLEN, 0, 0 }; +static struct edstate undobuf = { undocbuf, CMDLEN, 0, 0, 0 }; static struct edstate *es; /* current editor state */ static struct edstate *undo; @@ -157,7 +159,7 @@ static char ibuf[CMDLEN]; /* input buffer */ static int first_insert; /* set when starting in insert mode */ static int saved_inslen; /* saved inslen for first insert */ static int inslen; /* length of input buffer */ -static int srchlen; /* length of current search pattern */ +static int srchlen; /* number of bytes in search pattern */ static char ybuf[CMDLEN]; /* yank buffer */ static int yanklen; /* length of yank buffer */ static int fsavecmd = ' '; /* last find command */ @@ -166,7 +168,7 @@ static char lastcmd[MAXVICMD]; /* last non-move command */ static int lastac; /* argcnt for lastcmd */ static int lastsearch = ' '; /* last search command */ static char srchpat[SRCHLEN]; /* last search pattern */ -static int insert; /* non-zero in insert mode */ +static int insert; /* mode: INSERT, REPLACE, or 0 */ static int hnum; /* position in history */ static int ohnum; /* history line copied (after mod) */ static int hlast; /* 1 past last position in history */ @@ -399,8 +401,12 @@ vi_hook(int ch) state = VCMD; } else if (ch == edchars.erase || ch == CTRL('h')) { if (srchlen != 0) { - srchlen--; - es->linelen -= char_len((unsigned char)locpat[srchlen]); + do { + srchlen--; + es->linelen -= char_len( + (unsigned char)locpat[srchlen]); + } while (srchlen > 0 && + isu8cont(locpat[srchlen])); es->cursor = es->linelen; refresh(0); return 0; @@ -568,25 +574,28 @@ vi_insert(int ch) vi_error(); return 0; } - if (inslen > 0) - inslen--; - es->cursor--; - if (es->cursor >= undo->linelen) - es->linelen--; - else - es->cbuf[es->cursor] = undo->cbuf[es->cursor]; } else { if (es->cursor == 0) { /* x_putc(BEL); no annoying bell here */ return 0; } - if (inslen > 0) - inslen--; - es->cursor--; - es->linelen--; - memmove(&es->cbuf[es->cursor], &es->cbuf[es->cursor+1], - es->linelen - es->cursor + 1); } + tcursor = es->cursor - 1; + while(tcursor > 0 && isu8cont(es->cbuf[tcursor])) + tcursor--; + if (insert == INSERT) + memmove(es->cbuf + tcursor, es->cbuf + es->cursor, + es->linelen - es->cursor); + if (insert == REPLACE && es->cursor < undo->linelen) + memcpy(es->cbuf + tcursor, undo->cbuf + tcursor, + es->cursor - tcursor); + else + es->linelen -= es->cursor - tcursor; + if (inslen < es->cursor - tcursor) + inslen = 0; + else + inslen -= es->cursor - tcursor; + es->cursor = tcursor; expanded = NONE; return 0; } @@ -699,7 +708,8 @@ vi_cmd(int argcnt, const char *cmd) if (is_move(*cmd)) { if ((cur = domove(argcnt, cmd, 0)) >= 0) { if (cur == es->linelen && cur != 0) - cur--; + while (isu8cont(es->cbuf[--cur])) + continue; es->cursor = cur; } else return -1; @@ -760,7 +770,8 @@ vi_cmd(int argcnt, const char *cmd) case 'a': modified = 1; hnum = hlast; if (es->linelen != 0) - es->cursor++; + while (isu8cont(es->cbuf[++es->cursor])) + continue; insert = INSERT; break; @@ -963,22 +974,25 @@ vi_cmd(int argcnt, const char *cmd) if (es->linelen == 0) return -1; modified = 1; hnum = hlast; - if (es->cursor + argcnt > es->linelen) - argcnt = es->linelen - es->cursor; - yank_range(es->cursor, es->cursor + argcnt); - del_range(es->cursor, es->cursor + argcnt); + for (cur = es->cursor; cur < es->linelen; cur++) + if (!isu8cont(es->cbuf[cur])) + if (argcnt-- == 0) + break; + yank_range(es->cursor, cur); + del_range(es->cursor, cur); break; case 'X': - if (es->cursor > 0) { - modified = 1; hnum = hlast; - if (es->cursor < argcnt) - argcnt = es->cursor; - yank_range(es->cursor - argcnt, es->cursor); - del_range(es->cursor - argcnt, es->cursor); - es->cursor -= argcnt; - } else + if (es->cursor == 0) return -1; + modified = 1; hnum = hlast; + for (cur = es->cursor; cur > 0; cur--) + if (!isu8cont(es->cbuf[cur])) + if (argcnt-- == 0) + break; + yank_range(cur, es->cursor); + del_range(cur, es->cursor); + es->cursor = cur; break; case 'u': @@ -1155,31 +1169,20 @@ domove(int argcnt, const char *cmd, int sub) switch (*cmd) { case 'b': - if (!sub && es->cursor == 0) - return -1; - ncursor = backword(argcnt); - break; - case 'B': if (!sub && es->cursor == 0) return -1; - ncursor = Backword(argcnt); + ncursor = (*cmd == 'b' ? backword : Backword)(argcnt); break; case 'e': - if (!sub && es->cursor + 1 >= es->linelen) - return -1; - ncursor = endword(argcnt); - if (sub && ncursor < es->linelen) - ncursor++; - break; - case 'E': if (!sub && es->cursor + 1 >= es->linelen) return -1; - ncursor = Endword(argcnt); - if (sub && ncursor < es->linelen) - ncursor++; + ncursor = (*cmd == 'e' ? endword : Endword)(argcnt); + if (!sub) + while (isu8cont((unsigned char)es->cbuf[--ncursor])) + continue; break; case 'f': @@ -1208,32 +1211,27 @@ domove(int argcnt, const char *cmd, int sub) case CTRL('h'): if (!sub && es->cursor == 0) return -1; - ncursor = es->cursor - argcnt; - if (ncursor < 0) - ncursor = 0; + for (ncursor = es->cursor; ncursor > 0; ncursor--) + if (!isu8cont(es->cbuf[ncursor])) + if (argcnt-- == 0) + break; break; case ' ': case 'l': if (!sub && es->cursor + 1 >= es->linelen) return -1; - if (es->linelen != 0) { - ncursor = es->cursor + argcnt; - if (ncursor > es->linelen) - ncursor = es->linelen; - } + for (ncursor = es->cursor; ncursor < es->linelen; ncursor++) + if (!isu8cont(es->cbuf[ncursor])) + if (argcnt-- == 0) + break; break; case 'w': - if (!sub && es->cursor + 1 >= es->linelen) - return -1; - ncursor = forwword(argcnt); - break; - case 'W': if (!sub && es->cursor + 1 >= es->linelen) return -1; - ncursor = Forwword(argcnt); + ncursor = (*cmd == 'w' ? forwword : Forwword)(argcnt); break; case '0': @@ -1253,13 +1251,12 @@ domove(int argcnt, const char *cmd, int sub) ncursor = es->linelen; if (ncursor) ncursor--; + while (isu8cont(es->cbuf[ncursor])) + ncursor--; break; case '$': - if (es->linelen != 0) - ncursor = es->linelen; - else - ncursor = 0; + ncursor = es->linelen; break; case '%': @@ -1301,7 +1298,8 @@ redo_insert(int count) if (putbuf(ibuf, inslen, insert==REPLACE) != 0) return -1; if (es->cursor > 0) - es->cursor--; + while (isu8cont(es->cbuf[--es->cursor])) + continue; insert = 0; return 0; } @@ -1346,16 +1344,15 @@ bracktype(int ch) * Non user interface editor routines below here */ -static int cur_col; /* current column on line */ -static int pwidth; /* width of prompt */ +static int cur_col; /* current display column */ +static int pwidth; /* display columns needed for prompt */ static int prompt_trunc; /* how much of prompt to truncate */ static int prompt_skip; /* how much of prompt to skip */ -static int winwidth; /* width of window */ -static char *wbuf[2]; /* window buffers */ +static int winwidth; /* available column positions */ +static char *wbuf[2]; /* current & previous window buffer */ static int wbuf_len; /* length of window buffers (x_cols-3)*/ -static int win; /* window buffer in use */ +static int win; /* number of window buffer in use */ static char morec; /* more character at right of window */ -static int lastref; /* argument to last refresh() */ static char holdbuf[CMDLEN]; /* place to hold last edit buffer */ static int holdlen; /* length of holdbuf */ @@ -1443,7 +1440,6 @@ edit_reset(char *buf, size_t len) winwidth = x_cols - pwidth - 3; win = 0; morec = ' '; - lastref = 1; holdlen = 0; } @@ -1514,80 +1510,100 @@ findch(int ch, int cnt, int forw, int incl) return ncursor; } +/* Move right one character, and then to the beginning of the next word. */ static int forwword(int argcnt) { - int ncursor; + int ncursor, skip_space, want_letnum; + unsigned char uc; ncursor = es->cursor; while (ncursor < es->linelen && argcnt--) { - if (letnum(es->cbuf[ncursor])) - while (letnum(es->cbuf[ncursor]) && - ncursor < es->linelen) - ncursor++; - else if (!isspace((unsigned char)es->cbuf[ncursor])) - while (!letnum(es->cbuf[ncursor]) && - !isspace((unsigned char)es->cbuf[ncursor]) && - ncursor < es->linelen) - ncursor++; - while (isspace((unsigned char)es->cbuf[ncursor]) && - ncursor < es->linelen) - ncursor++; + skip_space = 0; + want_letnum = -1; + ncursor--; + while (++ncursor < es->linelen) { + uc = es->cbuf[ncursor]; + if (isspace(uc)) { + skip_space = 1; + continue; + } else if (skip_space) + break; + if (uc & 0x80) + continue; + if (want_letnum == -1) + want_letnum = letnum(uc); + else if (want_letnum != letnum(uc)) + break; + } } return ncursor; } +/* Move left one character, and then to the beginning of the word. */ static int backword(int argcnt) { - int ncursor; + int ncursor, skip_space, want_letnum; + unsigned char uc; ncursor = es->cursor; while (ncursor > 0 && argcnt--) { - while (--ncursor > 0 && isspace((unsigned char)es->cbuf[ncursor])) - ; - if (ncursor > 0) { - if (letnum(es->cbuf[ncursor])) - while (--ncursor >= 0 && - letnum(es->cbuf[ncursor])) - ; - else - while (--ncursor >= 0 && - !letnum(es->cbuf[ncursor]) && - !isspace((unsigned char)es->cbuf[ncursor])) - ; - ncursor++; + skip_space = 1; + want_letnum = -1; + while (ncursor-- > 0) { + uc = es->cbuf[ncursor]; + if (isspace(uc)) { + if (skip_space) + continue; + else + break; + } + skip_space = 0; + if (uc & 0x80) + continue; + if (want_letnum == -1) + want_letnum = letnum(uc); + else if (want_letnum != letnum(uc)) + break; } + ncursor++; } return ncursor; } +/* Move right one character, and then to the byte after the word. */ static int endword(int argcnt) { - int ncursor; + int ncursor, skip_space, want_letnum; + unsigned char uc; ncursor = es->cursor; while (ncursor < es->linelen && argcnt--) { - while (++ncursor < es->linelen - 1 && - isspace((unsigned char)es->cbuf[ncursor])) - ; - if (ncursor < es->linelen - 1) { - if (letnum(es->cbuf[ncursor])) - while (++ncursor < es->linelen && - letnum(es->cbuf[ncursor])) - ; - else - while (++ncursor < es->linelen && - !letnum(es->cbuf[ncursor]) && - !isspace((unsigned char)es->cbuf[ncursor])) - ; - ncursor--; + skip_space = 1; + want_letnum = -1; + while (++ncursor < es->linelen) { + uc = es->cbuf[ncursor]; + if (isspace(uc)) { + if (skip_space) + continue; + else + break; + } + skip_space = 0; + if (uc & 0x80) + continue; + if (want_letnum == -1) + want_letnum = letnum(uc); + else if (want_letnum != letnum(uc)) + break; } } return ncursor; } +/* Move right one character, and then to the beginning of the next big word. */ static int Forwword(int argcnt) { @@ -1605,6 +1621,7 @@ Forwword(int argcnt) return ncursor; } +/* Move left one character, and then to the beginning of the big word. */ static int Backword(int argcnt) { @@ -1623,22 +1640,20 @@ Backword(int argcnt) return ncursor; } +/* Move right one character, and then to the byte after the big word. */ static int Endword(int argcnt) { int ncursor; ncursor = es->cursor; - while (ncursor < es->linelen - 1 && argcnt--) { - while (++ncursor < es->linelen - 1 && + while (ncursor < es->linelen && argcnt--) { + while (++ncursor < es->linelen && isspace((unsigned char)es->cbuf[ncursor])) ; - if (ncursor < es->linelen - 1) { - while (++ncursor < es->linelen && - !isspace((unsigned char)es->cbuf[ncursor])) - ; - ncursor--; - } + while (ncursor < es->linelen && + !isspace((unsigned char)es->cbuf[ncursor])) + ncursor++; } return ncursor; } @@ -1720,10 +1735,6 @@ redraw_line(int newline) static void refresh(int leftside) { - if (leftside < 0) - leftside = lastref; - else - lastref = leftside; if (outofwin()) rewindow(); display(wbuf[1 - win], wbuf[win], leftside); @@ -1770,24 +1781,38 @@ rewindow(void) es->winleft = holdcur1; } +/* Printing the byte ch at display column col moves to which column? */ static int newcol(int ch, int col) { if (ch == '\t') return (col | 7) + 1; + if (isu8cont(ch)) + return col; return col + char_len(ch); } +/* Display wb1 assuming that wb2 is currently displayed. */ static void display(char *wb1, char *wb2, int leftside) { + char *twb1; /* pointer into the buffer to display */ + char *twb2; /* pointer into the previous display buffer */ + static int lastb = -1; /* last byte# written from wb1, if UTF-8 */ + int cur; /* byte# in the main command line buffer */ + int col; /* display column loop variable */ + int ncol; /* display column of the cursor */ + int cnt; /* remaining display columns to fill */ + int moreright; + char mc; /* new "more character" at the right of window */ unsigned char ch; - char *twb1, *twb2, mc; - int cur, col, cnt; - int ncol = 0; - int moreright; - col = 0; + /* + * Fill the current display buffer with data from cbuf. + * In this first loop, col does not include the prompt. + */ + + ncol = col = 0; cur = es->winleft; moreright = 0; twb1 = wb1; @@ -1816,7 +1841,8 @@ display(char *wb1, char *wb2, int leftside) } } else { *twb1++ = ch; - col++; + if (!isu8cont(ch)) + col++; } } } @@ -1826,6 +1852,9 @@ display(char *wb1, char *wb2, int leftside) } if (cur == es->cursor) ncol = col + pwidth; + + /* Pad the current display buffer to the right margin. */ + if (col < winwidth) { while (col < winwidth) { *twb1++ = ' '; @@ -1835,21 +1864,62 @@ display(char *wb1, char *wb2, int leftside) moreright++; *twb1 = ' '; + /* + * Update the terminal display with data from wb1. + * In this final loop, col includes the prompt. + */ + col = pwidth; cnt = winwidth; - twb1 = wb1; - twb2 = wb2; - while (cnt--) { + for (twb1 = wb1, twb2 = wb2; cnt; twb1++, twb2++) { if (*twb1 != *twb2) { + + /* + * When a byte changes in the middle of a UTF-8 + * character, back up to the start byte, unless + * the previous byte was the last one written. + */ + + if (col > 0 && isu8cont(*twb1)) { + col--; + if (lastb >= 0 && twb1 == wb1 + lastb + 1) + cur_col = col; + else while (twb1 > wb1 && isu8cont(*twb1)) { + twb1--; + twb2--; + } + } + if (cur_col != col) ed_mov_opt(col, wb1); + + /* + * Always write complete characters, and + * advance all pointers accordingly. + */ + x_putc(*twb1); + while (isu8cont(twb1[1])) { + x_putc(*++twb1); + twb2++; + } + lastb = *twb1 & 0x80 ? twb1 - wb1 : -1; cur_col++; - } - twb1++; - twb2++; + } else if (isu8cont(*twb1)) + continue; + + /* + * For changed continuation bytes, we backed up. + * For unchanged ones, we jumped to the next byte. + * So, getting here, we had a real column. + */ + col++; + cnt--; } + + /* Update the "more character". */ + if (es->winleft > 0 && moreright) /* POSIX says to use * for this but that is a globbing * character and may confuse people; + is more innocuous @@ -1866,31 +1936,52 @@ display(char *wb1, char *wb2, int leftside) x_putc(mc); cur_col++; morec = mc; + lastb = -1; } - if (cur_col != ncol) + + /* Move the cursor to its new position. */ + + if (cur_col != ncol) { ed_mov_opt(ncol, wb1); + lastb = -1; + } } +/* Move the display cursor to display column number col. */ static void ed_mov_opt(int col, char *wb) { - if (col < cur_col) { - if (col + 1 < cur_col - col) { + int ci; + + /* The cursor is already at the right place. */ + + if (cur_col == col) + return; + + /* The cursor is too far right. */ + + if (cur_col > col) { + if (cur_col > 2 * col + 1) { + /* Much too far right, redraw from scratch. */ x_putc('\r'); vi_pprompt(0); cur_col = pwidth; - while (cur_col++ < col) - x_putc(*wb++); } else { - while (cur_col-- > col) + /* Slightly too far right, back up. */ + do { x_putc('\b'); + } while (--cur_col > col); + return; } - } else { - wb = &wb[cur_col - pwidth]; - while (cur_col++ < col) - x_putc(*wb++); } - cur_col = col; + + /* Advance the cursor. */ + + for (ci = pwidth; ci < col || isu8cont(*wb); + ci = newcol((unsigned char)*wb++, ci)) + if (ci > cur_col || (ci == cur_col && !isu8cont(*wb))) + x_putc(*wb); + cur_col = ci; } @@ -2075,7 +2166,11 @@ print_expansions(struct edstate *e, int command) return 0; } -/* How long is char when displayed (not counting tabs) */ +/* + * The number of bytes needed to encode byte c. + * Control bytes get "M-" or "^" prepended. + * This function does not handle tabs. + */ static int char_len(int c) { @@ -2129,4 +2224,9 @@ vi_macro_reset(void) } } +static int +isu8cont(unsigned char c) +{ + return !Flag(FVISHOW8) && (c & (0x80 | 0x40)) == 0x80; +} #endif /* VI */