Skip to content

Commit 4b75382

Browse files
committed
Always dispatch redraw events sequentially, one at a time
After converting a fork of neovim-gtk that I've been working on over to using nvim-rs, I noticed some rather strange issues with redrawing. In particular, initiating completion in cmdline mode would rarely end up resulting in the status bar being over-written by a line of completions, followed by a blank line (which should have contained the command being typed) all the way at the bottom of nvim's screen. _________________________________________ | | <- status line missing | foo/ bar/ baz/ biz/ boz/ bogz/ poggers/ | | fortnite/ memes/ runningoutofnames/ | |_|_______________________________________| <- should be :e After some investigation, I noticed that nvim-rs dispatches all of its requests asynchronously. This works fine for most things, with the particular exception of UI redraw requests. Because each UI redraw request assumes the state of the screen is equal to the result of the previous redraw request, the client needs to have the guarantee that the redraw requests coming in are always coming in the order they were originally sent out by nvim. Since we currently don't expose the msgid of each notification to our Handler, not maintaining that order means it's impossible for the Handler to ascertain the correct order in which to perform each redraw. So, let's fix this by implementing a queue for redraw requests, which we use to flush redraw notifications from Neovim sequentially to the client in parallel with all other RPC notifications.
1 parent 1c03688 commit 4b75382

File tree

1 file changed

+73
-3
lines changed

1 file changed

+73
-3
lines changed

src/neovim.rs

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! An active neovim session.
22
use std::{
3+
collections::VecDeque,
34
future::Future,
45
sync::{
56
atomic::{AtomicU64, Ordering},
@@ -44,6 +45,69 @@ type ResponseResult = Result<Result<Value, Value>, Arc<DecodeError>>;
4445

4546
type Queue = Arc<Mutex<Vec<(u64, oneshot::Sender<ResponseResult>)>>>;
4647

48+
/// The current state of redraw notifications. Redraws must be dispatched
49+
/// sequentially in the order that they were received, as each redraw
50+
/// notification assumes it starts with the result from the previous redraw. See
51+
/// `:help ui-events` in Neovim for more info.
52+
struct RedrawQueue {
53+
/// Whether there's a future already active that's flushing redraw
54+
/// notifications
55+
is_active: bool,
56+
/// The queue of pending redraw notifications
57+
queue: VecDeque<Vec<Value>>,
58+
}
59+
60+
impl RedrawQueue {
61+
fn new() -> Self {
62+
RedrawQueue {
63+
is_active: false,
64+
queue: VecDeque::<Vec<Value>>::new(),
65+
}
66+
}
67+
}
68+
69+
async fn queue_redraw<H, W>(
70+
queue: &Arc<Mutex<RedrawQueue>>,
71+
handler: H,
72+
nvim: Neovim<H::Writer>,
73+
params: Vec<Value>,
74+
)
75+
where
76+
W: AsyncWrite + Send + Unpin + 'static,
77+
H: Handler<Writer = W>
78+
{
79+
let mut guard = queue.lock().await;
80+
81+
guard.queue.push_front(params);
82+
if guard.is_active {
83+
return;
84+
}
85+
guard.is_active = true;
86+
drop(guard);
87+
88+
let queue = queue.clone();
89+
let handler_c = handler.clone();
90+
let neovim = nvim.clone();
91+
handler.spawn(async move {
92+
loop {
93+
let redraw = {
94+
let mut guard = queue.lock().await;
95+
96+
if let Some(redraw) = guard.queue.pop_back() {
97+
redraw
98+
} else {
99+
guard.is_active = false;
100+
return;
101+
}
102+
};
103+
104+
handler_c
105+
.handle_notify("redraw".into(), redraw, neovim.clone())
106+
.await;
107+
}
108+
});
109+
}
110+
47111
/// An active Neovim session.
48112
pub struct Neovim<W>
49113
where
@@ -190,6 +254,7 @@ where
190254
R: AsyncRead + Send + Unpin + 'static,
191255
{
192256
let mut rest: Vec<u8> = vec![];
257+
let ui_queue = Arc::new(Mutex::new(RedrawQueue::new()));
193258

194259
loop {
195260
let msg = match model::decode(&mut reader, &mut rest).await {
@@ -251,9 +316,14 @@ where
251316
RpcMessage::RpcNotification { method, params } => {
252317
let handler_c = handler.clone();
253318
let neovim = neovim.clone();
254-
handler.spawn(async move {
255-
handler_c.handle_notify(method, params, neovim).await
256-
});
319+
320+
if method == "redraw" {
321+
queue_redraw(&ui_queue, handler_c, neovim, params).await;
322+
} else {
323+
handler.spawn(async move {
324+
handler_c.handle_notify(method, params, neovim).await
325+
});
326+
}
257327
}
258328
};
259329
}

0 commit comments

Comments
 (0)