diff options
28 files changed, 470 insertions, 353 deletions
diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs index d939aff5..fb3cb011 100644 --- a/alacritty/src/cli.rs +++ b/alacritty/src/cli.rs @@ -2,13 +2,14 @@ use std::cmp::max; use std::path::PathBuf; use log::{self, error, LevelFilter}; +use serde::{Deserialize, Serialize}; use serde_yaml::Value; use structopt::StructOpt; -use alacritty_terminal::config::Program; +use alacritty_terminal::config::{Program, PtyConfig}; use crate::config::window::{Class, DEFAULT_NAME}; -use crate::config::{serde_utils, Config}; +use crate::config::{serde_utils, UiConfig}; /// CLI options for the main Alacritty executable. #[derive(StructOpt, Debug)] @@ -34,10 +35,6 @@ pub struct Options { #[structopt(long)] pub embed: Option<String>, - /// Start the shell in the specified working directory. - #[structopt(long)] - pub working_directory: Option<PathBuf>, - /// Specify alternative configuration file [default: $XDG_CONFIG_HOME/alacritty/alacritty.yml]. #[cfg(not(any(target_os = "macos", windows)))] #[structopt(long)] @@ -53,10 +50,6 @@ pub struct Options { #[structopt(long)] pub config_file: Option<PathBuf>, - /// Remain open after child process exits. - #[structopt(long)] - pub hold: bool, - /// Path for IPC socket creation. #[cfg(unix)] #[structopt(long)] @@ -70,10 +63,6 @@ pub struct Options { #[structopt(short, conflicts_with("quiet"), parse(from_occurrences))] verbose: u8, - /// Command and args to execute (must be last argument). - #[structopt(short = "e", long, allow_hyphen_values = true)] - command: Vec<String>, - /// Override configuration file options [example: cursor.style=Beam]. #[structopt(short = "o", long)] option: Vec<String>, @@ -82,6 +71,10 @@ pub struct Options { #[structopt(skip)] pub config_options: Value, + /// Terminal options which could be passed via IPC. + #[structopt(flatten)] + pub terminal_options: TerminalOptions, + /// Subcommand passed to the CLI. #[cfg(unix)] #[structopt(subcommand)] @@ -106,42 +99,42 @@ impl Options { } /// Override configuration file with options from the CLI. - pub fn override_config(&self, config: &mut Config) { - if let Some(working_directory) = &self.working_directory { + pub fn override_config(&self, config: &mut UiConfig) { + if let Some(working_directory) = &self.terminal_options.working_directory { if working_directory.is_dir() { - config.working_directory = Some(working_directory.to_owned()); + config.terminal_config.pty_config.working_directory = + Some(working_directory.to_owned()); } else { error!("Invalid working directory: {:?}", working_directory); } } - if let Some(command) = self.command() { - config.shell = Some(command); + if let Some(command) = self.terminal_options.command() { + config.terminal_config.pty_config.shell = Some(command); } - config.hold = self.hold; + config.terminal_config.pty_config.hold = self.terminal_options.hold; if let Some(title) = self.title.clone() { - config.ui_config.window.title = title + config.window.title = title } if let Some(class) = &self.class { - config.ui_config.window.class = class.clone(); + config.window.class = class.clone(); } #[cfg(unix)] { - config.ui_config.ipc_socket |= self.socket.is_some(); + config.ipc_socket |= self.socket.is_some(); } - config.ui_config.window.dynamic_title &= self.title.is_none(); - config.ui_config.window.embed = self.embed.as_ref().and_then(|embed| embed.parse().ok()); - config.ui_config.debug.print_events |= self.print_events; - config.ui_config.debug.log_level = max(config.ui_config.debug.log_level, self.log_level()); - config.ui_config.debug.ref_test |= self.ref_test; + config.window.dynamic_title &= self.title.is_none(); + config.window.embed = self.embed.as_ref().and_then(|embed| embed.parse().ok()); + config.debug.print_events |= self.print_events; + config.debug.log_level = max(config.debug.log_level, self.log_level()); + config.debug.ref_test |= self.ref_test; - if config.ui_config.debug.print_events { - config.ui_config.debug.log_level = - max(config.ui_config.debug.log_level, LevelFilter::Info); + if config.debug.print_events { + config.debug.log_level = max(config.debug.log_level, LevelFilter::Info); } } @@ -164,12 +157,6 @@ impl Options { (..) => LevelFilter::Off, } } - - /// Shell override passed through the CLI. - pub fn command(&self) -> Option<Program> { - let (program, args) = self.command.split_first()?; - Some(Program::WithArgs { program: program.clone(), args: args.to_vec() }) - } } /// Format an option in the format of `parent.field=value` to a serde Value. @@ -214,6 +201,47 @@ fn parse_class(input: &str) -> Result<Class, String> { } } +/// Terminal specific cli options which can be passed to new windows via IPC. +#[derive(Serialize, Deserialize, StructOpt, Default, Debug, Clone, PartialEq)] +pub struct TerminalOptions { + /// Start the shell in the specified working directory. + #[structopt(long)] + pub working_directory: Option<PathBuf>, + + /// Remain open after child process exit. + #[structopt(long)] + pub hold: bool, + + /// Command and args to execute (must be last argument). + #[structopt(short = "e", long, allow_hyphen_values = true)] + command: Vec<String>, +} + +impl TerminalOptions { + pub fn new() -> Self { + Default::default() + } + + pub fn is_empty(&self) -> bool { + self.working_directory.is_none() && !self.hold && self.command.is_empty() + } + + /// Shell override passed through the CLI. + pub fn command(&self) -> Option<Program> { + let (program, args) = self.command.split_first()?; + Some(Program::WithArgs { program: program.clone(), args: args.to_vec() }) + } +} + +impl From<TerminalOptions> for PtyConfig { + fn from(mut options: TerminalOptions) -> Self { + let working_directory = options.working_directory.take(); + let shell = options.command(); + let hold = options.hold; + PtyConfig { hold, shell, working_directory } + } +} + /// Available CLI subcommands. #[cfg(unix)] #[derive(StructOpt, Debug)] @@ -236,10 +264,10 @@ pub struct MessageOptions { /// Available socket messages. #[cfg(unix)] -#[derive(StructOpt, Debug)] +#[derive(StructOpt, Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum SocketMessage { /// Create a new window in the same Alacritty process. - CreateWindow, + CreateWindow(TerminalOptions), } #[cfg(test)] @@ -257,32 +285,32 @@ mod tests { #[test] fn dynamic_title_ignoring_options_by_default() { - let mut config = Config::default(); - let old_dynamic_title = config.ui_config.window.dynamic_title; + let mut config = UiConfig::default(); + let old_dynamic_title = config.window.dynamic_title; Options::new().override_config(&mut config); - assert_eq!(old_dynamic_title, config.ui_config.window.dynamic_title); + assert_eq!(old_dynamic_title, config.window.dynamic_title); } #[test] fn dynamic_title_overridden_by_options() { - let mut config = Config::default(); + let mut config = UiConfig::default(); let options = Options { title: Some("foo".to_owned()), ..Options::new() }; options.override_config(&mut config); - assert!(!config.ui_config.window.dynamic_title); + assert!(!config.window.dynamic_title); } #[test] fn dynamic_title_not_overridden_by_config() { - let mut config = Config::default(); + let mut config = UiConfig::default(); - config.ui_config.window.title = "foo".to_owned(); + config.window.title = "foo".to_owned(); Options::new().override_config(&mut config); - assert!(config.ui_config.window.dynamic_title); + assert!(config.window.dynamic_title); } #[test] diff --git a/alacritty/src/config/mod.rs b/alacritty/src/config/mod.rs index 4a3c0ae9..10e504d3 100644 --- a/alacritty/src/config/mod.rs +++ b/alacritty/src/config/mod.rs @@ -7,7 +7,7 @@ use serde::Deserialize; use serde_yaml::mapping::Mapping; use serde_yaml::Value; -use alacritty_terminal::config::{Config as TermConfig, LOG_TARGET_CONFIG}; +use alacritty_terminal::config::LOG_TARGET_CONFIG; pub mod bell; pub mod color; @@ -27,13 +27,11 @@ pub use crate::config::bindings::{ }; #[cfg(test)] pub use crate::config::mouse::{ClickHandler, Mouse}; -use crate::config::ui_config::UiConfig; +pub use crate::config::ui_config::UiConfig; /// Maximum number of depth for the configuration file imports. const IMPORT_RECURSION_LIMIT: usize = 5; -pub type Config = TermConfig<UiConfig>; - /// Result from config loading. pub type Result<T> = std::result::Result<T, Error>; @@ -100,7 +98,7 @@ impl From<serde_yaml::Error> for Error { } /// Load the configuration file. -pub fn load(options: &Options) -> Config { +pub fn load(options: &Options) -> UiConfig { let config_options = options.config_options.clone(); let config_path = options.config_file.clone().or_else(installed_config); @@ -112,9 +110,9 @@ pub fn load(options: &Options) -> Config { .as_ref() .and_then(|config_path| load_from(config_path, config_options.clone()).ok()) .unwrap_or_else(|| { - let mut config = Config::deserialize(config_options).unwrap_or_default(); + let mut config = UiConfig::deserialize(config_options).unwrap_or_default(); match config_path { - Some(config_path) => config.ui_config.config_paths.push(config_path), + Some(config_path) => config.config_paths.push(config_path), None => info!(target: LOG_TARGET_CONFIG, "No config file found; using default"), } config @@ -126,7 +124,7 @@ pub fn load(options: &Options) -> Config { } /// Attempt to reload the configuration file. -pub fn reload(config_path: &Path, options: &Options) -> Result<Config> { +pub fn reload(config_path: &Path, options: &Options) -> Result<UiConfig> { // Load config, propagating errors. let config_options = options.config_options.clone(); let mut config = load_from(config_path, config_options)?; @@ -136,17 +134,17 @@ pub fn reload(config_path: &Path, options: &Options) -> Result<Config> { Ok(config) } -/// Modifications after the `Config` object is created. -fn after_loading(config: &mut Config, options: &Options) { +/// Modifications after the `UiConfig` object is created. +fn after_loading(config: &mut UiConfig, options: &Options) { // Override config with CLI options. options.override_config(config); // Create key bindings for regex hints. - config.ui_config.generate_hint_bindings(); + config.generate_hint_bindings(); } /// Load configuration file and log errors. -fn load_from(path: &Path, cli_config: Value) -> Result<Config> { +fn load_from(path: &Path, cli_config: Value) -> Result<UiConfig> { match read_config(path, cli_config) { Ok(config) => Ok(config), Err(err) => { @@ -157,7 +155,7 @@ fn load_from(path: &Path, cli_config: Value) -> Result<Config> { } /// Deserialize configuration file from path. -fn read_config(path: &Path, cli_config: Value) -> Result<Config> { +fn read_config(path: &Path, cli_config: Value) -> Result<UiConfig> { let mut config_paths = Vec::new(); let mut config_value = parse_config(path, &mut config_paths, IMPORT_RECURSION_LIMIT)?; @@ -165,8 +163,8 @@ fn read_config(path: &Path, cli_config: Value) -> Result<Config> { config_value = serde_utils::merge(config_value, cli_config); // Deserialize to concrete type. - let mut config = Config::deserialize(config_value)?; - config.ui_config.config_paths = config_paths; + let mut config = UiConfig::deserialize(config_value)?; + config.config_paths = config_paths; Ok(config) } @@ -307,7 +305,7 @@ mod tests { fn config_read_eof() { let config_path: PathBuf = DEFAULT_ALACRITTY_CONFIG.into(); let mut config = read_config(&config_path, Value::Null).unwrap(); - config.ui_config.config_paths = Vec::new(); - assert_eq!(config, Config::default()); + config.config_paths = Vec::new(); + assert_eq!(config, UiConfig::default()); } } diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs index 3ba59ea8..353249d5 100644 --- a/alacritty/src/config/ui_config.rs +++ b/alacritty/src/config/ui_config.rs @@ -9,7 +9,9 @@ use serde::{self, Deserialize, Deserializer}; use unicode_width::UnicodeWidthChar; use alacritty_config_derive::ConfigDeserialize; -use alacritty_terminal::config::{Percentage, Program, LOG_TARGET_CONFIG}; +use alacritty_terminal::config::{ + Config as TerminalConfig, Percentage, Program, LOG_TARGET_CONFIG, +}; use alacritty_terminal::term::search::RegexSearch; use crate::config::bell::BellConfig; @@ -66,6 +68,10 @@ pub struct UiConfig { #[cfg(unix)] pub ipc_socket: bool, + /// Config for the alacritty_terminal itself. + #[config(flatten)] + pub terminal_config: TerminalConfig, + /// Keybindings. key_bindings: KeyBindings, @@ -91,6 +97,7 @@ impl Default for UiConfig { config_paths: Default::default(), key_bindings: Default::default(), mouse_bindings: Default::default(), + terminal_config: Default::default(), background_opacity: Default::default(), bell: Default::default(), colors: Default::default(), diff --git a/alacritty/src/daemon.rs b/alacritty/src/daemon.rs index 4a6b6f83..b199c353 100644 --- a/alacritty/src/daemon.rs +++ b/alacritty/src/daemon.rs @@ -8,7 +8,6 @@ use std::os::windows::process::CommandExt; use std::process::{Command, Stdio}; use log::{debug, warn}; - #[cfg(windows)] use winapi::um::winbase::{CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW}; diff --git a/alacritty/src/display/content.rs b/alacritty/src/display/content.rs index db73a73b..72d79f7e 100644 --- a/alacritty/src/display/content.rs +++ b/alacritty/src/display/content.rs @@ -4,7 +4,6 @@ use std::mem; use std::ops::{Deref, DerefMut, RangeInclusive}; use alacritty_terminal::ansi::{Color, CursorShape, NamedColor}; -use alacritty_terminal::config::Config; use alacritty_terminal::event::EventListener; use alacritty_terminal::grid::{Dimensions, Indexed}; use alacritty_terminal::index::{Column, Direction, Line, Point}; @@ -13,7 +12,7 @@ use alacritty_terminal::term::color::{CellRgb, Rgb}; use alacritty_terminal::term::search::{Match, RegexIter, RegexSearch}; use alacritty_terminal::term::{RenderableContent as TerminalContent, Term, TermMode}; -use crate::config::ui_config::UiConfig; +use crate::config::UiConfig; use crate::display::color::{List, DIM_FACTOR}; use crate::display::hint::HintState; use crate::display::{self, Display, MAX_SEARCH_LINES}; @@ -32,14 +31,14 @@ pub struct RenderableContent<'a> { cursor_point: Point<usize>, search: Option<Regex<'a>>, hint: Option<Hint<'a>>, - config: &'a Config<UiConfig>, + config: &'a UiConfig, colors: &'a List, focused_match: Option<&'a Match>, } impl<'a> RenderableContent<'a> { pub fn new<T: EventListener>( - config: &'a Config<UiConfig>, + config: &'a UiConfig, display: &'a mut Display, term: &'a Term<T>, search_state: &'a SearchState, @@ -54,7 +53,7 @@ impl<'a> RenderableContent<'a> { || search_state.regex().is_some() { CursorShape::Hidden - } else if !term.is_focused && config.cursor.unfocused_hollow { + } else if !term.is_focused && config.terminal_config.cursor.unfocused_hollow { CursorShape::HollowBlock } else { terminal_content.cursor.shape @@ -113,9 +112,9 @@ impl<'a> RenderableContent<'a> { // Cursor colors. let color = if self.terminal_content.mode.contains(TermMode::VI) { - self.config.ui_config.colors.vi_mode_cursor + self.config.colors.vi_mode_cursor } else { - self.config.ui_config.colors.cursor + self.config.colors.cursor }; let cursor_color = self.terminal_content.colors[NamedColor::Cursor].map_or(color.background, CellRgb::Rgb); @@ -131,8 +130,8 @@ impl<'a> RenderableContent<'a> { // Invert cursor color with insufficient contrast to prevent invisible cursors. if insufficient_contrast { - cursor_color = self.config.ui_config.colors.primary.foreground; - text_color = self.config.ui_config.colors.primary.background; + cursor_color = self.config.colors.primary.foreground; + text_color = self.config.colors.primary.background; } Some(RenderableCursor { @@ -204,7 +203,7 @@ impl RenderableCell { mem::swap(&mut fg, &mut bg); 1.0 } else { - Self::compute_bg_alpha(&content.config.ui_config, cell.bg) + Self::compute_bg_alpha(content.config, cell.bg) }; let is_selected = content.terminal_content.selection.map_or(false, |selection| { @@ -217,7 +216,7 @@ impl RenderableCell { let display_offset = content.terminal_content.display_offset; let viewport_start = Point::new(Line(-(display_offset as i32)), Column(0)); - let colors = &content.config.ui_config.colors; + let colors = &content.config.colors; let mut character = cell.c; if let Some((c, is_first)) = @@ -293,18 +292,18 @@ impl RenderableCell { /// Get the RGB color from a cell's foreground color. fn compute_fg_rgb(content: &mut RenderableContent<'_>, fg: Color, flags: Flags) -> Rgb { - let ui_config = &content.config.ui_config; + let config = &content.config; match fg { Color::Spec(rgb) => match flags & Flags::DIM { Flags::DIM => rgb * DIM_FACTOR, _ => rgb, }, Color::Named(ansi) => { - match (ui_config.draw_bold_text_with_bright_colors, flags & Flags::DIM_BOLD) { + match (config.draw_bold_text_with_bright_colors, flags & Flags::DIM_BOLD) { // If no bright foreground is set, treat it like the BOLD flag doesn't exist. (_, Flags::DIM_BOLD) if ansi == NamedColor::Foreground - && ui_config.colors.primary.bright_foreground.is_none() => + && config.colors.primary.bright_foreground.is_none() => { content.color(NamedColor::DimForeground as usize) }, @@ -320,7 +319,7 @@ impl RenderableCell { }, Color::Indexed(idx) => { let idx = match ( - ui_config.draw_bold_text_with_bright_colors, + config.draw_bold_text_with_bright_colors, flags & Flags::DIM_BOLD, idx, ) { diff --git a/alacritty/src/display/hint.rs b/alacritty/src/display/hint.rs index 2157a058..c318fc09 100644 --- a/alacritty/src/display/hint.rs +++ b/alacritty/src/display/hint.rs @@ -8,7 +8,7 @@ use alacritty_terminal::term::search::{Match, RegexIter, RegexSearch}; use alacritty_terminal::term::{Term, TermMode}; use crate::config::ui_config::{Hint, HintAction}; -use crate::config::Config; +use crate::config::UiConfig; use crate::display::content::RegexMatches; use crate::display::MAX_SEARCH_LINES; @@ -242,13 +242,13 @@ impl HintLabels { /// Check if there is a hint highlighted at the specified point. pub fn highlighted_at<T>( term: &Term<T>, - config: &Config, + config: &UiConfig, point: Point, mouse_mods: ModifiersState, ) -> Option<HintMatch> { let mouse_mode = term.mode().intersects(TermMode::MOUSE_MODE); - config.ui_config.hints.enabled.iter().find_map(|hint| { + config.hints.enabled.iter().find_map(|hint| { // Check if all required modifiers are pressed. let highlight = hint.mouse.map_or(false, |mouse| { mouse.enabled diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs index a942a88d..5cd59711 100644 --- a/alacritty/src/display/mod.rs +++ b/alacritty/src/display/mod.rs @@ -35,7 +35,7 @@ use crate::config::font::Font; use crate::config::window::Dimensions; #[cfg(not(windows))] use crate::config::window::StartupMode; -use crate::config::Config; +use crate::config::UiConfig; use crate::display::bell::VisualBell; use crate::display::color::List; use crate::display::content::RenderableContent; @@ -202,7 +202,7 @@ pub struct Display { impl Display { pub fn new<E>( - config: &Config, + config: &UiConfig, event_loop: &EventLoopWindowTarget<E>, #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] wayland_event_queue: Option<&EventQueue>, @@ -221,11 +221,11 @@ impl Display { }; // Guess the target window dimensions. - let metrics = GlyphCache::static_metrics(config.ui_config.font.clone(), estimated_dpr)?; + let metrics = GlyphCache::static_metrics(config.font.clone(), estimated_dpr)?; let (cell_width, cell_height) = compute_cell_size(config, &metrics); // Guess the target window size if the user has specified the number of lines/columns. - let dimensions = config.ui_config.window.dimensions(); + let dimensions = config.window.dimensions(); let estimated_size = dimensions.map(|dimensions| { window_size(config, dimensions, cell_width, cell_height, estimated_dpr) }); @@ -261,7 +261,7 @@ impl Display { } } - let padding = config.ui_config.window.padding(window.dpr); + let padding = config.window.padding(window.dpr); let viewport_size = window.inner_size(); // Create new size with at least one column and row. @@ -272,7 +272,7 @@ impl Display { cell_height, padding.0, padding.1, - config.ui_config.window.dynamic_padding && dimensions.is_none(), + config.window.dynamic_padding && dimensions.is_none(), ); info!("Cell size: {} x {}", cell_width, cell_height); @@ -283,25 +283,25 @@ impl Display { renderer.resize(&size_info); // Clear screen. - let background_color = config.ui_config.colors.primary.background; - renderer.with_api(&config.ui_config, &size_info, |api| { + let background_color = config.colors.primary.background; + renderer.with_api(config, &size_info, |api| { api.clear(background_color); }); // Set subpixel anti-aliasing. #[cfg(target_os = "macos")] - crossfont::set_font_smoothing(config.ui_config.font.use_thin_strokes); + crossfont::set_font_smoothing(config.font.use_thin_strokes); // Disable shadows for transparent windows on macOS. #[cfg(target_os = "macos")] - window.set_has_shadow(config.ui_config.window_opacity() >= 1.0); + window.set_has_shadow(config.window_opacity() >= 1.0); // On Wayland we can safely ignore this call, since the window isn't visible until you // actually draw something into it and commit those changes. #[cfg(not(any(target_os = "macos", windows)))] if is_x11 { window.swap_buffers(); - renderer.with_api(&config.ui_config, &size_info, |api| { + renderer.with_api(config, &size_info, |api| { api.finish(); }); } @@ -312,13 +312,13 @@ impl Display { // // TODO: replace `set_position` with `with_position` once available. // Upstream issue: https://github.com/rust-windowing/winit/issues/806. - if let Some(position) = config.ui_config.window.position { + if let Some(position) = config.window.position { window.set_outer_position(PhysicalPosition::from((position.x, position.y))); } #[allow(clippy::single_match)] #[cfg(not(windows))] - match config.ui_config.window.startup_mode { + match config.window.startup_mode { #[cfg(target_os = "macos")] StartupMode::SimpleFullscreen => window.set_simple_fullscreen(true), #[cfg(not(target_os = "macos"))] @@ -326,7 +326,7 @@ impl Display { _ => (), } - let hint_state = HintState::new(config.ui_config.hints.alphabet()); + let hint_state = HintState::new(config.hints.alphabet()); Ok(Self { window, @@ -340,8 +340,8 @@ impl Display { #[cfg(not(any(target_os = "macos", windows)))] is_x11, cursor_hidden: false, - visual_bell: VisualBell::from(&config.ui_config.bell), - colors: List::from(&config.ui_config.colors), + visual_bell: VisualBell::from(&config.bell), + colors: List::from(&config.colors), pending_update: Default::default(), }) } @@ -349,10 +349,10 @@ impl Display { fn new_glyph_cache( dpr: f64, renderer: &mut QuadRenderer, - config: &Config, + config: &UiConfig, ) -> Result<(GlyphCache, f32, f32), Error> { - let font = config.ui_config.font.clone(); - let rasterizer = Rasterizer::new(dpr as f32, config.ui_config.font.use_thin_strokes)?; + let font = config.font.clone(); + let rasterizer = Rasterizer::new(dpr as f32, config.font.use_thin_strokes)?; // Initialize glyph cache. let glyph_cache = { @@ -380,7 +380,7 @@ impl Display { /// Update font size and cell dimensions. /// /// This will return a tuple of the cell width and height. - fn update_glyph_cache(&mut self, config: &Config, font: &Font) -> (f32, f32) { + fn update_glyph_cache(&mut self, config: &UiConfig, font: &Font) -> (f32, f32) { |