From 619407eb548c7df56bc99b945338e9446f846fbb Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 14 Dec 2023 16:08:00 +0800 Subject: [PATCH] feat(nvim_open_term): convert LF => CRLF (#26384) Problem: Unlike termopen(), nvim_open_term() PTYs do not carriage-return the cursor on newline ("\n") input. nvim --clean :let chan_id = nvim_open_term(1, {}) :call chansend(chan_id, ["here", "are", "some", "lines"]) Actual behavior: here are some lines Expected behaviour: here are some lines Solution: Add `force_crlf` option, and enable it by default. --- runtime/doc/api.txt | 2 + runtime/doc/news.txt | 2 + runtime/lua/vim/_meta/api.lua | 2 + runtime/lua/vim/_meta/api_keysets.lua | 1 + src/nvim/api/keysets_defs.h | 1 + src/nvim/api/vim.c | 3 +- src/nvim/channel.c | 1 + src/nvim/terminal.c | 18 ++++++- src/nvim/terminal.h | 1 + test/functional/terminal/channel_spec.lua | 51 ++++++++++++++++++++ test/functional/terminal/scrollback_spec.lua | 22 ++++----- 11 files changed, 91 insertions(+), 13 deletions(-) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 4aba1f8141..48bbdc33df 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -1231,6 +1231,8 @@ nvim_open_term({buffer}, {*opts}) *nvim_open_term()* is sent as a "\r", not as a "\n". |textlock| applies. It is possible to call |nvim_chan_send()| directly in the callback however. ["input", term, bufnr, data] + • force_crlf: (boolean, default true) Convert "\n" to + "\r\n". Return: ~ Channel id, or 0 on error diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 04f143f0c3..9bfc577e87 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -344,6 +344,8 @@ The following changes to existing APIs or features add new behavior. • Diagnostic sign text is no longer configured with |sign_define()|. Use |vim.diagnostic.config()| instead. +• Added "force_crlf" option field in |nvim_open_term()|. + ============================================================================== REMOVED FEATURES *news-removed* diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 4ad0a2e791..231e1c3404 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1446,6 +1446,8 @@ function vim.api.nvim_notify(msg, log_level, opts) end --- is sent as a "\r", not as a "\n". `textlock` applies. It --- is possible to call `nvim_chan_send()` directly in the --- callback however. ["input", term, bufnr, data] +--- • force_crlf: (boolean, default true) Convert "\n" to +--- "\r\n". --- @return integer function vim.api.nvim_open_term(buffer, opts) end diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index 6a3e574455..f64cdb8afd 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -217,6 +217,7 @@ error('Cannot require a meta file') --- @class vim.api.keyset.open_term --- @field on_input? function +--- @field force_crlf? boolean --- @class vim.api.keyset.option --- @field scope? string diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index d1cbe43de0..c0daa0ca74 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -344,4 +344,5 @@ typedef struct { typedef struct { OptionalKeys is_set__open_term_; LuaRef on_input; + Boolean force_crlf; } Dict(open_term); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index a52d7493e3..2f3d527b9e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -985,6 +985,7 @@ fail: /// as a "\r", not as a "\n". |textlock| applies. It is possible /// to call |nvim_chan_send()| directly in the callback however. /// ["input", term, bufnr, data] +/// - force_crlf: (boolean, default true) Convert "\n" to "\r\n". /// @param[out] err Error details, if any /// @return Channel id, or 0 on error Integer nvim_open_term(Buffer buffer, Dict(open_term) *opts, Error *err) @@ -1002,7 +1003,6 @@ Integer nvim_open_term(Buffer buffer, Dict(open_term) *opts, Error *err) } LuaRef cb = LUA_NOREF; - if (HAS_KEY(opts, open_term, on_input)) { cb = opts->on_input; opts->on_input = LUA_NOREF; @@ -1020,6 +1020,7 @@ Integer nvim_open_term(Buffer buffer, Dict(open_term) *opts, Error *err) .write_cb = term_write, .resize_cb = term_resize, .close_cb = term_close, + .force_crlf = GET_BOOL_OR_TRUE(opts, open_term, force_crlf), }; channel_incref(chan); terminal_open(&chan->term, buf, topts); diff --git a/src/nvim/channel.c b/src/nvim/channel.c index ca8cbed8f9..fb4711f7d9 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -801,6 +801,7 @@ void channel_terminal_open(buf_T *buf, Channel *chan) .write_cb = term_write, .resize_cb = term_resize, .close_cb = term_close, + .force_crlf = false, }; buf->b_p_channel = (OptInt)chan->id; // 'channel' option channel_incref(chan); diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index af9693c4b0..3b70ee922a 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -43,6 +43,7 @@ #include #include +#include "klib/kvec.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii_defs.h" @@ -80,6 +81,7 @@ #include "nvim/optionstr.h" #include "nvim/pos_defs.h" #include "nvim/state.h" +#include "nvim/strings.h" #include "nvim/terminal.h" #include "nvim/types_defs.h" #include "nvim/ui.h" @@ -786,7 +788,21 @@ void terminal_receive(Terminal *term, const char *data, size_t len) return; } - vterm_input_write(term->vt, data, len); + if (term->opts.force_crlf) { + StringBuilder crlf_data = KV_INITIAL_VALUE; + + for (size_t i = 0; i < len; i++) { + if (data[i] == '\n' && (i == 0 || (i > 0 && data[i - 1] != '\r'))) { + kv_push(crlf_data, '\r'); + } + kv_push(crlf_data, data[i]); + } + + vterm_input_write(term->vt, crlf_data.items, kv_size(crlf_data)); + kv_destroy(crlf_data); + } else { + vterm_input_write(term->vt, data, len); + } vterm_screen_flush_damage(term->vts); } diff --git a/src/nvim/terminal.h b/src/nvim/terminal.h index db62bd2a5b..ffa97f17b2 100644 --- a/src/nvim/terminal.h +++ b/src/nvim/terminal.h @@ -16,6 +16,7 @@ typedef struct { terminal_write_cb write_cb; terminal_resize_cb resize_cb; terminal_close_cb close_cb; + bool force_crlf; } TerminalOptions; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/test/functional/terminal/channel_spec.lua b/test/functional/terminal/channel_spec.lua index 6fb1a21561..b9abcd61c8 100644 --- a/test/functional/terminal/channel_spec.lua +++ b/test/functional/terminal/channel_spec.lua @@ -192,4 +192,55 @@ describe('no crash when TermOpen autocommand', function() ]]} assert_alive() end) + + it('nvim_open_term({force_crlf=true}) converts newlines', function() + local buf = meths.create_buf(false, true) + local win = meths.get_current_win() + local term = meths.open_term(buf, {force_crlf = true}) + screen:try_resize(8, 10) + meths.win_set_buf(win, buf) + meths.chan_send(term, 'here\nthere\nfoo\r\nbar\n\ntest') + screen:expect{grid=[[ + ^here | + there | + foo | + bar | + | + test | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + meths.chan_send(term, '\nfirst') + screen:expect{grid=[[ + ^here | + there | + foo | + bar | + | + test | + first | + {0:~ }| + {0:~ }| + | + ]]} + meths.buf_delete(buf, {force = true}) + buf = meths.create_buf(false, true) + term = meths.open_term(buf, {force_crlf = false}) + meths.win_set_buf(win, buf) + meths.chan_send(term, 'here\nthere') + screen:expect{grid=[[ + ^here | + there | + | + | + | + | + | + | + | + | + ]]} + end) end) diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index d2c636b03f..00104734ef 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -576,21 +576,21 @@ describe("pending scrollback line handling", function() ]] screen:expect [[ {1: 1 }^a | - {1: 2 } a | - {1: 3 } a | - {1: 4 } a | - {1: 5 } a | - {1: 6 } a | + {1: 2 }a | + {1: 3 }a | + {1: 4 }a | + {1: 5 }a | + {1: 6 }a | | ]] feed('G') screen:expect [[ - {1: 7 } a | - {1: 8 } a | - {1: 9 } a | - {1: 10 } a | - {1: 11 } a | - {1: 12 } ^a | + {1: 7 }a | + {1: 8 }a | + {1: 9 }a | + {1: 10 }a | + {1: 11 }a | + {1: 12 }^a | | ]] assert_alive()