zsh-workers
 help / color / mirror / code / Atom feed
From: Bart Schaefer <schaefer@brasslantern.com>
To: zsh-workers@zsh.org
Subject: Re: [BUG] With CORRECT_ALL, an interrupted correct puts a truncated entry in history
Date: Sun, 17 Dec 2023 11:37:12 -0800	[thread overview]
Message-ID: <CAH+w=7bLFPJX+sX+P2uzWferX2EhZxxcVqe94w2_Bz25dOrKvQ@mail.gmail.com> (raw)
In-Reply-To: <20231217135407.GA3237@zira.vinc17.org>

[-- Attachment #1: Type: text/plain, Size: 2932 bytes --]

On Sun, Dec 17, 2023 at 5:54 AM Vincent Lefevre <vincent@vinc17.net> wrote:
>
> On 2023-12-09 13:44:42 -0800, Bart Schaefer wrote:
> > Ah.  It's in the buffer managed in ZLE as $BUFFER, but corrections
> > apply left to right as that buffer is converted into a parse tree,
> > they don't alter the buffer itself.
>
> But if one does a first correction, for the second proposed
> correction, one types 'e' to edit, one gets the buffer with
> the first correction applied.

When you type 'e', correction (and alias expansion!) is temporarily
disabled, the parser is allowed to run to the end, then the parsed
result is reassembled and pushed onto the editor stack (like "print
-z") and zle restarts.

Watch what happens if you type 'e' for a correction at the PS2 prompt.
The parser hasn't reached the end yet, so you get back a PS2 with
empty input, and that will keep happening until a full valid statement
is entered or you interrupt, and then you get back the entire input so
far.

This is another reason 'a' doesn't throw away the history -- you could
lose many lines of typing above the point of canceling the correction.

> > > Or just use 'e', then immediately put the command in the history
> > > without running it. Is there a zle widget for that? I would find
> > > this useful even when there are no spelling corrections.
> >
> > Correction is not itself a ZLE action -- it happens after ZLE has
> > returned control to the parser, and works even if ZLE is disabled.  So
> > there's no widget to fiddle with it.
>
> But what matters to that after 'e', one is in ZLE.

You asked if there was a widget to immediately put the command in the
history after 'e'.  There's not a widget to do all of that, because
there's not a widget triggered on 'e'.

That said ... attached is an actual ZLE implementation of correctall.
I'm not entirely happy with it yet -- problems are:
 * the tricks using "zstyle -e" are rather fragile
 * _path_files alters the leading path and then offers only to correct
the file name at the end ...
 * ... which makes it a pain to prompt correctly for the 'n' action
 * quoted words muddy things.
Features of interest:
 * uses completion, so corrections are context-sensitive rather than
simple file globs
 * offers a menu for multiple corrections (which muddies what 'y'
should do, unfortunately)
 * 'u' (undo) backs out if you TAB too often at that menu
 * 'a' does in fact discard the current line from the history, but at
PS2 it keeps what went before
 * '!' skips all remaining corrections and runs the command

The patch includes a fix for an error I kept encountering in
_approximate ... I think this but happens when the "fake" style is
used, but haven't run it to ground other than working around it.

I will not be pushing this in its current state, use at your own risk
(which would be true even if it were pushed, I suppose).

[-- Attachment #2: correct-all-words.txt --]
[-- Type: text/plain, Size: 6421 bytes --]

diff --git a/Completion/Base/Completer/_approximate b/Completion/Base/Completer/_approximate
index 96860b5a7..3e19621d2 100644
--- a/Completion/Base/Completer/_approximate
+++ b/Completion/Base/Completer/_approximate
@@ -63,7 +63,7 @@ compadd() {
     PREFIX="(#a${_comp_correct})$PREFIX"
   fi
 
-  (( $_correct_group && ${${argv[1,(r)-(|-)]}[(I)-*[JV]]} )) &&
+  (( ${_correct_group:-0} && ${${argv[1,(r)-(|-)]}[(I)-*[JV]]} )) &&
       _correct_expl[_correct_group]=${argv[1,(r)-(-|)][(R)-*[JV]]}
 
   compadd@_approximate "$_correct_expl[@]" "$@"
diff --git a/Functions/Zle/correct-all-words b/Functions/Zle/correct-all-words
new file mode 100644
index 000000000..bef262472
--- /dev/null
+++ b/Functions/Zle/correct-all-words
@@ -0,0 +1,161 @@
+#autoload
+# Intended to be called during accept-line or in zle-line-finish, but can
+# be called by any widget to apply correction to all words in $BUFFER or
+# used as a widget itself
+
+# Compare modify-current-argument
+
+setopt localoptions noksharrays multibyte norecexact
+zmodload zsh/complist || return 1
+
+local -a reply cmdline
+local key REPLY REPLY2 MENUSELECT
+integer pos posword poschar
+unset MENUSELECT	# Bug with no-select plus yes=2 below
+
+local curcontext="${curcontext:-}"
+local widget="correct-${${WIDGET/correct-all-words/}:-all-words}"
+if [[ -z "$curcontext" ]]; then
+  curcontext="${widget}:::"
+else
+  curcontext="${widget}:${curcontext#*:}"
+fi
+local mycontext="${curcontext}"
+
+# This breaks out of read-from-minibuffer if only the original matches
+local shown_original
+zstyle -e ":completion:${widget}:*" original \
+       'if (( compstate[nmatches] == 0 )); \
+       then reply=(false);
+       elif [[ ${compstate[unambiguous]} = ${key} ]]; \
+       then shown_original=unambiguous; zle -U n; reply=(false); \
+       elif [[ -z ${shown_original} ]];
+       then shown_original=original; reply=(true);
+       else reply=(false); fi'
+
+# Would be nice to make these conditional, but it's hard to test for
+# these specifically if a more general context has the style defined
+zstyle ":completion:${widget}:*" menu no-select yes=2
+zstyle ":completion:${widget}:*" group-name ''
+zstyle ":completion:${widget}:*" group-order original corrections
+zstyle ":completion:${widget}:*" show-ambiguity true
+zstyle ":completion:${widget}:*" show-completer true
+zstyle ":completion:${widget}:*" accept-exact false
+
+# Keep completion functions out of the results
+zstyle ":completion:${widget}:*" ignored-patterns '_*'
+
+# This shows earliest the words with the fewest necessary corrections
+zstyle ":completion:${widget}:*" sort false
+
+# Overload fake description to produce a prompt for multiple corrections
+zstyle -e ":completion:${widget}:*:original" fake \
+	 'if (( compstate[nmatches] > 1 )) && \
+	     [[ -z ${shown_original} ]] ; \
+	 then bindkey -M correctall y _correct_word; \
+	 shown_original=fake-original; \
+	 reply=("${key//:/\\:}:TAB to choose, ENTER to accept, n to skip"); \
+	 fi'
+zstyle -e ":completion:${widget}:*:corrections" fake \
+	 'if [[ ${shown_original} = wanted ]] || \
+	     ( (( compstate[nmatches] > 0 )) && \
+	       [[ -z ${shown_original} ]] ); \
+	 then bindkey -M correctall y _correct_word; \
+	 shown_original=fake-corrections; \
+	 reply+=("${key//:/\\:}:TAB to choose, ENTER to accept, n to skip"); \
+	 fi'
+
+# There's no good semantics for 'y' when there are multiple possible
+# corrections.  Left as $'\t\n' it'll skip ahead after a correction is
+# chosen from the list.  Changed to .accept-line it becomes equivalent
+# to 'n' if no choice has been made yet.  The above makes it cycle the
+# menu, but maybe it would be better just to have it do nothing?
+
+autoload -Uz split-shell-arguments read-from-minibuffer mkshadow
+
+split-shell-arguments
+[[ ${#reply} -lt 2 ]] && return 1
+(( posword = REPLY, poschar = REPLY2 ))
+cmdline=("${reply[@]}")		# In case something else uses $reply ...
+
+bindkey -N correctall
+bindkey -M correctall $'\t' _correct_word
+bindkey -M correctall ' ' _correct_word
+for key in n a e \! $'\n' $'\r'
+do
+  bindkey -M correctall $key .accept-line
+done
+bindkey -M correctall u .undo
+bindkey -M correctall '^_' .undo
+bindkey -M correctall -s '^G' e
+bindkey -M correctall -s '^U' a		# Should copy from main XXX
+bindkey -M correctall -s y $'\t\n'
+
+# Work around a bug with recursive-edit from zle-line-finish
+[[ $WIDGET = zle-line-finish ]] && zle recursive-edit	# Fails
+
+local lmini rmini
+{
+  mkshadow -s all-words _original_file _correct_word
+
+  # Force unedited original to precede _path_files additions.
+  # Otherwise the "original" style above handles this.
+  function _original_file {
+    [[ ${key} = */* ]] && shown_original=wanted
+    return 1	# Force call to _correct
+  }
+
+  # This is to avoid having _correct_word stomp on $curcontext,
+  # plus break out of read-from-minibuffer when nothing matches
+  function _correct_word {
+    local shown_original ret REPLY
+    _main_complete _original_file _correct
+    ret=$?
+    [[ ${compstate[nmatches]} -eq 0 ]] && zle -U n
+    return ret
+  }
+
+  # "Real" words from reply[2], reply[4], etc., see split-shell-arguments
+  for pos in {2..${#cmdline}..2}
+  do
+    # Don't try to correct numbers and non-syntax punctuation
+    [[ ${cmdline[pos]} = *[A-Za-z]* ]] || continue
+    
+    {
+      key=${cmdline[pos]}
+      curcontext="${mycontext}"
+
+      # Can't use -K KEYMAP because read-from-minibuffer always resets
+      bindkey -A main llatcerroc
+      bindkey -A correctall main
+
+      # lmini="${PREBUFFER}${(j::)cmdline[1,pos-1]}"	# Too much?
+      lmini=${(j::)cmdline[1,pos-1]}
+      rmini=${(j::)cmdline[pos+1,-1]}
+
+      zle -U $'\t'	# Start minibuffer in completion
+      read-from-minibuffer "Correct $key [nyae!]: " "${lmini}${key}" "${rmini}"
+      REPLY=${${REPLY#${lmini}}%${rmini}}
+    } always { bindkey -A llatcerroc main }
+    case ${KEYS} in
+      (a) zle send-break; break;;
+      (n) continue;;
+      (y|$'\n'|$'\r') cmdline[pos]=${REPLY:-${cmdline[pos]}};;
+      (e) [[ ${WIDGET} = zle-line-finish ]] && {
+	    print -z "${(j::)cmdline}"
+	    cmdline=()
+	    (( posword = 0, poschar = 0 ))
+	  }
+	  ;&
+      (*) break;;
+    esac
+  done
+} always {
+  rmshadow
+  zle -R -c
+}
+
+BUFFER="${(j::)cmdline}"
+CURSOR=$(( ${#${(j::)cmdline[1,posword-1]}} + poschar ))
+
+[[ "${KEYS}" = \! ]] && zle .accept-line

  reply	other threads:[~2023-12-18 19:06 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-12-05  2:12 Vincent Lefevre
2023-12-05  4:10 ` Bart Schaefer
2023-12-05 12:57   ` Vincent Lefevre
2023-12-05 21:22     ` Bart Schaefer
2023-12-06 15:03       ` Vincent Lefevre
2023-12-09 21:44         ` Bart Schaefer
2023-12-17 13:54           ` Vincent Lefevre
2023-12-17 19:37             ` Bart Schaefer [this message]
2023-12-18  3:37               ` Vincent Lefevre
2023-12-18  6:36                 ` Bart Schaefer
2024-01-21  5:06     ` Bart Schaefer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='CAH+w=7bLFPJX+sX+P2uzWferX2EhZxxcVqe94w2_Bz25dOrKvQ@mail.gmail.com' \
    --to=schaefer@brasslantern.com \
    --cc=zsh-workers@zsh.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).