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

Re: bracketed paste mode in xterm and urxvt



On 9 Jun, Bart wrote:
> This sounds like the same dilemma that we had with ZLE_RPROMPT_INDENT,
> which we addressed with a variable rather than an option.  This would
> address several problems:  namespace (ZLE-specific stuff should really
> stop leaking into the base shell options); a place to store the string
> to use in case it turns out not to be the same for all terminal types;
> and distinguishing among unset/set/empty/non-empty semantics.

That makes sense, thanks.

So this next attempt uses ZLE_BRACKETED_PASTE_ON/OFF though I'm open to
better ideas.
I've not made them special, just initialised them with a call to
setsparam as that seemed simple enough for the purpose. Is that
reasonable or should they be special?

I also looked around for places where keys are read and handled such
that bracketedpaste() would not be called. I've not done anything in
the case of single bytes such as for the vi r and f commands but I have
added handling for the history and completion incremental searches and
for the execute-named-command widget. It would be possible to avoid this
special handling by adding logic to getkeycmd() so that they get a
stream of self-insert widgets. That would somehow defeat the
purpose of bracketed paste, however.

In both the incremental search modes, backspace works like undo so the
entire paste is undone. This same effect is also apparent if you press
backspace after changing search direction with Ctrl-S or Ctrl-R.

Oliver

diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo
index eb3eb36..48c973a 100644
--- a/Doc/Zsh/params.yo
+++ b/Doc/Zsh/params.yo
@@ -1642,6 +1642,25 @@ item(tt(ZDOTDIR))(
 The directory to search for shell startup files (.zshrc, etc),
 if not tt($HOME).
 )
+vindex(ZLE_BRACKETED_PASTE_ON)
+vindex(ZLE_BRACKETED_PASTE_OFF)
+cindex(bracketed paste)
+cindex(enabling bracketed paste)
+xitem(tt(ZLE_BRACKETED_PASTE_ON))
+item(tt(ZLE_BRACKETED_PASTE_OFF))(
+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.
+
+These parameters contain 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 parameters 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 16d661f..9066681 100644
--- a/Doc/Zsh/zle.yo
+++ b/Doc/Zsh/zle.yo
@@ -2057,6 +2057,11 @@ 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.
+See also the ZLE_BRACKETED_PASTE_ON 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 f542066..a55567f 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;
@@ -3294,17 +3278,43 @@ domenuselect(Hookdef dummy, Chdata dat)
                     } else {
                         msearchstr = "";
                         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 b41661a..6a07212 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 cc66f99..f8aab20 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 c6fae25..d355f41 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 cec44c0..62ed157 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 = getsparam("ZLE_BRACKETED_PASTE_ON")))
+	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 = getsparam("ZLE_BRACKETED_PASTE_OFF")))
+	fputs(bracket, shout);
+
     if (done && !exit_pending && !errflag)
 	zlecallhook(finish, NULL);
 
@@ -2028,6 +2034,9 @@ setup_(UNUSED(Module m))
 
     clwords = (char **) zshcalloc((clwsize = 16) * sizeof(char *));
 
+    setsparam("ZLE_BRACKETED_PASTE_ON", ztrdup("\033[?2004h"));
+    setsparam("ZLE_BRACKETED_PASTE_OFF", ztrdup("\033[?2004l"));
+
     return 0;
 }
 
diff --git a/Src/Zle/zle_misc.c b/Src/Zle/zle_misc.c
index 4669ef2..ef9d0a8 100644
--- a/Src/Zle/zle_misc.c
+++ b/Src/Zle/zle_misc.c
@@ -736,6 +736,54 @@ yankpop(UNUSED(char **args))
 }
 
 /**/
+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(UNUSED(char **args))
+{
+    int n;
+    ZLE_STRING_T wpaste;
+    char *pbuf = bracketedstring();
+
+    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 +1312,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;



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