This commit is contained in:
Brian Walshe 2024-12-17 14:09:47 +00:00 committed by GitHub
commit fa5110b18b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 141 additions and 7 deletions

View File

@ -2010,15 +2010,21 @@ NOTE: These commands cannot be used with |:global| or |:vglobal|.
*:r!* *:read!* *:r!* *:read!*
:[range]r[ead] [++opt] !{cmd} :[range]r[ead] [++opt] !{cmd}
Execute {cmd} and insert its standard output below Execute shell {cmd} and insert its standard output
the cursor or the specified line. A temporary file is below the cursor or the specified line. A temporary
used to store the output of the command which is then file is used to store the output of the command which
read into the buffer. 'shellredir' is used to save is then read into the buffer. 'shellredir' is used to
the output of the command, which can be set to include save the output of the command, which can be set to
stderr or not. {cmd} is executed like with ":!{cmd}", include stderr or not. {cmd} is executed like with
any '!' is replaced with the previous command |:!|. ":!{cmd}", any '!' is replaced with the previous
command |:!|.
See |++opt| for the possible values of [++opt]. See |++opt| for the possible values of [++opt].
*:r:* *:read:*
:[range]r[ead] :{cmd}
Execute Ex command {cmd} and insert its output below
the cursor or the specified line.
These commands insert the contents of a file, or the output of a command, These commands insert the contents of a file, or the output of a command,
into the buffer. They can be undone. They cannot be repeated with the "." into the buffer. They can be undone. They cannot be repeated with the "."
command. They work on a line basis, insertion starts below the line in which command. They work on a line basis, insertion starts below the line in which

View File

@ -217,6 +217,8 @@ EDITOR
"~/" are now expanded to the user's profile directory, not a relative path "~/" are now expanded to the user's profile directory, not a relative path
to a literal "~" directory. to a literal "~" directory.
• |hl-PmenuMatch| and |hl-PmenuMatchSel| show matched text in completion popup. • |hl-PmenuMatch| and |hl-PmenuMatchSel| show matched text in completion popup.
• |:read:| reads the output of an Ex command into the buffer. Example:>
:read :ls
EVENTS EVENTS

View File

@ -12,8 +12,10 @@
#include <uv.h> #include <uv.h>
#include "auto/config.h" #include "auto/config.h"
#include "klib/kvec.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
#include "nvim/arglist.h" #include "nvim/arglist.h"
#include "nvim/ascii_defs.h" #include "nvim/ascii_defs.h"
#include "nvim/autocmd.h" #include "nvim/autocmd.h"
@ -5826,6 +5828,31 @@ static void ex_syncbind(exarg_T *eap)
} }
} }
void do_read_cmd(exarg_T *eap)
{
Object cmd = CSTR_AS_OBJ(eap->arg + 1);
String cmd_var_name = cstr_as_string("_ex_cmd");
StringBuilder put_cmd = KV_INITIAL_VALUE;
Error error = ERROR_INIT;
nvim_set_var(cmd_var_name, cmd, &error);
if (error.type != kErrorTypeNone) {
emsg(error.msg);
return;
}
kv_printf(put_cmd, "%dput=execute(g:%s) | ", eap->line2, cmd_var_name.data);
kv_printf(put_cmd, "execute 'norm! )`.' | ");
kv_printf(put_cmd, "execute 'd _' | ");
do_cmdline(put_cmd.items, eap->ea_getline, eap->cookie, eap->flags);
kv_destroy(put_cmd);
nvim_del_var(cmd_var_name, &error);
if (error.type != kErrorTypeNone) {
emsg(error.msg);
return;
}
}
static void ex_read(exarg_T *eap) static void ex_read(exarg_T *eap)
{ {
int empty = (curbuf->b_ml.ml_flags & ML_EMPTY); int empty = (curbuf->b_ml.ml_flags & ML_EMPTY);
@ -5835,6 +5862,11 @@ static void ex_read(exarg_T *eap)
return; return;
} }
if (*eap->arg == ':') {
do_read_cmd(eap);
return;
}
if (u_save(eap->line2, (linenr_T)(eap->line2 + 1)) == FAIL) { if (u_save(eap->line2, (linenr_T)(eap->line2 + 1)) == FAIL) {
return; return;
} }

View File

@ -0,0 +1,94 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local eq, write_file, clear, pcall_err = t.eq, t.write_file, n.clear, t.pcall_err
local fn = n.fn
local setline, getline, setcharpos, execute = fn.setline, fn.getline, fn.setcharpos, fn.execute
local tmp_file = 'text.txt'
local original_text = { 'First', 'Last' }
local read_text = { ' This is a line starts with a space', ' This one starts with two spaces.' }
local inserted_middle = { original_text[1], read_text[1], read_text[2], original_text[2] }
local inserted_start = { read_text[1], read_text[2], original_text[1], original_text[2] }
local function test_read(cmd, expected)
setline(1, original_text)
setcharpos('.', { 0, 0, 0 })
execute(cmd)
for i, e in ipairs(expected) do
eq(e, getline(i))
end
end
local function test_undo(cmd)
setline(1, original_text)
execute(cmd)
execute('undo')
eq(original_text, { getline(1), getline(2) })
end
describe(':read', function()
local function cleanup()
os.remove(tmp_file)
end
before_each(function()
clear()
cleanup()
write_file(tmp_file, table.concat(read_text, '\n'), true)
end)
after_each(cleanup)
it('inserts text from file', function()
test_read('read ' .. tmp_file, inserted_middle)
eq({ 0, 2, 2, 0 }, fn.getpos('.'))
end)
it('inserts text from shell', function()
test_read('read !cat ' .. tmp_file, inserted_middle)
eq({ 0, 3, 3, 0 }, fn.getpos('.'))
end)
it('inserts text from Ex command', function()
local make_lines = string.format('let lines="%s"', table.concat(read_text, '\\n'))
execute(make_lines)
test_read('read :echo lines', inserted_middle)
end)
it('inserts text from file at specific position', function()
test_read('0read ' .. tmp_file, inserted_start)
eq({ 0, 1, 2, 0 }, fn.getpos('.'))
end)
it('inserts text from shell cmd at specific position', function()
test_read('0read !cat ' .. tmp_file, inserted_start)
eq({ 0, 2, 3, 0 }, fn.getpos('.'))
end)
it('executes next command when using |', function()
local make_lines = string.format('let lines="%s"', table.concat(read_text, '\\n'))
execute(make_lines)
execute("let guard = 'fail'")
test_read("read :echo lines | let guard='pass'", inserted_middle)
eq('pass', vim.trim(execute('echo guard')))
end)
it('sets fileformat, fileencoding, bomb correctly', function()
execute('set fileformat=dos')
execute('set fileencoding=latin1')
execute('set bomb')
execute('read ++edit ' .. tmp_file)
eq('fileformat=unix', vim.trim(execute('set fileformat?')))
eq('fileencoding=utf-8', vim.trim(execute('set fileencoding?')))
eq('nobomb', vim.trim(execute('set bomb?')))
end)
it('file reads can be undone', function()
test_undo('read ' .. tmp_file)
end)
it('shell reads can be undone', function()
test_undo('read !cat ' .. tmp_file)
end)
it('command reads can be undone', function()
local make_lines = string.format('let lines="%s"', table.concat(read_text, '\\n'))
execute(make_lines)
test_undo('read :echo lines')
end)
it('errors out correctly when a non-existant file is used', function()
eq("Vim(read):E484: Can't open file asdfasdf", pcall_err(execute, ':read asdfasdf'))
end)
it('errors out correctly when an invalid command is used', function()
eq('Vim:E492: Not an editor command: asdfasdf', pcall_err(execute, ':read :asdfasdf'))
end)
end)