From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 10468 invoked by alias); 12 May 2015 21:01:43 -0000 Mailing-List: contact zsh-workers-help@zsh.org; run by ezmlm Precedence: bulk X-No-Archive: yes List-Id: Zsh Workers List List-Post: List-Help: X-Seq: 35093 Received: (qmail 25563 invoked from network); 12 May 2015 21:01:40 -0000 X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on f.primenet.com.au X-Spam-Level: X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FROM,RCVD_IN_DNSWL_NONE autolearn=ham version=3.3.2 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yahoo.co.uk; s=s2048; t=1431464071; bh=IGdn81zJ389LzKOEKE9WHsNkDF6eStnWpDrtiL/Ym7Y=; h=From:To:Subject:Date:From:Subject; b=ju55gnm4PNqiEIsxJpUOsMcTTtMNkPU0UP9J9tWVx4HBf3CrLjhbSXP9xMN6jpFTXciN8DtXj5INegbYo7CI9tJd6DJWePvM3NH7nrnNpDX+d2HnLcEGQWub0yHTm/HExfMH1DjHvip8IYmIBy8pJ6QTYCZ4iR8HlJHLVO22gqx4lJaG+aY5O0LYu2BotLaHlPlo08a+4r5SLF7Dht267kEdCmK8TKekupfAKbM0DoVh6zpRqWRkLMusY+x+HT8RxN+lz+ULyyBZjIK6IbH9lU6xnvWCXGwaAcgocpv4cXMKt3C6mlMlTy5HmJGiHq+BxlODA+S5vlAt4S8l6vlBbQ== X-Yahoo-Newman-Id: 806262.24627.bm@smtp139.mail.ir2.yahoo.com X-Yahoo-Newman-Property: ymail-3 X-YMail-OSG: tpGh19sVM1ll2CqbbAkmAvXPK3uTwLiQh45851tUp5c8dR. J2dSfdpM.eSgOiJ9o3y7uTaMFvYrCqwdVH7sfZi82sWLbsk5N2PJZktjOyRo MwE0AZFjeF6WJxqIsT4Kdvmjq5JIpyRECoLADf3WIMomGMzmxyLQN4TGhazE mD0R7o0ks4hlF824sG1lmB_KWXXlClKwiv.YSk5xI4LEh8xYaytdpnH0WMyW 1r5zgcaYbxFFUqpILMvMiBaK__BrAq0.U._2oyCGbjHv8tWee67JBz5p2FGk HSgl0b8DUMwsCbC_.dkMLCiiwTVAkFFFQxGAzHK_67PP96olzRUFtoyUa4G_ aW5t.YmTfHFAzWOkHffIrWesFcwCZt.ZD3F5JeBKaiBu3H529M_KcJdrGZWF n.Fbw2LYKGdPFlvMb7No3XN4XLwtccfAlK20bemrK3o6vTz1u79CS2dnUyKT UV5SacSaiduPseqpk4kbIA8jJYI9zqrRMoplEcFGB1rPpMtOcyP.QtEFWgHd qQ5_cBX4B9Wmcxl9ZgSie7FcfjrejnJUY X-Yahoo-SMTP: opAkk_CswBAce_kJ3nIPlH80cJI- From: Oliver Kiddle To: Zsh workers Subject: PATCH: vi mode text objects for brackets/quotes MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Date: Tue, 12 May 2015 22:54:30 +0200 Message-ID: <3600.1431464070@quattro> This adds two shell function implementations of Vim-style text objects. select-bracketed is for matching characters between matching brackets and select-quoted matches between quote characters. It'll also work with other delimiter characters such as perhaps a comma. The functionality of both of these is builtin to vim (i.e. not a plugin). A third function, surround, is like the popular vim plugin for adding, removing or deleting "surroundings", i.e. quotes or brackets. I had hoped that these would serve as useful examples but trying to get good Vim compatibility in subtle situations resulted in them becoming more complicated. I've not written any documentation other than comments at the top of the files but this does add two NEWS entries covering the vi-mode changes since 5.0.7. Oliver diff --git a/NEWS b/NEWS index 86b0bd4..55b1be7 100644 --- a/NEWS +++ b/NEWS @@ -24,6 +24,15 @@ Changes from 5.0.7 to 5.0.8 as a reference to a variable, e.g. ${(ps.$sep.)foo} to split $foo on a string given by $sep. +- The default binding of 'u' in vi command mode has changed to undo + multiple changes when invoked repeatedly. '^R' is now bound to redo + changes. To revert to toggling of the last edit use: + bindkey -a u vi-undo-change + +- Compatibility with Vim has been improved for vi editing mode. Most + notably, Vim style text objects are supported and the region can be + manipulated with vi commands in the same manner as Vim's visual mode. + - Elements of the watch variable may now be patterns. - The logic for retrying history locking has been improved. diff --git a/Functions/Zle/select-bracketed b/Functions/Zle/select-bracketed new file mode 100644 index 0000000..00f51be --- /dev/null +++ b/Functions/Zle/select-bracketed @@ -0,0 +1,56 @@ +# Text object for matching characters between matching pairs of brackets +# +# So for example, given (( i+1 )), the vi command ci( will change +# all the text between matching colons. +# +# The following is an example of how to enable this: +# autoload -U select-bracketed +# zle -N select-bracketed +# for m in visual viopp; do +# for c in {a,i}${(s..)^:-'()[]{}<>bB'}; do +# bindkey -M $m $c select-bracketed +# done +# done + +local style=${${1:-$KEYS}[1]} matching="(){}[]<>bbBB" +local -i find=${NUMERIC:-1} idx=${matching[(I)[${${1:-$KEYS}[2]}]]}%9 +(( idx )) || return 1 # no corresponding closing bracket +local lmatch=${matching[1 + (idx-1) & ~1]} +local rmatch=${matching[1 + (idx-1) | 1]} +local -i start=CURSOR+1 end=CURSOR+1 rfind=find + +[[ $BUFFER[start] = "$rmatch" ]] && (( start--, end-- )) +if (( REGION_ACTIVE && MARK != CURSOR)); then + (( MARK < CURSOR && (start=end=MARK+1) )) + local -i origstart=start-1 + [[ $style = i ]] && (( origstart-- )) +fi + +while (( find )); do + for (( ; find && start; --start )); do + case $BUFFER[start] in + "$lmatch") (( find-- )) ;; + "$rmatch") (( find++ )) ;; + esac + done + + (( find )) && return 1 # opening bracket not found + + while (( rfind && end++ < $#BUFFER )); do + case $BUFFER[end] in + "$lmatch") (( rfind++ )) ;; + "$rmatch") (( rfind-- )) ;; + esac + done + + (( rfind )) && return 1 # closing bracket not found + + (( REGION_ACTIVE && MARK != CURSOR && start >= origstart && + ( find=rfind=${NUMERIC:-1} ) )) +done + +[[ $style = i ]] && (( start++, end-- )) +(( REGION_ACTIVE = !!REGION_ACTIVE )) +[[ $KEYMAP = vicmd ]] && (( REGION_ACTIVE && end-- )) +MARK=$start +CURSOR=$end diff --git a/Functions/Zle/select-quoted b/Functions/Zle/select-quoted new file mode 100644 index 0000000..904f1e4 --- /dev/null +++ b/Functions/Zle/select-quoted @@ -0,0 +1,71 @@ +# Text object for matching characters between a particular delimiter +# +# So for example, given "text", the vi command vi" will select +# all the text between the double quotes +# +# The following is an example of how to enable this: +# autoload -U select-quoted +# zle -N select-quoted +# for m in visual viopp; do +# for c in {a,i}{\',\",\`}; do +# bindkey -M $m $c select-quoted +# done +# done + +setopt localoptions noksharrays + +local matching=${${1:-$KEYS}[2]} +local -i start=CURSOR+2 end=CURSOR+2 found=0 alt=0 count=0 + +if ((REGION_ACTIVE )); then + if (( MARK < CURSOR )); then + start=MARK+2 + else + end=MARK+2 + fi +fi + +[[ $BUFFER[CURSOR+1] = $matching && $BUFFER[CURSOR] != \\ ]] && count=1 +while (( (count || ! alt) && --start )) && [[ $BUFFER[start] != $'\n' ]]; do + if [[ $BUFFER[start] = "$matching" ]]; then + if [[ $BUFFER[start-1] = \\ ]]; then + (( start-- )) + elif (( ! found )); then + found=start + else + (( ! alt )) && alt=start + (( count && ++count )) + fi + fi +done + +for (( start=CURSOR+2; ! found && start+1 < $#BUFFER; start++ )); do + case $BUFFER[start] in + $'\n') return 1 ;; + \\) (( start++ )) ;; + "$matching") + (( end=start+1, found=start )) + ;; + esac +done + +[[ $BUFFER[end-1] = \\ ]] && (( end++ )) +until [[ $BUFFER[end] == "$matching" ]]; do + [[ $BUFFER[end] = \\ ]] && (( end++ )) + if [[ $BUFFER[end] = $'\n' ]] || (( ++end > $#BUFFER )); then + end=0 + break + fi +done + +if (( alt && (!end || count == 2) )); then + end=found + found=alt +fi +(( end )) || return 1 + +[[ ${${1:-$KEYS}[1]} = a ]] && (( found-- )) || (( end-- )) +(( REGION_ACTIVE = !!REGION_ACTIVE )) +[[ $KEYMAP = vicmd ]] && (( REGION_ACTIVE && end-- )) +MARK=found +CURSOR=end diff --git a/Functions/Zle/surround b/Functions/Zle/surround new file mode 100644 index 0000000..b7be30b --- /dev/null +++ b/Functions/Zle/surround @@ -0,0 +1,75 @@ +# Implementation of some functionality of the popular vim surround plugin. +# Allows "surroundings" to be changed: parentheses, brackets and quotes. + +# To use +# autoload -Uz surround +# zle -N delete-surround surround +# zle -N add-surround surround +# zle -N change-surround surround +# bindkey -a cs change-surround +# bindkey -a ds delete-surround +# bindkey -a ys add-surround +# bindkey -M visual S add-surround +# +# This doesn't allow yss to operate on a line but VS will work + +setopt localoptions noksharrays +autoload -Uz select-quoted select-bracketed +local before after +local -A matching +matching=( \( \) \{ \} \< \> \[ \] ) + +case $WIDGET in + change-*) + local MARK="$MARK" CURSOR="$CURSOR" call + read -k 1 before + if [[ ${(kvj::)matching} = *$before* ]]; then + call=select-bracketed + else + call=select-quoted + fi + read -k 1 after + $call "a$before" || return 1 + before="$after" + if [[ -n $matching[$before] ]]; then + after=" $matching[$before]" + before+=' ' + elif [[ -n $matching[(r)[$before:q]] ]]; then + before="${(k)matching[(r)[$before:q]]}" + fi + BUFFER[CURSOR]="$after" + BUFFER[MARK+1]="$before" + CURSOR=MARK + ;; + delete-*) + local MARK="$MARK" CURSOR="$CURSOR" call + read -k 1 before + if [[ ${(kvj::)matching} = *$before* ]]; then + call=select-bracketed + else + call=select-quoted + fi + if $call "a$before"; then + BUFFER[CURSOR]='' + BUFFER[MARK+1]='' + CURSOR=MARK + fi + ;; + add-*) + local save_cut="$CUTBUFFER" + zle .vi-change || return + local save_cur="$CURSOR" + zle .vi-cmd-mode + read -k 1 before + after="$before" + if [[ -n $matching[$before] ]]; then + after=" $matching[$before]" + before+=' ' + elif [[ -n $matching[(r)[$before:q]] ]]; then + before="${(k)matching[(r)[$before:q]]}" + fi + CUTBUFFER="$before$CUTBUFFER$after" + zle .vi-put-after -n 1 + CUTBUFFER="$save_cut" CURSOR="$save_cur" + ;; +esac