about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog16
-rw-r--r--Doc/Zsh/contrib.yo163
-rw-r--r--Functions/Zle/backward-kill-word-match36
-rw-r--r--Functions/Zle/backward-word-match29
-rw-r--r--Functions/Zle/capitalize-word-match23
-rw-r--r--Functions/Zle/down-case-word-match23
-rw-r--r--Functions/Zle/forward-word-match39
-rw-r--r--Functions/Zle/kill-word-match36
-rw-r--r--Functions/Zle/match-words-by-style167
-rw-r--r--Functions/Zle/read-from-minibuffer32
-rw-r--r--Functions/Zle/select-word-style88
-rw-r--r--Functions/Zle/transpose-words-match31
-rw-r--r--Functions/Zle/up-case-word-match23
13 files changed, 679 insertions, 27 deletions
diff --git a/ChangeLog b/ChangeLog
index c0de1ae7f..7a0881f09 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo
index 3dd398634..7865d7a89 100644
--- a/Doc/Zsh/contrib.yo
+++ b/Doc/Zsh/contrib.yo
@@ -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:
-
-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)
+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(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.
diff --git a/Functions/Zle/backward-kill-word-match b/Functions/Zle/backward-kill-word-match
new file mode 100644
index 000000000..77ad7bf1a
--- /dev/null
+++ b/Functions/Zle/backward-kill-word-match
@@ -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
diff --git a/Functions/Zle/backward-word-match b/Functions/Zle/backward-word-match
new file mode 100644
index 000000000..bda10d1c4
--- /dev/null
+++ b/Functions/Zle/backward-word-match
@@ -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
diff --git a/Functions/Zle/capitalize-word-match b/Functions/Zle/capitalize-word-match
new file mode 100644
index 000000000..aa25b8e02
--- /dev/null
+++ b/Functions/Zle/capitalize-word-match
@@ -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
diff --git a/Functions/Zle/down-case-word-match b/Functions/Zle/down-case-word-match
new file mode 100644
index 000000000..87d543f8d
--- /dev/null
+++ b/Functions/Zle/down-case-word-match
@@ -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
diff --git a/Functions/Zle/forward-word-match b/Functions/Zle/forward-word-match
new file mode 100644
index 000000000..65bed784d
--- /dev/null
+++ b/Functions/Zle/forward-word-match
@@ -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
diff --git a/Functions/Zle/kill-word-match b/Functions/Zle/kill-word-match
new file mode 100644
index 000000000..9d21c4a15
--- /dev/null
+++ b/Functions/Zle/kill-word-match
@@ -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
diff --git a/Functions/Zle/match-words-by-style b/Functions/Zle/match-words-by-style
new file mode 100644
index 000000000..9dcc165a9
--- /dev/null
+++ b/Functions/Zle/match-words-by-style
@@ -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")
diff --git a/Functions/Zle/read-from-minibuffer b/Functions/Zle/read-from-minibuffer
index 93eec42a5..6af7f2a39 100644
--- a/Functions/Zle/read-from-minibuffer
+++ b/Functions/Zle/read-from-minibuffer
@@ -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
diff --git a/Functions/Zle/select-word-style b/Functions/Zle/select-word-style
new file mode 100644
index 000000000..288517ef1
--- /dev/null
+++ b/Functions/Zle/select-word-style
@@ -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
diff --git a/Functions/Zle/transpose-words-match b/Functions/Zle/transpose-words-match
new file mode 100644
index 000000000..52891b6ac
--- /dev/null
+++ b/Functions/Zle/transpose-words-match
@@ -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
diff --git a/Functions/Zle/up-case-word-match b/Functions/Zle/up-case-word-match
new file mode 100644
index 000000000..781290332
--- /dev/null
+++ b/Functions/Zle/up-case-word-match
@@ -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