commit 71f3a9c5900fa1882a77f2300baac462a5bf480f
parent 6577d72d819dde32abeacd6a72d6ba64483f7ddc
Author: Gregory Anders <greg@gpanders.com>
Date: Wed, 30 Apr 2025 16:34:23 -0500
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.
Diffstat:
9 files changed, 100 insertions(+), 46 deletions(-)
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
@@ -1170,7 +1170,7 @@ nvim_open_term({buffer}, {opts}) *nvim_open_term()*
By default (and currently the only option) the terminal will not be
connected to an external process. Instead, input sent on the channel will
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
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
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.
• on_input: Lua callback for input sent, i e keypresses in
terminal mode. Note: keypresses are sent raw as they would
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
@@ -172,7 +172,8 @@ STARTUP
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
diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
@@ -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
--- connected to an external process. Instead, input sent on the channel
--- 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
--- 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' })
--- ```
---
---- @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.
--- - 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
diff --git a/src/clint.py b/src/clint.py
@@ -879,6 +879,7 @@ def CheckIncludes(filename, lines, error):
"mpack/mpack_core.h",
"mpack/object.h",
"nvim/func_attr.h",
+ "nvim/strings.h",
"termkey/termkey.h",
"vterm/vterm.h",
"xdiff/xdiff.h",
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
@@ -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
/// connected to an external process. Instead, input sent on the channel
/// 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
/// 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' })
/// ```
///
-/// @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.
/// - 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
@@ -1041,12 +1042,26 @@ Integer nvim_open_term(Buffer buffer, Dict(open_term) *opts, Error *err)
.close_cb = term_close,
.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);
terminal_open(&chan->term, buf, topts);
if (chan->term != NULL) {
terminal_check_size(chan->term);
}
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;
}
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
@@ -4154,3 +4154,54 @@ void buf_set_changedtick(buf_T *const buf, const varnumber_T changedtick)
&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;
+ }
+ }
+}
diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h
@@ -8,6 +8,7 @@
#include "nvim/gettext_defs.h" // IWYU pragma: keep
#include "nvim/macros_defs.h"
#include "nvim/marktree_defs.h"
+#include "nvim/strings.h"
#include "nvim/types_defs.h"
/// Values for buflist_getfile()
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
@@ -8,7 +8,7 @@
#include "auto/config.h"
#include "klib/kvec.h"
#include "nvim/ascii_defs.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/errors.h"
#include "nvim/eval.h"
@@ -1204,44 +1204,7 @@ static size_t word_length(const char *str)
/// before we finish writing.
static void read_input(StringBuilder *buf)
{
- size_t written = 0;
- 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;
- }
- }
+ read_buffer_into(curbuf, curbuf->b_op_start.lnum, curbuf->b_op_end.lnum, buf);
}
static size_t write_output(char *output, size_t remaining, bool eof)
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
@@ -3773,6 +3773,8 @@ describe('API', function()
},
[102] = { background = Screen.colors.LightMagenta, 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)
@@ -3887,6 +3889,24 @@ describe('API', function()
}
eq('ba\024blaherrejösses!', exec_lua [[ return stream ]])
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)
describe('nvim_del_mark', function()