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