aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/api.txt6
-rw-r--r--runtime/lua/vim/_meta/api.lua4
-rw-r--r--src/nvim/api/vim.c39
-rw-r--r--test/functional/api/vim_spec.lua69
-rw-r--r--test/functional/terminal/tui_spec.lua35
5 files changed, 125 insertions, 28 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index a7c71d9a32..e7a8e788b9 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -1155,8 +1155,8 @@ nvim_paste({data}, {crlf}, {phase}) *nvim_paste()*
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).
+ `vim.paste()`). On error or cancel, subsequent calls are ignored
+ ("drained") until the next paste is initiated (phase 1 or -1).
Attributes: ~
not allowed when |textlock| is active
@@ -1173,7 +1173,7 @@ nvim_paste({data}, {crlf}, {phase}) *nvim_paste()*
Return: ~
• true: Client may continue pasting.
- • false: Client must cancel the paste.
+ • false: Client should cancel the paste.
nvim_put({lines}, {type}, {after}, {follow}) *nvim_put()*
Puts text at cursor, in any mode.
diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
index f14606fc99..b53a9ad321 100644
--- a/runtime/lua/vim/_meta/api.lua
+++ b/runtime/lua/vim/_meta/api.lua
@@ -1816,8 +1816,8 @@ function vim.api.nvim_parse_expression(expr, flags, highlight) end
---
--- 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).
+--- `vim.paste()`). On error or cancel, subsequent calls are ignored
+--- ("drained") until the next paste is initiated (phase 1 or -1).
---
--- @param data string Multiline input. May be binary (containing NUL bytes).
--- @param crlf boolean Also break lines at CR and CRLF.
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index c1add75af9..b31accd3b0 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1219,8 +1219,8 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
///
/// 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).
+/// `vim.paste()`). On error or cancel, 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 crlf Also break lines at CR and CRLF.
@@ -1233,20 +1233,19 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
/// @param[out] err Error details, if any
/// @return
/// - true: Client may continue pasting.
-/// - false: Client must cancel the paste.
+/// - false: Client should cancel the paste.
Boolean nvim_paste(String data, Boolean crlf, Integer phase, Arena *arena, Error *err)
FUNC_API_SINCE(6)
FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
- static bool draining = false;
- bool cancel = false;
+ static bool cancelled = false;
VALIDATE_INT((phase >= -1 && phase <= 3), "phase", phase, {
return false;
});
if (phase == -1 || phase == 1) { // Start of paste-stream.
- draining = false;
- } else if (draining) {
+ cancelled = false;
+ } else if (cancelled) {
// Skip remaining chunks. Report error only once per "stream".
goto theend;
}
@@ -1255,26 +1254,26 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Arena *arena, Error
ADD_C(args, ARRAY_OBJ(lines));
ADD_C(args, INTEGER_OBJ(phase));
Object rv = NLUA_EXEC_STATIC("return vim.paste(...)", args, kRetNilBool, arena, err);
- if (ERROR_SET(err)) {
- draining = true;
- goto theend;
+ // vim.paste() decides if client should cancel.
+ if (ERROR_SET(err) || (rv.type == kObjectTypeBoolean && !rv.data.boolean)) {
+ cancelled = true;
}
- if (phase == -1 || phase == 1) {
+ if (!cancelled && (phase == -1 || phase == 1)) {
paste_store(kFalse, NULL_STRING, crlf);
}
- // 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) {
+ if (!cancelled) {
paste_store(kNone, data, crlf);
}
-theend:
- if (cancel || phase == -1 || phase == 3) { // End of paste-stream.
- draining = false;
+ if (phase == 3 || phase == (cancelled ? 2 : -1)) {
paste_store(kTrue, NULL_STRING, crlf);
}
-
- return !cancel;
+theend:
+ ;
+ bool retval = !cancelled;
+ if (phase == -1 || phase == 3) { // End of paste-stream.
+ cancelled = false;
+ }
+ return retval;
}
/// Puts text at cursor, in any mode.
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index b35ccb0c40..1b34945f13 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -1358,9 +1358,72 @@ describe('API', function()
test_paste_repeat_visual_select(true)
end)
end)
- it('vim.paste() failure', function()
- api.nvim_exec_lua('vim.paste = (function(lines, phase) error("fake fail") end)', {})
- eq('fake fail', pcall_err(request, 'nvim_paste', 'line 1\nline 2\nline 3', false, 1))
+ local function test_paste_cancel_error(is_error)
+ before_each(function()
+ exec_lua(([[
+ vim.paste = (function(overridden)
+ return function(lines, phase)
+ for i, line in ipairs(lines) do
+ if line == 'CANCEL' then
+ %s
+ end
+ end
+ return overridden(lines, phase)
+ end
+ end)(vim.paste)
+ ]]):format(is_error and 'error("fake fail")' or 'return false'))
+ end)
+ local function check_paste_cancel_error(data, crlf, phase)
+ if is_error then
+ eq('fake fail', pcall_err(api.nvim_paste, data, crlf, phase))
+ else
+ eq(false, api.nvim_paste(data, crlf, phase))
+ end
+ end
+ it('in phase -1', function()
+ feed('A')
+ check_paste_cancel_error('CANCEL', true, -1)
+ feed('<Esc>')
+ expect('')
+ feed('.')
+ expect('')
+ end)
+ it('in phase 1', function()
+ feed('A')
+ check_paste_cancel_error('CANCEL', true, 1)
+ feed('<Esc>')
+ expect('')
+ feed('.')
+ expect('')
+ end)
+ it('in phase 2', function()
+ feed('A')
+ eq(true, api.nvim_paste('aaa', true, 1))
+ expect('aaa')
+ check_paste_cancel_error('CANCEL', true, 2)
+ feed('<Esc>')
+ expect('aaa')
+ feed('.')
+ expect('aaaaaa')
+ end)
+ it('in phase 3', function()
+ feed('A')
+ eq(true, api.nvim_paste('aaa', true, 1))
+ expect('aaa')
+ eq(true, api.nvim_paste('bbb', true, 2))
+ expect('aaabbb')
+ check_paste_cancel_error('CANCEL', true, 3)
+ feed('<Esc>')
+ expect('aaabbb')
+ feed('.')
+ expect('aaabbbaaabbb')
+ end)
+ end
+ describe('vim.paste() cancel', function()
+ test_paste_cancel_error(false)
+ end)
+ describe('vim.paste() error', function()
+ test_paste_cancel_error(true)
end)
end)
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index b85ddec0f0..a7d87bb231 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -1298,6 +1298,41 @@ describe('TUI', function()
expect_child_buf_lines({ 'foo', '' })
end)
+ it('paste: vim.paste() cancel (retval=false) with streaming #30462', function()
+ child_session:request(
+ 'nvim_exec_lua',
+ [[
+ vim.paste = (function(overridden)
+ return function(lines, phase)
+ for i, line in ipairs(lines) do
+ if line:find('!') then
+ return false
+ end
+ end
+ return overridden(lines, phase)
+ end
+ end)(vim.paste)
+ ]],
+ {}
+ )
+ feed_data('A')
+ wait_for_mode('i')
+ feed_data('\027[200~aaa')
+ expect_child_buf_lines({ 'aaa' })
+ feed_data('bbb')
+ expect_child_buf_lines({ 'aaabbb' })
+ feed_data('ccc!') -- This chunk is cancelled.
+ expect_child_buf_lines({ 'aaabbb' })
+ feed_data('ddd\027[201~') -- This chunk is ignored.
+ expect_child_buf_lines({ 'aaabbb' })
+ feed_data('\027[27u')
+ wait_for_mode('n')
+ feed_data('.') -- Dot-repeat only includes chunks actually pasted.
+ expect_child_buf_lines({ 'aaabbbaaabbb' })
+ feed_data('$\027[200~eee\027[201~') -- A following paste works normally.
+ expect_child_buf_lines({ 'aaabbbaaabbbeee' })
+ end)
+
it("paste: 'nomodifiable' buffer", function()
child_session:request('nvim_command', 'set nomodifiable')
child_session:request(