diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt index a9b8c5fae8..1837e14623 100644 --- a/runtime/doc/if_lua.txt +++ b/runtime/doc/if_lua.txt @@ -533,6 +533,25 @@ inspect({object}, {options}) *vim.inspect()* See also: ~ 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| + diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt index eb6d562e18..833be8a103 100644 --- a/runtime/doc/provider.txt +++ b/runtime/doc/provider.txt @@ -222,11 +222,11 @@ 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 adds features like |quote-+| to get and -set the OS clipboard buffer directly. When you middle-click or CTRL-SHIFT-v -(macOS: CMD-v) to paste text into your terminal, this is "paste", not -"clipboard": the terminal application (Nvim) just gets a stream of text, it -does not interact with the clipboard directly. +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, @@ -235,19 +235,21 @@ pasted text. https://cirw.in/blog/bracketed-paste This works automatically if your terminal supports it. *ui-paste* -GUIs can opt-into Nvim's amazing paste-handling by calling |nvim_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. +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. Example: > +You can implement a custom paste handler by redefining |vim.paste()|. +Example: > - vim._paste = (function(lines, phase) + vim.paste = (function(lines, phase) vim.api.nvim_put(lines, 'c', true, true) end) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 910b76d02d..4f132ddbae 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1243,7 +1243,7 @@ Boolean nvim_paste(String data, Integer phase, Error *err) 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, + rv = nvim_execute_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args, err); if (ERROR_SET(err)) { draining = true; diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 05951fbd0f..fd34b8545d 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -93,47 +93,6 @@ local function _os_proc_children(ppid) return children end --- Default nvim_paste() handler. -local _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)() - -- TODO(ZyX-I): Create compatibility layer. --{{{1 package.path updater function -- Last inserted paths. Used to clear out items from package.[c]path when they @@ -202,6 +161,59 @@ local function inspect(object, options) -- luacheck: no unused error(object, options) -- Stub for gen_vimdoc.py 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| @@ -227,8 +239,8 @@ local module = { _update_package_paths = _update_package_paths, _os_proc_children = _os_proc_children, _os_proc_info = _os_proc_info, - _paste = _paste, _system = _system, + paste = paste, schedule_wrap = schedule_wrap, } diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 01e4a3a1a0..47a04795f8 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -396,7 +396,7 @@ describe('API', function() eq(false, nvim('get_option', 'paste')) end) it('vim.paste() failure', function() - nvim('execute_lua', 'vim._paste = (function(lines, phase) error("fake fail") end)', {}) + nvim('execute_lua', 'vim.paste = (function(lines, phase) error("fake fail") end)', {}) expect_err([[Error executing lua: %[string "%"]:1: fake fail]], request, 'nvim_paste', 'line 1\nline 2\nline 3', 1) end) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index e7db39b5d0..aafbbc04b1 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -326,8 +326,8 @@ describe('TUI', function() 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 + _G.save_paste_fn = vim.paste + vim.paste = function(lines, phase) error("fake fail") end ]], {}) -- Start pasting... feed_data('\027[200~line 1\nline 2\n') @@ -360,7 +360,7 @@ describe('TUI', function() ]]} -- Paste works if vim.paste() succeeds. child_session:request('nvim_execute_lua', [[ - vim._paste = _G.save_paste_fn + vim.paste = _G.save_paste_fn ]], {}) feed_data('\027[200~line A\nline B\n\027[201~') feed_data('\n') --