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.
This commit is contained in:
Raphael 2023-12-14 16:08:00 +08:00 committed by GitHub
parent 36552adb39
commit 619407eb54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 91 additions and 13 deletions

View File

@ -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

View File

@ -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*

View File

@ -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

View File

@ -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

View File

@ -344,4 +344,5 @@ typedef struct {
typedef struct {
OptionalKeys is_set__open_term_;
LuaRef on_input;
Boolean force_crlf;
} Dict(open_term);

View File

@ -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);

View File

@ -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);

View File

@ -43,6 +43,7 @@
#include <vterm.h>
#include <vterm_keycodes.h>
#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);
}

View File

@ -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

View File

@ -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)

View File

@ -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()