about summary refs log tree commit diff
path: root/Src/Zle
diff options
context:
space:
mode:
authorOliver Kiddle <opk@zsh.org>2014-11-21 01:08:25 +0100
committerOliver Kiddle <opk@zsh.org>2014-11-21 11:40:00 +0100
commit58da0f495cdf2bbef6a7043f5f06c77991c79a9e (patch)
tree76a1b58d876f394eab08cdbbb6f66251e235895a /Src/Zle
parent0151ab07491f6a0532d3c283752ad864252fc654 (diff)
downloadzsh-58da0f495cdf2bbef6a7043f5f06c77991c79a9e.tar.gz
zsh-58da0f495cdf2bbef6a7043f5f06c77991c79a9e.tar.xz
zsh-58da0f495cdf2bbef6a7043f5f06c77991c79a9e.zip
33730: vim style text objects for selecting words
Diffstat (limited to 'Src/Zle')
-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
4 files changed, 335 insertions, 1 deletions
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);