neovim
neovim copied to clipboard
Neovim emits an ill-formed redraw event with Neovide and noice.nvim
Describe the bug
Related to https://github.com/folke/noice.nvim/issues/17, https://github.com/neovide/neovide/issues/1751, and maybe #21265 Neovide version (patched for debugging): https://github.com/ryo33/neovide/commit/db6ba6d783e098614521b3b0e7299fcc22209a50 Noice.nvim version: https://github.com/folke/noice.nvim/commit/d8a1f3056ad713b5d471048f8d029264828e22c0 nvim-notify version: https://github.com/rcarriga/nvim-notify/commit/bdd647f61a05c9b8a57c83b78341a0690e9c29d7 nui.nvim version: https://github.com/MunifTanjim/nui.nvim/commit/d147222a1300901656f3ebd5b95f91732785a329
Abstract
Using Neovide GUI with noice.nvim suddenly crashes occasionally. I've found that, in most cases, it's caused by Neovim emitting an Ill-formed redraw event and subsequent rpc messages are broken (see Appendix 2.) Some people report the sudden crash in the same case, and one reported the almost same backtrace as mine.
The event from neovide_backtrace.log
with printing in ron format at: (File: src/bridge/handler.rs; Line: 77, Column: 25)
[
"msg_show",
[
[],
[
true,
],
[
"msg_showcmd",
[
[],
],
],
],
]
The situation
- Neovide call
ui_attach
withext_linegrid
andrgb
. - noice.nvim call
ui_attach
withext_messages
,ext_cmdline
, andext_popupmenu
. - noice.nvim sometimes trigger redraw while handling events.
- https://github.com/neovide/neovide/issues/1751#issuecomment-1435915499
- https://github.com/neovide/neovide/issues/1751#issuecomment-1436498838
Appendix 1.
Starting Neovim with nvim -u NONE
and running the following lua code without --
in front of print("hello")
crashes neovim silently. This might be related to this issue. If someone tells me that this is an unexpected behavior, I'll open another issue.
local first = true
local ns = vim.api.nvim_create_namespace "test"
vim.ui_attach(ns, {
ext_messages = true,
}, function(event, kind, content, replace_last)
if first then
-- print("hello")
first = false
end
end)
Edited: I noticed there is #21265
Appendix 2.
To ensure that the MessagePack and RPC implementation Neovide uses isn't doing wrong, I write the following Python script:
import sys
import pprint
from pynvim import attach
path = sys.argv[1]
nvim = attach("socket", path = path)
nvim.ui_attach(1000, 1000, True, ext_linegrid = True)
pp = pprint.PrettyPrinter(indent=2, width=40)
def on_notify(name, args):
if name != "redraw":
return
str = pp.pformat(args)
if "'msg_show'" not in str:
return
print(str)
def on_request(name, args):
pass
nvim.run_loop(on_request, on_notify)
It prints:
Click me
[ ['cmdline_hide', [1]],
[ 'msg_show',
[ 'emsg',
[[4, 'E32: No file name']],
False]],
['mode_change', ['normal', 0]],
['mouse_on', []],
['flush', []]]
[ [ 'win_viewport',
[ 2,
<Window(handle=1000)>,
0,
10,
1,
0,
6]],
[ 'grid_line',
[1, 2, 0, [[' ', 0]]],
[1, 3, 0, [[' ', 0]]],
[1, 5, 0, [['a', 0]]],
[1, 6, 0, [['~', 7], [' ', 7, 71]]],
[1, 7, 0, [['~', 7], [' ', 7, 71]]],
[1, 8, 0, [['~', 7], [' ', 7, 71]]],
[1, 26, 54, [['2', 9]]]],
[ 'hl_attr_define',
[ 383,
{ 'background': 16711935,
'foreground': 11140968},
{ 'background': 13,
'foreground': 0},
[]],
[ 384,
{ 'background': 16711935,
'foreground': 5203794},
{ 'background': 13,
'foreground': 0},
[]]],
[ 'grid_line',
[ 1,
0,
20,
[ ['╭', 65],
['─', 65, 50],
['╮']]],
[1, 1, 20, [['│', 65]]],
[1, 1, 71, [['│', 65]]],
[1, 2, 20, [['│', 65]]],
[1, 2, 71, [['│', 65]]],
[1, 3, 20, [['│', 65]]],
[1, 3, 71, [['│', 65]]],
[ 1,
4,
20,
[ ['╰', 65],
['─', 65, 50],
['╯']]],
[ 1,
1,
21,
[ [' ', 41],
['\uf057', 343],
[' '],
['E'],
['r', 343, 2],
['o'],
['r'],
[' ', 343, 5],
[' ', 41, 31],
['2', 343, 2],
[':'],
['4'],
['3'],
[' ', 41]]],
[1, 2, 21, [['━', 342, 50]]],
[ 1,
3,
21,
[ ['E', 382],
['3'],
['2'],
[':'],
[' '],
['N'],
['o'],
[' '],
['f'],
['i'],
['l'],
['e'],
[' '],
['n'],
['a'],
['m'],
['e'],
[' ', 41, 33]]]],
[ 'msg_show',
['', [[0, '3 fewer lines']], True]],
['msg_showmode', [[]]],
[ 'win_viewport',
[ 2,
<Window(handle=1000)>,
0,
7,
1,
0,
6]],
[ 'grid_line',
[ 1,
5,
20,
[ ['╭', 67],
['─', 67, 50],
['╮']]],
[ 1,
6,
20,
[ ['│', 67],
[' ', 41],
['\uf05a', 383],
[' '],
['M'],
['e'],
['s', 383, 2],
['a'],
['g'],
['e'],
['s'],
[' ', 41, 33],
['2', 383, 2],
[':'],
['4'],
['3'],
[' ', 41],
['│', 67]]],
[ 1,
7,
20,
[ ['│', 67],
['━', 384, 50],
['│', 67]]],
[ 1,
8,
20,
[ ['│', 67],
['3', 41],
[' '],
['f'],
['e'],
['w'],
['e'],
['r'],
[' '],
['l'],
['i'],
['n'],
['e'],
['s'],
[' ', 41, 37],
['│', 67]]],
[ 1,
9,
20,
[ ['╰', 67],
['─', 67, 50],
['╯']]]],
['grid_cursor_goto', [1, 26, 55]],
['flush', []]]
[ [ 'msg_show',
[ [],
[True],
['msg_showcmd', [[]]]]],
['grid_cursor_goto', [1, 1, 0]],
['mode_change', ['normal', 0]],
['flush', []],
[ 2,
'redraw',
[ [ 'grid_line',
[ 1,
5,
20,
[ ['╭', 67],
['─', 67, 50],
['╮']]],
[1, 6, 20, [['│', 67]]],
[1, 6, 71, [['│', 67]]],
[1, 7, 20, [['│', 67]]],
[1, 7, 71, [['│', 67]]],
[1, 8, 20, [['│', 67]]],
[1, 8, 71, [['│', 67]]],
[ 1,
9,
20,
[ ['╰', 67],
['─', 67, 50],
['╯']]],
[ 1,
6,
21,
[ [' ', 41],
['\uf05a', 383],
[' '],
['M'],
['e'],
['s', 383, 2],
['a'],
['g'],
['e'],
['s'],
[' ', 41, 33],
['2', 383, 2],
[':'],
['4'],
['3'],
[' ', 41]]],
[1, 7, 21, [['━', 384, 50]]],
[ 1,
8,
21,
[ ['3', 41],
[' '],
['f'],
['e'],
['w'],
['e'],
['r'],
[' '],
['l'],
['i'],
['n'],
['e'],
['s'],
[' ', 41, 37]]],
[ 1,
0,
20,
[ ['╭', 65],
['─', 65, 50],
['╮']]],
[1, 1, 20, [['│', 65]]],
[1, 1, 71, [['│', 65]]],
[1, 2, 20, [['│', 65]]],
[1, 2, 71, [['│', 65]]],
[1, 3, 20, [['│', 65]]],
[1, 3, 71, [['│', 65]]],
[ 1,
4,
20,
[ ['╰', 65],
['─', 65, 50],
['╯']]],
[ 1,
1,
21,
[ [' ', 41],
['\uf057', 343],
[' '],
['E'],
['r', 343, 2],
['o'],
['r'],
[' ', 343, 5],
[' ', 41, 31],
['2', 343, 2],
[':'],
['4'],
['3'],
[' ', 41]]],
[1, 2, 21, [['━', 342, 50]]],
[ 1,
3,
21,
[ ['E', 382],
['3'],
['2'],
[':'],
[' '],
['N'],
['o'],
[' '],
['f'],
['i'],
['l'],
['e'],
[' '],
['n'],
['a'],
['m'],
['e'],
[' ', 41, 33]]]],
['msg_showmode', [[]]],
[ 'win_viewport',
[ 2,
<Window(handle=1000)>,
0,
7,
1,
0,
6]],
['flush', []]]]]
Steps to reproduce
neovide -- -u NONE :packadd nvim-notify :packadd nui.nvim :packadd noice.nvim :lua require("noice").setup {} " 1. input several lines " 2. type 'd2k' in normal mode " repeat 1 and 2 a few times
Expected behavior
Neovim emits a well-formed redraw event.
Neovim version (nvim -v)
NVIM v0.9.0-dev-1004+gbfe6b4944-dirty
Vim (not Nvim) behaves the same?
no
Operating system/version
Ubuntu 22.04.1 LTS
Terminal name/version
GNOME Terminal Version 3.44.0 for GNOME 42
$TERM environment variable
xterm-256color
Installation
homebrew (linuxbrew)
It might be also weird that Neovide receives that “msg_show” although Neovide does not subscribe “ext_messages”.
I did some research on this and found that in ui_call_event
msg_show args are valid before calling lua callbacks and become corrupt after. Then UI_CALL
is called with bad args and we see ill-formed events later.
Bad args vs good args:
ui_call_event end: msg_show
// args after lua callbacks
arr[0]: 5
empty array
arr[1]: 5
array @ 0x16b4fcac0
arr[0]: 1775635200
arr[2]: 1
array @ 0x16b4fcbe0
// original args
arr[0]: 4
str: confirm
arr[1]: 5
array @ 0x16b4fcac0
arr[0]: 5
array @ 0x16b4fc9a0
arr[0]: 2
arr[1]: 4
str:
Swap file "~/.local/state/nvim/swap//%Users%censored%/sample.lua.swp" already exists!
[O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it, (Q)uit, (A)bort:
arr[2]: 1
Here's a diff I used:
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 45959b7b6..77501ad46 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -656,10 +656,39 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *err)
}
}
+static void dump_array(Array args, int depth) {
+ char buf[128] = {};
+ for (int i = 0; i < depth; i++) {
+ strcat(buf, " ");
+ }
+ if (args.size == 0) {
+ printf("%s empty array\n", buf);
+ return;
+ }
+ for(size_t i = 0; i < args.size; i++) {
+ printf("%s arr[%d]: %d\n", buf, i, args.items[i].type);
+ if (args.items[i].type == kObjectTypeString) {
+ printf("%s str: %s\n", buf, args.items[i].data.string);
+ }
+ if (args.items[i].type == kObjectTypeArray) {
+ dump_array(args.items[i].data.array, depth + 1);
+ }
+ }
+}
+
void ui_call_event(char *name, Array args)
{
UIEventCallback *event_cb;
bool handled = false;
+
+ printf("ui_call_event start: %s\n", name);
+
+ if (strcmp("msg_show", name) == 0) {
+ dump_array(args, 0);
+ }
+
+ Array orig_args = copy_array(args, NULL);
+
map_foreach_value(&ui_event_cbs, event_cb, {
Error err = ERROR_INIT;
Object res = nlua_call_ref(event_cb->cb, name, args, false, &err);
@@ -672,8 +701,14 @@ void ui_call_event(char *name, Array args)
api_clear_error(&err);
})
+ printf("ui_call_event end: %s\n", name);
+
+ if (strcmp("msg_show", name) == 0) {
+ dump_array(args, 0);
+ dump_array(orig_args, 0);
+ }
if (!handled) {
- UI_CALL(true, event, ui, name, args);
+ UI_CALL(true, event, ui, name, orig_args);
}
ui_log(name);
It seems like what happens is:
1.ui_call_event("msg_show", args = { size: 3, items: 0xWHATEVER })
is called, where 0xWHATEVER is the addr of static call_buf.items
2.it enters lua callbacks loop
3.vim command redraw
is called from a lua callback
4.msg_ext_flush_showmode
is called from updatescreen
or w/e
5. ui_call_msg_showmode
reuses the same call_buf
for its argument, so call_buf.items[0] becomes empty array
7. we return to ui_call_event("msg_show", args = { size: 3, items: ...})
, where items[0] is an empty array, not a string, which was expected
8. UI_CALL
is called with wrong arguments
I made a dirty hack which works around the problem for me. Not sure if it would make sense to make a PR, but if anyone is looking for a solution here, it may (or may not) work.
3.vim command redraw is called from a lua callback
This is the culprit. we need to make this strictly verboten and fix the use cases that use that some other way.
It seems that from the ui_attach
lua callback, I can control whether the attached GUI also receives the ext event.
I'm further doing some tests, but it seems I can make it work so Neovide no longer crahses, even with --multigrid
enabled
@folke on the latest noice version, with --multigrid
option on neovide im still getting an error:
You're using a GUI that uses ext_multigrid. Noice can't work when the GUI has ext_multigrid enabled.
did i mess smth up or is that expected with multigrid