feat(terminal): parse current buffer contents in nvim_open_term() (#33720)

When nvim_open_term() is called with a non-empty buffer, the buffer
contents are piped into the PTY.
This commit is contained in:
Gregory Anders
2025-04-30 16:34:23 -05:00
committed by GitHub
parent 6577d72d81
commit 71f3a9c590
9 changed files with 100 additions and 46 deletions

View File

@@ -1170,7 +1170,7 @@ nvim_open_term({buffer}, {opts}) *nvim_open_term()*
By default (and currently the only option) the terminal will not be By default (and currently the only option) the terminal will not be
connected to an external process. Instead, input sent on the channel will connected to an external process. Instead, input sent on the channel will
be echoed directly by the terminal. This is useful to display ANSI be echoed directly by the terminal. This is useful to display ANSI
terminal sequences returned as part of a rpc message, or similar. terminal sequences returned as part of an RPC message, or similar.
Note: to directly initiate the terminal using the right size, display the Note: to directly initiate the terminal using the right size, display the
buffer in a configured window before calling this. For instance, for a buffer in a configured window before calling this. For instance, for a
@@ -1195,7 +1195,8 @@ nvim_open_term({buffer}, {opts}) *nvim_open_term()*
Since: 0.5.0 Since: 0.5.0
Parameters: ~ Parameters: ~
• {buffer} the buffer to use (expected to be empty) • {buffer} Buffer to use. Buffer contents (if any) will be written to
the PTY.
• {opts} Optional parameters. • {opts} Optional parameters.
• on_input: Lua callback for input sent, i e keypresses in • on_input: Lua callback for input sent, i e keypresses in
terminal mode. Note: keypresses are sent raw as they would terminal mode. Note: keypresses are sent raw as they would

View File

@@ -172,7 +172,8 @@ STARTUP
TERMINAL TERMINAL
todo |nvim_open_term()| can be called with a non-empty buffer. The buffer
contents are piped to the PTY and displayed as terminal output.
TREESITTER TREESITTER

View File

@@ -1653,7 +1653,7 @@ function vim.api.nvim_notify(msg, log_level, opts) end
--- By default (and currently the only option) the terminal will not be --- By default (and currently the only option) the terminal will not be
--- connected to an external process. Instead, input sent on the channel --- connected to an external process. Instead, input sent on the channel
--- will be echoed directly by the terminal. This is useful to display --- will be echoed directly by the terminal. This is useful to display
--- ANSI terminal sequences returned as part of a rpc message, or similar. --- ANSI terminal sequences returned as part of an RPC message, or similar.
--- ---
--- Note: to directly initiate the terminal using the right size, display the --- Note: to directly initiate the terminal using the right size, display the
--- buffer in a configured window before calling this. For instance, for a --- buffer in a configured window before calling this. For instance, for a
@@ -1675,7 +1675,8 @@ function vim.api.nvim_notify(msg, log_level, opts) end
--- end, { desc = 'Highlights ANSI termcodes in curbuf' }) --- end, { desc = 'Highlights ANSI termcodes in curbuf' })
--- ``` --- ```
--- ---
--- @param buffer integer the buffer to use (expected to be empty) --- @param buffer integer Buffer to use. Buffer contents (if any) will be written
--- to the PTY.
--- @param opts vim.api.keyset.open_term Optional parameters. --- @param opts vim.api.keyset.open_term Optional parameters.
--- - on_input: Lua callback for input sent, i e keypresses in terminal --- - on_input: Lua callback for input sent, i e keypresses in terminal
--- mode. Note: keypresses are sent raw as they would be to the pty --- mode. Note: keypresses are sent raw as they would be to the pty

View File

@@ -879,6 +879,7 @@ def CheckIncludes(filename, lines, error):
"mpack/mpack_core.h", "mpack/mpack_core.h",
"mpack/object.h", "mpack/object.h",
"nvim/func_attr.h", "nvim/func_attr.h",
"nvim/strings.h",
"termkey/termkey.h", "termkey/termkey.h",
"vterm/vterm.h", "vterm/vterm.h",
"xdiff/xdiff.h", "xdiff/xdiff.h",

View File

@@ -974,7 +974,7 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
/// By default (and currently the only option) the terminal will not be /// By default (and currently the only option) the terminal will not be
/// connected to an external process. Instead, input sent on the channel /// connected to an external process. Instead, input sent on the channel
/// will be echoed directly by the terminal. This is useful to display /// will be echoed directly by the terminal. This is useful to display
/// ANSI terminal sequences returned as part of a rpc message, or similar. /// ANSI terminal sequences returned as part of an RPC message, or similar.
/// ///
/// Note: to directly initiate the terminal using the right size, display the /// Note: to directly initiate the terminal using the right size, display the
/// buffer in a configured window before calling this. For instance, for a /// buffer in a configured window before calling this. For instance, for a
@@ -996,7 +996,8 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
/// end, { desc = 'Highlights ANSI termcodes in curbuf' }) /// end, { desc = 'Highlights ANSI termcodes in curbuf' })
/// ``` /// ```
/// ///
/// @param buffer the buffer to use (expected to be empty) /// @param buffer Buffer to use. Buffer contents (if any) will be written
/// to the PTY.
/// @param opts Optional parameters. /// @param opts Optional parameters.
/// - on_input: Lua callback for input sent, i e keypresses in terminal /// - on_input: Lua callback for input sent, i e keypresses in terminal
/// mode. Note: keypresses are sent raw as they would be to the pty /// mode. Note: keypresses are sent raw as they would be to the pty
@@ -1041,12 +1042,26 @@ Integer nvim_open_term(Buffer buffer, Dict(open_term) *opts, Error *err)
.close_cb = term_close, .close_cb = term_close,
.force_crlf = GET_BOOL_OR_TRUE(opts, open_term, force_crlf), .force_crlf = GET_BOOL_OR_TRUE(opts, open_term, force_crlf),
}; };
// Read existing buffer contents (if any)
StringBuilder contents = KV_INITIAL_VALUE;
read_buffer_into(buf, 1, buf->b_ml.ml_line_count, &contents);
channel_incref(chan); channel_incref(chan);
terminal_open(&chan->term, buf, topts); terminal_open(&chan->term, buf, topts);
if (chan->term != NULL) { if (chan->term != NULL) {
terminal_check_size(chan->term); terminal_check_size(chan->term);
} }
channel_decref(chan); channel_decref(chan);
// Write buffer contents to channel. channel_send takes ownership of the
// buffer so we do not need to free it.
if (contents.size > 0) {
const char *error = NULL;
channel_send(chan->id, contents.items, contents.size, true, &error);
VALIDATE(!error, "%s", error, {});
}
return (Integer)chan->id; return (Integer)chan->id;
} }

View File

@@ -4154,3 +4154,54 @@ void buf_set_changedtick(buf_T *const buf, const varnumber_T changedtick)
&old_val); &old_val);
} }
} }
/// Read the given buffer contents into a string.
void read_buffer_into(buf_T *buf, linenr_T start, linenr_T end, StringBuilder *sb)
FUNC_ATTR_NONNULL_ALL
{
assert(buf);
assert(sb);
if (buf->b_ml.ml_flags & ML_EMPTY) {
return;
}
size_t written = 0;
size_t len = 0;
linenr_T lnum = start;
char *lp = ml_get_buf(buf, lnum);
size_t lplen = (size_t)ml_get_buf_len(buf, lnum);
while (true) {
if (lplen == 0) {
len = 0;
} else if (lp[written] == NL) {
// NL -> NUL translation
len = 1;
kv_push(*sb, NUL);
} else {
char *s = vim_strchr(lp + written, NL);
len = s == NULL ? lplen - written : (size_t)(s - (lp + written));
kv_concat_len(*sb, lp + written, len);
}
if (len == lplen - written) {
// Finished a line, add a NL, unless this line should not have one.
if (lnum != end
|| (!buf->b_p_bin && buf->b_p_fixeol)
|| (lnum != buf->b_no_eol_lnum
&& (lnum != buf->b_ml.ml_line_count || buf->b_p_eol))) {
kv_push(*sb, NL);
}
lnum++;
if (lnum > end) {
break;
}
lp = ml_get_buf(buf, lnum);
lplen = (size_t)ml_get_buf_len(buf, lnum);
written = 0;
} else if (len > 0) {
written += len;
}
}
}

View File

@@ -8,6 +8,7 @@
#include "nvim/gettext_defs.h" // IWYU pragma: keep #include "nvim/gettext_defs.h" // IWYU pragma: keep
#include "nvim/macros_defs.h" #include "nvim/macros_defs.h"
#include "nvim/marktree_defs.h" #include "nvim/marktree_defs.h"
#include "nvim/strings.h"
#include "nvim/types_defs.h" #include "nvim/types_defs.h"
/// Values for buflist_getfile() /// Values for buflist_getfile()

View File

@@ -8,7 +8,7 @@
#include "auto/config.h" #include "auto/config.h"
#include "klib/kvec.h" #include "klib/kvec.h"
#include "nvim/ascii_defs.h" #include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h" #include "nvim/buffer.h"
#include "nvim/charset.h" #include "nvim/charset.h"
#include "nvim/errors.h" #include "nvim/errors.h"
#include "nvim/eval.h" #include "nvim/eval.h"
@@ -1204,44 +1204,7 @@ static size_t word_length(const char *str)
/// before we finish writing. /// before we finish writing.
static void read_input(StringBuilder *buf) static void read_input(StringBuilder *buf)
{ {
size_t written = 0; read_buffer_into(curbuf, curbuf->b_op_start.lnum, curbuf->b_op_end.lnum, buf);
size_t len = 0;
linenr_T lnum = curbuf->b_op_start.lnum;
char *lp = ml_get(lnum);
size_t lplen = (size_t)ml_get_len(lnum);
while (true) {
if (lplen == 0) {
len = 0;
} else if (lp[written] == NL) {
// NL -> NUL translation
len = 1;
kv_push(*buf, NUL);
} else {
char *s = vim_strchr(lp + written, NL);
len = s == NULL ? lplen - written : (size_t)(s - (lp + written));
kv_concat_len(*buf, lp + written, len);
}
if (len == lplen - written) {
// Finished a line, add a NL, unless this line should not have one.
if (lnum != curbuf->b_op_end.lnum
|| (!curbuf->b_p_bin && curbuf->b_p_fixeol)
|| (lnum != curbuf->b_no_eol_lnum
&& (lnum != curbuf->b_ml.ml_line_count || curbuf->b_p_eol))) {
kv_push(*buf, NL);
}
lnum++;
if (lnum > curbuf->b_op_end.lnum) {
break;
}
lp = ml_get(lnum);
lplen = (size_t)ml_get_len(lnum);
written = 0;
} else if (len > 0) {
written += len;
}
}
} }
static size_t write_output(char *output, size_t remaining, bool eof) static size_t write_output(char *output, size_t remaining, bool eof)

View File

@@ -3773,6 +3773,8 @@ describe('API', function()
}, },
[102] = { background = Screen.colors.LightMagenta, reverse = true }, [102] = { background = Screen.colors.LightMagenta, reverse = true },
[103] = { background = Screen.colors.LightMagenta, bold = true, reverse = true }, [103] = { background = Screen.colors.LightMagenta, bold = true, reverse = true },
[104] = { fg_indexed = true, foreground = tonumber('0xe00000') },
[105] = { fg_indexed = true, foreground = tonumber('0xe0e000') },
} }
end) end)
@@ -3887,6 +3889,24 @@ describe('API', function()
} }
eq('ba\024blaherrejösses!', exec_lua [[ return stream ]]) eq('ba\024blaherrejösses!', exec_lua [[ return stream ]])
end) end)
it('parses text from the current buffer', function()
local b = api.nvim_create_buf(true, true)
api.nvim_buf_set_lines(b, 0, -1, true, { '\027[31mHello\000\027[0m', '\027[33mworld\027[0m' })
api.nvim_set_current_buf(b)
screen:expect([[
{18:^^[}[31mHello{18:^@^[}[0m |
{18:^[}[33mworld{18:^[}[0m |
{1:~ }|*32
|
]])
api.nvim_open_term(b, {})
screen:expect([[
{104:^Hello} |
{105:world} |
|*33
]])
end)
end) end)
describe('nvim_del_mark', function() describe('nvim_del_mark', function()