Merge pull request #25872 from gpanders/osc52

feat(clipboard): add OSC 52 clipboard support
This commit is contained in:
Gregory Anders 2023-11-07 08:47:27 -06:00 committed by GitHub
commit cd31a72f9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 75 additions and 4 deletions

View File

@ -208,6 +208,9 @@ The following new APIs and features were added.
• The |TermResponse| autocommand event can be used with |v:termresponse| to
read escape sequence responses from the terminal.
• A clipboard provider which uses OSC 52 to copy the selection to the system
clipboard is now bundled by default. |clipboard-osc52|
==============================================================================
CHANGED FEATURES *news-changed*

View File

@ -253,7 +253,35 @@ For Windows WSL, try this g:clipboard definition:
\ },
\ 'cache_enabled': 0,
\ }
<
*clipboard-osc52*
Nvim bundles a clipboard provider that allows copying to the system clipboard
using OSC 52. OSC 52 is an Operating System Command control sequence that
writes the copied text to the terminal emulator. If the terminal emulator
supports OSC 52 then it will write the copied text into the system clipboard.
This is most useful when using Nvim remotely (e.g. via ssh) as Nvim does not
have direct access to the system clipboard in that case.
Because not all terminal emulators support OSC 52, this provider must be opted
into explicitly by setting the following |g:clipboard| definition: >lua
vim.g.clipboard = {
name = 'OSC 52',
copy = {
['+'] = require('vim.clipboard.osc52').copy,
['*'] = require('vim.clipboard.osc52').copy,
},
paste = {
['+'] = require('vim.clipboard.osc52').paste,
['*'] = require('vim.clipboard.osc52').paste,
},
}
<
Note that not all terminal emulators support reading from the system clipboard
(and even for those that do, users should be aware of the security
implications), so using OSC 52 for pasting may not be possible.
<
==============================================================================
Paste *provider-paste* *paste*

View File

@ -0,0 +1,38 @@
local M = {}
function M.copy(lines)
local s = table.concat(lines, '\n')
io.stdout:write(string.format('\x1b]52;;%s\x1b\\', vim.base64.encode(s)))
end
function M.paste()
local contents = nil
local id = vim.api.nvim_create_autocmd('TermResponse', {
callback = function(args)
local resp = args.data ---@type string
local encoded = resp:match('\x1b%]52;%w?;([A-Za-z0-9+/=]*)')
if encoded then
contents = vim.base64.decode(encoded)
return true
end
end,
})
io.stdout:write('\x1b]52;;?\x1b\\')
vim.wait(1000, function()
return contents ~= nil
end)
-- Delete the autocommand if it didn't already delete itself
pcall(vim.api.nvim_del_autocmd, id)
if contents then
return vim.split(contents, '\n')
end
vim.notify('Timed out waiting for a clipboard response from the terminal', vim.log.levels.WARN)
return 0
end
return M

View File

@ -11,7 +11,6 @@
#include "nvim/api/private/helpers.h"
#include "nvim/ascii.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
#include "nvim/event/defs.h"
#include "nvim/log.h"
#include "nvim/macros.h"
@ -31,6 +30,7 @@
#include "nvim/event/rstream.h"
#include "nvim/msgpack_rpc/channel.h"
#define READ_STREAM_SIZE 0xfff
#define KEY_BUFFER_SIZE 0xfff
static const struct kitty_key_map_entry {
@ -153,7 +153,9 @@ void tinput_init(TermInput *input, Loop *loop)
termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS);
// setup input handle
rstream_init_fd(loop, &input->read_stream, input->in_fd, 0xfff);
rstream_init_fd(loop, &input->read_stream, input->in_fd, READ_STREAM_SIZE);
termkey_set_buffer_size(input->tk, rbuffer_capacity(input->read_stream.buffer));
// initialize a timer handle for handling ESC with libtermkey
time_watcher_init(loop, &input->timer_handle, input);
}
@ -758,9 +760,9 @@ static void handle_raw_buffer(TermInput *input, bool force)
RBUFFER_UNTIL_EMPTY(input->read_stream.buffer, ptr, len) {
size_t consumed = termkey_push_bytes(input->tk, ptr, MIN(count, len));
// termkey_push_bytes can return (size_t)-1, so it is possible that
// `consumed > input->read_stream.buffer->size`, but since tk_getkeys is
// `consumed > rbuffer_size(input->read_stream.buffer)`, but since tk_getkeys is
// called soon, it shouldn't happen.
assert(consumed <= input->read_stream.buffer->size);
assert(consumed <= rbuffer_size(input->read_stream.buffer));
rbuffer_consumed(input->read_stream.buffer, consumed);
// Process the keys now: there is no guarantee `count` will
// fit into libtermkey's input buffer.