Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: [PATCH] Add execute-command() widget function (was Re: [RFC][PATCH] Add change-directory() widget function)



New patch attached.

(Also, I switched from macOS's Mail app back again to Gmail's web app. Let's see how that goes for the line wrapping. I'm unable to reproduce it when reading back my own emails in either app. I don't have to do any horizontal scrolling when reading them.)

On Fri, Apr 23, 2021 at 2:27 AM Bart Schaefer <schaefer@xxxxxxxxxxxxxxxx> wrote:
On Thu, Apr 22, 2021 at 3:55 AM Marlon Richert <marlon.richert@xxxxxxxxx> wrote:
>
> If I would use push-input instead of push-line-or-edit, the buffer would not get restored immediately after using the widget. You’d end up with a blank command line instead. I want it to work the same from PS2 as it does from PS1.

I see.  (Also, I forgot that push-input is implemented via
push-line-or-edit, not the other way around.)

Then how about:

execute-command() {
  local finish="zle .reset-prompt"
  case $CONTEXT in
  (cont) finish="zle .push-line-or-edit"
    ;&
  (start)
    print -S "${${(q-)@}}"
    eval "${(q-)@}"
    ;;
  (*) return 75
    ;;
  esac
  eval "$finish"
}

I tried a similar approach with `eval` before, but that doesn't play nice with all prompts, as I outlined in workers/48622:

On Tue, Apr 20, 2021 at 4:36 PM Marlon Richert <marlon.richert@xxxxxxxxx> wrote:
There was some discussion as to whether this should or shouldn’t leave the executed commands visible in the terminal and/or history. I tried a version of it that doesn’t put the commands in the buffer but executes them directly and then does zle .reset-prompt, but this can leave the prompt in an incorrect state if the prompt uses precmd hooks. I also tried “manually” running precmd_functions before zle .reset-prompt, but this causes problems when any precmd hook contains code that doesn’t work when executed inside a widget (for example, echoing an escape code that result in a response from the terminal). Therefore, I instead settled on pushing the current line, setting the buffer, and then accepting the line. This seems to me the least likely to break anything, but I’m open to alternatives.

From 9845e9d84366c7731f6e0c13c8b661e31616e7d0 Mon Sep 17 00:00:00 2001
From: Marlon Richert <marlon.richert@xxxxxxxxx>
Date: Sat, 24 Apr 2021 22:59:27 +0300
Subject: [PATCH] Add execute-command() widget function

---
 Doc/Zsh/contrib.yo            | 43 ++++++++++++++++++++++++++++
 Functions/Zle/execute-command | 54 +++++++++++++++++++++++++++++++++++
 2 files changed, 97 insertions(+)
 create mode 100644 Functions/Zle/execute-command

diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index 8bf1a208e..8142b8418 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -2502,6 +2502,49 @@ arguments:
 
 example(zstyle :zle:edit-command-line editor gvim -f)
 )
+tindex(execute-command)
+tindex(cd-upward)
+tindex(cd-backward)
+tindex(cd-forward)
+item(tt(execute-command))(
+This function helps you implement widgets that execute a specified command
+(passed to the function as a single string argument) without losing the current
+command line, in a fashion similar to the tt(run-help) and tt(which-command) 
+widgets (see
+ifzman(the subsection bf(Miscellaneous) in zmanref(zshzle))\
+ifnzman(noderef(ZLE widgets standard Miscellaneous))). The function does this
+as follows:
+enumeration(
+eit() Push the buffer onto the buffer stack.
+eit() Execute the supplied argument string.
+eit() Let the ZLE pop the buffer off the top of the buffer stack and load it 
+  into the editing buffer.
+)
+
+You can use this, for example, to create key bindings that let you instantly
+change directories, even while in the middle of typing another command:
+
+example(autoload -Uz execute-command
+zle -N cd-parent; cd-parent+LPAR()+RPAR() {
+  execute-command -- 'cd ..'
+}
+zle -N pushd-prev; pushd-prev+LPAR()+RPAR() {
+  local sign='+'; [[ -o pushdminus ]] && sign='-'
+  execute-command -- "pushd ${sign}1 > /dev/null"
+}
+zle -N pushd-next; pushd-next+LPAR()+RPAR() { 
+  local sign='-'; [[ -o pushdminus ]] && sign='+'
+  execute-command -- "pushd ${sign}0 > /dev/null" 
+}
+bindkey  "^[$terminfo[cuu1]" cd-parent  # Alt-Up in raw mode
+bindkey "^[$terminfo[kcuu1]" cd-parent  # Alt-Up in app mode
+bindkey                '^[-' pushd-prev # Alt-Minus
+bindkey                '^[=' pushd-next # Alt-Equals)
+
+Note that widgets created with this function cannot be used inside a tt(select)
+loop or tt(vared). Under those circumstances, the function does nothing and
+returns non-zero.
+)
 tindex(expand-absolute-path)
 item(tt(expand-absolute-path))(
 Expand the file name under the cursor to an absolute path, resolving
diff --git a/Functions/Zle/execute-command b/Functions/Zle/execute-command
new file mode 100644
index 000000000..4014fa854
--- /dev/null
+++ b/Functions/Zle/execute-command
@@ -0,0 +1,54 @@
+# Lets you implement widgets that can execute arbitrary commands without losing
+# the current command line, in a fashion similar to 'run-help' and
+# 'which-command' widgets. See the manual for examples.
+
+zmodload -F zsh/zutil b:zparseopts
+autoload -Uz add-zle-hook-widget
+
+case $CONTEXT in
+  ( start ) # PS1
+    ;;
+  ( cont )  # PS2
+    # Add a one-time hook that will re-run this widget at the top-level prompt.
+    local hook=line-init
+    local func=${(q):-:${(%):-%N}:$hook:$WIDGET}
+    eval "$func() {
+      # Make sure we don't run twice.
+      add-zle-hook-widget -d $hook $func
+
+      # Don't leave anything behind.
+      zle -D $func
+      unfunction $func
+
+      # Use -w to ensure \$WIDGET is set to our original widget, not the hook.
+      # This doesn't matter at present, but might matter in future or if this
+      # code gets copy-pasted elsewhere.
+      zle ${(q)WIDGET} -w
+    }"
+    add-zle-hook-widget $hook ${(Q)func}
+
+    # Move the entire current multiline construct into the editor buffer. This
+    # function is then aborted and we return to the top-level prompt, which
+    # triggers the hook above.
+    # We don't use .push-input here, because that would result in a blank
+    # buffer afterwards.
+    zle .push-line-or-edit
+    return  # Command flow never actually gets here. See above.
+    ;;
+  ( * )
+    # We don't want this to be used in a select loop or in vared:
+    # * At a select prompt, the command wouldn't be "executed"; it'd be fed to
+    #   select as the value of the selection.
+    # * In vared, it would replace the contents of the variable with the
+    #   command string and then exit vared.
+    return 75 # EX_TEMPFAIL; see `man 3 sysexits`.
+    ;;
+esac
+
+# Push the current buffer onto the buffer stack and clear the buffer. The ZLE
+# will auto-restore it at the next top-level prompt.
+zle .push-line
+
+zparseopts -D - # Remove - or -- argument.
+BUFFER="$1"
+zle .accept-line
-- 
2.31.1



Messages sorted by: Reverse Date, Date, Thread, Author