diff --git a/CHANGELOG.md b/CHANGELOG.md index f17ddaf..475847e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,15 +17,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added ToggleFullscreen action - On macOS, there's a ToggleSimpleFullscreen action which allows switching to fullscreen without occupying another space -- A new window option `startup_mode` which controls how the window is created +- A new window option `window.startup_mode` which controls how the window is created - `_NET_WM_ICON` property is set on X11 now, allowing for WMs to show icons in titlebars - Current Git commit hash to `alacritty --version` +- Config options `window.title` and `window.class` +- Config option `working_directory` +- Config group `debug` with the options `debug.log_level`, `debug.print_events` + and `debug.ref_test` ### Changed - On Windows, Alacritty will now use the native DirectWrite font API - The `start_maximized` window option is now `startup_mode: Maximized` - Cells with identical foreground and background will now show their text upon selection/inversion +- Default Window padding to 0x0 +- Moved config option `render_timer` and `persistent_logging` to the `debug` group ### Fixed @@ -36,6 +42,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Mouse mode generating events when the cell has not changed - Selections not automatically expanding across double-width characters +### Removed + +- Deprecated `mouse.faux_scrollback_lines` config field +- Deprecated `custom_cursor_colors` config field +- Deprecated `hide_cursor_when_typing` config field +- Deprecated `cursor_style` config field +- Deprecated `unfocused_hollow_cursor` config field +- Deprecated `dimensions` config field + ## Version 0.3.2 ### Fixed diff --git a/Cargo.lock b/Cargo.lock index a7e2669..9e502f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,8 +32,10 @@ dependencies = [ "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_tools_util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -44,7 +46,6 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "clipboard 0.5.0 (git+https://github.com/chrisduerr/rust-clipboard)", "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "dunce 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -77,7 +78,6 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "winpty 0.1.0", "x11-dl 2.18.3 (registry+https://github.com/rust-lang/crates.io-index)", - "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -544,7 +544,7 @@ name = "derivative" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -681,7 +681,7 @@ name = "euclid_macros" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -709,7 +709,7 @@ name = "failure_derive" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1373,9 +1373,9 @@ dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.21 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.10.22 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.45 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.46 (registry+https://github.com/rust-lang/crates.io-index)", "schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", "security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1461,7 +1461,7 @@ name = "num-derive" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1538,7 +1538,7 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.21" +version = "0.10.22" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1546,7 +1546,7 @@ dependencies = [ "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.54 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.45 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.46 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1556,7 +1556,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "openssl-sys" -version = "0.9.45" +version = "0.9.46" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1679,7 +1679,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "proc-macro2" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1712,7 +1712,7 @@ name = "quote" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2047,7 +2047,7 @@ name = "serde_derive" version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2243,7 +2243,7 @@ name = "syn" version = "0.15.34" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2253,7 +2253,7 @@ name = "synstructure" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2690,7 +2690,7 @@ name = "wayland-scanner" version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2700,7 +2700,7 @@ name = "wayland-scanner" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -3068,9 +3068,9 @@ dependencies = [ "checksum objc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "31d20fd2b37e07cf5125be68357b588672e8cefe9a96f8c17a9d46053b3e590d" "checksum objc-foundation 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" "checksum objc_id 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -"checksum openssl 0.10.21 (registry+https://github.com/rust-lang/crates.io-index)" = "615b325b964d8fb0533e7fad5867f63677bbc79a274c9cd7a19443e1a6fcdd9e" +"checksum openssl 0.10.22 (registry+https://github.com/rust-lang/crates.io-index)" = "a51f452b82d622fc8dd973d7266e9055ac64af25b957d9ced3989142dc61cb6b" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" -"checksum openssl-sys 0.9.45 (registry+https://github.com/rust-lang/crates.io-index)" = "ce906a1d521507a94645974fc9ab0fb70ceeb789f7240b85617ca3d8d2cd2f46" +"checksum openssl-sys 0.9.46 (registry+https://github.com/rust-lang/crates.io-index)" = "05636e06b4f8762d4b81d24a351f3966f38bd25ccbcfd235606c91fdb82cc60f" "checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" "checksum osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b" "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" @@ -3085,7 +3085,7 @@ dependencies = [ "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" "checksum png 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "63daf481fdd0defa2d1d2be15c674fbfa1b0fd71882c303a91f9a79b3252c359" "checksum podio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "780fb4b6698bbf9cf2444ea5d22411cef2953f0824b98f33cf454ec5615645bd" -"checksum proc-macro2 0.4.29 (registry+https://github.com/rust-lang/crates.io-index)" = "64c827cea7a7ab30ce4593e5e04d7a11617ad6ece2fa230605a78b00ff965316" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum publicsuffix 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5afecba86dcf1e4fd610246f89899d1924fe12e1e89f555eb7c7f710f3c5ad1d" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" diff --git a/alacritty.yml b/alacritty.yml index beb290c..ac3fc28 100644 --- a/alacritty.yml +++ b/alacritty.yml @@ -34,8 +34,8 @@ window: # Blank space added around the window in pixels. This padding is scaled # by DPI and the specified value is always added at both opposing sides. padding: - x: 2 - y: 2 + x: 0 + y: 0 # Spread additional padding evenly around the terminal content. dynamic_padding: false @@ -62,6 +62,12 @@ window: # - SimpleFullscreen startup_mode: Windowed + # Window title + #title: Alacritty + + # Window class (Linux only): + #class: Alacritty + scrolling: # Maximum number of lines in the scrollback buffer. # Specifying '0' will disable scrolling. @@ -161,12 +167,6 @@ font: # effect. use_thin_strokes: true -# Display the time it takes to redraw each frame. -render_timer: false - -# Keep the log file after quitting Alacritty. -persistent_logging: false - # If `true`, bold text is drawn using the bright color variants. draw_bold_text_with_bright_colors: true @@ -370,6 +370,12 @@ live_config_reload: true # args: # - --login +# Startup directory +# +# Directory the shell is started in. If this is unset, or `None`, the working +# directory of the parent process will be used. +working_directory: None + # Windows 10 ConPTY backend (Windows only) # # This will enable better color support and may resolve other issues, @@ -384,6 +390,30 @@ enable_experimental_conpty_backend: false # Send ESC (\x1b) before characters when alt is pressed. alt_send_esc: true +debug: + # Display the time it takes to redraw each frame. + render_timer: false + + # Keep the log file after quitting Alacritty. + persistent_logging: false + + # Log level + # + # Values for `log_level`: + # - None + # - Error + # - Warn + # - Info + # - Debug + # - Trace + log_level: Warn + + # Print all received window events. + print_events: false + + # Record all characters and escape sequences as test data. + ref_test: false + # Key bindings # # Key bindings are specified as a list of objects. Each binding will specify a @@ -423,6 +453,7 @@ alt_send_esc: true # Values for `mods`: # - Command # - Control +# - Option # - Super # - Shift # - Alt diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index 742492f..7a3f5f8 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -15,11 +15,15 @@ log = "0.4" time = "0.1.40" env_logger = "0.6.0" crossbeam-channel = "0.3.8" +serde_yaml = "0.8" [build-dependencies] rustc_tools_util = "0.1" -[target.'cfg(target_os = "macos")'.dependencies] +[target.'cfg(not(windows))'.dependencies] +xdg = "2" + +[target.'cfg(any(target_os = "macos", windows))'.dependencies] dirs = "1.0.2" [target.'cfg(windows)'.dependencies] diff --git a/alacritty/build.rs b/alacritty/build.rs index 2a1ce9e..1ad0def 100644 --- a/alacritty/build.rs +++ b/alacritty/build.rs @@ -13,7 +13,6 @@ // limitations under the License. fn main() { - let hash = rustc_tools_util::get_commit_hash() - .expect("couldn't get commit hash"); + let hash = rustc_tools_util::get_commit_hash().expect("couldn't get commit hash"); println!("cargo:rustc-env=GIT_HASH={}", hash); } diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs index 9090621..3ecea64 100644 --- a/alacritty/src/cli.rs +++ b/alacritty/src/cli.rs @@ -12,184 +12,293 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::path::PathBuf; +use std::borrow::Cow; +use std::cmp::max; +use std::path::{Path, PathBuf}; -use ::log; use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; +use log::{self, LevelFilter}; -use alacritty_terminal::config::{Options, Delta, Dimensions, Shell}; +use alacritty_terminal::config::{Config, Delta, Dimensions, Shell}; use alacritty_terminal::index::{Column, Line}; use alacritty_terminal::window::DEFAULT_NAME; -/// Build `Options` from command line arguments. -pub fn options() -> Options { - let mut options = Options::default(); - - let version_string = format!("{} ({})", - crate_version!(), - env!("GIT_HASH")); - - let matches = App::new(crate_name!()) - .version(version_string.as_str()) - .author(crate_authors!("\n")) - .about(crate_description!()) - .arg(Arg::with_name("ref-test").long("ref-test").help("Generates ref test")) - .arg( - Arg::with_name("live-config-reload") - .long("live-config-reload") - .help("Enable automatic config reloading"), - ) - .arg( - Arg::with_name("no-live-config-reload") - .long("no-live-config-reload") - .help("Disable automatic config reloading") - .conflicts_with("live-config-reload"), - ) - .arg( - Arg::with_name("print-events") - .long("print-events") - .help("Print all events to stdout"), - ) - .arg( - Arg::with_name("persistent-logging") - .long("persistent-logging") - .help("Keep the log file after quitting Alacritty"), - ) - .arg( - Arg::with_name("dimensions") - .long("dimensions") - .short("d") - .value_names(&["columns", "lines"]) - .help( - "Defines the window dimensions. Falls back to size specified by window \ - manager if set to 0x0 [default: 0x0]", - ), - ) - .arg( - Arg::with_name("position") - .long("position") - .allow_hyphen_values(true) - .value_names(&["x-pos", "y-pos"]) - .help( - "Defines the window position. Falls back to position specified by window \ - manager if unset [default: unset]", - ), - ) - .arg( - Arg::with_name("title") - .long("title") - .short("t") - .takes_value(true) - .help(&format!("Defines the window title [default: {}]", DEFAULT_NAME)), - ) - .arg( - Arg::with_name("class") - .long("class") - .takes_value(true) - .help(&format!("Defines window class on Linux [default: {}]", DEFAULT_NAME)), - ) - .arg( - Arg::with_name("q") - .short("q") - .multiple(true) - .conflicts_with("v") - .help("Reduces the level of verbosity (the min level is -qq)"), - ) - .arg( - Arg::with_name("v") - .short("v") - .multiple(true) - .conflicts_with("q") - .help("Increases the level of verbosity (the max level is -vvv)"), - ) - .arg( - Arg::with_name("working-directory") - .long("working-directory") - .takes_value(true) - .help("Start the shell in the specified working directory"), - ) - .arg(Arg::with_name("config-file").long("config-file").takes_value(true).help( - "Specify alternative configuration file [default: \ - $XDG_CONFIG_HOME/alacritty/alacritty.yml]", - )) - .arg( - Arg::with_name("command") - .long("command") - .short("e") - .multiple(true) - .takes_value(true) - .min_values(1) - .allow_hyphen_values(true) - .help("Command and args to execute (must be last argument)"), - ) - .get_matches(); - - if matches.is_present("ref-test") { - options.ref_test = true; - } - - if matches.is_present("print-events") { - options.print_events = true; - } - - if matches.is_present("live-config-reload") { - options.live_config_reload = Some(true); - } else if matches.is_present("no-live-config-reload") { - options.live_config_reload = Some(false); - } - - if matches.is_present("persistent-logging") { - options.persistent_logging = true; - } - - if let Some(mut dimensions) = matches.values_of("dimensions") { - let width = dimensions.next().map(|w| w.parse().map(Column)); - let height = dimensions.next().map(|h| h.parse().map(Line)); - if let (Some(Ok(width)), Some(Ok(height))) = (width, height) { - options.dimensions = Some(Dimensions::new(width, height)); - } - } - - if let Some(mut position) = matches.values_of("position") { - let x = position.next().map(str::parse); - let y = position.next().map(str::parse); - if let (Some(Ok(x)), Some(Ok(y))) = (x, y) { - options.position = Some(Delta { x, y }); - } - } - - options.class = matches.value_of("class").map(ToOwned::to_owned); - options.title = matches.value_of("title").map(ToOwned::to_owned); - - match matches.occurrences_of("q") { - 0 => {}, - 1 => options.log_level = log::LevelFilter::Error, - 2 | _ => options.log_level = log::LevelFilter::Off, - } - - match matches.occurrences_of("v") { - 0 if !options.print_events => {}, - 0 | 1 => options.log_level = log::LevelFilter::Info, - 2 => options.log_level = log::LevelFilter::Debug, - 3 | _ => options.log_level = log::LevelFilter::Trace, - } - - if let Some(dir) = matches.value_of("working-directory") { - options.working_dir = Some(PathBuf::from(dir.to_string())); - } - - if let Some(path) = matches.value_of("config-file") { - options.config = Some(PathBuf::from(path.to_string())); - } - - if let Some(mut args) = matches.values_of("command") { - // The following unwrap is guaranteed to succeed. - // If 'command' exists it must also have a first item since - // Arg::min_values(1) is set. - let command = String::from(args.next().unwrap()); - let args = args.map(String::from).collect(); - options.command = Some(Shell::new_with_args(command, args)); - } - - options +/// Options specified on the command line +pub struct Options { + pub live_config_reload: Option, + pub print_events: bool, + pub ref_test: bool, + pub dimensions: Option, + pub position: Option>, + pub title: Option, + pub class: Option, + pub log_level: LevelFilter, + pub command: Option>, + pub working_dir: Option, + pub config: Option, + pub persistent_logging: bool, +} + +impl Default for Options { + fn default() -> Options { + Options { + live_config_reload: None, + print_events: false, + ref_test: false, + dimensions: None, + position: None, + title: None, + class: None, + log_level: LevelFilter::Warn, + command: None, + working_dir: None, + config: None, + persistent_logging: false, + } + } +} + +impl Options { + /// Build `Options` from command line arguments. + pub fn new() -> Self { + let mut options = Options::default(); + + let version_string = format!("{} ({})", crate_version!(), env!("GIT_HASH")); + + let matches = App::new(crate_name!()) + .version(version_string.as_str()) + .author(crate_authors!("\n")) + .about(crate_description!()) + .arg(Arg::with_name("ref-test").long("ref-test").help("Generates ref test")) + .arg( + Arg::with_name("live-config-reload") + .long("live-config-reload") + .help("Enable automatic config reloading"), + ) + .arg( + Arg::with_name("no-live-config-reload") + .long("no-live-config-reload") + .help("Disable automatic config reloading") + .conflicts_with("live-config-reload"), + ) + .arg( + Arg::with_name("print-events") + .long("print-events") + .help("Print all events to stdout"), + ) + .arg( + Arg::with_name("persistent-logging") + .long("persistent-logging") + .help("Keep the log file after quitting Alacritty"), + ) + .arg( + Arg::with_name("dimensions") + .long("dimensions") + .short("d") + .value_names(&["columns", "lines"]) + .help( + "Defines the window dimensions. Falls back to size specified by window \ + manager if set to 0x0 [default: 0x0]", + ), + ) + .arg( + Arg::with_name("position") + .long("position") + .allow_hyphen_values(true) + .value_names(&["x-pos", "y-pos"]) + .help( + "Defines the window position. Falls back to position specified by window \ + manager if unset [default: unset]", + ), + ) + .arg( + Arg::with_name("title") + .long("title") + .short("t") + .takes_value(true) + .help(&format!("Defines the window title [default: {}]", DEFAULT_NAME)), + ) + .arg( + Arg::with_name("class") + .long("class") + .takes_value(true) + .help(&format!("Defines window class on Linux [default: {}]", DEFAULT_NAME)), + ) + .arg( + Arg::with_name("q") + .short("q") + .multiple(true) + .conflicts_with("v") + .help("Reduces the level of verbosity (the min level is -qq)"), + ) + .arg( + Arg::with_name("v") + .short("v") + .multiple(true) + .conflicts_with("q") + .help("Increases the level of verbosity (the max level is -vvv)"), + ) + .arg( + Arg::with_name("working-directory") + .long("working-directory") + .takes_value(true) + .help("Start the shell in the specified working directory"), + ) + .arg(Arg::with_name("config-file").long("config-file").takes_value(true).help( + "Specify alternative configuration file [default: \ + $XDG_CONFIG_HOME/alacritty/alacritty.yml]", + )) + .arg( + Arg::with_name("command") + .long("command") + .short("e") + .multiple(true) + .takes_value(true) + .min_values(1) + .allow_hyphen_values(true) + .help("Command and args to execute (must be last argument)"), + ) + .get_matches(); + + if matches.is_present("ref-test") { + options.ref_test = true; + } + + if matches.is_present("print-events") { + options.print_events = true; + } + + if matches.is_present("live-config-reload") { + options.live_config_reload = Some(true); + } else if matches.is_present("no-live-config-reload") { + options.live_config_reload = Some(false); + } + + if matches.is_present("persistent-logging") { + options.persistent_logging = true; + } + + if let Some(mut dimensions) = matches.values_of("dimensions") { + let width = dimensions.next().map(|w| w.parse().map(Column)); + let height = dimensions.next().map(|h| h.parse().map(Line)); + if let (Some(Ok(width)), Some(Ok(height))) = (width, height) { + options.dimensions = Some(Dimensions::new(width, height)); + } + } + + if let Some(mut position) = matches.values_of("position") { + let x = position.next().map(str::parse); + let y = position.next().map(str::parse); + if let (Some(Ok(x)), Some(Ok(y))) = (x, y) { + options.position = Some(Delta { x, y }); + } + } + + options.class = matches.value_of("class").map(ToOwned::to_owned); + options.title = matches.value_of("title").map(ToOwned::to_owned); + + match matches.occurrences_of("q") { + 0 => {}, + 1 => options.log_level = LevelFilter::Error, + 2 | _ => options.log_level = LevelFilter::Off, + } + + match matches.occurrences_of("v") { + 0 if !options.print_events => options.log_level = LevelFilter::Warn, + 0 | 1 => options.log_level = LevelFilter::Info, + 2 => options.log_level = LevelFilter::Debug, + 3 | _ => options.log_level = LevelFilter::Trace, + } + + if let Some(dir) = matches.value_of("working-directory") { + options.working_dir = Some(PathBuf::from(dir.to_string())); + } + + if let Some(path) = matches.value_of("config-file") { + options.config = Some(PathBuf::from(path.to_string())); + } + + if let Some(mut args) = matches.values_of("command") { + // The following unwrap is guaranteed to succeed. + // If 'command' exists it must also have a first item since + // Arg::min_values(1) is set. + let command = String::from(args.next().unwrap()); + let args = args.map(String::from).collect(); + options.command = Some(Shell::new_with_args(command, args)); + } + + options + } + + pub fn config_path(&self) -> Option> { + self.config.as_ref().map(|p| Cow::Borrowed(p.as_path())) + } + + pub fn into_config(self, mut config: Config) -> Config { + config.set_live_config_reload( + self.live_config_reload.unwrap_or_else(|| config.live_config_reload()), + ); + config.set_working_directory( + self.working_dir.or_else(|| config.working_directory().to_owned()), + ); + config.shell = self.command.or(config.shell); + + config.window.dimensions = self.dimensions.unwrap_or(config.window.dimensions); + config.window.position = self.position.or(config.window.position); + config.window.title = self.title.or(config.window.title); + config.window.class = self.class.or(config.window.class); + + config.set_dynamic_title(config.dynamic_title() && config.window.title.is_none()); + + config.debug.print_events = self.print_events || config.debug.print_events; + config.debug.log_level = max(config.debug.log_level, self.log_level); + config.debug.ref_test = self.ref_test || config.debug.ref_test; + + if config.debug.print_events { + config.debug.log_level = max(config.debug.log_level, LevelFilter::Info); + } + + config + } +} + +#[cfg(test)] +mod test { + use alacritty_terminal::config::{Config, DEFAULT_ALACRITTY_CONFIG}; + + use crate::cli::Options; + + #[test] + fn dynamic_title_ignoring_options_by_default() { + let config: Config = + ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); + let old_dynamic_title = config.dynamic_title(); + + let config = Options::default().into_config(config); + + assert_eq!(old_dynamic_title, config.dynamic_title()); + } + + #[test] + fn dynamic_title_overridden_by_options() { + let config: Config = + ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); + + let mut options = Options::default(); + options.title = Some("foo".to_owned()); + let config = options.into_config(config); + + assert!(!config.dynamic_title()); + } + + #[test] + fn dynamic_title_overridden_by_config() { + let mut config: Config = + ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); + + config.window.title = Some("foo".to_owned()); + let config = Options::default().into_config(config); + + assert!(!config.dynamic_title()); + } } diff --git a/alacritty/src/config.rs b/alacritty/src/config.rs new file mode 100644 index 0000000..ea487ec --- /dev/null +++ b/alacritty/src/config.rs @@ -0,0 +1,210 @@ +use std::borrow::Cow; +use std::env; +use std::fs::File; +use std::io::{self, Read, Write}; +use std::path::{Path, PathBuf}; + +#[cfg(windows)] +use dirs; +use log::{error, warn}; +use serde_yaml; +#[cfg(not(windows))] +use xdg; + +use alacritty_terminal::config::{Config, DEFAULT_ALACRITTY_CONFIG}; + +pub const SOURCE_FILE_PATH: &str = file!(); + +/// Result from config loading +pub type Result = ::std::result::Result; + +/// Errors occurring during config loading +#[derive(Debug)] +pub enum Error { + /// Config file not found + NotFound, + + /// Couldn't read $HOME environment variable + ReadingEnvHome(env::VarError), + + /// io error reading file + Io(io::Error), + + /// Not valid yaml or missing parameters + Yaml(serde_yaml::Error), +} + +impl ::std::error::Error for Error { + fn cause(&self) -> Option<&dyn (::std::error::Error)> { + match *self { + Error::NotFound => None, + Error::ReadingEnvHome(ref err) => Some(err), + Error::Io(ref err) => Some(err), + Error::Yaml(ref err) => Some(err), + } + } + + fn description(&self) -> &str { + match *self { + Error::NotFound => "Couldn't locate config file", + Error::ReadingEnvHome(ref err) => err.description(), + Error::Io(ref err) => err.description(), + Error::Yaml(ref err) => err.description(), + } + } +} + +impl ::std::fmt::Display for Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + match *self { + Error::NotFound => write!(f, "{}", ::std::error::Error::description(self)), + Error::ReadingEnvHome(ref err) => { + write!(f, "Couldn't read $HOME environment variable: {}", err) + }, + Error::Io(ref err) => write!(f, "Error reading config file: {}", err), + Error::Yaml(ref err) => write!(f, "Problem with config: {}", err), + } + } +} + +impl From for Error { + fn from(val: env::VarError) -> Error { + Error::ReadingEnvHome(val) + } +} + +impl From for Error { + fn from(val: io::Error) -> Error { + if val.kind() == io::ErrorKind::NotFound { + Error::NotFound + } else { + Error::Io(val) + } + } +} + +impl From for Error { + fn from(val: serde_yaml::Error) -> Error { + Error::Yaml(val) + } +} + +/// Get the location of the first found default config file paths +/// according to the following order: +/// +/// 1. $XDG_CONFIG_HOME/alacritty/alacritty.yml +/// 2. $XDG_CONFIG_HOME/alacritty.yml +/// 3. $HOME/.config/alacritty/alacritty.yml +/// 4. $HOME/.alacritty.yml +#[cfg(not(windows))] +pub fn installed_config<'a>() -> Option> { + // Try using XDG location by default + xdg::BaseDirectories::with_prefix("alacritty") + .ok() + .and_then(|xdg| xdg.find_config_file("alacritty.yml")) + .or_else(|| { + xdg::BaseDirectories::new() + .ok() + .and_then(|fallback| fallback.find_config_file("alacritty.yml")) + }) + .or_else(|| { + if let Ok(home) = env::var("HOME") { + // Fallback path: $HOME/.config/alacritty/alacritty.yml + let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml"); + if fallback.exists() { + return Some(fallback); + } + // Fallback path: $HOME/.alacritty.yml + let fallback = PathBuf::from(&home).join(".alacritty.yml"); + if fallback.exists() { + return Some(fallback); + } + } + None + }) + .map(Into::into) +} + +#[cfg(windows)] +pub fn installed_config<'a>() -> Option> { + dirs::config_dir() + .map(|path| path.join("alacritty\\alacritty.yml")) + .filter(|new| new.exists()) + .map(Cow::from) +} + +#[cfg(not(windows))] +pub fn write_defaults() -> io::Result> { + let path = xdg::BaseDirectories::with_prefix("alacritty") + .map_err(|err| io::Error::new(io::ErrorKind::NotFound, err.to_string().as_str())) + .and_then(|p| p.place_config_file("alacritty.yml"))?; + + File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?; + + Ok(path.into()) +} + +#[cfg(windows)] +pub fn write_defaults() -> io::Result> { + let mut path = dirs::config_dir().ok_or_else(|| { + io::Error::new(io::ErrorKind::NotFound, "Couldn't find profile directory") + })?; + + path = path.join("alacritty/alacritty.yml"); + + std::fs::create_dir_all(path.parent().unwrap())?; + + File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?; + + Ok(path.into()) +} + +pub fn load_from(path: PathBuf) -> Config { + let mut config = reload_from(&path).unwrap_or_else(|_| Config::default()); + config.config_path = Some(path); + config +} + +pub fn reload_from(path: &PathBuf) -> Result { + match read_config(path) { + Ok(config) => Ok(config), + Err(err) => { + error!("Unable to load config {:?}: {}", path, err); + Err(err) + }, + } +} + +fn read_config(path: &PathBuf) -> Result { + let mut contents = String::new(); + File::open(path)?.read_to_string(&mut contents)?; + + // Prevent parsing error with empty string + if contents.is_empty() { + return Ok(Config::default()); + } + + let config = serde_yaml::from_str(&contents)?; + + print_deprecation_warnings(&config); + + Ok(config) +} + +fn print_deprecation_warnings(config: &Config) { + if config.window.start_maximized.is_some() { + warn!( + "Config window.start_maximized is deprecated; please use window.startup_mode instead" + ); + } + + if config.render_timer.is_some() { + warn!("Config render_timer is deprecated; please use debug.render_timer instead"); + } + + if config.persistent_logging.is_some() { + warn!( + "Config persistent_logging is deprecated; please use debug.persistent_logging instead" + ); + } +} diff --git a/alacritty/src/logging.rs b/alacritty/src/logging.rs index 0b67440..d4cb70c 100644 --- a/alacritty/src/logging.rs +++ b/alacritty/src/logging.rs @@ -29,23 +29,26 @@ use crossbeam_channel::Sender; use log::{self, Level}; use time; -use alacritty_terminal::config::Options; use alacritty_terminal::message_bar::Message; use alacritty_terminal::term::color; +use crate::cli::Options; + const ALACRITTY_LOG_ENV: &str = "ALACRITTY_LOG"; pub fn initialize( options: &Options, message_tx: Sender, ) -> Result, log::SetLoggerError> { + log::set_max_level(options.log_level); + // Use env_logger if RUST_LOG environment variable is defined. Otherwise, // use the alacritty-only logger. if ::std::env::var("RUST_LOG").is_ok() { ::env_logger::try_init()?; Ok(None) } else { - let logger = Logger::new(options.log_level, message_tx); + let logger = Logger::new(message_tx); let path = logger.file_path(); log::set_boxed_logger(Box::new(logger))?; Ok(path) @@ -53,22 +56,17 @@ pub fn initialize( } pub struct Logger { - level: log::LevelFilter, logfile: Mutex, stdout: Mutex>, message_tx: Sender, } impl Logger { - // False positive, see: https://github.com/rust-lang-nursery/rust-clippy/issues/734 - #[allow(clippy::new_ret_no_self)] - fn new(level: log::LevelFilter, message_tx: Sender) -> Self { - log::set_max_level(level); - + fn new(message_tx: Sender) -> Self { let logfile = Mutex::new(OnDemandLogFile::new()); let stdout = Mutex::new(LineWriter::new(io::stdout())); - Logger { level, logfile, stdout, message_tx } + Logger { logfile, stdout, message_tx } } fn file_path(&self) -> Option { @@ -82,7 +80,7 @@ impl Logger { impl log::Log for Logger { fn enabled(&self, metadata: &log::Metadata<'_>) -> bool { - metadata.level() <= self.level + metadata.level() <= log::max_level() } fn log(&self, record: &log::Record<'_>) { diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs index 24f2d6c..54533d7 100644 --- a/alacritty/src/main.rs +++ b/alacritty/src/main.rs @@ -42,7 +42,7 @@ use std::env; use std::os::unix::io::AsRawFd; use alacritty_terminal::clipboard::Clipboard; -use alacritty_terminal::config::{self, Config, Options, Monitor}; +use alacritty_terminal::config::{Config, Monitor}; use alacritty_terminal::display::Display; use alacritty_terminal::event_loop::{self, EventLoop, Msg}; #[cfg(target_os = "macos")] @@ -56,8 +56,11 @@ use alacritty_terminal::util::fmt::Red; use alacritty_terminal::{die, event}; mod cli; +mod config; mod logging; +use crate::cli::Options; + fn main() { panic::attach_handler(); @@ -70,7 +73,7 @@ fn main() { } // Load command line options - let options = cli::options(); + let options = Options::new(); // Setup storage for message UI let message_buffer = MessageBuffer::new(); @@ -83,15 +86,19 @@ fn main() { // If the file is a command line argument, we won't write a generated default file let config_path = options .config_path() - .or_else(Config::installed_config) - .or_else(|| Config::write_defaults().ok()) + .or_else(config::installed_config) + .or_else(|| config::write_defaults().ok()) .map(|path| path.to_path_buf()); let config = if let Some(path) = config_path { - Config::load_from(path).update_dynamic_title(&options) + config::load_from(path) } else { error!("Unable to write the default config"); Config::default() }; + let config = options.into_config(config); + + // Update the log level from config + log::set_max_level(config.debug.log_level); // Switch to home directory #[cfg(target_os = "macos")] @@ -101,10 +108,10 @@ fn main() { locale::set_locale_environment(); // Store if log file should be deleted before moving config - let persistent_logging = options.persistent_logging || config.persistent_logging(); + let persistent_logging = config.persistent_logging(); // Run alacritty - if let Err(err) = run(config, &options, message_buffer) { + if let Err(err) = run(config, message_buffer) { die!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", Red(err)); } @@ -120,13 +127,9 @@ fn main() { /// /// Creates a window, the terminal state, pty, I/O event loop, input processor, /// config change monitor, and runs the main display loop. -fn run( - mut config: Config, - options: &Options, - message_buffer: MessageBuffer, -) -> Result<(), Box> { +fn run(config: Config, message_buffer: MessageBuffer) -> Result<(), Box> { info!("Welcome to Alacritty"); - if let Some(config_path) = config.path() { + if let Some(config_path) = &config.config_path { info!("Configuration loaded from {:?}", config_path.display()); }; @@ -136,7 +139,7 @@ fn run( // Create a display. // // The display manages a window and can draw the terminal - let mut display = Display::new(&config, options)?; + let mut display = Display::new(&config)?; info!("PTY Dimensions: {:?} x {:?}", display.size().lines(), display.size().cols()); @@ -162,7 +165,7 @@ fn run( // The pty forks a process to run the shell on the slave side of the // pseudoterminal. A file descriptor for the master side is retained for // reading/writing to the shell. - let pty = tty::new(&config, options, &display.size(), window_id); + let pty = tty::new(&config, &display.size(), window_id); // Get a reference to something that we can resize // @@ -181,7 +184,7 @@ fn run( // synchronized since the I/O loop updates the state, and the display // consumes it periodically. let event_loop = - EventLoop::new(Arc::clone(&terminal), display.notifier(), pty, options.ref_test); + EventLoop::new(Arc::clone(&terminal), display.notifier(), pty, config.debug.ref_test); // The event loop channel allows write requests from the event processor // to be sent to the loop and ultimately written to the pty. @@ -193,9 +196,7 @@ fn run( let mut processor = event::Processor::new( event_loop::Notifier(event_loop.channel()), display.resize_channel(), - options, &config, - options.ref_test, display.size().to_owned(), ); @@ -203,14 +204,10 @@ fn run( // // The monitor watches the config file for changes and reloads it. Pending // config changes are processed in the main loop. - let config_monitor = match (options.live_config_reload, config.live_config_reload()) { - // Start monitor if CLI flag says yes - (Some(true), _) | - // Or if no CLI flag was passed and the config says yes - (None, true) => config.path() - .map(|path| config::Monitor::new(path, display.notifier())), - // Otherwise, don't start the monitor - _ => None, + let config_monitor = if config.live_config_reload() { + config.config_path.as_ref().map(|path| Monitor::new(path, display.notifier())) + } else { + None }; // Kick off the I/O thread @@ -228,8 +225,7 @@ fn run( // Clear old config messages from bar terminal_lock.message_buffer_mut().remove_topic(config::SOURCE_FILE_PATH); - if let Ok(new_config) = Config::reload_from(path) { - config = new_config.update_dynamic_title(options); + if let Ok(config) = config::reload_from(path) { display.update_config(&config); processor.update_config(&config); terminal_lock.update_config(&config); diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml index f038222..032191b 100644 --- a/alacritty_terminal/Cargo.toml +++ b/alacritty_terminal/Cargo.toml @@ -23,7 +23,6 @@ serde_yaml = "0.8" vte = "0.3" mio = "0.6" mio-extras = "2" -xdg = "2" log = "0.4" fnv = "1" unicode-width = "0.1" @@ -48,7 +47,6 @@ mio-named-pipes = "0.1" miow = "0.3" dunce = "1.0" winapi = { version = "0.3.7", features = ["impl-default", "winuser", "synchapi", "roerrorapi", "winerror", "wincon", "wincontypes"]} -dirs = "1.0" widestring = "0.4" mio-anonymous-pipes = "0.1" diff --git a/alacritty_terminal/src/clipboard.rs b/alacritty_terminal/src/clipboard.rs index a761dcb..a310c99 100644 --- a/alacritty_terminal/src/clipboard.rs +++ b/alacritty_terminal/src/clipboard.rs @@ -89,7 +89,7 @@ impl Clipboard { Err(err) => { debug!("Unable to load text from clipboard: {}", err); String::new() - } + }, Ok(text) => text, } } diff --git a/alacritty_terminal/src/config/bindings.rs b/alacritty_terminal/src/config/bindings.rs index 7e69182..010c0ea 100644 --- a/alacritty_terminal/src/config/bindings.rs +++ b/alacritty_terminal/src/config/bindings.rs @@ -11,10 +11,16 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use glutin::{ModifiersState, MouseButton}; -use super::Key; -use crate::input::{Action, KeyBinding, MouseBinding}; +use std::fmt; +use std::str::FromStr; + +use glutin::{ModifiersState, MouseButton}; +use serde::de::Error as SerdeError; +use serde::de::{self, MapAccess, Unexpected, Visitor}; +use serde::{Deserialize, Deserializer}; + +use crate::input::{Action, Binding, KeyBinding, MouseBinding}; use crate::term::TermMode; macro_rules! bindings { @@ -231,3 +237,748 @@ pub fn platform_key_bindings() -> Vec { pub fn platform_key_bindings() -> Vec { vec![] } + +#[derive(Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum Key { + Scancode(u32), + Key1, + Key2, + Key3, + Key4, + Key5, + Key6, + Key7, + Key8, + Key9, + Key0, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + Escape, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + Snapshot, + Scroll, + Pause, + Insert, + Home, + Delete, + End, + PageDown, + PageUp, + Left, + Up, + Right, + Down, + Back, + Return, + Space, + Compose, + Numlock, + Numpad0, + Numpad1, + Numpad2, + Numpad3, + Numpad4, + Numpad5, + Numpad6, + Numpad7, + Numpad8, + Numpad9, + AbntC1, + AbntC2, + Add, + Apostrophe, + Apps, + At, + Ax, + Backslash, + Calculator, + Capital, + Colon, + Comma, + Convert, + Decimal, + Divide, + Equals, + Grave, + Kana, + Kanji, + LAlt, + LBracket, + LControl, + LShift, + LWin, + Mail, + MediaSelect, + MediaStop, + Minus, + Multiply, + Mute, + MyComputer, + NavigateForward, + NavigateBackward, + NextTrack, + NoConvert, + NumpadComma, + NumpadEnter, + NumpadEquals, + OEM102, + Period, + PlayPause, + Power, + PrevTrack, + RAlt, + RBracket, + RControl, + RShift, + RWin, + Semicolon, + Slash, + Sleep, + Stop, + Subtract, + Sysrq, + Tab, + Underline, + Unlabeled, + VolumeDown, + VolumeUp, + Wake, + WebBack, + WebFavorites, + WebForward, + WebHome, + WebRefresh, + WebSearch, + WebStop, + Yen, + Caret, + Copy, + Paste, + Cut, +} + +impl Key { + pub fn from_glutin_input(key: ::glutin::VirtualKeyCode) -> Self { + use glutin::VirtualKeyCode::*; + // Thank you, vim macros and regex! + match key { + Key1 => Key::Key1, + Key2 => Key::Key2, + Key3 => Key::Key3, + Key4 => Key::Key4, + Key5 => Key::Key5, + Key6 => Key::Key6, + Key7 => Key::Key7, + Key8 => Key::Key8, + Key9 => Key::Key9, + Key0 => Key::Key0, + A => Key::A, + B => Key::B, + C => Key::C, + D => Key::D, + E => Key::E, + F => Key::F, + G => Key::G, + H => Key::H, + I => Key::I, + J => Key::J, + K => Key::K, + L => Key::L, + M => Key::M, + N => Key::N, + O => Key::O, + P => Key::P, + Q => Key::Q, + R => Key::R, + S => Key::S, + T => Key::T, + U => Key::U, + V => Key::V, + W => Key::W, + X => Key::X, + Y => Key::Y, + Z => Key::Z, + Escape => Key::Escape, + F1 => Key::F1, + F2 => Key::F2, + F3 => Key::F3, + F4 => Key::F4, + F5 => Key::F5, + F6 => Key::F6, + F7 => Key::F7, + F8 => Key::F8, + F9 => Key::F9, + F10 => Key::F10, + F11 => Key::F11, + F12 => Key::F12, + F13 => Key::F13, + F14 => Key::F14, + F15 => Key::F15, + F16 => Key::F16, + F17 => Key::F17, + F18 => Key::F18, + F19 => Key::F19, + F20 => Key::F20, + F21 => Key::F21, + F22 => Key::F22, + F23 => Key::F23, + F24 => Key::F24, + Snapshot => Key::Snapshot, + Scroll => Key::Scroll, + Pause => Key::Pause, + Insert => Key::Insert, + Home => Key::Home, + Delete => Key::Delete, + End => Key::End, + PageDown => Key::PageDown, + PageUp => Key::PageUp, + Left => Key::Left, + Up => Key::Up, + Right => Key::Right, + Down => Key::Down, + Back => Key::Back, + Return => Key::Return, + Space => Key::Space, + Compose => Key::Compose, + Numlock => Key::Numlock, + Numpad0 => Key::Numpad0, + Numpad1 => Key::Numpad1, + Numpad2 => Key::Numpad2, + Numpad3 => Key::Numpad3, + Numpad4 => Key::Numpad4, + Numpad5 => Key::Numpad5, + Numpad6 => Key::Numpad6, + Numpad7 => Key::Numpad7, + Numpad8 => Key::Numpad8, + Numpad9 => Key::Numpad9, + AbntC1 => Key::AbntC1, + AbntC2 => Key::AbntC2, + Add => Key::Add, + Apostrophe => Key::Apostrophe, + Apps => Key::Apps, + At => Key::At, + Ax => Key::Ax, + Backslash => Key::Backslash, + Calculator => Key::Calculator, + Capital => Key::Capital, + Colon => Key::Colon, + Comma => Key::Comma, + Convert => Key::Convert, + Decimal => Key::Decimal, + Divide => Key::Divide, + Equals => Key::Equals, + Grave => Key::Grave, + Kana => Key::Kana, + Kanji => Key::Kanji, + LAlt => Key::LAlt, + LBracket => Key::LBracket, + LControl => Key::LControl, + LShift => Key::LShift, + LWin => Key::LWin, + Mail => Key::Mail, + MediaSelect => Key::MediaSelect, + MediaStop => Key::MediaStop, + Minus => Key::Minus, + Multiply => Key::Multiply, + Mute => Key::Mute, + MyComputer => Key::MyComputer, + NavigateForward => Key::NavigateForward, + NavigateBackward => Key::NavigateBackward, + NextTrack => Key::NextTrack, + NoConvert => Key::NoConvert, + NumpadComma => Key::NumpadComma, + NumpadEnter => Key::NumpadEnter, + NumpadEquals => Key::NumpadEquals, + OEM102 => Key::OEM102, + Period => Key::Period, + PlayPause => Key::PlayPause, + Power => Key::Power, + PrevTrack => Key::PrevTrack, + RAlt => Key::RAlt, + RBracket => Key::RBracket, + RControl => Key::RControl, + RShift => Key::RShift, + RWin => Key::RWin, + Semicolon => Key::Semicolon, + Slash => Key::Slash, + Sleep => Key::Sleep, + Stop => Key::Stop, + Subtract => Key::Subtract, + Sysrq => Key::Sysrq, + Tab => Key::Tab, + Underline => Key::Underline, + Unlabeled => Key::Unlabeled, + VolumeDown => Key::VolumeDown, + VolumeUp => Key::VolumeUp, + Wake => Key::Wake, + WebBack => Key::WebBack, + WebFavorites => Key::WebFavorites, + WebForward => Key::WebForward, + WebHome => Key::WebHome, + WebRefresh => Key::WebRefresh, + WebSearch => Key::WebSearch, + WebStop => Key::WebStop, + Yen => Key::Yen, + Caret => Key::Caret, + Copy => Key::Copy, + Paste => Key::Paste, + Cut => Key::Cut, + } + } +} + +struct ModeWrapper { + pub mode: TermMode, + pub not_mode: TermMode, +} + +impl<'a> Deserialize<'a> for ModeWrapper { + fn deserialize(deserializer: D) -> ::std::result::Result + where + D: Deserializer<'a>, + { + struct ModeVisitor; + + impl<'a> Visitor<'a> for ModeVisitor { + type Value = ModeWrapper; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Combination of AppCursor | AppKeypad, possibly with negation (~)") + } + + fn visit_str(self, value: &str) -> ::std::result::Result + where + E: de::Error, + { + let mut res = ModeWrapper { mode: TermMode::empty(), not_mode: TermMode::empty() }; + + for modifier in value.split('|') { + match modifier.trim().to_lowercase().as_str() { + "appcursor" => res.mode |= TermMode::APP_CURSOR, + "~appcursor" => res.not_mode |= TermMode::APP_CURSOR, + "appkeypad" => res.mode |= TermMode::APP_KEYPAD, + "~appkeypad" => res.not_mode |= TermMode::APP_KEYPAD, + "~alt" => res.not_mode |= TermMode::ALT_SCREEN, + "alt" => res.mode |= TermMode::ALT_SCREEN, + _ => error!("Unknown mode {:?}", modifier), + } + } + + Ok(res) + } + } + deserializer.deserialize_str(ModeVisitor) + } +} + +struct MouseButtonWrapper(MouseButton); + +impl MouseButtonWrapper { + fn into_inner(self) -> MouseButton { + self.0 + } +} + +impl<'a> Deserialize<'a> for MouseButtonWrapper { + fn deserialize(deserializer: D) -> ::std::result::Result + where + D: Deserializer<'a>, + { + struct MouseButtonVisitor; + + impl<'a> Visitor<'a> for MouseButtonVisitor { + type Value = MouseButtonWrapper; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Left, Right, Middle, or a number") + } + + fn visit_str(self, value: &str) -> ::std::result::Result + where + E: de::Error, + { + match value { + "Left" => Ok(MouseButtonWrapper(MouseButton::Left)), + "Right" => Ok(MouseButtonWrapper(MouseButton::Right)), + "Middle" => Ok(MouseButtonWrapper(MouseButton::Middle)), + _ => { + if let Ok(index) = u8::from_str(value) { + Ok(MouseButtonWrapper(MouseButton::Other(index))) + } else { + Err(E::invalid_value(Unexpected::Str(value), &self)) + } + }, + } + } + } + + deserializer.deserialize_str(MouseButtonVisitor) + } +} + +/// Bindings are deserialized into a `RawBinding` before being parsed as a +/// `KeyBinding` or `MouseBinding`. +#[derive(PartialEq, Eq)] +struct RawBinding { + key: Option, + mouse: Option, + mods: ModifiersState, + mode: TermMode, + notmode: TermMode, + action: Action, +} + +impl RawBinding { + fn into_mouse_binding(self) -> ::std::result::Result { + if let Some(mouse) = self.mouse { + Ok(Binding { + trigger: mouse, + mods: self.mods, + action: self.action, + mode: self.mode, + notmode: self.notmode, + }) + } else { + Err(self) + } + } + + fn into_key_binding(self) -> ::std::result::Result { + if let Some(key) = self.key { + Ok(KeyBinding { + trigger: key, + mods: self.mods, + action: self.action, + mode: self.mode, + notmode: self.notmode, + }) + } else { + Err(self) + } + } +} + +impl<'a> Deserialize<'a> for RawBinding { + fn deserialize(deserializer: D) -> ::std::result::Result + where + D: Deserializer<'a>, + { + enum Field { + Key, + Mods, + Mode, + Action, + Chars, + Mouse, + Command, + } + + impl<'a> Deserialize<'a> for Field { + fn deserialize(deserializer: D) -> ::std::result::Result + where + D: Deserializer<'a>, + { + struct FieldVisitor; + + static FIELDS: &'static [&'static str] = + &["key", "mods", "mode", "action", "chars", "mouse", "command"]; + + impl<'a> Visitor<'a> for FieldVisitor { + type Value = Field; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("binding fields") + } + + fn visit_str(self, value: &str) -> ::std::result::Result + where + E: de::Error, + { + match value { + "key" => Ok(Field::Key), + "mods" => Ok(Field::Mods), + "mode" => Ok(Field::Mode), + "action" => Ok(Field::Action), + "chars" => Ok(Field::Chars), + "mouse" => Ok(Field::Mouse), + "command" => Ok(Field::Command), + _ => Err(E::unknown_field(value, FIELDS)), + } + } + } + + deserializer.deserialize_str(FieldVisitor) + } + } + + struct RawBindingVisitor; + impl<'a> Visitor<'a> for RawBindingVisitor { + type Value = RawBinding; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("binding specification") + } + + fn visit_map(self, mut map: V) -> ::std::result::Result + where + V: MapAccess<'a>, + { + let mut mods: Option = None; + let mut key: Option = None; + let mut chars: Option = None; + let mut action: Option = None; + let mut mode: Option = None; + let mut not_mode: Option = None; + let mut mouse: Option = None; + let mut command: Option = None; + + use ::serde::de::Error; + + while let Some(struct_key) = map.next_key::()? { + match struct_key { + Field::Key => { + if key.is_some() { + return Err(::duplicate_field("key")); + } + + let val = map.next_value::()?; + if val.is_u64() { + let scancode = val.as_u64().unwrap(); + if scancode > u64::from(::std::u32::MAX) { + return Err(::custom(format!( + "Invalid key binding, scancode too big: {}", + scancode + ))); + } + key = Some(Key::Scancode(scancode as u32)); + } else { + let k = Key::deserialize(val).map_err(V::Error::custom)?; + key = Some(k); + } + }, + Field::Mods => { + if mods.is_some() { + return Err(::duplicate_field("mods")); + } + + mods = Some(map.next_value::()?.into_inner()); + }, + Field::Mode => { + if mode.is_some() { + return Err(::duplicate_field("mode")); + } + + let mode_deserializer = map.next_value::()?; + mode = Some(mode_deserializer.mode); + not_mode = Some(mode_deserializer.not_mode); + }, + Field::Action => { + if action.is_some() { + return Err(::duplicate_field("action")); + } + + action = Some(map.next_value::()?); + }, + Field::Chars => { + if chars.is_some() { + return Err(::duplicate_field("chars")); + } + + chars = Some(map.next_value()?); + }, + Field::Mouse => { + if chars.is_some() { + return Err(::duplicate_field("mouse")); + } + + mouse = Some(map.next_value::()?.into_inner()); + }, + Field::Command => { + if command.is_some() { + return Err(::duplicate_field("command")); + } + + command = Some(map.next_value::()?); + }, + } + } + + let action = match (action, chars, command) { + (Some(action), None, None) => action, + (None, Some(chars), None) => Action::Esc(chars), + (None, None, Some(cmd)) => match cmd { + CommandWrapper::Just(program) => Action::Command(program, vec![]), + CommandWrapper::WithArgs { program, args } => { + Action::Command(program, args) + }, + }, + (None, None, None) => { + return Err(V::Error::custom("must specify chars, action or command")); + }, + _ => { + return Err(V::Error::custom("must specify only chars, action or command")) + }, + }; + + let mode = mode.unwrap_or_else(TermMode::empty); + let not_mode = not_mode.unwrap_or_else(TermMode::empty); + let mods = mods.unwrap_or_else(ModifiersState::default); + + if mouse.is_none() && key.is_none() { + return Err(V::Error::custom("bindings require mouse button or key")); + } + + Ok(RawBinding { mode, notmode: not_mode, action, key, mouse, mods }) + } + } + + const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"]; + + deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor) + } +} + +impl<'a> Deserialize<'a> for MouseBinding { + fn deserialize(deserializer: D) -> ::std::result::Result + where + D: Deserializer<'a>, + { + let raw = RawBinding::deserialize(deserializer)?; + raw.into_mouse_binding().map_err(|_| D::Error::custom("expected mouse binding")) + } +} + +impl<'a> Deserialize<'a> for KeyBinding { + fn deserialize(deserializer: D) -> ::std::result::Result + where + D: Deserializer<'a>, + { + let raw = RawBinding::deserialize(deserializer)?; + raw.into_key_binding().map_err(|_| D::Error::custom("expected key binding")) + } +} + +#[serde(untagged)] +#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +pub enum CommandWrapper { + Just(String), + WithArgs { + program: String, + #[serde(default)] + args: Vec, + }, +} + +impl CommandWrapper { + pub fn program(&self) -> &str { + match self { + CommandWrapper::Just(program) => program, + CommandWrapper::WithArgs { program, .. } => program, + } + } + + pub fn args(&self) -> &[String] { + match self { + CommandWrapper::Just(_) => &[], + CommandWrapper::WithArgs { args, .. } => args, + } + } +} + +/// Newtype for implementing deserialize on glutin Mods +/// +/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the +/// impl below. +#[derive(Debug, Copy, Clone, Hash, Default, Eq, PartialEq)] +pub struct ModsWrapper(ModifiersState); + +impl ModsWrapper { + pub fn into_inner(self) -> ModifiersState { + self.0 + } +} + +impl<'a> de::Deserialize<'a> for ModsWrapper { + fn deserialize(deserializer: D) -> ::std::result::Result + where + D: de::Deserializer<'a>, + { + struct ModsVisitor; + + impl<'a> Visitor<'a> for ModsVisitor { + type Value = ModsWrapper; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Some subset of Command|Shift|Super|Alt|Option|Control") + } + + fn visit_str(self, value: &str) -> ::std::result::Result + where + E: de::Error, + { + let mut res = ModifiersState::default(); + for modifier in value.split('|') { + match modifier.trim().to_lowercase().as_str() { + "command" | "super" => res.logo = true, + "shift" => res.shift = true, + "alt" | "option" => res.alt = true, + "control" => res.ctrl = true, + "none" => (), + _ => error!("Unknown modifier {:?}", modifier), + } + } + + Ok(ModsWrapper(res)) + } + } + + deserializer.deserialize_str(ModsVisitor) + } +} diff --git a/alacritty_terminal/src/config/colors.rs b/alacritty_terminal/src/config/colors.rs new file mode 100644 index 0000000..a9e7a6d --- /dev/null +++ b/alacritty_terminal/src/config/colors.rs @@ -0,0 +1,178 @@ +use serde::{Deserialize, Deserializer}; + +use crate::config::failure_default; +use crate::term::color::Rgb; + +#[serde(default)] +#[derive(Deserialize, Debug, Default, PartialEq, Eq)] +pub struct Colors { + #[serde(deserialize_with = "failure_default")] + pub primary: PrimaryColors, + #[serde(deserialize_with = "failure_default")] + pub cursor: CursorColors, + #[serde(deserialize_with = "failure_default")] + pub selection: SelectionColors, + #[serde(deserialize_with = "failure_default")] + normal: NormalColors, + #[serde(deserialize_with = "failure_default")] + bright: BrightColors, + #[serde(deserialize_with = "failure_default")] + pub dim: Option, + #[serde(deserialize_with = "failure_default")] + pub indexed_colors: Vec, +} + +impl Colors { + pub fn normal(&self) -> &AnsiColors { + &self.normal.0 + } + + pub fn bright(&self) -> &AnsiColors { + &self.bright.0 + } +} + +#[serde(default)] +#[derive(Deserialize, Default, Debug, PartialEq, Eq)] +pub struct IndexedColor { + #[serde(deserialize_with = "deserialize_color_index")] + pub index: u8, + #[serde(deserialize_with = "failure_default")] + pub color: Rgb, +} + +fn deserialize_color_index<'a, D>(deserializer: D) -> ::std::result::Result +where + D: Deserializer<'a>, +{ + let value = serde_yaml::Value::deserialize(deserializer)?; + match u8::deserialize(value) { + Ok(index) => { + if index < 16 { + error!( + "Problem with config: indexed_color's index is {}, but a value bigger than 15 \ + was expected; ignoring setting", + index + ); + + // Return value out of range to ignore this color + Ok(0) + } else { + Ok(index) + } + }, + Err(err) => { + error!("Problem with config: {}; ignoring setting", err); + + // Return value out of range to ignore this color + Ok(0) + }, + } +} + +#[serde(default)] +#[derive(Deserialize, Debug, Copy, Clone, Default, PartialEq, Eq)] +pub struct CursorColors { + #[serde(deserialize_with = "failure_default")] + pub text: Option, + #[serde(deserialize_with = "failure_default")] + pub cursor: Option, +} + +#[serde(default)] +#[derive(Deserialize, Debug, Copy, Clone, Default, PartialEq, Eq)] +pub struct SelectionColors { + #[serde(deserialize_with = "failure_default")] + pub text: Option, + #[serde(deserialize_with = "failure_default")] + pub background: Option, +} + +#[serde(default)] +#[derive(Deserialize, Debug, PartialEq, Eq)] +pub struct PrimaryColors { + #[serde(default = "default_background", deserialize_with = "failure_default")] + pub background: Rgb, + #[serde(default = "default_foreground", deserialize_with = "failure_default")] + pub foreground: Rgb, + #[serde(deserialize_with = "failure_default")] + pub bright_foreground: Option, + #[serde(deserialize_with = "failure_default")] + pub dim_foreground: Option, +} + +impl Default for PrimaryColors { + fn default() -> Self { + PrimaryColors { + background: default_background(), + foreground: default_foreground(), + bright_foreground: Default::default(), + dim_foreground: Default::default(), + } + } +} + +fn default_background() -> Rgb { + Rgb { r: 0, g: 0, b: 0 } +} + +fn default_foreground() -> Rgb { + Rgb { r: 0xea, g: 0xea, b: 0xea } +} + +/// The 8-colors sections of config +#[derive(Deserialize, Debug, PartialEq, Eq)] +pub struct AnsiColors { + #[serde(deserialize_with = "failure_default")] + pub black: Rgb, + #[serde(deserialize_with = "failure_default")] + pub red: Rgb, + #[serde(deserialize_with = "failure_default")] + pub green: Rgb, + #[serde(deserialize_with = "failure_default")] + pub yellow: Rgb, + #[serde(deserialize_with = "failure_default")] + pub blue: Rgb, + #[serde(deserialize_with = "failure_default")] + pub magenta: Rgb, + #[serde(deserialize_with = "failure_default")] + pub cyan: Rgb, + #[serde(deserialize_with = "failure_default")] + pub white: Rgb, +} + +#[derive(Deserialize, Debug, PartialEq, Eq)] +struct NormalColors(AnsiColors); + +impl Default for NormalColors { + fn default() -> Self { + NormalColors(AnsiColors { + black: Rgb { r: 0x00, g: 0x00, b: 0x00 }, + red: Rgb { r: 0xd5, g: 0x4e, b: 0x53 }, + green: Rgb { r: 0xb9, g: 0xca, b: 0x4a }, + yellow: Rgb { r: 0xe6, g: 0xc5, b: 0x47 }, + blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda }, + magenta: Rgb { r: 0xc3, g: 0x97, b: 0xd8 }, + cyan: Rgb { r: 0x70, g: 0xc0, b: 0xba }, + white: Rgb { r: 0xea, g: 0xea, b: 0xea }, + }) + } +} + +#[derive(Deserialize, Debug, PartialEq, Eq)] +struct BrightColors(AnsiColors); + +impl Default for BrightColors { + fn default() -> Self { + BrightColors(AnsiColors { + black: Rgb { r: 0x66, g: 0x66, b: 0x66 }, + red: Rgb { r: 0xff, g: 0x33, b: 0x34 }, + green: Rgb { r: 0x9e, g: 0xc4, b: 0x00 }, + yellow: Rgb { r: 0xe7, g: 0xc5, b: 0x47 }, + blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda }, + magenta: Rgb { r: 0xb7, g: 0x7e, b: 0xe0 }, + cyan: Rgb { r: 0x54, g: 0xce, b: 0xd6 }, + white: Rgb { r: 0xff, g: 0xff, b: 0xff }, + }) + } +} diff --git a/alacritty_terminal/src/config/debug.rs b/alacritty_terminal/src/config/debug.rs new file mode 100644 index 0000000..b7d1144 --- /dev/null +++ b/alacritty_terminal/src/config/debug.rs @@ -0,0 +1,61 @@ +use log::LevelFilter; +use serde::Deserializer; + +use crate::config::failure_default; + +/// Debugging options +#[serde(default)] +#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Debug { + #[serde(default = "default_log_level", deserialize_with = "deserialize_log_level")] + pub log_level: LevelFilter, + + #[serde(deserialize_with = "failure_default")] + pub print_events: bool, + + /// Keep the log file after quitting + #[serde(deserialize_with = "failure_default")] + pub persistent_logging: bool, + + /// Should show render timer + #[serde(deserialize_with = "failure_default")] + pub render_timer: bool, + + /// Record ref test + #[serde(deserialize_with = "failure_default")] + pub ref_test: bool, +} + +impl Default for Debug { + fn default() -> Self { + Self { + log_level: default_log_level(), + print_events: Default::default(), + persistent_logging: Default::default(), + render_timer: Default::default(), + ref_test: Default::default(), + } + } +} + +fn default_log_level() -> LevelFilter { + LevelFilter::Warn +} + +fn deserialize_log_level<'a, D>(deserializer: D) -> Result +where + D: Deserializer<'a>, +{ + Ok(match failure_default::(deserializer)?.to_lowercase().as_str() { + "off" | "none" => LevelFilter::Off, + "error" => LevelFilter::Error, + "warn" => LevelFilter::Warn, + "info" => LevelFilter::Info, + "debug" => LevelFilter::Debug, + "trace" => LevelFilter::Trace, + level => { + error!("Problem with config: invalid log level {}; using level Warn", level); + default_log_level() + }, + }) +} diff --git a/alacritty_terminal/src/config/font.rs b/alacritty_terminal/src/config/font.rs new file mode 100644 index 0000000..3c78ad2 --- /dev/null +++ b/alacritty_terminal/src/config/font.rs @@ -0,0 +1,200 @@ +use std::fmt; + +use font::Size; +use serde::de::Visitor; +use serde::{Deserialize, Deserializer}; + +#[cfg(target_os = "macos")] +use crate::config::DefaultTrueBool; +use crate::config::{failure_default, Delta}; + +/// Font config +/// +/// Defaults are provided at the level of this struct per platform, but not per +/// field in this struct. It might be nice in the future to have defaults for +/// each value independently. Alternatively, maybe erroring when the user +/// doesn't provide complete config is Ok. +#[serde(default)] +#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +pub struct Font { + /// Normal font face + #[serde(deserialize_with = "failure_default")] + normal: FontDescription, + + /// Bold font face + #[serde(deserialize_with = "failure_default")] + italic: SecondaryFontDescription, + + /// Italic font face + #[serde(deserialize_with = "failure_default")] + bold: SecondaryFontDescription, + + /// Font size in points + #[serde(deserialize_with = "DeserializeSize::deserialize")] + pub size: Size, + + /// Extra spacing per character + #[serde(deserialize_with = "failure_default")] + pub offset: Delta, + + /// Glyph offset within character cell + #[serde(deserialize_with = "failure_default")] + pub glyph_offset: Delta, + + #[cfg(target_os = "macos")] + #[serde(deserialize_with = "failure_default")] + use_thin_strokes: DefaultTrueBool, +} + +impl Default for Font { + fn default() -> Font { + Font { + size: default_font_size(), + normal: Default::default(), + bold: Default::default(), + italic: Default::default(), + glyph_offset: Default::default(), + offset: Default::default(), + #[cfg(target_os = "macos")] + use_thin_strokes: Default::default(), + } + } +} + +impl Font { + /// Get a font clone with a size modification + pub fn with_size(self, size: Size) -> Font { + Font { size, ..self } + } + + // Get normal font description + pub fn normal(&self) -> &FontDescription { + &self.normal + } + + // Get italic font description + pub fn italic(&self) -> FontDescription { + self.italic.desc(&self.normal) + } + + // Get bold font description + pub fn bold(&self) -> FontDescription { + self.bold.desc(&self.normal) + } + + #[cfg(target_os = "macos")] + pub fn use_thin_strokes(&self) -> bool { + self.use_thin_strokes.0 + } + + #[cfg(not(target_os = "macos"))] + pub fn use_thin_strokes(&self) -> bool { + false + } +} + +fn default_font_size() -> Size { + Size::new(11.) +} + +/// Description of the normal font +#[serde(default)] +#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +pub struct FontDescription { + #[serde(deserialize_with = "failure_default")] + pub family: String, + #[serde(deserialize_with = "failure_default")] + pub style: Option, +} + +impl Default for FontDescription { + fn default() -> FontDescription { + FontDescription { + #[cfg(not(any(target_os = "macos", windows)))] + family: "monospace".into(), + #[cfg(target_os = "macos")] + family: "Menlo".into(), + #[cfg(windows)] + family: "Consolas".into(), + style: None, + } + } +} + +/// Description of the italic and bold font +#[serde(default)] +#[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)] +pub struct SecondaryFontDescription { + #[serde(deserialize_with = "failure_default")] + family: Option, + #[serde(deserialize_with = "failure_default")] + style: Option, +} + +impl SecondaryFontDescription { + pub fn desc(&self, fallback: &FontDescription) -> FontDescription { + FontDescription { + family: self.family.clone().unwrap_or_else(|| fallback.family.clone()), + style: self.style.clone(), + } + } +} + +trait DeserializeSize: Sized { + fn deserialize<'a, D>(_: D) -> ::std::result::Result + where + D: serde::de::Deserializer<'a>; +} + +impl DeserializeSize for Size { + fn deserialize<'a, D>(deserializer: D) -> ::std::result::Result + where + D: serde::de::Deserializer<'a>, + { + use std::marker::PhantomData; + + struct NumVisitor<__D> { + _marker: PhantomData<__D>, + } + + impl<'a, __D> Visitor<'a> for NumVisitor<__D> + where + __D: serde::de::Deserializer<'a>, + { + type Value = f64; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("f64 or u64") + } + + fn visit_f64(self, value: f64) -> ::std::result::Result + where + E: ::serde::de::Error, + { + Ok(value) + } + + fn visit_u64(self, value: u64) -> ::std::result::Result + where + E: ::serde::de::Error, + { + Ok(value as f64) + } + } + + let value = serde_yaml::Value::deserialize(deserializer)?; + let size = value + .deserialize_any(NumVisitor:: { _marker: PhantomData }) + .map(|v| Size::new(v as _)); + + // Use default font size as fallback + match size { + Ok(size) => Ok(size), + Err(err) => { + let size = default_font_size(); + error!("Problem with config: {}; using size {}", err, size.as_f32_pts()); + Ok(size) + }, + } + } +} diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs index 6eebbdf..0af7e81 100644 --- a/alacritty_terminal/src/config/mod.rs +++ b/alacritty_terminal/src/config/mod.rs @@ -1,263 +1,344 @@ -//! Configuration definitions and file loading -//! -//! Alacritty reads from a config file at startup to determine various runtime -//! parameters including font family and style, font size, etc. In the future, -//! the config file will also hold user and platform specific keybindings. +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use std::borrow::Cow; use std::collections::HashMap; -use std::fs::File; -use std::io::{self, Read, Write}; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use std::sync::mpsc; -use std::time::Duration; -use std::{env, fmt}; +use std::path::PathBuf; -use font::Size; -use glutin::ModifiersState; -use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; -use serde::de::Error as SerdeError; -use serde::de::{MapAccess, Unexpected, Visitor}; -use serde::{self, de, Deserialize}; -use serde_yaml; +use serde::{Deserialize, Deserializer}; + +mod bindings; +mod colors; +mod debug; +mod font; +mod monitor; +mod mouse; +mod scrolling; +#[cfg(test)] +mod test; +mod visual_bell; +mod window; use crate::ansi::CursorStyle; -use crate::index::{Column, Line}; -use crate::input::{Action, Binding, KeyBinding, MouseBinding}; -use crate::term::color::Rgb; +use crate::input::{Binding, KeyBinding, MouseBinding}; -pub use self::options::Options; -mod options; -mod bindings; +pub use crate::config::bindings::Key; +pub use crate::config::colors::Colors; +pub use crate::config::debug::Debug; +pub use crate::config::font::{Font, FontDescription}; +pub use crate::config::monitor::Monitor; +pub use crate::config::mouse::{ClickHandler, Mouse}; +pub use crate::config::scrolling::Scrolling; +pub use crate::config::visual_bell::{VisualBellAnimation, VisualBellConfig}; +pub use crate::config::window::{Decorations, Dimensions, StartupMode, WindowConfig}; -pub const SOURCE_FILE_PATH: &str = file!(); -const MAX_SCROLLBACK_LINES: u32 = 100_000; -static DEFAULT_ALACRITTY_CONFIG: &'static str = +pub static DEFAULT_ALACRITTY_CONFIG: &'static str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../alacritty.yml")); +const MAX_SCROLLBACK_LINES: u32 = 100_000; + +/// Top-level config type +#[derive(Debug, PartialEq, Deserialize)] +pub struct Config { + /// Pixel padding + #[serde(default, deserialize_with = "failure_default")] + pub padding: Option>, + + /// TERM env variable + #[serde(default, deserialize_with = "failure_default")] + pub env: HashMap, + + /// Font configuration + #[serde(default, deserialize_with = "failure_default")] + pub font: Font, + + /// Should draw bold text with brighter colors instead of bold font + #[serde(default, deserialize_with = "failure_default")] + draw_bold_text_with_bright_colors: DefaultTrueBool, + + #[serde(default, deserialize_with = "failure_default")] + pub colors: Colors, + + /// Background opacity from 0.0 to 1.0 + #[serde(default, deserialize_with = "failure_default")] + background_opacity: Alpha, + + /// Window configuration + #[serde(default, deserialize_with = "failure_default")] + pub window: WindowConfig, + + /// Keybindings + #[serde(default = "default_key_bindings", deserialize_with = "deserialize_key_bindings")] + pub key_bindings: Vec, + + /// Bindings for the mouse + #[serde(default = "default_mouse_bindings", deserialize_with = "deserialize_mouse_bindings")] + pub mouse_bindings: Vec, + + #[serde(default, deserialize_with = "failure_default")] + pub selection: Selection, + + #[serde(default, deserialize_with = "failure_default")] + pub mouse: Mouse, + + /// Path to a shell program to run on startup + #[serde(default, deserialize_with = "failure_default")] + pub shell: Option>, + + /// Path where config was loaded from + #[serde(default, deserialize_with = "failure_default")] + pub config_path: Option, + + /// Visual bell configuration + #[serde(default, deserialize_with = "failure_default")] + pub visual_bell: VisualBellConfig, + + /// Use dynamic title + #[serde(default, deserialize_with = "failure_default")] + dynamic_title: DefaultTrueBool, + + /// Live config reload + #[serde(default, deserialize_with = "failure_default")] + live_config_reload: DefaultTrueBool, + + /// Number of spaces in one tab + #[serde(default, deserialize_with = "failure_default")] + tabspaces: Tabspaces, + + /// How much scrolling history to keep + #[serde(default, deserialize_with = "failure_default")] + pub scrolling: Scrolling, + + /// Cursor configuration + #[serde(default, deserialize_with = "failure_default")] + pub cursor: Cursor, + + /// Enable experimental conpty backend instead of using winpty. + /// Will only take effect on Windows 10 Oct 2018 and later. + #[cfg(windows)] + #[serde(default, deserialize_with = "failure_default")] + pub enable_experimental_conpty_backend: bool, + + /// Send escape sequences using the alt key. + #[serde(default, deserialize_with = "failure_default")] + alt_send_esc: DefaultTrueBool, + + /// Shell startup directory + #[serde(default, deserialize_with = "failure_default")] + working_directory: WorkingDirectory, + + /// Debug options + #[serde(default, deserialize_with = "failure_default")] + pub debug: Debug, + + // TODO: DEPRECATED + #[serde(default, deserialize_with = "failure_default")] + pub render_timer: Option, + + // TODO: DEPRECATED + #[serde(default, deserialize_with = "failure_default")] + pub persistent_logging: Option, +} + +impl Default for Config { + fn default() -> Self { + serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("default config is invalid") + } +} + +impl Config { + pub fn tabspaces(&self) -> usize { + self.tabspaces.0 + } + + #[inline] + pub fn draw_bold_text_with_bright_colors(&self) -> bool { + self.draw_bold_text_with_bright_colors.0 + } + + /// Should show render timer + #[inline] + pub fn render_timer(&self) -> bool { + self.render_timer.unwrap_or(self.debug.render_timer) + } + + /// Live config reload + #[inline] + pub fn live_config_reload(&self) -> bool { + self.live_config_reload.0 + } + + #[inline] + pub fn set_live_config_reload(&mut self, live_config_reload: bool) { + self.live_config_reload.0 = live_config_reload; + } + + #[inline] + pub fn dynamic_title(&self) -> bool { + self.dynamic_title.0 + } + + #[inline] + pub fn set_dynamic_title(&mut self, dynamic_title: bool) { + self.dynamic_title.0 = dynamic_title; + } + + /// Send escape sequences using the alt key + #[inline] + pub fn alt_send_esc(&self) -> bool { + self.alt_send_esc.0 + } + + /// Keep the log file after quitting Alacritty + #[inline] + pub fn persistent_logging(&self) -> bool { + self.persistent_logging.unwrap_or(self.debug.persistent_logging) + } + + #[inline] + pub fn background_opacity(&self) -> f32 { + self.background_opacity.0 + } + + #[inline] + pub fn working_directory(&self) -> &Option { + &self.working_directory.0 + } + + #[inline] + pub fn set_working_directory(&mut self, working_directory: Option) { + self.working_directory.0 = working_directory; + } +} + +#[derive(Default, Debug, PartialEq, Eq)] +struct WorkingDirectory(Option); + +impl<'de> Deserialize<'de> for WorkingDirectory { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = serde_yaml::Value::deserialize(deserializer)?; + + // Accept `None` to use the default path + if value.as_str().filter(|v| v.to_lowercase() == "none").is_some() { + return Ok(WorkingDirectory(None)); + } + + Ok(match PathBuf::deserialize(value) { + Ok(path) => WorkingDirectory(Some(path)), + Err(err) => { + error!("Problem with config: {}; using None", err); + WorkingDirectory(None) + }, + }) + } +} + +fn default_key_bindings() -> Vec { + bindings::default_key_bindings() +} + +fn default_mouse_bindings() -> Vec { + bindings::default_mouse_bindings() +} + +fn deserialize_key_bindings<'a, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'a>, +{ + deserialize_bindings(deserializer, bindings::default_key_bindings()) +} + +fn deserialize_mouse_bindings<'a, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'a>, +{ + deserialize_bindings(deserializer, bindings::default_mouse_bindings()) +} + +fn deserialize_bindings<'a, D, T>( + deserializer: D, + mut default: Vec>, +) -> Result>, D::Error> +where + D: Deserializer<'a>, + T: Copy + Eq + std::hash::Hash + std::fmt::Debug, + Binding: Deserialize<'a>, +{ + let mut bindings: Vec> = failure_default(deserializer)?; + + for binding in bindings.iter() { + default.retain(|b| !b.triggers_match(binding)); + } + + bindings.extend(default); + + Ok(bindings) +} #[serde(default)] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +#[derive(Deserialize, Default, Clone, Debug, PartialEq, Eq)] pub struct Selection { - #[serde(deserialize_with = "deserialize_escape_chars")] - pub semantic_escape_chars: String, + #[serde(deserialize_with = "failure_default")] + semantic_escape_chars: EscapeChars, #[serde(deserialize_with = "failure_default")] pub save_to_clipboard: bool, } -impl Default for Selection { - fn default() -> Selection { - Selection { - semantic_escape_chars: default_escape_chars(), - save_to_clipboard: Default::default(), - } +impl Selection { + pub fn semantic_escape_chars(&self) -> &str { + &self.semantic_escape_chars.0 } } -fn deserialize_escape_chars<'a, D>(deserializer: D) -> ::std::result::Result -where - D: de::Deserializer<'a>, -{ - match String::deserialize(deserializer) { - Ok(escape_chars) => Ok(escape_chars), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_escape_chars()) - }, - } -} +#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] +struct EscapeChars(String); -fn default_escape_chars() -> String { - String::from(",│`|:\"' ()[]{}<>") -} - -#[serde(default)] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct ClickHandler { - #[serde(deserialize_with = "deserialize_duration_ms")] - pub threshold: Duration, -} - -impl Default for ClickHandler { +impl Default for EscapeChars { fn default() -> Self { - ClickHandler { threshold: default_threshold_ms() } - } -} - -fn default_threshold_ms() -> Duration { - Duration::from_millis(300) -} - -fn deserialize_duration_ms<'a, D>(deserializer: D) -> ::std::result::Result -where - D: de::Deserializer<'a>, -{ - match u64::deserialize(deserializer) { - Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_threshold_ms()) - }, + EscapeChars(String::from(",│`|:\"' ()[]{}<>")) } } #[serde(default)] -#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct Mouse { +#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq)] +pub struct Cursor { #[serde(deserialize_with = "failure_default")] - pub double_click: ClickHandler, + pub style: CursorStyle, #[serde(deserialize_with = "failure_default")] - pub triple_click: ClickHandler, - #[serde(deserialize_with = "failure_default")] - pub hide_when_typing: bool, - #[serde(deserialize_with = "failure_default")] - pub url: Url, - - // TODO: DEPRECATED - pub faux_scrollback_lines: Option, + unfocused_hollow: DefaultTrueBool, } -#[serde(default)] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct Url { - // Program for opening links - #[serde(deserialize_with = "deserialize_launcher")] - pub launcher: Option, - - // Modifier used to open links - #[serde(deserialize_with = "deserialize_modifiers")] - pub modifiers: ModifiersState, -} - -fn deserialize_launcher<'a, D>( - deserializer: D, -) -> ::std::result::Result, D::Error> -where - D: de::Deserializer<'a>, -{ - let default = Url::default().launcher; - - // Deserialize to generic value - let val = match serde_yaml::Value::deserialize(deserializer) { - Ok(val) => val, - Err(err) => { - error!("Problem with config: {}; using {}", err, default.clone().unwrap().program()); - return Ok(default); - }, - }; - - // Accept `None` to disable the launcher - if val.as_str().filter(|v| v.to_lowercase() == "none").is_some() { - return Ok(None); - } - - match >::deserialize(val) { - Ok(launcher) => Ok(launcher), - Err(err) => { - error!("Problem with config: {}; using {}", err, default.clone().unwrap().program()); - Ok(default) - }, - } -} - -impl Default for Url { - fn default() -> Url { - Url { - #[cfg(not(any(target_os = "macos", windows)))] - launcher: Some(CommandWrapper::Just(String::from("xdg-open"))), - #[cfg(target_os = "macos")] - launcher: Some(CommandWrapper::Just(String::from("open"))), - #[cfg(windows)] - launcher: Some(CommandWrapper::Just(String::from("explorer"))), - modifiers: Default::default(), - } - } -} - -fn deserialize_modifiers<'a, D>(deserializer: D) -> ::std::result::Result -where - D: de::Deserializer<'a>, -{ - ModsWrapper::deserialize(deserializer).map(ModsWrapper::into_inner) -} - -/// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert -/// Penner's Easing Functions. -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)] -pub enum VisualBellAnimation { - Ease, // CSS - EaseOut, // CSS - EaseOutSine, // Penner - EaseOutQuad, // Penner - EaseOutCubic, // Penner - EaseOutQuart, // Penner - EaseOutQuint, // Penner - EaseOutExpo, // Penner - EaseOutCirc, // Penner - Linear, -} - -impl Default for VisualBellAnimation { +impl Default for Cursor { fn default() -> Self { - VisualBellAnimation::EaseOutExpo + Self { style: Default::default(), unfocused_hollow: Default::default() } } } -#[serde(default)] -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct VisualBellConfig { - /// Visual bell animation function - #[serde(deserialize_with = "failure_default")] - animation: VisualBellAnimation, - - /// Visual bell duration in milliseconds - #[serde(deserialize_with = "failure_default")] - duration: u16, - - /// Visual bell flash color - #[serde(deserialize_with = "rgb_from_hex")] - color: Rgb, -} - -impl Default for VisualBellConfig { - fn default() -> VisualBellConfig { - VisualBellConfig { - animation: Default::default(), - duration: Default::default(), - color: default_visual_bell_color(), - } - } -} - -fn default_visual_bell_color() -> Rgb { - Rgb { r: 255, g: 255, b: 255 } -} - -impl VisualBellConfig { - /// Visual bell animation - #[inline] - pub fn animation(&self) -> VisualBellAnimation { - self.animation - } - - /// Visual bell duration in milliseconds - #[inline] - pub fn duration(&self) -> Duration { - Duration::from_millis(u64::from(self.duration)) - } - - /// Visual bell flash color - #[inline] - pub fn color(&self) -> Rgb { - self.color +impl Cursor { + pub fn unfocused_hollow(self) -> bool { + self.unfocused_hollow.0 } } #[derive(Debug, Deserialize, PartialEq, Eq)] pub struct Shell<'a> { - program: Cow<'a, str>, + pub program: Cow<'a, str>, #[serde(default, deserialize_with = "failure_default")] - args: Vec, + pub args: Vec, } impl<'a> Shell<'a> { @@ -274,1794 +355,6 @@ impl<'a> Shell<'a> { { Shell { program: program.into(), args } } - - pub fn program(&self) -> &str { - &*self.program - } - - pub fn args(&self) -> &[String] { - self.args.as_slice() - } -} - -/// Wrapper around f32 that represents an alpha value between 0.0 and 1.0 -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct Alpha(f32); - -impl Alpha { - pub fn new(value: f32) -> Self { - Alpha(Self::clamp_to_valid_range(value)) - } - - pub fn set(&mut self, value: f32) { - self.0 = Self::clamp_to_valid_range(value); - } - - #[inline] - pub fn get(self) -> f32 { - self.0 - } - - fn clamp_to_valid_range(value: f32) -> f32 { - if value < 0.0 { - 0.0 - } else if value > 1.0 { - 1.0 - } else { - value - } - } -} - -impl Default for Alpha { - fn default() -> Self { - Alpha(1.0) - } -} - -#[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)] -pub enum StartupMode { - Windowed, - Maximized, - Fullscreen, - #[cfg(target_os = "macos")] - SimpleFullscreen, -} - -impl Default for StartupMode { - fn default() -> StartupMode { - StartupMode::Windowed - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum Decorations { - Full, - Transparent, - Buttonless, - None, -} - -impl Default for Decorations { - fn default() -> Decorations { - Decorations::Full - } -} - -impl<'de> Deserialize<'de> for Decorations { - fn deserialize(deserializer: D) -> ::std::result::Result - where - D: de::Deserializer<'de>, - { - struct DecorationsVisitor; - - impl<'de> Visitor<'de> for DecorationsVisitor { - type Value = Decorations; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Some subset of full|transparent|buttonless|none") - } - - #[cfg(target_os = "macos")] - fn visit_str(self, value: &str) -> ::std::result::Result - where - E: de::Error, - { - match value.to_lowercase().as_str() { - "transparent" => Ok(Decorations::Transparent), - "buttonless" => Ok(Decorations::Buttonless), - "none" => Ok(Decorations::None), - "full" => Ok(Decorations::Full), - "true" => { - error!( - "Deprecated decorations boolean value, use one of \ - transparent|buttonless|none|full instead; falling back to \"full\"" - ); - Ok(Decorations::Full) - }, - "false" => { - error!( - "Deprecated decorations boolean value, use one of \ - transparent|buttonless|none|full instead; falling back to \"none\"" - ); - Ok(Decorations::None) - }, - _ => { - error!("Invalid decorations value: {}; using default value", value); - Ok(Decorations::Full) - }, - } - } - - #[cfg(not(target_os = "macos"))] - fn visit_str(self, value: &str) -> ::std::result::Result - where - E: de::Error, - { - match value.to_lowercase().as_str() { - "none" => Ok(Decorations::None), - "full" => Ok(Decorations::Full), - "true" => { - error!( - "Deprecated decorations boolean value, use one of none|full instead; \ - falling back to \"full\"" - ); - Ok(Decorations::Full) - }, - "false" => { - error!( - "Deprecated decorations boolean value, use one of none|full instead; \ - falling back to \"none\"" - ); - Ok(Decorations::None) - }, - "transparent" | "buttonless" => { - error!("macOS-only decorations value: {}; using default value", value); - Ok(Decorations::Full) - }, - _ => { - error!("Invalid decorations value: {}; using default value", value); - Ok(Decorations::Full) - }, - } - } - } - - deserializer.deserialize_str(DecorationsVisitor) - } -} - -#[serde(default)] -#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Eq)] -pub struct WindowConfig { - /// Initial dimensions - #[serde(default, deserialize_with = "failure_default")] - dimensions: Dimensions, - - /// Initial position - #[serde(default, deserialize_with = "failure_default")] - position: Option>, - - /// Pixel padding - #[serde(deserialize_with = "deserialize_padding")] - padding: Delta, - - /// Draw the window with title bar / borders - #[serde(deserialize_with = "failure_default")] - decorations: Decorations, - - /// Spread out additional padding evenly - #[serde(deserialize_with = "failure_default")] - dynamic_padding: bool, - - /// Startup mode - #[serde(deserialize_with = "failure_default")] - startup_mode: StartupMode, - - /// TODO: DEPRECATED - #[serde(deserialize_with = "failure_default")] - start_maximized: Option, -} - -impl Default for WindowConfig { - fn default() -> Self { - WindowConfig { - dimensions: Default::default(), - position: Default::default(), - padding: default_padding(), - decorations: Default::default(), - dynamic_padding: Default::default(), - start_maximized: Default::default(), - startup_mode: Default::default(), - } - } -} - -fn default_padding() -> Delta { - Delta { x: 2, y: 2 } -} - -fn deserialize_padding<'a, D>(deserializer: D) -> ::std::result::Result, D::Error> -where - D: de::Deserializer<'a>, -{ - match Delta::deserialize(deserializer) { - Ok(delta) => Ok(delta), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_padding()) - }, - } -} - -impl WindowConfig { - pub fn decorations(&self) -> Decorations { - self.decorations - } - - pub fn dynamic_padding(&self) -> bool { - self.dynamic_padding - } - - pub fn startup_mode(&self) -> StartupMode { - self.startup_mode - } - - pub fn position(&self) -> Option> { - self.position - } -} - -/// Top-level config type -#[derive(Debug, PartialEq, Deserialize)] -pub struct Config { - /// Pixel padding - #[serde(default, deserialize_with = "failure_default")] - padding: Option>, - - /// TERM env variable - #[serde(default, deserialize_with = "failure_default")] - env: HashMap, - - /// Font configuration - #[serde(default, deserialize_with = "failure_default")] - font: Font, - - /// Should show render timer - #[serde(default, deserialize_with = "failure_default")] - render_timer: bool, - - /// Should draw bold text with brighter colors instead of bold font - #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] - draw_bold_text_with_bright_colors: bool, - - #[serde(default, deserialize_with = "failure_default")] - colors: Colors, - - /// Background opacity from 0.0 to 1.0 - #[serde(default, deserialize_with = "failure_default")] - background_opacity: Alpha, - - /// Window configuration - #[serde(default, deserialize_with = "failure_default")] - window: WindowConfig, - - /// Keybindings - #[serde(default = "default_key_bindings", deserialize_with = "deserialize_key_bindings")] - key_bindings: Vec, - - /// Bindings for the mouse - #[serde(default = "default_mouse_bindings", deserialize_with = "deserialize_mouse_bindings")] - mouse_bindings: Vec, - - #[serde(default, deserialize_with = "failure_default")] - selection: Selection, - - #[serde(default, deserialize_with = "failure_default")] - mouse: Mouse, - - /// Path to a shell program to run on startup - #[serde(default, deserialize_with = "failure_default")] - shell: Option>, - - /// Path where config was loaded from - #[serde(default, deserialize_with = "failure_default")] - config_path: Option, - - /// Visual bell configuration - #[serde(default, deserialize_with = "failure_default")] - visual_bell: VisualBellConfig, - - /// Use dynamic title - #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] - dynamic_title: bool, - - /// Live config reload - #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] - live_config_reload: bool, - - /// Number of spaces in one tab - #[serde(default = "default_tabspaces", deserialize_with = "deserialize_tabspaces")] - tabspaces: usize, - - /// How much scrolling history to keep - #[serde(default, deserialize_with = "failure_default")] - scrolling: Scrolling, - - /// Cursor configuration - #[serde(default, deserialize_with = "failure_default")] - cursor: Cursor, - - /// Keep the log file after quitting - #[serde(default, deserialize_with = "failure_default")] - persistent_logging: bool, - - /// Enable experimental conpty backend instead of using winpty. - /// Will only take effect on Windows 10 Oct 2018 and later. - #[cfg(windows)] - #[serde(default, deserialize_with = "failure_default")] - enable_experimental_conpty_backend: bool, - - /// Send escape sequences using the alt key. - #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] - alt_send_esc: bool, - - // TODO: DEPRECATED - custom_cursor_colors: Option, - - // TODO: DEPRECATED - hide_cursor_when_typing: Option, - - // TODO: DEPRECATED - cursor_style: Option, - - // TODO: DEPRECATED - unfocused_hollow_cursor: Option, - - // TODO: DEPRECATED - dimensions: Option, -} - -impl Default for Config { - fn default() -> Self { - serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("default config is invalid") - } -} - -fn default_key_bindings() -> Vec { - bindings::default_key_bindings() -} - -fn default_mouse_bindings() -> Vec { - bindings::default_mouse_bindings() -} - -fn deserialize_key_bindings<'a, D>( - deserializer: D, -) -> ::std::result::Result, D::Error> -where - D: de::Deserializer<'a>, -{ - deserialize_bindings(deserializer, bindings::default_key_bindings()) -} - -fn deserialize_mouse_bindings<'a, D>( - deserializer: D, -) -> ::std::result::Result, D::Error> -where - D: de::Deserializer<'a>, -{ - deserialize_bindings(deserializer, bindings::default_mouse_bindings()) -} - -fn deserialize_bindings<'a, D, T>( - deserializer: D, - mut default: Vec>, -) -> ::std::result::Result>, D::Error> -where - D: de::Deserializer<'a>, - T: Copy + Eq + std::hash::Hash + std::fmt::Debug, - Binding: de::Deserialize<'a>, -{ - let mut bindings: Vec> = failure_default_vec(deserializer)?; - - for binding in bindings.iter() { - default.retain(|b| !b.triggers_match(binding)); - } - - bindings.extend(default); - - Ok(bindings) -} - -fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result, D::Error> -where - D: de::Deserializer<'a>, - T: Deserialize<'a>, -{ - // Deserialize as generic vector - let vec = match Vec::::deserialize(deserializer) { - Ok(vec) => vec, - Err(err) => { - error!("Problem with config: {}; using empty vector", err); - return Ok(Vec::new()); - }, - }; - - // Move to lossy vector - let mut bindings: Vec = Vec::new(); - for value in vec { - match T::deserialize(value) { - Ok(binding) => bindings.push(binding), - Err(err) => { - error!("Problem with config: {}; skipping value", err); - }, - } - } - - Ok(bindings) -} - -fn default_tabspaces() -> usize { - 8 -} - -fn deserialize_tabspaces<'a, D>(deserializer: D) -> ::std::result::Result -where - D: de::Deserializer<'a>, -{ - match usize::deserialize(deserializer) { - Ok(value) => Ok(value), - Err(err) => { - error!("Problem with config: {}; using 8", err); - Ok(default_tabspaces()) - }, - } -} - -fn deserialize_true_bool<'a, D>(deserializer: D) -> ::std::result::Result -where - D: de::Deserializer<'a>, -{ - match bool::deserialize(deserializer) { - Ok(value) => Ok(value), - Err(err) => { - error!("Problem with config: {}; using true", err); - Ok(true) - }, - } -} - -fn default_true_bool() -> bool { - true -} - -fn failure_default<'a, D, T>(deserializer: D) -> ::std::result::Result -where - D: de::Deserializer<'a>, - T: Deserialize<'a> + Default, -{ - match T::deserialize(deserializer) { - Ok(value) => Ok(value), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(T::default()) - }, - } -} - -/// Struct for scrolling related settings -#[serde(default)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] -pub struct Scrolling { - #[serde(deserialize_with = "deserialize_scrolling_history")] - pub history: u32, - #[serde(deserialize_with = "deserialize_scrolling_multiplier")] - pub multiplier: u8, - #[serde(deserialize_with = "deserialize_scrolling_multiplier")] - pub faux_multiplier: u8, - #[serde(deserialize_with = "failure_default")] - pub auto_scroll: bool, -} - -impl Default for Scrolling { - fn default() -> Self { - Self { - history: default_scrolling_history(), - multiplier: default_scrolling_multiplier(), - faux_multiplier: default_scrolling_multiplier(), - auto_scroll: Default::default(), - } - } -} - -fn default_scrolling_history() -> u32 { - 10_000 -} - -// Default for normal and faux scrolling -fn default_scrolling_multiplier() -> u8 { - 3 -} - -fn deserialize_scrolling_history<'a, D>(deserializer: D) -> ::std::result::Result -where - D: de::Deserializer<'a>, -{ - match u32::deserialize(deserializer) { - Ok(lines) => { - if lines > MAX_SCROLLBACK_LINES { - error!( - "Problem with config: scrollback size is {}, but expected a maximum of {}; \ - using {1} instead", - lines, MAX_SCROLLBACK_LINES, - ); - Ok(MAX_SCROLLBACK_LINES) - } else { - Ok(lines) - } - }, - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_scrolling_history()) - }, - } -} - -fn deserialize_scrolling_multiplier<'a, D>(deserializer: D) -> ::std::result::Result -where - D: de::Deserializer<'a>, -{ - match u8::deserialize(deserializer) { - Ok(lines) => Ok(lines), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_scrolling_multiplier()) - }, - } -} - -/// Newtype for implementing deserialize on glutin Mods -/// -/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the -/// impl below. -#[derive(Debug, Copy, Clone, Hash, Default, Eq, PartialEq)] -struct ModsWrapper(ModifiersState); - -impl ModsWrapper { - fn into_inner(self) -> ModifiersState { - self.0 - } -} - -impl<'a> de::Deserialize<'a> for ModsWrapper { - fn deserialize(deserializer: D) -> ::std::result::Result - where - D: de::Deserializer<'a>, - { - struct ModsVisitor; - - impl<'a> Visitor<'a> for ModsVisitor { - type Value = ModsWrapper; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Some subset of Command|Shift|Super|Alt|Option|Control") - } - - fn visit_str(self, value: &str) -> ::std::result::Result - where - E: de::Error, - { - let mut res = ModifiersState::default(); - for modifier in value.split('|') { - match modifier.trim() { - "Command" | "Super" => res.logo = true, - "Shift" => res.shift = true, - "Alt" | "Option" => res.alt = true, - "Control" => res.ctrl = true, - "None" => (), - _ => error!("Unknown modifier {:?}", modifier), - } - } - - Ok(ModsWrapper(res)) - } - } - - deserializer.deserialize_str(ModsVisitor) - } -} - -struct ActionWrapper(crate::input::Action); - -impl ActionWrapper { - fn into_inner(self) -> crate::input::Action { - self.0 - } -} - -impl<'a> de::Deserialize<'a> for ActionWrapper { - fn deserialize(deserializer: D) -> ::std::result::Result - where - D: de::Deserializer<'a>, - { - struct ActionVisitor; - - impl<'a> Visitor<'a> for ActionVisitor { - type Value = ActionWrapper; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str( - "Paste, Copy, PasteSelection, IncreaseFontSize, DecreaseFontSize, \ - ResetFontSize, ScrollPageUp, ScrollPageDown, ScrollLineUp, ScrollLineDown, \ - ScrollToTop, ScrollToBottom, ClearHistory, Hide, ClearLogNotice, \ - SpawnNewInstance, ToggleFullscreen, ToggleSimpleFullscreen, None or Quit", - ) - } - - fn visit_str(self, value: &str) -> ::std::result::Result - where - E: de::Error, - { - Ok(ActionWrapper(match value { - "Paste" => Action::Paste, - "Copy" => Action::Copy, - "PasteSelection" => Action::PasteSelection, - "IncreaseFontSize" => Action::IncreaseFontSize, - "DecreaseFontSize" => Action::DecreaseFontSize, - "ResetFontSize" => Action::ResetFontSize, - "ScrollPageUp" => Action::ScrollPageUp, - "ScrollPageDown" => Action::ScrollPageDown, - "ScrollLineUp" => Action::ScrollLineUp, - "ScrollLineDown" => Action::ScrollLineDown, - "ScrollToTop" => Action::ScrollToTop, - "ScrollToBottom" => Action::ScrollToBottom, - "ClearHistory" => Action::ClearHistory, - "Hide" => Action::Hide, - "Quit" => Action::Quit, - "ClearLogNotice" => Action::ClearLogNotice, - "SpawnNewInstance" => Action::SpawnNewInstance, - "ToggleFullscreen" => Action::ToggleFullscreen, - #[cfg(target_os = "macos")] - "ToggleSimpleFullscreen" => Action::ToggleSimpleFullscreen, - "None" => Action::None, - _ => return Err(E::invalid_value(Unexpected::Str(value), &self)), - })) - } - } - deserializer.deserialize_str(ActionVisitor) - } -} - -#[serde(untagged)] -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] -pub enum CommandWrapper { - Just(String), - WithArgs { - program: String, - #[serde(default)] - args: Vec, - }, -} - -impl CommandWrapper { - pub fn program(&self) -> &str { - match self { - CommandWrapper::Just(program) => program, - CommandWrapper::WithArgs { program, .. } => program, - } - } - - pub fn args(&self) -> &[String] { - match self { - CommandWrapper::Just(_) => &[], - CommandWrapper::WithArgs { args, .. } => args, - } - } -} - -use crate::term::{mode, TermMode}; - -struct ModeWrapper { - pub mode: TermMode, - pub not_mode: TermMode, -} - -impl<'a> de::Deserialize<'a> for ModeWrapper { - fn deserialize(deserializer: D) -> ::std::result::Result - where - D: de::Deserializer<'a>, - { - struct ModeVisitor; - - impl<'a> Visitor<'a> for ModeVisitor { - type Value = ModeWrapper; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Combination of AppCursor | AppKeypad, possibly with negation (~)") - } - - fn visit_str(self, value: &str) -> ::std::result::Result - where - E: de::Error, - { - let mut res = ModeWrapper { mode: TermMode::empty(), not_mode: TermMode::empty() }; - - for modifier in value.split('|') { - match modifier.trim() { - "AppCursor" => res.mode |= mode::TermMode::APP_CURSOR, - "~AppCursor" => res.not_mode |= mode::TermMode::APP_CURSOR, - "AppKeypad" => res.mode |= mode::TermMode::APP_KEYPAD, - "~AppKeypad" => res.not_mode |= mode::TermMode::APP_KEYPAD, - "~Alt" => res.not_mode |= mode::TermMode::ALT_SCREEN, - "Alt" => res.mode |= mode::TermMode::ALT_SCREEN, - _ => error!("Unknown mode {:?}", modifier), - } - } - - Ok(res) - } - } - deserializer.deserialize_str(ModeVisitor) - } -} - -struct MouseButton(::glutin::MouseButton); - -impl MouseButton { - fn into_inner(self) -> ::glutin::MouseButton { - self.0 - } -} - -impl<'a> de::Deserialize<'a> for MouseButton { - fn deserialize(deserializer: D) -> ::std::result::Result - where - D: de::Deserializer<'a>, - { - struct MouseButtonVisitor; - - impl<'a> Visitor<'a> for MouseButtonVisitor { - type Value = MouseButton; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Left, Right, Middle, or a number") - } - - fn visit_str(self, value: &str) -> ::std::result::Result - where - E: de::Error, - { - match value { - "Left" => Ok(MouseButton(::glutin::MouseButton::Left)), - "Right" => Ok(MouseButton(::glutin::MouseButton::Right)), - "Middle" => Ok(MouseButton(::glutin::MouseButton::Middle)), - _ => { - if let Ok(index) = u8::from_str(value) { - Ok(MouseButton(::glutin::MouseButton::Other(index))) - } else { - Err(E::invalid_value(Unexpected::Str(value), &self)) - } - }, - } - } - } - - deserializer.deserialize_str(MouseButtonVisitor) - } -} - -/// Bindings are deserialized into a `RawBinding` before being parsed as a -/// `KeyBinding` or `MouseBinding`. -#[derive(PartialEq, Eq)] -struct RawBinding { - key: Option, - mouse: Option<::glutin::MouseButton>, - mods: ModifiersState, - mode: TermMode, - notmode: TermMode, - action: Action, -} - -impl RawBinding { - fn into_mouse_binding(self) -> ::std::result::Result { - if let Some(mouse) = self.mouse { - Ok(Binding { - trigger: mouse, - mods: self.mods, - action: self.action, - mode: self.mode, - notmode: self.notmode, - }) - } else { - Err(self) - } - } - - fn into_key_binding(self) -> ::std::result::Result { - if let Some(key) = self.key { - Ok(KeyBinding { - trigger: key, - mods: self.mods, - action: self.action, - mode: self.mode, - notmode: self.notmode, - }) - } else { - Err(self) - } - } -} - -impl<'a> de::Deserialize<'a> for RawBinding { - fn deserialize(deserializer: D) -> ::std::result::Result - where - D: de::Deserializer<'a>, - { - enum Field { - Key, - Mods, - Mode, - Action, - Chars, - Mouse, - Command, - } - - impl<'a> de::Deserialize<'a> for Field { - fn deserialize(deserializer: D) -> ::std::result::Result - where - D: de::Deserializer<'a>, - { - struct FieldVisitor; - - static FIELDS: &'static [&'static str] = - &["key", "mods", "mode", "action", "chars", "mouse", "command"]; - - impl<'a> Visitor<'a> for FieldVisitor { - type Value = Field; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("binding fields") - } - - fn visit_str(self, value: &str) -> ::std::result::Result - where - E: de::Error, - { - match value { - "key" => Ok(Field::Key), - "mods" => Ok(Field::Mods), - "mode" => Ok(Field::Mode), - "action" => Ok(Field::Action), - "chars" => Ok(Field::Chars), - "mouse" => Ok(Field::Mouse), - "command" => Ok(Field::Command), - _ => Err(E::unknown_field(value, FIELDS)), - } - } - } - - deserializer.deserialize_str(FieldVisitor) - } - } - - struct RawBindingVisitor; - impl<'a> Visitor<'a> for RawBindingVisitor { - type Value = RawBinding; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("binding specification") - } - - fn visit_map(self, mut map: V) -> ::std::result::Result - where - V: MapAccess<'a>, - { - let mut mods: Option = None; - let mut key: Option = None; - let mut chars: Option = None; - let mut action: Option = None; - let mut mode: Option = None; - let mut not_mode: Option = None; - let mut mouse: Option<::glutin::MouseButton> = None; - let mut command: Option = None; - - use ::serde::de::Error; - - while let Some(struct_key) = map.next_key::()? { - match struct_key { - Field::Key => { - if key.is_some() { - return Err(::duplicate_field("key")); - } - - let val = map.next_value::()?; - if val.is_u64() { - let scancode = val.as_u64().unwrap(); - if scancode > u64::from(::std::u32::MAX) { - return Err(::custom(format!( - "Invalid key binding, scancode too big: {}", - scancode - ))); - } - key = Some(Key::Scancode(scancode as u32)); - } else { - let k = Key::deserialize(val).map_err(V::Error::custom)?; - key = Some(k); - } - }, - Field::Mods => { - if mods.is_some() { - return Err(::duplicate_field("mods")); - } - - mods = Some(map.next_value::()?.into_inner()); - }, - Field::Mode => { - if mode.is_some() { - return Err(::duplicate_field("mode")); - } - - let mode_deserializer = map.next_value::()?; - mode = Some(mode_deserializer.mode); - not_mode = Some(mode_deserializer.not_mode); - }, - Field::Action => { - if action.is_some() { - return Err(::duplicate_field("action")); - } - - action = Some(map.next_value::()?.into_inner()); - }, - Field::Chars => { - if chars.is_some() { - return Err(::duplicate_field("chars")); - } - - chars = Some(map.next_value()?); - }, - Field::Mouse => { - if chars.is_some() { - return Err(::duplicate_field("mouse")); - } - - mouse = Some(map.next_value::()?.into_inner()); - }, - Field::Command => { - if command.is_some() { - return Err(::duplicate_field("command")); - } - - command = Some(map.next_value::()?); - }, - } - } - - let action = match (action, chars, command) { - (Some(action), None, None) => action, - (None, Some(chars), None) => Action::Esc(chars), - (None, None, Some(cmd)) => match cmd { - CommandWrapper::Just(program) => Action::Command(program, vec![]), - CommandWrapper::WithArgs { program, args } => { - Action::Command(program, args) - }, - }, - (None, None, None) => { - return Err(V::Error::custom("must specify chars, action or command")); - }, - _ => { - return Err(V::Error::custom("must specify only chars, action or command")) - }, - }; - - let mode = mode.unwrap_or_else(TermMode::empty); - let not_mode = not_mode.unwrap_or_else(TermMode::empty); - let mods = mods.unwrap_or_else(ModifiersState::default); - - if mouse.is_none() && key.is_none() { - return Err(V::Error::custom("bindings require mouse button or key")); - } - - Ok(RawBinding { mode, notmode: not_mode, action, key, mouse, mods }) - } - } - - const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"]; - - deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor) - } -} - -impl<'a> de::Deserialize<'a> for Alpha { - fn deserialize(deserializer: D) -> ::std::result::Result - where - D: de::Deserializer<'a>, - { - let value = f32::deserialize(deserializer)?; - Ok(Alpha::new(value)) - } -} - -impl<'a> de::Deserialize<'a> for MouseBinding { - fn deserialize(deserializer: D) -> ::std::result::Result - where - D: de::Deserializer<'a>, - { - let raw = RawBinding::deserialize(deserializer)?; - raw.into_mouse_binding().map_err(|_| D::Error::custom("expected mouse binding")) - } -} - -impl<'a> de::Deserialize<'a> for KeyBinding { - fn deserialize(deserializer: D) -> ::std::result::Result - where - D: de::Deserializer<'a>, - { - let raw = RawBinding::deserialize(deserializer)?; - raw.into_key_binding().map_err(|_| D::Error::custom("expected key binding")) - } -} - -/// Errors occurring during config loading -#[derive(Debug)] -pub enum Error { - /// Config file not found - NotFound, - - /// Config file empty - Empty, - - /// Couldn't read $HOME environment variable - ReadingEnvHome(env::VarError), - - /// io error reading file - Io(io::Error), - - /// Not valid yaml or missing parameters - Yaml(serde_yaml::Error), -} - -#[serde(default)] -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct Colors { - #[serde(deserialize_with = "failure_default")] - pub primary: PrimaryColors, - #[serde(deserialize_with = "failure_default")] - pub cursor: CursorColors, - #[serde(deserialize_with = "failure_default")] - pub selection: SelectionColors, - #[serde(deserialize_with = "deserialize_normal_colors")] - pub normal: AnsiColors, - #[serde(deserialize_with = "deserialize_bright_colors")] - pub bright: AnsiColors, - #[serde(deserialize_with = "failure_default")] - pub dim: Option, - #[serde(deserialize_with = "failure_default_vec")] - pub indexed_colors: Vec, -} - -impl Default for Colors { - fn default() -> Colors { - Colors { - primary: Default::default(), - cursor: Default::default(), - selection: Default::default(), - normal: default_normal_colors(), - bright: default_bright_colors(), - dim: Default::default(), - indexed_colors: Default::default(), - } - } -} - -fn default_normal_colors() -> AnsiColors { - AnsiColors { - black: Rgb { r: 0x00, g: 0x00, b: 0x00 }, - red: Rgb { r: 0xd5, g: 0x4e, b: 0x53 }, - green: Rgb { r: 0xb9, g: 0xca, b: 0x4a }, - yellow: Rgb { r: 0xe6, g: 0xc5, b: 0x47 }, - blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda }, - magenta: Rgb { r: 0xc3, g: 0x97, b: 0xd8 }, - cyan: Rgb { r: 0x70, g: 0xc0, b: 0xba }, - white: Rgb { r: 0xea, g: 0xea, b: 0xea }, - } -} - -fn default_bright_colors() -> AnsiColors { - AnsiColors { - black: Rgb { r: 0x66, g: 0x66, b: 0x66 }, - red: Rgb { r: 0xff, g: 0x33, b: 0x34 }, - green: Rgb { r: 0x9e, g: 0xc4, b: 0x00 }, - yellow: Rgb { r: 0xe7, g: 0xc5, b: 0x47 }, - blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda }, - magenta: Rgb { r: 0xb7, g: 0x7e, b: 0xe0 }, - cyan: Rgb { r: 0x54, g: 0xce, b: 0xd6 }, - white: Rgb { r: 0xff, g: 0xff, b: 0xff }, - } -} - -fn deserialize_normal_colors<'a, D>(deserializer: D) -> ::std::result::Result -where - D: de::Deserializer<'a>, -{ - match AnsiColors::deserialize(deserializer) { - Ok(escape_chars) => Ok(escape_chars), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_normal_colors()) - }, - } -} - -fn deserialize_bright_colors<'a, D>(deserializer: D) -> ::std::result::Result -where - D: de::Deserializer<'a>, -{ - match AnsiColors::deserialize(deserializer) { - Ok(escape_chars) => Ok(escape_chars), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_bright_colors()) - }, - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct IndexedColor { - #[serde(deserialize_with = "deserialize_color_index")] - pub index: u8, - #[serde(deserialize_with = "rgb_from_hex")] - pub color: Rgb, -} - -fn deserialize_color_index<'a, D>(deserializer: D) -> ::std::result::Result -where - D: de::Deserializer<'a>, -{ - match u8::deserialize(deserializer) { - Ok(index) => { - if index < 16 { - error!( - "Problem with config: indexed_color's index is {}, but a value bigger than 15 \ - was expected; ignoring setting", - index - ); - - // Return value out of range to ignore this color - Ok(0) - } else { - Ok(index) - } - }, - Err(err) => { - error!("Problem with config: {}; ignoring setting", err); - - // Return value out of range to ignore this color - Ok(0) - }, - } -} - -#[serde(default)] -#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct Cursor { - #[serde(deserialize_with = "failure_default")] - pub style: CursorStyle, - #[serde(deserialize_with = "deserialize_true_bool")] - pub unfocused_hollow: bool, -} - -impl Default for Cursor { - fn default() -> Self { - Self { style: Default::default(), unfocused_hollow: true } - } -} - -#[serde(default)] -#[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq, Eq)] -pub struct CursorColors { - #[serde(deserialize_with = "deserialize_optional_color")] - pub text: Option, - #[serde(deserialize_with = "deserialize_optional_color")] - pub cursor: Option, -} - -#[serde(default)] -#[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq, Eq)] -pub struct SelectionColors { - #[serde(deserialize_with = "deserialize_optional_color")] - pub text: Option, - #[serde(deserialize_with = "deserialize_optional_color")] - pub background: Option, -} - -#[serde(default)] -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct PrimaryColors { - #[serde(deserialize_with = "rgb_from_hex")] - pub background: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub foreground: Rgb, - #[serde(deserialize_with = "deserialize_optional_color")] - pub bright_foreground: Option, - #[serde(deserialize_with = "deserialize_optional_color")] - pub dim_foreground: Option, -} - -impl Default for PrimaryColors { - fn default() -> Self { - PrimaryColors { - background: default_background(), - foreground: default_foreground(), - bright_foreground: Default::default(), - dim_foreground: Default::default(), - } - } -} - -fn deserialize_optional_color<'a, D>( - deserializer: D, -) -> ::std::result::Result, D::Error> -where - D: de::Deserializer<'a>, -{ - match Option::deserialize(deserializer) { - Ok(Some(color)) => { - let color: serde_yaml::Value = color; - Ok(Some(rgb_from_hex(color).unwrap())) - }, - Ok(None) => Ok(None), - Err(err) => { - error!("Problem with config: {}; using standard foreground color", err); - Ok(None) - }, - } -} - -fn default_background() -> Rgb { - Rgb { r: 0, g: 0, b: 0 } -} - -fn default_foreground() -> Rgb { - Rgb { r: 0xea, g: 0xea, b: 0xea } -} - -/// The 8-colors sections of config -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct AnsiColors { - #[serde(deserialize_with = "rgb_from_hex")] - pub black: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub red: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub green: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub yellow: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub blue: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub magenta: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub cyan: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub white: Rgb, -} - -/// Deserialize an Rgb from a hex string -/// -/// This is *not* the deserialize impl for Rgb since we want a symmetric -/// serialize/deserialize impl for ref tests. -fn rgb_from_hex<'a, D>(deserializer: D) -> ::std::result::Result -where - D: de::Deserializer<'a>, -{ - struct RgbVisitor; - - impl<'a> Visitor<'a> for RgbVisitor { - type Value = Rgb; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("hex color like 0xff00ff") - } - - fn visit_str(self, value: &str) -> ::std::result::Result - where - E: ::serde::de::Error, - { - Rgb::from_str(&value[..]) - .map_err(|_| E::custom("failed to parse rgb; expected hex color like 0xff00ff")) - } - } - - let rgb = deserializer.deserialize_str(RgbVisitor); - - // Use #ff00ff as fallback color - match rgb { - Ok(rgb) => Ok(rgb), - Err(err) => { - error!("Problem with config: {}; using color #ff00ff", err); - Ok(Rgb { r: 255, g: 0, b: 255 }) - }, - } -} - -impl FromStr for Rgb { - type Err = (); - - fn from_str(s: &str) -> ::std::result::Result { - let mut chars = s.chars(); - let mut rgb = Rgb::default(); - - macro_rules! component { - ($($c:ident),*) => { - $( - match chars.next().and_then(|c| c.to_digit(16)) { - Some(val) => rgb.$c = (val as u8) << 4, - None => return Err(()) - } - - match chars.next().and_then(|c| c.to_digit(16)) { - Some(val) => rgb.$c |= val as u8, - None => return Err(()) - } - )* - } - } - - match chars.next() { - Some('0') => { - if chars.next() != Some('x') { - return Err(()); - } - }, - Some('#') => (), - _ => return Err(()), - } - - component!(r, g, b); - - Ok(rgb) - } -} - -impl ::std::error::Error for Error { - fn cause(&self) -> Option<&dyn (::std::error::Error)> { - match *self { - Error::NotFound | Error::Empty => None, - Error::ReadingEnvHome(ref err) => Some(err), - Error::Io(ref err) => Some(err), - Error::Yaml(ref err) => Some(err), - } - } - - fn description(&self) -> &str { - match *self { - Error::NotFound => "Couldn't locate config file", - Error::Empty => "Empty config file", - Error::ReadingEnvHome(ref err) => err.description(), - Error::Io(ref err) => err.description(), - Error::Yaml(ref err) => err.description(), - } - } -} - -impl ::std::fmt::Display for Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match *self { - Error::NotFound | Error::Empty => { - write!(f, "{}", ::std::error::Error::description(self)) - }, - Error::ReadingEnvHome(ref err) => { - write!(f, "Couldn't read $HOME environment variable: {}", err) - }, - Error::Io(ref err) => write!(f, "Error reading config file: {}", err), - Error::Yaml(ref err) => write!(f, "Problem with config: {}", err), - } - } -} - -impl From for Error { - fn from(val: env::VarError) -> Error { - Error::ReadingEnvHome(val) - } -} - -impl From for Error { - fn from(val: io::Error) -> Error { - if val.kind() == io::ErrorKind::NotFound { - Error::NotFound - } else { - Error::Io(val) - } - } -} - -impl From for Error { - fn from(val: serde_yaml::Error) -> Error { - Error::Yaml(val) - } -} - -/// Result from config loading -pub type Result = ::std::result::Result; - -impl Config { - /// Get the location of the first found default config file paths - /// according to the following order: - /// - /// 1. $XDG_CONFIG_HOME/alacritty/alacritty.yml - /// 2. $XDG_CONFIG_HOME/alacritty.yml - /// 3. $HOME/.config/alacritty/alacritty.yml - /// 4. $HOME/.alacritty.yml - #[cfg(not(windows))] - pub fn installed_config<'a>() -> Option> { - // Try using XDG location by default - ::xdg::BaseDirectories::with_prefix("alacritty") - .ok() - .and_then(|xdg| xdg.find_config_file("alacritty.yml")) - .or_else(|| { - ::xdg::BaseDirectories::new() - .ok() - .and_then(|fallback| fallback.find_config_file("alacritty.yml")) - }) - .or_else(|| { - if let Ok(home) = env::var("HOME") { - // Fallback path: $HOME/.config/alacritty/alacritty.yml - let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml"); - if fallback.exists() { - return Some(fallback); - } - // Fallback path: $HOME/.alacritty.yml - let fallback = PathBuf::from(&home).join(".alacritty.yml"); - if fallback.exists() { - return Some(fallback); - } - } - None - }) - .map(Into::into) - } - - // TODO: Remove old configuration location warning (Deprecated 03/12/2018) - #[cfg(windows)] - pub fn installed_config<'a>() -> Option> { - let old = dirs::home_dir().map(|path| path.join("alacritty.yml")); - let new = dirs::config_dir().map(|path| path.join("alacritty\\alacritty.yml")); - - if let Some(old_path) = old.as_ref().filter(|old| old.exists()) { - warn!( - "Found configuration at: {}; this file should be moved to the new location: {}", - old_path.to_string_lossy(), - new.as_ref().map(|new| new.to_string_lossy()).unwrap(), - ); - - old.map(Cow::from) - } else { - new.filter(|new| new.exists()).map(Cow::from) - } - } - - #[cfg(not(windows))] - pub fn write_defaults() -> io::Result> { - let path = xdg::BaseDirectories::with_prefix("alacritty") - .map_err(|err| io::Error::new(io::ErrorKind::NotFound, err.to_string().as_str())) - .and_then(|p| p.place_config_file("alacritty.yml"))?; - - File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?; - - Ok(path.into()) - } - - #[cfg(windows)] - pub fn write_defaults() -> io::Result> { - let mut path = dirs::config_dir().ok_or_else(|| { - io::Error::new(io::ErrorKind::NotFound, "Couldn't find profile directory") - })?; - - path = path.join("alacritty/alacritty.yml"); - - std::fs::create_dir_all(path.parent().unwrap())?; - - File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?; - - Ok(path.into()) - } - - /// Get list of colors - /// - /// The ordering returned here is expected by the terminal. Colors are simply indexed in this - /// array for performance. - pub fn colors(&self) -> &Colors { - &self.colors - } - - #[inline] - pub fn background_opacity(&self) -> Alpha { - self.background_opacity - } - - pub fn key_bindings(&self) -> &[KeyBinding] { - &self.key_bindings[..] - } - - pub fn mouse_bindings(&self) -> &[MouseBinding] { - &self.mouse_bindings[..] - } - - pub fn mouse(&self) -> &Mouse { - &self.mouse - } - - pub fn selection(&self) -> &Selection { - &self.selection - } - - pub fn tabspaces(&self) -> usize { - self.tabspaces - } - - pub fn padding(&self) -> &Delta { - self.padding.as_ref().unwrap_or(&self.window.padding) - } - - #[inline] - pub fn draw_bold_text_with_bright_colors(&self) -> bool { - self.draw_bold_text_with_bright_colors - } - - /// Get font config - #[inline] - pub fn font(&self) -> &Font { - &self.font - } - - /// Get window dimensions - #[inline] - pub fn dimensions(&self) -> Dimensions { - self.dimensions.unwrap_or(self.window.dimensions) - } - - /// Get window config - #[inline] - pub fn window(&self) -> &WindowConfig { - &self.window - } - - /// Get visual bell config - #[inline] - pub fn visual_bell(&self) -> &VisualBellConfig { - &self.visual_bell - } - - /// Should show render timer - #[inline] - pub fn render_timer(&self) -> bool { - self.render_timer - } - - #[cfg(target_os = "macos")] - #[inline] - pub fn use_thin_strokes(&self) -> bool { - self.font.use_thin_strokes - } - - #[cfg(not(target_os = "macos"))] - #[inline] - pub fn use_thin_strokes(&self) -> bool { - false - } - - pub fn path(&self) -> Option<&Path> { - self.config_path.as_ref().map(PathBuf::as_path) - } - - pub fn shell(&self) -> Option<&Shell<'_>> { - self.shell.as_ref() - } - - pub fn env(&self) -> &HashMap { - &self.env - } - - /// Should hide mouse cursor when typing - #[inline] - pub fn hide_mouse_when_typing(&self) -> bool { - self.hide_cursor_when_typing.unwrap_or(self.mouse.hide_when_typing) - } - - /// Style of the cursor - #[inline] - pub fn cursor_style(&self) -> CursorStyle { - self.cursor_style.unwrap_or(self.cursor.style) - } - - /// Use hollow block cursor when unfocused - #[inline] - pub fn unfocused_hollow_cursor(&self) -> bool { - self.unfocused_hollow_cursor.unwrap_or(self.cursor.unfocused_hollow) - } - - /// Live config reload - #[inline] - pub fn live_config_reload(&self) -> bool { - self.live_config_reload - } - - #[inline] - pub fn dynamic_title(&self) -> bool { - self.dynamic_title - } - - /// Scrolling settings - #[inline] - pub fn scrolling(&self) -> Scrolling { - self.scrolling - } - - /// Cursor foreground color - #[inline] - pub fn cursor_text_color(&self) -> Option { - self.colors.cursor.text - } - - /// Cursor background color - #[inline] - pub fn cursor_cursor_color(&self) -> Option { - self.colors.cursor.cursor - } - - /// Enable experimental conpty backend (Windows only) - #[cfg(windows)] - #[inline] - pub fn enable_experimental_conpty_backend(&self) -> bool { - self.enable_experimental_conpty_backend - } - - /// Send escape sequences using the alt key - #[inline] - pub fn alt_send_esc(&self) -> bool { - self.alt_send_esc - } - - // Update the history size, used in ref tests - pub fn set_history(&mut self, history: u32) { - self.scrolling.history = history; - } - - /// Keep the log file after quitting Alacritty - #[inline] - pub fn persistent_logging(&self) -> bool { - self.persistent_logging - } - - /// Overrides the `dynamic_title` configuration based on `--title`. - pub fn update_dynamic_title(mut self, options: &Options) -> Self { - if options.title.is_some() { - self.dynamic_title = false; - } - self - } - - pub fn load_from(path: PathBuf) -> Config { - let mut config = Config::reload_from(&path).unwrap_or_else(|_| Config::default()); - config.config_path = Some(path); - config - } - - pub fn reload_from(path: &PathBuf) -> Result { - match Config::read_config(path) { - Ok(config) => Ok(config), - Err(err) => { - error!("Unable to load config {:?}: {}", path, err); - Err(err) - }, - } - } - - fn read_config(path: &PathBuf) -> Result { - let mut contents = String::new(); - File::open(path)?.read_to_string(&mut contents)?; - - // Prevent parsing error with empty string - if contents.is_empty() { - return Ok(Config::default()); - } - - let mut config: Config = serde_yaml::from_str(&contents)?; - config.print_deprecation_warnings(); - - Ok(config) - } - - fn print_deprecation_warnings(&mut self) { - if self.dimensions.is_some() { - warn!("Config dimensions is deprecated; please use window.dimensions instead"); - } - - if self.padding.is_some() { - warn!("Config padding is deprecated; please use window.padding instead"); - } - - if self.mouse.faux_scrollback_lines.is_some() { - warn!( - "Config mouse.faux_scrollback_lines is deprecated; please use \ - mouse.faux_scrolling_lines instead" - ); - } - - if let Some(custom_cursor_colors) = self.custom_cursor_colors { - warn!("Config custom_cursor_colors is deprecated"); - - if !custom_cursor_colors { - self.colors.cursor.cursor = None; - self.colors.cursor.text = None; - } - } - - if self.cursor_style.is_some() { - warn!("Config cursor_style is deprecated; please use cursor.style instead"); - } - - if self.hide_cursor_when_typing.is_some() { - warn!( - "Config hide_cursor_when_typing is deprecated; please use mouse.hide_when_typing \ - instead" - ); - } - - if self.unfocused_hollow_cursor.is_some() { - warn!( - "Config unfocused_hollow_cursor is deprecated; please use cursor.unfocused_hollow \ - instead" - ); - } - - if let Some(start_maximized) = self.window.start_maximized { - warn!( - "Config window.start_maximized is deprecated; please use window.startup_mode \ - instead" - ); - - // While `start_maximized` is deprecated its setting takes precedence. - if start_maximized { - self.window.startup_mode = StartupMode::Maximized; - } - } - } -} - -/// Window Dimensions -/// -/// Newtype to avoid passing values incorrectly -#[serde(default)] -#[derive(Default, Debug, Copy, Clone, Deserialize, PartialEq, Eq)] -pub struct Dimensions { - /// Window width in character columns - #[serde(deserialize_with = "failure_default")] - columns: Column, - - /// Window Height in character lines - #[serde(deserialize_with = "failure_default")] - lines: Line, -} - -impl Dimensions { - pub fn new(columns: Column, lines: Line) -> Self { - Dimensions { columns, lines } - } - - /// Get lines - #[inline] - pub fn lines_u32(&self) -> u32 { - self.lines.0 as u32 - } - - /// Get columns - #[inline] - pub fn columns_u32(&self) -> u32 { - self.columns.0 as u32 - } } /// A delta for a point in a 2 dimensional plane @@ -2076,675 +369,66 @@ pub struct Delta { pub y: T, } -trait DeserializeSize: Sized { - fn deserialize<'a, D>(_: D) -> ::std::result::Result - where - D: serde::de::Deserializer<'a>; +/// Wrapper around f32 that represents an alpha value between 0.0 and 1.0 +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Alpha(f32); + +impl Alpha { + pub fn new(value: f32) -> Self { + Alpha(if value < 0.0 { + 0.0 + } else if value > 1.0 { + 1.0 + } else { + value + }) + } } -impl DeserializeSize for Size { - fn deserialize<'a, D>(deserializer: D) -> ::std::result::Result +impl Default for Alpha { + fn default() -> Self { + Alpha(1.0) + } +} + +impl<'a> Deserialize<'a> for Alpha { + fn deserialize(deserializer: D) -> Result where - D: serde::de::Deserializer<'a>, + D: Deserializer<'a>, { - use std::marker::PhantomData; - - struct NumVisitor<__D> { - _marker: PhantomData<__D>, - } - - impl<'a, __D> Visitor<'a> for NumVisitor<__D> - where - __D: serde::de::Deserializer<'a>, - { - type Value = f64; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("f64 or u64") - } - - fn visit_f64(self, value: f64) -> ::std::result::Result - where - E: ::serde::de::Error, - { - Ok(value) - } - - fn visit_u64(self, value: u64) -> ::std::result::Result - where - E: ::serde::de::Error, - { - Ok(value as f64) - } - } - - let size = deserializer - .deserialize_any(NumVisitor:: { _marker: PhantomData }) - .map(|v| Size::new(v as _)); - - // Use default font size as fallback - match size { - Ok(size) => Ok(size), - Err(err) => { - let size = default_font_size(); - error!("Problem with config: {}; using size {}", err, size.as_f32_pts()); - Ok(size) - }, - } + Ok(Alpha::new(f32::deserialize(deserializer)?)) } } -/// Font config -/// -/// Defaults are provided at the level of this struct per platform, but not per -/// field in this struct. It might be nice in the future to have defaults for -/// each value independently. Alternatively, maybe erroring when the user -/// doesn't provide complete config is Ok. -#[serde(default)] -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] -pub struct Font { - /// Normal font face - #[serde(deserialize_with = "failure_default")] - normal: FontDescription, +#[derive(Deserialize, Debug, PartialEq, Eq)] +struct Tabspaces(usize); - /// Bold font face - #[serde(deserialize_with = "failure_default")] - italic: SecondaryFontDescription, - - /// Italic font face - #[serde(deserialize_with = "failure_default")] - bold: SecondaryFontDescription, - - /// Font size in points - #[serde(deserialize_with = "DeserializeSize::deserialize")] - pub size: Size, - - /// Extra spacing per character - #[serde(deserialize_with = "failure_default")] - offset: Delta, - - /// Glyph offset within character cell - #[serde(deserialize_with = "failure_default")] - glyph_offset: Delta, - - #[cfg(target_os = "macos")] - #[serde(deserialize_with = "deserialize_true_bool")] - use_thin_strokes: bool, - - // TODO: Deprecated - #[serde(deserialize_with = "deserialize_scale_with_dpi")] - scale_with_dpi: Option<()>, -} - -impl Default for Font { - fn default() -> Font { - Font { - #[cfg(target_os = "macos")] - use_thin_strokes: true, - size: default_font_size(), - normal: Default::default(), - bold: Default::default(), - italic: Default::default(), - scale_with_dpi: Default::default(), - glyph_offset: Default::default(), - offset: Default::default(), - } +impl Default for Tabspaces { + fn default() -> Self { + Tabspaces(8) } } -impl Font { - /// Get the font size in points - #[inline] - pub fn size(&self) -> Size { - self.size - } +#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq)] +struct DefaultTrueBool(bool); - /// Get offsets to font metrics - #[inline] - pub fn offset(&self) -> &Delta { - &self.offset - } - - /// Get cell offsets for glyphs - #[inline] - pub fn glyph_offset(&self) -> &Delta { - &self.glyph_offset - } - - /// Get a font clone with a size modification - pub fn with_size(self, size: Size) -> Font { - Font { size, ..self } - } - - // Get normal font description - pub fn normal(&self) -> &FontDescription { - &self.normal - } - - // Get italic font description - pub fn italic(&self) -> FontDescription { - self.italic.desc(&self.normal) - } - - // Get bold font description - pub fn bold(&self) -> FontDescription { - self.bold.desc(&self.normal) +impl Default for DefaultTrueBool { + fn default() -> Self { + DefaultTrueBool(true) } } -fn default_font_size() -> Size { - Size::new(11.) -} - -fn deserialize_scale_with_dpi<'a, D>(deserializer: D) -> ::std::result::Result, D::Error> +pub fn failure_default<'a, D, T>(deserializer: D) -> Result where - D: de::Deserializer<'a>, + D: Deserializer<'a>, + T: Deserialize<'a> + Default, { - // This is necessary in order to get serde to complete deserialization of the configuration - let _ignored = bool::deserialize(deserializer); - error!( - "The scale_with_dpi setting has been removed, on X11 the WINIT_HIDPI_FACTOR environment \ - variable can be used instead." - ); - Ok(None) -} - -/// Description of the normal font -#[serde(default)] -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] -pub struct FontDescription { - #[serde(deserialize_with = "failure_default")] - pub family: String, - #[serde(deserialize_with = "failure_default")] - pub style: Option, -} - -impl Default for FontDescription { - fn default() -> FontDescription { - FontDescription { - #[cfg(not(any(target_os = "macos", windows)))] - family: "monospace".into(), - #[cfg(target_os = "macos")] - family: "Menlo".into(), - #[cfg(windows)] - family: "Consolas".into(), - style: None, - } - } -} - -/// Description of the italic and bold font -#[serde(default)] -#[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)] -pub struct SecondaryFontDescription { - #[serde(deserialize_with = "failure_default")] - family: Option, - #[serde(deserialize_with = "failure_default")] - style: Option, -} - -impl SecondaryFontDescription { - pub fn desc(&self, fallback: &FontDescription) -> FontDescription { - FontDescription { - family: self.family.clone().unwrap_or_else(|| fallback.family.clone()), - style: self.style.clone(), - } - } -} - -pub struct Monitor { - _thread: ::std::thread::JoinHandle<()>, - rx: mpsc::Receiver, -} - -pub trait OnConfigReload { - fn on_config_reload(&mut self); -} - -impl OnConfigReload for crate::display::Notifier { - fn on_config_reload(&mut self) { - self.notify(); - } -} - -impl Monitor { - /// Get pending config changes - pub fn pending(&self) -> Option { - let mut config = None; - while let Ok(new) = self.rx.try_recv() { - config = Some(new); - } - - config - } - - pub fn new(path: P, mut handler: H) -> Monitor - where - H: OnConfigReload + Send + 'static, - P: Into, - { - let path = path.into(); - - let (config_tx, config_rx) = mpsc::channel(); - - Monitor { - _thread: crate::util::thread::spawn_named("config watcher", move || { - let (tx, rx) = mpsc::channel(); - // The Duration argument is a debouncing period. - let mut watcher = - watcher(tx, Duration::from_millis(10)).expect("Unable to spawn file watcher"); - let config_path = ::std::fs::canonicalize(path).expect("canonicalize config path"); - - // Get directory of config - let mut parent = config_path.clone(); - parent.pop(); - - // Watch directory - watcher - .watch(&parent, RecursiveMode::NonRecursive) - .expect("watch alacritty.yml dir"); - - loop { - match rx.recv().expect("watcher event") { - DebouncedEvent::Rename(..) => continue, - DebouncedEvent::Write(path) - | DebouncedEvent::Create(path) - | DebouncedEvent::Chmod(path) => { - if path != config_path { - continue; - } - - let _ = config_tx.send(path); - handler.on_config_reload(); - }, - _ => {}, - } - } - }), - rx: config_rx, - } - } -} - -#[derive(Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub enum Key { - Scancode(u32), - Key1, - Key2, - Key3, - Key4, - Key5, - Key6, - Key7, - Key8, - Key9, - Key0, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - Escape, - F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - F13, - F14, - F15, - F16, - F17, - F18, - F19, - F20, - F21, - F22, - F23, - F24, - Snapshot, - Scroll, - Pause, - Insert, - Home, - Delete, - End, - PageDown, - PageUp, - Left, - Up, - Right, - Down, - Back, - Return, - Space, - Compose, - Numlock, - Numpad0, - Numpad1, - Numpad2, - Numpad3, - Numpad4, - Numpad5, - Numpad6, - Numpad7, - Numpad8, - Numpad9, - AbntC1, - AbntC2, - Add, - Apostrophe, - Apps, - At, - Ax, - Backslash, - Calculator, - Capital, - Colon, - Comma, - Convert, - Decimal, - Divide, - Equals, - Grave, - Kana, - Kanji, - LAlt, - LBracket, - LControl, - LShift, - LWin, - Mail, - MediaSelect, - MediaStop, - Minus, - Multiply, - Mute, - MyComputer, - NavigateForward, - NavigateBackward, - NextTrack, - NoConvert, - NumpadComma, - NumpadEnter, - NumpadEquals, - OEM102, - Period, - PlayPause, - Power, - PrevTrack, - RAlt, - RBracket, - RControl, - RShift, - RWin, - Semicolon, - Slash, - Sleep, - Stop, - Subtract, - Sysrq, - Tab, - Underline, - Unlabeled, - VolumeDown, - VolumeUp, - Wake, - WebBack, - WebFavorites, - WebForward, - WebHome, - WebRefresh, - WebSearch, - WebStop, - Yen, - Caret, - Copy, - Paste, - Cut, -} - -impl Key { - pub fn from_glutin_input(key: ::glutin::VirtualKeyCode) -> Self { - use glutin::VirtualKeyCode::*; - // Thank you, vim macros and regex! - match key { - Key1 => Key::Key1, - Key2 => Key::Key2, - Key3 => Key::Key3, - Key4 => Key::Key4, - Key5 => Key::Key5, - Key6 => Key::Key6, - Key7 => Key::Key7, - Key8 => Key::Key8, - Key9 => Key::Key9, - Key0 => Key::Key0, - A => Key::A, - B => Key::B, - C => Key::C, - D => Key::D, - E => Key::E, - F => Key::F, - G => Key::G, - H => Key::H, - I => Key::I, - J => Key::J, - K => Key::K, - L => Key::L, - M => Key::M, - N => Key::N, - O => Key::O, - P => Key::P, - Q => Key::Q, - R => Key::R, - S => Key::S, - T => Key::T, - U => Key::U, - V => Key::V, - W => Key::W, - X => Key::X, - Y => Key::Y, - Z => Key::Z, - Escape => Key::Escape, - F1 => Key::F1, - F2 => Key::F2, - F3 => Key::F3, - F4 => Key::F4, - F5 => Key::F5, - F6 => Key::F6, - F7 => Key::F7, - F8 => Key::F8, - F9 => Key::F9, - F10 => Key::F10, - F11 => Key::F11, - F12 => Key::F12, - F13 => Key::F13, - F14 => Key::F14, - F15 => Key::F15, - F16 => Key::F16, - F17 => Key::F17, - F18 => Key::F18, - F19 => Key::F19, - F20 => Key::F20, - F21 => Key::F21, - F22 => Key::F22, - F23 => Key::F23, - F24 => Key::F24, - Snapshot => Key::Snapshot, - Scroll => Key::Scroll, - Pause => Key::Pause, - Insert => Key::Insert, - Home => Key::Home, - Delete => Key::Delete, - End => Key::End, - PageDown => Key::PageDown, - PageUp => Key::PageUp, - Left => Key::Left, - Up => Key::Up, - Right => Key::Right, - Down => Key::Down, - Back => Key::Back, - Return => Key::Return, - Space => Key::Space, - Compose => Key::Compose, - Numlock => Key::Numlock, - Numpad0 => Key::Numpad0, - Numpad1 => Key::Numpad1, - Numpad2 => Key::Numpad2, - Numpad3 => Key::Numpad3, - Numpad4 => Key::Numpad4, - Numpad5 => Key::Numpad5, - Numpad6 => Key::Numpad6, - Numpad7 => Key::Numpad7, - Numpad8 => Key::Numpad8, - Numpad9 => Key::Numpad9, - AbntC1 => Key::AbntC1, - AbntC2 => Key::AbntC2, - Add => Key::Add, - Apostrophe => Key::Apostrophe, - Apps => Key::Apps, - At => Key::At, - Ax => Key::Ax, - Backslash => Key::Backslash, - Calculator => Key::Calculator, - Capital => Key::Capital, - Colon => Key::Colon, - Comma => Key::Comma, - Convert => Key::Convert, - Decimal => Key::Decimal, - Divide => Key::Divide, - Equals => Key::Equals, - Grave => Key::Grave, - Kana => Key::Kana, - Kanji => Key::Kanji, - LAlt => Key::LAlt, - LBracket => Key::LBracket, - LControl => Key::LControl, - LShift => Key::LShift, - LWin => Key::LWin, - Mail => Key::Mail, - MediaSelect => Key::MediaSelect, - MediaStop => Key::MediaStop, - Minus => Key::Minus, - Multiply => Key::Multiply, - Mute => Key::Mute, - MyComputer => Key::MyComputer, - NavigateForward => Key::NavigateForward, - NavigateBackward => Key::NavigateBackward, - NextTrack => Key::NextTrack, - NoConvert => Key::NoConvert, - NumpadComma => Key::NumpadComma, - NumpadEnter => Key::NumpadEnter, - NumpadEquals => Key::NumpadEquals, - OEM102 => Key::OEM102, - Period => Key::Period, - PlayPause => Key::PlayPause, - Power => Key::Power, - PrevTrack => Key::PrevTrack, - RAlt => Key::RAlt, - RBracket => Key::RBracket, - RControl => Key::RControl, - RShift => Key::RShift, - RWin => Key::RWin, - Semicolon => Key::Semicolon, - Slash => Key::Slash, - Sleep => Key::Sleep, - Stop => Key::Stop, - Subtract => Key::Subtract, - Sysrq => Key::Sysrq, - Tab => Key::Tab, - Underline => Key::Underline, - Unlabeled => Key::Unlabeled, - VolumeDown => Key::VolumeDown, - VolumeUp => Key::VolumeUp, - Wake => Key::Wake, - WebBack => Key::WebBack, - WebFavorites => Key::WebFavorites, - WebForward => Key::WebForward, - WebHome => Key::WebHome, - WebRefresh => Key::WebRefresh, - WebSearch => Key::WebSearch, - WebStop => Key::WebStop, - Yen => Key::Yen, - Caret => Key::Caret, - Copy => Key::Copy, - Paste => Key::Paste, - Cut => Key::Cut, - } - } -} - -#[cfg(test)] -mod tests { - use super::{Config, DEFAULT_ALACRITTY_CONFIG}; - use crate::config::Options; - - #[test] - fn parse_config() { - let config: Config = - ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); - - // Sanity check that mouse bindings are being parsed - assert!(!config.mouse_bindings.is_empty()); - - // Sanity check that key bindings are being parsed - assert!(!config.key_bindings.is_empty()); - } - - #[test] - fn dynamic_title_ignoring_options_by_default() { - let config: Config = - ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); - let old_dynamic_title = config.dynamic_title; - let options = Options::default(); - let config = config.update_dynamic_title(&options); - assert_eq!(old_dynamic_title, config.dynamic_title); - } - - #[test] - fn dynamic_title_overridden_by_options() { - let config: Config = - ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); - let mut options = Options::default(); - options.title = Some("foo".to_owned()); - let config = config.update_dynamic_title(&options); - assert!(!config.dynamic_title); - } - - #[test] - fn default_match_empty() { - let default = Config::default(); - - let empty = serde_yaml::from_str("key: val\n").unwrap(); - - assert_eq!(default, empty); + let value = serde_yaml::Value::deserialize(deserializer)?; + match T::deserialize(value) { + Ok(value) => Ok(value), + Err(err) => { + error!("Problem with config: {}; using default value", err); + Ok(T::default()) + }, } } diff --git a/alacritty_terminal/src/config/monitor.rs b/alacritty_terminal/src/config/monitor.rs new file mode 100644 index 0000000..6d2ab41 --- /dev/null +++ b/alacritty_terminal/src/config/monitor.rs @@ -0,0 +1,79 @@ +use std::path::PathBuf; +use std::sync::mpsc; +use std::time::Duration; + +use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; + +pub struct Monitor { + _thread: ::std::thread::JoinHandle<()>, + rx: mpsc::Receiver, +} + +pub trait OnConfigReload { + fn on_config_reload(&mut self); +} + +impl OnConfigReload for crate::display::Notifier { + fn on_config_reload(&mut self) { + self.notify(); + } +} + +impl Monitor { + /// Get pending config changes + pub fn pending(&self) -> Option { + let mut config = None; + while let Ok(new) = self.rx.try_recv() { + config = Some(new); + } + + config + } + + pub fn new(path: P, mut handler: H) -> Monitor + where + H: OnConfigReload + Send + 'static, + P: Into, + { + let path = path.into(); + + let (config_tx, config_rx) = mpsc::channel(); + + Monitor { + _thread: crate::util::thread::spawn_named("config watcher", move || { + let (tx, rx) = mpsc::channel(); + // The Duration argument is a debouncing period. + let mut watcher = + watcher(tx, Duration::from_millis(10)).expect("Unable to spawn file watcher"); + let config_path = ::std::fs::canonicalize(path).expect("canonicalize config path"); + + // Get directory of config + let mut parent = config_path.clone(); + parent.pop(); + + // Watch directory + watcher + .watch(&parent, RecursiveMode::NonRecursive) + .expect("watch alacritty.yml dir"); + + loop { + match rx.recv().expect("watcher event") { + DebouncedEvent::Rename(..) => continue, + DebouncedEvent::Write(path) + | DebouncedEvent::Create(path) + | DebouncedEvent::Chmod(path) => { + if path != config_path { + continue; + } + + let _ = config_tx.send(path); + handler.on_config_reload(); + }, + _ => {}, + } + } + }), + rx: config_rx, + } + } +} diff --git a/alacritty_terminal/src/config/mouse.rs b/alacritty_terminal/src/config/mouse.rs new file mode 100644 index 0000000..7a04cbe --- /dev/null +++ b/alacritty_terminal/src/config/mouse.rs @@ -0,0 +1,108 @@ +use std::time::Duration; + +use glutin::ModifiersState; +use serde::{Deserialize, Deserializer}; + +use crate::config::bindings::{CommandWrapper, ModsWrapper}; +use crate::config::failure_default; + +#[serde(default)] +#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq)] +pub struct Mouse { + #[serde(deserialize_with = "failure_default")] + pub double_click: ClickHandler, + #[serde(deserialize_with = "failure_default")] + pub triple_click: ClickHandler, + #[serde(deserialize_with = "failure_default")] + pub hide_when_typing: bool, + #[serde(deserialize_with = "failure_default")] + pub url: Url, +} + +#[serde(default)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +pub struct Url { + // Program for opening links + #[serde(deserialize_with = "deserialize_launcher")] + pub launcher: Option, + + // Modifier used to open links + #[serde(deserialize_with = "failure_default")] + modifiers: ModsWrapper, +} + +impl Url { + pub fn mods(&self) -> ModifiersState { + self.modifiers.into_inner() + } +} + +fn deserialize_launcher<'a, D>( + deserializer: D, +) -> ::std::result::Result, D::Error> +where + D: Deserializer<'a>, +{ + let default = Url::default().launcher; + + // Deserialize to generic value + let val = serde_yaml::Value::deserialize(deserializer)?; + + // Accept `None` to disable the launcher + if val.as_str().filter(|v| v.to_lowercase() == "none").is_some() { + return Ok(None); + } + + match >::deserialize(val) { + Ok(launcher) => Ok(launcher), + Err(err) => { + error!("Problem with config: {}; using {}", err, default.clone().unwrap().program()); + Ok(default) + }, + } +} + +impl Default for Url { + fn default() -> Url { + Url { + #[cfg(not(any(target_os = "macos", windows)))] + launcher: Some(CommandWrapper::Just(String::from("xdg-open"))), + #[cfg(target_os = "macos")] + launcher: Some(CommandWrapper::Just(String::from("open"))), + #[cfg(windows)] + launcher: Some(CommandWrapper::Just(String::from("explorer"))), + modifiers: Default::default(), + } + } +} + +#[serde(default)] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +pub struct ClickHandler { + #[serde(deserialize_with = "deserialize_duration_ms")] + pub threshold: Duration, +} + +impl Default for ClickHandler { + fn default() -> Self { + ClickHandler { threshold: default_threshold_ms() } + } +} + +fn default_threshold_ms() -> Duration { + Duration::from_millis(300) +} + +fn deserialize_duration_ms<'a, D>(deserializer: D) -> ::std::result::Result +where + D: Deserializer<'a>, +{ + let value = serde_yaml::Value::deserialize(deserializer)?; + match u64::deserialize(value) { + Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)), + Err(err) => { + error!("Problem with config: {}; using default value", err); + Ok(default_threshold_ms()) + }, + } +} diff --git a/alacritty_terminal/src/config/options.rs b/alacritty_terminal/src/config/options.rs deleted file mode 100644 index 4b4f1be..0000000 --- a/alacritty_terminal/src/config/options.rs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2019 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use ::log; - -use crate::config::{Delta, Dimensions, Shell}; -use std::borrow::Cow; -use std::path::{Path, PathBuf}; - -/// Options specified on the command line -pub struct Options { - pub live_config_reload: Option, - pub print_events: bool, - pub ref_test: bool, - pub dimensions: Option, - pub position: Option>, - pub title: Option, - pub class: Option, - pub log_level: log::LevelFilter, - pub command: Option>, - pub working_dir: Option, - pub config: Option, - pub persistent_logging: bool, -} - -impl Default for Options { - fn default() -> Options { - Options { - live_config_reload: None, - print_events: false, - ref_test: false, - dimensions: None, - position: None, - title: None, - class: None, - log_level: log::LevelFilter::Warn, - command: None, - working_dir: None, - config: None, - persistent_logging: false, - } - } -} - -impl Options { - pub fn dimensions(&self) -> Option { - self.dimensions - } - - pub fn position(&self) -> Option> { - self.position - } - - pub fn command(&self) -> Option<&Shell<'_>> { - self.command.as_ref() - } - - pub fn config_path(&self) -> Option> { - self.config.as_ref().map(|p| Cow::Borrowed(p.as_path())) - } -} diff --git a/alacritty_terminal/src/config/scrolling.rs b/alacritty_terminal/src/config/scrolling.rs new file mode 100644 index 0000000..d62b102 --- /dev/null +++ b/alacritty_terminal/src/config/scrolling.rs @@ -0,0 +1,81 @@ +use serde::{Deserialize, Deserializer}; + +use crate::config::{failure_default, MAX_SCROLLBACK_LINES}; + +/// Struct for scrolling related settings +#[serde(default)] +#[derive(Deserialize, Copy, Clone, Default, Debug, PartialEq, Eq)] +pub struct Scrolling { + #[serde(deserialize_with = "failure_default")] + history: ScrollingHistory, + #[serde(deserialize_with = "failure_default")] + multiplier: ScrollingMultiplier, + #[serde(deserialize_with = "failure_default")] + faux_multiplier: ScrollingMultiplier, + #[serde(deserialize_with = "failure_default")] + pub auto_scroll: bool, +} + +impl Scrolling { + pub fn history(self) -> u32 { + self.history.0 + } + + pub fn multiplier(self) -> u8 { + self.multiplier.0 + } + + pub fn faux_multiplier(self) -> u8 { + self.faux_multiplier.0 + } + + // Update the history size, used in ref tests + pub fn set_history(&mut self, history: u32) { + self.history = ScrollingHistory(history); + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] +struct ScrollingMultiplier(u8); + +impl Default for ScrollingMultiplier { + fn default() -> Self { + ScrollingMultiplier(3) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +struct ScrollingHistory(u32); + +impl Default for ScrollingHistory { + fn default() -> Self { + ScrollingHistory(10_000) + } +} + +impl<'de> Deserialize<'de> for ScrollingHistory { + fn deserialize(deserializer: D) -> ::std::result::Result + where + D: Deserializer<'de>, + { + let value = serde_yaml::Value::deserialize(deserializer)?; + match u32::deserialize(value) { + Ok(lines) => { + if lines > MAX_SCROLLBACK_LINES { + error!( + "Problem with config: scrollback size is {}, but expected a maximum of \ + {}; using {1} instead", + lines, MAX_SCROLLBACK_LINES, + ); + Ok(ScrollingHistory(MAX_SCROLLBACK_LINES)) + } else { + Ok(ScrollingHistory(lines)) + } + }, + Err(err) => { + error!("Problem with config: {}; using default value", err); + Ok(Default::default()) + }, + } + } +} diff --git a/alacritty_terminal/src/config/test.rs b/alacritty_terminal/src/config/test.rs new file mode 100644 index 0000000..e789092 --- /dev/null +++ b/alacritty_terminal/src/config/test.rs @@ -0,0 +1,22 @@ +use crate::config::{Config, DEFAULT_ALACRITTY_CONFIG}; + +#[test] +fn parse_config() { + let config: Config = + ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); + + // Sanity check that mouse bindings are being parsed + assert!(!config.mouse_bindings.is_empty()); + + // Sanity check that key bindings are being parsed + assert!(!config.key_bindings.is_empty()); +} + +#[test] +fn default_match_empty() { + let default = Config::default(); + + let empty = serde_yaml::from_str("key: val\n").unwrap(); + + assert_eq!(default, empty); +} diff --git a/alacritty_terminal/src/config/visual_bell.rs b/alacritty_terminal/src/config/visual_bell.rs new file mode 100644 index 0000000..3a31b24 --- /dev/null +++ b/alacritty_terminal/src/config/visual_bell.rs @@ -0,0 +1,64 @@ +use std::time::Duration; + +use crate::config::failure_default; +use crate::term::color::Rgb; + +#[serde(default)] +#[derive(Debug, Deserialize, PartialEq, Eq)] +pub struct VisualBellConfig { + /// Visual bell animation function + #[serde(deserialize_with = "failure_default")] + pub animation: VisualBellAnimation, + + /// Visual bell duration in milliseconds + #[serde(deserialize_with = "failure_default")] + pub duration: u16, + + /// Visual bell flash color + #[serde(deserialize_with = "failure_default")] + pub color: Rgb, +} + +impl Default for VisualBellConfig { + fn default() -> VisualBellConfig { + VisualBellConfig { + animation: Default::default(), + duration: Default::default(), + color: default_visual_bell_color(), + } + } +} + +impl VisualBellConfig { + /// Visual bell duration in milliseconds + #[inline] + pub fn duration(&self) -> Duration { + Duration::from_millis(u64::from(self.duration)) + } +} + +/// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert +/// Penner's Easing Functions. +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)] +pub enum VisualBellAnimation { + Ease, // CSS + EaseOut, // CSS + EaseOutSine, // Penner + EaseOutQuad, // Penner + EaseOutCubic, // Penner + EaseOutQuart, // Penner + EaseOutQuint, // Penner + EaseOutExpo, // Penner + EaseOutCirc, // Penner + Linear, +} + +impl Default for VisualBellAnimation { + fn default() -> Self { + VisualBellAnimation::EaseOutExpo + } +} + +fn default_visual_bell_color() -> Rgb { + Rgb { r: 255, g: 255, b: 255 } +} diff --git a/alacritty_terminal/src/config/window.rs b/alacritty_terminal/src/config/window.rs new file mode 100644 index 0000000..d7f3dcb --- /dev/null +++ b/alacritty_terminal/src/config/window.rs @@ -0,0 +1,119 @@ +use crate::config::{failure_default, Delta}; +use crate::index::{Column, Line}; + +#[serde(default)] +#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)] +pub struct WindowConfig { + /// Initial dimensions + #[serde(deserialize_with = "failure_default")] + pub dimensions: Dimensions, + + /// Initial position + #[serde(deserialize_with = "failure_default")] + pub position: Option>, + + /// Pixel padding + #[serde(deserialize_with = "failure_default")] + pub padding: Delta, + + /// Draw the window with title bar / borders + #[serde(deserialize_with = "failure_default")] + pub decorations: Decorations, + + /// Spread out additional padding evenly + #[serde(deserialize_with = "failure_default")] + pub dynamic_padding: bool, + + /// Startup mode + #[serde(deserialize_with = "failure_default")] + startup_mode: StartupMode, + + /// Window title + #[serde(deserialize_with = "failure_default")] + pub title: Option, + + /// Window class + #[serde(deserialize_with = "failure_default")] + pub class: Option, + + /// TODO: DEPRECATED + #[serde(deserialize_with = "failure_default")] + pub start_maximized: Option, +} + +impl WindowConfig { + pub fn startup_mode(&self) -> StartupMode { + match self.start_maximized { + Some(true) => StartupMode::Maximized, + _ => self.startup_mode, + } + } +} + +#[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)] +pub enum StartupMode { + Windowed, + Maximized, + Fullscreen, + #[cfg(target_os = "macos")] + SimpleFullscreen, +} + +impl Default for StartupMode { + fn default() -> StartupMode { + StartupMode::Windowed + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +pub enum Decorations { + #[serde(rename = "full")] + Full, + #[cfg(target_os = "macos")] + #[serde(rename = "transparent")] + Transparent, + #[cfg(target_os = "macos")] + #[serde(rename = "buttonless")] + Buttonless, + #[serde(rename = "none")] + None, +} + +impl Default for Decorations { + fn default() -> Decorations { + Decorations::Full + } +} + +/// Window Dimensions +/// +/// Newtype to avoid passing values incorrectly +#[serde(default)] +#[derive(Default, Debug, Copy, Clone, Deserialize, PartialEq, Eq)] +pub struct Dimensions { + /// Window width in character columns + #[serde(deserialize_with = "failure_default")] + columns: Column, + + /// Window Height in character lines + #[serde(deserialize_with = "failure_default")] + lines: Line, +} + +impl Dimensions { + pub fn new(columns: Column, lines: Line) -> Self { + Dimensions { columns, lines } + } + + /// Get lines + #[inline] + pub fn lines_u32(&self) -> u32 { + self.lines.0 as u32 + } + + /// Get columns + #[inline] + pub fn columns_u32(&self) -> u32 { + self.columns.0 as u32 + } +} diff --git a/alacritty_terminal/src/display.rs b/alacritty_terminal/src/display.rs index 9304398..c087c1c 100644 --- a/alacritty_terminal/src/display.rs +++ b/alacritty_terminal/src/display.rs @@ -15,6 +15,7 @@ //! The display subsystem including window management, font rasterization, and //! GPU drawing. use std::f64; +#[cfg(not(any(target_os = "macos", target_os = "windows")))] use std::ffi::c_void; use std::sync::mpsc; @@ -22,7 +23,7 @@ use glutin::dpi::{PhysicalPosition, PhysicalSize}; use glutin::EventsLoop; use parking_lot::MutexGuard; -use crate::config::{Config, Options, StartupMode}; +use crate::config::{Config, StartupMode}; use crate::index::Line; use crate::message_bar::Message; use crate::meter::Meter; @@ -134,7 +135,7 @@ impl Display { &self.size_info } - pub fn new(config: &Config, options: &Options) -> Result { + pub fn new(config: &Config) -> Result { // Extract some properties from config let render_timer = config.render_timer(); @@ -146,8 +147,7 @@ impl Display { // Guess the target window dimensions let metrics = GlyphCache::static_metrics(config, estimated_dpr as f32)?; let (cell_width, cell_height) = Self::compute_cell_size(config, &metrics); - let dimensions = - Self::calculate_dimensions(config, options, estimated_dpr, cell_width, cell_height); + let dimensions = Self::calculate_dimensions(config, estimated_dpr, cell_width, cell_height); debug!("Estimated DPR: {}", estimated_dpr); debug!("Estimated Cell Size: {} x {}", cell_width, cell_height); @@ -155,7 +155,7 @@ impl Display { // Create the window where Alacritty will be displayed let logical = dimensions.map(|d| PhysicalSize::new(d.0, d.1).to_logical(estimated_dpr)); - let mut window = Window::new(event_loop, &options, config.window(), logical)?; + let mut window = Window::new(event_loop, &config, logical)?; let dpr = window.hidpi_factor(); info!("Device pixel ratio: {}", dpr); @@ -170,11 +170,11 @@ impl Display { let (glyph_cache, cell_width, cell_height) = Self::new_glyph_cache(dpr, &mut renderer, config)?; - let mut padding_x = f64::from(config.padding().x) * dpr; - let mut padding_y = f64::from(config.padding().y) * dpr; + let mut padding_x = f64::from(config.window.padding.x) * dpr; + let mut padding_y = f64::from(config.window.padding.y) * dpr; if let Some((width, height)) = - Self::calculate_dimensions(config, options, dpr, cell_width, cell_height) + Self::calculate_dimensions(config, dpr, cell_width, cell_height) { if dimensions == Some((width, height)) { info!("Estimated DPR correctly, skipping resize"); @@ -182,7 +182,7 @@ impl Display { viewport_size = PhysicalSize::new(width, height); window.set_inner_size(viewport_size.to_logical(dpr)); } - } else if config.window().dynamic_padding() { + } else if config.window.dynamic_padding { // Make sure additional padding is spread evenly let cw = f64::from(cell_width); let ch = f64::from(cell_height); @@ -219,7 +219,7 @@ impl Display { let (tx, rx) = mpsc::channel(); // Clear screen - let background_color = config.colors().primary.background; + let background_color = config.colors.primary.background; renderer.with_api(config, &size_info, |api| { api.clear(background_color); }); @@ -232,7 +232,7 @@ impl Display { tx, rx, meter: Meter::new(), - font_size: config.font().size(), + font_size: config.font.size, size_info, last_message: None, }) @@ -240,22 +240,21 @@ impl Display { fn calculate_dimensions( config: &Config, - options: &Options, dpr: f64, cell_width: f32, cell_height: f32, ) -> Option<(f64, f64)> { - let dimensions = options.dimensions().unwrap_or_else(|| config.dimensions()); + let dimensions = config.window.dimensions; if dimensions.columns_u32() == 0 || dimensions.lines_u32() == 0 - || config.window().startup_mode() != StartupMode::Windowed + || config.window.startup_mode() != StartupMode::Windowed { return None; } - let padding_x = f64::from(config.padding().x) * dpr; - let padding_y = f64::from(config.padding().y) * dpr; + let padding_x = f64::from(config.window.padding.x) * dpr; + let padding_y = f64::from(config.window.padding.y) * dpr; // Calculate new size based on cols/lines specified in config let grid_width = cell_width as u32 * dimensions.columns_u32(); @@ -272,8 +271,8 @@ impl Display { renderer: &mut QuadRenderer, config: &Config, ) -> Result<(GlyphCache, f32, f32), Error> { - let font = config.font().clone(); - let rasterizer = font::Rasterizer::new(dpr as f32, config.use_thin_strokes())?; + let font = config.font.clone(); + let rasterizer = font::Rasterizer::new(dpr as f32, config.font.use_thin_strokes())?; // Initialize glyph cache let glyph_cache = { @@ -304,7 +303,7 @@ impl Display { let size = self.font_size; self.renderer.with_loader(|mut api| { - let _ = cache.update_font_size(config.font(), size, dpr, &mut api); + let _ = cache.update_font_size(&config.font, size, dpr, &mut api); }); let (cw, ch) = Self::compute_cell_size(config, &cache.font_metrics()); @@ -313,8 +312,8 @@ impl Display { } fn compute_cell_size(config: &Config, metrics: &font::Metrics) -> (f32, f32) { - let offset_x = f64::from(config.font().offset().x); - let offset_y = f64::from(config.font().offset().y); + let offset_x = f64::from(config.font.offset.x); + let offset_y = f64::from(config.font.offset.y); ( f32::max(1., ((metrics.average_advance + offset_x) as f32).floor()), f32::max(1., ((metrics.line_height + offset_y) as f32).floor()), @@ -395,10 +394,10 @@ impl Display { self.size_info.width = width; self.size_info.height = height; - let mut padding_x = f32::from(config.padding().x) * dpr as f32; - let mut padding_y = f32::from(config.padding().y) * dpr as f32; + let mut padding_x = f32::from(config.window.padding.x) * dpr as f32; + let mut padding_y = f32::from(config.window.padding.y) * dpr as f32; - if config.window().dynamic_padding() { + if config.window.dynamic_padding { padding_x = padding_x + ((width - 2. * padding_x) % cell_width) / 2.; padding_y = padding_y + ((height - 2. * padding_y) % cell_height) / 2.; } diff --git a/alacritty_terminal/src/event.rs b/alacritty_terminal/src/event.rs index 31fa595..f844bf6 100644 --- a/alacritty_terminal/src/event.rs +++ b/alacritty_terminal/src/event.rs @@ -14,7 +14,7 @@ use parking_lot::MutexGuard; use serde_json as json; use crate::clipboard::ClipboardType; -use crate::config::{self, Config, Options}; +use crate::config::{self, Config}; use crate::display::OnResize; use crate::grid::Scroll; use crate::index::{Column, Line, Point, Side}; @@ -312,31 +312,29 @@ impl Processor { pub fn new( notifier: N, resize_tx: mpsc::Sender, - options: &Options, config: &Config, - ref_test: bool, size_info: SizeInfo, ) -> Processor { Processor { - key_bindings: config.key_bindings().to_vec(), - mouse_bindings: config.mouse_bindings().to_vec(), - mouse_config: config.mouse().to_owned(), - scrolling_config: config.scrolling(), - print_events: options.print_events, + key_bindings: config.key_bindings.to_vec(), + mouse_bindings: config.mouse_bindings.to_vec(), + mouse_config: config.mouse.to_owned(), + scrolling_config: config.scrolling, + print_events: config.debug.print_events, wait_for_event: true, notifier, resize_tx, - ref_test, + ref_test: config.debug.ref_test, mouse: Default::default(), size_info, - hide_mouse_when_typing: config.hide_mouse_when_typing(), + hide_mouse_when_typing: config.mouse.hide_when_typing, hide_mouse: false, received_count: 0, suppress_chars: false, last_modifiers: Default::default(), pending_events: Vec::with_capacity(4), window_changes: Default::default(), - save_to_clipboard: config.selection().save_to_clipboard, + save_to_clipboard: config.selection.save_to_clipboard, alt_send_esc: config.alt_send_esc(), is_fullscreen: false, is_simple_fullscreen: false, @@ -580,10 +578,10 @@ impl Processor { } pub fn update_config(&mut self, config: &Config) { - self.key_bindings = config.key_bindings().to_vec(); - self.mouse_bindings = config.mouse_bindings().to_vec(); - self.mouse_config = config.mouse().to_owned(); - self.save_to_clipboard = config.selection().save_to_clipboard; + self.key_bindings = config.key_bindings.to_vec(); + self.mouse_bindings = config.mouse_bindings.to_vec(); + self.mouse_config = config.mouse.to_owned(); + self.save_to_clipboard = config.selection.save_to_clipboard; self.alt_send_esc = config.alt_send_esc(); } } diff --git a/alacritty_terminal/src/input.rs b/alacritty_terminal/src/input.rs index d3bbd74..bd1610a 100644 --- a/alacritty_terminal/src/input.rs +++ b/alacritty_terminal/src/input.rs @@ -193,9 +193,10 @@ impl Binding { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub enum Action { /// Write an escape sequence + #[serde(skip)] Esc(String), /// Paste contents of system clipboard @@ -238,6 +239,7 @@ pub enum Action { ClearHistory, /// Run given command + #[serde(skip)] Command(String, Vec), /// Hides the Alacritty window @@ -281,17 +283,13 @@ impl Action { ctx.copy_selection(ClipboardType::Primary); }, Action::Paste => { - let text = ctx.terminal_mut() - .clipboard() - .load(ClipboardType::Primary); + let text = ctx.terminal_mut().clipboard().load(ClipboardType::Primary); self.paste(ctx, &text); }, Action::PasteSelection => { // Only paste if mouse events are not captured by an application if !mouse_mode { - let text = ctx.terminal_mut() - .clipboard() - .load(ClipboardType::Secondary); + let text = ctx.terminal_mut().clipboard().load(ClipboardType::Secondary); self.paste(ctx, &text); } }, @@ -454,7 +452,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG | TermMode::MOUSE_REPORT_CLICK; // Only show URLs as launchable when all required modifiers are pressed - let url = if self.mouse_config.url.modifiers.relaxed_eq(modifiers) + let url = if self.mouse_config.url.mods().relaxed_eq(modifiers) && (!self.ctx.terminal().mode().intersects(mouse_mode) || modifiers.shift) && self.mouse_config.url.launcher.is_some() { @@ -663,7 +661,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { // Spawn URL launcher when clicking on URLs fn launch_url(&self, modifiers: ModifiersState, point: Point) -> Option<()> { - if !self.mouse_config.url.modifiers.relaxed_eq(modifiers) + if !self.mouse_config.url.mods().relaxed_eq(modifiers) || self.ctx.mouse().block_url_launcher { return None; @@ -715,10 +713,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { let height = self.ctx.size_info().cell_height as i32; // Make sure the new and deprecated setting are both allowed - let faux_multiplier = self - .mouse_config - .faux_scrollback_lines - .unwrap_or(self.scrolling_config.faux_multiplier as usize); + let faux_multiplier = self.scrolling_config.faux_multiplier() as usize; if self.ctx.terminal().mode().intersects(mouse_modes) { self.ctx.mouse_mut().scroll_px += new_scroll_px; @@ -746,7 +741,7 @@ impl<'a, A: ActionContext + 'a> Processor<'a, A> { } self.ctx.write_to_pty(content); } else { - let multiplier = i32::from(self.scrolling_config.multiplier); + let multiplier = i32::from(self.scrolling_config.multiplier()); self.ctx.mouse_mut().scroll_px += new_scroll_px * multiplier; let lines = self.ctx.mouse().scroll_px / height; @@ -1117,13 +1112,12 @@ mod tests { threshold: Duration::from_millis(1000), }, hide_when_typing: false, - faux_scrollback_lines: None, url: Default::default(), }, scrolling_config: &config::Scrolling::default(), - key_bindings: &config.key_bindings()[..], - mouse_bindings: &config.mouse_bindings()[..], - save_to_clipboard: config.selection().save_to_clipboard, + key_bindings: &config.key_bindings[..], + mouse_bindings: &config.mouse_bindings[..], + save_to_clipboard: config.selection.save_to_clipboard, alt_send_esc: config.alt_send_esc(), }; diff --git a/alacritty_terminal/src/renderer/mod.rs b/alacritty_terminal/src/renderer/mod.rs index 82c6c2d..53445a1 100644 --- a/alacritty_terminal/src/renderer/mod.rs +++ b/alacritty_terminal/src/renderer/mod.rs @@ -197,19 +197,19 @@ impl GlyphCache { // Need to load at least one glyph for the face before calling metrics. // The glyph requested here ('m' at the time of writing) has no special // meaning. - rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; + rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?; - let metrics = rasterizer.metrics(regular, font.size())?; + let metrics = rasterizer.metrics(regular, font.size)?; let mut cache = GlyphCache { cache: HashMap::default(), cursor_cache: HashMap::default(), rasterizer, - font_size: font.size(), + font_size: font.size, font_key: regular, bold_key: bold, italic_key: italic, - glyph_offset: *font.glyph_offset(), + glyph_offset: font.glyph_offset, metrics, }; @@ -232,7 +232,7 @@ impl GlyphCache { font: &config::Font, rasterizer: &mut Rasterizer, ) -> Result<(FontKey, FontKey, FontKey), font::Error> { - let size = font.size(); + let size = font.size; // Load regular font let regular_desc = @@ -320,7 +320,7 @@ impl GlyphCache { let font = font.to_owned().with_size(size); let (regular, bold, italic) = Self::compute_font_keys(&font, &mut self.rasterizer)?; - self.rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; + self.rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?; let metrics = self.rasterizer.metrics(regular, size)?; info!("Font size changed to {:?} with DPR of {}", font.size, dpr); @@ -342,15 +342,15 @@ impl GlyphCache { // // This should only be used *before* OpenGL is initialized and the glyph cache can be filled. pub fn static_metrics(config: &Config, dpr: f32) -> Result { - let font = config.font().clone(); + let font = config.font.clone(); - let mut rasterizer = font::Rasterizer::new(dpr, config.use_thin_strokes())?; + let mut rasterizer = font::Rasterizer::new(dpr, config.font.use_thin_strokes())?; let regular_desc = GlyphCache::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal); - let regular = rasterizer.load_font(®ular_desc, font.size())?; - rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; + let regular = rasterizer.load_font(®ular_desc, font.size)?; + rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?; - rasterizer.metrics(regular, font.size()) + rasterizer.metrics(regular, font.size) } } @@ -713,7 +713,7 @@ impl QuadRenderer { } // Draw visual bell - let color = config.visual_bell().color(); + let color = config.visual_bell.color; let rect = Rect::new(0., 0., props.width, props.height); self.render_rect(&rect, color, visual_bell_intensity as f32, props); @@ -890,7 +890,7 @@ impl QuadRenderer { impl<'a> RenderApi<'a> { pub fn clear(&self, color: Rgb) { - let alpha = self.config.background_opacity().get(); + let alpha = self.config.background_opacity(); unsafe { gl::ClearColor( (f32::from(color.r) / 255.0).min(1.0) * alpha, diff --git a/alacritty_terminal/src/term/color.rs b/alacritty_terminal/src/term/color.rs index 39def61..481b281 100644 --- a/alacritty_terminal/src/term/color.rs +++ b/alacritty_terminal/src/term/color.rs @@ -1,5 +1,9 @@ use std::fmt; use std::ops::{Index, IndexMut, Mul}; +use std::str::FromStr; + +use serde::de::Visitor; +use serde::{Deserialize, Deserializer}; use crate::ansi; use crate::config::Colors; @@ -9,7 +13,7 @@ pub const COUNT: usize = 270; pub const RED: Rgb = Rgb { r: 0xff, g: 0x0, b: 0x0 }; pub const YELLOW: Rgb = Rgb { r: 0xff, g: 0xff, b: 0x0 }; -#[derive(Debug, Eq, PartialEq, Copy, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Eq, PartialEq, Copy, Clone, Default, Serialize)] pub struct Rgb { pub r: u8, pub g: u8, @@ -33,6 +37,99 @@ impl Mul for Rgb { } } +/// Deserialize an Rgb from a hex string +/// +/// This is *not* the deserialize impl for Rgb since we want a symmetric +/// serialize/deserialize impl for ref tests. +impl<'de> Deserialize<'de> for Rgb { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct RgbVisitor; + + // Used for deserializing reftests + #[derive(Deserialize)] + struct RgbDerivedDeser { + r: u8, + g: u8, + b: u8, + } + + impl<'a> Visitor<'a> for RgbVisitor { + type Value = Rgb; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("hex color like 0xff00ff") + } + + fn visit_str(self, value: &str) -> ::std::result::Result + where + E: ::serde::de::Error, + { + Rgb::from_str(&value[..]) + .map_err(|_| E::custom("failed to parse rgb; expected hex color like 0xff00ff")) + } + } + + // Return an error if the syntax is incorrect + let value = serde_yaml::Value::deserialize(deserializer)?; + + // Attempt to deserialize from struct form + if let Ok(RgbDerivedDeser { r, g, b }) = RgbDerivedDeser::deserialize(value.clone()) { + return Ok(Rgb { r, g, b }); + } + + // Deserialize from hex notation (either 0xff00ff or #ff00ff) + match value.deserialize_str(RgbVisitor) { + Ok(rgb) => Ok(rgb), + Err(err) => { + error!("Problem with config: {}; using color #000000", err); + Ok(Rgb::default()) + }, + } + } +} + +impl FromStr for Rgb { + type Err = (); + + fn from_str(s: &str) -> ::std::result::Result { + let mut chars = s.chars(); + let mut rgb = Rgb::default(); + + macro_rules! component { + ($($c:ident),*) => { + $( + match chars.next().and_then(|c| c.to_digit(16)) { + Some(val) => rgb.$c = (val as u8) << 4, + None => return Err(()) + } + + match chars.next().and_then(|c| c.to_digit(16)) { + Some(val) => rgb.$c |= val as u8, + None => return Err(()) + } + )* + } + } + + match chars.next() { + Some('0') => { + if chars.next() != Some('x') { + return Err(()); + } + }, + Some('#') => (), + _ => return Err(()), + } + + component!(r, g, b); + + Ok(rgb) + } +} + /// List of indexed colors /// /// The first 16 entries are the standard ansi named colors. Items 16..232 are @@ -60,24 +157,24 @@ impl<'a> From<&'a Colors> for List { impl List { pub fn fill_named(&mut self, colors: &Colors) { // Normals - self[ansi::NamedColor::Black] = colors.normal.black; - self[ansi::NamedColor::Red] = colors.normal.red; - self[ansi::NamedColor::Green] = colors.normal.green; - self[ansi::NamedColor::Yellow] = colors.normal.yellow; - self[ansi::NamedColor::Blue] = colors.normal.blue; - self[ansi::NamedColor::Magenta] = colors.normal.magenta; - self[ansi::NamedColor::Cyan] = colors.normal.cyan; - self[ansi::NamedColor::White] = colors.normal.white; + self[ansi::NamedColor::Black] = colors.normal().black; + self[ansi::NamedColor::Red] = colors.normal().red; + self[ansi::NamedColor::Green] = colors.normal().green; + self[ansi::NamedColor::Yellow] = colors.normal().yellow; + self[ansi::NamedColor::Blue] = colors.normal().blue; + self[ansi::NamedColor::Magenta] = colors.normal().magenta; + self[ansi::NamedColor::Cyan] = colors.normal().cyan; + self[ansi::NamedColor::White] = colors.normal().white; // Brights - self[ansi::NamedColor::BrightBlack] = colors.bright.black; - self[ansi::NamedColor::BrightRed] = colors.bright.red; - self[ansi::NamedColor::BrightGreen] = colors.bright.green; - self[ansi::NamedColor::BrightYellow] = colors.bright.yellow; - self[ansi::NamedColor::BrightBlue] = colors.bright.blue; - self[ansi::NamedColor::BrightMagenta] = colors.bright.magenta; - self[ansi::NamedColor::BrightCyan] = colors.bright.cyan; - self[ansi::NamedColor::BrightWhite] = colors.bright.white; + self[ansi::NamedColor::BrightBlack] = colors.bright().black; + self[ansi::NamedColor::BrightRed] = colors.bright().red; + self[ansi::NamedColor::BrightGreen] = colors.bright().green; + self[ansi::NamedColor::BrightYellow] = colors.bright().yellow; + self[ansi::NamedColor::BrightBlue] = colors.bright().blue; + self[ansi::NamedColor::BrightMagenta] = colors.bright().magenta; + self[ansi::NamedColor::BrightCyan] = colors.bright().cyan; + self[ansi::NamedColor::BrightWhite] = colors.bright().white; self[ansi::NamedColor::BrightForeground] = colors.primary.bright_foreground.unwrap_or(colors.primary.foreground); @@ -106,14 +203,14 @@ impl List { }, None => { trace!("Deriving dim colors from normal colors"); - self[ansi::NamedColor::DimBlack] = colors.normal.black * 0.66; - self[ansi::NamedColor::DimRed] = colors.normal.red * 0.66; - self[ansi::NamedColor::DimGreen] = colors.normal.green * 0.66; - self[ansi::NamedColor::DimYellow] = colors.normal.yellow * 0.66; - self[ansi::NamedColor::DimBlue] = colors.normal.blue * 0.66; - self[ansi::NamedColor::DimMagenta] = colors.normal.magenta * 0.66; - self[ansi::NamedColor::DimCyan] = colors.normal.cyan * 0.66; - self[ansi::NamedColor::DimWhite] = colors.normal.white * 0.66; + self[ansi::NamedColor::DimBlack] = colors.normal().black * 0.66; + self[ansi::NamedColor::DimRed] = colors.normal().red * 0.66; + self[ansi::NamedColor::DimGreen] = colors.normal().green * 0.66; + self[ansi::NamedColor::DimYellow] = colors.normal().yellow * 0.66; + self[ansi::NamedColor::DimBlue] = colors.normal().blue * 0.66; + self[ansi::NamedColor::DimMagenta] = colors.normal().magenta * 0.66; + self[ansi::NamedColor::DimCyan] = colors.normal().cyan * 0.66; + self[ansi::NamedColor::DimWhite] = colors.normal().white * 0.66; }, } } diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index 68a5fdc..c83dc72 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -227,8 +227,8 @@ impl<'a> RenderableCellsIter<'a> { let cursor = &term.cursor.point; let cursor_visible = term.mode.contains(TermMode::SHOW_CURSOR) && grid.contains(cursor); let cursor_cell = if cursor_visible { - let offset_x = config.font().offset().x; - let offset_y = config.font().offset().y; + let offset_x = config.font.offset.x; + let offset_y = config.font.offset.y; let is_wide = grid[cursor].flags.contains(cell::Flags::WIDE_CHAR) && (cursor.col + 1) < grid.num_cols(); @@ -278,7 +278,7 @@ impl RenderableCell { let mut fg_rgb = Self::compute_fg_rgb(config, colors, cell.fg, cell.flags); let mut bg_rgb = Self::compute_bg_rgb(colors, cell.bg); - let selection_background = config.colors().selection.background; + let selection_background = config.colors.selection.background; if let (true, Some(col)) = (selected, selection_background) { // Override selection background with config colors bg_rgb = col; @@ -294,7 +294,7 @@ impl RenderableCell { } // Override selection text with config colors - if let (true, Some(col)) = (selected, config.colors().selection.text) { + if let (true, Some(col)) = (selected, config.colors.selection.text) { fg_rgb = col; } @@ -317,7 +317,7 @@ impl RenderableCell { // If no bright foreground is set, treat it like the BOLD flag doesn't exist (_, cell::Flags::DIM_BOLD) if ansi == NamedColor::Foreground - && config.colors().primary.bright_foreground.is_none() => + && config.colors.primary.bright_foreground.is_none() => { colors[NamedColor::DimForeground] }, @@ -389,7 +389,7 @@ impl<'a> Iterator for RenderableCellsIter<'a> { renderable_cell.inner = RenderableCellContent::Cursor((self.cursor_style, cursor_cell)); - if let Some(color) = self.config.cursor_cursor_color() { + if let Some(color) = self.config.colors.cursor.cursor { renderable_cell.fg = color; } @@ -401,7 +401,7 @@ impl<'a> Iterator for RenderableCellsIter<'a> { if self.cursor_style == CursorStyle::Block { std::mem::swap(&mut cell.bg, &mut cell.fg); - if let Some(color) = self.config.cursor_text_color() { + if let Some(color) = self.config.colors.cursor.text { cell.fg = color; } } @@ -563,9 +563,9 @@ fn cubic_bezier(p0: f64, p1: f64, p2: f64, p3: f64, x: f64) -> f64 { impl VisualBell { pub fn new(config: &Config) -> VisualBell { - let visual_bell_config = config.visual_bell(); + let visual_bell_config = &config.visual_bell; VisualBell { - animation: visual_bell_config.animation(), + animation: visual_bell_config.animation, duration: visual_bell_config.duration(), start_time: None, } @@ -658,8 +658,8 @@ impl VisualBell { } pub fn update_config(&mut self, config: &Config) { - let visual_bell_config = config.visual_bell(); - self.animation = visual_bell_config.animation(); + let visual_bell_config = &config.visual_bell; + self.animation = visual_bell_config.animation; self.duration = visual_bell_config.duration(); } } @@ -853,7 +853,7 @@ impl Term { let num_cols = size.cols(); let num_lines = size.lines(); - let history_size = config.scrolling().history as usize; + let history_size = config.scrolling.history() as usize; let grid = Grid::new(num_lines, num_cols, history_size, Cell::default()); let alt = Grid::new(num_lines, num_cols, 0 /* scroll history */, Cell::default()); @@ -862,7 +862,7 @@ impl Term { let scroll_region = Line(0)..grid.num_lines(); - let colors = color::List::from(config.colors()); + let colors = color::List::from(&config.colors); Term { next_title: None, @@ -874,8 +874,8 @@ impl Term { grid, alt_grid: alt, alt: false, - font_size: config.font().size(), - original_font_size: config.font().size(), + font_size: config.font.size, + original_font_size: config.font.size, active_charset: Default::default(), cursor: Default::default(), cursor_save: Default::default(), @@ -887,12 +887,12 @@ impl Term { colors, color_modified: [false; color::COUNT], original_colors: colors, - semantic_escape_chars: config.selection().semantic_escape_chars.clone(), + semantic_escape_chars: config.selection.semantic_escape_chars().to_owned(), cursor_style: None, - default_cursor_style: config.cursor_style(), + default_cursor_style: config.cursor.style, dynamic_title: config.dynamic_title(), tabspaces, - auto_scroll: config.scrolling().auto_scroll, + auto_scroll: config.scrolling.auto_scroll, message_buffer, should_exit: false, clipboard, @@ -912,20 +912,20 @@ impl Term { } pub fn update_config(&mut self, config: &Config) { - self.semantic_escape_chars = config.selection().semantic_escape_chars.clone(); - self.original_colors.fill_named(config.colors()); - self.original_colors.fill_cube(config.colors()); - self.original_colors.fill_gray_ramp(config.colors()); + self.semantic_escape_chars = config.selection.semantic_escape_chars().to_owned(); + self.original_colors.fill_named(&config.colors); + self.original_colors.fill_cube(&config.colors); + self.original_colors.fill_gray_ramp(&config.colors); for i in 0..color::COUNT { if !self.color_modified[i] { self.colors[i] = self.original_colors[i]; } } self.visual_bell.update_config(config); - self.default_cursor_style = config.cursor_style(); + self.default_cursor_style = config.cursor.style; self.dynamic_title = config.dynamic_title(); - self.auto_scroll = config.scrolling().auto_scroll; - self.grid.update_history(config.scrolling().history as usize, &self.cursor.template); + self.auto_scroll = config.scrolling.auto_scroll; + self.grid.update_history(config.scrolling.history() as usize, &self.cursor.template); } #[inline] @@ -1107,7 +1107,7 @@ impl Term { let alt_screen = self.mode.contains(TermMode::ALT_SCREEN); let selection = self.grid.selection.as_ref().and_then(|s| s.to_span(self, alt_screen)); - let cursor = if window_focused || !config.unfocused_hollow_cursor() { + let cursor = if window_focused || !config.cursor.unfocused_hollow() { self.cursor_style.unwrap_or(self.default_cursor_style) } else { CursorStyle::HollowBlock @@ -2285,7 +2285,7 @@ mod tests { let mut term: Term = Term::new(&config, size, MessageBuffer::new(), Clipboard::new_nop()); term.change_font_size(font_size); - let expected_font_size: Size = config.font().size() + Size::new(font_size); + let expected_font_size: Size = config.font.size + Size::new(font_size); assert_eq!(term.font_size, expected_font_size); } @@ -2336,7 +2336,7 @@ mod tests { term.change_font_size(10.0); term.reset_font_size(); - let expected_font_size: Size = config.font().size(); + let expected_font_size: Size = config.font.size; assert_eq!(term.font_size, expected_font_size); } diff --git a/alacritty_terminal/src/tty/mod.rs b/alacritty_terminal/src/tty/mod.rs index 2a6410d..c0ac7a3 100644 --- a/alacritty_terminal/src/tty/mod.rs +++ b/alacritty_terminal/src/tty/mod.rs @@ -90,7 +90,7 @@ pub fn setup_env(config: &Config) { env::set_var("COLORTERM", "truecolor"); // Set env vars from config - for (key, value) in config.env().iter() { + for (key, value) in config.env.iter() { env::set_var(key, value); } } diff --git a/alacritty_terminal/src/tty/unix.rs b/alacritty_terminal/src/tty/unix.rs index 668fe7b..0cf1a82 100644 --- a/alacritty_terminal/src/tty/unix.rs +++ b/alacritty_terminal/src/tty/unix.rs @@ -15,7 +15,7 @@ //! tty related functionality //! -use crate::config::{Config, Options, Shell}; +use crate::config::{Config, Shell}; use crate::display::OnResize; use crate::term::SizeInfo; use crate::tty::{ChildEvent, EventedPty, EventedReadWrite}; @@ -154,12 +154,7 @@ impl Pty { } /// Create a new tty and return a handle to interact with it. -pub fn new( - config: &Config, - options: &Options, - size: &T, - window_id: Option, -) -> Pty { +pub fn new(config: &Config, size: &T, window_id: Option) -> Pty { let win_size = size.to_winsize(); let mut buf = [0; 1024]; let pw = get_pw_entry(&mut buf); @@ -174,12 +169,10 @@ pub fn new( } else { Shell::new(pw.shell) }; - let shell = config.shell().unwrap_or(&default_shell); + let shell = config.shell.as_ref().unwrap_or(&default_shell); - let initial_command = options.command().unwrap_or(shell); - - let mut builder = Command::new(initial_command.program()); - for arg in initial_command.args() { + let mut builder = Command::new(&*shell.program); + for arg in &shell.args { builder.arg(arg); } @@ -230,7 +223,7 @@ pub fn new( }); // Handle set working directory option - if let Some(ref dir) = options.working_dir { + if let Some(ref dir) = config.working_directory() { builder.current_dir(dir.as_path()); } diff --git a/alacritty_terminal/src/tty/windows/conpty.rs b/alacritty_terminal/src/tty/windows/conpty.rs index 0789dd9..bd602c3 100644 --- a/alacritty_terminal/src/tty/windows/conpty.rs +++ b/alacritty_terminal/src/tty/windows/conpty.rs @@ -37,7 +37,7 @@ use winapi::um::processthreadsapi::{ use winapi::um::winbase::{EXTENDED_STARTUPINFO_PRESENT, STARTF_USESTDHANDLES, STARTUPINFOEXW}; use winapi::um::wincontypes::{COORD, HPCON}; -use crate::config::{Config, Options, Shell}; +use crate::config::{Config, Shell}; use crate::display::OnResize; use crate::term::SizeInfo; @@ -98,13 +98,8 @@ impl Drop for Conpty { unsafe impl Send for Conpty {} unsafe impl Sync for Conpty {} -pub fn new<'a>( - config: &Config, - options: &Options, - size: &SizeInfo, - _window_id: Option, -) -> Option> { - if !config.enable_experimental_conpty_backend() { +pub fn new<'a>(config: &Config, size: &SizeInfo, _window_id: Option) -> Option> { + if !config.enable_experimental_conpty_backend { return None; } @@ -143,7 +138,7 @@ pub fn new<'a>( let mut startup_info_ex: STARTUPINFOEXW = Default::default(); - let title = options.title.as_ref().map(String::as_str).unwrap_or("Alacritty"); + let title = config.window.title.as_ref().map(String::as_str).unwrap_or("Alacritty"); let title = U16CString::from_str(title).unwrap(); startup_info_ex.StartupInfo.lpTitle = title.as_ptr() as LPWSTR; @@ -209,13 +204,12 @@ pub fn new<'a>( // Get process commandline let default_shell = &Shell::new("powershell"); - let shell = config.shell().unwrap_or(default_shell); - let initial_command = options.command().unwrap_or(shell); - let mut cmdline = initial_command.args().to_vec(); - cmdline.insert(0, initial_command.program().into()); + let shell = config.shell.as_ref().unwrap_or(default_shell); + let mut cmdline = shell.args.clone(); + cmdline.insert(0, shell.program.to_string()); // Warning, here be borrow hell - let cwd = options.working_dir.as_ref().map(|dir| canonicalize(dir).unwrap()); + let cwd = config.working_directory().as_ref().map(|dir| canonicalize(dir).unwrap()); let cwd = cwd.as_ref().map(|dir| dir.to_str().unwrap()); // Create the client application, using startup info containing ConPTY info diff --git a/alacritty_terminal/src/tty/windows/mod.rs b/alacritty_terminal/src/tty/windows/mod.rs index a3d3fae..7537d33 100644 --- a/alacritty_terminal/src/tty/windows/mod.rs +++ b/alacritty_terminal/src/tty/windows/mod.rs @@ -24,7 +24,7 @@ use winapi::shared::winerror::WAIT_TIMEOUT; use winapi::um::synchapi::WaitForSingleObject; use winapi::um::winbase::WAIT_OBJECT_0; -use crate::config::{Config, Options}; +use crate::config::Config; use crate::display::OnResize; use crate::term::SizeInfo; use crate::tty::{EventedPty, EventedReadWrite}; @@ -83,19 +83,14 @@ impl<'a> Pty<'a> { } } -pub fn new<'a>( - config: &Config, - options: &Options, - size: &SizeInfo, - window_id: Option, -) -> Pty<'a> { - if let Some(pty) = conpty::new(config, options, size, window_id) { +pub fn new<'a>(config: &Config, size: &SizeInfo, window_id: Option) -> Pty<'a> { + if let Some(pty) = conpty::new(config, size, window_id) { info!("Using Conpty agent"); IS_CONPTY.store(true, Ordering::Relaxed); pty } else { info!("Using Winpty agent"); - winpty::new(config, options, size, window_id) + winpty::new(config, size, window_id) } } diff --git a/alacritty_terminal/src/tty/windows/winpty.rs b/alacritty_terminal/src/tty/windows/winpty.rs index 7aa976e..8795ca6 100644 --- a/alacritty_terminal/src/tty/windows/winpty.rs +++ b/alacritty_terminal/src/tty/windows/winpty.rs @@ -27,7 +27,7 @@ use winapi::um::winbase::FILE_FLAG_OVERLAPPED; use winpty::Config as WinptyConfig; use winpty::{ConfigFlags, MouseMode, SpawnConfig, SpawnFlags, Winpty}; -use crate::config::{Config, Options, Shell}; +use crate::config::{Config, Shell}; use crate::display::OnResize; use crate::term::SizeInfo; @@ -75,12 +75,7 @@ impl<'a> Drop for Agent<'a> { /// This is a placeholder value until we see how often long responses happen const AGENT_TIMEOUT: u32 = 10000; -pub fn new<'a>( - config: &Config, - options: &Options, - size: &SizeInfo, - _window_id: Option, -) -> Pty<'a> { +pub fn new<'a>(config: &Config, size: &SizeInfo, _window_id: Option) -> Pty<'a> { // Create config let mut wconfig = WinptyConfig::new(ConfigFlags::empty()).unwrap(); @@ -94,13 +89,12 @@ pub fn new<'a>( // Get process commandline let default_shell = &Shell::new("powershell"); - let shell = config.shell().unwrap_or(default_shell); - let initial_command = options.command().unwrap_or(shell); - let mut cmdline = initial_command.args().to_vec(); - cmdline.insert(0, initial_command.program().into()); + let shell = config.shell.as_ref().unwrap_or(default_shell); + let mut cmdline = shell.args.clone(); + cmdline.insert(0, shell.program.to_string()); // Warning, here be borrow hell - let cwd = options.working_dir.as_ref().map(|dir| canonicalize(dir).unwrap()); + let cwd = config.working_directory().as_ref().map(|dir| canonicalize(dir).unwrap()); let cwd = cwd.as_ref().map(|dir| dir.to_str().unwrap()); // Spawn process diff --git a/alacritty_terminal/src/window.rs b/alacritty_terminal/src/window.rs index e269a7b..97f29c5 100644 --- a/alacritty_terminal/src/window.rs +++ b/alacritty_terminal/src/window.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. use std::convert::From; +#[cfg(not(any(target_os = "macos", target_os = "windows")))] use std::ffi::c_void; use std::fmt::Display; @@ -30,7 +31,7 @@ use glutin::{ #[cfg(not(target_os = "macos"))] use image::ImageFormat; -use crate::config::{Options, Decorations, StartupMode, WindowConfig}; +use crate::config::{Config, Decorations, StartupMode, WindowConfig}; // It's required to be in this directory due to the `windows.rc` file #[cfg(not(target_os = "macos"))] @@ -146,13 +147,13 @@ impl Window { /// This creates a window and fully initializes a window. pub fn new( event_loop: EventsLoop, - options: &Options, - window_config: &WindowConfig, + config: &Config, dimensions: Option, ) -> Result { - let title = options.title.as_ref().map_or(DEFAULT_NAME, |t| t); - let class = options.class.as_ref().map_or(DEFAULT_NAME, |c| c); - let window_builder = Window::get_platform_window(title, class, window_config); + let title = config.window.title.as_ref().map_or(DEFAULT_NAME, |t| t); + let class = config.window.class.as_ref().map_or(DEFAULT_NAME, |c| c); + + let window_builder = Window::get_platform_window(title, class, &config.window); let windowed_context = create_gl_window(window_builder.clone(), &event_loop, false, dimensions) .or_else(|_| create_gl_window(window_builder, &event_loop, true, dimensions))?; @@ -162,7 +163,7 @@ impl Window { // Maximize window after mapping in X11 #[cfg(not(any(target_os = "macos", windows)))] { - if event_loop.is_x11() && window_config.startup_mode() == StartupMode::Maximized { + if event_loop.is_x11() && config.window.startup_mode() == StartupMode::Maximized { window.set_maximized(true); } } @@ -171,21 +172,20 @@ impl Window { // // TODO: replace `set_position` with `with_position` once available // Upstream issue: https://github.com/tomaka/winit/issues/806 - let position = options.position().or_else(|| window_config.position()); - if let Some(position) = position { + if let Some(position) = config.window.position { let physical = PhysicalPosition::from((position.x, position.y)); let logical = physical.to_logical(window.get_hidpi_factor()); window.set_position(logical); } - if let StartupMode::Fullscreen = window_config.startup_mode() { + if let StartupMode::Fullscreen = config.window.startup_mode() { let current_monitor = window.get_current_monitor(); window.set_fullscreen(Some(current_monitor)); } #[cfg(target_os = "macos")] { - if let StartupMode::SimpleFullscreen = window_config.startup_mode() { + if let StartupMode::SimpleFullscreen = config.window.startup_mode() { use glutin::os::macos::WindowExt; window.set_simple_fullscreen(true); } @@ -286,7 +286,7 @@ impl Window { ) -> WindowBuilder { use glutin::os::unix::WindowBuilderExt; - let decorations = match window_config.decorations() { + let decorations = match window_config.decorations { Decorations::None => false, _ => true, }; @@ -312,7 +312,7 @@ impl Window { _class: &str, window_config: &WindowConfig, ) -> WindowBuilder { - let decorations = match window_config.decorations() { + let decorations = match window_config.decorations { Decorations::None => false, _ => true, }; @@ -342,7 +342,7 @@ impl Window { .with_transparency(true) .with_maximized(window_config.startup_mode() == StartupMode::Maximized); - match window_config.decorations() { + match window_config.decorations { Decorations::Full => window, Decorations::Transparent => window .with_title_hidden(true) diff --git a/alacritty_terminal/tests/ref.rs b/alacritty_terminal/tests/ref.rs index a244715..135b5f2 100644 --- a/alacritty_terminal/tests/ref.rs +++ b/alacritty_terminal/tests/ref.rs @@ -91,7 +91,7 @@ fn ref_test(dir: &Path) { let ref_config: RefConfig = json::from_str(&serialized_cfg).unwrap_or_default(); let mut config: Config = Default::default(); - config.set_history(ref_config.history_size); + config.scrolling.set_history(ref_config.history_size); let mut terminal = Term::new(&config, size, MessageBuffer::new(), Clipboard::new_nop()); let mut parser = ansi::Processor::new();