diff --git a/src/bridge/mod.rs b/src/bridge/mod.rs index 427ede6f24..7af7e61f18 100644 --- a/src/bridge/mod.rs +++ b/src/bridge/mod.rs @@ -6,27 +6,42 @@ pub mod session; mod setup; mod ui_commands; -use std::{process::exit, sync::Arc, thread}; - use log::{error, info}; use nvim_rs::{error::CallError, Neovim, UiAttachOptions, Value}; +use std::{io::Error, process::exit}; +use tokio::{ + runtime::{Builder, Runtime}, + task::JoinHandle, +}; use crate::{ cmd_line::CmdLineSettings, error_handling::ResultPanicExplanation, running_tracker::*, settings::*, }; +use handler::NeovimHandler; +use session::{NeovimInstance, NeovimSession}; +use setup::setup_neovide_specific_state; pub use command::create_nvim_command; pub use events::*; -use handler::NeovimHandler; pub use session::NeovimWriter; -use session::{NeovimInstance, NeovimSession}; -use setup::setup_neovide_specific_state; pub use ui_commands::{start_ui_command_handler, ParallelCommand, SerialCommand, UiCommand}; const INTRO_MESSAGE_LUA: &str = include_str!("../../lua/intro.lua"); const NEOVIM_REQUIRED_VERSION: &str = "0.9.2"; +enum RuntimeState { + Idle, + Invalid, + Launched(NeovimSession), + Attached(JoinHandle<()>), +} + +pub struct NeovimRuntime { + runtime: Runtime, + state: RuntimeState, +} + fn neovim_instance() -> NeovimInstance { if let Some(address) = SETTINGS.get::().server { NeovimInstance::Server { address } @@ -35,14 +50,6 @@ fn neovim_instance() -> NeovimInstance { } } -pub fn start_bridge() { - // hoisted out of the actual thread so error messages while trying to find nvim can be printed before forking - let instance = neovim_instance(); - thread::spawn(|| { - start_neovim_runtime(instance); - }); -} - pub async fn setup_intro_message_autocommand( nvim: &Neovim, ) -> Result> { @@ -60,17 +67,16 @@ pub async fn show_intro_message( nvim.exec_lua(INTRO_MESSAGE_LUA, args).await } -#[tokio::main] -async fn start_neovim_runtime(instance: NeovimInstance) { +async fn launch() -> NeovimSession { + let neovim_instance = neovim_instance(); let handler = NeovimHandler::new(); - let session = NeovimSession::new(instance, handler) + let session = NeovimSession::new(neovim_instance, handler) .await .unwrap_or_explained_panic("Could not locate or start neovim process"); - let nvim = Arc::new(session.neovim); - // Check the neovim version to ensure its high enough - match nvim + match session + .neovim .command_output(&format!("echo has('nvim-{NEOVIM_REQUIRED_VERSION}')")) .await .as_deref() @@ -81,12 +87,19 @@ async fn start_neovim_runtime(instance: NeovimInstance) { exit(0); } } - let settings = SETTINGS.get::(); let should_handle_clipboard = settings.wsl || settings.server.is_some(); - setup_neovide_specific_state(&nvim, should_handle_clipboard).await; + setup_neovide_specific_state(&session.neovim, should_handle_clipboard).await; + start_ui_command_handler(session.neovim.clone()); + SETTINGS.read_initial_values(&session.neovim).await; + SETTINGS.setup_changed_listeners(&session.neovim).await; + session +} + +async fn run(session: NeovimSession) { + let settings = SETTINGS.get::(); let mut options = UiAttachOptions::new(); options.set_linegrid_external(true); options.set_multigrid_external(!settings.no_multi_grid); @@ -95,16 +108,14 @@ async fn start_neovim_runtime(instance: NeovimInstance) { // Triggers loading the user's config // Set to DEFAULT_WINDOW_GEOMETRY first, draw_frame will resize it later let geometry = DEFAULT_WINDOW_GEOMETRY; - nvim.ui_attach(geometry.width as i64, geometry.height as i64, &options) + session + .neovim + .ui_attach(geometry.width as i64, geometry.height as i64, &options) .await .unwrap_or_explained_panic("Could not attach ui to neovim process"); info!("Neovim process attached"); - start_ui_command_handler(nvim.clone()); - SETTINGS.read_initial_values(&nvim).await; - SETTINGS.setup_changed_listeners(&nvim).await; - match session.io_handle.await { Err(join_error) => error!("Error joining IO loop: '{}'", join_error), Ok(Err(error)) => { @@ -116,3 +127,38 @@ async fn start_neovim_runtime(instance: NeovimInstance) { }; RUNNING_TRACKER.quit("neovim processed failed"); } + +impl NeovimRuntime { + pub fn new() -> Result { + let runtime = Builder::new_multi_thread().enable_all().build()?; + + Ok(Self { + runtime, + state: RuntimeState::Idle, + }) + } + + pub fn launch(&mut self) { + assert!(matches!(self.state, RuntimeState::Idle)); + self.state = RuntimeState::Launched(self.runtime.block_on(launch())); + } + + pub fn attach(&mut self) { + assert!(matches!(self.state, RuntimeState::Launched(..))); + if let RuntimeState::Launched(session) = + std::mem::replace(&mut self.state, RuntimeState::Invalid) + { + self.state = RuntimeState::Attached(self.runtime.spawn(run(session))); + } + } +} + +impl Drop for NeovimRuntime { + fn drop(&mut self) { + if let RuntimeState::Attached(join_handle) = + std::mem::replace(&mut self.state, RuntimeState::Idle) + { + let _ = self.runtime.block_on(join_handle); + } + } +} diff --git a/src/bridge/session.rs b/src/bridge/session.rs index b298a18e40..917e6f2bf5 100644 --- a/src/bridge/session.rs +++ b/src/bridge/session.rs @@ -4,6 +4,7 @@ use std::{ io::{Error, ErrorKind, Result}, process::Stdio, + sync::Arc, }; use nvim_rs::{error::LoopError, neovim::Neovim, Handler}; @@ -22,7 +23,7 @@ type BoxedReader = Box; type BoxedWriter = Box; pub struct NeovimSession { - pub neovim: Neovim, + pub neovim: Arc>, pub io_handle: JoinHandle>>, } @@ -36,7 +37,10 @@ impl NeovimSession { Neovim::::new(reader.compat(), Box::new(writer.compat_write()), handler); let io_handle = spawn(io); - Ok(Self { neovim, io_handle }) + Ok(Self { + neovim: Arc::new(neovim), + io_handle, + }) } } diff --git a/src/main.rs b/src/main.rs index ac339360e8..46c448ac69 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,7 +41,7 @@ use flexi_logger::{Cleanup, Criterion, Duplicate, FileSpec, Logger, Naming}; use log::trace; use backtrace::Backtrace; -use bridge::start_bridge; +use bridge::NeovimRuntime; use cmd_line::CmdLineSettings; use editor::start_editor; use renderer::{cursor_renderer::CursorSettings, RendererSettings}; @@ -174,12 +174,15 @@ fn protected_main() -> ExitCode { CursorSettings::register(); KeyboardSettings::register(); - start_bridge(); - start_editor(); - maybe_disown(); - + let mut runtime = NeovimRuntime::new().unwrap(); + runtime.launch(); let event_loop = create_event_loop(); let window = create_window(&event_loop); + runtime.attach(); + + maybe_disown(); + start_editor(); + match main_loop(window, event_loop) { Ok(()) => 0, // All error codes have to be u8, so just do a direct cast with wrap around, even if the value is negative,