diff options
-rw-r--r-- | .gitattributes | 1 | ||||
-rw-r--r-- | .github/workflows/ci.yml | 8 | ||||
-rw-r--r-- | .github/workflows/release.yml | 10 | ||||
-rw-r--r-- | CHANGELOG.md | 6 | ||||
-rw-r--r-- | Cargo.lock | 65 | ||||
-rw-r--r-- | INSTALL.md | 12 | ||||
-rw-r--r-- | Makefile | 35 | ||||
-rw-r--r-- | alacritty/Cargo.toml | 2 | ||||
-rw-r--r-- | alacritty/build.rs | 10 | ||||
-rw-r--r-- | alacritty/src/cli.rs | 326 | ||||
-rw-r--r-- | alacritty/src/config/mod.rs | 6 | ||||
-rw-r--r-- | alacritty/src/config/monitor.rs | 37 | ||||
-rw-r--r-- | alacritty/src/config/ui_config.rs | 6 | ||||
-rw-r--r-- | alacritty/src/display/content.rs | 15 | ||||
-rw-r--r-- | alacritty/src/input.rs | 9 | ||||
-rw-r--r-- | alacritty/src/logging.rs | 2 | ||||
-rw-r--r-- | alacritty/src/renderer/mod.rs | 2 | ||||
-rw-r--r-- | alacritty/src/renderer/rects.rs | 201 | ||||
-rw-r--r-- | alacritty_terminal/Cargo.toml | 2 | ||||
-rw-r--r-- | alacritty_terminal/src/ansi.rs | 23 | ||||
-rw-r--r-- | alacritty_terminal/src/config/mod.rs | 2 | ||||
-rw-r--r-- | alacritty_terminal/src/term/cell.rs | 9 | ||||
-rw-r--r-- | alacritty_terminal/src/term/mod.rs | 32 |
23 files changed, 560 insertions, 261 deletions
diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 3d432e03..00000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -/CHANGELOG.md merge=union diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b51876f..f0b713cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,3 +25,11 @@ jobs: run: | rustup component add clippy cargo clippy --all-targets + check-macos-arm: + runs-on: macos-11 + steps: + - uses: actions/checkout@v2 + - name: Install target + run: rustup update && rustup target add aarch64-apple-darwin + - name: Check build + run: cargo check --target=aarch64-apple-darwin diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 682e344b..ae8dc448 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,14 +10,18 @@ env: jobs: macos: - runs-on: macos-latest + runs-on: macos-11 steps: - uses: actions/checkout@v2 + - name: Install ARM target + run: rustup update && rustup target add aarch64-apple-darwin - name: Test run: cargo test --release - - name: Make App - run: make dmg + - name: Test ARM + run: cargo test --release --target=aarch64-apple-darwin + - name: Make DMG + run: make dmg-universal - name: Upload Application run: | mv ./target/release/osx/Alacritty.dmg ./Alacritty-${GITHUB_REF##*/}.dmg diff --git a/CHANGELOG.md b/CHANGELOG.md index 788ad860..023eaec5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - `ExpandSelection` is now a configurable mouse binding action - Config option `background_opacity`, you should use `window.opacity` instead +- Reload configuration files when their symbolic link is replaced ## 0.9.0 @@ -52,6 +53,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Minimum Rust version has been bumped to 1.45.0 +### Packaging + +- Updated shell completions +- Added ARM executable to prebuilt macOS binaries + ### Added - IME composition preview not appearing on Windows @@ -21,7 +21,6 @@ dependencies = [ "alacritty_config_derive", "alacritty_terminal", "bitflags", - "clap", "cocoa 0.24.0", "copypasta", "crossfont", @@ -41,6 +40,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "structopt", "time", "unicode-width", "wayland-client", @@ -755,6 +755,15 @@ dependencies = [ ] [[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1280,6 +1289,30 @@ dependencies = [ ] [[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] name = "proc-macro2" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1548,6 +1581,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" [[package]] +name = "structopt" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b041cdcb67226aca307e6e7be44c8806423d83e018bd662360a93dabce4d71" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7813934aecf5f51a54775e00068c237de98489463968231a51746bbbc03f9c10" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "syn" version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1613,6 +1670,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc" [[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + +[[package]] name = "unicode-width" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -125,7 +125,7 @@ a `zypper` command that should install all of them. If something is still found to be missing, please open an issue. ```sh -zypper install cmake freetype-devel fontconfig-devel libxcb-devel +zypper install cmake freetype-devel fontconfig-devel libxcb-devel libxkbcommon-dev ``` #### Slackware @@ -251,6 +251,16 @@ make app cp -r target/release/osx/Alacritty.app /Applications/ ``` +#### Universal Binary + +The following will build an executable that runs on both x86 and ARM macos +architectures: + +```sh +rustup target add x86_64-apple-darwin aarch64-apple-darwin +make app-universal +``` + ## Post Build There are some extra things you might want to set up after installing Alacritty. @@ -26,15 +26,21 @@ vpath $(DMG_NAME) $(APP_DIR) all: help -help: ## Prints help for targets with comments +help: ## Print this help message @grep -E '^[a-zA-Z._-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' -binary: | $(TARGET) ## Build release binary with cargo -$(TARGET): +binary: $(TAGET)-native ## Build a release binary +binary-universal: $(TAGET)-universal ## Build a universal release binary +$(TARGET)-native: MACOSX_DEPLOYMENT_TARGET="10.11" cargo build --release +$(TARGET)-universal: + MACOSX_DEPLOYMENT_TARGET="10.11" cargo build --release --target=x86_64-apple-darwin + MACOSX_DEPLOYMENT_TARGET="10.11" cargo build --release --target=aarch64-apple-darwin + @lipo target/{x86_64,aarch64}-apple-darwin/release/$(TARGET) -create -output $(APP_BINARY) -app: | $(APP_NAME) ## Clone Alacritty.app template and mount binary -$(APP_NAME): $(TARGET) +app: $(APP_NAME)-native ## Create an Alacritty.app +app-universal: $(APP_NAME)-universal ## Create a universal Alacritty.app +$(APP_NAME)-%: $(TARGET)-% @mkdir -p $(APP_BINARY_DIR) @mkdir -p $(APP_EXTRAS_DIR) @mkdir -p $(APP_COMPLETIONS_DIR) @@ -44,10 +50,11 @@ $(APP_NAME): $(TARGET) @cp -fp $(APP_BINARY) $(APP_BINARY_DIR) @cp -fp $(COMPLETIONS) $(APP_COMPLETIONS_DIR) @touch -r "$(APP_BINARY)" "$(APP_DIR)/$(APP_NAME)" - @echo "Created '$@' in '$(APP_DIR)'" + @echo "Created '$(APP_NAME)' in '$(APP_DIR)'" -dmg: | $(DMG_NAME) ## Pack Alacritty.app into .dmg -$(DMG_NAME): $(APP_NAME) +dmg: $(DMG_NAME)-native ## Create an Alacritty.dmg +dmg-universal: $(DMG_NAME)-universal ## Create a universal Alacritty.dmg +$(DMG_NAME)-%: $(APP_NAME)-% @echo "Packing disk image..." @ln -sf /Applications $(DMG_DIR)/Applications @hdiutil create $(DMG_DIR)/$(DMG_NAME) \ @@ -55,12 +62,14 @@ $(DMG_NAME): $(APP_NAME) -fs HFS+ \ -srcfolder $(APP_DIR) \ -ov -format UDZO - @echo "Packed '$@' in '$(APP_DIR)'" + @echo "Packed '$(APP_NAME)' in '$(APP_DIR)'" -install: $(DMG_NAME) ## Mount disk image +install: $(INSTALL)-native ## Mount disk image +install-universal: $(INSTALL)-native ## Mount universal disk image +$(INSTALL)-%: $(DMG_NAME)-% @open $(DMG_DIR)/$(DMG_NAME) -.PHONY: app binary clean dmg install $(TARGET) +.PHONY: app binary clean dmg install $(TARGET) $(TARGET)-universal -clean: ## Remove all artifacts - -rm -rf $(APP_DIR) +clean: ## Remove all build artifacts + @cargo clean diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index cb269350..6ac20526 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -18,7 +18,7 @@ path = "../alacritty_config_derive" version = "0.1.0" [dependencies] -clap = "2" +structopt = "0.3.22" log = { version = "0.4", features = ["std", "serde"] } time = "0.1.40" fnv = "1" diff --git a/alacritty/build.rs b/alacritty/build.rs index c093afca..e81da150 100644 --- a/alacritty/build.rs +++ b/alacritty/build.rs @@ -6,7 +6,11 @@ use std::process::Command; use gl_generator::{Api, Fallbacks, GlobalGenerator, Profile, Registry}; fn main() { - println!("cargo:rustc-env=GIT_HASH={}", commit_hash()); + let mut version = String::from(env!("CARGO_PKG_VERSION")); + if let Some(commit_hash) = commit_hash() { + version = format!("{} ({})", version, commit_hash); + } + println!("cargo:rustc-env=VERSION={}", version); let dest = env::var("OUT_DIR").unwrap(); let mut file = File::create(&Path::new(&dest).join("gl_bindings.rs")).unwrap(); @@ -19,11 +23,11 @@ fn main() { embed_resource::compile("./windows/windows.rc"); } -fn commit_hash() -> String { +fn commit_hash() -> Option<String> { Command::new("git") .args(&["rev-parse", "--short", "HEAD"]) .output() .ok() .and_then(|output| String::from_utf8(output.stdout).ok()) - .unwrap_or_default() + .map(|hash| hash.trim().into()) } diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs index 682bdde3..a1480807 100644 --- a/alacritty/src/cli.rs +++ b/alacritty/src/cli.rs @@ -1,219 +1,101 @@ use std::cmp::max; use std::path::PathBuf; -use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; use log::{self, error, LevelFilter}; use serde_yaml::Value; +use structopt::StructOpt; use alacritty_terminal::config::Program; use crate::config::serde_utils; -use crate::config::window::DEFAULT_NAME; +use crate::config::window::{Class, DEFAULT_NAME}; use crate::config::Config; -#[cfg(not(any(target_os = "macos", windows)))] -const CONFIG_PATH: &str = "$XDG_CONFIG_HOME/alacritty/alacritty.yml"; -#[cfg(windows)] -const CONFIG_PATH: &str = "%APPDATA%\\alacritty\\alacritty.yml"; -#[cfg(target_os = "macos")] -const CONFIG_PATH: &str = "$HOME/.config/alacritty/alacritty.yml"; - /// Options specified on the command line. +#[derive(StructOpt, Debug)] +#[structopt(author, about, version = env!("VERSION"))] pub struct Options { + /// Print all events to stdout. + #[structopt(long)] pub print_events: bool, + + /// Generates ref test. + #[structopt(long)] pub ref_test: bool, + + /// Defines the window title [default: Alacritty]. + #[structopt(short, long)] pub title: Option<String>, - pub class_instance: Option<String>, - pub class_general: Option<String>, - pub embed: Option<String>, - pub log_level: LevelFilter, - pub command: Option<Program>, - pub hold: bool, - pub working_directory: Option<PathBuf>, - pub config_path: Option<PathBuf>, - pub config_options: Value, -} -impl Default for Options { - fn default() -> Options { - Options { - print_events: false, - ref_test: false, - title: None, - class_instance: None, - class_general: None, - embed: None, - log_level: LevelFilter::Warn, - command: None, - hold: false, - working_directory: None, - config_path: None, - config_options: Value::Null, - } - } -} + /// Defines window class/app_id on X11/Wayland [default: Alacritty]. + #[structopt(long, value_name = "instance> | <instance>,<general", parse(try_from_str = parse_class))] + pub class: Option<Class>, -impl Options { - /// Build `Options` from command line arguments. - pub fn new() -> Self { - let mut version = crate_version!().to_owned(); - let commit_hash = env!("GIT_HASH"); - if !commit_hash.is_empty() { - version = format!("{} ({})", version, commit_hash); - } + /// Defines the X11 window ID (as a decimal integer) to embed Alacritty within. + #[structopt(long)] + pub embed: Option<String>, - let mut options = Options::default(); - - let matches = App::new(crate_name!()) - .version(version.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("print-events") - .long("print-events") - .help("Print all events to stdout"), - ) - .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") - .value_name("instance> | <instance>,<general") - .takes_value(true) - .use_delimiter(true) - .help(&format!( - "Defines window class/app_id on X11/Wayland [default: {}]", - DEFAULT_NAME - )), - ) - .arg( - Arg::with_name("embed").long("embed").takes_value(true).help( - "Defines the X11 window ID (as a decimal integer) to embed Alacritty within", - ), - ) - .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( - &format!("Specify alternative configuration file [default: {}]", CONFIG_PATH), - )) - .arg( - Arg::with_name("command") - .long("command") - .short("e") - .multiple(true) - .takes_value(true) - .allow_hyphen_values(true) - .help("Command and args to execute (must be last argument)"), - ) - .arg(Arg::with_name("hold").long("hold").help("Remain open after child process exits")) - .arg( - Arg::with_name("option") - .long("option") - .short("o") - .multiple(true) - .takes_value(true) - .help("Override configuration file options [example: cursor.style=Beam]"), - ) - .get_matches(); - - if matches.is_present("ref-test") { - options.ref_test = true; - } + /// Start the shell in the specified working directory. + #[structopt(long)] + pub working_directory: Option<PathBuf>, - if matches.is_present("print-events") { - options.print_events = true; - } + /// Specify alternative configuration file [default: $XDG_CONFIG_HOME/alacritty/alacritty.yml]. + #[cfg(not(any(target_os = "macos", windows)))] + #[structopt(long)] + pub config_file: Option<PathBuf>, - if let Some(mut class) = matches.values_of("class") { - options.class_instance = class.next().map(|instance| instance.to_owned()); - options.class_general = class.next().map(|general| general.to_owned()); - } + /// Specify alternative configuration file [default: %APPDATA%\alacritty\alacritty.yml]. + #[cfg(windows)] + #[structopt(long)] + pub config_file: Option<PathBuf>, - options.title = matches.value_of("title").map(ToOwned::to_owned); - options.embed = matches.value_of("embed").map(ToOwned::to_owned); + /// Specify alternative configuration file [default: $HOME/.config/alacritty/alacritty.yml]. + #[cfg(target_os = "macos")] + #[structopt(long)] + pub config_file: Option<PathBuf>, - match matches.occurrences_of("q") { - 0 => (), - 1 => options.log_level = LevelFilter::Error, - _ => options.log_level = LevelFilter::Off, - } + /// Remain open after child process exits. + #[structopt(long)] + pub hold: bool, - 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, - _ => options.log_level = LevelFilter::Trace, - } + /// CLI options for config overrides. + #[structopt(skip)] + pub config_options: Value, - if let Some(dir) = matches.value_of("working-directory") { - options.working_directory = Some(PathBuf::from(dir.to_string())); - } + /// Reduces the level of verbosity (the min level is -qq). + #[structopt(short, conflicts_with("verbose"), parse(from_occurrences))] + quiet: u8, - if let Some(path) = matches.value_of("config-file") { - options.config_path = Some(PathBuf::from(path.to_string())); - } + /// Increases the level of verbosity (the max level is -vvv). + #[structopt(short, conflicts_with("quiet"), parse(from_occurrences))] + verbose: u8, - 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 program = String::from(args.next().unwrap()); - let args = args.map(String::from).collect(); - options.command = Some(Program::WithArgs { program, args }); - } + /// Command and args to execute (must be last argument). + #[structopt(short = "e", long, allow_hyphen_values = true)] + command: Vec<String>, - if matches.is_present("hold") { - options.hold = true; - } + /// Override configuration file options [example: cursor.style=Beam]. + #[structopt(short = "o", long)] + option: Vec<String>, +} - if let Some(config_options) = matches.values_of("option") { - for option in config_options { - match option_as_value(option) { - Ok(value) => { - options.config_options = serde_utils::merge(options.config_options, value); - }, - Err(_) => eprintln!("Invalid CLI config option: {:?}", option), - } +impl Options { + pub fn new() -> Self { + let mut options = Self::from_args(); + + // Convert `--option` flags into serde `Value`. + for option in &options.option { + match option_as_value(option) { + Ok(value) => { + options.config_options = serde_utils::merge(options.config_options, value); + }, + Err(_) => eprintln!("Invalid CLI config option: {:?}", option), } } options } - /// Configuration file path. - pub fn config_path(&self) -> Option<PathBuf> { - self.config_path.clone() - } - - /// CLI config options as deserializable serde value. - pub fn config_options(&self) -> &Value { - &self.config_options - } - /// Override configuration file with options from the CLI. pub fn override_config(&self, config: &mut Config) { if let Some(working_directory) = &self.working_directory { @@ -224,8 +106,8 @@ impl Options { } } - if let Some(command) = &self.command { - config.shell = Some(command.clone()); + if let Some(command) = self.command() { + config.shell = Some(command); } config.hold = self.hold; @@ -233,17 +115,14 @@ impl Options { if let Some(title) = self.title.clone() { config.ui_config.window.title = title } - if let Some(class_instance) = self.class_instance.clone() { - config.ui_config.window.class.instance = class_instance; - } - if let Some(class_general) = self.class_general.clone() { - config.ui_config.window.class.general = class_general; + if let Some(class) = &self.class { + config.ui_config.window.class = class.clone(); } config.ui_config.window.dynamic_title &= self.title.is_none(); config.ui_config.window.embed = self.embed.as_ref().and_then(|embed| embed.parse().ok()); config.ui_config.debug.print_events |= self.print_events; - config.ui_config.debug.log_level = max(config.ui_config.debug.log_level, self.log_level); + config.ui_config.debug.log_level = max(config.ui_config.debug.log_level, self.log_level()); config.ui_config.debug.ref_test |= self.ref_test; if config.ui_config.debug.print_events { @@ -251,6 +130,32 @@ impl Options { max(config.ui_config.debug.log_level, LevelFilter::Info); } } + + /// Logging filter level. + pub fn log_level(&self) -> LevelFilter { + match (self.quiet, self.verbose) { + // Force at least `Info` level for `--print-events`. + (_, 0) if self.print_events => LevelFilter::Info, + + // Default. + (0, 0) => LevelFilter::Warn, + + // Verbose. + (_, 1) => LevelFilter::Info, + (_, 2) => LevelFilter::Debug, + (0, _) => LevelFilter::Trace, + + // Quiet. + (1, _) => LevelFilter::Error, + (..) => LevelFilter::Off, + } + } + + /// Shell override passed through the CLI. + pub fn command(&self) -> Option<Program> { + let (program, args) = self.command.split_first()?; + Some(Program::WithArgs { program: program.clone(), args: args.to_vec() }) + } } /// Format an option in the format of `parent.field=value` to a serde Value. @@ -278,6 +183,23 @@ fn option_as_value(option: &str) -> Result<Value, serde_yaml::Error> { serde_yaml::from_str(&yaml_text) } +/// Parse the class CLI parameter. +fn parse_class(input: &str) -> Result<Class, String> { + match input.find(',') { + Some(position) => { + let general = input[position + 1..].to_owned(); + + // Warn the user if they've passed too many values. + if general.contains(',') { + return Err(String::from("Too many parameters")); + } + + Ok(Class { instance: input[..position].into(), general }) + }, + None => Ok(Class { instance: input.into(), general: DEFAULT_NAME.into() }), + } +} + #[cfg(test)] mod tests { use super::*; @@ -289,7 +211,7 @@ mod tests { let mut config = Config::default(); let old_dynamic_title = config.ui_config.window.dynamic_title; - Options::default().override_config(&mut config); + Options::new().override_config(&mut config); assert_eq!(old_dynamic_title, config.ui_config.window.dynamic_title); } @@ -298,7 +220,7 @@ mod tests { fn dynamic_title_overridden_by_options() { let mut config = Config::default(); - let options = Options { title: Some("foo".to_owned()), ..Options::default() }; + let options = Options { title: Some("foo".to_owned()), ..Options::new() }; options.override_config(&mut config); assert!(!config.ui_config.window.dynamic_title); @@ -309,7 +231,7 @@ mod tests { let mut config = Config::default(); config.ui_config.window.title = "foo".to_owned(); - Options::default().override_config(&mut config); + Options::new().override_config(&mut config); assert!(config.ui_config.window.dynamic_title); } @@ -350,4 +272,24 @@ mod tests { assert_eq!(value, Value::Mapping(expected)); } + + #[test] + fn parse_instance_class() { + let class = parse_class("one").unwrap(); + assert_eq!(class.instance, "one"); + assert_eq!(class.general, DEFAULT_NAME); + } + + #[test] + fn parse_general_class() { + let class = parse_class("one,two").unwrap(); + assert_eq!(class.instance, "one"); + assert_eq!(class.general, "two"); + } + + #[test] + fn parse_invalid_class() { + let class = parse_class("one,two,three"); + assert!(class.is_err()); + } } diff --git a/alacritty/src/config/mod.rs b/alacritty/src/config/mod.rs index 43cade6d..4a3c0ae9 100644 --- a/alacritty/src/config/mod.rs +++ b/alacritty/src/config/mod.rs @@ -101,8 +101,8 @@ impl From<serde_yaml::Error> for Error { /// Load the configuration file. pub fn load(options: &Options) -> Config { - let config_options = options.config_options().clone(); - let config_path = options.config_path().or_else(installed_config); + let config_options = options.config_options.clone(); + let config_path = options.config_file.clone().or_else(installed_config); // Load the config using the following fallback behavior: // - Config path + CLI overrides @@ -128,7 +128,7 @@ pub fn load(options: &Options) -> Config { /// Attempt to reload the configuration file. pub fn reload(config_path: &Path, options: &Options) -> Result<Config> { // Load config, propagating errors. - let config_options = options.config_options().clone(); + let config_options = options.config_options.clone(); let mut config = load_from(config_path, config_options)?; after_loading(&mut config, options); diff --git a/alacritty/src/config/monitor.rs b/alacritty/src/config/monitor.rs index 4a694fac..e3dd0556 100644 --- a/alacritty/src/config/monitor.rs +++ b/alacritty/src/config/monitor.rs @@ -1,4 +1,3 @@ -use std::fs; use std::path::PathBuf; use std::sync::mpsc; use std::time::Duration; @@ -16,23 +15,21 @@ const DEBOUNCE_DELAY: Duration = Duration::from_millis(10); const DEBOUNCE_DELAY: Duration = Duration::from_millis(1000); pub fn watch(mut paths: Vec<PathBuf>, event_proxy: EventProxy) { - // Canonicalize all paths, filtering out the ones that do not exist. - paths = paths - .drain(..) - .filter_map(|path| match fs::canonicalize(&path) { - Ok(path) => Some(path), - Err(err) => { - error!("Unable to canonicalize config path {:?}: {}", path, err); - None - }, - }) - .collect(); - // Don't monitor config if there is no path to watch. if paths.is_empty() { return; } + // Canonicalize paths, keeping the base paths for symlinks. + for i in 0..paths.len() { + if let Ok(canonical_path) = paths[i].canonicalize() { + match paths[i].symlink_metadata() { + Ok(metadata) if metadata.file_type().is_symlink() => paths.push(canonical_path), + _ => paths[i] = canonical_path, + } + } + } + // The Duration argument is a debouncing period. let (tx, rx) = mpsc::channel(); let mut watcher = match watcher(tx, DEBOUNCE_DELAY) { @@ -73,17 +70,15 @@ pub fn watch(mut paths: Vec<PathBuf>, event_proxy: EventProxy) { }; match event { - DebouncedEvent::Rename(..) => continue, - DebouncedEvent::Write(path) + DebouncedEvent::Rename(_, path) + | DebouncedEvent::Write(path) | DebouncedEvent::Create(path) - | DebouncedEvent::Chmod(path) => { - if !paths.contains(&path) { - continue; - } - + | DebouncedEvent::Chmod(path) + if paths.contains(&path) => + { // Always reload the primary configuration file. event_proxy.send_event(Event::ConfigReload(paths[0].clone())); - }, + } _ => {}, } } diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs index f0917cf5..3ce02161 100644 --- a/alacritty/src/config/ui_config.rs +++ b/alacritty/src/config/ui_config.rs @@ -70,7 +70,7 @@ pub struct UiConfig { /// Background opacity from 0.0 to 1.0. #[config(deprecated = "use window.opacity instead")] - window_opacity: Option<Percentage>, + background_opacity: Option<Percentage>, } impl Default for UiConfig { @@ -85,7 +85,7 @@ impl Default for UiConfig { config_paths: Default::default(), key_bindings: Default::default(), mouse_bindings: Default::default(), - window_opacity: Default::default(), + background_opacity: Default::default(), bell: Default::default(), colors: Default::default(), draw_bold_text_with_bright_colors: Default::default(), @@ -117,7 +117,7 @@ impl UiConfig { #[inline] pub fn window_opacity(&self) -> f32 { - self.window_opacity.unwrap_or(self.window.opacity).as_f32() + self.background_opacity.unwrap_or(self.window.opacity).as_f32() } #[inline] diff --git a/alacritty/src/display/content.rs b/alacritty/src/display/content.rs index 85719c06..297aefd6 100644 --- a/alacritty/src/display/content.rs +++ b/alacritty/src/display/content.rs @@ -191,6 +191,7 @@ pub struct RenderableCell { pub graphic: Option<GraphicCell>, pub fg: Rgb, pub bg: Rgb, + pub sp: Rgb, // Special pub bg_alpha: f32, pub flags: Flags, } @@ -200,6 +201,11 @@ impl RenderableCell { // Lookup RGB values. let mut fg = Self::compute_fg_rgb(content, cell.fg, cell.flags); let mut bg = Self::compute_bg_rgb(content, cell.bg); + let mut sp = if cell.sp == Color::Named(NamedColor::Foreground) { + fg + } else { + Self::compute_bg_rgb(content, cell.sp) + }; let mut bg_alpha = if cell.flags.contains(Flags::INVERSE) { mem::swap(&mut fg, &mut bg); @@ -266,6 +272,7 @@ impl RenderableCell { point, fg, bg, + sp, } } @@ -274,7 +281,13 @@ impl RenderableCell { self.bg_alpha == 0. && self.character == ' ' && self.zerowidth.is_none() - && !self.flags.intersects(Flags::UNDERLINE | Flags::STRIKEOUT | Flags::DOUBLE_UNDERLINE) + && !self.flags.intersects( + Flags::UNDERLINE | + Flags::STRIKEOUT | + Flags::DOUBLE_UNDERLINE | + Flags::UNDERCURL | + Flags::OVERLINE | + Flags::DOTTED_UNDERLINE) } /// Apply [`CellRgb`] colors to the cell's colors. diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 98a1b723..b8ae4b00 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -799,11 +799,18 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { if self.ctx.config().ui_config.alt_send_esc && *self.ctx.received_count() == 0 && self.ctx.modifiers().alt() - && utf8_len == 1 { bytes.insert(0, b'\x1b'); } + if self.ctx.modifiers().logo() + { + bytes.insert(0, b'\\'); + bytes.insert(0, b'\x1b'); + bytes.insert(0, b'@'); + bytes.insert(0, b'\x1b'); + } + self.ctx.write_to_pty(bytes); *self.ctx.received_count() += 1; diff --git a/alacritty/src/logging.rs b/alacritty/src/logging.rs index 545905a2..7cef3887 100644 --- a/alacritty/src/logging.rs +++ b/alacritty/src/logging.rs @@ -29,7 +29,7 @@ pub fn initialize( options: &Options, event_proxy: EventLoopProxy<Event>, ) -> Result<Option<PathBuf>, log::SetLoggerError> { - log::set_max_level(options.log_level); + log::set_max_level(options.log_level()); let logger = Logger::new(event_proxy); let path = logger.file_path(); diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs index 71ed6e28..fba47c40 100644 --- a/alacritty/src/renderer/mod.rs +++ b/alacritty/src/renderer/mod.rs @@ -853,6 +853,7 @@ impl<'a> RenderApi<'a> { bg: Rgb, string: &str, ) { + let sp = Rgb { b: 0, g: 0, r: 0 }; let cells = string .chars() .enumerate() @@ -865,6 +866,7 @@ impl<'a> RenderApi<'a> { bg_alpha: 1.0, fg, bg, + sp, }) .collect::<Vec<_>>(); diff --git a/alacritty/src/renderer/rects.rs b/alacritty/src/renderer/rects.rs index 77c22011..25ae93d6 100644 --- a/alacritty/src/renderer/rects.rs +++ b/alacritty/src/renderer/rects.rs @@ -62,7 +62,18 @@ impl RenderLine { end: Point<usize>, color: Rgb, ) { - let (position, thickness) = match flag { + match flag { + Flags::UNDERCURL => { + Self::push_undercurl_rects( + rects, + size, + metrics.descent, + start, + end, + metrics.underline_position + 1., + metrics.underline_thickness, + color) + } Flags::DOUBLE_UNDERLINE => { // Position underlines so each one has 50% of descent available. let top_pos = 0.25 * metrics.descent; @@ -78,22 +89,181 @@ impl RenderLine { color, )); - (bottom_pos, metrics.underline_thickness) + rects.push(Self::create_rect( + size, + metrics.descent, + start, + end, + bottom_pos, + metrics.underline_thickness, + color, + )); + }, + Flags::UNDERLINE => { + rects.push(Self::create_rect( + size, + metrics.descent, + start, + end, + metrics.underline_position + 1., + metrics.underline_thickness, + color, + )); }, - Flags::UNDERLINE => (metrics.underline_position, metrics.underline_thickness), - Flags::STRIKEOUT => (metrics.strikeout_position, metrics.strikeout_thickness), + Flags::OVERLINE => { + let start_x = start.column.0 as f32 * size.cell_width(); + let end_x = (end.column.0 + 1) as f32 * size.cell_width(); + rects.push(RenderRect::new( + start_x + size.padding_x(), + (start.line as f32 * size.cell_height()) + (size.cell_height() / 8.), + end_x - start_x, + metrics.underline_thickness, + color, + 1., + )); + }, + Flags::STRIKEOUT => { + rects.push(Self::create_rect( + size, + metrics.descent, + start, + end, + metrics.strikeout_position, + metrics.strikeout_thickness, + color, + )); + }, + Flags::DOTTED_UNDERLINE => { + Self::push_dotted_underline_rects( + rects, + size, + metrics.descent, + start, + end, + metrics.underline_position + 1., + metrics.underline_thickness, + color) + } _ => unimplemented!("Invalid flag for cell line drawing specified"), }; + } - rects.push(Self::create_rect( - size, - metrics.descent, - start, - end, - position, - thickness, - color, - )); + fn push_undercurl_rects( + rects: &mut Vec<RenderRect>, + size: &SizeInfo, + descent: f32, + start: Point<usize>, + end: Point<usize>, + position: f32, + mut thickness: f32, + color: Rgb, + ) { + let start_x = start.column.0 as f32 * size.cell_width(); + let end_x = (end.column.0 + 1) as f32 * size.cell_width(); + + // Make sure lines are always visible. + thickness = thickness.max(1.); + + let line_bottom = (start.line as f32 + 1.) * size.cell_height(); + let baseline = line_bottom + descent; + + let mut y = (baseline - position - thickness / 2.).ceil(); + let max_y = line_bottom - thickness; + if y > max_y { + y = max_y; + } + + let period_div = 1.5 * (2. * std::f32::consts::PI) / size.cell_width(); + let amplitude_mul = size.cell_height() / 10.; + + let mut x = start_x; + let mut idx = 0; + while x < end_x { + let altr = [1., 0., -1., 0.][idx % 4]; + + let fl = (x * period_div).sin() * amplitude_mul; + let al = (fl.round() - fl).abs(); + + rects.push(RenderRect::new( + x + size.padding_x(), + y + size.padding_y() + fl, + 1., + thickness, + color, + (1. - al).powf(2.), + )); + + let fl = (x * period_div).sin() * amplitude_mul - 0.5; + let al = (fl.round() - fl).abs(); + + rects.push(RenderRect::new( + x + size.padding_x(), + y + size.padding_y() + fl, + 1., + thickness, + color, + (1. - al).powf(2.), + )); + + let fl = (x * period_div).sin() * amplitude_mul + 0.5; + let al = (fl.round() - fl).abs(); + + rects.push(RenderRect::new( + x + size.padding_x(), + y + size.padding_y() + fl, + 1., + thickness, + color, + (1. - al).powf(2.), + )); + + x += 1.; + idx += 1; + } + } + + fn push_dotted_underline_rects( + rects: &mut Vec<RenderRect>, + size: &SizeInfo, + descent: f32, + start: Point<usize>, + end: Point<usize>, + position: f32, + mut thickness: f32, + color: Rgb, + ) { + let start_x = start.column.0 as f32 * size.cell_width(); + let end_x = (end.column.0 + 1) as f32 * size.cell_width(); + + // Make sure lines are always visible. + thickness = thickness.max(1.); + + let line_bottom = (start.line as f32 + 1.) * size.cell_height(); + let baseline = line_bottom + descent; + + let mut y = (baseline - position - thickness / 2.).ceil(); + let max_y = line_bottom - thickness; + if y > max_y { + y = max_y; + } + + let mut x = start_x; + let mut idx = 0; + while x < end_x { + if idx % 4 == 0 { + let rect = RenderRect::new( + x + size.padding_x(), + y + size.padding_y(), + thickness + 1., + thickness + 1., + color, + 1., + ); + rects.push(rect); + } + x += 1.; + idx += 1; + } } /// Create a line's rect at a position relative to the baseline. @@ -161,6 +331,9 @@ impl RenderLines { self.update_flag(cell, Flags::UNDERLINE); self.update_flag(cell, Flags::DOUBLE_UNDERLINE); self.update_flag(cell, Flags::STRIKEOUT); + self.update_flag(cell, Flags::UNDERCURL); + self.update_flag(cell, Flags::OVERLINE); + self.update_flag(cell, Flags::DOTTED_UNDERLINE); } /// Update the lines for a specific flag. @@ -188,7 +361,7 @@ impl RenderLines { } // Start new line if there currently is none. - let line = RenderLine { start: cell.point, end, color: cell.fg }; + let line = RenderLine { start: cell.point, end, color: cell.sp }; match self.inner.get_mut(&flag) { Some(lines) => lines.push(line), None => { diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml index e0a37b17..c25c4cab 100644 --- a/alacritty_terminal/Cargo.toml +++ b/alacritty_terminal/Cargo.toml @@ -35,7 +35,7 @@ signal-hook = { version = "0.1", features = ["mio-support"] } miow = "0.3" winapi = { version = "0.3.7", features = [ "impl-default", "basetsd", "libloaderapi", "minwindef", "ntdef", "processthreadsapi", "winbase", - "wincon", "wincontypes", "winerror", "winnt", "winuser", + "wincon", "wincontypes", "winerror", "winnt", "winuser", "consoleapi", ]} mio-anonymous-pipes = "0.2" diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs index 14617de1..eaaf5d62 100644 --- a/alacritty_terminal/src/ansi.rs +++ b/alacritty_terminal/src/ansi.rs @@ -790,6 +790,10 @@ pub enum Attr { Underline, /// Underlined twice. DoubleUnderline, + /// Undercurl twice. + Undercurl, + /// Underlined with dots. + DottedUnderline, /// Blink cursor slowly. BlinkSlow, /// Blink cursor fast. @@ -800,6 +804,8 @@ pub enum Attr { Hidden, /// Strikeout text. Strike, + /// Strikeout text. + Overline, /// Cancel bold. CancelBold, /// Cancel bold and dim. @@ -816,10 +822,14 @@ pub enum Attr { CancelHidden, /// Cancel strikeout. CancelStrike, + /// Cancel strikeout. + CancelOverline, /// Set indexed foreground color. Foreground(Color), /// Set indexed background color. Background(Color), + /// Set indexed special color (for underlines). + Special(Color), } /// Identifiers which can be assigned to a graphic character set. @@ -1002,6 +1012,7 @@ where .trim() .to_owned(); self.handler.set_title(Some(title)); + return; } unhandled(params); @@ -1356,6 +1367,8 @@ fn attrs_from_sgr_parameters(params: &mut ParamsIter<'_>) -> Vec<Option<Attr>> { [3] => Some(Attr::Italic), [4, 0] => Some(Attr::CancelUnderline), [4, 2] => Some(Attr::DoubleUnderline), + [4, 3] => Some(Attr::Undercurl), + [4, 4] => Some(Attr::DottedUnderline), [4, ..] => Some(Attr::Underline), [5] => Some(Attr::BlinkSlow), [6] => Some(Attr::BlinkFast), @@ -1410,6 +1423,16 @@ fn attrs_from_sgr_parameters(params: &mut ParamsIter<'_>) -> Vec<Option<Attr>> { parse_sgr_color(&mut iter).map(Attr::Background) }, [49] => Some(Attr::Background(Color::Named(NamedColor::Background))), + [53] => Some(Attr::Overline), + [55] => Some(Attr::CancelOverline), + [58, params @ ..] => { + let rgb_start = if params.len() > 4 { 2 } else { 1 }; + let rgb_iter = params[rgb_start..].iter().copied(); + let mut iter = iter::once(params[0]).chain(rgb_iter); + + parse_sgr_color(&mut iter).map(Attr::Special) + }, + [59] => Some(Attr::Special(Color::Named(NamedColor::Foreground))), [90] => Some(Attr::Foreground(Color::Named(NamedColor::BrightBlack))), [91] => Some(Attr::Foreground(Color::Named(NamedColor::BrightRed))), [92] => Some(Attr::Foreground(Color::Named(NamedColor::BrightGreen))), diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs index 0b313598..382314bd 100644 --- a/alacritty_terminal/src/config/mod.rs +++ b/alacritty_terminal/src/config/mod.rs @@ -66,6 +66,7 @@ pub struct Cursor { pub style: ConfigCursorStyle, pub vi_mode_style: Option<ConfigCursorStyle>, pub unfocused_hollow: bool, + pub cursor_crosshairs: bool, thickness: Percentage, blink_interval: u64, @@ -79,6 +80,7 @@ impl Default for Cursor { blink_interval: 750, style: Default::default(), vi_mode_style: Default::default(), + cursor_crosshairs: false, } } } diff --git a/alacritty_terminal/src/term/cell.rs b/alacritty_terminal/src/term/cell.rs index 18aad87d..14229d15 100644 --- a/alacritty_terminal/src/term/cell.rs +++ b/alacritty_terminal/src/term/cell.rs @@ -26,6 +26,9 @@ bitflags! { const LEADING_WIDE_CHAR_SPACER = 0b0000_0100_0000_0000; const DOUBLE_UNDERLINE = 0b0000_1000_0000_0000; const GRAPHICS = 0b0001_0000_0000_0000; + const UNDERCURL = 0b0010_0000_0000_0000; + const DOTTED_UNDERLINE = 0b0100_0000_0000_0000; + const OVERLINE = 0b1000_0000_0000_0000; } } @@ -66,6 +69,7 @@ pub struct Cell { pub c: char, pub fg: Color, pub bg: Color, + pub sp: Color, pub flags: Flags, #[serde(default)] extra: Option<Box<CellExtra>>, @@ -78,6 +82,7 @@ impl Default for Cell { c: ' ', bg: Color::Named(NamedColor::Background), fg: Color::Named(NamedColor::Foreground), + sp: Color::Named(NamedColor::Foreground), flags: Flags::empty(), extra: None, } @@ -135,10 +140,14 @@ impl GridCell for Cell { (self.c == ' ' || self.c == '\t') && self.bg == Color::Named(NamedColor::Background) && self.fg == Color::Named(NamedColor::Foreground) + && self.sp == Color::Named(NamedColor::Foreground) && !self.flags.intersects( Flags::INVERSE | Flags::UNDERLINE | Flags::DOUBLE_UNDERLINE + | Flags::UNDERCURL + | Flags::DOTTED_UNDERLINE + | Flags::OVERLINE | Flags::STRIKEOUT | Flags::WRAPLINE | Flags::WIDE_CHAR_SPACER diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index 90af5ce7..17f64099 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -802,6 +802,7 @@ impl<T> Term<T> { let c = self.grid.cursor.charsets[self.active_charset].map(c); let fg = self.grid.cursor.template.fg; let bg = self.grid.cursor.template.bg; + let sp = self.grid.cursor.template.sp; let flags = self.grid.cursor.template.flags; let mut cursor_cell = self.grid.cursor_cell(); @@ -831,6 +832,7 @@ impl<T> Term<T> { cursor_cell.c = c; cursor_cell.fg = fg; cursor_cell.bg = bg; + cursor_cell.sp = sp; cursor_cell.flags = flags; } } @@ -1513,9 +1515,11 @@ impl<T: EventListener> Handler for Term<T> { match attr { Attr::Foreground(color) => cursor.template.fg = color, Attr::Background(color) => cursor.template.bg = color, + Attr::Special(color) => cursor.template.sp = color, Attr::Reset => { cursor.template.fg = Color::Named(NamedColor::Foreground); cursor.template.bg = Color::Named(NamedColor::Background); + cursor.template.sp = Color::Named(NamedColor::Foreground); cursor.template.flags = Flags::empty(); }, Attr::Reverse => cursor.template.flags.insert(Flags::INVERSE), @@ -1528,14 +1532,40 @@ impl<T: EventListener> Handler for Term<T> { Attr::CancelItalic => cursor.template.flags.remove(Flags::ITALIC), Attr::Underline => { cursor.template.flags.remove(Flags::DOUBLE_UNDERLINE); + cursor.template.flags.remove(Flags::UNDERCURL); + cursor.template.flags.remove(Flags::DOTTED_UNDERLINE); cursor.template.flags.insert(Flags::UNDERLINE); }, Attr::DoubleUnderline => { cursor.template.flags.remove(Flags::UNDERLINE); + cursor.template.flags.remove(Flags::UNDERCURL); + cursor.template.flags.remove(Flags::DOTTED_UNDERLINE); cursor.template.flags.insert(Flags::DOUBLE_UNDERLINE); }, + Attr::Undercurl => { + cursor.template.flags.remove(Flags::UNDERLINE); + cursor.template.flags.remove(Flags::DOUBLE_UNDERLINE); + cursor.template.flags.remove(Flags::DOTTED_UNDERLINE); + cursor.template.flags.insert(Flags::UNDERCURL); + }, + Attr::DottedUnderline => { + cursor.template.flags.remove(Flags::UNDERLINE); + cursor.template.flags.remove(Flags::DOUBLE_UNDERLINE); + cursor.template.flags.remove(Flags::UNDERCURL); + cursor.template.flags.insert(Flags::DOTTED_UNDERLINE); + }, Attr::CancelUnderline => { - cursor.template.flags.remove(Flags::UNDERLINE | Flags::DOUBLE_UNDERLINE); + cursor.template.flags.remove( + Flags::UNDERLINE | + Flags::DOUBLE_UNDERLINE | + Flags::UNDERCURL | + Flags::DOTTED_UNDERLINE); + }, + Attr::Overline => { + cursor.template.flags.insert(Flags::OVERLINE); + }, + Attr::CancelOverline => { + cursor.template.flags.remove(Flags::OVERLINE); }, Attr::Hidden => cursor.template.flags.insert(Flags::HIDDEN), Attr::CancelHidden => cursor.template.flags.remove(Flags::HIDDEN), |