mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge #4448 'paste: redesign'
fix #3447 fix #3566 fix #7066 fix #7212 fix #7273 fix #7455 fix #10415 NA vim-patches: vim-patch:8.1.1198 vim-patch:8.1.0224 vim-patch:8.0.1299 vim-patch:8.0.0569 vim-patch:8.0.0303 vim-patch:8.0.0296 vim-patch:8.0.0244 vim-patch:8.0.0238 vim-patch:8.0.0232 vim-patch:8.0.0231 vim-patch:8.0.0230 vim-patch:8.0.0210
This commit is contained in:
commit
82d52b229d
@ -793,6 +793,49 @@ nvim_get_namespaces() *nvim_get_namespaces()*
|
|||||||
Return: ~
|
Return: ~
|
||||||
dict that maps from names to namespace ids.
|
dict that maps from names to namespace ids.
|
||||||
|
|
||||||
|
nvim_paste({data}, {phase}) *nvim_paste()*
|
||||||
|
Pastes at cursor, in any mode.
|
||||||
|
|
||||||
|
Invokes the `vim.paste` handler, which handles each mode
|
||||||
|
appropriately. Sets redo/undo. Faster than |nvim_input()|.
|
||||||
|
|
||||||
|
Errors ('nomodifiable', `vim.paste()` failure, …) are
|
||||||
|
reflected in `err` but do not affect the return value (which
|
||||||
|
is strictly decided by `vim.paste()` ). On error, subsequent
|
||||||
|
calls are ignored ("drained") until the next paste is
|
||||||
|
initiated (phase 1 or -1).
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{data} Multiline input. May be binary (containing NUL
|
||||||
|
bytes).
|
||||||
|
{phase} -1: paste in a single call (i.e. without
|
||||||
|
streaming). To "stream" a paste, call `nvim_paste` sequentially with these `phase` values:
|
||||||
|
• 1: starts the paste (exactly once)
|
||||||
|
• 2: continues the paste (zero or more times)
|
||||||
|
• 3: ends the paste (exactly once)
|
||||||
|
|
||||||
|
Return: ~
|
||||||
|
|
||||||
|
• true: Client may continue pasting.
|
||||||
|
• false: Client must cancel the paste.
|
||||||
|
|
||||||
|
nvim_put({lines}, {type}, {after}, {follow}) *nvim_put()*
|
||||||
|
Puts text at cursor, in any mode.
|
||||||
|
|
||||||
|
Compare |:put| and |p| which are always linewise.
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{lines} |readfile()|-style list of lines.
|
||||||
|
|channel-lines|
|
||||||
|
{type} Edit behavior:
|
||||||
|
• "b" |blockwise-visual| mode
|
||||||
|
• "c" |characterwise| mode
|
||||||
|
• "l" |linewise| mode
|
||||||
|
• "" guess by contents
|
||||||
|
{after} Insert after cursor (like |p|), or before (like
|
||||||
|
|P|).
|
||||||
|
{follow} Place cursor at end of inserted text.
|
||||||
|
|
||||||
nvim_subscribe({event}) *nvim_subscribe()*
|
nvim_subscribe({event}) *nvim_subscribe()*
|
||||||
Subscribes to event broadcasts.
|
Subscribes to event broadcasts.
|
||||||
|
|
||||||
|
@ -4214,17 +4214,6 @@ getchar([expr]) *getchar()*
|
|||||||
: endwhile
|
: endwhile
|
||||||
:endfunction
|
:endfunction
|
||||||
<
|
<
|
||||||
You may also receive synthetic characters, such as
|
|
||||||
|<CursorHold>|. Often you will want to ignore this and get
|
|
||||||
another character: >
|
|
||||||
:function GetKey()
|
|
||||||
: let c = getchar()
|
|
||||||
: while c == "\<CursorHold>"
|
|
||||||
: let c = getchar()
|
|
||||||
: endwhile
|
|
||||||
: return c
|
|
||||||
:endfunction
|
|
||||||
|
|
||||||
getcharmod() *getcharmod()*
|
getcharmod() *getcharmod()*
|
||||||
The result is a Number which is the state of the modifiers for
|
The result is a Number which is the state of the modifiers for
|
||||||
the last obtained character with getchar() or in another way.
|
the last obtained character with getchar() or in another way.
|
||||||
|
@ -533,6 +533,25 @@ inspect({object}, {options}) *vim.inspect()*
|
|||||||
See also: ~
|
See also: ~
|
||||||
https://github.com/kikito/inspect.lua
|
https://github.com/kikito/inspect.lua
|
||||||
|
|
||||||
|
paste({lines}, {phase}) *vim.paste()*
|
||||||
|
Paste handler, invoked by |nvim_paste()| when a conforming UI
|
||||||
|
(such as the |TUI|) pastes text into the editor.
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{lines} |readfile()|-style list of lines to paste.
|
||||||
|
|channel-lines|
|
||||||
|
{phase} -1: "non-streaming" paste: the call contains all
|
||||||
|
lines. If paste is "streamed", `phase` indicates the stream state:
|
||||||
|
• 1: starts the paste (exactly once)
|
||||||
|
• 2: continues the paste (zero or more times)
|
||||||
|
• 3: ends the paste (exactly once)
|
||||||
|
|
||||||
|
Return: ~
|
||||||
|
false if client should cancel the paste.
|
||||||
|
|
||||||
|
See also: ~
|
||||||
|
|paste|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -218,6 +218,41 @@ The "copy" function stores a list of lines and the register type. The "paste"
|
|||||||
function returns the clipboard as a `[lines, regtype]` list, where `lines` is
|
function returns the clipboard as a `[lines, regtype]` list, where `lines` is
|
||||||
a list of lines and `regtype` is a register type conforming to |setreg()|.
|
a list of lines and `regtype` is a register type conforming to |setreg()|.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
Paste *provider-paste* *paste*
|
||||||
|
|
||||||
|
"Paste" is a separate concept from |clipboard|: paste means "dump a bunch of
|
||||||
|
text to the editor", whereas clipboard provides features like |quote-+| to get
|
||||||
|
and set the OS clipboard directly. For example, middle-click or CTRL-SHIFT-v
|
||||||
|
(macOS: CMD-v) in your terminal is "paste", not "clipboard": the terminal
|
||||||
|
application (Nvim) just gets a stream of text, it does not interact with the
|
||||||
|
clipboard directly.
|
||||||
|
|
||||||
|
*bracketed-paste-mode*
|
||||||
|
Pasting in the |TUI| depends on the "bracketed paste" terminal capability,
|
||||||
|
which allows terminal applications to distinguish between user input and
|
||||||
|
pasted text. https://cirw.in/blog/bracketed-paste
|
||||||
|
This works automatically if your terminal supports it.
|
||||||
|
|
||||||
|
*ui-paste*
|
||||||
|
GUIs can paste by calling |nvim_paste()|.
|
||||||
|
|
||||||
|
PASTE BEHAVIOR ~
|
||||||
|
|
||||||
|
Paste always inserts text after the cursor. In cmdline-mode only the first
|
||||||
|
line is pasted, to avoid accidentally executing many commands. Use the
|
||||||
|
|cmdline-window| if you really want to paste multiple lines to the cmdline.
|
||||||
|
|
||||||
|
When pasting a huge amount of text, screen updates are throttled and the
|
||||||
|
message area shows a "..." pulse.
|
||||||
|
|
||||||
|
You can implement a custom paste handler by redefining |vim.paste()|.
|
||||||
|
Example: >
|
||||||
|
|
||||||
|
vim.paste = (function(lines, phase)
|
||||||
|
vim.api.nvim_put(lines, 'c', true, true)
|
||||||
|
end)
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
X11 selection mechanism *clipboard-x11* *x11-selection*
|
X11 selection mechanism *clipboard-x11* *x11-selection*
|
||||||
|
|
||||||
|
@ -219,12 +219,6 @@ effect on some UIs.
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
Using the mouse *mouse-using*
|
Using the mouse *mouse-using*
|
||||||
|
|
||||||
*bracketed-paste-mode*
|
|
||||||
Nvim enables bracketed paste by default. Bracketed paste mode allows terminal
|
|
||||||
applications to distinguish between typed text and pasted text. Thus you can
|
|
||||||
paste text without Nvim trying to format or indent the text.
|
|
||||||
See also https://cirw.in/blog/bracketed-paste
|
|
||||||
|
|
||||||
*mouse-mode-table* *mouse-overview*
|
*mouse-mode-table* *mouse-overview*
|
||||||
Overview of what the mouse buttons do, when 'mousemodel' is "extend":
|
Overview of what the mouse buttons do, when 'mousemodel' is "extend":
|
||||||
|
|
||||||
|
@ -745,6 +745,35 @@ String ga_take_string(garray_T *ga)
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates "readfile()-style" ArrayOf(String).
|
||||||
|
///
|
||||||
|
/// - NUL bytes are replaced with NL (form-feed).
|
||||||
|
/// - If last line ends with NL an extra empty list item is added.
|
||||||
|
Array string_to_array(const String input)
|
||||||
|
{
|
||||||
|
Array ret = ARRAY_DICT_INIT;
|
||||||
|
for (size_t i = 0; i < input.size; i++) {
|
||||||
|
const char *start = input.data + i;
|
||||||
|
const char *end = xmemscan(start, NL, input.size - i);
|
||||||
|
const size_t line_len = (size_t)(end - start);
|
||||||
|
i += line_len;
|
||||||
|
|
||||||
|
String s = {
|
||||||
|
.size = line_len,
|
||||||
|
.data = xmemdupz(start, line_len),
|
||||||
|
};
|
||||||
|
memchrsub(s.data, NUL, NL, line_len);
|
||||||
|
ADD(ret, STRING_OBJ(s));
|
||||||
|
// If line ends at end-of-buffer, add empty final item.
|
||||||
|
// This is "readfile()-style", see also ":help channel-lines".
|
||||||
|
if (i + 1 == input.size && end[0] == NL) {
|
||||||
|
ADD(ret, STRING_OBJ(cchar_to_string(NUL)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/// Set, tweak, or remove a mapping in a mode. Acts as the implementation for
|
/// Set, tweak, or remove a mapping in a mode. Acts as the implementation for
|
||||||
/// functions like @ref nvim_buf_set_keymap.
|
/// functions like @ref nvim_buf_set_keymap.
|
||||||
///
|
///
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
#include "nvim/ex_docmd.h"
|
#include "nvim/ex_docmd.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
#include "nvim/memline.h"
|
#include "nvim/memline.h"
|
||||||
|
#include "nvim/mark.h"
|
||||||
#include "nvim/memory.h"
|
#include "nvim/memory.h"
|
||||||
#include "nvim/message.h"
|
#include "nvim/message.h"
|
||||||
#include "nvim/popupmnu.h"
|
#include "nvim/popupmnu.h"
|
||||||
@ -36,6 +37,7 @@
|
|||||||
#include "nvim/eval.h"
|
#include "nvim/eval.h"
|
||||||
#include "nvim/eval/typval.h"
|
#include "nvim/eval/typval.h"
|
||||||
#include "nvim/fileio.h"
|
#include "nvim/fileio.h"
|
||||||
|
#include "nvim/ops.h"
|
||||||
#include "nvim/option.h"
|
#include "nvim/option.h"
|
||||||
#include "nvim/state.h"
|
#include "nvim/state.h"
|
||||||
#include "nvim/syntax.h"
|
#include "nvim/syntax.h"
|
||||||
@ -52,6 +54,20 @@
|
|||||||
# include "api/vim.c.generated.h"
|
# include "api/vim.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// `msg_list` controls the collection of abort-causing non-exception errors,
|
||||||
|
// which would otherwise be ignored. This pattern is from do_cmdline().
|
||||||
|
//
|
||||||
|
// TODO(bfredl): prepare error-handling at "top level" (nv_event).
|
||||||
|
#define TRY_WRAP(code) \
|
||||||
|
do { \
|
||||||
|
struct msglist **saved_msg_list = msg_list; \
|
||||||
|
struct msglist *private_msg_list; \
|
||||||
|
msg_list = &private_msg_list; \
|
||||||
|
private_msg_list = NULL; \
|
||||||
|
code \
|
||||||
|
msg_list = saved_msg_list; /* Restore the exception context. */ \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
void api_vim_init(void)
|
void api_vim_init(void)
|
||||||
FUNC_API_NOEXPORT
|
FUNC_API_NOEXPORT
|
||||||
{
|
{
|
||||||
@ -390,13 +406,7 @@ Object nvim_eval(String expr, Error *err)
|
|||||||
static int recursive = 0; // recursion depth
|
static int recursive = 0; // recursion depth
|
||||||
Object rv = OBJECT_INIT;
|
Object rv = OBJECT_INIT;
|
||||||
|
|
||||||
// `msg_list` controls the collection of abort-causing non-exception errors,
|
TRY_WRAP({
|
||||||
// which would otherwise be ignored. This pattern is from do_cmdline().
|
|
||||||
struct msglist **saved_msg_list = msg_list;
|
|
||||||
struct msglist *private_msg_list;
|
|
||||||
msg_list = &private_msg_list;
|
|
||||||
private_msg_list = NULL;
|
|
||||||
|
|
||||||
// Initialize `force_abort` and `suppress_errthrow` at the top level.
|
// Initialize `force_abort` and `suppress_errthrow` at the top level.
|
||||||
if (!recursive) {
|
if (!recursive) {
|
||||||
force_abort = false;
|
force_abort = false;
|
||||||
@ -421,8 +431,8 @@ Object nvim_eval(String expr, Error *err)
|
|||||||
}
|
}
|
||||||
|
|
||||||
tv_clear(&rettv);
|
tv_clear(&rettv);
|
||||||
msg_list = saved_msg_list; // Restore the exception context.
|
|
||||||
recursive--;
|
recursive--;
|
||||||
|
});
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
@ -472,13 +482,7 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// `msg_list` controls the collection of abort-causing non-exception errors,
|
TRY_WRAP({
|
||||||
// which would otherwise be ignored. This pattern is from do_cmdline().
|
|
||||||
struct msglist **saved_msg_list = msg_list;
|
|
||||||
struct msglist *private_msg_list;
|
|
||||||
msg_list = &private_msg_list;
|
|
||||||
private_msg_list = NULL;
|
|
||||||
|
|
||||||
// Initialize `force_abort` and `suppress_errthrow` at the top level.
|
// Initialize `force_abort` and `suppress_errthrow` at the top level.
|
||||||
if (!recursive) {
|
if (!recursive) {
|
||||||
force_abort = false;
|
force_abort = false;
|
||||||
@ -500,8 +504,8 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err)
|
|||||||
rv = vim_to_object(&rettv);
|
rv = vim_to_object(&rettv);
|
||||||
}
|
}
|
||||||
tv_clear(&rettv);
|
tv_clear(&rettv);
|
||||||
msg_list = saved_msg_list; // Restore the exception context.
|
|
||||||
recursive--;
|
recursive--;
|
||||||
|
});
|
||||||
|
|
||||||
free_vim_args:
|
free_vim_args:
|
||||||
while (i > 0) {
|
while (i > 0) {
|
||||||
@ -1204,6 +1208,141 @@ Dictionary nvim_get_namespaces(void)
|
|||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pastes at cursor, in any mode.
|
||||||
|
///
|
||||||
|
/// Invokes the `vim.paste` handler, which handles each mode appropriately.
|
||||||
|
/// Sets redo/undo. Faster than |nvim_input()|.
|
||||||
|
///
|
||||||
|
/// Errors ('nomodifiable', `vim.paste()` failure, …) are reflected in `err`
|
||||||
|
/// but do not affect the return value (which is strictly decided by
|
||||||
|
/// `vim.paste()`). On error, subsequent calls are ignored ("drained") until
|
||||||
|
/// the next paste is initiated (phase 1 or -1).
|
||||||
|
///
|
||||||
|
/// @param data Multiline input. May be binary (containing NUL bytes).
|
||||||
|
/// @param phase -1: paste in a single call (i.e. without streaming).
|
||||||
|
/// To "stream" a paste, call `nvim_paste` sequentially with
|
||||||
|
/// these `phase` values:
|
||||||
|
/// - 1: starts the paste (exactly once)
|
||||||
|
/// - 2: continues the paste (zero or more times)
|
||||||
|
/// - 3: ends the paste (exactly once)
|
||||||
|
/// @param[out] err Error details, if any
|
||||||
|
/// @return
|
||||||
|
/// - true: Client may continue pasting.
|
||||||
|
/// - false: Client must cancel the paste.
|
||||||
|
Boolean nvim_paste(String data, Integer phase, Error *err)
|
||||||
|
FUNC_API_SINCE(6)
|
||||||
|
{
|
||||||
|
static bool draining = false;
|
||||||
|
bool cancel = false;
|
||||||
|
|
||||||
|
if (phase < -1 || phase > 3) {
|
||||||
|
api_set_error(err, kErrorTypeValidation, "Invalid phase: %"PRId64, phase);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Array args = ARRAY_DICT_INIT;
|
||||||
|
Object rv = OBJECT_INIT;
|
||||||
|
if (phase == -1 || phase == 1) { // Start of paste-stream.
|
||||||
|
draining = false;
|
||||||
|
} else if (draining) {
|
||||||
|
// Skip remaining chunks. Report error only once per "stream".
|
||||||
|
goto theend;
|
||||||
|
}
|
||||||
|
Array lines = string_to_array(data);
|
||||||
|
ADD(args, ARRAY_OBJ(lines));
|
||||||
|
ADD(args, INTEGER_OBJ(phase));
|
||||||
|
rv = nvim_execute_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args,
|
||||||
|
err);
|
||||||
|
if (ERROR_SET(err)) {
|
||||||
|
draining = true;
|
||||||
|
goto theend;
|
||||||
|
}
|
||||||
|
if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 1)) {
|
||||||
|
ResetRedobuff();
|
||||||
|
AppendCharToRedobuff('a'); // Dot-repeat.
|
||||||
|
}
|
||||||
|
// vim.paste() decides if client should cancel. Errors do NOT cancel: we
|
||||||
|
// want to drain remaining chunks (rather than divert them to main input).
|
||||||
|
cancel = (rv.type == kObjectTypeBoolean && !rv.data.boolean);
|
||||||
|
if (!cancel && !(State & CMDLINE)) { // Dot-repeat.
|
||||||
|
for (size_t i = 0; i < lines.size; i++) {
|
||||||
|
String s = lines.items[i].data.string;
|
||||||
|
assert(data.size <= INT_MAX);
|
||||||
|
AppendToRedobuffLit((char_u *)s.data, (int)s.size);
|
||||||
|
// readfile()-style: "\n" is indicated by presence of N+1 item.
|
||||||
|
if (i + 1 < lines.size) {
|
||||||
|
AppendCharToRedobuff(NL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 3)) {
|
||||||
|
AppendCharToRedobuff(ESC); // Dot-repeat.
|
||||||
|
}
|
||||||
|
theend:
|
||||||
|
api_free_object(rv);
|
||||||
|
api_free_array(args);
|
||||||
|
if (cancel || phase == -1 || phase == 3) { // End of paste-stream.
|
||||||
|
draining = false;
|
||||||
|
// XXX: Tickle main loop to ensure cursor is updated.
|
||||||
|
loop_schedule_deferred(&main_loop, event_create(loop_dummy_event, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return !cancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Puts text at cursor, in any mode.
|
||||||
|
///
|
||||||
|
/// Compare |:put| and |p| which are always linewise.
|
||||||
|
///
|
||||||
|
/// @param lines |readfile()|-style list of lines. |channel-lines|
|
||||||
|
/// @param type Edit behavior:
|
||||||
|
/// - "b" |blockwise-visual| mode
|
||||||
|
/// - "c" |characterwise| mode
|
||||||
|
/// - "l" |linewise| mode
|
||||||
|
/// - "" guess by contents
|
||||||
|
/// @param after Insert after cursor (like |p|), or before (like |P|).
|
||||||
|
/// @param follow Place cursor at end of inserted text.
|
||||||
|
/// @param[out] err Error details, if any
|
||||||
|
void nvim_put(ArrayOf(String) lines, String type, Boolean after,
|
||||||
|
Boolean follow, Error *err)
|
||||||
|
FUNC_API_SINCE(6)
|
||||||
|
{
|
||||||
|
yankreg_T *reg = xcalloc(sizeof(yankreg_T), 1);
|
||||||
|
if (!prepare_yankreg_from_object(reg, type, lines.size)) {
|
||||||
|
api_set_error(err, kErrorTypeValidation, "Invalid type: '%s'", type.data);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
if (lines.size == 0) {
|
||||||
|
goto cleanup; // Nothing to do.
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < lines.size; i++) {
|
||||||
|
if (lines.items[i].type != kObjectTypeString) {
|
||||||
|
api_set_error(err, kErrorTypeValidation,
|
||||||
|
"Invalid lines (expected array of strings)");
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
String line = lines.items[i].data.string;
|
||||||
|
reg->y_array[i] = (char_u *)xmemdupz(line.data, line.size);
|
||||||
|
memchrsub(reg->y_array[i], NUL, NL, line.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_yankreg_from_object(reg, false);
|
||||||
|
|
||||||
|
TRY_WRAP({
|
||||||
|
try_start();
|
||||||
|
bool VIsual_was_active = VIsual_active;
|
||||||
|
msg_silent++; // Avoid "N more lines" message.
|
||||||
|
do_put(0, reg, after ? FORWARD : BACKWARD, 1, follow ? PUT_CURSEND : 0);
|
||||||
|
msg_silent--;
|
||||||
|
VIsual_active = VIsual_was_active;
|
||||||
|
try_end(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
free_register(reg);
|
||||||
|
xfree(reg);
|
||||||
|
}
|
||||||
|
|
||||||
/// Subscribes to event broadcasts.
|
/// Subscribes to event broadcasts.
|
||||||
///
|
///
|
||||||
/// @param channel_id Channel id (passed automatically by the dispatcher)
|
/// @param channel_id Channel id (passed automatically by the dispatcher)
|
||||||
|
@ -36,6 +36,10 @@ void loop_init(Loop *loop, void *data)
|
|||||||
/// Processes all `Loop.fast_events` events.
|
/// Processes all `Loop.fast_events` events.
|
||||||
/// Does NOT process `Loop.events`, that is an application-specific decision.
|
/// 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
|
/// @returns true if `ms` timeout was reached
|
||||||
bool loop_poll_events(Loop *loop, int ms)
|
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)
|
void loop_on_put(MultiQueue *queue, void *data)
|
||||||
{
|
{
|
||||||
Loop *loop = 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
|
// 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
|
// 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 when a event is enqueued.
|
||||||
uv_stop(&loop->uv);
|
uv_stop(&loop->uv);
|
||||||
}
|
}
|
||||||
@ -158,10 +162,15 @@ size_t loop_size(Loop *loop)
|
|||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void loop_dummy_event(void **argv)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
static void async_cb(uv_async_t *handle)
|
static void async_cb(uv_async_t *handle)
|
||||||
{
|
{
|
||||||
Loop *l = handle->loop->data;
|
Loop *l = handle->loop->data;
|
||||||
uv_mutex_lock(&l->mutex);
|
uv_mutex_lock(&l->mutex);
|
||||||
|
// Flush thread_events to fast_events for processing on main loop.
|
||||||
while (!multiqueue_empty(l->thread_events)) {
|
while (!multiqueue_empty(l->thread_events)) {
|
||||||
Event ev = multiqueue_get(l->thread_events);
|
Event ev = multiqueue_get(l->thread_events);
|
||||||
multiqueue_put_event(l->fast_events, ev);
|
multiqueue_put_event(l->fast_events, ev);
|
||||||
|
@ -532,7 +532,7 @@ static int command_line_check(VimState *state)
|
|||||||
|
|
||||||
static int command_line_execute(VimState *state, int key)
|
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
|
return -1; // get another key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 char_u noremapbuf_init[TYPELEN_INIT]; /* initial typebuf.tb_noremap */
|
||||||
|
|
||||||
static size_t last_recorded_len = 0; // number of last recorded chars
|
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
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "getchar.c.generated.h"
|
# include "getchar.c.generated.h"
|
||||||
@ -524,15 +523,12 @@ void AppendToRedobuff(const char *s)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// Append to Redo buffer literally, escaping special characters with CTRL-V.
|
||||||
* Append to Redo buffer literally, escaping special characters with CTRL-V.
|
/// K_SPECIAL and CSI are escaped as well.
|
||||||
* K_SPECIAL and CSI are escaped as well.
|
///
|
||||||
*/
|
/// @param str String to append
|
||||||
void
|
/// @param len Length of `str` or -1 for up to the NUL.
|
||||||
AppendToRedobuffLit (
|
void AppendToRedobuffLit(const char_u *str, int len)
|
||||||
char_u *str,
|
|
||||||
int len /* length of "str" or -1 for up to the NUL */
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
if (block_redo) {
|
if (block_redo) {
|
||||||
return;
|
return;
|
||||||
@ -1902,14 +1898,8 @@ static int vgetorpeek(int advance)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a key that can toggle the 'paste' option
|
if (*p_pt != NUL && mp == NULL && (State & (INSERT|NORMAL))) {
|
||||||
if (mp == NULL && (State & (INSERT|NORMAL))) {
|
bool match = typebuf_match_len(p_pt, &mlen);
|
||||||
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 (match) {
|
if (match) {
|
||||||
// write chars to script file(s)
|
// write chars to script file(s)
|
||||||
if (mlen > typebuf.tb_maplen) {
|
if (mlen > typebuf.tb_maplen) {
|
||||||
@ -1940,8 +1930,7 @@ static int vgetorpeek(int advance)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((mp == NULL || max_mlen >= mp_match_len)
|
if ((mp == NULL || max_mlen >= mp_match_len)
|
||||||
&& keylen != KEYLEN_PART_MAP
|
&& keylen != KEYLEN_PART_MAP) {
|
||||||
&& !(keylen == KEYLEN_PART_KEY && c1 == ui_toggle[0])) {
|
|
||||||
// No matching mapping found or found a non-matching mapping that
|
// No matching mapping found or found a non-matching mapping that
|
||||||
// matches at least what the matching mapping matched
|
// matches at least what the matching mapping matched
|
||||||
keylen = 0;
|
keylen = 0;
|
||||||
|
@ -72,12 +72,6 @@
|
|||||||
# define VIMRC_FILE ".nvimrc"
|
# define VIMRC_FILE ".nvimrc"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
kNone = -1,
|
|
||||||
kFalse = 0,
|
|
||||||
kTrue = 1,
|
|
||||||
} TriState;
|
|
||||||
|
|
||||||
EXTERN struct nvim_stats_s {
|
EXTERN struct nvim_stats_s {
|
||||||
int64_t fsync;
|
int64_t fsync;
|
||||||
int64_t redraw;
|
int64_t redraw;
|
||||||
|
@ -309,7 +309,6 @@ static const struct key_name_entry {
|
|||||||
{ K_ZERO, "Nul" },
|
{ K_ZERO, "Nul" },
|
||||||
{ K_SNR, "SNR" },
|
{ K_SNR, "SNR" },
|
||||||
{ K_PLUG, "Plug" },
|
{ K_PLUG, "Plug" },
|
||||||
{ K_PASTE, "Paste" },
|
|
||||||
{ K_COMMAND, "Cmd" },
|
{ K_COMMAND, "Cmd" },
|
||||||
{ 0, NULL }
|
{ 0, NULL }
|
||||||
// NOTE: When adding a long name update MAX_KEY_NAME_LEN.
|
// NOTE: When adding a long name update MAX_KEY_NAME_LEN.
|
||||||
@ -941,3 +940,14 @@ char_u *replace_termcodes(const char_u *from, const size_t from_len,
|
|||||||
return *bufp;
|
return *bufp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Logs a single key as a human-readable keycode.
|
||||||
|
void log_key(int log_level, int key)
|
||||||
|
{
|
||||||
|
if (log_level < MIN_LOG_LEVEL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char *keyname = key == K_EVENT
|
||||||
|
? "K_EVENT"
|
||||||
|
: (char *)get_special_key_name(key, mod_mask);
|
||||||
|
LOG(log_level, "input: %s", keyname);
|
||||||
|
}
|
||||||
|
@ -239,14 +239,12 @@ enum key_extra {
|
|||||||
|
|
||||||
, KE_DROP = 95 // DnD data is available
|
, KE_DROP = 95 // DnD data is available
|
||||||
// , KE_CURSORHOLD = 96 // CursorHold event
|
// , 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_FOCUSGAINED = 98 // focus gained
|
||||||
, KE_FOCUSLOST = 99 // focus lost
|
, KE_FOCUSLOST = 99 // focus lost
|
||||||
// , KE_MOUSEMOVE = 100 // mouse moved with no button down
|
// , KE_MOUSEMOVE = 100 // mouse moved with no button down
|
||||||
// , KE_CANCEL = 101 // return from vgetc
|
// , KE_CANCEL = 101 // return from vgetc
|
||||||
, KE_EVENT = 102 // event
|
, KE_EVENT = 102 // event
|
||||||
, KE_PASTE = 103 // special key to toggle the 'paste' option.
|
|
||||||
// sent only by UIs
|
|
||||||
, KE_COMMAND = 104 // <Cmd> special key
|
, KE_COMMAND = 104 // <Cmd> special key
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -443,7 +441,6 @@ enum key_extra {
|
|||||||
#define K_DROP TERMCAP2KEY(KS_EXTRA, KE_DROP)
|
#define K_DROP TERMCAP2KEY(KS_EXTRA, KE_DROP)
|
||||||
|
|
||||||
#define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT)
|
#define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT)
|
||||||
#define K_PASTE TERMCAP2KEY(KS_EXTRA, KE_PASTE)
|
|
||||||
#define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND)
|
#define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND)
|
||||||
|
|
||||||
/* Bits for modifier mask */
|
/* Bits for modifier mask */
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
--
|
--
|
||||||
-- Guideline: "If in doubt, put it in the runtime".
|
-- Guideline: "If in doubt, put it in the runtime".
|
||||||
--
|
--
|
||||||
-- Most functions should live directly on `vim.`, not sub-modules. The only
|
-- Most functions should live directly in `vim.`, not in submodules.
|
||||||
-- "forbidden" names are those claimed by legacy `if_lua`:
|
-- The only "forbidden" names are those claimed by legacy `if_lua`:
|
||||||
-- $ vim
|
-- $ vim
|
||||||
-- :lua for k,v in pairs(vim) do print(k) end
|
-- :lua for k,v in pairs(vim) do print(k) end
|
||||||
-- buffer
|
-- buffer
|
||||||
@ -161,6 +161,69 @@ local function inspect(object, options) -- luacheck: no unused
|
|||||||
error(object, options) -- Stub for gen_vimdoc.py
|
error(object, options) -- Stub for gen_vimdoc.py
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Paste handler, invoked by |nvim_paste()| when a conforming UI
|
||||||
|
--- (such as the |TUI|) pastes text into the editor.
|
||||||
|
---
|
||||||
|
--@see |paste|
|
||||||
|
---
|
||||||
|
--@param lines |readfile()|-style list of lines to paste. |channel-lines|
|
||||||
|
--@param phase -1: "non-streaming" paste: the call contains all lines.
|
||||||
|
--- If paste is "streamed", `phase` indicates the stream state:
|
||||||
|
--- - 1: starts the paste (exactly once)
|
||||||
|
--- - 2: continues the paste (zero or more times)
|
||||||
|
--- - 3: ends the paste (exactly once)
|
||||||
|
--@returns false if client should cancel the paste.
|
||||||
|
local function paste(lines, phase) end -- luacheck: no unused
|
||||||
|
paste = (function()
|
||||||
|
local tdots, tredraw, tick, got_line1 = 0, 0, 0, false
|
||||||
|
return function(lines, phase)
|
||||||
|
local call = vim.api.nvim_call_function
|
||||||
|
local now = vim.loop.now()
|
||||||
|
local mode = call('mode', {}):sub(1,1)
|
||||||
|
if phase < 2 then -- Reset flags.
|
||||||
|
tdots, tredraw, tick, got_line1 = now, now, 0, false
|
||||||
|
end
|
||||||
|
if mode == 'c' and not got_line1 then -- cmdline-mode: paste only 1 line.
|
||||||
|
got_line1 = (#lines > 1)
|
||||||
|
vim.api.nvim_set_option('paste', true) -- For nvim_input().
|
||||||
|
local line1, _ = string.gsub(lines[1], '[\r\n\012\027]', ' ')
|
||||||
|
vim.api.nvim_input(line1) -- Scrub "\r".
|
||||||
|
elseif mode == 'i' or mode == 'R' then
|
||||||
|
vim.api.nvim_put(lines, 'c', false, true)
|
||||||
|
else
|
||||||
|
vim.api.nvim_put(lines, 'c', true, true)
|
||||||
|
end
|
||||||
|
if (now - tredraw >= 1000) or phase == -1 or phase > 2 then
|
||||||
|
tredraw = now
|
||||||
|
vim.api.nvim_command('redraw')
|
||||||
|
vim.api.nvim_command('redrawstatus')
|
||||||
|
end
|
||||||
|
if phase ~= -1 and (now - tdots >= 100) then
|
||||||
|
local dots = ('.'):rep(tick % 4)
|
||||||
|
tdots = now
|
||||||
|
tick = tick + 1
|
||||||
|
-- Use :echo because Lua print('') is a no-op, and we want to clear the
|
||||||
|
-- message when there are zero dots.
|
||||||
|
vim.api.nvim_command(('echo "%s"'):format(dots))
|
||||||
|
end
|
||||||
|
if phase == -1 or phase == 3 then
|
||||||
|
vim.api.nvim_command('echo ""')
|
||||||
|
vim.api.nvim_set_option('paste', false)
|
||||||
|
end
|
||||||
|
return true -- Paste will not continue if not returning `true`.
|
||||||
|
end
|
||||||
|
end)()
|
||||||
|
|
||||||
|
--- Defers the wrapped callback until the Nvim API is safe to call.
|
||||||
|
---
|
||||||
|
--@see |vim-loop-callbacks|
|
||||||
|
local function schedule_wrap(cb)
|
||||||
|
return (function (...)
|
||||||
|
local args = {...}
|
||||||
|
vim.schedule(function() cb(unpack(args)) end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
local function __index(t, key)
|
local function __index(t, key)
|
||||||
if key == 'inspect' then
|
if key == 'inspect' then
|
||||||
t.inspect = require('vim.inspect')
|
t.inspect = require('vim.inspect')
|
||||||
@ -172,21 +235,12 @@ local function __index(t, key)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Defers the wrapped callback until when the nvim API is safe to call.
|
|
||||||
---
|
|
||||||
--- See |vim-loop-callbacks|
|
|
||||||
local function schedule_wrap(cb)
|
|
||||||
return (function (...)
|
|
||||||
local args = {...}
|
|
||||||
vim.schedule(function() cb(unpack(args)) end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
local module = {
|
local module = {
|
||||||
_update_package_paths = _update_package_paths,
|
_update_package_paths = _update_package_paths,
|
||||||
_os_proc_children = _os_proc_children,
|
_os_proc_children = _os_proc_children,
|
||||||
_os_proc_info = _os_proc_info,
|
_os_proc_info = _os_proc_info,
|
||||||
_system = _system,
|
_system = _system,
|
||||||
|
paste = paste,
|
||||||
schedule_wrap = schedule_wrap,
|
schedule_wrap = schedule_wrap,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2732,7 +2732,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
|
|||||||
* Using inserted text works differently, because the register includes
|
* Using inserted text works differently, because the register includes
|
||||||
* special characters (newlines, etc.).
|
* special characters (newlines, etc.).
|
||||||
*/
|
*/
|
||||||
if (regname == '.') {
|
if (regname == '.' && !reg) {
|
||||||
bool non_linewise_vis = (VIsual_active && VIsual_mode != 'V');
|
bool non_linewise_vis = (VIsual_active && VIsual_mode != 'V');
|
||||||
|
|
||||||
// PUT_LINE has special handling below which means we use 'i' to start.
|
// PUT_LINE has special handling below which means we use 'i' to start.
|
||||||
@ -2815,7 +2815,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
|
|||||||
* For special registers '%' (file name), '#' (alternate file name) and
|
* For special registers '%' (file name), '#' (alternate file name) and
|
||||||
* ':' (last command line), etc. we have to create a fake yank register.
|
* ':' (last command line), etc. we have to create a fake yank register.
|
||||||
*/
|
*/
|
||||||
if (get_spec_reg(regname, &insert_string, &allocated, true)) {
|
if (!reg && get_spec_reg(regname, &insert_string, &allocated, true)) {
|
||||||
if (insert_string == NULL) {
|
if (insert_string == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -5675,6 +5675,71 @@ end:
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @param[out] reg Expected to be empty
|
||||||
|
bool prepare_yankreg_from_object(yankreg_T *reg, String regtype, size_t lines)
|
||||||
|
{
|
||||||
|
if (regtype.size > 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char type = regtype.data ? regtype.data[0] : NUL;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 0:
|
||||||
|
reg->y_type = kMTUnknown;
|
||||||
|
break;
|
||||||
|
case 'v': case 'c':
|
||||||
|
reg->y_type = kMTCharWise;
|
||||||
|
break;
|
||||||
|
case 'V': case 'l':
|
||||||
|
reg->y_type = kMTLineWise;
|
||||||
|
break;
|
||||||
|
case 'b': case Ctrl_V:
|
||||||
|
reg->y_type = kMTBlockWise;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
reg->y_array = xcalloc(lines, sizeof(uint8_t *));
|
||||||
|
reg->y_size = lines;
|
||||||
|
reg->additional_data = NULL;
|
||||||
|
reg->timestamp = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finish_yankreg_from_object(yankreg_T *reg, bool clipboard_adjust)
|
||||||
|
{
|
||||||
|
if (reg->y_size > 0 && strlen((char *)reg->y_array[reg->y_size-1]) == 0) {
|
||||||
|
// a known-to-be charwise yank might have a final linebreak
|
||||||
|
// but otherwise there is no line after the final newline
|
||||||
|
if (reg->y_type != kMTCharWise) {
|
||||||
|
if (reg->y_type == kMTUnknown || clipboard_adjust) {
|
||||||
|
xfree(reg->y_array[reg->y_size-1]);
|
||||||
|
reg->y_size--;
|
||||||
|
}
|
||||||
|
if (reg->y_type == kMTUnknown) {
|
||||||
|
reg->y_type = kMTLineWise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (reg->y_type == kMTUnknown) {
|
||||||
|
reg->y_type = kMTCharWise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reg->y_type == kMTBlockWise) {
|
||||||
|
size_t maxlen = 0;
|
||||||
|
for (size_t i = 0; i < reg->y_size; i++) {
|
||||||
|
size_t rowlen = STRLEN(reg->y_array[i]);
|
||||||
|
if (rowlen > maxlen) {
|
||||||
|
maxlen = rowlen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(maxlen <= INT_MAX);
|
||||||
|
reg->y_width = (int)maxlen - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static bool get_clipboard(int name, yankreg_T **target, bool quiet)
|
static bool get_clipboard(int name, yankreg_T **target, bool quiet)
|
||||||
{
|
{
|
||||||
// show message on error
|
// show message on error
|
||||||
|
@ -448,7 +448,7 @@ static void process_interrupts(void)
|
|||||||
|
|
||||||
size_t consume_count = 0;
|
size_t consume_count = 0;
|
||||||
RBUFFER_EACH_REVERSE(input_buffer, c, i) {
|
RBUFFER_EACH_REVERSE(input_buffer, c, i) {
|
||||||
if ((uint8_t)c == 3) {
|
if ((uint8_t)c == Ctrl_C) {
|
||||||
got_int = true;
|
got_int = true;
|
||||||
consume_count = i;
|
consume_count = i;
|
||||||
break;
|
break;
|
||||||
@ -456,7 +456,7 @@ static void process_interrupts(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (got_int && consume_count) {
|
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);
|
rbuffer_consumed(input_buffer, consume_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,9 +65,7 @@ getkey:
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL
|
#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL
|
||||||
char *keyname = key == K_EVENT
|
log_key(DEBUG_LOG_LEVEL, key);
|
||||||
? "K_EVENT" : (char *)get_special_key_name(key, mod_mask);
|
|
||||||
DLOG("input: %s", keyname);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int execute_result = s->execute(s, key);
|
int execute_result = s->execute(s, key);
|
||||||
|
@ -475,10 +475,6 @@ static int terminal_execute(VimState *state, int key)
|
|||||||
TerminalState *s = (TerminalState *)state;
|
TerminalState *s = (TerminalState *)state;
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
// Temporary fix until paste events gets implemented
|
|
||||||
case K_PASTE:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case K_LEFTMOUSE:
|
case K_LEFTMOUSE:
|
||||||
case K_LEFTDRAG:
|
case K_LEFTDRAG:
|
||||||
case K_LEFTRELEASE:
|
case K_LEFTRELEASE:
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
#include "nvim/os/input.h"
|
#include "nvim/os/input.h"
|
||||||
#include "nvim/event/rstream.h"
|
#include "nvim/event/rstream.h"
|
||||||
|
|
||||||
#define PASTETOGGLE_KEY "<Paste>"
|
|
||||||
#define KEY_BUFFER_SIZE 0xfff
|
#define KEY_BUFFER_SIZE 0xfff
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
@ -26,7 +25,7 @@
|
|||||||
void tinput_init(TermInput *input, Loop *loop)
|
void tinput_init(TermInput *input, Loop *loop)
|
||||||
{
|
{
|
||||||
input->loop = loop;
|
input->loop = loop;
|
||||||
input->paste_enabled = false;
|
input->paste = 0;
|
||||||
input->in_fd = 0;
|
input->in_fd = 0;
|
||||||
input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE);
|
input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE);
|
||||||
uv_mutex_init(&input->key_buffer_mutex);
|
uv_mutex_init(&input->key_buffer_mutex);
|
||||||
@ -105,13 +104,28 @@ static void tinput_wait_enqueue(void **argv)
|
|||||||
{
|
{
|
||||||
TermInput *input = argv[0];
|
TermInput *input = argv[0];
|
||||||
RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) {
|
RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) {
|
||||||
size_t consumed = input_enqueue((String){.data = buf, .size = len});
|
const String keys = { .data = buf, .size = len };
|
||||||
if (consumed) {
|
if (input->paste) {
|
||||||
rbuffer_consumed(input->key_buffer, consumed);
|
Error err = ERROR_INIT;
|
||||||
}
|
// Paste phase: "continue" (unless handler canceled).
|
||||||
rbuffer_reset(input->key_buffer);
|
input->paste = !nvim_paste(keys, input->paste, &err)
|
||||||
if (consumed < len) {
|
? 0 : (1 == input->paste ? 2 : input->paste);
|
||||||
break;
|
rbuffer_consumed(input->key_buffer, len);
|
||||||
|
rbuffer_reset(input->key_buffer);
|
||||||
|
if (ERROR_SET(&err)) {
|
||||||
|
// TODO(justinmk): emsgf() does not display, why?
|
||||||
|
msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, "paste: %s", err.msg);
|
||||||
|
api_clear_error(&err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const size_t consumed = input_enqueue(keys);
|
||||||
|
if (consumed) {
|
||||||
|
rbuffer_consumed(input->key_buffer, consumed);
|
||||||
|
}
|
||||||
|
rbuffer_reset(input->key_buffer);
|
||||||
|
if (consumed < len) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uv_mutex_lock(&input->key_buffer_mutex);
|
uv_mutex_lock(&input->key_buffer_mutex);
|
||||||
@ -292,9 +306,12 @@ static void tk_getkeys(TermInput *input, bool force)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result != TERMKEY_RES_AGAIN || input->paste_enabled) {
|
if (result != TERMKEY_RES_AGAIN) {
|
||||||
return;
|
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();
|
int ms = get_key_code_timeout();
|
||||||
|
|
||||||
@ -326,8 +343,8 @@ static bool handle_focus_event(TermInput *input)
|
|||||||
if (rbuffer_size(input->read_stream.buffer) > 2
|
if (rbuffer_size(input->read_stream.buffer) > 2
|
||||||
&& (!rbuffer_cmp(input->read_stream.buffer, "\x1b[I", 3)
|
&& (!rbuffer_cmp(input->read_stream.buffer, "\x1b[I", 3)
|
||||||
|| !rbuffer_cmp(input->read_stream.buffer, "\x1b[O", 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';
|
bool focus_gained = *rbuffer_get(input->read_stream.buffer, 2) == 'I';
|
||||||
|
// Advance past the sequence
|
||||||
rbuffer_consumed(input->read_stream.buffer, 3);
|
rbuffer_consumed(input->read_stream.buffer, 3);
|
||||||
aucmd_schedule_focusgained(focus_gained);
|
aucmd_schedule_focusgained(focus_gained);
|
||||||
return true;
|
return true;
|
||||||
@ -341,18 +358,33 @@ static bool handle_bracketed_paste(TermInput *input)
|
|||||||
&& (!rbuffer_cmp(input->read_stream.buffer, "\x1b[200~", 6)
|
&& (!rbuffer_cmp(input->read_stream.buffer, "\x1b[200~", 6)
|
||||||
|| !rbuffer_cmp(input->read_stream.buffer, "\x1b[201~", 6))) {
|
|| !rbuffer_cmp(input->read_stream.buffer, "\x1b[201~", 6))) {
|
||||||
bool enable = *rbuffer_get(input->read_stream.buffer, 4) == '0';
|
bool enable = *rbuffer_get(input->read_stream.buffer, 4) == '0';
|
||||||
|
if (input->paste && enable) {
|
||||||
|
return false; // Pasting "start paste" code literally.
|
||||||
|
}
|
||||||
// Advance past the sequence
|
// Advance past the sequence
|
||||||
rbuffer_consumed(input->read_stream.buffer, 6);
|
rbuffer_consumed(input->read_stream.buffer, 6);
|
||||||
if (input->paste_enabled == enable) {
|
if (!!input->paste == enable) {
|
||||||
return true;
|
return true; // Spurious "disable paste" code.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
// Flush before starting paste.
|
||||||
|
tinput_flush(input, true);
|
||||||
|
// Paste phase: "first-chunk".
|
||||||
|
input->paste = 1;
|
||||||
|
} else if (input->paste) {
|
||||||
|
// Paste phase: "last-chunk".
|
||||||
|
input->paste = input->paste == 2 ? 3 : -1;
|
||||||
|
tinput_flush(input, true);
|
||||||
|
// Paste phase: "disabled".
|
||||||
|
input->paste = 0;
|
||||||
}
|
}
|
||||||
tinput_enqueue(input, PASTETOGGLE_KEY, sizeof(PASTETOGGLE_KEY) - 1);
|
|
||||||
input->paste_enabled = enable;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ESC NUL => <Esc>
|
||||||
static bool handle_forced_escape(TermInput *input)
|
static bool handle_forced_escape(TermInput *input)
|
||||||
{
|
{
|
||||||
if (rbuffer_size(input->read_stream.buffer) > 1
|
if (rbuffer_size(input->read_stream.buffer) > 1
|
||||||
@ -477,9 +509,11 @@ static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_,
|
|||||||
continue;
|
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
|
// Find the next ESC and push everything up to it (excluding), so it will
|
||||||
// as expected.
|
// be the first thing encountered on the next iteration. The `handle_*`
|
||||||
|
// calls (above) depend on this.
|
||||||
|
//
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
RBUFFER_EACH(input->read_stream.buffer, c, i) {
|
RBUFFER_EACH(input->read_stream.buffer, c, i) {
|
||||||
count = i + 1;
|
count = i + 1;
|
||||||
@ -488,15 +522,28 @@ static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Push bytes directly (paste).
|
||||||
|
if (input->paste) {
|
||||||
|
RBUFFER_UNTIL_EMPTY(input->read_stream.buffer, ptr, len) {
|
||||||
|
size_t consumed = MIN(count, len);
|
||||||
|
assert(consumed <= input->read_stream.buffer->size);
|
||||||
|
tinput_enqueue(input, ptr, consumed);
|
||||||
|
rbuffer_consumed(input->read_stream.buffer, consumed);
|
||||||
|
if (!(count -= consumed)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Push through libtermkey (translates to "<keycode>" strings, etc.).
|
||||||
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 > input->read_stream.buffer->size`, 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 <= input->read_stream.buffer->size);
|
||||||
rbuffer_consumed(input->read_stream.buffer, consumed);
|
rbuffer_consumed(input->read_stream.buffer, consumed);
|
||||||
// Need to process the keys now since there's 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.
|
||||||
tk_getkeys(input, false);
|
tk_getkeys(input, false);
|
||||||
if (!(count -= consumed)) {
|
if (!(count -= consumed)) {
|
||||||
@ -505,7 +552,8 @@ static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_,
|
|||||||
}
|
}
|
||||||
} while (rbuffer_size(input->read_stream.buffer));
|
} while (rbuffer_size(input->read_stream.buffer));
|
||||||
tinput_flush(input, true);
|
tinput_flush(input, true);
|
||||||
// Make sure the next input escape sequence fits into the ring buffer
|
// Make sure the next input escape sequence fits into the ring buffer without
|
||||||
// without wrap around, otherwise it could be misinterpreted.
|
// 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);
|
rbuffer_reset(input->read_stream.buffer);
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@
|
|||||||
|
|
||||||
typedef struct term_input {
|
typedef struct term_input {
|
||||||
int in_fd;
|
int in_fd;
|
||||||
bool paste_enabled;
|
// Phases: -1=all 0=disabled 1=first-chunk 2=continue 3=last-chunk
|
||||||
|
int8_t paste;
|
||||||
bool waiting;
|
bool waiting;
|
||||||
TermKey *tk;
|
TermKey *tk;
|
||||||
#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18
|
#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18
|
||||||
|
@ -430,7 +430,7 @@ static void tui_main(UIBridgeData *bridge, UI *ui)
|
|||||||
tui_terminal_after_startup(ui);
|
tui_terminal_after_startup(ui);
|
||||||
// Tickle `main_loop` with a dummy event, else the initial "focus-gained"
|
// Tickle `main_loop` with a dummy event, else the initial "focus-gained"
|
||||||
// terminal response may not get processed until user hits a key.
|
// terminal response may not get processed until user hits a key.
|
||||||
loop_schedule_deferred(&main_loop, event_create(tui_dummy_event, 0));
|
loop_schedule_deferred(&main_loop, event_create(loop_dummy_event, 0));
|
||||||
}
|
}
|
||||||
// "Passive" (I/O-driven) loop: TUI thread "main loop".
|
// "Passive" (I/O-driven) loop: TUI thread "main loop".
|
||||||
while (!tui_is_stopped(ui)) {
|
while (!tui_is_stopped(ui)) {
|
||||||
@ -449,10 +449,6 @@ static void tui_main(UIBridgeData *bridge, UI *ui)
|
|||||||
xfree(data);
|
xfree(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tui_dummy_event(void **argv)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handoff point between the main (ui_bridge) thread and the TUI thread.
|
/// Handoff point between the main (ui_bridge) thread and the TUI thread.
|
||||||
static void tui_scheduler(Event event, void *d)
|
static void tui_scheduler(Event event, void *d)
|
||||||
{
|
{
|
||||||
|
@ -23,4 +23,10 @@ typedef int LuaRef;
|
|||||||
|
|
||||||
typedef struct expand expand_T;
|
typedef struct expand expand_T;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
kNone = -1,
|
||||||
|
kFalse = 0,
|
||||||
|
kTrue = 1,
|
||||||
|
} TriState;
|
||||||
|
|
||||||
#endif // NVIM_TYPES_H
|
#endif // NVIM_TYPES_H
|
||||||
|
@ -5,6 +5,7 @@ local NIL = helpers.NIL
|
|||||||
local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq
|
local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq
|
||||||
local command = helpers.command
|
local command = helpers.command
|
||||||
local eval = helpers.eval
|
local eval = helpers.eval
|
||||||
|
local expect = helpers.expect
|
||||||
local funcs = helpers.funcs
|
local funcs = helpers.funcs
|
||||||
local iswin = helpers.iswin
|
local iswin = helpers.iswin
|
||||||
local meth_pcall = helpers.meth_pcall
|
local meth_pcall = helpers.meth_pcall
|
||||||
@ -365,6 +366,126 @@ describe('API', function()
|
|||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
describe('nvim_paste', function()
|
||||||
|
it('validates args', function()
|
||||||
|
expect_err('Invalid phase: %-2', request,
|
||||||
|
'nvim_paste', 'foo', -2)
|
||||||
|
expect_err('Invalid phase: 4', request,
|
||||||
|
'nvim_paste', 'foo', 4)
|
||||||
|
end)
|
||||||
|
it('non-streaming', function()
|
||||||
|
-- With final "\n".
|
||||||
|
nvim('paste', 'line 1\nline 2\nline 3\n', -1)
|
||||||
|
expect([[
|
||||||
|
line 1
|
||||||
|
line 2
|
||||||
|
line 3
|
||||||
|
]])
|
||||||
|
-- Cursor follows the paste.
|
||||||
|
eq({0,4,1,0}, funcs.getpos('.'))
|
||||||
|
eq(false, nvim('get_option', 'paste'))
|
||||||
|
command('%delete _')
|
||||||
|
-- Without final "\n".
|
||||||
|
nvim('paste', 'line 1\nline 2\nline 3', -1)
|
||||||
|
expect([[
|
||||||
|
line 1
|
||||||
|
line 2
|
||||||
|
line 3]])
|
||||||
|
-- Cursor follows the paste.
|
||||||
|
eq({0,3,6,0}, funcs.getpos('.'))
|
||||||
|
eq(false, nvim('get_option', 'paste'))
|
||||||
|
end)
|
||||||
|
it('vim.paste() failure', function()
|
||||||
|
nvim('execute_lua', 'vim.paste = (function(lines, phase) error("fake fail") end)', {})
|
||||||
|
expect_err([[Error executing lua: %[string "%<nvim>"]:1: fake fail]],
|
||||||
|
request, 'nvim_paste', 'line 1\nline 2\nline 3', 1)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('nvim_put', function()
|
||||||
|
it('validates args', function()
|
||||||
|
expect_err('Invalid lines %(expected array of strings%)', request,
|
||||||
|
'nvim_put', {42}, 'l', false, false)
|
||||||
|
expect_err("Invalid type: 'x'", request,
|
||||||
|
'nvim_put', {'foo'}, 'x', false, false)
|
||||||
|
end)
|
||||||
|
it("fails if 'nomodifiable'", function()
|
||||||
|
command('set nomodifiable')
|
||||||
|
expect_err([[Vim:E21: Cannot make changes, 'modifiable' is off]], request,
|
||||||
|
'nvim_put', {'a','b'}, 'l', true, true)
|
||||||
|
end)
|
||||||
|
it('inserts text', function()
|
||||||
|
-- linewise
|
||||||
|
nvim('put', {'line 1','line 2','line 3'}, 'l', true, true)
|
||||||
|
expect([[
|
||||||
|
|
||||||
|
line 1
|
||||||
|
line 2
|
||||||
|
line 3]])
|
||||||
|
eq({0,4,1,0}, funcs.getpos('.'))
|
||||||
|
command('%delete _')
|
||||||
|
-- charwise
|
||||||
|
nvim('put', {'line 1','line 2','line 3'}, 'c', true, false)
|
||||||
|
expect([[
|
||||||
|
line 1
|
||||||
|
line 2
|
||||||
|
line 3]])
|
||||||
|
eq({0,1,1,0}, funcs.getpos('.')) -- follow=false
|
||||||
|
-- blockwise
|
||||||
|
nvim('put', {'AA','BB'}, 'b', true, true)
|
||||||
|
expect([[
|
||||||
|
lAAine 1
|
||||||
|
lBBine 2
|
||||||
|
line 3]])
|
||||||
|
eq({0,2,4,0}, funcs.getpos('.'))
|
||||||
|
command('%delete _')
|
||||||
|
-- Empty lines list.
|
||||||
|
nvim('put', {}, 'c', true, true)
|
||||||
|
eq({0,1,1,0}, funcs.getpos('.'))
|
||||||
|
expect([[]])
|
||||||
|
-- Single empty line.
|
||||||
|
nvim('put', {''}, 'c', true, true)
|
||||||
|
eq({0,1,1,0}, funcs.getpos('.'))
|
||||||
|
expect([[
|
||||||
|
]])
|
||||||
|
nvim('put', {'AB'}, 'c', true, true)
|
||||||
|
-- after=false, follow=true
|
||||||
|
nvim('put', {'line 1','line 2'}, 'c', false, true)
|
||||||
|
expect([[
|
||||||
|
Aline 1
|
||||||
|
line 2B]])
|
||||||
|
eq({0,2,7,0}, funcs.getpos('.'))
|
||||||
|
command('%delete _')
|
||||||
|
nvim('put', {'AB'}, 'c', true, true)
|
||||||
|
-- after=false, follow=false
|
||||||
|
nvim('put', {'line 1','line 2'}, 'c', false, false)
|
||||||
|
expect([[
|
||||||
|
Aline 1
|
||||||
|
line 2B]])
|
||||||
|
eq({0,1,2,0}, funcs.getpos('.'))
|
||||||
|
eq('', nvim('eval', 'v:errmsg'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('detects charwise/linewise text (empty {type})', function()
|
||||||
|
-- linewise (final item is empty string)
|
||||||
|
nvim('put', {'line 1','line 2','line 3',''}, '', true, true)
|
||||||
|
expect([[
|
||||||
|
|
||||||
|
line 1
|
||||||
|
line 2
|
||||||
|
line 3]])
|
||||||
|
eq({0,4,1,0}, funcs.getpos('.'))
|
||||||
|
command('%delete _')
|
||||||
|
-- charwise (final item is non-empty)
|
||||||
|
nvim('put', {'line 1','line 2','line 3'}, '', true, true)
|
||||||
|
expect([[
|
||||||
|
line 1
|
||||||
|
line 2
|
||||||
|
line 3]])
|
||||||
|
eq({0,3,6,0}, funcs.getpos('.'))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
describe('nvim_strwidth', function()
|
describe('nvim_strwidth', function()
|
||||||
it('works', function()
|
it('works', function()
|
||||||
eq(3, nvim('strwidth', 'abc'))
|
eq(3, nvim('strwidth', 'abc'))
|
||||||
@ -626,12 +747,12 @@ describe('API', function()
|
|||||||
-- Make any RPC request (can be non-async: op-pending does not block).
|
-- Make any RPC request (can be non-async: op-pending does not block).
|
||||||
nvim('get_current_buf')
|
nvim('get_current_buf')
|
||||||
-- Buffer should not change.
|
-- Buffer should not change.
|
||||||
helpers.expect([[
|
expect([[
|
||||||
FIRST LINE
|
FIRST LINE
|
||||||
SECOND LINE]])
|
SECOND LINE]])
|
||||||
-- Now send input to complete the operator.
|
-- Now send input to complete the operator.
|
||||||
nvim('input', 'j')
|
nvim('input', 'j')
|
||||||
helpers.expect([[
|
expect([[
|
||||||
first line
|
first line
|
||||||
second line]])
|
second line]])
|
||||||
end)
|
end)
|
||||||
@ -664,7 +785,7 @@ describe('API', function()
|
|||||||
nvim('get_api_info')
|
nvim('get_api_info')
|
||||||
-- Send input to complete the mapping.
|
-- Send input to complete the mapping.
|
||||||
nvim('input', 'd')
|
nvim('input', 'd')
|
||||||
helpers.expect([[
|
expect([[
|
||||||
FIRST LINE
|
FIRST LINE
|
||||||
SECOND LINE]])
|
SECOND LINE]])
|
||||||
eq('it worked...', helpers.eval('g:foo'))
|
eq('it worked...', helpers.eval('g:foo'))
|
||||||
@ -680,7 +801,7 @@ describe('API', function()
|
|||||||
nvim('get_api_info')
|
nvim('get_api_info')
|
||||||
-- Send input to complete the mapping.
|
-- Send input to complete the mapping.
|
||||||
nvim('input', 'x')
|
nvim('input', 'x')
|
||||||
helpers.expect([[
|
expect([[
|
||||||
FIRST LINE
|
FIRST LINE
|
||||||
SECOND LINfooE]])
|
SECOND LINfooE]])
|
||||||
end)
|
end)
|
||||||
|
@ -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 helpers = require('test.functional.helpers')(nil)
|
||||||
local Screen = require('test.functional.ui.screen')
|
local Screen = require('test.functional.ui.screen')
|
||||||
local nvim_dir = helpers.nvim_dir
|
local nvim_dir = helpers.nvim_dir
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
-- TUI acceptance tests.
|
-- TUI acceptance tests.
|
||||||
-- Uses :terminal as a way to send keys and assert screen state.
|
-- Uses :terminal as a way to send keys and assert screen state.
|
||||||
|
--
|
||||||
|
-- "bracketed paste" terminal feature:
|
||||||
|
-- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode
|
||||||
|
|
||||||
local helpers = require('test.functional.helpers')(after_each)
|
local helpers = require('test.functional.helpers')(after_each)
|
||||||
local uname = helpers.uname
|
local uname = helpers.uname
|
||||||
local thelpers = require('test.functional.terminal.helpers')
|
local thelpers = require('test.functional.terminal.helpers')
|
||||||
@ -21,11 +25,14 @@ if helpers.pending_win32(pending) then return end
|
|||||||
|
|
||||||
describe('TUI', function()
|
describe('TUI', function()
|
||||||
local screen
|
local screen
|
||||||
|
local child_session
|
||||||
|
|
||||||
before_each(function()
|
before_each(function()
|
||||||
clear()
|
clear()
|
||||||
screen = thelpers.screen_setup(0, '["'..nvim_prog
|
local child_server = helpers.new_pipename()
|
||||||
..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler undodir=. directory=. viewdir=. backupdir=."]')
|
screen = thelpers.screen_setup(0,
|
||||||
|
string.format([=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "%s laststatus=2 background=dark"]]=],
|
||||||
|
nvim_prog, child_server, nvim_set))
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
{1: } |
|
{1: } |
|
||||||
{4:~ }|
|
{4:~ }|
|
||||||
@ -35,12 +42,31 @@ describe('TUI', function()
|
|||||||
|
|
|
|
||||||
{3:-- TERMINAL --} |
|
{3:-- TERMINAL --} |
|
||||||
]])
|
]])
|
||||||
|
child_session = helpers.connect(child_server)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
after_each(function()
|
after_each(function()
|
||||||
screen:detach()
|
screen:detach()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
-- Wait for mode in the child Nvim (avoid "typeahead race" #10826).
|
||||||
|
local function wait_for_mode(mode)
|
||||||
|
retry(nil, nil, function()
|
||||||
|
local _, m = child_session:request('nvim_get_mode')
|
||||||
|
eq(mode, m.mode)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Assert buffer contents in the child Nvim.
|
||||||
|
local function expect_child_buf_lines(expected)
|
||||||
|
assert(type({}) == type(expected))
|
||||||
|
retry(nil, nil, function()
|
||||||
|
local _, buflines = child_session:request(
|
||||||
|
'nvim_buf_get_lines', 0, 0, -1, false)
|
||||||
|
eq(expected, buflines)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
it('rapid resize #7572 #7628', function()
|
it('rapid resize #7572 #7628', function()
|
||||||
-- Need buffer rows to provoke the behavior.
|
-- Need buffer rows to provoke the behavior.
|
||||||
feed_data(":edit test/functional/fixtures/bigfile.txt:")
|
feed_data(":edit test/functional/fixtures/bigfile.txt:")
|
||||||
@ -128,7 +154,7 @@ describe('TUI', function()
|
|||||||
]])
|
]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('accepts ascii control sequences', function()
|
it('accepts ASCII control sequences', function()
|
||||||
feed_data('i')
|
feed_data('i')
|
||||||
feed_data('\022\007') -- ctrl+g
|
feed_data('\022\007') -- ctrl+g
|
||||||
feed_data('\022\022') -- ctrl+v
|
feed_data('\022\022') -- ctrl+v
|
||||||
@ -146,75 +172,359 @@ describe('TUI', function()
|
|||||||
]], attrs)
|
]], attrs)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('automatically sends <Paste> for bracketed paste sequences', function()
|
it('paste: Insert mode', function()
|
||||||
-- Pasting can be really slow in the TUI, specially in ASAN.
|
-- "bracketed paste"
|
||||||
-- This will be fixed later but for now we require a high timeout.
|
feed_data('i""\027i\027[200~')
|
||||||
screen.timeout = 60000
|
screen:expect([[
|
||||||
|
"{1:"} |
|
||||||
|
{4:~ }|
|
||||||
|
{4:~ }|
|
||||||
|
{4:~ }|
|
||||||
|
{5:[No Name] [+] }|
|
||||||
|
{3:-- INSERT --} |
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]])
|
||||||
|
feed_data('pasted from terminal')
|
||||||
|
expect_child_buf_lines({'"pasted from terminal"'})
|
||||||
|
screen:expect([[
|
||||||
|
"pasted from terminal{1:"} |
|
||||||
|
{4:~ }|
|
||||||
|
{4:~ }|
|
||||||
|
{4:~ }|
|
||||||
|
{5:[No Name] [+] }|
|
||||||
|
{3:-- INSERT --} |
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]])
|
||||||
|
feed_data('\027[201~') -- End paste.
|
||||||
|
feed_data('\027\000') -- ESC: go to Normal mode.
|
||||||
|
wait_for_mode('n')
|
||||||
|
screen:expect([[
|
||||||
|
"pasted from termina{1:l}" |
|
||||||
|
{4:~ }|
|
||||||
|
{4:~ }|
|
||||||
|
{4:~ }|
|
||||||
|
{5:[No Name] [+] }|
|
||||||
|
|
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]])
|
||||||
|
-- Dot-repeat/redo.
|
||||||
|
feed_data('2.')
|
||||||
|
expect_child_buf_lines(
|
||||||
|
{'"pasted from terminapasted from terminalpasted from terminall"'})
|
||||||
|
screen:expect([[
|
||||||
|
"pasted from terminapasted from terminalpasted fro|
|
||||||
|
m termina{1:l}l" |
|
||||||
|
{4:~ }|
|
||||||
|
{4:~ }|
|
||||||
|
{5:[No Name] [+] }|
|
||||||
|
|
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]])
|
||||||
|
-- Undo.
|
||||||
|
feed_data('u')
|
||||||
|
expect_child_buf_lines({'"pasted from terminal"'})
|
||||||
|
feed_data('u')
|
||||||
|
expect_child_buf_lines({''})
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('paste: normal-mode', function()
|
||||||
|
feed_data(':set ruler')
|
||||||
|
wait_for_mode('c')
|
||||||
|
feed_data('\n')
|
||||||
|
wait_for_mode('n')
|
||||||
|
local expected = {'line 1', ' line 2', 'ESC:\027 / CR: \013'}
|
||||||
|
local expected_attr = {
|
||||||
|
[3] = {bold = true},
|
||||||
|
[4] = {foreground = tonumber('0x00000c')},
|
||||||
|
[5] = {bold = true, reverse = true},
|
||||||
|
[11] = {foreground = tonumber('0x000051')},
|
||||||
|
[12] = {reverse = true, foreground = tonumber('0x000051')},
|
||||||
|
}
|
||||||
|
-- "bracketed paste"
|
||||||
|
feed_data('\027[200~'..table.concat(expected,'\n')..'\027[201~')
|
||||||
|
screen:expect{
|
||||||
|
grid=[[
|
||||||
|
line 1 |
|
||||||
|
line 2 |
|
||||||
|
ESC:{11:^[} / CR: {12:^}{11:M} |
|
||||||
|
{4:~ }|
|
||||||
|
{5:[No Name] [+] 3,13-14 All}|
|
||||||
|
|
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]],
|
||||||
|
attr_ids=expected_attr}
|
||||||
|
-- Dot-repeat/redo.
|
||||||
|
feed_data('.')
|
||||||
|
screen:expect{
|
||||||
|
grid=[[
|
||||||
|
line 2 |
|
||||||
|
ESC:{11:^[} / CR: {11:^M}line 1 |
|
||||||
|
line 2 |
|
||||||
|
ESC:{11:^[} / CR: {12:^}{11:M} |
|
||||||
|
{5:[No Name] [+] 5,13-14 Bot}|
|
||||||
|
|
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]],
|
||||||
|
attr_ids=expected_attr}
|
||||||
|
-- Undo.
|
||||||
|
feed_data('u')
|
||||||
|
expect_child_buf_lines(expected)
|
||||||
|
feed_data('u')
|
||||||
|
expect_child_buf_lines({''})
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('paste: cmdline-mode inserts 1 line', function()
|
||||||
|
feed_data('ifoo\n') -- Insert some text (for dot-repeat later).
|
||||||
|
feed_data('\027:""') -- Enter Cmdline-mode.
|
||||||
|
feed_data('\027[D') -- <Left> to place cursor between quotes.
|
||||||
|
wait_for_mode('c')
|
||||||
|
-- "bracketed paste"
|
||||||
|
feed_data('\027[200~line 1\nline 2\n\027[201~')
|
||||||
|
screen:expect{grid=[[
|
||||||
|
foo |
|
||||||
|
|
|
||||||
|
{4:~ }|
|
||||||
|
{4:~ }|
|
||||||
|
{5:[No Name] [+] }|
|
||||||
|
:"line 1{1:"} |
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]]}
|
||||||
|
-- Dot-repeat/redo.
|
||||||
|
feed_data('\027\000')
|
||||||
|
wait_for_mode('n')
|
||||||
|
feed_data('.')
|
||||||
|
screen:expect{grid=[[
|
||||||
|
foo |
|
||||||
|
foo |
|
||||||
|
{1: } |
|
||||||
|
{4:~ }|
|
||||||
|
{5:[No Name] [+] }|
|
||||||
|
|
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]]}
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('paste: cmdline-mode collects chunks of unfinished line', function()
|
||||||
|
local function expect_cmdline(expected)
|
||||||
|
retry(nil, nil, function()
|
||||||
|
local _, cmdline = child_session:request(
|
||||||
|
'nvim_call_function', 'getcmdline', {})
|
||||||
|
eq(expected, cmdline)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
feed_data('\027:""') -- Enter Cmdline-mode.
|
||||||
|
feed_data('\027[D') -- <Left> to place cursor between quotes.
|
||||||
|
wait_for_mode('c')
|
||||||
|
feed_data('\027[200~stuff 1 ')
|
||||||
|
expect_cmdline('"stuff 1 "')
|
||||||
|
-- Discards everything after the first line.
|
||||||
|
feed_data('more\nstuff 2\nstuff 3\n')
|
||||||
|
expect_cmdline('"stuff 1 more"')
|
||||||
|
feed_data('stuff 3')
|
||||||
|
expect_cmdline('"stuff 1 more"')
|
||||||
|
-- End the paste sequence.
|
||||||
|
feed_data('\027[201~')
|
||||||
|
feed_data(' typed')
|
||||||
|
expect_cmdline('"stuff 1 more typed"')
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('paste: recovers from vim.paste() failure', function()
|
||||||
|
child_session:request('nvim_execute_lua', [[
|
||||||
|
_G.save_paste_fn = vim.paste
|
||||||
|
vim.paste = function(lines, phase) error("fake fail") end
|
||||||
|
]], {})
|
||||||
|
-- Prepare something for dot-repeat/redo.
|
||||||
|
feed_data('ifoo\n\027\000')
|
||||||
|
wait_for_mode('n')
|
||||||
|
screen:expect{grid=[[
|
||||||
|
foo |
|
||||||
|
{1: } |
|
||||||
|
{4:~ }|
|
||||||
|
{4:~ }|
|
||||||
|
{5:[No Name] [+] }|
|
||||||
|
|
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]]}
|
||||||
|
-- Start pasting...
|
||||||
|
feed_data('\027[200~line 1\nline 2\n')
|
||||||
|
wait_for_mode('n')
|
||||||
|
screen:expect{any='paste: Error executing lua'}
|
||||||
|
-- Remaining chunks are discarded after vim.paste() failure.
|
||||||
|
feed_data('line 3\nline 4\n')
|
||||||
|
feed_data('line 5\nline 6\n')
|
||||||
|
feed_data('line 7\nline 8\n')
|
||||||
|
-- Stop paste.
|
||||||
|
feed_data('\027[201~')
|
||||||
|
feed_data('\n') -- <Enter>
|
||||||
|
--Dot-repeat/redo is not modified by failed paste.
|
||||||
|
feed_data('.')
|
||||||
|
screen:expect{grid=[[
|
||||||
|
foo |
|
||||||
|
foo |
|
||||||
|
{1: } |
|
||||||
|
{4:~ }|
|
||||||
|
{5:[No Name] [+] }|
|
||||||
|
|
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]]}
|
||||||
|
-- Editor should still work after failed/drained paste.
|
||||||
|
feed_data('ityped input...\027\000')
|
||||||
|
screen:expect{grid=[[
|
||||||
|
foo |
|
||||||
|
foo |
|
||||||
|
typed input..{1:.} |
|
||||||
|
{4:~ }|
|
||||||
|
{5:[No Name] [+] }|
|
||||||
|
|
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]]}
|
||||||
|
-- Paste works if vim.paste() succeeds.
|
||||||
|
child_session:request('nvim_execute_lua', [[
|
||||||
|
vim.paste = _G.save_paste_fn
|
||||||
|
]], {})
|
||||||
|
feed_data('\027[200~line A\nline B\n\027[201~')
|
||||||
|
feed_data('\n') -- <Enter>
|
||||||
|
screen:expect{grid=[[
|
||||||
|
foo |
|
||||||
|
typed input...line A |
|
||||||
|
line B |
|
||||||
|
{1: } |
|
||||||
|
{5:[No Name] [+] }|
|
||||||
|
|
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]]}
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("paste: 'nomodifiable' buffer", function()
|
||||||
|
child_session:request('nvim_command', 'set nomodifiable')
|
||||||
|
feed_data('\027[200~fail 1\nfail 2\n\027[201~')
|
||||||
|
screen:expect{any='Vim:E21'}
|
||||||
|
feed_data('\n') -- <Enter>
|
||||||
|
child_session:request('nvim_command', 'set modifiable')
|
||||||
|
feed_data('\027[200~success 1\nsuccess 2\n\027[201~')
|
||||||
|
screen:expect{grid=[[
|
||||||
|
success 1 |
|
||||||
|
success 2 |
|
||||||
|
{1: } |
|
||||||
|
{4:~ }|
|
||||||
|
{5:[No Name] [+] }|
|
||||||
|
|
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]]}
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- TODO
|
||||||
|
it('paste: other modes', function()
|
||||||
|
-- Other modes act like CTRL-C + paste.
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('paste: exactly 64 bytes #10311', function()
|
||||||
|
local expected = string.rep('z', 64)
|
||||||
|
feed_data('i')
|
||||||
|
wait_for_mode('i')
|
||||||
|
-- "bracketed paste"
|
||||||
|
feed_data('\027[200~'..expected..'\027[201~')
|
||||||
|
feed_data(' end')
|
||||||
|
expected = expected..' end'
|
||||||
|
expect_child_buf_lines({expected})
|
||||||
|
screen:expect([[
|
||||||
|
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz|
|
||||||
|
zzzzzzzzzzzzzz end{1: } |
|
||||||
|
{4:~ }|
|
||||||
|
{4:~ }|
|
||||||
|
{5:[No Name] [+] }|
|
||||||
|
{3:-- INSERT --} |
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('paste: big burst of input', function()
|
||||||
|
feed_data(':set ruler\n')
|
||||||
|
local t = {}
|
||||||
|
for i = 1, 3000 do
|
||||||
|
t[i] = 'item ' .. tostring(i)
|
||||||
|
end
|
||||||
|
feed_data('i')
|
||||||
|
wait_for_mode('i')
|
||||||
|
-- "bracketed paste"
|
||||||
|
feed_data('\027[200~'..table.concat(t, '\n')..'\027[201~')
|
||||||
|
expect_child_buf_lines(t)
|
||||||
|
feed_data(' end')
|
||||||
|
screen:expect([[
|
||||||
|
item 2997 |
|
||||||
|
item 2998 |
|
||||||
|
item 2999 |
|
||||||
|
item 3000 end{1: } |
|
||||||
|
{5:[No Name] [+] 3000,14 Bot}|
|
||||||
|
{3:-- INSERT --} |
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]])
|
||||||
|
feed_data('\027\000') -- ESC: go to Normal mode.
|
||||||
|
wait_for_mode('n')
|
||||||
|
-- Dot-repeat/redo.
|
||||||
|
feed_data('.')
|
||||||
|
screen:expect([[
|
||||||
|
item 2997 |
|
||||||
|
item 2998 |
|
||||||
|
item 2999 |
|
||||||
|
item 3000 en{1:d}d |
|
||||||
|
{5:[No Name] [+] 5999,13 Bot}|
|
||||||
|
|
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('paste: forwards spurious "start paste" code', function()
|
||||||
|
-- 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_data('i\027[200~')
|
feed_data('i\027[200~')
|
||||||
|
feed_data('\npasted from terminal (1)\n')
|
||||||
|
-- Send spurious "start paste" sequence.
|
||||||
|
feed_data('\027[200~')
|
||||||
|
feed_data('\n')
|
||||||
|
-- Send the "stop paste" sequence.
|
||||||
|
feed_data('\027[201~')
|
||||||
|
|
||||||
|
screen:expect{grid=[[
|
||||||
|
|
|
||||||
|
pasted from terminal (1) |
|
||||||
|
{6:^[}[200~ |
|
||||||
|
{1: } |
|
||||||
|
{5:[No Name] [+] }|
|
||||||
|
{3:-- INSERT --} |
|
||||||
|
{3:-- TERMINAL --} |
|
||||||
|
]],
|
||||||
|
attr_ids={
|
||||||
|
[1] = {reverse = true},
|
||||||
|
[2] = {background = tonumber('0x00000b')},
|
||||||
|
[3] = {bold = true},
|
||||||
|
[4] = {foreground = tonumber('0x00000c')},
|
||||||
|
[5] = {bold = true, reverse = true},
|
||||||
|
[6] = {foreground = tonumber('0x000051')},
|
||||||
|
}}
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('paste: ignores spurious "stop paste" code', function()
|
||||||
|
-- If "stop paste" sequence is received without a preceding "start paste"
|
||||||
|
-- sequence, it should be ignored.
|
||||||
|
feed_data('i')
|
||||||
|
-- Send "stop paste" sequence.
|
||||||
|
feed_data('\027[201~')
|
||||||
screen:expect([[
|
screen:expect([[
|
||||||
{1: } |
|
{1: } |
|
||||||
{4:~ }|
|
{4:~ }|
|
||||||
{4:~ }|
|
{4:~ }|
|
||||||
{4:~ }|
|
{4:~ }|
|
||||||
{5:[No Name] }|
|
{5:[No Name] }|
|
||||||
{3:-- INSERT (paste) --} |
|
|
||||||
{3:-- TERMINAL --} |
|
|
||||||
]])
|
|
||||||
feed_data('pasted from terminal')
|
|
||||||
screen:expect([[
|
|
||||||
pasted from terminal{1: } |
|
|
||||||
{4:~ }|
|
|
||||||
{4:~ }|
|
|
||||||
{4:~ }|
|
|
||||||
{5:[No Name] [+] }|
|
|
||||||
{3:-- INSERT (paste) --} |
|
|
||||||
{3:-- TERMINAL --} |
|
|
||||||
]])
|
|
||||||
feed_data('\027[201~')
|
|
||||||
screen:expect([[
|
|
||||||
pasted from terminal{1: } |
|
|
||||||
{4:~ }|
|
|
||||||
{4:~ }|
|
|
||||||
{4:~ }|
|
|
||||||
{5:[No Name] [+] }|
|
|
||||||
{3:-- INSERT --} |
|
{3:-- INSERT --} |
|
||||||
{3:-- TERMINAL --} |
|
{3:-- TERMINAL --} |
|
||||||
]])
|
]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('handles pasting a specific amount of text', function()
|
-- TODO
|
||||||
-- Need extra time for this test, specially in ASAN.
|
it('paste: handles missing "stop paste" code', function()
|
||||||
screen.timeout = 60000
|
|
||||||
feed_data('i\027[200~'..string.rep('z', 64)..'\027[201~')
|
|
||||||
screen:expect([[
|
|
||||||
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz|
|
|
||||||
zzzzzzzzzzzzzz{1: } |
|
|
||||||
{4:~ }|
|
|
||||||
{4:~ }|
|
|
||||||
{5:[No Name] [+] }|
|
|
||||||
{3:-- INSERT --} |
|
|
||||||
{3:-- TERMINAL --} |
|
|
||||||
]])
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('can handle arbitrarily long bursts of input', function()
|
|
||||||
-- Need extra time for this test, specially in ASAN.
|
|
||||||
screen.timeout = 60000
|
|
||||||
feed_command('set ruler')
|
|
||||||
local t = {}
|
|
||||||
for i = 1, 3000 do
|
|
||||||
t[i] = 'item ' .. tostring(i)
|
|
||||||
end
|
|
||||||
feed_data('i\027[200~'..table.concat(t, '\n')..'\027[201~')
|
|
||||||
screen:expect([[
|
|
||||||
item 2997 |
|
|
||||||
item 2998 |
|
|
||||||
item 2999 |
|
|
||||||
item 3000{1: } |
|
|
||||||
{5:[No Name] [+] 3000,10 Bot}|
|
|
||||||
{3:-- INSERT --} |
|
|
||||||
{3:-- TERMINAL --} |
|
|
||||||
]])
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('allows termguicolors to be set at runtime', function()
|
it('allows termguicolors to be set at runtime', function()
|
||||||
|
@ -110,77 +110,6 @@ describe('mappings', function()
|
|||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('feeding large chunks of input with <Paste>', function()
|
|
||||||
local screen
|
|
||||||
before_each(function()
|
|
||||||
clear()
|
|
||||||
screen = Screen.new()
|
|
||||||
screen:attach()
|
|
||||||
feed_command('set ruler')
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('ok', function()
|
|
||||||
if helpers.skip_fragile(pending) then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local t = {}
|
|
||||||
for i = 1, 20000 do
|
|
||||||
t[i] = 'item ' .. tostring(i)
|
|
||||||
end
|
|
||||||
feed('i<Paste>')
|
|
||||||
screen:expect([[
|
|
||||||
^ |
|
|
||||||
~ |
|
|
||||||
~ |
|
|
||||||
~ |
|
|
||||||
~ |
|
|
||||||
~ |
|
|
||||||
~ |
|
|
||||||
~ |
|
|
||||||
~ |
|
|
||||||
~ |
|
|
||||||
~ |
|
|
||||||
~ |
|
|
||||||
~ |
|
|
||||||
-- INSERT (paste) -- |
|
|
||||||
]])
|
|
||||||
feed(table.concat(t, '<Enter>'))
|
|
||||||
screen:expect([[
|
|
||||||
item 19988 |
|
|
||||||
item 19989 |
|
|
||||||
item 19990 |
|
|
||||||
item 19991 |
|
|
||||||
item 19992 |
|
|
||||||
item 19993 |
|
|
||||||
item 19994 |
|
|
||||||
item 19995 |
|
|
||||||
item 19996 |
|
|
||||||
item 19997 |
|
|
||||||
item 19998 |
|
|
||||||
item 19999 |
|
|
||||||
item 20000^ |
|
|
||||||
-- INSERT (paste) -- |
|
|
||||||
]])
|
|
||||||
feed('<Paste>')
|
|
||||||
screen:expect([[
|
|
||||||
item 19988 |
|
|
||||||
item 19989 |
|
|
||||||
item 19990 |
|
|
||||||
item 19991 |
|
|
||||||
item 19992 |
|
|
||||||
item 19993 |
|
|
||||||
item 19994 |
|
|
||||||
item 19995 |
|
|
||||||
item 19996 |
|
|
||||||
item 19997 |
|
|
||||||
item 19998 |
|
|
||||||
item 19999 |
|
|
||||||
item 20000^ |
|
|
||||||
-- INSERT -- 20000,11 Bot |
|
|
||||||
]])
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
describe('input utf sequences that contain CSI/K_SPECIAL', function()
|
describe('input utf sequences that contain CSI/K_SPECIAL', function()
|
||||||
before_each(clear)
|
before_each(clear)
|
||||||
it('ok', function()
|
it('ok', function()
|
||||||
|
Loading…
Reference in New Issue
Block a user