mirror of https://github.com/sgoudham/neovide.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
337 lines
9.6 KiB
Rust
337 lines
9.6 KiB
Rust
pub mod create;
|
|
mod events;
|
|
mod handler;
|
|
mod tx_wrapper;
|
|
mod ui_commands;
|
|
|
|
use std::path::Path;
|
|
use std::process::Stdio;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::sync::Arc;
|
|
|
|
use crossfire::mpsc::RxUnbounded;
|
|
use log::{error, info, warn};
|
|
use nvim_rs::UiAttachOptions;
|
|
use rmpv::Value;
|
|
use tokio::process::Command;
|
|
use tokio::runtime::Runtime;
|
|
|
|
use crate::channel_utils::*;
|
|
use crate::settings::*;
|
|
use crate::{cmd_line::CmdLineSettings, error_handling::ResultPanicExplanation};
|
|
pub use events::*;
|
|
use handler::NeovimHandler;
|
|
use regex::Regex;
|
|
pub use tx_wrapper::{TxWrapper, WrapTx};
|
|
pub use ui_commands::UiCommand;
|
|
|
|
#[cfg(windows)]
|
|
fn set_windows_creation_flags(cmd: &mut Command) {
|
|
cmd.creation_flags(0x0800_0000); // CREATE_NO_WINDOW
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
fn platform_build_nvim_cmd(bin: &str) -> Option<Command> {
|
|
if SETTINGS.get::<CmdLineSettings>().wsl {
|
|
let mut cmd = Command::new("wsl");
|
|
cmd.args(&[
|
|
bin.trim(),
|
|
"-c",
|
|
"let \\$PATH=system(\"bash -ic 'echo \\$PATH' 2>/dev/null\")",
|
|
]);
|
|
Some(cmd)
|
|
} else if Path::new(&bin).exists() {
|
|
Some(Command::new(bin))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn platform_build_nvim_cmd(bin: &str) -> Option<Command> {
|
|
if Path::new(&bin).exists() {
|
|
Some(Command::new(bin))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn build_nvim_cmd() -> Command {
|
|
if let Some(path) = SETTINGS.get::<CmdLineSettings>().neovim_bin {
|
|
if let Some(cmd) = platform_build_nvim_cmd(&path) {
|
|
return cmd;
|
|
} else {
|
|
warn!("NEOVIM_BIN is invalid falling back to first bin in PATH");
|
|
}
|
|
}
|
|
#[cfg(windows)]
|
|
if SETTINGS.get::<CmdLineSettings>().wsl {
|
|
if let Ok(output) = std::process::Command::new("wsl")
|
|
.args(&["bash", "-ic", "which nvim"])
|
|
.output()
|
|
{
|
|
if output.status.success() {
|
|
let path = String::from_utf8(output.stdout).unwrap();
|
|
let mut cmd = Command::new("wsl");
|
|
cmd.args(&[
|
|
path.trim(),
|
|
"-c",
|
|
"let \\$PATH=system(\"bash -ic 'echo \\$PATH' 2>/dev/null\")",
|
|
]);
|
|
return cmd;
|
|
} else {
|
|
error!("nvim not found in WSL path");
|
|
std::process::exit(1);
|
|
}
|
|
} else {
|
|
error!("wsl which nvim failed");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
if let Ok(path) = which::which("nvim") {
|
|
if let Some(cmd) = platform_build_nvim_cmd(path.to_str().unwrap()) {
|
|
cmd
|
|
} else {
|
|
error!("nvim does not have proper permissions!");
|
|
std::process::exit(1);
|
|
}
|
|
} else {
|
|
error!("nvim not found!");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
|
|
#[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={} -complete=expression {} call rpcnotify({}, 'neovide.{}')",
|
|
nargs, command, channel, event
|
|
);
|
|
} else {
|
|
return format!(
|
|
"command! -nargs={} -complete=expression {} call rpcnotify({}, 'neovide.{}', <args>)",
|
|
nargs, command, channel, event
|
|
);
|
|
};
|
|
}
|
|
|
|
pub fn create_nvim_command() -> Command {
|
|
let mut cmd = build_nvim_cmd();
|
|
|
|
cmd.arg("--embed")
|
|
.args(SETTINGS.get::<CmdLineSettings>().neovim_args.iter())
|
|
.args(SETTINGS.get::<CmdLineSettings>().files_to_open.iter());
|
|
|
|
info!("Starting neovim with: {:?}", cmd);
|
|
|
|
#[cfg(not(debug_assertions))]
|
|
cmd.stderr(Stdio::piped());
|
|
|
|
#[cfg(debug_assertions)]
|
|
cmd.stderr(Stdio::inherit());
|
|
|
|
#[cfg(windows)]
|
|
set_windows_creation_flags(&mut cmd);
|
|
|
|
cmd
|
|
}
|
|
|
|
enum ConnectionMode {
|
|
Child,
|
|
RemoteTcp(String),
|
|
}
|
|
|
|
fn connection_mode() -> ConnectionMode {
|
|
if let Some(arg) = SETTINGS.get::<CmdLineSettings>().remote_tcp {
|
|
ConnectionMode::RemoteTcp(arg)
|
|
} else {
|
|
ConnectionMode::Child
|
|
}
|
|
}
|
|
|
|
async fn start_neovim_runtime(
|
|
ui_command_sender: LoggingTx<UiCommand>,
|
|
ui_command_receiver: RxUnbounded<UiCommand>,
|
|
redraw_event_sender: LoggingTx<RedrawEvent>,
|
|
running: Arc<AtomicBool>,
|
|
) {
|
|
let handler = NeovimHandler::new(ui_command_sender.clone(), redraw_event_sender.clone());
|
|
let (mut 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);
|
|
}
|
|
|
|
let close_watcher_running = running.clone();
|
|
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(())) => {}
|
|
};
|
|
close_watcher_running.store(false, Ordering::Relaxed);
|
|
});
|
|
|
|
if let Ok(output) = nvim.command_output("version").await {
|
|
let re = Regex::new(r"NVIM v0.[4-9]\d*.\d+").unwrap();
|
|
if !re.is_match(&output) {
|
|
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);
|
|
}
|
|
} else {
|
|
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::<CmdLineSettings>();
|
|
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);
|
|
|
|
let ui_command_running = running.clone();
|
|
let input_nvim = nvim.clone();
|
|
tokio::spawn(async move {
|
|
loop {
|
|
if !ui_command_running.load(Ordering::Relaxed) {
|
|
break;
|
|
}
|
|
|
|
match ui_command_receiver.recv().await {
|
|
Ok(ui_command) => {
|
|
let input_nvim = input_nvim.clone();
|
|
tokio::spawn(async move {
|
|
ui_command.execute(&input_nvim).await;
|
|
});
|
|
}
|
|
Err(_) => {
|
|
ui_command_running.store(false, Ordering::Relaxed);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
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(
|
|
ui_command_sender: LoggingTx<UiCommand>,
|
|
ui_command_receiver: RxUnbounded<UiCommand>,
|
|
redraw_event_sender: LoggingTx<RedrawEvent>,
|
|
running: Arc<AtomicBool>,
|
|
) -> Bridge {
|
|
let runtime = Runtime::new().unwrap();
|
|
runtime.spawn(start_neovim_runtime(
|
|
ui_command_sender,
|
|
ui_command_receiver,
|
|
redraw_event_sender,
|
|
running,
|
|
));
|
|
Bridge { _runtime: runtime }
|
|
}
|