From 58da0f495cdf2bbef6a7043f5f06c77991c79a9e Mon Sep 17 00:00:00 2001 From: Oliver Kiddle Date: Fri, 21 Nov 2014 01:08:25 +0100 Subject: 33730: vim style text objects for selecting words --- ChangeLog | 8 +- Doc/Zsh/zle.yo | 46 +++++++- Src/Zle/iwidgets.list | 6 + Src/Zle/textobjects.c | 321 ++++++++++++++++++++++++++++++++++++++++++++++++++ Src/Zle/zle.mdd | 3 +- Src/Zle/zle_keymap.c | 6 + Test/X02zlevi.ztst | 39 ++++++ 7 files changed, 426 insertions(+), 3 deletions(-) create mode 100644 Src/Zle/textobjects.c diff --git a/ChangeLog b/ChangeLog index d88596e57..9e1b0206e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2014-11-21 Oliver Kiddle + + * 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 * Sebastien Alaiwan: 33728: Completion/Unix/Command/_bzr: @@ -20,7 +26,7 @@ 2014-11-17 Oliver Kiddle * 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 @@ -380,6 +380,45 @@ zletest $'- word word\e4|2daw' 0:delete all word with numeric argument >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 -- cgit 1.4.1