zsh-users
 help / color / mirror / code / Atom feed
* error using custom widget: widgets can only be called when ZLE is active
@ 2006-12-09 23:18 Mikel Ward
  2006-12-10  0:10 ` error using custom widget: widgets can only be called when ZLE is active [solved] Mikel Ward
  0 siblings, 1 reply; 5+ messages in thread
From: Mikel Ward @ 2006-12-09 23:18 UTC (permalink / raw)
  To: zsh-users

Hi


I'm trying to make a keyboard shortcut in Z Shell to expand the previous
word on the command line its canonical path ("realpath") if it's a file.

For example, I have a directory in /etc/apache2/mods-enabled that
contains symlinks to files in /etc/apache2/mods-available.

I want to be able to do:
> cd /etc/apache2/mods-enabled

> ls -l proxy.load<Press ^X />

and have the command change to:
> ls -l /etc/apache2/mods-available/proxy.load


>From what I gather, I can do this using a zle custom widget.  I chose to
call the function implementing it expand-word-path and bind this to ^X /.

For some reason, it doesn't work.  In zsh 4.3.2 on Debian Linux I get an
error that ZLE is inactive, as you can see below.

----------
> expand-word-path()
{
zle backward-word
zle set-mark-command
zle forward-word
zle copy-region-as-kill
file=$(zle yank-pop)
zle -U `realpath $file`
}

> zle -N expand-word-path

> bindkey "^X/" expand-word-path

> ls /etc/apache2/mods-enabled/rewrite.load<^X/>

zle:1: widgets can only be called when ZLE is active
realpath: need at least one filename
Usage:
 realpath [-s|--strip] [-z|--zero] filename ...
 realpath -h|--help
 realpath -v|--version

expand-word-path:zle:7: not enough arguments for -U
----------

The manual says:
    zle widget [ -n num ] [ -N ] args ...
        Invoke the specified widget. This can only be
        done when ZLE is active; normally this will be
        within a user-defined widget.

This suggests to me that ZLE should be active in the context I'm using
it, but clearly it's not.


I've tried searching for other custom widgets, but surprisingly there's
not any out there.


Can anyone suggest what I'm doing wrong or point me to some other custom
widgets that do work?


Thanks


Mike


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

* Re: error using custom widget: widgets can only be called when ZLE is active [solved]
  2006-12-09 23:18 error using custom widget: widgets can only be called when ZLE is active Mikel Ward
@ 2006-12-10  0:10 ` Mikel Ward
  2006-12-10  0:33   ` zsh widget to resolve symlinks Mikel Ward
  0 siblings, 1 reply; 5+ messages in thread
From: Mikel Ward @ 2006-12-10  0:10 UTC (permalink / raw)
  To: zsh-users

Hi

The error message lead me to believe the error was in the first line of
the function (i.e. zsh backward-word), but by deduction I found out the
actual problem was trying to run zle in a subshell.

Manipulating the kill ring array directly instead of calling "zle
yank-pop" seems to work.

expand-word-path ()
{
        zle backward-word
        zle set-mark-command
        zle forward-word
        zle copy-region-as-kill
        word=$killring[0]
        killring[0]=()
        realpath=$(realpath $word 2>/dev/null)
        if test -n "$realpath"
        then
                zle backward-kill-word
                zle -U "$realpath"
        fi
}

The only thing now is invoking it on an empty word multiple times does
weird things.  Probably some edge cases I have to look at, such as when
$word is empty.


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

* zsh widget to resolve symlinks
  2006-12-10  0:10 ` error using custom widget: widgets can only be called when ZLE is active [solved] Mikel Ward
@ 2006-12-10  0:33   ` Mikel Ward
  2006-12-10 17:56     ` Peter Stephenson
  0 siblings, 1 reply; 5+ messages in thread
From: Mikel Ward @ 2006-12-10  0:33 UTC (permalink / raw)
  To: zsh-users

Here's a working version of the widget to expand a shell word into its
canonical path.

It turns out copy-region-as-kill doesn't use killring but rather
CUTBUFFER.  This is a little confusing!

I'm also surprised that CUTBUFFER persists invocations of zle, yet it
isn't set in the top-level interactive shell.  If I don't reset
CUTBUFFER as the first thing I do, invoking the widget for a second time
on an empty command line inserts the result of the previous invocation,
for example:
> ls .zshrc<^X />
expands to
> ls /home/michael/etc/zshrc

but then
> <^X />
expands to
> /home/michael/etc/zshrc

The only problem I'm aware of with this version is where the file name
contains spaces.  I can't think of a way to easily do this, since we
have the copy-prev-shell-word widget, but no equivalent
kill-prev-shell-word that I would need to update the display.  (I could
get the word, but how would I overwrite it with its real path?)

expand-word-path ()
{
        CUTBUFFER=
        zle backward-word
        zle set-mark-command
        zle forward-word
        zle copy-region-as-kill
        local word=$CUTBUFFER

        local realpath=$(realpath $word 2>/dev/null)
        if test -n "$realpath"
        then
                zle backward-kill-word
                zle -U "$realpath"
        fi
}


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

* Re: zsh widget to resolve symlinks
  2006-12-10  0:33   ` zsh widget to resolve symlinks Mikel Ward
@ 2006-12-10 17:56     ` Peter Stephenson
  2006-12-14 11:22       ` PATCH: argument splitting (was Re: zsh widget to resolve symlinks) Peter Stephenson
  0 siblings, 1 reply; 5+ messages in thread
From: Peter Stephenson @ 2006-12-10 17:56 UTC (permalink / raw)
  To: zsh-users

Mikel Ward wrote:
> It turns out copy-region-as-kill doesn't use killring but rather
> CUTBUFFER.  This is a little confusing!

Right, CUTBUFFER is the most recently killed, killring holds the entries
before that.  This is something of a historical curiosity.  The manual
needs to be more explicit about the link between the two.

> I'm also surprised that CUTBUFFER persists invocations of zle, yet it
> isn't set in the top-level interactive shell.

This reflects how zle works.  If you use "yank" when editing, it will
insert the last string cut or copied even if it wasn't in the current
command line, and even though zle wasn't active in the intervening period.

In recent zsh, you can hook in code to be run at the start of the line
by defining zle-line-init as a widget; however, you probably don't want
to reset CUTBUFFER because of its unexpected effect on yanking.

You can avoid using CUTBUFFER at all with something like (note I haven't
actually tried this but it shouldn't be too far out):

zle backward-word
integer pos1=$((CURSOR+1))
zle forward-word
# this will grab any whitespace at the end, too...
integer pos2=$((CURSOR))
local word="$BUFFER[$pos1,$pos2]"

and to replace the word, leaving the cursor after it,

LBUFFER="${LBUFFER[1,$pos-1]}$realpath"

If I were writing the widget, I'd probably use raw access to $BUFFER
etc. to find the start and end of the word (hence avoiding the use of
$CURSOR apart from finding out where to start), and ${(z)...} trickery
to access shell words as done in match-words-by-style (which might give
you some other ideas).

It occurred to me recently that a generic widget to split the line into
shell words and whitespace would be extremely useful, making this sort
of task much easier.  However, I haven't got around to it yet.

-- 
Peter Stephenson <p.w.stephenson@ntlworld.com>
Web page now at http://homepage.ntlworld.com/p.w.stephenson/


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

* PATCH: argument splitting (was Re: zsh widget to resolve symlinks)
  2006-12-10 17:56     ` Peter Stephenson
@ 2006-12-14 11:22       ` Peter Stephenson
  0 siblings, 0 replies; 5+ messages in thread
From: Peter Stephenson @ 2006-12-14 11:22 UTC (permalink / raw)
  To: zsh-users

Peter Stephenson <p.w.stephenson@ntlworld.com> wrote:
> It occurred to me recently that a generic widget to split the line into
> shell words and whitespace would be extremely useful, making this sort
> of task much easier.  However, I haven't got around to it yet.

Here it is, with a handy example function that makes some simple
replacements on the current shell argument very easy.  I'll commit it on
the main line, but I believe the functions here should run fine in released
versions of 4.2.

The original problem should now reduce to the following code:

  autoload -U modify-current-argument
  modify-current-argument '${(q)$(eval realpath "$ARG")}'

The eval and the (q) are there to try to be careful about quoting,
use of ~'s, etc.

(Some of the other documentation for ZLE functions in the subsection "Widgets"
should really go into "Utility Functions", too.)

Index: Doc/Zsh/contrib.yo
===================================================================
RCS file: /cvsroot/zsh/zsh/Doc/Zsh/contrib.yo,v
retrieving revision 1.64
diff -u -r1.64 contrib.yo
--- Doc/Zsh/contrib.yo	10 Oct 2006 11:24:38 -0000	1.64
+++ Doc/Zsh/contrib.yo	14 Dec 2006 11:07:01 -0000
@@ -1142,6 +1142,60 @@
 )
 enditem()
 
+subsect(Utility Functions)
+
+These functions are useful in constructing widgets.  They
+should be loaded with `tt(autoload -U) var(function)' and called
+as indicated from user-defined widgets.
+
+startitem()
+tindex(split-shell-arguments)
+item(tt(split-shell-arguments))(
+This function splits the line currently being edited into shell arguments
+and whitespace.  The result is stored in the array tt(reply).  The array
+contains all the parts of the line in order, starting with any whitespace
+before the first argument, and finishing with any whitespace after the last
+argument.  Hence (so long as the option tt(KSH_ARRAYS) is not set)
+whitespace is given by odd indices in the array and arguments by
+even indices.  Note that no stripping of quotes is done; joining together
+all the elements of tt(reply) in order is guaranteed to produce the
+original line.
+
+The parameter tt(REPLY) is set to the index of the word in tt(reply) which
+contains the character after the cursor, where the first element has index
+1.  The parameter tt(REPLY2) is set to the index of the character under the
+cursor in that word, where the first character has index 1.
+
+Hence tt(reply), tt(REPLY) and tt(REPLY2) should all be made local to
+the enclosing function.
+
+See the function tt(modify-current-argument), described below, for
+an example of how to call this function.
+)
+tindex(modify-current-argument)
+item(tt(modify-current-argument) var(expr-using-)tt($ARG))(
+This function provides a simple method of allowing user-defined widgets
+to modify the command line argument under the cursor (or immediately to the
+left of the cursor if the cursor is between arguments).  The argument
+should be an expression which when evaluated operates on the shell
+parameter tt(ARG), which will have been set to the command line argument
+under the cursor.  The expression should be suitably quoted to prevent
+it being evaluated too early.
+
+For example, a user-defined widget containing the following code
+converts the characters in the argument under the cursor into all upper
+case:
+
+example(modify-current-word '${(U)ARG}')
+
+The following strips any quoting from the current word (whether backslashes
+or one of the styles of quotes), and replaces it with single quoting
+throughout:
+
+example(modify-current-word '${(qq)${(Q)ARG}}')
+)
+enditem()
+
 subsect(Styles)
 
 The behavior of several of the above widgets can be controlled by the use
Index: Functions/Zle/modify-current-argument
===================================================================
RCS file: Functions/Zle/modify-current-argument
diff -N Functions/Zle/modify-current-argument
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ Functions/Zle/modify-current-argument	14 Dec 2006 11:07:02 -0000
@@ -0,0 +1,51 @@
+# Take an expression suitable for interpolation in double quotes that
+# performs a replacement on the parameter "ARG".  Replaces the
+# shell argument (which may be a quoted string) under or before the
+# cursor with that.  Ensure the expression is suitable quoted.
+#
+# For example, to uppercase the entire shell argument:
+#   modify-current-word '${(U)ARG}'
+# To strip the current quoting from the word (whether backslashes or
+# single, double or dollar quotes) and use single quotes instead:
+#   modify-current-word '${(qq)${(Q)ARG}}'
+
+# Retain most options from the calling function for the eval.
+# Reset some that might confuse things.
+setopt localoptions noksharrays multibyte
+
+local -a reply
+integer REPLY REPLY2
+
+autoload -U split-shell-arguments
+split-shell-arguments
+
+# Can't do this unless there's some text under or left of us.
+(( REPLY < 2 )) && return 1
+
+# Get the index of the word we want.
+if (( REPLY & 1 )); then
+  # Odd position; need previous word.
+  (( REPLY-- ))
+  # Pretend position was just after the end of it.
+  (( REPLY2 = ${#reply[REPLY]} + 1 ))
+fi
+
+# Length of all characters before current.
+# Force use of character (not index) counting and join without IFS.
+integer wordoff="${(cj..)#reply[1,REPLY-1]}"
+
+# Replacement for current word.  This could do anything to ${reply[REPLY]}.
+local ARG="${reply[REPLY]}" repl
+eval repl=\"$1\"
+# New line:  all words before and after current word, with
+# no additional spaces since we've already got the whitespace
+# and the replacement word in the middle.
+BUFFER="${(j..)reply[1,REPLY-1]}${repl}${(j..)reply[REPLY+1,-1]}"
+
+# Keep cursor at same position in replaced word.
+# Redundant here, but useful if $repl changes the length.
+# Limit to the next position after the end of the word.
+integer repmax=$(( ${#repl} + 1 ))
+# Remember CURSOR starts from offset 0 for some reason, so
+# subtract 1 from positions.
+(( CURSOR = wordoff + (REPLY2 > repmax ? repmax : REPLY2) - 1 ))
Index: Functions/Zle/split-shell-arguments
===================================================================
RCS file: Functions/Zle/split-shell-arguments
diff -N Functions/Zle/split-shell-arguments
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ Functions/Zle/split-shell-arguments	14 Dec 2006 11:07:02 -0000
@@ -0,0 +1,58 @@
+# Split a command line into shell arguments and whitespace in $reply.
+# Odd elements (starting from 1) are whitespace, even elements
+# are shell arguments (possibly quoted strings).  Whitespace at
+# start and end is always included in the array but may be an empty string.
+# $REPLY holds NO_KSH_ARRAYS index of current word in $reply.
+# $REPLY2 holds NO_KSH_ARRAYS index of current character in current word.
+# Hence ${reply[$REPLY][$REPLY2]} is the character under the cursor.
+#
+# reply, REPLY, REPLY2 should therefore be local to the enclosing function.
+#
+# The following formula replaces the current shell word, or previous word
+# if the cursor is on whitespace, by uppercasing all characters.
+
+emulate -L zsh
+setopt extendedglob
+
+local -a bufwords lbufwords
+local word
+integer pos=1 cpos=$((CURSOR+1)) opos iword ichar
+
+bufwords=(${(z)BUFFER})
+
+reply=()
+while [[ ${BUFFER[pos]} = [[:space:]] ]]; do
+  (( pos++ ))
+done
+reply+=${BUFFER[1,pos-1]}
+(( cpos < pos )) && (( iword = 1, ichar = cpos ))
+
+for word in "${bufwords[@]}"; do
+  (( opos = pos ))
+  (( pos += ${#word} ))
+  reply+=("$word")
+  if (( iword == 0  &&  cpos < pos )); then
+    (( iword = ${#reply} ))
+    (( ichar = cpos - opos + 1 ))
+  fi
+
+  (( opos = pos ))
+  while [[ ${BUFFER[pos]} = [[:space:]] ]]; do
+    (( pos++ ))
+  done
+  reply+=("${BUFFER[opos,pos-1]}")
+  if (( iword == 0  &&  cpos < pos )); then
+    (( iword = ${#reply} ))
+    (( ichar = cpos - opos + 1 ))
+  fi
+done
+
+if (( iword == 0 )); then
+  # At the end of the line, so off the indexable positions
+  # (but still a valid cursor position).
+  (( REPLY = ${#reply} ))
+  (( REPLY2 = 1 ))
+else
+  (( REPLY = iword ))
+  (( REPLY2 = ichar ))
+fi

-- 
Peter Stephenson <pws@csr.com>                  Software Engineer
CSR PLC, Churchill House, Cambridge Business Park, Cowley Road
Cambridge, CB4 0WZ, UK                          Tel: +44 (0)1223 692070


To access the latest news from CSR copy this link into a web browser:  http://www.csr.com/email_sig.php


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

end of thread, other threads:[~2006-12-14 11:29 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2006-12-09 23:18 error using custom widget: widgets can only be called when ZLE is active Mikel Ward
2006-12-10  0:10 ` error using custom widget: widgets can only be called when ZLE is active [solved] Mikel Ward
2006-12-10  0:33   ` zsh widget to resolve symlinks Mikel Ward
2006-12-10 17:56     ` Peter Stephenson
2006-12-14 11:22       ` PATCH: argument splitting (was Re: zsh widget to resolve symlinks) Peter Stephenson

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