about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog4
-rw-r--r--Completion/Base/Utility/_shadow66
-rw-r--r--Doc/Zsh/compsys.yo53
-rw-r--r--Doc/Zsh/contrib.yo21
-rw-r--r--Functions/Misc/mkshadow11
5 files changed, 112 insertions, 43 deletions
diff --git a/ChangeLog b/ChangeLog
index bb6afe127..412dbda61 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2023-08-27  Bart Schaefer  <schaefer@zsh.org>
 
+	* 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 "$@"