From 361de369edd3bd17066b5f72e056e487545b364e Mon Sep 17 00:00:00 2001 From: Axel Beckert Date: Sun, 15 May 2022 00:41:38 +0200 Subject: 50220: Documentation: Fix typos found by Debian's Lintian tool --- Doc/Zsh/builtins.yo | 2 +- Doc/Zsh/options.yo | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'Doc') diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 280f1d72a..641e46cf9 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -1936,7 +1936,7 @@ retain their special attributes when made local. For each var(name)tt(=)var(value) assignment, the parameter var(name) is set to var(value). If the assignment is omitted and var(name) -does em(not) refer to an existing parameter, a new parameter is intialized +does em(not) refer to an existing parameter, a new parameter is initialized to empty string, zero, or empty array (as appropriate), em(unless) the shell option tt(TYPESET_TO_UNSET) is set. When that option is set, the parameter attributes are recorded but the parameter remains unset. diff --git a/Doc/Zsh/options.yo b/Doc/Zsh/options.yo index 443676b78..5e673eb5c 100644 --- a/Doc/Zsh/options.yo +++ b/Doc/Zsh/options.yo @@ -1952,7 +1952,7 @@ pindex(NOTYPESETTOUNSET) item(tt(TYPESET_TO_UNSET) )( When declaring a new parameter with any of the `tt(typeset)' family of related commands, the parameter remains unset unless and until a -value is explicity assigned to it, either in the `tt(typeset)' command +value is explicitly assigned to it, either in the `tt(typeset)' command itself or as a later assignment statement. ) pindex(VERBOSE) -- cgit v1.2.3 From b26b6b3fe00b94a2d4370b1afd2644034947b6b8 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Tue, 7 Jun 2022 10:02:14 +0100 Subject: Tweaks to MULTI_FUNC_DEF Output multiple function definitions using "function" form. Note exceptions to errors with NO_MULTI_FUNC_DEF --- ChangeLog | 6 ++++++ Doc/Zsh/options.yo | 5 +++++ Src/text.c | 12 ++++++++++-- Test/C04funcdef.ztst | 20 ++++++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 5490385f7..71f879776 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2022-06-07 Peter Stephenson + + * 50339: Doc/Zsh/options.yo, Src/text.c, Test/C04funcdef.ztst: + Make multiple function output safer with NO_MULTI_FUNC_DEF and + document exceptions to errors raised by MULTI_FUNC_DEF. + 2022-06-04 Bart Schaefer * 50323: Completion/Base/Utility/_shadow (new file), diff --git a/Doc/Zsh/options.yo b/Doc/Zsh/options.yo index 5e673eb5c..bf73664c9 100644 --- a/Doc/Zsh/options.yo +++ b/Doc/Zsh/options.yo @@ -1884,6 +1884,11 @@ fn2)var(...)tt(LPAR()RPAR())'; if the option is not set, this causes a parse error. Definition of multiple functions with the tt(function) keyword is always allowed. Multiple function definitions are not often used and can cause obscure errors. + +Note that no error is raised if multiple functions are defined as a +result of a set of names that were originally read as a single word on +the command line, for example `tt(TRAP{INT,QUIT})'. Although there are +no plans to change this behaviour at present, it is not guaranteed. ) pindex(MULTIOS) pindex(NO_MULTIOS) diff --git a/Src/text.c b/Src/text.c index 5cd7685fd..56127c457 100644 --- a/Src/text.c +++ b/Src/text.c @@ -578,11 +578,16 @@ gettext2(Estate state) Wordcode end = p + WC_FUNCDEF_SKIP(code); int nargs = *state->pc++; + if (nargs > 1) + taddstr("function "); taddlist(state, nargs); if (nargs) taddstr(" "); if (tjob) { - taddstr("() { ... }"); + if (nargs > 1) + taddstr("{ ... }"); + else + taddstr("() { ... }"); state->pc = end; if (!nargs) { /* @@ -594,7 +599,10 @@ gettext2(Estate state) } stack = 1; } else { - taddstr("() {"); + if (nargs > 1) + taddstr("{"); + else + taddstr("() {"); tindent++; taddnl(1); n = tpush(code, 1); diff --git a/Test/C04funcdef.ztst b/Test/C04funcdef.ztst index af469c527..b8509b25c 100644 --- a/Test/C04funcdef.ztst +++ b/Test/C04funcdef.ztst @@ -53,6 +53,26 @@ >b: redirection >a: redirection + define_multiple() { + fn1 fn2 fn3() { + print This is $0 + } + } + which -x2 define_multiple + define_multiple + fn1 + fn2 + fn3 +0: Safe output of multiple function definitions +>define_multiple () { +> function fn1 fn2 fn3 { +> print This is $0 +> } +>} +>This is fn1 +>This is fn2 +>This is fn3 + functions -M m1 m1() { (( $# )) } print $(( m1() )) -- cgit v1.2.3 From 61f35bb6264b04fc24e09144a2515227d5531826 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 11 Jun 2022 15:02:46 -0700 Subject: 50355: documentation and return status consistency in zsh/system module --- ChangeLog | 6 ++++++ Doc/Zsh/mod_system.yo | 16 +++++++++++++--- Src/Modules/system.c | 19 +++++++++++++++++-- 3 files changed, 36 insertions(+), 5 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 33e3a4b17..b5781f398 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2022-06-11 Bart Schaefer + + * 50355: Doc/Zsh/mod_system.yo, Src/Modules/system.c: make return + status values of sysopen consistent with other sys* functions, + make ERRNO values consistent for all, and update documentation + 2022-06-11 Jun-ichi Takimoto * 50356: Etc/FAQ.yo: work around a yodl bug (mishandling of \'e) diff --git a/Doc/Zsh/mod_system.yo b/Doc/Zsh/mod_system.yo index 884c3e753..e25201faa 100644 --- a/Doc/Zsh/mod_system.yo +++ b/Doc/Zsh/mod_system.yo @@ -74,6 +74,11 @@ truncate file to size 0 ) enditem() +A return status of 0 indicates the descriptor was successfully opened, +otherwise an error message is printed, and 1 is returned for an error +in the parameters to the command, or 2 is returned for a system error. +The parameter tt(ERRNO) is nonzero for system errors. + To close the file, use one of the following: example(tt(exec {)var(fd)tt(}<&-) @@ -123,11 +128,11 @@ error for which a message is printed to standard error. ) item(2)( There was an error on the read, or on polling the input file descriptor -for a timeout. The parameter tt(ERRNO) gives the error. +for a timeout. The parameter tt(ERRNO) identifies the error. ) item(3)( Data were successfully read, but there was an error writing them -to var(outfd). The parameter tt(ERRNO) gives the error. +to var(outfd). The parameter tt(ERRNO) identifies the error. ) item(4)( The attempt to read timed out. Note this does not set tt(ERRNO) as this @@ -147,6 +152,11 @@ expression. The tt(-u) option allows the file descriptor to be specified. By default the offset is specified relative to the start or the file but, with the tt(-w) option, it is possible to specify that the offset should be relative to the current position or the end of the file. + +The return status may be 0 for success, 1 for an error in the parameters +to the command, or 2 for an error on the seek; no error message is +printed in the last case, but the parameter tt(ERRNO) reflects +the error that occurred. ) item(tt(syswrite) [ tt(-c) var(countvar) ] [ tt(-o) var(outfd) ] var(data))( The data (a single string of bytes) are written to the file descriptor @@ -166,7 +176,7 @@ returning early. The return status may be 0 for success, 1 for an error in the parameters to the command, or 2 for an error on the write; no error message is -printed in the last case, but the parameter tt(ERRNO) will reflect +printed in the last case, but the parameter tt(ERRNO) reflects the error that occurred. ) xitem(tt(zsystem flock) [ tt(-t) var(timeout) ] [ tt(-i) var(interval) ] [ tt(-f) var(var) ] [tt(-er)] var(file)) diff --git a/Src/Modules/system.c b/Src/Modules/system.c index ea11ef037..929a8b002 100644 --- a/Src/Modules/system.c +++ b/Src/Modules/system.c @@ -74,6 +74,8 @@ bin_sysread(char *nam, char **args, Options ops, UNUSED(int func)) int infd = 0, outfd = -1, bufsize = SYSREAD_BUFSIZE, count; char *outvar = NULL, *countvar = NULL, *inbuf; + errno = 0; /* Distinguish non-system errors */ + /* -i: input file descriptor if not stdin */ if (OPT_ISSET(ops, 'i')) { infd = getposint(OPT_ARG(ops, 'i'), nam); @@ -238,6 +240,8 @@ bin_syswrite(char *nam, char **args, Options ops, UNUSED(int func)) int outfd = 1, len, count, totcount; char *countvar = NULL; + errno = 0; /* Distinguish non-system errors */ + /* -o: output file descriptor if not stdout */ if (OPT_ISSET(ops, 'o')) { outfd = getposint(OPT_ARG(ops, 'o'), nam); @@ -303,6 +307,13 @@ static struct { const char *name; int oflag; } openopts[] = { { "trunc", O_TRUNC } }; +/* + * Return values of bin_sysopen: + * 0 Success + * 1 Error in parameters to command + * 2 Error on open, ERRNO set by system + */ + /**/ static int bin_sysopen(char *nam, char **args, Options ops, UNUSED(int func)) @@ -319,6 +330,8 @@ bin_sysopen(char *nam, char **args, Options ops, UNUSED(int func)) int fdflags = 0; #endif + errno = 0; /* Distinguish non-system errors */ + if (!OPT_ISSET(ops, 'u')) { zwarnnam(nam, "file descriptor not specified"); return 1; @@ -374,12 +387,12 @@ bin_sysopen(char *nam, char **args, Options ops, UNUSED(int func)) if (fd == -1) { zwarnnam(nam, "can't open file %s: %e", *args, errno); - return 1; + return 2; } moved_fd = (explicit > -1) ? redup(fd, explicit) : movefd(fd); if (moved_fd == -1) { zwarnnam(nam, "can't open file %s", *args); - return 1; + return 2; } #ifdef FD_CLOEXEC @@ -423,6 +436,8 @@ bin_sysseek(char *nam, char **args, Options ops, UNUSED(int func)) char *whence; off_t pos; + errno = 0; /* Distinguish non-system errors */ + /* -u: file descriptor if not stdin */ if (OPT_ISSET(ops, 'u')) { fd = getposint(OPT_ARG(ops, 'u'), nam); -- cgit v1.2.3 From 6e827d8f9a50653aa1905d8aff8cc91e6e2423c4 Mon Sep 17 00:00:00 2001 From: Julian Prein Date: Mon, 19 Sep 2022 02:02:18 +0000 Subject: 50648: Use $ZCALC_HISTORY where appropriate --- ChangeLog | 5 +++++ Doc/Zsh/contrib.yo | 7 +++++-- Functions/Misc/zcalc | 4 +++- 3 files changed, 13 insertions(+), 3 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 375172ec9..48c65d01b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2022-09-25 Peter Stephenson + + * 50648: Functions/Misc/zcalc: Julian Prein: Use ZCALC_HISTFILE + where defined for zcalc history. + 2022-09-21 Jun-ichi Takimoto * Nicholas Vinson: 50641: aczsh.m4, configure.ac: use 'int main()' diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index 0ef59dbc9..96de5aa9b 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -3972,8 +3972,11 @@ calculation is stored. For example, the result of the calculation on the line preceded by `tt(4> )' is available as tt($4). The last value calculated is available as tt(ans). Full command line editing, including the history of previous calculations, is available; the history is saved in -the file tt(~/.zcalc_history). To exit, enter a blank line or type `tt(:q)' -on its own (`tt(q)' is allowed for historical compatibility). +the file tt($ZCALC_HISTFILE). If tt($ZCALC_HISTFILE) is unset, +tt($ZDOTDIR/.zcalc_history) is used instead, which in turn falls backs to +tt($HOME/.zcalc_history) if tt($ZDOTDIR) is unset. To exit, enter a blank +line or type `tt(:q)' on its own (`tt(q)' is allowed for historical +compatibility). A line ending with a single backslash is treated in the same fashion as it is in command line editing: the backslash is removed, the diff --git a/Functions/Misc/zcalc b/Functions/Misc/zcalc index 480373345..6cd2822c9 100644 --- a/Functions/Misc/zcalc +++ b/Functions/Misc/zcalc @@ -124,8 +124,10 @@ integer _rpn_mode _matched _show_stack _i _n integer _max_stack _push local -a _expressions stack + # We use our own history file with an automatic pop on exit. -history -ap "${ZDOTDIR:-$HOME}/.zcalc_history" +history -ap "${ZCALC_HISTFILE:-${ZDOTDIR:-$HOME}/.zcalc_history}" + _forms=( '%2$g' '%.*g' '%.*f' '%.*E' '') -- cgit v1.2.3 From 6b5ee0c17c817f12537ab7d3e5027b015f8f9475 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sun, 23 Oct 2022 16:25:04 -0700 Subject: users/28243: update "typeset +" documentation --- ChangeLog | 6 +++++- Doc/Zsh/builtins.yo | 8 +++++--- 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 0c11fe53b..b97244f68 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,8 @@ -2022-10-22 Bart Schaefer +2022-10-23 Bart Schaefer + + * users/28243: Doc/Zsh/builtins.yo: update "typeset +" doc + +2022-10-22 Bart Schaefer * 50714: Completion/Unix/Command/_git (_git-diff): also complete in the 2nd argument position anything that could be in the 1st. diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 641e46cf9..dd54a0fc8 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -2047,8 +2047,9 @@ startitem() item(tt(PLUS()))( If `tt(PLUS())' appears by itself in a separate word as the last option, then the names of all parameters (functions with tt(-f)) are printed, but -the values (function bodies) are not. No var(name) arguments may appear, -and it is an error for any other options to follow `tt(PLUS())'. The +the values (function bodies) are not. If var(name) arguments appear, +both those names and their values are printed in the form of assignments. +It is an error for any other options to follow `tt(PLUS())', but the effect of `tt(PLUS())' is as if all attribute flags which precede it were given with a `tt(PLUS())' prefix. For example, `tt(typeset -U PLUS())' is equivalent to `tt(typeset +U)' and displays the names of all arrays having @@ -2081,7 +2082,8 @@ Except when assignments are made with var(name)tt(=)var(value), using tt(+m) forces the matching parameters and their attributes to be printed, even inside a function. Note that tt(-m) is ignored if no patterns are given, so `tt(typeset -m)' displays attributes but `tt(typeset -a +m)' -does not. +does not. Ordinary scalar string parameters have no attributes, so for +those tt(+m) prints only the names. ) item(tt(-p) [ var(n) ])( If the tt(-p) option is given, parameters and values are printed in the -- cgit v1.2.3 From b76dcecfe3461aa9a9e29dffe2484f097611f9ff Mon Sep 17 00:00:00 2001 From: Axel Beckert Date: Tue, 25 Oct 2022 12:49:48 +0200 Subject: 50840: Doc/Zsh/grammar.yo: Correct NO_MATCH to NOMATCH --- ChangeLog | 4 ++++ Doc/Zsh/grammar.yo | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index b97244f68..45db78834 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2022-10-25 Axel Beckert + + * 50840: Doc/Zsh/grammar.yo: Correct NO_MATCH to NOMATCH. + 2022-10-23 Bart Schaefer * users/28243: Doc/Zsh/builtins.yo: update "typeset +" doc diff --git a/Doc/Zsh/grammar.yo b/Doc/Zsh/grammar.yo index f8f4ada86..9af211090 100644 --- a/Doc/Zsh/grammar.yo +++ b/Doc/Zsh/grammar.yo @@ -536,7 +536,7 @@ itemiz(Errors creating command or process substitutions) itemiz(Syntax errors in glob qualifiers) itemiz(File generation errors where not caught by the option tt(BAD_PATTERN)) itemiz(All bad patterns used for matching within case statements) -itemiz(File generation failures where not caused by tt(NO_MATCH) or +itemiz(File generation failures where not caused by tt(NOMATCH) or similar options) itemiz(All file generation errors where the pattern was used to create a multio) -- cgit v1.2.3 From 159c892b9be15d123bb1ca2517876172f1a2e77a Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Mon, 31 Oct 2022 16:50:16 -0700 Subject: 50855: Clarify how commands are hashed, and searched-for by "whence". --- ChangeLog | 5 +++++ Doc/Zsh/builtins.yo | 21 ++++++++++++++------- Doc/Zsh/params.yo | 1 + 3 files changed, 20 insertions(+), 7 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index bf2d73a8f..0f918288b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2022-10-31 Bart Schaefer + + * 50855: Doc/Zsh/builtins.yo, Doc/Zsh/params.yo: Clarify how + commands are hashed, and searched-for by "whence". + 2022-10-31 Peter Grayson * 50844: Completion/Unix/Command/_stgit: Remove _stgit completion diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index dd54a0fc8..b6217f66d 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -1052,9 +1052,11 @@ The choice of hash table to work on is determined by the tt(-d) option; without the option the command hash table is used, and with the option the named directory hash table is used. -A command var(name) starting with a tt(/) is never hashed, whether by -explicit use of the tt(hash) command or otherwise. Such a command -is always found by direct look up in the file system. +A command var(name) starting with a tt(/) or with a relative path +starting with tt(./) or tt(../) is never executed by lookup in +the command hash table, and these can only be added to the table by +explicit use of the tt(hash) command. Such a command is always +found by direct look up in the file system. Given no arguments, and neither the tt(-r) or tt(-f) options, the selected hash table will be listed in full. @@ -1063,9 +1065,11 @@ The tt(-r) option causes the selected hash table to be emptied. It will be subsequently rebuilt in the normal fashion. The tt(-f) option causes the selected hash table to be fully rebuilt immediately. For the command hash table this hashes -all the absolute directories in the tt(PATH), -and for the named directory hash table this adds all users' home directories. +all (and em(only)) the absolute directories in the tt(PATH), +and for the named directory hash table this adds all users' home +directories. These two options cannot be used with any arguments. +Both options remove any explicitly-added elements. The tt(-m) option causes the arguments to be taken as patterns (which should be quoted) and the elements of the hash table @@ -2523,11 +2527,14 @@ item(tt(-a))( Do a search for all occurrences of var(name) throughout the command path. Normally only the first occurrence is printed. +When combined with tt(-m), only names appearing in the command hash +table are searched, but all occurrences of those names are printed. ) item(tt(-m))( The arguments are taken as patterns (pattern characters should be -quoted), and the information is displayed for each command matching one -of these patterns. +quoted), and the information is displayed for each entry in the command +hash table matching one of these patterns. The hash table is first +refilled, in case of changes to tt(PATH). ) item(tt(-s))( If a pathname contains symlinks, print the symlink-free pathname as well. diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index b543d1c38..2a30085a8 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -1414,6 +1414,7 @@ An array (colon-separated list) of directories to search for commands. When this parameter is set, each directory is scanned and all files found are put in a hash table. +Directories named by relative path are not scanned for hashing. ) vindex(POSTEDIT) item(tt(POSTEDIT) )( -- cgit v1.2.3 From f8d93888a8efd6c8142e74ece83b38632661de47 Mon Sep 17 00:00:00 2001 From: Jun-ichi Takimoto Date: Wed, 2 Nov 2022 16:27:27 +0900 Subject: 50851: restore typtab when necessary inittyptab() must be called when returning from a function with "setopt localoptions MULTIBYTE|BANGHIST|SHSTDIN", and also in function dosetopt() when setting these options (via $options, for example). We intentionally did not take account of the options EMACS/VI because these options are obsolete and their use is not recommended. --- ChangeLog | 6 ++++++ Doc/Zsh/options.yo | 16 ++++++++++------ Src/exec.c | 12 ++++++++++++ Src/options.c | 6 +++++- 4 files changed, 33 insertions(+), 7 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index c6daa5998..6146209cf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2022-11-02 Jun-ichi Takimoto + + * 50851: Doc/Zsh/options.yo, Src/exec.c, Src/options.c: restore + state (such as typtab) when returning from a function with + localoptions (but do not take care of EMACS/VI options). + 2022-10-31 Bart Schaefer * 50855: Doc/Zsh/builtins.yo, Doc/Zsh/params.yo: Clarify how diff --git a/Doc/Zsh/options.yo b/Doc/Zsh/options.yo index bf73664c9..445052617 100644 --- a/Doc/Zsh/options.yo +++ b/Doc/Zsh/options.yo @@ -2550,10 +2550,12 @@ pindex(NO_EMACS) pindex(NOEMACS) item(tt(EMACS))( If ZLE is loaded, turning on this option has the equivalent effect -of `tt(bindkey -e)'. In addition, the VI option is unset. +of `tt(bindkey -e)'. In addition, the tt(VI) option is unset. Turning it off has no effect. The option setting is -not guaranteed to reflect the current keymap. This option is -provided for compatibility; tt(bindkey) is the recommended interface. +not guaranteed to reflect the current keymap, and the tt(LOCALOPTIONS) +option does not work correctly. This option is provided only for +compatibility, and its use is highly discouraged. tt(bindkey) is the +recommended interface. ) pindex(OVERSTRIKE) pindex(NO_OVERSTRIKE) @@ -2582,10 +2584,12 @@ pindex(NO_VI) pindex(NOVI) item(tt(VI))( If ZLE is loaded, turning on this option has the equivalent effect -of `tt(bindkey -v)'. In addition, the EMACS option is unset. +of `tt(bindkey -v)'. In addition, the tt(EMACS) option is unset. Turning it off has no effect. The option setting is -not guaranteed to reflect the current keymap. This option is -provided for compatibility; tt(bindkey) is the recommended interface. +not guaranteed to reflect the current keymap, and the tt(LOCALOPTIONS) +option does not work correctly. This option is provided only for +compatibility, and its use is highly discouraged. tt(bindkey) is the +recommended interface. ) pindex(ZLE) pindex(NO_ZLE) diff --git a/Src/exec.c b/Src/exec.c index f2911807c..c8bcf4ee5 100644 --- a/Src/exec.c +++ b/Src/exec.c @@ -5961,11 +5961,23 @@ doshfunc(Shfunc shfunc, LinkList doshargs, int noreturnval) emulation = funcsave->emulation; sticky = funcsave->sticky; } else if (isset(LOCALOPTIONS)) { + /* we need to call inittyptab() if these options change */ + int init_typtab = +#ifdef MULTIBYTE_SUPPORT + funcsave->opts[MULTIBYTE] != opts[MULTIBYTE] || +#endif + funcsave->opts[BANGHIST] != opts[BANGHIST] || + funcsave->opts[SHINSTDIN] != opts[SHINSTDIN]; + /* take care of SUNKEYBOARDHACK but not of EMACS/VI */ + if (funcsave->opts[SUNKEYBOARDHACK] != opts[SUNKEYBOARDHACK]) + keyboardhackchar = funcsave->opts[SUNKEYBOARDHACK] ? '`' : '\0'; /* restore all shell options except PRIVILEGED and RESTRICTED */ funcsave->opts[PRIVILEGED] = opts[PRIVILEGED]; funcsave->opts[RESTRICTED] = opts[RESTRICTED]; memcpy(opts, funcsave->opts, sizeof(opts)); emulation = funcsave->emulation; + if (init_typtab) + inittyptab(); } else { /* just restore a couple. */ opts[XTRACE] = funcsave->opts[XTRACE]; diff --git a/Src/options.c b/Src/options.c index a1fe918fc..a994b563e 100644 --- a/Src/options.c +++ b/Src/options.c @@ -904,7 +904,11 @@ dosetopt(int optno, int value, int force, char *new_opts) keyboardhackchar = (value ? '`' : '\0'); } new_opts[optno] = value; - if (optno == BANGHIST || optno == SHINSTDIN) + if ( +#ifdef MULTIBYTE_SUPPORT + optno == MULTIBYTE || +#endif + optno == BANGHIST || optno == SHINSTDIN) inittyptab(); return 0; } -- cgit v1.2.3 From c4d557bb0a9cf6a7241f760ad466e2d91359ceb2 Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Thu, 17 Nov 2022 20:05:12 +0100 Subject: 50934: use OSC 52 escape sequence when copying to "* or "+ vi buffers --- ChangeLog | 6 ++++++ Doc/Zsh/zle.yo | 11 ++++++++--- Src/Zle/zle.h | 3 +++ Src/Zle/zle_utils.c | 32 +++++++++++++++++++++++++++++++- Src/Zle/zle_vi.c | 9 ++++++--- 5 files changed, 54 insertions(+), 7 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 6478f5480..c42163434 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2022-11-17 Oliver Kiddle + + * 50934: Doc/Zsh/zle.yo, Src/Zle/zle.h, Src/Zle/zle_utils.c, + Src/Zle/zle_vi.c: use OSC 52 escape sequence when copying to + "* or "+ vi buffers + 2022-11-09 Bart Schaefer * 50929: Src/exec.c: fix handling of ERR_RETURN bent by 50928. diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo index 2d033a0a1..58700072a 100644 --- a/Doc/Zsh/zle.yo +++ b/Doc/Zsh/zle.yo @@ -2470,10 +2470,11 @@ command. tt(run-help) is normally aliased to tt(man). tindex(vi-set-buffer) item(tt(vi-set-buffer) (unbound) (tt(")) (unbound))( Specify a buffer to be used in the following command. -There are 37 buffers that can be specified: +There are 39 buffers that can be specified: the 26 `named' buffers tt("a) to tt("z), the `yank' buffer tt("0), -the nine `queued' buffers tt("1) to tt("9) and the `black hole' buffer -tt("_). The named buffers can also be specified as tt("A) to tt("Z). +the nine `queued' buffers tt("1) to tt("9), the `black hole' buffer +tt("_) and the system selection tt("*) and clipboard tt("+). +The named buffers can also be specified as tt("A) to tt("Z). When a buffer is specified for a cut, change or yank command, the text concerned replaces the previous contents of the specified buffer. If @@ -2482,6 +2483,10 @@ appended to the buffer instead of overwriting it. When using the tt("_) buffer, nothing happens. This can be useful for deleting text without affecting any buffers. +Updating the system clipboard relies on specific support from the terminal. +Reading it is not possible so a paste command with tt("*) or tt("+) will do +nothing. + If no buffer is specified for a cut or change command, tt("1) is used, and the contents of tt("1) to tt("8) are each shifted along one buffer; the contents of tt("9) is lost. If no buffer is specified for a yank diff --git a/Src/Zle/zle.h b/Src/Zle/zle.h index 391586c4a..f59545397 100644 --- a/Src/Zle/zle.h +++ b/Src/Zle/zle.h @@ -258,6 +258,9 @@ struct modifier { #define MOD_NULL (1<<5) /* throw away text for the vi cut buffer */ #define MOD_CHAR (1<<6) /* force character-wise movement */ #define MOD_LINE (1<<7) /* force line-wise movement */ +#define MOD_PRI (1<<8) /* OS primary selection for the vi cut buffer */ +#define MOD_CLIP (1<<9) /* OS clipboard for the vi cut buffer */ +#define MOD_OSSEL (MOD_PRI | MOD_CLIP) /* either system selection */ /* current modifier status */ diff --git a/Src/Zle/zle_utils.c b/Src/Zle/zle_utils.c index 526216fa7..3d9017dcf 100644 --- a/Src/Zle/zle_utils.c +++ b/Src/Zle/zle_utils.c @@ -936,6 +936,28 @@ cut(int i, int ct, int flags) cuttext(zleline + i, ct, flags); } +static char* +base64_encode(const char *src, size_t len) { + static const char* base64_table = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + const unsigned char *end = (unsigned char *)src + len; + const unsigned char *in = (unsigned char *)src; + char *ret = zhalloc(1 + 4 * ((len + 2) / 3)); /* 4 bytes out for 3 in */ + char *cur = ret; + + for (; end - in > 0; in += 3, cur += 4) { + unsigned int n = *in << 16; + cur[3] = end - in > 2 ? base64_table[(n |= in[2]) & 0x3f] : '='; + cur[2] = end - in > 1 ? base64_table[((n |= in[1]<<8) >> 6) & 0x3f] : '='; + cur[1] = base64_table[(n >> 12) & 0x3f]; + cur[0] = base64_table[n >> 18]; + } + *cur = '\0'; + + return ret; +} + /* * As cut, but explicitly supply the text together with its length. */ @@ -948,7 +970,15 @@ cuttext(ZLE_STRING_T line, int ct, int flags) return; UNMETACHECK(); - if (zmod.flags & MOD_VIBUF) { + if (zmod.flags & MOD_OSSEL) { + int cutll; + char *mbcut = zlelineasstring(line, ct, 0, &cutll, NULL, 1); + unmetafy(mbcut, &cutll); + mbcut = base64_encode(mbcut, cutll); + + fprintf(shout, "\e]52;%c;%s\a", zmod.flags & MOD_CLIP ? 'c' : 'p', + mbcut); + } else if (zmod.flags & MOD_VIBUF) { struct cutbuffer *b = &vibuf[zmod.vibuf]; if (!(zmod.flags & MOD_VIAPP) || !b->buf) { diff --git a/Src/Zle/zle_vi.c b/Src/Zle/zle_vi.c index 0f198d0e8..24d9de6ea 100644 --- a/Src/Zle/zle_vi.c +++ b/Src/Zle/zle_vi.c @@ -1014,6 +1014,9 @@ int visetbuffer(char **args) { ZLE_INT_T ch; + ZLE_CHAR_T *match = ZWS("_*+"); + int registermod[] = { MOD_NULL, MOD_PRI, MOD_CLIP }; + ZLE_CHAR_T *found; if (*args) { ch = **args; @@ -1022,12 +1025,12 @@ visetbuffer(char **args) } else { ch = getfullchar(0); } - if (ch == ZWC('_')) { - zmod.flags |= MOD_NULL; + if ((found = ZS_strchr(match, ch))) { + zmod.flags |= registermod[found - match]; prefixflag = 1; return 0; } else - zmod.flags &= ~MOD_NULL; + zmod.flags &= ~(MOD_NULL | MOD_OSSEL); if ((ch < ZWC('0') || ch > ZWC('9')) && (ch < ZWC('a') || ch > ZWC('z')) && (ch < ZWC('A') || ch > ZWC('Z'))) -- cgit v1.2.3 From 14559421e2a054f9b09d6fa1971158a5e7e162a6 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Mon, 12 Dec 2022 10:27:29 +0000 Subject: 51134: document interactive behaviour with ERR_EXIT and ERR_RETURN --- ChangeLog | 5 +++++ Doc/Zsh/options.yo | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 0a2a880f4..d8672c028 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2022-12-12 Peter Stephenson + + * 51134: Doc/Zsh/options.yo: document interactions between + ERR_EXIT and ERR_RETURN and interactive shells. + 2022-12-09 Bart Schaefer * 51161: Src/exec.c: correct errno after closing xtrace FD diff --git a/Doc/Zsh/options.yo b/Doc/Zsh/options.yo index 445052617..e92969531 100644 --- a/Doc/Zsh/options.yo +++ b/Doc/Zsh/options.yo @@ -1744,6 +1744,9 @@ Exiting due to tt(ERR_EXIT) has certain interactions with asynchronous jobs noted in ifzman(the section JOBS in zmanref(zshmisc))\ ifnzman(noderef(Jobs & Signals)). + +Note this behaviour is not disabled in interactive shells --- +a non-zero status on the command line causes the shell to exit. ) pindex(ERR_RETURN) pindex(NO_ERR_RETURN) @@ -1756,7 +1759,10 @@ If a command has a non-zero exit status, return immediately from the enclosing function. The logic is similar to that for tt(ERR_EXIT), except that an implicit tt(return) statement is executed instead of an tt(exit). This will trigger an exit at the outermost level of a -non-interactive script. +non-interactive script. At the top level of an interactive shell, +it will trigger a return to the command prompt; in other +words, the sequence of commands typed by the user may be +thought of as a function for this purpose. Normally this option inherits the behaviour of tt(ERR_EXIT) that code followed by `tt(&&)' `tt(||)' does not trigger a return. Hence -- cgit v1.2.3 From 727079f7e547fe17e73fa6e240fe5c07ab01abe9 Mon Sep 17 00:00:00 2001 From: Philippe Altherr Date: Tue, 13 Dec 2022 21:05:13 -0800 Subject: 51198: Clarify and expand ERR_EXIT and ERR_RETURN documentation --- ChangeLog | 3 ++ Doc/Zsh/options.yo | 84 ++++++++++++++++++++++++++++++------------------------ 2 files changed, 49 insertions(+), 38 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 747d3eba3..cea087a01 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2022-12-13 Bart Schaefer + * Philippe Altherr: 51198: Doc/Zsh/options.yo: Clarify and expand + ERR_EXIT and ERR_RETURN documentation to include updated behavior + * Philippe Altherr: 51193: NEWS, README: Discuss ERR_EXIT changes 2022-12-12 Peter Stephenson diff --git a/Doc/Zsh/options.yo b/Doc/Zsh/options.yo index e92969531..cbd3d0f8e 100644 --- a/Doc/Zsh/options.yo +++ b/Doc/Zsh/options.yo @@ -1723,22 +1723,30 @@ pindex(NOERREXIT) cindex(exit status, trapping) item(tt(ERR_EXIT) (tt(-e), ksh: tt(-e)))( If a command has a non-zero exit status, execute the tt(ZERR) -trap, if set, and exit. This is disabled while running initialization -scripts. - -The behaviour is also disabled inside tt(DEBUG) traps. In this -case the option is handled specially: it is unset on entry to -the trap. If the option tt(DEBUG_BEFORE_CMD) is set, -as it is by default, and the option tt(ERR_EXIT) is found to have been set -on exit, then the command for which the tt(DEBUG) trap is being executed is -skipped. The option is restored after the trap exits. - -Non-zero status in a command list containing tt(&&) or tt(||) is ignored -for commands not at the end of the list. Hence - -example(false && true) - -does not trigger exit. +trap, if set, and exit. + +The option is ignored when executing the commands following tt(while), +tt(until), tt(if), or tt(elif), a pipeline beginning with tt(!), or +any command other than the last in command list containing tt(&&) or +tt(||). Hence neither `tt(if foo; then true; fi)', nor `tt(foo && +true)' trigger exit when tt(foo) returns with a non-zero exit status. +Note that if tt(foo) is a function, the option is also ignored during +its whole execution. + +The option is also ignored when executing a complex command (tt(if), +tt(for), tt(while), tt(until), tt(repeat), tt(case), tt(select), +tt(always), or a list in braces) if its exit status comes from a +command executed while the option is ignored. Hence, the tt(if) +command in `tt(if true; then false && true; fi)' does not trigger +exit. + +Finally, the option is also ignored while running initialization +scripts and inside tt(DEBUG) traps. In the latter case, the option is +handled specially: it is unset on entry to the trap. If the option +tt(DEBUG_BEFORE_CMD) is set, as it is by default, and the option +tt(ERR_EXIT) is found to have been set on exit, then the command for +which the tt(DEBUG) trap is being executed is skipped. The option is +restored after the trap exits. Exiting due to tt(ERR_EXIT) has certain interactions with asynchronous jobs noted in @@ -1755,29 +1763,29 @@ pindex(NOERRRETURN) cindex(function return, on error) cindex(return from function, on error) item(tt(ERR_RETURN))( + If a command has a non-zero exit status, return immediately from the -enclosing function. The logic is similar to that for tt(ERR_EXIT), -except that an implicit tt(return) statement is executed instead of an -tt(exit). This will trigger an exit at the outermost level of a -non-interactive script. At the top level of an interactive shell, -it will trigger a return to the command prompt; in other -words, the sequence of commands typed by the user may be -thought of as a function for this purpose. - -Normally this option inherits the behaviour of tt(ERR_EXIT) that -code followed by `tt(&&)' `tt(||)' does not trigger a return. Hence -in the following: - -example(summit || true) - -no return is forced as the combined effect always has a zero return -status. - -Note. however, that if tt(summit) in the above example is itself a -function, code inside it is considered separately: it may force a return -from tt(summit) (assuming the option remains set within tt(summit)), but -not from the enclosing context. This behaviour is different from -tt(ERR_EXIT) which is unaffected by function scope. +enclosing function. Except as explained below, an implicit tt(return) +statement is executed following the same logic described for +tt(ERR_EXIT). This will trigger an exit at the outermost level of a +non-interactive script. At the top level of an interactive shell, it +will trigger a return to the command prompt; in other words, the +sequence of commands typed by the user may be thought of as a function +for this purpose. + +Unlike for tt(ERR_EXIT), when a function is called while the option is +being ignored, the option is NOT ignored during the execution of the +function. Hence, if tt(foo) in `tt(foo && true)' is a function, code +inside it is considered separately: it may force a return from tt(foo) +(assuming the option remains set within tt(foo)). + +Like for tt(ERR_EXIT), the option is ignored inside tt(DEBUG) traps +but it's not unset on entry to the trap and setting or unsetting it +inside the trap has no special effect. + +If tt(ERR_RETURN) and tt(ERR_EXIT) are both set, it may happen that +both exit and return should be triggered. In that case only exit is +triggered. ) pindex(EVAL_LINENO) pindex(NO_EVAL_LINENO) -- cgit v1.2.3 From 35a2f155c3b92e67957325e1f49e409546378e3e Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Sat, 17 Dec 2022 00:09:37 +0100 Subject: 51214: handle read -d and a delimiter that can't be decoded into a character Terminate input at the raw byte value of the delimiter. Also document and test the use of an empty string as a way to specify NUL as the delimiter. --- ChangeLog | 4 ++++ Doc/Zsh/builtins.yo | 3 ++- Src/builtin.c | 7 +++++-- Test/B04read.ztst | 4 ++++ Test/D07multibyte.ztst | 14 ++++++++++++++ 5 files changed, 29 insertions(+), 3 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 5b0af2135..130bec319 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2022-12-16 Oliver Kiddle + * 51214: Doc/Zsh/builtins.yo, Src/builtin.c, Test/B04read.ztst, + Test/D07multibyte.ztst: with read -d and a delimiter that can't be + decoded into a character terminate input at the raw byte value + * Jun T.: 51207: Src/builtin.c, Test/B04read.ztst: fix for read -d when the delimiter is a byte >= 0x80 diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index b6217f66d..56428a714 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -1589,7 +1589,8 @@ Input is read from the coprocess. ) item(tt(-d) var(delim))( Input is terminated by the first character of var(delim) instead of -by newline. +by newline. For compatibility with other shells, if var(delim) is an +empty string, input is terminated at the first NUL. ) item(tt(-t) [ var(num) ])( Test if input is available before attempting to read. If var(num) diff --git a/Src/builtin.c b/Src/builtin.c index 951970138..70a950666 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -6282,6 +6282,7 @@ bin_read(char *name, char **args, Options ops, UNUSED(int func)) long izle_timeout = 0; #ifdef MULTIBYTE_SUPPORT wchar_t delim = L'\n', wc; + int rawbyte = 0; mbstate_t mbs; char *laststart; size_t ret; @@ -6412,9 +6413,11 @@ bin_read(char *name, char **args, Options ops, UNUSED(int func)) wi = WEOF; if (wi != WEOF) delim = (wchar_t)wi; - else + else { delim = (wchar_t) (unsigned char) ((delimstr[0] == Meta) ? delimstr[1] ^ 32 : delimstr[0]); + rawbyte = 1; + } #else delim = (unsigned char) ((delimstr[0] == Meta) ? delimstr[1] ^ 32 : delimstr[0]); @@ -6842,7 +6845,7 @@ bin_read(char *name, char **args, Options ops, UNUSED(int func)) break; } *bptr = (char)c; - if (isset(MULTIBYTE)) { + if (isset(MULTIBYTE) && !rawbyte) { ret = mbrtowc(&wc, bptr, 1, &mbs); if (!ret) /* NULL */ ret = 1; diff --git a/Test/B04read.ztst b/Test/B04read.ztst index 96adf51c7..14bdaeef5 100644 --- a/Test/B04read.ztst +++ b/Test/B04read.ztst @@ -82,6 +82,10 @@ >Testing the >null hypothesis + read -ed '' <<<$'one\0two' +0:empty delimiter terminates at nulls +>one + print -n $'first line\x80second line\x80' | while read -d $'\x80' line; do print $line; done 0:read with a delimiter >= 0x80 diff --git a/Test/D07multibyte.ztst b/Test/D07multibyte.ztst index 6909346cb..413c4fe73 100644 --- a/Test/D07multibyte.ztst +++ b/Test/D07multibyte.ztst @@ -212,6 +212,20 @@ >first >second + read -ed £ +0:read with multibyte delimiter where bytes of delimiter also occur in input +one¤twoãthree + + read -ed $'\xa0' <<<$'first\xa0second' +0:read delimited by a byte that isn't a valid multibyte character +>first + + read -ed $'\xc2' +0:read delimited by a single byte terminates if the byte is part of a multibyte character +one + (IFS=« read -d » -A array print -l $array) -- cgit v1.2.3 From d23bcf11714063498a9aec17a360af485026fef2 Mon Sep 17 00:00:00 2001 From: Max Coplan Date: Fri, 30 Dec 2022 15:41:27 -0800 Subject: 51263: fix typo - `an path` -> `a path` --- ChangeLog | 2 ++ Doc/Zsh/files.yo | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index ea064eab8..9efe679f4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,7 @@ 2022-12-31 Oliver Kiddle + * Max Coplan: 51263: Doc/Zsh/files.yo: fix typo, an -> a + * Shohei YOSHIDA: 51255: Completion/Unix/Command/_ruby: Update erb completion for version 4.0.2 diff --git a/Doc/Zsh/files.yo b/Doc/Zsh/files.yo index 65debe044..c6c7da307 100644 --- a/Doc/Zsh/files.yo +++ b/Doc/Zsh/files.yo @@ -14,7 +14,7 @@ cindex(zshenv) Commands are first read from tt(zshenv()); this cannot be overridden. Subsequent behaviour is modified by the tt(RCS) and tt(GLOBAL_RCS) options; the former affects all startup files, while the -second only affects global startup files (those shown here with an +second only affects global startup files (those shown here with a path starting with a tt(/)). If one of the options is unset at any point, any subsequent startup file+LPAR()s+RPAR() of the corresponding -- cgit v1.2.3 From c01479a2ede78b9b53057322e4b9f5bd0a103a00 Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Tue, 10 Jan 2023 20:57:03 +0100 Subject: 51280: add support for italic and faint fonts in the line editor --- ChangeLog | 4 ++++ Doc/Zsh/zle.yo | 10 +++++++++- Src/Zle/zle_refresh.c | 26 +++----------------------- Src/init.c | 19 ++++++++++++++++++- Src/prompt.c | 39 ++++++++++++++++++++++++++++++++++++-- Src/zsh.h | 52 +++++++++++++++++++++++++++++---------------------- 6 files changed, 101 insertions(+), 49 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index b3518f1bf..9e2938bf8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2023-01-10 Oliver Kiddle + * 51280: Doc/Zsh/zle.yo, Src/Zle/zle_refresh.c, Src/init.c, + Src/prompt.c, Src/zsh.h: add support for italic and faint + fonts in the line editor + * 51258, 51272: Src/Modules/watch.c, Src/Zle/complist.c, Src/Zle/zle.h, Src/Zle/zle_main.c, Src/Zle/zle_refresh.c, Src/Zle/zle_tricky.c, Src/Zle/zle_utils.c, Src/builtin.c, diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo index 58700072a..60254e828 100644 --- a/Doc/Zsh/zle.yo +++ b/Doc/Zsh/zle.yo @@ -2780,9 +2780,13 @@ This works similarly to the foreground colour, except the background is not usually affected by the bold attribute. ) item(tt(bold))( -The characters in the given context are shown in a bold font. +The characters in the given context are shown with a bold font weight. Not all terminals distinguish bold fonts. ) +item(tt(faint))( +The characters in the given context are shown with a faint font weight. +Not all terminals distinguish faint fonts. +) item(tt(standout))( The characters in the given context are shown in the terminal's standout mode. The actual effect is specific to the terminal; on many terminals it @@ -2796,6 +2800,10 @@ The characters in the given context are shown underlined. Some terminals show the foreground in a different colour instead; in this case whitespace will not be highlighted. ) +item(tt(italic))( +The characters in the given context are shown in a italic font. +Not all terminals support italic fonts. +) enditem() The characters described above as `special' are as follows. The diff --git a/Src/Zle/zle_refresh.c b/Src/Zle/zle_refresh.c index ae8e5c109..d40400886 100644 --- a/Src/Zle/zle_refresh.c +++ b/Src/Zle/zle_refresh.c @@ -1193,23 +1193,10 @@ zrefresh(void) offset = predisplaylen; /* increment over it */ if (rhp->start + offset <= tmppos && tmppos < rhp->end + offset) { - if (rhp->atr & (TXTFGCOLOUR|TXTBGCOLOUR)) { - /* override colour with later entry */ - base_attr = rhp->atr; - } else { - /* no colour set yet */ - base_attr |= rhp->atr; - } + base_attr = mixattrs(rhp->atr, base_attr); } } - if (special_attr & (TXTFGCOLOUR|TXTBGCOLOUR)) { - /* keep colours from special attributes */ - all_attr = special_attr | - (base_attr & ~TXT_ATTR_COLOUR_MASK); - } else { - /* keep colours from standard attributes */ - all_attr = special_attr | base_attr; - } + all_attr = mixattrs(special_attr, base_attr); if (t == scs) /* if cursor is here, remember it */ rpms.nvcs = rpms.s - nbuf[rpms.nvln = rpms.ln]; @@ -2441,14 +2428,7 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs) } } } - if (special_attr & (TXTFGCOLOUR|TXTBGCOLOUR)) { - /* keep colours from special attributes */ - all_attr = special_attr | - (base_attr & ~TXT_ATTR_COLOUR_MASK); - } else { - /* keep colours from standard attributes */ - all_attr = special_attr | base_attr; - } + all_attr = mixattrs(special_attr, base_attr); if (tmpline[t0] == ZWC('\t')) { for (*vp++ = zr_sp; (vp - vbuf) & 7; ) diff --git a/Src/init.c b/Src/init.c index 75ef2e094..68621a0ad 100644 --- a/Src/init.c +++ b/Src/init.c @@ -747,7 +747,7 @@ init_shout(void) static char *tccapnams[TC_COUNT] = { "cl", "le", "LE", "nd", "RI", "up", "UP", "do", "DO", "dc", "DC", "ic", "IC", "cd", "ce", "al", "dl", "ta", - "md", "so", "us", "me", "se", "ue", "ch", + "md", "mh", "so", "us", "ZH", "me", "se", "ue", "ZR", "ch", "ku", "kd", "kl", "kr", "sc", "rc", "bc", "AF", "AB" }; @@ -872,6 +872,23 @@ init_term(void) /* The following is an attempt at a heuristic, * but it fails in some cases */ /* rprompt_indent = ((hasam && !hasbw) || hasye || !tccan(TCLEFT)); */ + + /* if there's no termcap entry for italics, use CSI 3 m */ + if (!tccan(TCITALICSBEG)) { + zsfree(tcstr[TCITALICSBEG]); + tcstr[TCITALICSBEG] = ztrdup("\033[3m"); + tclen[TCITALICSBEG] = 4; + } + if (!tccan(TCITALICSEND)) { + zsfree(tcstr[TCITALICSEND]); + tcstr[TCITALICSEND] = ztrdup("\033[23m"); + tclen[TCITALICSEND] = 5; + } + if (!tccan(TCFAINTBEG)) { + zsfree(tcstr[TCFAINTBEG]); + tcstr[TCFAINTBEG] = ztrdup("\033[2m"); + tclen[TCFAINTBEG] = 4; + } } return 1; } diff --git a/Src/prompt.c b/Src/prompt.c index 880194f87..488a90d09 100644 --- a/Src/prompt.c +++ b/Src/prompt.c @@ -1590,7 +1590,15 @@ applytextattributes(int flags) turnoff &= turnoff - 1; } - if (keepcount < turncount || (change & ~txtpendingattrs & TXTBOLDFACE)) { + /* enabling bold can be relied upon to disable faint + * (the converse not so as that commonly does nothing at all) */ + if (txtcurrentattrs & TXTFAINT && txtpendingattrs & TXTBOLDFACE) { + --turncount; + change &= ~TXTFAINT; + } + + if (keepcount < turncount || + (change & ~txtpendingattrs & TXT_ATTR_FONT_WEIGHT)) { tsetcap(TCALLATTRSOFF, flags); /* this cleared all attributes, may need to restore some */ change = txtpendingattrs & TXT_ATTR_ALL & ~txtunknownattrs; @@ -1607,13 +1615,19 @@ applytextattributes(int flags) /* in some cases, that clears all attributes */ change = txtpendingattrs & TXT_ATTR_ALL & ~txtunknownattrs; } + if (change & ~txtpendingattrs & TXTITALIC) + tsetcap(TCITALICSEND, flags); } if (change & txtpendingattrs & TXTBOLDFACE) tsetcap(TCBOLDFACEBEG, flags); + if (change & txtpendingattrs & TXTFAINT) + tsetcap(TCFAINTBEG, flags); if (change & txtpendingattrs & TXTSTANDOUT) tsetcap(TCSTANDOUTBEG, flags); if (change & txtpendingattrs & TXTUNDERLINE) tsetcap(TCUNDERLINEBEG, flags); + if (change & txtpendingattrs & TXTITALIC) + tsetcap(TCITALICSBEG, flags); if (change & TXT_ATTR_FG_MASK) set_colour_attribute(txtpendingattrs, COL_SEQ_FG, flags); @@ -1678,6 +1692,25 @@ tunsetattrs(zattr newattrs) txtpendingattrs &= ~TXT_ATTR_BG_MASK; } +/* Merge two attribute sets. In an case where attributes might conflict + * choose those from the first parameter. Foreground and background + * colours are taken together - less likely to end up with unreadable + * combinations. */ + +/**/ +mod_export zattr +mixattrs(zattr primary, zattr secondary) +{ + zattr result = secondary; + /* take colours from primary */ + if (primary & (TXTFGCOLOUR|TXTBGCOLOUR)) + result &= ~TXT_ATTR_COLOUR_MASK; + /* take font weight from primary */ + if (primary & TXT_ATTR_FONT_WEIGHT) + result &= ~TXT_ATTR_FONT_WEIGHT; + return result | primary; +} + /***************************************************************************** * Utilities dealing with colour and other forms of highlighting. * @@ -1700,9 +1733,11 @@ struct highlight { static const struct highlight highlights[] = { { "none", 0, TXT_ATTR_ALL }, - { "bold", TXTBOLDFACE, 0 }, + { "bold", TXTBOLDFACE, TXTFAINT }, + { "faint", TXTFAINT, TXTBOLDFACE }, { "standout", TXTSTANDOUT, 0 }, { "underline", TXTUNDERLINE, 0 }, + { "italic", TXTITALIC, 0 }, { NULL, 0, 0 } }; diff --git a/Src/zsh.h b/Src/zsh.h index 35ae033e3..e834c7e06 100644 --- a/Src/zsh.h +++ b/Src/zsh.h @@ -2646,22 +2646,25 @@ struct ttyinfo { #define TCDELLINE 16 #define TCNEXTTAB 17 #define TCBOLDFACEBEG 18 -#define TCSTANDOUTBEG 19 -#define TCUNDERLINEBEG 20 -#define TCALLATTRSOFF 21 -#define TCSTANDOUTEND 22 -#define TCUNDERLINEEND 23 -#define TCHORIZPOS 24 -#define TCUPCURSOR 25 -#define TCDOWNCURSOR 26 -#define TCLEFTCURSOR 27 -#define TCRIGHTCURSOR 28 -#define TCSAVECURSOR 29 -#define TCRESTRCURSOR 30 -#define TCBACKSPACE 31 -#define TCFGCOLOUR 32 -#define TCBGCOLOUR 33 -#define TC_COUNT 34 +#define TCFAINTBEG 19 +#define TCSTANDOUTBEG 20 +#define TCUNDERLINEBEG 21 +#define TCITALICSBEG 22 +#define TCALLATTRSOFF 23 +#define TCSTANDOUTEND 24 +#define TCUNDERLINEEND 25 +#define TCITALICSEND 27 +#define TCHORIZPOS 27 +#define TCUPCURSOR 28 +#define TCDOWNCURSOR 29 +#define TCLEFTCURSOR 30 +#define TCRIGHTCURSOR 31 +#define TCSAVECURSOR 32 +#define TCRESTRCURSOR 33 +#define TCBACKSPACE 34 +#define TCFGCOLOUR 35 +#define TCBGCOLOUR 36 +#define TC_COUNT 37 #define tccan(X) (tclen[X]) @@ -2676,12 +2679,14 @@ struct ttyinfo { #endif #define TXTBOLDFACE 0x0001 -#define TXTSTANDOUT 0x0002 -#define TXTUNDERLINE 0x0004 -#define TXTFGCOLOUR 0x0008 -#define TXTBGCOLOUR 0x0010 +#define TXTFAINT 0x0002 +#define TXTSTANDOUT 0x0004 +#define TXTUNDERLINE 0x0008 +#define TXTITALIC 0x0010 +#define TXTFGCOLOUR 0x0020 +#define TXTBGCOLOUR 0x0040 -#define TXT_ATTR_ALL 0x001F +#define TXT_ATTR_ALL 0x007F /* * Indicates to zle_refresh.c that the character entry is an @@ -2690,7 +2695,10 @@ struct ttyinfo { #define TXT_MULTIWORD_MASK 0x0400 /* used when, e.g an invalid colour is specified */ -#define TXT_ERROR 0xF00000F000000800 +#define TXT_ERROR 0xF00000F000000003 + +/* Mask for font weight */ +#define TXT_ATTR_FONT_WEIGHT (TXTBOLDFACE|TXTFAINT) /* Mask for colour to use in foreground */ #define TXT_ATTR_FG_COL_MASK 0x000000FFFFFF0000 -- cgit v1.2.3 From be2c91bbc3361398a18ea4b77d493dded0a60e79 Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Tue, 10 Jan 2023 21:13:52 +0100 Subject: 51291: support for highlighting ellipses in the line editor --- ChangeLog | 3 +++ Doc/Zsh/zle.yo | 3 +++ Src/Zle/zle_refresh.c | 60 ++++++++++++++++++++++++++++++--------------------- Src/prompt.c | 3 +++ 4 files changed, 44 insertions(+), 25 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 1ed133a71..e1d81845b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2023-01-10 Oliver Kiddle + * 51291: Doc/Zsh/zle.yo, Src/Zle/zle_refresh.c, Src/prompt.c: + support for highlighting ellipses in the line editor + * 51290: Src/Zle/zle_refresh.c: fix display of control characters with SINGLE_LINE_ZLE set diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo index 60254e828..c622483d7 100644 --- a/Doc/Zsh/zle.yo +++ b/Doc/Zsh/zle.yo @@ -2691,6 +2691,9 @@ for different completions. item(tt(paste))( Following a command to paste text, the characters that were inserted. ) +item(tt(ellipsis))( +Markers used to indicate where the text doesn't fit within the terminal. +) enditem() When tt(region_highlight) is set, the contexts that describe a region DASH()- diff --git a/Src/Zle/zle_refresh.c b/Src/Zle/zle_refresh.c index b196370dc..a587f696a 100644 --- a/Src/Zle/zle_refresh.c +++ b/Src/Zle/zle_refresh.c @@ -203,12 +203,12 @@ int predisplaylen, postdisplaylen; /* - * Attributes used by default on the command line, and - * attributes for highlighting special (unprintable) characters - * displayed on screen. + * Attributes used by default on the command line, + * for highlighting special (unprintable) characters displayed on screen, + * and for ellipsis continuation markers. */ -static zattr default_attr, special_attr; +static zattr default_attr, special_attr, ellipsis_attr; /* * Array of region highlights, no special termination. @@ -259,8 +259,8 @@ static const REFRESH_ELEMENT zr_cr = { ZWC('\r'), TXT_ERROR }; static const REFRESH_ELEMENT zr_dt = { ZWC('.'), TXT_ERROR }; #endif static const REFRESH_ELEMENT zr_nl = { ZWC('\n'), TXT_ERROR }; -static const REFRESH_ELEMENT zr_sp = { ZWC(' '), TXT_ERROR }; -static const REFRESH_ELEMENT zr_zr = { ZWC('\0'), TXT_ERROR }; +static const REFRESH_ELEMENT zr_sp = { ZWC(' '), 0 }; +static const REFRESH_ELEMENT zr_zr = { ZWC('\0'), 0 }; /* * Constant arrays to be copied into place: these are memcpy'd, @@ -269,10 +269,10 @@ static const REFRESH_ELEMENT zr_zr = { ZWC('\0'), TXT_ERROR }; static const REFRESH_ELEMENT zr_end_ellipsis[] = { { ZWC(' '), 0 }, { ZWC('<'), 0 }, - { ZWC('.'), 0 }, - { ZWC('.'), 0 }, - { ZWC('.'), 0 }, - { ZWC('.'), 0 }, + { ZWC('.'), TXT_ERROR }, + { ZWC('.'), TXT_ERROR }, + { ZWC('.'), TXT_ERROR }, + { ZWC('.'), TXT_ERROR }, { ZWC(' '), 0 }, }; #define ZR_END_ELLIPSIS_SIZE \ @@ -281,16 +281,16 @@ static const REFRESH_ELEMENT zr_end_ellipsis[] = { static const REFRESH_ELEMENT zr_mid_ellipsis1[] = { { ZWC(' '), 0 }, { ZWC('<'), 0 }, - { ZWC('.'), 0 }, - { ZWC('.'), 0 }, - { ZWC('.'), 0 }, - { ZWC('.'), 0 }, + { ZWC('.'), TXT_ERROR }, + { ZWC('.'), TXT_ERROR }, + { ZWC('.'), TXT_ERROR }, + { ZWC('.'), TXT_ERROR }, }; #define ZR_MID_ELLIPSIS1_SIZE \ ((int)(sizeof(zr_mid_ellipsis1)/sizeof(zr_mid_ellipsis1[0]))) static const REFRESH_ELEMENT zr_mid_ellipsis2[] = { - { ZWC('>'), 0 }, + { ZWC('>'), TXT_ERROR }, { ZWC(' '), 0 }, }; #define ZR_MID_ELLIPSIS2_SIZE \ @@ -298,10 +298,10 @@ static const REFRESH_ELEMENT zr_mid_ellipsis2[] = { static const REFRESH_ELEMENT zr_start_ellipsis[] = { { ZWC('>'), 0 }, - { ZWC('.'), 0 }, - { ZWC('.'), 0 }, - { ZWC('.'), 0 }, - { ZWC('.'), 0 }, + { ZWC('.'), TXT_ERROR }, + { ZWC('.'), TXT_ERROR }, + { ZWC('.'), TXT_ERROR }, + { ZWC('.'), TXT_ERROR }, }; #define ZR_START_ELLIPSIS_SIZE \ ((int)(sizeof(zr_start_ellipsis)/sizeof(zr_start_ellipsis[0]))) @@ -321,6 +321,7 @@ zle_set_highlight(void) int isearch_attr_set = 0; int suffix_attr_set = 0; int paste_attr_set = 0; + int ellipsis_attr_set = 0; struct region_highlight *rhp; special_attr = default_attr = 0; @@ -361,6 +362,9 @@ zle_set_highlight(void) } else if (strpfx("paste:", *atrs)) { match_highlight(*atrs + 6, &(region_highlights[3].atr)); paste_attr_set = 1; + } else if (strpfx("ellipsis:", *atrs)) { + match_highlight(*atrs + 9, &ellipsis_attr); + ellipsis_attr_set = 1; } } } @@ -376,6 +380,9 @@ zle_set_highlight(void) region_highlights[2].atr = TXTBOLDFACE; if (!paste_attr_set) region_highlights[3].atr = TXTSTANDOUT; + if (!ellipsis_attr_set) + ellipsis_attr = TXTBGCOLOUR | ((zattr) 3 << TXT_ATTR_BG_COL_SHIFT) | + TXTFGCOLOUR | ((zattr) 4 << TXT_ATTR_FG_COL_SHIFT); allocate_colour_buffer(); } @@ -599,10 +606,8 @@ zwcputc(const REFRESH_ELEMENT *c) VARARR(char, mbtmp, MB_CUR_MAX + 1); #endif - if (c->atr != TXT_ERROR) { - treplaceattrs(c->atr); - applytextattributes(0); - } + treplaceattrs(c->atr); + applytextattributes(0); #ifdef MULTIBYTE_SUPPORT if (c->atr & TXT_MULTIWORD_MASK) { @@ -1479,6 +1484,7 @@ zrefresh(void) } #endif ZR_memcpy(rpms.sen, zr_end_ellipsis, ZR_END_ELLIPSIS_SIZE); + rpms.sen[1].atr = ellipsis_attr; #ifdef MULTIBYTE_SUPPORT /* Extend to the end if we backed off for a wide character */ if (extra_ellipsis) { @@ -1514,6 +1520,7 @@ zrefresh(void) } #endif ZR_memcpy(rpms.sen, zr_mid_ellipsis1, ZR_MID_ELLIPSIS1_SIZE); + rpms.sen[1].atr = ellipsis_attr; rpms.sen += ZR_MID_ELLIPSIS1_SIZE; #ifdef MULTIBYTE_SUPPORT /* Extend if we backed off for a wide character */ @@ -1523,6 +1530,7 @@ zrefresh(void) } #endif ZR_memcpy(rpms.sen, zr_mid_ellipsis2, ZR_MID_ELLIPSIS2_SIZE); + rpms.sen[1].atr = prompt_attr; nbuf[rpms.tosln][winw] = nbuf[rpms.tosln][winw + 1] = zr_zr; } @@ -1555,7 +1563,9 @@ zrefresh(void) t0 = winw - lpromptw; t0 = t0 > ZR_START_ELLIPSIS_SIZE ? ZR_START_ELLIPSIS_SIZE : t0; ZR_memcpy(nbuf[0] + lpromptw, zr_start_ellipsis, t0); + (*nbuf + lpromptw)->atr = ellipsis_attr; ZR_memset(nbuf[0] + lpromptw + t0, zr_sp, winw - t0 - lpromptw); + (*nbuf + lpromptw + t0)->atr = prompt_attr; nbuf[0][winw] = nbuf[0][winw + 1] = zr_zr; } @@ -2514,11 +2524,11 @@ singlerefresh(ZLE_STRING_T tmpline, int tmpll, int tmpcs) } if (winpos) { vbuf[winpos].chr = ZWC('<'); /* line continues to the left */ - vbuf[winpos].atr = 0; + vbuf[winpos].atr = ellipsis_attr; } if ((int)ZR_strlen(vbuf + winpos) > (winw - hasam)) { vbuf[winpos + winw - hasam - 1].chr = ZWC('>'); /* line continues to right */ - vbuf[winpos + winw - hasam - 1].atr = 0; + vbuf[winpos + winw - hasam - 1].atr = ellipsis_attr; vbuf[winpos + winw - hasam] = zr_zr; } ZR_strcpy(nbuf[0], vbuf + winpos); diff --git a/Src/prompt.c b/Src/prompt.c index 4f29a6728..39fcf5eb7 100644 --- a/Src/prompt.c +++ b/Src/prompt.c @@ -1648,6 +1648,9 @@ cleartextattributes(int flags) mod_export void treplaceattrs(zattr newattrs) { + if (newattrs == TXT_ERROR) + return; + if (txtunknownattrs) { /* Set current attributes to the opposite of the new ones * for any that are unknown so that applytextattributes() -- cgit v1.2.3 From 88ccf2be1e3ba70608e69639c8929254ff01fcc8 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Tue, 31 Jan 2023 17:08:57 -0800 Subject: 51337: parameter attributes cross-reference typeset equivalents (or lack thereof) --- ChangeLog | 5 +++++ Doc/Zsh/expn.yo | 31 ++++++++++++++++--------------- Doc/Zsh/mod_parameter.yo | 3 ++- 3 files changed, 23 insertions(+), 16 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 44b24c534..f2456de50 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2023-01-31 Bart Schaefer + + * 51337: Doc/Zsh/expn.yo, Doc/Zsh/mod_parameter.yo: parameter + attributes cross-reference typeset equivalents (or lack thereof) + 2023-01-27 Daniel Shahaf * unposted (cribbed from users/28784 by Roman): diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index eb8cdbae5..ad55c24ba 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -1093,7 +1093,7 @@ tt(KSH_ARRAYS) option a subscript `tt([*])' or `tt([@])' is needed to operate on the whole array, as usual. ) item(tt(L))( -Convert all letters in the result to lower case. +Convert all letters in the result to lower case, like `tt(typeset -l)'. ) item(tt(n))( Sort decimal integers numerically; if the first differing @@ -1177,34 +1177,35 @@ of the parameter would usually appear. This string consists of keywords separated by hyphens (`tt(-)'). The first keyword in the string describes the main type, it can be one of `tt(scalar)', `tt(array)', `tt(integer)', `tt(float)' or `tt(association)'. The other keywords describe the type in -more detail: +more detail, in most cases corresponding to options of the `tt(typeset)' +command: startitem() item(tt(local))( for local parameters ) item(tt(left))( -for left justified parameters +for left justified parameters (tt(-L)) ) item(tt(right_blanks))( -for right justified parameters with leading blanks +for right justified parameters with leading blanks (tt(-R)) ) item(tt(right_zeros))( -for right justified parameters with leading zeros +for right justified parameters with leading zeros (tt(-Z)) ) item(tt(lower))( for parameters whose value is converted to all lower case when it is -expanded +expanded (tt(-l)) ) item(tt(upper))( for parameters whose value is converted to all upper case when it is -expanded +expanded (tt(-u)) ) item(tt(readonly))( -for readonly parameters +for readonly parameters (tt(-r)) ) item(tt(tag))( -for tagged parameters +for tagged parameters (tt(-t)) ) item(tt(tied))( for parameters tied to another parameter in the manner of tt(PATH) @@ -1212,16 +1213,16 @@ for parameters tied to another parameter in the manner of tt(PATH) special parameters or user-defined with `tt(typeset -T)' ) item(tt(export))( -for exported parameters +for exported parameters (tt(-x) or `tt(export)') ) item(tt(unique))( -for arrays which keep only the first occurrence of duplicated values +for arrays which keep only the first occurrence of duplicated values (tt(-U)) ) item(tt(hide))( -for parameters with the `hide' flag +for parameters with the `hide' flag (tt(-h)) ) item(tt(hideval))( -for parameters with the `hideval' flag +for parameters with the `hideval' flag (tt(-H)) ) item(tt(special))( for special parameters defined by the shell @@ -1229,10 +1230,10 @@ for special parameters defined by the shell enditem() ) item(tt(u))( -Expand only the first occurrence of each unique word. +Expand only the first occurrence of each unique word, like `tt(typeset -U)'. ) item(tt(U))( -Convert all letters in the result to upper case. +Convert all letters in the result to upper case, like `tt(typeset -u)'. ) item(tt(v))( Used with tt(k), substitute (as two consecutive words) both the key diff --git a/Doc/Zsh/mod_parameter.yo b/Doc/Zsh/mod_parameter.yo index f3bcd7957..18fd3606e 100644 --- a/Doc/Zsh/mod_parameter.yo +++ b/Doc/Zsh/mod_parameter.yo @@ -125,7 +125,8 @@ zmanref(zshexpn) ifnzman(\ noderef(Parameter Expansion) )\ -. +. The value may also be `tt(undefined)' indicating a parameter that +may be autoloaded from a module but has not yet been referenced. Setting or unsetting keys in this array is not possible. ) vindex(modules) -- cgit v1.2.3 From 76d095df9de31d46b0ca042039855ffc286f5fdb Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Thu, 2 Feb 2023 10:12:17 +0000 Subject: 51306: error message in ${unset?error} should be expanded --- ChangeLog | 3 +++ Doc/Zsh/expn.yo | 4 +++- Src/subst.c | 6 +++++- Test/D04parameter.ztst | 5 +++++ 4 files changed, 16 insertions(+), 2 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index ee9a623d4..6bdaeedbf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2023-02-02 Peter Stephenson + * 51306: Doc/Zsh/expn.yo, Src/subst.c, Test/D04parameter.ztst: + error message in ${unset?...} should be expanded. + * 51307: Src/input.c, Src/parse.c, Test/A02alias.ztst: error on attempt to expand alias in function definition name didn't find the original alias and printed an extra error. diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index ad55c24ba..fd5443b20 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -665,7 +665,9 @@ item(tt(${)var(name)tt(:?)var(word)tt(}))( In the first form, if var(name) is set, or in the second form if var(name) is both set and non-null, then substitute its value; otherwise, print var(word) and exit from the shell. Interactive shells instead return to -the prompt. If var(word) is omitted, then a standard message is printed. +the prompt. If var(word) is omitted, then a standard message is +printed. Note that var(word) is expanded even though its value +is not substituted onto the command line. ) enditem() diff --git a/Src/subst.c b/Src/subst.c index 897188862..4ad9fee1a 100644 --- a/Src/subst.c +++ b/Src/subst.c @@ -3076,7 +3076,11 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, if (vunset) { if (isset(EXECOPT)) { *idend = '\0'; - zerr("%s: %s", idbeg, *s ? s : "parameter not set"); + if (*s){ + singsub(&s); + zerr("%s: %s", idbeg, s); + } else + zerr("%s: %s", idbeg, "parameter not set"); /* * In interactive shell we need to return to * top-level prompt --- don't clear this error diff --git a/Test/D04parameter.ztst b/Test/D04parameter.ztst index 29275f13f..a11652d1e 100644 --- a/Test/D04parameter.ztst +++ b/Test/D04parameter.ztst @@ -110,6 +110,11 @@ *>*foo:1: 1: no arguments given >reached + message="expand me and remove quotes" + (: ${UNSET_PARAM?$message}) +1:${...?....} performs expansion on the message +?(eval):2: UNSET_PARAM: expand me and remove quotes + print ${set1:+word1} ${set1+word2} ${null1:+word3} ${null1+word4} print ${unset1:+word5} ${unset1+word6} 0:${...:+...}, ${...+...} -- cgit v1.2.3 From bffdbccda69683ce857dfad457e3209c0f00aa0c Mon Sep 17 00:00:00 2001 From: Daniel Shahaf Date: Thu, 2 Feb 2023 17:54:17 +0000 Subject: 51354: Fix markup in man page version --- ChangeLog | 4 ++++ Doc/Zsh/params.yo | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 6bdaeedbf..1a66e94e9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2023-02-02 Daniel Shahaf + + * 51354: Doc/Zsh/params.yo: Fix markup in man page version + 2023-02-02 Peter Stephenson * 51306: Doc/Zsh/expn.yo, Src/subst.c, Test/D04parameter.ztst: diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index 2a30085a8..55009c6de 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -1668,7 +1668,7 @@ well as any directory names. The default is `tt(/tmp/zsh)'. vindex(TMPSUFFIX) item(tt(TMPSUFFIX))( A filename suffix which the shell will use for temporary files created -by process substitutions (e.g., `tt(=LPAR()var(list)RPAR())'). +by process substitutions (e.g., `tt(=LPAR())var(list)tt(RPAR())'). Note that the value should include a leading dot `tt(.)' if intended to be interpreted as a file extension. The default is not to append any suffix, thus this parameter should be assigned only when needed -- cgit v1.2.3 From 102145b0487ddd7d2a048a0787b79146434d2cd6 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sun, 12 Feb 2023 11:25:42 -0800 Subject: 51362: Begin documentation for named references. --- ChangeLog | 3 ++ Doc/Zsh/builtins.yo | 20 ++++++++++-- Doc/Zsh/expn.yo | 82 ++++++++++++++++++++++++++++++++++++++++++++++-- Doc/Zsh/mod_parameter.yo | 2 +- Doc/Zsh/params.yo | 46 ++++++++++++++++++++++++++- 5 files changed, 146 insertions(+), 7 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index db768ccbc..221b9838d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2023-02-12 Bart Schaefer + * 51362: Doc/Zsh/builtins.yo, Doc/Zsh/expn.yo, Doc/Zsh/params.yo, + Doc/Zsh/mod_parameter.yo: Begin documentation for named references. + * 51361: Test/K01nameref.ztst, Test/V10private.ztst: Tests for 51360. diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 56428a714..64c47346f 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -2037,6 +2037,20 @@ To initialize a parameter var(param) to a command output and mark it readonly, use tt(typeset -r )var(param) or tt(readonly )var(param) after the parameter assignment statement. +cindex(named reference) +cindex(reference, named) +The flag tt(-n) creates a em(named reference) to another parameter. +The second parameter need not exist at the time the reference is +created. No other attribute flags may be used in conjunction with +tt(-n). The var(name) assigned-to may not be an array element nor use +a subscript, but the var(value) assigned may be any valid parameter +name syntax, even a subscripted array element (incuding an associative +array element) or an array slice, which is evaluated when the named +reference is expanded. +See ifzman(zmanref(zshexpn))ifnzman(noderef(Parameter Expansion)) and +ifzman(zmanref(zshparam))ifnzman(noderef(Parameters)) for details of the +behavior of named references. + If no attribute flags are given, and either no var(name) arguments are present or the flag tt(+m) is used, then each parameter name printed is preceded by a list of the attributes of that parameter (tt(array), @@ -2242,9 +2256,9 @@ automatically given the tt(-h) attribute to avoid name clashes. item(tt(-H))( Hide value: specifies that tt(typeset) will not display the value of the parameter when listing parameters; the display for such parameters is -always as if the `tt(PLUS())' flag had been given. Use of the parameter is -in other respects normal, and the option does not apply if the parameter is -specified by name, or by pattern with the tt(-m) option. This is on by +always as if the `tt(PLUS())' flag were given, but use of the parameter is +in other respects normal. This effect does not apply when the parameter is +specified by name or by pattern with the tt(-m) option. This is on by default for the parameters in the tt(zsh/parameter) and tt(zsh/mapfile) modules. Note, however, that unlike the tt(-h) flag this is also useful for non-special parameters. diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index fd5443b20..ff6087cac 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -1226,6 +1226,9 @@ for parameters with the `hide' flag (tt(-h)) item(tt(hideval))( for parameters with the `hideval' flag (tt(-H)) ) +item(tt(nameref))( +for named references having an empty value (tt(-n)) +) item(tt(special))( for special parameters defined by the shell ) @@ -1523,6 +1526,77 @@ Include the unmatched portion in the result (the em(R)est). ) enditem() +subsect(Named References) +cindex(named references) +cindex(namerefs) +cindex(reference variables) +cindex(parameters, nameref) +The command +ifzman() +indent(tt(typeset -n )var(pname)tt(=)var(rname)) + +initializes a parameter var(pname) as a reference to a second +parameter var(rname). With the few exceptions described here, when +var(pname) is used in any of the expansion forms described above, the +parameter var(rname) is expanded instead. This is similar to the +action of the `tt((P))' expansion flag, but when var(rname) has itself +been declared a named reference, that third parameter referenced by +var(pname) is also expanded, and so on. With `tt((P))' this must be +done explicitly, so for example +tt(${LPAR()P)tt(RPAR()${LPAR()P)tt(RPAR())var(name)tt(}}). + +Unlike `tt((P))', named references in substitutions that perform +assignment, such as tt(${)var(pname)tt(::=)var(word)tt(}), do not +create new arrays when var(rname) is in the form of an array element +or slice and no such array (or associative array) is presently set. +This includes arrays declared, but not initialized, when the option +tt(TYPESET_TO_UNSET) is in effect. The var(word) is substituted but +no assignment occurs. + +Also unlike `tt((P))' named references always expand parameters at +the scope in which var(rname) existed when `tt(typeset -n)' was +called. This can be used to expand or assign parameters from an +earlier scope even if a local of the same name has been declared at +a later scope. Example: +ifzman() +example(tt(caller=OUTER) +tt(func LPAR()RPAR() {) +tt( print before local: $caller) +tt( typeset -n outer=$1) +tt( local caller=INNER) +tt( print by reference: $outer) +tt( outer=RESULT) +tt(}) +tt(func caller) +tt(print after func: $caller)) + +displays the output +ifzman() +example(tt(before local: OUTER) +tt(by reference: OUTER) +tt(after func: RESULT)) + +When var(rname) includes an array subscript, the subscript expression +is interpreted at the time tt(${)var(pname)tt(}) is expanded. Any +form of subscript is allowed, including those that select individual +elements, substrings of scalar strings, or multiple elements as with +array slices or the `tt((i))', `tt((I))', `tt((r))', `tt((R))' and +`tt((w))' subscript flags. + +When var(rname) is an array (but not an array element or slice), the +named reference may also be used in substitutions requiring an +var(arrayname), so these are equivalent: +ifzman() +example(tt(${)var(name)tt(:|)var(rname)tt(}) +tt(${)var(name)tt(:|)var(pname)tt(})) + +Expansions of the form `tt(${LPAR()t)tt(RPAR())var(pname)tt(})' expand +the type information of var(rname), unless var(rname) is empty, in which +case `tt(nameref)' is expanded, or when no variable var(rname) exists, +in which case the expansion is empty. + +See also ifzman(zmanref(zshparam))ifnzman(noderef(Parameters)). + subsect(Rules) cindex(parameter expansion rules) cindex(rules, parameter expansion) @@ -1545,12 +1619,16 @@ substitutions; the nested substitution will return either a scalar or an array as determined by the flags, possibly adjusted for quoting. All the following steps take place where applicable at all levels of substitution. -Note that, unless the `tt((P))' flag is present, the flags and any +Note that, unless the `tt((P))' flag or a named reference is present, +the flags and any subscripts apply directly to the value of the nested substitution; for example, the expansion tt(${${foo}}) behaves exactly the same as -tt(${foo}). When the `tt((P))' flag is present in a nested substitution, +tt(${foo}). When a named reference or the `tt((P))' flag is used in a +nested substitution, the other substitution rules are applied to the value em(before) it is interpreted as a name, so tt(${${(P)foo}}) may differ from tt(${(P)foo}). +When both a named reference and the `tt((P))' flag appear, the named +reference is resolved before `tt((P))' is applied. At each nested level of substitution, the substituted words undergo all forms of single-word substitution (i.e. not filename generation), including diff --git a/Doc/Zsh/mod_parameter.yo b/Doc/Zsh/mod_parameter.yo index 18fd3606e..0e89d65c8 100644 --- a/Doc/Zsh/mod_parameter.yo +++ b/Doc/Zsh/mod_parameter.yo @@ -125,7 +125,7 @@ zmanref(zshexpn) ifnzman(\ noderef(Parameter Expansion) )\ -. The value may also be `tt(undefined)' indicating a parameter that +. The value may also be `tt(undefined)' indicating a parameter that may be autoloaded from a module but has not yet been referenced. Setting or unsetting keys in this array is not possible. ) diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index 55009c6de..2dfd5bd14 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -80,6 +80,7 @@ startmenu() menu(Array Parameters) menu(Positional Parameters) menu(Local Parameters) +menu(Named References) menu(Parameters Set By The Shell) menu(Parameters Used By The Shell) endmenu() @@ -592,7 +593,7 @@ array assignment of the form `var(n)tt(=LPAR())var(value) ...tt(RPAR())' is allowed, and has the effect of shifting all the values at positions greater than var(n) by as many positions as necessary to accommodate the new values. -texinode(Local Parameters)(Parameters Set By The Shell)(Positional Parameters)(Parameters) +texinode(Local Parameters)(Named References)(Positional Parameters)(Parameters) sect(Local Parameters) Shell function executions delimit scopes for shell parameters. (Parameters are dynamically scoped.) The tt(typeset) builtin, and its @@ -626,6 +627,49 @@ find the programs in tt(/new/directory) inside a function. Note that the restriction in older versions of zsh that local parameters were never exported has been removed. +cindex(named references) +cindex(references, named) +texinode(Named References)(Parameters Set By The Shell)(Local Parameters)(Parameters) +sect(Named References) +Zsh supports two different mechanisms for indirect parameter referencing: +ifzman() +example(tt(typeset )var(name)tt(=)var(rname) +tt(print -r -- ${LPAR()P)tt(RPAR())var(name)tt(})) +ifzman() +example(tt(typeset -n )var(pname)tt(=)var(rname) +tt(print -r -- ${)var(pname)tt(})) + +The `tt((P))' flag method is older and should be used when a script +needs to be backwards-compatible. This is described fully in +ifzman(zmanref(zshexpn))ifnzman(noderef(Parameter Expansion)). + +When a em(named reference) is created with `tt(typeset -n)', all uses +of var(pname) in assignments and expansions instead assign to or +expand var(rname). This also applies to `tt(unset )var(pname)' and to +most subsequent uses of `tt(typeset)' with the exception of +`tt(typeset +n)', so to remove a named reference it is necessary to +use: +ifzman() +example(tt(typeset +n )var(pname) +tt(unset )var(pname)) + +When `tt(typeset -n )var(pname)tt(=)var(rname)' appears in a given +(global or function) scope, `tt(${)var(pname)tt(})' refers to +var(rname) in the scope of the tt(typeset) command. This differs +from ksh93 where the scope used is that of var(rname) even if the +current var(rname) would be found in a surrounding scope. +em(This is a misfeature.) + +An empty reference such as one of +ifzman() +example(tt(typeset -n )var(pname) +tt(typeset -n )var(pname)tt(=) +tt(typeset -n )var(pname)tt(="")) + +acts as a placeholder. The first non-empty assignment to var(pname) +initializes the reference, and subsequently any expansions of, or +assignments to, var(pname) act on the referenced parameter. + texinode(Parameters Set By The Shell)(Parameters Used By The Shell)(Local Parameters)(Parameters) sect(Parameters Set By The Shell) In the parameter lists that follow, the mark `' indicates that the -- cgit v1.2.3 From e807ac1157015581c1466407cbe722179244be37 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sun, 12 Feb 2023 11:32:11 -0800 Subject: 51375: Clarify documentation, fix typos, add indexing. --- ChangeLog | 3 +++ Doc/Zsh/builtins.yo | 12 +++++++++--- Doc/Zsh/expn.yo | 4 ++-- Doc/Zsh/mod_parameter.yo | 8 ++++++++ Doc/Zsh/params.yo | 36 ++++++++++++++++++++++-------------- 5 files changed, 44 insertions(+), 19 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index af448c5e3..1353de45d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2023-02-12 Bart Schaefer + * 51375: Doc/Zsh/builtins.yo, Doc/Zsh/expn.yo, Doc/Zsh/params.yo, + Doc/Zsh/mod_parameter.yo: Clarify, fix typos, add indexing. + * 51374: Src/Modules/parameter.c, Src/params.c, Test/README, Test/K01nameref.ztst: Expose named references in $parameters, fix substitution error. diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 64c47346f..97a82226b 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -2041,16 +2041,22 @@ cindex(named reference) cindex(reference, named) The flag tt(-n) creates a em(named reference) to another parameter. The second parameter need not exist at the time the reference is -created. No other attribute flags may be used in conjunction with -tt(-n). The var(name) assigned-to may not be an array element nor use +created. No attributes except tt(-g) may be used in conjunction with +tt(-n). The var(name) so created may not be an array element nor use a subscript, but the var(value) assigned may be any valid parameter -name syntax, even a subscripted array element (incuding an associative +name syntax, even a subscripted array element (including an associative array element) or an array slice, which is evaluated when the named reference is expanded. See ifzman(zmanref(zshexpn))ifnzman(noderef(Parameter Expansion)) and ifzman(zmanref(zshparam))ifnzman(noderef(Parameters)) for details of the behavior of named references. +Local function scoping rules for `tt(typeset)' do apply with `tt(-n)', +so a declaration within a function persists only until the end of the +function unless `tt(-g -n)' is specified, and any local parameter (of +any type) with the same var(name) supplants a named reference from a +surrounding scope. + If no attribute flags are given, and either no var(name) arguments are present or the flag tt(+m) is used, then each parameter name printed is preceded by a list of the attributes of that parameter (tt(array), diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index ff6087cac..8b1c69c55 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -1592,8 +1592,8 @@ tt(${)var(name)tt(:|)var(pname)tt(})) Expansions of the form `tt(${LPAR()t)tt(RPAR())var(pname)tt(})' expand the type information of var(rname), unless var(rname) is empty, in which -case `tt(nameref)' is expanded, or when no variable var(rname) exists, -in which case the expansion is empty. +case the expansion is `tt(nameref)', or when no variable var(rname) +exists, in which case the expansion is empty. See also ifzman(zmanref(zshparam))ifnzman(noderef(Parameters)). diff --git a/Doc/Zsh/mod_parameter.yo b/Doc/Zsh/mod_parameter.yo index 0e89d65c8..3defef2b7 100644 --- a/Doc/Zsh/mod_parameter.yo +++ b/Doc/Zsh/mod_parameter.yo @@ -127,6 +127,14 @@ noderef(Parameter Expansion) )\ . The value may also be `tt(undefined)' indicating a parameter that may be autoloaded from a module but has not yet been referenced. +When the key is the name of a named reference, the value is +`tt(nameref-)' prepended to the type of the referenced parameter, +for example +ifzman() +example(tt(% typeset -n parms=parameters) +tt(% print -r ${parameters[parms]}) +tt(nameref-association-readonly-hide-hideval-special)) + Setting or unsetting keys in this array is not possible. ) vindex(modules) diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index 2dfd5bd14..c7ecf0f64 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -85,6 +85,8 @@ menu(Parameters Set By The Shell) menu(Parameters Used By The Shell) endmenu() texinode(Array Parameters)(Positional Parameters)()(Parameters) +cindex(array parameters) +cindex(parameters, array) sect(Array Parameters) To assign an array value, write one of: findex(set, use of) @@ -567,6 +569,8 @@ entire second parameter concatenated with the filename generation pattern `tt([3,5])'. texinode(Positional Parameters)(Local Parameters)(Array Parameters)(Parameters) +cindex(positional parameters) +cindex(parameters, positional) sect(Positional Parameters) The positional parameters provide access to the command-line arguments of a shell function, shell script, or the shell itself; see @@ -594,6 +598,8 @@ allowed, and has the effect of shifting all the values at positions greater than var(n) by as many positions as necessary to accommodate the new values. texinode(Local Parameters)(Named References)(Positional Parameters)(Parameters) +cindex(local parameters) +cindex(parameters, local) sect(Local Parameters) Shell function executions delimit scopes for shell parameters. (Parameters are dynamically scoped.) The tt(typeset) builtin, and its @@ -627,9 +633,9 @@ find the programs in tt(/new/directory) inside a function. Note that the restriction in older versions of zsh that local parameters were never exported has been removed. +texinode(Named References)(Parameters Set By The Shell)(Local Parameters)(Parameters) cindex(named references) cindex(references, named) -texinode(Named References)(Parameters Set By The Shell)(Local Parameters)(Parameters) sect(Named References) Zsh supports two different mechanisms for indirect parameter referencing: ifzman() @@ -641,24 +647,24 @@ tt(print -r -- ${)var(pname)tt(})) The `tt((P))' flag method is older and should be used when a script needs to be backwards-compatible. This is described fully in -ifzman(zmanref(zshexpn))ifnzman(noderef(Parameter Expansion)). +the Parameter Expansion Flags section of +ifzman(zmanref(zshexpn))ifnzman(noderef(Parameter Expansion)). Zsh +versions em(greater than) tt(5.9.0) are required for `tt(typeset -n)'. +This manual was generated with Zsh tt(version()). When a em(named reference) is created with `tt(typeset -n)', all uses of var(pname) in assignments and expansions instead assign to or expand var(rname). This also applies to `tt(unset )var(pname)' and to most subsequent uses of `tt(typeset)' with the exception of -`tt(typeset +n)', so to remove a named reference it is necessary to -use: +`tt(typeset -n)' and `tt(typeset +n)', so to remove a named reference +it is necessary to use one of: ifzman() -example(tt(typeset +n )var(pname) -tt(unset )var(pname)) +example(tt(typeset -n )var(pname) +tt(typeset +n )var(pname)) -When `tt(typeset -n )var(pname)tt(=)var(rname)' appears in a given -(global or function) scope, `tt(${)var(pname)tt(})' refers to -var(rname) in the scope of the tt(typeset) command. This differs -from ksh93 where the scope used is that of var(rname) even if the -current var(rname) would be found in a surrounding scope. -em(This is a misfeature.) +followed by +ifzman() +indent(tt(unset )var(pname)) An empty reference such as one of ifzman() @@ -668,9 +674,11 @@ tt(typeset -n )var(pname)tt(="")) acts as a placeholder. The first non-empty assignment to var(pname) initializes the reference, and subsequently any expansions of, or -assignments to, var(pname) act on the referenced parameter. +assignments to, var(pname) act on the referenced parameter. This +is explained in the Named References section of +ifzman(zmanref(zshexpn))ifnzman(noderef(Parameter Expansion)). -texinode(Parameters Set By The Shell)(Parameters Used By The Shell)(Local Parameters)(Parameters) +texinode(Parameters Set By The Shell)(Parameters Used By The Shell)(Named References)(Parameters) sect(Parameters Set By The Shell) In the parameter lists that follow, the mark `' indicates that the parameter is special. `' indicates that the parameter does not exist -- cgit v1.2.3 From acb15e3cc9af6c5b51e570765e6734e958d32aef Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sun, 12 Feb 2023 11:57:31 -0800 Subject: 51403: Tests and documentation for 51402, clean up some other tests. --- ChangeLog | 4 ++ Doc/Zsh/builtins.yo | 16 +++-- Doc/Zsh/expn.yo | 5 +- Doc/Zsh/func.yo | 34 +++++++++++ Doc/Zsh/grammar.yo | 3 + Doc/Zsh/params.yo | 4 +- Test/K01nameref.ztst | 170 ++++++++++++++++++++++++++++++++++++++++++++++++--- 7 files changed, 218 insertions(+), 18 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 2b2f2a08b..0cc33c7f1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2023-02-12 Bart Schaefer + * 51403: Doc/Zsh/builtins.yo, Doc/Zsh/expn.yo, Doc/Zsh/func.yo, + Doc/Zsh/grammar.yo, Doc/Zsh/params.yo, Test/K01nameref.ztst: + Tests and documentation for 51402, clean up some other tests. + * 51402: Src/builtin.c, Src/loop.c, Src/params.c, Src/zsh.h: Add ksh/bash features (unset -n, for ref), readonly refs, better error checking and messages, code injection safety, diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 97a82226b..92917c06c 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -2041,12 +2041,13 @@ cindex(named reference) cindex(reference, named) The flag tt(-n) creates a em(named reference) to another parameter. The second parameter need not exist at the time the reference is -created. No attributes except tt(-g) may be used in conjunction with +created. Only tt(-g) and tt(-r) may be used in conjunction with tt(-n). The var(name) so created may not be an array element nor use a subscript, but the var(value) assigned may be any valid parameter name syntax, even a subscripted array element (including an associative array element) or an array slice, which is evaluated when the named -reference is expanded. +reference is expanded. It is an error for a named reference to refer +to itself, even indirectly through a chain of references. See ifzman(zmanref(zshexpn))ifnzman(noderef(Parameter Expansion)) and ifzman(zmanref(zshparam))ifnzman(noderef(Parameters)) for details of the behavior of named references. @@ -2443,7 +2444,7 @@ with the command `tt(zmodload -F zsh/rlimits b:unlimit)'. ) findex(unset) cindex(parameters, unsetting) -item(tt(unset) [ tt(-fmv) ] var(name) ...)( +item(tt(unset) [ tt(-fmv) ] [ tt(-n) ] var(name) ...)( Each named parameter is unset. Local parameters remain local even if unset; they appear unset within scope, but the previous value will still reappear when the scope ends. @@ -2457,10 +2458,13 @@ be quoted) and all parameters with matching names are unset. Note that this cannot be used when unsetting associative array elements, as the subscript will be treated as part of the pattern. -The tt(-v) flag specifies that var(name) refers to parameters. This is the -default behaviour. +The tt(-v) flag specifies that var(name) refers to parameters. This is +the default behaviour. If the tt(-n) option is supplied, and +var(name) is a a named reference, var(name) will be unset rather than +the variable it references. -tt(unset -f) is equivalent to tt(unfunction). +tt(unset -f) is equivalent to tt(unfunction). The tt(-n) option has +no effect with tt(-f). ) findex(unsetopt) cindex(options, unsetting) diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index 8b1c69c55..ef01794e6 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -1581,7 +1581,10 @@ is interpreted at the time tt(${)var(pname)tt(}) is expanded. Any form of subscript is allowed, including those that select individual elements, substrings of scalar strings, or multiple elements as with array slices or the `tt((i))', `tt((I))', `tt((r))', `tt((R))' and -`tt((w))' subscript flags. +`tt((w))' subscript flags. However, the subscript is evaluated with +the tt(NO_EXEC) option in effect, so command substitution and other +similar constructs produce no output, although are not syntactically +excluded. When var(rname) is an array (but not an array element or slice), the named reference may also be used in substitutions requiring an diff --git a/Doc/Zsh/func.yo b/Doc/Zsh/func.yo index 12db3f56a..d4914df7a 100644 --- a/Doc/Zsh/func.yo +++ b/Doc/Zsh/func.yo @@ -13,6 +13,40 @@ Functions are executed like commands with the arguments passed as positional parameters. (See noderef(Command Execution).) +Parameters declared by any of the `tt(typeset)' family of commands +during the execution of a function become em(local) to the function +unless the `tt(-g)' option is used. This is the em(scope) of the +parameter, which extends dynamically to any other functions called by +the declaring function. In most cases, local parameters take the +place of any other parameter having the same name that was assigned or +declared in an earlier function scope. +(See noderef(Local Parameters).) + +A named parameter declared with the `tt(-n)' option to any of the +`tt(typeset)' commands becomes a reference to a parameter in scope at +the time of assignment to the named reference, which may be at a +different call level than the declaring function. For this reason, +it is good practice to declare a named reference as soon as the +referent parameter is in scope, and as early as possible in the +function if the reference is to a parameter in a calling scope. + +A typical use of named references is to pass the name +of the referent as a positional parameter. For example, +ifzman() +example(pop+LPAR()RPAR() { + local -n ref=$1 + local last=$ref[$#ref] + ref[$#ref]=LPAR()RPAR() + print -r -- $last +} +array=LPAR() a list of five values RPAR() +pop array) + +prints the word `tt(values)' and shortens `tt($array)' to +`tt(LPAR() a list of five RPAR())'. There are no local parameters in +tt(pop) at the time `tt(ref=$1)' is assigned, so `tt(ref)' becomes a +reference to `tt(array)' in the caller. + Functions execute in the same process as the caller and share all files and present working directory with the diff --git a/Doc/Zsh/grammar.yo b/Doc/Zsh/grammar.yo index 9af211090..1b834f41a 100644 --- a/Doc/Zsh/grammar.yo +++ b/Doc/Zsh/grammar.yo @@ -187,6 +187,9 @@ Expand the list of var(word)s, and set the parameter var(name) to each of them in turn, executing var(list) each time. If the `tt(in) var(word)' is omitted, use the positional parameters instead of the var(word)s. +If any var(name) has been declared as a named reference, +the corresponding var(word) is treated as the name of a +parameter and var(name) is made a reference to that. The var(term) consists of one or more newline or tt(;) which terminate the var(word)s, and are optional when the diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index c7ecf0f64..946c00793 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -656,8 +656,8 @@ When a em(named reference) is created with `tt(typeset -n)', all uses of var(pname) in assignments and expansions instead assign to or expand var(rname). This also applies to `tt(unset )var(pname)' and to most subsequent uses of `tt(typeset)' with the exception of -`tt(typeset -n)' and `tt(typeset +n)', so to remove a named reference -it is necessary to use one of: +`tt(typeset -n)' and `tt(typeset +n)', so to remove a named reference, +use either `tt(unset -n )var(pname)' or one of: ifzman() example(tt(typeset -n )var(pname) tt(typeset +n )var(pname)) diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst index a663194a7..61c2b006a 100644 --- a/Test/K01nameref.ztst +++ b/Test/K01nameref.ztst @@ -23,6 +23,12 @@ ptr=var typeset -n 0:assign nameref placeholder +>ptr=var + + typeset ptr=var + typeset -n ptr + typeset -n +0:convert scalar to nameref >ptr=var typeset -n ptr=var @@ -46,6 +52,11 @@ F:Other type changes are fatal errors, should this also be? >typeset -n ptr=var >typeset -t var + typeset -n ptr=var[2] + typeset -t ptr +1:change type of referenced array element +*?*var\[2\]: can't change type via subscript reference + typeset -n ptr[1]=var 1:illegal nameref name *?*reference variable cannot be an array @@ -91,6 +102,14 @@ F:Other type changes are fatal errors, should this also be? typeset -p var 0:unset via nameref + typeset -n ptr=var + typeset var=value + unset -n ptr + typeset -p var ptr +0:unset of the nameref itself +F:If earlier tests change, might get "no such variable" here +>typeset var=value + typeset -n ptr=var typeset var=value typeset -p ptr var @@ -105,11 +124,11 @@ F:Other type changes are fatal errors, should this also be? typeset -n ptr=var ptr=value typeset -p var ptr - unset var # for next test 0:assign new scalar via nameref >typeset -g var=value >typeset -n ptr=var + unset var typeset -n ptr=var typeset var=(val1 val2) typeset -p ptr var @@ -132,11 +151,11 @@ F:unexpected side-effects of previous tests typeset -n ptr=var ptr=(val1 val2) typeset -p var ptr - unset var # for next test 0:assign new array via nameref >typeset -g -a var=( val1 val2 ) >typeset -n ptr=var + unset var typeset -n ptr2=var typeset -n ptr1=ptr2 typeset var=value @@ -178,12 +197,12 @@ F:unexpected side-effects of previous tests typeset -n ptr1=ptr2 typeset ptr1=newvalue typeset -p ptr1 ptr2 var - unset var # for next test 0:typeset new parameter indirectly >typeset -n ptr1=ptr2 >typeset -n ptr2=var >typeset var=newvalue + unset var typeset -n ptr2=var typeset -n ptr1=ptr2 typeset var=value @@ -219,19 +238,18 @@ F:unexpected side-effects of previous tests typeset -n ptr1=ptr2 typeset ptr1=(val1 val2) typeset -p ptr1 ptr2 var - unset var # for next test 0:typeset new array indirectly >typeset -n ptr1=ptr2 >typeset -n ptr2=var >typeset -a var=( val1 val2 ) - typeset -p ptr1 ptr2 var + typeset -p ptr1 ptr2 1:check state of paramtab FOUR F:unexpected side-effects of previous tests *?*no such variable: ptr1 *?*no such variable: ptr2 -*?*no such variable: var + unset var typeset -n ptr2=var typeset -n ptr1=ptr2 ptr1=(val1 val2) @@ -244,6 +262,20 @@ F:unexpected side-effects of previous tests typeset -n ptr1=ptr2 typeset -n ptr2=ptr1 1:direct nameref loop not allowed +*?*invalid self reference + + unset var + typeset -gn ptr1=var + typeset -p ptr1 +0:global reference to unset var +>typeset -g -n ptr1=var + + unset -n ptr1 + typeset -gn ptr1 + typeset -p ptr1 + ptr1=ptr1 +1:global direct reference +>typeset -g -n ptr1 *?*invalid self reference typeset -n ptr1=ptr2 @@ -252,28 +284,39 @@ F:unexpected side-effects of previous tests 1:indirect nameref loop not allowed *?*invalid self reference + typeset -n ptr1 ptr2 + ptr1=ptr2 + ptr2=ptr1 +1:looping assignment not allowed +*?*invalid self reference + + unset -n ptr2 typeset -n ptr2='path[2]' print -r -- $ptr2 0q:nameref to array element, no braces >${path[2]} + unset -n ptr2 typeset -n ptr2='path[2]' print -r -- ${ptr2} 0q:nameref to array element, with braces >${path[2]} + unset -n ptr1 typeset -A hash=(x MISS y HIT) typeset -n ptr1='hash[y]' print -r -- $ptr1 0:nameref to hash element, no braces >HIT + unset -n ptr1 typeset -A hash=(x MISS y HIT) typeset -n ptr1='hash[y]' print -r -- ${ptr1} 0:nameref to hash element, with braces >HIT + unset -n ptr2 typeset -a ary=(1 2) typeset -n ptr2='ary[2]' ptr2=TWO @@ -281,6 +324,7 @@ F:unexpected side-effects of previous tests 0:assign array element by nameref >typeset -a ary=( 1 TWO ) + unset -n ptr2 typeset -n ptr2='ary[2]' ptr2=TWO typeset -p ary @@ -288,6 +332,7 @@ F:unexpected side-effects of previous tests F:ksh93 does not implement this either >typeset -a ary=( '' TWO ) + unset -n ptr1 typeset -A hash=(x MISS y MISS) typeset -n ptr1='hash[y]' ptr1=HIT @@ -295,6 +340,7 @@ F:ksh93 does not implement this either 0:assign to hash element by nameref >typeset -A hash=( [x]=MISS [y]=HIT ) + unset -n ptr1 typeset -A hash typeset -n ptr1='hash[y]' ptr1=HIT @@ -303,10 +349,13 @@ F:ksh93 does not implement this either F:ksh93 does not implement this either >typeset -A hash=( [y]=HIT ) + unset -n ptr1 typeset -n ptr1='not good' 1:invalid nameref *?*invalid variable name: not good + unset -n ptr1 + unset hash typeset -A hash typeset -n ptr1='hash[y]' print ${ptr1::=HIT} @@ -316,24 +365,25 @@ F:ksh93 does not implement this either >typeset -n ptr1='hash[y]' >typeset -A hash=( [y]=HIT ) + unset -n ptr + unset gval typeset -n ptr=gval gval=global () { local gval=local; print $ptr; typeset -p ptr gval } - unset gval # for next test 0:up-reference part 1 >global >typeset -g -n ptr=gval >typeset gval=local - typeset -p ptr ptr1 ptr2 val gval + typeset -p ptr ptr1 ptr2 val 1:check state of paramtab FIVE F:unexpected side-effects of previous tests *?*no such variable: ptr *?*no such variable: ptr1 *?*no such variable: ptr2 *?*no such variable: val -*?*no such variable: gval + unset gval typeset -n ptr1=gval typeset gval () { typeset gval=local; ptr1=global } @@ -509,4 +559,106 @@ F:Same test, should part 5 output look like this? >bry[2] >ipsum + unset -n ref + unset var + typeset -n ref=var + typeset var=GLOBAL + () { + typeset -n ref=$1 + print -r $ref + ref=RESET + typeset -p ref var + } ref + typeset -p ref var +0:local reference points to same-name global reference, part 1 +>GLOBAL +>typeset -n ref=ref +>typeset -g var=RESET +>typeset -n ref=var +>typeset var=RESET + + unset -n ref + unset var + typeset -n ref=var + () { + typeset -n ref=$1 + print -r $ref + ref=RESET + typeset -p ref var + } ref + typeset -p ref var +0:local reference points to same-name global reference, part 2 +> +>typeset -n ref=ref +>typeset -g var=RESET +>typeset -n ref=var +>typeset -g var=RESET + + unset -n ref + unset one + typeset -n ref + typeset one=ONE + for ref in one ref two; do print -r $ref; done +1:for-loop variable is a reference, part 1 +>ONE +*?*ref: invalid self reference + + unset -n ref + unset one + typeset -n ref + () { + typeset one=ONE + for ref in one ref two; do print -r ${(t)ref}; done + } +1:for-loop variable is a reference, part 2 +>scalar-local +*?*ref: invalid self reference + + unset -n ref + unset one var + typeset -n ref=var + () { + typeset one=ONE + typeset -n ref=ref + for ref in one ref two; do + typeset -p ref + print -r $ref + done + typeset -p ref + } + typeset -p ref +0:for-loop variable is a reference, part 3 +>typeset -n ref=one +>ONE +>typeset -n ref=ref +> +>typeset -n ref=two +> +>typeset -n ref=two +>typeset -n ref=var + + unset -n ref + unset one + typeset -n ref + () { + setopt localoptions warn_nested_var + typeset one=ONE + for ref in one two; do print -r ${(t)ref}; done + typeset -n ref + for ref in one two; do print -r ${(t)ref}; done + } +0:for-loop variable is a reference, part 4, warnings +>scalar-local +> +>scalar-local +> +*?*ref: global reference to local variable: one + + typeset -n ptr='ary[$(echo 2)]' + typeset -a ary=(one two three) + print $ptr +1:attempt deferred command substitution in subscript +F:runs in `setopt noexec` so $(...) returns nothing +*?*bad math expression: empty string + %clean -- cgit v1.2.3 From 3d8c567d581831ed2a60b34d8a855531b0cf197d Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sun, 26 Feb 2023 19:18:19 -0800 Subject: Fix typo --- ChangeLog | 2 ++ Doc/Zsh/restricted.yo | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 2c07606c5..6ce0d9fbf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,7 @@ 2023-02-26 Bart Schaefer + * unposted: Doc/Zsh/restricted.yo: fix typo + * 51460: Src/module.c: avoid crash on bad parameter autofeature 2023-02-24 Oliver Kiddle diff --git a/Doc/Zsh/restricted.yo b/Doc/Zsh/restricted.yo index 33dfc96c6..7948cfe8a 100644 --- a/Doc/Zsh/restricted.yo +++ b/Doc/Zsh/restricted.yo @@ -65,7 +65,7 @@ variables. Except for the few listed above, zsh does not restrict the setting of environment variables. If a `tt(perl)', `tt(python)', `tt(bash)', or other general purpose -interpreted script it treated as a restricted +interpreted script is treated as a restricted command, the user can work around the restriction by setting specially crafted `tt(PERL5LIB)', `tt(PYTHONPATH)', `tt(BASHENV)' (etc.) environment variables. On GNU systems, any -- cgit v1.2.3 From 4bc1f6e0d2b60976b9c0412d72097ee1c812139b Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sun, 5 Mar 2023 14:06:25 -0800 Subject: 51484: Extend named reference handling for special parameters, improve doc. --- ChangeLog | 3 ++ Doc/Zsh/builtins.yo | 2 + Src/params.c | 108 +++++++++++++++++++++++++++++++--------------------- 3 files changed, 69 insertions(+), 44 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 38a0395d6..30e9850e5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2023-03-05 Bart Schaefer + * 51484: Src/builtins.yo Src/params.c: Extend named reference + handling for special parameters, improve doc. + * 51483: Src/Zle/compcore.c, Src/Zle/zle_tricky.c, Src/lex.c, Src/params.c, Src/subst.c, Src/utils.c, Src/zsh.h, Src/ztype.h: Enable assignment and expansion of parameters with ksh-like diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 92917c06c..5393cb149 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -1924,6 +1924,8 @@ redef(SPACES)(0)(tt(ifztexi(NOTRANS(@ @ @ @ @ @ @ @ ))ifnztexi( ))) xitem(tt(typeset )[ {tt(PLUS())|tt(-)}tt(AHUaghlmrtux) ] \ [ {tt(PLUS())|tt(-)}tt(EFLRZip) [ var(n) ] ]) xitem(SPACES()[ tt(+) ] [ var(name)[tt(=)var(value)] ... ]) +xitem(tt(typeset )[ {tt(PLUS())|tt(-)}tt(n) ] \ +[ tt(-gr) ] [ var(name)[tt(=)var(value)] ... ]) xitem(tt(typeset )tt(-T) [ {tt(PLUS())|tt(-)}tt(Uglrux) ] [ {tt(PLUS())|tt(-)}tt(LRZp) [ var(n) ] ]) xitem(SPACES()[ tt(+) | var(SCALAR)[tt(=)var(value)] var(array)[tt(=LPAR())var(value) ...tt(RPAR())] [ var(sep) ] ]) item(tt(typeset) tt(-f) [ {tt(PLUS())|tt(-)}tt(TUkmtuz) ] [ tt(+) ] [ var(name) ... ])( diff --git a/Src/params.c b/Src/params.c index d3b6a7d43..c9f4b3017 100644 --- a/Src/params.c +++ b/Src/params.c @@ -475,6 +475,15 @@ static initparam argvparam_pm = IPDEF9("", &pparams, NULL, \ ((V) && (!(V)->pm || ((V)->pm->node.flags & PM_UNSET) || \ !(V)->pm->node.nam || !*(V)->pm->node.nam)) +/* + * For named references. Simple named references are just like scalars + * for efficiency, but special named references need get/set functions. + */ +#define GETREFNAME(PM) (((PM)->node.flags & PM_SPECIAL) ? \ + (PM)->gsu.s->getfn(PM) : (PM)->u.str) +#define SETREFNAME(PM,S) (((PM)->node.flags & PM_SPECIAL) ? \ + (PM)->gsu.s->setfn(PM,(S)) : ((PM)->u.str = (S))) + static Param argvparam; /* "parameter table" - hash table containing the parameters @@ -520,7 +529,7 @@ getparamnode(HashTable ht, const char *nam) HashNode hn = gethashnode2(ht, nam); Param pm = (Param) hn; - if (pm && pm->u.str && (pm->node.flags & PM_AUTOLOAD)) { + if (pm && (pm->node.flags & PM_AUTOLOAD) && pm->u.str) { char *mn = dupstring(pm->u.str); (void)ensurefeature(mn, "p:", (pm->node.flags & PM_AUTOALL) ? NULL : @@ -1002,12 +1011,13 @@ createparam(char *name, int flags) struct asgment stop; stop.flags = PM_NAMEREF | (flags & PM_LOCAL); stop.name = oldpm->node.nam; - stop.value.scalar = oldpm->u.str; + stop.value.scalar = GETREFNAME(oldpm); lastpm = (Param)resolve_nameref(oldpm, &stop); if (lastpm) { if (lastpm->node.flags & PM_NAMEREF) { - if (lastpm->u.str && *(lastpm->u.str)) { - name = lastpm->u.str; + char *refname = GETREFNAME(lastpm); + if (refname && *refname) { + name = refname; oldpm = NULL; } else { if (!(lastpm->node.flags & PM_READONLY)) @@ -2145,25 +2155,28 @@ fetchvalue(Value v, char **pptr, int bracks, int flags) memset(v, 0, sizeof(*v)); else v = (Value) hcalloc(sizeof *v); - if ((pm->node.flags & PM_NAMEREF) && pm->u.str && *(pm->u.str)) { - /* only happens for namerefs pointing to array elements */ - char *ref = dupstring(pm->u.str); - char *ss = pm->width ? ref + pm->width : NULL; - if (ss) { - sav = *ss; - *ss = 0; + if (pm->node.flags & PM_NAMEREF) { + char *refname = GETREFNAME(pm); + if (refname && *refname) { + /* only happens for namerefs pointing to array elements */ + char *ref = dupstring(refname); + char *ss = pm->width ? ref + pm->width : NULL; + if (ss) { + sav = *ss; + *ss = 0; + } + Param p1 = (Param)gethashnode2(paramtab, ref); + if (!(p1 && (pm = upscope(p1, pm->base))) || + ((pm->node.flags & PM_UNSET) && + !(pm->node.flags & PM_DECLARED))) + return NULL; + if (ss) { + flags |= SCANPM_NOEXEC; + *ss = sav; + s = dyncat(ss,*pptr); + } else + s = *pptr; } - Param p1 = (Param)gethashnode2(paramtab, ref); - if (!(p1 && (pm = upscope(p1, pm->base))) || - ((pm->node.flags & PM_UNSET) && - !(pm->node.flags & PM_DECLARED))) - return NULL; - if (ss) { - flags |= SCANPM_NOEXEC; - *ss = sav; - s = dyncat(ss,*pptr); - } else - s = *pptr; } if (PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)) { /* Overload v->isarr as the flag bits for hashed arrays. */ @@ -3648,7 +3661,7 @@ mod_export Param setiparam_no_convert(char *s, zlong val) { /* - * If the target is already an integer, thisgets converted + * If the target is already an integer, this gets converted * back. Low technology rules. */ char buf[BDIGBUFSIZE]; @@ -6115,22 +6128,23 @@ resolve_nameref(Param pm, const Asgment stop) const char *seek = stop ? stop->value.scalar : NULL; if (pm && (pm->node.flags & PM_NAMEREF)) { - if (pm && (pm->node.flags & (PM_UNSET|PM_TAGGED))) { + char *refname = GETREFNAME(pm); + if (pm->node.flags & (PM_UNSET|PM_TAGGED)) { /* Semaphore with createparam() */ pm->node.flags &= ~PM_UNSET; if (pm->node.flags & PM_NEWREF) /* See setloopvar() */ return NULL; - if (pm->u.str && *(pm->u.str) && (pm->node.flags & PM_TAGGED)) + if (refname && *refname && (pm->node.flags & PM_TAGGED)) pm->node.flags |= PM_SELFREF; /* See setscope() */ return (HashNode) pm; - } else if (pm->u.str) { + } else if (refname) { if ((pm->node.flags & PM_TAGGED) || - (stop && strcmp(pm->u.str, stop->name) == 0)) { - /* zwarnnam(pm->u.str, "invalid self reference"); */ + (stop && strcmp(refname, stop->name) == 0)) { + /* zwarnnam(refname, "invalid self reference"); */ return stop ? (HashNode)pm : NULL; } - if (*(pm->u.str)) - seek = pm->u.str; + if (*refname) + seek = refname; } } else if (pm && !(stop && (stop->flags & PM_NAMEREF))) @@ -6180,8 +6194,13 @@ setloopvar(char *name, char *value) Param pm = (Param) gethashnode2(realparamtab, name); if (pm && (pm->node.flags & PM_NAMEREF)) { + if (pm->node.flags & PM_READONLY) { + /* Bash error is: "%s: readonly variable" */ + zerr("read-only reference: %s", pm->node.nam); + return; + } pm->base = pm->width = 0; - pm->u.str = ztrdup(value); + SETREFNAME(pm, ztrdup(value)); pm->node.flags &= ~PM_UNSET; pm->node.flags |= PM_NEWREF; setscope(pm); @@ -6197,7 +6216,8 @@ setscope(Param pm) if (pm->node.flags & PM_NAMEREF) { Param basepm; struct asgment stop; - char *t = pm->u.str ? itype_end(pm->u.str, INAMESPC, 0) : NULL; + char *refname = GETREFNAME(pm); + char *t = refname ? itype_end(refname, INAMESPC, 0) : NULL; /* Temporarily change nameref to array parameter itself */ if (t && *t == '[') @@ -6211,7 +6231,7 @@ setscope(Param pm) stop.flags |= PM_LOCAL; basepm = (Param)resolve_nameref(pm, &stop); if (t) { - pm->width = t - pm->u.str; + pm->width = t - refname; *t = '['; } if (basepm) { @@ -6220,23 +6240,23 @@ setscope(Param pm) if (pm->node.flags & PM_SELFREF) { /* Loop signalled by resolve_nameref() */ if (upscope(pm, pm->base) == pm) { - zerr("%s: invalid self reference", pm->u.str); + zerr("%s: invalid self reference", refname); unsetparam_pm(pm, 0, 1); return; } pm->node.flags &= ~PM_SELFREF; } else if (pm->base == pm->level) { - if (pm->u.str && *(pm->u.str) && - strcmp(pm->node.nam, pm->u.str) == 0) { - zerr("%s: invalid self reference", pm->u.str); + if (refname && *refname && + strcmp(pm->node.nam, refname) == 0) { + zerr("%s: invalid self reference", refname); unsetparam_pm(pm, 0, 1); return; } } - } else if (basepm->u.str) { + } else if ((t = GETREFNAME(basepm))) { if (basepm->base <= basepm->level && - strcmp(pm->node.nam, basepm->u.str) == 0) { - zerr("%s: invalid self reference", pm->u.str); + strcmp(pm->node.nam, t) == 0) { + zerr("%s: invalid self reference", refname); unsetparam_pm(pm, 0, 1); return; } @@ -6251,11 +6271,11 @@ setscope(Param pm) unsetparam_pm(pm, 0, 1); } else if (isset(WARNNESTEDVAR)) zwarn("reference %s in enclosing scope set to local variable %s", - pm->node.nam, pm->u.str); + pm->node.nam, refname); } - if (pm->u.str && upscope(pm, pm->base) == pm && - strcmp(pm->node.nam, pm->u.str) == 0) { - zerr("%s: invalid self reference", pm->u.str); + if (refname && upscope(pm, pm->base) == pm && + strcmp(pm->node.nam, refname) == 0) { + zerr("%s: invalid self reference", refname); unsetparam_pm(pm, 0, 1); } } -- cgit v1.2.3 From ea0bd72dd84a783355969e0a9a1584ce7b1ea08b Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sun, 5 Mar 2023 14:16:31 -0800 Subject: 51485: module for several ksh93 features, mostly enabled only in ksh emulation. --- ChangeLog | 4 + Doc/Makefile.in | 2 +- Doc/Zsh/mod_ksh93.yo | 211 ++++++++++++++++++++++++++++++++++++++++ Src/Modules/ksh93.c | 265 ++++++++++++++++++++++++++++++++++++++++++++++++++ Src/Modules/ksh93.mdd | 8 ++ Src/utils.c | 2 +- 6 files changed, 490 insertions(+), 2 deletions(-) create mode 100644 Doc/Zsh/mod_ksh93.yo create mode 100644 Src/Modules/ksh93.c create mode 100644 Src/Modules/ksh93.mdd (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 30e9850e5..a6a1b2d8c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2023-03-05 Bart Schaefer + * 51485: Doc/Makefile.in, Doc/Zsh/mod_ksh93.yo, Src/utils.c, + Src/Modules/ksh93.c, Src/Modules/ksh93.mdd: module for several + ksh93 features, mostly enabled only in ksh emulation. + * 51484: Src/builtins.yo Src/params.c: Extend named reference handling for special parameters, improve doc. diff --git a/Doc/Makefile.in b/Doc/Makefile.in index 23e5fc7e2..136b080d6 100644 --- a/Doc/Makefile.in +++ b/Doc/Makefile.in @@ -63,7 +63,7 @@ Zsh/mod_compctl.yo Zsh/mod_complete.yo Zsh/mod_complist.yo \ Zsh/mod_computil.yo Zsh/mod_curses.yo \ Zsh/mod_datetime.yo Zsh/mod_db_gdbm.yo Zsh/mod_deltochar.yo \ Zsh/mod_example.yo Zsh/mod_files.yo Zsh/mod_langinfo.yo \ -Zsh/mod_mapfile.yo Zsh/mod_mathfunc.yo \ +Zsh/mod_ksh93.yo Zsh/mod_mapfile.yo Zsh/mod_mathfunc.yo \ Zsh/mod_nearcolor.yo Zsh/mod_newuser.yo \ Zsh/mod_parameter.yo Zsh/mod_pcre.yo Zsh/mod_private.yo \ Zsh/mod_regex.yo Zsh/mod_sched.yo Zsh/mod_socket.yo \ diff --git a/Doc/Zsh/mod_ksh93.yo b/Doc/Zsh/mod_ksh93.yo new file mode 100644 index 000000000..3ae644bd1 --- /dev/null +++ b/Doc/Zsh/mod_ksh93.yo @@ -0,0 +1,211 @@ +COMMENT(!MOD!zsh/ksh93 +Extended ksh93 compatibility for "emulate ksh" +!MOD!) +cindex(ksh93) +The tt(zsh/ksh93) module provides one builtin and several parameters to +improve compatibility with ksh93. As of this writing, several ksh93 +features are still missing. + +subsect(Ksh Builtins) +The single builtin provided by this module is: + +startitem() +findex(nameref) +cindex(named references, creating) +item(tt(nameref) [ tt(-r) ] var(pname)[tt(=)var(rname)])( +Equivalent to tt(typeset -n )var(pname)tt(=)var(rname) + +However, tt(nameref) is a builtin command rather than a reserved word, +so when var(rname) uses subscript syntax it must be quoted against +globbing. Subscripts in referenced parameters are not supported in +ksh93, so this is not a significant compatibility issue. +) +enditem() + +subsect(Ksh Parameters) +cindex(parameters, ksh) +Parameters supplied by this module that are marked with `' below are +available only in ksh emulation. + +startitem() +vindex(.sh.command) +item(tt(.sh.command))( +A named reference to `tt(ZSH_DEBUG_CMD)' +) +vindex(.sh.edchar) +item(tt(.sh.edchar) )( +In a ZLE widget, equivalent to the `tt(KEYS)' special parameter. In a +future release, assignments to this parameter will affect the input +stream seen by ZLE. +) +vindex(.sh.edcol) +item(tt(.sh.edcol))( +A named reference to the ZLE special parameter `tt(CURSOR)'. +) +vindex(.sh.edmode) +item(tt(.sh.edmode) )( +In a ZLE widget, this parameter has the value tt(ESC) (tt($'\e')) if the +`tt(main)' keymap is selected, and the empty string otherwise. This is +intended for use with vi-mode key bindings (`tt(bindkey -v)'). In a +future revision, assigning `tt(.sh.edchar=${.sh.edmode})' is expected +to initiate `tt(vicmd)' mode when `tt(viins)' is active, and do +nothing when `tt(vicmd)' is already active. +) +vindex(.sh.edtext) +item(tt(.sh.edtext))( +A named reference to the `tt(BUFFER)' special ZLE parameter. +) +vindex(.sh.file) +item(tt(.sh.file))( +A named reference to the `tt(ZSH_SCRIPT)' parameter. +) +vindex(.sh.fun) +item(tt(.sh.fun) )( +In a shell function, the function's name. Usually the same as `tt($0)', +but not affected by tt(POSIX_ARGZERO) or tt(FUNCTION_ARGZERO) options. +) +vindex(.sh.level) +item(tt(.sh.level) )( +In a shell function, initially set to the depth of the call stack. This +may be assigned to change the apparent depth. However, such assignment +does not affect the scope of local parameters. +) +vindex(.sh.lineno) +item(tt(.sh.lineno))( +A named reference to the `tt(LINENO)' special parameter. +) +vindex(.sh.match) +item(tt(.sh.match))( +An array equivalent to the `tt(match)' parameter as assigned by the +`tt(LPAR()#b)tt(RPAR())' extended globbing flag. When the +tt(KSH_ARRAYS) option is set, `tt(${.sh.match[0]})' gives the value of +the `tt(MATCH)' parameter as set by the `tt(LPAR()#m)tt(RPAR())' extended +globbing flag. Currently, the tt(EXTENDED_GLOB) option must be enabled +and those flags must be explicitly used in a pattern in order for these +values of `tt(.sh.match)' to be set. +) +vindex(.sh.name) +item(tt(.sh.name) )( +When the `tt(vared)' command is used, this parameter may be used in +user-defined ZLE widgets to get the name of the variable being edited. +A future release is expected to use this parameter in the implementation +of "discipline functions". +) +vindex(.sh.subscript) +item(tt(.sh.subscript) )( +When `tt(vared)' has been used on an array element, this parameter holds +the array index (either a number, or an associative array key). +) +vindex(.sh.subshell) +item(tt(.sh.subshell))( +A named reference to the `tt(ZSH_SUBSHELL)' parameter. +) +vindex(.sh.value) +item(tt(.sh.value) )( +In `tt(vared)' this is a named reference to the ZLE special `tt(BUFFER)'. +A future release is expected to use this parameter in the implementation +of "discipline functions". +) +vindex(.sh.version) +item(tt(.sh.version))( +A named reference to `tt(ZSH_PATCHLEVEL)'. +) +enditem() + +subsect(Future Compatibility) + +The following features of ksh93 are not currently supported but may be +available in a future release. + +startitem() +item(var(pathdir)tt(/.paths))( +Each directory var(pathdir) in the tt(PATH) parameter may contain a +text file `tt(.paths)' which may define additional directories to +be searched for function definitions (tt(FPATH)), external executables +(tt(PATH)), and plugin files (similar to tt(MODULE_PATH)). + +em(THIS FEATURE IS UNLIKELY EVER TO BE IMPLEMENTED.) +) +item(tt(builtin -f )var(file))( +Installs a new shell builtin command dynamically linked from var(file), +where var(file) is found by a path search and the base name of the file +is the name of the builtin to be added. + +Similar to `tt(zmodload -F zsh/)var(file)tt( +b:)var(file)'. + +em(THIS FEATURE IS UNLIKELY EVER TO BE IMPLEMENTED.) +) +item(tt(namespace )var(ident)tt( { )var(list)tt( }))( +This reserved word executes the current shell compound command +tt({ )var(list)tt( }), with the special behavior that all functions +and parameters `var(name)' declared within var(list) are implicitly +prefixed to become `tt(.)var(ident)tt(.)var(name)', and similarly any +reference to a function or parameter `var(name)' is searched for as +`tt(.)var(ident)tt(.)var(name)' before falling back to `var(name)'. + +em(THIS FEATURE IS NOT YET IMPLEMENTED.) +) +item(tt(.sh.math) )( +This parameter is more accurately considered a namespace. A function +defintion of the form +ifzman() +indent(tt(function .sh.math.)var(name)tt( )var(ident)tt( ... { )var(list)tt( })) + +is equivalent to the native zsh definition +ifzman() +example(tt(function )var(name)tt( {) +tt( local )var(ident)tt(=$1 ...) +tt( )var(list) +tt(}) +tt(functions -M )var(name)tt( 1 3)) +ifzman() +Up to 3 var(ident) arguments, interpreted as floating point numbers, +may be provided for a function defined with tt(.sh.math) in this way. +The names (but not definitions) of all such functions are available +via tt(${.sh.math[@]}). + +em(THIS FEATURE IS NOT YET IMPLEMENTED.) +) +item(tt(var(name)tt(.get)))( +A shell function having this name, if defined, is invoked whenever the +parameter tt(${)var(name)tt(}) is referenced, including by commands +such as `tt(typeset -p)'. If the special variable `tt(.sh.value)' is +assigned by the function, that value is substituted instead of the +true value of var(name). This does not change the value of var(name), +but there is no way to access the actual value without first removing +the function. + +Additionally, an explicit reference to tt(${)var(name)tt(.get}) +calls the function var(name)tt(.get) even if there is no parameter +`var(name)' and substitutes the standard output of the function. + +em(THIS FEATURE IS NOT YET IMPLEMENTED.) +) +xitem(tt(var(name)tt(.set))) +item(tt(var(name)tt(.append)))( +Shell functions having these names are invoked when the parameter +var(name) is assigned or (for array types) has a new field appended. +The function may change the result of the operation by assigning to +the `tt(.sh.value)' special parameter, or block the change by +unsetting `tt(.sh.value)'. + +Explicit reference to tt(${)var(name)tt(.set}) or tt(${)var(name)tt(.append}) +substitutes the standard output of the function. + +em(THIS FEATURE IS NOT YET IMPLEMENTED.) +) +item(tt(var(name)tt(.unset)))( +When a function of this name is defined, it is called whenever the +parameter var(name) would be unset. The function must explicitly +`tt(unset )var(name)', otherwise the variable remains set. + +em(THIS FEATURE IS NOT YET IMPLEMENTED.) +) +item(tt(${ )var(list)tt(;}))( +Note the space after the opening brace (tt({)). This executes var(list) +in the current shell and substitutes its standard output in the manner +of `tt($LPAR())var(list)tt(RPAR())' but without forking. + +em(THIS FEATURE IS NOT YET IMPLEMENTED.) +) +enditem() diff --git a/Src/Modules/ksh93.c b/Src/Modules/ksh93.c new file mode 100644 index 000000000..9dc75c93c --- /dev/null +++ b/Src/Modules/ksh93.c @@ -0,0 +1,265 @@ +/* + * ksh93.c - support for more ksh93 features + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 2022 Barton E. Schaefer + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Barton E. Schaefer or the Zsh Development + * Group be liable to any party for direct, indirect, special, incidental, or + * consequential damages arising out of the use of this software and its + * documentation, even if Barton E. Schaefer and the Zsh + * Development Group have been advised of the possibility of such damage. + * + * Barton E. Schaefer and the Zsh Development Group + * specifically disclaim any warranties, including, but not limited to, the + * implied warranties of merchantability and fitness for a particular purpose. + * The software provided hereunder is on an "as is" basis, and + * Barton E. Schaefer and the Zsh Development Group have no + * obligation to provide maintenance, support, updates, enhancements, or + * modifications. + * + */ + +#include "ksh93.mdh" +#include "ksh93.pro" + +/* Implementing "namespace" requires creating a new keword. Hrm. */ + +/* + * Standard module configuration/linkage + */ + +static struct builtin bintab[] = { + BUILTIN("nameref", BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "gr", "n") +}; + +#include "zsh.mdh" + +static void +edcharsetfn(Param pm, char *x) +{ + /* + * To make this work like ksh, we must intercept $KEYS before the widget + * is looked up, so that changing the key sequence causes a different + * widget to be substituted. Somewhat similar to "bindkey -s". + * + * Ksh93 adds SIGKEYBD to the trap list for this purpose. + */ + ; +} + +static char ** +matchgetfn(Param pm) +{ + char **zsh_match = getaparam("match"); + + /* For this to work accurately, ksh emulation should always imply + * that the (#m) and (#b) extendedglob operators are enabled. + * + * When we have a 0th element (ksharrays), it is $MATCH. Elements + * 1st and larger mirror the $match array. + */ + + if (pm->u.arr) + freearray(pm->u.arr); + if (zsh_match && *zsh_match) { + if (isset(KSHARRAYS)) { + char **ap = + (char **) zalloc(sizeof(char *) * (arrlen(zsh_match)+1)); + pm->u.arr = ap; + *ap++ = ztrdup(getsparam("MATCH")); + while (*zsh_match) + *ap = ztrdup(*zsh_match++); + } else + pm->u.arr = zarrdup(zsh_match); + } else if (isset(KSHARRAYS)) { + pm->u.arr = mkarray(ztrdup(getsparam("MATCH"))); + } else + pm->u.arr = NULL; + + return arrgetfn(pm); +} + +static const struct gsu_scalar constant_gsu = + { strgetfn, NULL, nullunsetfn }; + +static const struct gsu_scalar sh_edchar_gsu = + { strvargetfn, edcharsetfn, nullunsetfn }; +static const struct gsu_scalar sh_edmode_gsu = + { strgetfn, nullstrsetfn, nullunsetfn }; +static const struct gsu_array sh_match_gsu = + { matchgetfn, arrsetfn, stdunsetfn }; +static const struct gsu_scalar sh_name_gsu = + { strvargetfn, nullstrsetfn, nullunsetfn }; +static const struct gsu_scalar sh_subscript_gsu = + { strvargetfn, nullstrsetfn, nullunsetfn }; + +static char *sh_name; +static char *sh_subscript; +static char *sh_edchar; +static char sh_edmode[2]; + +/* + * Some parameters listed here do not appear in ksh93.mdd autofeatures + * because they are only instantiated by ksh93_wrapper() below. This + * obviously includes those commented out here. + */ +static struct paramdef partab[] = { + PARAMDEF(".sh.command", PM_NAMEREF|PM_READONLY, "ZSH_DEBUG_CMD", &constant_gsu), + PARAMDEF(".sh.edchar", PM_SCALAR|PM_SPECIAL, &sh_edchar, &sh_edchar_gsu), + PARAMDEF(".sh.edcol", PM_NAMEREF|PM_READONLY, "CURSOR", &constant_gsu), + PARAMDEF(".sh.edmode", PM_SCALAR|PM_READONLY|PM_SPECIAL, &sh_edmode, &sh_edmode_gsu), + PARAMDEF(".sh.edtext", PM_NAMEREF|PM_READONLY, "BUFFER", &constant_gsu), + PARAMDEF(".sh.file", PM_NAMEREF|PM_READONLY, "ZSH_SCRIPT", &constant_gsu), + /* PARAMDEF(".sh.fun", PM_SCALAR|PM_UNSET, NULL, &constant_gsu), */ + /* PARAMDEF(".sh.level", PM_INTEGER|PM_UNSET, NULL, &constant_gsu), */ + PARAMDEF(".sh.lineno", PM_NAMEREF|PM_READONLY, "LINENO", &constant_gsu), + PARAMDEF(".sh.match", PM_ARRAY|PM_READONLY, NULL, &sh_match_gsu), + PARAMDEF(".sh.name", PM_SCALAR|PM_READONLY|PM_SPECIAL, &sh_name, &sh_name_gsu), + PARAMDEF(".sh.subscript", PM_SCALAR|PM_READONLY|PM_SPECIAL, &sh_subscript, &sh_subscript_gsu), + PARAMDEF(".sh.subshell", PM_NAMEREF|PM_READONLY, "ZSH_SUBSHELL", &constant_gsu), + /* SPECIALPMDEF(".sh.value", 0, NULL, NULL, NULL), */ + PARAMDEF(".sh.version", PM_NAMEREF|PM_READONLY, "ZSH_PATCHLEVEL", &constant_gsu) +}; + +static struct features module_features = { + bintab, sizeof(bintab)/sizeof(*bintab), + NULL, 0, + NULL, 0, + partab, sizeof(partab)/sizeof(*partab), + 0 +}; + +/**/ +static int +ksh93_wrapper(Eprog prog, FuncWrap w, char *name) +{ + Funcstack f; + Param pm; + zlong num = funcstack->prev ? getiparam(".sh.level") : 0; + + if (!EMULATION(EMULATE_KSH)) + return 1; + + if (num == 0) + for (f = funcstack; f; f = f->prev, num++); + else + num++; + + queue_signals(); + ++locallevel; /* Make these local */ + if ((pm = createparam(".sh.level", PM_LOCAL|PM_UNSET))) { + pm->level = locallevel; /* Why is this necessary? */ + setiparam(".sh.level", num); + } + if ((pm = createparam(".sh.fun", PM_LOCAL|PM_UNSET))) { + pm->level = locallevel; + setsparam(".sh.fun", ztrdup(name)); + pm->node.flags |= PM_READONLY; + } + if (zleactive) { + extern mod_import_variable char *curkeymapname; /* XXX */ + extern mod_import_variable char *varedarg; /* XXX */ + /* How to distinguish emacs bindings? */ + if (curkeymapname && strcmp(curkeymapname, "main") == 0) + strcpy(sh_edmode, "\e"); + else + strcpy(sh_edmode, ""); + if (!sh_edchar) + sh_edchar = dupstring(getsparam("KEYS")); + if (varedarg) { + char *ie = itype_end((sh_name = dupstring(varedarg)), INAMESPC, 0); + if (ie && *ie) { + *ie++ = '\0'; + /* Assume bin_vared has validated subscript */ + sh_subscript = dupstring(ie); + ie = sh_subscript + strlen(sh_subscript); + *--ie = '\0'; + } else + sh_subscript = NULL; + if ((pm = createparam(".sh.value", PM_LOCAL|PM_NAMEREF|PM_UNSET))) { + pm->level = locallevel; + setloopvar(".sh.value", "BUFFER"); /* Hack */ + } + } else + sh_name = sh_subscript = NULL; + } else { + sh_edchar = sh_name = sh_subscript = NULL; + strcpy(sh_edmode, ""); + /* TODO: + * - disciplines + * - special handling of .sh.value in math + */ + } + --locallevel; + unqueue_signals(); + + return 1; +} + +static struct funcwrap wrapper[] = { + WRAPDEF(ksh93_wrapper), +}; + +/**/ +int +setup_(UNUSED(Module m)) +{ + return 0; +} + +/**/ +int +features_(Module m, char ***features) +{ + *features = featuresarray(m, &module_features); + return 0; +} + +/**/ +int +enables_(Module m, int **enables) +{ + return handlefeatures(m, &module_features, enables); +} + +/**/ +int +boot_(Module m) +{ + return addwrapper(m, wrapper); +} + +/**/ +int +cleanup_(Module m) +{ + struct paramdef *p; + + deletewrapper(m, wrapper); + + /* Clean up namerefs, otherwise deleteparamdef() is confused */ + for (p = partab; p < partab + sizeof(partab)/sizeof(*partab); ++p) { + if (p->flags & PM_NAMEREF) { + HashNode hn = gethashnode2(paramtab, p->name); + if (hn) + ((Param)hn)->node.flags &= ~PM_NAMEREF; + } + } + return setfeatureenables(m, &module_features, NULL); +} + +/**/ +int +finish_(UNUSED(Module m)) +{ + return 0; +} diff --git a/Src/Modules/ksh93.mdd b/Src/Modules/ksh93.mdd new file mode 100644 index 000000000..2759884a0 --- /dev/null +++ b/Src/Modules/ksh93.mdd @@ -0,0 +1,8 @@ +name=zsh/ksh93 +link=either +load=yes + +autofeatures="b:nameref" +autofeatures_emu="b:nameref p:.sh.command p:.sh.edcol p:.sh.edtext p:.sh.file p:.sh.lineno p:.sh.match p:.sh.subshell p:.sh.version" + +objects="ksh93.o" diff --git a/Src/utils.c b/Src/utils.c index 1393ecb13..8ce9a175d 100644 --- a/Src/utils.c +++ b/Src/utils.c @@ -4319,7 +4319,7 @@ itype_end(const char *ptr, int itype, int once) { if (itype == INAMESPC) { itype = IIDENT; - if (once == 0 && !isset(POSIXIDENTIFIERS)) { + if (once == 0 && (!isset(POSIXIDENTIFIERS) || EMULATION(EMULATE_KSH))) { /* Special case for names containing ".", ksh93 namespaces */ char *t = itype_end(ptr + (*ptr == '.'), itype, 0); if (t > ptr+1) { -- cgit v1.2.3 From b17431e6dd4977a166b8d577fee03ccd68822957 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Mon, 6 Mar 2023 20:04:31 -0800 Subject: 51511: Documentation for namespaces --- ChangeLog | 2 ++ Doc/Zsh/expn.yo | 12 ++++++++++++ Doc/Zsh/params.yo | 17 ++++++++++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 96adb9c57..4fe025adf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,7 @@ 2023-03-06 Bart Schaefer + * 51511: Doc/Zsh/expn.yo, Doc/Zsh/params.yo: Document namespaces + * 51510: Src/builtin.c, Src/params.c, Src/utils.c, Src/zsh.h, Test/K02parameter.ztst: parameters with a leading namespace are skipped in output of "set" and "typeset", add tests for ksh-like diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index ef01794e6..19f5909ea 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -604,6 +604,16 @@ and other operators, such as `tt(${PREFIX:-"/usr/local"})'. Parameter expansions can also be nested. These topics will be introduced below. The full rules are complicated and are noted at the end. +cindex(namespace) +Parameter expansions may optionally include a em(namespace) prefix in +the format `tt(.)var(identifier)tt(.)' This currently has no special +meaning to the shell, but provides a convenient means of grouping +related parameters. Expansions using a namespace em(must) include +braces (tt({) and tt(})) as shown in the descriptions below, and +only one namespace prefix is allowed. Note that, for support of +possible future features, the first `tt(.)' is optional, but omitting +it is discouraged. + In the expansions discussed below that require a pattern, the form of the pattern is the same as that used for filename generation; see noderef(Filename Generation). Note that these patterns, along with @@ -616,6 +626,8 @@ substitution on the expansion of parameter tt($i). In the following descriptions, `var(word)' refers to a single word substituted on the command line, not necessarily a space delimited word. +The reference to `var(name)' in each description presumes any optional +namespace prefix. startitem() item(tt(${)var(name)tt(}))( diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index 946c00793..528c27f93 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -52,6 +52,20 @@ cindex(assignment) ifzman() indent(var(name)tt(=)var(value)) +cindex(namespace) +The var(name) in an assignment may optionally include a em(namespace) +prefix: +ifzman() +indent(tt(.)var(namespace)tt(.)var(parameter)tt(=)var(value)) + +Namespaces have no special meaning to the shell except that parameters +with a `tt(.)' prefix are not listed by the `tt(set)' builtin, nor +shown by the `tt(typeset)' builtin unless explicitly named or the +`tt(-m)' option is used. They provide a convenient way to group +related variables. Note that, for support of possible future features, +the first `tt(.)' is optional, but omitting it is discouraged. Unlike +ksh, a namespace need not be declared before it is referenced. + In scalar assignment, var(value) is expanded as a single string, in which the elements of arrays are joined together; filename expansion is not performed unless the option tt(GLOB_ASSIGN) is set. @@ -70,7 +84,8 @@ change its type to integer or float, and with tt(GLOB_ASSIGN) assigning a pattern to a variable may change its type to an array. To reference the value of a parameter, write `tt($)var(name)' or -`tt(${)var(name)tt(})'. See +`tt(${)var(name)tt(})'. The latter form is required when var(name) +includes a namespace prefix. See ifzman(em(Parameter Expansion) in zmanref(zshexpn))\ ifnzman(noderef(Parameter Expansion)) for complete details. That section also explains the effect -- cgit v1.2.3 From 42640b26136cbc1e35df08210028a3c3f41161f9 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Mon, 6 Mar 2023 20:08:12 -0800 Subject: 51511: More discussion of unsupported ksh features --- ChangeLog | 2 ++ Doc/Zsh/mod_ksh93.yo | 71 +++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 56 insertions(+), 17 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 4fe025adf..91c0584b3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,7 @@ 2023-03-06 Bart Schaefer + * 51512: Doc/Zsh/mod_ksh93.yo: More about unsupported features + * 51511: Doc/Zsh/expn.yo, Doc/Zsh/params.yo: Document namespaces * 51510: Src/builtin.c, Src/params.c, Src/utils.c, Src/zsh.h, diff --git a/Doc/Zsh/mod_ksh93.yo b/Doc/Zsh/mod_ksh93.yo index 3ae644bd1..d58b979b9 100644 --- a/Doc/Zsh/mod_ksh93.yo +++ b/Doc/Zsh/mod_ksh93.yo @@ -118,23 +118,6 @@ The following features of ksh93 are not currently supported but may be available in a future release. startitem() -item(var(pathdir)tt(/.paths))( -Each directory var(pathdir) in the tt(PATH) parameter may contain a -text file `tt(.paths)' which may define additional directories to -be searched for function definitions (tt(FPATH)), external executables -(tt(PATH)), and plugin files (similar to tt(MODULE_PATH)). - -em(THIS FEATURE IS UNLIKELY EVER TO BE IMPLEMENTED.) -) -item(tt(builtin -f )var(file))( -Installs a new shell builtin command dynamically linked from var(file), -where var(file) is found by a path search and the base name of the file -is the name of the builtin to be added. - -Similar to `tt(zmodload -F zsh/)var(file)tt( +b:)var(file)'. - -em(THIS FEATURE IS UNLIKELY EVER TO BE IMPLEMENTED.) -) item(tt(namespace )var(ident)tt( { )var(list)tt( }))( This reserved word executes the current shell compound command tt({ )var(list)tt( }), with the special behavior that all functions @@ -208,4 +191,58 @@ of `tt($LPAR())var(list)tt(RPAR())' but without forking. em(THIS FEATURE IS NOT YET IMPLEMENTED.) ) +item(tt(typeset -C )var(name)[tt(=)var(values)tt( ...)])( +Creates a em(compound variable). + +em(THIS FEATURE IS NOT YET IMPLEMENTED, and the syntax of var(values) +is unlikely to be fully ksh compatible.) +) +enditem() + +subsect(Other Differences) + +The following features of ksh93 are not currently supported and there are +no plans to implement them. + +startitem() +item(var(pathdir)tt(/.paths))( +Each directory var(pathdir) in the tt(PATH) parameter may contain a +text file `tt(.paths)' which may define additional directories to +be searched for function definitions (tt(FPATH)), external executables +(tt(PATH)), and plugin files (similar to tt(MODULE_PATH)). + +em(THIS FEATURE IS UNLIKELY EVER TO BE IMPLEMENTED.) +) +item(tt(builtin -f )var(file))( +Installs a new shell builtin command dynamically linked from var(file), +where var(file) is found by a path search and the base name of the file +is the name of the builtin to be added. + +Similar to `tt(zmodload -F zsh/)var(file)tt( +b:)var(file)'. + +em(THIS FEATURE IS UNLIKELY EVER TO BE IMPLEMENTED.) +) +item(tt(typeset -H )var(name)[tt(=)var(value)])( +Used for em(host-name file mapping) on some operating systems. + +em(THIS FEATURE IS UNLIKELY EVER TO BE IMPLEMENTED.) +) +item(tt(typeset ){tt(PLUS()|-)}tt(M )var(mapname)tt( )var(name)[tt(=)var(value)])( +Declares a character translation var(mapname) for values assigned to var(name). +) +xitem(tt(typeset -S)) +xitem(tt(typeset -T ) [tt(-f)] [ var(tname)tt(=LPAR())var(values)tt(RPAR()) ]) +item(tt(typeset -h )var(str))( +Used to define em(typed variables) and properties of their em(sub-variables). +The tt(-T) and tt(-h) options conflict with existing zsh usage. +) +item(tt(typeset -X )var(name)[tt(=)var(value)])( +Declares a floating-point variable displayed in hexadecimal format. +) +item(tt(typeset -b )var(name)[tt(=)var(value)])( +Declares a parameter stored in em(base64) format. +) +item(tt(typeset -m )var(newname)tt(=)var(oldname))( +em(Moves) var(oldname) to var(newname). Conflicts with native zsh usage. +) enditem() -- cgit v1.2.3 From 25dceb1dea4ee1c23dcc02f3f7f5c0bf345c492c Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 11 Mar 2023 13:20:21 -0800 Subject: 51557: Clarify availability of ksh-mode parameters, improve vi-mode detection. --- ChangeLog | 5 +++++ Doc/Zsh/mod_ksh93.yo | 8 +++++--- Src/Modules/ksh93.c | 5 +++-- 3 files changed, 13 insertions(+), 5 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index f15072191..cc2890b2f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2023-03-11 Bart Schaefer + + * 51557: Doc/zsh/mod_ksh93.yo, Src/Modules/ksh93.c: Clarify + availability of ksh-mode parameters, improve vi-mode detection. + 2023-03-07 Bart Schaefer * 51534: Util/printdefines: update for recent changes diff --git a/Doc/Zsh/mod_ksh93.yo b/Doc/Zsh/mod_ksh93.yo index d58b979b9..99dab385f 100644 --- a/Doc/Zsh/mod_ksh93.yo +++ b/Doc/Zsh/mod_ksh93.yo @@ -24,8 +24,10 @@ enditem() subsect(Ksh Parameters) cindex(parameters, ksh) -Parameters supplied by this module that are marked with `' below are -available only in ksh emulation. +Parameters supplied by this module that are marked with `' below +are available only when ksh emulation is active before entry to the +shell function, that is, `tt(emulate ksh)' locally to a function does +not populate these parameters for that function. startitem() vindex(.sh.command) @@ -45,7 +47,7 @@ A named reference to the ZLE special parameter `tt(CURSOR)'. vindex(.sh.edmode) item(tt(.sh.edmode) )( In a ZLE widget, this parameter has the value tt(ESC) (tt($'\e')) if the -`tt(main)' keymap is selected, and the empty string otherwise. This is +tt(VI) option is set and the `tt(main)' keymap is selected. This is intended for use with vi-mode key bindings (`tt(bindkey -v)'). In a future revision, assigning `tt(.sh.edchar=${.sh.edmode})' is expected to initiate `tt(vicmd)' mode when `tt(viins)' is active, and do diff --git a/Src/Modules/ksh93.c b/Src/Modules/ksh93.c index 9dc75c93c..51999dd71 100644 --- a/Src/Modules/ksh93.c +++ b/Src/Modules/ksh93.c @@ -168,8 +168,9 @@ ksh93_wrapper(Eprog prog, FuncWrap w, char *name) if (zleactive) { extern mod_import_variable char *curkeymapname; /* XXX */ extern mod_import_variable char *varedarg; /* XXX */ - /* How to distinguish emacs bindings? */ - if (curkeymapname && strcmp(curkeymapname, "main") == 0) + /* bindkey -v forces VIMODE so this test is as good as any */ + if (curkeymapname && isset(VIMODE) && + strcmp(curkeymapname, "main") == 0) strcpy(sh_edmode, "\e"); else strcpy(sh_edmode, ""); -- cgit v1.2.3 From 29503debc778535011f085589d94448db25f8efa Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 11 Mar 2023 13:22:10 -0800 Subject: 51558: Clarify "for" with positional parameters and named reference. --- ChangeLog | 4 +++- Doc/Zsh/grammar.yo | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index cc2890b2f..210c4f9c3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,8 @@ 2023-03-11 Bart Schaefer - * 51557: Doc/zsh/mod_ksh93.yo, Src/Modules/ksh93.c: Clarify + * 51558: Doc/Zsh/grammar.yo: Clarify "for" loops + named refs. + + * 51557: Doc/Zsh/mod_ksh93.yo, Src/Modules/ksh93.c: Clarify availability of ksh-mode parameters, improve vi-mode detection. 2023-03-07 Bart Schaefer diff --git a/Doc/Zsh/grammar.yo b/Doc/Zsh/grammar.yo index 1b834f41a..915b93bc0 100644 --- a/Doc/Zsh/grammar.yo +++ b/Doc/Zsh/grammar.yo @@ -190,6 +190,9 @@ use the positional parameters instead of the var(word)s. If any var(name) has been declared as a named reference, the corresponding var(word) is treated as the name of a parameter and var(name) is made a reference to that. +Note that for the positional parameters, this treats the +value of each positional as parameter name rather than +creating a reference to the position. The var(term) consists of one or more newline or tt(;) which terminate the var(word)s, and are optional when the -- cgit v1.2.3 From 8a9aea907ac4844e2ee7348c4fa6417ae9873991 Mon Sep 17 00:00:00 2001 From: Jun-ichi Takimoto Date: Sun, 9 Apr 2023 20:44:58 +0900 Subject: 51631: initialize $_ by copying it from environment --- ChangeLog | 3 +++ Doc/Zsh/params.yo | 5 ++++- Src/init.c | 9 ++++++--- 3 files changed, 13 insertions(+), 4 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 1eebe2660..32fd0780d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2023-04-09 Jun-ichi Takimoto + * 51631: Doc/Zsh/params.yo, Src/init.c: initialize $_ by copying + it from environment + * 51632: Src/exec.c: unmetafy $_ when exporting it to child 2023-04-03 Jun-ichi Takimoto diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index 528c27f93..2db4210eb 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -779,7 +779,10 @@ last pipeline. ) vindex(_) item(tt(_) )( -The last argument of the previous command. +Initially, if tt(_) exists in the environment, then this parameter is set to +its value. This value may be the full pathname of the current zsh +executable or the script command file. +Later, this parameter is set to the last argument of the previous command. Also, this parameter is set in the environment of every command executed to the full pathname of the command. ) diff --git a/Src/init.c b/Src/init.c index 68621a0ad..7e98af44c 100644 --- a/Src/init.c +++ b/Src/init.c @@ -1084,9 +1084,12 @@ setupvals(char *cmd, char *runscript, char *zsh_name) ztrdup(DEFAULT_IFS_SH) : ztrdup(DEFAULT_IFS); wordchars = ztrdup(DEFAULT_WORDCHARS); postedit = ztrdup(""); - zunderscore = (char *) zalloc(underscorelen = 32); - underscoreused = 1; - *zunderscore = '\0'; + /* If _ is set in environment then initialize our $_ by copying it */ + zunderscore = getenv("_"); + zunderscore = zunderscore ? metafy(zunderscore, -1, META_DUP) : ztrdup(""); + underscoreused = strlen(zunderscore) + 1; + underscorelen = (underscoreused + 31) & ~31; + zunderscore = (char *)zrealloc(zunderscore, underscorelen); zoptarg = ztrdup(""); zoptind = 1; -- cgit v1.2.3 From e5f8cc99f524db8db7c7fbe5957609db63869d0e Mon Sep 17 00:00:00 2001 From: Jun-ichi Takimoto Date: Tue, 11 Apr 2023 21:43:15 +0900 Subject: 51639: new parameter ZSH_EXEPATH (full path of zsh executable) The full pathname is obatined by a reliable method on macOS and systems that support procfs. But on other systems (FreeBSD, OpenBSD, ...) it is guessed from argv[0], PWD and PATH. --- ChangeLog | 6 +++ Doc/Zsh/params.yo | 4 ++ Src/init.c | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++- configure.ac | 19 +++++++++ 4 files changed, 145 insertions(+), 1 deletion(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 32fd0780d..b2c847da5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2023-04-11 Jun-ichi Takimoto + + * 51639: Doc/Zsh/params.yo, Src/init.c, configure.ac: add new + parameter ZSH_EXEPATH that is set to the full pathname of the + executable file of the current zsh + 2023-04-09 Jun-ichi Takimoto * 51631: Doc/Zsh/params.yo, Src/init.c: initialize $_ by copying diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index 2db4210eb..57d10b8bd 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -1112,6 +1112,10 @@ item(tt(ZSH_EXECUTION_STRING))( If the shell was started with the option tt(-c), this contains the argument passed to the option. Otherwise it is not set. ) +vindex(ZSH_EXEPATH) +item(tt(ZSH_EXEPATH))( +Full pathname of the executable file of the current zsh process. +) vindex(ZSH_NAME) item(tt(ZSH_NAME))( Expands to the basename of the command used to invoke this instance diff --git a/Src/init.c b/Src/init.c index 7e98af44c..ffb017e22 100644 --- a/Src/init.c +++ b/Src/init.c @@ -246,6 +246,9 @@ loop(int toplevel, int justonce) static int restricted; +/* original argv[0]. This is already metafied */ +static char *argv0; + /**/ static void parseargs(char *zsh_name, char **argv, char **runscript, char **cmdptr, @@ -257,7 +260,7 @@ parseargs(char *zsh_name, char **argv, char **runscript, char **cmdptr, if (**argv == '-') flags |= PARSEARGS_LOGIN; - argzero = posixzero = *argv++; + argv0 = argzero = posixzero = *argv++; SHIN = 0; /* @@ -893,6 +896,106 @@ init_term(void) return 1; } +/* + * Get (or guess) the absolute pathname of the current zsh exeutable. + * Try OS-specific method, and if it fails, guess the absolute pathname + * from argv0, pwd, and PATH. 'name' and 'cwd' are unmetefied versions of + * argv0 and pwd. + * Returns a zalloc()ed string (not metafied), or NULL if failed. + */ +#ifdef __APPLE__ +#include +#endif + +/**/ +static char * +getmypath(const char *name, const char *cwd) +{ + char *buf; + int namelen; + + if (!name) + return NULL; + if (*name == '-') + ++name; + if ((namelen = strlen(name)) == 0) + return NULL; +#if defined(__APPLE__) + { + uint32_t n = PATH_MAX; + int ret; + buf = (char *)zalloc(PATH_MAX); + if ((ret = _NSGetExecutablePath(buf, &n)) < 0) { + /* try again with increased buffer size */ + buf = (char *)zrealloc(buf, n); + ret = _NSGetExecutablePath(buf, &n); + } + if (ret == 0 && strlen(buf) > 0) + return buf; + else + free(buf); + } +#elif defined(PROC_SELF_EXE) + { + ssize_t n; + buf = (char *)zalloc(PATH_MAX); + n = readlink(PROC_SELF_EXE, buf, PATH_MAX); + if (n > 0 && n < PATH_MAX) { + buf[n] = '\0'; + return buf; + } + else + free(buf); + } +#endif + /* guess the absolute pathname of 'name' */ + if (name[namelen-1] == '/') /* name should not end with '/' */ + return NULL; + else if (name[0] == '/') { + /* name is already an absolute pathname */ + return ztrdup(name); + } + else if (strchr(name, '/')) { + /* relative path */ + if (!cwd) + return NULL; + buf = (char *)zalloc(strlen(cwd) + namelen + 2); + sprintf(buf, "%s/%s", cwd, name); + return buf; + } +#ifdef HAVE_REALPATH + else { + /* search each dir in PARH */ + const char *path, *sep; + char *real, *try; + int pathlen, dirlen; + + path = getenv("PATH"); + if (!path || (pathlen = strlen(path)) == 0) + return NULL; + /* for simplicity, allocate buf even if REALPATH_ACCEPTS_NULL is on */ + buf = (char *)zalloc(PATH_MAX); + try = (char *)zalloc(pathlen + namelen + 2); + do { + sep = strchr(path, ':'); + dirlen = sep ? sep - path : strlen(path); + strncpy(try, path, dirlen); + try[dirlen] = '/'; + try[dirlen+1] = '\0'; + strcat(try, name); + real = realpath(try, buf); + if (sep) + path = sep + 1; + } while (!real && sep); + free(try); + if (!real) + free(buf); + return real; /* this may be NULL */ + } +#endif + return NULL; +} + /* Initialize lots of global variables and hash tables */ /**/ @@ -1195,6 +1298,18 @@ setupvals(char *cmd, char *runscript, char *zsh_name) /* Colour sequences for outputting colours in prompts and zle */ set_default_colour_sequences(); + /* ZSH_EXEPATH */ + { + char *mypath, *exename, *cwd; + exename = unmetafy(ztrdup(argv0), NULL); + cwd = pwd ? unmetafy(ztrdup(pwd), NULL) : NULL; + mypath = getmypath(exename, cwd); + free(exename); + free(cwd); + if (mypath) { + setsparam("ZSH_EXEPATH", metafy(mypath, -1, META_REALLOC)); + } + } if (cmd) setsparam("ZSH_EXECUTION_STRING", ztrdup(cmd)); if (runscript) diff --git a/configure.ac b/configure.ac index e6ced85d9..d8a17791a 100644 --- a/configure.ac +++ b/configure.ac @@ -2011,6 +2011,25 @@ if test x$zsh_cv_sys_path_dev_fd != xno; then AC_DEFINE_UNQUOTED(PATH_DEV_FD, "$zsh_cv_sys_path_dev_fd") fi +dnl ---------------------------------------------------- +dnl CHECK FOR SYMLINK TO THE CURRENT EXECUTABLE IN /proc +dnl ---------------------------------------------------- +dnl Linux: /proc/self/exe +dnl NetBSD: /proc/curproc/exe (or /proc/self/exe, but not /proc/curproc/file) +dnl DragonFly: /proc/curproc/file +dnl Solaris: /proc/self/path/a.out +AH_TEMPLATE([PROC_SELF_EXE], +[Define to the path of the symlink to the current executable file.]) +AC_CACHE_CHECK(for symlink to the current executable in /proc, +zsh_cv_proc_self_exe, +[for zsh_cv_proc_self_exe in /proc/self/exe /proc/curproc/exe \ + /proc/curproc/file /proc/self/path/a.out no; do + test -L $zsh_cv_proc_self_exe && break +done]) +if test x$zsh_cv_proc_self_exe != xno; then + AC_DEFINE_UNQUOTED(PROC_SELF_EXE, "$zsh_cv_proc_self_exe") +fi + dnl --------------------------------- dnl CHECK FOR RFS SUPERROOT DIRECTORY dnl --------------------------------- -- cgit v1.2.3 From f376f95c47202fb1c00f41577347e25ed5d37439 Mon Sep 17 00:00:00 2001 From: Jim Date: Thu, 11 May 2023 12:32:49 -0700 Subject: 51609: fix reference to select(2) --- ChangeLog | 3 +++ Doc/Zsh/mod_zselect.yo | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 36a934683..d91ca40b6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2023-05-11 Bart Schaefer + * Jim : 51609: Doc/Zsh/mod_zselect.yo: + fix reference to select(2) + * Shohei YOSHIDA: 51340: Completion/Unix/Command/_rake: update for version 13 diff --git a/Doc/Zsh/mod_zselect.yo b/Doc/Zsh/mod_zselect.yo index faf59c165..42287f116 100644 --- a/Doc/Zsh/mod_zselect.yo +++ b/Doc/Zsh/mod_zselect.yo @@ -13,7 +13,7 @@ blocks until a file descriptor is ready for reading or writing, or has an error condition, with an optional timeout. If this is not available on your system, the command prints an error message and returns status 2 (normal errors return status 1). For more information, see your system's -documentation for manref(select)(3). Note there is no connection with the +documentation for manref(select)(2). Note there is no connection with the shell builtin of the same name. Arguments and options may be intermingled in any order. Non-option -- cgit v1.2.3 From f3f371deb376478176866fd770fbcf9bc0d0609f Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Sat, 13 May 2023 00:56:48 +0200 Subject: 51728: assign pcre named capture groups to a hash --- ChangeLog | 3 +++ Doc/Zsh/mod_pcre.yo | 10 ++++++---- Src/Modules/pcre.c | 43 +++++++++++++++++++++++++++++++++---------- Test/V07pcre.ztst | 14 ++++++++++++++ 4 files changed, 56 insertions(+), 14 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 285b73b2c..2835a9405 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2023-05-13 Oliver Kiddle + * 51728: Doc/Zsh/mod_pcre.yo, Src/Modules/pcre.c, + Test/V07pcre.ztst: assign pcre named capture groups to a hash + * 51723: Src/Modules/pcre.c, Test/V07pcre.ztst, configure.ac: migrate pcre module to pcre2 diff --git a/Doc/Zsh/mod_pcre.yo b/Doc/Zsh/mod_pcre.yo index c2817f519..6d073985d 100644 --- a/Doc/Zsh/mod_pcre.yo +++ b/Doc/Zsh/mod_pcre.yo @@ -20,12 +20,12 @@ including those that indicate newline. ) findex(pcre_study) item(tt(pcre_study))( -Studies the previously-compiled PCRE which may result in faster -matching. +Requests JIT compilation for the previously-compiled PCRE which +may result in faster matching. ) findex(pcre_match) item(tt(pcre_match) [ tt(-v) var(var) ] [ tt(-a) var(arr) ] \ -[ tt(-n) var(offset) ] [ tt(-b) ] var(string))( +[ tt(-A) var(assoc) ] [ tt(-n) var(offset) ] [ tt(-b) ] var(string))( Returns successfully if tt(string) matches the previously-compiled PCRE. @@ -36,7 +36,9 @@ substrings, unless the tt(-a) option is given, in which case it will set the array var(arr). Similarly, the variable tt(MATCH) will be set to the entire matched portion of the string, unless the tt(-v) option is given, in which case the variable -var(var) will be set. +var(var) will be set. Furthermore, any named captures will +be stored in the associative array tt(.pcre.match) unless an +alternative is given with tt(-A). No variables are altered if there is no successful match. A tt(-n) option starts searching for a match from the byte var(offset) position in var(string). If the tt(-b) option is given, diff --git a/Src/Modules/pcre.c b/Src/Modules/pcre.c index 079ecc2c5..6be1f76e2 100644 --- a/Src/Modules/pcre.c +++ b/Src/Modules/pcre.c @@ -129,14 +129,17 @@ bin_pcre_study(char *nam, UNUSED(char **args), UNUSED(Options ops), UNUSED(int f } static int -zpcre_get_substrings(char *arg, pcre2_match_data *mdata, int captured_count, - char *matchvar, char *substravar, int want_offset_pair, - int matchedinarr, int want_begin_end) +zpcre_get_substrings(pcre2_code *pat, char *arg, pcre2_match_data *mdata, + int captured_count, char *matchvar, char *substravar, char *namedassoc, + int want_offset_pair, int matchedinarr, int want_begin_end) { PCRE2_SIZE *ovec; char *match_all, **matches; char offset_all[50]; int capture_start = 1; + int vec_off; + PCRE2_SPTR ntable; /* table of named captures */ + uint32_t ncount, nsize; if (matchedinarr) { /* bash-style ovec[0] entire-matched string in the array */ @@ -174,7 +177,7 @@ zpcre_get_substrings(char *arg, pcre2_match_data *mdata, int captured_count, if (substravar && (!want_begin_end || nelem)) { char **x; - int vec_off, i; + int i; matches = x = (char **) zalloc(sizeof(char *) * (captured_count+1-capture_start)); for (i = capture_start; i < captured_count; i++) { vec_off = 2*i; @@ -184,6 +187,23 @@ zpcre_get_substrings(char *arg, pcre2_match_data *mdata, int captured_count, setaparam(substravar, matches); } + if (!pcre2_pattern_info(pat, PCRE2_INFO_NAMECOUNT, &ncount) && ncount + && !pcre2_pattern_info(pat, PCRE2_INFO_NAMEENTRYSIZE, &nsize) + && !pcre2_pattern_info(pat, PCRE2_INFO_NAMETABLE, &ntable)) + { + char **hash, **hashptr; + uint32_t nidx; + hashptr = hash = (char **)zshcalloc((ncount+1)*2*sizeof(char *)); + for (nidx = 0; nidx < ncount; nidx++) { + vec_off = (ntable[nsize * nidx] << 9) + 2 * ntable[nsize * nidx + 1]; + /* would metafy the key but pcre limits characters in the name */ + *hashptr++ = ztrdup((char *) ntable + nsize * nidx + 2); + *hashptr++ = metafy(arg + ovec[vec_off], + ovec[vec_off+1]-ovec[vec_off], META_DUP); + } + sethparam(namedassoc, hash); + } + if (want_begin_end) { /* * cond-infix rather than builtin; also not bash; so we set a bunch @@ -286,6 +306,7 @@ bin_pcre_match(char *nam, char **args, Options ops, UNUSED(int func)) char *matched_portion = NULL; char *plaintext = NULL; char *receptacle = NULL; + char *named = ".pcre.match"; int return_value = 1; /* The subject length and offset start are both int values in pcre_exec */ int subject_len; @@ -305,6 +326,9 @@ bin_pcre_match(char *nam, char **args, Options ops, UNUSED(int func)) if(OPT_HASARG(ops,c='v')) { matched_portion = OPT_ARG(ops,c); } + if (OPT_HASARG(ops, c='A')) { + named = OPT_ARG(ops, c); + } if(OPT_HASARG(ops,c='n')) { /* The offset position to start the search, in bytes. */ if ((offset_start = getposint(OPT_ARG(ops,c), nam)) < 0) return 1; @@ -326,8 +350,8 @@ bin_pcre_match(char *nam, char **args, Options ops, UNUSED(int func)) if (ret==0) return_value = 0; else if (ret == PCRE2_ERROR_NOMATCH) /* no match */; else if (ret>0) { - zpcre_get_substrings(plaintext, pcre_mdata, ret, matched_portion, receptacle, - want_offset_pair, 0, 0); + zpcre_get_substrings(pcre_pattern, plaintext, pcre_mdata, ret, matched_portion, + receptacle, named, want_offset_pair, 0, 0); return_value = 0; } else { @@ -405,9 +429,8 @@ cond_pcre_match(char **a, int id) break; } else if (r>0) { - zpcre_get_substrings(lhstr_plain, pcre_mdata, r, svar, avar, 0, - isset(BASHREMATCH), - !isset(BASHREMATCH)); + zpcre_get_substrings(pcre_pat, lhstr_plain, pcre_mdata, r, svar, avar, + ".pcre.match", 0, isset(BASHREMATCH), !isset(BASHREMATCH)); return_value = 1; break; } @@ -443,7 +466,7 @@ static struct conddef cotab[] = { static struct builtin bintab[] = { BUILTIN("pcre_compile", 0, bin_pcre_compile, 1, 1, 0, "aimxs", NULL), - BUILTIN("pcre_match", 0, bin_pcre_match, 1, 1, 0, "a:v:n:b", NULL), + BUILTIN("pcre_match", 0, bin_pcre_match, 1, 1, 0, "A:a:v:n:b", NULL), BUILTIN("pcre_study", 0, bin_pcre_study, 0, 0, 0, NULL, NULL) }; diff --git a/Test/V07pcre.ztst b/Test/V07pcre.ztst index 6eb366964..027fea3aa 100644 --- a/Test/V07pcre.ztst +++ b/Test/V07pcre.ztst @@ -182,3 +182,17 @@ [[ abc =~ 'a(d*)bc' ]] && print "$#MATCH; $#match; ${#match[1]}" 0:empty capture >3; 1; 0 + + [[ category/name-12345 =~ '(?x)^ + (? [^/]* ) / + (? + (? \w+ ) - + (? \d+ ))$' ]] + typeset -p1 .pcre.match +0:named captures +>typeset -g -A .pcre.match=( +> [category]=category +> [name]=name +> [package]=name-12345 +> [version]=12345 +>) -- cgit v1.2.3 From b4d1c756f50909b4a13e5c8fe5f26f71e9d54f63 Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Sat, 13 May 2023 00:59:00 +0200 Subject: 51738: support pcre's alternative DFA matching algorithm --- ChangeLog | 3 +++ Doc/Zsh/mod_pcre.yo | 6 +++++- Src/Modules/pcre.c | 53 ++++++++++++++++++++++++++++++++++++----------------- Test/V07pcre.ztst | 5 +++++ 4 files changed, 49 insertions(+), 18 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 2835a9405..18bc4a698 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2023-05-13 Oliver Kiddle + * 51738: Doc/Zsh/mod_pcre.yo, Src/Modules/pcre.c, + Test/V07pcre.ztst: support pcre's DFA matching algorithm + * 51728: Doc/Zsh/mod_pcre.yo, Src/Modules/pcre.c, Test/V07pcre.ztst: assign pcre named capture groups to a hash diff --git a/Doc/Zsh/mod_pcre.yo b/Doc/Zsh/mod_pcre.yo index 6d073985d..da73ac85a 100644 --- a/Doc/Zsh/mod_pcre.yo +++ b/Doc/Zsh/mod_pcre.yo @@ -25,7 +25,7 @@ may result in faster matching. ) findex(pcre_match) item(tt(pcre_match) [ tt(-v) var(var) ] [ tt(-a) var(arr) ] \ -[ tt(-A) var(assoc) ] [ tt(-n) var(offset) ] [ tt(-b) ] var(string))( +[ tt(-A) var(assoc) ] [ tt(-n) var(offset) ] [ tt(-bd) ] var(string))( Returns successfully if tt(string) matches the previously-compiled PCRE. @@ -69,6 +69,10 @@ print -l $accum) ) enditem() +The option tt(-d) uses the alternative breadth-first DFA search algorithm of +pcre. This sets tt(match), or the array given with tt(-a), to all the matches +found from the same start point in the subject. + The tt(zsh/pcre) module makes available the following test condition: startitem() diff --git a/Src/Modules/pcre.c b/Src/Modules/pcre.c index 6be1f76e2..96f3c6e65 100644 --- a/Src/Modules/pcre.c +++ b/Src/Modules/pcre.c @@ -305,30 +305,29 @@ bin_pcre_match(char *nam, char **args, Options ops, UNUSED(int func)) pcre2_match_data *pcre_mdata = NULL; char *matched_portion = NULL; char *plaintext = NULL; - char *receptacle = NULL; - char *named = ".pcre.match"; + char *receptacle; + char *named = NULL; int return_value = 1; /* The subject length and offset start are both int values in pcre_exec */ int subject_len; int offset_start = 0; int want_offset_pair = 0; + int use_dfa = 0; if (pcre_pattern == NULL) { zwarnnam(nam, "no pattern has been compiled"); return 1; } - matched_portion = "MATCH"; - receptacle = "match"; - if(OPT_HASARG(ops,c='a')) { - receptacle = OPT_ARG(ops,c); - } - if(OPT_HASARG(ops,c='v')) { - matched_portion = OPT_ARG(ops,c); - } - if (OPT_HASARG(ops, c='A')) { - named = OPT_ARG(ops, c); + if (!(use_dfa = OPT_ISSET(ops, 'd'))) { + matched_portion = OPT_HASARG(ops, c='v') ? OPT_ARG(ops, c) : "MATCH"; + named = OPT_HASARG(ops, c='A') ? OPT_ARG(ops, c) : ".pcre.match"; + } else if (OPT_HASARG(ops, c='v') || OPT_HASARG(ops, c='A')) { + zwarnnam(nam, "-d cannot be combined with -%c", c); + return 1; } + receptacle = OPT_HASARG(ops, 'a') ? OPT_ARG(ops, 'a') : "match"; + if(OPT_HASARG(ops,c='n')) { /* The offset position to start the search, in bytes. */ if ((offset_start = getposint(OPT_ARG(ops,c), nam)) < 0) return 1; @@ -341,7 +340,25 @@ bin_pcre_match(char *nam, char **args, Options ops, UNUSED(int func)) if (offset_start > 0 && offset_start >= subject_len) ret = PCRE2_ERROR_NOMATCH; - else { + else if (use_dfa) { + PCRE2_SIZE old, wscount = 128, capcount = 128; + void *workspace = zhalloc(sizeof(int) * wscount); + pcre_mdata = pcre2_match_data_create(capcount, NULL); + do { + ret = pcre2_dfa_match(pcre_pattern, (PCRE2_SPTR) plaintext, subject_len, + offset_start, 0, pcre_mdata, NULL, (int *) workspace, wscount); + if (ret == PCRE2_ERROR_DFA_WSSIZE) { + old = wscount; + wscount += wscount / 2; + workspace = hrealloc(workspace, sizeof(int) * old, sizeof(int) * wscount); + } else if (ret == 0) { + capcount += capcount / 2; + pcre2_match_data_free(pcre_mdata); + pcre_mdata = pcre2_match_data_create(capcount, NULL); + } else + break; + } while(1); + } else { pcre_mdata = pcre2_match_data_create_from_pattern(pcre_pattern, NULL); ret = pcre2_match(pcre_pattern, (PCRE2_SPTR) plaintext, subject_len, offset_start, 0, pcre_mdata, NULL); @@ -350,12 +367,14 @@ bin_pcre_match(char *nam, char **args, Options ops, UNUSED(int func)) if (ret==0) return_value = 0; else if (ret == PCRE2_ERROR_NOMATCH) /* no match */; else if (ret>0) { - zpcre_get_substrings(pcre_pattern, plaintext, pcre_mdata, ret, matched_portion, - receptacle, named, want_offset_pair, 0, 0); + zpcre_get_substrings(pcre_pattern, plaintext, pcre_mdata, ret, + matched_portion, receptacle, named, want_offset_pair, use_dfa, 0); return_value = 0; } else { - zwarnnam(nam, "error in pcre2_match [%d]", ret); + PCRE2_UCHAR buffer[256]; + pcre2_get_error_message(ret, buffer, sizeof(buffer)); + zwarnnam(nam, "error in pcre matching for /%s/: %s", plaintext, buffer); } if (pcre_mdata) @@ -466,7 +485,7 @@ static struct conddef cotab[] = { static struct builtin bintab[] = { BUILTIN("pcre_compile", 0, bin_pcre_compile, 1, 1, 0, "aimxs", NULL), - BUILTIN("pcre_match", 0, bin_pcre_match, 1, 1, 0, "A:a:v:n:b", NULL), + BUILTIN("pcre_match", 0, bin_pcre_match, 1, 1, 0, "A:a:v:n:bd", NULL), BUILTIN("pcre_study", 0, bin_pcre_study, 0, 0, 0, NULL, NULL) }; diff --git a/Test/V07pcre.ztst b/Test/V07pcre.ztst index 027fea3aa..585698d05 100644 --- a/Test/V07pcre.ztst +++ b/Test/V07pcre.ztst @@ -196,3 +196,8 @@ > [package]=name-12345 > [version]=12345 >) + + pcre_compile 'cat(er(pillar)?)?' + pcre_match -d 'the caterpillar catchment' && print $match +0:pcre_match -d +>caterpillar cater cat -- cgit v1.2.3 From bb441f77a70bf7b5f13ab8f9113ba9b7a1593479 Mon Sep 17 00:00:00 2001 From: Marlon Richert Date: Thu, 18 May 2023 23:44:54 +0300 Subject: 51758: Make dynamic dir completion easier to implement --- ChangeLog | 6 ++++ Completion/Zsh/Context/_dynamic_directory_name | 30 +++++++++++++----- Doc/Zsh/expn.yo | 44 ++++++++++---------------- Test/Y01completion.ztst | 11 +++++++ 4 files changed, 56 insertions(+), 35 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 85fc1de96..e1b1c7da4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2023-05-21 Oliver Kiddle + + * Marlon Richert: 51758: Test/Y01completion.ztst, Doc/Zsh/expn.yo, + Completion/Zsh/Context/_dynamic_directory_name: Make dynamic dir + completion easier to implement + 2023-05-13 Peter Stephenson * 51722: Src/Modules/parameter.c: Add safety to extracting diff --git a/Completion/Zsh/Context/_dynamic_directory_name b/Completion/Zsh/Context/_dynamic_directory_name index f449c3b12..5e0d73a8d 100644 --- a/Completion/Zsh/Context/_dynamic_directory_name +++ b/Completion/Zsh/Context/_dynamic_directory_name @@ -1,15 +1,29 @@ #autoload +local -a dirfuncs=( + ${(k)functions[zsh_directory_name]} + $zsh_directory_name_functions +) +local descr='dynamically named directory' -local func -integer ret=1 +if (( $#dirfuncs )); then + local -a expl + local -i ret + local func suf tag=dynamically-named-directories -if [[ -n $functions[zsh_directory_name] || \ - ${+zsh_directory_name_functions} -ne 0 ]] ; then - [[ -n $functions[zsh_directory_name] ]] && zsh_directory_name c && ret=0 - for func in $zsh_directory_name_functions; do - $func c && ret=0 + [[ $ISUFFIX != \]* ]] && + suf=-S] + + _tags "$tag" + while _tags; do + while _next_label "$tag" expl "$descr" $suf; do + for func in $dirfuncs; do + $func c && ret=0 + done + done + (( ret )) || break done return ret + else - _message 'dynamic directory name: implemented as zsh_directory_name c' + _message "${descr}: implement as zsh_directory_name c" fi diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index 19f5909ea..6f86d0c54 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -2066,34 +2066,24 @@ tt(/home/pws/perforce). In this simple case a static name for the directory would be just as effective. example(zsh_directory_name+LPAR()RPAR() { - emulate -L zsh - setopt extendedglob + emulate -L zsh -o extendedglob local -a match mbegin mend - if [[ $1 = d ]]; then - # turn the directory into a name - if [[ $2 = (#b)(/home/pws/perforce/)([^/]##)* ]]; then - typeset -ga reply - reply=(p:$match[2] $(( ${#match[1]} + ${#match[2]} )) ) - else - return 1 - fi - elif [[ $1 = n ]]; then - # turn the name into a directory - [[ $2 != (#b)p:(?*) ]] && return 1 - typeset -ga reply - reply=(/home/pws/perforce/$match[1]) - elif [[ $1 = c ]]; then - # complete names - local expl - local -a dirs - dirs=(/home/pws/perforce/*(/:t)) - dirs=(p:${^dirs}) - _wanted dynamic-dirs expl 'dynamic directory' compadd -S\] -a dirs - return - else - return 1 - fi - return 0 + local base=/home/pws/perforce + case $1 in + ( d ) # Turn the directory into a name. + [[ $2 == (#b)($base/)([^/]##)* ]] && + reply=( p:$match[2] $(( $#match[1] + $#match[2] )) ) + ;; + ( n ) # Turn the name into a directory. + [[ $2 == (#b)p:(?*) ]] && + reply=( $base/$match[1] ) + ;; + ( c ) # Complete names. + local -a dirs=( $base/*(/:t) ) + # Completion system populates $expl with flags for compadd. + compadd "$expl[@]" p:$^dirs + ;; + esac }) texinode(Static named directories)(`=' expansion)(Dynamic named directories)(Filename Expansion) diff --git a/Test/Y01completion.ztst b/Test/Y01completion.ztst index f976f9f91..b3ec12e52 100644 --- a/Test/Y01completion.ztst +++ b/Test/Y01completion.ztst @@ -75,6 +75,17 @@ >line: {: ~user2}{} >line: {: ~user1}{} + comptesteval 'zsh_directory_name() { compadd "$expl[@]" -- name1 name2 }' + comptest $': ~[\t\t\t\t' +0:dynamic directory names after ~[ +>line: {: ~[name}{} +>line: {: ~[name}{} +>DESCRIPTION:{dynamically named directory} +>NO:{name1} +>NO:{name2} +>line: {: ~[name1]}{} +>line: {: ~[name2]}{} + comptest $'echo ;:\C-b\C-b\t' 0:directories and files before separator >line: {echo }{;:} -- cgit v1.2.3 From 1f64d0912720ba35f92020c3c1e128039cac5df8 Mon Sep 17 00:00:00 2001 From: Marlon Richert Date: Thu, 18 May 2023 23:53:27 +0300 Subject: 51760: r and R were listed in the wrong order. --- ChangeLog | 3 +++ Doc/Zsh/compwid.yo | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index e1b1c7da4..2d4c68038 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2023-05-21 Oliver Kiddle + * Marlon Richert: 51760: Doc/Zsh/compwid.yo: r and R were listed + in the wrong order. + * Marlon Richert: 51758: Test/Y01completion.ztst, Doc/Zsh/expn.yo, Completion/Zsh/Context/_dynamic_directory_name: Make dynamic dir completion easier to implement diff --git a/Doc/Zsh/compwid.yo b/Doc/Zsh/compwid.yo index d32a0702f..9461ace17 100644 --- a/Doc/Zsh/compwid.yo +++ b/Doc/Zsh/compwid.yo @@ -112,7 +112,7 @@ vindex(QIPREFIX) item(tt(QIPREFIX))( This parameter is read-only and contains the quoted string up to the word being completed. E.g. when completing `tt("foo)', this parameter -contains the double quote. If the tt(-q) option of tt(compset) is used +contains the double quote. If the tt(-q) option of tt(compset) is used (see below), and the original string was `tt("foo bar)' with the cursor on the `tt(bar)', this parameter contains `tt("foo )'. ) @@ -1082,8 +1082,8 @@ enditem() ) xitem(tt(l:)tt(|)var(word-pat)tt(=)var(match-pat)) xitem(tt(L:)tt(|)var(word-pat)tt(=)var(match-pat)) -xitem(tt(R:)var(word-pat)tt(|)tt(=)var(match-pat)) -item(tt(r:)var(word-pat)tt(|)tt(=)var(match-pat))( +xitem(tt(r:)var(word-pat)tt(|)tt(=)var(match-pat)) +item(tt(R:)var(word-pat)tt(|)tt(=)var(match-pat))( If there is a substring at the tt(l:)eft or tt(r:)ight edge of the current word that matches var(word-pat), then broaden the corresponding part of the match -- cgit v1.2.3 From 78102120b9c9e7485e7f537864fc2c24fbe0071a Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Tue, 6 Jun 2023 09:16:46 +0100 Subject: 51816: add :S history modifier with pattern match --- ChangeLog | 6 ++++++ Doc/Zsh/expn.yo | 18 ++++++++++++------ Src/hist.c | 17 ++++++++++++----- Src/subst.c | 12 +++++++++--- Test/D04parameter.ztst | 10 ++++++++++ 5 files changed, 49 insertions(+), 14 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 2c0f6287a..c760fccab 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2023-06-06 Peter Stephenson + + * 51816: Doc/Zsh/expn.yo, Src/hist.c, Src/subst.c, + Test/D04parameter.ztst: add :S history modifier with pattern + match. + 2023-06-06 Jun-ichi Takimoto * Marlon Richert: 51779: Test/Y01completion.ztst: update diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index 6f86d0c54..7bc736470 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -312,7 +312,8 @@ zero) that are neither `tt(.)' nor `tt(/)' and that continue to the end of the string. For example, the extension of `tt(foo.orig.c)' is `tt(.c)', and `tt(dir.c/foo)' has no extension. ) -item(tt(s/)var(l)tt(/)var(r)[tt(/)])( +xitem(tt(s/)var(l)tt(/)var(r)[tt(/)]) +item(tt(S/)var(l)tt(/)var(r)[tt(/)])( Substitute var(r) for var(l) as described below. The substitution is done only for the first string that matches var(l). For arrays and for filename @@ -324,13 +325,17 @@ perform global substitution, i.e. substitute every occurrence of var(r) for var(l). Note that the tt(g) or tt(:G) must appear in exactly the position shown. +The use of tt(S) instead of tt(s) is identical except that +the source is treated as a pattern, just as if the option +tt(HIST_SUBST_PATTERN) were set. + See further notes on this form of substitution below. ) item(tt(&))( -Repeat the previous tt(s) substitution. Like tt(s), may be preceded -immediately by a tt(g). In parameter expansion the tt(&) must appear -inside braces, and in filename generation it must be quoted with a -backslash. +Repeat the previous tt(s) or tt(S) substitution, whichever was most +recent. Like tt(s) and tt(S), may be preceded immediately by a tt(g). +In parameter expansion the tt(&) must appear inside braces, and in +filename generation it must be quoted with a backslash. ) item(tt(t) [ var(digits) ])( Remove all leading pathname components, leaving the final component (tail). @@ -377,7 +382,8 @@ substitutions or expansions are performed once at the time the qualifier is parsed, even before the `tt(:s)' expression itself is divided into var(l) and var(r) sides. -If the option tt(HIST_SUBST_PATTERN) is set, var(l) is treated as +If the option tt(HIST_SUBST_PATTERN) is set or the original substitution +was started with a capital tt(S), var(l) is treated as a pattern of the usual form described in ifzman(the section FILENAME GENERATION below)\ ifnzman(noderef(Filename Generation)). This can be used in diff --git a/Src/hist.c b/Src/hist.c index 7e6394406..b4dc53d90 100644 --- a/Src/hist.c +++ b/Src/hist.c @@ -163,6 +163,11 @@ char *hsubl; /**/ char *hsubr; +/* state of histsubstpattern at last substitution */ + +/**/ +int hsubpatopt; + /* pointer into the history line */ /**/ @@ -624,7 +629,7 @@ histsubchar(int c) return substfailed(); if (!hsubl) return -1; - if (subst(&sline, hsubl, hsubr, gbal)) + if (subst(&sline, hsubl, hsubr, gbal, 0)) return substfailed(); } else { /* Line doesn't begin ^foo^bar */ @@ -831,7 +836,7 @@ histsubchar(int c) if ((c = ingetc()) == 'g') { gbal = 1; c = ingetc(); - if (c != 's' && c != '&') { + if (c != 's' && c != 'S' && c != '&') { zerr("'s' or '&' modifier expected after 'g'"); return -1; } @@ -891,11 +896,13 @@ histsubchar(int c) } break; case 's': + case 'S': + hsubpatopt = (c == 'S'); if (getsubsargs(sline, &gbal, &cflag)) return -1; /* fall through */ case '&': if (hsubl && hsubr) { - if (subst(&sline, hsubl, hsubr, gbal)) + if (subst(&sline, hsubl, hsubr, gbal, hsubpatopt)) return substfailed(); } else { herrflush(); @@ -2315,7 +2322,7 @@ casemodify(char *str, int how) /**/ int -subst(char **strptr, char *in, char *out, int gbal) +subst(char **strptr, char *in, char *out, int gbal, int forcepat) { char *str = *strptr, *substcut, *sptr; int off, inlen, outlen; @@ -2323,7 +2330,7 @@ subst(char **strptr, char *in, char *out, int gbal) if (!*in) in = str, gbal = 0; - if (isset(HISTSUBSTPATTERN)) { + if (isset(HISTSUBSTPATTERN) || forcepat) { int fl = SUB_LONG|SUB_REST|SUB_RETFAIL; char *oldin = in; if (gbal) diff --git a/Src/subst.c b/Src/subst.c index 974d6171e..14947ae36 100644 --- a/Src/subst.c +++ b/Src/subst.c @@ -4351,6 +4351,8 @@ modify(char **str, char **ptr, int inbrace) break; case 's': + case 'S': + hsubpatopt = (**ptr == 'S'); c = **ptr; (*ptr)++; ptr1 = *ptr; @@ -4445,7 +4447,7 @@ modify(char **str, char **ptr, int inbrace) break; case '&': - c = 's'; + c = hsubpatopt ? 'S' : 's'; break; case 'g': @@ -4534,8 +4536,10 @@ modify(char **str, char **ptr, int inbrace) copy = casemodify(tt, CASMOD_UPPER); break; case 's': + case 'S': + hsubpatopt = (c == 'S'); if (hsubl && hsubr) - subst(©, hsubl, hsubr, gbal); + subst(©, hsubl, hsubr, gbal, hsubpatopt); break; case 'q': copy = quotestring(copy, QT_BACKSLASH_SHOWNULL); @@ -4620,8 +4624,10 @@ modify(char **str, char **ptr, int inbrace) *str = casemodify(*str, CASMOD_UPPER); break; case 's': + case 'S': + hsubpatopt = (c == 'S'); if (hsubl && hsubr) - subst(str, hsubl, hsubr, gbal); + subst(str, hsubl, hsubr, gbal, hsubpatopt); break; case 'q': *str = quotestring(*str, QT_BACKSLASH); diff --git a/Test/D04parameter.ztst b/Test/D04parameter.ztst index 7990c2958..2fd2f975f 100644 --- a/Test/D04parameter.ztst +++ b/Test/D04parameter.ztst @@ -2754,3 +2754,13 @@ F:behavior, see http://austingroupbugs.net/view.php?id=888 0:(users/28784 inspired this) substituting a single-quoted backslash, part #3: control >xfooy + spacestring="string with spaces" + print ${spacestring:gs/[[:space:]]/ /} + print ${spacestring:g&} + print ${spacestring:gS/[[:space:]]//} + print ${spacestring:g&} +0:Different behaviour of :s and :S modifiers +>string with spaces +>string with spaces +>stringwithspaces +>stringwithspaces -- cgit v1.2.3 From ecd3f9c9506c7720dc6c0833dc5d5eb00e4459c4 Mon Sep 17 00:00:00 2001 From: Jun-ichi Takimoto Date: Mon, 19 Jun 2023 11:19:25 +0900 Subject: 51862: support texinfo-7.0 --- ChangeLog | 4 ++++ Doc/Makefile.in | 3 ++- configure.ac | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index ca290ea2a..14349dcf2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2023-06-19 Jun-ichi Takimoto + + * 51862: Doc/Makefile.in, configure.ac: support texinfo-7.0 + 2023-06-08 Jun-ichi Takimoto * 51826: Src/hist.c: correctly handle metafied null character diff --git a/Doc/Makefile.in b/Doc/Makefile.in index 136b080d6..dabe11fe3 100644 --- a/Doc/Makefile.in +++ b/Doc/Makefile.in @@ -43,6 +43,7 @@ TEXI2DVI = @TEXI2DVI@ DVIPS = dvips TEXI2PDF = @TEXI2PDF@ TEXI2HTML = @TEXI2HTML@ +SET_TEXI2ANY_VAR = @SET_TEXI2ANY_VAR@ PAPERSIZE = @PAPERSIZE@ .SUFFIXES: .yo .1 @@ -266,7 +267,7 @@ texi2html.conf: $(sdir_top)/Config/version.mk d=`echo $(VERSION_DATE)`; \ v="Zsh version $(VERSION), released on $$d."; \ case '$(TEXI2HTML)' in \ - *texi2any*) echo "set_from_init_file('PRE_BODY_CLOSE','$$v');" ;; \ + *texi2any*) echo "$(SET_TEXI2ANY_VAR)('PRE_BODY_CLOSE','$$v');" ;; \ *) echo "\$$PRE_BODY_CLOSE = '$$v';" ;; \ esac > $@ diff --git a/configure.ac b/configure.ac index 4710d1659..ba76f9a60 100644 --- a/configure.ac +++ b/configure.ac @@ -623,7 +623,12 @@ fi if test x"$TEXI2HTML" = xtexi2any; then TEXI2HTML='texi2any -c TEXI2HTML=1' + case `texi2any --version 2>/dev/null | sed -e 's/^.*) *//' -e 1q` in + [[1-6]].*) SET_TEXI2ANY_VAR=set_from_init_file ;; + *) SET_TEXI2ANY_VAR=texinfo_set_from_init_file ;; + esac fi +AC_SUBST(SET_TEXI2ANY_VAR) case "$LC_PAPER" in ??_US*) PAPERSIZE=us ;; -- cgit v1.2.3 From 1b9bc3441ca0e6d155243084d6e7b98925dc02cb Mon Sep 17 00:00:00 2001 From: Jun-ichi Takimoto Date: Mon, 26 Jun 2023 16:52:40 +0900 Subject: 51884: reset IFS if it contains invalid characters This happens only if MULTIBYTE option is on. --- ChangeLog | 6 ++++++ Doc/Zsh/params.yo | 7 +++++-- Src/params.c | 3 +++ Src/utils.c | 42 ++++++++++++++++++++++++++---------------- Test/D04parameter.ztst | 21 +++++++++++++++++++++ 5 files changed, 61 insertions(+), 18 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 0011cc947..51a091aff 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2023-06-26 Jun-ichi Takimoto + + * 51884: Doc/Zsh/params.yo, Src/params.c, Src/utils.c, + Test/D04parameter.ztst: if MULTIBYTE option is on and IFS contains + invalid bytes in curret locale then reset it to default + 2023-06-22 Bart Schaefer * 51887: Src/math.c, Src/params.c, Test/K02parameter.ztst: diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index 57d10b8bd..e0410d673 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -1325,15 +1325,18 @@ Internal field separators (by default space, tab, newline and NUL), that are used to separate words which result from command or parameter expansion and words read by the tt(read) builtin. Any characters from the set space, tab and -newline that appear in the IFS are called em(IFS white space). +newline that appear in the tt(IFS) are called em(IFS white space). One or more IFS white space characters or one non-IFS white space character together with any adjacent IFS white space character delimit a field. If an IFS white space character appears twice consecutively -in the IFS, this character is treated as if it were not an IFS white +in the tt(IFS), this character is treated as if it were not an IFS white space character. If the parameter is unset, the default is used. Note this has a different effect from setting the parameter to an empty string. + +If tt(MULTIBYTE) option is on and tt(IFS) contains invalid characters in +the current locale, it is reset to the default. ) vindex(KEYBOARD_HACK) item(tt(KEYBOARD_HACK))( diff --git a/Src/params.c b/Src/params.c index 2b0837e03..f5750a4b4 100644 --- a/Src/params.c +++ b/Src/params.c @@ -4748,6 +4748,7 @@ setlang(char *x) if ((x = getsparam_u(ln->name)) && *x) setlocale(ln->category, x); unqueue_signals(); + inittyptab(); } /**/ @@ -4771,6 +4772,7 @@ lc_allsetfn(Param pm, char *x) else { setlocale(LC_ALL, unmeta(x)); clear_mbstate(); + inittyptab(); } } @@ -4809,6 +4811,7 @@ lcsetfn(Param pm, char *x) } unqueue_signals(); clear_mbstate(); /* LC_CTYPE may have changed */ + inittyptab(); } #endif /* USE_LOCALE */ diff --git a/Src/utils.c b/Src/utils.c index f13e3a79d..94a33453f 100644 --- a/Src/utils.c +++ b/Src/utils.c @@ -74,9 +74,6 @@ set_widearray(char *mb_array, Widechar_array wca) } wca->len = 0; - if (!isset(MULTIBYTE)) - return; - if (mb_array) { VARARR(wchar_t, tmpwcs, strlen(mb_array)); wchar_t *wcptr = tmpwcs; @@ -87,8 +84,7 @@ set_widearray(char *mb_array, Widechar_array wca) int mblen; if ((unsigned char) *mb_array <= 0x7f) { - mb_array++; - *wcptr++ = (wchar_t)*mb_array; + *wcptr++ = (wchar_t)*mb_array++; continue; } @@ -4121,8 +4117,9 @@ inittyptab(void) * having IIDENT here is a good idea at all, but this code * should disappear into history... */ - for (t0 = 0240; t0 != 0400; t0++) - typtab[t0] = IALPHA | IALNUM | IIDENT | IUSER | IWORD; + if isset(MULTIBYTE) + for (t0 = 0240; t0 != 0400; t0++) + typtab[t0] = IALPHA | IALNUM | IIDENT | IUSER | IWORD; #endif /* typtab['.'] |= IIDENT; */ /* Allow '.' in variable names - broken */ typtab['_'] = IIDENT | IUSER; @@ -4137,11 +4134,24 @@ inittyptab(void) typtab[t0] |= ITOK | IMETA; for (t0 = (int) (unsigned char) Snull; t0 <= (int) (unsigned char) Nularg; t0++) typtab[t0] |= ITOK | IMETA | INULL; - for (s = ifs ? ifs : EMULATION(EMULATE_KSH|EMULATE_SH) ? - DEFAULT_IFS_SH : DEFAULT_IFS; *s; s++) { + /* ifs */ +#define CURRENT_DEFAULT_IFS (EMULATION(EMULATE_KSH|EMULATE_SH) ? \ + DEFAULT_IFS_SH : DEFAULT_IFS) +#ifdef MULTIBYTE_SUPPORT + if (isset(MULTIBYTE)) { + set_widearray(ifs ? ifs : CURRENT_DEFAULT_IFS, &ifs_wide); + if (ifs && !ifs_wide.chars) { + zwarn("IFS has an invalid character; resetting IFS to default"); + zsfree(ifs); + ifs = ztrdup(CURRENT_DEFAULT_IFS); + set_widearray(ifs, &ifs_wide); + } + } +#endif + for (s = ifs ? ifs : CURRENT_DEFAULT_IFS; *s; s++) { int c = (unsigned char) (*s == Meta ? *++s ^ 32 : *s); #ifdef MULTIBYTE_SUPPORT - if (!isascii(c)) { + if (isset(MULTIBYTE) && !isascii(c)) { /* see comment for wordchars below */ continue; } @@ -4154,10 +4164,15 @@ inittyptab(void) } typtab[c] |= ISEP; } + /* wordchars */ +#ifdef MULTIBYTE_SUPPORT + if (isset(MULTIBYTE)) + set_widearray(wordchars, &wordchars_wide); +#endif for (s = wordchars ? wordchars : DEFAULT_WORDCHARS; *s; s++) { int c = (unsigned char) (*s == Meta ? *++s ^ 32 : *s); #ifdef MULTIBYTE_SUPPORT - if (!isascii(c)) { + if (isset(MULTIBYTE) && !isascii(c)) { /* * If we have support for multibyte characters, we don't * handle non-ASCII characters here; instead, we turn @@ -4170,11 +4185,6 @@ inittyptab(void) #endif typtab[c] |= IWORD; } -#ifdef MULTIBYTE_SUPPORT - set_widearray(wordchars, &wordchars_wide); - set_widearray(ifs ? ifs : EMULATION(EMULATE_KSH|EMULATE_SH) ? - DEFAULT_IFS_SH : DEFAULT_IFS, &ifs_wide); -#endif for (s = SPECCHARS; *s; s++) typtab[(unsigned char) *s] |= ISPECIAL; if (typtab_flags & ZTF_SP_COMMA) diff --git a/Test/D04parameter.ztst b/Test/D04parameter.ztst index 2fd2f975f..0d44558a7 100644 --- a/Test/D04parameter.ztst +++ b/Test/D04parameter.ztst @@ -2280,6 +2280,27 @@ F:We do not care what $OLDPWD is, as long as it does not cause an error F:As of this writing, var=$@ and var="$@" with null IFS have unspecified F:behavior, see http://austingroupbugs.net/view.php?id=888 + ( + IFS=$'\x80' + if [[ $IFS = $' \t\n\0' ]]; then + echo OK # if $'\x80' is illegal (e.g. Linux) + else # otherwise (e.g. macOS), it should work as a separator + s=$'foo\x80\bar' + [[ ${${=s}[1]} = foo ]] && echo OK + fi + ) +0D:reset IFS to default if it contains illegal character +>OK + + ( + unsetopt multibyte + IFS=$'\xc3\xa9' + s=$'foo\xc3bar\xa9boo' + echo ${${=s}[2]} + ) +0:eight bit chars in IFS should work if multibute option is off +>bar + () { setopt localoptions extendedglob [[ $- = [[:alnum:]]## ]] || print Failed 1 -- cgit v1.2.3 From d70e3780fc74eb6c7d8d7ea7701266725e180f23 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Wed, 26 Jul 2023 19:39:15 -0700 Subject: unposted (cf. 51899): document _shadow --- ChangeLog | 4 ++++ Doc/Zsh/compsys.yo | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 8ecc30f20..836d200dd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2023-07-26 Bart Schaefer + + * unposted (cf. 51899): Doc/Zsh/compsys.yo: document _shadow + 2023-07-20 Peter Stephenson * 51977: Src/jobs.c, Test/E01options.ztst: Combination of diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo index 584ede441..33baeab49 100644 --- a/Doc/Zsh/compsys.yo +++ b/Doc/Zsh/compsys.yo @@ -5227,6 +5227,50 @@ the group name. This function is called automatically from tt(_description) and hence is not normally called explicitly. ) +findex(_shadow) +findex(_unshadow) +xitem(tt(_shadow) [ tt(-s) var(suffix) ] var(command_name) ...) +item(tt(_unshadow) [ tt(-s) var(suffix) ] var(command_name) ...)( +The tt(_shadow) function creates a copy of each of the shell functions +in the var(command_name) arguments. The original functions can then +be replaced by new implementations. A later call to tt(_unshadow), +with the same var(command_name) list, removes the new implementations, +if any, and restores the originals. + +Recommended usage is to pair tt(_shadow) and tt(_unshadow) calls by +use of an `tt(always)' block: +example({ + _shadow fname + function fname { + # Do your new thing + } + # Invoke callers of fname +} always { + _unshadow fname +}) + +Any var(command_name) may instead be a builtin, but in that case no +copy is created. The expectation is that an initial tt(_shadow) is +followed by creating a wrapper function, and therafter any nested or +recursive calls thus copy and replace the wrapper function. +example({ + _shadow compadd + compadd LPAR()RPAR() { builtin compadd -O tmparr "$@" } +} always { + _unshadow compadd +}) + +The var(suffix), if supplied, is prepended by an `tt(@)' character and +then appended to each var(command_name) to create the copy. Thus +example(_shadow -s XX foo) +creates a function named `tt(foo@XX)' (unless `tt(foo)' is a builtin). +Note that a nested call to tt(_shadow) with the same var(suffix) may +result in name collisions and unexpected results, but this provides a +well-known name for the original function if the new implementation +needs to call it as a wrapper. The same var(suffix) must be used in +the call to tt(_unshadow). When no var(suffix) is present, +tt(_shadow) creates a unique suffix to avoid name collisions. +) findex(_store_cache) item(tt(_store_cache) var(cache_identifier) var(param) ...)( This function, together with tt(_retrieve_cache) and -- cgit v1.2.3 From baa19d2a85758d6b6bcbcd8b78f065a3be262fb3 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Wed, 26 Jul 2023 20:15:21 -0700 Subject: 51945: assorted documentation improvements, bug fixes, and new test 1) Document the behavior of "typeset -n existing_var" (via Jun T. comment) 2) Prohibit "typeset -nm pattern" because, well, it's insane. Add test. 3) Improve doc for ${(!)ref} including ${{t!)ref} (Jun T.) 4) Fix doc for how-to unset of a named ref (Jun T.) 5) Allow "typeset +r -n ref" and "typeset +r +n ref" (Jun T.) 6) Fix "typeset -r -n ref=param" to create readonly references 7) Avoid accidental removal of PM_UNSET flag (Jun T.) and update test 8) Fix "typeset -gn ref=value" and add a test for it 9) Add tests for read-only reference behavior 10) Fix infinite recursion when resolving scope of an unset local named reference, add test. --- ChangeLog | 5 ++++ Doc/Zsh/builtins.yo | 8 +++++- Doc/Zsh/expn.yo | 8 +++++- Doc/Zsh/params.yo | 4 +-- Src/builtin.c | 41 +++++++++++++++++++++++------- Src/params.c | 14 ++++++++--- Test/K01nameref.ztst | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 132 insertions(+), 18 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index d725239f3..f0b6e8c4b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,10 @@ 2023-07-26 Bart Schaefer + * 51945: Doc/Zsh/builtins.yo, Doc/Zsh/expn.yo, Doc/Zsh/params.yo, + Src/builtin.c, Src/params.c, Test/K01nameref.ztst: improve named + references documentation, fixes for typeset -r and -g behavior, + fix unset reference behavior including scoping crash, more tests + * Shohei YOSHIDA: 51979: Completion/Linux/Command/_free: Update free completion for procps-ng version 4.0.3 diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 5393cb149..33b13ac16 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -2060,6 +2060,11 @@ function unless `tt(-g -n)' is specified, and any local parameter (of any type) with the same var(name) supplants a named reference from a surrounding scope. +A scalar parameter, including an existing named reference, may be +converted to a new named reference by `tt(typeset -n )var(name)', so +the `tt(-p)' option must be included to display the value of a +specific named reference var(name). + If no attribute flags are given, and either no var(name) arguments are present or the flag tt(+m) is used, then each parameter name printed is preceded by a list of the attributes of that parameter (tt(array), @@ -2104,7 +2109,8 @@ is not used in this case). If the tt(+g) flag is combined with tt(-m), a new local parameter is created for every matching parameter that is not already local. Otherwise -tt(-m) applies all other flags or assignments to the existing parameters. +tt(-m) applies all other flags or assignments to the existing parameters, +except that the tt(-n) option cannot create named references in this way. Except when assignments are made with var(name)tt(=)var(value), using tt(+m) forces the matching parameters and their attributes to be printed, diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index 7bc736470..f87832e75 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -987,6 +987,11 @@ means the same thing as the more readable `(tt(%%qqq))'. The following flags are supported: startitem() +item(tt(!))( +When the parameter being expanded is a named reference, the reference +itself is examined and thus is em(not) resolved to its referent. In +ksh emulation, the parens around this flag are optional. +) item(tt(#))( Evaluate the resulting words as numeric expressions and interpret these as character codes. Output the corresponding characters. Note @@ -1245,7 +1250,8 @@ item(tt(hideval))( for parameters with the `hideval' flag (tt(-H)) ) item(tt(nameref))( -for named references having an empty value (tt(-n)) +for named references (tt(typeset -n)) either having an empty value or +when combined with `tt(!)' as in `tt(${LPAR()!t)tt(RPAR()var(rname)})' ) item(tt(special))( for special parameters defined by the shell diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index e0410d673..5653b3bc9 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -672,9 +672,9 @@ of var(pname) in assignments and expansions instead assign to or expand var(rname). This also applies to `tt(unset )var(pname)' and to most subsequent uses of `tt(typeset)' with the exception of `tt(typeset -n)' and `tt(typeset +n)', so to remove a named reference, -use either `tt(unset -n )var(pname)' or one of: +use either `tt(unset -n )var(pname)' (preferred) or one of: ifzman() -example(tt(typeset -n )var(pname) +example(tt(typeset -n )var(pname=) tt(typeset +n )var(pname)) followed by diff --git a/Src/builtin.c b/Src/builtin.c index 1568cf44c..31af66c7c 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2248,10 +2248,14 @@ typeset_single(char *cname, char *pname, Param pm, int func, zerrnam(cname, "%s: restricted", pname); return pm; } - if ((pm->node.flags & PM_READONLY) && - (pm->node.flags & PM_NAMEREF & off)) { - zerrnam(cname, "%s: read-only reference", pname); - return pm; + if ((pm->node.flags & PM_READONLY) && !(off & PM_READONLY) && + /* It seems as though these checks should not be specific to + * PM_NAMEREF, but changing that changes historic behavior */ + ((on & PM_NAMEREF) != (pm->node.flags & PM_NAMEREF) || + (asg && (pm->node.flags & PM_NAMEREF)))) { + zerrnam(cname, "%s: read-only %s", pname, + (pm->node.flags & PM_NAMEREF) ? "reference" : "variable"); + return NULL; } if ((on & PM_UNIQUE) && !(pm->node.flags & PM_READONLY & ~off)) { Param apm; @@ -2693,7 +2697,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) off |= bit; } if (OPT_MINUS(ops,'n')) { - if ((on & ~PM_READONLY)|off) { + if ((on|off) & ~PM_READONLY) { zwarnnam(name, "no other attributes allowed with -n"); return 1; } @@ -3021,6 +3025,13 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) /* With the -m option, treat arguments as glob patterns */ if (OPT_ISSET(ops,'m')) { if (!OPT_ISSET(ops,'p')) { + if (on & PM_NAMEREF) { + /* It's generally unwise to mass-change the types of + * parameters, but for namerefs it would be fatal */ + unqueue_signals(); + zerrnam(name, "invalid reference"); + return 1; + } if (!(on|roff)) printflags |= PRINT_TYPE; if (!on) @@ -3104,13 +3115,25 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) } if (hn) { /* namerefs always start over fresh */ - if (((Param)hn)->level >= locallevel) { + if (((Param)hn)->level >= locallevel || + (!(on & PM_LOCAL) && ((Param)hn)->level < locallevel)) { Param oldpm = (Param)hn; - if (!asg->value.scalar && oldpm->u.str) + if (!asg->value.scalar && + PM_TYPE(oldpm->node.flags) == PM_SCALAR && + oldpm->u.str) asg->value.scalar = dupstring(oldpm->u.str); - unsetparam_pm((Param)hn, 0, 1); + /* Defer read-only error to typeset_single() */ + if (!(hn->flags & PM_READONLY)) + unsetparam_pm(oldpm, 0, 1); } - hn = NULL; + /* Passing a NULL pm to typeset_single() makes the + * nameref read-only before assignment, which breaks + * typeset -rn ref=var + * so this is special-cased to permit that action + * like assign-at-create for other parameter types. + */ + if (!(hn->flags & PM_READONLY)) + hn = NULL; } } diff --git a/Src/params.c b/Src/params.c index f5750a4b4..5841308d7 100644 --- a/Src/params.c +++ b/Src/params.c @@ -546,7 +546,7 @@ getparamnode(HashTable ht, const char *nam) } } - if (hn && ht == realparamtab) + if (hn && ht == realparamtab && !(hn->flags & PM_UNSET)) hn = resolve_nameref((Param)hn, NULL); return hn; } @@ -3729,7 +3729,9 @@ unsetparam_pm(Param pm, int altflag, int exp) char *altremove; if ((pm->node.flags & PM_READONLY) && pm->level <= locallevel) { - zerr("read-only variable: %s", pm->node.nam); + zerr("read-only %s: %s", + (pm->node.flags & PM_NAMEREF) ? "reference" : "variable", + pm->node.nam); return 1; } if ((pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) { @@ -6182,8 +6184,12 @@ resolve_nameref(Param pm, const Asgment stop) seek = refname; } } - else if (pm && !(stop && (stop->flags & PM_NAMEREF))) - return (HashNode)pm; + else if (pm) { + if (!(stop && (stop->flags & PM_NAMEREF))) + return (HashNode)pm; + if (!(pm->node.flags & PM_NAMEREF)) + return (pm->level < locallevel ? NULL : (HashNode)pm); + } if (seek) { queue_signals(); /* pm->width is the offset of any subscript */ diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst index 6a5e767df..d8c098a98 100644 --- a/Test/K01nameref.ztst +++ b/Test/K01nameref.ztst @@ -515,7 +515,7 @@ F:Same test, should part 5 output look like this? >ptr1=val >ptr2= >typeset -n ptr1=ptr2 ->typeset -n ptr2='' +>typeset -n ptr2 >typeset ptr2=val if zmodload zsh/parameter; then @@ -694,4 +694,72 @@ F:Checking for a bug in zmodload that affects later tests F:runs in `setopt noexec` so $(...) returns nothing *?*bad math expression: empty string + unset -n ref + typeset -n ref=GLOBAL + () { + typeset -gn ref=RESET + } + typeset -p ref +0:reset global reference within function +>typeset -n ref=RESET + + unset -n ref + typeset -rn ref=RO + typeset -p ref + (typeset -n ref=RW) + print status: $? expected: 1 + typeset +r -n ref + typeset -p ref + typeset -r +n ref + typeset -p ref + (typeset -rn ref) + print status: $? expected: 1 + typeset +r -n ref=RW # Assignment occurs after type change, + typeset -p ref RO # so RO=RW here. Potentially confusing. + typeset -r -n ref=RX # No type change, so referent changes ... + typeset -p ref RO # ... and previous refererent does not. + typeset +rn ref=RW # Here ref=RW, again type changed first. + typeset -p ref +0:add and remove readonly attribute with references +>typeset -rn ref=RO +*?*: ref: read-only reference +>status: 1 expected: 1 +>typeset -n ref=RO +>typeset -r ref=RO +*?*: ref: read-only variable +>status: 1 expected: 1 +>typeset -n ref=RO +>typeset -g RO=RW +>typeset -rn ref=RX +>typeset -g RO=RW +>typeset ref=RW + + () { + typeset -n r1 r2= + typeset -p r1 r2 + print -- ${(!)r1-unset} + print -- ${+r1} + typeset -p r1 + } +0:unset nameref remains unset when resolved +F:relies on global TYPESET_TO_UNSET in %prep +>typeset -n r1 +>typeset -n r2='' +>unset +>0 +>typeset -n r1 + + bar=xx + typeset -n foo=bar + () { typeset -n foo; foo=zz; foo=zz; print $bar $zz } + () { typeset -n foo; foo=zz; local zz; foo=zz; print $bar $zz } +0:regression: local nameref may not in-scope a global parameter +F:previously this could create an infinite recursion and crash +>xx +>xx zz + + typeset -nm foo=bar +1:create nameref by pattern match not allowed +*?*typeset:1: invalid reference + %clean -- cgit v1.2.3 From c4cfb674653996e68830022c7066f19ce78421b5 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Wed, 26 Jul 2023 20:27:51 -0700 Subject: unposted (cf. 51968): improve documentation of typeset -gn and -r --- ChangeLog | 3 +++ Doc/Zsh/builtins.yo | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index f0b6e8c4b..c62a3da34 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2023-07-26 Bart Schaefer + * unposted (cf. 51968): Doc/Zsh/builtins.yo: improve description + of typeset -gn and -r + * 51945: Doc/Zsh/builtins.yo, Doc/Zsh/expn.yo, Doc/Zsh/params.yo, Src/builtin.c, Src/params.c, Test/K01nameref.ztst: improve named references documentation, fixes for typeset -r and -g behavior, diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 33b13ac16..5eb93272a 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -1924,8 +1924,8 @@ redef(SPACES)(0)(tt(ifztexi(NOTRANS(@ @ @ @ @ @ @ @ ))ifnztexi( ))) xitem(tt(typeset )[ {tt(PLUS())|tt(-)}tt(AHUaghlmrtux) ] \ [ {tt(PLUS())|tt(-)}tt(EFLRZip) [ var(n) ] ]) xitem(SPACES()[ tt(+) ] [ var(name)[tt(=)var(value)] ... ]) -xitem(tt(typeset )[ {tt(PLUS())|tt(-)}tt(n) ] \ -[ tt(-gr) ] [ var(name)[tt(=)var(value)] ... ]) +xitem(tt(typeset ){tt(PLUS())|tt(-)}tt(n) [ tt(-g) ] \ +[ {tt(PLUS())|tt(-)}tt(r) ] [ var(name)[tt(=)var(value)] ... ]) xitem(tt(typeset )tt(-T) [ {tt(PLUS())|tt(-)}tt(Uglrux) ] [ {tt(PLUS())|tt(-)}tt(LRZp) [ var(n) ] ]) xitem(SPACES()[ tt(+) | var(SCALAR)[tt(=)var(value)] var(array)[tt(=LPAR())var(value) ...tt(RPAR())] [ var(sep) ] ]) item(tt(typeset) tt(-f) [ {tt(PLUS())|tt(-)}tt(TUkmtuz) ] [ tt(+) ] [ var(name) ... ])( -- cgit v1.2.3 From 7233c7a750eb0a321a67281404b74b4ea0917384 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Wed, 16 Aug 2023 13:17:14 +0100 Subject: 29130, 21931 (Ray): document what typeset -t is for. This replaces documenting what it isn't for. --- ChangeLog | 5 +++++ Doc/Zsh/builtins.yo | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 13230c3b6..efcbfc644 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2023-08-16 Peter Stephenson + + * 29130, 29131 (Ray): Doc/Zsh/builtins.yo: document what typeset + -t is for, not what it isn't for. + 2023-08-14 Jun-ichi Takimoto * 52037: Completion/Unix/Command/_date, diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 5eb93272a..8f310f6cf 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -2320,8 +2320,10 @@ readonly). Special variables that have been made readonly retain their value and readonly attribute when made local. ) item(tt(-t))( -Tags the named parameters. Tags have no special meaning to the shell. -This flag has a different meaning when used with tt(-f); see above. +Tags the named parameters. Tags only exist to flag the parameter for +the user's own purposes --- the list of tagged parameters can be queried +using `tt(typeset -t)'. Tags have no other use. Note that the tt(-t) +flag has a different meaning when used with tt(-f); see above. ) item(tt(-u))( Convert the result to upper case whenever the parameter is expanded. -- cgit v1.2.3 From 2a854aae481e7eb064729db28a71ed0efb2f33e6 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sun, 27 Aug 2023 15:22:14 -0700 Subject: 52028: improvements to _shadow / _unshadow, plus helper and doc --- ChangeLog | 4 +++ Completion/Base/Utility/_shadow | 66 ++++++++++++++++++++++++++++------------- Doc/Zsh/compsys.yo | 53 +++++++++++++++++++-------------- Doc/Zsh/contrib.yo | 21 +++++++++++++ Functions/Misc/mkshadow | 11 +++++++ 5 files changed, 112 insertions(+), 43 deletions(-) create mode 100644 Functions/Misc/mkshadow (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index bb6afe127..412dbda61 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2023-08-27 Bart Schaefer + * 52028: Completion/Base/Utility/_shadow, Doc/Zsh/compsys.yo, + Doc/Zsh/contrib.yo, Functions/Misc/mkshadow: improve _shadow + and _unshadow, add helper function and update documentation + * Robert Woods: 52053: Src/utils.c: whitelist capability CAP_WAKE_ALARM in 'privasserted' function diff --git a/Completion/Base/Utility/_shadow b/Completion/Base/Utility/_shadow index 5b0f79c36..b5a8acb24 100644 --- a/Completion/Base/Utility/_shadow +++ b/Completion/Base/Utility/_shadow @@ -8,7 +8,7 @@ # } # # Invoke callers of fname # } always { -# _unshadow fname +# _unshadow # } ## Alternate usage: # { @@ -19,7 +19,7 @@ # } # # Invoke callers of fname # } always { -# _unshadow -s suffix fname +# _unshadow # } ## @@ -33,36 +33,62 @@ zmodload zsh/parameter # Or what? # This probably never comes up, but protect ourself from recursive call # chains that may duplicate the top elements of $funcstack by creating # a counter of _shadow calls and using it to make shadow names unique. -typeset -gHi _shadowdepth=0 +builtin typeset -gHi .shadow.depth=0 +builtin typeset -gHa .shadow.stack # Create a copy of each fname so that a caller may redefine _shadow() { - local -A fsfx=( -s ${funcstack[2]}:${functrace[2]}:$((_shadowdepth+1)) ) - local fname + emulate -L zsh + local -A fsfx=( -s ${funcstack[2]}:${functrace[2]}:$((.shadow.depth+1)) ) + local fname shadowname + local -a fnames zparseopts -K -A fsfx -D s: for fname; do - local shadowname=${fname}@${fsfx[-s]} - (( ${+functions[$fname]} )) && - builtin functions -c $fname $shadowname + shadowname=${fname}@${fsfx[-s]} + if (( ${+functions[$shadowname]} )) + then + # Called again with the same -s, just ignore it + continue + elif (( ${+functions[$fname]} )) + then + builtin functions -c -- $fname $shadowname + fnames+=(f@$fname) + elif (( ${+builtins[$fname]} )) + then + eval "function -- $shadowname { builtin $fname \"\$@\" }" + fnames+=(b@$fname) + else + eval "function -- $shadowname { command $fname \"\$@\" }" + fnames+=(c@$fname) + fi done - ((_shadowdepth++)) + [[ -z $REPLY ]] && REPLY=${fsfx[-s]} + builtin set -A .shadow.stack ${fsfx[-s]} $fnames -- ${.shadow.stack} + ((.shadow.depth++)) } # Remove the redefined function and shadowing name _unshadow() { - local -A fsfx=( -s ${funcstack[2]}:${functrace[2]}:${_shadowdepth} ) - local fname - zparseopts -K -A fsfx -D s: - for fname; do - local shadowname=${fname}@${fsfx[-s]} - if (( ${+functions[$shadowname]} )); then - builtin functions -c $shadowname $fname - builtin unfunction $shadowname - elif (( ${+functions[$fname]} )); then - builtin unfunction $fname + emulate -L zsh + local fname shadowname fsfx=${.shadow.stack[1]} + local -a fnames + [[ -n $fsfx ]] || return 1 + shift .shadow.stack + while [[ ${.shadow.stack[1]?no shadows} != -- ]]; do + fname=${.shadow.stack[1]#?@} + shadowname=${fname}@${fsfx} + if (( ${+functions[$fname]} )); then + builtin unfunction -- $fname fi + case ${.shadow.stack[1]} in + (f@*) builtin functions -c -- $shadowname $fname ;& + ([bc]@*) builtin unfunction -- $shadowname ;; + esac + shift .shadow.stack done - ((_shadowdepth--)) + [[ -z $REPLY ]] && REPLY=$fsfx + shift .shadow.stack + ((.shadow.depth--)) } # This is tricky. When we call _shadow recursively from autoload, diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo index 33baeab49..3f708eb5a 100644 --- a/Doc/Zsh/compsys.yo +++ b/Doc/Zsh/compsys.yo @@ -5229,13 +5229,12 @@ and hence is not normally called explicitly. ) findex(_shadow) findex(_unshadow) -xitem(tt(_shadow) [ tt(-s) var(suffix) ] var(command_name) ...) -item(tt(_unshadow) [ tt(-s) var(suffix) ] var(command_name) ...)( +xitem(tt(_shadow) [ tt(-s) var(suffix) ] [ -- ] var(command_name) ...) +item(tt(_unshadow))( The tt(_shadow) function creates a copy of each of the shell functions in the var(command_name) arguments. The original functions can then -be replaced by new implementations. A later call to tt(_unshadow), -with the same var(command_name) list, removes the new implementations, -if any, and restores the originals. +be replaced by new implementations. A later call to tt(_unshadow) +removes the new implementations, if any, and restores the originals. Recommended usage is to pair tt(_shadow) and tt(_unshadow) calls by use of an `tt(always)' block: @@ -5246,30 +5245,38 @@ example({ } # Invoke callers of fname } always { - _unshadow fname + _unshadow }) -Any var(command_name) may instead be a builtin, but in that case no -copy is created. The expectation is that an initial tt(_shadow) is -followed by creating a wrapper function, and therafter any nested or -recursive calls thus copy and replace the wrapper function. +The var(suffix), if supplied, is prepended by an `tt(@)' character and +then appended to each var(command_name) to create the copy. Thus +example(_shadow -s XX foo) +creates a function named `tt(foo@XX)'. This provides a well-known +name for the original implementation if the new implementation needs +to call it as a wrapper. If a nested call to tt(_shadow) uses the +same var(suffix), em(no new copy is made). The presumption thus is +that suffixes and new implementations correspond one to one. + +If var(command_name) is a builtin or external command, and there has been +no preceding tt(_shadow) replacement made, the function so created calls +the shadowed name prefixed by the tt(builtin) or tt(command) keywords as +appropriate. example({ - _shadow compadd - compadd LPAR()RPAR() { builtin compadd -O tmparr "$@" } + _shadow -s wrap compadd + compadd LPAR()RPAR() { + # compadd@wrap runs builtin compadd + compadd@wrap -O tmparr "$@" } } always { - _unshadow compadd + _unshadow }) -The var(suffix), if supplied, is prepended by an `tt(@)' character and -then appended to each var(command_name) to create the copy. Thus -example(_shadow -s XX foo) -creates a function named `tt(foo@XX)' (unless `tt(foo)' is a builtin). -Note that a nested call to tt(_shadow) with the same var(suffix) may -result in name collisions and unexpected results, but this provides a -well-known name for the original function if the new implementation -needs to call it as a wrapper. The same var(suffix) must be used in -the call to tt(_unshadow). When no var(suffix) is present, -tt(_shadow) creates a unique suffix to avoid name collisions. +When no var(suffix) argument is present, tt(_shadow) creates a unique +suffix to avoid name collisions. + +Arguments of tt(_unshadow) are ignored. Every listed var(command_name) +for the most recent call to tt(_shadow) is removed. This differs from +an early implementation that required tt(_unshadow) to receive the +same var(suffix) and var(command_name) list as tt(_shadow). ) findex(_store_cache) item(tt(_store_cache) var(cache_identifier) var(param) ...)( diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index 96de5aa9b..ef11d77ad 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -4336,6 +4336,27 @@ example(is-at-least 3.1.6-15 && setopt NO_GLOBAL_RCS is-at-least 3.1.0 && setopt HIST_REDUCE_BLANKS is-at-least 2.6-17 || print "You can't use is-at-least here.") ) +findex(mkshadow) +findex(rmshadow) +xitem(tt(mkshadow) [ tt(-s) var(suffix) ] [ -- ] var(command_name) ...) +item(tt(rmshadow))( +These functions are an interface to the tt(_shadow) and tt(_unshadow) +completion utilities to make them more easily accessible in other +contexts. Usage is exactly as for the completion utility: +example({ + mkshadow fname + function fname { + # Do your new thing + } + # Invoke callers of fname +} always { + rmshadow +}) + +Upon return, the value of tt($REPLY) is the suffix used to create a +copy of the original var(command_name), so var(command_name)tt(@$REPLY) +invokes that original. +) findex(nslookup) item(tt(nslookup) [ var(arg) ... ])( This wrapper function for the tt(nslookup) command requires the diff --git a/Functions/Misc/mkshadow b/Functions/Misc/mkshadow new file mode 100644 index 000000000..2ae3a0f2c --- /dev/null +++ b/Functions/Misc/mkshadow @@ -0,0 +1,11 @@ +#autoload +# Front-end to the completion helper _shadow for use outside completion. +# This just forces proper autoload of _shadow/_unshadow and calls them. + +autoload _shadow +mkshadow() { unset REPLY; _shadow "$@" } +rmshadow() { unset REPLY; _unshadow } + +# Bootstrap because of autoload special case +unset REPLY +_shadow "$@" -- cgit v1.2.3 From 3aaef16569a6b9bd5ca0a2a323cc0643772f9c56 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 16 Sep 2023 17:34:39 -0700 Subject: 52154, 52155: Implement, document, and test non-forking command substitution. Comprises workers/51957, 51985, 51987, 51988, 51993, 52131, 52139, plus fixes for return values, parse errors, and trailing newlines (which were incorrectly removed) in ${ ... } --- ChangeLog | 6 +++ Doc/Zsh/expn.yo | 44 +++++++++++++--- Src/lex.c | 72 +++++++++++++++++++++----- Src/subst.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 254 insertions(+), 25 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 27dcf0e58..0390ea2b5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ 2023-09-16 Bart Schaefer + * 52155: Test/D10nofork.ztst: Tests for non-forking substitution. + + * 52154: Doc/Zsh/expn.yo, Src/lex.c, Src/subst.c: implement + and document non-forking command substitutions ${|...} and + ${ ... }. Based on Sebastian Gniazdowski: 51702. + * 52153: Src/input.c, Src/Modules/mapfile.c: $mapfile[fname] should not trim newlines (only applies when not HAVE_MMAP) diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index f87832e75..5be40bf25 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -1881,23 +1881,55 @@ sect(Command Substitution) cindex(command substitution) cindex(substitution, command) A command enclosed in parentheses preceded by a dollar sign, like -`tt($LPAR())...tt(RPAR())', or quoted with grave -accents, like `tt(`)...tt(`)', is replaced with its standard output, with -any trailing newlines deleted. -If the substitution is not enclosed in double quotes, the -output is broken into words using the tt(IFS) parameter. +`tt($LPAR())...tt(RPAR())', or quoted with grave accents, like +`tt(`)...tt(`)', is executed in a subshell and replaced by its +standard output, with any trailing newlines deleted. If the +substitution is not enclosed in double quotes, the output is broken +into words using the tt(IFS) parameter. vindex(IFS, use of) The substitution `tt($LPAR()cat) var(foo)tt(RPAR())' may be replaced by the faster `tt($LPAR()<)var(foo)tt(RPAR())'. In this case var(foo) undergoes single word shell expansions (em(parameter expansion), em(command substitution) and em(arithmetic expansion)), but not -filename generation. +filename generation. No subshell is created. If the option tt(GLOB_SUBST) is set, the result of any unquoted command substitution, including the special form just mentioned, is eligible for filename generation. +A command with a leading pipe character, enclosed in braces prefixed by +a dollar sign, as in `tt(${|)...tt(})', is executed in the current shell +context, rather than in a subshell, and is replaced by the value of the +parameter tt(REPLY) at the end of the command. There em(must not) be +any whitespace between the opening brace and the pipe character. Any +prior value of tt($REPLY) is saved and restored around this substitution, +in the manner of a function local parameter. Other parameters declared +within the substitution also behave as locals, as if in a function, +unless `tt(typeset -g)' is used. Trailing newlines are em(not) deleted +from the final replacement in this case, and it is subject to filename +generation in the same way as `tt($LPAR())...tt(RPAR())' but is em(not) +split on tt(IFS) unless the tt(SH_WORD_SPLIT) option is set. + +Substitutions of the form `tt(${|)var(param)tt(|)...tt(})' are similar, +except that the substitution is replaced by the value of the parameter +named by var(param). No implicit save or restore applies to var(param) +except as noted for tt(REPLY), and var(param) should em(not) be declared +within the command. If var(param) names an array, array expansion rules +apply. + +A command enclosed in braces preceded by a dollar sign, and set off from +the braces by whitespace, like `tt(${ )...tt( })', is replaced by its +standard output. Like `tt(${|)...tt(})' and unlike +`tt($LPAR())...tt(RPAR())', the command executes in the current shell +context with function local behaviors and does not create a subshell. + +Note that because the `tt(${|)...tt(})' and `tt(${ )...tt( })' forms +must be parsed at once as both string tokens and commands, all other +braces (`tt({)' or `tt(})') within the command either must be quoted, +or must appear in syntactically valid pairs, such as around complex +commands, function bodies, or parameter references. + texinode(Arithmetic Expansion)(Brace Expansion)(Command Substitution)(Expansion) sect(Arithmetic Expansion) cindex(arithmetic expansion) diff --git a/Src/lex.c b/Src/lex.c index 2f7937410..33b17cc95 100644 --- a/Src/lex.c +++ b/Src/lex.c @@ -937,7 +937,7 @@ static enum lextok gettokstr(int c, int sub) { int bct = 0, pct = 0, brct = 0, seen_brct = 0, fdpar = 0; - int intpos = 1, in_brace_param = 0; + int intpos = 1, in_brace_param = 0, cmdsubst = 0; int inquote, unmatched = 0; enum lextok peek; #ifdef DEBUG @@ -1135,7 +1135,7 @@ gettokstr(int c, int sub) c = Inpar; break; case LX2_INBRACE: - if (isset(IGNOREBRACES) || sub) + if ((isset(IGNOREBRACES) && !cmdsubst) || sub) c = '{'; else { if (!lexbuf.len && incmdpos) { @@ -1157,8 +1157,11 @@ gettokstr(int c, int sub) if (in_brace_param) { cmdpop(); } - if (bct-- == in_brace_param) - in_brace_param = 0; + if (bct-- == in_brace_param) { + if (cmdsubst) + cmdpop(); + in_brace_param = cmdsubst = 0; + } c = Outbrace; break; case LX2_COMMA: @@ -1405,16 +1408,24 @@ gettokstr(int c, int sub) } add(c); c = hgetc(); - if (intpos) + if (intpos) intpos--; - if (lexstop) + if (lexstop) break; + if (!cmdsubst && in_brace_param && act == LX2_STRING && + (c == '|' || c == Bar || inblank(c))) { + cmdsubst = in_brace_param; + cmdpush(CS_CURSH); + } } brk: if (errflag) { if (in_brace_param) { - while(bct-- >= in_brace_param) + while(bct >= in_brace_param) { + if (bct-- == cmdsubst) + cmdpop(); cmdpop(); + } } return LEXERR; } @@ -1422,8 +1433,11 @@ gettokstr(int c, int sub) if (unmatched && !(lexflags & LEXFLAGS_ACTIVE)) zerr("unmatched %c", unmatched); if (in_brace_param) { - while(bct-- >= in_brace_param) + while(bct >= in_brace_param) { + if (bct-- == cmdsubst) + cmdpop(); cmdpop(); + } zerr("closing brace expected"); } else if (unset(IGNOREBRACES) && !sub && lexbuf.len > 1 && peek == STRING && lexbuf.ptr[-1] == '}' && @@ -1459,8 +1473,8 @@ gettokstr(int c, int sub) static int dquote_parse(char endchar, int sub) { - int pct = 0, brct = 0, bct = 0, intick = 0, err = 0; - int c; + int pct = 0, brct = 0, bct = 0, intick = 0, err = 0, cmdsubst = 0; + int c, bskip = 0; int math = endchar == ')' || endchar == ']' || infor; int zlemath = math && zlemetacs > zlemetall + addedx - inbufct; @@ -1529,11 +1543,25 @@ dquote_parse(char endchar, int sub) c = Qstring; } break; + case '{': + if (cmdsubst && !intick) { + /* In nofork substitution, tokenize as if unquoted */ + c = Inbrace; + bskip++; + } + break; case '}': if (intick || !bct) break; c = Outbrace; - bct--; + if (bskip) { + bskip--; + break; + } + if (bct-- == cmdsubst) { + cmdsubst = 0; + cmdpop(); + } cmdpop(); break; case '`': @@ -1588,14 +1616,34 @@ dquote_parse(char endchar, int sub) if (err || lexstop) break; add(c); + if (!cmdsubst && c == Inbrace) { + /* Check for ${|...} nofork command substitution */ + if ((c = hgetc()) && !lexstop) { + if (c == '|' || inblank(c)) { + cmdsubst = bct; + cmdpush(CS_CURSH); + } + hungetc(c); + } + } } if (intick == 2) ALLOWHIST if (intick) { cmdpop(); } - while (bct--) + while (bct) { + if (bct-- == cmdsubst) { + /* + * You would think this is an error, but if we call it one, + * parsestrnoerr() returns nonzero to subst_parse_str() and + * subsequently "bad substitution" is not reported + */ + /* err = 1 */ + cmdpop(); + } cmdpop(); + } if (lexstop) err = intick || endchar || err; else if (err == 1) { diff --git a/Src/subst.c b/Src/subst.c index d68159227..52afd6484 100644 --- a/Src/subst.c +++ b/Src/subst.c @@ -1867,6 +1867,10 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, * joining the array into a string (for compatibility with ksh/bash). */ int quoted_array_with_offset = 0; + /* Indicates ${|...;} */ + char *rplyvar = NULL; + /* Indicates ${ ... ;} */ + char *rplytmp = NULL; *s++ = '\0'; /* @@ -1894,8 +1898,147 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, * flags in parentheses, but also one ksh hack. */ if (c == Inbrace) { + /* The command string to be run by ${|...;} */ + char *cmdarg = NULL; + size_t slen = 0; inbrace = 1; s++; + + /* Short-path for the nofork command substitution ${|cmd;} + * See other comments about kludges for why this is here. + * + * The command string is extracted and executed, and the + * substitution assigned. There's no (...)-flags processing, + * i.e. no ${|(U)cmd;}, because it looks quite awful and + * should not be part of command substitution in any case. + * Use ${(U)${|cmd;}} as you would for ${(U)$(cmd;)}. + */ + if (*s == '|' || *s == Bar || inblank(*s)) { + char *outbracep = s; + char sav = *s; + *s = Inbrace; + if (skipparens(Inbrace, Outbrace, &outbracep) == 0) { + slen = outbracep - s - 1; + if ((*s = sav) != Bar) { + sav = *outbracep; + *outbracep = '\0'; + tokenize(s); + *outbracep = sav; + } + } + } + if (slen > 1) { + char *outbracep = s + slen; + if (*outbracep == Outbrace) { + if ((rplyvar = itype_end(s+1, INAMESPC, 0))) { + if (*rplyvar == Inbrack && + (rplyvar = parse_subscript(++rplyvar, 1, ']'))) + ++rplyvar; + } + if (rplyvar == s+1 && *rplyvar == Bar) { + /* Is ${||...} a subtitution error or a syntax error? + zerr("bad substitution"); + return NULL; + */ + rplyvar = NULL; + } + if (rplyvar && *rplyvar == Bar) { + cmdarg = dupstrpfx(rplyvar+1, outbracep-rplyvar-1); + rplyvar = dupstrpfx(s+1,rplyvar-s-1); + } else { + cmdarg = dupstrpfx(s+1, outbracep-s-1); + rplyvar = "REPLY"; + } + if (inblank(*s)) { + /* + * Admittedly a hack. Take advantage of the enforced + * locality of REPLY and the semantics of $(level = locallevel; + /* if (rplyval) setsparam("REPLY", ztrdup(rplyval)); */ + } + + if (rplyvar && cmdarg && *cmdarg) { + int obreaks = breaks; + Eprog cmdprog; + /* Execute the shell command */ + untokenize(cmdarg); + cmdprog = parse_string(cmdarg, 0); + if (cmdprog) { + execode(cmdprog, 1, 0, "cmdsubst"); + cmdoutval = lastval; + /* "return" behaves as if in a function */ + if (retflag) { + retflag = 0; + breaks = obreaks; /* Is this ever not zero? */ + } + } else /* parse error */ + errflag |= ERRFLAG_ERROR; + if (rplytmp && !errflag) { + int onoerrs = noerrs; + noerrs = 2; + if ((cmdarg = ztuff(rplytmp))) + setsparam("REPLY", cmdarg); + noerrs = onoerrs; + } + } + + if (rplytmp) + unlink(rplytmp); + if (rplyvar) { + if (strcmp(rplyvar, "REPLY") == 0) { + if ((val = dupstring(getsparam("REPLY")))) + vunset = 0; + else { + vunset = 1; + val = dupstring(""); + } + } else { + s = dyncat(rplyvar, s); + rplyvar = NULL; + } + endparamscope(); + if (exit_pending) { + if (mypid == getpid()) { + /* + * paranoia: don't check for jobs, but there + * shouldn't be any if not interactive. + */ + stopmsg = 1; + zexit(exit_val, ZEXIT_NORMAL); + } else + _exit(exit_val); + } + } + /* * In ksh emulation a leading `!' is a special flag working * sort of like our (k). This is true only for arrays or @@ -2590,14 +2733,14 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, * we let fetchvalue set the main string pointer s to * the end of the bit it's fetched. */ - if (!(v = fetchvalue(&vbuf, (subexp ? &ov : &s), - (wantt ? -1 : - ((unset(KSHARRAYS) || inbrace) ? 1 : -1)), - scanflags)) || - (v->pm && (v->pm->node.flags & PM_UNSET)) || - (v->flags & VALFLAG_EMPTY)) + if (!rplyvar && + (!(v = fetchvalue(&vbuf, (subexp ? &ov : &s), + (wantt ? -1 : + ((unset(KSHARRAYS) || inbrace) ? 1 : -1)), + scanflags)) || + (v->pm && (v->pm->node.flags & PM_UNSET)) || + (v->flags & VALFLAG_EMPTY))) vunset = 1; - if (wantt) { /* * Handle the (t) flag: value now becomes the type -- cgit v1.2.3 From d92b1a3547e4d7e602316e12f0a896356579c1ab Mon Sep 17 00:00:00 2001 From: Atte Peltomäki Date: Thu, 20 Jul 2023 13:52:08 +0300 Subject: 51980: Add glob qualifier grouping operator to completion Also improve wording in documentation to make glob qualifier grouping easier to find by explicit use of terms 'logical OR' and 'logical AND'. --- ChangeLog | 7 +++++-- Completion/Zsh/Type/_globquals | 1 + Doc/Zsh/expn.yo | 7 ++++--- 3 files changed, 10 insertions(+), 5 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 44171d558..71e1b2f76 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,10 +1,13 @@ 2023-09-20 Oliver Kiddle + * Atte Peltomäki: 51980: Completion/Zsh/Type/_globquals, + Doc/Zsh/expn.yo: Add glob qualifier grouping operator to completion + * Jörg Sommer: 52102: Completion/Debian/Command/_apt: Add subcommand autopurge to apt completion - * Wesley Schwengle: 52141: Completion/Unix/Command/_git: add trailer - token completion for git commit --trailer + * Wesley Schwengle: 52141: Completion/Unix/Command/_git: + add trailer token completion for git commit --trailer * 52163: Completion/Unix/Command/_zfs: completion update for OpenZFS 2.2 diff --git a/Completion/Zsh/Type/_globquals b/Completion/Zsh/Type/_globquals index bc3165eba..beb47ed30 100644 --- a/Completion/Zsh/Type/_globquals +++ b/Completion/Zsh/Type/_globquals @@ -268,6 +268,7 @@ case $state in "P:prepend word" "Y:+ at most ARG matches" "[:+ range of files" + ",:logical OR" "):end of qualifiers" "\::modifier" ) diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index 5be40bf25..86a5f70c8 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -3117,9 +3117,10 @@ so both can be used on the same glob expression; for example by writing ) enditem() -More than one of these lists can be combined, separated by commas. The -whole list matches if at least one of the sublists matches (they are -`or'ed, the qualifiers in the sublists are `and'ed). Some qualifiers, +Multiple consecutive qualifiers are joined into a list by implicit logical AND. +More than one of these lists can be combined using comma `tt(,)' as logical OR. +The whole list matches if at least one of the sublists matches. +Some qualifiers, however, affect all matches generated, independent of the sublist in which they are given. These are the qualifiers `tt(M)', `tt(T)', `tt(N)', `tt(D)', `tt(n)', `tt(o)', `tt(O)' and the subscripts given -- cgit v1.2.3 From e4e9afe373479076ee448b16944a421836ba5a40 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 23 Sep 2023 09:30:55 -0500 Subject: 52180: clarify array behavior of ${|var|...} and REPLY --- ChangeLog | 5 +++++ Doc/Zsh/expn.yo | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index e4965b804..0f3c9402d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2023-09-23 Bart Schaefer + + * 52180: Doc/Zsh/expn.yo: clarify array behavior of ${|var|...} + and the REPLY parameter + 2023-09-22 Bart Schaefer * 52176: Src/subst.c: metafy return from ${ ... } substitution diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index 86a5f70c8..837a85db6 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -1915,8 +1915,9 @@ Substitutions of the form `tt(${|)var(param)tt(|)...tt(})' are similar, except that the substitution is replaced by the value of the parameter named by var(param). No implicit save or restore applies to var(param) except as noted for tt(REPLY), and var(param) should em(not) be declared -within the command. If var(param) names an array, array expansion rules -apply. +within the command. If, after evaluating the expression, var(param) +names an array, array expansion rules apply. However, tt(REPLY) is +always expanded in scalar context, even if assigned an array. A command enclosed in braces preceded by a dollar sign, and set off from the braces by whitespace, like `tt(${ )...tt( })', is replaced by its -- cgit v1.2.3 From 457ab9f9fe12a093dd41a6c96bff6db209471ecc Mon Sep 17 00:00:00 2001 From: Mikael Magnusson Date: Mon, 6 Jun 2022 12:43:57 +0200 Subject: 52222: Document bracketed-paste-url-magic --- ChangeLog | 2 ++ Doc/Zsh/contrib.yo | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 2774ca119..323ae074d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,8 @@ * 51490: Src/Zle/computil.c: Use time_t for lastt which stores result of time(0) + * 52222: Doc/Zsh/contrib.yo: Document bracketed-paste-url-magic + 2023-10-15 Bart Schaefer * 52218: Etc/BUGS, NEWS, README: notes since 5.9 release diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index ef11d77ad..f43ac2257 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -2448,6 +2448,33 @@ history is restricted, so cursor motions, etc., may not pass outside of the pasted content. Text assigned to tt(BUFFER) by the active widgets is copied back into tt(PASTED) before tt(paste-finish). ) +tindex(bracketed-paste-url-magic) +item(tt(bracketed-paste-url-magic))( +This widget is a simpler version of using tt(bracketed-paste-magic) +to enable tt(quote-url-magic). Rather than re-interpreting everything as +keystrokes, it simply handles quoting of pasted urls itself, and all other +text is handled like the default bracketed-paste widget. + +It limits the quoting to pastes that consist of a single url, but you +can also enable or disable quoting explicitly for a paste by setting +NUMERIC to 1 or 2 respectively (ie, by pressing alt-1 or alt-2 before +pasting). + +It is also possible to customize the list of schemas used to +decide if something is a url by setting the tt(schema) style in the +tt(:bracketed-paste-url-magic) context, for example: +ifzman() +example(zstyle :bracketed-paste-url-magic schema http:// myspecialschema:) + +The default list of schemas is tt(http:// https:// ftp:// ftps:// file:// \ + ssh:// sftp:// magnet:). + +The widget itself is installed in a similar way as +tt(bracketed-paste-magic) above, by +ifzman() +example(autoload -Uz bracketed-paste-url-magic +zle -N bracketed-paste bracketed-paste-url-magic) +) tindex(copy-earlier-word) item(tt(copy-earlier-word))( This widget works like a combination of tt(insert-last-word) and -- cgit v1.2.3 From de635b4ee56c188ccbaf0009027f9d1c0d42af0f Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Tue, 31 Oct 2023 01:04:19 +0100 Subject: 52253: support pcre callouts with shell evaluation of the callout string --- ChangeLog | 3 +++ Doc/Zsh/mod_pcre.yo | 5 +++++ Src/Modules/pcre.c | 34 ++++++++++++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 2 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 719222048..3f1014db3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2023-11-02 Oliver Kiddle + * 52253: Src/Modules/pcre.c: support pcre callouts with shell + evaluation of the callout string + * 52260: Completion/Unix/Command/_sudo: handle variable assignments before the command in sudo completion diff --git a/Doc/Zsh/mod_pcre.yo b/Doc/Zsh/mod_pcre.yo index da73ac85a..41fab4475 100644 --- a/Doc/Zsh/mod_pcre.yo +++ b/Doc/Zsh/mod_pcre.yo @@ -69,6 +69,11 @@ print -l $accum) ) enditem() +If the regular expression contains callouts, these are executed as shell code. +During the execution of the callout, the string the regular expression is +matching against is available in the parameter tt(.pcre.subject). If there is a +non-zero return status from the shell code, the callout does not match. + The option tt(-d) uses the alternative breadth-first DFA search algorithm of pcre. This sets tt(match), or the array given with tt(-a), to all the matches found from the same start point in the subject. diff --git a/Src/Modules/pcre.c b/Src/Modules/pcre.c index f5cda6d38..e6b59831f 100644 --- a/Src/Modules/pcre.c +++ b/Src/Modules/pcre.c @@ -128,6 +128,31 @@ bin_pcre_study(char *nam, UNUSED(char **args), UNUSED(Options ops), UNUSED(int f return 0; } +static int +pcre_callout(pcre2_callout_block_8 *block, void *) +{ + Eprog prog; + int ret=0; + + if (!block->callout_number && + ((prog = parse_string((char *) block->callout_string, 0)))) + { + int ef = errflag, lv = lastval; + + setsparam(".pcre.subject", + metafy((char *) block->subject, block->subject_length, META_DUP)); + setiparam(".pcre.pos", block->current_position + 1); + execode(prog, 1, 0, "pcre"); + ret = lastval | errflag; + + /* Restore any user interrupt error status */ + errflag = ef | (errflag & ERRFLAG_INT); + lastval = lv; + } + + return ret; +} + static int zpcre_get_substrings(pcre2_code *pat, char *arg, pcre2_match_data *mdata, int captured_count, char *matchvar, char *substravar, char *namedassoc, @@ -339,6 +364,9 @@ bin_pcre_match(char *nam, char **args, Options ops, UNUSED(int func)) plaintext = ztrdup(*args); unmetafy(plaintext, &subject_len); + pcre2_match_context_8 *mcontext = pcre2_match_context_create(NULL); + pcre2_set_callout(mcontext, &pcre_callout, 0); + if (offset_start > 0 && offset_start >= subject_len) ret = PCRE2_ERROR_NOMATCH; else if (use_dfa) { @@ -347,7 +375,7 @@ bin_pcre_match(char *nam, char **args, Options ops, UNUSED(int func)) pcre_mdata = pcre2_match_data_create(capcount, NULL); do { ret = pcre2_dfa_match(pcre_pattern, (PCRE2_SPTR) plaintext, subject_len, - offset_start, 0, pcre_mdata, NULL, (int *) workspace, wscount); + offset_start, 0, pcre_mdata, mcontext, (int *) workspace, wscount); if (ret == PCRE2_ERROR_DFA_WSSIZE) { old = wscount; wscount += wscount / 2; @@ -362,7 +390,7 @@ bin_pcre_match(char *nam, char **args, Options ops, UNUSED(int func)) } else { pcre_mdata = pcre2_match_data_create_from_pattern(pcre_pattern, NULL); ret = pcre2_match(pcre_pattern, (PCRE2_SPTR) plaintext, subject_len, - offset_start, 0, pcre_mdata, NULL); + offset_start, 0, pcre_mdata, mcontext); } if (ret==0) return_value = 0; @@ -380,6 +408,8 @@ bin_pcre_match(char *nam, char **args, Options ops, UNUSED(int func)) if (pcre_mdata) pcre2_match_data_free(pcre_mdata); + if (mcontext) + pcre2_match_context_free(mcontext); zsfree(plaintext); return return_value; -- cgit v1.2.3 From e32da8611197a56482ac3da7a85fc9e1d02e7b98 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 18 Nov 2023 16:08:28 -0800 Subject: Unposted (cf. 52296): correct description of "vared -e" --- Doc/Zsh/zle.yo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Doc') diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo index c622483d7..495bd86a8 100644 --- a/Doc/Zsh/zle.yo +++ b/Doc/Zsh/zle.yo @@ -376,7 +376,7 @@ If the tt(-p) flag is given, the following string will be taken as the prompt to display at the left. If the tt(-r) flag is given, the following string gives the prompt to display at the right. If the tt(-h) flag is specified, the history can be accessed from ZLE. If the -tt(-e) flag is given, typing tt(^D) (Control-D) on an empty line +tt(-e) flag is given, typing tt(^D) (Control-D) in an empty buffer causes tt(vared) to exit immediately with a non-zero return value. The tt(-M) option gives a keymap to link to the tt(main) keymap during -- cgit v1.2.3 From fbec213cc5ab0d0316c98bd9ddb883bae3bbdbc5 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Thu, 23 Nov 2023 13:23:55 -0800 Subject: 52325: Clarify doc for edge cases of named references and nofork substitution Unposted whitespace change avoids a parse error in ${ ... } with comments. --- ChangeLog | 9 +++++++++ Doc/Zsh/expn.yo | 8 +++++++- Doc/Zsh/mod_ksh93.yo | 7 ------- Doc/Zsh/params.yo | 10 +++++++--- Src/subst.c | 2 +- Test/D10nofork.ztst | 2 +- 6 files changed, 25 insertions(+), 13 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 8932d60d9..64155b175 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2023-11-23 Bart Schaefer + + * 52325: Doc/Zsh/expn.yo, Doc/Zsh/mod_ksh93.yo, Doc/Zsh/params.yo: + Clarify side-effects of $argv and named references to specials, + update ksh93 feature compatibility. + + unposted: Src/subst.c, Test/D10nofork.ztst: whitespace tweak to + avoid unexpected parse error when comments are used in ${ ... } + 2023-11-22 Oliver Kiddle * unposted: Completion/Unix/Command/_ri: fix missing closing brace diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index 837a85db6..e5506d469 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -1600,6 +1600,10 @@ example(tt(before local: OUTER) tt(by reference: OUTER) tt(after func: RESULT)) +Note, however, that named references to em(special) parameters acquire +the behavior of the special parameter, regardless of the scope where +the reference is declared. + When var(rname) includes an array subscript, the subscript expression is interpreted at the time tt(${)var(pname)tt(}) is expanded. Any form of subscript is allowed, including those that select individual @@ -1929,7 +1933,9 @@ Note that because the `tt(${|)...tt(})' and `tt(${ )...tt( })' forms must be parsed at once as both string tokens and commands, all other braces (`tt({)' or `tt(})') within the command either must be quoted, or must appear in syntactically valid pairs, such as around complex -commands, function bodies, or parameter references. +commands, function bodies, or parameter references. Furthermore, +comments are always recognized, even when tt(NO_INTERACTIVE_COMMENTS) +is in effect. texinode(Arithmetic Expansion)(Brace Expansion)(Command Substitution)(Expansion) sect(Arithmetic Expansion) diff --git a/Doc/Zsh/mod_ksh93.yo b/Doc/Zsh/mod_ksh93.yo index 99dab385f..9cd708d10 100644 --- a/Doc/Zsh/mod_ksh93.yo +++ b/Doc/Zsh/mod_ksh93.yo @@ -184,13 +184,6 @@ When a function of this name is defined, it is called whenever the parameter var(name) would be unset. The function must explicitly `tt(unset )var(name)', otherwise the variable remains set. -em(THIS FEATURE IS NOT YET IMPLEMENTED.) -) -item(tt(${ )var(list)tt(;}))( -Note the space after the opening brace (tt({)). This executes var(list) -in the current shell and substitutes its standard output in the manner -of `tt($LPAR())var(list)tt(RPAR())' but without forking. - em(THIS FEATURE IS NOT YET IMPLEMENTED.) ) item(tt(typeset -C )var(name)[tt(=)var(values)tt( ...)])( diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index 5653b3bc9..68df4a16f 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -748,9 +748,13 @@ vindex(argv) item(tt(argv) )( Same as tt(*). Assigning to tt(argv) changes the local positional parameters, but tt(argv) is em(not) itself a local parameter. -Deleting tt(argv) with tt(unset) in any function deletes it everywhere, -although only the innermost positional parameter array is deleted (so -tt(*) and tt(@) in other scopes are not affected). +Deleting tt(argv) with tt(unset) in any function deletes it everywhere. +This can be avoided by declaring `tt(local +h argv)' before unsetting. +Even when not so declared, only the innermost positional parameter +array is deleted (so tt(*) and tt(@) in other scopes are not affected). + +A named reference to tt(argv) em(does not) permit a called function to +access the positional parameters of its caller. ) vindex(@) item(tt(@) )( diff --git a/Src/subst.c b/Src/subst.c index 4ef1d1269..3a1187350 100644 --- a/Src/subst.c +++ b/Src/subst.c @@ -1956,7 +1956,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, * Then fall through to the regular handling of $REPLY * to manage word splitting, expansion flags, etc. */ - char *outfmt = ">| %s { %s ;}"; /* 13 */ + char *outfmt = ">| %s {\n%s\n;}"; /* 13 */ if ((rplytmp = gettempname(NULL, 1))) { /* Prevent shenanigans with $TMPPREFIX */ char *tmpfile = quotestring(rplytmp, QT_BACKSLASH); diff --git a/Test/D10nofork.ztst b/Test/D10nofork.ztst index 024b8ffcc..d6a5588df 100644 --- a/Test/D10nofork.ztst +++ b/Test/D10nofork.ztst @@ -422,7 +422,7 @@ F:must do this before evaluating the next test block purr ${ { echo nested } } DONE 1:ignored braces, part 4 -?(eval):1: parse error near `}' +?(eval):3: parse error near `}' # "break" blocks function calls in outer loop # Could use print, but that might get fixed -- cgit v1.2.3 From 9e6a54a3680f079fe5433d985c196e6c30ea57e8 Mon Sep 17 00:00:00 2001 From: Jun-ichi Takimoto Date: Mon, 4 Dec 2023 16:47:24 +0900 Subject: 52356: add missing function index entries --- ChangeLog | 5 +++++ Doc/Zsh/builtins.yo | 3 +++ 2 files changed, 8 insertions(+) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index ba87b44c3..881a83021 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2023-12-04 Jun-ichi Takimoto + + * 52356: (cf. 52327 Dennis Eriksen): Doc/Zsh/builtins.yo: add + missing function index entries + 2023-12-02 Bart Schaefer * 52361: Completion/Unix/Command/_ant: fix quoting of target files diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 8f310f6cf..cab265a25 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -683,6 +683,7 @@ the same as if the commands had been executed directly by the shell; if there are no var(args) or they contain no commands (i.e. are an empty string or whitespace) the return status is zero. ) +findex(exec) item(tt(exec) [ tt(-cl) ] [ tt(-a) var(argv0) ] [ var(command) [ var(arg) ... ] ])( Replace the current shell with var(command) rather than forking. If var(command) is a shell builtin command or a shell function, @@ -1633,6 +1634,7 @@ cancels both tt(-p) and tt(-u). The tt(-c) or tt(-l) flags cancel any and all of tt(-kpquz). ) +findex(readonly) cindex(parameters, marking readonly) item(tt(readonly))( Same as tt(typeset -r). With the tt(POSIX_BUILTINS) option set, same @@ -2413,6 +2415,7 @@ the mask is printed as an octal number. Note that in the symbolic form the permissions you specify are those which are to be allowed (not denied) to the users specified. ) +findex(unalias) cindex(aliases, removing) item(tt(unalias) [ tt(-ams) ] var(name) ...)( Removes aliases. This command works the same as tt(unhash -a), except that -- cgit v1.2.3 From 618f842b464c090f4a6fb02a9f4d217be9270466 Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Tue, 5 Dec 2023 18:49:42 +0100 Subject: 52326, 52372: add -q option to kill for sigqueue --- ChangeLog | 3 +++ Completion/Zsh/Command/_kill | 1 + Doc/Zsh/builtins.yo | 5 ++++- Src/jobs.c | 36 ++++++++++++++++++++++++++++++++++-- configure.ac | 1 + 5 files changed, 43 insertions(+), 3 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index aa995e0d2..cd3cd4e84 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2023-12-05 Oliver Kiddle + * 52326, 52372: configure.ac, Src/jobs.c, Doc/Zsh/builtins.yo, + Completion/Zsh/Command/_kill: add -q option to kill for sigqueue + * 52373: Completion/Base/Utility/_numbers, Completion/Solaris/Command/_dumpadm, Completion/Unix/Command/_xz: fix _numbers for suffixes containing % and update affected functions diff --git a/Completion/Zsh/Command/_kill b/Completion/Zsh/Command/_kill index b9dfde3f0..084cf42c8 100644 --- a/Completion/Zsh/Command/_kill +++ b/Completion/Zsh/Command/_kill @@ -5,6 +5,7 @@ typeset -A opt_args _arguments -C \ '(-s -l 1)-n[specify signal number]:signal number' \ + '(-l)-q[send the specified integer with the signal using sigqueue]:value' \ '(-n -l 1)-s[specify signal name]:signal:_signals -s' \ '(-n -s)-l[list signal names or numbers of specified signals]:*:signal:_signals' \ '(-n -s -l)1::signal:_signals -p -s' \ diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index cab265a25..1aa807633 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -1143,7 +1143,7 @@ findex(kill) cindex(killing jobs) cindex(jobs, killing) xitem(tt(kill) [ tt(-s) var(signal_name) | tt(-n) var(signal_number) | \ -tt(-)var(sig) ] var(job) ...) +tt(-)var(sig) ] [ tt(-q) var(value) ] var(job) ...) item(tt(kill) tt(-l) [ var(sig) ... ])( Sends either tt(SIGTERM) or the specified signal to the given jobs or processes. @@ -1170,6 +1170,9 @@ tt(kill -IO) and tt(kill -POLL) have the same effect. Many systems will allow process IDs to be negative to kill a process group or zero to kill the current process group. + +The tt(-q) option allows an integer value to be sent with the signal +on systems that support tt(sigqueue+LPAR()RPAR()). ) findex(let) item(tt(let) var(arg) ...)( diff --git a/Src/jobs.c b/Src/jobs.c index a3b9f667a..4e9767ee4 100644 --- a/Src/jobs.c +++ b/Src/jobs.c @@ -2677,9 +2677,35 @@ bin_kill(char *nam, char **argv, UNUSED(Options ops), UNUSED(int func)) { int sig = SIGTERM; int returnval = 0; +#ifdef HAVE_SIGQUEUE + union sigval sigqueue_info; +#endif + int use_sigqueue = 0, got_sig = 0; + + while (*argv && **argv == '-') { + if (!use_sigqueue && (*argv)[1] == 'q' && (*argv)[2] == '\0') { + char *endp; + + if (!*++argv) { + zwarnnam(nam, "-q: argument expected"); + return 1; + } +#ifdef HAVE_SIGQUEUE + sigqueue_info.sival_int = +#endif + zstrtol(*argv, &endp, 10); + if (*endp) { + zwarnnam(nam, "invalid number: %s", *argv); + return 1; + } + use_sigqueue = 1; + argv++; + continue; + } + if (got_sig) + break; /* check for, and interpret, a signal specifier */ - if (*argv && **argv == '-') { if (idigit((*argv)[1])) { char *endp; /* signal specified by number */ @@ -2796,6 +2822,7 @@ bin_kill(char *nam, char **argv, UNUSED(Options ops), UNUSED(int func)) } } argv++; + got_sig = 1; } /* Discard the standard "-" and "--" option breaks */ @@ -2844,7 +2871,12 @@ bin_kill(char *nam, char **argv, UNUSED(Options ops), UNUSED(int func)) returnval++; } else { int pid = atoi(*argv); - if (kill(pid, sig) == -1) { + if ( +#ifdef HAVE_SIGQUEUE + use_sigqueue ? sigqueue(pid, sig, sigqueue_info) : +#endif + kill(pid, sig) == -1) + { zwarnnam("kill", "kill %s failed: %e", *argv, errno); returnval++; } diff --git a/configure.ac b/configure.ac index c5263035e..9cb6e032b 100644 --- a/configure.ac +++ b/configure.ac @@ -1299,6 +1299,7 @@ AC_CHECK_FUNCS(strftime strptime mktime timelocal \ mkfifo _mktemp mkstemp \ waitpid wait3 \ sigaction sigblock sighold sigrelse sigsetmask sigprocmask \ + sigqueue \ killpg setpgid setpgrp tcsetpgrp tcgetattr nice \ gethostname gethostbyname2 getipnodebyname \ inet_aton inet_pton inet_ntop \ -- cgit v1.2.3 From d6e4ddd4d48b6ac9c0a29b95e0e2fc0e6012d725 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Fri, 5 Jan 2024 20:38:58 -0800 Subject: 52465: use NULL_GLOB when expanding zmv input pattern to avoid NOMATCH exit --- ChangeLog | 5 +++++ Doc/Zsh/contrib.yo | 8 ++++---- Functions/Misc/zmv | 8 +++++++- 3 files changed, 16 insertions(+), 5 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index e7b785c7f..1c7e7786f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2024-01-05 Bart Schaefer + + * 52465: Doc/Zsh/contrib.yo, Functions/Misc/zmv: use NULL_GLOB + when expanding the input pattern to avoid NOMATCH exit + 2023-12-06 Jun-ichi Takimoto * 52413: Completion/Unix/Command/_iconv: support Citrus version diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index f43ac2257..e1781a5e1 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -4668,10 +4668,10 @@ renames `tt(foo.lis)' to `tt(foo.txt)', `tt(my.old.stuff.lis)' to The pattern is always treated as an tt(EXTENDED_GLOB) pattern. Any file whose name is not changed by the substitution is simply ignored. Any -error (a substitution resulted in an empty string, two substitutions gave -the same result, the destination was an existing regular file and tt(-f) -was not given) causes the entire function to abort without doing -anything. +error (no files matched the var(srcpat), substitution resulted in an empty +string, two substitutions gave the same result, the destination was an +existing regular file and tt(-f) was not given) causes the entire function +to abort without doing anything. In addition to pattern replacement, the variable tt($f) can be referred to in the second (replacement) argument. This makes it possible to diff --git a/Functions/Misc/zmv b/Functions/Misc/zmv index 269fe5ba5..5c03e9ea1 100644 --- a/Functions/Misc/zmv +++ b/Functions/Misc/zmv @@ -236,12 +236,18 @@ if [[ $pat = (#b)(*)\((\*\*##/)\)(*) ]]; then else fpat=$pat fi -files=(${~fpat}) [[ -n $hasglobqual ]] && pat=$opat errs=() +() { + # (#qN) breaks bareglobqual -Q option, so: + setopt localoptions nullglob + files=(${~fpat}) +} +(( ${#files} )) || errs=( "no files matched \`$fpat'" ) + for f in $files; do if [[ $pat = (#b)(*)\(\*\*##/\)(*) ]]; then # This looks like a recursive glob. This isn't good enough, -- cgit v1.2.3 From c72b4a74ef3e5031a72cab13d7d6365e8d7ef0fc Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Wed, 24 Jan 2024 17:32:45 -0800 Subject: 52473: zstyle -q for testing existence of a zstyle setting --- ChangeLog | 2 ++ Doc/Zsh/mod_zutil.yo | 7 +++++++ Src/Modules/zutil.c | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index b64c62830..7b30da246 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,7 @@ 2024-01-24 Bart Schaefer + * 52473: Doc/Zsh/mod_zutil.yo, Src/Modules/zutil.c: zstyle -q + * 52468: Src/builtin.c, Src/utils.c: save and restore state of correct TTY when using read -s / -d diff --git a/Doc/Zsh/mod_zutil.yo b/Doc/Zsh/mod_zutil.yo index 3cf9e5028..9946618d6 100644 --- a/Doc/Zsh/mod_zutil.yo +++ b/Doc/Zsh/mod_zutil.yo @@ -11,6 +11,7 @@ xitem(tt(zstyle) [ tt(-L) [ var(metapattern) [ var(style) ] ] ]) xitem(tt(zstyle) [ tt(-e) | tt(-) | tt(-)tt(-) ] var(pattern) var(style) var(string) ...) xitem(tt(zstyle -d) [ var(pattern) [ var(style) ... ] ]) xitem(tt(zstyle -g) var(name) [ var(pattern) [ var(style) ] ]) +xitem(tt(zstyle -q) var(context) var(style)) xitem(tt(zstyle -){tt(a)|tt(b)|tt(s)} var(context) var(style) var(name) [ var(sep) ]) xitem(tt(zstyle -){tt(T)|tt(t)} var(context) var(style) [ var(string) ... ]) item(tt(zstyle -m) var(context) var(style) var(pattern))( @@ -105,6 +106,12 @@ enditem() The other forms can be used to look up or test styles for a given context. startitem() +item(tt(zstyle -q) var(context) var(style))( +Return tt(0) if var(style) is defined in var(context). This does not +evaluate expressions defined by tt(zstyle -e) and does not examine any +values set by var(style). The expected use is to test whether a style +has been defined for var(context) before asserting a new style. +) item(tt(zstyle -s) var(context) var(style) var(name) [ var(sep) ])( The parameter var(name) is set to the value of the style interpreted as a string. If the value contains several strings they are concatenated with diff --git a/Src/Modules/zutil.c b/Src/Modules/zutil.c index 8b863d5c8..293a62dcf 100644 --- a/Src/Modules/zutil.c +++ b/Src/Modules/zutil.c @@ -461,6 +461,28 @@ lookupstyle(char *ctxt, char *style) return found; } +static int +testforstyle(char *ctxt, char *style) +{ + Style s; + Stypat p; + int found = 0; + + s = (Style)zstyletab->getnode2(zstyletab, style); + if (s) { + MatchData match; + savematch(&match); + for (p = s->pats; p; p = p->next) + if (pattry(p->prog, ctxt)) { + found = 1; + break; + } + restorematch(&match); + } + + return !found; /* 0 == success */ +} + static int bin_zstyle(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) { @@ -570,6 +592,7 @@ bin_zstyle(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) case 't': min = 2; max = -1; break; case 'T': min = 2; max = -1; break; case 'm': min = 3; max = 3; break; + case 'q': min = 2; max = 2; break; case 'g': min = 1; max = 3; break; default: zwarnnam(nam, "invalid option: %s", args[0]); @@ -723,6 +746,15 @@ bin_zstyle(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) return 1; } break; + case 'q': + { + int success; + queue_signals(); /* Protect PAT_STATIC */ + success = testforstyle(args[1], args[2]); + unqueue_signals(); + return success; + } + break; case 'g': { int ret = 1; -- cgit v1.2.3 From 2a538491ebdaaf41ad07e5b7a4d4d994faafb355 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Wed, 24 Jan 2024 18:06:44 -0800 Subject: 52496 + 52377: clarify SPROMPT behavior when CORRECT_ALL is set --- ChangeLog | 3 +++ Doc/Zsh/options.yo | 8 +++++++- Doc/Zsh/params.yo | 10 +++++++--- 3 files changed, 17 insertions(+), 4 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index b876cb6d1..ecb883c75 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2024-01-24 Bart Schaefer + * 52496 + 52377: Doc/Zsh/options.yo, Doc/Zsh/params.yo: clarify + SPROMPT behavior when CORRECT_ALL is set + * 52492: Src/math.c: prevent indexing error when using recursive arithmetic in array subscript (operator stops on operand error) diff --git a/Doc/Zsh/options.yo b/Doc/Zsh/options.yo index cbd3d0f8e..c3af8dd33 100644 --- a/Doc/Zsh/options.yo +++ b/Doc/Zsh/options.yo @@ -1214,6 +1214,9 @@ Note that, when the tt(HASH_LIST_ALL) option is not set or when some directories in the path are not readable, this may falsely report spelling errors the first time some commands are used. +Refer to the shell variable tt(SPROMPT) for an explanation of the +`tt([nyae])' (no/yes/abort/edit) prompt that is offered. + The shell variable tt(CORRECT_IGNORE) may be set to a pattern to match words that will never be offered as corrections. ) @@ -1222,7 +1225,10 @@ pindex(NO_CORRECT_ALL) pindex(CORRECTALL) pindex(NOCORRECTALL) item(tt(CORRECT_ALL) (tt(-O)))( -Try to correct the spelling of all arguments in a line. +Try to correct the spelling of all arguments in a line, in order from +left to right, treating each as a file name. Answering `tt(a)' or +`tt(e)' at any prompt stops all corrections, otherwise every correction +is prompted for. The shell variable tt(CORRECT_IGNORE_FILE) may be set to a pattern to match file names that will never be offered as corrections. diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index 68df4a16f..a6fbe6723 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -1635,9 +1635,13 @@ All other prompt escapes are also allowed. The actions available at the prompt are tt([nyae]): startsitem() -sitem(tt(n) +LPAR()`no'+RPAR() +LPAR()default+RPAR())(Discard the correction and run the command.) -sitem(tt(y) +LPAR()`yes'+RPAR())(Make the correction and run the command.) -sitem(tt(a) +LPAR()`abort'+RPAR())(Discard the entire command line without running it.) +sitem(tt(n) +LPAR()`no'+RPAR() +LPAR()default+RPAR())(Discard the correction. +If there are no more corrections, accept the command line, else (with +tt(CORRECT_ALL)) prompt for the next.) +sitem(tt(y) +LPAR()`yes'+RPAR())(Make the correction. If there are no more +corrections, accept the command line.) +sitem(tt(a) +LPAR()`abort'+RPAR())(Place the entire command line in the +history for later edit, but without accepting it.) sitem(tt(e) +LPAR()`edit'+RPAR())(Resume editing the command line.) endsitem() ) -- cgit v1.2.3 From 18400b68e49b242da55ca3a465ea496d26f47938 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sun, 28 Jan 2024 18:22:36 -0800 Subject: 52510: document how ${ ... } et al. affect use of "private"; add index entries --- ChangeLog | 3 +++ Doc/Zsh/expn.yo | 3 +++ Doc/Zsh/mod_private.yo | 3 +++ 3 files changed, 9 insertions(+) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 6c52ccd22..46cd45647 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2024-01-28 Bart Schaefer + * 52510: Doc/Zsh/expn.yo, Doc/Zsh/mod_private.yo: document how + ${ ... } et al. affect use of "private"; add index entries + * 52509: configure.ac, Src/utils.c: manage internals of stdio objects when performing redirections. diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index e5506d469..2acfd08c9 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -1915,6 +1915,9 @@ from the final replacement in this case, and it is subject to filename generation in the same way as `tt($LPAR())...tt(RPAR())' but is em(not) split on tt(IFS) unless the tt(SH_WORD_SPLIT) option is set. +cindex(substitution, command, current shell) +cindex(substitution, command, non forking) +cindex(substitution, nofork) Substitutions of the form `tt(${|)var(param)tt(|)...tt(})' are similar, except that the substitution is replaced by the value of the parameter named by var(param). No implicit save or restore applies to var(param) diff --git a/Doc/Zsh/mod_private.yo b/Doc/Zsh/mod_private.yo index 184fa2be8..69a5f58be 100644 --- a/Doc/Zsh/mod_private.yo +++ b/Doc/Zsh/mod_private.yo @@ -84,6 +84,9 @@ created outside the local scope when it was not previously declared.) itemiz(An exported private remains in the environment of inner scopes but appears unset for the current shell in those scopes. Generally, exporting private parameters should be avoided.) +itemiz(Declaring a private parameter in a current shell command substitution +such as `tt(${ )...tt( })' limits the parameter to the scope of the command +substitution, just as if the parameter were declared in a function.) enditemize() Note that this differs from the static scope defined by compiled languages -- cgit v1.2.3 From 8801665e5b241c3adac9c36b6135d057c5ab2a59 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 3 Feb 2024 12:07:14 -0800 Subject: 52513: fixes and doc for using nofork substitutions with private parameters Also fixes a crash bug with {fd}>&N redirections and private parameters --- ChangeLog | 7 +++ Doc/Zsh/mod_private.yo | 9 +++- Src/Modules/param_private.c | 53 ++++++++++++-------- Src/params.c | 17 +++++-- Test/V10private.ztst | 118 +++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 170 insertions(+), 34 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 46cd45647..fbfae8589 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2024-02-03 Bart Schaefer + + * 52513: Doc/Zsh/mod_private.yo, Src/Modules/param_private.c, + Src/params.c, Test/v10private.ztst: nofork substitutions can + use private parameters; fix crash bug on {privateFD}>&N; add + tests and documentation + 2024-01-28 Bart Schaefer * 52510: Doc/Zsh/expn.yo, Doc/Zsh/mod_private.yo: document how diff --git a/Doc/Zsh/mod_private.yo b/Doc/Zsh/mod_private.yo index 69a5f58be..08ac4cafe 100644 --- a/Doc/Zsh/mod_private.yo +++ b/Doc/Zsh/mod_private.yo @@ -84,9 +84,14 @@ created outside the local scope when it was not previously declared.) itemiz(An exported private remains in the environment of inner scopes but appears unset for the current shell in those scopes. Generally, exporting private parameters should be avoided.) +itemiz(Current shell command substitutions such as `tt(${|)...tt(})', +`tt(${|)var(var)tt(|)...tt(})' and `tt(${ )...tt( })' may read and assign +private parameters from the enclosing function.) itemiz(Declaring a private parameter in a current shell command substitution -such as `tt(${ )...tt( })' limits the parameter to the scope of the command -substitution, just as if the parameter were declared in a function.) +limits that parameter to the scope of the command substitution, just as if +the parameter were declared in a function. This also prevents access by +any enclosed current shell command substitutions, but other substitutions +may use the private parameter because those have the same calling scope.) enditemize() Note that this differs from the static scope defined by compiled languages diff --git a/Src/Modules/param_private.c b/Src/Modules/param_private.c index 7ef6633da..5003d4627 100644 --- a/Src/Modules/param_private.c +++ b/Src/Modules/param_private.c @@ -298,7 +298,7 @@ pps_setfn(Param pm, char *x) { struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.s); GsuScalar gsu = (GsuScalar)(c->g); - if (locallevel == pm->level) + if (locallevel == pm->level || locallevel > private_wraplevel) gsu->setfn(pm, x); else setfn_error(pm); @@ -338,7 +338,7 @@ ppi_setfn(Param pm, zlong x) { struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.i); GsuInteger gsu = (GsuInteger)(c->g); - if (locallevel == pm->level) + if (locallevel == pm->level || locallevel > private_wraplevel) gsu->setfn(pm, x); else setfn_error(pm); @@ -378,7 +378,7 @@ ppf_setfn(Param pm, double x) { struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.f); GsuFloat gsu = (GsuFloat)(c->g); - if (locallevel == pm->level) + if (locallevel == pm->level || locallevel > private_wraplevel) gsu->setfn(pm, x); else setfn_error(pm); @@ -419,7 +419,7 @@ ppa_setfn(Param pm, char **x) { struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.a); GsuArray gsu = (GsuArray)(c->g); - if (locallevel == pm->level) + if (locallevel == pm->level || locallevel > private_wraplevel) gsu->setfn(pm, x); else setfn_error(pm); @@ -461,7 +461,7 @@ pph_setfn(Param pm, HashTable x) { struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.h); GsuHash gsu = (GsuHash)(c->g); - if (locallevel == pm->level) + if (locallevel == pm->level || locallevel > private_wraplevel) gsu->setfn(pm, x); else setfn_error(pm); @@ -539,19 +539,20 @@ static struct funcwrap wrapper[] = { WRAPDEF(wrap_private) }; +/**/ +static int private_wraplevel = 0; + /**/ static int wrap_private(Eprog prog, FuncWrap w, char *name) { - static int wraplevel = 0; - - if (wraplevel < locallevel /* && strcmp(name, "(anon)") != 0 */) { - int owl = wraplevel; - wraplevel = locallevel; + if (private_wraplevel < locallevel /* && strcmp(name, "(anon)") != 0 */) { + int owl = private_wraplevel; + private_wraplevel = locallevel; scanhashtable(paramtab, 0, 0, 0, scopeprivate, PM_UNSET); runshfunc(prog, w, name); scanhashtable(paramtab, 0, 0, 0, scopeprivate, 0); - wraplevel = owl; + private_wraplevel = owl; return 0; } return 1; @@ -573,22 +574,32 @@ getprivatenode(HashTable ht, const char *nam) pm = (Param) hn; /* how would an autoloaded private behave? return here? */ } - while (!fakelevel && pm && locallevel > pm->level && is_private(pm)) { + while (!fakelevel && pm && is_private(pm) && locallevel > pm->level) { + if (pm->level == private_wraplevel + 1) { + /* Variable is in the current function scope */ + break; + } +#if 0 if (!(pm->node.flags & PM_UNSET)) { /* * private parameters are always marked PM_UNSET before we - * increment locallevel, so the only way we get here is - * when createparam() wants a new parameter that is not at - * the current locallevel and it has therefore cleared the - * PM_UNSET flag. + * increment locallevel, so there are three possible ways + * to get here: + * 1) createparam() wants a new parameter that is not at + * the current locallevel and it has therefore cleared the + * PM_UNSET flag + * 2) locallevel has been incremented (startparamscope()) + * outside the usual function call stack (private_wraplevel) + * 3) dynamic scoping is fetching a value from a surrounding + * scope, we don't know if that's for assign or just expand + * The first of those is now caught in createparam() when + * testing PM_RO_BY_DESIGN and the second occurs only in + * nofork substitution or handling of ZLE specials. If the + * third is an assignment, the GSU setfn rejects it. */ DPUTS(pm->old, "BUG: PM_UNSET cleared in wrong scope"); - setfn_error(pm); - /* - * TODO: instead of throwing an error here, create a global - * parameter, insert as pm->old, handle WARN_CREATE_GLOBAL. - */ } +#endif pm = pm->old; } diff --git a/Src/params.c b/Src/params.c index 9f0cbcd67..a722a20f6 100644 --- a/Src/params.c +++ b/Src/params.c @@ -1049,7 +1049,7 @@ createparam(char *name, int flags) /* POSIXBUILTINS horror: we need to retain 'export' flags */ (isset(POSIXBUILTINS) && (oldpm->node.flags & PM_EXPORTED))) { if (oldpm->node.flags & PM_RO_BY_DESIGN) { - zerr("%s: can't change parameter attribute", + zerr("%s: can't modify read-only parameter", name); return NULL; } @@ -3615,9 +3615,18 @@ assignnparam(char *s, mnumber val, int flags) pm = createparam(t, ss ? PM_ARRAY : isset(POSIXIDENTIFIERS) ? PM_SCALAR : (val.type & MN_INTEGER) ? PM_INTEGER : PM_FFLOAT); - if (!pm) - pm = (Param) paramtab->getnode(paramtab, t); - DPUTS(!pm, "BUG: parameter not created"); + if (errflag) { + /* assume error message already output */ + unqueue_signals(); + return NULL; + } + if (!pm && !(pm = (Param) paramtab->getnode(paramtab, t))) { + DPUTS(!pm, "BUG: parameter not created"); + if (!errflag) + zerr("%s: parameter not found", t); + unqueue_signals(); + return NULL; + } if (ss) { *ss = '['; } else if (val.type & MN_INTEGER) { diff --git a/Test/V10private.ztst b/Test/V10private.ztst index d902cac56..9eeda0f47 100644 --- a/Test/V10private.ztst +++ b/Test/V10private.ztst @@ -10,6 +10,8 @@ sed -e 's,# test_zsh_param_private,zmodload zsh/param/private,' < $ZTST_srcdir/B02typeset.ztst > private.TMP/B02 fi + setopt TYPESET_TO_UNSET + %test (zmodload -u zsh/param/private && zmodload zsh/param/private) @@ -246,7 +248,7 @@ F:note "typeset" rather than "private" in output from outer 1:privates are not visible in anonymous functions, part 3 >X top level >array_test not set -?(anon):4: array_test: can't change parameter attribute +?(anon):4: array_test: can't modify read-only parameter F:future revision will create a global with this assignment typeset -a array_test @@ -311,11 +313,30 @@ F:future revision will create a global with this assignment () { typeset -n ptr1=ptr2 - private -n ptr2 + private -n ptr2 # TYPESET_TO_UNSET makes this not a "placeholder" typeset -p ptr1 ptr2 typeset val=LOCAL () { - ptr1=val + ptr1=val # Test dies here as ptr2 is private and unset + typeset -n + printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2" + } + typeset -p ptr1 ptr2 + } + typeset -p ptr2 +1:up-reference for private namerefs, end unset and not in scope +F:See K01nameref.ztst up-reference part 5 +F:Here ptr1 finds private ptr2 by scope mismatch +>typeset -n ptr1=ptr2 +*?*read-only variable: ptr2 + + () { + typeset -n ptr1=ptr2 + private -n ptr2= # Assignment makes this a placeholder, not unset + typeset -p ptr1 ptr2 + typeset val=LOCAL + () { + ptr1=val # This is a silent no-op, why? typeset -n printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2" } @@ -323,8 +344,9 @@ F:future revision will create a global with this assignment } typeset -p ptr2 1:up-reference for private namerefs, end not in scope -F:See K01typeset.ztst up-reference part 5 -F:Here ptr1 finds private ptr2 by scope mismatch, assignment silently fails +F:See K01nameref.ztst up-reference part 5 +F:Here ptr1 finds private ptr2 by scope mismatch +F:Assignment silently fails, is that correct? >typeset -n ptr1=ptr2 >ptr1=ptr2 >ptr1= @@ -335,11 +357,11 @@ F:Here ptr1 finds private ptr2 by scope mismatch, assignment silently fails typeset ptr2 () { typeset -n ptr1=ptr2 - private -n ptr2 + private -n ptr2 # Set/unset is irrelevant, not referenced typeset -p ptr1 ptr2 typeset val=LOCAL () { - ptr1=val; + ptr1=val typeset -n printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2" } @@ -401,6 +423,88 @@ F:Should we allow "public" namerefs to private parameters? 1:private may not change parameter type ?(anon):private:2: can't change type of private param: x + () { + private fd1 fd2 + exec {fd1}>&1 + print OK + () { exec {fd2}>&2 } + print BAD $fd2 + } +1:redirection cannot assign private in wrong scope +F:Better if caught in checkclobberparam() but exec.c doesn't know scope +>OK +?(anon): fd2: can't modify read-only parameter + + () { + private z=outer + print ${(t)z} $z + print ${| + print ${(t)z} $z + REPLY=$z + } + } +0:nofork may read private in calling function +>scalar-local-hide-special outer +>scalar-local-hide-special outer +>outer + + () { + private z=outer + print ${(t)z} $z + print ${| REPLY=${|z| z=nofork} } + print ${(t)z} $z + } +0:nofork may write to private in calling function +>scalar-local-hide-special outer +>nofork +>scalar-local-hide-special nofork + + () { + local q=outer + print ${| + private q=nofork + REPLY=${| REPLY=$q} + } + } +0:nofork cannot see private in surrounding nofork +>outer + + () { + private z=outer + print ${(t)z} $z + print ${|z| + private q + z=${|q| q=nofork} + } + print ${(t)z} $z + } +1:nofork may not change private in surrounding nofork +>scalar-local-hide-special outer +*?*: q: can't modify read-only parameter + + () { + private q=outer + print ${| + () { REPLY="{$q}" } + } + print ${|q| + () { q=nofork } + } + } +1:function may not access private from inside nofork +>{} +*?*: q: can't modify read-only parameter + + () { + print ${| + private q + () { q=nofork } + } + } +1:function may not access private declared in nofork +*?*: q: can't modify read-only parameter + %clean + unsetopt TYPESET_TO_UNSET rm -r private.TMP -- cgit v1.2.3 From 791aaf88cca53036d19ad2e247c783744ccf3837 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sun, 4 Feb 2024 20:46:49 -0800 Subject: cf. users/29635: additional detail of parameter expansion in math context. --- ChangeLog | 3 +++ Doc/Zsh/arith.yo | 5 +++++ 2 files changed, 8 insertions(+) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index c553b02cf..964eeef23 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2024-02-04 Bart Schaefer + * unposted (cf. users/29635): Doc/Zsh/arith.yo: additional detail + of parameter expansion in math context. + * 52521: Etc/BUGS: mapfile handling of empty/unreadable files 2024-02-04 Mikael Magnusson diff --git a/Doc/Zsh/arith.yo b/Doc/Zsh/arith.yo index bc3e35ad5..9f5298821 100644 --- a/Doc/Zsh/arith.yo +++ b/Doc/Zsh/arith.yo @@ -206,6 +206,11 @@ example, example(((val2 = val1 * 2))) assigns twice the value of tt($val1) to the parameter named tt(val2). +If the expansion of tt($val1) is text rather than a number, then when +tt(val1) is referenced that text is itself evaluated as a math expression +as if surrounded by parentheses `tt(LPAR()$val1)tt(RPAR())'. Expansion +continues until there are no more parameter references, a number has +resulted, or an expression error occurs. An internal integer representation of a named parameter can be specified with the tt(integer) builtin. -- cgit v1.2.3 From 173c0b14ab9ad1f54e74a2573d8212a720df1762 Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Thu, 15 Feb 2024 14:50:56 +0100 Subject: 52535: documentation for highlight groups and layers --- ChangeLog | 3 +++ Doc/Makefile.in | 3 ++- Doc/Zsh/mod_hlgroup.yo | 25 +++++++++++++++++++++++++ Doc/Zsh/prompt.yo | 8 ++++++++ Doc/Zsh/zle.yo | 11 +++++++++++ NEWS | 6 ++++++ 6 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 Doc/Zsh/mod_hlgroup.yo (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 6d064eaf6..3cbf7d699 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2024-02-15 Oliver Kiddle + * 52535: Doc/Makefile.in, Doc/Zsh/prompt.yo, Doc/Zsh/zle.yo, + Doc/Zsh/mod_hlgroup.yo, NEWS: documentation for highlight groups + * 52533: Src/Modules/hlgroup.c, Src/Modules/hlgroup.mdd, Src/prompt.c: add module to provide alternate readonly views of the content of .zle.hlgroups diff --git a/Doc/Makefile.in b/Doc/Makefile.in index dabe11fe3..d9be182e9 100644 --- a/Doc/Makefile.in +++ b/Doc/Makefile.in @@ -63,7 +63,8 @@ Zsh/mod_attr.yo Zsh/mod_cap.yo Zsh/mod_clone.yo \ Zsh/mod_compctl.yo Zsh/mod_complete.yo Zsh/mod_complist.yo \ Zsh/mod_computil.yo Zsh/mod_curses.yo \ Zsh/mod_datetime.yo Zsh/mod_db_gdbm.yo Zsh/mod_deltochar.yo \ -Zsh/mod_example.yo Zsh/mod_files.yo Zsh/mod_langinfo.yo \ +Zsh/mod_example.yo Zsh/mod_files.yo \ +Zsh/mod_hlgroup.yo Zsh/mod_langinfo.yo \ Zsh/mod_ksh93.yo Zsh/mod_mapfile.yo Zsh/mod_mathfunc.yo \ Zsh/mod_nearcolor.yo Zsh/mod_newuser.yo \ Zsh/mod_parameter.yo Zsh/mod_pcre.yo Zsh/mod_private.yo \ diff --git a/Doc/Zsh/mod_hlgroup.yo b/Doc/Zsh/mod_hlgroup.yo new file mode 100644 index 000000000..efe8934a1 --- /dev/null +++ b/Doc/Zsh/mod_hlgroup.yo @@ -0,0 +1,25 @@ +COMMENT(!MOD!zsh/hlgroup +Alternative views of highlighting groups +!MOD!) +The tt(zsh/hlgroup) module defines special parameters that represent +highlighting groups in different forms to ease the use of the groups when +configuring other tools. + +In each case, these are readonly associative arrays where accessing elements +uses values from the underlying tt(.zle.hlgroups) variable. + +startitem() +vindex(.zle.esc) +item(tt(.zle.esc))( +This associative array contains the literal escape sequences used to apply the +highlighting for each group. An example use would be when setting the +tt(LESS_TERMCAP_xx) environment variables for the tt(less) pager. +) +vindex(.zle.sgr) +item(tt(.zle.sgr))( +Where highlighting makes use of CSI escape sequences, this parameter contains +the "Select Graphic Rendition" number sequence. This is useful with, for +example the tt(GREP_COLORS) and tt(LSCOLORS) environment variables and the +tt(list-colors) style. +) +enditem() diff --git a/Doc/Zsh/prompt.yo b/Doc/Zsh/prompt.yo index 909012c8e..de988ab7c 100644 --- a/Doc/Zsh/prompt.yo +++ b/Doc/Zsh/prompt.yo @@ -246,6 +246,14 @@ item(tt(%K) LPAR()tt(%k)RPAR())( Start (stop) using a different bacKground colour. The syntax is identical to that for tt(%F) and tt(%f). ) +item(tt(%H))( +Change all character visual attributes using a highlighting specification from +the tt(.zle.hlgroups) associative array. The key is specified in following +braces so, for example tt(%H{error}) will use the highlighting specification +for the `error' group. If the key is not found in the associative array then +it has no effect. Highlighting specifications are in the same format as for +the tt(zle_highlight) parameter. +) item(tt(%{)...tt(%}))( Include a string as a literal escape sequence. The string within the braces should not change the cursor diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo index 495bd86a8..31eb3f3ba 100644 --- a/Doc/Zsh/zle.yo +++ b/Doc/Zsh/zle.yo @@ -2807,6 +2807,17 @@ item(tt(italic))( The characters in the given context are shown in a italic font. Not all terminals support italic fonts. ) +item(tt(hl=)var(group))( +Use the specified highlighting group. The var(group) is used as a key into +the associative array tt(.zle.hlgroups) to determine the actual highlighting. +) +item(tt(layer=)var(layer))( +The layer is used to determine precedence when multiple highlighting regions +overlap. The var(layer) is a decimal integer, with higher numbers taking +precedence over lower numbers. The default layer is 10 with 30 used as the +default for tt(special), 20 for tt(region) and tt(isearch) and 15 for +tt(paste). +) enditem() The characters described above as `special' are as follows. The diff --git a/NEWS b/NEWS index 4d4699f7e..d0a8584e2 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,12 @@ Changes since 5.9 In region_highlight and zle_highlight, italic and faint can be specified as font attributes for terminals that support them. +Highlighting groups can be referenced in region_highlight and +zle_highlight for common attribute combinations and a layer can be +specified to indicate precedence where highlighted regions overlap. +Highlighting groups are also supported in the prompt via a new %H +prompt escape. + Ellipsis markers shown by the line editor to indicate where the line doesn't fit in the terminal can be highlighted. -- cgit v1.2.3 From fb9a7cc5dd07910afa7ebea174b379350cae4c91 Mon Sep 17 00:00:00 2001 From: midchildan Date: Sat, 3 Feb 2024 01:28:00 +0900 Subject: 52520: add new features and improvements to the "incarg" ZLE widget - Decrement integers without defining a new widget - Preserve the number of leading zeros - Increment binaries, octals, and hexadecimals - Move the cursor to the end of the incremented integer - Create a sequence of integers across terminal panes - Add a Vim variant - Also add tests --- ChangeLog | 4 + Doc/Zsh/contrib.yo | 30 ++++- Functions/Zle/incarg | 273 ++++++++++++++++++++++++++++++++----- Test/X05zleincarg.ztst | 360 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 630 insertions(+), 37 deletions(-) create mode 100644 Test/X05zleincarg.ztst (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 36029a2a8..faae11c80 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2024-02-15 Oliver Kiddle + * 52520: midchildan: Doc/Zsh/contrib.yo, Functions/Zle/incarg, + Test/X05zleincarg.ztst: add new features and improvements to the + "incarg" ZLE widget + * github #112: Poncho: Completion/Unix/Command/_todo.sh: Completion: todo.sh uses shorthelp and not showhelp diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index e1781a5e1..718686587 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -2620,12 +2620,30 @@ zle -N history-pattern-search-forward history-pattern-search) tindex(incarg) vindex(incarg, use of) item(tt(incarg))( -Typing the keystrokes for this widget with the cursor placed on or to the -left of an integer causes that integer to be incremented by one. With a -numeric argument, the number is incremented by the amount of the -argument (decremented if the numeric argument is negative). The shell -parameter tt(incarg) may be set to change the default increment to -something other than one. +This widget allows you to increment integers on the current line. In addition +to decimals, it can handle hexadecimals prefixed with tt(0x), binaries with +tt(0b), and octals with tt(0o). + +By default, the target integer will be incremented by one. With a numeric +argument, the integer is incremented by the amount of the argument. The shell +parameter tt(incarg) may be set to change the default increment to something +other than one. + +The behavior of this widget changes depending on the widget name. + +When the widget is named tt(incarg), the widget will increment an integer +placed under the cursor placed or just to the left of it. tt(decarg), on the +other hand, decrements the integer. When the name is prefixed with tt(vim-), +the cursor will jump to the nearest integer after the cursor before incrementing +it. + +There's also a tt(sync-) prefix that can be added to the widget name. This +variant is used for creating a sequence of numbers on split terminals with +synchronized key input. The first pane won't increment the integer at all, but +each pane after that will have the integer incremented once more than the +previous pane. It currently supports tmux and iTerm2. + +The prefixes tt(vim-) and tt(sync-) can be combined into tt(vim-sync-). example(bindkey '^X+' incarg) ) diff --git a/Functions/Zle/incarg b/Functions/Zle/incarg index cff0cfe4c..1131b148b 100644 --- a/Functions/Zle/incarg +++ b/Functions/Zle/incarg @@ -1,43 +1,254 @@ -# Shell function to increment an integer either under the cursor or just -# to the left of it. Use +emulate -L zsh + +# A ZLE widget to increment an integer. +# +# In addition to decimals, it can handle hexadecimals prefixed with "0x", +# binaries with "0b", and octals with "0o". +# +# By default, the target integer will be incremented by one. With a numeric +# argument, the integer is incremented by the amount of the argument. The shell +# parameter "incarg" may be set to change the default increment to something +# other than one. +# +# The behavior of this widget changes depending on how it is named. +# +# - incarg / decarg +# +# incarg will increment an integer either under the cursor or just to the left +# of it. decarg, on the other hand, will decrement it. +# +# For example, +# +# echo 41 +# ^^^ cursor anywhere here +# +# with incarg gives +# +# echo 42 +# ^ cursor will move here +# +# - sync-incarg / sync-decarg +# +# The sync- variant is used for creating a sequence of numbers on split +# terminals with synchronized key input. The first pane won't be incremented +# at all, but each pane after that will have the number incremented once more +# than the previous pane. +# +# Currently supports tmux and iTerm2. +# +# - vim-incarg / vim-decarg +# +# This behaves like Vim's CTRL-A / CTRL-X. It moves the cursor to the nearest +# number after the cursor and increments or decrements it. +# +# - vim-sync-incarg / vim-sync-decarg +# +# This combines the behavior of the vim- and sync- variants. It's inspired by +# Vim's g_CTRL-A / g_CTRL-X. +# +# Example Usage: +# # autoload -Uz incarg -# zle -N incarg -# bindkey "..." incarg -# to define it. For example, -# echo 41 -# ^^^ cursor anywhere here -# with incarg gives -# echo 42 -# with the cursor in the same place. -# -# A numeric argument gives a number other than 1 to add (may be negative). -# If you're going to do it a lot with one particular number, you can set -# the parameter incarg to that number (a numeric argument still takes -# precedence). +# for widget in vim-{,sync-}{inc,dec}arg; do +# zle -N "$widget" incarg +# done +# bindkey -a \ +# '^A' vim-incarg \ +# '^X' vim-decarg \ +# 'g^A' vim-sync-incarg \ +# 'g^X' vim-sync-decarg -emulate -L zsh -setopt extendedglob +setopt localoptions extended_glob +local match mbegin mend MATCH MBEGIN MEND i -local rrest lrest num +# find the number and determine the base +integer pos=$(( CURSOR + 1 )) base=0 -rrest=${RBUFFER##[0-9]#} -if [[ $RBUFFER = [0-9]* ]]; then - if [[ -z $rrest ]]; then - num=$RBUFFER - else - num=${RBUFFER[1,-$#rrest-1]} +# avoid miscalculating positions when cursor is at the end of the line +while (( pos > 0 )) && [[ "$BUFFER[pos]" == '' ]]; do + (( pos-- )) +done + +# check for a prefix (e.g., 0x) before the cursor +for (( i = 0; i < 2; i++ )); do + case "$BUFFER[1,pos]" in + *0[xX][0-9a-fA-F]##) base=16 ;; + *0[oO][0-7]##) base=8 ;; + *0[bB][01]##) base=2 ;; + *[1-9]) base=10 ;; + *0) ;; # there may be a prefix right after the cursor + *) + # the non-Vim variant looks right before the cursor too, but not after it + if [[ "$WIDGET" != vi* ]]; then + if (( i == 0 )); then + (( pos-- )) + continue + else + return 1 + fi + fi + ;; + esac + + break +done + +# check for a prefix on the cursor +if (( base == 0 && pos < $#BUFFER )); then + case "$BUFFER[1,pos+1]" in + *0[xX][0-9a-fA-F]) base=16; (( pos++ )) ;; + *0[oO][0-7]) base=8; (( pos++ )) ;; + *0[bB][01]) base=2; (( pos++ )) ;; + esac +fi + +if (( base == 0 )); then + if [[ "$WIDGET" == vi* ]]; then + # jump to the nearest number after the cursor + while [[ "$BUFFER[pos]" == [^0-9] ]]; do + (( pos++ )) + (( pos > $#BUFFER )) && return 1 + done fi + + # check for a prefix right after the cursor and jump right after it, if any + if (( pos <= 1 )) || [[ "$BUFFER[pos-1]" == [^0-9] ]]; then + case "$BUFFER[pos,-1]" in + 0[xX][0-9a-fA-F]*) base=16; (( pos += 2 )) ;; + 0[oO][0-7]*) base=8; (( pos += 2 )) ;; + 0[bB][01]*) base=2; (( pos += 2 )) ;; + esac + fi +fi + +if (( base == 0 )); then + base=10 fi -lrest=${LBUFFER%%[0-9]#} -if [[ $LBUFFER = *[0-9] ]]; then - if [[ -z $lrest ]]; then - num="$LBUFFER$num" +# find the start of the number +integer first="$pos" +case "$base" in + 10) + while [[ "$BUFFER[first-1]" == [0-9] ]]; do + (( first-- )) + done + if [[ $BUFFER[first-1] = - ]]; then + (( first-- )) + fi + ;; + 2) + while [[ "$BUFFER[first-1]" == [01] ]]; do + (( first-- )) + done + ;; + 8) + while [[ "$BUFFER[first-1]" == [0-7] ]]; do + (( first-- )) + done + ;; + 16) + while [[ "$BUFFER[first-1]" == [0-9a-fA-F] ]]; do + (( first-- )) + done + ;; +esac + +# find the end of the number +integer last="$pos" +case "$base" in + 10) + while [[ "$BUFFER[last+1]" == [0-9] ]]; do + (( last++ )) + done + ;; + 2) + while [[ "$BUFFER[last+1]" == [01] ]]; do + (( last++ )) + done + ;; + 8) + while [[ "$BUFFER[last+1]" == [0-7] ]]; do + (( last++ )) + done + ;; + 16) + while [[ "$BUFFER[last+1]" == [0-9a-fA-F] ]]; do + (( last++ )) + done + ;; +esac + +# calculate the number of digits +integer ndigits=0 +case "$BUFFER[first,first+1]" in + 0*|-0) ndigits=$(( last - first + 1 )) ;; +esac + +# determine the amount to increment +integer delta=${NUMERIC:-${incarg:-1}} +if [[ "$WIDGET" = *decarg ]]; then + (( delta = -delta )) +fi +if [[ "$WIDGET" = *sync-* ]]; then + integer pane_index=0 + if [[ -n "$TMUX_PANE" ]]; then + pane_index="$(tmux display-message -pt "$TMUX_PANE" '#{pane_index}')" + elif [[ "$ITERM_SESSION_ID" =~ '^w[0-9]+t[0-9]+p([0-9]+)' ]]; then + pane_index="$match[1]" else - num="${LBUFFER[$#lrest+1,-1]}$num" + zle -M "[$WIDGET] unsupported terminal" + return 1 + fi + (( delta *= pane_index )) +fi + +local old="$BUFFER[first,last]" +integer oldlen=$#BUFFER + +local fmt1 fmt2 +case "$base" in + 10) fmt1=d; fmt2='#10' ;; + 2) fmt1=s; fmt2='##2' ;; + 8) fmt1=s; fmt2='##8' ;; + 16) fmt1="$BUFFER[first-1]"; fmt2='#16' ;; +esac + +local raw_result padded +raw_result="$( \ + printf "%0$ndigits$fmt1" $(( [$fmt2] "$base#$old" + delta )) 2> /dev/null)" +padded="${raw_result// /0}" + +integer oldnum="$base#$old" newnum="$base#$padded" 2> /dev/null +if (( base != 10 && newnum < 0 + || delta > 0 && newnum < oldnum + || delta < 0 && newnum > oldnum )); then + zle -M "[$WIDGET] The resulting number is either too big or too small." + return 1 +fi + +# adjust the number of leading zeros if the sign of the integer changed +local new +if (( base == 10 && ndigits == $#padded )); then + if (( oldnum < 0 && newnum >= 0 )); then + new="${padded#0}" + elif (( oldnum >= 0 && newnum < 0 )); then + new="-0${padded#-}" fi fi +if [[ -z "$new" ]]; then + new="$padded" +fi + +if zstyle -t ":zle:$WIDGET" debug; then + zle -M "[$WIDGET] base: $base delta: $delta old: '$old' new: '$new'" +fi + +BUFFER[first,last]="$new" -[[ -n $num ]] && (( num += ${NUMERIC:-${incarg:-1}} )) +integer offset=0 +if [[ "$WIDGET" == vi* ]]; then + offset=-1 +fi +(( CURSOR = last + $#BUFFER - oldlen + offset )) -BUFFER="$lrest$num$rrest" +return 0 diff --git a/Test/X05zleincarg.ztst b/Test/X05zleincarg.ztst new file mode 100644 index 000000000..2a6aa2d3f --- /dev/null +++ b/Test/X05zleincarg.ztst @@ -0,0 +1,360 @@ +# Tests the incarg ZLE widget + +%prep + ZSH_TEST_LANG=$(ZTST_find_UTF8) + if ( zmodload zsh/zpty 2>/dev/null ); then + . $ZTST_srcdir/comptest + comptestinit -v -z $ZTST_testdir/../Src/zsh + else + ZTST_unimplemented="the zsh/zpty module is not available" + fi + zpty_run ' + autoload -Uz incarg + for name in {,vim-}{,sync-}{inc,dec}arg; do + zle -N "$name" incarg + done + bindkey -v "^N" incarg + bindkey -v "^P" decarg + bindkey -v "^F" sync-incarg + bindkey -v "^B" sync-decarg + bindkey -a "^N" vim-incarg + bindkey -a "^P" vim-decarg + bindkey -a "^F" vim-sync-incarg + bindkey -a "^B" vim-sync-decarg + unset TMUX_PANE ITERM_SESSION_ID + tmux() { + echo "$TMUX_PANE" + } + ' + +%test + + zletest $'0\C-n' +0:increment an integer with incarg +>BUFFER: 1 +>CURSOR: 1 + + zletest $'0\C-p' +0:decrement an integer with decarg +>BUFFER: -1 +>CURSOR: 2 + + zletest $'echo 0\e0\C-n' +0:increment an integer with vim-incarg +>BUFFER: echo 1 +>CURSOR: 5 + + zletest $'echo 0\e0\C-p' +0:decrement an integer with vim-decarg +>BUFFER: echo -1 +>CURSOR: 6 + + zletest $'0\C-f' +0:sync-incarg does nothing on unsupported terminals +>BUFFER: 0 +>CURSOR: 1 + + zpty_run 'TMUX_PANE=0' + zletest $'0\C-f' + zpty_run 'unset TMUX_PANE' +0:sync-incarg on tmux in pane 0 +>BUFFER: 0 +>CURSOR: 1 + + zpty_run 'TMUX_PANE=1' + zletest $'0\C-f' + zpty_run 'unset TMUX_PANE' +0:sync-incarg on tmux in pane 1 +>BUFFER: 1 +>CURSOR: 1 + + zpty_run 'TMUX_PANE=2' + zletest $'0\C-f' + zpty_run 'unset TMUX_PANE' +0:sync-incarg on tmux in pane 2 +>BUFFER: 2 +>CURSOR: 1 + + zpty_run 'ITERM_SESSION_ID=w0t0p0:00000000-0000-0000-0000-000000000000' + zletest $'0\C-f' + zpty_run 'unset ITERM_SESSION_ID' +0:sync-incarg on tmux in pane 0 +>BUFFER: 0 +>CURSOR: 1 + + zpty_run 'ITERM_SESSION_ID=w0t0p1:00000000-0000-0000-0000-000000000000' + zletest $'0\C-f' + zpty_run 'unset ITERM_SESSION_ID' +0:sync-incarg on tmux in pane 1 +>BUFFER: 1 +>CURSOR: 1 + + zpty_run 'ITERM_SESSION_ID=w0t0p2:00000000-0000-0000-0000-000000000000' + zletest $'0\C-f' + zpty_run 'unset ITERM_SESSION_ID' +0:sync-incarg on tmux in pane 2 +>BUFFER: 2 +>CURSOR: 1 + + zpty_run 'TMUX_PANE=1' + zpty_run 'ITERM_SESSION_ID=w0t0p2:00000000-0000-0000-0000-000000000000' + zletest $'0\C-f' + zpty_run 'unset TMUX_PANE ITERM_SESSION_ID' +0:tmux pane number takes precedence over iTerm2's +>BUFFER: 1 +>CURSOR: 1 + + zletest $'0\e2\C-n' +0:Providing a numeric argument will change the incremented amount +>BUFFER: 2 +>CURSOR: 0 + + zpty_run 'incarg=3' + zletest $'0\e\C-n' + zpty_run 'unset incarg' +0:Setting the incarg variable will change the default incremented amount +>BUFFER: 3 +>CURSOR: 0 + + zpty_run 'incarg=3' + zletest $'0\e2\C-n' + zpty_run 'unset incarg' +0:A numeric argument will take precedence over the incarg variable +>BUFFER: 2 +>CURSOR: 0 + + zpty_run 'TMUX_PANE=2' + zletest $'0\e2\C-f' + zpty_run 'unset TMUX_PANE' +0:Providing a numeric argument will work for the sync- variants of incarg +>BUFFER: 4 +>CURSOR: 0 + + zletest $'000\C-n' +0:Incrementing a decimal integer preserves leading zeros +>BUFFER: 001 +>CURSOR: 3 + + zletest $'-001\C-n\C-n' +0:Leading zeros are preserved when the digit turns from negative to positive +>BUFFER: 001 +>CURSOR: 3 + + zletest $'001\C-p\C-p' +0:Leading zeros are preserved when the digit turns from positive to negative +>BUFFER: -001 +>CURSOR: 4 + + zletest $'001\e1000\C-n' +0:Incrementing an integer works when the result has more zeros than the original +>BUFFER: 1001 +>CURSOR: 3 + + zletest $'001\e2000\C-p' +0:Decrementing an integer with leading zeros works when the result has more digits than the original +>BUFFER: -1999 +>CURSOR: 4 + + zletest $'0b11\C-n' +0:Increment a binary integer +>BUFFER: 0b100 +>CURSOR: 5 + + zletest $'0B11\C-n' +0:Increment a binary integer with an upper case prefix +>BUFFER: 0B100 +>CURSOR: 5 + + zletest $'0b100\C-p' +0:Decrement a binary integer +>BUFFER: 0b11 +>CURSOR: 4 + + zletest $'0b0011\C-n' +0:Increment a binary integer preserves leading zeros +>BUFFER: 0b0100 +>CURSOR: 6 + + zletest $'0b001\e8\C-n' +0:Incrementing a binary integer work when the result has more zeros than the original +>BUFFER: 0b1001 +>CURSOR: 5 + + zletest $'0b0\C-p' +0:Decrementing a binary integer to a negative value will fail +>BUFFER: 0b0 +>CURSOR: 3 + + zletest $'0o7\C-n' +0:Increment an octal integer +>BUFFER: 0o10 +>CURSOR: 4 + + zletest $'0O7\C-n' +0:Increment an octal integer with an upper case prefix +>BUFFER: 0O10 +>CURSOR: 4 + + zletest $'0o10\C-p' +0:Decrement an octal integer +>BUFFER: 0o7 +>CURSOR: 3 + + zletest $'0o0\C-p' +0:Decrementing an octal integer to a negative value will fail +>BUFFER: 0o0 +>CURSOR: 3 + + zletest $'0x9\C-n' +0:Increment a hexadecimal integer +>BUFFER: 0xa +>CURSOR: 3 + + zletest $'0X9\C-n' +0:Increment a hexadecimal integer with an upper case prefix +>BUFFER: 0XA +>CURSOR: 3 + + zletest $'0xf\C-n' +0:Increment a hexadecimal integer with no numeric digit +>BUFFER: 0x10 +>CURSOR: 4 + + zletest $'0x10\C-p' +0:Decrement a hexadecimal integer +>BUFFER: 0xf +>CURSOR: 3 + + zletest $'0x0\C-p' +0:Decrementing an octal integer to a negative value will fail +>BUFFER: 0x0 +>CURSOR: 3 + + zletest $'0x0b1\C-n' +0:a number that starts with 0x0b is interpreted as a hexadecimal integer +>BUFFER: 0x0b2 +>CURSOR: 5 + + zletest $'10x9\e0\C-n' +0:[0-9]0x[0-9a-f] will become [0-9]1x[0-9a-f] when incremented from the left of x +>BUFFER: 11x9 +>CURSOR: 1 + + zletest $'10x9\eFx\C-n' +0:[0-9]0x[0-9a-f] will increment the hexadecimal 0x[0-9a-f] when the cursor is on x +>BUFFER: 10xa +>CURSOR: 3 + + zletest $'10x9\e\C-n' +0:[0-9]0x[0-9a-f] will increment the hexadecimal 0x[0-9a-f] when the cursor is on the right of x +>BUFFER: 10xa +>CURSOR: 3 + + zletest $'10b1\e0\C-n' +0:[0-9]0b[01] will become [0-9]1b[01] when incremented from the left of b +>BUFFER: 11b1 +>CURSOR: 1 + + zletest $'10b1\eFb\C-n' +0:[0-9]0b[01] will increment the binary 0b[01] when the cursor is on b +>BUFFER: 10b10 +>CURSOR: 4 + + zletest $'10b1\e\C-n' +0:[0-9]0b[01] will increment the binary 0b[01] when the cursor is on the right of b +>BUFFER: 10b10 +>CURSOR: 4 + + zletest $'10o7\e0\C-n' +0:[0-9]0o[0-7] will become [0-9]1o[0-7] when incremented from the left of o +>BUFFER: 11o7 +>CURSOR: 1 + + zletest $'10o7\eFo\C-n' +0:[0-9]0o[0-7] will increment the octal 0o[0-7] when the cursor is on o +>BUFFER: 10o10 +>CURSOR: 4 + + zletest $'10o7\e\C-n' +0:[0-9]0o[0-7] will increment the octal 0o[0-7] when the cursor is on the right of o +>BUFFER: 10o10 +>CURSOR: 4 + + zletest $'0b0x9\eF0\C-n' +0:0b0x[0-9a-f] will increment the binary 0b0 when the cursor is on the left of x +>BUFFER: 0b1x9 +>CURSOR: 2 + + zletest $'0b0x9\eFx\C-n' +0:0b0x[0-9a-f] will increment the hexadecimal 0x[0-9a-f] when the cursor is on top of x +>BUFFER: 0b0xa +>CURSOR: 4 + + zletest $'0b0x9\e\C-n' +0:0b0x[0-9a-f] will increment the hexadecimal 0x[0-9a-f] when the cursor is on the right of x +>BUFFER: 0b0xa +>CURSOR: 4 + + zletest $'echo 012ab\eF i\C-n' +0:incarg does nothing when the cursor is placed just to the left of an integer +>BUFFER: echo 012ab +>CURSOR: 4 + + zletest $'echo 012ab\eF0i\C-n' +0:incarg works when the cursor is placed at the leftmost digit of an integer +>BUFFER: echo 013ab +>CURSOR: 8 + + zletest $'echo 012ab\eF1i\C-n' +0:incarg works when the cursor is placed at the inner digit of an integer +>BUFFER: echo 013ab +>CURSOR: 8 + + zletest $'echo 012ab\eF2i\C-n' +0:incarg works when the cursor is placed at the rightmost digit of an integer +>BUFFER: echo 013ab +>CURSOR: 8 + + zletest $'echo 012ab\eFai\C-n' +0:incarg works when the cursor is placed just to the right of an integer +>BUFFER: echo 013ab +>CURSOR: 8 + + zletest $'echo 012ab\ei\C-n' +0:incarg does nothing when the cursor is placed more than a single letter away to the right +>BUFFER: echo 012ab +>CURSOR: 9 + + zletest $'echo 012ab\eF \C-n' +0:vim-incarg works when the cursor is placed to the left of an integer +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'echo 012ab\eF0\C-n' +0:vim-incarg works when the cursor is placed at the leftmost digit of an integer +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'echo 012ab\eF1\C-n' +0:vim-incarg works when the cursor is placed at the inner digit of an integer +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'echo 012ab\eF2\C-n' +0:incarg works when the cursor is placed at the rightmost digit of an integer +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'echo 012ab\eFa\C-n' +0:vim-incarg does nothing when the cursor is placed to the right of an integer +>BUFFER: echo 012ab +>CURSOR: 8 + + zletest $'echo 012ab\ei\C-n' +0:vim-incarg does nothing when the cursor is placed more than a single letter away to the right +>BUFFER: echo 012ab +>CURSOR: 9 + +%clean + + zmodload -ui zsh/zpty -- cgit v1.2.3 From 374051cae5fe304f1694ee0bf96b1fbb4fe2ae07 Mon Sep 17 00:00:00 2001 From: midchildan Date: Tue, 6 Feb 2024 01:16:01 +0900 Subject: 52523: fixes to preceding incarg patch --- Doc/Zsh/contrib.yo | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'Doc') diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index 718686587..ea00f0ccc 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -2633,7 +2633,7 @@ The behavior of this widget changes depending on the widget name. When the widget is named tt(incarg), the widget will increment an integer placed under the cursor placed or just to the left of it. tt(decarg), on the -other hand, decrements the integer. When the name is prefixed with tt(vim-), +other hand, decrements the integer. When the name is prefixed with tt(vi), the cursor will jump to the nearest integer after the cursor before incrementing it. @@ -2643,7 +2643,8 @@ synchronized key input. The first pane won't increment the integer at all, but each pane after that will have the integer incremented once more than the previous pane. It currently supports tmux and iTerm2. -The prefixes tt(vim-) and tt(sync-) can be combined into tt(vim-sync-). +The prefixes tt(vi) and tt(sync-) can be combined, for example, into +tt(vim-sync-). In this case, the tt(vi) prefix should come first. example(bindkey '^X+' incarg) ) -- cgit v1.2.3 From a6ea12286709273e7896916dc8a107cd52f01d26 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Mon, 19 Feb 2024 11:10:04 +0000 Subject: 52549: document return works in a script --- ChangeLog | 7 ++++++- Doc/Zsh/builtins.yo | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 4ed8e919d..e3762340a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,9 @@ -2024-02-19 Jun-ichi Takimoto +2024-02-19 Peter Stephenson + + * 52549: Doc/Zsh/builtins.yo: document that return already works + in a script. + +x2024-02-19 Jun-ichi Takimoto * 52544: Completion/Unix/Type/_diff_options: support macOS Ventura or newer diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 1aa807633..7a8654f27 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -1649,7 +1649,9 @@ cindex(functions, returning from) item(tt(return) [ var(n) ])( Causes a shell function or `tt(.)' script to return to the invoking script with the return status specified by -an arithmetic expression var(n). +an arithmetic expression var(n). Also causes a non-interctive +shell to exit, allowing files containing shell code to be used +both as scripts and as autoloadable shell functions. For example, the following prints `tt(42)': example(() { integer foo=40; return "foo + 2" } -- cgit v1.2.3 From 6b21e5c0e201876f6659b563b56da9a993ae6c03 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Tue, 20 Feb 2024 20:16:03 -0800 Subject: 52559: revise "typeset -p" with respect to local readonly special parameters Update doc and tests to describe handling of global readonly specials and to account for side-effects on zsh/param/private. --- ChangeLog | 9 +++++++++ Doc/Zsh/builtins.yo | 8 ++++++++ Doc/Zsh/mod_private.yo | 7 ++++--- Src/Modules/param_private.c | 2 +- Src/params.c | 24 ++++++++++++++++++++++-- Test/B02typeset.ztst | 14 ++++++++++++++ Test/K01nameref.ztst | 4 ++-- Test/V10private.ztst | 13 +++++++++---- 8 files changed, 69 insertions(+), 12 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index e3762340a..090644e8b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2024-02-20 Bart Schaefer + + * 52559: Doc/Zsh/builtins.yo, Doc/Zsh/mod_private.yo, + Src/Modules/param_private.c, Src/params.c, Test/B02typeset.ztst, + Test/K01nameref.ztst, Test/V10private.ztst: revise "typeset -p" + with respect to local readonly special parameters; update doc + and tests to describe handling of global readonly specials and + to account for side-effects on zsh/param/private. + 2024-02-19 Peter Stephenson * 52549: Doc/Zsh/builtins.yo: document that return already works diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 7a8654f27..784089594 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -2136,6 +2136,14 @@ tt(-p) may be followed by an optional integer argument. Currently only the value tt(1) is supported. In this case arrays and associative arrays are printed with newlines between indented elements for readability. + +The names and values of readonly special parameters +(most of the parameters marked `' in +ifzman(zmanref(zshparam))ifnzman(noderef(Parameters Set By The Shell)), +except those documented as settable) +are not printed with `tt(-)tt(p)' because to execute those typeset commands +would cause errors. However, these parameters are printed when they +have been made local to the scope where `tt(typeset -p)' is run. ) item(tt(-T) [ var(scalar)[tt(=)var(value)] var(array)[tt(=LPAR())var(value) ...tt(RPAR())] [ var(sep) ] ])( This flag has a different meaning when used with tt(-f); see below. diff --git a/Doc/Zsh/mod_private.yo b/Doc/Zsh/mod_private.yo index 08ac4cafe..24c099f38 100644 --- a/Doc/Zsh/mod_private.yo +++ b/Doc/Zsh/mod_private.yo @@ -16,9 +16,10 @@ The tt(private) builtin accepts all the same options and arguments as tt(local) (ifzman(zmanref(zshbuiltins))ifnzman(noderef(Shell Builtin Commands))) except for the `tt(-)tt(T)' option. Tied parameters may not be made private. -The `tt(-)tt(p)' option is presently a no-op because the state of -private parameters cannot reliably be reloaded. This also applies -to printing private parameters with `tt(typeset -p)'. +The `tt(-)tt(p)' option is presently disabled because the state of +private parameters cannot reliably be reloaded. When `tt(typeset -)tt(p)' +outputs a private parameter, it is treated as a local with the +`tt(-)tt(h)' (hide) option enabled. If used at the top level (outside a function scope), tt(private) creates a normal parameter in the same manner as tt(declare) or tt(typeset). A diff --git a/Src/Modules/param_private.c b/Src/Modules/param_private.c index 5003d4627..044617190 100644 --- a/Src/Modules/param_private.c +++ b/Src/Modules/param_private.c @@ -646,7 +646,7 @@ printprivatenode(HashNode hn, int printflags) static struct builtin bintab[] = { /* Copied from BUILTIN("local"), "P" added */ - BUILTIN("private", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_private, 0, -1, 0, "AE:%F:%HL:%PR:%TUZ:%ahi:%lnmprtux", "P") + BUILTIN("private", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_private, 0, -1, 0, "AE:%F:%HL:%PR:%TUZ:%ahi:%lnmrtux", "P") }; static struct features module_features = { diff --git a/Src/params.c b/Src/params.c index fce3af940..b329d2079 100644 --- a/Src/params.c +++ b/Src/params.c @@ -5896,6 +5896,7 @@ static const struct paramtypes pmtypes[] = { { PM_ARRAY, "array", 'a', 0}, { PM_HASHED, "association", 'A', 0}, { 0, "local", 0, PMTF_TEST_LEVEL}, + { PM_HIDE, "hide", 'h', 0 }, { PM_LEFT, "left justified", 'L', PMTF_USE_WIDTH}, { PM_RIGHT_B, "right justified", 'R', PMTF_USE_WIDTH}, { PM_RIGHT_Z, "zero filled", 'Z', PMTF_USE_WIDTH}, @@ -6025,13 +6026,21 @@ printparamnode(HashNode hn, int printflags) printflags |= PRINT_NAMEONLY; if (printflags & (PRINT_TYPESET|PRINT_POSIX_READONLY|PRINT_POSIX_EXPORT)) { - if (p->node.flags & (PM_RO_BY_DESIGN|PM_AUTOLOAD)) { + if (p->node.flags & PM_AUTOLOAD) { /* * It's not possible to restore the state of * these, so don't output. */ return; } + if (p->node.flags & PM_RO_BY_DESIGN) { + /* + * Compromise: cannot be restored out of context, + * but show anyway if printed in scope of declaration + */ + if (p->level != locallevel || p->level == 0) + return; + } /* * The zsh variants of export -p/readonly -p also report other * flags to indicate other attributes or scope. The POSIX variants @@ -6064,8 +6073,19 @@ printparamnode(HashNode hn, int printflags) for (pmptr = pmtypes, i = 0; i < PMTYPES_SIZE; i++, pmptr++) { int doprint = 0; if (pmptr->flags & PMTF_TEST_LEVEL) { - if (p->level) + if (p->level) { + /* + if ((p->node.flags & PM_SPECIAL) && + (p->node.flags & PM_LOCAL) && + !(p->node.flags & PM_HIDE)) { + if (doneminus) + putchar(' '); + printf("+h "); + doneminus = 0; + } + */ doprint = 1; + } } else if ((pmptr->binflag != PM_EXPORTED || p->level || (p->node.flags & (PM_LOCAL|PM_ARRAY|PM_HASHED))) && (p->node.flags & pmptr->binflag)) diff --git a/Test/B02typeset.ztst b/Test/B02typeset.ztst index 8b3988151..d90f17d13 100644 --- a/Test/B02typeset.ztst +++ b/Test/B02typeset.ztst @@ -959,6 +959,20 @@ > [three]='' >) + () { + local -h status + typeset -p status + } +0:parameter hiding preserved by "typeset -p" +>typeset -h status='' + + () { + local status + typeset -p status + } +0:read-only special params are output when localized +>typeset -i10 -r status=0 + (export PATH MANPATH path=(/bin) MANPATH=/ diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst index ebb70dd92..ff48e2289 100644 --- a/Test/K01nameref.ztst +++ b/Test/K01nameref.ztst @@ -464,7 +464,7 @@ F:unexpected side-effects of previous tests } 0:up-reference part 3, hidden global >outside ->typeset var +>typeset -h var () { typeset notdef @@ -541,7 +541,7 @@ F:Same test, should part 5 output look like this? fi 0:up-reference part 3, autoloading with hidden special >nameref-local-nameref-local ->typeset parameters +>typeset -h parameters if [[ $options[typesettounset] != on ]]; then ZTST_skip='Ignoring zmodload bug that resets TYPESET_TO_UNSET' diff --git a/Test/V10private.ztst b/Test/V10private.ztst index 9eeda0f47..4140d4e96 100644 --- a/Test/V10private.ztst +++ b/Test/V10private.ztst @@ -28,7 +28,7 @@ print $scalar_test 0:basic scope hiding >toplevel ->local scalar_test +>local hide scalar_test >0 >toplevel @@ -54,7 +54,7 @@ print $+unset_test 0:variable defined only in scope >0 ->local unset_test +>local hide unset_test >setme >0 @@ -70,7 +70,7 @@ } print $array_test 0:nested scope with different type, correctly restored ->local array_test +>local hide array_test >in function >top level @@ -113,7 +113,7 @@ typeset -a hash_test=(top level) typeset -p hash_test inner () { - private -p hash_test + typeset -p hash_test print ${(t)hash_test} ${(kv)hash_test} } outer () { @@ -328,6 +328,7 @@ F:future revision will create a global with this assignment F:See K01nameref.ztst up-reference part 5 F:Here ptr1 finds private ptr2 by scope mismatch >typeset -n ptr1=ptr2 +>typeset -hn ptr2 *?*read-only variable: ptr2 () { @@ -348,10 +349,12 @@ F:See K01nameref.ztst up-reference part 5 F:Here ptr1 finds private ptr2 by scope mismatch F:Assignment silently fails, is that correct? >typeset -n ptr1=ptr2 +>typeset -hn ptr2='' >ptr1=ptr2 >ptr1= >ptr2= >typeset -n ptr1=ptr2 +>typeset -hn ptr2='' *?*no such variable: ptr2 typeset ptr2 @@ -372,11 +375,13 @@ F:Assignment silently fails, is that correct? F:See K01typeset.ztst up-reference part 5 F:Here ptr1 points to global ptr2 so assignment succeeds >typeset -n ptr1=ptr2 +>typeset -hn ptr2 >ptr1=ptr2 >ptr2=val >ptr1=val >ptr2=val >typeset -n ptr1=ptr2 +>typeset -hn ptr2 >typeset ptr2=val () { -- cgit v1.2.3 From 5331ff11c64d9d292f4fe817723af6e0a067fa1f Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Wed, 28 Feb 2024 00:21:11 +0100 Subject: 52594: support for POSIX real-time signals with kill and trap Also add new -L option to kill for a more verbose listing of signals --- ChangeLog | 9 ++++ Completion/Zsh/Command/_kill | 9 ++-- Doc/Zsh/builtins.yo | 6 ++- Doc/Zsh/params.yo | 5 +- Src/Modules/parameter.c | 2 +- Src/builtin.c | 30 ++++++------ Src/exec.c | 2 +- Src/hashtable.c | 20 ++++---- Src/init.c | 3 ++ Src/jobs.c | 113 +++++++++++++++++++++++++++++++++++++------ Src/params.c | 14 +++++- Src/signals.c | 84 +++++++++++++++++++++++++++++--- Src/signals.h | 9 ++++ Src/signames2.awk | 6 +-- 14 files changed, 249 insertions(+), 63 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 684820f92..53038c221 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2024-02-28 Oliver Kiddle + + * 52594: Completion/Zsh/Command/_kill, Doc/Zsh/builtins.yo, + Doc/Zsh/params.yo, Src/Modules/parameter.c, Src/builtin.c, + Src/exec.c, Src/hashtable.c, Src/init.c, Src/jobs.c, Src/params.c, + Src/signals.c, Src/signals.h, Src/signames2.awk: support for + POSIX real-time signals with kill and trap and add -L option to + kill for more verbose listing of signals + 2024-02-24 Bart Schaefer * 52597: Src/math.c: fix multibyte and metafied character counts diff --git a/Completion/Zsh/Command/_kill b/Completion/Zsh/Command/_kill index 084cf42c8..3b5c02151 100644 --- a/Completion/Zsh/Command/_kill +++ b/Completion/Zsh/Command/_kill @@ -4,10 +4,11 @@ local curcontext="$curcontext" line state ret=1 typeset -A opt_args _arguments -C \ - '(-s -l 1)-n[specify signal number]:signal number' \ - '(-l)-q[send the specified integer with the signal using sigqueue]:value' \ - '(-n -l 1)-s[specify signal name]:signal:_signals -s' \ - '(-n -s)-l[list signal names or numbers of specified signals]:*:signal:_signals' \ + '(-s -l -L 1)-n[specify signal number]:signal number' \ + '(-l -L)-q[send the specified integer with the signal using sigqueue]:value' \ + '(-n -l -L 1)-s[specify signal name]:signal:_signals -s' \ + '-l[list signal names or numbers of specified signals]:*:signal:_signals' \ + '(- *)-L[list each signal and corresponding number]' \ '(-n -s -l)1::signal:_signals -p -s' \ '*:processes:->processes' && ret=0 diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 784089594..6318053d8 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -1144,7 +1144,8 @@ cindex(killing jobs) cindex(jobs, killing) xitem(tt(kill) [ tt(-s) var(signal_name) | tt(-n) var(signal_number) | \ tt(-)var(sig) ] [ tt(-q) var(value) ] var(job) ...) -item(tt(kill) tt(-l) [ var(sig) ... ])( +xitem(tt(kill) tt(-l) [ var(sig) ... ]) +item(tt(kill) tt(-L))( Sends either tt(SIGTERM) or the specified signal to the given jobs or processes. Signals are given by number or by names, with or without the `tt(SIG)' @@ -1158,7 +1159,8 @@ specified the signal names are listed. Otherwise, for each var(sig) that is a name, the corresponding signal number is listed. For each var(sig) that is a signal number or a number representing the exit status of a process which was terminated or -stopped by a signal the name of the signal is printed. +stopped by a signal the name of the signal is printed. The final +form with tt(-L) lists each signal name with its corresponding number. On some systems, alternative signal names are allowed for a few signals. Typical examples are tt(SIGCHLD) and tt(SIGCLD) or tt(SIGPOLL) and diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index a6fbe6723..8c5e67e70 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -953,7 +953,10 @@ has index 1, the signals are offset by 1 from the signal number used by the operating system. For example, on typical Unix-like systems tt(HUP) is signal number 1, but is referred to as tt($signals[2]). This is because of tt(EXIT) at position 1 in the array, which is used -internally by zsh but is not known to the operating system. +internally by zsh but is not known to the operating system. On many systems +there is a block of reserved or unused signal numbers before the POSIX +real-time signals so the array index can't be used as an accurate indicator +of their signal number. Use, for example, tt(kill -l SIGRTMIN) instead. ) vindex(TRY_BLOCK_ERROR) item(tt(TRY_BLOCK_ERROR) )( diff --git a/Src/Modules/parameter.c b/Src/Modules/parameter.c index a05ea2fe4..7441c30b8 100644 --- a/Src/Modules/parameter.c +++ b/Src/Modules/parameter.c @@ -309,7 +309,7 @@ setfunction(char *name, char *val, int dis) shfunc_set_sticky(shf); if (!strncmp(name, "TRAP", 4) && - (sn = getsignum(name + 4)) != -1) { + (sn = getsigidx(name + 4)) != -1) { if (settrap(sn, NULL, ZSIG_FUNC)) { freeeprog(shf->funcdef); zfree(shf, sizeof(*shf)); diff --git a/Src/builtin.c b/Src/builtin.c index f72d14da4..44dfed651 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -3425,16 +3425,16 @@ bin_functions(char *name, char **argv, Options ops, int func) newsh->sticky = sticky_emulation_dup(shf->sticky, 0); /* is newsh a signal trap? (adapted from exec.c) */ if (!strncmp(s, "TRAP", 4)) { - int signum = getsignum(s + 4); - if (signum != -1) { - if (settrap(signum, NULL, ZSIG_FUNC)) { + int sigidx = getsigidx(s + 4); + if (sigidx != -1) { + if (settrap(sigidx, NULL, ZSIG_FUNC)) { freeeprog(newsh->funcdef); dircache_set(&newsh->filename, NULL); zfree(newsh, sizeof(*newsh)); return 1; } /* Remove any old node explicitly */ - removetrapnode(signum); + removetrapnode(sigidx); } } shfunctab->addnode(shfunctab, ztrdup(s), &newsh->node); @@ -3713,15 +3713,15 @@ bin_functions(char *name, char **argv, Options ops, int func) /* no flags, so just print */ printshfuncexpand(&shf->node, pflags, expand); } else if (on & PM_UNDEFINED) { - int signum = -1, ok = 1; + int sigidx = -1, ok = 1; if (!strncmp(*argv, "TRAP", 4) && - (signum = getsignum(*argv + 4)) != -1) { + (sigidx = getsigidx(*argv + 4)) != -1) { /* * Because of the possibility of alternative names, * we must remove the trap explicitly. */ - removetrapnode(signum); + removetrapnode(sigidx); } if (**argv == '/') { @@ -3757,8 +3757,8 @@ bin_functions(char *name, char **argv, Options ops, int func) shfunc_set_sticky(shf); add_autoload_function(shf, *argv); - if (signum != -1) { - if (settrap(signum, NULL, ZSIG_FUNC)) { + if (sigidx != -1) { + if (settrap(sigidx, NULL, ZSIG_FUNC)) { shfunctab->removenode(shfunctab, *argv); shfunctab->freenode(&shf->node); returnval = 1; @@ -7346,7 +7346,7 @@ bin_trap(char *name, char **argv, UNUSED(Options ops), UNUSED(int func)) /* If given no arguments, list all currently-set traps */ if (!*argv) { queue_signals(); - for (sig = 0; sig < VSIGCOUNT; sig++) { + for (sig = 0; sig < TRAPCOUNT; sig++) { if (sigtrapped[sig] & ZSIG_FUNC) { HashNode hn; @@ -7372,13 +7372,13 @@ bin_trap(char *name, char **argv, UNUSED(Options ops), UNUSED(int func)) /* If we have a signal number, unset the specified * * signals. With only -, remove all traps. */ - if ((getsignum(*argv) != -1) || (!strcmp(*argv, "-") && argv++)) { + if ((getsigidx(*argv) != -1) || (!strcmp(*argv, "-") && argv++)) { if (!*argv) { - for (sig = 0; sig < VSIGCOUNT; sig++) + for (sig = 0; sig < TRAPCOUNT; sig++) unsettrap(sig); } else { for (; *argv; argv++) { - sig = getsignum(*argv); + sig = getsigidx(*argv); if (sig == -1) { zwarnnam(name, "undefined signal: %s", *argv); break; @@ -7403,12 +7403,12 @@ bin_trap(char *name, char **argv, UNUSED(Options ops), UNUSED(int func)) Eprog t; int flags; - sig = getsignum(*argv); + sig = getsigidx(*argv); if (sig == -1) { zwarnnam(name, "undefined signal: %s", *argv); break; } - if (idigit(**argv) || + if (idigit(**argv) || (sig >= VSIGCOUNT) || !strcmp(sigs[sig], *argv) || (!strncmp("SIG", *argv, 3) && !strcmp(sigs[sig], *argv+3))) { /* The signal was specified by number or by canonical name (with diff --git a/Src/exec.c b/Src/exec.c index 0750738ce..d85adbea5 100644 --- a/Src/exec.c +++ b/Src/exec.c @@ -5427,7 +5427,7 @@ execfuncdef(Estate state, Eprog redir_prog) } else { /* is this shell function a signal trap? */ if (!strncmp(s, "TRAP", 4) && - (signum = getsignum(s + 4)) != -1) { + (signum = getsigidx(s + 4)) != -1) { if (settrap(signum, NULL, ZSIG_FUNC)) { freeeprog(shf->funcdef); dircache_set(&shf->filename, NULL); diff --git a/Src/hashtable.c b/Src/hashtable.c index bb165505e..75b06c4ad 100644 --- a/Src/hashtable.c +++ b/Src/hashtable.c @@ -836,10 +836,10 @@ static HashNode removeshfuncnode(UNUSED(HashTable ht), const char *nam) { HashNode hn; - int signum; + int sigidx; - if (!strncmp(nam, "TRAP", 4) && (signum = getsignum(nam + 4)) != -1) - hn = removetrap(signum); + if (!strncmp(nam, "TRAP", 4) && (sigidx = getsigidx(nam + 4)) != -1) + hn = removetrap(sigidx); else hn = removehashnode(shfunctab, nam); @@ -856,10 +856,10 @@ disableshfuncnode(HashNode hn, UNUSED(int flags)) { hn->flags |= DISABLED; if (!strncmp(hn->nam, "TRAP", 4)) { - int signum = getsignum(hn->nam + 4); - if (signum != -1) { - sigtrapped[signum] &= ~ZSIG_FUNC; - unsettrap(signum); + int sigidx = getsigidx(hn->nam + 4); + if (sigidx != -1) { + sigtrapped[sigidx] &= ~ZSIG_FUNC; + unsettrap(sigidx); } } } @@ -876,9 +876,9 @@ enableshfuncnode(HashNode hn, UNUSED(int flags)) shf->node.flags &= ~DISABLED; if (!strncmp(shf->node.nam, "TRAP", 4)) { - int signum = getsignum(shf->node.nam + 4); - if (signum != -1) { - settrap(signum, NULL, ZSIG_FUNC); + int sigidx = getsigidx(shf->node.nam + 4); + if (sigidx != -1) { + settrap(sigidx, NULL, ZSIG_FUNC); } } } diff --git a/Src/init.c b/Src/init.c index 83b79d3d4..ec21521b1 100644 --- a/Src/init.c +++ b/Src/init.c @@ -1382,6 +1382,9 @@ setupshin(char *runscript) void init_signals(void) { + sigtrapped = (int *) hcalloc(TRAPCOUNT * sizeof(int)); + siglists = (Eprog *) hcalloc(TRAPCOUNT * sizeof(Eprog)); + if (interact) { int i; signal_setmask(signal_mask(0)); diff --git a/Src/jobs.c b/Src/jobs.c index 118c5e61b..49decc661 100644 --- a/Src/jobs.c +++ b/Src/jobs.c @@ -1073,6 +1073,21 @@ should_report_time(Job j) return 0; } +/**/ +char * +sigmsg(int sig) +{ + static char *unknown = "unknown signal"; +#if defined(SIGRTMIN) && defined(SIGRTMAX) + static char rtmsg[] = "real-time event XXX"; + if (sig >= SIGRTMIN && sig <= SIGRTMAX) { + sprintf(rtmsg + sizeof(rtmsg) - 4, "%u", sig - SIGRTMIN + 1); + return rtmsg; + } +#endif + return sig <= SIGCOUNT ? sig_msg[sig] : unknown; +} + /* !(lng & 3) means jobs * * (lng & 1) means jobs -l * * (lng & 2) means jobs -p @@ -2694,7 +2709,7 @@ static const struct { int bin_kill(char *nam, char **argv, UNUSED(Options ops), UNUSED(int func)) { - int sig = SIGTERM; + int status, sig = SIGTERM; int returnval = 0; #ifdef HAVE_SIGQUEUE union sigval sigqueue_info; @@ -2740,23 +2755,29 @@ bin_kill(char *nam, char **argv, UNUSED(Options ops), UNUSED(int func)) if ((*argv)[1] == 'l' && (*argv)[2] == '\0') { if (argv[1]) { while (*++argv) { - sig = zstrtol(*argv, &signame, 10); + status = zstrtol(*argv, &signame, 10); if (signame == *argv) { + signame = casemodify(signame, CASMOD_UPPER); if (!strncmp(signame, "SIG", 3)) signame += 3; for (sig = 1; sig <= SIGCOUNT; sig++) - if (!strcasecmp(sigs[sig], signame)) + if (!strcmp(sigs[sig], signame)) break; if (sig > SIGCOUNT) { int i; for (i = 0; alt_sigs[i].name; i++) - if (!strcasecmp(alt_sigs[i].name, signame)) + if (!strcmp(alt_sigs[i].name, signame)) { sig = alt_sigs[i].num; break; } } +#if defined(SIGRTMIN) && defined(SIGRTMAX) + if (sig > SIGCOUNT && (sig = rtsigno(signame))) { + printf("%d\n", sig); + } else +#endif if (sig > SIGCOUNT) { zwarnnam(nam, "unknown signal: SIG%s", signame); @@ -2769,14 +2790,15 @@ bin_kill(char *nam, char **argv, UNUSED(Options ops), UNUSED(int func)) signame); returnval++; } else { - if (WIFSIGNALED(sig)) - sig = WTERMSIG(sig); - else if (WIFSTOPPED(sig)) - sig = WSTOPSIG(sig); + sig = status & ~0200; if (1 <= sig && sig <= SIGCOUNT) printf("%s\n", sigs[sig]); +#if defined(SIGRTMIN) && defined(SIGRTMAX) + else if ((signame = rtsigname(sig, 0))) + printf("%s\n", signame); +#endif else - printf("%d\n", sig); + printf("%d\n", status); } } } @@ -2785,10 +2807,42 @@ bin_kill(char *nam, char **argv, UNUSED(Options ops), UNUSED(int func)) printf("%s", sigs[1]); for (sig = 2; sig <= SIGCOUNT; sig++) printf(" %s", sigs[sig]); +#if defined(SIGRTMIN) && defined(SIGRTMAX) + for (sig = SIGRTMIN; sig <= SIGRTMAX; sig++) + printf(" %s", rtsigname(sig, 0)); +#endif putchar('\n'); return 0; } + /* with argument "-L" list signals with their numbers in a table */ + if ((*argv)[1] == 'L' && (*argv)[2] == '\0') { +#if defined(SIGRTMIN) && defined(SIGRTMAX) + const int width = SIGRTMAX >= 100 ? 3 : 2; +#else + const int width = SIGCOUNT >= 100 ? 3 : 2; +#endif + for (sig = 1; sig < SIGCOUNT +#if defined(SIGRTMIN) && defined(SIGRTMAX) + + 1 +#endif + ; sig++) + { + printf("%*d) %-10s%c", width, sig, sigs[sig], + sig % 5 ? ' ' : '\n'); + } +#if defined(SIGRTMIN) && defined(SIGRTMAX) + for (sig = SIGRTMIN; sig < SIGRTMAX; sig++) { + printf("%*d) %-10s%c", width, sig, rtsigname(sig, 0), + (sig - SIGRTMIN + SIGCOUNT + 1) % 5 ? ' ' : '\n'); + } + printf("%*d) RTMAX\n", width, sig); +#else + printf("%*d) %s\n", width, sig, sigs[sig]); +#endif + return 0; + } + if ((*argv)[1] == 'n' && (*argv)[2] == '\0') { char *endp; @@ -2833,9 +2887,13 @@ bin_kill(char *nam, char **argv, UNUSED(Options ops), UNUSED(int func)) break; } } - if (sig > SIGCOUNT) { + if (sig > SIGCOUNT +#if defined(SIGRTMIN) && defined(SIGRTMAX) + && !(sig = rtsigno(signame)) +#endif + ) { zwarnnam(nam, "unknown signal: SIG%s", signame); - zwarnnam(nam, "type kill -l for a list of signals"); + zwarnnam(nam, "type kill -L for a list of signals"); return 1; } } @@ -2916,18 +2974,19 @@ bin_kill(char *nam, char **argv, UNUSED(Options ops), UNUSED(int func)) return returnval < 126 ? returnval : 1; } -/* Get a signal number from a string */ + +/* Get index into table of traps from a string describing a signal */ /**/ mod_export int -getsignum(const char *s) +getsigidx(const char *s) { int x, i; /* check for a signal specified by number */ x = atoi(s); - if (idigit(*s) && x >= 0 && x < VSIGCOUNT) - return x; + if (idigit(*s) && x >= 0) + return SIGIDX(x); /* search for signal by name */ if (!strncmp(s, "SIG", 3)) @@ -2943,11 +3002,16 @@ getsignum(const char *s) return alt_sigs[i].num; } +#if defined(SIGRTMIN) && defined(SIGRTMAX) + if ((x = rtsigno(s))) + return SIGIDX(x); +#endif + /* no matching signal */ return -1; } -/* Get the name for a signal. */ +/* Get the name for a signal given the index into the traps table. */ /**/ mod_export const char * @@ -2961,6 +3025,11 @@ getsigname(int sig) return alt_sigs[i].name; } else +#if defined(SIGRTMIN) && defined(SIGRTMAX) + if (sig >= VSIGCOUNT) + return rtsigname(SIGNUM(sig), 0); + else +#endif return sigs[sig]; /* shouldn't reach here */ @@ -2985,10 +3054,22 @@ gettrapnode(int sig, int ignoredisable) else getptr = shfunctab->getnode; +#if defined(SIGRTMIN) && defined(SIGRTMAX) + if (sig >= VSIGCOUNT) + sprintf(fname, "TRAP%s", rtsigname(SIGNUM(sig), 0)); + else +#endif sprintf(fname, "TRAP%s", sigs[sig]); if ((hn = getptr(shfunctab, fname))) return hn; +#if defined(SIGRTMIN) && defined(SIGRTMAX) + if (sig >= VSIGCOUNT) { + sprintf(fname, "TRAP%s", rtsigname(SIGNUM(sig), 1)); + return getptr(shfunctab, fname); + } +#endif + for (i = 0; alt_sigs[i].name; i++) { if (alt_sigs[i].num == sig) { sprintf(fname, "TRAP%s", alt_sigs[i].name); diff --git a/Src/params.c b/Src/params.c index 225acb8a1..7c5e9d8ff 100644 --- a/Src/params.c +++ b/Src/params.c @@ -946,8 +946,18 @@ createparamtable(void) setsparam("ZSH_ARGZERO", ztrdup(posixzero)); setsparam("ZSH_VERSION", ztrdup_metafy(ZSH_VERSION)); setsparam("ZSH_PATCHLEVEL", ztrdup_metafy(ZSH_PATCHLEVEL)); - setaparam("signals", sigptr = zalloc((SIGCOUNT+4) * sizeof(char *))); - for (t = sigs; (*sigptr++ = ztrdup_metafy(*t++)); ); + setaparam("signals", sigptr = zalloc((TRAPCOUNT + 1) * sizeof(char *))); + t = sigs; +#if defined(SIGRTMIN) && defined(SIGRTMAX) + while (t - sigs <= SIGCOUNT) + *sigptr++ = ztrdup_metafy(*t++); + { + int sig; + for (sig = SIGRTMIN; sig <= SIGRTMAX; sig++) + *sigptr++ = ztrdup_metafy(rtsigname(sig, 0)); + } +#endif + while ((*sigptr++ = ztrdup_metafy(*t++))) /* empty */ ; noerrs = 0; } diff --git a/Src/signals.c b/Src/signals.c index b1a843e2c..d28853ea6 100644 --- a/Src/signals.c +++ b/Src/signals.c @@ -31,10 +31,12 @@ #include "signals.pro" /* Array describing the state of each signal: an element contains * - * 0 for the default action or some ZSIG_* flags ored together. */ + * 0 for the default action or some ZSIG_* flags ored together. * + * Contains TRAPCOUNT elements but can't be allocated statically * + * because that's a dynamic value on Linux */ /**/ -mod_export int sigtrapped[VSIGCOUNT]; +mod_export int *sigtrapped; /* * Trap programme lists for each signal. @@ -48,7 +50,7 @@ mod_export int sigtrapped[VSIGCOUNT]; */ /**/ -mod_export Eprog siglists[VSIGCOUNT]; +mod_export Eprog *siglists; /* Total count of trapped signals */ @@ -892,7 +894,7 @@ dosavetrap(int sig, int level) * Set a trap: note this does not handle manipulation of * the function table for TRAPNAL functions. * - * sig is the signal number. + * sig is index into the table of trapped signals. * * l is the list to be eval'd for a trap defined with the "trap" * builtin and should be NULL for a function trap. @@ -931,6 +933,10 @@ settrap(int sig, Eprog l, int flags) #endif sig != SIGCHLD) signal_ignore(sig); +#if defined(SIGRTMIN) && defined(SIGRTMAX) + else if (sig >= VSIGCOUNT && sig < TRAPCOUNT) + signal_ignore(SIGNUM(sig)); +#endif } else { nsigtrapped++; sigtrapped[sig] = ZSIG_TRAPPED; @@ -940,6 +946,10 @@ settrap(int sig, Eprog l, int flags) #endif sig != SIGCHLD) install_handler(sig); +#if defined(SIGRTMIN) && defined(SIGRTMAX) + if (sig >= VSIGCOUNT && sig < TRAPCOUNT) + install_handler(SIGNUM(sig)); +#endif } sigtrapped[sig] |= flags; /* @@ -1019,6 +1029,11 @@ removetrap(int sig) #endif sig != SIGCHLD) signal_default(sig); +#if defined(SIGRTMIN) && defined(SIGRTMAX) + else if (sig >= VSIGCOUNT && sig < TRAPCOUNT) + signal_default(SIGNUM(sig)); +#endif + if (sig == SIGEXIT) exit_trap_posix = 0; @@ -1172,7 +1187,7 @@ endtrapscope(void) static int handletrap(int sig) { - if (!sigtrapped[sig]) + if (!sigtrapped[SIGIDX(sig)]) return 0; if (trap_queueing_enabled) @@ -1189,7 +1204,7 @@ handletrap(int sig) return 1; } - dotrap(sig); + dotrap(SIGIDX(sig)); if (sig == SIGALRM) { @@ -1481,3 +1496,60 @@ dotrap(int sig) restore_queue_signals(q); } + +#if defined(SIGRTMIN) && defined(SIGRTMAX) + +/* Realtime signals, these are a contiguous block that can + * be separated from the other signals with an unused gap. */ + +/**/ +int +rtsigno(const char* signame) +{ + const int maxofs = SIGRTMAX - SIGRTMIN; + const char *end = signame + 5; + int offset; + struct rtdir { int sig; int dir; char op; } x = { 0, 0, 0 }; + if (!strncmp(signame, "RTMIN", 5)) { + x = (struct rtdir) { SIGRTMIN, 1, '+' }; + } else if (!strncmp(signame, "RTMAX", 5)) { + x = (struct rtdir) { SIGRTMAX, -1, '-' }; + } else + return 0; + + if (signame[5] == x.op) { + if ((offset = strtol(signame + 6, (char **) &end, 10)) > maxofs) + return 0; + x.sig += offset * x.dir; + } + if (*end) + return 0; + + return x.sig; +} + +/**/ +char * +rtsigname(int signo, int alt) +{ + char* buf = (char *) zhalloc(10); + int minofs = signo - SIGRTMIN; + int maxofs = SIGRTMAX - signo; + int offset; + int form = alt ^ (maxofs < minofs); + + if (signo < SIGRTMIN || signo > SIGRTMAX) + return NULL; + + strcpy(buf, "RT"); + strcpy(buf+2, form ? "MAX-" : "MIN+"); + offset = form ? maxofs : minofs; + if (offset) { + snprintf(buf + 6, 4, "%d", offset); + } else { + buf[5] = '\0'; + } + return buf; +} + +#endif diff --git a/Src/signals.h b/Src/signals.h index 41ac88cce..391f11fed 100644 --- a/Src/signals.h +++ b/Src/signals.h @@ -36,6 +36,15 @@ #define SIGZERR (SIGCOUNT+1) #define SIGDEBUG (SIGCOUNT+2) #define VSIGCOUNT (SIGCOUNT+3) +#if defined(SIGRTMIN) && defined(SIGRTMAX) +# define TRAPCOUNT (VSIGCOUNT + SIGRTMAX - SIGRTMIN + 1) +# define SIGNUM(x) ((x) >= VSIGCOUNT ? (x) - VSIGCOUNT + SIGRTMIN : (x)) +# define SIGIDX(x) ((x) >= SIGRTMIN && (x) <= SIGRTMAX ? (x) - SIGRTMIN + VSIGCOUNT : (x)) +#else +# define TRAPCOUNT VSIGCOUNT +# define SIGNUM(x) (x) +# define SIGIDX(x) (x) +#endif #define SIGEXIT 0 #ifdef SV_BSDSIG diff --git a/Src/signames2.awk b/Src/signames2.awk index 4d1557cd8..5738030c6 100644 --- a/Src/signames2.awk +++ b/Src/signames2.awk @@ -15,7 +15,7 @@ if (signam == "CHLD" && sig[signum] == "CLD") sig[signum] = "" if (signam == "POLL" && sig[signum] == "IO") sig[signum] = "" if (signam == "ABRT" && sig[signum] == "IOT") sig[signum] = "" - if (sig[signum] == "") { + if (signam !~ /RTM(IN|AX)/ && sig[signum] == "") { sig[signum] = signam if (0 + max < 0 + signum && signum < 60) max = signum @@ -66,10 +66,6 @@ END { printf "#include %czsh.mdh%c\n", 34, 34 printf "\n" printf "/**/\n" - printf "#define sigmsg(sig) ((sig) <= SIGCOUNT ? sig_msg[sig]" - printf " : %c%s%c)", 34, "unknown signal", 34 - printf "\n" - printf "/**/\n" printf "mod_export char *sig_msg[SIGCOUNT+2] = {\n" printf "\t%c%s%c,\n", 34, "done", 34 -- cgit v1.2.3 From 36a2d5cfa49a6b7d699269cb4f22d5f3d0255bc8 Mon Sep 17 00:00:00 2001 From: midchildan Date: Thu, 29 Feb 2024 22:34:33 +0900 Subject: 52641: incarg: add a backward variant and make it repeatable --- ChangeLog | 5 + Doc/Zsh/contrib.yo | 8 +- Functions/Zle/incarg | 64 ++++++++- Test/X05zleincarg.ztst | 358 ++++++++++++++++++++++++++++++++++++++----------- 4 files changed, 347 insertions(+), 88 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 9718d0cae..5c839cd19 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2024-03-05 Oliver Kiddle + + * 52641: midchildan: Doc/Zsh/contrib.yo, Functions/Zle/incarg, + Test/X05zleincarg.ztst: add a backward variant and make it repeatable + 2024-03-02 Bart Schaefer * 52652: Src/params.c, Test/D04parameter.ztst: fix obscure bug diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index ea00f0ccc..e682c800a 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -2635,7 +2635,8 @@ When the widget is named tt(incarg), the widget will increment an integer placed under the cursor placed or just to the left of it. tt(decarg), on the other hand, decrements the integer. When the name is prefixed with tt(vi), the cursor will jump to the nearest integer after the cursor before incrementing -it. +it. The tt(vi) prefix can also be combined with a tt(backward-) prefix to make +the widget search backwards for numbers. There's also a tt(sync-) prefix that can be added to the widget name. This variant is used for creating a sequence of numbers on split terminals with @@ -2643,8 +2644,9 @@ synchronized key input. The first pane won't increment the integer at all, but each pane after that will have the integer incremented once more than the previous pane. It currently supports tmux and iTerm2. -The prefixes tt(vi) and tt(sync-) can be combined, for example, into -tt(vim-sync-). In this case, the tt(vi) prefix should come first. +The prefixes tt(vi), tt(backward-), and tt(sync-) can be combined, for example, +into tt(vim-sync-) or tt(vim-backward-sync-). The tt(vi) prefix needs to be +at the very beginning. example(bindkey '^X+' incarg) ) diff --git a/Functions/Zle/incarg b/Functions/Zle/incarg index 9d56b21f6..0cfaf9ff5 100644 --- a/Functions/Zle/incarg +++ b/Functions/Zle/incarg @@ -41,11 +41,20 @@ emulate -L zsh # This behaves like Vim's CTRL-A / CTRL-X. It moves the cursor to the nearest # number after the cursor and increments or decrements it. # +# - vim-backward-incarg / vim-backward-decarg +# +# This behaves like vim-incarg & vim-decarg, but it searches backwards for a +# number. +# # - vim-sync-incarg / vim-sync-decarg # # This combines the behavior of the vim- and sync- variants. It's inspired by # Vim's g_CTRL-A / g_CTRL-X. # +# - vim-backward-sync-incarg / vim-backward-sync-decarg +# +# This combines the behavior of the vim-backward- and sync- variants. +# # Example Usage: # # autoload -Uz incarg @@ -58,9 +67,13 @@ emulate -L zsh # 'g^A' vim-sync-incarg \ # 'g^X' vim-sync-decarg +zle -f vichange + setopt localoptions extended_glob local match mbegin mend MATCH MBEGIN MEND i +[[ -z "$BUFFER" ]] && return 1 + # find the number and determine the base integer pos=$(( CURSOR + 1 )) base=0 @@ -104,11 +117,35 @@ fi if (( base == 0 )); then if [[ "$WIDGET" == vi* ]]; then - # jump to the nearest number after the cursor - while [[ "$BUFFER[pos]" == [^0-9] ]]; do - (( pos++ )) - (( pos > $#BUFFER )) && return 1 - done + if [[ "$WIDGET" == *backward-* ]]; then + # search backwards for a number + while true; do + case "$BUFFER[1,pos]" in + *0[xX][0-9a-fA-F]##) base=16 ;; + *0[oO][0-7]##) base=8 ;; + *0[bB][01]##) base=2 ;; + *[0-9]) base=10 ;; + *-) + case "$BUFFER[pos,-1]" in + -0[xX][0-9a-fA-F]*) ;; + -0[oO][0-7]*) ;; + -0[bB][01]*) ;; + -[0-9]*) base=10 ;; + esac + ;; + esac + (( base != 0 )) && break + + (( pos-- )) + (( pos <= 0 )) && return 1 + done + else + # jump to the nearest number after the cursor + while [[ "$BUFFER[pos]" == [^0-9] ]]; do + (( pos++ )) + (( pos > $#BUFFER )) && return 1 + done + fi fi # check for a prefix right after the cursor and jump right after it, if any @@ -204,6 +241,12 @@ fi local old="$BUFFER[first,last]" integer oldlen=$#BUFFER +integer oldnum="$base#$old" 2> /dev/null + +# -00 should increment to 01 instead of 001 +if [[ "$BUFFER[first]" == '-' ]] && (( oldnum == 0 )); then + (( ndigits-- )) +fi local fmt1 fmt2 case "$base" in @@ -214,10 +257,12 @@ case "$base" in esac local raw_result padded +# $(( )) outputs an error message to stderr when integer truncation occurs printf -v raw_result "%0$ndigits$fmt1" $(( [$fmt2] "$base#$old" + delta )) 2> /dev/null padded="${raw_result// /0}" +integer newnum="$base#$padded" 2> /dev/null -integer oldnum="$base#$old" newnum="$base#$padded" 2> /dev/null +# try to detect integer truncation if (( base != 10 && newnum < 0 || delta > 0 && newnum < oldnum || delta < 0 && newnum > oldnum )); then @@ -242,7 +287,12 @@ if zstyle -t ":zle:$WIDGET" debug; then zle -M "[$WIDGET] base: $base delta: $delta old: '$old' new: '$new'" fi -BUFFER[first,last]="$new" +if (( 0 < first && first <= last && last <= $#BUFFER )); then + BUFFER[first,last]="$new" +else + zle -M "[$WIDGET] The detected location of the integer was invalid. [location=BUFFER[$first,$last]]" + return 1 +fi integer offset=0 if [[ "$WIDGET" == vi* ]]; then diff --git a/Test/X05zleincarg.ztst b/Test/X05zleincarg.ztst index 2a6aa2d3f..cd9817c82 100644 --- a/Test/X05zleincarg.ztst +++ b/Test/X05zleincarg.ztst @@ -10,7 +10,7 @@ fi zpty_run ' autoload -Uz incarg - for name in {,vim-}{,sync-}{inc,dec}arg; do + for name in {,vim-,vim-backward-}{,sync-}{inc,dec}arg; do zle -N "$name" incarg done bindkey -v "^N" incarg @@ -21,6 +21,8 @@ bindkey -a "^P" vim-decarg bindkey -a "^F" vim-sync-incarg bindkey -a "^B" vim-sync-decarg + bindkey -a "^E" vim-backward-incarg + bindkey -a "^Y" vim-backward-decarg unset TMUX_PANE ITERM_SESSION_ID tmux() { echo "$TMUX_PANE" @@ -29,26 +31,40 @@ %test +# Basic increment & decrement + zletest $'0\C-n' -0:increment an integer with incarg +0:incarg increments an integer >BUFFER: 1 >CURSOR: 1 zletest $'0\C-p' -0:decrement an integer with decarg +0:decarg decrements an integer >BUFFER: -1 >CURSOR: 2 zletest $'echo 0\e0\C-n' -0:increment an integer with vim-incarg +0:vim-incarg increments an integer >BUFFER: echo 1 >CURSOR: 5 zletest $'echo 0\e0\C-p' -0:decrement an integer with vim-decarg +0:vim-decarg decrements an integer >BUFFER: echo -1 >CURSOR: 6 + zletest $'echo 0 foo\e\C-e' +0:vim-backward-incarg increments an integer +>BUFFER: echo 1 foo +>CURSOR: 5 + + zletest $'echo 0 foo\e\C-y' +0:vim-backward-decarg decrements an integer +>BUFFER: echo -1 foo +>CURSOR: 6 + +# sync- variants + zletest $'0\C-f' 0:sync-incarg does nothing on unsupported terminals >BUFFER: 0 @@ -57,42 +73,42 @@ zpty_run 'TMUX_PANE=0' zletest $'0\C-f' zpty_run 'unset TMUX_PANE' -0:sync-incarg on tmux in pane 0 +0:sync-incarg does nothing on tmux in pane 0 >BUFFER: 0 >CURSOR: 1 zpty_run 'TMUX_PANE=1' zletest $'0\C-f' zpty_run 'unset TMUX_PANE' -0:sync-incarg on tmux in pane 1 +0:sync-incarg increments by 1 on tmux in pane 1 >BUFFER: 1 >CURSOR: 1 zpty_run 'TMUX_PANE=2' zletest $'0\C-f' zpty_run 'unset TMUX_PANE' -0:sync-incarg on tmux in pane 2 +0:sync-incarg increments by 2 on tmux in pane 2 >BUFFER: 2 >CURSOR: 1 zpty_run 'ITERM_SESSION_ID=w0t0p0:00000000-0000-0000-0000-000000000000' zletest $'0\C-f' zpty_run 'unset ITERM_SESSION_ID' -0:sync-incarg on tmux in pane 0 +0:sync-incarg does nothing on tmux in pane 0 >BUFFER: 0 >CURSOR: 1 zpty_run 'ITERM_SESSION_ID=w0t0p1:00000000-0000-0000-0000-000000000000' zletest $'0\C-f' zpty_run 'unset ITERM_SESSION_ID' -0:sync-incarg on tmux in pane 1 +0:sync-incarg increments by 1 on tmux in pane 1 >BUFFER: 1 >CURSOR: 1 zpty_run 'ITERM_SESSION_ID=w0t0p2:00000000-0000-0000-0000-000000000000' zletest $'0\C-f' zpty_run 'unset ITERM_SESSION_ID' -0:sync-incarg on tmux in pane 2 +0:sync-incarg increments by 2 on tmux in pane 2 >BUFFER: 2 >CURSOR: 1 @@ -100,230 +116,281 @@ zpty_run 'ITERM_SESSION_ID=w0t0p2:00000000-0000-0000-0000-000000000000' zletest $'0\C-f' zpty_run 'unset TMUX_PANE ITERM_SESSION_ID' -0:tmux pane number takes precedence over iTerm2's +0:sync-incarg prioritizes tmux pane number over iTerm2's >BUFFER: 1 >CURSOR: 1 zletest $'0\e2\C-n' -0:Providing a numeric argument will change the incremented amount +0:incarg changes the incremented amount based on the numeric argument >BUFFER: 2 >CURSOR: 0 zpty_run 'incarg=3' zletest $'0\e\C-n' zpty_run 'unset incarg' -0:Setting the incarg variable will change the default incremented amount +0:incarg changes the default incremented amount based on the incarg variable >BUFFER: 3 >CURSOR: 0 zpty_run 'incarg=3' zletest $'0\e2\C-n' zpty_run 'unset incarg' -0:A numeric argument will take precedence over the incarg variable +0:incarg prioritizes the numeric argument over the incarg variable >BUFFER: 2 >CURSOR: 0 zpty_run 'TMUX_PANE=2' zletest $'0\e2\C-f' zpty_run 'unset TMUX_PANE' -0:Providing a numeric argument will work for the sync- variants of incarg +0:The sync- variants of incarg takes the numeric argument into account >BUFFER: 4 >CURSOR: 0 +# Leading zeros + zletest $'000\C-n' -0:Incrementing a decimal integer preserves leading zeros +0:incarg preserves leading zeros of decimal integers >BUFFER: 001 >CURSOR: 3 zletest $'-001\C-n\C-n' -0:Leading zeros are preserved when the digit turns from negative to positive +0:incarg preserves leading zeros when the digit turns from negative to positive >BUFFER: 001 >CURSOR: 3 zletest $'001\C-p\C-p' -0:Leading zeros are preserved when the digit turns from positive to negative +0:incarg preserves leading zeros when the digit turns from positive to negative >BUFFER: -001 >CURSOR: 4 zletest $'001\e1000\C-n' -0:Incrementing an integer works when the result has more zeros than the original +0:incarg works when the result has more number of digits than the original >BUFFER: 1001 >CURSOR: 3 zletest $'001\e2000\C-p' -0:Decrementing an integer with leading zeros works when the result has more digits than the original +0:decargs works on integers with leading zeros when the result has more digits than the original >BUFFER: -1999 >CURSOR: 4 + zletest $'-000\C-n' +0:incarg produces the correct number of zeros when incrementing integers starting with -0 +>BUFFER: 001 +>CURSOR: 3 + + zletest $'-000\C-p' +0:decarg produces the correct number of zeros when incrementing integers starting with -0 +>BUFFER: -001 +>CURSOR: 4 + + zpty_run 'incarg=0' + zletest $'-000\C-n' + zpty_run 'unset incarg' +0:incarg removes the sign when the target integer starts with -0 and the increment amount is 0 +>BUFFER: 000 +>CURSOR: 3 + + zletest $'-0\C-n' +0:incarg turns -0 into 1 +>BUFFER: 1 +>CURSOR: 1 + + zletest $'-0\C-p' +0:decarg turns -0 into -1 +>BUFFER: -1 +>CURSOR: 2 + + zpty_run 'incarg=0' + zletest $'-0\C-n' + zpty_run 'unset incarg' +0:incarg turns -0 into 0 when the increment amount is 0 +>BUFFER: 0 +>CURSOR: 1 + +# Binaries + zletest $'0b11\C-n' -0:Increment a binary integer +0:incarg can increment a binary integer >BUFFER: 0b100 >CURSOR: 5 zletest $'0B11\C-n' -0:Increment a binary integer with an upper case prefix +0:incarg can increment a binary integer with an upper case prefix >BUFFER: 0B100 >CURSOR: 5 zletest $'0b100\C-p' -0:Decrement a binary integer +0:decarg can decrement a binary integer >BUFFER: 0b11 >CURSOR: 4 zletest $'0b0011\C-n' -0:Increment a binary integer preserves leading zeros +0:incarg can preserve leading zeros of binaries >BUFFER: 0b0100 >CURSOR: 6 zletest $'0b001\e8\C-n' -0:Incrementing a binary integer work when the result has more zeros than the original +0:incarg works on binaries when the result has more zeros than the original >BUFFER: 0b1001 >CURSOR: 5 zletest $'0b0\C-p' -0:Decrementing a binary integer to a negative value will fail +0:decarg fails to produce a negative binary value >BUFFER: 0b0 >CURSOR: 3 +# Octals + zletest $'0o7\C-n' -0:Increment an octal integer +0:incarg can increment an octal integer >BUFFER: 0o10 >CURSOR: 4 zletest $'0O7\C-n' -0:Increment an octal integer with an upper case prefix +0:incarg can increment an octal integer with an upper case prefix >BUFFER: 0O10 >CURSOR: 4 zletest $'0o10\C-p' -0:Decrement an octal integer +0:decarg can decrement an octal integer >BUFFER: 0o7 >CURSOR: 3 zletest $'0o0\C-p' -0:Decrementing an octal integer to a negative value will fail +0:decarg fails to produce a negative octal value >BUFFER: 0o0 >CURSOR: 3 +# Hexadecimals + zletest $'0x9\C-n' -0:Increment a hexadecimal integer +0:incarg can increment a hexadecimal integer >BUFFER: 0xa >CURSOR: 3 zletest $'0X9\C-n' -0:Increment a hexadecimal integer with an upper case prefix +0:incarg can increment a hexadecimal integer with an upper case prefix >BUFFER: 0XA >CURSOR: 3 zletest $'0xf\C-n' -0:Increment a hexadecimal integer with no numeric digit +0:incarg can increment a hexadecimal integer with no numeric digit >BUFFER: 0x10 >CURSOR: 4 zletest $'0x10\C-p' -0:Decrement a hexadecimal integer +0:decarg can decrement a hexadecimal integer >BUFFER: 0xf >CURSOR: 3 zletest $'0x0\C-p' -0:Decrementing an octal integer to a negative value will fail +0:decarg fails to produce a negative hexadecimal value >BUFFER: 0x0 >CURSOR: 3 zletest $'0x0b1\C-n' -0:a number that starts with 0x0b is interpreted as a hexadecimal integer +0:incarg interprets integers starting with 0x0b as a hexadecimal >BUFFER: 0x0b2 >CURSOR: 5 + zletest $'0x0b1\e\C-e' +0:vim-backward-incarg interprets integers starting with 0x0b as a hexadecimal +>BUFFER: 0x0b2 +>CURSOR: 4 + +# Cursor position - incarg + + zletest $'echo 012ab\eF i\C-n' +0:incarg does nothing when the cursor is placed just to the left of an integer +>BUFFER: echo 012ab +>CURSOR: 4 + + zletest $'echo 012ab\eF0i\C-n' +0:incarg works when the cursor is placed at the leftmost digit of an integer +>BUFFER: echo 013ab +>CURSOR: 8 + + zletest $'echo 012ab\eF1i\C-n' +0:incarg works when the cursor is placed at the inner digit of an integer +>BUFFER: echo 013ab +>CURSOR: 8 + + zletest $'echo 012ab\eF2i\C-n' +0:incarg works when the cursor is placed at the rightmost digit of an integer +>BUFFER: echo 013ab +>CURSOR: 8 + + zletest $'echo 012ab\eFai\C-n' +0:incarg works when the cursor is placed just to the right of an integer +>BUFFER: echo 013ab +>CURSOR: 8 + + zletest $'echo 012ab\ei\C-n' +0:incarg does nothing when the cursor is placed more than a single letter away to the right +>BUFFER: echo 012ab +>CURSOR: 9 + zletest $'10x9\e0\C-n' -0:[0-9]0x[0-9a-f] will become [0-9]1x[0-9a-f] when incremented from the left of x +0:incarg turns [0-9]0x[0-9a-f] into [0-9]1x[0-9a-f] when the cursor is at the left of x >BUFFER: 11x9 >CURSOR: 1 zletest $'10x9\eFx\C-n' -0:[0-9]0x[0-9a-f] will increment the hexadecimal 0x[0-9a-f] when the cursor is on x +0:incarg takes [0-9]0x[0-9a-f] and increments the hexadecimal part when the cursor is on x >BUFFER: 10xa >CURSOR: 3 zletest $'10x9\e\C-n' -0:[0-9]0x[0-9a-f] will increment the hexadecimal 0x[0-9a-f] when the cursor is on the right of x +0:incarg takes [0-9]0x[0-9a-f] and increments the hexadecimal part when the cursor is at the right of x >BUFFER: 10xa >CURSOR: 3 zletest $'10b1\e0\C-n' -0:[0-9]0b[01] will become [0-9]1b[01] when incremented from the left of b +0:incarg turns [0-9]0b[01] into [0-9]1b[01] when the cursor is at the left of b >BUFFER: 11b1 >CURSOR: 1 zletest $'10b1\eFb\C-n' -0:[0-9]0b[01] will increment the binary 0b[01] when the cursor is on b +0:incarg takes [0-9]0b[01] and increments the binary part when the cursor is on b >BUFFER: 10b10 >CURSOR: 4 zletest $'10b1\e\C-n' -0:[0-9]0b[01] will increment the binary 0b[01] when the cursor is on the right of b +0:incarg takes [0-9]0b[01] and increments binary part when the cursor is at the right of b >BUFFER: 10b10 >CURSOR: 4 zletest $'10o7\e0\C-n' -0:[0-9]0o[0-7] will become [0-9]1o[0-7] when incremented from the left of o +0:incarg turns [0-9]0o[0-7] into [0-9]1o[0-7] when the cursor is at the left of o >BUFFER: 11o7 >CURSOR: 1 zletest $'10o7\eFo\C-n' -0:[0-9]0o[0-7] will increment the octal 0o[0-7] when the cursor is on o +0:incarg takes [0-9]0o[0-7] and increments the octal part when the cursor is on o >BUFFER: 10o10 >CURSOR: 4 zletest $'10o7\e\C-n' -0:[0-9]0o[0-7] will increment the octal 0o[0-7] when the cursor is on the right of o +0:incarg takes [0-9]0o[0-7] and increments the octal part when the cursor is at the right of o >BUFFER: 10o10 >CURSOR: 4 zletest $'0b0x9\eF0\C-n' -0:0b0x[0-9a-f] will increment the binary 0b0 when the cursor is on the left of x +0:incarg takes 0b0x[0-9a-f] and increments the binary part when the cursor is at the left of x >BUFFER: 0b1x9 >CURSOR: 2 zletest $'0b0x9\eFx\C-n' -0:0b0x[0-9a-f] will increment the hexadecimal 0x[0-9a-f] when the cursor is on top of x +0:incarg takes 0b0x[0-9a-f] and increments the hexadecimal part when the cursor is on x >BUFFER: 0b0xa >CURSOR: 4 zletest $'0b0x9\e\C-n' -0:0b0x[0-9a-f] will increment the hexadecimal 0x[0-9a-f] when the cursor is on the right of x +0:incarg takes 0b0x[0-9a-f] and increments the hexadecimal part when the cursor is at the right of x >BUFFER: 0b0xa >CURSOR: 4 - zletest $'echo 012ab\eF i\C-n' -0:incarg does nothing when the cursor is placed just to the left of an integer ->BUFFER: echo 012ab ->CURSOR: 4 - - zletest $'echo 012ab\eF0i\C-n' -0:incarg works when the cursor is placed at the leftmost digit of an integer ->BUFFER: echo 013ab ->CURSOR: 8 - - zletest $'echo 012ab\eF1i\C-n' -0:incarg works when the cursor is placed at the inner digit of an integer ->BUFFER: echo 013ab ->CURSOR: 8 - - zletest $'echo 012ab\eF2i\C-n' -0:incarg works when the cursor is placed at the rightmost digit of an integer ->BUFFER: echo 013ab ->CURSOR: 8 - - zletest $'echo 012ab\eFai\C-n' -0:incarg works when the cursor is placed just to the right of an integer ->BUFFER: echo 013ab ->CURSOR: 8 - - zletest $'echo 012ab\ei\C-n' -0:incarg does nothing when the cursor is placed more than a single letter away to the right ->BUFFER: echo 012ab ->CURSOR: 9 +# Cursor position - vim-incarg zletest $'echo 012ab\eF \C-n' 0:vim-incarg works when the cursor is placed to the left of an integer @@ -355,6 +422,141 @@ >BUFFER: echo 012ab >CURSOR: 9 +# Cursor position - vim-backward-incarg + + zletest $'echo 012ab\eF \C-e' +0:vim-backward-incarg does nothing when the cursor is placed just to the left of an integer +>BUFFER: echo 012ab +>CURSOR: 4 + + zletest $'echo 012ab\eF0\C-e' +0:vim-backward-incarg works when the cursor is placed at the leftmost digit of an integer +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'echo 012ab\eF1\C-e' +0:vim-backward-incarg works when the cursor is placed at the inner digit of an integer +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'echo 012ab\eF2\C-e' +0:vim-backward-incarg works when the cursor is placed at the rightmost digit of an integer +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'echo 012ab\eFa\C-e' +0:vim-backward-incarg works when the cursor is placed just to the right of an integer +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'echo 012ab\e\C-e' +0:vim-backward-incarg works when the cursor is placed more than a single letter away to the right +>BUFFER: echo 013ab +>CURSOR: 7 + + zletest $'10x9\eFx\C-e' +0:vim-backward-incarg will take [0-9]0x[0-9a-f] and increment the hexadecimal part when the cursor is on x +>BUFFER: 10xa +>CURSOR: 3 + + zletest $'10x9\e\C-e' +0:vim-backward-incarg will take [0-9]0x[0-9a-f] and increment the hexadecimal part when the cursor is on the right of x +>BUFFER: 10xa +>CURSOR: 3 + + zletest $'10b1\e0\C-e' +0:vim-backward-incarg will turn [0-9]0b[01] into [0-9]1b[01] when the cursor is at the left of b +>BUFFER: 11b1 +>CURSOR: 1 + + zletest $'10b1\eFb\C-e' +0:vim-backward-incarg will take [0-9]0b[01] and increment the binary part when the cursor is on b +>BUFFER: 10b10 +>CURSOR: 4 + + zletest $'10b1\e\C-e' +0:vim-backward-incarg will take [0-9]0b[01] and increment the binary part when the cursor is on the right of b +>BUFFER: 10b10 +>CURSOR: 4 + + zletest $'10o7\e0\C-e' +0:vim-backward-incarg will turn [0-9]0o[0-7] into [0-9]1o[0-7] when the cursor is at the left of o +>BUFFER: 11o7 +>CURSOR: 1 + + zletest $'10o7\eFo\C-e' +0:vim-backward-incarg will take [0-9]0o[0-7] and increment the octal part when the cursor is on o +>BUFFER: 10o10 +>CURSOR: 4 + + zletest $'10o7\e\C-e' +0:vim-backward-incarg will take [0-9]0o[0-7] and increment the octal part when the cursor is at the right of o +>BUFFER: 10o10 +>CURSOR: 4 + + zletest $'0b0x9\eF0\C-e' +0:vim-backward-incarg will take 0b0x[0-9a-f] and increment the binary 0b0 when the cursor is on the left of x +>BUFFER: 0b1x9 +>CURSOR: 2 + + zletest $'0b0x9\eFx\C-e' +0:vim-backward-incarg will take 0b0x[0-9a-f] and increment the hexadecimal part when the cursor is on x +>BUFFER: 0b0xa +>CURSOR: 4 + + zletest $'0b0x9\e\C-e' +0:vim-backward-incarg will take 0b0x[0-9a-f] and increment the hexadecimal part when the cursor is at the right of x +>BUFFER: 0b0xa +>CURSOR: 4 + +# Repeats + + zletest $'echo 0\e0\C-n.' +0:vim-incarg is compatible with the repeat command +>BUFFER: echo 2 +>CURSOR: 5 + + zletest $'echo 0\e0\C-p.' +0:vim-decarg is compatible with the repeat command +>BUFFER: echo -2 +>CURSOR: 6 + + zletest $'echo 0 foo\e\C-e.' +0:vim-backward-incarg is compatible with the repeat command +>BUFFER: echo 2 foo +>CURSOR: 5 + + zletest $'echo 0\e010\C-n.' +0:Repeats of vim-incarg takes the numeric argument into account +>BUFFER: echo 20 +>CURSOR: 6 + + zletest $'echo 0 foo\e10\C-e.' +0:Repeats of vim-backward-incarg takes the numeric argument into account +>BUFFER: echo 20 foo +>CURSOR: 6 + + zpty_run 'TMUX_PANE=0' + zletest $'echo 0\e0\C-f.' + zpty_run 'unset TMUX_PANE' +0:Repeats of vim-sync-incarg work in pane 0 +>BUFFER: echo 0 +>CURSOR: 5 + + zpty_run 'TMUX_PANE=1' + zletest $'echo 0\e0\C-f.' + zpty_run 'unset TMUX_PANE' +0:Repeats of vim-sync-incarg work in pane 1 +>BUFFER: echo 2 +>CURSOR: 5 + + zpty_run 'TMUX_PANE=2' + zletest $'echo 0\e0\C-f.' + zpty_run 'unset TMUX_PANE' +0:Repeats of vim-sync-incarg work in pane 2 +>BUFFER: echo 4 +>CURSOR: 5 + %clean zmodload -ui zsh/zpty -- cgit v1.2.3 From 05c7b21e2b30873d002b50b37e2fbd3803d4b608 Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Tue, 5 Mar 2024 00:11:02 +0100 Subject: 52646: extend support for highlight groups to completion explanation strings and WATCHFMT --- ChangeLog | 7 +++++-- Completion/Zsh/Type/_ps1234 | 21 +++++++++++++-------- Doc/Zsh/compsys.yo | 4 ++-- Doc/Zsh/compwid.yo | 3 ++- Src/Modules/watch.c | 9 ++++++++- Src/Zle/complist.c | 7 +++++++ Src/Zle/zle_tricky.c | 8 ++++++++ Src/prompt.c | 8 +++++--- 8 files changed, 50 insertions(+), 17 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 5c839cd19..0dd7268d1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,10 @@ 2024-03-05 Oliver Kiddle + * 52646: Completion/Zsh/Type/_ps1234, Doc/Zsh/compsys.yo, + Doc/Zsh/compwid.yo, Src/Modules/watch.c, Src/Zle/complist.c, + Src/Zle/zle_tricky.c, Src/prompt.c: extend support for highlight + groups to completion explanation strings and WATCHFMT + * 52641: midchildan: Doc/Zsh/contrib.yo, Functions/Zle/incarg, Test/X05zleincarg.ztst: add a backward variant and make it repeatable @@ -31,8 +36,6 @@ * 52623: Src/signames2.awk: add some Solaris signal descriptions -2024-02-28 Oliver Kiddle - * 52594: Completion/Zsh/Command/_kill, Doc/Zsh/builtins.yo, Doc/Zsh/params.yo, Src/Modules/parameter.c, Src/builtin.c, Src/exec.c, Src/hashtable.c, Src/init.c, Src/jobs.c, Src/params.c, diff --git a/Completion/Zsh/Type/_ps1234 b/Completion/Zsh/Type/_ps1234 index 07dea5905..e4391dc00 100644 --- a/Completion/Zsh/Type/_ps1234 +++ b/Completion/Zsh/Type/_ps1234 @@ -1,17 +1,18 @@ #compdef -value-,PROMPT,-default- -value-,PROMPT2,-default- -value-,PROMPT3,-default- -value-,PROMPT4,-default- -value-,RPROMPT,-default- -value-,RPROMPT2,-default- -value-,PS1,-default- -value-,PS2,-default- -value-,PS3,-default- -value-,PS4,-default- -value-,RPS1,-default- -value-,RPS2,-default- -value-,SPROMPT,-default- -value-,PROMPT_EOL_MARK,-default- -local -a specs ccol -local expl grp cols bs suf pre changed=1 ret=1 +local -a specs ccol suf +local expl grp cols bs pre changed=1 ret=1 local -A ansi [[ -z $compstate[quote] ]] && bs='\' +suf=( -S '' ) # first strip off any complete prompt specifications leaving only the # current, incomplete, one while (( changed )); do changed=0 - compset -P '%[DFK](\\|){[^}]#}' && changed=1 # formats with arg: %x{...} - compset -P '%[0-9-\\]#[^DFK(0-9-<>\\\[]' && changed=1 # normal formats + compset -P '%[DFHK](\\|){[^}]#}' && changed=1 # formats with arg: %x{...} + compset -P '%[0-9-\\]#[^DFHK(0-9-<>\\\[]' && changed=1 # normal formats compset -P '%[0-9-\\]#(<[^<]#<|>[^>]#>|\[[^\]]#\])' && changed=1 # truncations compset -P '%[0-9-\\]#(\\|)\([0-9-]#[^0-9]?|[^%]' && changed=1 # start of ternary compset -P '[^%]##' && changed=1 # sundry other characters @@ -41,15 +42,15 @@ if compset -P '%[FK]'; then grp="$expl[expl[(i)-J]+1]" print -v ccol -f "($grp)=%s=%s" ${(kv)ansi} _comp_colors+=( $ccol ) - compadd "$expl[@]" $suf $pre -k ansi && ret=0 - if (( $#suf )) && compset -P "(<->|%v)"; then + compadd "$expl[@]" "$suf[@]" $pre -k ansi && ret=0 + if [[ $ISUFFIX != (\\|)}* ]] && compset -P "(<->|%v)"; then _wanted ansi-colors expl 'closing brace' compadd -S '' \} && ret=0 elif (( $+terminfo[colors] )); then (( cols = $terminfo[colors] - 1 )) (( cols = cols > 255 ? 255 : cols )) _description -V terminal-colors expl 'terminal color' grp="$expl[expl[(i)-J]+1]" - compadd "$expl[@]" $suf $pre {0..$cols} + compadd "$expl[@]" "$suf[@]" $pre {0..$cols} for c in {0..$cols}; do _comp_colors+=( "($grp)=${c}=${${${(%):-%F{$c\}}#?\[}%m}" ) done @@ -93,11 +94,14 @@ elif compset -P '%[0-9-\\]#(\\|)\([0-9-]#'; then 'w:day of week (Sunday = 0)' ) [[ $IPREFIX != *- ]] && _describe -t ternary-prompt-expressions \ - 'ternary prompt format test character' specs $suf && ret=0 + 'ternary prompt format test character' specs "$suf[@]" && ret=0 _message -e numbers number elif compset -P '%D(\\|){'; then compset -S '(\\|)}*' _date_formats zsh && ret=0 +elif compset -P '%H(\\|){'; then + compset -S '(\\|)}*' || suf=( -S "$bs}" ) + _wanted highlight-groups expl 'highlight group' compadd "$suf[@]" -k .zle.hlgroups && ret=0 elif [[ -prefix '%' ]] || ! zstyle -t ":completion:${curcontext}:prompt-format-specifiers" prefix-needed then @@ -152,6 +156,7 @@ then 'B:start bold' 'b:stop bold' 'E:clear to end of line' + 'H{:use highlight group' 'U:start underline' 'u:stop underline' 'S:start standout' diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo index 3f708eb5a..f75298a1b 100644 --- a/Doc/Zsh/compsys.yo +++ b/Doc/Zsh/compsys.yo @@ -2023,8 +2023,8 @@ position shown as a percentage of the total length otherwise. In each case the form with the uppercase letter will be replaced by a string of fixed width, padded to the right with spaces, while the lowercase form will be replaced by a variable width string. As in other prompt strings, the -escape sequences `tt(%S)', `tt(%s)', `tt(%B)', `tt(%b)', `tt(%U)', -`tt(%u)' for entering and leaving the display modes +escape sequence `tt(%H)` along with `tt(%S)', `tt(%s)', `tt(%B)', `tt(%b)', +`tt(%U)', `tt(%u)' for entering and leaving the display modes standout, bold and underline, and `tt(%F)', `tt(%f)', `tt(%K)', `tt(%k)' for changing the foreground background colour, are also available, as is the form `tt(%{)...tt(%})' for enclosing escape sequences which display with zero diff --git a/Doc/Zsh/compwid.yo b/Doc/Zsh/compwid.yo index 9461ace17..b0c9b0a5f 100644 --- a/Doc/Zsh/compwid.yo +++ b/Doc/Zsh/compwid.yo @@ -597,7 +597,8 @@ ifnzman((see noderef(Prompt Expansion)))\ ifzman(as described in the section EXPANSION OF PROMPT SEQUENCES in zmanref(zshmisc)): `tt(%B)', `tt(%S)', `tt(%U)', `tt(%F)', `tt(%K)' and their lower case -counterparts, as well as `tt(%{)...tt(%})'. `tt(%F)', `tt(%K)' and +counterparts, as well as `tt(%H)' and `tt(%{)...tt(%})'. `tt(%F)', +`tt(%K)', `tt(%H)' and `tt(%{)...tt(%})' take arguments in the same form as prompt expansion. (Note that the sequence `tt(%G)' is not available; an argument to `tt(%{)' should be used instead.) The sequence `tt(%%)' diff --git a/Src/Modules/watch.c b/Src/Modules/watch.c index 97d4fa608..ba17cf940 100644 --- a/Src/Modules/watch.c +++ b/Src/Modules/watch.c @@ -373,6 +373,13 @@ watchlog2(int inout, WATCH_STRUCT_UTMP *u, char *fmt, int prnt, int fini) case 'f': tunsetattrs(TXTFGCOLOUR); break; + case 'H': + if (*fmt == '{') { + fmt = parsehighlight(fmt + 1, '}', &atr); + if (atr && atr != TXT_ERROR) + treplaceattrs(atr); + } + break; case 'K': if (*fmt == '{') { fmt++; @@ -428,7 +435,7 @@ watchlog_match(char *teststr, char *actual, size_t buflen) int ret = 0; Patprog pprog; char *str = dupstring(teststr); - int len = strnlen(actual, buflen); + size_t len = strnlen(actual, buflen); char *user = metafy(actual, len, len == buflen ? META_HEAPDUP : META_USEHEAP); diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c index 9cb89a60d..5619160a9 100644 --- a/Src/Zle/complist.c +++ b/Src/Zle/complist.c @@ -1181,6 +1181,13 @@ compprintfmt(char *fmt, int n, int dopr, int doesc, int ml, int *stop) if (dopr) tunsetattrs(TXTBGCOLOUR); break; + case ZWC('H'): + if (*p == '{') { + p = parsehighlight(p + 1, '}', &atr); + if (atr != TXT_ERROR && dopr) + treplaceattrs(atr); + } + break; case ZWC('{'): if (arg) cc += arg; diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c index 225ce8c74..aa3c71bc2 100644 --- a/Src/Zle/zle_tricky.c +++ b/Src/Zle/zle_tricky.c @@ -2501,6 +2501,14 @@ printfmt(char *fmt, int n, int dopr, int doesc) case 'k': tunsetattrs(TXTBGCOLOUR); break; + case 'H': + if (p[1] == '{') { + p = parsehighlight(p + 2, '}', &atr); + --p; + if (atr != TXT_ERROR) + treplaceattrs(atr); + } + break; case '{': if (arg) cc += arg; diff --git a/Src/prompt.c b/Src/prompt.c index 7acbe0e47..e10b05215 100644 --- a/Src/prompt.c +++ b/Src/prompt.c @@ -270,7 +270,8 @@ zattrescape(zattr atr, int *len) } /* Parse the argument for %H */ -static char * +/**/ +mod_export char * parsehighlight(char *arg, char endchar, zattr *atr) { static int entered = 0; @@ -295,9 +296,9 @@ parsehighlight(char *arg, char endchar, zattr *atr) } else *atr = TXT_ERROR; if (ep) - *ep = endchar; + *ep++ = endchar; else - ep = strchr(arg, '\0') - 1; + ep = strchr(arg, '\0'); entered = 0; return ep; } @@ -635,6 +636,7 @@ putpromptchar(int doprint, int endchar) case 'H': if (bv->fm[1] == '{') { bv->fm = parsehighlight(bv->fm + 2, '}', &atr); + --bv->fm; if (atr != TXT_ERROR) { treplaceattrs(atr); applytextattributes(TSC_PROMPT); -- cgit v1.2.3 From 610b18875ad9f4498a57e9af6903bcac3b14ff46 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Mon, 4 Mar 2024 21:07:01 -0800 Subject: 52650 plus minor fixes: add -u for named references pointing to "upper" scope --- ChangeLog | 9 +++++++++ Doc/Zsh/builtins.yo | 8 ++++++-- Doc/Zsh/expn.yo | 19 +++++++++++++++++++ Doc/Zsh/func.yo | 13 ++++++++----- Doc/Zsh/mod_ksh93.yo | 2 +- Etc/FAQ.yo | 7 +++++++ Src/Modules/ksh93.c | 2 +- Src/builtin.c | 2 +- Src/exec.c | 22 +++++++++++++++++++++- Src/params.c | 32 +++++++++++++++++++------------ Test/K01nameref.ztst | 53 +++++++++++++++++++++++++++++++++++++++++++++++++--- Test/V10private.ztst | 4 ++-- 12 files changed, 145 insertions(+), 28 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 0dd7268d1..2d4d9922e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2024-03-04 Bart Schaefer + + * 52650 plus minor fixes: Doc/Zsh/builtins.yo, Doc/Zsh/expn.yo, + Doc/Zsh/func.yo, Doc/Zsh/mod_ksh93.yo, Etc/FAQ.yo, + Src/Modules/ksh93.c, Src/builtin.c, Src/exec.c, Src/params.c, + Test/D04parameter.ztst, Test/K01nameref.ztst, + Test/V10private.ztst: add -u option for named references that + point to the "upper" scope, failed assignments have status 1 + 2024-03-05 Oliver Kiddle * 52646: Completion/Zsh/Type/_ps1234, Doc/Zsh/compsys.yo, diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 6318053d8..7a9684ac8 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -2052,13 +2052,17 @@ cindex(named reference) cindex(reference, named) The flag tt(-n) creates a em(named reference) to another parameter. The second parameter need not exist at the time the reference is -created. Only tt(-g) and tt(-r) may be used in conjunction with +created. Only tt(-g), tt(-u), and tt(-r) may be used in conjunction with tt(-n). The var(name) so created may not be an array element nor use a subscript, but the var(value) assigned may be any valid parameter name syntax, even a subscripted array element (including an associative array element) or an array slice, which is evaluated when the named reference is expanded. It is an error for a named reference to refer -to itself, even indirectly through a chain of references. +to itself, even indirectly through a chain of references. When tt(-u) +is applied to a named reference, the parameter identified by var(value) +is always found in the calling function scope rather than the current +local scope. In this case, if there is no such parameter in the calling +scope, assignments to the named reference may fail, setting tt($?) to 1. See ifzman(zmanref(zshexpn))ifnzman(noderef(Parameter Expansion)) and ifzman(zmanref(zshparam))ifnzman(noderef(Parameters)) for details of the behavior of named references. diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index 2acfd08c9..183ca6e03 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -1600,6 +1600,25 @@ example(tt(before local: OUTER) tt(by reference: OUTER) tt(after func: RESULT)) +To force a named reference to refer to the outer scope, even if a local +has already been declared, add the tt(-u) option when declaring the +named reference. In this case var(rname) should already exist in the +outer scope, otherwise the behavior of assignment through var(pname) +is not defined and may change the scope of the reference or fail with +a status of 1. Example of correct usage: +ifzman() +example(tt(caller=OUTER) +tt(func LPAR()RPAR() {) +tt( print before local: $caller) +tt( local caller=INNER) +tt( print after local: $caller) +tt( typeset -n -u outer=$1) +tt( print by reference: $outer) +tt( outer=RESULT) +tt(}) +tt(func caller) +tt(print after func: $caller)) + Note, however, that named references to em(special) parameters acquire the behavior of the special parameter, regardless of the scope where the reference is declared. diff --git a/Doc/Zsh/func.yo b/Doc/Zsh/func.yo index d4914df7a..7b71e34e9 100644 --- a/Doc/Zsh/func.yo +++ b/Doc/Zsh/func.yo @@ -31,10 +31,12 @@ referent parameter is in scope, and as early as possible in the function if the reference is to a parameter in a calling scope. A typical use of named references is to pass the name -of the referent as a positional parameter. For example, +of the referent as a positional parameter. In this case it is +good practice to use the tt(-u) option to reference the calling +scope. For example, ifzman() example(pop+LPAR()RPAR() { - local -n ref=$1 + local -nu ref=$1 local last=$ref[$#ref] ref[$#ref]=LPAR()RPAR() print -r -- $last @@ -43,9 +45,10 @@ array=LPAR() a list of five values RPAR() pop array) prints the word `tt(values)' and shortens `tt($array)' to -`tt(LPAR() a list of five RPAR())'. There are no local parameters in -tt(pop) at the time `tt(ref=$1)' is assigned, so `tt(ref)' becomes a -reference to `tt(array)' in the caller. +`tt(LPAR() a list of five RPAR())'. With tt(-nu), `tt(ref)' becomes a +reference to `tt(array)' in the caller. There are no local parameters in +tt(pop) at the time `tt(ref=$1)' is assigned, so in this example tt(-u) +could have been omitted, but it makes the intention clear. Functions execute in the same process as the caller and share all files diff --git a/Doc/Zsh/mod_ksh93.yo b/Doc/Zsh/mod_ksh93.yo index 9cd708d10..7508758aa 100644 --- a/Doc/Zsh/mod_ksh93.yo +++ b/Doc/Zsh/mod_ksh93.yo @@ -12,7 +12,7 @@ The single builtin provided by this module is: startitem() findex(nameref) cindex(named references, creating) -item(tt(nameref) [ tt(-r) ] var(pname)[tt(=)var(rname)])( +item(tt(nameref) [ tt(-gur) ] var(pname)[tt(=)var(rname)])( Equivalent to tt(typeset -n )var(pname)tt(=)var(rname) However, tt(nameref) is a builtin command rather than a reserved word, diff --git a/Etc/FAQ.yo b/Etc/FAQ.yo index 145ef02c9..4a86050e6 100644 --- a/Etc/FAQ.yo +++ b/Etc/FAQ.yo @@ -1025,6 +1025,13 @@ label(210) HIT:SPOT ) + Dynamic scoping applies to named references, so for example a named + reference declared in global scope may be used in function scopes. + In ksh, local parameters have static scope, so named references in + zsh may have side-effects that do not occur in ksh. To limit those + effects, mytt(zmodload zsh/param/private) and declare all named + references mytt(private). + Named references may be used in zsh versions later than 5.9. sect(What is zsh's support for non-forking command substitution?) diff --git a/Src/Modules/ksh93.c b/Src/Modules/ksh93.c index 9af5e1d69..6760cbca0 100644 --- a/Src/Modules/ksh93.c +++ b/Src/Modules/ksh93.c @@ -38,7 +38,7 @@ */ static struct builtin bintab[] = { - BUILTIN("nameref", BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "gr", "n") + BUILTIN("nameref", BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "gur", "n") }; #include "zsh.mdh" diff --git a/Src/builtin.c b/Src/builtin.c index 83144677b..ba9cb03c2 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2699,7 +2699,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) off |= bit; } if (OPT_MINUS(ops,'n')) { - if ((on|off) & ~PM_READONLY) { + if ((on|off) & ~(PM_READONLY|PM_UPPER)) { zwarnnam(name, "no other attributes allowed with -n"); return 1; } diff --git a/Src/exec.c b/Src/exec.c index d85adbea5..0231bc361 100644 --- a/Src/exec.c +++ b/Src/exec.c @@ -2604,6 +2604,17 @@ addvars(Estate state, Wordcode pc, int addflags) opts[ALLEXPORT] = allexp; } else pm = assignsparam(name, val, myflags); + if (!pm) { + lastval = 1; + /* + * This is cheating but some exec functions propagate + * assignment status only from command substitution + * + * zerr("%s: assignment failed", name); + */ + if (!cmdoutval) + cmdoutval = 1; + } if (errflag) { state->pc = opc; return; @@ -2628,7 +2639,16 @@ addvars(Estate state, Wordcode pc, int addflags) } fprintf(xtrerr, ") "); } - assignaparam(name, arr, myflags); + if (!assignaparam(name, arr, myflags)) { + lastval = 1; + /* + * See above RE "cheating" + * + * zerr("%s: array assignment failed", name); + */ + if (!cmdoutval) + cmdoutval = 1; + } if (errflag) { state->pc = opc; return; diff --git a/Src/params.c b/Src/params.c index e83e4aa5e..263cd0c52 100644 --- a/Src/params.c +++ b/Src/params.c @@ -1060,8 +1060,7 @@ createparam(char *name, int flags) "BUG: local parameter is not unset"); oldpm = lastpm; } - } else - flags |= PM_NAMEREF; + } } DPUTS(oldpm && oldpm->level > locallevel, @@ -6267,10 +6266,12 @@ resolve_nameref(Param pm, const Asgment stop) } } else if ((hn = gethashnode2(realparamtab, seek))) { if (pm) { - if (!(stop && (stop->flags & (PM_LOCAL)))) - hn = (HashNode)upscope((Param)hn, - ((pm->node.flags & PM_NAMEREF) ? - pm->base : ((Param)hn)->level)); + if (!(stop && (stop->flags & (PM_LOCAL)))) { + int scope = ((pm->node.flags & PM_NAMEREF) ? + ((pm->node.flags & PM_UPPER) ? -1 : + pm->base) : ((Param)hn)->level); + hn = (HashNode)upscope((Param)hn, scope); + } /* user can't tag a nameref, safe for loop detection */ pm->node.flags |= PM_TAGGED; } @@ -6316,11 +6317,13 @@ setloopvar(char *name, char *value) static void setscope(Param pm) { - if (pm->node.flags & PM_NAMEREF) { + queue_signals(); + if (pm->node.flags & PM_NAMEREF) do { Param basepm; struct asgment stop; char *refname = GETREFNAME(pm); char *t = refname ? itype_end(refname, INAMESPC, 0) : NULL; + int q = queue_signal_level(); /* Temporarily change nameref to array parameter itself */ if (t && *t == '[') @@ -6330,9 +6333,11 @@ setscope(Param pm) stop.name = ""; stop.value.scalar = NULL; stop.flags = PM_NAMEREF; - if (locallevel) + if (locallevel && !(pm->node.flags & PM_UPPER)) stop.flags |= PM_LOCAL; + dont_queue_signals(); /* Prevent unkillable loops */ basepm = (Param)resolve_nameref(pm, &stop); + restore_queue_signals(q); if (t) { pm->width = t - refname; *t = '['; @@ -6345,7 +6350,7 @@ setscope(Param pm) if (upscope(pm, pm->base) == pm) { zerr("%s: invalid self reference", refname); unsetparam_pm(pm, 0, 1); - return; + break; } pm->node.flags &= ~PM_SELFREF; } else if (pm->base == pm->level) { @@ -6353,7 +6358,7 @@ setscope(Param pm) strcmp(pm->node.nam, refname) == 0) { zerr("%s: invalid self reference", refname); unsetparam_pm(pm, 0, 1); - return; + break; } } } else if ((t = GETREFNAME(basepm))) { @@ -6361,7 +6366,7 @@ setscope(Param pm) strcmp(pm->node.nam, t) == 0) { zerr("%s: invalid self reference", refname); unsetparam_pm(pm, 0, 1); - return; + break; } } } else @@ -6381,7 +6386,8 @@ setscope(Param pm) zerr("%s: invalid self reference", refname); unsetparam_pm(pm, 0, 1); } - } + } while (0); + unqueue_signals(); } /**/ @@ -6393,6 +6399,8 @@ upscope(Param pm, int reflevel) pm = up; up = up->old; } + if (reflevel < 0 && locallevel > 0) + return pm->level == locallevel ? up : pm; return pm; } diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst index ff48e2289..47d32697c 100644 --- a/Test/K01nameref.ztst +++ b/Test/K01nameref.ztst @@ -492,7 +492,6 @@ F:unexpected side-effects of previous tests } typeset -p ptr2 1:up-reference part 5, stacked namerefs, end not in scope -F:What is the correct behavior for the scope of ptr1? >typeset -n ptr1=ptr2 >typeset -n ptr2 >ptr1=ptr2 @@ -529,6 +528,49 @@ F:Same test, should part 5 output look like this? >typeset -n ptr2 >typeset ptr2=val + () { + () { + local var + typeset -nu ptr1=var + ptr1=outer && print -u2 assignment expected to fail + typeset -n ptr2=var + ptr2=inner + typeset -n + printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2" + } + typeset -p var + } + typeset -p var +1:up-reference part 7, upscope namerefs, end not in scope +>ptr1=var +>ptr2=var +>ptr1= +>ptr2=inner +*?*typeset*: no such variable: var +*?*typeset*: no such variable: var + + typeset var + () { + () { + local var + typeset -nu ptr1=var + ptr1=outer || print -u2 assignment expected to succeed + typeset -n ptr2=var + ptr2=inner + typeset -n + printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2" + } + typeset -p var + } + typeset -p var +0:up-reference part 8, upscope namerefs, end in scope +>ptr1=var +>ptr2=var +>ptr1=outer +>ptr2=inner +>typeset -g var=outer +>typeset var=outer + if zmodload zsh/parameter; then () { zmodload -u zsh/parameter @@ -539,7 +581,7 @@ F:Same test, should part 5 output look like this? } else ZTST_skip='Cannot zmodload zsh/parameter, skipping autoload test' fi -0:up-reference part 3, autoloading with hidden special +0:up-reference part 9, autoloading with hidden special >nameref-local-nameref-local >typeset -h parameters @@ -762,12 +804,17 @@ F:relies on global TYPESET_TO_UNSET in %prep bar=xx typeset -n foo=bar - () { typeset -n foo; foo=zz; foo=zz; print $bar $zz } + () { + typeset -n foo; foo=zz + foo=zz || print -u2 foo: assignment failed + print $bar $zz + } () { typeset -n foo; foo=zz; local zz; foo=zz; print $bar $zz } 0:regression: local nameref may not in-scope a global parameter F:previously this could create an infinite recursion and crash >xx >xx zz +*?*foo: assignment failed typeset -nm foo=bar 1:create nameref by pattern match not allowed diff --git a/Test/V10private.ztst b/Test/V10private.ztst index efa346002..ed51316f3 100644 --- a/Test/V10private.ztst +++ b/Test/V10private.ztst @@ -378,7 +378,7 @@ F:Here ptr1 finds private ptr2 by scope mismatch typeset -p ptr1 ptr2 typeset val=LOCAL () { - ptr1=val # This is a silent no-op, why? + ptr1=val || print -u2 ptr1: assignment failed typeset -n printf "%s=%s\n" ptr1 "$ptr1" ptr2 "$ptr2" } @@ -388,7 +388,6 @@ F:Here ptr1 finds private ptr2 by scope mismatch 1:up-reference for private namerefs, end not in scope F:See K01nameref.ztst up-reference part 5 F:Here ptr1 finds private ptr2 by scope mismatch -F:Assignment silently fails, is that correct? >typeset -n ptr1=ptr2 >typeset -hn ptr2='' >ptr1=ptr2 @@ -396,6 +395,7 @@ F:Assignment silently fails, is that correct? >ptr2= >typeset -n ptr1=ptr2 >typeset -hn ptr2='' +*?*ptr1: assignment failed *?*no such variable: ptr2 typeset ptr2 -- cgit v1.2.3 From b56250e9b99d9cd0e70261ff6e7f6f33583c1b04 Mon Sep 17 00:00:00 2001 From: Stephane Chazelas Date: Tue, 5 Mar 2024 19:57:35 +0000 Subject: 52685: fix typo in the name of bash's BASH_ENV variable. --- ChangeLog | 5 +++++ Doc/Zsh/restricted.yo | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index b4ebfedbc..68f826342 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2024-03-05 Stephane Chazelas + + * 52685: Doc/Zsh/restricted.yo: fix typo in the name of bash's + BASH_ENV variable. + 2024-03-04 Bart Schaefer * unposted (cf. 52615): Src/builtin.c: use META_NOALLOC for 52591 diff --git a/Doc/Zsh/restricted.yo b/Doc/Zsh/restricted.yo index 7948cfe8a..a84fd28ea 100644 --- a/Doc/Zsh/restricted.yo +++ b/Doc/Zsh/restricted.yo @@ -68,7 +68,7 @@ If a `tt(perl)', `tt(python)', `tt(bash)', or other general purpose interpreted script is treated as a restricted command, the user can work around the restriction by setting specially crafted `tt(PERL5LIB)', `tt(PYTHONPATH)', -`tt(BASHENV)' (etc.) environment variables. On GNU systems, any +`tt(BASH_ENV)' (etc.) environment variables. On GNU systems, any command can be made to run arbitrary code when performing character set conversion (including zsh itself) by setting a `tt(GCONV_PATH)' environment variable. Those are only a few examples. -- cgit v1.2.3 From 330821de01ebf1115079222f719c9a28cc26ff57 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Tue, 5 Mar 2024 21:13:33 -0800 Subject: 52692: local typeset of the name of a named reference hides the reference --- ChangeLog | 7 +++++++ Doc/Zsh/params.yo | 5 +++-- Src/builtin.c | 15 ++++++++------- Src/params.c | 3 ++- Test/K01nameref.ztst | 51 ++++++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 68 insertions(+), 13 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 68f826342..a3cec14ab 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2024-03-05 Bart Schaefer + + * 52692: Doc/Zsh/params.yo, Src/builtin.c, Src/params.c, + Test/K01nameref.ztst: local declaration (typeset) of the + name of a named reference hides the reference rather than + following it. Also fix two related crash bugs. + 2024-03-05 Stephane Chazelas * 52685: Doc/Zsh/restricted.yo: fix typo in the name of bash's diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index 8c5e67e70..d179a0d1d 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -670,8 +670,9 @@ This manual was generated with Zsh tt(version()). When a em(named reference) is created with `tt(typeset -n)', all uses of var(pname) in assignments and expansions instead assign to or expand var(rname). This also applies to `tt(unset )var(pname)' and to -most subsequent uses of `tt(typeset)' with the exception of -`tt(typeset -n)' and `tt(typeset +n)', so to remove a named reference, +most subsequent uses of `tt(typeset)' with the exceptions of declaring +a local in a called function, or updating a current-scope parameter with +`tt(typeset -n)' or `tt(typeset +n)'. Thus to remove a named reference, use either `tt(unset -n )var(pname)' (preferred) or one of: ifzman() example(tt(typeset -n )var(pname=) diff --git a/Src/builtin.c b/Src/builtin.c index 6f98990f9..829b899f8 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2030,11 +2030,10 @@ typeset_single(char *cname, char *pname, Param pm, int func, int usepm, tc, keeplocal = 0, newspecial = NS_NONE, readonly, dont_set = 0; char *subscript; - if (pm && (pm->node.flags & PM_NAMEREF) && !((off|on) & PM_NAMEREF)) { - if (!(off & PM_NAMEREF)) { - if ((pm = (Param)resolve_nameref(pm, NULL))) - pname = pm->node.nam; - } + if (pm && (pm->node.flags & PM_NAMEREF) && !((off|on) & PM_NAMEREF) && + (pm->level == locallevel || !(on & PM_LOCAL))) { + if ((pm = (Param)resolve_nameref(pm, NULL))) + pname = pm->node.nam; if (pm && (pm->node.flags & PM_NAMEREF) && (on & ~(PM_NAMEREF|PM_LOCAL|PM_READONLY))) { /* Changing type of PM_SPECIAL|PM_AUTOLOAD is a fatal error. * @@ -3125,8 +3124,10 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) oldpm->u.str) asg->value.scalar = dupstring(oldpm->u.str); /* Defer read-only error to typeset_single() */ - if (!(hn->flags & PM_READONLY)) + if (!(hn->flags & PM_READONLY)) { unsetparam_pm(oldpm, 0, 1); + hn = NULL; + } } /* Passing a NULL pm to typeset_single() makes the * nameref read-only before assignment, which breaks @@ -3134,7 +3135,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) * so this is special-cased to permit that action * like assign-at-create for other parameter types. */ - if (!(hn->flags & PM_READONLY)) + if (hn && !(hn->flags & PM_READONLY)) hn = NULL; } } diff --git a/Src/params.c b/Src/params.c index 4bcf41c22..973df3fe5 100644 --- a/Src/params.c +++ b/Src/params.c @@ -1034,7 +1034,8 @@ createparam(char *name, int flags) } if (oldpm && !(flags & PM_NAMEREF) && - (!(oldpm->node.flags & PM_RO_BY_DESIGN) || !(flags & PM_LOCAL)) && + (oldpm->level == locallevel ? + !(oldpm->node.flags & PM_RO_BY_DESIGN) : !(flags & PM_LOCAL)) && (oldpm->node.flags & PM_NAMEREF) && (oldpm = upscope(oldpm, oldpm->base))) { Param lastpm; diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst index e45b922e2..bb0d11821 100644 --- a/Test/K01nameref.ztst +++ b/Test/K01nameref.ztst @@ -51,9 +51,19 @@ 0:remove nameref attribute >typeset ptr=var - typeset -n ptr - typeset -t ptr - typeset -p ptr + typeset -n ptr=gvar + () { + local ptr + typeset -p ptr + } + typeset -p ptr +0:Local non-reference hides outside reference +>typeset ptr +>typeset -n ptr=gvar + + typeset -n ptr + typeset -t ptr + typeset -p ptr 0:change type of a placeholder F:Other type changes are fatal errors, should this also be? >typeset -n ptr='' @@ -845,4 +855,39 @@ F:previously this could create an infinite recursion and crash 1:create nameref by pattern match not allowed *?*typeset:1: invalid reference +# +# The following tests are run in interactive mode, using PS1 as an +# assignable special with side-effects. This crashed at one time. +# + + # Note bypassing TYPESET_TO_UNSET here + $ZTST_testdir/../Src/zsh -fis <<<$' + typeset -n p=PS1 + () { + typeset -p p + local p + typeset -p p + p=xx + typeset -p p + } + ' +0:regression: assign to local that shadows global named reference +>typeset -g -n p=PS1 +>typeset p='' +>typeset p=xx +*?* + + # Note bypassing TYPESET_TO_UNSET here + $ZTST_testdir/../Src/zsh -fis <<<$' + () { + typeset p=PS1 + typeset -n p + p=zz + } + typeset -p PS1 + ' +0:regression - converting a string into a named reference +>typeset PS1=zz +*?* + %clean -- cgit v1.2.3 From fa9b3ad5977ede0a4635cd86276dd0f0c2f6f03e Mon Sep 17 00:00:00 2001 From: Stephane Chazelas Date: Fri, 8 Mar 2024 11:07:05 +0000 Subject: 52704: improve zsh_eval_context documentation --- ChangeLog | 6 ++++++ Doc/Zsh/params.yo | 11 ++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index a3cec14ab..ef6c9f02d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2024-03-08 Stephane Chazelas + + * 52704: Doc/Zsh/params.yo, mention new ${ ... } and ${|...} + operators and fix process substitution terminology in + documentation of $zsh_eval_context. + 2024-03-05 Bart Schaefer * 52692: Doc/Zsh/params.yo, Src/builtin.c, Src/params.c, diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index d179a0d1d..9516c84de 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -1031,11 +1031,12 @@ Code specified by the tt(-c) option to the command line that invoked the shell. ) item(tt(cmdsubst))( -Command substitution using the tt(`)var(...)tt(`) or -tt($+LPAR())var(...)tt(RPAR()) construct. +Command substitution using of the tt(`)var(...)tt(`), +tt($+LPAR())var(...)tt(RPAR()), tt(${ )var(...)tt( }) or +tt(${|)var(...)tt(}) constructs. ) item(tt(equalsubst))( -File substitution using the tt(=+LPAR())var(...)tt(RPAR()) construct. +The tt(=+LPAR())var(...)tt(RPAR()) form of process substitution. ) item(tt(eval))( Code executed by the tt(eval) builtin. @@ -1063,13 +1064,13 @@ item(tt(globsort))( Code executed to order files by the tt(o) glob qualifier. ) item(tt(insubst))( -File substitution using the tt(LPAR())var(...)tt(RPAR()) construct. +The tt(>LPAR())var(...)tt(RPAR()) form of process substitution. ) item(tt(sched))( Code executed by the tt(sched) builtin. -- cgit v1.2.3 From d1e041188db6900e7f8aca2e9fec71d74270babc Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Wed, 13 Mar 2024 11:36:16 -0700 Subject: unposted: update doc for "colors" for workers/47489,50212 (italic and bright) --- ChangeLog | 5 +++++ Doc/Zsh/contrib.yo | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 389054f14..87053e78f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2024-03-13 Bart Schaefer + + * unposted: Doc/Zsh/contrib.yo: update doc for "colors" to match + workers/47489,50212 (italic and bright) + 2024-03-12 Bart Schaefer * unposted: Src/Modules/ksh93.c: "typeset -p" has problems with diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index e682c800a..c1bea6022 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -4313,13 +4313,17 @@ tt(colors) more than once. The eight base colors are: tt(black), tt(red), tt(green), tt(yellow), tt(blue), tt(magenta), tt(cyan), and tt(white). Each of these has codes for foreground and background. In addition there are seven intensity attributes: -tt(bold), tt(faint), tt(standout), tt(underline), tt(blink), tt(reverse), +tt(bold), tt(faint), tt(italic), tt(underline), tt(blink), tt(reverse), and tt(conceal). Finally, there are seven codes used to negate attributes: tt(none) (reset all attributes to the defaults), tt(normal) -(neither bold nor faint), tt(no-standout), tt(no-underline), tt(no-blink), +(neither bold nor faint), tt(no-italic), tt(no-underline), tt(no-blink), tt(no-reverse), and tt(no-conceal). Some terminals do not support all combinations of colors and intensities. +Prior to zsh tt(5.8.1) the intensity tt(standout) was provided. It has +been replaced by the more specific tt(italic) and tt(reverse) to match +the specification, but some terminals may swap these or make one of them +produce blinking text even if the tt(blink) code is not usable. The associative arrays are: -- cgit v1.2.3 From 21fe2dce441116e90a6611527f1fcbccdc97a856 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Wed, 13 Mar 2024 21:10:23 -0700 Subject: 52753: Clarify "nocorrect" when introducing precommand modifiers. --- ChangeLog | 3 +++ Doc/Zsh/grammar.yo | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 87053e78f..ab59d83d7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2024-03-13 Bart Schaefer + * 52753: Doc/Zsh/grammar.yo: Clarify "nocorrect" when introducing + precommand modifiers. + * unposted: Doc/Zsh/contrib.yo: update doc for "colors" to match workers/47489,50212 (italic and bright) diff --git a/Doc/Zsh/grammar.yo b/Doc/Zsh/grammar.yo index 915b93bc0..b80f9750c 100644 --- a/Doc/Zsh/grammar.yo +++ b/Doc/Zsh/grammar.yo @@ -100,14 +100,15 @@ More generally, a list can be seen as a set of any shell commands whatsoever, including the complex commands below; this is implied wherever the word `list' appears in later descriptions. For example, the commands in a shell function form a special sort of list. + texinode(Precommand Modifiers)(Complex Commands)(Simple Commands & Pipelines)(Shell Grammar) sect(Precommand Modifiers) cindex(precommand modifiers) cindex(modifiers, precommand) -A simple command may be preceded by a em(precommand modifier), -which will alter how the command is interpreted. These modifiers are -shell builtin commands with the exception of tt(nocorrect) which is -a reserved word. +With the exception of tt(nocorrect), which is a reserved word that +affects further parsing when it is found in command position, each +of the following builtin commands is a em(precommand modifier) which +may precede a simple command to alter how that command is interpreted. startitem() findex(-) @@ -158,8 +159,7 @@ before any parsing is done. It has no effect in non-interactive shells. ) findex(noglob) item(tt(noglob))( -Filename generation (globbing) is not performed on any of -the words. +Filename generation (globbing) is not performed on any of the words. ) enditem() texinode(Complex Commands)(Alternate Forms For Complex Commands)(Precommand Modifiers)(Shell Grammar) -- cgit v1.2.3 From 25182cc2e69ab1cfeeb3f0faa1d28d774393043b Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sun, 17 Mar 2024 14:28:28 -0700 Subject: 52759: ${ ... } trims one trailing newline; "${ ... }" preserves that newline. --- ChangeLog | 5 +++++ Doc/Zsh/expn.yo | 3 +++ Etc/FAQ.yo | 21 ++++++++++++--------- Src/subst.c | 8 ++++++-- Test/D10nofork.ztst | 41 +++++++++++++++++++++++++++++++++++------ 5 files changed, 61 insertions(+), 17 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index c3f770477..7e5f68059 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,10 @@ 2024-03-14 Bart Schaefer + * 52759: Doc/Zsh/expn.yo, Etc/FAQ.yo, Src/subst.c, + Test/D10nofork.ztst: change ${ ... } substitution to trim one + trailing newline; instead "${ ... }" (with quotes) preserves that + newline. All trailing newlines are still trimmed in emulations. + * unposted: Etc/BUGS: HIST_IGNORE_DUPS mishandles quoted whitespace. * 52752: Src/params.c, Test/B02typeset.ztst: more typeset -p fixes diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index 183ca6e03..0e121e784 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -1950,6 +1950,9 @@ the braces by whitespace, like `tt(${ )...tt( })', is replaced by its standard output. Like `tt(${|)...tt(})' and unlike `tt($LPAR())...tt(RPAR())', the command executes in the current shell context with function local behaviors and does not create a subshell. +Word splitting does not apply unless tt(SH_WORD_SPLIT) is set, but a +single trailing newline is stripped unless the substitution is enclosed +in double quotes. Note that because the `tt(${|)...tt(})' and `tt(${ )...tt( })' forms must be parsed at once as both string tokens and commands, all other diff --git a/Etc/FAQ.yo b/Etc/FAQ.yo index 4a86050e6..4d71c8f30 100644 --- a/Etc/FAQ.yo +++ b/Etc/FAQ.yo @@ -1091,20 +1091,23 @@ sect(Comparisons of forking and non-forking command substitution) mytt(set -- pos1 pos2 etc). Nothing that happens within mytt($(command)) affects the caller. - mytt($(command)) removes trailing newlines from the output of mytt(command) - when substituting, whereas mytt(${ command }) and its variants do not. - The latter is consistent with mytt(${|...}) from mksh but differs from - bash and ksh, so in emulation modes, newlines are stripped from command - output (not from tt(REPLY) assignments). - When not enclosed in double quotes, the expansion of mytt($(command)) is split on tt(IFS) into an array of words. In contrast, and unlike both bash and ksh, unquoted non-forking substitutions behave like parameter expansions with respect to the tt(SH_WORD_SPLIT) option. - When mytt(command) is myem(not) a builtin, mytt(${ command }) does fork, and - typically forks the same number of times as mytt($(command)), because in - the latter case zsh usually optimizes the final fork into an exec. + Both of the mytt(${|...}) formats retain any trailing newlines, + except as handled by the tt(SH_WORD_SPLIT) option, consistent with + mytt(${|...}) from mksh. mytt(${ command }) removes a single final + newline, but mytt("${ command }") retains it. This differs from + bash and ksh, so in emulation modes, newlines are stripped even from + quoted command output. In all cases, mytt($(command)) removes all + trailing newlines from the output of mytt(command). + + When mytt(command) is myem(not) a builtin, mytt(${ command }) does + fork, and typically forks the same number of times as + mytt($(command)), because in the latter case zsh usually optimizes + the final fork into an exec. Redirecting input from files has subtle differences: itemization( diff --git a/Src/subst.c b/Src/subst.c index 49f7336bb..9d20a2d0e 100644 --- a/Src/subst.c +++ b/Src/subst.c @@ -1900,6 +1900,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, /* The command string to be run by ${|...;} */ char *cmdarg = NULL; size_t slen = 0; + int trim = (!EMULATION(EMULATE_ZSH)) ? 2 : !qt; inbrace = 1; s++; @@ -2005,10 +2006,13 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags, int onoerrs = noerrs, rplylen; noerrs = 2; rplylen = zstuff(&cmdarg, rplytmp); - if (! EMULATION(EMULATE_ZSH)) { + if (trim) { /* bash and ksh strip trailing newlines here */ - while (rplylen > 0 && cmdarg[rplylen-1] == '\n') + while (rplylen > 0 && cmdarg[rplylen-1] == '\n') { rplylen--; + if (trim == 1) + break; + } cmdarg[rplylen] = 0; } noerrs = onoerrs; diff --git a/Test/D10nofork.ztst b/Test/D10nofork.ztst index d6a5588df..fc6b84613 100644 --- a/Test/D10nofork.ztst +++ b/Test/D10nofork.ztst @@ -86,9 +86,39 @@ F:setting option inside is too late for that substitution ?(eval):8: no matches found: f?* purr ${| REPLY=$'trailing newlines remain\n\n' } -0:newline removal should not occur +0:newline removal should not occur, part 1 >trailing newlines remain > +> + + purr ${ echo $'one trailing newline\nremoved\n\n\n' } +0:newline removal in ${ ... }, zsh mode +>one trailing newline +>removed +> +> +> + + () { + emulate -L ksh + purl ${ echo $'all trailing newlines\nremoved\n\n\n' } + purr "${ echo $'all trailing newlines\nremoved\n\n\n' }" + } +0:newline removal in ${ ... }, emulation mode, shwordsplit +>all +>trailing +>newlines +>removed +>all trailing newlines +>removed + + purr "${ echo $'no trailing newlines\nremoved\n\n\n' }" +0:newline removal should not occur, part 2 +>no trailing newlines +>removed +> +> +> > () { @@ -159,7 +189,7 @@ F:Why not use this error in the previous case as well? 1:unbalanced braces, part 4+ ?(eval):1: closing brace expected - purr ${ purr STDOUT } + purr "${ purr STDOUT }" 0:capture stdout >STDOUT > @@ -322,7 +352,7 @@ F:Fiddly here to get EOF past the test syntax 0:here-string behavior >in a here string - <<<${ purr $'stdout as a here string' } + <<<"${ purr $'stdout as a here string' }" 0:another capture stdout >stdout as a here string > @@ -331,7 +361,7 @@ F:Fiddly here to get EOF past the test syntax wrap=${ purr "capture in environment assignment" } typeset -p wrap 0:assignment context >typeset -g wrap='REPLY in environment assignment' ->typeset -g wrap=$'capture in environment assignment\n' +>typeset -g wrap='capture in environment assignment' # Repeat return and exit tests with stdout capture @@ -410,7 +440,7 @@ F:must do this before evaluating the next test block 0:ignored braces, part 1 >buried} - purr ${ purr ${REPLY:-buried}}} + purr "${ purr ${REPLY:-buried}}}" 0:ignored braces, part 2 >buried >} @@ -418,7 +448,6 @@ F:must do this before evaluating the next test block purr ${ { echo nested ;} } 0:ignored braces, part 3 >nested -> purr ${ { echo nested } } DONE 1:ignored braces, part 4 -- cgit v1.2.3 From 49c6978dbbb717847344e7cef99e4ee26f56d234 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Mon, 1 Apr 2024 22:38:27 -0700 Subject: 52865: Documentation update for 52864 --- ChangeLog | 3 +++ Doc/Zsh/expn.yo | 13 +++++++------ Doc/Zsh/params.yo | 4 ++-- Etc/FAQ.yo | 20 +++++++++++--------- 4 files changed, 23 insertions(+), 17 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 9476c50ab..643a33dac 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2024-04-01 Bart Schaefer + * 52865: Doc/Zsh/expn.yo, Doc/Zsh/params.yo, Etc/FAQ.yo: + Documentation update for 52864 + * 52864: Src/lex.c, Src/subst.c, Test/D10nofork.ztst, Test/V10private.ztst: Change ${|var|...} to ${{var} ...}, limit local REPLY behavior to ${|...}, update tests. diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo index 0e121e784..7eade4a11 100644 --- a/Doc/Zsh/expn.yo +++ b/Doc/Zsh/expn.yo @@ -1937,13 +1937,14 @@ split on tt(IFS) unless the tt(SH_WORD_SPLIT) option is set. cindex(substitution, command, current shell) cindex(substitution, command, non forking) cindex(substitution, nofork) -Substitutions of the form `tt(${|)var(param)tt(|)...tt(})' are similar, +Substitutions of the form `tt(${{)var(param)tt(}) ...tt(})' are similar, except that the substitution is replaced by the value of the parameter named by var(param). No implicit save or restore applies to var(param) -except as noted for tt(REPLY), and var(param) should em(not) be declared -within the command. If, after evaluating the expression, var(param) -names an array, array expansion rules apply. However, tt(REPLY) is -always expanded in scalar context, even if assigned an array. +and var(param) should em(not) be declared within the command. No space +is allowed within `tt(${{)' and space or newline is required after +`tt({)var(param)tt(})'. The var(param) may include a subscript, and if, +after evaluating the expression, var(param) names an array, then array +expansion rules apply to the final substitution. A command enclosed in braces preceded by a dollar sign, and set off from the braces by whitespace, like `tt(${ )...tt( })', is replaced by its @@ -1954,7 +1955,7 @@ Word splitting does not apply unless tt(SH_WORD_SPLIT) is set, but a single trailing newline is stripped unless the substitution is enclosed in double quotes. -Note that because the `tt(${|)...tt(})' and `tt(${ )...tt( })' forms +Note that because `tt(${|)...tt(})' and the two related substitutions must be parsed at once as both string tokens and commands, all other braces (`tt({)' or `tt(})') within the command either must be quoted, or must appear in syntactically valid pairs, such as around complex diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index 9516c84de..02ce796a9 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -1032,8 +1032,8 @@ the shell. ) item(tt(cmdsubst))( Command substitution using of the tt(`)var(...)tt(`), -tt($+LPAR())var(...)tt(RPAR()), tt(${ )var(...)tt( }) or -tt(${|)var(...)tt(}) constructs. +tt($+LPAR())var(...)tt(RPAR()),tt(${{)var(name)tt(}) var(...)tt(}), +tt(${|)var(...)tt(}), or tt(${ )var(...)tt( }) constructs. ) item(tt(equalsubst))( The tt(=+LPAR())var(...)tt(RPAR()) form of process substitution. diff --git a/Etc/FAQ.yo b/Etc/FAQ.yo index 4d71c8f30..4e11637ea 100644 --- a/Etc/FAQ.yo +++ b/Etc/FAQ.yo @@ -1047,15 +1047,18 @@ label(211) ) Runs code in the current shell context and then substitutes mytt(${REPLY}). The result is not split into words unless the tt(SH_WORD_SPLIT) option - is set, for example by mytt(${=${| code }}). + is set, for example by mytt(${=${| code }}). mytt($REPLY) is a local + parameter within the substitution so its value in the surrounding scope + is not changed. eit() An extension to #1 verb( - ${|var| code } + ${{var} code } ) Runs code in the current shell and then substitutes mytt(${var}). If mytt(${var}) names an array, the result is an array of those elements, - but no further splitting is done without tt(SH_WORD_SPLIT). + but no further splitting is done without tt(SH_WORD_SPLIT). mytt(${var}) + is myem(not) local to the substitution. eit() The traditional ksh form, except that the closing mytt(;) may usually be omitted: @@ -1071,12 +1074,11 @@ label(211) In all three forms mytt(code) behaves myem(similarly) to an anonymous function invoked like: verb( - () { local REPLY; code } "$@" + () { code } "$@" ) - Thus, mytt($REPLY) is implicitly local and returns to its previous - value after the substitution ends, all other parameters declared from - inside the substitution are also local by default, and positional - parameters mytt($1), mytt($2), etc. are those of the calling context. + Thus, all parameters declared inside the substitution are local by + default, and positional parameters mytt($1), mytt($2), etc. are those + of the calling context. The most significant limitation is that braces (mytt({) and mytt(})) within the substitutions must either be in balanced pairs, or must be @@ -1096,7 +1098,7 @@ sect(Comparisons of forking and non-forking command substitution) bash and ksh, unquoted non-forking substitutions behave like parameter expansions with respect to the tt(SH_WORD_SPLIT) option. - Both of the mytt(${|...}) formats retain any trailing newlines, + Both mytt(${|...}) and mytt(${{var} ...}) retain any trailing newlines, except as handled by the tt(SH_WORD_SPLIT) option, consistent with mytt(${|...}) from mksh. mytt(${ command }) removes a single final newline, but mytt("${ command }") retains it. This differs from -- cgit v1.2.3 From acdcf9d8542a4461c0fceb98fdfef7380a128f78 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Wed, 8 May 2024 09:28:06 +0100 Subject: 52915: be explicit about pattern syntax in conditions --- ChangeLog | 5 +++++ Doc/Zsh/cond.yo | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index df3f9b73c..d3980ecfe 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2024-05-08 Peter Stephenson + + * 52915: Doc/Zsh/cond.yo: be explicit about behaviour of globbing + patterns within conditions. + 2024-04-07 Mikael Magnusson * 52878: Src/subst.c: Fix ${foo:^bar} where bar is an associative diff --git a/Doc/Zsh/cond.yo b/Doc/Zsh/cond.yo index 000e576d0..db92cc766 100644 --- a/Doc/Zsh/cond.yo +++ b/Doc/Zsh/cond.yo @@ -241,7 +241,11 @@ ifnzman(\ noderef(Filename Generation)\ )\ , but there is no special behaviour -of `tt(/)' nor initial dots, and no glob qualifiers are allowed. +of `tt(/)' nor initial dot, and the patterns `tt(**/)' and `tt(***/)' behave +the same as `tt(*/)', in which the `tt(*)' has its standard behaviour +but may also match further `tt(/)' characters. Also, no bare glob +qualifiers are allowed, though the form `((#q)var(...))' is allowed as +shown above. In each of the above expressions, if var(file) is of the form `tt(/dev/fd/)var(n)', -- cgit v1.2.3 From 98a877d32c5ec047c3985183db655cccc707920f Mon Sep 17 00:00:00 2001 From: Jun-ichi Takimoto Date: Mon, 24 Jun 2024 12:54:08 +0900 Subject: 52968: use pdfroff to create intro.pdf roff2ps is removed in groff-1.23.0 (Jul. 2023). The "doubled output" problem was fixed in groff-1.22.3 (Nov. 2014). --- ChangeLog | 4 ++++ Doc/Makefile.in | 8 +++----- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 455cf38c3..36fae8a51 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2024-06-24 Jun-ichi Takimoto + + * 52968: Doc/Makefile.in: use pdfroff to create intro.pdf + 2024-06-13 Jun-ichi Takimoto * 52951: Src/builtin.c: make sure to close memstream for 'print -v' diff --git a/Doc/Makefile.in b/Doc/Makefile.in index d9be182e9..401fb942b 100644 --- a/Doc/Makefile.in +++ b/Doc/Makefile.in @@ -118,15 +118,13 @@ zsh.pdf zsh_a4.pdf zsh_us.pdf: $(sdir)/zsh.texi $(TEXI2PDF) -o $@ -t @afourpaper $(sdir)/zsh.texi; \ fi -# Use roff2ps / ps2pdf because pdfroff produces doubled output. intro.pdf intro.a4.pdf intro.us.pdf: $(sdir)/intro.ms if test $@ = intro.us.pdf || \ { test $@ = intro.pdf && test "$(PAPERSIZE)" = us; }; then \ - roff2ps -ms -P-pletter < $(sdir)/intro.ms > intro.ps; \ + pdfroff -ms -P-pletter $(sdir)/intro.ms > $@; \ else \ - roff2ps -ms -P-pa4 < $(sdir)/intro.ms > intro.ps; \ - fi; \ - ps2pdf -sOutputFile=$@ intro.ps + pdfroff -ms -P-pa4 $(sdir)/intro.ms > $@; \ + fi texi: $(sdir)/zsh.texi .PHONY: texi -- cgit v1.2.3 From 606ef4b430a8848e89e07dbc3e3db47fc019a505 Mon Sep 17 00:00:00 2001 From: Jun-ichi Takimoto Date: Mon, 26 Aug 2024 20:29:36 +0900 Subject: unposted: remove reference to removed sample code the example in expn.yo was mdified by commit bb441f77a7 and does not use _wanted any more --- ChangeLog | 4 ++++ Doc/Zsh/compsys.yo | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index eec96a494..f05f967ab 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2024-08-26 Jun-ichi Takimoto + + * unposted: Doc/Zsh/compsys.yo: remove reference to removed code + 2024-08-15 Bart Schaefer * unposted (see 53034): Src/lex.c, Test/A06assign.ztst: fix parsing diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo index f75298a1b..77627bacc 100644 --- a/Doc/Zsh/compsys.yo +++ b/Doc/Zsh/compsys.yo @@ -5448,10 +5448,6 @@ example(local expl _wanted tag expl 'description' \ compadd -- var(match1) var(match2)...) -See also the use of tt(_wanted) in the example function in -ifzman(the subsection `Dynamic named directories' in zmanref(zshexpn))\ -ifnzman(noderef(Dynamic named directories)). - Note that, as for tt(_requested), the var(command) must be able to accept options to be passed down to tt(compadd). -- cgit v1.2.3 From 58bda5913007f53c91ae60cd22483dd222ea5618 Mon Sep 17 00:00:00 2001 From: Clinton Bunch Date: Fri, 30 Aug 2024 08:06:06 -0500 Subject: 53056: new zsh/random module defining an SRANDOM parameter and zrand_float() and zrand_int() math functions --- ChangeLog | 6 + Completion/Zsh/Type/_module_math_func | 2 +- Doc/Makefile.in | 2 +- Doc/Zsh/mod_random.yo | 56 ++++++ Src/Modules/random.c | 322 ++++++++++++++++++++++++++++++++++ Src/Modules/random.mdd | 7 + Src/Modules/random_real.c | 213 ++++++++++++++++++++++ configure.ac | 2 + 8 files changed, 608 insertions(+), 2 deletions(-) create mode 100644 Doc/Zsh/mod_random.yo create mode 100644 Src/Modules/random.c create mode 100644 Src/Modules/random.mdd create mode 100644 Src/Modules/random_real.c (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 753353153..51a859143 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ 2024-08-31 Oliver Kiddle + * Clinton Bunch: 53056: Completion/Zsh/Type/_module_math_func, + Doc/Makefile.in, Doc/Zsh/mod_random.yo, Src/Modules/random.c, + Src/Modules/random.mdd, Src/Modules/random_real.c, configure.ac: + new zsh/random module defining an SRANDOM parameter and + zrand_float() and zrand_int() math functions + * github #120: Semnodime: Completion/Unix/Command/_git: update _git to reflect `--recursive` being an alias diff --git a/Completion/Zsh/Type/_module_math_func b/Completion/Zsh/Type/_module_math_func index 5044bdf4c..e92b78b71 100644 --- a/Completion/Zsh/Type/_module_math_func +++ b/Completion/Zsh/Type/_module_math_func @@ -2,7 +2,7 @@ local mod local -a funcs alts -local -a modules=( example mathfunc system ) +local -a modules=( example mathfunc system random ) for mod in $modules; do funcs=( ${${${(f)"$(zmodload -Fl zsh/$mod 2>/dev/null)"}:#^+f:*}##+f:} ) diff --git a/Doc/Makefile.in b/Doc/Makefile.in index 401fb942b..fa2a336ad 100644 --- a/Doc/Makefile.in +++ b/Doc/Makefile.in @@ -68,7 +68,7 @@ Zsh/mod_hlgroup.yo Zsh/mod_langinfo.yo \ Zsh/mod_ksh93.yo Zsh/mod_mapfile.yo Zsh/mod_mathfunc.yo \ Zsh/mod_nearcolor.yo Zsh/mod_newuser.yo \ Zsh/mod_parameter.yo Zsh/mod_pcre.yo Zsh/mod_private.yo \ -Zsh/mod_regex.yo Zsh/mod_sched.yo Zsh/mod_socket.yo \ +Zsh/mod_regex.yo Zsh/mod_random.yo Zsh/mod_sched.yo Zsh/mod_socket.yo \ Zsh/mod_stat.yo Zsh/mod_system.yo Zsh/mod_tcp.yo \ Zsh/mod_termcap.yo Zsh/mod_terminfo.yo \ Zsh/mod_watch.yo \ diff --git a/Doc/Zsh/mod_random.yo b/Doc/Zsh/mod_random.yo new file mode 100644 index 000000000..d2892bf79 --- /dev/null +++ b/Doc/Zsh/mod_random.yo @@ -0,0 +1,56 @@ +COMMENT(!MOD!zsh/random +Some High-quality randomness parameters and functions. +!MOD!) +The tt(zsh/random) module gets random data from the kernel random pool. If no +kernel random pool can be found, the module will not load. + +subsect(Parameters) + +startitem() +vindex(SRANDOM) +item(tt(SRANDOM)) ( +A random positive 32-bit integer between 0 and 4,294,967,295. This parameter +is read-only. The name was chosen for compatibility with Bash and to +distinguish it from tt(RANDOM) which has a documented repeatable behavior. +) +enditem() + +subsect(Math Functions) + +startitem() +item(tt(zrand_float+LPAR()RPAR())) ( +Returns a random floating point number between 0 and 1 inclusive. +) +enditem() + +startitem() +item(tt(zrand_int)+LPAR()tt(upper), tt(lower), tt(inclusive)RPAR()) ( +Returns a random integer between tt(lower) and tt(upper). All parameters are +optional. If none are specified it is equivalent to +tt(SRANDOM). + +tt(upper) is the upper bound on the resultant number and defaults to +4,294,967,295. + +tt(lower) is the lower bound and defaults to 0. + +The defaults of these two arguments are also the maximum and minimum to which +either can be set. + +tt(inclusive) is a flag that controls whether the result is ever equal to +tt(upper). By default it is not. If this argument is set to a non-zero value +then it may be. + +This is to facilitate a construct like tt($a[zrand_int($#a)+1]) rather +than tt($a[zrand_int+LPAR()$#a-1+RPAR()+1]). +For example, if $#a is 16, you would use tt(zrand_int+LPAR()16RPAR()) which has +16 possible return values 0-15. Because the function can return zero, in order +to use it as an array index from 1-16 you need to add one. It would +be an array index range error for it to also potentially return 16 ($#a). You +could, however, use the construct tt(zrand_int+LPAR()16,1,1+RPAR()) instead of +adding 1 to achieve the same result, but it is more verbose. + +Most statistics algorithms seem to also expect 0 to tt(upper)-1, so this was +deemed the most commonly desired case and chosen as the default. +) +enditem() diff --git a/Src/Modules/random.c b/Src/Modules/random.c new file mode 100644 index 000000000..a153d8f64 --- /dev/null +++ b/Src/Modules/random.c @@ -0,0 +1,322 @@ +/* + * random.c - module to access kernel random sources. + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 2022 Clinton Bunch + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Clinton Bunch or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Clinton Bunch and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Clinton Bunch and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Clinton Bunch and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "random.mdh" + +#include +#include +#include +#include +#include + +#include + +#ifdef HAVE_SYS_RANDOM_H +#include +#endif + +/* Simplify select URANDOM specific code */ +#if !defined(HAVE_ARC4RANDOM_BUF) && !defined(HAVE_GETRANDOM) +#define USE_URANDOM +#endif + +/* buffer to pre-load integers for SRANDOM to lessen the context switches */ +static uint32_t rand_buff[8]; +static int buf_cnt = -1; + +#ifdef USE_URANDOM +/* File descriptor for /dev/urandom */ +int randfd = -1; +#endif /* USE_URANDOM */ + +static zlong get_srandom(UNUSED(Param p)); + +/**/ +ssize_t +getrandom_buffer(void *buf, size_t len) +{ + ssize_t ret; + size_t val = 0; + uint8_t *bufptr = buf; + + do { + errno = 0; +#ifdef HAVE_ARC4RANDOM_BUF + arc4random_buf(buf,len); + ret = len; +#elif defined(HAVE_GETRANDOM) + ret=getrandom(bufptr,(len - val),0); +#else + ret=read(randfd,bufptr,(len - val)); +#endif + if (ret < 0) { + if (errno != EINTR || errflag || retflag || breaks || contflag) { + zwarn("Unable to get random data: %e.", errno); + return -1; + } + } + bufptr += ret; + val += ret; + } while (ret < len); + return ret; +} + +/* + * Generate count number of random 32-bit integers between 0 and max-1 + * Got this algorithm from + *https://lemire.me/blog/2016/06/30/fast-random-shuffling/ + * adapting the public domain code. + * + */ + +/**/ +void +get_bound_random_buffer(uint32_t *buffer, size_t count, uint32_t max) +{ + uint64_t multi_result; + uint32_t threshold; + uint32_t leftover; + + size_t i; /* loop counter */ + + getrandom_buffer((void*) buffer, count*sizeof(uint32_t)); + if (max == UINT32_MAX) + return; + + for(i=0;i> 32; + } +} + +/* + * Provides for the SRANDOM parameter and returns an unsigned 32-bit random + * integer. + */ + +/**/ +static zlong +get_srandom(UNUSED(Param pm)) { + + if(buf_cnt <= 0) { + getrandom_buffer((void*) rand_buff,sizeof(rand_buff)); + buf_cnt=sizeof(rand_buff)/sizeof(rand_buff[0]); + } + return rand_buff[--buf_cnt]; +} + +/* + * Implements math function zrand_int takes 0 to 3 arguments an upper bound, + * a lower bound and a flag as to whether the range is inclusive or not. The + * default is exclusive. If neither upper or lower is specified this is no + * different than SRANDOM. + */ + +/**/ +static mnumber +math_zrand_int(UNUSED(char *name), int argc, mnumber *argv, UNUSED(int id)) +{ + mnumber ret; + uint32_t i; + zlong lower=0, upper=UINT32_MAX,incl=0, diff; + + ret.type = MN_INTEGER; + + switch (argc) { + case 0: ret.u.l=get_srandom(NULL); + return ret; + break; + case 3: incl = (argv[2].u.l != 0)?1:0; + case 2: lower = argv[1].u.l; + case 1: upper = argv[0].u.l; + default: diff = upper-lower+incl; + } + + if (lower < 0 || lower >= UINT32_MAX) { + zwarn("Lower bound (%z) out of range: 0-4294967295",lower); + } else if (upper < lower) { + zwarn("Upper bound (%z) must be greater than Lower Bound (%z)",upper,lower); + } else if (upper < 0 || upper >= UINT32_MAX) { + zwarn("Upper bound (%z) out of range: 0-4294967295",upper); + } + + if ( diff == 0 ) { + ret.u.l=upper; /* still not convinced this shouldn't be an error. */ + } else { + get_bound_random_buffer(&i,1,(uint32_t) diff); + ret.u.l=i+lower; + } + return ret; +} + +/* + * Implements the mathfunc zrand_float and returns a random floating-point + * number between 0 and 1. + * + */ + +/**/ +static mnumber +math_zrand_float(UNUSED(char *name), UNUSED(int argc), UNUSED(mnumber *argv), + UNUSED(int id)) +{ + mnumber ret; + double r; + + r = random_real(); + if (r < 0) { + zwarnnam(name, "Failed to get sufficient random data."); + } + ret.type = MN_FLOAT; + ret.u.d = r; + + return ret; +} + +static const struct gsu_integer srandom_gsu = +{ get_srandom, nullintsetfn, stdunsetfn }; + +static struct paramdef patab[] = { + {"SRANDOM", PM_INTEGER | PM_READONLY_SPECIAL | PM_HIDEVAL, NULL, + &srandom_gsu, NULL, NULL, NULL}, +}; + +static struct mathfunc mftab[] = { + NUMMATHFUNC("zrand_float", math_zrand_float, 0, 0, 0), + NUMMATHFUNC("zrand_int", math_zrand_int, 0, 3, 0), +}; + +static struct features module_features = { + NULL, 0, + NULL, 0, + mftab, sizeof(mftab)/sizeof(*mftab), + patab, sizeof(patab)/sizeof(*patab), + 0 +}; + +/**/ +int +setup_(UNUSED(Module m)) +{ +#ifdef USE_URANDOM + /* Check for the existence of /dev/urandom */ + + struct stat st; + + errno=0; + if (stat("/dev/urandom",&st) < 0) { + zwarn("Error getting kernel random pool: %e.", errno); + return 1; + } + + errno=0; + if (!(S_ISCHR(st.st_mode)) ) { + zwarn("Error getting kernel random pool: %e.", errno); + return 1; + } +#endif /* USE_URANDOM */ + return 0; +} + +/**/ +int +features_(Module m, char ***features) +{ + *features = featuresarray(m, &module_features); + return 0; +} + +/**/ +int +enables_(Module m, int **enables) +{ + return handlefeatures(m, &module_features, enables); +} + +/**/ +int +boot_(UNUSED(Module m)) +{ +#ifdef USE_URANDOM + /* + * Open /dev/urandom. Here because of a weird issue on HP-UX 11.31 + * When opening in setup_ open returned 0. In boot_, it returns + * an unused file descriptor. Decided against ifdef HPUX as it works + * here just as well for other platforms. + * + */ + + int tmpfd=-1; + + errno=0; + if ((tmpfd = open("/dev/urandom", O_RDONLY)) < 0) { + zwarn("Could not access kernel random pool: %e.",errno); + return 1; + } + randfd = movefd(tmpfd); + addmodulefd(randfd,FDT_MODULE); + if (randfd < 0) { + zwarn("Could not access kernel random pool."); + return 1; + } +#endif /* USE_URANDOM */ + return 0; +} + +/**/ +int +cleanup_(Module m) +{ + return setfeatureenables(m, &module_features, NULL); +} + +/**/ +int +finish_(UNUSED(Module m)) +{ +#ifdef USE_URANDOM + if (randfd >= 0) + zclose(randfd); +#endif /* USE_URANDOM */ + return 0; +} diff --git a/Src/Modules/random.mdd b/Src/Modules/random.mdd new file mode 100644 index 000000000..7a75f29ff --- /dev/null +++ b/Src/Modules/random.mdd @@ -0,0 +1,7 @@ +name=zsh/random +link=either +load=yes + +autofeatures="p:SRANDOM f:zrand_float f:zrand_int" + +objects="random.o random_real.o" diff --git a/Src/Modules/random_real.c b/Src/Modules/random_real.c new file mode 100644 index 000000000..4a8fcae19 --- /dev/null +++ b/Src/Modules/random_real.c @@ -0,0 +1,213 @@ +/* This file contains code under different copyrights separated by */ +/* ====@@@@@=== */ + +/* + * random_real.c - module to access kernel random sources. + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 2022 Clinton Bunch + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Clinton Bunch or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Clinton Bunch and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Clinton Bunch and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Clinton Bunch and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "random.mdh" + +#include +#include +#include + + +/* Count the number of leading zeros, hopefully in gcc/clang by HW + * instruction */ +#if defined(__GNUC__) || defined(__clang__) +#define clz64(x) __builtin_clzll(x) +#else +#define clz64(x) _zclz64(x) + +/**/ +int +_zclz64(uint64_t x) { + int n = 0; + + if (x == 0) + return 64; + + if (!(x & 0xFFFFFFFF00000000ull)) { + n+=32; + x<<=32; + } + if (!(x & 0xFFFF000000000000ull)) { + n+=16; + x<<=16; + } + if (!(x & 0xFF00000000000000ull)) { + n+=8; + x<<=8; + } + if (!(x & 0xF000000000000000ull)) { + n+=4; + x<<=4; + } + if (!(x & 0xC000000000000000ull)) { + n+=2; + x<<=1; + } + if (!(x & 0x8000000000000000ull)) { + n+=1; + } + return n; +} +#endif /* __GNU_C__ or __clang__ */ + +/**/ +uint64_t +random_64bit(void) { + uint64_t r; + + if(getrandom_buffer(&r,sizeof(r)) < 0) { + zwarn("zsh/random: Can't get sufficient random data."); + return 1; /* 0 will cause loop */ + } + + return r; +} + +/* ====@@@@@=== */ +/* Following code is under the below copyright, despite changes for error + * handling and removing GCCisms */ + +/*- + * Copyright (c) 2014 Taylor R. Campbell + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Uniform random floats: How to generate a double-precision + * floating-point numbers in [0, 1] uniformly at random given a uniform + * random source of bits. + * + * See + * for explanation. + * + * Updated 2015-02-22 to replace ldexp(x, ) by x * ldexp(1, + * ), since glibc and NetBSD libm both have slow software + * bit-twiddling implementations of ldexp, but GCC can constant-fold + * the latter. + */ + +/* + * random_real: Generate a stream of bits uniformly at random and + * interpret it as the fractional part of the binary expansion of a + * number in [0, 1], 0.00001010011111010100...; then round it. + */ + +/**/ +double +random_real(void) +{ + int exponent = 0; + uint64_t significand = 0; + uint64_t r = 0; + unsigned shift; + + /* + * Read zeros into the exponent until we hit a one; the rest + * will go into the significand. + */ + while (significand == 0) { + exponent -= 64; + + /* Get random_64bit and check for error */ + errno = 0; + significand = random_64bit(); + if (errno) + return -1; + /* + * If the exponent falls below -1074 = emin + 1 - p, + * the exponent of the smallest subnormal, we are + * guaranteed the result will be rounded to zero. This + * case is so unlikely it will happen in realistic + * terms only if random_64bit is broken. + */ + if (exponent < -1074) + return 0; + } + + /* + * There is a 1 somewhere in significand, not necessarily in + * the most significant position. If there are leading zeros, + * shift them into the exponent and refill the less-significant + * bits of the significand. Can't predict one way or another + * whether there are leading zeros: there's a fifty-fifty + * chance, if random_64bit is uniformly distributed. + */ + shift = clz64(significand); + if (shift != 0) { + + errno = 0; + r = random_64bit(); + if (errno) + return -1; + + exponent -= shift; + significand <<= shift; + significand |= (r >> (64 - shift)); + } + + /* + * Set the sticky bit, since there is almost surely another 1 + * in the bit stream. Otherwise, we might round what looks + * like a tie to even when, almost surely, were we to look + * further in the bit stream, there would be a 1 breaking the + * tie. + */ + significand |= 1; + + /* + * Finally, convert to double (rounding) and scale by + * 2^exponent. + */ + return ldexp((double)significand, exponent); +} +/* ====@@@@@=== */ diff --git a/configure.ac b/configure.ac index 78621042d..a88101f2b 100644 --- a/configure.ac +++ b/configure.ac @@ -636,6 +636,7 @@ fi AC_CHECK_HEADERS(sys/time.h sys/times.h sys/select.h termcap.h termio.h \ termios.h sys/param.h sys/filio.h string.h memory.h \ limits.h fcntl.h libc.h sys/utsname.h sys/resource.h \ + sys/random.h \ locale.h errno.h stdio.h stdarg.h varargs.h stdlib.h \ unistd.h sys/capability.h \ utmp.h utmpx.h sys/types.h pwd.h grp.h poll.h sys/mman.h \ @@ -1292,6 +1293,7 @@ AC_CHECK_FUNCS(strftime strptime mktime timelocal \ cygwin_conv_path \ nanosleep \ srand_deterministic \ + getrandom arc4random_buf \ setutxent getutxent endutxent getutent) AC_FUNC_STRCOLL -- cgit v1.2.3 From 6b9704e2c4e4c8524137a9c15bf9b166a975f3eb Mon Sep 17 00:00:00 2001 From: Clinton Bunch Date: Sat, 31 Aug 2024 09:10:12 -0500 Subject: 53060: silence build warnings --- ChangeLog | 3 +++ Doc/Zsh/mod_random.yo | 2 +- Src/Modules/random.c | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 51a859143..7e181e959 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2024-08-31 Oliver Kiddle + * Clinton Bunch: 53060: Doc/Zsh/mod_random.yo, + Src/Modules/random.c: silence build warnings + * Clinton Bunch: 53056: Completion/Zsh/Type/_module_math_func, Doc/Makefile.in, Doc/Zsh/mod_random.yo, Src/Modules/random.c, Src/Modules/random.mdd, Src/Modules/random_real.c, configure.ac: diff --git a/Doc/Zsh/mod_random.yo b/Doc/Zsh/mod_random.yo index d2892bf79..4f5622e61 100644 --- a/Doc/Zsh/mod_random.yo +++ b/Doc/Zsh/mod_random.yo @@ -41,7 +41,7 @@ tt(inclusive) is a flag that controls whether the result is ever equal to tt(upper). By default it is not. If this argument is set to a non-zero value then it may be. -This is to facilitate a construct like tt($a[zrand_int($#a)+1]) rather +This is to facilitate a construct like tt($a[zrand_int+LPAR()$#a+RPAR()+1]) rather than tt($a[zrand_int+LPAR()$#a-1+RPAR()+1]). For example, if $#a is 16, you would use tt(zrand_int+LPAR()16RPAR()) which has 16 possible return values 0-15. Because the function can return zero, in order diff --git a/Src/Modules/random.c b/Src/Modules/random.c index a153d8f64..88ac9543c 100644 --- a/Src/Modules/random.c +++ b/Src/Modules/random.c @@ -62,8 +62,10 @@ ssize_t getrandom_buffer(void *buf, size_t len) { ssize_t ret; +#ifndef HAVE_ARC4RANDOM_BUF size_t val = 0; uint8_t *bufptr = buf; +#endif do { errno = 0; @@ -81,8 +83,10 @@ getrandom_buffer(void *buf, size_t len) return -1; } } +#ifndef HAVE_ARC4RANDOM_BUF bufptr += ret; val += ret; +#endif } while (ret < len); return ret; } -- cgit v1.2.3 From 393cb298aaa849bae62e7294fecb1b60d0dd3910 Mon Sep 17 00:00:00 2001 From: Eric Cook Date: Mon, 11 Nov 2024 10:46:08 -0500 Subject: 53174: add Completion/Base/Utility/_as_if --- ChangeLog | 4 ++++ Completion/Base/Utility/_as_if | 10 ++++++++++ Doc/Zsh/compsys.yo | 8 ++++++++ 3 files changed, 22 insertions(+) create mode 100644 Completion/Base/Utility/_as_if (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index a0e742244..bdb60cf33 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,6 +3,10 @@ * 53144: Completion/Unix/Command/_sysctl: support completion with the dot delimiter on linux + * Matthew Martin: 53174 + comments: + Completion/Base/Utility/_as_if: complete `as if' + a different command were being completed + 2024-11-05 Bart Schaefer * 53209 + comments + test: Src/params.c, Test/D04parameter.ztst: diff --git a/Completion/Base/Utility/_as_if b/Completion/Base/Utility/_as_if new file mode 100644 index 000000000..c961aaa88 --- /dev/null +++ b/Completion/Base/Utility/_as_if @@ -0,0 +1,10 @@ +#autoload +local words=("$words[@]") CURRENT=$CURRENT +local _comp_command1 _comp_command2 _comp_command + +words[1]=("$@") +(( CURRENT += $# - 1 )) + +_set_command + +_dispatch "$_comp_command" "$_comp_command1" "$_comp_command2" -default- diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo index 77627bacc..9b7f91148 100644 --- a/Doc/Zsh/compsys.yo +++ b/Doc/Zsh/compsys.yo @@ -4290,6 +4290,14 @@ arguments. The first describes the first argument as a be completed. The last description gives all other arguments the description `var(page number)' but does not offer completions. ) +findex(_as_if) +item(tt(_as_if) var(command) [var(arg) ... ])( +This function is useful when one command should be completed as if it were +another command with particular arguments. For example to complete tt(foo) as +if it were tt(bar --baz), use + +example(compdef '_as_if bar --baz' foo) +) findex(_cache_invalid) item(tt(_cache_invalid) var(cache_identifier))( This function returns status zero if the completions cache corresponding to -- cgit v1.2.3 From 6973a9ea843b1504e4c39ed560ee6e99d829ee8f Mon Sep 17 00:00:00 2001 From: Jun-ichi Takimoto Date: Tue, 12 Nov 2024 12:34:39 +0900 Subject: 53137 + 53213: support creating intro.html --- ChangeLog | 5 +++++ Doc/Makefile.in | 13 ++++++++++--- Doc/intro.ms | 40 +++++++++++++++++++++++++++++++++++----- 3 files changed, 50 insertions(+), 8 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index bdb60cf33..22ee71a4e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2024-11-12 Jun-ichi Takimoto + + * 53137 + 53213: Doc/Makefile.in, Doc/intro.ms, allow creating + intro.html + 2024-11-11 Eric Cook * 53144: Completion/Unix/Command/_sysctl: diff --git a/Doc/Makefile.in b/Doc/Makefile.in index fa2a336ad..a986aa1c5 100644 --- a/Doc/Makefile.in +++ b/Doc/Makefile.in @@ -121,9 +121,16 @@ zsh.pdf zsh_a4.pdf zsh_us.pdf: $(sdir)/zsh.texi intro.pdf intro.a4.pdf intro.us.pdf: $(sdir)/intro.ms if test $@ = intro.us.pdf || \ { test $@ = intro.pdf && test "$(PAPERSIZE)" = us; }; then \ - pdfroff -ms -P-pletter $(sdir)/intro.ms > $@; \ + pdfroff -mspdf --no-kill-null-pages -P-pletter --pdf-output=$@ $<; \ else \ - pdfroff -ms -P-pa4 $(sdir)/intro.ms > $@; \ + pdfroff -mspdf --no-kill-null-pages -P-pa4 --pdf-output=$@ $<; \ + fi + +intro.html: $(sdir)/intro.ms + if groff -ms -Thtml -P-jintro $< > tmp.html; then \ + mv tmp.html $@; \ + else \ + rm -f tmp.html; false; \ fi texi: $(sdir)/zsh.texi @@ -170,7 +177,7 @@ a4_ps: zsh_a4.ps zsh_a4.ps: zsh_a4.dvi $(DVIPS) -t a4 -o $@ zsh_a4.dvi -html: zsh_toc.html +html: zsh_toc.html intro.html .PHONY: html zsh_toc.html: $(sdir)/zsh.texi texi2html.conf diff --git a/Doc/intro.ms b/Doc/intro.ms index 4dd08f601..49f6cc07f 100644 --- a/Doc/intro.ms +++ b/Doc/intro.ms @@ -3,8 +3,32 @@ .if \n(.g \{\ .if "\*(.T"ascii" .ftr C R .if "\*(.T"latin1" .ftr C R +.if "\*(.T"html" .nr HTML 1 .nr De \n[.ss] .\} +.\" ----- macro defintions ----- +.\" Ds/De: start/end of example +.\" Sh: section header +.\" XXX: It seems we can't use the same definition for both pdf and html +.\" (at least with groff-12.3.0). +.\" +.\" for HTML output +.ie \n[HTML] \{\ +.de Ds +.DS I .5i +.ft C +.. +.de De +.DE +.ft R +.. +.de Sh +.NH +\\$1 +.. +.\} +.\" for other output (such as PDF) +.el \{\ .de Ds .DS I .5i .ft C @@ -21,12 +45,13 @@ .el .ss .. .de Sh -.SH -\\$1 -.XS -\\$1 -.XE +.NH +.XN \\$1 .. +.\} +.\" +.\" ----- Cover page ----- +.if !\n[HTML] \{\ .nr HM 4i .ce 99 .ps 18 @@ -50,6 +75,9 @@ bas@phys.uva.nl\fP .sv |1i .pn 1 .bp +.\} +.\" +.\" ----- main text ----- .TL An Introduction to the Z Shell .AU @@ -2712,6 +2740,7 @@ I (Bas de Bakker) would be happy to receive mail if anyone has any tricks or ideas to add to this document, or if there are some points that could be made clearer or covered more thoroughly. Please notify me of any errors in this document. +.if !\n[HTML] \{\ .if o \{\ .bp .sv 1i @@ -2719,3 +2748,4 @@ me of any errors in this document. .pn 1 .bp .PX +.\} -- cgit v1.2.3 From d905c7a0d7d8fb789e7f508eb4610ddfa280cf54 Mon Sep 17 00:00:00 2001 From: dana Date: Mon, 16 Dec 2024 10:37:55 -0600 Subject: 52108: docs: document benefits of `_normal -p` note: commit was severely delayed --- ChangeLog | 4 ++++ Doc/Zsh/compsys.yo | 12 +++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index d62b6a49a..8d8fb713d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2024-12-16 dana + + * 52108: Doc/Zsh/compsys.yo: document benefits of `_normal -p` + 2024-11-23 Oliver Kiddle * Clinton Bunch: 53228: Etc/FAQ.yo: Remove references to diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo index 9b7f91148..7ae67773b 100644 --- a/Doc/Zsh/compsys.yo +++ b/Doc/Zsh/compsys.yo @@ -4397,8 +4397,10 @@ the functions for the fields if they are called. findex(_command_names) item(tt(_command_names) [ tt(-e) | tt(-) ])( This function completes words that are valid at command position: names of -aliases, builtins, hashed commands, functions, and so on. With the tt(-e) -flag, only hashed commands are completed. The tt(-) flag is ignored. +aliases, builtins, hashed commands, functions, and so on. If the tt(-e) +flag is given, or if the list of precommands contains a non-builtin command +(e.g. because tt(_normal -p) has been used previously), only hashed commands +are completed. The tt(-) flag is ignored. ) findex(_comp_locale) item(tt(_comp_locale))( @@ -4784,7 +4786,11 @@ functions) regardless of prior precommands (e.g. `tt(zsh -c)'). ) item(tt(-p) var(precommand))( Append var(precommand) to the list of precommands. This option should be -used in nearly all cases in which tt(-P) is not applicable. +used in nearly all cases in which tt(-P) is not applicable. An advantage +of using this option is that it can automatically signal to +tt(_command_names) that subsequent completion should be limited to hashed +commands, which is usually the desired behaviour following commands like +tt(chroot) and tt(nohup). ) enditem() -- cgit v1.2.3 From 6bb792dba89016c250bc9f2581c9c267dd322254 Mon Sep 17 00:00:00 2001 From: dana Date: Thu, 26 Dec 2024 09:36:45 -0600 Subject: 53257: use monotonic clock where appropriate update the following features to use the monotonic clock for calculating time deltas and intervals: * MAILCHECK parameter * PERIOD parameter * SECONDS parameter * %(nS.t.f) prompt-expansion sequence * time built-in's elapsed time and cpu % values * zsh/zftp ZFTP_TMOUT parameter * zsh/zprof timings also use CLOCK_MONOTONIC_RAW instead of CLOCK_MONOTONIC on macOS --- ChangeLog | 9 ++++++ Doc/Zsh/params.yo | 18 ++++++++---- Doc/Zsh/prompt.yo | 3 +- Src/Modules/zftp.c | 4 +-- Src/Modules/zprof.c | 19 ++++++------ Src/compat.c | 12 ++++++++ Src/exec.c | 21 +++++++------- Src/hist.c | 6 ++-- Src/init.c | 7 ++--- Src/jobs.c | 78 ++++++++++++++++++++++++++++++++++---------------- Src/params.c | 38 ++++++++++++------------ Src/prompt.c | 2 +- Src/signals.c | 3 +- Src/utils.c | 25 ++++++++++++---- Src/zsh.h | 4 +-- Test/A08time.ztst | 39 +++++++++++++++++++++++-- Test/D01prompt.ztst | 7 +++++ Test/D04parameter.ztst | 22 ++++++++++++++ 18 files changed, 225 insertions(+), 92 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index a58002d71..989cc0aa3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2024-12-26 dana + + * 53257: Doc/Zsh/params.yo, Doc/Zsh/prompt.yo, + Src/Modules/zftp.c, Src/Modules/zprof.c, Src/compat.c, + Src/exec.c, Src/hist.c, Src/init.c, Src/jobs.c, Src/params.c, + Src/prompt.c, Src/signals.c, Src/utils.c, Src/zsh.h, + Test/A08time.ztst, Test/D01prompt.ztst, Test/D04parameter.ztst: + use monotonic clock where appropriate + 2024-12-16 dana * 53251: Completion/Unix/Command/_man: fix page completion on diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index 02ce796a9..69298855f 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -926,7 +926,9 @@ referenced or seeded in the parent shell in between subshell invocations. ) vindex(SECONDS) item(tt(SECONDS) )( -The number of seconds since shell invocation. If this parameter +The number of seconds since shell invocation. On most platforms, this +is a monotonic value, so it is not affected by NTP time jumps or other +clock changes (though it may be affected by slewing). If this parameter is assigned a value, then the value returned upon reference will be the value that was assigned plus the number of seconds since the assignment. @@ -936,8 +938,10 @@ be changed using the tt(typeset) command. The type may be changed only to one of the floating point types or back to integer. For example, `tt(typeset -F SECONDS)' causes the value to be reported as a floating point number. The -value is available to microsecond accuracy, although the shell may -show more or fewer digits depending on the use of tt(typeset). See +value is nominally available to nanosecond precision, although this +varies by platform (and probably isn't accurate to 1 ns regardless), +and the shell may show more or fewer digits depending on the +use of tt(typeset). See the documentation for the builtin tt(typeset) in ifzman(zmanref(zshbuiltins))\ ifnzman(noderef(Shell Builtin Commands)) for more details. @@ -1735,8 +1739,12 @@ A star may be inserted between the percent sign and flags printing time (e.g., `tt(%*E)'); this causes the time to be printed in `var(hh)tt(:)var(mm)tt(:)var(ss)tt(.)var(ttt)' format (hours and minutes are only printed if they are not zero). -Alternatively, `tt(m)' or `tt(u)' may be used (e.g., `tt(%mE)') to produce -time output in milliseconds or microseconds, respectively. +Alternatively, `tt(m)', `tt(u)', or `tt(n)' may be used (e.g., +`tt(%mE)') to produce time output in milliseconds, microseconds, or +nanoseconds, respectively. Note that some timings on some platforms +are not actually nanosecond-precise (nor accurate to 1 ns when +they are); in fact on many systems user and kernel times are not +even microsecond-precise. ) vindex(TMOUT) item(tt(TMOUT))( diff --git a/Doc/Zsh/prompt.yo b/Doc/Zsh/prompt.yo index de988ab7c..108cb62e5 100644 --- a/Doc/Zsh/prompt.yo +++ b/Doc/Zsh/prompt.yo @@ -195,7 +195,8 @@ sitem(tt(%K))(the hour of the day on the 24-hour clock) sitem(tt(%L))(the hour of the day on the 12-hour clock) endsitem() -In addition, if the system supports the POSIX tt(gettimeofday) system +In addition, if the system supports the POSIX tt(clock_gettime) +or tt(gettimeofday) system call, tt(%.) provides decimal fractions of a second since the epoch with leading zeroes. By default three decimal places are provided, but a number of digits up to 9 may be given following the tt(%); hence tt(%6.) diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c index b60e5bf31..230ad86f6 100644 --- a/Src/Modules/zftp.c +++ b/Src/Modules/zftp.c @@ -397,7 +397,7 @@ zfalarm(int tmout) signal(SIGALRM, zfhandler); oalremain = alarm(tmout); if (oalremain) - oaltime = time(NULL); + oaltime = zmonotime(NULL); /* * We'll leave sigtrapped, sigfuncs and TRAPXXX as they are; since the * shell's handler doesn't get the signal, they don't matter. @@ -431,7 +431,7 @@ zfunalarm(void) * I love the way alarm() uses unsigned int while time_t * is probably something completely different. */ - unsigned int tdiff = time(NULL) - oaltime; + time_t tdiff = zmonotime(NULL) - oaltime; alarm(oalremain < tdiff ? 1 : oalremain - tdiff); } else alarm(0); diff --git a/Src/Modules/zprof.c b/Src/Modules/zprof.c index 171a15b90..f5a50effc 100644 --- a/Src/Modules/zprof.c +++ b/Src/Modules/zprof.c @@ -239,8 +239,7 @@ zprof_wrapper(Eprog prog, FuncWrap w, char *name) struct sfunc sf, *sp; Pfunc f = NULL; Parc a = NULL; - struct timeval tv; - struct timezone dummy; + struct timespec ts; double prev = 0, now; char *name_for_lookups; @@ -278,19 +277,19 @@ zprof_wrapper(Eprog prog, FuncWrap w, char *name) stack = &sf; f->calls++; - tv.tv_sec = tv.tv_usec = 0; - gettimeofday(&tv, &dummy); - sf.beg = prev = ((((double) tv.tv_sec) * 1000.0) + - (((double) tv.tv_usec) / 1000.0)); + ts.tv_sec = ts.tv_nsec = 0; + zgettime_monotonic_if_available(&ts); + sf.beg = prev = ((((double) ts.tv_sec) * 1000.0) + + (((double) ts.tv_nsec) / 1000000.0)); } runshfunc(prog, w, name); if (active) { if (zprof_module && !(zprof_module->node.flags & MOD_UNLOAD)) { - tv.tv_sec = tv.tv_usec = 0; - gettimeofday(&tv, &dummy); + ts.tv_sec = ts.tv_nsec = 0; + zgettime_monotonic_if_available(&ts); - now = ((((double) tv.tv_sec) * 1000.0) + - (((double) tv.tv_usec) / 1000.0)); + now = ((((double) ts.tv_sec) * 1000.0) + + (((double) ts.tv_nsec) / 1000000.0)); f->self += now - sf.beg; for (sp = sf.prev; sp && sp->p != f; sp = sp->prev); if (!sp) diff --git a/Src/compat.c b/Src/compat.c index 8b31ad9f4..918d98e69 100644 --- a/Src/compat.c +++ b/Src/compat.c @@ -136,7 +136,19 @@ zgettime_monotonic_if_available(struct timespec *ts) #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) struct timespec dts; + +/* + * On at least some versions of macOS it appears that CLOCK_MONOTONIC is not + * actually monotonic -- there are reports that it can go backwards. + * CLOCK_MONOTONIC_RAW does not have this problem. On top of that, it is faster + * to read and it has nanosecond precision. We could use it on other systems + * too, but on Linux at least it seems that CLOCK_MONOTONIC is preferred + */ +#if defined(__APPLE__) && defined(CLOCK_MONOTONIC_RAW) + if (clock_gettime(CLOCK_MONOTONIC_RAW, &dts) < 0) { +#else if (clock_gettime(CLOCK_MONOTONIC, &dts) < 0) { +#endif zwarn("unable to retrieve CLOCK_MONOTONIC time: %e", errno); ret--; } else { diff --git a/Src/exec.c b/Src/exec.c index bc07e8c39..874ff41f7 100644 --- a/Src/exec.c +++ b/Src/exec.c @@ -348,10 +348,9 @@ setlimits(char *nam) /**/ static pid_t -zfork(struct timeval *tv) +zfork(struct timespec *ts) { pid_t pid; - struct timezone dummy_tz; /* * Is anybody willing to explain this test? @@ -360,8 +359,8 @@ zfork(struct timeval *tv) zerr("job table full"); return -1; } - if (tv) - gettimeofday(tv, &dummy_tz); + if (ts) + zgettime_monotonic_if_available(ts); /* * Queueing signals is necessary on Linux because fork() * manipulates mutexes, leading to deadlock in memory @@ -460,7 +459,7 @@ zfork(struct timeval *tv) int list_pipe = 0, simple_pline = 0; static pid_t list_pipe_pid; -static struct timeval list_pipe_start; +static struct timespec list_pipe_start; static int nowait, pline_level = 0; static int list_pipe_child = 0, list_pipe_job; static char list_pipe_text[JOBTEXTSIZE]; @@ -1863,7 +1862,7 @@ execpline(Estate state, wordcode slcode, int how, int last1) (jobtab[list_pipe_job].stat & STAT_STOPPED)))) { pid_t pid = 0; int synch[2]; - struct timeval bgtime; + struct timespec bgtime; /* * A pipeline with the shell handling the right @@ -2284,7 +2283,7 @@ closemn(struct multio **mfds, int fd, int type) char buf[TCBUFSIZE]; int len, i; pid_t pid; - struct timeval bgtime; + struct timespec bgtime; /* * We need to block SIGCHLD in case the process @@ -2829,7 +2828,7 @@ execcmd_fork(Estate state, int how, int type, Wordcode varspc, pid_t pid; int synch[2], flags; struct entersubsh_ret esret; - struct timeval bgtime; + struct timespec bgtime; child_block(); esret.gleader = -1; @@ -2947,7 +2946,7 @@ execcmd_exec(Estate state, Execcmd_params eparams, * for the "time" keyword */ child_times_t shti, chti; - struct timeval then; + struct timespec then; if (how & Z_TIMED) shelltime(&shti, &chti, &then, 0); @@ -5060,7 +5059,7 @@ getproc(char *cmd, char **eptr) int out = *cmd == Inang; char *pnam; pid_t pid; - struct timeval bgtime; + struct timespec bgtime; #ifndef PATH_DEV_FD int fd; @@ -5149,7 +5148,7 @@ getpipe(char *cmd, int nullexec) Eprog prog; int pipes[2], out = *cmd == Inang; pid_t pid; - struct timeval bgtime; + struct timespec bgtime; char *ends; if (!(prog = parsecmd(cmd, &ends))) diff --git a/Src/hist.c b/Src/hist.c index 1a00c30ed..d0960a284 100644 --- a/Src/hist.c +++ b/Src/hist.c @@ -2891,9 +2891,9 @@ flockhistfile(char *fn, int keep_trying) /* * Timeout is ten seconds. */ - end_time = time(NULL) + (time_t)10; + end_time = zmonotime(NULL) + (time_t)10; while (fcntl(flock_fd, F_SETLKW, &lck) == -1) { - if (!keep_trying || time(NULL) >= end_time || + if (!keep_trying || zmonotime(NULL) >= end_time || /* * Randomise wait to minimise clashes with shells exiting at * the same time. @@ -3137,7 +3137,7 @@ static int lockhistct; static int checklocktime(char *lockfile, long *sleep_usp, time_t then) { - time_t now = time(NULL); + time_t now = zmonotime(NULL); if (now + 10 < then) { /* File is more than 10 seconds in the future? */ diff --git a/Src/init.c b/Src/init.c index 61f759ded..378aee348 100644 --- a/Src/init.c +++ b/Src/init.c @@ -1022,7 +1022,6 @@ setupvals(char *cmd, char *runscript, char *zsh_name) #ifdef USE_GETPWUID struct passwd *pswd; #endif - struct timezone dummy_tz; char *ptr; int i, j; #if defined(SITEFPATH_DIR) || defined(FPATH_DIR) || defined (ADDITIONAL_FPATH) || defined(FIXED_FPATH_DIR) @@ -1109,8 +1108,8 @@ setupvals(char *cmd, char *runscript, char *zsh_name) hatchar = '^'; termflags = TERM_UNKNOWN; curjob = prevjob = coprocin = coprocout = -1; - gettimeofday(&shtimer, &dummy_tz); /* init $SECONDS */ - srand((unsigned int)(shtimer.tv_sec + shtimer.tv_usec)); /* seed $RANDOM */ + zgettime_monotonic_if_available(&shtimer); /* init $SECONDS */ + srand((unsigned int)(shtimer.tv_sec + shtimer.tv_nsec)); /* seed $RANDOM */ /* Set default path */ path = (char **) zalloc(sizeof(*path) * 5); @@ -1297,7 +1296,7 @@ setupvals(char *cmd, char *runscript, char *zsh_name) #endif breaks = loops = 0; - lastmailcheck = time(NULL); + lastmailcheck = zmonotime(NULL); locallevel = sourcelevel = 0; sfcontext = SFC_NONE; trap_return = 0; diff --git a/Src/jobs.c b/Src/jobs.c index 39c664388..ad14f6312 100644 --- a/Src/jobs.c +++ b/Src/jobs.c @@ -136,7 +136,7 @@ int numpipestats, pipestats[MAX_PIPESTATS]; /**/ static struct timeval * -dtime(struct timeval *dt, struct timeval *t1, struct timeval *t2) +dtime_tv(struct timeval *dt, struct timeval *t1, struct timeval *t2) { dt->tv_sec = t2->tv_sec - t1->tv_sec; dt->tv_usec = t2->tv_usec - t1->tv_usec; @@ -147,6 +147,21 @@ dtime(struct timeval *dt, struct timeval *t1, struct timeval *t2) return dt; } +/* As above, but with timespecs */ + +/**/ +static struct timespec * +dtime_ts(struct timespec *dt, struct timespec *t1, struct timespec *t2) +{ + dt->tv_sec = t2->tv_sec - t1->tv_sec; + dt->tv_nsec = t2->tv_nsec - t1->tv_nsec; + if (dt->tv_nsec < 0) { + dt->tv_nsec += 1000000000.0; + dt->tv_sec -= 1.0; + } + return dt; +} + /* change job table entry from stopped to running */ /**/ @@ -349,7 +364,6 @@ get_usage(void) void update_process(Process pn, int status) { - struct timezone dummy_tz; #ifdef HAVE_GETRUSAGE struct timeval childs = child_usage.ru_stime; struct timeval childu = child_usage.ru_utime; @@ -360,12 +374,12 @@ update_process(Process pn, int status) /* get time-accounting info */ get_usage(); - gettimeofday(&pn->endtime, &dummy_tz); /* record time process exited */ + zgettime_monotonic_if_available(&pn->endtime); /* record time process exited */ pn->status = status; /* save the status returned by WAIT */ #ifdef HAVE_GETRUSAGE - dtime(&pn->ti.ru_stime, &childs, &child_usage.ru_stime); - dtime(&pn->ti.ru_utime, &childu, &child_usage.ru_utime); + dtime_tv(&pn->ti.ru_stime, &childs, &child_usage.ru_stime); + dtime_tv(&pn->ti.ru_utime, &childu, &child_usage.ru_utime); #else pn->ti.st = shtms.tms_cstime - childs; /* compute process system space time */ pn->ti.ut = shtms.tms_cutime - childu; /* compute process user space time */ @@ -753,7 +767,7 @@ printhhmmss(double secs) } static void -printtime(struct timeval *real, child_times_t *ti, char *desc) +printtime(struct timespec *real, child_times_t *ti, char *desc) { char *s; double elapsed_time, user_time, system_time; @@ -774,21 +788,21 @@ printtime(struct timeval *real, child_times_t *ti, char *desc) } /* go ahead and compute these, since almost every TIMEFMT will have them */ - elapsed_time = real->tv_sec + real->tv_usec / 1000000.0; + elapsed_time = real->tv_sec + real->tv_nsec / 1000000000.0; #ifdef HAVE_GETRUSAGE user_time = ti->ru_utime.tv_sec + ti->ru_utime.tv_usec / 1000000.0; system_time = ti->ru_stime.tv_sec + ti->ru_stime.tv_usec / 1000000.0; total_time = user_time + system_time; percent = 100.0 * total_time - / (real->tv_sec + real->tv_usec / 1000000.0); + / (real->tv_sec + real->tv_nsec / 1000000000.0); #else { long clktck = get_clktck(); user_time = ti->ut / (double) clktck; system_time = ti->st / (double) clktck; percent = 100.0 * (ti->ut + ti->st) - / (clktck * real->tv_sec + clktck * real->tv_usec / 1000000.0); + / (clktck * real->tv_sec + clktck * real->tv_nsec / 1000000000.0); } #endif @@ -844,6 +858,23 @@ printtime(struct timeval *real, child_times_t *ti, char *desc) break; } break; + case 'n': + switch (*++s) { + case 'E': + fprintf(stderr, "%0.fns", elapsed_time * 1000000000.0); + break; + case 'U': + fprintf(stderr, "%0.fns", user_time * 1000000000.0); + break; + case 'S': + fprintf(stderr, "%0.fns", system_time * 1000000000.0); + break; + default: + fprintf(stderr, "%%n"); + s--; + break; + } + break; case '*': switch (*++s) { case 'E': @@ -991,12 +1022,12 @@ static void dumptime(Job jn) { Process pn; - struct timeval dtimeval; + struct timespec dtimespec; if (!jn->procs) return; for (pn = jn->procs; pn; pn = pn->next) - printtime(dtime(&dtimeval, &pn->bgtime, &pn->endtime), &pn->ti, + printtime(dtime_ts(&dtimespec, &pn->bgtime, &pn->endtime), &pn->ti, pn->text); } @@ -1506,7 +1537,7 @@ deletejob(Job jn, int disowning) /**/ void -addproc(pid_t pid, char *text, int aux, struct timeval *bgtime, +addproc(pid_t pid, char *text, int aux, struct timespec *bgtime, int gleader, int list_pipe_job_used) { Process pn, *pnlist; @@ -1894,16 +1925,15 @@ spawnjob(void) /**/ void -shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int delta) +shelltime(child_times_t *shell, child_times_t *kids, struct timespec *then, int delta) { - struct timezone dummy_tz; - struct timeval dtimeval, now; + struct timespec dtimespec, now; child_times_t ti; #ifndef HAVE_GETRUSAGE struct tms buf; #endif - gettimeofday(&now, &dummy_tz); + zgettime_monotonic_if_available(&now); #ifdef HAVE_GETRUSAGE getrusage(RUSAGE_SELF, &ti); @@ -1916,8 +1946,8 @@ shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int d if (shell) { if (delta) { #ifdef HAVE_GETRUSAGE - dtime(&ti.ru_utime, &shell->ru_utime, &ti.ru_utime); - dtime(&ti.ru_stime, &shell->ru_stime, &ti.ru_stime); + dtime_tv(&ti.ru_utime, &shell->ru_utime, &ti.ru_utime); + dtime_tv(&ti.ru_stime, &shell->ru_stime, &ti.ru_stime); #else ti.ut -= shell->ut; ti.st -= shell->st; @@ -1926,15 +1956,15 @@ shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int d *shell = ti; } if (delta) - dtime(&dtimeval, then, &now); + dtime_ts(&dtimespec, then, &now); else { if (then) *then = now; - dtime(&dtimeval, &shtimer, &now); + dtime_ts(&dtimespec, &shtimer, &now); } if (!delta == !shell) - printtime(&dtimeval, &ti, "shell"); + printtime(&dtimespec, &ti, "shell"); #ifdef HAVE_GETRUSAGE getrusage(RUSAGE_CHILDREN, &ti); @@ -1945,8 +1975,8 @@ shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int d if (kids) { if (delta) { #ifdef HAVE_GETRUSAGE - dtime(&ti.ru_utime, &kids->ru_utime, &ti.ru_utime); - dtime(&ti.ru_stime, &kids->ru_stime, &ti.ru_stime); + dtime_tv(&ti.ru_utime, &kids->ru_utime, &ti.ru_utime); + dtime_tv(&ti.ru_stime, &kids->ru_stime, &ti.ru_stime); #else ti.ut -= shell->ut; ti.st -= shell->st; @@ -1955,7 +1985,7 @@ shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int d *kids = ti; } if (!delta == !kids) - printtime(&dtimeval, &ti, "children"); + printtime(&dtimespec, &ti, "children"); } /* see if jobs need printing */ diff --git a/Src/params.c b/Src/params.c index 6f137585b..d1c06b893 100644 --- a/Src/params.c +++ b/Src/params.c @@ -137,11 +137,11 @@ unsigned char hatchar, hashchar; unsigned char keyboardhackchar = '\0'; /* $SECONDS = now.tv_sec - shtimer.tv_sec - * + (now.tv_usec - shtimer.tv_usec) / 1000000.0 + * + (now.tv_nsec - shtimer.tv_nsec) / 1000000000.0 * (rounded to an integer if the parameter is not set to float) */ /**/ -struct timeval shtimer; +struct timespec shtimer; /* 0 if this $TERM setup is usable, otherwise it contains TERM_* flags */ @@ -4496,13 +4496,12 @@ randomsetfn(UNUSED(Param pm), zlong v) zlong intsecondsgetfn(UNUSED(Param pm)) { - struct timeval now; - struct timezone dummy_tz; + struct timespec now; - gettimeofday(&now, &dummy_tz); + zgettime_monotonic_if_available(&now); return (zlong)(now.tv_sec - shtimer.tv_sec - - (now.tv_usec < shtimer.tv_usec ? 1 : 0)); + (now.tv_nsec < shtimer.tv_nsec ? 1 : 0)); } /* Function to set value of special parameter `SECONDS' */ @@ -4511,48 +4510,47 @@ intsecondsgetfn(UNUSED(Param pm)) void intsecondssetfn(UNUSED(Param pm), zlong x) { - struct timeval now; - struct timezone dummy_tz; + struct timespec now; zlong diff; - gettimeofday(&now, &dummy_tz); + zgettime_monotonic_if_available(&now); + diff = (zlong)now.tv_sec - x; shtimer.tv_sec = diff; if ((zlong)shtimer.tv_sec != diff) zwarn("SECONDS truncated on assignment"); - shtimer.tv_usec = now.tv_usec; + shtimer.tv_nsec = now.tv_nsec; } /**/ double floatsecondsgetfn(UNUSED(Param pm)) { - struct timeval now; - struct timezone dummy_tz; + struct timespec now; - gettimeofday(&now, &dummy_tz); + zgettime_monotonic_if_available(&now); return (double)(now.tv_sec - shtimer.tv_sec) + - (double)(now.tv_usec - shtimer.tv_usec) / 1000000.0; + (double)(now.tv_nsec - shtimer.tv_nsec) / 1000000000.0; } /**/ void floatsecondssetfn(UNUSED(Param pm), double x) { - struct timeval now; - struct timezone dummy_tz; + struct timespec now; + + zgettime_monotonic_if_available(&now); - gettimeofday(&now, &dummy_tz); shtimer.tv_sec = now.tv_sec - (zlong)x; - shtimer.tv_usec = now.tv_usec - (zlong)((x - (zlong)x) * 1000000.0); + shtimer.tv_nsec = now.tv_nsec - (zlong)((x - (zlong)x) * 1000000000.0); } /**/ double getrawseconds(void) { - return (double)shtimer.tv_sec + (double)shtimer.tv_usec / 1000000.0; + return (double)shtimer.tv_sec + (double)shtimer.tv_nsec / 1000000000.0; } /**/ @@ -4560,7 +4558,7 @@ void setrawseconds(double x) { shtimer.tv_sec = (zlong)x; - shtimer.tv_usec = (zlong)((x - (zlong)x) * 1000000.0); + shtimer.tv_nsec = (zlong)((x - (zlong)x) * 1000000000.0); } /**/ diff --git a/Src/prompt.c b/Src/prompt.c index e10b05215..f36aba9d3 100644 --- a/Src/prompt.c +++ b/Src/prompt.c @@ -469,7 +469,7 @@ putpromptchar(int doprint, int endchar) test = 1; break; case 'S': - if (time(NULL) - shtimer.tv_sec >= arg) + if (zmonotime(NULL) - shtimer.tv_sec >= arg) test = 1; break; case 'v': diff --git a/Src/signals.c b/Src/signals.c index 86f1a49f6..de42f302d 100644 --- a/Src/signals.c +++ b/Src/signals.c @@ -342,8 +342,7 @@ wait_for_processes(void) zwarn("job can't be suspended"); } else { #if defined(HAVE_WAIT3) && defined(HAVE_GETRUSAGE) - struct timezone dummy_tz; - gettimeofday(&pn->endtime, &dummy_tz); + zgettime_monotonic_if_available(&pn->endtime); #ifdef WIFCONTINUED if (WIFCONTINUED(status)) pn->status = SP_RUNNING; diff --git a/Src/utils.c b/Src/utils.c index ce4e875fd..5c91dfcda 100644 --- a/Src/utils.c +++ b/Src/utils.c @@ -1570,14 +1570,14 @@ preprompt(void) /* If 1) the parameter PERIOD exists, 2) a hook function for * * "periodic" exists, 3) it's been greater than PERIOD since we * * executed any such hook, then execute it now. */ - if (period && ((zlong)time(NULL) > (zlong)lastperiodic + period) && + if (period && ((zlong)zmonotime(NULL) > (zlong)lastperiodic + period) && !callhookfunc("periodic", NULL, 1, NULL)) - lastperiodic = time(NULL); + lastperiodic = zmonotime(NULL); if (errflag) return; /* Check mail */ - currentmailcheck = time(NULL); + currentmailcheck = zmonotime(NULL); if (mailcheck && (zlong) difftime(currentmailcheck, lastmailcheck) > mailcheck) { char *mailfile; @@ -2761,6 +2761,19 @@ timespec_diff_us(const struct timespec *t1, const struct timespec *t2) return (reverse ? LONG_MIN : LONG_MAX); } +/* Like time(), but uses the monotonic clock */ + +/**/ +mod_export int +zmonotime(time_t *tloc) +{ + struct timespec ts; + zgettime_monotonic_if_available(&ts); + if (tloc) + *tloc = ts.tv_sec; + return ts.tv_sec; +} + /* * Sleep for the given number of microseconds --- must be within * range of a long at the moment, but this is only used for @@ -2794,7 +2807,9 @@ zsleep(long us) /** * Sleep for time (fairly) randomly up to max_us microseconds. - * Don't let the wallclock time extend beyond end_time. + * Don't let the time extend beyond end_time. end_time is compared to + * the current *monotonic* clock time, so do NOT base it on e.g. time(2); + * use zmonotime() or zgettime_monotonic_if_available(). * Return 1 if that seemed to work, else 0. * * For best results max_us should be a multiple of 2**16 or large @@ -2806,7 +2821,7 @@ int zsleep_random(long max_us, time_t end_time) { long r; - time_t now = time(NULL); + time_t now = zmonotime(NULL); /* * Randomish backoff. Doesn't need to be fundamentally diff --git a/Src/zsh.h b/Src/zsh.h index 090abf8f5..85b5c9bdc 100644 --- a/Src/zsh.h +++ b/Src/zsh.h @@ -1115,8 +1115,8 @@ struct process { char text[JOBTEXTSIZE]; /* text to print when 'jobs' is run */ int status; /* return code from waitpid/wait3() */ child_times_t ti; - struct timeval bgtime; /* time job was spawned */ - struct timeval endtime; /* time job exited */ + struct timespec bgtime; /* time job was spawned */ + struct timespec endtime; /* time job exited */ }; struct execstack { diff --git a/Test/A08time.ztst b/Test/A08time.ztst index 22a460f5e..4a41cc76a 100644 --- a/Test/A08time.ztst +++ b/Test/A08time.ztst @@ -11,9 +11,44 @@ (time cat) >&/dev/null 0:`time' keyword (status only) - ( TIMEFMT='%E %mE %uE %* %m%mm %u%uu'; time (:) ) + ( TIMEFMT='%E %mE %uE %nE %* %m%mm %u%uu %n%nn'; time (:) ) 0:`time' keyword with custom TIMEFMT -*?[0-9]##.[0-9](#c2)s [0-9]##ms [0-9]##us %\* %m%mm %u%uu +*?[0-9]##.[0-9](#c2)s [0-9]##ms [0-9]##us [0-9]##ns %\* %m%mm %u%uu %n%nn + + ( TIMEFMT='x %U %S %E'; time (:) ) +0:TIMEFMT %[USE] use centisecond precision +*?x( <0-9>.<00-99>s)(#c3) + + ( TIMEFMT='x %*U %*S %*E'; time (:) ) +0:TIMEFMT %*[USE] use millisecond precision +*?x( <0-9>.<000-999>)(#c3) + + ( TIMEFMT='%nU %nS'; time (read -k3 -t0.1) ) +1:TIMEFMT %nU and %nS are limited to microsecond precision +*?[1-9][0-9]#000ns [1-9][0-9]#000ns + +# SECONDS (after - before) must be greater than the elapsed time, but not much +# greater. 25% was picked arbitrarily as something that hopefully will prevent +# the test from failing on slow machines + ( + typeset -F SECONDS + TIMEFMT=%nE + a=$SECONDS + t=$( (time (read -k3 -t0.1)) 2>&1 ) + b=$SECONDS + s=$(( b - a )) + t=$(( ${t%ns}.0 / 10**9 )) + echo $s $t $(( s > t )) $(( t > s - (s * 0.25) )) + ) +0:`time' elapsed time matches SECONDS +*>[0-9.]## [0-9.]## 1 1 + +# Again, the wide range here is an attempt to prevent this test from failing on +# slow machines. We don't care about the exact time, just that it's vaguely sane +# and that each representation has the same basis + ( TIMEFMT='%E %mE %uE %nE %*E'; time (read -k3 -t0.1) ) +1:TIMEFMT elapsed time values +*?0.<10-50>s <10-500>ms <100000-500000>us <100000000-500000000>ns 0.<100-500> time x=1 0:`time' simple assignment diff --git a/Test/D01prompt.ztst b/Test/D01prompt.ztst index 55861cca1..f42e19714 100644 --- a/Test/D01prompt.ztst +++ b/Test/D01prompt.ztst @@ -68,6 +68,13 @@ print -P '%(?.true.false)' 0:ternary prompt escapes >true +>false + + sec=$SECONDS + eval "print -P '%(${sec}S.true.false)'" + eval "print -P '%($((sec+30))S.true.false)'" +0:ternary prompt escape with test character S +>true >false print -P 'start %10<...1 >1 + # Integer + a=$SECONDS + sleep 1 + b=$SECONDS + print -r - $a $b $(( b > a )) + # Float + typeset -F SECONDS + a=$SECONDS + repeat 10 : + b=$SECONDS + print -r - $a $b $(( b > a )) + # Assignment + a=$SECONDS + SECONDS=8888 + repeat 10 : + b=$SECONDS + print -r - $(( a < 8888 )) $(( b > 8888 )) +0:SECONDS +*>[0-9]## [0-9]## 1 +*>[0-9]##.[0-9]## [0-9]##.[0-9]## 1 +*>1 1 + foo=("|" "?") [[ "|" = ${(j.|.)foo} ]] && print yes || print no [[ "|" = ${(j.|.)~foo} ]] && print yes || print no -- cgit v1.2.3 From d051857e03d474bf7a122148f323bd90d8c16b36 Mon Sep 17 00:00:00 2001 From: dana Date: Thu, 26 Dec 2024 09:39:11 -0600 Subject: 53260: zparseopts: add options -v (argv) and -G (GNU-style parsing) --- ChangeLog | 3 + Doc/Zsh/mod_zutil.yo | 44 ++++++++++-- Src/Modules/zutil.c | 116 +++++++++++++++++++++++++------ Test/V12zparseopts.ztst | 181 ++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 313 insertions(+), 31 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 989cc0aa3..9a40cae5e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2024-12-26 dana + * 53260: Doc/Zsh/mod_zutil.yo, Src/Modules/zutil.c, + Test/V12zparseopts.ztst: add -v and -G options to zparseopts + * 53257: Doc/Zsh/params.yo, Doc/Zsh/prompt.yo, Src/Modules/zftp.c, Src/Modules/zprof.c, Src/compat.c, Src/exec.c, Src/hist.c, Src/init.c, Src/jobs.c, Src/params.c, diff --git a/Doc/Zsh/mod_zutil.yo b/Doc/Zsh/mod_zutil.yo index 9946618d6..76907352f 100644 --- a/Doc/Zsh/mod_zutil.yo +++ b/Doc/Zsh/mod_zutil.yo @@ -228,7 +228,7 @@ item(tt(zregexparse))( This implements some internals of the tt(_regex_arguments) function. ) findex(zparseopts) -item(tt(zparseopts) [ tt(-D) tt(-E) tt(-F) tt(-K) tt(-M) ] [ tt(-a) var(array) ] [ tt(-A) var(assoc) ] [ tt(-) ] var(spec) ...)( +item(tt(zparseopts) [ tt(-D) tt(-E) tt(-F) tt(-G) tt(-K) tt(-M) ] [ tt(-a) var(array) ] [ tt(-A) var(assoc) ] [ tt(-v) var(argv) ] [ tt(-) ] var(spec) ...)( This builtin simplifies the parsing of options in positional parameters, i.e. the set of arguments given by tt($*). Each var(spec) describes one option and must be of the form `var(opt)[tt(=)var(array)]'. If an option @@ -282,19 +282,19 @@ first colon. ) enditem() -In all cases, option-arguments must appear either immediately following the +By default, option-arguments may appear either immediately following the option in the same positional parameter or in the next one. Even an optional argument may appear in the next parameter, unless it begins with a `tt(-)'. -There is no special handling of `tt(=)' as with GNU-style argument parsers; -given the var(spec) `tt(-foo:)', the positional parameter `tt(-)tt(-foo=bar)' -is parsed as `tt(-)tt(-foo)' with an argument of `tt(=bar)'. +Additionally, there is no special consideration given to an `tt(=)' +between an option and its argument. To make parsing use the more common +GNU-style conventions, use the tt(-G) option. When the names of two options that take no arguments overlap, the longest one wins, so that parsing for the var(spec)s `tt(-foo -foobar)' (for example) is unambiguous. However, due to the aforementioned handling of option-arguments, ambiguities may arise when at least one overlapping var(spec) takes an argument, as in `tt(-foo: -foobar)'. In that case, the last matching -var(spec) wins. +var(spec) wins. (This is not an issue with GNU-style parsing.) The options of tt(zparseopts) itself cannot be stacked because, for example, the stack `tt(-DEK)' is indistinguishable from a var(spec) for @@ -338,6 +338,33 @@ Note that the appearance in the positional parameters of an option without its required argument always aborts parsing and returns an error as described above regardless of whether this option is used. ) +item(tt(-G))( +This option specifies that options are parsed in the GNU style, +similar to tt(getopt_long+LPAR()3+RPAR()). In particular: + +Given the var(spec) `tt(-foo:)', the positional parameter +`tt(-)tt(-foo=bar)' is interpreted as option `tt(-)tt(-foo)' with +argument `tt(bar)', rather than argument `tt(=bar)' as is the default, +whilst the positional parameter `tt(-)tt(-foobar)' is considered an +unknown option (unless another var(spec) describes it). This applies +to both singly and doubly hyphenated long options. + +An empty option-argument can be given to its long option in the same +parameter using a trailing `tt(=)'. + +An optional argument to a long option must be given with the +equals syntax, and an optional argument to a short option must +follow it immediately in the same parameter; in both cases the +following parameter is considered unrelated. + +Whenever a long option and its argument are stored in the same array +element, an `tt(=)' is used to separate them. + +A mandatory option-argument given in a separate parameter from its +option (as in `tt(-)tt(-foo) tt(bar)'), or any option-argument given to +a short option in the same parameter, is always treated the same +regardless of whether this option is in effect. +) item(tt(-K))( With this option, the arrays specified with the tt(-a) option and with the `tt(=)var(array)' forms are kept unchanged when none of the var(spec)s for @@ -355,6 +382,11 @@ is found, the values are stored as usual. This changes only the way the values are stored, not the way tt($*) is parsed, so results may be unpredictable if the `var(name)tt(+)' specifier is used inconsistently. ) +item(tt(-v) var(argv))( +With this option, the elements of the named array var(argv) are used as the +positional parameters to parse, rather than those given by tt($*). The +array may be modified according to the tt(-D) option. +) enditem() For example, diff --git a/Src/Modules/zutil.c b/Src/Modules/zutil.c index 5eccea7a9..9b2721a09 100644 --- a/Src/Modules/zutil.c +++ b/Src/Modules/zutil.c @@ -1528,12 +1528,14 @@ struct zoptdesc { Zoptval vals, last; }; -#define ZOF_ARG 1 -#define ZOF_OPT 2 -#define ZOF_MULT 4 -#define ZOF_SAME 8 -#define ZOF_MAP 16 -#define ZOF_CYC 32 +#define ZOF_ARG 1 +#define ZOF_OPT 2 +#define ZOF_MULT 4 +#define ZOF_SAME 8 +#define ZOF_MAP 16 +#define ZOF_CYC 32 +#define ZOF_GNUS 64 +#define ZOF_GNUL 128 struct zoptarr { Zoptarr next; @@ -1568,9 +1570,29 @@ static Zoptdesc lookup_opt(char *str) { Zoptdesc p; - for (p = opt_descs; p; p = p->next) { - if ((p->flags & ZOF_ARG) ? strpfx(p->name, str) : !strcmp(p->name, str)) + /* + * Option takes argument, with GNU-style handling of =. This should only + * be set for long options, though we don't care about that here. Unlike + * the default behaviour, matching is unambiguous + */ + if (p->flags & ZOF_GNUL) { + if (!strcmp(p->name, str) || /* Inefficient, whatever */ + (strpfx(p->name, str) && str[strlen(p->name)] == '=')) + return p; + /* + * Option takes argument, default 'cuddled' style. This is weird with + * long options because we naively accept whatever option has a prefix + * match for the given parameter. Thus if you have specs `-foo:` and + * `-foobar:` (or even `-foobar` with no optarg), without the GNU + * setting, the behaviour depends on the order in which they were + * defined. It is documented to work this way though + */ + } else if (p->flags & ZOF_ARG) { + if (strpfx(p->name, str)) + return p; + /* Option takes no argument */ + } else if (!strcmp(p->name, str)) return p; } return NULL; @@ -1641,10 +1663,13 @@ add_opt_val(Zoptdesc d, char *arg) if (d->arr) d->arr->num += (arg ? 2 : 1); } else if (arg) { - char *s = (char *) zhalloc(strlen(d->name) + strlen(arg) + 2); + /* 3 here is '-' + '=' + NUL */ + char *s = (char *) zhalloc(strlen(d->name) + strlen(arg) + 3); *s = '-'; strcpy(s + 1, d->name); + if (d->flags & ZOF_GNUL) + strcat(s, "="); strcat(s, arg); v->str = s; if (d->arr) @@ -1713,7 +1738,8 @@ static int bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) { char *o, *p, *n, **pp, **aval, **ap, *assoc = NULL, **cp, **np; - int del = 0, flags = 0, extract = 0, fail = 0, keep = 0; + char *paramsname = NULL, **params; + int del = 0, flags = 0, extract = 0, fail = 0, gnu = 0, keep = 0; Zoptdesc sopts[256], d; Zoptarr a, defarr = NULL; Zoptval v; @@ -1758,6 +1784,14 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) } fail = 1; break; + case 'G': + if (o[2]) { + args--; + o = NULL; + break; + } + gnu = 1; + break; case 'K': if (o[2]) { args--; @@ -1808,6 +1842,20 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) return 1; } break; + case 'v': + if (paramsname) { + zwarnnam(nam, "argv array given more than once"); + return 1; + } + if (o[2]) + paramsname = o + 2; + else if (*args) + paramsname = *args++; + else { + zwarnnam(nam, "missing array name"); + return 1; + } + break; default: /* Anything else is an option description */ args--; @@ -1849,6 +1897,9 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) if (*p == ':') { f |= ZOF_ARG; *p = '\0'; + if (gnu) { + f |= o[1] ? ZOF_GNUL : ZOF_GNUS; + } if (*++p == ':') { p++; f |= ZOF_OPT; @@ -1901,8 +1952,10 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) return 1; } } - np = cp = pp = ((extract && del) ? arrdup(pparams) : pparams); + params = getaparam((paramsname = paramsname ? paramsname : "argv")); + np = cp = pp = ((extract && del) ? arrdup(params) : params); for (; (o = *pp); pp++) { + /* Not an option */ if (*o != '-') { if (extract) { if (del) @@ -1911,6 +1964,7 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) } else break; } + /* '-' or '--', end parsing */ if (!o[1] || (o[1] == '-' && !o[2])) { if (del && extract) *cp++ = o; @@ -1918,6 +1972,7 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) break; } if (!(d = lookup_opt(o + 1))) { + /* No match for whole param, try each character as a short option */ while (*++o) { if (!(d = sopts[(unsigned char) *o])) { if (fail) { @@ -1931,19 +1986,27 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) break; } if (d->flags & ZOF_ARG) { + /* Optarg in same parameter */ if (o[1]) { add_opt_val(d, o + 1); break; + /* + * Mandatory optarg or (if not GNU style) optional optarg in + * next parameter + */ } else if (!(d->flags & ZOF_OPT) || - (pp[1] && pp[1][0] != '-')) { + (!(d->flags & (ZOF_GNUL | ZOF_GNUS)) && + pp[1] && pp[1][0] != '-')) { if (!pp[1]) { zwarnnam(nam, "missing argument for option: -%s", d->name); return 1; } add_opt_val(d, *++pp); + /* Missing optional optarg */ } else add_opt_val(d, NULL); + /* No optarg required */ } else add_opt_val(d, NULL); } @@ -1956,21 +2019,37 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) break; } } else { + /* Whole param matches a defined option */ if (d->flags & ZOF_ARG) { char *e = o + strlen(d->name) + 1; - if (*e) + /* GNU style allows an empty optarg in the same parameter */ + if ((d->flags & ZOF_GNUL) && *e == '=') { + add_opt_val(d, ++e); + /* + * Non-empty optarg in same parameter. lookup_opt() test ensures + * that this won't be a GNU-style long option, where this would + * be invalid + */ + } else if (*e) { add_opt_val(d, e); - else if (!(d->flags & ZOF_OPT) || - (pp[1] && pp[1][0] != '-')) { + /* + * Mandatory optarg or (if not GNU style) optional optarg in + * next parameter + */ + } else if (!(d->flags & ZOF_OPT) || + (!(d->flags & (ZOF_GNUL | ZOF_GNUS)) && + pp[1] && pp[1][0] != '-')) { if (!pp[1]) { zwarnnam(nam, "missing argument for option: -%s", d->name); return 1; } add_opt_val(d, *++pp); + /* Missing optional optarg */ } else add_opt_val(d, NULL); + /* No optarg required */ } else add_opt_val(d, NULL); } @@ -2041,12 +2120,9 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) if (del) { if (extract) { *cp = NULL; - freearray(pparams); - pparams = zarrdup(np); + setaparam(paramsname, zarrdup(np)); } else { - pp = zarrdup(pp); - freearray(pparams); - pparams = pp; + setaparam(paramsname, zarrdup(pp)); } } return 0; diff --git a/Test/V12zparseopts.ztst b/Test/V12zparseopts.ztst index 816e1d041..a2743ea0e 100644 --- a/Test/V12zparseopts.ztst +++ b/Test/V12zparseopts.ztst @@ -103,6 +103,14 @@ 0:zparseopts -M >ret: 0, optv: --aaa bar, opts: --aaa bar, argv: 1 2 3 + () { + local -a optv argvv=( -a -b -c 1 2 3 ) + zparseopts -D -a optv -v argvv - a b c + print -r - ret: $?, optv: $optv, argvv: $argvv, argv: $argv + } -x -y -z 7 8 9 +0:zparseopts -v +>ret: 0, optv: -a -b -c, argvv: 1 2 3, argv: -x -y -z 7 8 9 + () { local -a optv aa ab zparseopts -a optv - a=aa b:=ab c:- z @@ -151,16 +159,35 @@ >ret: 0, optv: -+ -: -= -\, argv: 1 2 3 >ret: 0, optv: --::: foo, argv: 1 2 3 - for specs in '-foo: -foobar' '-foobar -foo:'; do + for specs in \ + '-foo: -foobar' '-foobar -foo:' '-foo: -foobar:' '-foobar: -foo:' + do + () { + local -a optv + zparseopts -a optv -D - $=specs + print -r - ret: $?, optv: $optv, argv: $argv + } --foobar 1 2 3 + done +0:overlapping option specs without -G (scan order) +>ret: 0, optv: --foobar, argv: 1 2 3 +>ret: 0, optv: --foo bar, argv: 1 2 3 +>ret: 0, optv: --foobar 1, argv: 2 3 +>ret: 0, optv: --foo bar, argv: 1 2 3 + + for specs in \ + '-foo: -foobar' '-foobar -foo:' '-foo: -foobar:' '-foobar: -foo:' + do () { local -a optv - zparseopts -a optv - $=specs + zparseopts -a optv -D -G - $=specs print -r - ret: $?, optv: $optv, argv: $argv } --foobar 1 2 3 done -0:overlapping option specs (scan order) ->ret: 0, optv: --foobar, argv: --foobar 1 2 3 ->ret: 0, optv: --foo bar, argv: --foobar 1 2 3 +0:overlapping option specs with -G (scan order) +>ret: 0, optv: --foobar, argv: 1 2 3 +>ret: 0, optv: --foobar, argv: 1 2 3 +>ret: 0, optv: --foobar 1, argv: 2 3 +>ret: 0, optv: --foobar 1, argv: 2 3 () { local -a optv @@ -170,3 +197,147 @@ 0:missing optarg ?(anon):zparseopts:2: missing argument for option: -c >ret: 1, optv: , argv: -ab1 -c + + for spec in -foo: -foo:- -foo::; do + () { + local -a optv + zparseopts -a optv -D -F - $=spec + print -r - ret: $?, optv: $optv, argv: $argv + } --foo=bar 1 2 3 + done +0:zparseopts without -G, =optarg handling +>ret: 0, optv: --foo =bar, argv: 1 2 3 +>ret: 0, optv: --foo=bar, argv: 1 2 3 +>ret: 0, optv: --foo=bar, argv: 1 2 3 + + for spec in -foo: -foo:- -foo::; do + () { + local -a optv + zparseopts -a optv -D -F -G - $=spec + print -r - ret: $?, optv: $optv, argv: $argv + } --foo=bar 1 2 3 + done +0:zparseopts -G, single parameter, with = +>ret: 0, optv: --foo bar, argv: 1 2 3 +>ret: 0, optv: --foo=bar, argv: 1 2 3 +>ret: 0, optv: --foo=bar, argv: 1 2 3 + + for spec in -foo: -foo:- -foo:: ; do + () { + local -a optv + zparseopts -a optv -D -F -G - $=spec + print -r - ret: $?, optv: $optv, argv: $argv + } --foobar 1 2 3 + done +0:zparseopts -G, single parameter, without = +?(anon):zparseopts:2: bad option: --foobar +>ret: 1, optv: , argv: --foobar 1 2 3 +?(anon):zparseopts:2: bad option: --foobar +>ret: 1, optv: , argv: --foobar 1 2 3 +?(anon):zparseopts:2: bad option: --foobar +>ret: 1, optv: , argv: --foobar 1 2 3 + + for spec in -foo: -foo:- -foo::; do + () { + local -a optv + zparseopts -a optv -D -F -G - $=spec + print -r - ret: $?, optv: $optv, argv: $argv + } --foo bar 1 2 3 + done +0:zparseopts -G, separate parameters +>ret: 0, optv: --foo bar, argv: 1 2 3 +>ret: 0, optv: --foo=bar, argv: 1 2 3 +>ret: 0, optv: --foo, argv: bar 1 2 3 + + for spec in -foo: -foo:- -foo::; do + () { + local -a optv + zparseopts -a optv -D -F -G - $=spec + print -r - ret: $?, optv: ${(j< >)${(q+)optv}}, argv: $argv + } --foo= 1 2 3 + done +0:zparseopts -G, empty optarg +>ret: 0, optv: --foo '', argv: 1 2 3 +>ret: 0, optv: '--foo=', argv: 1 2 3 +>ret: 0, optv: '--foo=', argv: 1 2 3 + + for gopt in '' -G; do + for spec args in \ + f: '-fbar 1 2 3' \ + f: '-f=bar 1 2 3' \ + f: '-f bar 1 2 3' \ + f:- '-fbar 1 2 3' \ + f:- '-f=bar 1 2 3' \ + f:- '-f bar 1 2 3' \ + f:: '-fbar 1 2 3' \ + f:: '-f=bar 1 2 3' \ + f:: '-f bar 1 2 3' + do + () { + local -a optv + zparseopts -a optv -D -F $gopt - $=spec + print -r - ret: $?, gopt: $gopt, optv: $optv, argv: $argv + } $=args + done + done +0:short options, with and without -G +>ret: 0, gopt: , optv: -f bar, argv: 1 2 3 +>ret: 0, gopt: , optv: -f =bar, argv: 1 2 3 +>ret: 0, gopt: , optv: -f bar, argv: 1 2 3 +>ret: 0, gopt: , optv: -fbar, argv: 1 2 3 +>ret: 0, gopt: , optv: -f=bar, argv: 1 2 3 +>ret: 0, gopt: , optv: -fbar, argv: 1 2 3 +>ret: 0, gopt: , optv: -fbar, argv: 1 2 3 +>ret: 0, gopt: , optv: -f=bar, argv: 1 2 3 +>ret: 0, gopt: , optv: -fbar, argv: 1 2 3 +>ret: 0, gopt: -G, optv: -f bar, argv: 1 2 3 +>ret: 0, gopt: -G, optv: -f =bar, argv: 1 2 3 +>ret: 0, gopt: -G, optv: -f bar, argv: 1 2 3 +>ret: 0, gopt: -G, optv: -fbar, argv: 1 2 3 +>ret: 0, gopt: -G, optv: -f=bar, argv: 1 2 3 +>ret: 0, gopt: -G, optv: -fbar, argv: 1 2 3 +>ret: 0, gopt: -G, optv: -fbar, argv: 1 2 3 +>ret: 0, gopt: -G, optv: -f=bar, argv: 1 2 3 +>ret: 0, gopt: -G, optv: -f, argv: bar 1 2 3 + + for gopt in '' -G; do + for spec args in \ + foo: '-foobar 1 2 3' \ + foo: '-foo=bar 1 2 3' \ + foo: '-foo bar 1 2 3' \ + foo:- '-foobar 1 2 3' \ + foo:- '-foo=bar 1 2 3' \ + foo:- '-foo bar 1 2 3' \ + foo:: '-foobar 1 2 3' \ + foo:: '-foo=bar 1 2 3' \ + foo:: '-foo bar 1 2 3' + do + () { + local -a optv + zparseopts -a optv -D -F $gopt - $=spec + print -r - ret: $?, gopt: $gopt, optv: $optv, argv: $argv + } $=args + done + done +0:Sun-style long options, with and without -G +>ret: 0, gopt: , optv: -foo bar, argv: 1 2 3 +>ret: 0, gopt: , optv: -foo =bar, argv: 1 2 3 +>ret: 0, gopt: , optv: -foo bar, argv: 1 2 3 +>ret: 0, gopt: , optv: -foobar, argv: 1 2 3 +>ret: 0, gopt: , optv: -foo=bar, argv: 1 2 3 +>ret: 0, gopt: , optv: -foobar, argv: 1 2 3 +>ret: 0, gopt: , optv: -foobar, argv: 1 2 3 +>ret: 0, gopt: , optv: -foo=bar, argv: 1 2 3 +>ret: 0, gopt: , optv: -foobar, argv: 1 2 3 +?(anon):zparseopts:2: bad option: -f +>ret: 1, gopt: -G, optv: , argv: -foobar 1 2 3 +>ret: 0, gopt: -G, optv: -foo bar, argv: 1 2 3 +>ret: 0, gopt: -G, optv: -foo bar, argv: 1 2 3 +?(anon):zparseopts:2: bad option: -f +>ret: 1, gopt: -G, optv: , argv: -foobar 1 2 3 +>ret: 0, gopt: -G, optv: -foo=bar, argv: 1 2 3 +>ret: 0, gopt: -G, optv: -foo=bar, argv: 1 2 3 +?(anon):zparseopts:2: bad option: -f +>ret: 1, gopt: -G, optv: , argv: -foobar 1 2 3 +>ret: 0, gopt: -G, optv: -foo=bar, argv: 1 2 3 +>ret: 0, gopt: -G, optv: -foo, argv: bar 1 2 3 -- cgit v1.2.3 From 3f43a2ffd1cfff92ed4627b75d84fbfc1b9abbbc Mon Sep 17 00:00:00 2001 From: Jun-ichi Takimoto Date: Fri, 3 Jan 2025 23:38:21 +0900 Subject: 53293: fix menu() and texinode() in yodl docs --- ChangeLog | 5 +++++ Doc/Zsh/compsys.yo | 10 +++++----- Doc/Zsh/index.yo | 2 +- Doc/Zsh/manual.yo | 3 ++- 4 files changed, 13 insertions(+), 7 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 21dcd47e5..c43adf70f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2025-01-03 Jun-ichi Takimoto + + * 53293: Doc/Zsh/compsys.yo, Doc/Zsh/index.yo, Doc/Zsh/manual.yo: + fix menu() and texinode() in yodl docs + 2025-01-02 dana * 53297: Test/A08time.ztst: allow %nU/%nS result to be 0 diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo index 7ae67773b..06b08cc72 100644 --- a/Doc/Zsh/compsys.yo +++ b/Doc/Zsh/compsys.yo @@ -89,9 +89,9 @@ menu(Initialization) menu(Completion System Configuration) menu(Control Functions) menu(Bindable Commands) -menu(Completion Functions) -menu(Completion Directories) +menu(Utility Functions) menu(Completion System Variables) +menu(Completion Directories) endmenu() texinode(Initialization)(Completion System Configuration)()(Completion System) @@ -3363,7 +3363,7 @@ endsitem() ) enditem() -texinode(Bindable Commands)(Completion Functions)(Control Functions)(Completion System) +texinode(Bindable Commands)(Utility Functions)(Control Functions)(Completion System) sect(Bindable Commands) cindex(completion system, bindable commands) @@ -3562,7 +3562,7 @@ emacs and vi respectively. ) enditem() -texinode(Completion Functions)(Completion System Variables)(Bindable Commands)(Completion System) +texinode(Utility Functions)(Completion System Variables)(Bindable Commands)(Completion System) sect(Utility Functions) cindex(completion system, utility functions) @@ -5481,7 +5481,7 @@ ifnzman(noderef(The zsh/zleparameter Module)). ) enditem() -texinode(Completion System Variables)(Completion Directories)(Completion Functions)(Completion System) +texinode(Completion System Variables)(Completion Directories)(Utility Functions)(Completion System) sect(Completion System Variables) cindex(completion system, variables) diff --git a/Doc/Zsh/index.yo b/Doc/Zsh/index.yo index 191e0f2f9..131d7a4f2 100644 --- a/Doc/Zsh/index.yo +++ b/Doc/Zsh/index.yo @@ -5,7 +5,7 @@ def(printindex)(2)(\ NL()\ NOTRANS(@printindex) ARG2\ )\ -texinode(Concept Index)(Variables Index)(Top)(Top) +texinode(Concept Index)(Variables Index)()(Top) printindex(Concept Index)(cp) texinode(Variables Index)(Options Index)(Concept Index)(Top) diff --git a/Doc/Zsh/manual.yo b/Doc/Zsh/manual.yo index dc4d6ed07..e54dd077c 100644 --- a/Doc/Zsh/manual.yo +++ b/Doc/Zsh/manual.yo @@ -126,7 +126,8 @@ menu(Initialization) menu(Completion System Configuration) menu(Control Functions) menu(Bindable Commands) -menu(Completion Functions) +menu(Utility Functions) +menu(Completion System Variables) menu(Completion Directories) Completion Using compctl -- cgit v1.2.3 From e3f7f2fc8527409f42330e44bf9ff44d8c50efe1 Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Thu, 30 Jan 2025 12:47:08 +0100 Subject: 53336: avoid GNU make specific use of $< in a non-inference rule --- ChangeLog | 3 +++ Doc/Makefile.in | 6 +++--- Etc/Makefile.in | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 1dce0b334..19e5b2497 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2025-01-30 Oliver Kiddle + * 53336: Doc/Makefile.in, Etc/Makefile.in: + avoid GNU make specific use of $< in a non-inference rule + * 53335: Src/hist.c, Src/string.c, Src/Zle/compcore.c, Src/Zle/compctl.c, Src/Zle/compmatch.c, Src/Zle/computil.c: Remove unused dupstring_glen() function and make use of the diff --git a/Doc/Makefile.in b/Doc/Makefile.in index a986aa1c5..d9eec58e8 100644 --- a/Doc/Makefile.in +++ b/Doc/Makefile.in @@ -121,13 +121,13 @@ zsh.pdf zsh_a4.pdf zsh_us.pdf: $(sdir)/zsh.texi intro.pdf intro.a4.pdf intro.us.pdf: $(sdir)/intro.ms if test $@ = intro.us.pdf || \ { test $@ = intro.pdf && test "$(PAPERSIZE)" = us; }; then \ - pdfroff -mspdf --no-kill-null-pages -P-pletter --pdf-output=$@ $<; \ + pdfroff -mspdf --no-kill-null-pages -P-pletter --pdf-output=$@ $(sdir)/intro.ms; \ else \ - pdfroff -mspdf --no-kill-null-pages -P-pa4 --pdf-output=$@ $<; \ + pdfroff -mspdf --no-kill-null-pages -P-pa4 --pdf-output=$@ $(sdir)/intro.ms; \ fi intro.html: $(sdir)/intro.ms - if groff -ms -Thtml -P-jintro $< > tmp.html; then \ + if groff -ms -Thtml -P-jintro $(sdir)/intro.ms > tmp.html; then \ mv tmp.html $@; \ else \ rm -f tmp.html; false; \ diff --git a/Etc/Makefile.in b/Etc/Makefile.in index 9adba95db..53729a0bc 100644 --- a/Etc/Makefile.in +++ b/Etc/Makefile.in @@ -43,10 +43,10 @@ INSTALL = @INSTALL@ all: FAQ FAQ.html FAQ: $(sdir)/FAQ.yo - $(YODL2TXT) -o FAQ.txt $< && mv FAQ.txt $@ + $(YODL2TXT) -o FAQ.txt $(sdir)/FAQ.yo && mv FAQ.txt $@ FAQ.html: $(sdir)/FAQ.yo - $(YODL2HTML) -o $@ $< + $(YODL2HTML) -o $@ $(sdir)/FAQ.yo # ========== DEPENDENCIES FOR CLEANUP ========== -- cgit v1.2.3 From 20990fa7e4bad34bd7e3c145f93f19cb811e8856 Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Thu, 30 Jan 2025 12:51:37 +0100 Subject: 53337: allow nameref -p --- ChangeLog | 2 ++ Doc/Zsh/mod_ksh93.yo | 2 +- Src/Modules/ksh93.c | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 19e5b2497..3ddb16ace 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,7 @@ 2025-01-30 Oliver Kiddle + * 53337: Doc/Zsh/mod_ksh93.yo, Src/Modules/ksh93.c: allow nameref -p + * 53336: Doc/Makefile.in, Etc/Makefile.in: avoid GNU make specific use of $< in a non-inference rule diff --git a/Doc/Zsh/mod_ksh93.yo b/Doc/Zsh/mod_ksh93.yo index 7508758aa..7d22064ee 100644 --- a/Doc/Zsh/mod_ksh93.yo +++ b/Doc/Zsh/mod_ksh93.yo @@ -12,7 +12,7 @@ The single builtin provided by this module is: startitem() findex(nameref) cindex(named references, creating) -item(tt(nameref) [ tt(-gur) ] var(pname)[tt(=)var(rname)])( +item(tt(nameref) [ tt(-gpur) ] var(pname)[tt(=)var(rname)])( Equivalent to tt(typeset -n )var(pname)tt(=)var(rname) However, tt(nameref) is a builtin command rather than a reserved word, diff --git a/Src/Modules/ksh93.c b/Src/Modules/ksh93.c index 3206c11f3..fa0785cda 100644 --- a/Src/Modules/ksh93.c +++ b/Src/Modules/ksh93.c @@ -38,7 +38,7 @@ */ static struct builtin bintab[] = { - BUILTIN("nameref", BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "gur", "n") + BUILTIN("nameref", BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "gpru", "n") }; #include "zsh.mdh" -- cgit v1.2.3 From 51cb3f0f83649b7ca59d62447519f7ea53fb5ec4 Mon Sep 17 00:00:00 2001 From: Jun-ichi Takimoto Date: Tue, 4 Feb 2025 15:39:36 +0900 Subject: unposted: remove a few more files by 'make clean' Doc/zsh.{idx,kys} and Etc/FAQ{01-06}.html. These files are already in .gitignore --- ChangeLog | 5 +++++ Doc/Makefile.in | 2 +- Etc/Makefile.in | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 6e5689d8d..59a04e638 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2025-02-04 Jun-ichi Takimoto + + * unposted: Doc/Makefile.in, Etc/Makefile.in: remove a few more + files by 'make clean' + 2025-01-30 Oliver Kiddle * github #118: Eisuke Kawashima: Completion/Unix/Command/_git: diff --git a/Doc/Makefile.in b/Doc/Makefile.in index d9eec58e8..f68f40a9e 100644 --- a/Doc/Makefile.in +++ b/Doc/Makefile.in @@ -414,7 +414,7 @@ uninstall.html: clean-here: rm -f *.html *.info* *.dvi *.ps *.pdf - rm -f *.aux *.cp *.cps *.fn *.fns *.ky *.log + rm -f *.aux *.cp *.cps *.fn *.fns *.ky *.kys *.log *.idx rm -f *.pg *.pgs *.toc *.tp *.tps *.vr *.vrs rm -rf infodir rm -f texi2html.conf diff --git a/Etc/Makefile.in b/Etc/Makefile.in index 53729a0bc..c1032d118 100644 --- a/Etc/Makefile.in +++ b/Etc/Makefile.in @@ -53,7 +53,7 @@ FAQ.html: $(sdir)/FAQ.yo @CLEAN_MK@ mostlyclean-here: - rm -f FAQ.html + rm -f FAQ*.html distclean-here: rm -f Makefile -- cgit v1.2.3 From 7a54b36fa88aa35f44c42715503f716a1612e3b7 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Wed, 12 Feb 2025 20:03:07 -0800 Subject: 53348: Revise handling of incompatible typeset options when used with -n --- ChangeLog | 5 +++++ Doc/Zsh/builtins.yo | 10 ++++++---- Src/builtin.c | 15 ++++++++++++--- Test/K01nameref.ztst | 2 +- 4 files changed, 24 insertions(+), 8 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 59a04e638..929abce32 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2025-02-12 Bart Schaefer + + * 53348: Doc/Zsh/builtins.yo, Src/builtin.c, Test/K01nameref.ztst: + Revise handling of incompatible typeset options when used with -n + 2025-02-04 Jun-ichi Takimoto * unposted: Doc/Makefile.in, Etc/Makefile.in: remove a few more diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 7a9684ac8..69bb86a0f 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -2052,8 +2052,10 @@ cindex(named reference) cindex(reference, named) The flag tt(-n) creates a em(named reference) to another parameter. The second parameter need not exist at the time the reference is -created. Only tt(-g), tt(-u), and tt(-r) may be used in conjunction with -tt(-n). The var(name) so created may not be an array element nor use +created. Only the tt(-H), tt(-g), and tt(-r) flags may be used in +conjunction with tt(-n), having their usual meanings. The tt(-u) +flag is special and may be applied to alter the scope of the reference. +The var(name) so created may not be an array element nor use a subscript, but the var(value) assigned may be any valid parameter name syntax, even a subscripted array element (including an associative array element) or an array slice, which is evaluated when the named @@ -2286,7 +2288,7 @@ special tt(PATH) parameter is not altered in any way. It is also possible to create a local parameter using `tt(typeset +h )var(special)', where the local copy of var(special) will retain its special properties regardless of having the tt(-h) attribute. Global special parameters loaded from shell -modules (currently those in tt(zsh/mapfile) and tt(zsh/parameter)) are +modules (for example, those in tt(zsh/mapfile) and tt(zsh/parameter)) are automatically given the tt(-h) attribute to avoid name clashes. ) item(tt(-H))( @@ -2349,7 +2351,7 @@ flag has a different meaning when used with tt(-f); see above. item(tt(-u))( Convert the result to upper case whenever the parameter is expanded. The value is em(not) converted when assigned. -This flag has a different meaning when used with tt(-f); see above. +This flag has different meanings when used with tt(-f) or tt(-n); see above. ) item(tt(-x))( Mark for automatic export to the environment of subsequently diff --git a/Src/builtin.c b/Src/builtin.c index 18d74b09e..2fab73b24 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2707,10 +2707,18 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) on |= bit; else if (OPT_PLUS(ops,optval)) off |= bit; + else + continue; + if (OPT_MINUS(ops,'n')) { + if ((on|off) & ~(PM_READONLY|PM_UPPER|PM_HIDEVAL)) { + zwarnnam(name, "-%c not allowed with -n", optval); + /* return 1; */ + } + } } if (OPT_MINUS(ops,'n')) { - if ((on|off) & ~(PM_READONLY|PM_UPPER)) { - zwarnnam(name, "no other attributes allowed with -n"); + if ((on|off) & ~(PM_READONLY|PM_UPPER|PM_HIDEVAL)) { + /* zwarnnam(name, "no other attributes allowed with -n"); */ return 1; } on |= PM_NAMEREF; @@ -3049,7 +3057,8 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) /* It's generally unwise to mass-change the types of * parameters, but for namerefs it would be fatal */ unqueue_signals(); - zerrnam(name, "invalid reference"); + zerrnam(name, "%cm not allowed with -n", + (OPT_PLUS(ops,'m') ? '+' : '-')); return 1; } if (!(on|roff)) diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst index bb0d11821..bacc3ade2 100644 --- a/Test/K01nameref.ztst +++ b/Test/K01nameref.ztst @@ -853,7 +853,7 @@ F:previously this could create an infinite recursion and crash typeset -nm foo=bar 1:create nameref by pattern match not allowed -*?*typeset:1: invalid reference +*?*typeset:1: -m not allowed with -n # # The following tests are run in interactive mode, using PS1 as an -- cgit v1.2.3 From e160cf85f05c3106189edf2158146b9f522d1f1c Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Thu, 27 Feb 2025 16:02:02 +0100 Subject: 53378: support new pipebuf resource limit on FreeBSD Also add other newer limits to the documentation. --- ChangeLog | 5 +++++ Doc/Zsh/builtins.yo | 14 ++++++++++++-- Src/Builtins/rlimits.c | 4 ++++ configure.ac | 1 + 4 files changed, 22 insertions(+), 2 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 05aa89c88..80745557f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2025-02-27 Oliver Kiddle + + * 53378: Doc/Zsh/builtins.yo, Src/Builtins/rlimits.c, configure.ac: + support new pipebuf resource limit on FreeBSD + 2025-02-15 Bart Schaefer * 53363: Src/builtin.c: permit "typeset -n +m pattern" diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index 69bb86a0f..103b6d842 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -1218,19 +1218,26 @@ sitem(tt(datasize))(Maximum data size (including stack) for each process.) sitem(tt(descriptors))(Maximum value for a file descriptor.) sitem(tt(filesize))(Largest single file allowed.) sitem(tt(kqueues))(Maximum number of kqueues allocated.) +sitem(tt(maxfilelocks))(Maximum number of file locks.) sitem(tt(maxproc))(Maximum number of processes.) sitem(tt(maxpthreads))(Maximum number of threads per process.) sitem(tt(memorylocked))(Maximum amount of memory locked in RAM.) sitem(tt(memoryuse))(Maximum resident set size.) sitem(tt(msgqueue))(Maximum number of bytes in POSIX message queues.) +sitem(tt(nice))(Maximum nice value.) +sitem(tt(pipebuf))(Maximum size of buffers for pipes/fifos.) sitem(tt(posixlocks))(Maximum number of POSIX locks per user.) sitem(tt(pseudoterminals))(Maximum number of pseudo-terminals.) sitem(tt(resident))(Maximum resident set size.) +sitem(tt(rt_priority))(Maximum real-time priority.) +sitem(tt(rt_time))(Maximum CPU time without a blocking system call.) sitem(tt(sigpending))(Maximum number of pending signals.) sitem(tt(sockbufsize))(Maximum size of all socket buffers.) sitem(tt(stacksize))(Maximum stack size for each process.) sitem(tt(swapsize))(Maximum amount of swap used.) +sitem(tt(umtxp))(Maximum number of POSIX thread library objects.) sitem(tt(vmemorysize))(Maximum amount of virtual memory.) +sitem(tt(vnodemonitors))(Maximum number of open vnode monitors.) endsitem() Which of these resource limits are available depends on the system. @@ -2390,16 +2397,18 @@ Not all the following resources are supported on all systems. Running tt(ulimit -a) will show which are supported. startsitem() -sitem(tt(-a))(Lists all of the current resource limits.) +sitem(tt(-a))(List all of the current resource limits.) sitem(tt(-b))(Socket buffer size in bytes LPAR()N.B. not kilobytes+RPAR()) sitem(tt(-c))(512-byte blocks on the size of core dumps.) sitem(tt(-d))(Kilobytes on the size of the data segment.) +sitem(tt(-e))(Maximum nice value.) sitem(tt(-f))(512-byte blocks on the size of files written.) sitem(tt(-i))(The number of pending signals.) sitem(tt(-k))(The number of kqueues allocated.) sitem(tt(-l))(Kilobytes on the size of locked-in memory.) sitem(tt(-m))(Kilobytes on the size of physical memory.) -sitem(tt(-n))(open file descriptors.) +sitem(tt(-n))(Open file descriptors.) +sitem(tt(-o))(Maximum number of POSIX thread library objects.) sitem(tt(-p))(The number of pseudo-terminals.) sitem(tt(-q))(Bytes in POSIX message queues.) sitem(tt(-r))(Maximum real time priority. On some systems where this @@ -2413,6 +2422,7 @@ sitem(tt(-v))(Kilobytes on the size of virtual memory. On some systems this refers to the limit called `address space'.) sitem(tt(-w))(Kilobytes on the size of swapped out memory.) sitem(tt(-x))(The number of locks on files.) +sitem(tt(-y))(Maximum size of buffers for pipes/fifos.) endsitem() A resource may also be specified by integer in the form `tt(-N) diff --git a/Src/Builtins/rlimits.c b/Src/Builtins/rlimits.c index f25dd2530..65226dc9a 100644 --- a/Src/Builtins/rlimits.c +++ b/Src/Builtins/rlimits.c @@ -145,6 +145,10 @@ static const resinfo_T known_resources[] = { {RLIMIT_UMTXP, "umtxp", ZLIMTYPE_NUMBER, 1, 'o', "umtx shared locks"}, # endif +# ifdef HAVE_RLIMIT_PIPEBUF /* FreeBSD */ + {RLIMIT_PIPEBUF, "pipebuf", ZLIMTYPE_MEMORY, 1024, + 'y', "size of buffers for pipes/fifos"}, +#endif # ifdef HAVE_RLIMIT_POSIXLOCKS /* DragonFly */ {RLIMIT_POSIXLOCKS, "posixlocks", ZLIMTYPE_NUMBER, 1, diff --git a/configure.ac b/configure.ac index b5548c2b9..db0828a56 100644 --- a/configure.ac +++ b/configure.ac @@ -1873,6 +1873,7 @@ zsh_LIMIT_PRESENT(RLIMIT_NPTS) zsh_LIMIT_PRESENT(RLIMIT_SWAP) zsh_LIMIT_PRESENT(RLIMIT_KQUEUES) zsh_LIMIT_PRESENT(RLIMIT_UMTXP) +zsh_LIMIT_PRESENT(RLIMIT_PIPEBUF) zsh_LIMIT_PRESENT(RLIMIT_NOVMON) zsh_LIMITS_EQUAL(VMEM, vmem, RSS, rss) -- cgit v1.2.3 From 8c3c45732131433645686cdb6bbbb8974230c5a9 Mon Sep 17 00:00:00 2001 From: dana Date: Sun, 13 Apr 2025 18:00:54 -0500 Subject: 53483: zparseopts -G: accept only '--' as parsing terminator --- ChangeLog | 4 ++++ Doc/Zsh/mod_zutil.yo | 14 +++++++++----- Src/Modules/zutil.c | 6 +++--- Test/V12zparseopts.ztst | 25 +++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 8 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 39b488e14..73127d0f2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2025-04-19 dana + * 53483: Doc/Zsh/mod_zutil.yo, Src/Modules/zutil.c, + Test/V12zparseopts.ztst: with `zparseopts -G`, accept only + '--' as parsing terminator + * 53482: Src/Modules/zutil.c, Src/Modules/Test/V12zparseopts.ztst: with `zparseopts -G`, always add options options with optional args to array with = diff --git a/Doc/Zsh/mod_zutil.yo b/Doc/Zsh/mod_zutil.yo index 76907352f..19f9989f4 100644 --- a/Doc/Zsh/mod_zutil.yo +++ b/Doc/Zsh/mod_zutil.yo @@ -242,8 +242,8 @@ Note that it is an error to give any var(spec) without an Unless the tt(-E) option is given, parsing stops at the first string that isn't described by one of the var(spec)s. Even with tt(-E), -parsing always stops at a positional parameter equal to `tt(-)' or -`tt(-)tt(-)'. See also tt(-F). +parsing always stops at a positional parameter equal to `tt(-)tt(-)' or +(without tt(-G)) `tt(-)'. See also tt(-F). The var(opt) description must be one of the following. Any of the special characters can appear in the option name provided it is preceded by a @@ -314,9 +314,9 @@ as the values. item(tt(-D))( If this option is given, all options found are removed from the positional parameters of the calling shell or shell function, up to but not including -any not described by the var(spec)s. If the first such parameter is `tt(-)' -or `tt(-)tt(-)', it is removed as well. This is similar to using the -tt(shift) builtin. +any not described by the var(spec)s. If the first such parameter is +`tt(-)tt(-)' or (without tt(-G)) `tt(-)', it is removed as well. This is +similar to using the tt(shift) builtin. ) item(tt(-E))( This changes the parsing rules to em(not) stop at the first string @@ -364,6 +364,10 @@ A mandatory option-argument given in a separate parameter from its option (as in `tt(-)tt(-foo) tt(bar)'), or any option-argument given to a short option in the same parameter, is always treated the same regardless of whether this option is in effect. + +Lastly, when this option is active, only `tt(-)tt(-)' is treated as an +explicit option-parsing terminator in the parsed arguments; a single +`tt(-)' is considered a normal operand. ) item(tt(-K))( With this option, the arrays specified with the tt(-a) option and with the diff --git a/Src/Modules/zutil.c b/Src/Modules/zutil.c index ef99303d2..676fe1872 100644 --- a/Src/Modules/zutil.c +++ b/Src/Modules/zutil.c @@ -1955,8 +1955,8 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) params = getaparam((paramsname = paramsname ? paramsname : "argv")); np = cp = pp = ((extract && del) ? arrdup(params) : params); for (; (o = *pp); pp++) { - /* Not an option */ - if (*o != '-') { + /* Not an option. With GNU style, this includes '-' */ + if (*o != '-' || (gnu && !o[1])) { if (extract) { if (del) *cp++ = o; @@ -1964,7 +1964,7 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func)) } else break; } - /* '-' or '--', end parsing */ + /* '--' or (with non-GNU style, see above) '-', end parsing */ if (!o[1] || (o[1] == '-' && !o[2])) { if (del && extract) *cp++ = o; diff --git a/Test/V12zparseopts.ztst b/Test/V12zparseopts.ztst index e465d0e0c..e6139ea5e 100644 --- a/Test/V12zparseopts.ztst +++ b/Test/V12zparseopts.ztst @@ -341,3 +341,28 @@ >ret: 1, gopt: -G, optv: , argv: -foobar 1 2 3 >ret: 0, gopt: -G, optv: -foo=bar, argv: 1 2 3 >ret: 0, gopt: -G, optv: -foo=, argv: bar 1 2 3 + + for term in - --; do + # With -D -E -G + () { + local -a optv + zparseopts -a optv -D -E -F -G - -foo -bar + print -r - ret: $?, term: $term, optv: $optv, argv: $argv + } --foo x --bar $term --baz + for gopt in '' -G; do + # With -D + with/without -G + () { + local -a optv + zparseopts -a optv -D -F $gopt - -foo -bar + print -r - ret: $?, term: $term, gopt: $gopt, optv: $optv, argv: $argv + } --foo $term --bar + done + done +0:only -- acts as explicit parsing terminator with -G +?(anon):zparseopts:2: bad option: --baz +>ret: 1, term: -, optv: , argv: --foo x --bar - --baz +>ret: 0, term: -, gopt: , optv: --foo, argv: --bar +>ret: 0, term: -, gopt: -G, optv: --foo, argv: - --bar +>ret: 0, term: --, optv: --foo --bar, argv: x -- --baz +>ret: 0, term: --, gopt: , optv: --foo, argv: --bar +>ret: 0, term: --, gopt: -G, optv: --foo, argv: --bar -- cgit v1.2.3 From 84ef0c523878625feeed8cd0a5c142929d8b4d06 Mon Sep 17 00:00:00 2001 From: dana Date: Sun, 27 Apr 2025 07:58:23 -0500 Subject: 53516: add zgetopt contrib function --- ChangeLog | 3 + Doc/Zsh/contrib.yo | 78 +++++++++++++++++++ Functions/Misc/zgetopt | 198 +++++++++++++++++++++++++++++++++++++++++++++++ NEWS | 5 +- Test/Z04zgetopt.ztst | 206 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 489 insertions(+), 1 deletion(-) create mode 100755 Functions/Misc/zgetopt create mode 100644 Test/Z04zgetopt.ztst (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 57050830f..de8fc6f3d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2025-04-27 dana + * 53516: Doc/Zsh/contrib.yo, Functions/Misc/zgetopt, NEWS, + Test/Z04zgetopt.ztst: add zgetopt contrib function + * 53515: Test/B01cd.ztst: correct ztst documentation error 2025-04-20 Eric Cook diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index c1bea6022..030b63029 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -4672,6 +4672,84 @@ Same as tt(zmv -C) and tt(zmv -L), respectively. These functions do not appear in the zsh distribution, but can be created by linking tt(zmv) to the names tt(zcp) and tt(zln) in some directory in your tt(fpath). ) +findex(zgetopt) +item(tt(zgetopt) [ tt(-a) ] [ tt(-A) var(array) ] [ tt(-l) var(spec) ] [ tt(-n) var(name) ] [ tt(-o) var(spec) ] tt(--) [ var(args) ])( +This is a wrapper around tt(zparseopts) (from tt(zsh/zutil)) which +provides an interface similar to the util-linux implementation of +tt(getopt+LPAR()1+RPAR()) (sometimes called `GNU tt(getopt)'). It +simplifies GNU-style argument parsing (including permutation) and +can make it easier to write functions and scripts with complex APIs, +particularly ones where the order of options is significant. + +The typical usage pattern is as follows: + +example(zgetopt -o abc: -l aaa,bbb,ccc: -- "$@" || return +while (( $# )); do + case $1 in + -a|--aaa+RPAR() ...; shift ;; # handle -a + -b|--bbb+RPAR() ...; shift ;; # handle -b + -c|--ccc+RPAR() ...; shift 2 ;; # handle -c and arg + --+RPAR() ...; shift; break ;; # end of options + esac +done +# handle operands) + +It can also be called as a stand-alone script from other shells +using the more traditional print-and-eval pattern: + +example(args="$( zgetopt -n myscript -o abc: -l aaa,bbb,ccc: -- "$@" )" || return +eval set -- "$args" +while [ $# -ne 0 ]; do ...; done) + +Options: + +startsitem() +sitem(tt(-A var(array)))(When called as a function, assign the parsed +arguments to the named array var(array). Defaults to tt(argv), which +overwrites the caller's positional parameters. Has no meaning when +called as a script, in which case the parsed and quoted arguments are +always printed to standard output. An empty string forces the +printing behaviour in either mode.) +sitem(tt(-a))(Use Sun-style single-hyphenated long options instead of +GNU-style double-hyphenated ones (tt(-foo) vs tt(--foo)). Note that +long options with optional optargs can't always be distinguished +accurately from short options with optional optargs when using this +option. Also, due to limitations of tt(zparseopts), a Sun-style long +option whose name is only one character long is always treated as a +short option.) +sitem(tt(-l var(spec)))(Specify long options to recognise when +parsing. These should be given using just the option name (no +dashes), suffixed by `tt(:)' or `tt(::)' if it takes a mandatory or +optional argument respectively. Multiple options can be defined +either by separating them by commas or by supplying -l again. +Example: tt(-l foo,bar: -l baz)) +sitem(tt(-n var(name)))(Specify the name to use in the error message +if argument parsing fails. Defaults to the name of the nearest +calling function or the base name of tt($ZSH_ARGZERO). Note that +errors related to the usage of tt(zgetopt) itself are always reported +as coming from tt(zgetopt).) +sitem(tt(-o var(spec)))(Specify short options to recognise when +parsing. These should be given as a single string, in the same format +used by the tt(getopts) built-in or the tt(getopt+LPAR()3+RPAR()) +library function, again using `tt(:)' or `tt(::)' to indicate a +mandatory or optional argument. The spec may be prefixed with `tt(+)' +to indicate that option parsing should stop at the first non-option +argument (equivalent to setting the environment variable +tt(POSIXLY_CORRECT)). Example: tt(-o ab:cd::)) +endsitem() + +At least one of tt(-o) or tt(-l) must be given. The function's own +options should be followed by zero or more arguments to parse. It is +critical that these be separated explicitly by `tt(--)', as in the +above examples, to ensure that the function can accurately +distinguish the arguments it's meant to parse from its own. + +Refer to the manual for util-linux's tt(getopt+LPAR()1+RPAR()) for +more information about the way arguments are parsed and results are +returned. Note however that this function is not intended to be a +complete re-implementation. In particular, it omits all +portability/compatibility features. +) item(tt(zkbd))( See `Keyboard Definition' ifzman(above)\ diff --git a/Functions/Misc/zgetopt b/Functions/Misc/zgetopt new file mode 100755 index 000000000..5fc1e7725 --- /dev/null +++ b/Functions/Misc/zgetopt @@ -0,0 +1,198 @@ +#!/bin/zsh -f + +# Wrapper around zparseopts which gives it an interface similar to util-linux's +# getopt(1). See zshcontrib(1) for documentation + +emulate -L zsh -o extended_glob +zmodload -i zsh/zutil || return 3 + +# Very stupid and brittle internal wrapper around zparseopts used to insert the +# caller name into its error messages, allowing us to implement --name. This +# MUST be called with -v, since argv has the options to zparseopts itself +__zgetopt_zparseopts() { + local __err __ret + + __err=$( zparseopts "$@" 2>&1 ) + __ret=$? + + zparseopts "$@" &> /dev/null && return + + # Raw error message should look like this: + # zgetopt_zparseopts:zparseopts:3: bad option: -x + [[ -n $__err ]] && print -ru2 - ${__err/#*:zparseopts:<->:/$name:} + return __ret +} + +local optspec pat i posix=0 +local -a match mbegin mend optvv argvv +local -a array alt lopts sopts name +local -a specs no_arg_opts req_arg_opts opt_arg_opts tmp + +# Same as leading + in short-opts spec +(( $+POSIXLY_CORRECT )) && posix=1 + +# This 0=... makes any error message we get here look a little nicer when we're +# called as a script. Unfortunately the function name overrides $0 in +# zwarnnam() in other scenarios, so this can't be used to implement --name +0=${0:t} zparseopts -D -F -G - \ + {A,-array}:-=array \ + {a,-alternative}=alt \ + {l,-longoptions,-long-options}+:-=lopts \ + {n,-name}:-=name \ + {o,-options}:-=sopts \ +|| { + print -ru2 "usage: ${0:t} [-A ] [-a] [-l ] [-n ] [-o ] -- " + return 2 +} + +# Default to the caller's name +(( $#name )) && name=( "${(@)name/#(-n|--name=)/}" ) +[[ -n $name ]] || name=( ${funcstack[2]:-${ZSH_ARGZERO:t}} ) + +(( $#array )) && array=( "${(@)array/#(-A|--array=)/}" ) + +if [[ $ZSH_EVAL_CONTEXT != toplevel ]]; then + [[ $array == *[^A-Za-z0-9_.]* ]] && { + print -ru2 - "${0:t}: invalid array name: $array" + return 2 + } + (( $#array )) || array=( argv ) + +elif [[ -n $array ]]; then + print -ru2 - "${0:t}: -A option not meaningful unless called as function" + return 2 +fi + +# getopt requires a short option spec; we'll require either short or long +(( $#sopts || $#lopts )) || { + print -ru2 - "${0:t}: missing option spec" + return 2 +} + +optspec=${(@)sopts/#(-o|--options=)/} +sopts=( ) + +for (( i = 1; i <= $#optspec; i++ )); do + # Leading '+': Act POSIXLY_CORRECT + if [[ $i == 1 && $optspec[i] == + ]]; then + posix=1 + # Leading '-': Should leave operands interspersed with options, but this is + # not really possible with zparseopts + elif [[ $i == 1 && $optspec[i] == - ]]; then + print -ru2 - "${0:t}: optspec with leading - (disable operand collection) not supported" + return 2 + # Special characters: [+=\\] because they're special to zparseopts, ':' + # because it's special to getopt, '-' because it's the parsing terminator + elif [[ $optspec[i] == [+:=\\-] ]]; then + print -ru2 - "${0:t}: invalid short-option name: $optspec[i]" + return 2 + # 'a' + elif [[ $optspec[i+1] != : ]]; then + sopts+=( $optspec[i] ) + # 'a:' + elif [[ $optspec[i+2] != : ]]; then + sopts+=( $optspec[i]: ) + (( i += 1 )) + # 'a::' + elif [[ $optspec[i+3] != : ]]; then + sopts+=( $optspec[i]:: ) + (( i += 2 )) + fi +done + +lopts=( ${(@)lopts/#(-l|--long(|-)options=)/} ) +lopts=( ${(@s<,>)lopts} ) + +# Don't allow characters that are special to zparseopts in long-option specs. +# See above +pat='(*[+=\\]*|:*|*:::##|*:[^:]*)' +[[ -n ${(@M)lopts:#$~pat} ]] && { + print -ru2 - "${0:t}: invalid long-option spec: ${${(@M)lopts:#$~pat}[1]}" + return 2 +} + +(( $#alt )) || lopts=( ${(@)lopts/#/-} ) + +specs=( $sopts $lopts ) + +# Used below to identify options with optional optargs +no_arg_opts=( ${(@)${(@M)specs:#*[^:]}/#/-} ) +req_arg_opts=( ${(@)${(@)${(@M)specs:#*[^:]:}/#/-}/%:#} ) +opt_arg_opts=( ${(@)${(@)${(@M)specs:#*::}/#/-}/%:#} ) + +# getopt returns all instances of each option given, so add + +specs=( ${(@)specs/%(#b)(:#)/+$match[1]} ) + +# POSIXLY_CORRECT: Stop parsing options after first non-option argument +if (( posix )); then + tmp=( "$@" ) + __zgetopt_zparseopts -D -F -G -a optvv -v tmp - $specs || return 1 + argvv=( "${(@)tmp}" ) + +# Default: Permute options following non-option arguments +else + tmp=( "$@" ) + __zgetopt_zparseopts -D -E -F -G -a optvv -v tmp - $specs || return 1 + argv=( "${(@)tmp}" ) + # -D + -E leaves an explicit -- in argv where-ever it might appear + local seen + while (( $# )); do + [[ -z $seen && $1 == -- ]] && seen=1 && shift && continue + argvv+=( "$1" ) + shift + done +fi + +# getopt outputs all optargs as separate parameters, even missing optional ones, +# so we scan through and add/separate those if needed. This can't be perfectly +# accurate if Sun-style (-a) long options are used with optional optargs -- e.g. +# if you have specs a:: and abc::, then argument -abc=d is ambiguous. We don't +# guarantee which one is prioritised +(( $#opt_arg_opts )) && { + local cur next + local -a old_optvv=( "${(@)optvv}" ) + optvv=( ) + + for (( i = 1; i <= $#old_optvv; i++ )); do + cur=$old_optvv[i] + next=$old_optvv[i+1] + # Option with no optarg + if [[ -n ${no_arg_opts[(r)$cur]} ]]; then + optvv+=( $cur ) + # Option with required optarg -- will appear in next element + elif [[ -n ${req_arg_opts[(r)$cur]} ]]; then + optvv+=( $cur "$next" ) + (( i++ )) + # Long option with optional optarg -- will appear in same element delimited + # by '=' (even if missing) + elif [[ $cur == *=* && -n ${opt_arg_opts[(r)${cur%%=*}]} ]]; then + optvv+=( ${cur%%=*} "${cur#*=}" ) + # Short option with optional optarg -- will appear in same element with no + # delimiter (thus the option appears alone if the optarg is missing) + elif [[ -n ${opt_arg_opts[(r)${(M)cur#-?}]} ]]; then + optvv+=( ${(M)cur#-?} "${cur#-?}" ) + # ??? + else + print -ru2 - "${0:t}: parse error, please report!" + print -ru2 - "${0:t}: specs: ${(j< >)${(@q+)specs}}" + print -ru2 - "${0:t}: old_optvv: ${(j< >)${(@q+)old_optvv}}" + print -ru2 - "${0:t}: cur: $cur" + optvv+=( $cur ) # I guess? + fi + done +} + +if [[ -n $array ]]; then + # Use EXIT trap to assign in caller's context + trap "$array=( ${(j< >)${(@q+)optvv}} -- ${(j< >)${(@q+)argvv}} )" EXIT + +elif [[ $ZSH_EVAL_CONTEXT != toplevel ]]; then + print -r - "${(@q+)optvv}" -- "${(@q+)argvv}" + +# If called as a script, use unconditional single-quoting. This is ugly but it's +# the closest to what getopt does and it offers compatibility with legacy shells +else + print -r - "${(@qq)optvv}" -- "${(@qq)argvv}" +fi + +return 0 diff --git a/NEWS b/NEWS index 7c5e1e06b..a1e74b9fb 100644 --- a/NEWS +++ b/NEWS @@ -51,7 +51,7 @@ result via assignment to the named param rather than always via $REPLY. The shell now uses monotonic time instead of wall time for most internal time tracking, making it immune to system clock changes due to NTP, etc. For the most part this is transparent to users. However, as a -side effect, some features like $SECONDS and the time builtin gained +side effect, some features like $SECONDS and the time keyword gained (nominal) nanosecond precision. The zsh/zutil module's zparseopts builtin learnt a -v option which can @@ -60,6 +60,9 @@ be used to specify the array of arguments to parse instead of $@. The zparseopts builtin also learnt a -G option which enables GNU-style argument parsing ('--opt=arg', etc.). +A new contrib function zgetopt was added. It wraps `zparseopts -G` to +provide an interface similar to util-linux's getopt(1). + The module zsh/pcre has been updated to use the pcre2 library. The new zsh/random module defines an SRANDOM parameter, zrand_float() diff --git a/Test/Z04zgetopt.ztst b/Test/Z04zgetopt.ztst new file mode 100644 index 000000000..c2bc22be0 --- /dev/null +++ b/Test/Z04zgetopt.ztst @@ -0,0 +1,206 @@ +%prep + + autoload -Uz zgetopt + +%test + + zgetopt -A '' -- a b c + zgetopt -A '' -o '' -- a b c + zgetopt -A '' -l '' -- a b c +0:-o or -l required +?zgetopt: missing option spec +>-- a b c +>-- a b c + + zgetopt -A '' -o - -- a b c + zgetopt -A '' -o -a -- a b c + zgetopt -A '' -o a- -- a b c + zgetopt -A '' -o a+ -- a b c + zgetopt -A '' -o a= -- a b c + zgetopt -A '' -o a\\ -- a b c + zgetopt -A '' -o :a -- a b c + zgetopt -A '' -o a::: -- a b c + zgetopt -A '' -o '' -- a b c + zgetopt -A '' -o + -- a b c +0:weird short-option specs +?zgetopt: optspec with leading - (disable operand collection) not supported +?zgetopt: optspec with leading - (disable operand collection) not supported +?zgetopt: invalid short-option name: - +?zgetopt: invalid short-option name: + +?zgetopt: invalid short-option name: = +?zgetopt: invalid short-option name: \ +?zgetopt: invalid short-option name: : +?zgetopt: invalid short-option name: : +>-- a b c +>-- a b c + + zgetopt -A '' -l a,+ -- a b c + zgetopt -A '' -l a,= -- a b c + zgetopt -A '' -l a,\\ -- a b c + zgetopt -A '' -l a,: -- a b c + zgetopt -A '' -l a,:b -- a b c + zgetopt -A '' -l a,b:b -- a b c + zgetopt -A '' -l a,b::: -- a b c + zgetopt -A '' -l '' -- a b c + zgetopt -A '' -l , -- a b c + zgetopt -A '' -l a,,,,,b -- a b c + zgetopt -A '' -l - -- a b c --- +0:weird long-option specs +?zgetopt: invalid long-option spec: + +?zgetopt: invalid long-option spec: = +?zgetopt: invalid long-option spec: \ +?zgetopt: invalid long-option spec: : +?zgetopt: invalid long-option spec: :b +?zgetopt: invalid long-option spec: b:b +?zgetopt: invalid long-option spec: b::: +>-- a b c +>-- a b c +>-- a b c +>--- -- a b c + + zgetopt -A '' -o ab:c:: -- a b c + zgetopt -A '' -o ab:c:: -- -a + zgetopt -A '' -o ab:c:: -- -a a b c + zgetopt -A '' -o ab:c:: -- -a a -b c + zgetopt -A '' -o ab:c:: -- -a a -b -c + zgetopt -A '' -o ab:c:: -- -a a -b -c d + zgetopt -A '' -o ab:c:: -- -a a -b -c -c + zgetopt -A '' -o ab:c:: -- -a a -b -c -c d + zgetopt -A '' -o ab:c:: -- -a a -b -c -cd +0:short options +>-- a b c +>-a -- +>-a -- a b c +>-a -b c -- a +>-a -b -c -- a +>-a -b -c -- a d +>-a -b -c -c '' -- a +>-a -b -c -c '' -- a d +>-a -b -c -c d -- a + + zgetopt -A '' -l aaa,bbb:,ccc:: -- a b c + zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa + zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa a b c + zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa a --bbb c + zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa a --bbb=c + zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa a --bbb --ccc + zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa a --bbb --ccc d + zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa a --bbb --ccc --ccc + zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa a --bbb --ccc --ccc d + zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa a --bbb --ccc --ccc=d +0:long options +>-- a b c +>--aaa -- +>--aaa -- a b c +>--aaa --bbb c -- a +>--aaa --bbb c -- a +>--aaa --bbb --ccc -- a +>--aaa --bbb --ccc -- a d +>--aaa --bbb --ccc --ccc '' -- a +>--aaa --bbb --ccc --ccc '' -- a d +>--aaa --bbb --ccc --ccc d -- a + + zgetopt -A '' -al aaa,bbb:,ccc:: -- a b c + zgetopt -A '' -al aaa,bbb:,ccc:: -- --aaa a b c + zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa + zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa a b c + zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa a -bbb c + zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa a -bbb=c + zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa a -bbb -ccc + zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa a -bbb -ccc d + zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa a -bbb -ccc -ccc + zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa a -bbb -ccc -ccc d + zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa a -bbb -ccc -ccc=d +0:long options with -a (Sun style) +>-- a b c +?(eval): bad option: --aaa +>-aaa -- +>-aaa -- a b c +>-aaa -bbb c -- a +>-aaa -bbb c -- a +>-aaa -bbb -ccc -- a +>-aaa -bbb -ccc -- a d +>-aaa -bbb -ccc -ccc '' -- a +>-aaa -bbb -ccc -ccc '' -- a d +>-aaa -bbb -ccc -ccc d -- a + + zgetopt -A '' -al a: -- -a=b +0:single-character long option with -a +>-a '=b' -- + + zgetopt -A '' -o '' +0:zero args to parse +>-- + + zgetopt -A '' -o '' -- -- a b c + zgetopt -A '' -o '' -- a b -- c + zgetopt -A '' -o '' -- a b c -- + zgetopt -A '' -o c -- a b -- -c + zgetopt -A '' -o c -- a b - -c +0:parsing terminator +>-- a b c +>-- a b c +>-- a b c +>-- a b -c +>-c -- a b - + + zgetopt -A '' -o a -- a -a b + zgetopt -A '' -o +a -- a -a b + POSIXLY_CORRECT=1 zgetopt -A '' -o a -- a -a b +0:POSIXLY_CORRECT +>-a -- a b +>-- a -a b +>-- a -a b + + zgetopt -A '' -o '' -- $'\a\'\a' +0:function-mode quoting style +>-- $'\C-G\'\C-G' + + zgetopt -A '' -o '' -- a -a b + zgetopt -A '' -o '' -- a --a b +1:bad options +?(eval): bad option: -a +?(eval): bad option: --a + + zgetopt -A '' ; echo $? # missing spec + zgetopt -A '' -o '' -x ; echo $? # bad option to zgetopt + zgetopt -A '' -o '' -- -y; echo $? # bad option to parse +0:return status +*?zgetopt: missing option spec +*>2 +*?zgetopt:zparseopts:*: bad option: -x +*?usage:* +*>2 +*?\(eval\): bad option: -y +*>1 + + () { zgetopt -o a -- "$@"; typeset -p argv } -a b c + () { local -a v; zgetopt -A v -o a -- "$@"; typeset -p argv v } -a b c +0:array output +>typeset -g -a argv=( -a -- b c ) +>typeset -g -a argv=( -a b c ) +>typeset -a v=( -a -- b c ) + + zgetopt -A '' -o a: -- -x + zgetopt -A '' -o a: -- -a + () { zgetopt -A '' -o a: -- "$@"; : } -x + func() { zgetopt -A '' -o a: -- "$@"; : }; func -x + f1() { zgetopt -A '' -o a: -- "$@"; : }; f2() { f1 "$@" }; f2 -x +0:automatic name +?(eval): bad option: -x +?(eval): missing argument for option: -a +?(anon): bad option: -x +?func: bad option: -x +?f1: bad option: -x + + zgetopt -n aaa -A '' -o a: -- -x + zgetopt -n aaa -A '' -o a: -- -a + () { zgetopt -n bbb -A '' -o a: -- "$@"; : } -x + func() { zgetopt -n ccc -A '' -o a: -- "$@"; : }; func -x + f1() { zgetopt -n ddd -A '' -o a: -- "$@"; : }; f2() { f1 "$@" }; f2 -x +0:manual name with -n +?aaa: bad option: -x +?aaa: missing argument for option: -a +?bbb: bad option: -x +?ccc: bad option: -x +?ddd: bad option: -x -- cgit v1.2.3 From 0e369f37d637874b45faa43a0896e70f45739abc Mon Sep 17 00:00:00 2001 From: dana Date: Sun, 27 Apr 2025 10:04:49 -0500 Subject: unposted: docs: adjust zgetopt wording per workers/53518 --- ChangeLog | 2 ++ Doc/Zsh/contrib.yo | 14 +++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index de8fc6f3d..a02917bad 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,7 @@ 2025-04-27 dana + * unposted: Doc/Zsh/contrib.yo: adjust zgetopt wording + * 53516: Doc/Zsh/contrib.yo, Functions/Misc/zgetopt, NEWS, Test/Z04zgetopt.ztst: add zgetopt contrib function diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index 030b63029..515b70f51 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -4710,13 +4710,13 @@ overwrites the caller's positional parameters. Has no meaning when called as a script, in which case the parsed and quoted arguments are always printed to standard output. An empty string forces the printing behaviour in either mode.) -sitem(tt(-a))(Use Sun-style single-hyphenated long options instead of -GNU-style double-hyphenated ones (tt(-foo) vs tt(--foo)). Note that -long options with optional optargs can't always be distinguished -accurately from short options with optional optargs when using this -option. Also, due to limitations of tt(zparseopts), a Sun-style long -option whose name is only one character long is always treated as a -short option.) +sitem(tt(-a))(Use `alternative'-style single-hyphenated long options +instead of GNU-style double-hyphenated ones (tt(-foo) vs tt(--foo)). +Note that long options with optional optargs can't always be +distinguished accurately from short options with optional optargs +when using this option. Also, due to limitations of tt(zparseopts), +a single-hyphenated long option whose name is only one character long +is always treated as a short option.) sitem(tt(-l var(spec)))(Specify long options to recognise when parsing. These should be given using just the option name (no dashes), suffixed by `tt(:)' or `tt(::)' if it takes a mandatory or -- cgit v1.2.3 From 9b68cf38f08fdf352fb1dc6fb97438210aee48b6 Mon Sep 17 00:00:00 2001 From: dana Date: Sun, 27 Apr 2025 10:36:49 -0500 Subject: unposted: document zparseopts's lack of support for abbreviating long options per workers/53520 --- ChangeLog | 4 ++++ Doc/Zsh/contrib.yo | 3 ++- Doc/Zsh/mod_zutil.yo | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index a02917bad..441a5dd5a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2025-04-27 dana + * unposted: Doc/Zsh/contrib.yo, Doc/Zsh/mod_zutil.yo: + document zparseopts's lack of support for abbreviating long + options + * unposted: Doc/Zsh/contrib.yo: adjust zgetopt wording * 53516: Doc/Zsh/contrib.yo, Functions/Misc/zgetopt, NEWS, diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index 515b70f51..7822460e8 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -4748,7 +4748,8 @@ Refer to the manual for util-linux's tt(getopt+LPAR()1+RPAR()) for more information about the way arguments are parsed and results are returned. Note however that this function is not intended to be a complete re-implementation. In particular, it omits all -portability/compatibility features. +portability/compatibility features. Also, like tt(zparseopts) +itself, it does not support abbreviating long options. ) item(tt(zkbd))( See `Keyboard Definition' diff --git a/Doc/Zsh/mod_zutil.yo b/Doc/Zsh/mod_zutil.yo index 19f9989f4..b296d878c 100644 --- a/Doc/Zsh/mod_zutil.yo +++ b/Doc/Zsh/mod_zutil.yo @@ -368,6 +368,9 @@ regardless of whether this option is in effect. Lastly, when this option is active, only `tt(-)tt(-)' is treated as an explicit option-parsing terminator in the parsed arguments; a single `tt(-)' is considered a normal operand. + +Note: Unlike most tt(getopt_long+LPAR()3+RPAR()) implementations, +tt(zparseopts) does not support abbreviating long options. ) item(tt(-K))( With this option, the arrays specified with the tt(-a) option and with the -- cgit v1.2.3 From 80de57d5a63870e61a3d48efa1406ac05bbd2b5b Mon Sep 17 00:00:00 2001 From: dana Date: Mon, 28 Apr 2025 16:18:42 -0500 Subject: 53527: remove zgetopt reverts most of 84ef0c523, 0e369f37d, and 9b68cf38f feature was not ready. may be re-added after 5.10 release --- ChangeLog | 5 ++ Doc/Zsh/contrib.yo | 79 ------------------- Functions/Misc/zgetopt | 198 ----------------------------------------------- NEWS | 3 - Test/Z04zgetopt.ztst | 206 ------------------------------------------------- 5 files changed, 5 insertions(+), 486 deletions(-) delete mode 100755 Functions/Misc/zgetopt delete mode 100644 Test/Z04zgetopt.ztst (limited to 'Doc') diff --git a/ChangeLog b/ChangeLog index 441a5dd5a..cdcf5d5e8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2025-04-28 dana + + * 53527: Doc/Zsh/contrib.yo, Functions/Misc/zgetopt, NEWS, + Test/Z04zgetopt.ztst: remove zgetopt (feature not ready) + 2025-04-27 dana * unposted: Doc/Zsh/contrib.yo, Doc/Zsh/mod_zutil.yo: diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index 7822460e8..c1bea6022 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -4672,85 +4672,6 @@ Same as tt(zmv -C) and tt(zmv -L), respectively. These functions do not appear in the zsh distribution, but can be created by linking tt(zmv) to the names tt(zcp) and tt(zln) in some directory in your tt(fpath). ) -findex(zgetopt) -item(tt(zgetopt) [ tt(-a) ] [ tt(-A) var(array) ] [ tt(-l) var(spec) ] [ tt(-n) var(name) ] [ tt(-o) var(spec) ] tt(--) [ var(args) ])( -This is a wrapper around tt(zparseopts) (from tt(zsh/zutil)) which -provides an interface similar to the util-linux implementation of -tt(getopt+LPAR()1+RPAR()) (sometimes called `GNU tt(getopt)'). It -simplifies GNU-style argument parsing (including permutation) and -can make it easier to write functions and scripts with complex APIs, -particularly ones where the order of options is significant. - -The typical usage pattern is as follows: - -example(zgetopt -o abc: -l aaa,bbb,ccc: -- "$@" || return -while (( $# )); do - case $1 in - -a|--aaa+RPAR() ...; shift ;; # handle -a - -b|--bbb+RPAR() ...; shift ;; # handle -b - -c|--ccc+RPAR() ...; shift 2 ;; # handle -c and arg - --+RPAR() ...; shift; break ;; # end of options - esac -done -# handle operands) - -It can also be called as a stand-alone script from other shells -using the more traditional print-and-eval pattern: - -example(args="$( zgetopt -n myscript -o abc: -l aaa,bbb,ccc: -- "$@" )" || return -eval set -- "$args" -while [ $# -ne 0 ]; do ...; done) - -Options: - -startsitem() -sitem(tt(-A var(array)))(When called as a function, assign the parsed -arguments to the named array var(array). Defaults to tt(argv), which -overwrites the caller's positional parameters. Has no meaning when -called as a script, in which case the parsed and quoted arguments are -always printed to standard output. An empty string forces the -printing behaviour in either mode.) -sitem(tt(-a))(Use `alternative'-style single-hyphenated long options -instead of GNU-style double-hyphenated ones (tt(-foo) vs tt(--foo)). -Note that long options with optional optargs can't always be -distinguished accurately from short options with optional optargs -when using this option. Also, due to limitations of tt(zparseopts), -a single-hyphenated long option whose name is only one character long -is always treated as a short option.) -sitem(tt(-l var(spec)))(Specify long options to recognise when -parsing. These should be given using just the option name (no -dashes), suffixed by `tt(:)' or `tt(::)' if it takes a mandatory or -optional argument respectively. Multiple options can be defined -either by separating them by commas or by supplying -l again. -Example: tt(-l foo,bar: -l baz)) -sitem(tt(-n var(name)))(Specify the name to use in the error message -if argument parsing fails. Defaults to the name of the nearest -calling function or the base name of tt($ZSH_ARGZERO). Note that -errors related to the usage of tt(zgetopt) itself are always reported -as coming from tt(zgetopt).) -sitem(tt(-o var(spec)))(Specify short options to recognise when -parsing. These should be given as a single string, in the same format -used by the tt(getopts) built-in or the tt(getopt+LPAR()3+RPAR()) -library function, again using `tt(:)' or `tt(::)' to indicate a -mandatory or optional argument. The spec may be prefixed with `tt(+)' -to indicate that option parsing should stop at the first non-option -argument (equivalent to setting the environment variable -tt(POSIXLY_CORRECT)). Example: tt(-o ab:cd::)) -endsitem() - -At least one of tt(-o) or tt(-l) must be given. The function's own -options should be followed by zero or more arguments to parse. It is -critical that these be separated explicitly by `tt(--)', as in the -above examples, to ensure that the function can accurately -distinguish the arguments it's meant to parse from its own. - -Refer to the manual for util-linux's tt(getopt+LPAR()1+RPAR()) for -more information about the way arguments are parsed and results are -returned. Note however that this function is not intended to be a -complete re-implementation. In particular, it omits all -portability/compatibility features. Also, like tt(zparseopts) -itself, it does not support abbreviating long options. -) item(tt(zkbd))( See `Keyboard Definition' ifzman(above)\ diff --git a/Functions/Misc/zgetopt b/Functions/Misc/zgetopt deleted file mode 100755 index 5fc1e7725..000000000 --- a/Functions/Misc/zgetopt +++ /dev/null @@ -1,198 +0,0 @@ -#!/bin/zsh -f - -# Wrapper around zparseopts which gives it an interface similar to util-linux's -# getopt(1). See zshcontrib(1) for documentation - -emulate -L zsh -o extended_glob -zmodload -i zsh/zutil || return 3 - -# Very stupid and brittle internal wrapper around zparseopts used to insert the -# caller name into its error messages, allowing us to implement --name. This -# MUST be called with -v, since argv has the options to zparseopts itself -__zgetopt_zparseopts() { - local __err __ret - - __err=$( zparseopts "$@" 2>&1 ) - __ret=$? - - zparseopts "$@" &> /dev/null && return - - # Raw error message should look like this: - # zgetopt_zparseopts:zparseopts:3: bad option: -x - [[ -n $__err ]] && print -ru2 - ${__err/#*:zparseopts:<->:/$name:} - return __ret -} - -local optspec pat i posix=0 -local -a match mbegin mend optvv argvv -local -a array alt lopts sopts name -local -a specs no_arg_opts req_arg_opts opt_arg_opts tmp - -# Same as leading + in short-opts spec -(( $+POSIXLY_CORRECT )) && posix=1 - -# This 0=... makes any error message we get here look a little nicer when we're -# called as a script. Unfortunately the function name overrides $0 in -# zwarnnam() in other scenarios, so this can't be used to implement --name -0=${0:t} zparseopts -D -F -G - \ - {A,-array}:-=array \ - {a,-alternative}=alt \ - {l,-longoptions,-long-options}+:-=lopts \ - {n,-name}:-=name \ - {o,-options}:-=sopts \ -|| { - print -ru2 "usage: ${0:t} [-A ] [-a] [-l ] [-n ] [-o ] -- " - return 2 -} - -# Default to the caller's name -(( $#name )) && name=( "${(@)name/#(-n|--name=)/}" ) -[[ -n $name ]] || name=( ${funcstack[2]:-${ZSH_ARGZERO:t}} ) - -(( $#array )) && array=( "${(@)array/#(-A|--array=)/}" ) - -if [[ $ZSH_EVAL_CONTEXT != toplevel ]]; then - [[ $array == *[^A-Za-z0-9_.]* ]] && { - print -ru2 - "${0:t}: invalid array name: $array" - return 2 - } - (( $#array )) || array=( argv ) - -elif [[ -n $array ]]; then - print -ru2 - "${0:t}: -A option not meaningful unless called as function" - return 2 -fi - -# getopt requires a short option spec; we'll require either short or long -(( $#sopts || $#lopts )) || { - print -ru2 - "${0:t}: missing option spec" - return 2 -} - -optspec=${(@)sopts/#(-o|--options=)/} -sopts=( ) - -for (( i = 1; i <= $#optspec; i++ )); do - # Leading '+': Act POSIXLY_CORRECT - if [[ $i == 1 && $optspec[i] == + ]]; then - posix=1 - # Leading '-': Should leave operands interspersed with options, but this is - # not really possible with zparseopts - elif [[ $i == 1 && $optspec[i] == - ]]; then - print -ru2 - "${0:t}: optspec with leading - (disable operand collection) not supported" - return 2 - # Special characters: [+=\\] because they're special to zparseopts, ':' - # because it's special to getopt, '-' because it's the parsing terminator - elif [[ $optspec[i] == [+:=\\-] ]]; then - print -ru2 - "${0:t}: invalid short-option name: $optspec[i]" - return 2 - # 'a' - elif [[ $optspec[i+1] != : ]]; then - sopts+=( $optspec[i] ) - # 'a:' - elif [[ $optspec[i+2] != : ]]; then - sopts+=( $optspec[i]: ) - (( i += 1 )) - # 'a::' - elif [[ $optspec[i+3] != : ]]; then - sopts+=( $optspec[i]:: ) - (( i += 2 )) - fi -done - -lopts=( ${(@)lopts/#(-l|--long(|-)options=)/} ) -lopts=( ${(@s<,>)lopts} ) - -# Don't allow characters that are special to zparseopts in long-option specs. -# See above -pat='(*[+=\\]*|:*|*:::##|*:[^:]*)' -[[ -n ${(@M)lopts:#$~pat} ]] && { - print -ru2 - "${0:t}: invalid long-option spec: ${${(@M)lopts:#$~pat}[1]}" - return 2 -} - -(( $#alt )) || lopts=( ${(@)lopts/#/-} ) - -specs=( $sopts $lopts ) - -# Used below to identify options with optional optargs -no_arg_opts=( ${(@)${(@M)specs:#*[^:]}/#/-} ) -req_arg_opts=( ${(@)${(@)${(@M)specs:#*[^:]:}/#/-}/%:#} ) -opt_arg_opts=( ${(@)${(@)${(@M)specs:#*::}/#/-}/%:#} ) - -# getopt returns all instances of each option given, so add + -specs=( ${(@)specs/%(#b)(:#)/+$match[1]} ) - -# POSIXLY_CORRECT: Stop parsing options after first non-option argument -if (( posix )); then - tmp=( "$@" ) - __zgetopt_zparseopts -D -F -G -a optvv -v tmp - $specs || return 1 - argvv=( "${(@)tmp}" ) - -# Default: Permute options following non-option arguments -else - tmp=( "$@" ) - __zgetopt_zparseopts -D -E -F -G -a optvv -v tmp - $specs || return 1 - argv=( "${(@)tmp}" ) - # -D + -E leaves an explicit -- in argv where-ever it might appear - local seen - while (( $# )); do - [[ -z $seen && $1 == -- ]] && seen=1 && shift && continue - argvv+=( "$1" ) - shift - done -fi - -# getopt outputs all optargs as separate parameters, even missing optional ones, -# so we scan through and add/separate those if needed. This can't be perfectly -# accurate if Sun-style (-a) long options are used with optional optargs -- e.g. -# if you have specs a:: and abc::, then argument -abc=d is ambiguous. We don't -# guarantee which one is prioritised -(( $#opt_arg_opts )) && { - local cur next - local -a old_optvv=( "${(@)optvv}" ) - optvv=( ) - - for (( i = 1; i <= $#old_optvv; i++ )); do - cur=$old_optvv[i] - next=$old_optvv[i+1] - # Option with no optarg - if [[ -n ${no_arg_opts[(r)$cur]} ]]; then - optvv+=( $cur ) - # Option with required optarg -- will appear in next element - elif [[ -n ${req_arg_opts[(r)$cur]} ]]; then - optvv+=( $cur "$next" ) - (( i++ )) - # Long option with optional optarg -- will appear in same element delimited - # by '=' (even if missing) - elif [[ $cur == *=* && -n ${opt_arg_opts[(r)${cur%%=*}]} ]]; then - optvv+=( ${cur%%=*} "${cur#*=}" ) - # Short option with optional optarg -- will appear in same element with no - # delimiter (thus the option appears alone if the optarg is missing) - elif [[ -n ${opt_arg_opts[(r)${(M)cur#-?}]} ]]; then - optvv+=( ${(M)cur#-?} "${cur#-?}" ) - # ??? - else - print -ru2 - "${0:t}: parse error, please report!" - print -ru2 - "${0:t}: specs: ${(j< >)${(@q+)specs}}" - print -ru2 - "${0:t}: old_optvv: ${(j< >)${(@q+)old_optvv}}" - print -ru2 - "${0:t}: cur: $cur" - optvv+=( $cur ) # I guess? - fi - done -} - -if [[ -n $array ]]; then - # Use EXIT trap to assign in caller's context - trap "$array=( ${(j< >)${(@q+)optvv}} -- ${(j< >)${(@q+)argvv}} )" EXIT - -elif [[ $ZSH_EVAL_CONTEXT != toplevel ]]; then - print -r - "${(@q+)optvv}" -- "${(@q+)argvv}" - -# If called as a script, use unconditional single-quoting. This is ugly but it's -# the closest to what getopt does and it offers compatibility with legacy shells -else - print -r - "${(@qq)optvv}" -- "${(@qq)argvv}" -fi - -return 0 diff --git a/NEWS b/NEWS index a1e74b9fb..d5f6ab620 100644 --- a/NEWS +++ b/NEWS @@ -60,9 +60,6 @@ be used to specify the array of arguments to parse instead of $@. The zparseopts builtin also learnt a -G option which enables GNU-style argument parsing ('--opt=arg', etc.). -A new contrib function zgetopt was added. It wraps `zparseopts -G` to -provide an interface similar to util-linux's getopt(1). - The module zsh/pcre has been updated to use the pcre2 library. The new zsh/random module defines an SRANDOM parameter, zrand_float() diff --git a/Test/Z04zgetopt.ztst b/Test/Z04zgetopt.ztst deleted file mode 100644 index c2bc22be0..000000000 --- a/Test/Z04zgetopt.ztst +++ /dev/null @@ -1,206 +0,0 @@ -%prep - - autoload -Uz zgetopt - -%test - - zgetopt -A '' -- a b c - zgetopt -A '' -o '' -- a b c - zgetopt -A '' -l '' -- a b c -0:-o or -l required -?zgetopt: missing option spec ->-- a b c ->-- a b c - - zgetopt -A '' -o - -- a b c - zgetopt -A '' -o -a -- a b c - zgetopt -A '' -o a- -- a b c - zgetopt -A '' -o a+ -- a b c - zgetopt -A '' -o a= -- a b c - zgetopt -A '' -o a\\ -- a b c - zgetopt -A '' -o :a -- a b c - zgetopt -A '' -o a::: -- a b c - zgetopt -A '' -o '' -- a b c - zgetopt -A '' -o + -- a b c -0:weird short-option specs -?zgetopt: optspec with leading - (disable operand collection) not supported -?zgetopt: optspec with leading - (disable operand collection) not supported -?zgetopt: invalid short-option name: - -?zgetopt: invalid short-option name: + -?zgetopt: invalid short-option name: = -?zgetopt: invalid short-option name: \ -?zgetopt: invalid short-option name: : -?zgetopt: invalid short-option name: : ->-- a b c ->-- a b c - - zgetopt -A '' -l a,+ -- a b c - zgetopt -A '' -l a,= -- a b c - zgetopt -A '' -l a,\\ -- a b c - zgetopt -A '' -l a,: -- a b c - zgetopt -A '' -l a,:b -- a b c - zgetopt -A '' -l a,b:b -- a b c - zgetopt -A '' -l a,b::: -- a b c - zgetopt -A '' -l '' -- a b c - zgetopt -A '' -l , -- a b c - zgetopt -A '' -l a,,,,,b -- a b c - zgetopt -A '' -l - -- a b c --- -0:weird long-option specs -?zgetopt: invalid long-option spec: + -?zgetopt: invalid long-option spec: = -?zgetopt: invalid long-option spec: \ -?zgetopt: invalid long-option spec: : -?zgetopt: invalid long-option spec: :b -?zgetopt: invalid long-option spec: b:b -?zgetopt: invalid long-option spec: b::: ->-- a b c ->-- a b c ->-- a b c ->--- -- a b c - - zgetopt -A '' -o ab:c:: -- a b c - zgetopt -A '' -o ab:c:: -- -a - zgetopt -A '' -o ab:c:: -- -a a b c - zgetopt -A '' -o ab:c:: -- -a a -b c - zgetopt -A '' -o ab:c:: -- -a a -b -c - zgetopt -A '' -o ab:c:: -- -a a -b -c d - zgetopt -A '' -o ab:c:: -- -a a -b -c -c - zgetopt -A '' -o ab:c:: -- -a a -b -c -c d - zgetopt -A '' -o ab:c:: -- -a a -b -c -cd -0:short options ->-- a b c ->-a -- ->-a -- a b c ->-a -b c -- a ->-a -b -c -- a ->-a -b -c -- a d ->-a -b -c -c '' -- a ->-a -b -c -c '' -- a d ->-a -b -c -c d -- a - - zgetopt -A '' -l aaa,bbb:,ccc:: -- a b c - zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa - zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa a b c - zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa a --bbb c - zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa a --bbb=c - zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa a --bbb --ccc - zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa a --bbb --ccc d - zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa a --bbb --ccc --ccc - zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa a --bbb --ccc --ccc d - zgetopt -A '' -l aaa,bbb:,ccc:: -- --aaa a --bbb --ccc --ccc=d -0:long options ->-- a b c ->--aaa -- ->--aaa -- a b c ->--aaa --bbb c -- a ->--aaa --bbb c -- a ->--aaa --bbb --ccc -- a ->--aaa --bbb --ccc -- a d ->--aaa --bbb --ccc --ccc '' -- a ->--aaa --bbb --ccc --ccc '' -- a d ->--aaa --bbb --ccc --ccc d -- a - - zgetopt -A '' -al aaa,bbb:,ccc:: -- a b c - zgetopt -A '' -al aaa,bbb:,ccc:: -- --aaa a b c - zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa - zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa a b c - zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa a -bbb c - zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa a -bbb=c - zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa a -bbb -ccc - zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa a -bbb -ccc d - zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa a -bbb -ccc -ccc - zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa a -bbb -ccc -ccc d - zgetopt -A '' -al aaa,bbb:,ccc:: -- -aaa a -bbb -ccc -ccc=d -0:long options with -a (Sun style) ->-- a b c -?(eval): bad option: --aaa ->-aaa -- ->-aaa -- a b c ->-aaa -bbb c -- a ->-aaa -bbb c -- a ->-aaa -bbb -ccc -- a ->-aaa -bbb -ccc -- a d ->-aaa -bbb -ccc -ccc '' -- a ->-aaa -bbb -ccc -ccc '' -- a d ->-aaa -bbb -ccc -ccc d -- a - - zgetopt -A '' -al a: -- -a=b -0:single-character long option with -a ->-a '=b' -- - - zgetopt -A '' -o '' -0:zero args to parse ->-- - - zgetopt -A '' -o '' -- -- a b c - zgetopt -A '' -o '' -- a b -- c - zgetopt -A '' -o '' -- a b c -- - zgetopt -A '' -o c -- a b -- -c - zgetopt -A '' -o c -- a b - -c -0:parsing terminator ->-- a b c ->-- a b c ->-- a b c ->-- a b -c ->-c -- a b - - - zgetopt -A '' -o a -- a -a b - zgetopt -A '' -o +a -- a -a b - POSIXLY_CORRECT=1 zgetopt -A '' -o a -- a -a b -0:POSIXLY_CORRECT ->-a -- a b ->-- a -a b ->-- a -a b - - zgetopt -A '' -o '' -- $'\a\'\a' -0:function-mode quoting style ->-- $'\C-G\'\C-G' - - zgetopt -A '' -o '' -- a -a b - zgetopt -A '' -o '' -- a --a b -1:bad options -?(eval): bad option: -a -?(eval): bad option: --a - - zgetopt -A '' ; echo $? # missing spec - zgetopt -A '' -o '' -x ; echo $? # bad option to zgetopt - zgetopt -A '' -o '' -- -y; echo $? # bad option to parse -0:return status -*?zgetopt: missing option spec -*>2 -*?zgetopt:zparseopts:*: bad option: -x -*?usage:* -*>2 -*?\(eval\): bad option: -y -*>1 - - () { zgetopt -o a -- "$@"; typeset -p argv } -a b c - () { local -a v; zgetopt -A v -o a -- "$@"; typeset -p argv v } -a b c -0:array output ->typeset -g -a argv=( -a -- b c ) ->typeset -g -a argv=( -a b c ) ->typeset -a v=( -a -- b c ) - - zgetopt -A '' -o a: -- -x - zgetopt -A '' -o a: -- -a - () { zgetopt -A '' -o a: -- "$@"; : } -x - func() { zgetopt -A '' -o a: -- "$@"; : }; func -x - f1() { zgetopt -A '' -o a: -- "$@"; : }; f2() { f1 "$@" }; f2 -x -0:automatic name -?(eval): bad option: -x -?(eval): missing argument for option: -a -?(anon): bad option: -x -?func: bad option: -x -?f1: bad option: -x - - zgetopt -n aaa -A '' -o a: -- -x - zgetopt -n aaa -A '' -o a: -- -a - () { zgetopt -n bbb -A '' -o a: -- "$@"; : } -x - func() { zgetopt -n ccc -A '' -o a: -- "$@"; : }; func -x - f1() { zgetopt -n ddd -A '' -o a: -- "$@"; : }; f2() { f1 "$@" }; f2 -x -0:manual name with -n -?aaa: bad option: -x -?aaa: missing argument for option: -a -?bbb: bad option: -x -?ccc: bad option: -x -?ddd: bad option: -x -- cgit v1.2.3