1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2025-01-01 05:16:05 +01:00

18394: New word movement and editing widgets.

This commit is contained in:
Peter Stephenson 2003-03-28 11:34:07 +00:00
parent 2941469f61
commit 1e57c42f47
13 changed files with 678 additions and 26 deletions

View file

@ -1,3 +1,19 @@
2003-03-28 Peter Stephenson <pws@csr.com>
* 18394: Doc/Zsh/contrib.yo,
Functions/Zle/backward-kill-word-match,
Functions/Zle/backward-word-match,
Functions/Zle/capitalize-word-match,
Functions/Zle/down-case-word-match,
Functions/Zle/forward-word-match, Functions/Zle/kill-word-match,
Functions/Zle/match-words-by-style,
Functions/Zle/read-from-minibuffer,
Functions/Zle/select-word-style,
Functions/Zle/transpose-words-match,
Functions/Zle/up-case-word-match: Replacement widgets for
word movement and editing, controlled by style and by
select-word-style widget/function.
2003-03-26 Peter Stephenson <pws@csr.com>
* 18392: Src/builtin.c: read with -p and -t options crashed

View file

@ -362,29 +362,141 @@ followed by an appropriate tt(bindkey) command to associate the function
with a key sequence. Suggested bindings are described below.
startitem()
tindex(bash-forward-word)
tindex(bash-backward-word)
tindex(bash-kill-word)
tindex(bash-backward-kill-word)
tindex(bash-transpose-words)
tindex(bash-up-case-word)
tindex(bash-down-case-word)
xitem(tt(bash-forward-word), tt(bash-backward-word))
xitem(tt(bash-kill-word), tt(bash-backward-kill-word))
xitem(tt(bash-up-case-word), tt(bash-down-case-word))
item(tt(bash-transpose-words))(
These work similarly to the corresponding builtin zle functions without the
`tt(bash-)' prefix, but a word is considered to consist of alphanumeric
characters only. If you wish to replace your existing bindings with these
four widgets, the following is sufficient:
item(bash-style word functions)(
If you are looking for functions to implement moving over and editing
words in the manner of bash, where only alphanumeric characters are
considered word characters, you can use the functions described in
the next section. The following is sufficient:
example(for widget in kill-word backward-kill-word \
forward-word backward-word \
up-case-word down-case-word \
transpose-words; do
autoload bash-$widget
zle -N $widget bash-$widget
done)
example(autoload -U select-word-style
select-word-style bash)
)
tindex(forward-word-match)
tindex(backward-word-match)
tindex(kill-word-match)
tindex(backward-kill-word-match)
tindex(transpose-words-match)
tindex(capitalize-word-match)
tindex(up-case-word-match)
tindex(down-case-word-match)
tindex(select-word-style)
tindex(match-word-by-style)
xitem(tt(forward-word-match), tt(backward-word-match))
xitem(tt(kill-word-match), tt(backward-kill-word-match))
xitem(tt(transpose-words-match), tt(capitalize-word-match))
xitem(tt(up-case-word-match), tt(down-case-word-match))
item(tt(select-word-style), tt(match-word-by-style))(
The eight `tt(-match)' functions are drop-in replacements for the
builtin widgets without the suffix. By default they behave in a similar
way. However, by the use of styles and the function tt(select-word-style),
the way words are matched can be altered.
The simplest way of configuring the functions is to use
tt(select-word-style), which can either be called as a normal function with
the appropriate argument, or invoked as a user-defined widget that will
prompt for the first character of the word style to be used. The first
time it is invoked, the eight tt(-match) functions will automatically
replace the builtin versions, so they do not need to be loaded explicitly.
The word styles available are as follows. Only the first character
is examined.
startitem()
item(tt(bash))(
Word characters are alphanumeric characters only.
)
item(tt(normal))(
As in normal shell operation: word characters are alphanumeric characters
plus any characters present in the string given by the parameter
tt($WORDCHARS).
)
item(tt(shell))(
Words are complete shell command arguments, possibly including complete
quoted strings, or any tokens special to the shell.
)
item(tt(whitespace))(
Words are any set of characters delimited by whitespace.
)
item(tt(default))(
Restore the default settings; this is usually the same as `tt(normal)'.
)
enditem()
More control can be obtained using the tt(zstyle) command, as described in
ifzman(zmanref(zshmodules))\
ifnzman(noderef(The zsh/zutil Module)). Each style is looked up in the
context tt(:zle:)var(widget) where var(widget) is the name of the
user-defined widget, not the name of the function implementing it, so in
the case of the definitions supplied by tt(select-word-style) the
appropriate contexts are tt(:zle:forward-word), and so on. The function
tt(select-word-style) itself always defines styles for the context
`tt(:zle:*)' which can be overridden by more specific (longer) patterns as
well as explicit contexts.
The style tt(word-style) specifies the rules to use. This may have the
following values.
startitem()
item(tt(normal))(
Use the standard shell rules, i.e. alphanumerics and tt($WORDCHARS), unless
overridden by the styles tt(word-chars) or tt(word-class).
)
item(tt(specified))(
Similar to tt(normal), but em(only) the specified characters, and not also
alphanumerics, are considered word characters.
)
item(tt(unspecified))(
The negation of specified. The given characters are those which will
em(not) be considered part of a word.
)
item(tt(shell))(
Words are obtained by using the syntactic rules for generating shell
command arguments. In addition, special tokens which are never command
arguments such as `tt(())' are also treated as words.
)
item(tt(whitespace))(
Words are whitespace-delimited strings of characters.
)
enditem()
The first three of those styles usually use tt($WORDCHARS), but the value
in the parameter can be overridden by the style tt(word-chars), which works
in exactly the same way as tt($WORDCHARS). In addition, the style
tt(word-class) uses character class syntax to group characters and takes
precedence over tt(word-chars) if both are set. The tt(word-class) style
does not include the surrounding brackets of the character class; for
example, `tt(-:[:alnum:])' is a valid tt(word-class) to include all
alphanumerics plus the characters `tt(-)' and `tt(:)'. Be careful
including `tt(])', `tt(^)' and `tt(-)' as these are special inside
character classes.
The final style is tt(skip-chars). This is mostly useful for
tt(transpose-words) and similar functions. If set, it gives a count of
characters starting at the cursor position which will not be considered
part of the word and are treated as space, regardless of what they actually
are. For example, if
example(zstyle ':zle:transpose-words' skip-chars 1)
has been set, and tt(transpose-words-match) is called with the cursor on
the var(X) of tt(foo)var(X)tt(bar), where var(X) can be any character, then
the resulting expression is tt(bar)var(X)tt(foo).
The word matching and all the handling of tt(zstyle) settings is actually
implemented by the function tt(match-word-by-style). This can be used to
create new user-defined widgets. The calling function should set the local
parameter tt(curcontext) to tt(:zle:)var(widget), create the local
parameter tt(matched_words) and call tt(match-word-by-style) with no
arguments. On return, tt(matched_words) will be set to an array with the
elements: (1) the start of the line (2) the word before the cursor (3) any
non-word characters between that word and the cursor (4) any non-word
character at the cursor position plus any remaining non-word characters
before the next word, including all characters specified by the
tt(skip-chars) style, (5) the word at or following the cursor (6) any
non-word characters following that word (7) the remainder of the line. Any
of the elements may be an empty string; the calling function should test
for this to decide whether it can perform its function.
)
tindex(copy-earlier-word)
item(tt(copy-earlier-word))(
@ -601,6 +713,11 @@ by a keyboard break (typically tt(^G)), the function returns status 1
and tt($REPLY) is not set. If an argument is supplied to the function
it is taken as a prompt, otherwise `tt(? )' is used.
One option is available: `tt(-k) var(num)' specifies that var(num)
characters are to be read instead of a whole line. The line editor is not
invoked recursively in this case. Note that unlike the tt(read) builtin
var(num) must be given; there is no default.
The name is a slight misnomer, as in fact the shell's own minibuffer is
not used. Hence it is still possible to call tt(executed-named-cmd) and
similar functions while reading a value.

View file

@ -0,0 +1,36 @@
emulate -L zsh
setopt extendedglob
autoload match-words-by-style
local curcontext=":zle:$WIDGET" word done
local -a matched_words
integer count=${NUMERIC:-1}
if (( count < 0 )); then
(( NUMERIC = -count ))
zle ${WIDGET##backward-}
return
fi
while (( count-- )); do
match-words-by-style
word="$matched_words[2]$matched_words[3]"
if [[ -n $word ]]; then
if [[ -n $done || $LASTWIDGET = *kill* ]]; then
CUTBUFFER="$word$CUTBUFFER"
else
killring=("$CUTBUFFER" "${(@)killring[1,-2]}")
CUTBUFFER=$word
fi
LBUFFER=$matched_words[1]
else
return 1
fi
done=1
done
return 0

View file

@ -0,0 +1,29 @@
emulate -L zsh
setopt extendedglob
autoload match-words-by-style
local curcontext=":zle:$WIDGET" word
local -a matched_words
integer count=${NUMERIC:-1}
if (( count < 0 )); then
(( NUMERIC = - count ))
zle ${WIDGET/backward/forward}
return
fi
while (( count-- )); do
match-words-by-style
word=$matched_words[2]$matched_words[3]
if [[ -n $word ]]; then
(( CURSOR -= ${#word} ))
else
return 1
fi
done
return 0

View file

@ -0,0 +1,23 @@
emulate -L zsh
setopt extendedglob
autoload match-words-by-style
local curcontext=":zle:$WIDGET" word
local -a matched_words
integer count=${NUMERIC:-1}
while (( count-- > 0 )); do
match-words-by-style
word=${(j..)matched_words[4,5]}
if [[ -n $word ]]; then
LBUFFER+=${(C)word}
RBUFFER=${(j..)matched_words[6,7]}
else
return 1
fi
done
return 0

View file

@ -0,0 +1,23 @@
emulate -L zsh
setopt extendedglob
autoload match-words-by-style
local curcontext=":zle:$WIDGET" word
local -a matched_words
integer count=${NUMERIC:-1}
while (( count-- > 0 )); do
match-words-by-style
word=${(j..)matched_words[4,5]}
if [[ -n word ]]; then
LBUFFER+=${(L)word}
RBUFFER=${(j..)matched_words[6,7]}
else
return 1
fi
done
return 0

View file

@ -0,0 +1,39 @@
emulate -L zsh
setopt extendedglob
autoload match-words-by-style
local curcontext=":zle:$WIDGET" word
local -a matched_words
integer count=${NUMERIC:-1}
if (( count < 0 )); then
(( NUMERIC = -count ))
zle ${WIDGET/forward/backward}
return
fi
while (( count-- )); do
match-words-by-style
# For some reason forward-word doesn't work like the other word
# word commnds; it skips whitespace only after any matched word
# characters.
if [[ -n $matched_words[4] ]]; then
# just skip the whitespace
word=$matched_words[4]
else
# skip the word and trailing whitespace
word=$matched_words[5]$matched_words[6]
fi
if [[ -n $word ]]; then
(( CURSOR += ${#word} ))
else
return 1
fi
done
return 0

View file

@ -0,0 +1,36 @@
emulate -L zsh
setopt extendedglob
autoload match-words-by-style
local curcontext=":zle:$WIDGET" word done
local -a matched_words
integer count=${NUMERIC:-1}
if (( count < 0 )); then
(( NUMERIC = -count ))
zle backward-$WIDGET
return
fi
while (( count-- )); do
match-words-by-style
word="${(j..)matched_words[4,5]}"
if [[ -n $word ]]; then
if [[ -n $done || $LASTWIDGET = *kill* ]]; then
CUTBUFFER="$CUTBUFFER$word"
else
killring=("$CUTBUFFER" "${(@)killring[1,-2]}")
CUTBUFFER=$word
fi
RBUFFER=$matched_words[6]
else
return 1
fi
done=1
done
return 0

View file

@ -0,0 +1,167 @@
# Match words by the style given below. The matching depends on the
# cursor position. The matched_words array is set to the matched portions
# separately. These look like:
# <stuff-at-start> <word-before-cursor> <whitespace-before-cursor>
# <whitespace-after-cursor> <word-after-cursor> <whitespace-after-word>
# <stuff-at-end>
# where the cursor position is always after the third item and `after'
# is to be interpreted as `after or on'. Some
# of the array elements will be empty; this depends on the style.
# For example
# foo bar rod stick
# ^
# with the cursor where indicated whill with typical settings produce the
# elements `foo ', `bar', ` ', ` ', `rod', ` ' and `stick'.
#
# The style word-style can be set to indicate what a word is.
# The three possibilities are:
#
# shell Words are shell words, i.e. elements of a command line.
# whitespace Words are space delimited words; only space or tab characters
# are considered to terminated a word.
# normal (the default): the usual zle logic is applied, with all
# alphanumeric characters plus any characters in $WORDCHARS
# considered parts of a word. The style word-chars overrides
# the parameter. (Any currently undefined value will be
# treated as `normal', but this should not be relied upon.)
# specified Similar to normal, except that only the words given
# in the string (and not also alphanumeric characters)
# are to be considerd parts of words.
# unspecified The negation of `specified': the characters given
# are those that aren't to be considered parts of a word.
# They should probably include white space.
#
# In the case of the `normal' or `(un)specified', more control on the
# behaviour can be obtained by setting the style `word-chars' for the
# current context. The value is used to override $WORDCHARS locally.
# Hence,
# zstyle ':zle:transpose-words*' word-style normal
# zstyle ':zle:transpose-words*' word-chars ''
# will force bash-style word recognition, i.e only alphanumeric characters
# are considerd parts of a word. It is up to the function which calls
# match-words-by-style to set the context in the variable curcontext,
# else a default context will be used (not recommended).
#
# You can override the use of word-chars with the style word-class.
# This specifies the same information, but as a character class.
# The surrounding square brackets shouldn't be given, but anything
# which can appear inside is allowed. For example,
# zstyle ':zle:*' word-class '-:[:alnum:]'
# is valid. Note the usual care with `]' , `^' and `-' must be taken if
# they need to appear as individual characters rather than for grouping.
#
# The final style is `skip-chars'. This is an integer; that many
# characters counting the one under the cursor will be treated as
# whitespace regardless and added to the front of the fourth element of
# matched_words. The default is zero, i.e. the character under the cursor
# will appear in <whitespace-after-cursor> if it is whitespace, else in
# <word-after-cursor>. This style is mostly useful for forcing
# transposition to ignore the current character.
emulate -L zsh
setopt extendedglob
local wordstyle spacepat wordpat1 wordpat2 opt charskip
local match mbegin mend pat1 pat2 word1 word2 ws1 ws2 ws3 skip
local MATCH MBEGIN MEND
if [[ -z $curcontext ]]; then
local curcontext=:zle:match-words-by-style
fi
zstyle -s $curcontext word-style wordstyle
zstyle -s $curcontext skip-chars skip
[[ -z $skip ]] && skip=0
case $wordstyle in
(shell) local bufwords
# This splits the line into words as the shell understands them.
bufwords=(${(z)LBUFFER})
# Work around bug: if stripping quotes failed, a bogus
# space is appended. Not a good test, since this may
# be a quoted space, but it's hard to get right.
wordpat1=${bufwords[-1]}
if [[ ${wordpat1[-1]} = ' ' ]]; then
wordpat1=${(q)wordpat1[1,-2]}
else
wordpat1="${(q)wordpat1}"
fi
# Take substring of RBUFFER to skip over $skip characters
# from the cursor position.
bufwords=(${(z)RBUFFER[1+$skip,-1]})
# Work around bug again.
wordpat2=${bufwords[1]}
if [[ ${wordpat2[-1]} = ' ' ]]
then
wordpat2=${(q)wordpat2[1,-2]}
else
wordpat2="${(q)wordpat2}"
fi
spacepat='[[:space:]]#'
;;
(*space) spacepat='[[:space:]]#'
wordpat1='[^[:space:]]##'
wordpat2=$wordpat1
;;
(*) local wc
# See if there is a character class.
if zstyle -s $curcontext word-class wc; then
# Treat as a character class: do minimal quoting.
wc=${wc//(#m)[\'\"\`\$\(\)\^]/\\$MATCH}
else
# See if there is a local version of $WORDCHARS.
zstyle -s $curcontext word-chars wc ||
wc=$WORDCHARS
if [[ $wc = (#b)(?*)-(*) ]]; then
# We need to bring any `-' to the front to avoid confusing
# character classes... we get away with `]' since in zsh
# this isn't a pattern character if it's quoted.
wc=-$match[1]$match[2]
fi
wc="${(q)wc}"
fi
# Quote $wc where necessary, because we don't want those
# characters to be considered as pattern characters later on.
if [[ $wordstyle = *specified ]]; then
if [[ $wordstyle != un* ]]; then
# The given set of characters are the word characters, nothing else
wordpat1="[${wc}]##"
# anything else is a space.
spacepat="[^${wc}]#"
else
# The other way round.
wordpat1="[^${wc}]##"
spacepat="[${wc}]#"
fi
else
# Normal: similar, but add alphanumerics.
wordpat1="[${wc}[:alnum:]]##"
spacepat="[^${wc}[:alnum:]]#"
fi
wordpat2=$wordpat1
;;
esac
# The eval makes any special characters in the parameters active.
# In particular, we need the surrounding `[' s to be `real'.
# This is why we quoted the wordpats in the `shell' option, where
# they have to be treated as literal strings at this point.
match=()
eval pat1='${LBUFFER%%(#b)('${wordpat1}')('${spacepat}')}'
word1=$match[1]
ws1=$match[2]
match=()
charskip=
repeat $skip charskip+=\?
eval pat2='${RBUFFER##(#b)('${charskip}${spacepat}')('\
${wordpat2}')('${spacepat}')}'
ws2=$match[1]
word2=$match[2]
ws3=$match[3]
matched_words=("$pat1" "$word1" "$ws1" "$ws2" "$word2" "$ws3" "$pat2")

View file

@ -1,3 +1,22 @@
emulate -L zsh
setopt extendedglob
local opt keys
integer stat
while getopts "k:" opt; do
case $opt in
(k)
keys=$OPTARG
;;
(*)
return 1
;;
esac
done
(( OPTIND > 1 )) && shift $(( OPTIND - 1 ))
local savelbuffer=$LBUFFER saverbuffer=$RBUFFER
local savepredisplay=$PREDISPLAY savepostdisplay=$POSTDISPLAY
@ -7,10 +26,15 @@ PREDISPLAY="$PREDISPLAY$savelbuffer$saverbuffer$POSTDISPLAY
${1:-? }"
POSTDISPLAY=
zle recursive-edit
integer stat=$?
(( stat )) || REPLY=$BUFFER
if [[ -n $keys ]]; then
zle -R
read -k $keys
stat=$?
else
zle recursive-edit
stat=$?
(( stat )) || REPLY=$BUFFER
fi
LBUFFER=$savelbuffer
RBUFFER=$saverbuffer

View file

@ -0,0 +1,88 @@
emulate -L zsh
setopt extendedglob
local -a word_functions
word_functions=(backward-kill-word backward-word
capitalize-word down-case-word
forward-word kill-word
transpose-words up-case-word)
[[ -z $1 ]] && autoload read-from-minibuffer
local REPLY detail f
if ! zle -l $word_functions[1]; then
for f in $word_functions; do
autoload -U $f-match
zle -N $f $f-match
done
fi
while true; do
if [[ -n $WIDGET && -z $1 ]]; then
read-from-minibuffer -k1 "Word styles (hit return for more detail):
(b)ash (n)ormal (s)hell (w)hitespace (N)one (A)bort
${detail}? " || return 1
else
REPLY=$1
fi
detail=
case $REPLY in
(b*)
# bash style
zstyle ':zle:*' word-style standard
zstyle ':zle:*' word-chars ''
;;
(n*)
# normal zsh style
zstyle ':zle:*' word-style standard
zstyle ':zle:*' word-chars "$WORDCHARS"
;;
(s*)
# shell command arguments or special tokens
zstyle ':zle:*' word-style shell
;;
(w*)
# whitespace-delimited
zstyle ':zle:*' word-style space
;;
(d*)
# default: could also return widgets to builtins here
zstyle -d ':zle:*' word-style
zstyle -d ':zle:*' word-chars
;;
(q*)
# quit without setting
return 1
;;
(*)
detail="\
(b)ash: Word characters are alphanumerics only
(n)ormal: Word characters are alphanumerics plus \$WORDCHARS
(s)hell: Words are command arguments using shell syntax
(w)hitespace: Words are whitespace-delimited
(d)efault: Use default, no special handling (usually same as \`n')
(q)uit: Quit without setting a new style
"
if [[ -z $WIDGET || -n $1 ]]; then
print "Usage: $0 word-style
where word-style is one of the characters in parentheses:
$detail" >&2
return 1
fi
continue
;;
esac
return
done

View file

@ -0,0 +1,31 @@
# Transpose words, matching the words using match-words-by-style, q.v.
# The group of word characters preceeding the cursor (not necessarily
# immediately) are transposed with the group of word characters following
# the cursor (again, not necessarily immediately).
#
# Note the style skip-chars, used in the context of the current widget.
# This gives a number of character starting from the cursor position
# which are never considered part of a word and hence are always left
# alone. The default is 0 and typically the only useful alternative
# is one. This would have the effect that `fooXbar' with the cursor
# on X would be turned into `barXfoo' with the cursor still on the X,
# regardless of what the character X is.
autoload match-words-by-style
local curcontext=":zle:$WIDGET" skip
local -a matched_words
integer count=${NUMERIC:-1}
while (( count-- > 0 )); do
match-words-by-style
[[ -z "$matched_words[2]$matched_words[5]" ]] && return 1
LBUFFER="$matched_words[1]$matched_words[5]${(j..)matched_words[3,4]}\
$matched_words[2]"
RBUFFER="${(j..)matched_words[6,7]}"
done
return 0

View file

@ -0,0 +1,23 @@
emulate -L zsh
setopt extendedglob
autoload match-words-by-style
local curcontext=":zle:$WIDGET" word
local -a matched_words
integer count=${NUMERIC:-1}
while (( count-- > 0 )); do
match-words-by-style
word=${(j..)matched_words[4,5]}
if [[ -n $word ]]; then
LBUFFER+=${(U)word}
RBUFFER=${(j..)matched_words[6,7]}
else
return 1
fi
done
return 0