From 1e57c42f470bdd2ab6179ec44dae96fd3377a1dd Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Fri, 28 Mar 2003 11:34:07 +0000 Subject: 18394: New word movement and editing widgets. --- Functions/Zle/backward-kill-word-match | 36 +++++++ Functions/Zle/backward-word-match | 29 ++++++ Functions/Zle/capitalize-word-match | 23 +++++ Functions/Zle/down-case-word-match | 23 +++++ Functions/Zle/forward-word-match | 39 ++++++++ Functions/Zle/kill-word-match | 36 +++++++ Functions/Zle/match-words-by-style | 167 +++++++++++++++++++++++++++++++++ Functions/Zle/read-from-minibuffer | 32 ++++++- Functions/Zle/select-word-style | 88 +++++++++++++++++ Functions/Zle/transpose-words-match | 31 ++++++ Functions/Zle/up-case-word-match | 23 +++++ 11 files changed, 523 insertions(+), 4 deletions(-) create mode 100644 Functions/Zle/backward-kill-word-match create mode 100644 Functions/Zle/backward-word-match create mode 100644 Functions/Zle/capitalize-word-match create mode 100644 Functions/Zle/down-case-word-match create mode 100644 Functions/Zle/forward-word-match create mode 100644 Functions/Zle/kill-word-match create mode 100644 Functions/Zle/match-words-by-style create mode 100644 Functions/Zle/select-word-style create mode 100644 Functions/Zle/transpose-words-match create mode 100644 Functions/Zle/up-case-word-match (limited to 'Functions') 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: +# +# +# +# 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 if it is whitespace, else in +# . 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 -- cgit 1.4.1