aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/eval.txt4
-rw-r--r--src/nvim/event/loop.c9
-rw-r--r--src/nvim/ex_getln.c2
-rw-r--r--src/nvim/getchar.c14
-rw-r--r--src/nvim/globals.h6
-rw-r--r--src/nvim/keymap.c1
-rw-r--r--src/nvim/keymap.h5
-rw-r--r--src/nvim/os/input.c4
-rw-r--r--src/nvim/terminal.c4
-rw-r--r--src/nvim/tui/input.c43
-rw-r--r--src/nvim/types.h6
-rw-r--r--test/functional/terminal/helpers.lua3
-rw-r--r--test/functional/terminal/paste_spec.lua204
-rw-r--r--test/functional/ui/input_spec.lua8
14 files changed, 265 insertions, 48 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 5d30ac15b3..2ec72f7717 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -4215,11 +4215,11 @@ getchar([expr]) *getchar()*
:endfunction
<
You may also receive synthetic characters, such as
- |<CursorHold>|. Often you will want to ignore this and get
+ |<LeftMouse>|. Often you will want to ignore this and get
another character: >
:function GetKey()
: let c = getchar()
- : while c == "\<CursorHold>"
+ : while c == "\<LeftMouse>"
: let c = getchar()
: endwhile
: return c
diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c
index 609c723c57..93ec28bb81 100644
--- a/src/nvim/event/loop.c
+++ b/src/nvim/event/loop.c
@@ -36,6 +36,10 @@ void loop_init(Loop *loop, void *data)
/// Processes all `Loop.fast_events` events.
/// Does NOT process `Loop.events`, that is an application-specific decision.
///
+/// @param loop
+/// @param ms 0: non-blocking poll.
+/// >0: timeout after `ms`.
+/// <0: wait forever.
/// @returns true if `ms` timeout was reached
bool loop_poll_events(Loop *loop, int ms)
{
@@ -104,10 +108,10 @@ static void loop_deferred_event(void **argv)
void loop_on_put(MultiQueue *queue, void *data)
{
Loop *loop = data;
- // Sometimes libuv will run pending callbacks(timer for example) before
+ // Sometimes libuv will run pending callbacks (timer for example) before
// blocking for a poll. If this happens and the callback pushes a event to one
// of the queues, the event would only be processed after the poll
- // returns(user hits a key for example). To avoid this scenario, we call
+ // returns (user hits a key for example). To avoid this scenario, we call
// uv_stop when a event is enqueued.
uv_stop(&loop->uv);
}
@@ -162,6 +166,7 @@ static void async_cb(uv_async_t *handle)
{
Loop *l = handle->loop->data;
uv_mutex_lock(&l->mutex);
+ // Flush thread_events to fast_events for processing on main loop.
while (!multiqueue_empty(l->thread_events)) {
Event ev = multiqueue_get(l->thread_events);
multiqueue_put_event(l->fast_events, ev);
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index e8d650accf..2b01e2d72b 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -532,7 +532,7 @@ static int command_line_check(VimState *state)
static int command_line_execute(VimState *state, int key)
{
- if (key == K_IGNORE || key == K_PASTE) {
+ if (key == K_IGNORE) {
return -1; // get another key
}
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 03f64c2019..d1b4751a00 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -151,7 +151,6 @@ static char_u typebuf_init[TYPELEN_INIT]; /* initial typebuf.tb_buf */
static char_u noremapbuf_init[TYPELEN_INIT]; /* initial typebuf.tb_noremap */
static size_t last_recorded_len = 0; // number of last recorded chars
-static const uint8_t ui_toggle[] = { K_SPECIAL, KS_EXTRA, KE_PASTE, 0 };
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "getchar.c.generated.h"
@@ -1902,14 +1901,8 @@ static int vgetorpeek(int advance)
}
}
- // Check for a key that can toggle the 'paste' option
- if (mp == NULL && (State & (INSERT|NORMAL))) {
- bool match = typebuf_match_len(ui_toggle, &mlen);
- if (!match && mlen != typebuf.tb_len && *p_pt != NUL) {
- // didn't match ui_toggle_key and didn't try the whole typebuf,
- // check the 'pastetoggle'
- match = typebuf_match_len(p_pt, &mlen);
- }
+ if (*p_pt != NUL && mp == NULL && (State & (INSERT|NORMAL))) {
+ bool match = typebuf_match_len(p_pt, &mlen);
if (match) {
// write chars to script file(s)
if (mlen > typebuf.tb_maplen) {
@@ -1940,8 +1933,7 @@ static int vgetorpeek(int advance)
}
if ((mp == NULL || max_mlen >= mp_match_len)
- && keylen != KEYLEN_PART_MAP
- && !(keylen == KEYLEN_PART_KEY && c1 == ui_toggle[0])) {
+ && keylen != KEYLEN_PART_MAP) {
// No matching mapping found or found a non-matching mapping that
// matches at least what the matching mapping matched
keylen = 0;
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 3bdbff79b4..82fc7c1218 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -72,12 +72,6 @@
# define VIMRC_FILE ".nvimrc"
#endif
-typedef enum {
- kNone = -1,
- kFalse = 0,
- kTrue = 1,
-} TriState;
-
EXTERN struct nvim_stats_s {
int64_t fsync;
int64_t redraw;
diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c
index 27052da9d8..2d117b5308 100644
--- a/src/nvim/keymap.c
+++ b/src/nvim/keymap.c
@@ -309,7 +309,6 @@ static const struct key_name_entry {
{ K_ZERO, "Nul" },
{ K_SNR, "SNR" },
{ K_PLUG, "Plug" },
- { K_PASTE, "Paste" },
{ K_COMMAND, "Cmd" },
{ 0, NULL }
// NOTE: When adding a long name update MAX_KEY_NAME_LEN.
diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h
index 7f0483826d..cc02a6fb4f 100644
--- a/src/nvim/keymap.h
+++ b/src/nvim/keymap.h
@@ -239,14 +239,12 @@ enum key_extra {
, KE_DROP = 95 // DnD data is available
// , KE_CURSORHOLD = 96 // CursorHold event
- , KE_NOP = 97 // doesn't do something
+ , KE_NOP = 97 // no-op: does nothing
, KE_FOCUSGAINED = 98 // focus gained
, KE_FOCUSLOST = 99 // focus lost
// , KE_MOUSEMOVE = 100 // mouse moved with no button down
// , KE_CANCEL = 101 // return from vgetc
, KE_EVENT = 102 // event
- , KE_PASTE = 103 // special key to toggle the 'paste' option.
- // sent only by UIs
, KE_COMMAND = 104 // <Cmd> special key
};
@@ -443,7 +441,6 @@ enum key_extra {
#define K_DROP TERMCAP2KEY(KS_EXTRA, KE_DROP)
#define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT)
-#define K_PASTE TERMCAP2KEY(KS_EXTRA, KE_PASTE)
#define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND)
/* Bits for modifier mask */
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 95e9e8e414..83ac3dfa62 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -448,7 +448,7 @@ static void process_interrupts(void)
size_t consume_count = 0;
RBUFFER_EACH_REVERSE(input_buffer, c, i) {
- if ((uint8_t)c == 3) {
+ if ((uint8_t)c == Ctrl_C) {
got_int = true;
consume_count = i;
break;
@@ -456,7 +456,7 @@ static void process_interrupts(void)
}
if (got_int && consume_count) {
- // Remove everything typed before the CTRL-C
+ // Remove all unprocessed input (typeahead) before the CTRL-C.
rbuffer_consumed(input_buffer, consume_count);
}
}
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 3faf6dd5bb..ffa05e2599 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -475,10 +475,6 @@ static int terminal_execute(VimState *state, int key)
TerminalState *s = (TerminalState *)state;
switch (key) {
- // Temporary fix until paste events gets implemented
- case K_PASTE:
- break;
-
case K_LEFTMOUSE:
case K_LEFTDRAG:
case K_LEFTRELEASE:
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index fe8ffee8e0..f090b1384a 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -16,7 +16,8 @@
#include "nvim/os/input.h"
#include "nvim/event/rstream.h"
-#define PASTETOGGLE_KEY "<Paste>"
+#define PASTE_KEY "<Paste>"
+#define PASTEPOST_KEY "<PastePost>"
#define KEY_BUFFER_SIZE 0xfff
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -292,9 +293,12 @@ static void tk_getkeys(TermInput *input, bool force)
}
}
- if (result != TERMKEY_RES_AGAIN || input->paste_enabled) {
+ if (result != TERMKEY_RES_AGAIN) {
return;
}
+ // else: Partial keypress event was found in the buffer, but it does not
+ // yet contain all the bytes required. `key` structure indicates what
+ // termkey_getkey_force() would return.
int ms = get_key_code_timeout();
@@ -326,8 +330,8 @@ static bool handle_focus_event(TermInput *input)
if (rbuffer_size(input->read_stream.buffer) > 2
&& (!rbuffer_cmp(input->read_stream.buffer, "\x1b[I", 3)
|| !rbuffer_cmp(input->read_stream.buffer, "\x1b[O", 3))) {
- // Advance past the sequence
bool focus_gained = *rbuffer_get(input->read_stream.buffer, 2) == 'I';
+ // Advance past the sequence
rbuffer_consumed(input->read_stream.buffer, 3);
aucmd_schedule_focusgained(focus_gained);
return true;
@@ -343,16 +347,31 @@ static bool handle_bracketed_paste(TermInput *input)
bool enable = *rbuffer_get(input->read_stream.buffer, 4) == '0';
// Advance past the sequence
rbuffer_consumed(input->read_stream.buffer, 6);
- if (input->paste_enabled == enable) {
+ if (enable && input->paste_enabled) {
+ // Bogus "paste start"; forward it.
+ tinput_enqueue(input, "<C-v><Esc>200~", sizeof("<C-v><Esc>200~") - 1);
+ return true;
+ } else if (!enable && !input->paste_enabled) {
+ // Bogus "paste stop"; ignore it.
return true;
}
- tinput_enqueue(input, PASTETOGGLE_KEY, sizeof(PASTETOGGLE_KEY) - 1);
+
input->paste_enabled = enable;
+ if (enable) {
+ loop_schedule(&main_loop, event_create(apply_pastepre, 0));
+ } else {
+ tinput_enqueue(input, PASTEPOST_KEY, sizeof(PASTEPOST_KEY) - 1);
+ }
return true;
}
return false;
}
+static void apply_pastepre(void **argv) // MAIN thread
+{
+ //TODO
+}
+
static bool handle_forced_escape(TermInput *input)
{
if (rbuffer_size(input->read_stream.buffer) > 1
@@ -477,9 +496,11 @@ static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_,
continue;
}
- // Find the next 'esc' and push everything up to it(excluding). This is done
- // so the `handle_bracketed_paste`/`handle_forced_escape` calls above work
- // as expected.
+ //
+ // Find the next ESC and push everything up to it (excluding), so it will
+ // be the first thing encountered on the next iteration. The `handle_*`
+ // calls (above) depend on this.
+ //
size_t count = 0;
RBUFFER_EACH(input->read_stream.buffer, c, i) {
count = i + 1;
@@ -488,7 +509,6 @@ static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_,
break;
}
}
-
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
@@ -505,7 +525,8 @@ static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_,
}
} while (rbuffer_size(input->read_stream.buffer));
tinput_flush(input, true);
- // Make sure the next input escape sequence fits into the ring buffer
- // without wrap around, otherwise it could be misinterpreted.
+ // Make sure the next input escape sequence fits into the ring buffer without
+ // wraparound, else it could be misinterpreted (because rbuffer_read_ptr()
+ // exposes the underlying buffer to callers unaware of the wraparound).
rbuffer_reset(input->read_stream.buffer);
}
diff --git a/src/nvim/types.h b/src/nvim/types.h
index 5bcc0c3e1b..87560a43da 100644
--- a/src/nvim/types.h
+++ b/src/nvim/types.h
@@ -23,4 +23,10 @@ typedef int LuaRef;
typedef struct expand expand_T;
+typedef enum {
+ kNone = -1,
+ kFalse = 0,
+ kTrue = 1,
+} TriState;
+
#endif // NVIM_TYPES_H
diff --git a/test/functional/terminal/helpers.lua b/test/functional/terminal/helpers.lua
index 18f0b9e4c1..2d99a08614 100644
--- a/test/functional/terminal/helpers.lua
+++ b/test/functional/terminal/helpers.lua
@@ -1,3 +1,6 @@
+-- To test tui/input.c, this module spawns `nvim` inside :terminal and sends
+-- bytes via jobsend(). Note: the functional/helpers.lua test-session methods
+-- operate on the _host_ session, _not_ the child session.
local helpers = require('test.functional.helpers')(nil)
local Screen = require('test.functional.ui.screen')
local nvim_dir = helpers.nvim_dir
diff --git a/test/functional/terminal/paste_spec.lua b/test/functional/terminal/paste_spec.lua
new file mode 100644
index 0000000000..1c1f57246c
--- /dev/null
+++ b/test/functional/terminal/paste_spec.lua
@@ -0,0 +1,204 @@
+-- TUI tests for "bracketed paste" mode.
+-- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode
+local helpers = require('test.functional.helpers')
+local child_tui = require('test.functional.tui.child_session')
+local Screen = require('test.functional.ui.screen')
+local execute = helpers.execute
+local nvim_dir = helpers.nvim_dir
+local eval = helpers.eval
+local eq = helpers.eq
+local feed_tui = child_tui.feed_data
+
+describe('tui paste', function()
+ local screen
+
+ before_each(function()
+ helpers.clear()
+ screen = child_tui.screen_setup(0, '["'..helpers.nvim_prog..
+ '", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile"]')
+
+ -- Pasting can be really slow in the TUI, especially in ASAN.
+ screen.timeout = 5000
+
+ screen:expect([[
+ {1: } |
+ ~ |
+ ~ |
+ ~ |
+ [No Name] |
+ |
+ -- TERMINAL -- |
+ ]])
+ end)
+
+ after_each(function()
+ screen:detach()
+ end)
+
+ local function setup_harness()
+ -- Delete the default PastePre/PastePost autocmds.
+ feed_tui(":autocmd! PastePre,PastePost\n")
+
+ -- Set up test handlers.
+ feed_tui(":autocmd PastePre * "..
+ "call feedkeys('iPastePre mode:'.mode(),'n')\n")
+ feed_tui(":autocmd PastePost * "..
+ "call feedkeys('PastePost mode:'.mode(),'n')\n")
+ end
+
+ it('handles long bursts of input', function()
+ execute('set ruler')
+ local t = {}
+ for i = 1, 3000 do
+ t[i] = 'item ' .. tostring(i)
+ end
+ feed_tui('i\027[200~')
+ feed_tui(table.concat(t, '\n'))
+ feed_tui('\027[201~')
+ screen:expect([[
+ item 2997 |
+ item 2998 |
+ item 2999 |
+ item 3000{1: } |
+ [No Name] [+] 3000,10 Bot|
+ -- INSERT -- |
+ -- TERMINAL -- |
+ ]])
+ end)
+
+ it('raises PastePre, PastePost in normal-mode', function()
+ setup_harness()
+
+ -- Send the "start paste" sequence.
+ feed_tui("\027[200~")
+ feed_tui("\npasted from terminal (1)\npasted from terminal (2)\n")
+ -- Send the "stop paste" sequence.
+ feed_tui("\027[201~")
+
+ screen:expect([[
+ PastePre mode:n |
+ pasted from terminal (1) |
+ pasted from terminal (2) |
+ PastePost mode:i{1: } |
+ [No Name] [+] |
+ -- INSERT -- |
+ -- TERMINAL -- |
+ ]])
+ end)
+
+ it('forwards spurious "start paste" sequence', function()
+ setup_harness()
+ -- If multiple "start paste" sequences are sent without a corresponding
+ -- "stop paste" sequence, only the first occurrence should be consumed.
+
+ -- Send the "start paste" sequence.
+ feed_tui("\027[200~")
+ feed_tui("\npasted from terminal (1)\n")
+ -- Send spurious "start paste" sequence.
+ feed_tui("\027[200~")
+ feed_tui("\n")
+ -- Send the "stop paste" sequence.
+ feed_tui("\027[201~")
+
+ screen:expect([[
+ PastePre mode:n |
+ pasted from terminal (1) |
+ {1:^[}200~ |
+ PastePost mode:i{2: } |
+ [No Name] [+] |
+ -- INSERT -- |
+ -- TERMINAL -- |
+ ]], {
+ [1] = {foreground = 4},
+ [2] = {reverse = true},
+ })
+ end)
+
+ it('ignores spurious "stop paste" sequence', function()
+ setup_harness()
+ -- If "stop paste" sequence is received without a preceding "start paste"
+ -- sequence, it should be ignored.
+
+ feed_tui("i")
+ -- Send "stop paste" sequence.
+ feed_tui("\027[201~")
+
+ screen:expect([[
+ {1: } |
+ ~ |
+ ~ |
+ ~ |
+ [No Name] |
+ -- INSERT -- |
+ -- TERMINAL -- |
+ ]])
+ end)
+
+ it('raises PastePre, PastePost in command-mode', function()
+ -- The default PastePre/PastePost handlers set the 'paste' option. To test,
+ -- we define a command-mode map, then assert that the mapping was ignored
+ -- during paste.
+ feed_tui(":cnoremap st XXX\n")
+
+ feed_tui(":not pasted")
+
+ -- Paste did not start, so the mapping _should_ apply.
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ ~ |
+ [No Name] |
+ :not paXXXed{1: } |
+ -- TERMINAL -- |
+ ]])
+
+ feed_tui("\003") -- CTRL-C
+ feed_tui(":")
+ feed_tui("\027[200~") -- Send the "start paste" sequence.
+ feed_tui("pasted")
+
+ -- Paste started, so the mapping should _not_ apply.
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ ~ |
+ [No Name] |
+ :pasted{1: } |
+ -- TERMINAL -- |
+ ]])
+
+ feed_tui("\003") -- CTRL-C
+ feed_tui(":")
+ feed_tui("\027[201~") -- Send the "stop paste" sequence.
+ feed_tui("not pasted")
+
+ -- Paste stopped, so the mapping _should_ apply.
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ ~ |
+ [No Name] |
+ :not paXXXed{1: } |
+ -- TERMINAL -- |
+ ]])
+
+ end)
+
+ -- TODO
+ it('sets undo-point after consecutive pastes', function()
+ end)
+
+ -- TODO
+ it('handles missing "stop paste" sequence', function()
+ end)
+
+ -- TODO: error when pasting into 'nomodifiable' buffer:
+ -- [error @ do_put:2656] 17043 - Failed to save undo information
+ it("handles 'nomodifiable' buffer gracefully", function()
+ end)
+
+end)
+
diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua
index 0009f2c31b..7b5c6aa29d 100644
--- a/test/functional/ui/input_spec.lua
+++ b/test/functional/ui/input_spec.lua
@@ -127,7 +127,7 @@ describe('feeding large chunks of input with <Paste>', function()
for i = 1, 20000 do
t[i] = 'item ' .. tostring(i)
end
- feed('i<Paste>')
+ command('doautocmd PastePre')
screen:expect([[
^ |
~ |
@@ -161,7 +161,7 @@ describe('feeding large chunks of input with <Paste>', function()
item 20000^ |
-- INSERT (paste) -- |
]])
- feed('<Paste>')
+ command('doautocmd PastePost')
screen:expect([[
item 19988 |
item 19989 |
@@ -175,8 +175,8 @@ describe('feeding large chunks of input with <Paste>', function()
item 19997 |
item 19998 |
item 19999 |
- item 20000^ |
- -- INSERT -- 20000,11 Bot |
+ item 2000^0 |
+ 20000,10 Bot |
]])
end)
end)