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

PATCH: use escape sequence for system clipboard



Terminals increasingly support a feature for setting the system
clipboard with the OSC 52[1] escape sequence. Vim has "* and "+
registers corresponding to the primary selection and clipboard
respectively. In vim, these often work even in a terminal because the
vim binary has been linked against X libraries. In the past, I pondered
adding hooks that could be defined to use xclip/pbcopy/whatever but the
escape sequence has the advantage of working through ssh without X
forwarding or even over something like telnet or cu. tmux also supports
it. A notable limitation is that put/paste commands are not supported. I
guess most terminals already have a key combination like Shift-Insert
for inserting the X selection.

This patch makes zsh generate the sequence if you do something like "+yy
in vi mode. Terminals that lack support for the feature typically
silently swallow and discard the sequence. There may be old ones where
it will spew crap into the terminal but I don't see that as an issue
given the obscure keystroke needed. Paste operations will simply do
nothing.

I don't know if there is a suitable corresponding way this could be
exposed to emacs mode users? Note that it does appear to work fairly
well to define a widget that uses the vi widgets. But perhaps we could
make it easier?

	x-copy() {
          (( REGION_ACTIVE )) || zle beep
	  zle vi-set-buffer \*
	  zle vi-yank
	}
        zle -N x-copy

If you use, rxvt-unicode note that it needs a perl extension to enable
the feature.

Oliver

[1] See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands

diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo
index 2d033a0a1..58700072a 100644
--- a/Doc/Zsh/zle.yo
+++ b/Doc/Zsh/zle.yo
@@ -2470,10 +2470,11 @@ command.  tt(run-help) is normally aliased to tt(man).
 tindex(vi-set-buffer)
 item(tt(vi-set-buffer) (unbound) (tt(")) (unbound))(
 Specify a buffer to be used in the following command.
-There are 37 buffers that can be specified:
+There are 39 buffers that can be specified:
 the 26 `named' buffers tt("a) to tt("z), the `yank' buffer tt("0),
-the nine `queued' buffers tt("1) to tt("9) and the `black hole' buffer
-tt("_).  The named buffers can also be specified as tt("A) to tt("Z).
+the nine `queued' buffers tt("1) to tt("9), the `black hole' buffer
+tt("_) and the system selection tt("*) and clipboard tt("+).
+The named buffers can also be specified as tt("A) to tt("Z).
 
 When a buffer is specified for a cut, change or yank command, the text
 concerned replaces the previous contents of the specified buffer. If
@@ -2482,6 +2483,10 @@ appended to the buffer instead of overwriting it. When using the tt("_)
 buffer, nothing happens. This can be useful for deleting text without
 affecting any buffers.
 
+Updating the system clipboard relies on specific support from the terminal.
+Reading it is not possible so a paste command with tt("*) or tt("+) will do
+nothing.
+
 If no buffer is specified for a cut or change command, tt("1) is used, and
 the contents of tt("1) to tt("8) are each shifted along one buffer;
 the contents of tt("9) is lost. If no buffer is specified for a yank
diff --git a/Src/Zle/zle.h b/Src/Zle/zle.h
index 391586c4a..f59545397 100644
--- a/Src/Zle/zle.h
+++ b/Src/Zle/zle.h
@@ -258,6 +258,9 @@ struct modifier {
 #define MOD_NULL  (1<<5)   /* throw away text for the vi cut buffer */
 #define MOD_CHAR  (1<<6)   /* force character-wise movement */
 #define MOD_LINE  (1<<7)   /* force line-wise movement */
+#define MOD_PRI   (1<<8)   /* OS primary selection for the vi cut buffer */
+#define MOD_CLIP  (1<<9)   /* OS clipboard for the vi cut buffer */
+#define MOD_OSSEL (MOD_PRI | MOD_CLIP)  /* either system selection */
 
 /* current modifier status */
 
diff --git a/Src/Zle/zle_utils.c b/Src/Zle/zle_utils.c
index 526216fa7..3d9017dcf 100644
--- a/Src/Zle/zle_utils.c
+++ b/Src/Zle/zle_utils.c
@@ -936,6 +936,28 @@ cut(int i, int ct, int flags)
   cuttext(zleline + i, ct, flags);
 }
 
+static char*
+base64_encode(const char *src, size_t len) {
+    static const char* base64_table =
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+    const unsigned char *end = (unsigned char *)src + len;
+    const unsigned char *in = (unsigned char *)src;
+    char *ret = zhalloc(1 + 4 * ((len + 2) / 3)); /* 4 bytes out for 3 in */
+    char *cur = ret;
+
+    for (; end - in > 0; in += 3, cur += 4) {
+        unsigned int n = *in << 16;
+        cur[3] = end - in > 2 ? base64_table[(n |= in[2]) & 0x3f] : '=';
+        cur[2] = end - in > 1 ? base64_table[((n |= in[1]<<8) >> 6) & 0x3f] : '=';
+        cur[1] = base64_table[(n >> 12) & 0x3f];
+        cur[0] = base64_table[n >> 18];
+    }
+    *cur = '\0';
+
+    return ret;
+}
+
 /*
  * As cut, but explicitly supply the text together with its length.
  */
@@ -948,7 +970,15 @@ cuttext(ZLE_STRING_T line, int ct, int flags)
 	return;
 
     UNMETACHECK();
-    if (zmod.flags & MOD_VIBUF) {
+    if (zmod.flags & MOD_OSSEL) {
+	int cutll;
+	char *mbcut = zlelineasstring(line, ct, 0, &cutll, NULL, 1);
+	unmetafy(mbcut, &cutll);
+	mbcut = base64_encode(mbcut, cutll);
+
+	fprintf(shout, "\e]52;%c;%s\a", zmod.flags & MOD_CLIP ? 'c' : 'p',
+		mbcut);
+    } else if (zmod.flags & MOD_VIBUF) {
 	struct cutbuffer *b = &vibuf[zmod.vibuf];
 
 	if (!(zmod.flags & MOD_VIAPP) || !b->buf) {
diff --git a/Src/Zle/zle_vi.c b/Src/Zle/zle_vi.c
index 0f198d0e8..c7fd0e27b 100644
--- a/Src/Zle/zle_vi.c
+++ b/Src/Zle/zle_vi.c
@@ -1014,6 +1014,9 @@ int
 visetbuffer(char **args)
 {
     ZLE_INT_T ch;
+    ZLE_CHAR_T *match = ZWS("_*+");
+    int registermod[] = { MOD_NULL, MOD_PRI, MOD_CLIP };
+    ZLE_CHAR_T *found;
 
     if (*args) {
 	ch = **args;
@@ -1022,12 +1025,13 @@ visetbuffer(char **args)
     } else {
 	ch = getfullchar(0);
     }
-    if (ch == ZWC('_')) {
-	zmod.flags |= MOD_NULL;
+    found = ZS_strchr(match, ch);
+    if (found) {
+	zmod.flags |= registermod[found - match];
 	prefixflag = 1;
 	return 0;
     } else
-	zmod.flags &= ~MOD_NULL;
+	zmod.flags &= ~(MOD_NULL | MOD_OSSEL);
     if ((ch < ZWC('0') || ch > ZWC('9')) &&
 	 (ch < ZWC('a') || ch > ZWC('z')) &&
 	 (ch < ZWC('A') || ch > ZWC('Z')))




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