mirror of
https://github.com/neovim/neovim.git
synced 2025-10-06 18:06:30 +00:00
feat(editor): ":restart" command #33953
Problem: Developing/troubleshooting plugins has friction because "restarting" Nvim requires quitting and manually starting again. #32484 Solution: - Implement a `:restart` command which emits `restart` UI event. - Handle the `restart` UI event in the builtin TUI client: stop the `nvim --embed` server, start a new one, and attach to it.
This commit is contained in:
@@ -66,6 +66,20 @@ Stop or detach the current UI
|
|||||||
|
|
||||||
Note: Not supported on Windows, currently.
|
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
|
GUI commands
|
||||||
|
|
||||||
|
@@ -224,6 +224,8 @@ UI
|
|||||||
• Error messages are more concise:
|
• Error messages are more concise:
|
||||||
• "Error detected while processing:" changed to "Error in:".
|
• "Error detected while processing:" changed to "Error in:".
|
||||||
• "Error executing Lua:" changed to "Lua:".
|
• "Error executing Lua:" changed to "Lua:".
|
||||||
|
• |:restart| detaches the embedded server from the UI and then restarts
|
||||||
|
it.
|
||||||
|
|
||||||
VIMSCRIPT
|
VIMSCRIPT
|
||||||
|
|
||||||
|
@@ -177,3 +177,5 @@ void msg_history_clear(void)
|
|||||||
|
|
||||||
void error_exit(Integer status)
|
void error_exit(Integer status)
|
||||||
FUNC_API_SINCE(12);
|
FUNC_API_SINCE(12);
|
||||||
|
void restart(void)
|
||||||
|
FUNC_API_SINCE(14) FUNC_API_REMOTE_ONLY FUNC_API_CLIENT_IMPL;
|
||||||
|
@@ -3368,6 +3368,12 @@ M.cmds = {
|
|||||||
addr_type = 'ADDR_LINES',
|
addr_type = 'ADDR_LINES',
|
||||||
func = 'ex_substitute',
|
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
|
-- commands that start with an uppercase letter
|
||||||
{
|
{
|
||||||
command = 'Next',
|
command = 'Next',
|
||||||
|
@@ -69,6 +69,7 @@
|
|||||||
#include "nvim/message.h"
|
#include "nvim/message.h"
|
||||||
#include "nvim/mouse.h"
|
#include "nvim/mouse.h"
|
||||||
#include "nvim/move.h"
|
#include "nvim/move.h"
|
||||||
|
#include "nvim/msgpack_rpc/channel.h"
|
||||||
#include "nvim/msgpack_rpc/server.h"
|
#include "nvim/msgpack_rpc/server.h"
|
||||||
#include "nvim/normal.h"
|
#include "nvim/normal.h"
|
||||||
#include "nvim/normal_defs.h"
|
#include "nvim/normal_defs.h"
|
||||||
@@ -101,6 +102,7 @@
|
|||||||
#include "nvim/tag.h"
|
#include "nvim/tag.h"
|
||||||
#include "nvim/types_defs.h"
|
#include "nvim/types_defs.h"
|
||||||
#include "nvim/ui.h"
|
#include "nvim/ui.h"
|
||||||
|
#include "nvim/ui_client.h"
|
||||||
#include "nvim/undo.h"
|
#include "nvim/undo.h"
|
||||||
#include "nvim/undo_defs.h"
|
#include "nvim/undo_defs.h"
|
||||||
#include "nvim/usercmd.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":
|
/// ":mode":
|
||||||
/// If no argument given, get the screen size and redraw.
|
/// If no argument given, get the screen size and redraw.
|
||||||
static void ex_mode(exarg_T *eap)
|
static void ex_mode(exarg_T *eap)
|
||||||
|
@@ -12,6 +12,7 @@
|
|||||||
#include "nvim/channel.h"
|
#include "nvim/channel.h"
|
||||||
#include "nvim/channel_defs.h"
|
#include "nvim/channel_defs.h"
|
||||||
#include "nvim/eval.h"
|
#include "nvim/eval.h"
|
||||||
|
#include "nvim/eval/typval.h"
|
||||||
#include "nvim/eval/typval_defs.h"
|
#include "nvim/eval/typval_defs.h"
|
||||||
#include "nvim/event/multiqueue.h"
|
#include "nvim/event/multiqueue.h"
|
||||||
#include "nvim/globals.h"
|
#include "nvim/globals.h"
|
||||||
@@ -37,6 +38,10 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static TUIData *tui = NULL;
|
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;
|
static bool ui_client_is_remote = false;
|
||||||
|
|
||||||
// uncrustify:off
|
// uncrustify:off
|
||||||
@@ -164,12 +169,8 @@ void ui_client_run(bool remote_ui)
|
|||||||
FUNC_ATTR_NORETURN
|
FUNC_ATTR_NORETURN
|
||||||
{
|
{
|
||||||
ui_client_is_remote = remote_ui;
|
ui_client_is_remote = remote_ui;
|
||||||
int width, height;
|
tui_start(&tui, &tui_width, &tui_height, &tui_term, &tui_rgb);
|
||||||
char *term;
|
ui_client_attach(tui_width, tui_height, tui_term, tui_rgb);
|
||||||
bool rgb;
|
|
||||||
tui_start(&tui, &width, &height, &term, &rgb);
|
|
||||||
|
|
||||||
ui_client_attach(width, height, term, rgb);
|
|
||||||
|
|
||||||
// TODO(justinmk): this is for log_spec. Can remove this after nvim_log #7062 is merged.
|
// TODO(justinmk): this is for log_spec. Can remove this after nvim_log #7062 is merged.
|
||||||
if (os_env_exists("__NVIM_TEST_LOG", true)) {
|
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);
|
(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
|
#ifdef EXITFREE
|
||||||
void ui_client_free_all_mem(void)
|
void ui_client_free_all_mem(void)
|
||||||
{
|
{
|
||||||
|
@@ -164,6 +164,90 @@ if t.skip(is_os('win')) then
|
|||||||
return
|
return
|
||||||
end
|
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()
|
describe('TUI', function()
|
||||||
local screen --[[@type test.functional.ui.screen]]
|
local screen --[[@type test.functional.ui.screen]]
|
||||||
local child_session --[[@type test.Session]]
|
local child_session --[[@type test.Session]]
|
||||||
|
Reference in New Issue
Block a user