diff --git a/ChangeLog b/ChangeLog index 0a0bd36a9..d42fae3f1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,10 @@ 2015-06-18 Oliver Kiddle + * 35474, 35492: Doc/Zsh/params.yo, Doc/Zsh/zle.yo, + Src/Zle/complist.c, Src/Zle/iwidgets.list, Src/Zle/zle_hist.c, + Src/Zle/zle_keymap.c, Src/Zle/zle_main.c: support the + bracketed paste mode of newer terminal emulators + * 35487, 35496: Doc/Zsh/zle.yo, Src/Zle/complist.c, Src/Zle/zle_hist.c: don't reinstate previous incremental search string when search direction changes diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo index eb3eb367e..e2091624b 100644 --- a/Doc/Zsh/params.yo +++ b/Doc/Zsh/params.yo @@ -1642,6 +1642,23 @@ item(tt(ZDOTDIR))( The directory to search for shell startup files (.zshrc, etc), if not tt($HOME). ) +vindex(zle_bracketed_paste) +cindex(bracketed paste) +cindex(enabling bracketed paste) +item(tt(zle_bracketed_paste))( +Many terminal emulators have a feature that allows applications to +identify when text is pasted into the terminal rather than being typed +normally. For ZLE, this means that special characters such as tabs +and newlines can be inserted instead of invoking editor commands. +Furthermore, pasted text forms a single undo event and if the region is +active, pasted text will replace the region. + +This two-element array contains the terminal escape sequences for +enabling and disabling the feature. These escape sequences are used to +enable bracketed paste when ZLE is active and disable it at other times. +Unsetting the parameter has the effect of ensuring that bracketed paste +remains disabled. +) vindex(ZLE_LINE_ABORTED) item(tt(ZLE_LINE_ABORTED))( This parameter is set by the line editor when an error occurs. It diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo index a89f566c3..d3f067031 100644 --- a/Doc/Zsh/zle.yo +++ b/Doc/Zsh/zle.yo @@ -2059,6 +2059,18 @@ tindex(beep) item(tt(beep))( Beep, unless the tt(BEEP) option is unset. ) +tindex(bracketed-paste) +item(tt(bracketed-paste))( +This widget is invoked when text is pasted to the terminal emulator. It +is not intended to be bound to actual keys but instead to the special +sequence generated by the terminal emulator when text is pasted. +If a numeric argument is given, shell quoting will be applied to the +pasted text before it is inserted. When called from a widget function, +an argument can be given to specify a variable to which pasted text is +assigned. + +See also the tt(zle_bracketed_paste) parameter. +) tindex(vi-cmd-mode) item(tt(vi-cmd-mode) (tt(^X^V)) (unbound) (tt(^[)))( Enter command mode; that is, select the `tt(vicmd)' keymap. diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c index 39c0c314d..0f73181f3 100644 --- a/Src/Zle/complist.c +++ b/Src/Zle/complist.c @@ -2269,41 +2269,16 @@ msearchpop(int *backp) } static Cmatch ** -msearch(Cmatch **ptr, int ins, int back, int rep, int *wrapp) +msearch(Cmatch **ptr, char *ins, int back, int rep, int *wrapp) { -#ifdef MULTIBYTE_SUPPORT - /* MB_CUR_MAX may not be constant */ - VARARR(char, s, MB_CUR_MAX+1); -#else - char s[2]; -#endif Cmatch **p, *l = NULL, m; int x = mcol, y = mline; int ex, ey, wrap = 0, owrap = (msearchstate & MS_WRAPPED); msearchpush(ptr, back); - if (ins) { -#ifdef MULTIBYTE_SUPPORT - if (lastchar_wide_valid) - { - mbstate_t mbs; - int len; - - memset(&mbs, 0, sizeof(mbs)); - len = wcrtomb(s, lastchar_wide, &mbs); - if (len < 0) - len = 0; - s[len] = '\0'; - } else -#endif - { - s[0] = lastchar; - s[1] = '\0'; - } - - msearchstr = dyncat(msearchstr, s); - } + if (ins) + msearchstr = dyncat(msearchstr, ins); if (back) { ex = mcols - 1; ey = -1; @@ -3273,14 +3248,23 @@ domenuselect(Hookdef dummy, Chdata dat) cmd == Th(z_historyincrementalsearchbackward) || ((mode == MM_FSEARCH || mode == MM_BSEARCH) && (cmd == Th(z_selfinsert) || - cmd == Th(z_selfinsertunmeta)))) { + cmd == Th(z_selfinsertunmeta) || + cmd == Th(z_bracketedpaste)))) { Cmatch **np, **op = p; int was = (mode == MM_FSEARCH || mode == MM_BSEARCH); - int ins = (cmd == Th(z_selfinsert) || cmd == Th(z_selfinsertunmeta)); + int ins = (cmd == Th(z_selfinsert) || cmd == Th(z_selfinsertunmeta) || + cmd == Th(z_bracketedpaste)); int back = (cmd == Th(z_historyincrementalsearchbackward)); int wrap; do { + char *toins = NULL; +#ifdef MULTIBYTE_SUPPORT + /* MB_CUR_MAX may not be constant */ + VARARR(char, insert, MB_CUR_MAX+1); +#else + char insert[2]; +#endif if (was) { p += wishcol - mcol; mcol = wishcol; @@ -3297,16 +3281,41 @@ domenuselect(Hookdef dummy, Chdata dat) msearchstack = NULL; msearchstate = MS_OK; } - } - if (cmd == Th(z_selfinsertunmeta)) { - fixunmeta(); - } + } else { + if (cmd == Th(z_selfinsertunmeta)) { + fixunmeta(); + } + if (cmd == Th(z_bracketedpaste)) { + toins = bracketedstring(); + } else { + toins = insert; +#ifdef MULTIBYTE_SUPPORT + if (lastchar_wide_valid) + { + mbstate_t mbs; + int len; + + memset(&mbs, 0, sizeof(mbs)); + len = wcrtomb(s, lastchar_wide, &mbs); + if (len < 0) + len = 0; + insert[len] = '\0'; + } else +#endif + { + insert[0] = lastchar; + insert[1] = '\0'; + } + } + } wrap = 0; - np = msearch(p, ins, (ins ? (mode == MM_BSEARCH) : back), + np = msearch(p, toins, (ins ? (mode == MM_BSEARCH) : back), (was && !ins), &wrap); if (!ins) mode = (back ? MM_BSEARCH : MM_FSEARCH); + else if (cmd == Th(z_bracketedpaste)) + free(toins); if (*msearchstr) { zsfree(lastsearch); diff --git a/Src/Zle/iwidgets.list b/Src/Zle/iwidgets.list index b41661a7d..6a07212d0 100644 --- a/Src/Zle/iwidgets.list +++ b/Src/Zle/iwidgets.list @@ -28,6 +28,7 @@ "beginning-of-history", beginningofhistory, 0 "beginning-of-line", beginningofline, 0 "beginning-of-line-hist", beginningoflinehist, 0 +"bracketed-paste", bracketedpaste, ZLE_MENUCMP | ZLE_KEEPSUFFIX "capitalize-word", capitalizeword, 0 "clear-screen", clearscreen, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_LASTCOL | ZLE_NOTCOMMAND "complete-word", completeword, ZLE_MENUCMP | ZLE_KEEPSUFFIX | ZLE_ISCOMP diff --git a/Src/Zle/zle_hist.c b/Src/Zle/zle_hist.c index 0b3b9e7b7..ffb7ce98f 100644 --- a/Src/Zle/zle_hist.c +++ b/Src/Zle/zle_hist.c @@ -1620,6 +1620,21 @@ doisearch(char **args, int dir, int pattern) feep = 1; else goto ins; + } else if (cmd == Th(z_bracketedpaste)) { + char *paste = bracketedstring(); + set_isrch_spot(top_spot++, hl, pos, pat_hl, pat_pos, end_pos, + zlemetacs, sbptr, dir, nomatch); + size_t pastelen = strlen(paste); + if (sbptr + pastelen >= sibuf - FIRST_SEARCH_CHAR - 2) { + int oldsize = sibuf; + sibuf += (pastelen >= sibuf) ? pastelen + 1 : sibuf; + ibuf = hrealloc(ibuf, oldsize, sibuf); + sbuf = ibuf + FIRST_SEARCH_CHAR; + } + strcpy(sbuf + sbptr, paste); + sbptr += pastelen; + patprog = NULL; + free(paste); } else if (cmd == Th(z_acceptsearch)) { break; } else { diff --git a/Src/Zle/zle_keymap.c b/Src/Zle/zle_keymap.c index c6fae251d..d355f41a4 100644 --- a/Src/Zle/zle_keymap.c +++ b/Src/Zle/zle_keymap.c @@ -1400,6 +1400,11 @@ default_bindings(void) bindkey(emap, "\30\30", refthingy(t_exchangepointandmark), NULL); bindkey(emap, "\30=", refthingy(t_whatcursorposition), NULL); + /* bracketed paste applicable to all keymaps */ + bindkey(emap, "\33[200~", refthingy(t_bracketedpaste), NULL); + bindkey(vmap, "\33[200~", refthingy(t_bracketedpaste), NULL); + bindkey(amap, "\33[200~", refthingy(t_bracketedpaste), NULL); + /* emacs mode: ESC sequences, all taken from the meta binding table */ buf[0] = '\33'; buf[2] = 0; diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c index cec44c0ed..7ccfb686a 100644 --- a/Src/Zle/zle_main.c +++ b/Src/Zle/zle_main.c @@ -1119,7 +1119,7 @@ zlecore(void) char * zleread(char **lp, char **rp, int flags, int context, char *init, char *finish) { - char *s; + char *s, **bracket; int old_errno = errno; int tmout = getiparam("TMOUT"); @@ -1248,6 +1248,9 @@ zleread(char **lp, char **rp, int flags, int context, char *init, char *finish) zlecallhook(init, NULL); + if ((bracket = getaparam("zle_bracketed_paste")) && arrlen(bracket) == 2) + fputs(*bracket, shout); + zrefresh(); zlecore(); @@ -1257,6 +1260,9 @@ zleread(char **lp, char **rp, int flags, int context, char *init, char *finish) "ZLE_VARED_ABORTED" : "ZLE_LINE_ABORTED", zlegetline(NULL, NULL)); + if ((bracket = getaparam("zle_bracketed_paste")) && arrlen(bracket) == 2) + fputs(bracket[1], shout); + if (done && !exit_pending && !errflag) zlecallhook(finish, NULL); @@ -2004,6 +2010,8 @@ static struct features module_features = { int setup_(UNUSED(Module m)) { + char **bpaste; + /* Set up editor entry points */ zle_entry_ptr = zle_main_entry; zle_load_state = 1; @@ -2028,6 +2036,11 @@ setup_(UNUSED(Module m)) clwords = (char **) zshcalloc((clwsize = 16) * sizeof(char *)); + bpaste = zshcalloc(3*sizeof(char *)); + bpaste[0] = ztrdup("\033[?2004h"); + bpaste[1] = ztrdup("\033[?2004l"); + setaparam("zle_bracketed_paste", bpaste); + return 0; } diff --git a/Src/Zle/zle_misc.c b/Src/Zle/zle_misc.c index 4669ef2ad..c2fb2e7f4 100644 --- a/Src/Zle/zle_misc.c +++ b/Src/Zle/zle_misc.c @@ -735,6 +735,58 @@ yankpop(UNUSED(char **args)) return 0; } +/**/ +char * +bracketedstring() +{ + static const char endesc[] = "\033[201~"; + int endpos = 0; + size_t psize = 64; + char *pbuf = zalloc(psize); + size_t current = 0; + int next, timeout; + + while (endesc[endpos]) { + if (current + 1 >= psize) + pbuf = zrealloc(pbuf, psize *= 2); + if ((next = getbyte(1L, &timeout)) == EOF) + break; + if (!endpos || next != endesc[endpos++]) + endpos = (next == *endesc); + if (imeta(next)) { + pbuf[current++] = Meta; + pbuf[current++] = next ^ 32; + } else if (next == '\r') + pbuf[current++] = '\n'; + else + pbuf[current++] = next; + } + pbuf[current-endpos] = '\0'; + return pbuf; +} + +/**/ +int +bracketedpaste(char **args) +{ + char *pbuf = bracketedstring(); + + if (*args) { + setsparam(*args, pbuf); + } else { + int n; + ZLE_STRING_T wpaste; + wpaste = stringaszleline((zmult == 1) ? pbuf : + quotestring(pbuf, NULL, QT_BACKSLASH), 0, &n, NULL, NULL); + zmult = 1; + if (region_active) + killregion(zlenoargs); + doinsert(wpaste, n); + free(pbuf); free(wpaste); + } + return 0; +} + /**/ int overwritemode(UNUSED(char **args)) @@ -1264,6 +1316,22 @@ executenamedcommand(char *prmt) if (listed) clearlist = listshown = 1; curlist = 0; + } else if (cmd == Th(z_bracketedpaste)) { + char *insert = bracketedstring(); + size_t inslen = strlen(insert); + if (len + inslen > NAMLEN) + feep = 1; + else { + strcpy(ptr, insert); + len += inslen; + ptr += inslen; + if (listed) { + clearlist = listshown = 1; + listed = 0; + } else + curlist = 0; + } + free(insert); } else { if(cmd == Th(z_acceptline) || cmd == Th(z_vicmdmode)) { Thingy r;