diff options
| author | Ayose <ayosec@gmail.com> | 2024-10-13 00:00:00 +0000 |
|---|---|---|
| committer | Ayose <ayosec@gmail.com> | 2024-10-13 00:00:00 +0000 |
| commit | c7213774e81dd6012035c5312374db8480ccabc2 (patch) | |
| tree | 6def43870a1a8b837aa8d5ff313731e7d12e78ee | |
| parent | 23c1b2fbcfdc84df806163ea26c5284e598cba2b (diff) | |
| parent | a2653293a8e6ac5f5fb9f7e075656799d8df3488 (diff) | |
| download | r-alacritty-c7213774e81dd6012035c5312374db8480ccabc2.tar.gz r-alacritty-c7213774e81dd6012035c5312374db8480ccabc2.tar.bz2 r-alacritty-c7213774e81dd6012035c5312374db8480ccabc2.zip | |
Merge remote-tracking branch 'vendor/master' into graphics
38 files changed, 826 insertions, 598 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fe8dd2c..c65e7f82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Stable run: cargo test - name: Stable (no default features) @@ -30,7 +30,7 @@ jobs: check-macos-x86_64: runs-on: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install target run: rustup update && rustup target add x86_64-apple-darwin - name: Build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e161816c..81d46970 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: runs-on: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install dependencies run: brew install scdoc - name: Install ARM target @@ -37,7 +37,7 @@ jobs: shell: bash steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Test run: cargo test --release - name: Build @@ -63,7 +63,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install dependencies run: | sudo apt-get install cmake pkg-config libfreetype6-dev libfontconfig1-dev \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 20df64f0..ca900247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,12 +17,18 @@ Notable changes to the `alacritty_terminal` crate are documented in its ### Added - Support relative path imports from config files +- `alacritty migrate` support for TOML configuration changes +- Support for Unicode 16 characters ### Changed - Pressing `Alt` with unicode input will now add `ESC` like for ASCII input - Decorations use opaque style and system window background on macOS - No longer source `~/.zshenv` on macOS +- Moved config options `import`, `working_directory`, `live_config_reload`, and `ipc_socket` + to the new `general` section +- Moved config option `shell` to `terminal.shell` +- `ctrl+shift+u` binding to open links to `ctrl+shift+o` to avoid collisions with IMEs ### Fixed @@ -37,6 +43,11 @@ Notable changes to the `alacritty_terminal` crate are documented in its - Kitty keyboard protocol reporting shifted key codes - Broken search with words broken across line boundary on the first character - Config import changes not being live reloaded +- Cursor color requests with default cursor colors +- Fullwidth semantic escape characters +- Windows app icon now displays properly in old alt+tab on Windows +- Alacritty not being properly activated with startup notify +- Invalid URL highlights after terminal scrolling ## 0.13.2 @@ -60,8 +60,10 @@ dependencies = [ "serde_json", "serde_yaml", "smallvec", + "tempfile", "toml", - "unicode-width", + "toml_edit 0.22.21", + "unicode-width-16", "windows-sys 0.52.0", "winit", "xdg", @@ -109,7 +111,7 @@ dependencies = [ "serde_json", "signal-hook", "smallvec", - "unicode-width", + "unicode-width-16", "vte-graphics", "windows-sys 0.52.0", ] @@ -894,9 +896,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", @@ -1761,9 +1763,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -1893,6 +1895,19 @@ dependencies = [ ] [[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] name = "thiserror" version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1946,14 +1961,14 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.15", + "toml_edit 0.22.21", ] [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -1971,15 +1986,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.15" +version = "0.22.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.13", + "winnow 0.6.18", ] [[package]] @@ -2011,10 +2026,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] -name = "unicode-width" -version = "0.1.13" +name = "unicode-width-16" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "9eba15036aa0f5bf8ed6cd12a624ddb61fd50b0779b1c05d89b663bcaed7b5c2" [[package]] name = "unsafe-libyaml" @@ -2348,6 +2363,15 @@ dependencies = [ ] [[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2589,9 +2613,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.13" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index 36a08c8b..f43fdab0 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -35,12 +35,14 @@ log = { version = "0.4", features = ["std", "serde"] } memoffset = "0.9.0" notify = "6.1.1" parking_lot = "0.12.0" -serde = { version = "1", features = ["derive"] } serde_json = "1" +serde = { version = "1", features = ["derive"] } serde_yaml = "0.9.25" smallvec = { version = "1.13.1", features = ["serde"] } +tempfile = "3.12.0" toml = "0.8.2" -unicode-width = "0.1" +toml_edit = "0.22.21" +unicode-width = { package = "unicode-width-16", version = "0.1.0" } winit = { version = "0.30.4", default-features = false, features = ["rwh_06", "serde"] } [build-dependencies] diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs index f0c9be7e..2b4afa02 100644 --- a/alacritty/src/cli.rs +++ b/alacritty/src/cli.rs @@ -88,8 +88,8 @@ impl Options { /// Override configuration file with options from the CLI. pub fn override_config(&mut self, config: &mut UiConfig) { #[cfg(unix)] - { - config.ipc_socket |= self.socket.is_some(); + if self.socket.is_some() { + config.ipc_socket = Some(true); } config.window.embed = self.embed.as_ref().and_then(|embed| parse_hex_or_decimal(embed)); diff --git a/alacritty/src/config/general.rs b/alacritty/src/config/general.rs new file mode 100644 index 00000000..ba559262 --- /dev/null +++ b/alacritty/src/config/general.rs @@ -0,0 +1,39 @@ +//! Miscellaneous configuration options. + +use std::path::PathBuf; + +use alacritty_config_derive::ConfigDeserialize; + +/// General config section. +/// +/// This section is for fields which can not be easily categorized, +/// to avoid common TOML issues with root-level fields. +#[derive(ConfigDeserialize, Clone, PartialEq, Debug)] +pub struct General { + /// Configuration file imports. + /// + /// This is never read since the field is directly accessed through the config's + /// [`toml::Value`], but still present to prevent unused field warnings. + pub import: Vec<String>, + + /// Shell startup directory. + pub working_directory: Option<PathBuf>, + + /// Live config reload. + pub live_config_reload: bool, + + /// Offer IPC through a unix socket. + #[allow(unused)] + pub ipc_socket: bool, +} + +impl Default for General { + fn default() -> Self { + Self { + live_config_reload: true, + ipc_socket: true, + working_directory: Default::default(), + import: Default::default(), + } + } +} diff --git a/alacritty/src/config/mod.rs b/alacritty/src/config/mod.rs index f8fccb13..ba9d674d 100644 --- a/alacritty/src/config/mod.rs +++ b/alacritty/src/config/mod.rs @@ -15,6 +15,7 @@ pub mod color; pub mod cursor; pub mod debug; pub mod font; +pub mod general; pub mod monitor; pub mod scrolling; pub mod selection; @@ -278,15 +279,15 @@ fn load_imports( merged } -// TODO: Merge back with `load_imports` once `alacritty migrate` is dropped. -// /// Get all import paths for a configuration. pub fn imports( config: &Value, base_path: &Path, recursion_limit: usize, ) -> StdResult<Vec<StdResult<PathBuf, String>>, String> { - let imports = match config.get("import") { + let imports = + config.get("import").or_else(|| config.get("general").and_then(|g| g.get("import"))); + let imports = match imports { Some(Value::Array(imports)) => imports, Some(_) => return Err("Invalid import type: expected a sequence".into()), None => return Ok(Vec::new()), @@ -300,7 +301,7 @@ pub fn imports( let mut import_paths = Vec::new(); for import in imports { - let mut path = match import { + let path = match import { Value::String(path) => PathBuf::from(path), _ => { import_paths.push(Err("Invalid import element type: expected path string".into())); @@ -308,23 +309,32 @@ pub fn imports( }, }; - // Resolve paths relative to user's home directory. - if let (Ok(stripped), Some(home_dir)) = (path.strip_prefix("~/"), home::home_dir()) { - path = home_dir.join(stripped); - } - - if path.is_relative() { - if let Some(base_path) = base_path.parent() { - path = base_path.join(path) - } - } + let normalized = normalize_import(base_path, path); - import_paths.push(Ok(path)); + import_paths.push(Ok(normalized)); } Ok(import_paths) } +/// Normalize import paths. +pub fn normalize_import(base_config_path: &Path, import_path: impl Into<PathBuf>) -> PathBuf { + let mut import_path = import_path.into(); + + // Resolve paths relative to user's home directory. + if let (Ok(stripped), Some(home_dir)) = (import_path.strip_prefix("~/"), home::home_dir()) { + import_path = home_dir.join(stripped); + } + + if import_path.is_relative() { + if let Some(base_config_dir) = base_config_path.parent() { + import_path = base_config_dir.join(import_path) + } + } + + import_path +} + /// Prune the nulls from the YAML to ensure TOML compatibility. fn prune_yaml_nulls(value: &mut serde_yaml::Value, warn_pruned: bool) { fn walk(value: &mut serde_yaml::Value, warn_pruned: bool) -> bool { diff --git a/alacritty/src/config/terminal.rs b/alacritty/src/config/terminal.rs index b41af5db..d0c0d9da 100644 --- a/alacritty/src/config/terminal.rs +++ b/alacritty/src/config/terminal.rs @@ -4,12 +4,14 @@ use toml::Value; use alacritty_config_derive::{ConfigDeserialize, SerdeReplace}; use alacritty_terminal::term::Osc52; -use crate::config::ui_config::StringVisitor; +use crate::config::ui_config::{Program, StringVisitor}; -#[derive(ConfigDeserialize, Default, Copy, Clone, Debug, PartialEq)] +#[derive(ConfigDeserialize, Default, Clone, Debug, PartialEq)] pub struct Terminal { /// OSC52 support mode. pub osc52: SerdeOsc52, + /// Path to a shell program to run on startup. + pub shell: Option<Program>, } #[derive(SerdeReplace, Default, Copy, Clone, Debug, PartialEq)] diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs index a40dcaf8..69716dee 100644 --- a/alacritty/src/config/ui_config.rs +++ b/alacritty/src/config/ui_config.rs @@ -26,7 +26,8 @@ use crate::config::color::Colors; use crate::config::cursor::Cursor; use crate::config::debug::Debug; use crate::config::font::Font; -use crate::config::mouse::{Mouse, MouseBindings}; +use crate::config::general::General; +use crate::config::mouse::Mouse; use crate::config::scrolling::Scrolling; use crate::config::selection::Selection; use crate::config::terminal::Terminal; @@ -38,8 +39,11 @@ use crate::config::LOG_TARGET_CONFIG; const URL_REGEX: &str = "(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file:|git://|ssh:|ftp://)\ [^\u{0000}-\u{001F}\u{007F}-\u{009F}<>\"\\s{-}\\^⟨⟩`]+"; -#[derive(ConfigDeserialize, Clone, Debug, PartialEq)] +#[derive(ConfigDeserialize, Default, Clone, Debug, PartialEq)] pub struct UiConfig { + /// Miscellaneous configuration options. + pub general: General, + /// Extra environment variables. pub env: HashMap<String, String>, @@ -64,14 +68,6 @@ pub struct UiConfig { /// Debug options. pub debug: Debug, - /// Send escape sequences using the alt key. - #[config(removed = "It's now always set to 'true'. If you're on macOS use \ - 'window.option_as_alt' to alter behavior of Option")] - pub alt_send_esc: Option<bool>, - - /// Live config reload. - pub live_config_reload: bool, - /// Bell configuration. pub bell: BellConfig, @@ -85,70 +81,35 @@ pub struct UiConfig { /// Regex hints for interacting with terminal content. pub hints: Hints, - /// Offer IPC through a unix socket. - #[cfg(unix)] - pub ipc_socket: bool, - /// Config for the alacritty_terminal itself. pub terminal: Terminal, - /// Path to a shell program to run on startup. - pub shell: Option<Program>, - - /// Shell startup directory. - pub working_directory: Option<PathBuf>, - /// Keyboard configuration. keyboard: Keyboard, - /// Should draw bold text with brighter colors instead of bold font. - #[config(deprecated = "use colors.draw_bold_text_with_bright_colors instead")] - draw_bold_text_with_bright_colors: bool, - - /// Keybindings. - #[config(deprecated = "use keyboard.bindings instead")] - key_bindings: Option<KeyBindings>, - - /// Bindings for the mouse. - #[config(deprecated = "use mouse.bindings instead")] - mouse_bindings: Option<MouseBindings>, + /// Path to a shell program to run on startup. + #[config(deprecated = "use terminal.shell instead")] + shell: Option<Program>, /// Configuration file imports. /// /// This is never read since the field is directly accessed through the config's /// [`toml::Value`], but still present to prevent unused field warnings. - import: Vec<String>, -} + #[config(deprecated = "use general.import instead")] + import: Option<Vec<String>>, -impl Default for UiConfig { - fn default() -> Self { - Self { - live_config_reload: true, - #[cfg(unix)] - ipc_socket: true, - draw_bold_text_with_bright_colors: Default::default(), - working_directory: Default::default(), - mouse_bindings: Default::default(), - config_paths: Default::default(), - key_bindings: Default::default(), - alt_send_esc: Default::default(), - scrolling: Default::default(), - selection: Default::default(), - keyboard: Default::default(), - terminal: Default::default(), - import: Default::default(), - cursor: Default::default(), - window: Default::default(), - colors: Default::default(), - shell: Default::default(), - mouse: Default::default(), - debug: Default::default(), - hints: Default::default(), - font: Default::default(), - bell: Default::default(), - env: Default::default(), - } - } + /// Shell startup directory. + #[config(deprecated = "use general.working_directory instead")] + working_directory: Option<PathBuf>, + + /// Live config reload. + #[config(deprecated = "use general.live_config_reload instead")] + live_config_reload: Option<bool>, + + /// Offer IPC through a unix socket. + #[cfg(unix)] + #[config(deprecated = "use general.ipc_socket instead")] + pub ipc_socket: Option<bool>, } impl UiConfig { @@ -166,26 +127,14 @@ impl UiConfig { /// Derive [`PtyOptions`] from the config. pub fn pty_config(&self) -> PtyOptions { - let shell = self.shell.clone().map(Into::into); - PtyOptions { - shell, - working_directory: self.working_directory.clone(), - hold: false, - env: HashMap::new(), - } + let shell = self.terminal.shell.clone().or_else(|| self.shell.clone()).map(Into::into); + let working_directory = + self.working_directory.clone().or_else(|| self.general.working_directory.clone()); + PtyOptions { working_directory, shell, hold: false, env: HashMap::new() } } /// Generate key bindings for all keyboard hints. pub fn generate_hint_bindings(&mut self) { - // Check which key bindings is most likely to be the user's configuration. - // - // Both will be non-empty due to the presence of the default keybindings. - let key_bindings = if let Some(key_bindings) = self.key_bindings.as_mut() { - &mut key_bindings.0 - } else { - &mut self.keyboard.bindings.0 - }; - for hint in &self.hints.enabled { let binding = match &hint.binding { Some(binding) => binding, @@ -200,7 +149,7 @@ impl UiConfig { action: Action::Hint(hint.clone()), }; - key_bindings.push(binding); + self.keyboard.bindings.0.push(binding); } } @@ -211,25 +160,23 @@ impl UiConfig { #[inline] pub fn key_bindings(&self) -> &[KeyBinding] { - if let Some(key_bindings) = self.key_bindings.as_ref() { - &key_bindings.0 - } else { - &self.keyboard.bindings.0 - } + &self.keyboard.bindings.0 } #[inline] pub fn mouse_bindings(&self) -> &[MouseBinding] { - if let Some(mouse_bindings) = self.mouse_bindings.as_ref() { - &mouse_bindings.0 - } else { - &self.mouse.bindings.0 - } + &self.mouse.bindings.0 } #[inline] - pub fn draw_bold_text_with_bright_colors(&self) -> bool { - self.colors.draw_bold_text_with_bright_colors || self.draw_bold_text_with_bright_colors + pub fn live_config_reload(&self) -> bool { + self.live_config_reload.unwrap_or(self.general.live_config_reload) + } + + #[cfg(unix)] + #[inline] + pub fn ipc_socket(&self) -> bool { + self.ipc_socket.unwrap_or(self.general.ipc_socket) } } @@ -335,7 +282,7 @@ impl Default for Hints { mouse: Some(HintMouse { enabled: true, mods: Default::default() }), binding: Some(HintBinding { key: BindingKey::Keycode { - key: Key::Character("u".into()), + key: Key::Character("o".into()), location: KeyLocation::Standard, }, mods: ModsWrapper(ModifiersState::SHIFT | ModifiersState::CONTROL), diff --git a/alacritty/src/display/content.rs b/alacritty/src/display/content.rs index f36f71b5..aad25b10 100644 --- a/alacritty/src/display/content.rs +++ b/alacritty/src/display/content.rs @@ -357,7 +357,7 @@ impl RenderableCell { _ => rgb.into(), }, Color::Named(ansi) => { - match (config.draw_bold_text_with_bright_colors(), flags & Flags::DIM_BOLD) { + match (config.colors.draw_bold_text_with_bright_colors, flags & Flags::DIM_BOLD) { // If no bright foreground is set, treat it like the BOLD flag doesn't exist. (_, Flags::DIM_BOLD) if ansi == NamedColor::Foreground @@ -377,7 +377,7 @@ impl RenderableCell { }, Color::Indexed(idx) => { let idx = match ( - config.draw_bold_text_with_bright_colors(), + config.colors.draw_bold_text_with_bright_colors, flags & Flags::DIM_BOLD, idx, ) { diff --git a/alacritty/src/display/damage.rs b/alacritty/src/display/damage.rs index 450643b7..8efe0133 100644 --- a/alacritty/src/display/damage.rs +++ b/alacritty/src/display/damage.rs @@ -189,6 +189,15 @@ impl FrameDamage { self.lines.push(LineDamageBounds::undamaged(line, num_cols)); } } + + /// Check if a range is damaged. + #[inline] + pub fn intersects(&self, start: Point<usize>, end: Point<usize>) -> bool { + self.full + || self.lines[start.line].left <= start.column + || self.lines[end.line].right >= end.column + || (start.line + 1..end.line).any(|line| self.lines[line].is_damaged()) + } } /// Convert viewport `y` coordinate to [`Rect`] damage coordinate. diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs index 25c004a9..76f0eba2 100644 --- a/alacritty/src/display/mod.rs +++ b/alacritty/src/display/mod.rs @@ -29,8 +29,7 @@ use alacritty_terminal::index::{Column, Direction, Line, Point}; use alacritty_terminal::selection::Selection; use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::{ - self, point_to_viewport, LineDamageBounds, Term, TermDamage, TermMode, MIN_COLUMNS, - MIN_SCREEN_LINES, + self, LineDamageBounds, Term, TermDamage, TermMode, MIN_COLUMNS, MIN_SCREEN_LINES, }; use alacritty_terminal::vte::ansi::{CursorShape, NamedColor}; @@ -756,41 +755,39 @@ impl Display { let vi_cursor_point = if vi_mode { Some(terminal.vi_mode_cursor.point) } else { None }; // Add damage from the terminal. - if self.collect_damage() { - match terminal.damage() { - TermDamage::Full => self.damage_tracker.frame().mark_fully_damaged(), - TermDamage::Partial(damaged_lines) => { - for damage in damaged_lines { - self.damage_tracker.frame().damage_line(damage); - } - }, - } - terminal.reset_damage(); + match terminal.damage() { + TermDamage::Full => self.damage_tracker.frame().mark_fully_damaged(), + TermDamage::Partial(damaged_lines) => { + for damage in damaged_lines { + self.damage_tracker.frame().damage_line(damage); + } + }, } + terminal.reset_damage(); let graphics_queues = terminal.graphics_take_queues(); // Drop terminal as early as possible to free lock. drop(terminal); - // Add damage from alacritty's UI elements overlapping terminal. - if self.collect_damage() { - let requires_full_damage = self.visual_bell.intensity() != 0. - || self.hint_state.active() - || search_state.regex().is_some(); - - if requires_full_damage { - self.damage_tracker.frame().mark_fully_damaged(); - self.damage_tracker.next_frame().mark_fully_damaged(); - } + // Invalidate highlighted hints if grid has changed. + self.validate_hints(display_offset); - let vi_cursor_viewport_point = - vi_cursor_point.and_then(|cursor| point_to_viewport(display_offset, cursor)); + // Add damage from alacritty's UI elements overlapping terminal. - self.damage_tracker.damage_vi_cursor(vi_cursor_viewport_point); - self.damage_tracker.damage_selection(selection_range, display_offset); + let requires_full_damage = self.visual_bell.intensity() != 0. + || self.hint_state.active() + || search_state.regex().is_some(); + if requires_full_damage { + self.damage_tracker.frame().mark_fully_damaged(); + self.damage_tracker.next_frame().mark_fully_damaged(); } + let vi_cursor_viewport_point = + vi_cursor_point.and_then(|cursor| term::point_to_viewport(display_offset, cursor)); + self.damage_tracker.damage_vi_cursor(vi_cursor_viewport_point); + self.damage_tracker.damage_selection(selection_range, display_offset); + // Make sure this window's OpenGL context is active. self.make_current(); @@ -819,42 +816,33 @@ impl Display { let vi_highlighted_hint = &self.vi_highlighted_hint; let damage_tracker = &mut self.damage_tracker; - self.renderer.draw_cells( - &size_info, - glyph_cache, - grid_cells.into_iter().map(|mut cell| { - // Underline hints hovered by mouse or vi mode cursor. - let point = term::viewport_to_point(display_offset, cell.point); + let cells = grid_cells.into_iter().map(|mut cell| { + let mut show_hint = false; - let mut show_hint = false; - - if has_highlighted_hint { - let hyperlink = - cell.extra.as_ref().and_then(|extra| extra.hyperlink.as_ref()); - if highlighted_hint - .as_ref() - .map_or(false, |hint| hint.should_highlight(point, hyperlink)) - || vi_highlighted_hint - .as_ref() - .map_or(false, |hint| hint.should_highlight(point, hyperlink)) - { - show_hint = true; - cell.flags.insert(Flags::UNDERLINE); - // Damage hints for the current and next frames. - damage_tracker.frame().damage_point(cell.point); - damage_tracker.next_frame().damage_point(cell.point); - } + // Underline hints hovered by mouse or vi mode cursor. + if has_highlighted_hint { + let point = term::viewport_to_point(display_offset, cell.point); + let hyperlink = cell.extra.as_ref().and_then(|extra| extra.hyperlink.as_ref()); + + let should_highlight = |hint: &Option<HintMatch>| { + hint.as_ref().map_or(false, |hint| hint.should_highlight(point, hyperlink)) + }; + if should_highlight(highlighted_hint) || should_highlight(vi_highlighted_hint) { + show_hint = true; + damage_tracker.frame().damage_point(cell.point); + cell.flags.insert(Flags::UNDERLINE); } + } - // Update underline/strikeout. - lines.update(&cell); + // Update underline/strikeout. + lines.update(&cell); - // Track any graphic present in the cell. - graphics_list.update(&cell, show_hint); + // Track any graphic present in the cell. + graphics_list.update(&cell, show_hint); - cell - }), - ); + cell + }); + self.renderer.draw_cells(&size_info, glyph_cache, cells); } let mut rects = lines.rects(&metrics, &size_info); @@ -1050,10 +1038,17 @@ impl Display { let mut dirty = vi_highlighted_hint != self.vi_highlighted_hint; self.vi_highlighted_hint = vi_highlighted_hint; + // Force full redraw if the vi mode highlight was cleared. + if dirty && self.vi_highlighted_hint.is_none() { + self.damage_tracker.frame().mark_fully_damaged(); + } + // Abort if mouse highlighting conditions are not met. if !mouse.inside_text_area || !term.selection.as_ref().map_or(true, Selection::is_empty) { - dirty |= self.highlighted_hint.is_some(); - self.highlighted_hint = None; + if self.highlighted_hint.take().is_some() { + self.damage_tracker.frame().mark_fully_damaged(); + dirty = true; + } return dirty; } @@ -1077,9 +1072,15 @@ impl Display { } } - dirty |= self.highlighted_hint != highlighted_hint; + let mouse_highlight_dirty = self.highlighted_hint != highlighted_hint; + dirty |= mouse_highlight_dirty; self.highlighted_hint = highlighted_hint; + // Force full redraw if the mouse cursor highlight was cleared. + if mouse_highlight_dirty && self.highlighted_hint.is_none() { + self.damage_tracker.frame().mark_fully_damaged(); + } + dirty } @@ -1138,7 +1139,7 @@ impl Display { ); // Damage preedit inside the terminal viewport. - if self.collect_damage() && point.line < self.size_info.screen_lines() { + if point.line < self.size_info.screen_lines() { let damage = LineDamageBounds::new(start.line, 0, num_cols); self.damage_tracker.frame().damage_line(damage); self.damage_tracker.next_frame().damage_line(damage); @@ -1249,13 +1250,11 @@ impl Display { let bg = config.colors.footer_bar_background(); for (uri, point) in uris.into_iter().zip(uri_lines) { // Damage the uri preview. - if self.collect_damage() { - let damage = LineDamageBounds::new(point.line, point.column.0, num_cols); - self.damage_tracker.frame().damage_line(damage); + let damage = LineDamageBounds::new(point.line, point.column.0, num_cols); + self.damage_tracker.frame().damage_line(damage); - // Damage the uri preview for the next frame as well. - self.damage_tracker.next_frame().damage_line(damage); - } + // Damage the uri preview for the next frame as well. + self.damage_tracker.next_frame().damage_line(damage); self.renderer.draw_string(point, fg, bg, uri, &self.size_info, &mut self.glyph_cache); } @@ -1295,12 +1294,10 @@ impl Display { let fg = config.colors.primary.background; let bg = config.colors.normal.red; - if self.collect_damage() { - let damage = LineDamageBounds::new(point.line, point.column.0, timing.len()); - self.damage_tracker.frame().damage_line(damage); - // Damage the render timer for the next frame. - self.damage_tracker.next_frame().damage_line(damage); - } + // Damage render timer for current and next frame. + let damage = LineDamageBounds::new(point.line, point.column.0, timing.len()); + self.damage_tracker.frame().damage_line(damage); + self.damage_tracker.next_frame().damage_line(damage); let glyph_cache = &mut self.glyph_cache; self.renderer.draw_string(point, fg, bg, timing.chars(), &self.size_info, glyph_cache); @@ -1320,12 +1317,10 @@ impl Display { let column = Column(self.size_info.columns().saturating_sub(text.len())); let point = Point::new(0, column); - if self.collect_damage() { - let damage = LineDamageBounds::new(point.line, point.column.0, columns - 1); - self.damage_tracker.frame().damage_line(damage); - // Damage it on the next frame in case it goes away. - self.damage_tracker.next_frame().damage_line(damage); - } + // Damage the line indicator for current and next frame. + let damage = LineDamageBounds::new(point.line, point.column.0, columns - 1); + self.damage_tracker.frame().damage_line(damage); + self.damage_tracker.next_frame().damage_line(damage); let colors = &config.colors; let fg = colors.line_indicator.foreground.unwrap_or(colors.primary.background); @@ -1338,12 +1333,6 @@ impl Display { } } - /// Returns `true` if damage information should be collected, `false` otherwise. - #[inline] - fn collect_damage(&self) -> bool { - matches!(self.raw_window_handle, RawWindowHandle::Wayland(_)) || self.damage_tracker.debug - } - /// Highlight damaged rects. /// /// This function is for debug purposes only. @@ -1359,6 +1348,34 @@ impl Display { } } + /// Check whether a hint highlight needs to be cleared. + fn validate_hints(&mut self, display_offset: usize) { + let frame = self.damage_tracker.frame(); + for (hint, reset_mouse) in + [(&mut self.highlighted_hint, true), (&mut self.vi_highlighted_hint, false)] + { + let (start, end) = match hint { + Some(hint) => (*hint.bounds().start(), *hint.bounds().end()), + None => return, + }; + + // Convert hint bounds to viewport coordinates. + let start = term::point_to_viewport(display_offset, start).unwrap_or_default(); + let end = term::point_to_viewport(display_offset, end).unwrap_or_else(|| { + Point::new(self.size_info.screen_lines() - 1, self.size_info.last_column()) + }); + + // Clear invalidated hints. + if frame.intersects(start, end) { + if reset_mouse { + self.window.set_mouse_cursor(CursorIcon::Default); + } + frame.mark_fully_damaged(); + *hint = None; + } + } + } + /// Request a new frame for a window on Wayland. fn request_frame(&mut self, scheduler: &mut Scheduler) { // Mark that we've used a frame. diff --git a/alacritty/src/display/window.rs b/alacritty/src/display/window.rs index 2bb59b2c..1427dc75 100644 --- a/alacritty/src/display/window.rs +++ b/alacritty/src/display/window.rs @@ -30,7 +30,7 @@ use winit::dpi::{PhysicalPosition, PhysicalSize}; use winit::event_loop::ActiveEventLoop; use winit::monitor::MonitorHandle; #[cfg(windows)] -use winit::platform::windows::IconExtWindows; +use winit::platform::windows::{IconExtWindows, WindowAttributesExtWindows}; use winit::raw_window_handle::{HasWindowHandle, RawWindowHandle}; use winit::window::{ CursorIcon, Fullscreen, ImePurpose, Theme, UserAttentionType, Window as WinitWindow, @@ -302,7 +302,8 @@ impl Window { WinitWindow::default_attributes() .with_decorations(window_config.decorations != Decorations::None) - .with_window_icon(icon.ok()) + .with_window_icon(icon.as_ref().ok().cloned()) + .with_taskbar_icon(icon.ok()) } #[cfg(target_os = "macos")] diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index 72009d88..b4600ae3 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -34,6 +34,7 @@ use alacritty_terminal::index::{Boundary, Column, Direction, Line, Point, Side}; use alacritty_terminal::selection::{Selection, SelectionType}; use alacritty_terminal::term::search::{Match, RegexSearch}; use alacritty_terminal::term::{self, ClipboardType, Term, TermMode}; +use alacritty_terminal::vte::ansi::NamedColor; #[cfg(unix)] use crate::cli::{IpcConfig, ParsedOptions}; @@ -109,7 +110,7 @@ impl Processor { // The monitor watches the config file for changes and reloads it. Pending // config changes are processed in the main loop. let mut config_monitor = None; - if config.live_config_reload { + if config.live_config_reload() { config_monitor = ConfigMonitor::new(config.config_paths.clone(), event_loop.create_proxy()); } @@ -1737,9 +1738,12 @@ impl input::Processor<EventProxy, ActionContext<'_, Notifier, EventProxy>> { } }, TerminalEvent::ColorRequest(index, format) => { - let color = self.ctx.terminal().colors()[index] - .map(Rgb) - .unwrap_or(self.ctx.display.colors[index]); + let color = match self.ctx.terminal().colors()[index] { + Some(color) => Rgb(color), + // Ignore cursor color requests unless it was changed. + None if index == NamedColor::Cursor as usize => return, + None => self.ctx.display.colors[index], + }; self.ctx.write_to_pty(format(color.0).into_bytes()); }, TerminalEvent::TextAreaSizeRequest(format) => { diff --git a/alacritty/src/logging.rs b/alacritty/src/logging.rs index 08e79469..67cad63e 100644 --- a/alacritty/src/logging.rs +++ b/alacritty/src/logging.rs @@ -113,11 +113,11 @@ impl Logger { let env_var = format!("%{}%", ALACRITTY_LOG_ENV); let message = format!( - "[{}] See log at {} ({}):\n{}", + "[{}] {}\nSee log at {} ({})", record.level(), + record.args(), logfile_path, env_var, - record.args(), ); let mut message = Message::new(message, message_type); diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs index da50c3e4..6bbf8dfd 100644 --- a/alacritty/src/main.rs +++ b/alacritty/src/main.rs @@ -174,7 +174,7 @@ fn alacritty(mut options: Options) -> Result<(), Box<dyn Error>> { // Create the IPC socket listener. #[cfg(unix)] - let socket_path = if config.ipc_socket { + let socket_path = if config.ipc_socket() { ipc::spawn_ipc_socket(&options, window_event_loop.create_proxy()) } else { None diff --git a/alacritty/src/migrate.rs b/alacritty/src/migrate.rs deleted file mode 100644 index 6d116858..00000000 --- a/alacritty/src/migrate.rs +++ /dev/null @@ -1,267 +0,0 @@ -//! Configuration file migration. - -use std::fs; -use std::path::Path; - -use toml::map::Entry; -use toml::{Table, Value}; - -use crate::cli::MigrateOptions; -use crate::config; - -/// Handle migration. -pub fn migrate(options: MigrateOptions) { - // Find configuration file path. - let config_path = options - .config_file - .clone() - .or_else(|| config::installed_config("toml")) - .or_else(|| config::installed_config("yml")); - - // Abort if system has no installed configuration. - let config_path = match config_path { - Some(config_path) => config_path, - None => { - eprintln!("No configuration file found"); - std::process::exit(1); - }, - }; - - // If we're doing a wet run, perform a dry run first for safety. - if !options.dry_run { - #[allow(clippy::redundant_clone)] - let mut options = options.clone(); - options.silent = true; - options.dry_run = true; - if let Err(err) = migrate_config(&options, &config_path, config::IMPORT_RECURSION_LIMIT) { - eprintln!("Configuration file migration failed:"); - eprintln!(" {config_path:?}: {err}"); - std::process::exit(1); - } - } - - // Migrate the root config. - match migrate_config(&options, &config_path, config::IMPORT_RECURSION_LIMIT) { - Ok(new_path) => { - if !options.silent { - println!("Successfully migrated {config_path:?} to {new_path:?}"); - } - }, - Err(err) => { - eprintln!("Configuration file migration failed:"); - eprintln!(" {config_path:?}: {err}"); - std::process::exit(1); - }, - } -} - -/// Migrate a specific configuration file. -fn migrate_config( - options: &MigrateOptions, - path: &Path, - recursion_limit: usize, -) -> Result<String, String> { - // Ensure configuration file has an extension. - let path_str = path.to_string_lossy(); - let (prefix, suffix) = match path_str.rsplit_once('.') { - Some((prefix, suffix)) => (prefix, suffix), - None => return Err("missing file extension".to_string()), - }; - - // Abort if config is already toml. - if suffix == "toml" { - return Err("already in TOML format".to_string()); - } - - // Try to parse the configuration file. - let mut config = match config::deserialize_config(path, !options.dry_run) { - Ok(config) => config, - Err(err) => return Err(format!("parsing error: {err}")), - }; - - // Migrate config imports. - if !options.skip_imports { - migrate_imports(options, &mut config, path, recursion_limit)?; - } - - // Migrate deprecated field names to their new location. - if !options.skip_renames { - migrate_renames(&mut config)?; - } - - // Convert to TOML format. - let toml = toml::to_string(&config).map_err(|err| format!("conversion error: {err}"))?; - let new_path = format!("{prefix}.toml"); - - if options.dry_run && !options.silent { - // Output new content to STDOUT. - println!( - "\nv-----Start TOML for {path:?}-----v\n\n{toml}\n^-----End TOML for {path:?}-----^\n" - ); - } else if !options.dry_run { - // Write the new toml configuration. - fs::write(&new_path, toml).map_err(|err| format!("filesystem error: {err}"))?; - } - - Ok(new_path) -} - -/// Migrate the imports of a config. -fn migrate_imports( - options: &MigrateOptions, - config: &mut Value, - base_path: &Path, - recursion_limit: usize, -) -> Result<(), String> { - let imports = match config::imports(config, base_path, recursion_limit) { - Ok(imports) => imports, - Err(err) => return Err(format!("import error: {err}")), - }; - - // Migrate the individual imports. - let mut new_imports = Vec::new(); - for import in imports { - let import = match import { - Ok(import) => import, - Err(err) => return Err(format!("import error: {err}")), - }; - - // Keep yaml import if path does not exist. - if !import.exists() { - if options.dry_run { - eprintln!("Keeping yaml config for nonexistent import: {import:?}"); - } - new_imports.push(Value::String(import.to_string_lossy().into())); - continue; - } - - let new_path = migrate_config(options, &import, recursion_limit - 1)?; - - // Print new import path. - if options.dry_run { - println!("Successfully migrated import {import:?} to {new_path:?}"); - } - - new_imports.push(Value::String(new_path)); - } - - // Update the imports field. - if let Some(import) = config.get_mut("import") { - *import = Value::Array(new_imports); - } - - Ok(()) -} - -/// Migrate deprecated fields. -fn migrate_renames(config: &mut Value) -> Result<(), String> { - let config_table = match config.as_table_mut() { - Some(config_table) => config_table, - None => return Ok(()), - }; - - // draw_bold_text_with_bright_colors -> colors.draw_bold_text_with_bright_colors - move_value(config_table, &["draw_bold_text_with_bright_colors"], &[ - "colors", - "draw_bold_text_with_bright_colors", - ])?; - - // key_bindings -> keyboard.bindings - move_value(config_table, &["key_bindings"], &["keyboard", "bindings"])?; - - // mouse_bindings -> mouse.bindings - move_value(config_table, &["mouse_bindings"], &["mouse", "bindings"])?; - - Ok(()) -} - -/// Move a toml value from one map to another. -fn move_value(config_table: &mut Table, origin: &[&str], target: &[&str]) -> Result<(), String> { - if let Some(value) = remove_node(config_table, origin)? { - if !insert_node_if_empty(config_table, target, value)? { - return Err(format!( - "conflict: both `{}` and `{}` are set", - origin.join("."), - target.join(".") - )); - } - } - - Ok(()) -} - -/// Remove a node from a tree of tables. -fn remove_node(table: &mut Table, path: &[&str]) -> Result<Option<Value>, String> { - if path.len() == 1 { - Ok(table.remove(path[0])) - } else { - let next_table_value = match table.get_mut(path[0]) { - Some(next_table_value) => next_table_value, - None => return Ok(None), - }; - - let next_table = match next_table_value.as_table_mut() { - Some(next_table) => next_table, - None => return Err(format!("invalid `{}` table", path[0])), - }; - - remove_node(next_table, &path[1..]) - } -} - -/// Try to insert a node into a tree of tables. -/// -/// Returns `false` if the node already exists. -fn insert_node_if_empty(table: &mut Table, path: &[&str], node: Value) -> Result<bool, String> { - if path.len() == 1 { - match table.entry(path[0]) { - Entry::Vacant(vacant_entry) => { - vacant_entry.insert(node); - Ok(true) - }, - Entry::Occupied(_) => Ok(false), - } - } else { - let next_table_value = table.entry(path[0]).or_insert_with(|| Value::Table(Table::new())); - - let next_table = match next_table_value.as_table_mut() { - Some(next_table) => next_table, - None => return Err(format!("invalid `{}` table", path[0])), - }; - - insert_node_if_empty(next_table, &path[1..], node) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn move_values() { - let input = r#" -root_value = 3 - -[table] -table_value = 5 - -[preexisting] -not_moved = 9 - "#; - - let mut value: Value = toml::from_str(input).unwrap(); - let table = value.as_table_mut().unwrap(); - - move_value(table, &["root_value"], &["new_table", "root_value"]).unwrap(); - move_value(table, &["table", "table_value"], &["preexisting", "subtable", "new_name"]) - .unwrap(); - - let output = toml::to_string(table).unwrap(); - - assert_eq!( - output, - "[new_table]\nroot_value = 3\n\n[preexisting]\nnot_moved = \ - 9\n\n[preexisting.subtable]\nnew_name = 5\n\n[table]\n" - ); - } -} diff --git a/alacritty/src/migrate/mod.rs b/alacritty/src/migrate/mod.rs new file mode 100644 index 00000000..58f381de --- /dev/null +++ b/alacritty/src/migrate/mod.rs @@ -0,0 +1,327 @@ +//! Configuration file migration. + +use std::fmt::Debug; +use std::path::Path; +use std::{fs, mem}; + +use tempfile::NamedTempFile; +use toml_edit::{DocumentMut, Item}; + +use crate::cli::MigrateOptions; +use crate::config; + +mod yaml; + +/// Handle migration. +pub fn migrate(options: MigrateOptions) { + // Find configuration file path. + let config_path = options + .config_file + .clone() + .or_else(|| config::installed_config("toml")) + .or_else(|| config::installed_config("yml")); + + // Abort if system has no installed configuration. + let config_path = match config_path { + Some(config_path) => config_path, + None => { + eprintln!("No configuration file found"); + std::process::exit(1); + }, + }; + + // If we're doing a wet run, perform a dry run first for safety. + if !options.dry_run { + #[allow(clippy::redundant_clone)] + let mut options = options.clone(); + options.silent = true; + options.dry_run = true; + if let Err(err) = migrate_config(&options, &config_path, config::IMPORT_RECURSION_LIMIT) { + eprintln!("Configuration file migration failed:"); + eprintln!(" {config_path:?}: {err}"); + std::process::exit(1); + } + } + + // Migrate the root config. + match migrate_config(&options, &config_path, config::IMPORT_RECURSION_LIMIT) { + Ok(migration) => { + if !options.silent { + println!("{}", migration.success_message(false)); + } + }, + Err(err) => { + eprintln!("Configuration file migration failed:"); + eprintln!(" {config_path:?}: {err}"); + std::process::exit(1); + }, + } +} + +/// Migrate a specific configuration file. +fn migrate_config<'a>( + options: &MigrateOptions, + path: &'a Path, + recursion_limit: usize, +) -> Result<Migration<'a>, String> { + // Ensure configuration file has an extension. + let path_str = path.to_string_lossy(); + let (prefix, suffix) = match path_str.rsplit_once('.') { + Some((prefix, suffix)) => (prefix, suffix), + None => return Err("missing file extension".to_string()), + }; + + // Handle legacy YAML files. + if suffix == "yml" { + let new_path = yaml::migrate(options, path, recursion_limit, prefix)?; + return Ok(Migration::Yaml((path, new_path))); + } + + // TOML only does renames, so return early if they are disabled. + if options.skip_renames { + if options.dry_run { + eprintln!("Ignoring TOML file {path:?} since `--skip-renames` was supplied"); + } + return Ok(Migration::Toml(path)); + } + + // Read TOML file and perform all in-file migrations. + let toml = fs::read_to_string(path).map_err(|err| format!("{err}"))?; + let mut migrated = migrate_toml(toml)?; + + // Recursively migrate imports. + migrate_imports(options, path, &mut migrated, recursion_limit)?; + + // Write migrated TOML file. + write_results(options, path, &migrated.to_string())?; + + Ok(Migration::Toml(path)) +} + +/// Migrate TOML config to the latest version. +fn migrate_toml(toml: String) -> Result<DocumentMut, String> { + // Parse TOML file. + let mut document = match toml.parse::<DocumentMut>() { + Ok(document) => document, + Err(err) => return Err(format!("TOML parsing error: {err}")), + }; + + // Move `draw_bold_text_with_bright_colors` to its own section. + move_value(&mut document, &["draw_bold_text_with_bright_colors"], &[ + "colors", + "draw_bold_text_with_bright_colors", + ])?; + + // Move bindings to their own section. + move_value(&mut document, &["key_bindings"], &["keyboard", "bindings"])?; + move_value(&mut document, &["mouse_bindings"], &["mouse", "bindings"])?; + + // Avoid warnings due to introduction of the new `general` section. + move_value(&mut document, &["live_config_reload"], &["general", "live_config_reload"])?; + move_value(&mut document, &["working_directory"], &["general", "working_directory"])?; + move_value(&mut document, &["ipc_socket"], &["general", "ipc_socket"])?; + move_value(&mut document, &["import"], &["general", "import"])?; + move_value(&mut document, &["shell"], &["terminal", "shell"])?; + + Ok(document) +} + +/// Migrate TOML imports to the latest version. +fn migrate_imports( + options: &MigrateOptions, + path: &Path, + document: &mut DocumentMut, + recursion_limit: usize, +) -> Result<(), String> { + // Check if any imports need to be processed. + let imports = match document + .get("general") + .and_then(|general| general.get("import")) + .and_then(|import| import.as_array()) + { + Some(array) if !array.is_empty() => array, + _ => return Ok(()), + }; + + // Abort once recursion limit is exceeded. + if recursion_limit == 0 { + return Err("Exceeded maximum configuration import depth".into()); + } + + // Migrate each import. + for import in imports.into_iter().filter_map(|item| item.as_str()) { + let normalized_path = config::normalize_import(path, import); + let migration = migrate_config(options, &normalized_path, recursion_limit)?; + if options.dry_run { + println!("{}", migration.success_message(true)); + } + } + + Ok(()) +} + +/// Move a TOML value from one map to another. +fn move_value(document: &mut DocumentMut, origin: &[&str], target: &[&str]) -> Result<(), String> { + // Find and remove the original item. + let (mut origin_key, mut origin_item) = (None, document.as_item_mut()); + for element in origin { + let table = match origin_item.as_table_like_mut() { + Some(table) => table, + None => panic!("Moving from unsupported TOML structure"), + }; + + let (key, item) = match table.get_key_value_mut(element) { + Some((key, item)) => (key, item), + None => return Ok(()), + }; + + dbg!(&key); + origin_key = Some(key); + origin_item = item; + + // Ensure no empty tables are left behind. + if let Some(table) = origin_item.as_table_mut() { + table.set_implicit(true) + } + } + + let origin_key_decor = + origin_key.map(|key| (key.leaf_decor().clone(), key.dotted_decor().clone())); + let origin_item = mem::replace(origin_item, Item::None); + + // Create all dependencies for the new location. + let mut target_item = document.as_item_mut(); + for (i, element) in target.iter().enumerate() { + let table = match target_item.as_table_like_mut() { + Some(table) => table, + None => panic!("Moving into unsupported TOML structure"), + }; + + if i + 1 == target.len() { + table.insert(element, origin_item); + // Move original key decorations. + if let Some((leaf, dotted)) = origin_key_decor { + let mut key = table.key_mut(element).unwrap(); + *key.leaf_decor_mut() = leaf; + *key.dotted_decor_mut() = dotted; + } + + break; + } else { + // Create missing parent tables. + target_item = target_item[element].or_insert(toml_edit::table()); + } + } + + Ok(()) +} + +/// Write migrated TOML to its target location. +fn write_results<P>(options: &MigrateOptions, path: P, toml: &str) -> Result<(), String> +where + P: AsRef<Path> + Debug, +{ + let path = path.as_ref(); + if options.dry_run && !options.silent { + // Output new content to STDOUT. + println!( + "\nv-----Start TOML for {path:?}-----v\n\n{toml}\n^-----End TOML for {path:?}-----^\n" + ); + } else if !options.dry_run { + // Atomically replace the configuration file. + let tmp = NamedTempFile::new_in(path.parent().unwrap()) + .map_err(|err| format!("could not create temporary file: {err}"))?; + fs::write(tmp.path(), toml).map_err(|err| format!("filesystem error: {err}"))?; + tmp.persist(path).map_err(|err| format!("atomic replacement failed: {err}"))?; + } + Ok(()) +} + +/// Performed migration mode. +enum Migration<'a> { + /// In-place TOML migration. + Toml(&'a Path), + /// YAML to TOML migration. + Yaml((&'a Path, String)), +} + +impl<'a> Migration<'a> { + /// Get the success message for this migration. + fn success_message(&self, import: bool) -> String { + match self { + Self::Yaml((original_path, new_path)) if import => { + format!("Successfully migrated import {original_path:?} to {new_path:?}") + }, + Self::Yaml((original_path, new_path)) => { + format!("Successfully migrated {original_path:?} to {new_path:?}") + }, + Self::Toml(original_path) if import => { + format!("Successfully migrated import {original_path:?}") + }, + Self::Toml(original_path) => format!("Successfully migrated {original_path:?}"), + } + } + + /// Get the file path after migration. + fn new_path(&self) -> String { + match self { + Self::Toml(path) => path.to_string_lossy().into(), + Self::Yaml((_, path)) => path.into(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn move_values() { + let input = r#" +# This is a root_value. +# +# Use it with care. +root_value = 3 + +[table] +table_value = 5 + +[preexisting] +not_moved = 9 + "#; + + let mut document = input.parse::<DocumentMut>().unwrap(); + + move_value(&mut document, &["root_value"], &["new_table", "root_value"]).unwrap(); + move_value(&mut document, &["table", "table_value"], &[ + "preexisting", + "subtable", + "new_name", + ]) + .unwrap(); + + let output = document.to_string(); + + let expected = r#" +[preexisting] +not_moved = 9 + +[preexisting.subtable] +new_name = 5 + +[new_table] + +# This is a root_value. +# +# Use it with care. +root_value = 3 + "#; + + assert_eq!(output, expected); + } + + #[test] + fn migrate_empty() { + assert!(migrate_toml(String::new()).unwrap().to_string().is_empty()); + } +} diff --git a/alacritty/src/migrate/yaml.rs b/alacritty/src/migrate/yaml.rs new file mode 100644 index 00000000..9607e95e --- /dev/null +++ b/alacritty/src/migrate/yaml.rs @@ -0,0 +1,87 @@ +//! Migration of legacy YAML files to TOML. + +use std::path::Path; + +use toml::Value; + +use crate::cli::MigrateOptions; +use crate::config; +use crate::migrate::{migrate_config, migrate_toml, write_results}; + +/// Migrate a legacy YAML config to TOML. +pub fn migrate( + options: &MigrateOptions, + path: &Path, + recursion_limit: usize, + prefix: &str, +) -> Result<String, String> { + // Try to parse the configuration file. + let mut config = match config::deserialize_config(path, !options.dry_run) { + Ok(config) => config, + Err(err) => return Err(format!("YAML parsing error: {err}")), + }; + + // Migrate config imports. + if !options.skip_imports { + migrate_imports(options, &mut config, path, recursion_limit)?; + } + + // Convert to TOML format. + let mut toml = toml::to_string(&config).map_err(|err| format!("conversion error: {err}"))?; + let new_path = format!("{prefix}.toml"); + + // Apply TOML migration, without recursing through imports. + toml = migrate_toml(toml)?.to_string(); + + // Write migrated TOML config. + write_results(options, &new_path, &toml)?; + + Ok(new_path) +} + +/// Migrate the imports of a config. +fn migrate_imports( + options: &MigrateOptions, + config: &mut Value, + base_path: &Path, + recursion_limit: usize, +) -> Result<(), String> { + let imports = match config::imports(config, base_path, recursion_limit) { + Ok(imports) => imports, + Err(err) => return Err(format!("import error: {err}")), + }; + + // Migrate the individual imports. + let mut new_imports = Vec::new(); + for import in imports { + let import = match import { + Ok(import) => import, + Err(err) => return Err(format!("import error: {err}")), + }; + + // Keep yaml import if path does not exist. + if !import.exists() { + if options.dry_run { + eprintln!("Keeping yaml config for nonexistent import: {import:?}"); + } + new_imports.push(Value::String(import.to_string_lossy().into())); + continue; + } + + let migration = migrate_config(options, &import, recursion_limit - 1)?; + + // Print success message. + if options.dry_run { + println!("{}", migration.success_message(true)); + } + + new_imports.push(Value::String(migration.new_path())); + } + + // Update the imports field. + if let Some(import) = config.get_mut("import") { + *import = Value::Array(new_imports); + } + + Ok(()) +} diff --git a/alacritty_config_derive/src/config_deserialize/de_struct.rs b/alacritty_config_derive/src/config_deserialize/de_struct.rs index d2a7dd82..ad38863e 100644 --- a/alacritty_config_derive/src/config_deserialize/de_struct.rs +++ b/alacritty_config_derive/src/config_deserialize/de_struct.rs @@ -155,6 +155,7 @@ fn field_deserializer(field_streams: &mut FieldStreams, field: &Field) -> Result if let Some(warning) = parsed.param { message = format!("{}; {}", message, warning.value()); } + message.push_str("\nUse `alacritty migrate` to automatically resolve it"); // Append stream to log deprecation/removal warning. match_assignment_stream.extend(quote! { diff --git a/alacritty_config_derive/tests/config.rs b/alacritty_config_derive/tests/config.rs index 27a968ed..be140cbe 100644 --- a/alacritty_config_derive/tests/config.rs +++ b/alacritty_config_derive/tests/config.rs @@ -1,4 +1,4 @@ -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, OnceLock}; use log::{Level, Log, Metadata, Record}; use serde::Deserialize; @@ -83,10 +83,8 @@ struct NewType(usize); #[test] fn config_deserialize() { - let logger = unsafe { - LOGGER = Some(Logger::default()); - LOGGER.as_mut().unwrap() - }; + static LOGGER: OnceLock<Logger> = OnceLock::new(); + let logger = LOGGER.get_or_init(Logger::default); log::set_logger(logger).unwrap(); log::set_max_level(log::LevelFilter::Warn); @@ -134,15 +132,16 @@ fn config_deserialize() { ]); let warn_logs = logger.warn_logs.lock().unwrap(); assert_eq!(warn_logs.as_slice(), [ - "Config warning: field1 has been deprecated; use field2 instead", - "Config warning: enom_error has been deprecated", - "Config warning: gone has been removed; it's gone", + "Config warning: field1 has been deprecated; use field2 instead\nUse `alacritty migrate` \ + to automatically resolve it", + "Config warning: enom_error has been deprecated\nUse `alacritty migrate` to automatically \ + resolve it", + "Config warning: gone has been removed; it's gone\nUse `alacritty migrate` to \ + automatically resolve it", "Unused config key: field3", ]); } -static mut LOGGER: Option<Logger> = None; - /// Logger storing all messages for later validation. #[derive(Default)] struct Logger { diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml index 3b1c89e5..b0463f1b 100644 --- a/alacritty_terminal/Cargo.toml +++ b/alacritty_terminal/Cargo.toml @@ -23,8 +23,8 @@ log = "0.4" parking_lot = "0.12.0" polling = "3.0.0" regex-automata = "0.4.3" -unicode-width = "0.1" vte = { version = "0.13.0", package = "vte-graphics", default-features = false, features = ["ansi", "serde"] } +unicode-width = { package = "unicode-width-16", version = "0.1.0" } serde = { version = "1", features = ["derive", "rc"], optional = true } smallvec = { version = "1.13.1", features = ["serde"] } diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs index 43302354..8c913de8 100644 --- a/alacritty_terminal/src/grid/mod.rs +++ b/alacritty_terminal/src/grid/mod.rs @@ -137,7 +137,7 @@ pub struct Grid<T> { max_scroll_limit: usize, } -impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { +impl<T: GridCell + Default + PartialEq> Grid<T> { pub fn new(lines: usize, columns: usize, max_scroll_limit: usize) -> Grid<T> { Grid { raw: Storage::with_capacity(lines, columns), @@ -356,7 +356,7 @@ impl<T> Grid<T> { /// Reset a visible region within the grid. pub fn reset_region<D, R: RangeBounds<Line>>(&mut self, bounds: R) where - T: ResetDiscriminant<D> + GridCell + Clone + Default, + T: ResetDiscriminant<D> + GridCell + Default, D: PartialEq, { let start = match bounds.start_bound() { @@ -392,7 +392,7 @@ impl<T> Grid<T> { #[inline] pub fn initialize_all(&mut self) where - T: GridCell + Clone + Default, + T: GridCell + Default, { // Remove all cached lines to clear them of any content. self.truncate(); diff --git a/alacritty_terminal/src/grid/resize.rs b/alacritty_terminal/src/grid/resize.rs index 92ee55d7..751abae1 100644 --- a/alacritty_terminal/src/grid/resize.rs +++ b/alacritty_terminal/src/grid/resize.rs @@ -9,7 +9,7 @@ use crate::term::cell::{Flags, ResetDiscriminant}; use crate::grid::row::Row; use crate::grid::{Dimensions, Grid, GridCell}; -impl<T: GridCell + Default + PartialEq + Clone> Grid<T> { +impl<T: GridCell + Default + PartialEq> Grid<T> { /// Resize the grid's width and/or height. pub fn resize<D>(&mut self, reflow: bool, lines: usize, columns: usize) where diff --git a/alacritty_terminal/src/grid/row.rs b/alacritty_terminal/src/grid/row.rs index 72f386ae..9a7f81fb 100644 --- a/alacritty_terminal/src/grid/row.rs +++ b/alacritty_terminal/src/grid/row.rs @@ -30,7 +30,7 @@ impl<T: PartialEq> PartialEq for Row<T> { } } -impl<T: Clone + Default> Row<T> { +impl<T: Default> Row<T> { /// Create a new terminal row. /// /// Ideally the `template` should be `Copy` in all performance sensitive scenarios. diff --git a/alacritty_terminal/src/grid/storage.rs b/alacritty_terminal/src/grid/storage.rs index 0a2be43b..abf57103 100644 --- a/alacritty_terminal/src/grid/storage.rs +++ b/alacritty_terminal/src/grid/storage.rs @@ -66,7 +66,7 @@ impl<T> Storage<T> { #[inline] pub fn with_capacity(visible_lines: usize, columns: usize) -> Storage<T> where - T: Clone + Default, + T: Default, { // Initialize visible lines; the scrollback buffer is initialized dynamically. let mut inner = Vec::with_capacity(visible_lines); @@ -79,7 +79,7 @@ impl<T> Storage<T> { #[inline] pub fn grow_visible_lines(&mut self, next: usize) where - T: Clone + Default, + T: Default, { // Number of lines the buffer needs to grow. let additional_lines = next - self.visible_lines; @@ -125,7 +125,7 @@ impl<T> Storage<T> { #[inline] pub fn initialize(&mut self, additional_rows: usize, columns: usize) where - T: Clone + Default, + T: Default, { if self.len + additional_rows > self.inner.len() { self.rezero(); diff --git a/alacritty_terminal/src/term/cell.rs b/alacritty_terminal/src/term/cell.rs index 6b32c781..c91ef2f1 100644 --- a/alacritty_terminal/src/term/cell.rs +++ b/alacritty_terminal/src/term/cell.rs @@ -126,9 +126,7 @@ impl ResetDiscriminant<Color> for Cell { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct CellExtra { zerowidth: Vec<char>, - underline_color: Option<Color>, - hyperlink: Option<Hyperlink>, #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index 706bbe32..6b0ccccd 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -57,30 +57,30 @@ bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct TermMode: u32 { const NONE = 0; - const SHOW_CURSOR = 0b0000_0000_0000_0000_0000_0001; - const APP_CURSOR = 0b0000_0000_0000_0000_0000_0010; - const APP_KEYPAD = 0b0000_0000_0000_0000_0000_0100; - const MOUSE_REPORT_CLICK = 0b0000_0000_0000_0000_0000_1000; - const BRACKETED_PASTE = 0b0000_0000_0000_0000_0001_0000; - const SGR_MOUSE = 0b0000_0000_0000_0000_0010_0000; - const MOUSE_MOTION = 0b0000_0000_0000_0000_0100_0000; - const LINE_WRAP = 0b0000_0000_0000_0000_1000_0000; - const LINE_FEED_NEW_LINE = 0b0000_0000_0000_0001_0000_0000; - const ORIGIN = 0b0000_0000_0000_0010_0000_0000; - const INSERT = 0b0000_0000_0000_0100_0000_0000; - const FOCUS_IN_OUT = 0b0000_0000_0000_1000_0000_0000; - const ALT_SCREEN = 0b0000_0000_0001_0000_0000_0000; - const MOUSE_DRAG = 0b0000_0000_0010_0000_0000_0000; - const MOUSE_MODE = 0b0000_0000_0010_0000_0100_1000; - const UTF8_MOUSE = 0b0000_0000_0100_0000_0000_0000; - const ALTERNATE_SCROLL = 0b0000_0000_1000_0000_0000_0000; - const VI = 0b0000_0001_0000_0000_0000_0000; - const URGENCY_HINTS = 0b0000_0010_0000_0000_0000_0000; - const DISAMBIGUATE_ESC_CODES = 0b0000_0100_0000_0000_0000_0000; - const REPORT_EVENT_TYPES = 0b0000_1000_0000_0000_0000_0000; - const REPORT_ALTERNATE_KEYS = 0b0001_0000_0000_0000_0000_0000; - const REPORT_ALL_KEYS_AS_ESC = 0b0010_0000_0000_0000_0000_0000; - const REPORT_ASSOCIATED_TEXT = 0b0100_0000_0000_0000_0000_0000; + const SHOW_CURSOR = 1; + const APP_CURSOR = 1 << 1; + const APP_KEYPAD = 1 << 2; + const MOUSE_REPORT_CLICK = 1 << 3; + const BRACKETED_PASTE = 1 << 4; + const SGR_MOUSE = 1 << 5; + const MOUSE_MOTION = 1 << 6; + const LINE_WRAP = 1 << 7; + const LINE_FEED_NEW_LINE = 1 << 8; + const ORIGIN = 1 << 9; + const INSERT = 1 << 10; + const FOCUS_IN_OUT = 1 << 11; + const ALT_SCREEN = 1 << 12; + const MOUSE_DRAG = 1 << 13; + const UTF8_MOUSE = 1 << 14; + const ALTERNATE_SCROLL = 1 << 15; + const VI = 1 << 16; + const URGENCY_HINTS = 1 << 17; + const DISAMBIGUATE_ESC_CODES = 1 << 18; + const REPORT_EVENT_TYPES = 1 << 19; + const REPORT_ALTERNATE_KEYS = 1 << 20; + const REPORT_ALL_KEYS_AS_ESC = 1 << 21; + const REPORT_ASSOCIATED_TEXT = 1 << 22; + const MOUSE_MODE = Self::MOUSE_REPORT_CLICK.bits() | Self::MOUSE_MOTION.bits() | Self::MOUSE_DRAG.bits(); const KITTY_KEYBOARD_PROTOCOL = Self::DISAMBIGUATE_ESC_CODES.bits() | Self::REPORT_EVENT_TYPES.bits() | Self::REPORT_ALTERNATE_KEYS.bits() diff --git a/alacritty_terminal/src/term/search.rs b/alacritty_terminal/src/term/search.rs index a5ae9337..33f6ee05 100644 --- a/alacritty_terminal/src/term/search.rs +++ b/alacritty_terminal/src/term/search.rs @@ -516,7 +516,14 @@ impl<T> Term<T> { #[must_use] pub fn semantic_search_left(&self, point: Point) -> Point { match self.inline_search_left(point, self.semantic_escape_chars()) { - Ok(point) => self.grid.iter_from(point).next().map_or(point, |cell| cell.point), + // If we found a match, reverse for at least one cell, skipping over wide cell spacers. + Ok(point) => { + let wide_spacer = Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER; + self.grid + .iter_from(point) + .find(|cell| !cell.flags.intersects(wide_spacer)) + .map_or(point, |cell| cell.point) + }, Err(point) => point, } } @@ -538,7 +545,7 @@ impl<T> Term<T> { let mut iter = self.grid.iter_from(point); let last_column = self.columns() - 1; - let wide = Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER; + let wide_spacer = Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER; while let Some(cell) = iter.prev() { if cell.point.column == last_column && !cell.flags.contains(Flags::WRAPLINE) { break; @@ -546,7 +553,7 @@ impl<T> Term<T> { point = cell.point; - if !cell.flags.intersects(wide) && needles.contains(cell.c) { + if !cell.flags.intersects(wide_spacer) && needles.contains(cell.c) { return Ok(point); } } @@ -559,7 +566,7 @@ impl<T> Term<T> { // Limit the starting point to the last line in the history point.line = max(point.line, self.topmost_line()); - let wide = Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER; + let wide_spacer = Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER; let last_column = self.columns() - 1; // Immediately stop if start point in on line break. @@ -570,7 +577,7 @@ impl<T> Term<T> { for cell in self.grid.iter_from(point) { point = cell.point; - if !cell.flags.intersects(wide) && needles.contains(cell.c) { + if !cell.flags.intersects(wide_spacer) && needles.contains(cell.c) { return Ok(point); } @@ -1171,4 +1178,16 @@ mod tests { let match_end = Point::new(Line(1), Column(2)); assert_eq!(term.regex_search_left(&mut regex, start, end), Some(match_start..=match_end)); } + + #[test] + fn fullwidth_semantic() { + #[rustfmt::skip] + let mut term = mock_term("test-x-test"); + term.config.semantic_escape_chars = "-".into(); + + let start = term.semantic_search_left(Point::new(Line(0), Column(6))); + let end = term.semantic_search_right(Point::new(Line(0), Column(6))); + assert_eq!(start, Point::new(Line(0), Column(6))); + assert_eq!(end, Point::new(Line(0), Column(6))); + } } diff --git a/alacritty_terminal/src/tty/mod.rs b/alacritty_terminal/src/tty/mod.rs index 55d263ca..eed2a76d 100644 --- a/alacritty_terminal/src/tty/mod.rs +++ b/alacritty_terminal/src/tty/mod.rs @@ -50,9 +50,10 @@ impl Shell { } } -/// This trait defines the behaviour needed to read and/or write to a stream. -/// It defines an abstraction over polling's interface in order to allow either one -/// read/write object or a separate read and write object. +/// Stream read and/or write behavior. +/// +/// This defines an abstraction over polling's interface in order to allow either +/// one read/write object or a separate read and write object. pub trait EventedReadWrite { type Reader: io::Read; type Writer: io::Write; @@ -97,10 +98,6 @@ pub fn setup_env() { // Advertise 24-bit color support. env::set_var("COLORTERM", "truecolor"); - - // Prevent child processes from inheriting startup notification env. - env::remove_var("DESKTOP_STARTUP_ID"); - env::remove_var("XDG_ACTIVATION_TOKEN"); } /// Check if a terminfo entry exists on the system. diff --git a/alacritty_terminal/src/tty/unix.rs b/alacritty_terminal/src/tty/unix.rs index 8084a753..6565f20b 100644 --- a/alacritty_terminal/src/tty/unix.rs +++ b/alacritty_terminal/src/tty/unix.rs @@ -227,6 +227,10 @@ pub fn from_fd(config: &Options, window_id: u64, master: OwnedFd, slave: OwnedFd builder.env(key, value); } + // Prevent child processes from inheriting linux-specific startup notification env. + builder.env_remove("XDG_ACTIVATION_TOKEN"); + builder.env_remove("DESKTOP_STARTUP_ID"); + unsafe { builder.pre_exec(move || { // Create a new process group. diff --git a/alacritty_terminal/src/tty/windows/conpty.rs b/alacritty_terminal/src/tty/windows/conpty.rs index 244681e7..28289f90 100644 --- a/alacritty_terminal/src/tty/windows/conpty.rs +++ b/alacritty_terminal/src/tty/windows/conpty.rs @@ -1,7 +1,7 @@ use log::{info, warn}; use std::collections::{HashMap, HashSet}; use std::ffi::OsStr; -use std::io::Error; +use std::io::{Error, Result}; use std::os::windows::ffi::OsStrExt; use std::os::windows::io::IntoRawHandle; use std::{mem, ptr}; @@ -107,7 +107,7 @@ impl Drop for Conpty { // The ConPTY handle can be sent between threads. unsafe impl Send for Conpty {} -pub fn new(config: &Options, window_size: WindowSize) -> Option<Pty> { +pub fn new(config: &Options, window_size: WindowSize) -> Result<Pty> { let api = ConptyApi::new(); let mut pty_handle: HPCON = 0; @@ -115,8 +115,8 @@ pub fn new(config: &Options, window_size: WindowSize) -> Option<Pty> { // size to be used. There may be small performance and memory advantages // to be gained by tuning this in the future, but it's likely a reasonable // start point. - let (conout, conout_pty_handle) = miow::pipe::anonymous(0).unwrap(); - let (conin_pty_handle, conin) = miow::pipe::anonymous(0).unwrap(); + let (conout, conout_pty_handle) = miow::pipe::anonymous(0)?; + let (conin_pty_handle, conin) = miow::pipe::anonymous(0)?; // Create the Pseudo Console, using the pipes. let result = unsafe { @@ -154,7 +154,7 @@ pub fn new(config: &Options, window_size: WindowSize) -> Option<Pty> { // This call was expected to return false. if failure { - panic_shell_spawn(); + return Err(Error::last_os_error()); } } @@ -180,7 +180,7 @@ pub fn new(config: &Options, window_size: WindowSize) -> Option<Pty> { ) > 0; if !success { - panic_shell_spawn(); + return Err(Error::last_os_error()); } } @@ -197,7 +197,7 @@ pub fn new(config: &Options, window_size: WindowSize) -> Option<Pty> { ) > 0; if !success { - panic_shell_spawn(); + return Err(Error::last_os_error()); } } @@ -230,17 +230,17 @@ pub fn new(config: &Options, window_size: WindowSize) -> Option<Pty> { ) > 0; if !success { - panic_shell_spawn(); + return Err(Error::last_os_error()); } } let conin = UnblockedWriter::new(conin, PIPE_CAPACITY); let conout = UnblockedReader::new(conout, PIPE_CAPACITY); - let child_watcher = ChildExitWatcher::new(proc_info.hProcess).unwrap(); + let child_watcher = ChildExitWatcher::new(proc_info.hProcess)?; let conpty = Conpty { handle: pty_handle as HPCON, api }; - Some(Pty::new(conpty, conout, conin, child_watcher)) + Ok(Pty::new(conpty, conout, conin, child_watcher)) } // Windows environment variables are case-insensitive, and the caller is responsible for @@ -300,11 +300,6 @@ fn add_windows_env_key_value_to_block(block: &mut Vec<u16>, key: &OsStr, value: block.push(0); } -// Panic with the last os error as message. -fn panic_shell_spawn() { - panic!("Unable to spawn shell: {}", Error::last_os_error()); -} - impl OnResize for Conpty { fn on_resize(&mut self, window_size: WindowSize) { let result = unsafe { (self.api.resize)(self.handle, window_size.into()) }; diff --git a/alacritty_terminal/src/tty/windows/mod.rs b/alacritty_terminal/src/tty/windows/mod.rs index 6af162c8..32e24677 100644 --- a/alacritty_terminal/src/tty/windows/mod.rs +++ b/alacritty_terminal/src/tty/windows/mod.rs @@ -1,5 +1,5 @@ use std::ffi::OsStr; -use std::io::{self, Error, ErrorKind, Result}; +use std::io::{self, Result}; use std::iter::once; use std::os::windows::ffi::OsStrExt; use std::sync::mpsc::TryRecvError; @@ -35,7 +35,6 @@ pub struct Pty { pub fn new(config: &Options, window_size: WindowSize, _window_id: u64) -> Result<Pty> { conpty::new(config, window_size) - .ok_or_else(|| Error::new(ErrorKind::Other, "failed to spawn conpty")) } impl Pty { diff --git a/extra/logo/alacritty-term.svg b/extra/logo/alacritty-term.svg index 065df538..9798a3df 100644 --- a/extra/logo/alacritty-term.svg +++ b/extra/logo/alacritty-term.svg @@ -423,7 +423,6 @@ transform="translate(-16,35.820639)" sodipodi:insensitive="true"><g id="g4199"><path - clip-path="none" sodipodi:nodetypes="ccccccc" inkscape:connector-curvature="0" id="path5352" @@ -440,4 +439,4 @@ d="M 19,32.395 31.5,0 6.5,0.13313911 Z" id="path9580" inkscape:connector-curvature="0" - sodipodi:nodetypes="cccc" /></g></g></svg>
\ No newline at end of file + sodipodi:nodetypes="cccc" /></g></g></svg> diff --git a/extra/man/alacritty-bindings.5.scd b/extra/man/alacritty-bindings.5.scd index 3de2986e..7f0bdf34 100644 --- a/extra/man/alacritty-bindings.5.scd +++ b/extra/man/alacritty-bindings.5.scd @@ -2,7 +2,7 @@ ALACRITTY-BINDINGS(5) # NAME -Alacritty Bindings - Default configuration file bindings. +alacritty-bindings - Default configuration file bindings. # SYNOPSIS diff --git a/extra/man/alacritty.1.scd b/extra/man/alacritty.1.scd index b82ba08e..c8a67bb8 100644 --- a/extra/man/alacritty.1.scd +++ b/extra/man/alacritty.1.scd @@ -61,13 +61,17 @@ set of features with high performance. Specify alternative configuration file. - Alacritty looks for the configuration file at the following paths: + Alacritty doesn't create the config file for you, but it looks for one in the + following locations on UNIX systems: + . _$XDG_CONFIG_HOME/alacritty/alacritty.toml_ . _$XDG_CONFIG_HOME/alacritty.toml_ . _$HOME/.config/alacritty/alacritty.toml_ . _$HOME/.alacritty.toml_ - On Windows, the configuration file is located at _%APPDATA%\\alacritty\\alacritty.toml_. + On Windows, the config file will be looked for in: + + . _%APPDATA%\\alacritty\\alacritty.toml_ *--embed* _<PARENT>_ diff --git a/extra/man/alacritty.5.scd b/extra/man/alacritty.5.scd index 718abfa3..8e92734a 100644 --- a/extra/man/alacritty.5.scd +++ b/extra/man/alacritty.5.scd @@ -14,18 +14,18 @@ can be found at _https://toml.io/en/v1.0.0_. Alacritty doesn't create the config file for you, but it looks for one in the following locations on UNIX systems: -. `$XDG_CONFIG_HOME/alacritty/alacritty.toml` -. `$XDG_CONFIG_HOME/alacritty.toml` -. `$HOME/.config/alacritty/alacritty.toml` -. `$HOME/.alacritty.toml` +. _$XDG_CONFIG_HOME/alacritty/alacritty.toml_ +. _$XDG_CONFIG_HOME/alacritty.toml_ +. _$HOME/.config/alacritty/alacritty.toml_ +. _$HOME/.alacritty.toml_ On Windows, the config file will be looked for in: -. `%APPDATA%\alacritty\alacritty.toml` +. _%APPDATA%\\alacritty\\alacritty.toml_ # GENERAL -This section documents the root level of the configuration file. +This section documents the *[general]* table of the configuration file. *import* = [_"<string>"_,] @@ -46,20 +46,6 @@ This section documents the root level of the configuration file. _"alacritty-theme/themes/gruvbox_dark.toml"_,++ ] -*shell* = _"<string>"_ | { program = _"<string>"_, args = [_"<string>"_,] } - - You can set _shell.program_ to the path of your favorite shell, e.g. - _/bin/zsh_. Entries in _shell.args_ are passed as arguments to the shell. - - Default: - Linux/BSD/macOS: _$SHELL_ or the user's login shell, if _$SHELL_ is unset++ -Windows: _"powershell"_ - - Example: - *[shell]*++ -program = _"/bin/zsh"_++ -args = [_"-l"_] - *working_directory* = _"<string>"_ | _"None"_ Directory the shell is started in. When this is unset, or _"None"_, the @@ -605,6 +591,20 @@ This section documents the *[cursor]* table of the configuration file. This section documents the *[terminal]* table of the configuration file. +*shell* = _"<string>"_ | { program = _"<string>"_, args = [_"<string>"_,] } + + You can set _shell.program_ to the path of your favorite shell, e.g. + _/bin/zsh_. Entries in _shell.args_ are passed as arguments to the shell. + + Default: + Linux/BSD/macOS: _$SHELL_ or the user's login shell, if _$SHELL_ is unset++ +Windows: _"powershell"_ + + Example: + *[shell]*++ +program = _"/bin/zsh"_++ +args = [_"-l"_] + *osc52* = _"Disabled"_ | _"OnlyCopy"_ | _"OnlyPaste"_ | _"CopyPaste"_ Controls the ability to write to the system clipboard with the _OSC 52_ @@ -730,7 +730,7 @@ hyperlinks = _true_++ post_processing = _true_++ persist = _false_++ mouse.enabled = _true_++ -binding = { key = _"U"_, mods = _"Control|Shift"_ }++ +binding = { key = _"O"_, mods = _"Control|Shift"_ }++ regex = _"(ipfs:|ipns:|magnet:|mailto:|gemini://|gopher://|https://|http://|news:|file:|git://|ssh:|ftp://)[^\\u0000-\\u001F\\u007F-\\u009F<>\\"\\\\s{-}\\\\^⟨⟩`]+"_ # KEYBOARD |