From 937dc9a84d85bb1ca5135badd52461a928c17f6a Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Fri, 15 Dec 2006 11:44:26 +0000 Subject: users/11083: functions for handling shell arguments in ZLE --- ChangeLog | 7 +++++ Doc/Zsh/contrib.yo | 54 ++++++++++++++++++++++++++++++++ Functions/Zle/.distfiles | 42 +++++++++++++------------ Functions/Zle/modify-current-argument | 51 ++++++++++++++++++++++++++++++ Functions/Zle/split-shell-arguments | 58 +++++++++++++++++++++++++++++++++++ 5 files changed, 193 insertions(+), 19 deletions(-) create mode 100644 Functions/Zle/modify-current-argument create mode 100644 Functions/Zle/split-shell-arguments diff --git a/ChangeLog b/ChangeLog index a15e690e9..b38c4099b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2006-12-15 Peter Stephenson + + * users/11083: Doc/Zsh/contrib.yo, Functions/Zle/.distfiles, + Functions/modify-current-argument, + Functions/split-shell-arguments: functions for handling spliting + of shell arguments in ZLE. + 2006-12-13 Peter Stephenson * 23052: Doc/Zsh/builtins.yo, Src/subst.c: multibyte diff --git a/Doc/Zsh/contrib.yo b/Doc/Zsh/contrib.yo index bb03fa68a..e6a69ecd7 100644 --- a/Doc/Zsh/contrib.yo +++ b/Doc/Zsh/contrib.yo @@ -1142,6 +1142,60 @@ investigate the command word found. The default is tt(whence -c). ) 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 diff --git a/Functions/Zle/.distfiles b/Functions/Zle/.distfiles index 38914d723..4f01a8c01 100644 --- a/Functions/Zle/.distfiles +++ b/Functions/Zle/.distfiles @@ -1,24 +1,28 @@ DISTFILES_SRC=' .distfiles -backward-kill-word-match backward-word-match -capitalize-word-match copy-earlier-word -cycle-completion-positions define-composed-chars -delete-whole-word-match down-case-word-match -down-line-or-beginning-search edit-command-line -forward-word-match history-beginning-search-menu -history-pattern-search history-search-end -incarg incremental-complete-word -insert-composed-char insert-files -insert-unicode-char keeper -keymap+widget kill-word-match +backward-kill-word-match backward-word-match +capitalize-word-match copy-earlier-word +cycle-completion-positions define-composed-chars +delete-whole-word-match down-case-word-match +down-line-or-beginning-search edit-command-line +forward-word-match history-beginning-search-menu +history-pattern-search history-search-end +incarg incremental-complete-word +insert-composed-char insert-files +insert-unicode-char keeper +keymap+widget kill-word-match match-word-context -match-words-by-style narrow-to-region -narrow-to-region-invisible predict-on -quote-and-complete-word read-from-minibuffer -replace-string select-word-style +match-words-by-style +modify-current-argument +narrow-to-region +narrow-to-region-invisible predict-on +quote-and-complete-word read-from-minibuffer +replace-string select-word-style replace-string-again -smart-insert-last-word transpose-words-match -up-case-word-match up-line-or-beginning-search -url-quote-magic which-command -zed-set-file-name +smart-insert-last-word +split-shell-arguments +transpose-words-match +up-case-word-match up-line-or-beginning-search +url-quote-magic which-command +zed-set-file-name ' diff --git a/Functions/Zle/modify-current-argument b/Functions/Zle/modify-current-argument new file mode 100644 index 000000000..be244ccc1 --- /dev/null +++ b/Functions/Zle/modify-current-argument @@ -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 )) diff --git a/Functions/Zle/split-shell-arguments b/Functions/Zle/split-shell-arguments new file mode 100644 index 000000000..e8a231732 --- /dev/null +++ b/Functions/Zle/split-shell-arguments @@ -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 -- cgit 1.4.1