patch 9.1.1984: terminal OSC52 support can be improved
Commit:
https://github.com/vim/vim/commit/02b8ec7da52bb11e49d3e0d801eb0d349c73677e
Author: Foxe Chen <
chen...@gmail.com>
Date: Mon Dec 15 21:45:07 2025 +0100
patch 9.1.1984: terminal OSC52 support can be improved
Problem: terminal OSC52 support to access the clipboard can be improved
Solution: Include and package the optional osc52 package, note: this
requires a Vim with clipboard provider feature (Foxe Chen).
related: #14995
closes: #18575
Signed-off-by: Foxe Chen <
chen...@gmail.com>
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/Filelist b/Filelist
index 4441d4cb4..7c8d1dd9e 100644
--- a/Filelist
+++ b/Filelist
@@ -856,7 +856,11 @@ RT_ALL = \
runtime/pack/dist/opt/netrw/autoload/netrw_gitignore.vim \
runtime/pack/dist/opt/netrw/doc/netrw.txt \
runtime/pack/dist/opt/netrw/plugin/netrwPlugin.vim \
- runtime/pack/dist/opt/netrw/syntax/netrw.vim
+ runtime/pack/dist/opt/netrw/syntax/netrw.vim \
+ runtime/pack/dist/opt/osc52/plugin/osc52.vim \
+ runtime/pack/dist/opt/osc52/autoload/osc52.vim \
+ runtime/pack/dist/opt/osc52/doc/osc52.txt \
+ runtime/pack/dist/opt/osc52/doc/tags
# Runtime files for all distributions without CR/LF translation.
RT_ALL_BIN = \
diff --git a/runtime/doc/tags b/runtime/doc/tags
index 22b470236..8b5d11a33 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -9616,6 +9616,7 @@ os_risc.txt os_risc.txt /*os_risc.txt*
os_unix.txt os_unix.txt /*os_unix.txt*
os_vms.txt os_vms.txt /*os_vms.txt*
os_win32.txt os_win32.txt /*os_win32.txt*
+osc52-install usr_05.txt /*osc52-install*
other-features vi_diff.txt /*other-features*
out_buf channel.txt /*out_buf*
out_cb channel.txt /*out_cb*
@@ -9640,6 +9641,7 @@ package-justify usr_25.txt /*package-justify*
package-matchit usr_05.txt /*package-matchit*
package-nohlsearch usr_05.txt /*package-nohlsearch*
package-open eval.txt /*package-open*
+package-osc52 usr_05.txt /*package-osc52*
package-termdebug terminal.txt /*package-termdebug*
package-translate_example repeat.txt /*package-translate_example*
package-translation repeat.txt /*package-translation*
diff --git a/runtime/doc/usr_05.txt b/runtime/doc/usr_05.txt
index 32f8431d3..c528c09a8 100644
--- a/runtime/doc/usr_05.txt
+++ b/runtime/doc/usr_05.txt
@@ -1,4 +1,4 @@
-*usr_05.txt* For Vim version 9.1. Last change: 2025 Nov 09
+*usr_05.txt* For Vim version 9.1. Last change: 2025 Dec 15
VIM USER MANUAL by Bram Moolenaar
@@ -399,13 +399,17 @@ The ":map" command (with no arguments) lists your current mappings. At
least the ones for Normal mode. More about mappings in section |40.1|.
==============================================================================
-*05.5* Adding a package *add-package* *matchit-install* *package-matchit*
+*05.5* Adding a package *add-package*
A package is a set of files that you can add to Vim. There are two kinds of
packages: optional and automatically loaded on startup.
The Vim distribution comes with a few packages that you can optionally use.
-For example, the matchit plugin. This plugin makes the "%" command jump to
+
+------------------------------------------------------------------------------
+Adding the matchit package *matchit-install* *package-matchit*
+------------------------------------------------------------------------------
+For example, the matchit package This plugin makes the "%" command jump to
matching HTML tags, if/else/endif in Vim scripts, etc. Very useful, although
it's not backwards compatible (that's why it is not enabled by default).
@@ -434,7 +438,9 @@ an archive or as a repository. For an archive you can follow these steps:
Here "fancytext" is the name of the package, it can be anything
else.
+------------------------------------------------------------------------------
Adding the editorconfig package *editorconfig-install* *package-editorconfig*
+------------------------------------------------------------------------------
Similar to the matchit package, to load the distributed editorconfig plugin
when Vim starts, add the following line to your vimrc file: >
@@ -444,7 +450,9 @@ After restarting your Vim, the plugin is active and you can read about it at: >
:h editorconfig.txt
+------------------------------------------------------------------------------
Adding the comment package *comment-install* *package-comment*
+------------------------------------------------------------------------------
Load the plugin with this command: >
packadd comment
@@ -457,7 +465,9 @@ the package loaded. Once the package is loaded, read about it at: >
:h comment.txt
+------------------------------------------------------------------------------
Adding the nohlsearch package *nohlsearch-install* *package-nohlsearch*
+------------------------------------------------------------------------------
Load the plugin with this command: >
packadd nohlsearch
@@ -471,7 +481,9 @@ To disable the effect of the plugin after it has been loaded: >
au! nohlsearch
<
+------------------------------------------------------------------------------
Adding the highlight-yank package *hlyank-install* *package-hlyank*
+------------------------------------------------------------------------------
Load the plugin with this command: >
packadd hlyank
@@ -497,6 +509,20 @@ To highlight in visual mode, use: >
To disable the effect of the plugin after it has been loaded: >
au! hlyank
+------------------------------------------------------------------------------
+Adding the osc52 package *osc52-install* *package-osc52*
+------------------------------------------------------------------------------
+
+Load the plugin with this command: >
+ packadd osc52
+<
+The osc52.vim package provides support for the OSC 52 terminal command, which
+allows an application to access the clipboard by communicating directly with
+your terminal.
+
+Once the package is loaded, read about it at: >
+ :h osc52.txt
+
More information about packages can be found here: |packages|.
==============================================================================
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index 77a337d44..1071613fe 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt* For Vim version 9.1. Last change: 2025 Dec 14
+*version9.txt* For Vim version 9.1. Last change: 2025 Dec 15
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -41627,8 +41627,9 @@ Other new features ~
------------------
- Support for Super key mappings in GTK using <D-Key>.
-- The new packages |package-comment|, |package-nohlsearch|, |package-hlyank| and
- |help-TOC| are included.
+- The new optional packages |package-comment|, |package-nohlsearch|,
+ |package-hlyank|, |help-TOC|, |package-helpcurwin| and |package-osc52| are
+ included.
- An interactive tutor plugin has been included |vim-tutor-mode| and can be
started via |:Tutor|.
diff --git a/runtime/pack/dist/opt/osc52/autoload/osc52.vim b/runtime/pack/dist/opt/osc52/autoload/osc52.vim
new file mode 100644
index 000000000..bd664f17c
--- /dev/null
+++ b/runtime/pack/dist/opt/osc52/autoload/osc52.vim
@@ -0,0 +1,92 @@
+vim9script
+
+export var allowed: bool = false
+
+export def Available(): bool
+ if get(g:, 'osc52_force_avail', 0)
+ return true
+ endif
+ return allowed
+enddef
+
+var sent_message: bool = false
+
+def OSCMessage(id: number)
+ echom "Waiting for OSC52 response... Press CTRL-C to cancel"
+ sent_message = true
+enddef
+
+export def Paste(reg: string): tuple<string, list<string>>
+ # Check if user has indicated that the terminal does not support OSC 52 paste
+ # (or has disabled it)
+ if get(g:, 'osc52_disable_paste', 0)
+ return ("c", [""])
+ endif
+
+ # Some terminals like Kitty respect the selection type parameter on both X11
+ # and Wayland. If the terminal doesn't then the selection type parameter
+ # should be ignored (no-op)
+ if reg == "+"
+ echoraw("\<Esc>]52;c;?\<Esc>\")
+ else
+ echoraw("\<Esc>]52;p;?\<Esc>\")
+ endif
+
+ var timerid: number = timer_start(1000, OSCMessage)
+ var interrupt: bool = false
+
+ # Wait for response from terminal. If we got interrupted (Ctrl-C), then do a
+ # redraw if we already sent the message, and return an empty string.
+ try
+ while true
+ var key: string = getcharstr(-1, {cursor: "hide"})
+
+ if key == "\<xOSC>" && match(v:termosc, '52;') != -1
+ break
+ elseif key == "\<C-c>"
+ interrupt = true
+ break
+ endif
+ endwhile
+
+ # This doesn't seem to catch Ctrl-C sent via term_sendkeys(), which is used in
+ # tests. So also check the result of getcharstr()/getchar().
+ catch /^Vim:Interrupt$/
+ interrupt = true
+ finally
+ timer_stop(timerid)
+ if sent_message
+ sent_message = false
+ :redraw!
+ endif
+ endtry
+
+ if interrupt
+ echo "Interrupted while waiting for OSC 52 response"
+ return ("c", [""])
+ endif
+
+ # Extract the base64 stuff
+ var stuff: string = matchstr(v:termosc, '52;.\+;\zs[A-Za-z0-9+/=]\+')
+ var decoded: blob
+
+ # "stuff" may be an invalid base64 string, so catch any errors
+ try
+ decoded = base64_decode(stuff)
+ catch /:E475/
+ decoded = null_blob
+ echo "Invalid OSC 52 response received"
+ endtry
+
+ return ("", blob2str(decoded))
+enddef
+
+export def Copy(reg: string, type: string, lines: list<string>): void
+ if reg == "+"
+ echoraw("\<Esc>]52;c;" .. base64_encode(str2blob(lines)) .. "\<Esc>\")
+ else
+ echoraw("\<Esc>]52;p;" .. base64_encode(str2blob(lines)) .. "\<Esc>\")
+ endif
+enddef
+
+# vim: set sw=2 sts=2 :
diff --git a/runtime/pack/dist/opt/osc52/doc/osc52.txt b/runtime/pack/dist/opt/osc52/doc/osc52.txt
new file mode 100644
index 000000000..2d98299b9
--- /dev/null
+++ b/runtime/pack/dist/opt/osc52/doc/osc52.txt
@@ -0,0 +1,64 @@
+*osc52.txt* For Vim version 9.1. Last change: 2025 Dec 15
+
+
+ VIM REFERENCE MANUAL
+
+Use the OSC 52 terminal command for clipboard support
+==============================================================================
+
+1. OVERVIEW *osc52-overview*
+
+The osc52.vim plugin provides support for the OSC 52 terminal command, which
+allows an application to access the clipboard by communicating with the
+terminal. This is useful in situations such as if you are in a SSH session.
+
+ *osc52-support*
+In order for this plugin to work, the terminal Vim is running in must
+recognize and handle the OSC 52 escape sequence. You can easily check this
+online. Additionally, while yanking is guaranteed to work, some terminals
+don't implement the paste functionality. If the terminal doesn't support
+pasting, then Vim will just block waiting for the data which will never come.
+In this case just press Ctrl-C to cancel the operation.
+
+ *osc52-selections*
+Note that this only applies to users on Wayland or X11 platforms
+
+Some terminals support the selection type parameter in the OSC 52 command.
+This originates from X11, and some terminals check this parameter and handle
+it accordingly. If your terminal handles this parameter, then the "+"
+register corresponds to the regular selection, and the "*" register
+corresponds to the primary selection. If your terminal does not handle it,
+then it is up to the terminal to handle what selection to use.
+
+2. HOW TO USE THE PLUGIN *osc52-how-to-use*
+
+The osc52.vim plugin relies on Vim's clipboard provider functionality, see
+|clipboard-providers|. In short, add these commands to your vimrc to get
+everything working: >vim
+ packadd osc52
+ set clipmethod+=osc52
+<
+This will make the osc52.vim provider the last resort if there are other
+values in |clipmethod|. This allows Vim, for example, to access the system
+clipboard directly if it can, but automatically switch to OSC 52 if it cannot
+(e.g. in an SSH session). Note that this does not happen when on a platform
+that doesn't use |clipmethod| for system clipboard functionality (MacOS,
+Windows). If OSC 52 support is detected, then it will always be used if set
+in |clipmethod| when it is the only value/method.
+
+ *g:osc52_force_avail*
+In most cases, the plugin should automatically detect and work if your
+terminal supports the OSC 52 command. Internally, it does this via a Primary
+Device Attributes (DA1) query. You may force enable the plugin by setting
+|g:osc52_force_avail| to true. You may check if the osc52.vim plugin is being
+used if the value of |v:clipmethod| is "osc52". Note that using a terminal
+multiplexer such as tmux, may prevent automatic OSC 52 detection.
+
+ *g:osc52_disable_paste*
+If your terminal does not support pasting via OSC 52, or has it disabled, then
+it is a good idea to set g:osc52_disable_paste to TRUE. This will cause an
+empty string to be returned when Vim attempts to query the osc52.vim provider,
+instead of doing a blocking wait, as said in |osc52-support|.
+
+==============================================================================
+vim:tw=78:ts=8:fo=tcq2:ft=help:
diff --git a/runtime/pack/dist/opt/osc52/doc/tags b/runtime/pack/dist/opt/osc52/doc/tags
new file mode 100644
index 000000000..9d723973b
--- /dev/null
+++ b/runtime/pack/dist/opt/osc52/doc/tags
@@ -0,0 +1,7 @@
+g:osc52_disable_paste osc52.txt /*g:osc52_disable_paste*
+g:osc52_force_avail osc52.txt /*g:osc52_force_avail*
+osc52-how-to-use osc52.txt /*osc52-how-to-use*
+osc52-overview osc52.txt /*osc52-overview*
+osc52-selections osc52.txt /*osc52-selections*
+osc52-support osc52.txt /*osc52-support*
+osc52.txt osc52.txt /*osc52.txt*
diff --git a/runtime/pack/dist/opt/osc52/plugin/osc52.vim b/runtime/pack/dist/opt/osc52/plugin/osc52.vim
new file mode 100644
index 000000000..ddec41ed1
--- /dev/null
+++ b/runtime/pack/dist/opt/osc52/plugin/osc52.vim
@@ -0,0 +1,45 @@
+vim9script
+
+# Vim plugin for OSC52 clipboard support
+#
+# Maintainer: The Vim Project <
https://github.com/vim/vim>
+# Last Change: 2025 October 14
+
+if !has("timers")
+ finish
+endif
+
+import autoload "../autoload/osc52.vim" as osc
+
+v:clipproviders["osc52"] = {
+ "available": osc.Available,
+ "paste": {
+ "*": osc.Paste,
+ "+": osc.Paste
+ },
+ "copy": {
+ "*": osc.Copy,
+ "+": osc.Copy
+ },
+}
+
+augroup VimOSC52Plugin
+ autocmd!
+ # Query support for OSC 52 using a DA1 query
+ autocmd TermResponseAll da1 {
+ if match(v:termda1, '?\zs.*52\ze') != -1
+ osc.allowed = true
+ :silent! clipreset
+ else
+ osc.allowed = false
+ :silent! clipreset
+ endif
+ }
+ autocmd VimEnter * {
+ if !has("gui_running") && !get(g:, 'osc52_force_avail', 0)
+ echoraw("\<Esc>[c")
+ endif
+ }
+augroup END
+
+# vim: set sw=2 sts=2 :
diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak
index 97ad9089a..9c0d11585 100644
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -249,6 +249,7 @@ NEW_TESTS = \
test_plugin_man \
test_plugin_matchparen \
test_plugin_netrw \
+ test_plugin_osc52 \
test_plugin_tar \
test_plugin_termdebug \
test_plugin_tohtml \
@@ -524,6 +525,7 @@ NEW_TESTS_RES = \
test_plugin_man.res \
test_plugin_matchparen.res \
test_plugin_netrw.res \
+ test_plugin_osc52.res \
test_plugin_tar.res \
test_plugin_termdebug.res \
test_plugin_tohtml.res \
diff --git a/src/testdir/dumps/Test_osc52_paste_01.dump b/src/testdir/dumps/Test_osc52_paste_01.dump
new file mode 100644
index 000000000..c0c562802
--- /dev/null
+++ b/src/testdir/dumps/Test_osc52_paste_01.dump
@@ -0,0 +1,20 @@
+> +0&#ffffff0@74
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|W+0#0000000&|a|i|t|i|n|g| |f|o|r| |O|S|C|5|2| |r|e|s|.@2|P|r|e|s@1| |C|T|R|L|-|C| |t|o| |c|a|n|c|e|l| @10|0|,|0|-|1| @8|A|l@1|
diff --git a/src/testdir/dumps/Test_osc52_paste_02.dump b/src/testdir/dumps/Test_osc52_paste_02.dump
new file mode 100644
index 000000000..8c53837f4
--- /dev/null
+++ b/src/testdir/dumps/Test_osc52_paste_02.dump
@@ -0,0 +1,20 @@
+| +0&#ffffff0@74
+>h|e|l@1|o| @69
+|w|o|r|l|d|!| @68
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+| +0#0000000&@56|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_osc52_paste_03.dump b/src/testdir/dumps/Test_osc52_paste_03.dump
new file mode 100644
index 000000000..1557941f2
--- /dev/null
+++ b/src/testdir/dumps/Test_osc52_paste_03.dump
@@ -0,0 +1,20 @@
+| +0&#ffffff0@74
+>h|e|l@1|o| @69
+|w|o|r|l|d|!| @68
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|I+0#0000000&|n|v|a|l|i|d| |O|S|C| |5|2| |r|e|s|p|o|n|s|e| |r|e|c|e|i|v|e|d| @24|2|,|1| @10|A|l@1|
diff --git a/src/testdir/dumps/Test_osc52_paste_04.dump b/src/testdir/dumps/Test_osc52_paste_04.dump
new file mode 100644
index 000000000..724cd2ca4
--- /dev/null
+++ b/src/testdir/dumps/Test_osc52_paste_04.dump
@@ -0,0 +1,20 @@
+| +0&#ffffff0@74
+>h|e|l@1|o| @69
+|w|o|r|l|d|!| @68
+|~+0#4040ff13&| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|I+0#0000000&|n|t|e|r@1|u|p|t|e|d| |w|h|i|l|e| |w|a|i|t|i|n|g| |f|o|r| |O|S|C| |5|2| |r|e|s|p|o|n|s|e| @11|2|,|1| @10|A|l@1|
diff --git a/src/testdir/test_plugin_osc52.vim b/src/testdir/test_plugin_osc52.vim
new file mode 100644
index 000000000..fff40936a
--- /dev/null
+++ b/src/testdir/test_plugin_osc52.vim
@@ -0,0 +1,95 @@
+" Test for the OSC 52 plugin
+
+CheckRunVimInTerminal
+" Does not run on BSD CI test runner
+CheckNotBSD
+
+source util/screendump.vim
+
+" Check if plugin correctly detects OSC 52 support if possible
+func Test_osc52_detect()
+ let lines =<< trim END
+ packadd osc52
+ set clipmethod=osc52
+ END
+ call writefile(lines, "Xosc52.vim", "D")
+ defer delete("Xosc52result")
+
+ let buf = RunVimInTerminal("-S Xosc52.vim", {})
+
+ " The plugin creates an autocmd listening for DA1 responses
+
+ " No support
+ call term_sendkeys(buf, "\<Esc>[?62;22;c")
+ call TermWait(buf)
+
+ call term_sendkeys(buf,
+ \ "\<Esc>:call writefile([v:termda1, v:clipmethod], 'Xosc52result')\<CR>")
+ call TermWait(buf)
+ call WaitForAssert({->
+ \ assert_equal(["\<Esc>[?62;22;c", "none"], readfile('Xosc52result'))})
+
+ " Yes support
+ call term_sendkeys(buf, "\<Esc>[?62;2;3;4;1;52;c")
+ call TermWait(buf)
+ call term_sendkeys(buf,
+ \ "\<Esc>:call writefile([v:termda1, v:clipmethod], 'Xosc52result')\<CR>")
+ call TermWait(buf)
+ call WaitForAssert({-> assert_equal(["\<Esc>[?62;2;3;4;1;52;c", "osc52"],
+ \ readfile('Xosc52result'))})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test if pasting works
+func Test_osc52_paste()
+ CheckScreendump
+
+ let lines =<< trim END
+ packadd osc52
+ set clipmethod=osc52
+ redraw!
+ END
+ call writefile(lines, "Xosc52.vim", "D")
+
+ let buf = RunVimInTerminal("-S Xosc52.vim", {})
+
+ call term_sendkeys(buf, "\<Esc>[?52;c")
+ call TermWait(buf)
+
+ call term_sendkeys(buf, "\"+p")
+ call TermWait(buf)
+
+ " Check to see if message is shown after a second of waiting for a response
+ sleep 1500m
+ call VerifyScreenDump(buf, 'Test_osc52_paste_01', {})
+
+ call term_sendkeys(buf, "\<Esc>]52;c;" ..
+ \ base64_encode(str2blob(["hello", "world!"])) .. "\<Esc>\")
+ call TermWait(buf)
+
+ " Check if message is gone
+ call VerifyScreenDump(buf, 'Test_osc52_paste_02', {})
+
+ " Test when invalid base64 content received (should emit a message)
+ call term_sendkeys(buf, "\"+p")
+ call TermWait(buf)
+
+ call term_sendkeys(buf, "\<Esc>]52;c;abc\<Esc>\")
+ call TermWait(buf)
+
+ call VerifyScreenDump(buf, 'Test_osc52_paste_03', {})
+
+ " Test if interrupt is handled and message is outputted
+ call term_sendkeys(buf, "\"+p")
+ call TermWait(buf)
+
+ call term_sendkeys(buf, "\<C-c>")
+ call TermWait(buf)
+
+ call VerifyScreenDump(buf, 'Test_osc52_paste_04', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index e1ea81c6b..7539181d9 100644
--- a/src/version.c
+++ b/src/version.c
@@ -734,6 +734,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 1984,
/**/
1983,
/**/