From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on inbox.vuxu.org X-Spam-Level: X-Spam-Status: No, score=-3.4 required=5.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED autolearn=ham autolearn_force=no version=3.4.4 Received: (qmail 24487 invoked from network); 11 Nov 2022 00:28:53 -0000 Received: from zero.zsh.org (2a02:898:31:0:48:4558:7a:7368) by inbox.vuxu.org with ESMTPUTF8; 11 Nov 2022 00:28:53 -0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=zsh.org; s=rsa-20210803; h=List-Archive:List-Owner:List-Post:List-Unsubscribe: List-Subscribe:List-Help:List-Id:Sender:Message-ID:Date:Content-ID: Content-Type:MIME-Version:Subject:To:From:Reply-To:Cc: Content-Transfer-Encoding:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References; bh=Oda8PT31y1RmKRddF78SRu+AlwMTXZuYQTZzdkOj3Lc=; b=axe0MFpKRtzcWQdCuAKJwpV1qr fN9O5J2oPCOqNkveQbeF7TfxRU4bOqeVRGVndhymU4a2Rog15e6c6Him62eU69yh3xX6fS2DAKZJx gVy+eUsLhR5AzxUXqfFiY0bgrR0Fn+EZwpI/XTl9p13MAQtlz5fdCMuYxhBZ7x8W7Q7kvxHlr0gLV n+ukxiU6Y/aszr/JAgJzPsnjCgbI7PsKhpGW3u7VVjZYgqE9wDhJehOCCwTCQ7zTX34azLhosPis+ 3IJ/I5IWMvsSJySojCAsX++he2JiH1J/UYHMygxb1jv4uFRxRqw+Sqp+eVPpGv1OXr50s3ZcYV/an KIwcou+Q==; Received: by zero.zsh.org with local id 1otHue-000LAg-DE; Fri, 11 Nov 2022 00:28:52 +0000 Received: by zero.zsh.org with esmtpsa (TLS1.3:TLS_AES_256_GCM_SHA384:256) id 1otHoK-000KjN-6g; Fri, 11 Nov 2022 00:22:20 +0000 Received: from [192.168.178.21] (helo=hydra) by mail.kiddle.eu with esmtp(Exim 4.95) (envelope-from ) id 1otHoD-000H9G-4N for zsh-workers@zsh.org; Fri, 11 Nov 2022 01:22:14 +0100 From: Oliver Kiddle To: Zsh workers Subject: PATCH: use escape sequence for system clipboard MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-ID: <65920.1668126133.1@hydra> Date: Fri, 11 Nov 2022 01:22:13 +0100 Message-ID: <65921-1668126133.132087@aS6N.AE_X.6s2G> X-Seq: 50934 Archived-At: X-Loop: zsh-workers@zsh.org Errors-To: zsh-workers-owner@zsh.org Precedence: list Precedence: bulk Sender: zsh-workers-request@zsh.org X-no-archive: yes List-Id: List-Help: , List-Subscribe: , List-Unsubscribe: , List-Post: List-Owner: List-Archive: 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')))