fix: harden neovim restart handling

This commit is contained in:
BreezeDelegate 2026-08-23 10:01:06 +00:00
parent ba8c41f423
commit c80d2c83e2
3 changed files with 62 additions and 23 deletions

View file

@ -28,6 +28,20 @@ pub type NeovimWriter = Box<dyn futures::AsyncWrite + Send + Unpin + 'static>;
type BoxedReader = Box<dyn AsyncRead + Send + Unpin + 'static>;
type BoxedWriter = Box<dyn AsyncWrite + Send + Unpin + 'static>;
#[cfg(any(target_os = "windows", test))]
fn normalize_windows_pipe_address(address: String) -> String {
const PIPE_PREFIX: &str = r"\\.\pipe\";
const FORWARD_SLASH_PIPE_PREFIX: &str = "//./pipe/";
if address.starts_with(PIPE_PREFIX) {
address
} else if let Some(name) = address.strip_prefix(FORWARD_SLASH_PIPE_PREFIX) {
format!("{PIPE_PREFIX}{name}")
} else {
format!("{PIPE_PREFIX}{address}")
}
}
pub struct NeovimSession {
pub neovim: Neovim<NeovimWriter>,
pub io_handle: JoinHandle<std::result::Result<(), Box<LoopError>>>,
@ -193,12 +207,7 @@ impl NeovimInstance {
#[cfg(windows)]
{
// Fixup the address if the pipe on windows does not start with \\.\pipe\.
let address = if address.starts_with("\\\\.\\pipe\\") {
address
} else {
format!("\\\\.\\pipe\\{address}")
};
let address = normalize_windows_pipe_address(address);
Ok(Self::split(
tokio::net::windows::named_pipe::ClientOptions::new().open(address)?,
))
@ -246,3 +255,27 @@ impl NeovimInstance {
(Box::new(reader), Box::new(writer))
}
}
#[cfg(test)]
mod tests {
use super::normalize_windows_pipe_address;
#[test]
fn preserves_canonical_windows_pipe_address() {
let address = r"\\.\pipe\nvim.123".to_string();
assert_eq!(normalize_windows_pipe_address(address.clone()), address);
}
#[test]
fn normalizes_neovim_forward_slash_pipe_address() {
assert_eq!(
normalize_windows_pipe_address("//./pipe/nvim.123".to_string()),
r"\\.\pipe\nvim.123"
);
}
#[test]
fn prefixes_bare_windows_pipe_name() {
assert_eq!(normalize_windows_pipe_address("nvim.123".to_string()), r"\\.\pipe\nvim.123");
}
}

View file

@ -861,12 +861,6 @@ impl ApplicationHandler<EventPayload> for Application {
return;
};
self.window_wrapper.queue_restart_route(route_id, details);
if let Some(window_id) = self.window_wrapper.window_id_for_route(route_id)
&& let Some(state) = self.render_states.get_mut(&window_id)
{
state.pending_draw_commands.clear();
state.should_render = ShouldRender::Immediately;
}
}
payload => {
self.window_wrapper.handle_user_event(EventPayload { payload, target });

View file

@ -1984,27 +1984,34 @@ impl WinitWindowWrapper {
}
pub fn queue_restart_route(&mut self, route_id: RouteId, details: RestartDetails) {
// The restart notification arrives before the old channel closes. Keep the current
// renderer untouched because Neovim may still abort the restart (for example with E37).
let grid_size = if let Some(window_id) = self.window_id_for_route(route_id) {
let grid_size = match self.routes.get(&window_id) {
match self.routes.get(&window_id) {
Some(route) => route.window.renderer.borrow().get_grid_size(),
None => return,
}
} else {
let Some(route_core) = self.route_cores.get(&route_id) else {
return;
};
route_core.renderer.borrow().get_grid_size()
};
self.pending_restart.insert(route_id, RestartRequest { details, grid_size });
}
fn prepare_route_for_restart(&mut self, route_id: RouteId) {
// This is called only after the old Neovim channel has actually closed.
if let Some(window_id) = self.window_id_for_route(route_id) {
self.clear_renderer(window_id);
if let Some(route) = self.routes.get_mut(&window_id) {
route.window.last_synced_grid_size = None;
}
grid_size
} else {
let Some(route_core) = self.route_cores.get_mut(&route_id) else {
return;
};
let grid_size = route_core.renderer.borrow().get_grid_size();
} else if let Some(route_core) = self.route_cores.get_mut(&route_id) {
route_core.renderer.borrow_mut().clear();
route_core.last_synced_grid_size = None;
grid_size
};
self.pending_restart.insert(route_id, RestartRequest { details, grid_size });
}
}
fn restart_neovim_route(
@ -2015,6 +2022,11 @@ impl WinitWindowWrapper {
) -> Result<(), ()> {
let handler = self.neovim_handler_for_route(route_id).ok_or(())?;
let cwd = self.route_cwd(route_id);
if self.runtime.is_none() {
return Err(());
}
self.prepare_route_for_restart(route_id);
let runtime = self.runtime.as_mut().ok_or(())?;
runtime