aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAyose <ayosec@gmail.com>2024-10-13 00:00:00 +0000
committerAyose <ayosec@gmail.com>2024-10-13 00:00:00 +0000
commitc7213774e81dd6012035c5312374db8480ccabc2 (patch)
tree6def43870a1a8b837aa8d5ff313731e7d12e78ee
parent23c1b2fbcfdc84df806163ea26c5284e598cba2b (diff)
parenta2653293a8e6ac5f5fb9f7e075656799d8df3488 (diff)
downloadr-alacritty-c7213774e81dd6012035c5312374db8480ccabc2.tar.gz
r-alacritty-c7213774e81dd6012035c5312374db8480ccabc2.tar.bz2
r-alacritty-c7213774e81dd6012035c5312374db8480ccabc2.zip
Merge remote-tracking branch 'vendor/master' into graphics
-rw-r--r--.github/workflows/ci.yml4
-rw-r--r--.github/workflows/release.yml6
-rw-r--r--CHANGELOG.md11
-rw-r--r--Cargo.lock58
-rw-r--r--alacritty/Cargo.toml6
-rw-r--r--alacritty/src/cli.rs4
-rw-r--r--alacritty/src/config/general.rs39
-rw-r--r--alacritty/src/config/mod.rs40
-rw-r--r--alacritty/src/config/terminal.rs6
-rw-r--r--alacritty/src/config/ui_config.rs131
-rw-r--r--alacritty/src/display/content.rs4
-rw-r--r--alacritty/src/display/damage.rs9
-rw-r--r--alacritty/src/display/mod.rs187
-rw-r--r--alacritty/src/display/window.rs5
-rw-r--r--alacritty/src/event.rs12
-rw-r--r--alacritty/src/logging.rs4
-rw-r--r--alacritty/src/main.rs2
-rw-r--r--alacritty/src/migrate.rs267
-rw-r--r--alacritty/src/migrate/mod.rs327
-rw-r--r--alacritty/src/migrate/yaml.rs87
-rw-r--r--alacritty_config_derive/src/config_deserialize/de_struct.rs1
-rw-r--r--alacritty_config_derive/tests/config.rs19
-rw-r--r--alacritty_terminal/Cargo.toml2
-rw-r--r--alacritty_terminal/src/grid/mod.rs6
-rw-r--r--alacritty_terminal/src/grid/resize.rs2
-rw-r--r--alacritty_terminal/src/grid/row.rs2
-rw-r--r--alacritty_terminal/src/grid/storage.rs6
-rw-r--r--alacritty_terminal/src/term/cell.rs2
-rw-r--r--alacritty_terminal/src/term/mod.rs48
-rw-r--r--alacritty_terminal/src/term/search.rs29
-rw-r--r--alacritty_terminal/src/tty/mod.rs11
-rw-r--r--alacritty_terminal/src/tty/unix.rs4
-rw-r--r--alacritty_terminal/src/tty/windows/conpty.rs25
-rw-r--r--alacritty_terminal/src/tty/windows/mod.rs3
-rw-r--r--extra/logo/alacritty-term.svg3
-rw-r--r--extra/man/alacritty-bindings.5.scd2
-rw-r--r--extra/man/alacritty.1.scd8
-rw-r--r--extra/man/alacritty.5.scd42
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
diff --git a/Cargo.lock b/Cargo.lock
index a32f3dd7..d98d2b5e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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