diff --git a/src/bridge/handler.rs b/src/bridge/handler.rs index da3eb13..7f8abac 100644 --- a/src/bridge/handler.rs +++ b/src/bridge/handler.rs @@ -10,6 +10,7 @@ use crate::{ editor::EditorCommand, error_handling::ResultPanicExplanation, event_aggregator::EVENT_AGGREGATOR, + running_tracker::*, settings::SETTINGS, }; @@ -48,6 +49,12 @@ impl Handler for NeovimHandler { "setting_changed" => { SETTINGS.handle_changed_notification(arguments); } + "neovide.quit" => { + let error_code = arguments[0] + .as_i64() + .expect("Could not parse error code from neovim"); + RUNNING_TRACKER.quit_with_code(error_code as i32, "Quit from neovim"); + } #[cfg(windows)] "neovide.register_right_click" => { EVENT_AGGREGATOR.send(UiCommand::Parallel(ParallelCommand::RegisterRightClick)); diff --git a/src/bridge/mod.rs b/src/bridge/mod.rs index 0bb7a0b..67ae89d 100644 --- a/src/bridge/mod.rs +++ b/src/bridge/mod.rs @@ -1,15 +1,15 @@ pub mod create; mod events; mod handler; +mod setup; mod tx_wrapper; mod ui_commands; -use std::{path::Path, process::Stdio, sync::Arc}; +use std::{path::Path, process::Stdio, sync::Arc, thread}; use log::{error, info, warn}; use nvim_rs::UiAttachOptions; -use rmpv::Value; -use tokio::{process::Command, runtime::Runtime}; +use tokio::process::Command; use crate::{ cmd_line::CmdLineSettings, error_handling::ResultPanicExplanation, running_tracker::*, @@ -18,17 +18,88 @@ use crate::{ pub use events::*; use handler::NeovimHandler; +use setup::setup_neovide_specific_state; pub use tx_wrapper::{TxWrapper, WrapTx}; pub use ui_commands::{start_ui_command_handler, ParallelCommand, SerialCommand, UiCommand}; +enum ConnectionMode { + Child, + RemoteTcp(String), +} + +fn connection_mode() -> ConnectionMode { + if let Some(arg) = SETTINGS.get::().remote_tcp { + ConnectionMode::RemoteTcp(arg) + } else { + ConnectionMode::Child + } +} + +pub fn start_bridge() { + thread::spawn(|| { + start_neovim_runtime(); + }); +} + +#[tokio::main] +async fn start_neovim_runtime() { + let handler = NeovimHandler::new(); + let (nvim, io_handler) = match connection_mode() { + ConnectionMode::Child => create::new_child_cmd(&mut create_nvim_command(), handler).await, + ConnectionMode::RemoteTcp(address) => create::new_tcp(address, handler).await, + } + .unwrap_or_explained_panic("Could not locate or start neovim process"); + + // Check the neovim version to ensure its high enough + match nvim.command_output("echo has('nvim-0.4')").await.as_deref() { + Ok("1") => {} // This is just a guard + _ => { + error!("Neovide requires nvim version 0.4 or higher. Download the latest version here https://github.com/neovim/neovim/wiki/Installing-Neovim"); + std::process::exit(0); + } + } + + setup_neovide_specific_state(&nvim).await; + + let settings = SETTINGS.get::(); + let geometry = settings.geometry; + let mut options = UiAttachOptions::new(); + options.set_linegrid_external(true); + options.set_multigrid_external(settings.multi_grid); + options.set_rgb(true); + + // Triggers loading the user's config + nvim.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"); + + let nvim = Arc::new(nvim); + + start_ui_command_handler(nvim.clone()); + SETTINGS.read_initial_values(&nvim).await; + SETTINGS.setup_changed_listeners(&nvim).await; + + match io_handler.await { + Err(join_error) => error!("Error joining IO loop: '{}'", join_error), + Ok(Err(error)) => { + if !error.is_channel_closed() { + error!("Error: '{}'", error); + } + } + Ok(Ok(())) => {} + }; + RUNNING_TRACKER.quit("neovim processed failed"); +} + #[cfg(windows)] fn set_windows_creation_flags(cmd: &mut Command) { cmd.creation_flags(0x0800_0000); // CREATE_NO_WINDOW } fn build_nvim_cmd_with_args(bin: &str) -> Command { - let mut args = Vec::::new(); - args.push("--embed".to_string()); + let mut args = vec!["--embed".to_string()]; args.extend(SETTINGS.get::().neovim_args); #[cfg(windows)] @@ -98,26 +169,6 @@ fn build_nvim_cmd() -> Command { } } -#[cfg(windows)] -pub fn build_neovide_command(channel: u64, num_args: u64, command: &str, event: &str) -> String { - let nargs: String = if num_args > 1 { - "+".to_string() - } else { - num_args.to_string() - }; - if num_args == 0 { - return format!( - "command! -nargs={} {} call rpcnotify({}, 'neovide.{}')", - nargs, command, channel, event - ); - } else { - return format!( - "command! -nargs={} -complete=expression {} call rpcnotify({}, 'neovide.{}', )", - nargs, command, channel, event - ); - }; -} - pub fn create_nvim_command() -> Command { let mut cmd = build_nvim_cmd(); @@ -134,155 +185,3 @@ pub fn create_nvim_command() -> Command { cmd } - -enum ConnectionMode { - Child, - RemoteTcp(String), -} - -fn connection_mode() -> ConnectionMode { - if let Some(arg) = SETTINGS.get::().remote_tcp { - ConnectionMode::RemoteTcp(arg) - } else { - ConnectionMode::Child - } -} - -async fn start_neovim_runtime() { - let handler = NeovimHandler::new(); - let (nvim, io_handler) = match connection_mode() { - ConnectionMode::Child => create::new_child_cmd(&mut create_nvim_command(), handler).await, - ConnectionMode::RemoteTcp(address) => create::new_tcp(address, handler).await, - } - .unwrap_or_explained_panic("Could not locate or start neovim process"); - - if nvim.get_api_info().await.is_err() { - error!("Cannot get neovim api info, either neovide is launched with an unknown command line option or neovim version not supported!"); - std::process::exit(-1); - } - - tokio::spawn(async move { - info!("Close watcher started"); - match io_handler.await { - Err(join_error) => error!("Error joining IO loop: '{}'", join_error), - Ok(Err(error)) => { - if !error.is_channel_closed() { - error!("Error: '{}'", error); - } - } - Ok(Ok(())) => {} - }; - RUNNING_TRACKER.quit("neovim processed failed"); - }); - - match nvim.command_output("echo has('nvim-0.4')").await.as_deref() { - Ok("1") => {} // This is just a guard - _ => { - error!("Neovide requires nvim version 0.4 or higher. Download the latest version here https://github.com/neovim/neovim/wiki/Installing-Neovim"); - std::process::exit(0); - } - } - - nvim.set_var("neovide", Value::Boolean(true)) - .await - .unwrap_or_explained_panic("Could not communicate with neovim process"); - - if let Err(command_error) = nvim.command("runtime! ginit.vim").await { - nvim.command(&format!( - "echomsg \"error encountered in ginit.vim {:?}\"", - command_error - )) - .await - .ok(); - } - - nvim.set_client_info( - "neovide", - vec![ - (Value::from("major"), Value::from(0u64)), - (Value::from("minor"), Value::from(6u64)), - ], - "ui", - vec![], - vec![], - ) - .await - .ok(); - - let neovide_channel: u64 = nvim - .list_chans() - .await - .ok() - .and_then(|channel_values| parse_channel_list(channel_values).ok()) - .and_then(|channel_list| { - channel_list.iter().find_map(|channel| match channel { - ChannelInfo { - id, - client: Some(ClientInfo { name, .. }), - .. - } if name == "neovide" => Some(*id), - _ => None, - }) - }) - .unwrap_or(0); - - info!( - "Neovide registered to nvim with channel id {}", - neovide_channel - ); - - #[cfg(windows)] - nvim.command(&build_neovide_command( - neovide_channel, - 0, - "NeovideRegisterRightClick", - "register_right_click", - )) - .await - .ok(); - - #[cfg(windows)] - nvim.command(&build_neovide_command( - neovide_channel, - 0, - "NeovideUnregisterRightClick", - "unregister_right_click", - )) - .await - .ok(); - - nvim.set_option("lazyredraw", Value::Boolean(false)) - .await - .ok(); - nvim.set_option("termguicolors", Value::Boolean(true)) - .await - .ok(); - - let settings = SETTINGS.get::(); - let geometry = settings.geometry; - let mut options = UiAttachOptions::new(); - options.set_linegrid_external(true); - options.set_multigrid_external(settings.multi_grid); - options.set_rgb(true); - nvim.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"); - - let nvim = Arc::new(nvim); - - start_ui_command_handler(nvim.clone()); - SETTINGS.read_initial_values(&nvim).await; - SETTINGS.setup_changed_listeners(&nvim).await; -} - -pub struct Bridge { - _runtime: Runtime, // Necessary to keep runtime running -} - -pub fn start_bridge() -> Bridge { - let runtime = Runtime::new().unwrap(); - runtime.spawn(start_neovim_runtime()); - Bridge { _runtime: runtime } -} diff --git a/src/bridge/setup.rs b/src/bridge/setup.rs new file mode 100644 index 0000000..f6b80f6 --- /dev/null +++ b/src/bridge/setup.rs @@ -0,0 +1,117 @@ +use log::info; +use nvim_rs::Neovim; +use rmpv::Value; + +use crate::{ + bridge::{events::*, TxWrapper}, + error_handling::ResultPanicExplanation, +}; + +pub async fn setup_neovide_specific_state(nvim: &Neovim) { + // Set variable indicating to user config that neovide is being used + nvim.set_var("neovide", Value::Boolean(true)) + .await + .unwrap_or_explained_panic("Could not communicate with neovim process"); + + if let Err(command_error) = nvim.command("runtime! ginit.vim").await { + nvim.command(&format!( + "echomsg \"error encountered in ginit.vim {:?}\"", + command_error + )) + .await + .ok(); + } + + // Set details about the neovide version + nvim.set_client_info( + "neovide", + vec![ + (Value::from("major"), Value::from(0u64)), + (Value::from("minor"), Value::from(6u64)), + ], + "ui", + vec![], + vec![], + ) + .await + .ok(); + + // Retrieve the channel number for communicating with neovide + let neovide_channel: u64 = nvim + .list_chans() + .await + .ok() + .and_then(|channel_values| parse_channel_list(channel_values).ok()) + .and_then(|channel_list| { + channel_list.iter().find_map(|channel| match channel { + ChannelInfo { + id, + client: Some(ClientInfo { name, .. }), + .. + } if name == "neovide" => Some(*id), + _ => None, + }) + }) + .unwrap_or(0); + + // Record the channel to the log + info!( + "Neovide registered to nvim with channel id {}", + neovide_channel + ); + + // Create a command for registering right click context hooking + #[cfg(windows)] + nvim.command(&build_neovide_command( + neovide_channel, + 0, + "NeovideRegisterRightClick", + "register_right_click", + )) + .await + .ok(); + + // Create a command for unregistering the right click context hooking + #[cfg(windows)] + nvim.command(&build_neovide_command( + neovide_channel, + 0, + "NeovideUnregisterRightClick", + "unregister_right_click", + )) + .await + .ok(); + + // Set some basic rendering options + nvim.set_option("lazyredraw", Value::Boolean(false)) + .await + .ok(); + nvim.set_option("termguicolors", Value::Boolean(true)) + .await + .ok(); + + // Create auto command for retrieving exit code from neovim on quit + nvim.command("autocmd VimLeave * call rpcnotify(1, 'neovide.quit', v:exiting)") + .await + .ok(); +} + +#[cfg(windows)] +pub fn build_neovide_command(channel: u64, num_args: u64, command: &str, event: &str) -> String { + let nargs: String = if num_args > 1 { + "+".to_string() + } else { + num_args.to_string() + }; + if num_args == 0 { + return format!( + "command! -nargs={} {} call rpcnotify({}, 'neovide.{}')", + nargs, command, channel, event + ); + } else { + return format!( + "command! -nargs={} -complete=expression {} call rpcnotify({}, 'neovide.{}', )", + nargs, command, channel, event + ); + }; +} diff --git a/src/main.rs b/src/main.rs index 1e1b469..8953956 100644 --- a/src/main.rs +++ b/src/main.rs @@ -140,8 +140,7 @@ fn main() { CursorSettings::register(); KeyboardSettings::register(); - // We need to keep the bridge reference around to prevent the tokio runtime from getting freed - let _bridge = start_bridge(); + start_bridge(); start_editor(); create_window(); } diff --git a/src/running_tracker.rs b/src/running_tracker.rs index 3558776..5c73839 100644 --- a/src/running_tracker.rs +++ b/src/running_tracker.rs @@ -1,5 +1,5 @@ use std::sync::{ - atomic::{AtomicBool, Ordering}, + atomic::{AtomicBool, AtomicI32, Ordering}, Arc, }; @@ -11,12 +11,14 @@ lazy_static! { pub struct RunningTracker { running: Arc, + exit_code: Arc, } impl RunningTracker { fn new() -> Self { Self { running: Arc::new(AtomicBool::new(true)), + exit_code: Arc::new(AtomicI32::new(0)), } } @@ -25,7 +27,17 @@ impl RunningTracker { info!("Quit {}", reason); } + pub fn quit_with_code(&self, code: i32, reason: &str) { + self.exit_code.store(code, Ordering::Relaxed); + self.running.store(false, Ordering::Relaxed); + info!("Quit with code {}: {}", code, reason); + } + pub fn is_running(&self) -> bool { self.running.load(Ordering::Relaxed) } + + pub fn exit_code(&self) -> i32 { + self.exit_code.load(Ordering::Relaxed) + } } diff --git a/src/window/mod.rs b/src/window/mod.rs index 407cb44..24dbcb5 100644 --- a/src/window/mod.rs +++ b/src/window/mod.rs @@ -369,7 +369,8 @@ pub fn create_window() { window_wrapper.saved_grid_size, window.outer_position().ok(), ); - std::process::exit(0); + + std::process::exit(RUNNING_TRACKER.exit_code()); } let frame_start = Instant::now();