From a285a0b36daab3657587a2961ea38e93b97715e3 Mon Sep 17 00:00:00 2001 From: t0muxx <30345035+t0muxx@users.noreply.github.com> Date: Wed, 14 Jun 2023 09:39:14 +0000 Subject: [PATCH 1/4] fix(tui): v:argv does not report the original argv[0] --- src/nvim/ui_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c index 3a18dfac4e..60b8f4f2ca 100644 --- a/src/nvim/ui_client.c +++ b/src/nvim/ui_client.c @@ -43,7 +43,7 @@ uint64_t ui_client_start_server(int argc, char **argv) varnumber_T exit_status; char **args = xmalloc(((size_t)(2 + argc)) * sizeof(char *)); int args_idx = 0; - args[args_idx++] = xstrdup(get_vim_var_str(VV_PROGPATH)); + args[args_idx++] = xstrdup(argv[0]); args[args_idx++] = xstrdup("--embed"); for (int i = 1; i < argc; i++) { args[args_idx++] = xstrdup(argv[i]); From 40a7228dccd7cecb601661caaaea3a7efe1d1590 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 8 Jul 2023 08:27:39 +0800 Subject: [PATCH 2/4] fix(startup): run embedded Nvim with real path --- src/nvim/channel.c | 13 ++++++++----- src/nvim/eval/funcs.c | 6 +++--- src/nvim/event/libuv_process.c | 2 +- src/nvim/event/process.c | 2 +- src/nvim/event/process.h | 8 ++++++++ src/nvim/os/pty_process_unix.c | 2 +- src/nvim/ui_client.c | 4 ++-- 7 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 820ce534e1..7ccc464056 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -309,6 +309,7 @@ static void close_cb(Stream *stream, void *data) /// /// @param[in] argv Arguments vector specifying the command to run, /// NULL-terminated +/// @param[in] exepath The path to the executable. If NULL, use `argv[0]`. /// @param[in] on_stdout Callback to read the job's stdout /// @param[in] on_stderr Callback to read the job's stderr /// @param[in] on_exit Callback to receive the job's exit status @@ -330,10 +331,11 @@ static void close_cb(Stream *stream, void *data) /// < 0 if the job can't start /// /// @returns [allocated] channel -Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader on_stderr, - Callback on_exit, bool pty, bool rpc, bool overlapped, bool detach, - ChannelStdinMode stdin_mode, const char *cwd, uint16_t pty_width, - uint16_t pty_height, dict_T *env, varnumber_T *status_out) +Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_stdout, + CallbackReader on_stderr, Callback on_exit, bool pty, bool rpc, + bool overlapped, bool detach, ChannelStdinMode stdin_mode, + const char *cwd, uint16_t pty_width, uint16_t pty_height, dict_T *env, + varnumber_T *status_out) { Channel *chan = channel_alloc(kChannelStreamProc); chan->on_data = on_stdout; @@ -364,6 +366,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader Process *proc = &chan->stream.proc; proc->argv = argv; + proc->exepath = exepath; proc->cb = channel_process_exit_cb; proc->events = chan->events; proc->detach = detach; @@ -371,7 +374,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader proc->env = env; proc->overlapped = overlapped; - char *cmd = xstrdup(proc->argv[0]); + char *cmd = xstrdup(process_get_exepath(proc)); bool has_out, has_err; if (proc->type == kProcessTypePty) { has_out = true; diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 3f6a876838..3a5f6efe34 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -4200,7 +4200,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) env = create_environment(job_env, clear_env, pty, term_name); - Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, + Channel *chan = channel_job_start(argv, NULL, on_stdout, on_stderr, on_exit, pty, rpc, overlapped, detach, stdin_mode, cwd, width, height, env, &rettv->vval.v_number); if (chan) { @@ -6729,7 +6729,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) // The last item of argv must be NULL argv[i] = NULL; - Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT, + Channel *chan = channel_job_start(argv, NULL, CALLBACK_READER_INIT, CALLBACK_READER_INIT, CALLBACK_NONE, false, true, false, false, kChannelStdinPipe, NULL, 0, 0, NULL, @@ -8825,7 +8825,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) const bool detach = false; ChannelStdinMode stdin_mode = kChannelStdinPipe; uint16_t term_width = (uint16_t)MAX(0, curwin->w_width_inner - win_col_off(curwin)); - Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, + Channel *chan = channel_job_start(argv, NULL, on_stdout, on_stderr, on_exit, pty, rpc, overlapped, detach, stdin_mode, cwd, term_width, (uint16_t)curwin->w_height_inner, env, &rettv->vval.v_number); diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index e528d21a71..1bb0511f12 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -24,7 +24,7 @@ int libuv_process_spawn(LibuvProcess *uvproc) FUNC_ATTR_NONNULL_ALL { Process *proc = (Process *)uvproc; - uvproc->uvopts.file = proc->argv[0]; + uvproc->uvopts.file = process_get_exepath(proc); uvproc->uvopts.args = proc->argv; uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE; #ifdef MSWIN diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 1219566e9b..95bf4d1c3b 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -131,7 +131,7 @@ int process_spawn(Process *proc, bool in, bool out, bool err) proc->internal_close_cb = decref; proc->refcount++; kl_push(WatcherPtr, proc->loop->children, proc); - DLOG("new: pid=%d argv=[%s]", proc->pid, proc->argv[0]); + DLOG("new: pid=%d exepath=[%s]", proc->pid, process_get_exepath(proc)); return 0; } diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h index e0057faffb..76dc2710b6 100644 --- a/src/nvim/event/process.h +++ b/src/nvim/event/process.h @@ -33,6 +33,7 @@ struct process { uint64_t stopped_time; // process_stop() timestamp const char *cwd; char **argv; + const char *exepath; dict_T *env; Stream in, out, err; /// Exit handler. If set, user must call process_free(). @@ -55,6 +56,7 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data) .stopped_time = 0, .cwd = NULL, .argv = NULL, + .exepath = NULL, .in = { .closed = false }, .out = { .closed = false }, .err = { .closed = false }, @@ -67,6 +69,12 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data) }; } +/// Get the path to the executable of the process. +static inline const char *process_get_exepath(Process *proc) +{ + return proc->exepath != NULL ? proc->exepath : proc->argv[0]; +} + static inline bool process_is_stopped(Process *proc) { bool exited = (proc->status >= 0); diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 2413f0339b..4e85012936 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -286,7 +286,7 @@ static void init_child(PtyProcess *ptyproc) return; } - char *prog = ptyproc->process.argv[0]; + const char *prog = process_get_exepath(proc); assert(proc->env); environ = tv_dict_to_env(proc->env); diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c index 60b8f4f2ca..3bd6a5554c 100644 --- a/src/nvim/ui_client.c +++ b/src/nvim/ui_client.c @@ -53,8 +53,8 @@ uint64_t ui_client_start_server(int argc, char **argv) CallbackReader on_err = CALLBACK_READER_INIT; on_err.fwd_err = true; - Channel *channel = channel_job_start(args, CALLBACK_READER_INIT, - on_err, CALLBACK_NONE, + Channel *channel = channel_job_start(args, get_vim_var_str(VV_PROGPATH), + CALLBACK_READER_INIT, on_err, CALLBACK_NONE, false, true, true, false, kChannelStdinPipe, NULL, 0, 0, NULL, &exit_status); From 3c5339ba367c12d90402c80a9ed19870fc17bdc9 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 8 Jul 2023 18:27:53 +0800 Subject: [PATCH 3/4] test(tui): add test for overriding argv[0] --- test/functional/terminal/tui_spec.lua | 43 +++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index c529f900be..3b9cf24875 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -29,6 +29,7 @@ local spawn_argv = helpers.spawn_argv local set_session = helpers.set_session local feed = helpers.feed local eval = helpers.eval +local write_file = helpers.write_file if helpers.skip(helpers.is_os('win')) then return end @@ -1789,9 +1790,6 @@ end) describe('TUI', function() before_each(clear) - after_each(function() - os.remove('testF') - end) it('resize at startup #17285 #15044 #11330', function() local screen = Screen.new(50, 10) @@ -1822,7 +1820,46 @@ describe('TUI', function() ]]) end) + it('argv[0] can be overridden #23953', function() + if not exec_lua('return pcall(require, "ffi")') then + pending('missing LuaJIT FFI') + end + local script_file = 'Xargv0.lua' + write_file(script_file, [=[ + local ffi = require('ffi') + ffi.cdef([[int execl(const char *, const char *, ...);]]) + ffi.C.execl(vim.v.progpath, 'Xargv0nvim', '--clean') + ]=]) + finally(function() + os.remove(script_file) + end) + local screen = thelpers.screen_setup(0, string.format([=[["%s", "--clean", "-l", "%s"]]=], + nvim_prog, script_file)) + screen:expect{grid=[[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] 0,0-1 All}| + | + {3:-- TERMINAL --} | + ]]} + feed_data(':put =v:argv + [v:progname]\n') + screen:expect{grid=[[ + Xargv0nvim | + --embed | + --clean | + {1:X}argv0nvim | + {5:[No Name] [+] 5,1 Bot}| + 4 more lines | + {3:-- TERMINAL --} | + ]]} + end) + it('with non-tty (pipe) stdout/stderr', function() + finally(function() + os.remove('testF') + end) local screen = thelpers.screen_setup(0, '"'..nvim_prog ..' -u NONE -i NONE --cmd \'set noswapfile noshowcmd noruler\' --cmd \'normal iabc\' > /dev/null 2>&1 && cat testF && rm testF"') feed_data(':w testF\n:q\n') From f54806fd7f4c0bae273ffcf8a4598a2d872e5a9e Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 2 Sep 2023 18:50:12 +0800 Subject: [PATCH 4/4] fix(ui): avoid ambiguity about chunk that clears part of line Co-authored-by: bfredl --- src/nvim/api/ui.c | 6 ++++- test/functional/terminal/tui_spec.lua | 34 ++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 0488a9cf07..5b1c49dc1a 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -829,6 +829,7 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int size_t ncells = (size_t)(endcol - startcol); int last_hl = -1; uint32_t nelem = 0; + bool was_space = false; for (size_t i = 0; i < ncells; i++) { repeat++; if (i == ncells - 1 || attrs[i] != attrs[i + 1] @@ -867,9 +868,12 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int data->ncells_pending += MIN(repeat, 2); last_hl = attrs[i]; repeat = 0; + was_space = strequal(chunk[i], " "); } } - if (endcol < clearcol) { + // If the last chunk was all spaces, add a clearing chunk even if there are + // no more cells to clear, so there is no ambiguity about what to clear. + if (endcol < clearcol || was_space) { nelem++; data->ncells_pending += 1; mpack_array(buf, 3); diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 3b9cf24875..ba0327379d 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -27,8 +27,6 @@ local is_os = helpers.is_os local new_pipename = helpers.new_pipename local spawn_argv = helpers.spawn_argv local set_session = helpers.set_session -local feed = helpers.feed -local eval = helpers.eval local write_file = helpers.write_file if helpers.skip(helpers.is_os('win')) then return end @@ -898,7 +896,7 @@ describe('TUI', function() feed_data('\022\027[107;33u') -- Meta + k feed_data('\022\027[13;41u') -- Super + Meta + Enter feed_data('\022\027[127;48u') -- Shift + Alt + Ctrl + Super + Meta + Backspace - feed('\n') + feed_data('\n') feed_data('\022\027[57376;9u') -- Super + F13 feed_data('\022\027[57377;33u') -- Meta + F14 feed_data('\022\027[57378;41u') -- Super + Meta + F15 @@ -1752,7 +1750,7 @@ describe('TUI', function() | {5:-- TERMINAL --} | ]]) - feed('i') + feed_data('i') screen:expect([[ {1: } | {2:~}{3: }| @@ -1898,6 +1896,30 @@ describe('TUI', function() {3:-- TERMINAL --} | ]]) end) + + it('draws line with many trailing spaces correctly #24955', function() + local screen = thelpers.screen_setup(0, '["'..nvim_prog..[[", "-u", "NONE", "-i", "NONE"]] + ..[[, "--cmd", "call setline(1, ['1st line' .. repeat(' ', 153), '2nd line'])"]]..']', 80) + screen:expect{grid=[[ + {1:1}st line | + | + | + 2nd line | + {5:[No Name] [+] 1,1 All}| + | + {3:-- TERMINAL --} | + ]]} + feed_data('$') + screen:expect{grid=[[ + 1st line | + | + {1: } | + 2nd line | + {5:[No Name] [+] 1,161 All}| + | + {3:-- TERMINAL --} | + ]]} + end) end) describe('TUI UIEnter/UILeave', function() @@ -2703,8 +2725,8 @@ describe("TUI as a client", function() local client_super = spawn_argv(true) set_session(server) - local server_pipe = eval'v:servername' - feed'iHalloj!' + local server_pipe = meths.get_vvar('servername') + server:request('nvim_input', 'iHalloj!') set_session(client_super) local screen = thelpers.screen_setup(0,