summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog8
-rw-r--r--Doc/Zsh/zle.yo46
-rw-r--r--Src/Zle/iwidgets.list6
-rw-r--r--Src/Zle/textobjects.c321
-rw-r--r--Src/Zle/zle.mdd3
-rw-r--r--Src/Zle/zle_keymap.c6
-rw-r--r--Test/X02zlevi.ztst39
7 files changed, 426 insertions, 3 deletions
diff --git a/ChangeLog b/ChangeLog
index d88596e57..9e1b0206e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2014-11-21 Oliver Kiddle <opk@zsh.org>
+
+ * 33730: Doc/Zsh/zle.yo, Src/Zle/iwidgets.list,
+ Src/Zle/textobjects.c, Src/Zle/zle.mdd, Src/Zle/zle_keymap.c,
+ Test/X02zlevi.ztst: vim style text objects for selecting words
+
2014-11-21 Peter Stephenson <p.stephenson@samsung.com>
* Sebastien Alaiwan: 33728: Completion/Unix/Command/_bzr:
@@ -20,7 +26,7 @@
2014-11-17 Oliver Kiddle <opk@zsh.org>
* 33704: Doc/Zsh/zle.yo, Src/Zle/zle_bindings.c,
- Src/Zle/zle_keX4aymap.c, Src/Zle/zle_refresh.c, Src/Zle/zle_vi.c,
+ Src/Zle/zle_keymap.c, Src/Zle/zle_refresh.c, Src/Zle/zle_vi.c,
Test/X02zlevi.ztst, Test/comptest: key bindings, documentation,
tests and minor fixes for vim style visual selection changes
diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo
index aa7ff4b57..f9dcd8016 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 26182974a..1a664e5e8 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 000000000..7f049c5dd
--- /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 c6e4d11c2..dd69eff2c 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 216e302d0..30d25ebaa 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 94afb60eb..6b7ca567e 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