mirror of
https://github.com/neovim/neovim.git
synced 2025-10-26 12:27:24 +00:00
Merge pull request #25872 from gpanders/osc52
feat(clipboard): add OSC 52 clipboard support
This commit is contained in:
@@ -208,6 +208,9 @@ The following new APIs and features were added.
|
|||||||
• The |TermResponse| autocommand event can be used with |v:termresponse| to
|
• The |TermResponse| autocommand event can be used with |v:termresponse| to
|
||||||
read escape sequence responses from the terminal.
|
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*
|
CHANGED FEATURES *news-changed*
|
||||||
|
|
||||||
|
|||||||
@@ -253,7 +253,35 @@ For Windows WSL, try this g:clipboard definition:
|
|||||||
\ },
|
\ },
|
||||||
\ 'cache_enabled': 0,
|
\ '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*
|
Paste *provider-paste* *paste*
|
||||||
|
|
||||||
|
|||||||
38
runtime/lua/vim/clipboard/osc52.lua
Normal file
38
runtime/lua/vim/clipboard/osc52.lua
Normal 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
|
||||||
@@ -11,7 +11,6 @@
|
|||||||
#include "nvim/api/private/helpers.h"
|
#include "nvim/api/private/helpers.h"
|
||||||
#include "nvim/ascii.h"
|
#include "nvim/ascii.h"
|
||||||
#include "nvim/charset.h"
|
#include "nvim/charset.h"
|
||||||
#include "nvim/eval.h"
|
|
||||||
#include "nvim/event/defs.h"
|
#include "nvim/event/defs.h"
|
||||||
#include "nvim/log.h"
|
#include "nvim/log.h"
|
||||||
#include "nvim/macros.h"
|
#include "nvim/macros.h"
|
||||||
@@ -31,6 +30,7 @@
|
|||||||
#include "nvim/event/rstream.h"
|
#include "nvim/event/rstream.h"
|
||||||
#include "nvim/msgpack_rpc/channel.h"
|
#include "nvim/msgpack_rpc/channel.h"
|
||||||
|
|
||||||
|
#define READ_STREAM_SIZE 0xfff
|
||||||
#define KEY_BUFFER_SIZE 0xfff
|
#define KEY_BUFFER_SIZE 0xfff
|
||||||
|
|
||||||
static const struct kitty_key_map_entry {
|
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);
|
termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS);
|
||||||
|
|
||||||
// setup input handle
|
// 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
|
// initialize a timer handle for handling ESC with libtermkey
|
||||||
time_watcher_init(loop, &input->timer_handle, input);
|
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) {
|
RBUFFER_UNTIL_EMPTY(input->read_stream.buffer, ptr, len) {
|
||||||
size_t consumed = termkey_push_bytes(input->tk, ptr, MIN(count, 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
|
// 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.
|
// 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);
|
rbuffer_consumed(input->read_stream.buffer, consumed);
|
||||||
// Process the keys now: there is no guarantee `count` will
|
// Process the keys now: there is no guarantee `count` will
|
||||||
// fit into libtermkey's input buffer.
|
// fit into libtermkey's input buffer.
|
||||||
|
|||||||
Reference in New Issue
Block a user