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.
neovide/src/main.rs

231 lines
7.7 KiB
Rust

#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
// Test naming occasionally uses camelCase with underscores to separate sections of
// the test name.
#[cfg_attr(test, allow(non_snake_case))]
#[macro_use]
extern crate neovide_derive;
#[macro_use]
extern crate clap;
mod bridge;
mod channel_utils;
mod cmd_line;
mod dimensions;
mod editor;
mod error_handling;
mod event_aggregator;
mod frame;
mod redraw_scheduler;
mod renderer;
mod running_tracker;
mod settings;
mod window;
mod windows_utils;
#[macro_use]
extern crate derive_new;
#[macro_use]
extern crate lazy_static;
use std::env::args;
#[cfg(not(test))]
use flexi_logger::{Cleanup, Criterion, Duplicate, Logger, Naming};
use log::trace;
use bridge::start_bridge;
use cmd_line::CmdLineSettings;
use editor::start_editor;
use renderer::{cursor_renderer::CursorSettings, RendererSettings};
use settings::SETTINGS;
use window::{create_window, KeyboardSettings, WindowSettings};
pub use channel_utils::*;
pub use event_aggregator::*;
pub use running_tracker::*;
pub use windows_utils::*;
fn main() {
// --------------
// | Architecture |
// --------------
//
// BRIDGE:
// The bridge is responsible for the connection to the neovim process itself. It is in charge
// of starting and communicating to and from the process. The bridge is async and has a
// couple of sub components:
//
// NEOVIM HANDLER:
// This component handles events from neovim sent specifically to the gui. This includes
// redraw events responsible for updating the gui state, and custom neovide specific
// events which are registered on startup and handle syncing of settings or features from
// the neovim process.
//
// UI COMMAND HANDLER:
// This component handles communication from other components to the neovim process. The
// commands are split into Serial and Parallel commands. Serial commands must be
// processed in order while parallel commands can be processed in any order and in
// parallel.
//
// EDITOR:
// The editor is responsible for processing and transforming redraw events into something
// more readily renderable. Ligature support and multi window management requires some
// significant preprocessing of the redraw events in order to capture what exactly should get
// drawn where. Futher this step takes a bit of processing power to accomplish, so it is done
// on it's own thread. Ideally heavily computationally expensive tasks should be done in the
// editor.
//
// RENDERER:
// The renderer is responsible for drawing the editor's output to the screen. It uses skia
// for drawing and is responsible for maintaining the various draw surfaces which are stored
// to prevent unnecessary redraws.
//
// WINDOW:
// The window is responsible for rendering and gathering input events from the user. This
// inncludes taking the draw commands from the editor and turning them into pixels on the
// screen. The ui commands are then forwarded back to the BRIDGE to convert them into
// commands for neovim to handle properly.
//
// ------------------
// | Other Components |
// ------------------
//
// Neovide also includes some other systems which are globally available via lazy static
// instantiations.
//
// EVENT AGGREGATOR:
// Central system which distributes events to each of the other components. This is done
// using TypeIds and channels. Any component can publish any Clone + Debug + Send + Sync type
// to the aggregator, but only one component can subscribe to any type. The system takes
// pains to ensure that channels are shared by thread in order to keep things performant.
// Also tokio channels are used so that the async components can properly await for events.
//
// SETTINGS:
// The settings system is live updated from global variables in neovim with the prefix
// "neovide". They allow us to configure and manage the functionality of neovide from neovim
// init scripts and variables.
//
// REDRAW SCHEDULER:
// The redraw scheduler is a simple system in charge of deciding if the renderer should draw
// another frame next frame, or if it can safely skip drawing to save battery and cpu power.
// Multiple other parts of the app "queue_next_frame" function to ensure animations continue
// properly or updates to the graphics are pushed to the screen.
#[cfg(target_os = "windows")]
windows_attach_to_console();
//Will exit if -h or -v
if let Err(err) = cmd_line::handle_command_line_arguments(args().collect()) {
eprintln!("{}", err);
return;
}
#[cfg(not(test))]
init_logger();
trace!("Neovide version: {}", crate_version!());
maybe_disown();
#[cfg(target_os = "windows")]
windows_fix_dpi();
#[cfg(target_os = "macos")]
handle_macos();
WindowSettings::register();
RendererSettings::register();
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_editor();
create_window();
}
#[cfg(not(test))]
pub fn init_logger() {
let settings = SETTINGS.get::<CmdLineSettings>();
let logger = if settings.log_to_file {
Logger::with_env_or_str("neovide")
.log_to_file()
.rotate(
Criterion::Size(10_000_000),
Naming::Timestamps,
Cleanup::KeepLogFiles(1),
)
.duplicate_to_stderr(Duplicate::Error)
} else {
Logger::with_env_or_str("neovide = error")
};
logger.start().expect("Could not start logger");
}
fn maybe_disown() {
use std::{env, process};
let settings = SETTINGS.get::<CmdLineSettings>();
if cfg!(debug_assertions) || settings.no_fork {
return;
}
if let Ok(current_exe) = env::current_exe() {
assert!(process::Command::new(current_exe)
.stdin(process::Stdio::null())
.stdout(process::Stdio::null())
.stderr(process::Stdio::null())
.arg("--nofork")
.args(env::args().skip(1))
.spawn()
.is_ok());
process::exit(0);
} else {
eprintln!("error in disowning process, cannot obtain the path for the current executable, continuing without disowning...");
}
}
#[cfg(target_os = "windows")]
fn windows_fix_dpi() {
use winapi::shared::windef::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
use winapi::um::winuser::SetProcessDpiAwarenessContext;
unsafe {
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
}
}
#[cfg(target_os = "macos")]
fn handle_macos() {
use std::env;
if env::var_os("TERM").is_none() {
let shell = env::var("SHELL").unwrap();
// printenv is the proper way to print env variables. using echo $PATH would break Fish.
let cmd = "printenv PATH";
if let Ok(path) = std::process::Command::new(shell)
.arg("-lic") // interactive login shell, this simulates opening a real terminal emulator
.arg(cmd)
.output()
{
env::set_var(
"PATH",
std::str::from_utf8(&path.stdout).unwrap().trim_end(),
);
}
}
}
#[cfg(target_os = "windows")]
fn windows_attach_to_console() {
// Attach to parent console tip found here: https://github.com/rust-lang/rust/issues/67159#issuecomment-987882771
use winapi::um::wincon::{AttachConsole, ATTACH_PARENT_PROCESS};
unsafe {
AttachConsole(ATTACH_PARENT_PROCESS);
}
}