zsh-workers
 help / color / mirror / code / Atom feed
* Editing the history in the shell
@ 2020-05-22 22:24 Bart Schaefer
  2020-05-23  2:28 ` Daniel Shahaf
  0 siblings, 1 reply; 10+ messages in thread
From: Bart Schaefer @ 2020-05-22 22:24 UTC (permalink / raw)
  To: Zsh hackers list

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

This is not quite a finished product yet (for one thing, no doc) but
it demonstrates most of the interesting ideas.

The patch implements "zed -h" which loads the history into vared and
arranges to replace the existing history with that before it exits.
This does NOT solve Markus' complaint about the history numbers, they
increase by the number of entries output by the editor. It makes no
attempt to account for various history options, so those may change
the final result if they affect "print -s".

It also handles "zed -h filename" which loads that file and saves it
back again rather than alter the current shell history.  There is a
glitch currently that may lose the most recent line from the file when
it is loaded (not when it is saved), which seems to be the fault of
"fc -p".  There are probably other bugs, I've tested only minimally.

Some unanswered questions:
1) Should it be possible to edit empty events into the history?
Currently empty lines are removed, but changing to IFS=$'\n\n' would
preserve them (thanks Oliver for the idea)
2) Should "vared -h" be used?  I'm inclined to say "no" because the
behavior becomes a bit strange; the entire content of the variable is
treated as one "history event" when navigating, i.e., if you move up
from the top (oldest) history event, the whole history disappears and
is replaced by the single newest event.
3) If the file for "zed -h filename" does not exist, should the editor
be populated from the current shell history?  Currently the editor
starts empty.

Patch both inlined and attached in case gmail garbles the lines.  I
followed the existing coding style of "zed", which is a little quirky.

diff --git a/Functions/Misc/zed b/Functions/Misc/zed
index 9eb4b2d..952e1a9 100644
--- a/Functions/Misc/zed
+++ b/Functions/Misc/zed
@@ -5,16 +5,18 @@
 # Edit small files with the command line editor.
 # Use ^X^W to save (or ZZ in vicmd mode), ^C to abort.
 # Option -f: edit shell functions.  (Also if called as fned.)
+# Option -h: edit shell history.  (Also if called as histed.)

 setopt localoptions noksharrays

 local var opts zed_file_name
 # We do not want timeout while we are editing a file
-integer TMOUT=0 okargs=1 fun bind
+integer TMOUT=0 okargs=1 fun hist bind
 local -a expand

-zparseopts -D -A opts f b x:
+zparseopts -D -A opts f h b x:
 fun=$+opts[-f]
+hist=$+opts[-h]
 bind=$+opts[-b]
 if [[ $opts[-x] == <-> ]]; then
   expand=(-x $opts[-x])
@@ -24,12 +26,15 @@ elif (( $+opts[-x] )); then
 fi

 [[ $0 = fned ]] && fun=1
+[[ $0 = histed ]] && hist=1
 (( bind )) && okargs=0
+(( hist && $# <= 2 )) && okargs=$#

-if (( $# != okargs )); then
+if (( $# != okargs || fun + hist > 1 )); then
     echo 'Usage:
 zed filename
 zed -f [ -x N ] function
+zed -h [ filename [ size ] ]
 zed -b' >&2
     return 1
 fi
@@ -133,6 +138,17 @@ if ((fun)) then
 }"
   fi
   vared -M zed -m zed-vicmd -i __zed_init var && eval function "$var"
+elif ((hist)) then
+  [[ -n $1 ]] && { fc -p -a $1 ${2:-$({ wc -l <$1 } 2>/dev/null)} || return }
+  var=( "${(@Oav)history:gs/\\/\\\\}" )
+  IFS=$'\n' vared -M zed -m zed-vicmd -i __zed_init var
+  if (( ? )); then
+    [[ -n $1 ]] && unset HISTFILE
+  else
+    hist=$HISTSIZE; HISTSIZE=0; fc -R /dev/null; HISTSIZE=$hist
+    for (( hist=1; hist <= $#var; hist++ )) print -s "$var[hist]"
+    [[ -n $1 ]] && SAVEHIST=$#var    # Resets on function exit
+  fi
 else
   zed_file_name=$1
   [[ -f $1 ]] && var="$(<$1)"

[-- Attachment #2: zed-h.txt --]
[-- Type: text/plain, Size: 1768 bytes --]

diff --git a/Functions/Misc/zed b/Functions/Misc/zed
index 9eb4b2d..952e1a9 100644
--- a/Functions/Misc/zed
+++ b/Functions/Misc/zed
@@ -5,16 +5,18 @@
 # Edit small files with the command line editor.
 # Use ^X^W to save (or ZZ in vicmd mode), ^C to abort.
 # Option -f: edit shell functions.  (Also if called as fned.)
+# Option -h: edit shell history.  (Also if called as histed.)
 
 setopt localoptions noksharrays
 
 local var opts zed_file_name
 # We do not want timeout while we are editing a file
-integer TMOUT=0 okargs=1 fun bind
+integer TMOUT=0 okargs=1 fun hist bind
 local -a expand
 
-zparseopts -D -A opts f b x:
+zparseopts -D -A opts f h b x:
 fun=$+opts[-f]
+hist=$+opts[-h]
 bind=$+opts[-b]
 if [[ $opts[-x] == <-> ]]; then
   expand=(-x $opts[-x])
@@ -24,12 +26,15 @@ elif (( $+opts[-x] )); then
 fi
 
 [[ $0 = fned ]] && fun=1
+[[ $0 = histed ]] && hist=1
 (( bind )) && okargs=0
+(( hist && $# <= 2 )) && okargs=$#
 
-if (( $# != okargs )); then
+if (( $# != okargs || fun + hist > 1 )); then
     echo 'Usage:
 zed filename
 zed -f [ -x N ] function
+zed -h [ filename [ size ] ]
 zed -b' >&2
     return 1
 fi
@@ -133,6 +138,17 @@ if ((fun)) then
 }"
   fi
   vared -M zed -m zed-vicmd -i __zed_init var && eval function "$var"
+elif ((hist)) then
+  [[ -n $1 ]] && { fc -p -a $1 ${2:-$({ wc -l <$1 } 2>/dev/null)} || return }
+  var=( "${(@Oav)history:gs/\\/\\\\}" )
+  IFS=$'\n' vared -M zed -m zed-vicmd -i __zed_init var
+  if (( ? )); then
+    [[ -n $1 ]] && unset HISTFILE
+  else
+    hist=$HISTSIZE; HISTSIZE=0; fc -R /dev/null; HISTSIZE=$hist
+    for (( hist=1; hist <= $#var; hist++ )) print -s "$var[hist]"
+    [[ -n $1 ]] && SAVEHIST=$#var	# Resets on function exit
+  fi
 else
   zed_file_name=$1
   [[ -f $1 ]] && var="$(<$1)"

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Editing the history in the shell
  2020-05-22 22:24 Editing the history in the shell Bart Schaefer
@ 2020-05-23  2:28 ` Daniel Shahaf
  2020-05-23 21:30   ` Bart Schaefer
  0 siblings, 1 reply; 10+ messages in thread
From: Daniel Shahaf @ 2020-05-23  2:28 UTC (permalink / raw)
  To: Bart Schaefer; +Cc: Zsh hackers list

Bart Schaefer wrote on Fri, 22 May 2020 15:24 -0700:
> 1) Should it be possible to edit empty events into the history?
> Currently empty lines are removed, but changing to IFS=$'\n\n' would
> preserve them (thanks Oliver for the idea)

Hmm.  I guess they might be useful as separators?

> Patch both inlined and attached in case gmail garbles the lines.

It did.

> +++ b/Functions/Misc/zed
> @@ -133,6 +138,17 @@ if ((fun)) then
> +  else
> +    hist=$HISTSIZE; HISTSIZE=0; fc -R /dev/null; HISTSIZE=$hist
> +    for (( hist=1; hist <= $#var; hist++ )) print -s "$var[hist]"

Evaluating «$#var» takes O(N) time, which makes this an O(N²) loop.
Save $#var into an auxiliary variable?

Also, s/print -s/print -s --/.

> +    [[ -n $1 ]] && SAVEHIST=$#var    # Resets on function exit
> +  fi

Cheers,

Daniel

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: Editing the history in the shell
  2020-05-23  2:28 ` Daniel Shahaf
@ 2020-05-23 21:30   ` Bart Schaefer
  2020-05-24 22:02     ` _arguments optspecs (was Re: Editing the history in the shell) Bart Schaefer
       [not found]     ` <20200525023515.7855610a@tarpaulin.shahaf.local2>
  0 siblings, 2 replies; 10+ messages in thread
From: Bart Schaefer @ 2020-05-23 21:30 UTC (permalink / raw)
  To: Zsh hackers list

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

Thanks for the review, Daniel.

On Fri, May 22, 2020 at 7:28 PM Daniel Shahaf <d.s@daniel.shahaf.name> wrote:
>
> Bart Schaefer wrote on Fri, 22 May 2020 15:24 -0700:
> > 1) Should it be possible to edit empty events into the history?
> > Currently empty lines are removed, but changing to IFS=$'\n\n' would
> > preserve them (thanks Oliver for the idea)
>
> Hmm.  I guess they might be useful as separators?

I did not do this yet.

> Evaluating «$#var» takes O(N) time, which makes this an O(N²) loop.
> Save $#var into an auxiliary variable?

Did that.

> Also, s/print -s/print -s --/.

I went all the way to "print -rs --" although -r may not have been necessary.

I believe the following fixes the last-line-lost problem with "fc -p"
and also a problem that you couldn't save more lines to a history file
than were read from it in the first place.  And I updated the _zed
completer, though not yet the doc.  I'm not entirely sure about some
of the _zed optspecs, but all the desired combinations seem to work
and all the undesired ones seem not to.

Inlined and attached, again.

diff --git a/Completion/Zsh/Command/_zed b/Completion/Zsh/Command/_zed
index 6b68fad..cb90dde 100644
--- a/Completion/Zsh/Command/_zed
+++ b/Completion/Zsh/Command/_zed
@@ -1,10 +1,14 @@
-#compdef zed fned
+#compdef zed fned histed

 case $service in
 (fned) _arguments -S : ':shell function:_functions';;
+(histed) _arguments -S : \
+    '1:history file:_files' \
+    '2:history size: ';;
 (zed) _arguments -S : \
     '(- 2):file:_files' \
-    '(1):shell function:_functions' \
-    '(1)-x+[specify spaces to use for indentation in function
expansion]:spaces' \
-    '(1)-f[edit function]';;
+    '(-h 1 2)-f[edit function]:shell function:_functions' \
+    '(-h 1 2)-x+[specify spaces to use for indentation in function
expansion]:spaces' \
+    '(-f -x 1)-h[edit history]:history file:_files' \
+    '2:history size: ';;
 esac
diff --git a/Functions/Misc/zed b/Functions/Misc/zed
index 9eb4b2d..21aa94f 100644
--- a/Functions/Misc/zed
+++ b/Functions/Misc/zed
@@ -5,16 +5,18 @@
 # Edit small files with the command line editor.
 # Use ^X^W to save (or ZZ in vicmd mode), ^C to abort.
 # Option -f: edit shell functions.  (Also if called as fned.)
+# Option -h: edit shell history.  (Also if called as histed.)

 setopt localoptions noksharrays

 local var opts zed_file_name
 # We do not want timeout while we are editing a file
-integer TMOUT=0 okargs=1 fun bind
+integer TMOUT=0 okargs=1 fun hist bind
 local -a expand

-zparseopts -D -A opts f b x:
+zparseopts -D -A opts f h b x:
 fun=$+opts[-f]
+hist=$+opts[-h]
 bind=$+opts[-b]
 if [[ $opts[-x] == <-> ]]; then
   expand=(-x $opts[-x])
@@ -24,23 +26,28 @@ elif (( $+opts[-x] )); then
 fi

 [[ $0 = fned ]] && fun=1
+[[ $0 = histed ]] && hist=1
+(( hist && $# <= 2 )) && okargs=$#
 (( bind )) && okargs=0

-if (( $# != okargs )); then
+if (( $# != okargs || bind + fun + hist > 1 )); then
     echo 'Usage:
 zed filename
 zed -f [ -x N ] function
+zed -h [ filename [ size ] ]
 zed -b' >&2
     return 1
 fi

 local curcontext=zed:::

-# Matching used in zstyle -m: hide result from caller.
-# Variables not used directly here.
-local -a match mbegin mend
-zstyle -m ":completion:zed:*" insert-tab '*' ||
-    zstyle ":completion:zed:*" insert-tab yes
+() {
+    # Matching used in zstyle -m: hide result from caller.
+    # Variables not used directly here.
+    local -a match mbegin mend
+    zstyle -m ":completion:zed:*" insert-tab '*' ||
+    zstyle ":completion:zed:*" insert-tab yes
+}

 zmodload zsh/terminfo 2>/dev/null

@@ -133,6 +140,28 @@ if ((fun)) then
 }"
   fi
   vared -M zed -m zed-vicmd -i __zed_init var && eval function "$var"
+elif ((hist)) then
+  if [[ -n $1 ]]; then
+    { fc -p -a $1 ${2:-$({ wc -l <$1 } 2>/dev/null)} || return }
+    let HISTSIZE++
+    print -s ""        # Work around fc -p limitation
+  fi
+  var=( "${(@Oav)history:gs/\\/\\\\}" )
+  IFS=$'\n' vared -M zed -m zed-vicmd -i __zed_init var
+  if (( ? )); then
+    [[ -n $1 ]] && unset HISTFILE
+  else
+    local savehist=$#var
+    hist=$HISTSIZE; HISTSIZE=0; fc -R /dev/null
+    if [[ -n $1 ]]; then
+      # Resets on function exit
+      HISTSIZE=$savehist
+      SAVEHIST=$savehist
+    else
+      HISTSIZE=$hist
+    fi
+    for (( hist=1; hist <= savehist; hist++ )) print -rs -- "$var[hist]"
+  fi
 else
   zed_file_name=$1
   [[ -f $1 ]] && var="$(<$1)"

[-- Attachment #2: zed-h.txt --]
[-- Type: text/plain, Size: 3298 bytes --]

diff --git a/Completion/Zsh/Command/_zed b/Completion/Zsh/Command/_zed
index 6b68fad..cb90dde 100644
--- a/Completion/Zsh/Command/_zed
+++ b/Completion/Zsh/Command/_zed
@@ -1,10 +1,14 @@
-#compdef zed fned
+#compdef zed fned histed
 
 case $service in
 (fned) _arguments -S : ':shell function:_functions';;
+(histed) _arguments -S : \
+	'1:history file:_files' \
+	'2:history size: ';;
 (zed) _arguments -S : \
 	'(- 2):file:_files' \
-	'(1):shell function:_functions' \
-	'(1)-x+[specify spaces to use for indentation in function expansion]:spaces' \
-	'(1)-f[edit function]';;
+	'(-h 1 2)-f[edit function]:shell function:_functions' \
+	'(-h 1 2)-x+[specify spaces to use for indentation in function expansion]:spaces' \
+	'(-f -x 1)-h[edit history]:history file:_files' \
+	'2:history size: ';;
 esac
diff --git a/Functions/Misc/zed b/Functions/Misc/zed
index 9eb4b2d..21aa94f 100644
--- a/Functions/Misc/zed
+++ b/Functions/Misc/zed
@@ -5,16 +5,18 @@
 # Edit small files with the command line editor.
 # Use ^X^W to save (or ZZ in vicmd mode), ^C to abort.
 # Option -f: edit shell functions.  (Also if called as fned.)
+# Option -h: edit shell history.  (Also if called as histed.)
 
 setopt localoptions noksharrays
 
 local var opts zed_file_name
 # We do not want timeout while we are editing a file
-integer TMOUT=0 okargs=1 fun bind
+integer TMOUT=0 okargs=1 fun hist bind
 local -a expand
 
-zparseopts -D -A opts f b x:
+zparseopts -D -A opts f h b x:
 fun=$+opts[-f]
+hist=$+opts[-h]
 bind=$+opts[-b]
 if [[ $opts[-x] == <-> ]]; then
   expand=(-x $opts[-x])
@@ -24,23 +26,28 @@ elif (( $+opts[-x] )); then
 fi
 
 [[ $0 = fned ]] && fun=1
+[[ $0 = histed ]] && hist=1
+(( hist && $# <= 2 )) && okargs=$#
 (( bind )) && okargs=0
 
-if (( $# != okargs )); then
+if (( $# != okargs || bind + fun + hist > 1 )); then
     echo 'Usage:
 zed filename
 zed -f [ -x N ] function
+zed -h [ filename [ size ] ]
 zed -b' >&2
     return 1
 fi
 
 local curcontext=zed:::
 
-# Matching used in zstyle -m: hide result from caller.
-# Variables not used directly here.
-local -a match mbegin mend
-zstyle -m ":completion:zed:*" insert-tab '*' ||
-    zstyle ":completion:zed:*" insert-tab yes
+() {
+    # Matching used in zstyle -m: hide result from caller.
+    # Variables not used directly here.
+    local -a match mbegin mend
+    zstyle -m ":completion:zed:*" insert-tab '*' ||
+	zstyle ":completion:zed:*" insert-tab yes
+}
 
 zmodload zsh/terminfo 2>/dev/null
 
@@ -133,6 +140,28 @@ if ((fun)) then
 }"
   fi
   vared -M zed -m zed-vicmd -i __zed_init var && eval function "$var"
+elif ((hist)) then
+  if [[ -n $1 ]]; then
+    { fc -p -a $1 ${2:-$({ wc -l <$1 } 2>/dev/null)} || return }
+    let HISTSIZE++  
+    print -s ""		# Work around fc -p limitation
+  fi
+  var=( "${(@Oav)history:gs/\\/\\\\}" )
+  IFS=$'\n' vared -M zed -m zed-vicmd -i __zed_init var
+  if (( ? )); then
+    [[ -n $1 ]] && unset HISTFILE
+  else
+    local savehist=$#var
+    hist=$HISTSIZE; HISTSIZE=0; fc -R /dev/null
+    if [[ -n $1 ]]; then
+      # Resets on function exit
+      HISTSIZE=$savehist
+      SAVEHIST=$savehist
+    else
+      HISTSIZE=$hist
+    fi
+    for (( hist=1; hist <= savehist; hist++ )) print -rs -- "$var[hist]"
+  fi
 else
   zed_file_name=$1
   [[ -f $1 ]] && var="$(<$1)"

^ permalink raw reply	[flat|nested] 10+ messages in thread

* _arguments optspecs (was Re: Editing the history in the shell)
  2020-05-23 21:30   ` Bart Schaefer
@ 2020-05-24 22:02     ` Bart Schaefer
  2020-05-25 17:29       ` Oliver Kiddle
       [not found]     ` <20200525023515.7855610a@tarpaulin.shahaf.local2>
  1 sibling, 1 reply; 10+ messages in thread
From: Bart Schaefer @ 2020-05-24 22:02 UTC (permalink / raw)
  To: Zsh hackers list

On Sat, May 23, 2020 at 2:30 PM Bart Schaefer <schaefer@brasslantern.com> wrote:
>
> I'm not entirely sure about some
> of the _zed optspecs, but all the desired combinations seem to work
> and all the undesired ones seem not to.

My uncertainty is around these three lines:

>      '(- 2):file:_files' \
> +    '(-f -x 1)-h[edit history]:history file:_files' \
> +    '2:history size: ';;

The first spec causes "file" to be offered (as the first non-option
argument)  if there is no option yet, and will not offer a second
argument.

The second spec offers -h followed by "history file" as the argument
to -h, and won't offer -f or -x or a first non-option argument.

The third spec expects "history size" as the second non-option
argument, but does not provide any completions.

What's strange is why "history size" is the second non-option
argument.  If I leave (1) out of the exclusion list on the second
spec, I get offered "file", so in that case the argument to -h is
being taken as an option argument; but after filling in that position
I next get "no more arguments" instead of "history files".  So it
appears that sometimes the argument of -h is not counted as a
non-option argument, and other times it is so counted, which is why
it's "2:" in the third optspec and the (1) is required in the second
optspec.

Does anybody actually understand this?  Is it explainable (or already
explained, where?) in the doc for _arguments?

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: _arguments optspecs (was Re: Editing the history in the shell)
  2020-05-24 22:02     ` _arguments optspecs (was Re: Editing the history in the shell) Bart Schaefer
@ 2020-05-25 17:29       ` Oliver Kiddle
  2020-05-25 22:32         ` Bart Schaefer
  2020-05-25 22:42         ` Bart Schaefer
  0 siblings, 2 replies; 10+ messages in thread
From: Oliver Kiddle @ 2020-05-25 17:29 UTC (permalink / raw)
  To: Bart Schaefer; +Cc: Zsh hackers list

Bart wrote:
> On Sat, May 23, 2020 at 2:30 PM Bart Schaefer <schaefer@brasslantern.com> wrote:
> >
> > I'm not entirely sure about some
> > of the _zed optspecs, but all the desired combinations seem to work
> > and all the undesired ones seem not to.

How about the following combination:

zed -f -x2 -- <tab>

> My uncertainty is around these three lines:
>
> >      '(- 2):file:_files' \
> > +    '(-f -x 1)-h[edit history]:history file:_files' \
> > +    '2:history size: ';;
>
> The first spec causes "file" to be offered (as the first non-option
> argument)  if there is no option yet, and will not offer a second
> argument.

The first thing to note is that if you leave the number out as in the
first spec, it will assign it the next number. So the 1 in the exclusion
list for -h does apply to the first spec. I try to avoid leaving the
number out for any spec that is the target of an exclusion.

> The second spec offers -h followed by "history file" as the argument
> to -h, and won't offer -f or -x or a first non-option argument.

Yes. And in excluding the first non-option argument, it will move on and
offer the second one in the first position. So you get "history size" in
position 1 despite it being labelled as 2.

> What's strange is why "history size" is the second non-option
> argument.  If I leave (1) out of the exclusion list on the second
> spec, I get offered "file", so in that case the argument to -h is
> being taken as an option argument; but after filling in that position
> I next get "no more arguments" instead of "history files".  So it
> appears that sometimes the argument of -h is not counted as a
> non-option argument, and other times it is so counted, which is why
> it's "2:" in the third optspec and the (1) is required in the second
> optspec.

I can't find any arrangement of words on a command-line that gives the
argument after -h as being anything other than "history file". Neither
is it being counted as one of the non-option arguments.

> Does anybody actually understand this?  Is it explainable (or already
> explained, where?) in the doc for _arguments?

It seems clear enough to me but I know enough of the implementation's
workings to be approaching it from that angle. Which may not mean I'm
best able to explain it.

There's a list of possible states. In each state, individual specs
are disabled if an option is found which would disable it. If you
exclude positional arguments, subsequent ones become active at an
earlier position. So you can exclude lots of them and there is always
something to complete. Having gaps is fairly useless as is apparent if
you try: compdef "_arguments '3:three'" foo
This approach may not be the most readable but I think it works:

	'(-h 1 3 4)-f[edit function]' \
	'(-h 1 3 4)-x+[specify spaces to use for indentation in function expansion]:spaces' \
	'(-f -x 1 2)-h[edit history]' \
	'(- 2 3 4)1:file:_files' \
	'(3 4)2:shell function:_functions' \
	'3:history file:_files' \
	'4:history size';;

Oliver

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: _arguments optspecs (was Re: Editing the history in the shell)
  2020-05-25 17:29       ` Oliver Kiddle
@ 2020-05-25 22:32         ` Bart Schaefer
  2020-05-26  7:56           ` Oliver Kiddle
  2020-05-25 22:42         ` Bart Schaefer
  1 sibling, 1 reply; 10+ messages in thread
From: Bart Schaefer @ 2020-05-25 22:32 UTC (permalink / raw)
  To: Oliver Kiddle; +Cc: Zsh hackers list

(Thanks for copying me, that got around gmail's blocking list-forwards
of yahoo.)

On Mon, May 25, 2020 at 10:29 AM Oliver Kiddle <okiddle@yahoo.co.uk> wrote:
>
> > On Sat, May 23, 2020 at 2:30 PM Bart Schaefer <schaefer@brasslantern.com> wrote:
> > >
> > > all the desired combinations seem to work
> > > and all the undesired ones seem not to.
>
> How about the following combination:
>
> zed -f -x2 -- <tab>

Ah, I didn't notice that

zed -f _zed -x2

is not accepted by zed itself.

> > My uncertainty is around these three lines:
> >
> > >      '(- 2):file:_files' \
> > > +    '(-f -x 1)-h[edit history]:history file:_files' \
> > > +    '2:history size: ';;
>
> The first thing to note is that if you leave the number out as in the
> first spec, it will assign it the next number. So the 1 in the exclusion
> list for -h does apply to the first spec. I try to avoid leaving the
> number out for any spec that is the target of an exclusion.

Do I understand, then, that you would have written the original
        '(- 2):file:_files' \
        '(1):shell function:_functions'
as
        '(- 2)1:file:_files' \
        '(1)2:shell function:_functions'
??  I can see where that might have puzzled me less.

> > The second spec offers -h followed by "history file" as the argument
> > to -h, and won't offer -f or -x or a first non-option argument.
>
> Yes. And in excluding the first non-option argument, it will move on and
> offer the second one in the first position. So you get "history size" in
> position 1 despite it being labelled as 2.

OK, that explains my confusion then.  Why does it do that?  If I
wanted to indicate that _arguments was NOT meant to "move on and offer
the second one in the first position", would I have to write
        '(1)2:history file: '
??  Or is there a way to say "exactly the Nth position" without also
specifying the (N-1)th position?

> > appears that sometimes the argument of -h is not counted as a
> > non-option argument, and other times it is so counted, which is why
> > it's "2:" in the third optspec and the (1) is required in the second
> > optspec.
>
> I can't find any arrangement of words on a command-line that gives the
> argument after -h as being anything other than "history file". Neither
> is it being counted as one of the non-option arguments.

"count the argument after -h as a non-option argument" and "offer the
second one in the first position" have equivalent results from the
point of view of a black-box test.  I just didn't know how to explain
what I was seeing.

> There's a list of possible states. In each state, individual specs
> are disabled if an option is found which would disable it. If you
> exclude positional arguments, subsequent ones become active at an
> earlier position.

Is that "subsequent" part stated in the doc anywhere that you can point to?

>         '(-h 1 3 4)-f[edit function]' \
>         '(-h 1 3 4)-x+[specify spaces to use for indentation in function expansion]:spaces' \
>         '(-f -x 1 2)-h[edit history]' \
>         '(- 2 3 4)1:file:_files' \
>         '(3 4)2:shell function:_functions' \
>         '3:history file:_files' \
>         '4:history size';;

I would never have thought of that, because without the implicit
"become active earlier" there is no third or fourth position.

Perhaps the way to explain/understand this is that "position" here
does not mean "position on the command line", rather it means "order
in which to attempt to fill the next available position on the command
line".

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: _arguments optspecs (was Re: Editing the history in the shell)
  2020-05-25 17:29       ` Oliver Kiddle
  2020-05-25 22:32         ` Bart Schaefer
@ 2020-05-25 22:42         ` Bart Schaefer
  1 sibling, 0 replies; 10+ messages in thread
From: Bart Schaefer @ 2020-05-25 22:42 UTC (permalink / raw)
  To: Oliver Kiddle; +Cc: Zsh hackers list

One other thing ...

On Mon, May 25, 2020 at 10:29 AM Oliver Kiddle <okiddle@yahoo.co.uk> wrote:
>
>         '4:history size';;

Doc says:

     The forms for ACTION are as follows.

      (single unquoted space)
          This is useful where an argument is required but it is not
          possible or desirable to generate matches for it.  The MESSAGE
          will be displayed but no completions listed.  Note that even
          in this case the colon at the end of the MESSAGE is needed; it
          may only be omitted when neither a MESSAGE nor an ACTION is
          given.

That seems to imply that you can write "4:" or "4:history size: " but
not what you did write.  Is the doc wrong?

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: _arguments optspecs (was Re: Editing the history in the shell)
  2020-05-25 22:32         ` Bart Schaefer
@ 2020-05-26  7:56           ` Oliver Kiddle
  0 siblings, 0 replies; 10+ messages in thread
From: Oliver Kiddle @ 2020-05-26  7:56 UTC (permalink / raw)
  To: Bart Schaefer; +Cc: Zsh hackers list

Bart Schaefer wrote:
> zed -f _zed -x2
>
> is not accepted by zed itself.

Yes, -f changes the interpretation of the normal arguments rather than
taking an argument itself. I assume that's also true for the new -h.

> > ... I try to avoid leaving the
> > number out for any spec that is the target of an exclusion.
>
> Do I understand, then, that you would have written the original
>         '(- 2):file:_files' \
>         '(1):shell function:_functions'
> as
>         '(- 2)1:file:_files' \
>         '(1)2:shell function:_functions'
> ??  I can see where that might have puzzled me less.

Yes, exactly.

> > > The second spec offers -h followed by "history file" as the argument
> > > to -h, and won't offer -f or -x or a first non-option argument.
> >
> > Yes. And in excluding the first non-option argument, it will move on and
> > offer the second one in the first position. So you get "history size" in
> > position 1 despite it being labelled as 2.
>
> OK, that explains my confusion then.  Why does it do that?  If I
> wanted to indicate that _arguments was NOT meant to "move on and offer
> the second one in the first position", would I have to write
>         '(1)2:history file: '
> ??  Or is there a way to say "exactly the Nth position" without also
> specifying the (N-1)th position?

I don't think that's possible. And in practical reality, I don't think
it would be especially useful. I guess the answer to why it does that is
because it is useful.

Note that exclusions apply to later arguments only so excluding (1) from
2:history␣file is meaningless.

> > I can't find any arrangement of words on a command-line that gives the
> > argument after -h as being anything other than "history file". Neither
> > is it being counted as one of the non-option arguments.
>
> "count the argument after -h as a non-option argument" and "offer the
> second one in the first position" have equivalent results from the
> point of view of a black-box test.  I just didn't know how to explain
> what I was seeing.

Yes, I can see that would be the case. The tag (as indicated by
_complete_help) will be different, however.

> > There's a list of possible states. In each state, individual specs
> > are disabled if an option is found which would disable it. If you
> > exclude positional arguments, subsequent ones become active at an
> > earlier position.
>
> Is that "subsequent" part stated in the doc anywhere that you can point to?

No. The only reference to putting numbers in the exclusion lists at all
is an example giving `(-two -three 1)`.

> >         '(-h 1 3 4)-f[edit function]' \
> >         '(-h 1 3 4)-x+[specify spaces to use for indentation in function expansion]:spaces' \
> >         '(-f -x 1 2)-h[edit history]' \
> >         '(- 2 3 4)1:file:_files' \
> >         '(3 4)2:shell function:_functions' \
> >         '3:history file:_files' \
> >         '4:history size';;
>
> I would never have thought of that, because without the implicit
> "become active earlier" there is no third or fourth position.
>
> Perhaps the way to explain/understand this is that "position" here
> does not mean "position on the command line", rather it means "order
> in which to attempt to fill the next available position on the command
> line".

Yes, regarding the numbers as a priority for ordering may be a good way
to explain this in the documentation.

The exception to this is if you specify them upfront with a gap:
  _arguments '5:five'
that'll complete nothing in positions 1-4 which is fairly useless.
This might be regarded at least as being undefined behaviour and perhaps
as a bug.

The documentation could also be clearer for the case where the number is
omitted. It is allocated by simply incrementing the number from the most
recent one. Note:-
  _arguments '5:five' '2:two' ':three' '3:trois'
results in:
  _arguments:comparguments:325: doubled argument definition: 3:trois

> One other thing ...

> On Mon, May 25, 2020 at 10:29 AM Oliver Kiddle <okiddle@yahoo.co.uk> wrote:
> >
> >         '4:history size';;

> Doc says:

>      The forms for ACTION are as follows.

>       (single unquoted space)
>           This is useful where an argument is required but it is not
>           possible or desirable to generate matches for it.  The MESSAGE
>           will be displayed but no completions listed.  Note that even
>           in this case the colon at the end of the MESSAGE is needed; it
>           may only be omitted when neither a MESSAGE nor an ACTION is
>           given.

> That seems to imply that you can write "4:" or "4:history size: " but
> not what you did write.  Is the doc wrong?

That was introduced in workers/11554 and I'm fairly certain it is wrong.
As a hunch, the _arguments source was followed when listing the possible
action forms and the subtlety that the empty string matches in the
following line (411) was overlooked:

  if [[ "$action" = \ # ]]; then

For what it's worth, I tend to leave the action out (going back to
before that documentation change) because it keeps the spec a tiny bit
shorter. And all too often _arguments specs are rather too long.

Oliver

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Fwd: Editing the history in the shell
       [not found]       ` <CAH+w=7bTCCC2XBr+_HCAPqXWuOV_YOrDv+36C6jJScKrxZjhLQ@mail.gmail.com>
@ 2020-05-27  3:32         ` Bart Schaefer
  2021-04-25 21:56           ` [PATCH] " Bart Schaefer
  0 siblings, 1 reply; 10+ messages in thread
From: Bart Schaefer @ 2020-05-27  3:32 UTC (permalink / raw)
  To: Zsh hackers list

On Sun, May 24, 2020 at 7:35 PM Daniel Shahaf <d.s@daniel.shahaf.name> wrote:
>
> Bart Schaefer wrote on Sat, 23 May 2020 14:30 -0700:
> > @@ -133,6 +140,28 @@ if ((fun)) then
> > +    for (( hist=1; hist <= savehist; hist++ )) print -rs -- "$var[hist]"
>
> This generates a syntax error when SHORT_LOOPS is unset.

I think zed has a long-standing problem with SH_WORD_SPLIT and
probably some other SH_ options?

% setopt shwordsplit
% zed "/tmp/one two"
Where does this go?
% cat "/tmp/one two"
cat: '/tmp/one two': No such file or directory
% cat /tmp/one
Where does this go?
%

So ... how far do we go to make "zed" compatible with nonstandard
options, and how do we approach doing it?  Try to use entirely
portable syntax, or invoke "emulate -L", or something in between?
Look at the handling of function names, too -- it creates the empty
function definition with ${(q-)1} but makes no attempt to quote $1
when calling "functions" or "autoload".

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH] Re: Editing the history in the shell
  2020-05-27  3:32         ` Fwd: Editing the history in the shell Bart Schaefer
@ 2021-04-25 21:56           ` Bart Schaefer
  0 siblings, 0 replies; 10+ messages in thread
From: Bart Schaefer @ 2021-04-25 21:56 UTC (permalink / raw)
  To: Zsh hackers list

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

Another one from last May.

On Fri, May 22, 2020 at 3:24 PM Bart Schaefer <schaefer@brasslantern.com> wrote:
>
> The patch implements "zed -h" which loads the history into vared and
> arranges to replace the existing history with that before it exits.
> This does NOT solve Markus' complaint about the history numbers, they
> increase by the number of entries output by the editor. It makes no
> attempt to account for various history options, so those may change
> the final result if they affect "print -s".
>
> It also handles "zed -h filename" which loads that file and saves it
> back again rather than alter the current shell history.

On Tue, May 26, 2020 at 8:32 PM Bart Schaefer <schaefer@brasslantern.com> wrote:
>
> I think zed has a long-standing problem with SH_WORD_SPLIT and
> probably some other SH_ options?
>
> % setopt shwordsplit
> % zed "/tmp/one two"
> Where does this go?
> % cat "/tmp/one two"
> cat: '/tmp/one two': No such file or directory
> % cat /tmp/one
> Where does this go?
> %
>
> So ... how far do we go to make "zed" compatible with nonstandard
> options, and how do we approach doing it?  Try to use entirely
> portable syntax, or invoke "emulate -L", or something in between?
> Look at the handling of function names, too -- it creates the empty
> function definition with ${(q-)1} but makes no attempt to quote $1
> when calling "functions" or "autoload".

The attached patch both implements zed -h and fixes issues with
SH_WORD_SPLIT and NO_SHORT_LOOPS, but does not attempt a comprehensive
repair, so the "how far do we go?" question remains.  Completion is
updated per Oliver's advice in the "_arguments optspecs" thread
(starts at workers/45909, messages from Oliver seem to have been
doubled up in the archives) but could use a once-over.  Doc also
updated.

A minor head-scratcher for me (insufficient note-taking to return to
this a year later) is that at one time the code for zed -h was using
  var=( "${(@Oav)history:gs/\\/\\\\}" )
before invoking vared.  Now I can't figure out when or why I thought
that was necessary; I'm able to edit my existing 500 commands / 734
lines of history without seeing any garbled backslashes, using only
  var=( "${(@Oav)history}" )
as in the patch.  Can anyone jog my memory?  Do backslashes ever need
double-escaping?

[-- Attachment #2: histed-patch.txt --]
[-- Type: text/plain, Size: 7112 bytes --]

diff --git a/Completion/Zsh/Command/_zed b/Completion/Zsh/Command/_zed
index 6b68fadf0..f84993d73 100644
--- a/Completion/Zsh/Command/_zed
+++ b/Completion/Zsh/Command/_zed
@@ -1,10 +1,16 @@
-#compdef zed fned
+#compdef zed fned histed
 
 case $service in
 (fned) _arguments -S : ':shell function:_functions';;
+(histed) _arguments -S : \
+	'1:history file:_files' \
+	'2:history size: ';;
 (zed) _arguments -S : \
-	'(- 2):file:_files' \
-	'(1):shell function:_functions' \
-	'(1)-x+[specify spaces to use for indentation in function expansion]:spaces' \
-	'(1)-f[edit function]';;
+        '(-h 1 3 4)-f[edit function]' \
+        '(-h 1 3 4)-x+[specify spaces to use for indentation in function expansion]:spaces' \
+        '(-f -x 1 2)-h[edit history]' \
+        '(- 2 3 4)1:file:_files' \
+        '(3 4)2:shell function:_functions' \
+        '3:history file:_files -g "*(D)"' \
+        '4:history size';;
 esac
diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index 8bf1a208e..0f2d0664d 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -4295,6 +4295,12 @@ Same as tt(zed -f).  This function does not appear in the zsh
 distribution, but can be created by linking tt(zed) to the name tt(fned)
 in some directory in your tt(fpath).
 )
+findex(histed)
+item(tt(histed) [ [ var(name) ] var(size) ])(
+Same as tt(zed -h).  This function does not appear in the zsh
+distribution, but can be created by linking tt(zed) to the name tt(histed)
+in some directory in your tt(fpath).
+)
 findex(is-at-least)
 item(tt(is-at-least) var(needed) [ var(present) ])(
 Perform a greater-than-or-equal-to comparison of two strings having the
@@ -4504,6 +4510,7 @@ tt(zargs) with the tt(-)tt(-help) option.
 )
 findex(zed)
 xitem(tt(zed) [ tt(-f) [ tt(-x) var(num) ] ] var(name))
+xitem(tt(zed) [ tt(-h) [ var(name) ] var(size) ])
 item(tt(zed -b))(
 This function uses the ZLE editor to edit a file or function.
 
@@ -4518,7 +4525,14 @@ the given number of spaces; `tt(-x 2)' is consistent with the layout
 of functions distributed with the shell.
 
 Without tt(-f), var(name) is the path name of the file to edit, which need
-not exist; it is created on write, if necessary.
+not exist; it is created on write, if necessary.  With tt(-h), the file is
+presumed to contain history events.
+
+When no file name is provided for tt(-h) the current shell history is edited
+in place.  The history is renumbered when zed exits successfully.
+
+When editing history, multi-line events must have a trailing backslash on
+every line before the last.
 
 While editing, the function sets the main keymap to tt(zed) and the
 vi command keymap to tt(zed-vicmd).  These will be copied from the existing
@@ -4543,7 +4557,11 @@ of the return key), or can be bound to a key in either of the tt(zed) or
 tt(zed-vicmd) keymaps after `tt(zed -b)' has been run.  When the widget is
 called, it prompts for a new name for the file being edited.  When zed
 exits the file will be written under that name and the original file will
-be left alone.  The widget has no effect with `tt(zed -f)'.
+be left alone.  The widget has no effect with `tt(zed -f)'.  When editing
+the current history with `tt(zed -h)', the history is first updated and
+then the file is written, but the global setting of tt(HISTFILE) is not
+altered.
+
 
 While tt(zed-set-file-name) is running, zed uses the keymap
 tt(zed-normal-keymap), which is linked from the main keymap in effect
diff --git a/Functions/Misc/zed b/Functions/Misc/zed
index 9eb4b2d93..7d0d590db 100644
--- a/Functions/Misc/zed
+++ b/Functions/Misc/zed
@@ -5,16 +5,18 @@
 # Edit small files with the command line editor.
 # Use ^X^W to save (or ZZ in vicmd mode), ^C to abort.
 # Option -f: edit shell functions.  (Also if called as fned.)
+# Option -h: edit shell history.  (Also if called as histed.)
 
 setopt localoptions noksharrays
 
 local var opts zed_file_name
 # We do not want timeout while we are editing a file
-integer TMOUT=0 okargs=1 fun bind
+integer TMOUT=0 okargs=1 fun hist bind
 local -a expand
 
-zparseopts -D -A opts f b x:
+zparseopts -D -A opts f h b x:
 fun=$+opts[-f]
+hist=$+opts[-h]
 bind=$+opts[-b]
 if [[ $opts[-x] == <-> ]]; then
   expand=(-x $opts[-x])
@@ -24,23 +26,28 @@ elif (( $+opts[-x] )); then
 fi
 
 [[ $0 = fned ]] && fun=1
+[[ $0 = histed ]] && hist=1
+(( hist && $# <= 2 )) && okargs=$#
 (( bind )) && okargs=0
 
-if (( $# != okargs )); then
+if (( $# != okargs || bind + fun + hist > 1 )); then
     echo 'Usage:
 zed filename
 zed -f [ -x N ] function
+zed -h [ filename [ size ] ]
 zed -b' >&2
     return 1
 fi
 
 local curcontext=zed:::
 
-# Matching used in zstyle -m: hide result from caller.
-# Variables not used directly here.
-local -a match mbegin mend
-zstyle -m ":completion:zed:*" insert-tab '*' ||
-    zstyle ":completion:zed:*" insert-tab yes
+() {
+    # Matching used in zstyle -m: hide result from caller.
+    # Variables not used directly here.
+    local -a match mbegin mend
+    zstyle -m ":completion:zed:*" insert-tab '*' ||
+	zstyle ":completion:zed:*" insert-tab yes
+}
 
 zmodload zsh/terminfo 2>/dev/null
 
@@ -124,22 +131,51 @@ fi
 setopt localoptions nobanghist
 
 if ((fun)) then
-  var="$(functions $expand -- $1)"
+  var="$(functions $expand -- "$1")"
   # If function is undefined but autoloadable, load it
   if [[ $var = *\#\ undefined* ]] then
-    var="$(autoload +X $1; functions -- $1)"
+    var="$(autoload +X "$1"; functions -- "$1")"
   elif [[ -z $var ]] then
     var="${(q-)1} () {
 }"
   fi
   vared -M zed -m zed-vicmd -i __zed_init var && eval function "$var"
+elif ((hist)) then
+  if [[ -n $1 ]]; then
+    { fc -p -a "$1" ${2:-$({ wc -l <"$1" } 2>/dev/null)} || return }
+    let HISTSIZE++  
+    print -s ""		# Work around fc -p limitation
+  fi
+  # When editing the current shell history, the "zed -h" command is not
+  # itself included because the current event is not added to the ring
+  # until the next prompt is printed.  This means "zed -h" is prepended
+  # to the result of the edit, because of the way "print -s" is defined.
+  var=( "${(@Oav)history}" )
+  IFS=$'\n' vared -M zed -m zed-vicmd -i __zed_init var
+  if (( ? )); then
+    [[ -n $1 ]] && unset HISTFILE
+  else
+    local HISTSIZE=0 savehist=$#var
+    fc -R /dev/null	# Remove entries other than those added here
+    HISTSIZE=$savehist	# Resets on function exit because local
+    [[ -n $1 ]] && SAVEHIST=$savehist	# Resets via foregoing fc -a
+    for (( hist=1; hist <= savehist; hist++ ))
+    do print -rs -- "$var[hist]"
+    done
+    if [[ -n $zed_file_name ]]; then
+      fc -W "$zed_file_name"
+      [[ -n $1 ]] && unset HISTFILE
+    fi
+    # Note prepend effect when global HISTSIZE greater than $savehist.
+    # This does not affect file editing.
+  fi
 else
-  zed_file_name=$1
-  [[ -f $1 ]] && var="$(<$1)"
+  zed_file_name="$1"
+  [[ -f $1 ]] && var="$(<"$1")"
   while vared -M zed -m zed-vicmd -i __zed_init var
   do
     {
-      print -r -- "$var" >| $zed_file_name
+      print -r -- "$var" >| "$zed_file_name"
     } always {
       (( TRY_BLOCK_ERROR = 0 ))
     } && break

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2021-04-25 21:57 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-05-22 22:24 Editing the history in the shell Bart Schaefer
2020-05-23  2:28 ` Daniel Shahaf
2020-05-23 21:30   ` Bart Schaefer
2020-05-24 22:02     ` _arguments optspecs (was Re: Editing the history in the shell) Bart Schaefer
2020-05-25 17:29       ` Oliver Kiddle
2020-05-25 22:32         ` Bart Schaefer
2020-05-26  7:56           ` Oliver Kiddle
2020-05-25 22:42         ` Bart Schaefer
     [not found]     ` <20200525023515.7855610a@tarpaulin.shahaf.local2>
     [not found]       ` <CAH+w=7bTCCC2XBr+_HCAPqXWuOV_YOrDv+36C6jJScKrxZjhLQ@mail.gmail.com>
2020-05-27  3:32         ` Fwd: Editing the history in the shell Bart Schaefer
2021-04-25 21:56           ` [PATCH] " Bart Schaefer

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).