mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
tui: Add support bracketed paste
Inspired by the vim-bracketed-paste plugin but adapted for the new TUI. Also initialize some variables of type `Error` that were uninitialized
This commit is contained in:
parent
26371f4ccc
commit
59fb8f8172
@ -1,10 +1,12 @@
|
|||||||
#include <termkey.h>
|
#include <termkey.h>
|
||||||
|
|
||||||
#include "nvim/ascii.h"
|
#include "nvim/ascii.h"
|
||||||
|
#include "nvim/misc2.h"
|
||||||
#include "nvim/os/os.h"
|
#include "nvim/os/os.h"
|
||||||
#include "nvim/os/input.h"
|
#include "nvim/os/input.h"
|
||||||
#include "nvim/os/rstream.h"
|
#include "nvim/os/rstream.h"
|
||||||
|
|
||||||
|
#define PASTETOGGLE_KEY "<f37>"
|
||||||
|
|
||||||
struct term_input {
|
struct term_input {
|
||||||
int in_fd;
|
int in_fd;
|
||||||
@ -113,7 +115,7 @@ static int get_key_code_timeout(void)
|
|||||||
bool timeout = false;
|
bool timeout = false;
|
||||||
// Check 'timeout' and 'ttimeout' to determine if we should send ESC
|
// Check 'timeout' and 'ttimeout' to determine if we should send ESC
|
||||||
// after 'ttimeoutlen'. See :help 'ttimeout' for more information
|
// after 'ttimeoutlen'. See :help 'ttimeout' for more information
|
||||||
Error err;
|
Error err = ERROR_INIT;
|
||||||
timeout = vim_get_option(cstr_as_string("timeout"), &err).data.boolean;
|
timeout = vim_get_option(cstr_as_string("timeout"), &err).data.boolean;
|
||||||
if (!timeout) {
|
if (!timeout) {
|
||||||
timeout = vim_get_option(cstr_as_string("ttimeout"), &err).data.boolean;
|
timeout = vim_get_option(cstr_as_string("ttimeout"), &err).data.boolean;
|
||||||
@ -164,6 +166,49 @@ static void timer_cb(uv_timer_t *handle)
|
|||||||
tk_getkeys(handle->data, true);
|
tk_getkeys(handle->data, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool handle_bracketed_paste(TermInput *input)
|
||||||
|
{
|
||||||
|
char *ptr = rbuffer_read_ptr(input->read_buffer);
|
||||||
|
size_t len = rbuffer_pending(input->read_buffer);
|
||||||
|
if (len > 5 && (!strncmp(ptr, "\x1b[200~", 6)
|
||||||
|
|| !strncmp(ptr, "\x1b[201~", 6))) {
|
||||||
|
bool enable = ptr[4] == '0';
|
||||||
|
// Advance past the sequence
|
||||||
|
rbuffer_consumed(input->read_buffer, 6);
|
||||||
|
if (enable) {
|
||||||
|
// Get the current mode
|
||||||
|
int state = get_real_state();
|
||||||
|
if (state & NORMAL) {
|
||||||
|
// Enter insert mode
|
||||||
|
input_enqueue(cstr_as_string("i"));
|
||||||
|
} else if (state & VISUAL) {
|
||||||
|
// Remove the selected text and enter insert mode
|
||||||
|
input_enqueue(cstr_as_string("c"));
|
||||||
|
} else if (!(state & INSERT)) {
|
||||||
|
// Don't mess with the paste option
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input_enqueue(cstr_as_string(PASTETOGGLE_KEY));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_forced_escape(TermInput *input)
|
||||||
|
{
|
||||||
|
char *ptr = rbuffer_read_ptr(input->read_buffer);
|
||||||
|
size_t len = rbuffer_pending(input->read_buffer);
|
||||||
|
if (len > 1 && ptr[0] == ESC && ptr[1] == NUL) {
|
||||||
|
// skip the ESC and NUL and push one <esc> to the input buffer
|
||||||
|
termkey_push_bytes(input->tk, ptr, 1);
|
||||||
|
rbuffer_consumed(input->read_buffer, 2);
|
||||||
|
tk_getkeys(input, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static void read_cb(RStream *rstream, void *rstream_data, bool eof)
|
static void read_cb(RStream *rstream, void *rstream_data, bool eof)
|
||||||
{
|
{
|
||||||
if (eof) {
|
if (eof) {
|
||||||
@ -174,15 +219,11 @@ static void read_cb(RStream *rstream, void *rstream_data, bool eof)
|
|||||||
TermInput *input = rstream_data;
|
TermInput *input = rstream_data;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
char *ptr = rbuffer_read_ptr(input->read_buffer);
|
if (handle_bracketed_paste(input) || handle_forced_escape(input)) {
|
||||||
size_t len = rbuffer_pending(input->read_buffer);
|
|
||||||
if (len > 1 && ptr[0] == ESC && ptr[1] == NUL) {
|
|
||||||
// skip the ESC and NUL and push one <esc> to the input buffer
|
|
||||||
termkey_push_bytes(input->tk, ptr, 1);
|
|
||||||
rbuffer_consumed(input->read_buffer, 2);
|
|
||||||
tk_getkeys(input, true);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
char *ptr = rbuffer_read_ptr(input->read_buffer);
|
||||||
|
size_t len = rbuffer_pending(input->read_buffer);
|
||||||
// Find the next 'esc' and push everything up to it(excluding)
|
// Find the next 'esc' and push everything up to it(excluding)
|
||||||
size_t i;
|
size_t i;
|
||||||
for (i = ptr[0] == ESC ? 1 : 0; i < len; i++) {
|
for (i = ptr[0] == ESC ? 1 : 0; i < len; i++) {
|
||||||
@ -228,6 +269,11 @@ static TermInput *term_input_new(void)
|
|||||||
// initialize a timer handle for handling ESC with libtermkey
|
// initialize a timer handle for handling ESC with libtermkey
|
||||||
uv_timer_init(uv_default_loop(), &rv->timer_handle);
|
uv_timer_init(uv_default_loop(), &rv->timer_handle);
|
||||||
rv->timer_handle.data = rv;
|
rv->timer_handle.data = rv;
|
||||||
|
// Set the pastetoggle option to a special key that will be sent when
|
||||||
|
// \e[20{0,1}~/ are received
|
||||||
|
Error err = ERROR_INIT;
|
||||||
|
vim_set_option(cstr_as_string("pastetoggle"),
|
||||||
|
STRING_OBJ(cstr_as_string(PASTETOGGLE_KEY)), &err);
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ typedef struct {
|
|||||||
Cell **screen;
|
Cell **screen;
|
||||||
struct {
|
struct {
|
||||||
size_t enable_mouse, disable_mouse;
|
size_t enable_mouse, disable_mouse;
|
||||||
|
size_t enable_bracketed_paste, disable_bracketed_paste;
|
||||||
} unibi_ext;
|
} unibi_ext;
|
||||||
} TUIData;
|
} TUIData;
|
||||||
|
|
||||||
@ -103,6 +104,8 @@ void tui_start(void)
|
|||||||
// Enter alternate screen and clear
|
// Enter alternate screen and clear
|
||||||
unibi_out(ui, unibi_enter_ca_mode, NULL);
|
unibi_out(ui, unibi_enter_ca_mode, NULL);
|
||||||
unibi_out(ui, unibi_clear_screen, NULL);
|
unibi_out(ui, unibi_clear_screen, NULL);
|
||||||
|
// Enable bracketed paste
|
||||||
|
unibi_out(ui, (int)data->unibi_ext.enable_bracketed_paste, NULL);
|
||||||
|
|
||||||
// setup output handle in a separate event loop(we wanna do synchronous
|
// setup output handle in a separate event loop(we wanna do synchronous
|
||||||
// write to the tty)
|
// write to the tty)
|
||||||
@ -162,6 +165,8 @@ static void tui_stop(UI *ui)
|
|||||||
unibi_out(ui, unibi_exit_attribute_mode, NULL);
|
unibi_out(ui, unibi_exit_attribute_mode, NULL);
|
||||||
unibi_out(ui, unibi_cursor_normal, NULL);
|
unibi_out(ui, unibi_cursor_normal, NULL);
|
||||||
unibi_out(ui, unibi_exit_ca_mode, NULL);
|
unibi_out(ui, unibi_exit_ca_mode, NULL);
|
||||||
|
// Disable bracketed paste
|
||||||
|
unibi_out(ui, (int)data->unibi_ext.disable_bracketed_paste, NULL);
|
||||||
flush_buf(ui);
|
flush_buf(ui);
|
||||||
uv_close((uv_handle_t *)&data->output_handle, NULL);
|
uv_close((uv_handle_t *)&data->output_handle, NULL);
|
||||||
uv_run(data->write_loop, UV_RUN_DEFAULT);
|
uv_run(data->write_loop, UV_RUN_DEFAULT);
|
||||||
@ -665,6 +670,8 @@ static void fix_terminfo(TUIData *data)
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool inside_tmux = os_getenv("TMUX") != NULL;
|
||||||
|
|
||||||
#define STARTS_WITH(str, prefix) (!memcmp(str, prefix, sizeof(prefix) - 1))
|
#define STARTS_WITH(str, prefix) (!memcmp(str, prefix, sizeof(prefix) - 1))
|
||||||
|
|
||||||
if (STARTS_WITH(term, "rxvt")) {
|
if (STARTS_WITH(term, "rxvt")) {
|
||||||
@ -689,13 +696,19 @@ static void fix_terminfo(TUIData *data)
|
|||||||
unibi_set_if_empty(ut, unibi_from_status_line, "\x07");
|
unibi_set_if_empty(ut, unibi_from_status_line, "\x07");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (STARTS_WITH(term, "xterm") || STARTS_WITH(term, "rxvt") || inside_tmux) {
|
||||||
|
data->unibi_ext.enable_bracketed_paste = unibi_add_ext_str(ut, NULL,
|
||||||
|
"\x1b[?2004h");
|
||||||
|
data->unibi_ext.disable_bracketed_paste = unibi_add_ext_str(ut, NULL,
|
||||||
|
"\x1b[?2004l");
|
||||||
|
}
|
||||||
|
|
||||||
end:
|
end:
|
||||||
// Fill some empty slots with common terminal strings
|
// Fill some empty slots with common terminal strings
|
||||||
data->unibi_ext.enable_mouse = unibi_add_ext_str(ut, NULL,
|
data->unibi_ext.enable_mouse = unibi_add_ext_str(ut, NULL,
|
||||||
"\x1b[?1002h\x1b[?1006h");
|
"\x1b[?1002h\x1b[?1006h");
|
||||||
data->unibi_ext.disable_mouse = unibi_add_ext_str(ut, NULL,
|
data->unibi_ext.disable_mouse = unibi_add_ext_str(ut, NULL,
|
||||||
"\x1b[?1002l\x1b[?1006l");
|
"\x1b[?1002l\x1b[?1006l");
|
||||||
|
|
||||||
unibi_set_if_empty(ut, unibi_cursor_address, "\x1b[%i%p1%d;%p2%dH");
|
unibi_set_if_empty(ut, unibi_cursor_address, "\x1b[%i%p1%d;%p2%dH");
|
||||||
unibi_set_if_empty(ut, unibi_exit_attribute_mode, "\x1b[0;10m");
|
unibi_set_if_empty(ut, unibi_exit_attribute_mode, "\x1b[0;10m");
|
||||||
unibi_set_if_empty(ut, unibi_set_a_foreground,
|
unibi_set_if_empty(ut, unibi_set_a_foreground,
|
||||||
@ -735,7 +748,7 @@ static char *get_term_option(UI *ui, char *option)
|
|||||||
|
|
||||||
char *rv = pmap_get(cstr_t)(data->option_cache, option);
|
char *rv = pmap_get(cstr_t)(data->option_cache, option);
|
||||||
if (!rv) {
|
if (!rv) {
|
||||||
Error err;
|
Error err = ERROR_INIT;
|
||||||
Object val = vim_get_option(cstr_as_string(option), &err);
|
Object val = vim_get_option(cstr_as_string(option), &err);
|
||||||
if (val.type == kObjectTypeString) {
|
if (val.type == kObjectTypeString) {
|
||||||
rv = val.data.string.data;
|
rv = val.data.string.data;
|
||||||
|
Loading…
Reference in New Issue
Block a user