Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: PATCH: key bindings, fixes, docs, tests for vi stuff



On 18 Nov, "Jun T." wrote:
> Apparently 'daw' is not working.

With this patch, that test failure should now be fixed.

I'd recommend Emacs mode users at least look at the new
select-in-shell-word widget. I can imagine it might be useful.

It is bound to "ia" in the viopp and visual keymaps along with aa which
is similar.

This also adds the usual vim aw aW iw and iW bindings that can be used
together with vi operators (c d y p etc) or visual selection mode.
Vim has many more, most not useful from zsh (sentences, paragraphs etc).

The code for select-in-shell-word is based on that for the (z)
expansion. I was hoping it would shed some light on the lexer but, it
didn't really.

Oliver

diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo
index aa7ff4b..f9dcd80 100644
--- a/Doc/Zsh/zle.yo
+++ b/Doc/Zsh/zle.yo
@@ -1975,7 +1975,7 @@ When a previous completion displayed a list below the prompt, this
 widget can be used to move the prompt below the list.
 )
 enditem()
-texinode(Miscellaneous)()(Completion)(Zle Widgets)
+texinode(Miscellaneous)(Text Objects)(Completion)(Zle Widgets)
 subsect(Miscellaneous)
 startitem()
 tindex(accept-and-hold)
@@ -2333,6 +2333,50 @@ If the last command executed was a digit as part of an argument,
 continue the argument.  Otherwise, execute vi-beginning-of-line.
 )
 enditem()
+texinode(Text Objects)()(Miscellaneous)(Zle Widgets)
+subsect(Text Objects)
+cindex(text objects)
+Text objects are commands that can be used to select a block of text
+according to some criteria. They are a feature of the vim text editor
+and so are primarily intended for use with vi operators or from visual
+selection mode. However, they can also be used from vi-insert or emacs
+mode. Key bindings listed below apply to the tt(viopp) and tt(visual)
+keymaps.
+
+startitem()
+tindex(select-a-blank-word)
+item(tt(select-a-blank-word) (aW))(
+Select a word including adjacent blanks, where a word is defined as a
+series of non-blank characters. With a numeric argument, multiple words
+will be selected.
+)
+tindex(select-a-shell-word)
+item(tt(select-a-shell-word) (aa))(
+Select the current command argument applying the normal rules for
+quoting.
+)
+tindex(select-a-word)
+item(tt(select-a-word) (aw))(
+Select a word including adjacent blanks, using the normal vi-style word
+definition. With a numeric argument, multiple words will be selected.
+)
+tindex(select-in-blank-word)
+item(tt(select-in-blank-word) (iW))(
+Select a word, where a word is defined as a series of non-blank
+characters. With a numeric argument, multiple words will be selected.
+)
+tindex(select-in-shell-word)
+item(tt(select-in-shell-word) (ia))(
+Select the current command argument applying the normal rules for
+quoting. If the argument begins and ends with matching quote characters,
+these are not included in the selection.
+)
+tindex(select-in-word)
+item(tt(select-in-word) (iw))(
+Select a word, using the normal vi-style word definition. With a numeric
+argument, multiple words will be selected.
+)
+enditem()
 
 texinode(Character Highlighting)()(Zle Widgets)(Zsh Line Editor)
 sect(Character Highlighting)
diff --git a/Src/Zle/iwidgets.list b/Src/Zle/iwidgets.list
index 2618297..1a664e5 100644
--- a/Src/Zle/iwidgets.list
+++ b/Src/Zle/iwidgets.list
@@ -100,6 +100,12 @@
 "reset-prompt", resetprompt, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
 "reverse-menu-complete", reversemenucomplete, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_ISCOMP
 "run-help", processcmd, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL
+"select-a-word", selectword, ZLE_KEEPSUFFIX
+"select-in-word", selectword, ZLE_KEEPSUFFIX
+"select-a-blank-word", selectword, ZLE_KEEPSUFFIX
+"select-in-blank-word", selectword, ZLE_KEEPSUFFIX
+"select-a-shell-word", selectargument, ZLE_KEEPSUFFIX
+"select-in-shell-word", selectargument, ZLE_KEEPSUFFIX
 "self-insert", selfinsert, ZLE_MENUCMP | ZLE_KEEPSUFFIX
 "self-insert-unmeta", selfinsertunmeta, ZLE_MENUCMP | ZLE_KEEPSUFFIX
 "send-break", sendbreak, 0
diff --git a/Src/Zle/textobjects.c b/Src/Zle/textobjects.c
new file mode 100644
index 0000000..7f049c5
--- /dev/null
+++ b/Src/Zle/textobjects.c
@@ -0,0 +1,321 @@
+/*
+ * textobjects.c - ZLE module implementing Vim style text objects
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 2014 Oliver Kiddle
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Oliver Kiddle or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Oliver Kiddle and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Oliver Kiddle and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Oliver Kiddle and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zle.mdh"
+#include "textobjects.pro"
+
+/* class of character: 0 is whitespace, 1 is word character, 2 is other */
+static int
+wordclass(ZLE_CHAR_T x)
+{
+    return (ZC_iblank(x) ? 0 : ((ZC_ialnum(x) || (ZWC('_') == x)) ? 1 : 2));
+}
+
+static int
+blankwordclass(ZLE_CHAR_T x)
+{
+    return (ZC_iblank(x) ? 0 : 1);
+}
+
+/**/
+int
+selectword(UNUSED(char **args))
+{
+    int n = zmult;
+    int all = (bindk == t_selectaword || bindk == t_selectablankword);
+    int (*viclass)(ZLE_CHAR_T) = (bindk == t_selectaword ||
+	    bindk == t_selectinword) ? wordclass : blankwordclass;
+    int sclass = viclass(zleline[zlecs]);
+    int doblanks = all && sclass;
+
+    if (!invicmdmode()) {
+	region_active = 1;
+	mark = zlecs;
+    }
+    if (!region_active || zlecs == mark) {
+	/* search back to first character of same class as the start position
+	 * also stop at the beginning of the line */
+	mark = zlecs;
+	while (mark) {
+	    int pos = mark;
+	    DECPOS(pos);
+	    if (zleline[pos] == ZWC('\n') || viclass(zleline[pos]) != sclass)
+		break;
+	    mark = pos;
+	}
+	/* similarly scan forward over characters of the same class */
+	while (zlecs < zlell) {
+	    INCCS();
+	    int pos = zlecs;
+	    /* single newlines within blanks are included */
+	    if (all && !sclass && pos < zlell && zleline[pos] == ZWC('\n'))
+		INCPOS(pos);
+
+	    if (zleline[pos] == ZWC('\n') || viclass(zleline[pos]) != sclass)
+		break;
+	}
+
+	if (all) {
+	    int nclass = viclass(zleline[zlecs]);
+	    /* if either start or new position is blank advance over
+	     * a new block of characters of a common type */
+	    if (!nclass || !sclass) {
+		while (zlecs < zlell) {
+		    INCCS();
+		    if (zleline[zlecs] == ZWC('\n') ||
+			    viclass(zleline[zlecs]) != nclass)
+			break;
+		}
+		if (n < 2)
+		    doblanks = 0;
+	    }
+	}
+    } else {
+	/* For visual mode, advance one char so repeated
+	 * invocations select subsequent words */
+	if (zlecs > mark) {
+	    if (zlecs < zlell)
+		INCCS();
+	} else if (zlecs)
+	    DECCS();
+	if (zlecs < mark) {
+	    /* visual mode with the cursor before the mark: move cursor back */
+	    while (n-- > 0) {
+		int pos = zlecs;
+		/* first over blanks */
+		if (all && (!viclass(zleline[pos]) ||
+			zleline[pos] == ZWC('\n'))) {
+		    all = 0;
+		    while (pos) {
+			DECPOS(pos);
+			if (zleline[pos] == ZWC('\n'))
+			    break;
+			zlecs = pos;
+			if (viclass(zleline[pos]))
+			    break;
+		    }
+		} else if (zlecs && zleline[zlecs] == ZWC('\n')) {
+		    /* for in widgets pass over one newline */
+		    DECPOS(pos);
+		    if (zleline[pos] != ZWC('\n'))
+			zlecs = pos;
+		}
+		pos = zlecs;
+		sclass = viclass(zleline[zlecs]);
+		/* now retreat over non-blanks */
+		while (zleline[pos] != ZWC('\n') &&
+			viclass(zleline[pos]) == sclass) {
+		    zlecs = pos;
+		    if (!pos) {
+			zlecs = 0;
+			break;
+		    }
+		    DECPOS(pos);
+		}
+		/* blanks again but only if there were none first time */
+		if (all && zlecs) {
+		    pos = zlecs;
+		    DECPOS(pos);
+		    if (!viclass(zleline[pos])) {
+			while (pos) {
+			    DECPOS(pos);
+			    if (zleline[pos] == ZWC('\n') ||
+				    viclass(zleline[pos]))
+				break;
+			    zlecs = pos;
+			}
+		    }
+		}
+	    }
+	    return 0;
+	}
+	n++;
+	doblanks = 0;
+    }
+    region_active = !!region_active; /* force to character wise */
+
+    /* for each digit argument, advance over further block of one class */
+    while (--n > 0) {
+	if (zlecs < zlell && zleline[zlecs] == ZWC('\n'))
+	    INCCS();
+	sclass = viclass(zleline[zlecs]);
+	while (zlecs < zlell) {
+	    INCCS();
+	    if (zleline[zlecs] == ZWC('\n') ||
+		    viclass(zleline[zlecs]) != sclass)
+		break;
+	}
+	/* for 'a' widgets, advance extra block if either consists of blanks */
+	if (all) {
+	    if (zlecs < zlell && zleline[zlecs] == ZWC('\n'))
+		INCCS();
+	    if (!sclass || !viclass(zleline[zlecs]) ) {
+		sclass = viclass(zleline[zlecs]);
+		if (n == 1 && !sclass)
+		    doblanks = 0;
+		while (zlecs < zlell) {
+		    INCCS();
+		    if (zleline[zlecs] == ZWC('\n') ||
+			    viclass(zleline[zlecs]) != sclass)
+			break;
+		}
+	    }
+	}
+    }
+
+    /* if we didn't remove blanks at either end we remove some at the start */
+    if (doblanks) {
+	int pos = mark;
+	while (pos) {
+	    DECPOS(pos);
+	    /* don't remove blanks at the start of the line, i.e indentation */
+	    if (zleline[pos] == ZWC('\n'))
+		break;
+	    if (!ZC_iblank(zleline[pos])) {
+		INCPOS(pos);
+		mark = pos;
+		break;
+	    }
+	}
+    }
+    /* Adjustment: vi operators don't include the cursor position, in insert
+     * or emacs mode the region also doesn't but for vi visual mode it is
+     * included. */
+    if (zlecs && zlecs > mark && !virangeflag)
+	DECCS();
+
+    return 0;
+}
+
+/**/
+int
+selectargument(UNUSED(char **args))
+{
+    int ne = noerrs, ocs = zlemetacs;
+    int owb = wb, owe= we, oadx = addedx, ona = noaliases;
+    char *p;
+    int ll, cs;
+    char *linein;
+    int wend = 0, wcur = 0;
+    int n = zmult;
+    int *wstarts;
+    int tmpsz;
+
+    if (n < 1 || 2*n > zlell + 1)
+	return 1;
+
+    /* if used from emacs mode enable the region */
+    if (!invicmdmode()) {
+	region_active = 1;
+	mark = zlecs;
+    }
+
+    wstarts = (int *) zhalloc(n * sizeof(int));
+    memset(wstarts, 0, n * sizeof(int));
+
+    addedx = 0;
+    noerrs = 1;
+    lexsave();
+    lexflags = LEXFLAGS_ACTIVE;
+    linein = zlegetline(&ll, &cs);
+    zlemetall = ll;
+    zlemetacs = cs;
+
+    if (!isfirstln && chline) {
+       p = (char *) zhalloc(hptr - chline + zlemetall + 2);
+       memcpy(p, chline, hptr - chline);
+       memcpy(p + (hptr - chline), linein, ll);
+       p[(hptr - chline) + ll] = '\0';
+       inpush(p, 0, NULL);
+       zlemetacs += hptr - chline;
+    } else {
+       p = (char *) zhalloc(ll + 1);
+       memcpy(p, linein, ll);
+       p[ll] = '\0';
+       inpush(p, 0, NULL);
+    }
+    if (zlemetacs)
+       zlemetacs--;
+    strinbeg(0);
+    noaliases = 1;
+    do {
+       wstarts[wcur++] = wend;
+       wcur %= n;
+       ctxtlex();
+       if (tok == ENDINPUT || tok == LEXERR)
+           break;
+       wend = zlemetall - inbufct;
+    } while (tok != ENDINPUT && tok != LEXERR && wend <= zlemetacs);
+    noaliases = ona;
+    strinend();
+    inpop();
+    errflag = 0;
+    noerrs = ne;
+    lexrestore();
+    zlemetacs = ocs;
+    wb = owb;
+    we = owe;
+    addedx = oadx;
+
+    /* convert offsets for mark and zlecs back to ZLE internal format */
+    linein[wend] = '\0'; /* a bit of a hack to get two offsets */
+    free(stringaszleline(linein, wstarts[wcur], &zlecs, &tmpsz, &mark));
+
+    if (bindk == t_selectinshellword) {
+	ZLE_CHAR_T *match = ZWS("`\'\"");
+	ZLE_CHAR_T *lmatch = ZWS("\'({"), *rmatch = ZWS("\')}");
+	ZLE_CHAR_T *ematch = match, *found;
+	int start, end = zlecs;
+	/* for 'in' widget, don't include initial blanks ... */
+	while (mark < zlecs && ZC_iblank(zleline[mark]))
+	    INCPOS(mark);
+	/* ... or a matching pair of quotes */
+	start = mark;
+	if (zleline[start] == ZWC('$')) {
+	    match = lmatch;
+	    ematch = rmatch;
+	    INCPOS(start);
+	}
+	found = ZS_strchr(match, zleline[start]);
+	if (found) {
+	    DECPOS(end);
+	    if (zleline[end] == ematch[found-match]) {
+		zlecs = end;
+		INCPOS(start);
+		mark = start;
+	    }
+	}
+    }
+
+    /* Adjustment: vi operators don't include the cursor position */
+    if (!virangeflag)
+       DECCS();
+
+    return 0;
+}
diff --git a/Src/Zle/zle.mdd b/Src/Zle/zle.mdd
index c6e4d11..dd69eff 100644
--- a/Src/Zle/zle.mdd
+++ b/Src/Zle/zle.mdd
@@ -7,7 +7,8 @@ autofeatures="b:bindkey b:vared b:zle"
 
 objects="zle_bindings.o zle_hist.o zle_keymap.o zle_main.o \
 zle_misc.o zle_move.o zle_params.o zle_refresh.o \
-zle_thingy.o zle_tricky.o zle_utils.o zle_vi.o zle_word.o"
+zle_thingy.o zle_tricky.o zle_utils.o zle_vi.o zle_word.o \
+textobjects.o"
 
 headers="zle.h zle_things.h"
 
diff --git a/Src/Zle/zle_keymap.c b/Src/Zle/zle_keymap.c
index 216e302..30d25eb 100644
--- a/Src/Zle/zle_keymap.c
+++ b/Src/Zle/zle_keymap.c
@@ -1343,6 +1343,12 @@ default_bindings(void)
 	add_cursor_key(kptr, TCDOWNCURSOR, t_downline, 'B');
 	bindkey(kptr, "k", refthingy(t_upline), NULL);
 	bindkey(kptr, "j", refthingy(t_downline), NULL);
+	bindkey(kptr, "aa", refthingy(t_selectashellword), NULL);
+	bindkey(kptr, "ia", refthingy(t_selectinshellword), NULL);
+	bindkey(kptr, "aw", refthingy(t_selectaword), NULL);
+	bindkey(kptr, "iw", refthingy(t_selectinword), NULL);
+	bindkey(kptr, "aW", refthingy(t_selectablankword), NULL);
+	bindkey(kptr, "iW", refthingy(t_selectinblankword), NULL);
     }
     /* escape in operator pending cancels the operation */
     bindkey(oppmap, "\33", refthingy(t_vicmdmode), NULL);
diff --git a/Test/X02zlevi.ztst b/Test/X02zlevi.ztst
index 94afb60..6b7ca56 100644
--- a/Test/X02zlevi.ztst
+++ b/Test/X02zlevi.ztst
@@ -382,6 +382,45 @@
 >BUFFER: -
 >CURSOR: 0
 
+  zletest $'----    word    ----word\eo    \eo----\eodone\eh' \
+      'vhawmaawmbawmcawmdawmeawmfawmgv`ara`brb`crc$r$`drd`ere`frf`grg'
+0:all word with existing selection and cursor before mark
+>BUFFER: g---f   worde   ----dord
+>c  $
+>b---
+>aone
+>CURSOR: 0
+
+  zletest $'----    word    word----\e0lvlawmaawmbawmcawvrd`ara`brb`crc'
+0:all word with existing selection and mark before cursor
+>BUFFER: ----   aword   bworc---d
+>CURSOR: 19
+
+  zletest $' --ww  ww---\eo\eoww\evhiwiw' m{a,b,c,d,e}iw vrE \`{a,b,c,d,e}r.
+0:in word with existing selection and cursor before mark
+>BUFFER: E.-.w. .w.--
+>
+>ww
+>CURSOR: 1
+
+  zletest $'  --ww  ww--\eO  \ev0o' m{a,b,c,d,e}iw vrE \`{a,b,c,d,e}r.
+0:in word with existing selection and mark before cursor
+>BUFFER:  .
+> .-.w. .wE--
+>CURSOR: 10
+
+  zletest $'  `one`  $(echo two) " three " $\'four\'\C-v\tfive ${six:-6}\e' \
+      vaaom{a,b,c,d,e,f}v \`{a,b,c,d,e,f}rX
+0:all argument for different arguments
+>BUFFER: X `one`X $(echo two)X" three "X$'four'XfiveX${six:-6}
+>CURSOR: 0
+
+  zletest $'{ls `echo x`  $((3+4)) "a b" $\'\\t\\n\' ${d%/}\e' \
+      cia{6,5,4,3,2,1}$'\eBB'
+0:in argument for different arguments
+>BUFFER: 1ls `2`  $(3) "4" $'5' ${6}
+>CURSOR: 0
+
 %clean
 
   zmodload -ui zsh/zpty



Messages sorted by: Reverse Date, Date, Thread, Author