aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBjörn Linse <bjorn.linse@gmail.com>2021-03-12 16:52:05 +0100
committerGitHub <noreply@github.com>2021-03-12 16:52:05 +0100
commitd38508d88ab78415368bce350072f9f0f73c15d2 (patch)
tree03183e664f513fe85d1539eec8e149ed564dbfcc /src
parentdc8273f2f1e615cac5cee86709ca4ae6dbd70edf (diff)
parented0893698777ae13c06cb3c76a8b7b4b9a967d5f (diff)
downloadrneovim-d38508d88ab78415368bce350072f9f0f73c15d2.tar.gz
rneovim-d38508d88ab78415368bce350072f9f0f73c15d2.tar.bz2
rneovim-d38508d88ab78415368bce350072f9f0f73c15d2.zip
Merge pull request #13567 from bfredl/termpipe
api: allow open non-current buffer as terminal (+ xmas bonus)
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/vim.c94
-rw-r--r--src/nvim/channel.c17
-rw-r--r--src/nvim/eval/funcs.c2
-rw-r--r--src/nvim/generators/gen_api_dispatch.lua14
-rw-r--r--src/nvim/generators/gen_eval.lua2
-rw-r--r--src/nvim/terminal.c38
6 files changed, 141 insertions, 26 deletions
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 586123aac1..1b428718b5 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -39,6 +39,7 @@
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/fileio.h"
+#include "nvim/move.h"
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/state.h"
@@ -1246,6 +1247,99 @@ fail:
return 0;
}
+/// Open a terminal instance in a buffer
+///
+/// By default (and currently the only option) the terminal will not be
+/// connected to an external process. Instead, input send on the channel
+/// will be echoed directly by the terminal. This is useful to disply
+/// ANSI terminal sequences returned as part of a 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
+/// floating display, first create an empty buffer using |nvim_create_buf()|,
+/// then display it using |nvim_open_win()|, and then call this function.
+/// Then |nvim_chan_send()| cal be called immediately to process sequences
+/// in a virtual terminal having the intended size.
+///
+/// @param buffer the buffer to use (expected to be empty)
+/// @param opts Optional parameters. Reserved for future use.
+/// @param[out] err Error details, if any
+Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err)
+ FUNC_API_SINCE(7)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+
+ if (opts.size > 0) {
+ api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ return 0;
+ }
+
+ TerminalOptions topts;
+ Channel *chan = channel_alloc(kChannelStreamInternal);
+ topts.data = chan;
+ // NB: overriden in terminal_check_size if a window is already
+ // displaying the buffer
+ topts.width = (uint16_t)MAX(curwin->w_width_inner - win_col_off(curwin), 0);
+ topts.height = (uint16_t)curwin->w_height_inner;
+ topts.write_cb = term_write;
+ topts.resize_cb = term_resize;
+ topts.close_cb = term_close;
+ Terminal *term = terminal_open(buf, topts);
+ terminal_check_size(term);
+ chan->term = term;
+ channel_incref(chan);
+ return (Integer)chan->id;
+}
+
+static void term_write(char *buf, size_t size, void *data)
+{
+ // TODO(bfredl): lua callback
+}
+
+static void term_resize(uint16_t width, uint16_t height, void *data)
+{
+ // TODO(bfredl): lua callback
+}
+
+static void term_close(void *data)
+{
+ Channel *chan = data;
+ terminal_destroy(chan->term);
+ chan->term = NULL;
+ channel_decref(chan);
+}
+
+
+/// Send data to channel `id`. For a job, it writes it to the
+/// stdin of the process. For the stdio channel |channel-stdio|,
+/// it writes to Nvim's stdout. For an internal terminal instance
+/// (|nvim_open_term()|) it writes directly to terimal output.
+/// See |channel-bytes| for more information.
+///
+/// This function writes raw data, not RPC messages. If the channel
+/// was created with `rpc=true` then the channel expects RPC
+/// messages, use |vim.rpcnotify()| and |vim.rpcrequest()| instead.
+///
+/// @param chan id of the channel
+/// @param data data to write. 8-bit clean: can contain NUL bytes.
+/// @param[out] err Error details, if any
+void nvim_chan_send(Integer chan, String data, Error *err)
+ FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY FUNC_API_LUA_ONLY
+{
+ const char *error = NULL;
+ if (!data.size) {
+ return;
+ }
+
+ channel_send((uint64_t)chan, data.data, data.size, &error);
+ if (error) {
+ api_set_error(err, kErrorTypeValidation, "%s", error);
+ }
+}
+
/// Open a new window.
///
/// Currently this is used to open floating and external windows.
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index 09a34ca9fe..7a08ba58d0 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -161,7 +161,7 @@ void channel_init(void)
///
/// Channel is allocated with refcount 1, which should be decreased
/// when the underlying stream closes.
-static Channel *channel_alloc(ChannelStreamType type)
+Channel *channel_alloc(ChannelStreamType type)
{
Channel *chan = xcalloc(1, sizeof(*chan));
if (type == kChannelStreamStdio) {
@@ -503,7 +503,7 @@ size_t channel_send(uint64_t id, char *data, size_t len, const char **error)
{
Channel *chan = find_channel(id);
if (!chan) {
- EMSG(_(e_invchan));
+ *error = _(e_invchan);
goto err;
}
@@ -518,6 +518,11 @@ size_t channel_send(uint64_t id, char *data, size_t len, const char **error)
return len * written;
}
+ if (chan->streamtype == kChannelStreamInternal && chan->term) {
+ terminal_receive(chan->term, data, len);
+ return len;
+ }
+
Stream *in = channel_instream(chan);
if (in->closed) {
@@ -724,8 +729,8 @@ static void channel_callback_call(Channel *chan, CallbackReader *reader)
/// Open terminal for channel
///
/// Channel `chan` is assumed to be an open pty channel,
-/// and curbuf is assumed to be a new, unmodified buffer.
-void channel_terminal_open(Channel *chan)
+/// and `buf` is assumed to be a new, unmodified buffer.
+void channel_terminal_open(buf_T *buf, Channel *chan)
{
TerminalOptions topts;
topts.data = chan;
@@ -734,8 +739,8 @@ void channel_terminal_open(Channel *chan)
topts.write_cb = term_write;
topts.resize_cb = term_resize;
topts.close_cb = term_close;
- curbuf->b_p_channel = (long)chan->id; // 'channel' option
- Terminal *term = terminal_open(topts);
+ buf->b_p_channel = (long)chan->id; // 'channel' option
+ Terminal *term = terminal_open(buf, topts);
chan->term = term;
channel_incref(chan);
}
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 60229e1ebc..25784c240f 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -10731,7 +10731,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
INTEGER_OBJ(pid), false, false, &err);
api_clear_error(&err);
- channel_terminal_open(chan);
+ channel_terminal_open(curbuf, chan);
channel_create_event(chan, NULL);
}
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua
index 7e78b9e9d6..d2a7c16186 100644
--- a/src/nvim/generators/gen_api_dispatch.lua
+++ b/src/nvim/generators/gen_api_dispatch.lua
@@ -104,8 +104,11 @@ for _,f in ipairs(shallowcopy(functions)) do
elseif startswith(f.name, "nvim_tabpage_") then
ismethod = true
end
+ f.remote = f.remote_only or not f.lua_only
+ f.lua = f.lua_only or not f.remote_only
+ f.eval = (not f.lua_only) and (not f.remote_only)
else
- f.remote_only = true
+ f.remote = true
f.since = 0
f.deprecated_since = 1
end
@@ -127,7 +130,8 @@ for _,f in ipairs(shallowcopy(functions)) do
newf.return_type = "Object"
end
newf.impl_name = f.name
- newf.remote_only = true
+ newf.lua = false
+ newf.eval = false
newf.since = 0
newf.deprecated_since = 1
functions[#functions+1] = newf
@@ -192,7 +196,7 @@ end
-- the real API.
for i = 1, #functions do
local fn = functions[i]
- if fn.impl_name == nil and not fn.lua_only then
+ if fn.impl_name == nil and fn.remote then
local args = {}
output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Error *error)')
@@ -323,7 +327,7 @@ void msgpack_rpc_init_method_table(void)
for i = 1, #functions do
local fn = functions[i]
- if not fn.lua_only then
+ if fn.remote then
output:write(' msgpack_rpc_add_method_handler('..
'(String) {.data = "'..fn.name..'", '..
'.size = sizeof("'..fn.name..'") - 1}, '..
@@ -492,7 +496,7 @@ local function process_function(fn)
end
for _, fn in ipairs(functions) do
- if not fn.remote_only or fn.name:sub(1, 4) == '_vim' then
+ if fn.lua or fn.name:sub(1, 4) == '_vim' then
process_function(fn)
end
end
diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua
index d16453530f..679895421a 100644
--- a/src/nvim/generators/gen_eval.lua
+++ b/src/nvim/generators/gen_eval.lua
@@ -25,7 +25,7 @@ local gperfpipe = io.open(funcsfname .. '.gperf', 'wb')
local funcs = require('eval').funcs
local metadata = mpack.unpack(io.open(metadata_file, 'rb'):read("*all"))
for _,fun in ipairs(metadata) do
- if not (fun.remote_only or fun.lua_only) then
+ if fun.eval then
funcs[fun.name] = {
args=#fun.parameters,
func='api_wrapper',
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index f6995cddb6..913ef3baed 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -169,19 +169,20 @@ void terminal_teardown(void)
multiqueue_free(refresh_timer.events);
time_watcher_close(&refresh_timer, NULL);
pmap_free(ptr_t)(invalidated_terminals);
+ invalidated_terminals = NULL;
}
// public API {{{
-Terminal *terminal_open(TerminalOptions opts)
+Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
{
// Create a new terminal instance and configure it
Terminal *rv = xcalloc(1, sizeof(Terminal));
rv->opts = opts;
rv->cursor.visible = true;
// Associate the terminal instance with the new buffer
- rv->buf_handle = curbuf->handle;
- curbuf->terminal = rv;
+ rv->buf_handle = buf->handle;
+ buf->terminal = rv;
// Create VTerm
rv->vt = vterm_new(opts.height, opts.width);
vterm_set_utf8(rv->vt, 1);
@@ -198,28 +199,36 @@ Terminal *terminal_open(TerminalOptions opts)
// have as many lines as screen rows when refresh_scrollback is called
rv->invalid_start = 0;
rv->invalid_end = opts.height;
- refresh_screen(rv, curbuf);
+
+ aco_save_T aco;
+ aucmd_prepbuf(&aco, buf);
+
+ refresh_screen(rv, buf);
set_option_value("buftype", 0, "terminal", OPT_LOCAL); // -V666
// Default settings for terminal buffers
- curbuf->b_p_ma = false; // 'nomodifiable'
- curbuf->b_p_ul = -1; // 'undolevels'
- curbuf->b_p_scbk = // 'scrollback' (initialize local from global)
+ buf->b_p_ma = false; // 'nomodifiable'
+ buf->b_p_ul = -1; // 'undolevels'
+ buf->b_p_scbk = // 'scrollback' (initialize local from global)
(p_scbk < 0) ? 10000 : MAX(1, p_scbk);
- curbuf->b_p_tw = 0; // 'textwidth'
+ buf->b_p_tw = 0; // 'textwidth'
set_option_value("wrap", false, NULL, OPT_LOCAL);
set_option_value("list", false, NULL, OPT_LOCAL);
- buf_set_term_title(curbuf, (char *)curbuf->b_ffname);
+ if (buf->b_ffname != NULL) {
+ buf_set_term_title(buf, (char *)buf->b_ffname);
+ }
RESET_BINDING(curwin);
// Reset cursor in current window.
curwin->w_cursor = (pos_T){ .lnum = 1, .col = 0, .coladd = 0 };
// Apply TermOpen autocmds _before_ configuring the scrollback buffer.
- apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, curbuf);
+ apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, buf);
// Local 'scrollback' _after_ autocmds.
- curbuf->b_p_scbk = (curbuf->b_p_scbk < 1) ? SB_MAX : curbuf->b_p_scbk;
+ buf->b_p_scbk = (buf->b_p_scbk < 1) ? SB_MAX : buf->b_p_scbk;
+
+ aucmd_restbuf(&aco);
// Configure the scrollback buffer.
- rv->sb_size = (size_t)curbuf->b_p_scbk;
+ rv->sb_size = (size_t)buf->b_p_scbk;
rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size);
// Configure the color palette. Try to get the color from:
@@ -511,7 +520,9 @@ void terminal_destroy(Terminal *term)
}
if (!term->refcount) {
- if (pmap_has(ptr_t)(invalidated_terminals, term)) {
+ // might be destroyed after terminal_teardown is invoked
+ if (invalidated_terminals
+ && pmap_has(ptr_t)(invalidated_terminals, term)) {
// flush any pending changes to the buffer
block_autocmds();
refresh_terminal(term);
@@ -1324,6 +1335,7 @@ static void refresh_scrollback(Terminal *term, buf_T *buf)
// focused) of a invalidated terminal
static void refresh_screen(Terminal *term, buf_T *buf)
{
+ assert(buf == curbuf); // TODO(bfredl): remove this condition
int changed = 0;
int added = 0;
int height;