diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt index bce2f574ae..ffcfa72a19 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -66,6 +66,20 @@ Stop or detach the current UI Note: Not supported on Windows, currently. +------------------------------------------------------------------------------ +Restart the embedded Nvim server + + *:restart* +:restart + Detaches the embedded server from the UI and then restarts + it. This fails when changes have been made + and Vim refuses to |abandon| the current buffer. + + Note: This only works if the UI started the server initially. +:restart! + Force restarts the embedded server irrespective of unsaved + buffers. + ------------------------------------------------------------------------------ GUI commands diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 3d751f4571..38a4c20d1c 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -224,6 +224,8 @@ UI • Error messages are more concise: • "Error detected while processing:" changed to "Error in:". • "Error executing Lua:" changed to "Lua:". +• |:restart| detaches the embedded server from the UI and then restarts + it. VIMSCRIPT diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index c859f5f8ee..a465c423bf 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -177,3 +177,5 @@ void msg_history_clear(void) void error_exit(Integer status) FUNC_API_SINCE(12); +void restart(void) + FUNC_API_SINCE(14) FUNC_API_REMOTE_ONLY FUNC_API_CLIENT_IMPL; diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 34723b6102..deef0e3bdb 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -3368,6 +3368,12 @@ M.cmds = { addr_type = 'ADDR_LINES', func = 'ex_substitute', }, + { + command = 'restart', + flags = bit.bor(BANG, FILES, CMDARG, ARGOPT, TRLBAR, CMDWIN, LOCK_OK), + addr_type = 'ADDR_NONE', + func = 'ex_restart', + }, -- commands that start with an uppercase letter { command = 'Next', diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 0629357bdf..fdd5c6c128 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -69,6 +69,7 @@ #include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" +#include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" #include "nvim/normal.h" #include "nvim/normal_defs.h" @@ -101,6 +102,7 @@ #include "nvim/tag.h" #include "nvim/types_defs.h" #include "nvim/ui.h" +#include "nvim/ui_client.h" #include "nvim/undo.h" #include "nvim/undo_defs.h" #include "nvim/usercmd.h" @@ -5592,6 +5594,42 @@ static void ex_detach(exarg_T *eap) } } +/// ":restart" command +/// Restarts the server by delegating the work to the UI. +static void ex_restart(exarg_T *eap) +{ + bool forceit = eap && eap->forceit; + + // Refuse to restart if text is locked (i.e in command line etc.) + if (text_locked()) { + text_locked_msg(); + return; + } + + // Refuse to restart if buffer is locked. + if (curbuf_locked()) { + return; + } + + win_T *wp = curwin; + + // If any buffer is changed and not saved, we cannot restart. + // But if called using bang (!), we will force restart. + if ((!buf_hide(wp->w_buffer) + && check_changed(wp->w_buffer, (p_awa ? CCGD_AW : 0) + | (forceit ? CCGD_FORCEIT : 0) + | CCGD_EXCMD)) + || check_more(true, forceit) == FAIL + || check_changed_any(forceit, true)) { + if (!forceit) { + return; + } + } + + // Send an ui restart event. + ui_call_restart(); +} + /// ":mode": /// If no argument given, get the screen size and redraw. static void ex_mode(exarg_T *eap) diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c index 3970eb83de..9645e688a2 100644 --- a/src/nvim/ui_client.c +++ b/src/nvim/ui_client.c @@ -12,6 +12,7 @@ #include "nvim/channel.h" #include "nvim/channel_defs.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" #include "nvim/event/multiqueue.h" #include "nvim/globals.h" @@ -37,6 +38,10 @@ #endif static TUIData *tui = NULL; +static int tui_width = 0; +static int tui_height = 0; +static char *tui_term = ""; +static bool tui_rgb = false; static bool ui_client_is_remote = false; // uncrustify:off @@ -164,12 +169,8 @@ void ui_client_run(bool remote_ui) FUNC_ATTR_NORETURN { ui_client_is_remote = remote_ui; - int width, height; - char *term; - bool rgb; - tui_start(&tui, &width, &height, &term, &rgb); - - ui_client_attach(width, height, term, rgb); + tui_start(&tui, &tui_width, &tui_height, &tui_term, &tui_rgb); + ui_client_attach(tui_width, tui_height, tui_term, tui_rgb); // TODO(justinmk): this is for log_spec. Can remove this after nvim_log #7062 is merged. if (os_env_exists("__NVIM_TEST_LOG", true)) { @@ -284,6 +285,59 @@ void ui_client_event_raw_line(GridLineEvent *g) (const schar_T *)grid_line_buf_char, grid_line_buf_attr); } +/// Restarts the embedded server without killing the UI. +void ui_client_event_restart(Array args) +{ + // 1. Client-side server detach. + ui_client_detach(); + + // 2. Close ui client channel (auto kills the `nvim --embed` server due to self-exit). + const char *error; + bool success = channel_close(ui_client_channel_id, kChannelPartAll, &error); + if (!success) { + ELOG("%s", error); + return; + } + + // 3. Get v:argv. + typval_T *tv = get_vim_var_tv(VV_ARGV); + if (tv->v_type != VAR_LIST || tv->vval.v_list == NULL) { + ELOG("failed to get vim var typval"); + return; + } + list_T *l = tv->vval.v_list; + int argc = tv_list_len(l); + + // Assert to be positive for safe conversion to size_t. + assert(argc > 0); + + char **argv = xmalloc(sizeof(char *) * ((size_t)argc + 1)); + listitem_T *li = tv_list_first(l); + for (int i = 0; i < argc && li != NULL; i++, li = TV_LIST_ITEM_NEXT(l, li)) { + if (TV_LIST_ITEM_TV(li)->v_type == VAR_STRING && TV_LIST_ITEM_TV(li)->vval.v_string != NULL) { + argv[i] = TV_LIST_ITEM_TV(li)->vval.v_string; + } else { + argv[i] = ""; + } + } + argv[argc] = NULL; + + // 4. Start a new `nvim --embed` server. + uint64_t rv = ui_client_start_server(argc, argv); + if (!rv) { + ELOG("failed to start nvim server"); + goto cleanup; + } + + // 5. Client-side server re-attach. + ui_client_channel_id = rv; + ui_client_attach(tui_width, tui_height, tui_term, tui_rgb); + + ILOG("restarted server id=%" PRId64, rv); +cleanup: + xfree(argv); +} + #ifdef EXITFREE void ui_client_free_all_mem(void) { diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index cc0bc7cfad..db4e091984 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -164,6 +164,90 @@ if t.skip(is_os('win')) then return end +describe('TUI :restart', function() + before_each(function() + os.remove(testlog) + end) + teardown(function() + os.remove(testlog) + end) + + it('resets buffer to blank', function() + local server_super = n.clear() + local client_super = n.new_session(true) + local job_opts = { + env = { + NVIM_LOG_FILE = testlog, + }, + } + + finally(function() + server_super:close() + client_super:close() + end) + + local screen = tt.setup_child_nvim({ + '-u', + 'NONE', + '-i', + 'NONE', + '--cmd', + 'colorscheme vim', + '--cmd', + nvim_set .. ' notermguicolors laststatus=2 background=dark', + }, job_opts) + + -- Check ":restart" on an unmodified buffer. + tt.feed_data('\027\027:restart\013') + screen:expect { + grid = [[ + ^ | + {4:~ }|*3 + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]], + } + + tt.feed_data('ithis will be removed') + screen:expect { + grid = [[ + this will be removed^ | + {4:~ }|*3 + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]], + } + + -- Check ":restart" on a modified buffer. + tt.feed_data('\027\027:restart\013') + screen:expect { + grid = [[ + this will be removed | + {5: }| + {8:E37: No write since last change} | + {8:E162: No write since last change for buffer "[No N}| + {8:ame]"} | + {10:Press ENTER or type command to continue}^ | + {3:-- TERMINAL --} | + ]], + } + + -- Check ":restart!". + tt.feed_data('\027\027:restart!\013') + screen:expect { + grid = [[ + ^ | + {4:~ }|*3 + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]], + } + end) +end) + describe('TUI', function() local screen --[[@type test.functional.ui.screen]] local child_session --[[@type test.Session]]