Merge "[alacritty] Remove `alacritty/` since we're not using it." into main
diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml
deleted file mode 100644
index 759ad8c..0000000
--- a/alacritty/Cargo.toml
+++ /dev/null
@@ -1,75 +0,0 @@
-[package]
-name = "alacritty"
-version = "0.4.2-dev"
-authors = ["Christian Duerr <contact@christianduerr.com", "Joe Wilm <joe@jwilm.com>"]
-license = "Apache-2.0"
-description = "GPU-accelerated terminal emulator"
-readme = "../README.md"
-homepage = "https://github.com/alacritty/alacritty"
-edition = "2018"
-
-[dependencies]
-alacritty_terminal = { path = "../alacritty_terminal" }
-clap = "2"
-log = "0.4"
-time = "0.1.40"
-env_logger = "0.7.1"
-fnv = "1"
-serde = { version = "1", features = ["derive"] }
-serde_yaml = "0.8"
-serde_json = "1"
-glutin = { version = "0.22.0", features = ["serde"] }
-notify = "4"
-libc = "0.2"
-unicode-width = "0.1"
-parking_lot = "0.9"
-font = { path = "../font" }
-urlocator = "0.1.0"
-
-[build-dependencies]
-gl_generator = "0.14.0"
-rustc_tools_util = "0.2.0"
-
-[target.'cfg(not(windows))'.dependencies]
-xdg = "2"
-
-[target.'cfg(not(target_os = "macos"))'.dependencies]
-image = { version = "0.22.3", default-features = false, features = ["ico"] }
-
-[target.'cfg(any(target_os = "macos", windows))'.dependencies]
-dirs = "2.0.2"
-
-[target.'cfg(not(any(target_os="windows", target_os="macos")))'.dependencies]
-x11-dl = "2"
-
-[target.'cfg(windows)'.dependencies]
-winapi = { version = "0.3.7", features = ["impl-default", "wincon"]}
-
-[target.'cfg(windows)'.build-dependencies]
-embed-resource = "1.3"
-
-[features]
-default = []
-# Enabling this feature makes shaders automatically reload when changed
-live-shader-reload = []
-nightly = []
-bench = []
-
-[package.metadata.deb]
-maintainer = "Christian Duerr <contact@christianduerr.com>"
-license-file = ["../LICENSE-APACHE", "3"]
-extended-description = """\
-Alacritty is the fastest terminal emulator in existence. Using the GPU for \
-rendering enables optimizations that simply aren't possible without it. """
-depends = "$auto"
-section = "rust"
-priority = "optional"
-assets = [
- ["../target/release/alacritty", "usr/bin/", "755"],
- ["../extra/linux/alacritty.desktop", "usr/share/applications/", "644"],
- ["../extra/logo/alacritty-term.svg", "usr/share/pixmaps/Alacritty.svg", "644"],
- ["../extra/completions/alacritty.bash", "usr/share/bash-completion/completions/alacritty", "644"],
- ["../extra/completions/alacritty.fish", "usr/share/fish/completions/alacritty.fish", "644"],
- ["../extra/completions/_alacritty", "usr/share/zsh/vendor-completions/_alacritty", "644"],
-]
-maintainer-scripts = "../extra/linux/debian"
diff --git a/alacritty/build.rs b/alacritty/build.rs
deleted file mode 100644
index 16f3a2b..0000000
--- a/alacritty/build.rs
+++ /dev/null
@@ -1,37 +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 gl_generator::{Api, Fallbacks, GlobalGenerator, Profile, Registry};
-
-use std::env;
-use std::fs::File;
-use std::path::Path;
-
-#[cfg(windows)]
-use embed_resource;
-
-fn main() {
- let hash = rustc_tools_util::get_commit_hash().unwrap_or_default();
- println!("cargo:rustc-env=GIT_HASH={}", hash);
-
- let dest = env::var("OUT_DIR").unwrap();
- let mut file = File::create(&Path::new(&dest).join("gl_bindings.rs")).unwrap();
-
- Registry::new(Api::Gl, (4, 5), Profile::Core, Fallbacks::All, ["GL_ARB_blend_func_extended"])
- .write_bindings(GlobalGenerator, &mut file)
- .unwrap();
-
- #[cfg(windows)]
- embed_resource::compile("../extra/windows/windows.rc");
-}
diff --git a/alacritty/src/cli.rs b/alacritty/src/cli.rs
deleted file mode 100644
index 7e45e24..0000000
--- a/alacritty/src/cli.rs
+++ /dev/null
@@ -1,339 +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 std::cmp::max;
-use std::path::PathBuf;
-
-use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
-use log::{self, error, LevelFilter};
-
-use alacritty_terminal::config::{Delta, Dimensions, Shell, DEFAULT_NAME};
-use alacritty_terminal::index::{Column, Line};
-
-use crate::config::Config;
-
-#[cfg(not(any(target_os = "macos", windows)))]
-const CONFIG_PATH: &str = "$XDG_CONFIG_HOME/alacritty/alacritty.yml";
-#[cfg(windows)]
-const CONFIG_PATH: &str = "%APPDATA%\\alacritty\\alacritty.yml";
-#[cfg(target_os = "macos")]
-const CONFIG_PATH: &str = "$HOME/.config/alacritty/alacritty.yml";
-
-/// Options specified on the command line
-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 embed: Option<String>,
- pub log_level: LevelFilter,
- pub command: Option<Shell<'static>>,
- pub hold: bool,
- 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,
- embed: None,
- log_level: LevelFilter::Warn,
- command: None,
- hold: false,
- working_dir: None,
- config: None,
- persistent_logging: false,
- }
- }
-}
-
-impl Options {
- /// Build `Options` from command line arguments.
- pub fn new() -> Self {
- let mut version = crate_version!().to_owned();
- let commit_hash = env!("GIT_HASH");
- if !commit_hash.is_empty() {
- version = format!("{} ({})", version, commit_hash);
- }
-
- let mut options = Options::default();
-
- let matches = App::new(crate_name!())
- .version(version.as_str())
- .author(crate_authors!("\n"))
- .about(crate_description!())
- .arg(Arg::with_name("ref-test").long("ref-test").help("Generates ref test"))
- .arg(
- Arg::with_name("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("embed").long("embed").takes_value(true).help(
- "Defines the X11 window ID (as a decimal integer) to embed Alacritty within",
- ),
- )
- .arg(
- Arg::with_name("q")
- .short("q")
- .multiple(true)
- .conflicts_with("v")
- .help("Reduces the level of verbosity (the min level is -qq)"),
- )
- .arg(
- Arg::with_name("v")
- .short("v")
- .multiple(true)
- .conflicts_with("q")
- .help("Increases the level of verbosity (the max level is -vvv)"),
- )
- .arg(
- Arg::with_name("working-directory")
- .long("working-directory")
- .takes_value(true)
- .help("Start the shell in the specified working directory"),
- )
- .arg(Arg::with_name("config-file").long("config-file").takes_value(true).help(
- &format!("Specify alternative configuration file [default: {}]", CONFIG_PATH),
- ))
- .arg(
- Arg::with_name("command")
- .long("command")
- .short("e")
- .multiple(true)
- .takes_value(true)
- .min_values(1)
- .allow_hyphen_values(true)
- .help("Command and args to execute (must be last argument)"),
- )
- .arg(Arg::with_name("hold").long("hold").help("Remain open after child process exits"))
- .get_matches();
-
- if matches.is_present("ref-test") {
- options.ref_test = true;
- }
-
- if matches.is_present("print-events") {
- options.print_events = true;
- }
-
- if matches.is_present("live-config-reload") {
- options.live_config_reload = Some(true);
- } else if matches.is_present("no-live-config-reload") {
- options.live_config_reload = Some(false);
- }
-
- if matches.is_present("persistent-logging") {
- options.persistent_logging = true;
- }
-
- if let Some(mut dimensions) = matches.values_of("dimensions") {
- let width = dimensions.next().map(|w| w.parse().map(Column));
- let height = dimensions.next().map(|h| h.parse().map(Line));
- if let (Some(Ok(width)), Some(Ok(height))) = (width, height) {
- options.dimensions = Some(Dimensions::new(width, height));
- }
- }
-
- if let Some(mut position) = matches.values_of("position") {
- let x = position.next().map(str::parse);
- let y = position.next().map(str::parse);
- if let (Some(Ok(x)), Some(Ok(y))) = (x, y) {
- options.position = Some(Delta { x, y });
- }
- }
-
- options.class = matches.value_of("class").map(ToOwned::to_owned);
- options.title = matches.value_of("title").map(ToOwned::to_owned);
- options.embed = matches.value_of("embed").map(ToOwned::to_owned);
-
- match matches.occurrences_of("q") {
- 0 => {},
- 1 => options.log_level = LevelFilter::Error,
- 2 | _ => options.log_level = LevelFilter::Off,
- }
-
- match matches.occurrences_of("v") {
- 0 if !options.print_events => options.log_level = LevelFilter::Warn,
- 0 | 1 => options.log_level = LevelFilter::Info,
- 2 => options.log_level = LevelFilter::Debug,
- 3 | _ => options.log_level = LevelFilter::Trace,
- }
-
- if let Some(dir) = matches.value_of("working-directory") {
- options.working_dir = Some(PathBuf::from(dir.to_string()));
- }
-
- if let Some(path) = matches.value_of("config-file") {
- options.config = Some(PathBuf::from(path.to_string()));
- }
-
- if let Some(mut args) = matches.values_of("command") {
- // The following unwrap is guaranteed to succeed.
- // If 'command' exists it must also have a first item since
- // Arg::min_values(1) is set.
- let command = String::from(args.next().unwrap());
- let args = args.map(String::from).collect();
- options.command = Some(Shell::new_with_args(command, args));
- }
-
- if matches.is_present("hold") {
- options.hold = true;
- }
-
- options
- }
-
- pub fn config_path(&self) -> Option<PathBuf> {
- self.config.clone()
- }
-
- pub fn into_config(self, mut config: Config) -> Config {
- match self.working_dir.or_else(|| config.working_directory.take()) {
- Some(ref wd) if !wd.is_dir() => error!("Unable to set working directory to {:?}", wd),
- wd => config.working_directory = wd,
- }
-
- if let Some(lcr) = self.live_config_reload {
- config.set_live_config_reload(lcr);
- }
- config.shell = self.command.or(config.shell);
-
- config.hold = self.hold;
-
- config.window.dimensions = self.dimensions.unwrap_or(config.window.dimensions);
- config.window.title = self.title.unwrap_or(config.window.title);
- config.window.position = self.position.or(config.window.position);
- config.window.embed = self.embed.and_then(|embed| embed.parse().ok());
-
- if let Some(class) = self.class {
- let parts: Vec<_> = class.split(',').collect();
- config.window.class.instance = parts[0].into();
- if let Some(&general) = parts.get(1) {
- config.window.class.general = general.into();
- }
- }
-
- config.set_dynamic_title(config.dynamic_title() && config.window.title == DEFAULT_NAME);
-
- 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;
- config.debug.persistent_logging =
- self.persistent_logging || config.debug.persistent_logging;
-
- if config.debug.print_events {
- config.debug.log_level = max(config.debug.log_level, LevelFilter::Info);
- }
-
- config
- }
-}
-
-#[cfg(test)]
-mod test {
- use crate::cli::Options;
- use crate::config::Config;
-
- #[test]
- fn dynamic_title_ignoring_options_by_default() {
- let config = Config::default();
- let old_dynamic_title = config.dynamic_title();
-
- let config = Options::default().into_config(config);
-
- assert_eq!(old_dynamic_title, config.dynamic_title());
- }
-
- #[test]
- fn dynamic_title_overridden_by_options() {
- let config = Config::default();
-
- let mut options = Options::default();
- options.title = Some("foo".to_owned());
- let config = options.into_config(config);
-
- assert!(!config.dynamic_title());
- }
-
- #[test]
- fn dynamic_title_overridden_by_config() {
- let mut config = Config::default();
-
- config.window.title = "foo".to_owned();
- let config = Options::default().into_config(config);
-
- assert!(!config.dynamic_title());
- }
-}
diff --git a/alacritty/src/config/bindings.rs b/alacritty/src/config/bindings.rs
deleted file mode 100644
index 1a9f95a..0000000
--- a/alacritty/src/config/bindings.rs
+++ /dev/null
@@ -1,1072 +0,0 @@
-// 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.
-#![allow(clippy::enum_glob_use)]
-
-use std::fmt;
-use std::str::FromStr;
-
-use glutin::event::VirtualKeyCode::*;
-use glutin::event::{ModifiersState, MouseButton, VirtualKeyCode};
-use log::error;
-use serde::de::Error as SerdeError;
-use serde::de::{self, MapAccess, Unexpected, Visitor};
-use serde::{Deserialize, Deserializer};
-
-use alacritty_terminal::config::LOG_TARGET_CONFIG;
-use alacritty_terminal::term::TermMode;
-
-/// Describes a state and action to take in that state
-///
-/// This is the shared component of `MouseBinding` and `KeyBinding`
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct Binding<T> {
- /// Modifier keys required to activate binding
- pub mods: ModifiersState,
-
- /// String to send to pty if mods and mode match
- pub action: Action,
-
- /// Terminal mode required to activate binding
- pub mode: TermMode,
-
- /// excluded terminal modes where the binding won't be activated
- pub notmode: TermMode,
-
- /// This property is used as part of the trigger detection code.
- ///
- /// For example, this might be a key like "G", or a mouse button.
- pub trigger: T,
-}
-
-/// Bindings that are triggered by a keyboard key
-pub type KeyBinding = Binding<Key>;
-
-/// Bindings that are triggered by a mouse button
-pub type MouseBinding = Binding<MouseButton>;
-
-impl Default for KeyBinding {
- fn default() -> KeyBinding {
- KeyBinding {
- mods: Default::default(),
- action: Action::Esc(String::new()),
- mode: TermMode::NONE,
- notmode: TermMode::NONE,
- trigger: Key::Keycode(A),
- }
- }
-}
-
-impl Default for MouseBinding {
- fn default() -> MouseBinding {
- MouseBinding {
- mods: Default::default(),
- action: Action::Esc(String::new()),
- mode: TermMode::NONE,
- notmode: TermMode::NONE,
- trigger: MouseButton::Left,
- }
- }
-}
-
-impl<T: Eq> Binding<T> {
- #[inline]
- pub fn is_triggered_by(&self, mode: TermMode, mods: ModifiersState, input: &T) -> bool {
- // Check input first since bindings are stored in one big list. This is
- // the most likely item to fail so prioritizing it here allows more
- // checks to be short circuited.
- self.trigger == *input
- && mode.contains(self.mode)
- && !mode.intersects(self.notmode)
- && (self.mods == mods)
- }
-
- #[inline]
- pub fn triggers_match(&self, binding: &Binding<T>) -> bool {
- // Check the binding's key and modifiers
- if self.trigger != binding.trigger || self.mods != binding.mods {
- return false;
- }
-
- // Completely empty modes match all modes
- if (self.mode.is_empty() && self.notmode.is_empty())
- || (binding.mode.is_empty() && binding.notmode.is_empty())
- {
- return true;
- }
-
- // Check for intersection (equality is required since empty does not intersect itself)
- (self.mode == binding.mode || self.mode.intersects(binding.mode))
- && (self.notmode == binding.notmode || self.notmode.intersects(binding.notmode))
- }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
-pub enum Action {
- /// Write an escape sequence.
- #[serde(skip)]
- Esc(String),
-
- /// Paste contents of system clipboard.
- Paste,
-
- /// Store current selection into clipboard.
- Copy,
-
- /// Paste contents of selection buffer.
- PasteSelection,
-
- /// Increase font size.
- IncreaseFontSize,
-
- /// Decrease font size.
- DecreaseFontSize,
-
- /// Reset font size to the config value.
- ResetFontSize,
-
- /// Scroll exactly one page up.
- ScrollPageUp,
-
- /// Scroll exactly one page down.
- ScrollPageDown,
-
- /// Scroll one line up.
- ScrollLineUp,
-
- /// Scroll one line down.
- ScrollLineDown,
-
- /// Scroll all the way to the top.
- ScrollToTop,
-
- /// Scroll all the way to the bottom.
- ScrollToBottom,
-
- /// Clear the display buffer(s) to remove history.
- ClearHistory,
-
- /// Run given command.
- #[serde(skip)]
- Command(String, Vec<String>),
-
- /// Hide the Alacritty window.
- Hide,
-
- /// Minimize the Alacritty window.
- Minimize,
-
- /// Quit Alacritty.
- Quit,
-
- /// Clear warning and error notices.
- ClearLogNotice,
-
- /// Spawn a new instance of Alacritty.
- SpawnNewInstance,
-
- /// Toggle fullscreen.
- ToggleFullscreen,
-
- /// Toggle simple fullscreen on macos.
- #[cfg(target_os = "macos")]
- ToggleSimpleFullscreen,
-
- /// Allow receiving char input.
- ReceiveChar,
-
- /// No action.
- None,
-}
-
-impl Default for Action {
- fn default() -> Action {
- Action::None
- }
-}
-
-impl From<&'static str> for Action {
- fn from(s: &'static str) -> Action {
- Action::Esc(s.into())
- }
-}
-
-macro_rules! bindings {
- (
- KeyBinding;
- $(
- $key:ident
- $(,$mods:expr)*
- $(,+$mode:expr)*
- $(,~$notmode:expr)*
- ;$action:expr
- );*
- $(;)*
- ) => {{
- bindings!(
- KeyBinding;
- $(
- Key::Keycode($key)
- $(,$mods)*
- $(,+$mode)*
- $(,~$notmode)*
- ;$action
- );*
- )
- }};
- (
- $ty:ident;
- $(
- $key:expr
- $(,$mods:expr)*
- $(,+$mode:expr)*
- $(,~$notmode:expr)*
- ;$action:expr
- );*
- $(;)*
- ) => {{
- let mut v = Vec::new();
-
- $(
- let mut _mods = ModifiersState::empty();
- $(_mods = $mods;)*
- let mut _mode = TermMode::empty();
- $(_mode = $mode;)*
- let mut _notmode = TermMode::empty();
- $(_notmode = $notmode;)*
-
- v.push($ty {
- trigger: $key,
- mods: _mods,
- mode: _mode,
- notmode: _notmode,
- action: $action,
- });
- )*
-
- v
- }};
-}
-
-pub fn default_mouse_bindings() -> Vec<MouseBinding> {
- bindings!(
- MouseBinding;
- MouseButton::Middle; Action::PasteSelection;
- )
-}
-
-pub fn default_key_bindings() -> Vec<KeyBinding> {
- let mut bindings = bindings!(
- KeyBinding;
- Paste; Action::Paste;
- Copy; Action::Copy;
- L, ModifiersState::CTRL; Action::ClearLogNotice;
- L, ModifiersState::CTRL; Action::Esc("\x0c".into());
- PageUp, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollPageUp;
- PageDown, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollPageDown;
- Home, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollToTop;
- End, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollToBottom;
- Home, +TermMode::APP_CURSOR; Action::Esc("\x1bOH".into());
- Home, ~TermMode::APP_CURSOR; Action::Esc("\x1b[H".into());
- Home, ModifiersState::SHIFT, +TermMode::ALT_SCREEN; Action::Esc("\x1b[1;2H".into());
- End, +TermMode::APP_CURSOR; Action::Esc("\x1bOF".into());
- End, ~TermMode::APP_CURSOR; Action::Esc("\x1b[F".into());
- End, ModifiersState::SHIFT, +TermMode::ALT_SCREEN; Action::Esc("\x1b[1;2F".into());
- PageUp; Action::Esc("\x1b[5~".into());
- PageUp, ModifiersState::SHIFT, +TermMode::ALT_SCREEN; Action::Esc("\x1b[5;2~".into());
- PageDown; Action::Esc("\x1b[6~".into());
- PageDown, ModifiersState::SHIFT, +TermMode::ALT_SCREEN; Action::Esc("\x1b[6;2~".into());
- Tab, ModifiersState::SHIFT; Action::Esc("\x1b[Z".into());
- Back; Action::Esc("\x7f".into());
- Back, ModifiersState::ALT; Action::Esc("\x1b\x7f".into());
- Insert; Action::Esc("\x1b[2~".into());
- Delete; Action::Esc("\x1b[3~".into());
- Up, +TermMode::APP_CURSOR; Action::Esc("\x1bOA".into());
- Up, ~TermMode::APP_CURSOR; Action::Esc("\x1b[A".into());
- Down, +TermMode::APP_CURSOR; Action::Esc("\x1bOB".into());
- Down, ~TermMode::APP_CURSOR; Action::Esc("\x1b[B".into());
- Right, +TermMode::APP_CURSOR; Action::Esc("\x1bOC".into());
- Right, ~TermMode::APP_CURSOR; Action::Esc("\x1b[C".into());
- Left, +TermMode::APP_CURSOR; Action::Esc("\x1bOD".into());
- Left, ~TermMode::APP_CURSOR; Action::Esc("\x1b[D".into());
- F1; Action::Esc("\x1bOP".into());
- F2; Action::Esc("\x1bOQ".into());
- F3; Action::Esc("\x1bOR".into());
- F4; Action::Esc("\x1bOS".into());
- F5; Action::Esc("\x1b[15~".into());
- F6; Action::Esc("\x1b[17~".into());
- F7; Action::Esc("\x1b[18~".into());
- F8; Action::Esc("\x1b[19~".into());
- F9; Action::Esc("\x1b[20~".into());
- F10; Action::Esc("\x1b[21~".into());
- F11; Action::Esc("\x1b[23~".into());
- F12; Action::Esc("\x1b[24~".into());
- F13; Action::Esc("\x1b[25~".into());
- F14; Action::Esc("\x1b[26~".into());
- F15; Action::Esc("\x1b[28~".into());
- F16; Action::Esc("\x1b[29~".into());
- F17; Action::Esc("\x1b[31~".into());
- F18; Action::Esc("\x1b[32~".into());
- F19; Action::Esc("\x1b[33~".into());
- F20; Action::Esc("\x1b[34~".into());
- NumpadEnter; Action::Esc("\n".into());
- );
-
- // Code Modifiers
- // ---------+---------------------------
- // 2 | Shift
- // 3 | Alt
- // 4 | Shift + Alt
- // 5 | Control
- // 6 | Shift + Control
- // 7 | Alt + Control
- // 8 | Shift + Alt + Control
- // ---------+---------------------------
- //
- // from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
- let mut modifiers = vec![
- ModifiersState::SHIFT,
- ModifiersState::ALT,
- ModifiersState::SHIFT | ModifiersState::ALT,
- ModifiersState::CTRL,
- ModifiersState::SHIFT | ModifiersState::CTRL,
- ModifiersState::ALT | ModifiersState::CTRL,
- ModifiersState::SHIFT | ModifiersState::ALT | ModifiersState::CTRL,
- ];
-
- for (index, mods) in modifiers.drain(..).enumerate() {
- let modifiers_code = index + 2;
- bindings.extend(bindings!(
- KeyBinding;
- Delete, mods; Action::Esc(format!("\x1b[3;{}~", modifiers_code));
- Up, mods; Action::Esc(format!("\x1b[1;{}A", modifiers_code));
- Down, mods; Action::Esc(format!("\x1b[1;{}B", modifiers_code));
- Right, mods; Action::Esc(format!("\x1b[1;{}C", modifiers_code));
- Left, mods; Action::Esc(format!("\x1b[1;{}D", modifiers_code));
- F1, mods; Action::Esc(format!("\x1b[1;{}P", modifiers_code));
- F2, mods; Action::Esc(format!("\x1b[1;{}Q", modifiers_code));
- F3, mods; Action::Esc(format!("\x1b[1;{}R", modifiers_code));
- F4, mods; Action::Esc(format!("\x1b[1;{}S", modifiers_code));
- F5, mods; Action::Esc(format!("\x1b[15;{}~", modifiers_code));
- F6, mods; Action::Esc(format!("\x1b[17;{}~", modifiers_code));
- F7, mods; Action::Esc(format!("\x1b[18;{}~", modifiers_code));
- F8, mods; Action::Esc(format!("\x1b[19;{}~", modifiers_code));
- F9, mods; Action::Esc(format!("\x1b[20;{}~", modifiers_code));
- F10, mods; Action::Esc(format!("\x1b[21;{}~", modifiers_code));
- F11, mods; Action::Esc(format!("\x1b[23;{}~", modifiers_code));
- F12, mods; Action::Esc(format!("\x1b[24;{}~", modifiers_code));
- F13, mods; Action::Esc(format!("\x1b[25;{}~", modifiers_code));
- F14, mods; Action::Esc(format!("\x1b[26;{}~", modifiers_code));
- F15, mods; Action::Esc(format!("\x1b[28;{}~", modifiers_code));
- F16, mods; Action::Esc(format!("\x1b[29;{}~", modifiers_code));
- F17, mods; Action::Esc(format!("\x1b[31;{}~", modifiers_code));
- F18, mods; Action::Esc(format!("\x1b[32;{}~", modifiers_code));
- F19, mods; Action::Esc(format!("\x1b[33;{}~", modifiers_code));
- F20, mods; Action::Esc(format!("\x1b[34;{}~", modifiers_code));
- ));
-
- // We're adding the following bindings with `Shift` manually above, so skipping them here
- // modifiers_code != Shift
- if modifiers_code != 2 {
- bindings.extend(bindings!(
- KeyBinding;
- Insert, mods; Action::Esc(format!("\x1b[2;{}~", modifiers_code));
- PageUp, mods; Action::Esc(format!("\x1b[5;{}~", modifiers_code));
- PageDown, mods; Action::Esc(format!("\x1b[6;{}~", modifiers_code));
- End, mods; Action::Esc(format!("\x1b[1;{}F", modifiers_code));
- Home, mods; Action::Esc(format!("\x1b[1;{}H", modifiers_code));
- ));
- }
- }
-
- bindings.extend(platform_key_bindings());
-
- bindings
-}
-
-#[cfg(not(any(target_os = "macos", test)))]
-fn common_keybindings() -> Vec<KeyBinding> {
- bindings!(
- KeyBinding;
- V, ModifiersState::CTRL | ModifiersState::SHIFT; Action::Paste;
- C, ModifiersState::CTRL | ModifiersState::SHIFT; Action::Copy;
- Insert, ModifiersState::SHIFT; Action::PasteSelection;
- Key0, ModifiersState::CTRL; Action::ResetFontSize;
- Equals, ModifiersState::CTRL; Action::IncreaseFontSize;
- Add, ModifiersState::CTRL; Action::IncreaseFontSize;
- Subtract, ModifiersState::CTRL; Action::DecreaseFontSize;
- Minus, ModifiersState::CTRL; Action::DecreaseFontSize;
- )
-}
-
-#[cfg(not(any(target_os = "macos", target_os = "windows", test)))]
-pub fn platform_key_bindings() -> Vec<KeyBinding> {
- common_keybindings()
-}
-
-#[cfg(all(target_os = "windows", not(test)))]
-pub fn platform_key_bindings() -> Vec<KeyBinding> {
- let mut bindings = bindings!(
- KeyBinding;
- Return, ModifiersState::ALT; Action::ToggleFullscreen;
- );
- bindings.extend(common_keybindings());
- bindings
-}
-
-#[cfg(all(target_os = "macos", not(test)))]
-pub fn platform_key_bindings() -> Vec<KeyBinding> {
- bindings!(
- KeyBinding;
- Key0, ModifiersState::LOGO; Action::ResetFontSize;
- Equals, ModifiersState::LOGO; Action::IncreaseFontSize;
- Add, ModifiersState::LOGO; Action::IncreaseFontSize;
- Minus, ModifiersState::LOGO; Action::DecreaseFontSize;
- Insert, ModifiersState::SHIFT; Action::Esc("\x1b[2;2~".into());
- F, ModifiersState::CTRL | ModifiersState::LOGO; Action::ToggleFullscreen;
- K, ModifiersState::LOGO; Action::ClearHistory;
- K, ModifiersState::LOGO; Action::Esc("\x0c".into());
- V, ModifiersState::LOGO; Action::Paste;
- C, ModifiersState::LOGO; Action::Copy;
- H, ModifiersState::LOGO; Action::Hide;
- M, ModifiersState::LOGO; Action::Minimize;
- Q, ModifiersState::LOGO; Action::Quit;
- W, ModifiersState::LOGO; Action::Quit;
- )
-}
-
-// Don't return any bindings for tests since they are commented-out by default
-#[cfg(test)]
-pub fn platform_key_bindings() -> Vec<KeyBinding> {
- vec![]
-}
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
-pub enum Key {
- Scancode(u32),
- Keycode(VirtualKeyCode),
-}
-
-impl<'a> Deserialize<'a> for Key {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'a>,
- {
- let value = serde_yaml::Value::deserialize(deserializer)?;
- match u32::deserialize(value.clone()) {
- Ok(scancode) => Ok(Key::Scancode(scancode)),
- Err(_) => {
- let keycode = VirtualKeyCode::deserialize(value).map_err(D::Error::custom)?;
- Ok(Key::Keycode(keycode))
- },
- }
- }
-}
-
-struct ModeWrapper {
- pub mode: TermMode,
- pub not_mode: TermMode,
-}
-
-impl<'a> Deserialize<'a> for ModeWrapper {
- fn deserialize<D>(deserializer: D) -> 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 | Alt, possibly with negation (~)",
- )
- }
-
- fn visit_str<E>(self, value: &str) -> 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.mode |= TermMode::ALT_SCREEN,
- "~alt" => res.not_mode |= TermMode::ALT_SCREEN,
- _ => error!(target: LOG_TARGET_CONFIG, "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) -> 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) -> 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) -> 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) -> 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) -> 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) -> Result<Field, D::Error>
- where
- D: Deserializer<'a>,
- {
- struct FieldVisitor;
-
- static FIELDS: &[&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) -> 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) -> 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<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) -> 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) -> 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) -> 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) -> Result<ModsWrapper, E>
- where
- E: de::Error,
- {
- let mut res = ModifiersState::empty();
- for modifier in value.split('|') {
- match modifier.trim().to_lowercase().as_str() {
- "command" | "super" => res.insert(ModifiersState::LOGO),
- "shift" => res.insert(ModifiersState::SHIFT),
- "alt" | "option" => res.insert(ModifiersState::ALT),
- "control" => res.insert(ModifiersState::CTRL),
- "none" => (),
- _ => error!(target: LOG_TARGET_CONFIG, "Unknown modifier {:?}", modifier),
- }
- }
-
- Ok(ModsWrapper(res))
- }
- }
-
- deserializer.deserialize_str(ModsVisitor)
- }
-}
-
-#[cfg(test)]
-mod test {
- use glutin::event::ModifiersState;
-
- use alacritty_terminal::term::TermMode;
-
- use crate::config::{Action, Binding};
-
- type MockBinding = Binding<usize>;
-
- impl Default for MockBinding {
- fn default() -> Self {
- Self {
- mods: Default::default(),
- action: Default::default(),
- mode: TermMode::empty(),
- notmode: TermMode::empty(),
- trigger: Default::default(),
- }
- }
- }
-
- #[test]
- fn binding_matches_itself() {
- let binding = MockBinding::default();
- let identical_binding = MockBinding::default();
-
- assert!(binding.triggers_match(&identical_binding));
- assert!(identical_binding.triggers_match(&binding));
- }
-
- #[test]
- fn binding_matches_different_action() {
- let binding = MockBinding::default();
- let mut different_action = MockBinding::default();
- different_action.action = Action::ClearHistory;
-
- assert!(binding.triggers_match(&different_action));
- assert!(different_action.triggers_match(&binding));
- }
-
- #[test]
- fn mods_binding_requires_strict_match() {
- let mut superset_mods = MockBinding::default();
- superset_mods.mods = ModifiersState::all();
- let mut subset_mods = MockBinding::default();
- subset_mods.mods = ModifiersState::ALT;
-
- assert!(!superset_mods.triggers_match(&subset_mods));
- assert!(!subset_mods.triggers_match(&superset_mods));
- }
-
- #[test]
- fn binding_matches_identical_mode() {
- let mut b1 = MockBinding::default();
- b1.mode = TermMode::ALT_SCREEN;
- let mut b2 = MockBinding::default();
- b2.mode = TermMode::ALT_SCREEN;
-
- assert!(b1.triggers_match(&b2));
- }
-
- #[test]
- fn binding_without_mode_matches_any_mode() {
- let b1 = MockBinding::default();
- let mut b2 = MockBinding::default();
- b2.mode = TermMode::APP_KEYPAD;
- b2.notmode = TermMode::ALT_SCREEN;
-
- assert!(b1.triggers_match(&b2));
- }
-
- #[test]
- fn binding_with_mode_matches_empty_mode() {
- let mut b1 = MockBinding::default();
- b1.mode = TermMode::APP_KEYPAD;
- b1.notmode = TermMode::ALT_SCREEN;
- let b2 = MockBinding::default();
-
- assert!(b1.triggers_match(&b2));
- }
-
- #[test]
- fn binding_matches_superset_mode() {
- let mut b1 = MockBinding::default();
- b1.mode = TermMode::APP_KEYPAD;
- let mut b2 = MockBinding::default();
- b2.mode = TermMode::ALT_SCREEN | TermMode::APP_KEYPAD;
-
- assert!(b1.triggers_match(&b2));
- }
-
- #[test]
- fn binding_matches_subset_mode() {
- let mut b1 = MockBinding::default();
- b1.mode = TermMode::ALT_SCREEN | TermMode::APP_KEYPAD;
- let mut b2 = MockBinding::default();
- b2.mode = TermMode::APP_KEYPAD;
-
- assert!(b1.triggers_match(&b2));
- }
-
- #[test]
- fn binding_matches_partial_intersection() {
- let mut b1 = MockBinding::default();
- b1.mode = TermMode::ALT_SCREEN | TermMode::APP_KEYPAD;
- let mut b2 = MockBinding::default();
- b2.mode = TermMode::APP_KEYPAD | TermMode::APP_CURSOR;
-
- assert!(b1.triggers_match(&b2));
- }
-
- #[test]
- fn binding_mismatches_notmode() {
- let mut b1 = MockBinding::default();
- b1.mode = TermMode::ALT_SCREEN;
- let mut b2 = MockBinding::default();
- b2.notmode = TermMode::ALT_SCREEN;
-
- assert!(!b1.triggers_match(&b2));
- }
-
- #[test]
- fn binding_mismatches_unrelated() {
- let mut b1 = MockBinding::default();
- b1.mode = TermMode::ALT_SCREEN;
- let mut b2 = MockBinding::default();
- b2.mode = TermMode::APP_KEYPAD;
-
- assert!(!b1.triggers_match(&b2));
- }
-
- #[test]
- fn binding_trigger_input() {
- let mut binding = MockBinding::default();
- binding.trigger = 13;
-
- let mods = binding.mods;
- let mode = binding.mode;
-
- assert!(binding.is_triggered_by(mode, mods, &13));
- assert!(!binding.is_triggered_by(mode, mods, &32));
- }
-
- #[test]
- fn binding_trigger_mods() {
- let mut binding = MockBinding::default();
- binding.mods = ModifiersState::ALT | ModifiersState::LOGO;
-
- let superset_mods = ModifiersState::all();
- let subset_mods = ModifiersState::empty();
-
- let t = binding.trigger;
- let mode = binding.mode;
-
- assert!(binding.is_triggered_by(mode, binding.mods, &t));
- assert!(!binding.is_triggered_by(mode, superset_mods, &t));
- assert!(!binding.is_triggered_by(mode, subset_mods, &t));
- }
-
- #[test]
- fn binding_trigger_modes() {
- let mut binding = MockBinding::default();
- binding.mode = TermMode::ALT_SCREEN;
-
- let t = binding.trigger;
- let mods = binding.mods;
-
- assert!(!binding.is_triggered_by(TermMode::INSERT, mods, &t));
- assert!(binding.is_triggered_by(TermMode::ALT_SCREEN, mods, &t));
- assert!(binding.is_triggered_by(TermMode::ALT_SCREEN | TermMode::INSERT, mods, &t));
- }
-
- #[test]
- fn binding_trigger_notmodes() {
- let mut binding = MockBinding::default();
- binding.notmode = TermMode::ALT_SCREEN;
-
- let t = binding.trigger;
- let mods = binding.mods;
-
- assert!(binding.is_triggered_by(TermMode::INSERT, mods, &t));
- assert!(!binding.is_triggered_by(TermMode::ALT_SCREEN, mods, &t));
- assert!(!binding.is_triggered_by(TermMode::ALT_SCREEN | TermMode::INSERT, mods, &t));
- }
-}
diff --git a/alacritty/src/config/mod.rs b/alacritty/src/config/mod.rs
deleted file mode 100644
index 56e09be..0000000
--- a/alacritty/src/config/mod.rs
+++ /dev/null
@@ -1,228 +0,0 @@
-use std::env;
-use std::fmt::{self, Display, Formatter};
-use std::fs;
-use std::io;
-use std::path::PathBuf;
-
-#[cfg(windows)]
-use dirs;
-use log::{error, warn};
-use serde_yaml;
-#[cfg(not(windows))]
-use xdg;
-
-use alacritty_terminal::config::{Config as TermConfig, LOG_TARGET_CONFIG};
-
-mod bindings;
-pub mod monitor;
-mod mouse;
-mod ui_config;
-
-pub use crate::config::bindings::{Action, Binding, Key};
-#[cfg(test)]
-pub use crate::config::mouse::{ClickHandler, Mouse};
-use crate::config::ui_config::UIConfig;
-
-pub type Config = TermConfig<UIConfig>;
-
-/// 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 source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- match self {
- Error::NotFound => None,
- Error::ReadingEnvHome(err) => err.source(),
- Error::Io(err) => err.source(),
- Error::Yaml(err) => err.source(),
- }
- }
-}
-
-impl Display for Error {
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- match self {
- Error::NotFound => write!(f, "Couldn't locate config file"),
- Error::ReadingEnvHome(err) => {
- write!(f, "Couldn't read $HOME environment variable: {}", err)
- },
- Error::Io(err) => write!(f, "Error reading config file: {}", err),
- Error::Yaml(err) => write!(f, "Problem with config: {}", err),
- }
- }
-}
-
-impl From<env::VarError> for Error {
- fn from(val: env::VarError) -> Self {
- Error::ReadingEnvHome(val)
- }
-}
-
-impl From<io::Error> for Error {
- fn from(val: io::Error) -> Self {
- 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) -> Self {
- 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() -> Option<PathBuf> {
- // 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
- })
-}
-
-#[cfg(windows)]
-pub fn installed_config() -> Option<PathBuf> {
- dirs::config_dir().map(|path| path.join("alacritty\\alacritty.yml")).filter(|new| new.exists())
-}
-
-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!(target: LOG_TARGET_CONFIG, "Unable to load config {:?}: {}", path, err);
- Err(err)
- },
- }
-}
-
-fn read_config(path: &PathBuf) -> Result<Config> {
- let mut contents = fs::read_to_string(path)?;
-
- // Remove UTF-8 BOM
- if contents.starts_with('\u{FEFF}') {
- contents = contents.split_off(3);
- }
-
- parse_config(&contents)
-}
-
-fn parse_config(contents: &str) -> Result<Config> {
- match serde_yaml::from_str(contents) {
- Err(error) => {
- // Prevent parsing error with an empty string and commented out file.
- if error.to_string() == "EOF while parsing a value" {
- Ok(Config::default())
- } else {
- Err(Error::Yaml(error))
- }
- },
- Ok(config) => {
- print_deprecation_warnings(&config);
- Ok(config)
- },
- }
-}
-
-fn print_deprecation_warnings(config: &Config) {
- if config.window.start_maximized.is_some() {
- warn!(
- target: LOG_TARGET_CONFIG,
- "Config window.start_maximized is deprecated; please use window.startup_mode instead"
- );
- }
-
- if config.render_timer.is_some() {
- warn!(
- target: LOG_TARGET_CONFIG,
- "Config render_timer is deprecated; please use debug.render_timer instead"
- );
- }
-
- if config.persistent_logging.is_some() {
- warn!(
- target: LOG_TARGET_CONFIG,
- "Config persistent_logging is deprecated; please use debug.persistent_logging instead"
- );
- }
-
- if config.scrolling.faux_multiplier().is_some() {
- warn!(
- target: LOG_TARGET_CONFIG,
- "Config scrolling.faux_multiplier is deprecated; the alternate scroll escape can now \
- be used to disable it and `scrolling.multiplier` controls the number of scrolled \
- lines"
- );
- }
-
- if config.scrolling.auto_scroll.is_some() {
- warn!(
- target: LOG_TARGET_CONFIG,
- "Config scrolling.auto_scroll has been removed and is now always disabled, it can be \
- safely removed from the config"
- );
- }
-}
-
-#[cfg(test)]
-mod test {
- static DEFAULT_ALACRITTY_CONFIG: &str =
- include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../alacritty.yml"));
-
- use super::Config;
-
- #[test]
- fn config_read_eof() {
- assert_eq!(super::parse_config(DEFAULT_ALACRITTY_CONFIG).unwrap(), Config::default());
- }
-}
diff --git a/alacritty/src/config/monitor.rs b/alacritty/src/config/monitor.rs
deleted file mode 100644
index 8dc5379..0000000
--- a/alacritty/src/config/monitor.rs
+++ /dev/null
@@ -1,58 +0,0 @@
-use std::path::PathBuf;
-use std::sync::mpsc;
-use std::time::Duration;
-
-use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
-
-use alacritty_terminal::event::{Event, EventListener};
-use alacritty_terminal::util;
-
-use crate::event::EventProxy;
-
-pub struct Monitor {
- _thread: ::std::thread::JoinHandle<()>,
-}
-
-impl Monitor {
- pub fn new<P>(path: P, event_proxy: EventProxy) -> Monitor
- where
- P: Into<PathBuf>,
- {
- let path = path.into();
-
- Monitor {
- _thread: 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;
- }
-
- event_proxy.send_event(Event::ConfigReload(path));
- },
- _ => {},
- }
- }
- }),
- }
- }
-}
diff --git a/alacritty/src/config/mouse.rs b/alacritty/src/config/mouse.rs
deleted file mode 100644
index b7832b4..0000000
--- a/alacritty/src/config/mouse.rs
+++ /dev/null
@@ -1,115 +0,0 @@
-use std::time::Duration;
-
-use glutin::event::ModifiersState;
-use log::error;
-use serde::{Deserialize, Deserializer};
-
-use alacritty_terminal::config::{failure_default, LOG_TARGET_CONFIG};
-
-use crate::config::bindings::{CommandWrapper, ModsWrapper};
-
-#[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!(
- target: LOG_TARGET_CONFIG,
- "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!(target: LOG_TARGET_CONFIG, "Problem with config: {}; using default value", err);
- Ok(default_threshold_ms())
- },
- }
-}
diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs
deleted file mode 100644
index d7a477a..0000000
--- a/alacritty/src/config/ui_config.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-use serde::{Deserialize, Deserializer};
-
-use alacritty_terminal::config::failure_default;
-
-use crate::config::bindings::{self, Binding, KeyBinding, MouseBinding};
-use crate::config::mouse::Mouse;
-
-#[derive(Debug, PartialEq, Deserialize)]
-pub struct UIConfig {
- #[serde(default, deserialize_with = "failure_default")]
- pub mouse: Mouse,
-
- /// Keybindings
- #[serde(default = "default_key_bindings", deserialize_with = "deserialize_key_bindings")]
- pub key_bindings: Vec<KeyBinding>,
-
- /// Bindings for the mouse
- #[serde(default = "default_mouse_bindings", deserialize_with = "deserialize_mouse_bindings")]
- pub mouse_bindings: Vec<MouseBinding>,
-}
-
-impl Default for UIConfig {
- fn default() -> Self {
- UIConfig {
- mouse: Mouse::default(),
- key_bindings: default_key_bindings(),
- mouse_bindings: default_mouse_bindings(),
- }
- }
-}
-
-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) -> Result<Vec<KeyBinding>, D::Error>
-where
- D: Deserializer<'a>,
-{
- deserialize_bindings(deserializer, bindings::default_key_bindings())
-}
-
-fn deserialize_mouse_bindings<'a, D>(deserializer: D) -> Result<Vec<MouseBinding>, D::Error>
-where
- D: Deserializer<'a>,
-{
- deserialize_bindings(deserializer, bindings::default_mouse_bindings())
-}
-
-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,
- Binding<T>: Deserialize<'a>,
-{
- let mut bindings: Vec<Binding<T>> = failure_default(deserializer)?;
-
- // Remove matching default bindings
- for binding in bindings.iter() {
- default.retain(|b| !b.triggers_match(binding));
- }
-
- bindings.extend(default);
-
- Ok(bindings)
-}
diff --git a/alacritty/src/cursor.rs b/alacritty/src/cursor.rs
deleted file mode 100644
index a3e6a2c..0000000
--- a/alacritty/src/cursor.rs
+++ /dev/null
@@ -1,113 +0,0 @@
-// 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.
-
-//! Helpers for creating different cursor glyphs from font metrics
-
-use std::cmp;
-
-use alacritty_terminal::ansi::CursorStyle;
-
-use font::{BitmapBuffer, Metrics, RasterizedGlyph};
-
-/// Width/Height of the cursor relative to the font width
-pub const CURSOR_WIDTH_PERCENTAGE: i32 = 15;
-
-pub fn get_cursor_glyph(
- cursor: CursorStyle,
- metrics: Metrics,
- offset_x: i8,
- offset_y: i8,
- is_wide: bool,
-) -> RasterizedGlyph {
- // Calculate the cell metrics
- let height = metrics.line_height as i32 + i32::from(offset_y);
- let mut width = metrics.average_advance as i32 + i32::from(offset_x);
- let line_width = cmp::max(width * CURSOR_WIDTH_PERCENTAGE / 100, 1);
-
- // Double the cursor width if it's above a double-width glyph
- if is_wide {
- width *= 2;
- }
-
- match cursor {
- CursorStyle::HollowBlock => get_box_cursor_glyph(height, width, line_width),
- CursorStyle::Underline => get_underline_cursor_glyph(width, line_width),
- CursorStyle::Beam => get_beam_cursor_glyph(height, line_width),
- CursorStyle::Block => get_block_cursor_glyph(height, width),
- CursorStyle::Hidden => RasterizedGlyph::default(),
- }
-}
-
-// Returns a custom underline cursor character
-pub fn get_underline_cursor_glyph(width: i32, line_width: i32) -> RasterizedGlyph {
- // Create a new rectangle, the height is relative to the font width
- let buf = vec![255u8; (width * line_width * 3) as usize];
-
- // Create a custom glyph with the rectangle data attached to it
- RasterizedGlyph {
- c: ' ',
- top: line_width,
- left: 0,
- height: line_width,
- width,
- buf: BitmapBuffer::RGB(buf),
- }
-}
-
-// Returns a custom beam cursor character
-pub fn get_beam_cursor_glyph(height: i32, line_width: i32) -> RasterizedGlyph {
- // Create a new rectangle that is at least one pixel wide
- let buf = vec![255u8; (line_width * height * 3) as usize];
-
- // Create a custom glyph with the rectangle data attached to it
- RasterizedGlyph {
- c: ' ',
- top: height,
- left: 0,
- height,
- width: line_width,
- buf: BitmapBuffer::RGB(buf),
- }
-}
-
-// Returns a custom box cursor character
-pub fn get_box_cursor_glyph(height: i32, width: i32, line_width: i32) -> RasterizedGlyph {
- // Create a new box outline rectangle
- let mut buf = Vec::with_capacity((width * height * 3) as usize);
- for y in 0..height {
- for x in 0..width {
- if y < line_width
- || y >= height - line_width
- || x < line_width
- || x >= width - line_width
- {
- buf.append(&mut vec![255u8; 3]);
- } else {
- buf.append(&mut vec![0u8; 3]);
- }
- }
- }
-
- // Create a custom glyph with the rectangle data attached to it
- RasterizedGlyph { c: ' ', top: height, left: 0, height, width, buf: BitmapBuffer::RGB(buf) }
-}
-
-// Returns a custom block cursor character
-pub fn get_block_cursor_glyph(height: i32, width: i32) -> RasterizedGlyph {
- // Create a completely filled glyph
- let buf = vec![255u8; (width * height * 3) as usize];
-
- // Create a custom glyph with the rectangle data attached to it
- RasterizedGlyph { c: ' ', top: height, left: 0, height, width, buf: BitmapBuffer::RGB(buf) }
-}
diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs
deleted file mode 100644
index 317c875..0000000
--- a/alacritty/src/display.rs
+++ /dev/null
@@ -1,496 +0,0 @@
-// 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.
-
-//! The display subsystem including window management, font rasterization, and
-//! GPU drawing.
-use std::f64;
-use std::fmt::{self, Formatter};
-use std::time::Instant;
-
-use glutin::dpi::{PhysicalPosition, PhysicalSize};
-use glutin::event::ModifiersState;
-use glutin::event_loop::EventLoop;
-#[cfg(not(any(target_os = "macos", windows)))]
-use glutin::platform::unix::EventLoopWindowTargetExtUnix;
-use glutin::window::CursorIcon;
-use log::{debug, info};
-use parking_lot::MutexGuard;
-
-use font::{self, Rasterize};
-
-use alacritty_terminal::config::{Font, StartupMode};
-use alacritty_terminal::event::{Event, OnResize};
-use alacritty_terminal::index::Line;
-use alacritty_terminal::message_bar::MessageBuffer;
-use alacritty_terminal::meter::Meter;
-use alacritty_terminal::selection::Selection;
-use alacritty_terminal::term::color::Rgb;
-use alacritty_terminal::term::{RenderableCell, SizeInfo, Term, TermMode};
-
-use crate::config::Config;
-use crate::event::{DisplayUpdate, Mouse};
-use crate::renderer::rects::{RenderLines, RenderRect};
-use crate::renderer::{self, GlyphCache, QuadRenderer};
-use crate::url::{Url, Urls};
-use crate::window::{self, Window};
-
-#[derive(Debug)]
-pub enum Error {
- /// Error with window management
- Window(window::Error),
-
- /// Error dealing with fonts
- Font(font::Error),
-
- /// Error in renderer
- Render(renderer::Error),
-
- /// Error during buffer swap
- ContextError(glutin::ContextError),
-}
-
-impl std::error::Error for Error {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- match self {
- Error::Window(err) => err.source(),
- Error::Font(err) => err.source(),
- Error::Render(err) => err.source(),
- Error::ContextError(err) => err.source(),
- }
- }
-}
-
-impl fmt::Display for Error {
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- match self {
- Error::Window(err) => err.fmt(f),
- Error::Font(err) => err.fmt(f),
- Error::Render(err) => err.fmt(f),
- Error::ContextError(err) => err.fmt(f),
- }
- }
-}
-
-impl From<window::Error> for Error {
- fn from(val: window::Error) -> Self {
- Error::Window(val)
- }
-}
-
-impl From<font::Error> for Error {
- fn from(val: font::Error) -> Self {
- Error::Font(val)
- }
-}
-
-impl From<renderer::Error> for Error {
- fn from(val: renderer::Error) -> Self {
- Error::Render(val)
- }
-}
-
-impl From<glutin::ContextError> for Error {
- fn from(val: glutin::ContextError) -> Self {
- Error::ContextError(val)
- }
-}
-
-/// The display wraps a window, font rasterizer, and GPU renderer
-pub struct Display {
- pub size_info: SizeInfo,
- pub window: Window,
- pub urls: Urls,
-
- /// Currently highlighted URL.
- pub highlighted_url: Option<Url>,
-
- renderer: QuadRenderer,
- glyph_cache: GlyphCache,
- meter: Meter,
-}
-
-impl Display {
- pub fn new(config: &Config, event_loop: &EventLoop<Event>) -> Result<Display, Error> {
- // Guess DPR based on first monitor
- let estimated_dpr =
- event_loop.available_monitors().next().map(|m| m.scale_factor()).unwrap_or(1.);
-
- // Guess the target window dimensions
- let metrics = GlyphCache::static_metrics(config.font.clone(), estimated_dpr)?;
- let (cell_width, cell_height) = compute_cell_size(config, &metrics);
- let dimensions =
- GlyphCache::calculate_dimensions(config, estimated_dpr, cell_width, cell_height);
-
- debug!("Estimated DPR: {}", estimated_dpr);
- debug!("Estimated Cell Size: {} x {}", cell_width, cell_height);
- debug!("Estimated Dimensions: {:?}", dimensions);
-
- // Create the window where Alacritty will be displayed
- let size = dimensions.map(|(width, height)| PhysicalSize::new(width, height));
-
- // Spawn window
- let mut window = Window::new(event_loop, &config, size)?;
-
- let dpr = window.scale_factor();
- info!("Device pixel ratio: {}", dpr);
-
- // get window properties for initializing the other subsystems
- let viewport_size = window.inner_size();
-
- // Create renderer
- let mut renderer = QuadRenderer::new()?;
-
- let (glyph_cache, cell_width, cell_height) =
- Self::new_glyph_cache(dpr, &mut renderer, config)?;
-
- 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 let Some((width, height)) =
- GlyphCache::calculate_dimensions(config, dpr, cell_width, cell_height)
- {
- let PhysicalSize { width: w, height: h } = window.inner_size();
- if w == width && h == height {
- info!("Estimated DPR correctly, skipping resize");
- } else {
- window.set_inner_size(PhysicalSize::new(width, height));
- }
- } else if config.window.dynamic_padding {
- // Make sure additional padding is spread evenly
- padding_x = dynamic_padding(padding_x, viewport_size.width as f32, cell_width);
- padding_y = dynamic_padding(padding_y, viewport_size.height as f32, cell_height);
- }
-
- padding_x = padding_x.floor();
- padding_y = padding_y.floor();
-
- info!("Cell Size: {} x {}", cell_width, cell_height);
- info!("Padding: {} x {}", padding_x, padding_y);
-
- // Create new size with at least one column and row
- let size_info = SizeInfo {
- dpr,
- width: (viewport_size.width as f32).max(cell_width + 2. * padding_x),
- height: (viewport_size.height as f32).max(cell_height + 2. * padding_y),
- cell_width,
- cell_height,
- padding_x,
- padding_y,
- };
-
- // Update OpenGL projection
- renderer.resize(&size_info);
-
- // Clear screen
- let background_color = config.colors.primary.background;
- renderer.with_api(&config, &size_info, |api| {
- api.clear(background_color);
- });
-
- // We should call `clear` when window is offscreen, so when `window.show()` happens it
- // would be with background color instead of uninitialized surface.
- #[cfg(not(any(target_os = "macos", windows)))]
- {
- // On Wayland we can safely ignore this call, since the window isn't visible until you
- // actually draw something into it.
- if event_loop.is_x11() {
- window.swap_buffers()
- }
- }
-
- window.set_visible(true);
-
- // Set window position
- //
- // TODO: replace `set_position` with `with_position` once available
- // Upstream issue: https://github.com/rust-windowing/winit/issues/806
- if let Some(position) = config.window.position {
- window.set_outer_position(PhysicalPosition::from((position.x, position.y)));
- }
-
- #[allow(clippy::single_match)]
- match config.window.startup_mode() {
- StartupMode::Fullscreen => window.set_fullscreen(true),
- #[cfg(target_os = "macos")]
- StartupMode::SimpleFullscreen => window.set_simple_fullscreen(true),
- #[cfg(not(any(target_os = "macos", windows)))]
- StartupMode::Maximized => window.set_maximized(true),
- _ => (),
- }
-
- Ok(Self {
- window,
- renderer,
- glyph_cache,
- meter: Meter::new(),
- size_info,
- urls: Urls::new(),
- highlighted_url: None,
- })
- }
-
- fn new_glyph_cache(
- dpr: f64,
- renderer: &mut QuadRenderer,
- config: &Config,
- ) -> Result<(GlyphCache, f32, f32), Error> {
- let font = config.font.clone();
- let rasterizer = font::Rasterizer::new(dpr as f32, config.font.use_thin_strokes())?;
-
- // Initialize glyph cache
- let glyph_cache = {
- info!("Initializing glyph cache...");
- let init_start = Instant::now();
-
- let cache =
- renderer.with_loader(|mut api| GlyphCache::new(rasterizer, &font, &mut api))?;
-
- let stop = init_start.elapsed();
- let stop_f = stop.as_secs() as f64 + f64::from(stop.subsec_nanos()) / 1_000_000_000f64;
- info!("... finished initializing glyph cache in {}s", stop_f);
-
- cache
- };
-
- // Need font metrics to resize the window properly. This suggests to me the
- // font metrics should be computed before creating the window in the first
- // place so that a resize is not needed.
- let (cw, ch) = compute_cell_size(config, &glyph_cache.font_metrics());
-
- Ok((glyph_cache, cw, ch))
- }
-
- /// Update font size and cell dimensions
- fn update_glyph_cache(&mut self, config: &Config, font: Font) {
- let size_info = &mut self.size_info;
- let cache = &mut self.glyph_cache;
-
- self.renderer.with_loader(|mut api| {
- let _ = cache.update_font_size(font, size_info.dpr, &mut api);
- });
-
- // Update cell size
- let (cell_width, cell_height) = compute_cell_size(config, &self.glyph_cache.font_metrics());
- size_info.cell_width = cell_width;
- size_info.cell_height = cell_height;
- }
-
- /// Process update events
- pub fn handle_update<T>(
- &mut self,
- terminal: &mut Term<T>,
- pty_resize_handle: &mut dyn OnResize,
- message_buffer: &MessageBuffer,
- config: &Config,
- update_pending: DisplayUpdate,
- ) {
- // Update font size and cell dimensions
- if let Some(font) = update_pending.font {
- self.update_glyph_cache(config, font);
- }
-
- let cell_width = self.size_info.cell_width;
- let cell_height = self.size_info.cell_height;
-
- // Recalculate padding
- let mut padding_x = f32::from(config.window.padding.x) * self.size_info.dpr as f32;
- let mut padding_y = f32::from(config.window.padding.y) * self.size_info.dpr as f32;
-
- // Update the window dimensions
- if let Some(size) = update_pending.dimensions {
- // Ensure we have at least one column and row
- self.size_info.width = (size.width as f32).max(cell_width + 2. * padding_x);
- self.size_info.height = (size.height as f32).max(cell_height + 2. * padding_y);
- }
-
- // Distribute excess padding equally on all sides
- if config.window.dynamic_padding {
- padding_x = dynamic_padding(padding_x, self.size_info.width, cell_width);
- padding_y = dynamic_padding(padding_y, self.size_info.height, cell_height);
- }
-
- self.size_info.padding_x = padding_x.floor() as f32;
- self.size_info.padding_y = padding_y.floor() as f32;
-
- let mut pty_size = self.size_info;
-
- // Subtract message bar lines from pty size
- if let Some(message) = message_buffer.message() {
- let lines = message.text(&self.size_info).len();
- pty_size.height -= pty_size.cell_height * lines as f32;
- }
-
- // Resize PTY
- pty_resize_handle.on_resize(&pty_size);
-
- // Resize terminal
- terminal.resize(&pty_size);
-
- // Resize renderer
- let physical = PhysicalSize::new(self.size_info.width as u32, self.size_info.height as u32);
- self.window.resize(physical);
- self.renderer.resize(&self.size_info);
- }
-
- /// Draw the screen
- ///
- /// A reference to Term whose state is being drawn must be provided.
- ///
- /// This call may block if vsync is enabled
- pub fn draw<T>(
- &mut self,
- terminal: MutexGuard<'_, Term<T>>,
- message_buffer: &MessageBuffer,
- config: &Config,
- mouse: &Mouse,
- mods: ModifiersState,
- ) {
- let grid_cells: Vec<RenderableCell> = terminal.renderable_cells(config).collect();
- let visual_bell_intensity = terminal.visual_bell.intensity();
- let background_color = terminal.background_color();
- let metrics = self.glyph_cache.font_metrics();
- let glyph_cache = &mut self.glyph_cache;
- let size_info = self.size_info;
-
- let selection = !terminal.selection().as_ref().map(Selection::is_empty).unwrap_or(true);
- let mouse_mode = terminal.mode().intersects(TermMode::MOUSE_MODE);
-
- // Update IME position
- #[cfg(not(windows))]
- self.window.update_ime_position(&terminal, &self.size_info);
-
- // Drop terminal as early as possible to free lock
- drop(terminal);
-
- self.renderer.with_api(&config, &size_info, |api| {
- api.clear(background_color);
- });
-
- let mut lines = RenderLines::new();
- let mut urls = Urls::new();
-
- // Draw grid
- {
- let _sampler = self.meter.sampler();
-
- self.renderer.with_api(&config, &size_info, |mut api| {
- // Iterate over all non-empty cells in the grid
- for cell in grid_cells {
- // Update URL underlines
- urls.update(size_info.cols().0, cell);
-
- // Update underline/strikeout
- lines.update(cell);
-
- // Draw the cell
- api.render_cell(cell, glyph_cache);
- }
- });
- }
-
- let mut rects = lines.rects(&metrics, &size_info);
-
- // Update visible URLs
- self.urls = urls;
- if let Some(url) = self.urls.highlighted(config, mouse, mods, mouse_mode, selection) {
- rects.append(&mut url.rects(&metrics, &size_info));
-
- self.window.set_mouse_cursor(CursorIcon::Hand);
-
- self.highlighted_url = Some(url);
- } else if self.highlighted_url.is_some() {
- self.highlighted_url = None;
-
- if mouse_mode {
- self.window.set_mouse_cursor(CursorIcon::Default);
- } else {
- self.window.set_mouse_cursor(CursorIcon::Text);
- }
- }
-
- // Push visual bell after url/underline/strikeout rects
- if visual_bell_intensity != 0. {
- let visual_bell_rect = RenderRect::new(
- 0.,
- 0.,
- size_info.width,
- size_info.height,
- config.visual_bell.color,
- visual_bell_intensity as f32,
- );
- rects.push(visual_bell_rect);
- }
-
- if let Some(message) = message_buffer.message() {
- let text = message.text(&size_info);
-
- // Create a new rectangle for the background
- let start_line = size_info.lines().0 - text.len();
- let y = size_info.cell_height.mul_add(start_line as f32, size_info.padding_y);
- let message_bar_rect =
- RenderRect::new(0., y, size_info.width, size_info.height - y, message.color(), 1.);
-
- // Push message_bar in the end, so it'll be above all other content
- rects.push(message_bar_rect);
-
- // Draw rectangles
- self.renderer.draw_rects(&size_info, rects);
-
- // Relay messages to the user
- let mut offset = 1;
- for message_text in text.iter().rev() {
- self.renderer.with_api(&config, &size_info, |mut api| {
- api.render_string(
- &message_text,
- Line(size_info.lines().saturating_sub(offset)),
- glyph_cache,
- None,
- );
- });
- offset += 1;
- }
- } else {
- // Draw rectangles
- self.renderer.draw_rects(&size_info, rects);
- }
-
- // Draw render timer
- if config.render_timer() {
- let timing = format!("{:.3} usec", self.meter.average());
- let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 };
- self.renderer.with_api(&config, &size_info, |mut api| {
- api.render_string(&timing[..], size_info.lines() - 2, glyph_cache, Some(color));
- });
- }
-
- self.window.swap_buffers();
- }
-}
-
-/// Calculate padding to spread it evenly around the terminal content
-#[inline]
-fn dynamic_padding(padding: f32, dimension: f32, cell_dimension: f32) -> f32 {
- padding + ((dimension - 2. * padding) % cell_dimension) / 2.
-}
-
-/// Calculate the cell dimensions based on font metrics.
-#[inline]
-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);
- (
- ((metrics.average_advance + offset_x) as f32).floor().max(1.),
- ((metrics.line_height + offset_y) as f32).floor().max(1.),
- )
-}
diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs
deleted file mode 100644
index bfadbae..0000000
--- a/alacritty/src/event.rs
+++ /dev/null
@@ -1,686 +0,0 @@
-//! Process window events
-use std::borrow::Cow;
-use std::cmp::max;
-use std::env;
-#[cfg(unix)]
-use std::fs;
-use std::fs::File;
-use std::io::Write;
-use std::mem;
-use std::sync::Arc;
-use std::time::Instant;
-
-use glutin::dpi::PhysicalSize;
-use glutin::event::{ElementState, Event as GlutinEvent, ModifiersState, MouseButton, WindowEvent};
-use glutin::event_loop::{ControlFlow, EventLoop, EventLoopProxy};
-use glutin::platform::desktop::EventLoopExtDesktop;
-use log::{debug, info, warn};
-use serde_json as json;
-
-use font::Size;
-
-use alacritty_terminal::clipboard::ClipboardType;
-use alacritty_terminal::config::Font;
-use alacritty_terminal::config::LOG_TARGET_CONFIG;
-use alacritty_terminal::event::OnResize;
-use alacritty_terminal::event::{Event, EventListener, Notify};
-use alacritty_terminal::grid::Scroll;
-use alacritty_terminal::index::{Column, Line, Point, Side};
-use alacritty_terminal::message_bar::{Message, MessageBuffer};
-use alacritty_terminal::selection::Selection;
-use alacritty_terminal::sync::FairMutex;
-use alacritty_terminal::term::cell::Cell;
-use alacritty_terminal::term::{SizeInfo, Term};
-#[cfg(not(windows))]
-use alacritty_terminal::tty;
-use alacritty_terminal::util::{limit, start_daemon};
-
-use crate::cli::Options;
-use crate::config;
-use crate::config::Config;
-use crate::display::Display;
-use crate::input::{self, ActionContext as _, FONT_SIZE_STEP};
-use crate::window::Window;
-
-#[derive(Default, Clone, Debug, PartialEq)]
-pub struct DisplayUpdate {
- pub dimensions: Option<PhysicalSize<u32>>,
- pub message_buffer: Option<()>,
- pub font: Option<Font>,
-}
-
-impl DisplayUpdate {
- fn is_empty(&self) -> bool {
- self.dimensions.is_none() && self.font.is_none() && self.message_buffer.is_none()
- }
-}
-
-pub struct ActionContext<'a, N, T> {
- pub notifier: &'a mut N,
- pub terminal: &'a mut Term<T>,
- pub size_info: &'a mut SizeInfo,
- pub mouse: &'a mut Mouse,
- pub received_count: &'a mut usize,
- pub suppress_chars: &'a mut bool,
- pub modifiers: &'a mut ModifiersState,
- pub window: &'a mut Window,
- pub message_buffer: &'a mut MessageBuffer,
- pub display_update_pending: &'a mut DisplayUpdate,
- pub config: &'a mut Config,
- font_size: &'a mut Size,
-}
-
-impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionContext<'a, N, T> {
- fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, val: B) {
- self.notifier.notify(val);
- }
-
- fn size_info(&self) -> SizeInfo {
- *self.size_info
- }
-
- fn scroll(&mut self, scroll: Scroll) {
- self.terminal.scroll_display(scroll);
-
- if let ElementState::Pressed = self.mouse().left_button_state {
- let (x, y) = (self.mouse().x, self.mouse().y);
- let size_info = self.size_info();
- let point = size_info.pixels_to_coords(x, y);
- let cell_side = self.mouse().cell_side;
- self.update_selection(Point { line: point.line, col: point.col }, cell_side);
- }
- }
-
- fn copy_selection(&mut self, ty: ClipboardType) {
- if let Some(selected) = self.terminal.selection_to_string() {
- if !selected.is_empty() {
- self.terminal.clipboard().store(ty, selected);
- }
- }
- }
-
- fn selection_is_empty(&self) -> bool {
- self.terminal.selection().as_ref().map(Selection::is_empty).unwrap_or(true)
- }
-
- fn clear_selection(&mut self) {
- *self.terminal.selection_mut() = None;
- self.terminal.dirty = true;
- }
-
- fn update_selection(&mut self, point: Point, side: Side) {
- let point = self.terminal.visible_to_buffer(point);
-
- // Update selection if one exists
- if let Some(ref mut selection) = self.terminal.selection_mut() {
- selection.update(point, side);
- }
-
- self.terminal.dirty = true;
- }
-
- fn simple_selection(&mut self, point: Point, side: Side) {
- let point = self.terminal.visible_to_buffer(point);
- *self.terminal.selection_mut() = Some(Selection::simple(point, side));
- self.terminal.dirty = true;
- }
-
- fn block_selection(&mut self, point: Point, side: Side) {
- let point = self.terminal.visible_to_buffer(point);
- *self.terminal.selection_mut() = Some(Selection::block(point, side));
- self.terminal.dirty = true;
- }
-
- fn semantic_selection(&mut self, point: Point) {
- let point = self.terminal.visible_to_buffer(point);
- *self.terminal.selection_mut() = Some(Selection::semantic(point));
- self.terminal.dirty = true;
- }
-
- fn line_selection(&mut self, point: Point) {
- let point = self.terminal.visible_to_buffer(point);
- *self.terminal.selection_mut() = Some(Selection::lines(point));
- self.terminal.dirty = true;
- }
-
- fn mouse_coords(&self) -> Option<Point> {
- let x = self.mouse.x as usize;
- let y = self.mouse.y as usize;
-
- if self.size_info.contains_point(x, y) {
- Some(self.size_info.pixels_to_coords(x, y))
- } else {
- None
- }
- }
-
- #[inline]
- fn mouse_mut(&mut self) -> &mut Mouse {
- self.mouse
- }
-
- #[inline]
- fn mouse(&self) -> &Mouse {
- self.mouse
- }
-
- #[inline]
- fn received_count(&mut self) -> &mut usize {
- &mut self.received_count
- }
-
- #[inline]
- fn suppress_chars(&mut self) -> &mut bool {
- &mut self.suppress_chars
- }
-
- #[inline]
- fn modifiers(&mut self) -> &mut ModifiersState {
- &mut self.modifiers
- }
-
- #[inline]
- fn window(&self) -> &Window {
- self.window
- }
-
- #[inline]
- fn window_mut(&mut self) -> &mut Window {
- self.window
- }
-
- #[inline]
- fn terminal(&self) -> &Term<T> {
- self.terminal
- }
-
- #[inline]
- fn terminal_mut(&mut self) -> &mut Term<T> {
- self.terminal
- }
-
- fn spawn_new_instance(&mut self) {
- let alacritty = env::args().next().unwrap();
-
- #[cfg(unix)]
- let args = {
- #[cfg(not(target_os = "freebsd"))]
- let proc_prefix = "";
- #[cfg(target_os = "freebsd")]
- let proc_prefix = "/compat/linux";
- let link_path = format!("{}/proc/{}/cwd", proc_prefix, tty::child_pid());
- if let Ok(path) = fs::read_link(link_path) {
- vec!["--working-directory".into(), path]
- } else {
- Vec::new()
- }
- };
- #[cfg(not(unix))]
- let args: Vec<String> = Vec::new();
-
- match start_daemon(&alacritty, &args) {
- Ok(_) => debug!("Started new Alacritty process: {} {:?}", alacritty, args),
- Err(_) => warn!("Unable to start new Alacritty process: {} {:?}", alacritty, args),
- }
- }
-
- fn change_font_size(&mut self, delta: f32) {
- *self.font_size = max(*self.font_size + delta, Size::new(FONT_SIZE_STEP));
- let font = self.config.font.clone().with_size(*self.font_size);
- self.display_update_pending.font = Some(font);
- self.terminal.dirty = true;
- }
-
- fn reset_font_size(&mut self) {
- *self.font_size = self.config.font.size;
- self.display_update_pending.font = Some(self.config.font.clone());
- self.terminal.dirty = true;
- }
-
- fn pop_message(&mut self) {
- self.display_update_pending.message_buffer = Some(());
- self.message_buffer.pop();
- }
-
- fn message(&self) -> Option<&Message> {
- self.message_buffer.message()
- }
-
- fn config(&self) -> &Config {
- self.config
- }
-}
-
-pub enum ClickState {
- None,
- Click,
- DoubleClick,
- TripleClick,
-}
-
-/// State of the mouse
-pub struct Mouse {
- pub x: usize,
- pub y: usize,
- pub left_button_state: ElementState,
- pub middle_button_state: ElementState,
- pub right_button_state: ElementState,
- pub last_click_timestamp: Instant,
- pub click_state: ClickState,
- pub scroll_px: i32,
- pub line: Line,
- pub column: Column,
- pub cell_side: Side,
- pub lines_scrolled: f32,
- pub block_url_launcher: bool,
- pub last_button: MouseButton,
- pub inside_grid: bool,
-}
-
-impl Default for Mouse {
- fn default() -> Mouse {
- Mouse {
- x: 0,
- y: 0,
- last_click_timestamp: Instant::now(),
- left_button_state: ElementState::Released,
- middle_button_state: ElementState::Released,
- right_button_state: ElementState::Released,
- click_state: ClickState::None,
- scroll_px: 0,
- line: Line(0),
- column: Column(0),
- cell_side: Side::Left,
- lines_scrolled: 0.0,
- block_url_launcher: false,
- last_button: MouseButton::Other(0),
- inside_grid: false,
- }
- }
-}
-
-/// The event processor
-///
-/// Stores some state from received events and dispatches actions when they are
-/// triggered.
-pub struct Processor<N> {
- notifier: N,
- mouse: Mouse,
- received_count: usize,
- suppress_chars: bool,
- modifiers: ModifiersState,
- config: Config,
- message_buffer: MessageBuffer,
- display: Display,
- font_size: Size,
-}
-
-impl<N: Notify + OnResize> Processor<N> {
- /// Create a new event processor
- ///
- /// Takes a writer which is expected to be hooked up to the write end of a
- /// pty.
- pub fn new(
- notifier: N,
- message_buffer: MessageBuffer,
- config: Config,
- display: Display,
- ) -> Processor<N> {
- Processor {
- notifier,
- mouse: Default::default(),
- received_count: 0,
- suppress_chars: false,
- modifiers: Default::default(),
- font_size: config.font.size,
- config,
- message_buffer,
- display,
- }
- }
-
- /// Run the event loop.
- pub fn run<T>(&mut self, terminal: Arc<FairMutex<Term<T>>>, mut event_loop: EventLoop<Event>)
- where
- T: EventListener,
- {
- let mut event_queue = Vec::new();
-
- event_loop.run_return(|event, _event_loop, control_flow| {
- if self.config.debug.print_events {
- info!("glutin event: {:?}", event);
- }
-
- // Ignore all events we do not care about
- if Self::skip_event(&event) {
- return;
- }
-
- match event {
- // Check for shutdown
- GlutinEvent::UserEvent(Event::Exit) => {
- *control_flow = ControlFlow::Exit;
- return;
- },
- // Process events
- GlutinEvent::RedrawEventsCleared => {
- *control_flow = ControlFlow::Wait;
-
- if event_queue.is_empty() {
- return;
- }
- },
- // Remap DPR change event to remove lifetime
- GlutinEvent::WindowEvent {
- event: WindowEvent::ScaleFactorChanged { scale_factor, new_inner_size },
- ..
- } => {
- *control_flow = ControlFlow::Poll;
- let size = (new_inner_size.width, new_inner_size.height);
- let event = GlutinEvent::UserEvent(Event::DPRChanged(scale_factor, size));
- event_queue.push(event);
- return;
- },
- // Transmute to extend lifetime, which exists only for `ScaleFactorChanged` event.
- // Since we remap that event to remove the lifetime, this is safe.
- event => unsafe {
- *control_flow = ControlFlow::Poll;
- event_queue.push(mem::transmute(event));
- return;
- },
- }
-
- let mut terminal = terminal.lock();
-
- let mut display_update_pending = DisplayUpdate::default();
-
- let context = ActionContext {
- terminal: &mut terminal,
- notifier: &mut self.notifier,
- mouse: &mut self.mouse,
- size_info: &mut self.display.size_info,
- received_count: &mut self.received_count,
- suppress_chars: &mut self.suppress_chars,
- modifiers: &mut self.modifiers,
- message_buffer: &mut self.message_buffer,
- display_update_pending: &mut display_update_pending,
- window: &mut self.display.window,
- font_size: &mut self.font_size,
- config: &mut self.config,
- };
- let mut processor =
- input::Processor::new(context, &self.display.urls, &self.display.highlighted_url);
-
- for event in event_queue.drain(..) {
- Processor::handle_event(event, &mut processor);
- }
-
- // Process DisplayUpdate events
- if !display_update_pending.is_empty() {
- self.display.handle_update(
- &mut terminal,
- &mut self.notifier,
- &self.message_buffer,
- &self.config,
- display_update_pending,
- );
- }
-
- if terminal.dirty {
- terminal.dirty = false;
-
- // Request immediate re-draw if visual bell animation is not finished yet
- if !terminal.visual_bell.completed() {
- event_queue.push(GlutinEvent::UserEvent(Event::Wakeup));
- }
-
- // Redraw screen
- self.display.draw(
- terminal,
- &self.message_buffer,
- &self.config,
- &self.mouse,
- self.modifiers,
- );
- }
- });
-
- // Write ref tests to disk
- self.write_ref_test_results(&terminal.lock());
- }
-
- /// Handle events from glutin
- ///
- /// Doesn't take self mutably due to borrow checking.
- fn handle_event<T>(
- event: GlutinEvent<Event>,
- processor: &mut input::Processor<T, ActionContext<N, T>>,
- ) where
- T: EventListener,
- {
- match event {
- GlutinEvent::UserEvent(event) => match event {
- Event::DPRChanged(scale_factor, (width, height)) => {
- let display_update_pending = &mut processor.ctx.display_update_pending;
-
- // Push current font to update its DPR
- display_update_pending.font =
- Some(processor.ctx.config.font.clone().with_size(*processor.ctx.font_size));
-
- // Resize to event's dimensions, since no resize event is emitted on Wayland
- display_update_pending.dimensions = Some(PhysicalSize::new(width, height));
-
- processor.ctx.size_info.dpr = scale_factor;
- processor.ctx.terminal.dirty = true;
- },
- Event::Title(title) => processor.ctx.window.set_title(&title),
- Event::Wakeup => processor.ctx.terminal.dirty = true,
- Event::Urgent => {
- processor.ctx.window.set_urgent(!processor.ctx.terminal.is_focused)
- },
- Event::ConfigReload(path) => {
- processor.ctx.message_buffer.remove_target(LOG_TARGET_CONFIG);
- processor.ctx.display_update_pending.message_buffer = Some(());
-
- if let Ok(config) = config::reload_from(&path) {
- let options = Options::new();
- let config = options.into_config(config);
-
- processor.ctx.terminal.update_config(&config);
-
- if processor.ctx.config.font != config.font {
- // Do not update font size if it has been changed at runtime
- if *processor.ctx.font_size == processor.ctx.config.font.size {
- *processor.ctx.font_size = config.font.size;
- }
-
- let font = config.font.clone().with_size(*processor.ctx.font_size);
- processor.ctx.display_update_pending.font = Some(font);
- }
-
- *processor.ctx.config = config;
-
- processor.ctx.terminal.dirty = true;
- }
- },
- Event::Message(message) => {
- processor.ctx.message_buffer.push(message);
- processor.ctx.display_update_pending.message_buffer = Some(());
- processor.ctx.terminal.dirty = true;
- },
- Event::MouseCursorDirty => processor.reset_mouse_cursor(),
- Event::Exit => (),
- },
- GlutinEvent::RedrawRequested(_) => processor.ctx.terminal.dirty = true,
- GlutinEvent::WindowEvent { event, window_id, .. } => {
- use glutin::event::WindowEvent::*;
- match event {
- CloseRequested => processor.ctx.terminal.exit(),
- Resized(size) => {
- #[cfg(windows)]
- {
- // Minimizing the window sends a Resize event with zero width and
- // height. But there's no need to ever actually resize to this.
- // Both WinPTY & ConPTY have issues when resizing down to zero size
- // and back.
- if size.width == 0 && size.height == 0 {
- return;
- }
- }
-
- processor.ctx.display_update_pending.dimensions = Some(size);
- processor.ctx.terminal.dirty = true;
- },
- KeyboardInput { input, is_synthetic: false, .. } => {
- processor.key_input(input);
- if input.state == ElementState::Pressed {
- // Hide cursor while typing
- if processor.ctx.config.ui_config.mouse.hide_when_typing {
- processor.ctx.window.set_mouse_visible(false);
- }
- }
- },
- ReceivedCharacter(c) => processor.received_char(c),
- MouseInput { state, button, .. } => {
- if !cfg!(target_os = "macos") || processor.ctx.terminal.is_focused {
- processor.ctx.window.set_mouse_visible(true);
- processor.mouse_input(state, button);
- processor.ctx.terminal.dirty = true;
- }
- },
- CursorMoved { position, .. } => {
- let (x, y) = position.into();
- let x = limit(x, 0, processor.ctx.size_info.width as i32);
- let y = limit(y, 0, processor.ctx.size_info.height as i32);
-
- processor.ctx.window.set_mouse_visible(true);
- processor.mouse_moved(x as usize, y as usize);
- },
- MouseWheel { delta, phase, .. } => {
- processor.ctx.window.set_mouse_visible(true);
- processor.mouse_wheel_input(delta, phase);
- },
- Focused(is_focused) => {
- if window_id == processor.ctx.window.window_id() {
- processor.ctx.terminal.is_focused = is_focused;
- processor.ctx.terminal.dirty = true;
-
- if is_focused {
- processor.ctx.window.set_urgent(false);
- } else {
- processor.ctx.window.set_mouse_visible(true);
- }
-
- processor.on_focus_change(is_focused);
- }
- },
- DroppedFile(path) => {
- let path: String = path.to_string_lossy().into();
- processor.ctx.write_to_pty(path.into_bytes());
- },
- CursorLeft { .. } => {
- processor.ctx.mouse.inside_grid = false;
-
- if processor.highlighted_url.is_some() {
- processor.ctx.terminal.dirty = true;
- }
- },
- KeyboardInput { is_synthetic: true, .. }
- | TouchpadPressure { .. }
- | ScaleFactorChanged { .. }
- | CursorEntered { .. }
- | AxisMotion { .. }
- | HoveredFileCancelled
- | Destroyed
- | ThemeChanged(_)
- | HoveredFile(_)
- | Touch(_)
- | Moved(_) => (),
- }
- },
- GlutinEvent::DeviceEvent { event, .. } => {
- use glutin::event::DeviceEvent::*;
- if let ModifiersChanged(modifiers) = event {
- processor.modifiers_input(modifiers);
- }
- },
- GlutinEvent::Suspended { .. }
- | GlutinEvent::NewEvents { .. }
- | GlutinEvent::MainEventsCleared
- | GlutinEvent::RedrawEventsCleared
- | GlutinEvent::Resumed
- | GlutinEvent::LoopDestroyed => (),
- }
- }
-
- /// Check if an event is irrelevant and can be skipped
- fn skip_event(event: &GlutinEvent<Event>) -> bool {
- match event {
- GlutinEvent::WindowEvent { event, .. } => {
- use glutin::event::WindowEvent::*;
- match event {
- KeyboardInput { is_synthetic: true, .. }
- | TouchpadPressure { .. }
- | CursorEntered { .. }
- | AxisMotion { .. }
- | HoveredFileCancelled
- | Destroyed
- | HoveredFile(_)
- | Touch(_)
- | Moved(_) => true,
- _ => false,
- }
- },
- GlutinEvent::Suspended { .. }
- | GlutinEvent::NewEvents { .. }
- | GlutinEvent::MainEventsCleared
- | GlutinEvent::LoopDestroyed => true,
- _ => false,
- }
- }
-
- // Write the ref test results to the disk
- pub fn write_ref_test_results<T>(&self, terminal: &Term<T>) {
- if !self.config.debug.ref_test {
- return;
- }
-
- // dump grid state
- let mut grid = terminal.grid().clone();
- grid.initialize_all(&Cell::default());
- grid.truncate();
-
- let serialized_grid = json::to_string(&grid).expect("serialize grid");
-
- let serialized_size = json::to_string(&self.display.size_info).expect("serialize size");
-
- let serialized_config = format!("{{\"history_size\":{}}}", grid.history_size());
-
- File::create("./grid.json")
- .and_then(|mut f| f.write_all(serialized_grid.as_bytes()))
- .expect("write grid.json");
-
- File::create("./size.json")
- .and_then(|mut f| f.write_all(serialized_size.as_bytes()))
- .expect("write size.json");
-
- File::create("./config.json")
- .and_then(|mut f| f.write_all(serialized_config.as_bytes()))
- .expect("write config.json");
- }
-}
-
-#[derive(Debug, Clone)]
-pub struct EventProxy(EventLoopProxy<Event>);
-
-impl EventProxy {
- pub fn new(proxy: EventLoopProxy<Event>) -> Self {
- EventProxy(proxy)
- }
-}
-
-impl EventListener for EventProxy {
- fn send_event(&self, event: Event) {
- let _ = self.0.send_event(event);
- }
-}
diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs
deleted file mode 100644
index 018140a..0000000
--- a/alacritty/src/input.rs
+++ /dev/null
@@ -1,1146 +0,0 @@
-// 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.
-//
-//! Handle input from glutin
-//!
-//! Certain key combinations should send some escape sequence back to the pty.
-//! In order to figure that out, state about which modifier keys are pressed
-//! needs to be tracked. Additionally, we need a bit of a state machine to
-//! determine what to do when a non-modifier key is pressed.
-use std::borrow::Cow;
-use std::cmp::min;
-use std::cmp::Ordering;
-use std::marker::PhantomData;
-use std::time::Instant;
-
-use glutin::event::{
- ElementState, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase,
-};
-use glutin::window::CursorIcon;
-use log::{debug, trace, warn};
-
-use alacritty_terminal::ansi::{ClearMode, Handler};
-use alacritty_terminal::clipboard::ClipboardType;
-use alacritty_terminal::event::EventListener;
-use alacritty_terminal::grid::Scroll;
-use alacritty_terminal::index::{Column, Line, Point, Side};
-use alacritty_terminal::message_bar::{self, Message};
-use alacritty_terminal::selection::Selection;
-use alacritty_terminal::term::mode::TermMode;
-use alacritty_terminal::term::{SizeInfo, Term};
-use alacritty_terminal::util::start_daemon;
-
-use crate::config::{Action, Binding, Config, Key};
-use crate::event::{ClickState, Mouse};
-use crate::url::{Url, Urls};
-use crate::window::Window;
-
-/// Font size change interval
-pub const FONT_SIZE_STEP: f32 = 0.5;
-
-/// Processes input from glutin.
-///
-/// An escape sequence may be emitted in case specific keys or key combinations
-/// are activated.
-pub struct Processor<'a, T: EventListener, A: ActionContext<T>> {
- pub ctx: A,
- pub urls: &'a Urls,
- pub highlighted_url: &'a Option<Url>,
- _phantom: PhantomData<T>,
-}
-
-pub trait ActionContext<T: EventListener> {
- fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, _: B);
- fn size_info(&self) -> SizeInfo;
- fn copy_selection(&mut self, _: ClipboardType);
- fn clear_selection(&mut self);
- fn update_selection(&mut self, point: Point, side: Side);
- fn simple_selection(&mut self, point: Point, side: Side);
- fn block_selection(&mut self, point: Point, side: Side);
- fn semantic_selection(&mut self, point: Point);
- fn line_selection(&mut self, point: Point);
- fn selection_is_empty(&self) -> bool;
- fn mouse_mut(&mut self) -> &mut Mouse;
- fn mouse(&self) -> &Mouse;
- fn mouse_coords(&self) -> Option<Point>;
- fn received_count(&mut self) -> &mut usize;
- fn suppress_chars(&mut self) -> &mut bool;
- fn modifiers(&mut self) -> &mut ModifiersState;
- fn scroll(&mut self, scroll: Scroll);
- fn window(&self) -> &Window;
- fn window_mut(&mut self) -> &mut Window;
- fn terminal(&self) -> &Term<T>;
- fn terminal_mut(&mut self) -> &mut Term<T>;
- fn spawn_new_instance(&mut self);
- fn change_font_size(&mut self, delta: f32);
- fn reset_font_size(&mut self);
- fn pop_message(&mut self);
- fn message(&self) -> Option<&Message>;
- fn config(&self) -> &Config;
-}
-
-trait Execute<T: EventListener> {
- fn execute<A: ActionContext<T>>(&self, ctx: &mut A);
-}
-
-impl<T, U: EventListener> Execute<U> for Binding<T> {
- /// Execute the action associate with this binding
- #[inline]
- fn execute<A: ActionContext<U>>(&self, ctx: &mut A) {
- self.action.execute(ctx)
- }
-}
-
-impl<T: EventListener> Execute<T> for Action {
- #[inline]
- fn execute<A: ActionContext<T>>(&self, ctx: &mut A) {
- match *self {
- Action::Esc(ref s) => {
- ctx.clear_selection();
- ctx.scroll(Scroll::Bottom);
- ctx.write_to_pty(s.clone().into_bytes())
- },
- Action::Copy => {
- ctx.copy_selection(ClipboardType::Clipboard);
- },
- Action::Paste => {
- let text = ctx.terminal_mut().clipboard().load(ClipboardType::Clipboard);
- paste(ctx, &text);
- },
- Action::PasteSelection => {
- let text = ctx.terminal_mut().clipboard().load(ClipboardType::Selection);
- paste(ctx, &text);
- },
- Action::Command(ref program, ref args) => {
- trace!("Running command {} with args {:?}", program, args);
-
- match start_daemon(program, args) {
- Ok(_) => debug!("Spawned new proc"),
- Err(err) => warn!("Couldn't run command {}", err),
- }
- },
- Action::ToggleFullscreen => ctx.window_mut().toggle_fullscreen(),
- #[cfg(target_os = "macos")]
- Action::ToggleSimpleFullscreen => ctx.window_mut().toggle_simple_fullscreen(),
- Action::Hide => ctx.window().set_visible(false),
- Action::Minimize => ctx.window().set_minimized(true),
- Action::Quit => ctx.terminal_mut().exit(),
- Action::IncreaseFontSize => ctx.change_font_size(FONT_SIZE_STEP),
- Action::DecreaseFontSize => ctx.change_font_size(FONT_SIZE_STEP * -1.),
- Action::ResetFontSize => ctx.reset_font_size(),
- Action::ScrollPageUp => ctx.scroll(Scroll::PageUp),
- Action::ScrollPageDown => ctx.scroll(Scroll::PageDown),
- Action::ScrollLineUp => ctx.scroll(Scroll::Lines(1)),
- Action::ScrollLineDown => ctx.scroll(Scroll::Lines(-1)),
- Action::ScrollToTop => ctx.scroll(Scroll::Top),
- Action::ScrollToBottom => ctx.scroll(Scroll::Bottom),
- Action::ClearHistory => ctx.terminal_mut().clear_screen(ClearMode::Saved),
- Action::ClearLogNotice => ctx.pop_message(),
- Action::SpawnNewInstance => ctx.spawn_new_instance(),
- Action::ReceiveChar | Action::None => (),
- }
- }
-}
-
-fn paste<T: EventListener, A: ActionContext<T>>(ctx: &mut A, contents: &str) {
- if ctx.terminal().mode().contains(TermMode::BRACKETED_PASTE) {
- ctx.write_to_pty(&b"\x1b[200~"[..]);
- ctx.write_to_pty(contents.replace("\x1b", "").into_bytes());
- ctx.write_to_pty(&b"\x1b[201~"[..]);
- } else {
- // In non-bracketed (ie: normal) mode, terminal applications cannot distinguish
- // pasted data from keystrokes.
- // In theory, we should construct the keystrokes needed to produce the data we are
- // pasting... since that's neither practical nor sensible (and probably an impossible
- // task to solve in a general way), we'll just replace line breaks (windows and unix
- // style) with a single carriage return (\r, which is what the Enter key produces).
- ctx.write_to_pty(contents.replace("\r\n", "\r").replace("\n", "\r").into_bytes());
- }
-}
-
-#[derive(Debug, Clone, PartialEq)]
-pub enum MouseState {
- Url(Url),
- MessageBar,
- MessageBarButton,
- Mouse,
- Text,
-}
-
-impl From<MouseState> for CursorIcon {
- fn from(mouse_state: MouseState) -> CursorIcon {
- match mouse_state {
- MouseState::Url(_) | MouseState::MessageBarButton => CursorIcon::Hand,
- MouseState::Text => CursorIcon::Text,
- _ => CursorIcon::Default,
- }
- }
-}
-
-impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
- pub fn new(ctx: A, urls: &'a Urls, highlighted_url: &'a Option<Url>) -> Self {
- Self { ctx, urls, highlighted_url, _phantom: Default::default() }
- }
-
- #[inline]
- pub fn mouse_moved(&mut self, x: usize, y: usize) {
- let size_info = self.ctx.size_info();
-
- self.ctx.mouse_mut().x = x;
- self.ctx.mouse_mut().y = y;
-
- let inside_grid = size_info.contains_point(x, y);
- let point = size_info.pixels_to_coords(x, y);
- let cell_side = self.get_mouse_side();
-
- let cell_changed =
- point.line != self.ctx.mouse().line || point.col != self.ctx.mouse().column;
-
- // If the mouse hasn't changed cells, do nothing
- if !cell_changed
- && self.ctx.mouse().cell_side == cell_side
- && self.ctx.mouse().inside_grid == inside_grid
- {
- return;
- }
-
- self.ctx.mouse_mut().inside_grid = inside_grid;
- self.ctx.mouse_mut().cell_side = cell_side;
- self.ctx.mouse_mut().line = point.line;
- self.ctx.mouse_mut().column = point.col;
-
- // Don't launch URLs if mouse has moved
- self.ctx.mouse_mut().block_url_launcher = true;
-
- // Update mouse state and check for URL change
- let mouse_state = self.mouse_state();
- self.update_url_state(&mouse_state);
- self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
-
- let last_term_line = self.ctx.terminal().grid().num_lines() - 1;
- if self.ctx.mouse().left_button_state == ElementState::Pressed
- && (self.ctx.modifiers().shift()
- || !self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE))
- {
- // Treat motion over message bar like motion over the last line
- let line = min(point.line, last_term_line);
-
- self.ctx.update_selection(Point { line, col: point.col }, cell_side);
- } else if inside_grid
- && cell_changed
- && point.line <= last_term_line
- && self.ctx.terminal().mode().intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG)
- {
- if self.ctx.mouse().left_button_state == ElementState::Pressed {
- self.mouse_report(32, ElementState::Pressed);
- } else if self.ctx.mouse().middle_button_state == ElementState::Pressed {
- self.mouse_report(33, ElementState::Pressed);
- } else if self.ctx.mouse().right_button_state == ElementState::Pressed {
- self.mouse_report(34, ElementState::Pressed);
- } else if self.ctx.terminal().mode().contains(TermMode::MOUSE_MOTION) {
- self.mouse_report(35, ElementState::Pressed);
- }
- }
- }
-
- fn get_mouse_side(&self) -> Side {
- let size_info = self.ctx.size_info();
- let x = self.ctx.mouse().x;
-
- let cell_x = x.saturating_sub(size_info.padding_x as usize) % size_info.cell_width as usize;
- let half_cell_width = (size_info.cell_width / 2.0) as usize;
-
- let additional_padding =
- (size_info.width - size_info.padding_x * 2.) % size_info.cell_width;
- let end_of_grid = size_info.width - size_info.padding_x - additional_padding;
-
- if cell_x > half_cell_width
- // Edge case when mouse leaves the window
- || x as f32 >= end_of_grid
- {
- Side::Right
- } else {
- Side::Left
- }
- }
-
- fn normal_mouse_report(&mut self, button: u8) {
- let (line, column) = (self.ctx.mouse().line, self.ctx.mouse().column);
- let utf8 = self.ctx.terminal().mode().contains(TermMode::UTF8_MOUSE);
-
- let max_point = if utf8 { 2015 } else { 223 };
-
- if line >= Line(max_point) || column >= Column(max_point) {
- return;
- }
-
- let mut msg = vec![b'\x1b', b'[', b'M', 32 + button];
-
- let mouse_pos_encode = |pos: usize| -> Vec<u8> {
- let pos = 32 + 1 + pos;
- let first = 0xC0 + pos / 64;
- let second = 0x80 + (pos & 63);
- vec![first as u8, second as u8]
- };
-
- if utf8 && column >= Column(95) {
- msg.append(&mut mouse_pos_encode(column.0));
- } else {
- msg.push(32 + 1 + column.0 as u8);
- }
-
- if utf8 && line >= Line(95) {
- msg.append(&mut mouse_pos_encode(line.0));
- } else {
- msg.push(32 + 1 + line.0 as u8);
- }
-
- self.ctx.write_to_pty(msg);
- }
-
- fn sgr_mouse_report(&mut self, button: u8, state: ElementState) {
- let (line, column) = (self.ctx.mouse().line, self.ctx.mouse().column);
- let c = match state {
- ElementState::Pressed => 'M',
- ElementState::Released => 'm',
- };
-
- let msg = format!("\x1b[<{};{};{}{}", button, column + 1, line + 1, c);
- self.ctx.write_to_pty(msg.into_bytes());
- }
-
- fn mouse_report(&mut self, button: u8, state: ElementState) {
- // Calculate modifiers value
- let mut mods = 0;
- let modifiers = self.ctx.modifiers();
- if modifiers.shift() {
- mods += 4;
- }
- if modifiers.alt() {
- mods += 8;
- }
- if modifiers.ctrl() {
- mods += 16;
- }
-
- // Report mouse events
- if self.ctx.terminal().mode().contains(TermMode::SGR_MOUSE) {
- self.sgr_mouse_report(button + mods, state);
- } else if let ElementState::Released = state {
- self.normal_mouse_report(3 + mods);
- } else {
- self.normal_mouse_report(button + mods);
- }
- }
-
- fn on_mouse_double_click(&mut self, button: MouseButton, point: Point) {
- if button == MouseButton::Left {
- self.ctx.semantic_selection(point);
- }
- }
-
- fn on_mouse_triple_click(&mut self, button: MouseButton, point: Point) {
- if button == MouseButton::Left {
- self.ctx.line_selection(point);
- }
- }
-
- fn on_mouse_press(&mut self, button: MouseButton) {
- let now = Instant::now();
- let elapsed = self.ctx.mouse().last_click_timestamp.elapsed();
- self.ctx.mouse_mut().last_click_timestamp = now;
-
- let button_changed = self.ctx.mouse().last_button != button;
-
- // Load mouse point, treating message bar and padding as closest cell
- let mouse = self.ctx.mouse();
- let mut point = self.ctx.size_info().pixels_to_coords(mouse.x, mouse.y);
- point.line = min(point.line, self.ctx.terminal().grid().num_lines() - 1);
-
- self.ctx.mouse_mut().click_state = match self.ctx.mouse().click_state {
- ClickState::Click
- if !button_changed
- && elapsed < self.ctx.config().ui_config.mouse.double_click.threshold =>
- {
- self.ctx.mouse_mut().block_url_launcher = true;
- self.on_mouse_double_click(button, point);
- ClickState::DoubleClick
- }
- ClickState::DoubleClick
- if !button_changed
- && elapsed < self.ctx.config().ui_config.mouse.triple_click.threshold =>
- {
- self.ctx.mouse_mut().block_url_launcher = true;
- self.on_mouse_triple_click(button, point);
- ClickState::TripleClick
- }
- _ => {
- // Don't launch URLs if this click cleared the selection
- self.ctx.mouse_mut().block_url_launcher = !self.ctx.selection_is_empty();
-
- self.ctx.clear_selection();
-
- // Start new empty selection
- let side = self.ctx.mouse().cell_side;
- if self.ctx.modifiers().ctrl() {
- self.ctx.block_selection(point, side);
- } else {
- self.ctx.simple_selection(point, side);
- }
-
- if !self.ctx.modifiers().shift()
- && self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE)
- {
- let code = match button {
- MouseButton::Left => 0,
- MouseButton::Middle => 1,
- MouseButton::Right => 2,
- // Can't properly report more than three buttons.
- MouseButton::Other(_) => return,
- };
- self.mouse_report(code, ElementState::Pressed);
- return;
- }
-
- ClickState::Click
- },
- };
- }
-
- fn on_mouse_release(&mut self, button: MouseButton) {
- if !self.ctx.modifiers().shift()
- && self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE)
- {
- let code = match button {
- MouseButton::Left => 0,
- MouseButton::Middle => 1,
- MouseButton::Right => 2,
- // Can't properly report more than three buttons.
- MouseButton::Other(_) => return,
- };
- self.mouse_report(code, ElementState::Released);
- return;
- } else if let (MouseButton::Left, MouseState::Url(url)) = (button, self.mouse_state()) {
- self.launch_url(url);
- }
-
- self.copy_selection();
- }
-
- /// Spawn URL launcher when clicking on URLs.
- fn launch_url(&self, url: Url) {
- if self.ctx.mouse().block_url_launcher {
- return;
- }
-
- if let Some(ref launcher) = self.ctx.config().ui_config.mouse.url.launcher {
- let mut args = launcher.args().to_vec();
- let start = self.ctx.terminal().visible_to_buffer(url.start());
- let end = self.ctx.terminal().visible_to_buffer(url.end());
- args.push(self.ctx.terminal().bounds_to_string(start, end));
-
- match start_daemon(launcher.program(), &args) {
- Ok(_) => debug!("Launched {} with args {:?}", launcher.program(), args),
- Err(_) => warn!("Unable to launch {} with args {:?}", launcher.program(), args),
- }
- }
- }
-
- pub fn mouse_wheel_input(&mut self, delta: MouseScrollDelta, phase: TouchPhase) {
- match delta {
- MouseScrollDelta::LineDelta(_columns, lines) => {
- let new_scroll_px = lines * self.ctx.size_info().cell_height;
- self.scroll_terminal(new_scroll_px as i32);
- },
- MouseScrollDelta::PixelDelta(lpos) => {
- match phase {
- TouchPhase::Started => {
- // Reset offset to zero
- self.ctx.mouse_mut().scroll_px = 0;
- },
- TouchPhase::Moved => {
- self.scroll_terminal(lpos.y as i32);
- },
- _ => (),
- }
- },
- }
- }
-
- fn scroll_terminal(&mut self, new_scroll_px: i32) {
- let height = self.ctx.size_info().cell_height as i32;
-
- if self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE) {
- self.ctx.mouse_mut().scroll_px += new_scroll_px;
-
- let code = if new_scroll_px > 0 { 64 } else { 65 };
- let lines = (self.ctx.mouse().scroll_px / height).abs();
-
- for _ in 0..lines {
- self.mouse_report(code, ElementState::Pressed);
- }
- } else if self
- .ctx
- .terminal()
- .mode()
- .contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL)
- && !self.ctx.modifiers().shift()
- {
- let multiplier = i32::from(
- self.ctx
- .config()
- .scrolling
- .faux_multiplier()
- .unwrap_or_else(|| self.ctx.config().scrolling.multiplier()),
- );
- self.ctx.mouse_mut().scroll_px += new_scroll_px * multiplier;
-
- let cmd = if new_scroll_px > 0 { b'A' } else { b'B' };
- let lines = (self.ctx.mouse().scroll_px / height).abs();
-
- let mut content = Vec::with_capacity(lines as usize * 3);
- for _ in 0..lines {
- content.push(0x1b);
- content.push(b'O');
- content.push(cmd);
- }
- self.ctx.write_to_pty(content);
- } else {
- let multiplier = i32::from(self.ctx.config().scrolling.multiplier());
- self.ctx.mouse_mut().scroll_px += new_scroll_px * multiplier;
-
- let lines = self.ctx.mouse().scroll_px / height;
-
- self.ctx.scroll(Scroll::Lines(lines as isize));
- }
-
- self.ctx.mouse_mut().scroll_px %= height;
- }
-
- pub fn on_focus_change(&mut self, is_focused: bool) {
- if self.ctx.terminal().mode().contains(TermMode::FOCUS_IN_OUT) {
- let chr = if is_focused { "I" } else { "O" };
-
- let msg = format!("\x1b[{}", chr);
- self.ctx.write_to_pty(msg.into_bytes());
- }
- }
-
- pub fn mouse_input(&mut self, state: ElementState, button: MouseButton) {
- match button {
- MouseButton::Left => self.ctx.mouse_mut().left_button_state = state,
- MouseButton::Middle => self.ctx.mouse_mut().middle_button_state = state,
- MouseButton::Right => self.ctx.mouse_mut().right_button_state = state,
- _ => (),
- }
-
- // Skip normal mouse events if the message bar has been clicked
- if self.message_close_at_cursor() && state == ElementState::Pressed {
- self.ctx.clear_selection();
- self.ctx.pop_message();
-
- // Reset cursor when message bar height changed or all messages are gone
- let size = self.ctx.size_info();
- let mouse_mode = self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE);
- let current_lines = (size.lines() - self.ctx.terminal().grid().num_lines()).0;
- let new_lines = self.ctx.message().map(|m| m.text(&size).len()).unwrap_or(0);
-
- let new_icon = match current_lines.cmp(&new_lines) {
- Ordering::Less => CursorIcon::Default,
- Ordering::Equal => CursorIcon::Hand,
- Ordering::Greater => {
- if mouse_mode {
- CursorIcon::Default
- } else {
- CursorIcon::Text
- }
- },
- };
-
- self.ctx.window_mut().set_mouse_cursor(new_icon);
- } else {
- match state {
- ElementState::Pressed => {
- self.process_mouse_bindings(button);
- self.on_mouse_press(button);
- },
- ElementState::Released => self.on_mouse_release(button),
- }
- }
-
- self.ctx.mouse_mut().last_button = button;
- }
-
- /// Process key input.
- pub fn key_input(&mut self, input: KeyboardInput) {
- match input.state {
- ElementState::Pressed => {
- *self.ctx.received_count() = 0;
- self.process_key_bindings(input);
- },
- ElementState::Released => *self.ctx.suppress_chars() = false,
- }
- }
-
- /// Modifier state change.
- pub fn modifiers_input(&mut self, modifiers: ModifiersState) {
- *self.ctx.modifiers() = modifiers;
-
- // Update mouse state and check for URL change
- let mouse_state = self.mouse_state();
- self.update_url_state(&mouse_state);
- self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
- }
-
- /// Process a received character.
- pub fn received_char(&mut self, c: char) {
- if *self.ctx.suppress_chars() {
- return;
- }
-
- self.ctx.scroll(Scroll::Bottom);
- self.ctx.clear_selection();
-
- let utf8_len = c.len_utf8();
- let mut bytes = Vec::with_capacity(utf8_len);
- unsafe {
- bytes.set_len(utf8_len);
- c.encode_utf8(&mut bytes[..]);
- }
-
- if self.ctx.config().alt_send_esc()
- && *self.ctx.received_count() == 0
- && self.ctx.modifiers().alt()
- && utf8_len == 1
- {
- bytes.insert(0, b'\x1b');
- }
-
- self.ctx.write_to_pty(bytes);
-
- *self.ctx.received_count() += 1;
- self.ctx.terminal_mut().dirty = false;
- }
-
- /// Reset mouse cursor based on modifier and terminal state.
- #[inline]
- pub fn reset_mouse_cursor(&mut self) {
- let mouse_state = self.mouse_state();
- self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
- }
-
- /// Attempt to find a binding and execute its action.
- ///
- /// The provided mode, mods, and key must match what is allowed by a binding
- /// for its action to be executed.
- fn process_key_bindings(&mut self, input: KeyboardInput) {
- let mods = *self.ctx.modifiers();
- let mut suppress_chars = None;
-
- for i in 0..self.ctx.config().ui_config.key_bindings.len() {
- let binding = &self.ctx.config().ui_config.key_bindings[i];
-
- let key = match (binding.trigger, input.virtual_keycode) {
- (Key::Scancode(_), _) => Key::Scancode(input.scancode),
- (_, Some(key)) => Key::Keycode(key),
- _ => continue,
- };
-
- if binding.is_triggered_by(*self.ctx.terminal().mode(), mods, &key) {
- // Binding was triggered; run the action
- let binding = binding.clone();
- binding.execute(&mut self.ctx);
-
- // Don't suppress when there has been a `ReceiveChar` action
- *suppress_chars.get_or_insert(true) &= binding.action != Action::ReceiveChar;
- }
- }
-
- // Don't suppress char if no bindings were triggered
- *self.ctx.suppress_chars() = suppress_chars.unwrap_or(false);
- }
-
- /// Attempt to find a binding and execute its action.
- ///
- /// The provided mode, mods, and key must match what is allowed by a binding
- /// for its action to be executed.
- fn process_mouse_bindings(&mut self, button: MouseButton) {
- let mods = *self.ctx.modifiers();
- let mode = *self.ctx.terminal().mode();
- let mouse_mode = mode.intersects(TermMode::MOUSE_MODE);
-
- for i in 0..self.ctx.config().ui_config.mouse_bindings.len() {
- let mut binding = self.ctx.config().ui_config.mouse_bindings[i].clone();
-
- // Require shift for all modifiers when mouse mode is active
- if mouse_mode {
- binding.mods |= ModifiersState::SHIFT;
- }
-
- if binding.is_triggered_by(mode, mods, &button) {
- binding.execute(&mut self.ctx);
- }
- }
- }
-
- /// Check if the cursor is hovering above the message bar.
- fn message_at_cursor(&mut self) -> bool {
- self.ctx.mouse().line >= self.ctx.terminal().grid().num_lines()
- }
-
- /// Whether the point is over the message bar's close button
- fn message_close_at_cursor(&self) -> bool {
- let mouse = self.ctx.mouse();
- mouse.inside_grid
- && mouse.column + message_bar::CLOSE_BUTTON_TEXT.len() >= self.ctx.size_info().cols()
- && mouse.line == self.ctx.terminal().grid().num_lines()
- }
-
- /// Copy text selection.
- fn copy_selection(&mut self) {
- if self.ctx.config().selection.save_to_clipboard {
- self.ctx.copy_selection(ClipboardType::Clipboard);
- }
- self.ctx.copy_selection(ClipboardType::Selection);
- }
-
- /// Trigger redraw when URL highlight changed.
- #[inline]
- fn update_url_state(&mut self, mouse_state: &MouseState) {
- if let MouseState::Url(url) = mouse_state {
- if Some(url) != self.highlighted_url.as_ref() {
- self.ctx.terminal_mut().dirty = true;
- }
- } else if self.highlighted_url.is_some() {
- self.ctx.terminal_mut().dirty = true;
- }
- }
-
- /// Location of the mouse cursor.
- fn mouse_state(&mut self) -> MouseState {
- // Check message bar before URL to ignore URLs in the message bar
- if self.message_close_at_cursor() {
- return MouseState::MessageBarButton;
- } else if self.message_at_cursor() {
- return MouseState::MessageBar;
- }
-
- // Check for URL at mouse cursor
- let mods = *self.ctx.modifiers();
- let selection =
- !self.ctx.terminal().selection().as_ref().map(Selection::is_empty).unwrap_or(true);
- let mouse_mode = self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE);
- let highlighted_url =
- self.urls.highlighted(self.ctx.config(), self.ctx.mouse(), mods, mouse_mode, selection);
-
- if let Some(url) = highlighted_url {
- return MouseState::Url(url);
- }
-
- // Check mouse mode if location is not special
- if self.ctx.terminal().mode().intersects(TermMode::MOUSE_MODE)
- && !self.ctx.modifiers().shift()
- {
- MouseState::Mouse
- } else {
- MouseState::Text
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use std::borrow::Cow;
- use std::time::Duration;
-
- use glutin::event::{
- ElementState, Event, ModifiersState, MouseButton, VirtualKeyCode, WindowEvent,
- };
-
- use alacritty_terminal::clipboard::{Clipboard, ClipboardType};
- use alacritty_terminal::event::{Event as TerminalEvent, EventListener};
- use alacritty_terminal::grid::Scroll;
- use alacritty_terminal::index::{Point, Side};
- use alacritty_terminal::message_bar::{Message, MessageBuffer};
- use alacritty_terminal::selection::Selection;
- use alacritty_terminal::term::{SizeInfo, Term, TermMode};
-
- use crate::config::{ClickHandler, Config};
- use crate::event::{ClickState, Mouse};
- use crate::url::Urls;
- use crate::window::Window;
-
- use super::{Action, Binding, Processor};
-
- const KEY: VirtualKeyCode = VirtualKeyCode::Key0;
-
- struct MockEventProxy;
-
- impl EventListener for MockEventProxy {
- fn send_event(&self, _event: TerminalEvent) {}
- }
-
- #[derive(PartialEq)]
- enum MultiClick {
- DoubleClick,
- TripleClick,
- None,
- }
-
- struct ActionContext<'a, T> {
- pub terminal: &'a mut Term<T>,
- pub selection: &'a mut Option<Selection>,
- pub size_info: &'a SizeInfo,
- pub mouse: &'a mut Mouse,
- pub message_buffer: &'a mut MessageBuffer,
- pub last_action: MultiClick,
- pub received_count: usize,
- pub suppress_chars: bool,
- pub modifiers: ModifiersState,
- config: &'a Config,
- }
-
- impl<'a, T: EventListener> super::ActionContext<T> for ActionContext<'a, T> {
- fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, _val: B) {}
-
- fn update_selection(&mut self, _point: Point, _side: Side) {}
-
- fn simple_selection(&mut self, _point: Point, _side: Side) {}
-
- fn block_selection(&mut self, _point: Point, _side: Side) {}
-
- fn copy_selection(&mut self, _: ClipboardType) {}
-
- fn clear_selection(&mut self) {}
-
- fn spawn_new_instance(&mut self) {}
-
- fn change_font_size(&mut self, _delta: f32) {}
-
- fn reset_font_size(&mut self) {}
-
- fn terminal(&self) -> &Term<T> {
- &self.terminal
- }
-
- fn terminal_mut(&mut self) -> &mut Term<T> {
- &mut self.terminal
- }
-
- fn size_info(&self) -> SizeInfo {
- *self.size_info
- }
-
- fn semantic_selection(&mut self, _point: Point) {
- // set something that we can check for here
- self.last_action = MultiClick::DoubleClick;
- }
-
- fn line_selection(&mut self, _point: Point) {
- self.last_action = MultiClick::TripleClick;
- }
-
- fn selection_is_empty(&self) -> bool {
- true
- }
-
- fn scroll(&mut self, scroll: Scroll) {
- self.terminal.scroll_display(scroll);
- }
-
- fn mouse_coords(&self) -> Option<Point> {
- let x = self.mouse.x as usize;
- let y = self.mouse.y as usize;
-
- if self.size_info.contains_point(x, y) {
- Some(self.size_info.pixels_to_coords(x, y))
- } else {
- None
- }
- }
-
- #[inline]
- fn mouse_mut(&mut self) -> &mut Mouse {
- self.mouse
- }
-
- #[inline]
- fn mouse(&self) -> &Mouse {
- self.mouse
- }
-
- fn received_count(&mut self) -> &mut usize {
- &mut self.received_count
- }
-
- fn suppress_chars(&mut self) -> &mut bool {
- &mut self.suppress_chars
- }
-
- fn modifiers(&mut self) -> &mut ModifiersState {
- &mut self.modifiers
- }
-
- fn window(&self) -> &Window {
- unimplemented!();
- }
-
- fn window_mut(&mut self) -> &mut Window {
- unimplemented!();
- }
-
- fn pop_message(&mut self) {
- self.message_buffer.pop();
- }
-
- fn message(&self) -> Option<&Message> {
- self.message_buffer.message()
- }
-
- fn config(&self) -> &Config {
- self.config
- }
- }
-
- macro_rules! test_clickstate {
- {
- name: $name:ident,
- initial_state: $initial_state:expr,
- initial_button: $initial_button:expr,
- input: $input:expr,
- end_state: $end_state:pat,
- last_action: $last_action:expr
- } => {
- #[test]
- fn $name() {
- let mut cfg = Config::default();
- cfg.ui_config.mouse = crate::config::Mouse {
- double_click: ClickHandler {
- threshold: Duration::from_millis(1000),
- },
- triple_click: ClickHandler {
- threshold: Duration::from_millis(1000),
- },
- hide_when_typing: false,
- url: Default::default(),
- };
-
- let size = SizeInfo {
- width: 21.0,
- height: 51.0,
- cell_width: 3.0,
- cell_height: 3.0,
- padding_x: 0.0,
- padding_y: 0.0,
- dpr: 1.0,
- };
-
- let mut terminal = Term::new(&cfg, &size, Clipboard::new_nop(), MockEventProxy);
-
- let mut mouse = Mouse::default();
- mouse.click_state = $initial_state;
- mouse.last_button = $initial_button;
-
- let mut selection = None;
-
- let mut message_buffer = MessageBuffer::new();
-
- let context = ActionContext {
- terminal: &mut terminal,
- selection: &mut selection,
- mouse: &mut mouse,
- size_info: &size,
- last_action: MultiClick::None,
- received_count: 0,
- suppress_chars: false,
- modifiers: Default::default(),
- message_buffer: &mut message_buffer,
- config: &cfg,
- };
-
- let urls = Urls::new();
- let mut processor = Processor::new(context, &urls, &None);
-
- let event: Event::<'_, TerminalEvent> = $input;
- if let Event::WindowEvent {
- event: WindowEvent::MouseInput {
- state,
- button,
- ..
- },
- ..
- } = event
- {
- processor.mouse_input(state, button);
- };
-
- assert!(match processor.ctx.mouse.click_state {
- $end_state => processor.ctx.last_action == $last_action,
- _ => false
- });
- }
- }
- }
-
- macro_rules! test_process_binding {
- {
- name: $name:ident,
- binding: $binding:expr,
- triggers: $triggers:expr,
- mode: $mode:expr,
- mods: $mods:expr,
- } => {
- #[test]
- fn $name() {
- if $triggers {
- assert!($binding.is_triggered_by($mode, $mods, &KEY));
- } else {
- assert!(!$binding.is_triggered_by($mode, $mods, &KEY));
- }
- }
- }
- }
-
- test_clickstate! {
- name: single_click,
- initial_state: ClickState::None,
- initial_button: MouseButton::Other(0),
- input: Event::WindowEvent {
- event: WindowEvent::MouseInput {
- state: ElementState::Pressed,
- button: MouseButton::Left,
- device_id: unsafe { ::std::mem::transmute_copy(&0) },
- modifiers: ModifiersState::default(),
- },
- window_id: unsafe { ::std::mem::transmute_copy(&0) },
- },
- end_state: ClickState::Click,
- last_action: MultiClick::None
- }
-
- test_clickstate! {
- name: double_click,
- initial_state: ClickState::Click,
- initial_button: MouseButton::Left,
- input: Event::WindowEvent {
- event: WindowEvent::MouseInput {
- state: ElementState::Pressed,
- button: MouseButton::Left,
- device_id: unsafe { ::std::mem::transmute_copy(&0) },
- modifiers: ModifiersState::default(),
- },
- window_id: unsafe { ::std::mem::transmute_copy(&0) },
- },
- end_state: ClickState::DoubleClick,
- last_action: MultiClick::DoubleClick
- }
-
- test_clickstate! {
- name: triple_click,
- initial_state: ClickState::DoubleClick,
- initial_button: MouseButton::Left,
- input: Event::WindowEvent {
- event: WindowEvent::MouseInput {
- state: ElementState::Pressed,
- button: MouseButton::Left,
- device_id: unsafe { ::std::mem::transmute_copy(&0) },
- modifiers: ModifiersState::default(),
- },
- window_id: unsafe { ::std::mem::transmute_copy(&0) },
- },
- end_state: ClickState::TripleClick,
- last_action: MultiClick::TripleClick
- }
-
- test_clickstate! {
- name: multi_click_separate_buttons,
- initial_state: ClickState::DoubleClick,
- initial_button: MouseButton::Left,
- input: Event::WindowEvent {
- event: WindowEvent::MouseInput {
- state: ElementState::Pressed,
- button: MouseButton::Right,
- device_id: unsafe { ::std::mem::transmute_copy(&0) },
- modifiers: ModifiersState::default(),
- },
- window_id: unsafe { ::std::mem::transmute_copy(&0) },
- },
- end_state: ClickState::Click,
- last_action: MultiClick::None
- }
-
- test_process_binding! {
- name: process_binding_nomode_shiftmod_require_shift,
- binding: Binding { trigger: KEY, mods: ModifiersState::SHIFT, action: Action::from("\x1b[1;2D"), mode: TermMode::NONE, notmode: TermMode::NONE },
- triggers: true,
- mode: TermMode::NONE,
- mods: ModifiersState::SHIFT,
- }
-
- test_process_binding! {
- name: process_binding_nomode_nomod_require_shift,
- binding: Binding { trigger: KEY, mods: ModifiersState::SHIFT, action: Action::from("\x1b[1;2D"), mode: TermMode::NONE, notmode: TermMode::NONE },
- triggers: false,
- mode: TermMode::NONE,
- mods: ModifiersState::empty(),
- }
-
- test_process_binding! {
- name: process_binding_nomode_controlmod,
- binding: Binding { trigger: KEY, mods: ModifiersState::CTRL, action: Action::from("\x1b[1;5D"), mode: TermMode::NONE, notmode: TermMode::NONE },
- triggers: true,
- mode: TermMode::NONE,
- mods: ModifiersState::CTRL,
- }
-
- test_process_binding! {
- name: process_binding_nomode_nomod_require_not_appcursor,
- binding: Binding { trigger: KEY, mods: ModifiersState::empty(), action: Action::from("\x1b[D"), mode: TermMode::NONE, notmode: TermMode::APP_CURSOR },
- triggers: true,
- mode: TermMode::NONE,
- mods: ModifiersState::empty(),
- }
-
- test_process_binding! {
- name: process_binding_appcursormode_nomod_require_appcursor,
- binding: Binding { trigger: KEY, mods: ModifiersState::empty(), action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE },
- triggers: true,
- mode: TermMode::APP_CURSOR,
- mods: ModifiersState::empty(),
- }
-
- test_process_binding! {
- name: process_binding_nomode_nomod_require_appcursor,
- binding: Binding { trigger: KEY, mods: ModifiersState::empty(), action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE },
- triggers: false,
- mode: TermMode::NONE,
- mods: ModifiersState::empty(),
- }
-
- test_process_binding! {
- name: process_binding_appcursormode_appkeypadmode_nomod_require_appcursor,
- binding: Binding { trigger: KEY, mods: ModifiersState::empty(), action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE },
- triggers: true,
- mode: TermMode::APP_CURSOR | TermMode::APP_KEYPAD,
- mods: ModifiersState::empty(),
- }
-
- test_process_binding! {
- name: process_binding_fail_with_extra_mods,
- binding: Binding { trigger: KEY, mods: ModifiersState::LOGO, action: Action::from("arst"), mode: TermMode::NONE, notmode: TermMode::NONE },
- triggers: false,
- mode: TermMode::NONE,
- mods: ModifiersState::ALT | ModifiersState::LOGO,
- }
-}
diff --git a/alacritty/src/logging.rs b/alacritty/src/logging.rs
deleted file mode 100644
index 9d837a7..0000000
--- a/alacritty/src/logging.rs
+++ /dev/null
@@ -1,201 +0,0 @@
-// 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.
-//
-//! Logging for alacritty.
-//!
-//! The main executable is supposed to call `initialize()` exactly once during
-//! startup. All logging messages are written to stdout, given that their
-//! log-level is sufficient for the level configured in `cli::Options`.
-use std::env;
-use std::fs::{File, OpenOptions};
-use std::io::{self, LineWriter, Stdout, Write};
-use std::path::PathBuf;
-use std::process;
-use std::sync::atomic::{AtomicBool, Ordering};
-use std::sync::{Arc, Mutex};
-
-use glutin::event_loop::EventLoopProxy;
-use log::{self, Level};
-use time;
-
-use alacritty_terminal::event::Event;
-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,
- event_proxy: EventLoopProxy<Event>,
-) -> 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(event_proxy);
- let path = logger.file_path();
- log::set_boxed_logger(Box::new(logger))?;
- Ok(path)
- }
-}
-
-pub struct Logger {
- logfile: Mutex<OnDemandLogFile>,
- stdout: Mutex<LineWriter<Stdout>>,
- event_proxy: Mutex<EventLoopProxy<Event>>,
-}
-
-impl Logger {
- fn new(event_proxy: EventLoopProxy<Event>) -> Self {
- let logfile = Mutex::new(OnDemandLogFile::new());
- let stdout = Mutex::new(LineWriter::new(io::stdout()));
-
- Logger { logfile, stdout, event_proxy: Mutex::new(event_proxy) }
- }
-
- fn file_path(&self) -> Option<PathBuf> {
- if let Ok(logfile) = self.logfile.lock() {
- Some(logfile.path().clone())
- } else {
- None
- }
- }
-}
-
-impl log::Log for Logger {
- fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
- metadata.level() <= log::max_level()
- }
-
- fn log(&self, record: &log::Record<'_>) {
- if self.enabled(record.metadata()) && record.target().starts_with("alacritty") {
- let now = time::strftime("%F %R", &time::now()).unwrap();
-
- let msg = if record.level() >= Level::Trace {
- format!(
- "[{}] [{}] [{}:{}] {}\n",
- now,
- record.level(),
- record.file().unwrap_or("?"),
- record.line().map(|l| l.to_string()).unwrap_or_else(|| "?".into()),
- record.args()
- )
- } else {
- format!("[{}] [{}] {}\n", now, record.level(), record.args())
- };
-
- if let Ok(ref mut logfile) = self.logfile.lock() {
- let _ = logfile.write_all(msg.as_ref());
-
- if record.level() <= Level::Warn {
- #[cfg(not(windows))]
- let env_var = format!("${}", ALACRITTY_LOG_ENV);
- #[cfg(windows)]
- let env_var = format!("%{}%", ALACRITTY_LOG_ENV);
-
- let msg = format!(
- "[{}] See log at {} ({}):\n{}",
- record.level(),
- logfile.path.to_string_lossy(),
- env_var,
- record.args(),
- );
- let color = match record.level() {
- Level::Error => color::RED,
- Level::Warn => color::YELLOW,
- _ => unreachable!(),
- };
-
- if let Ok(event_proxy) = self.event_proxy.lock() {
- let mut message = Message::new(msg, color);
- message.set_target(record.target().to_owned());
-
- let _ = event_proxy.send_event(Event::Message(message));
- }
- }
- }
-
- if let Ok(ref mut stdout) = self.stdout.lock() {
- let _ = stdout.write_all(msg.as_ref());
- }
- }
- }
-
- fn flush(&self) {}
-}
-
-struct OnDemandLogFile {
- file: Option<LineWriter<File>>,
- created: Arc<AtomicBool>,
- path: PathBuf,
-}
-
-impl OnDemandLogFile {
- fn new() -> Self {
- let mut path = env::temp_dir();
- path.push(format!("Alacritty-{}.log", process::id()));
-
- // Set log path as an environment variable
- env::set_var(ALACRITTY_LOG_ENV, path.as_os_str());
-
- OnDemandLogFile { path, file: None, created: Arc::new(AtomicBool::new(false)) }
- }
-
- fn file(&mut self) -> Result<&mut LineWriter<File>, io::Error> {
- // Allow to recreate the file if it has been deleted at runtime
- if self.file.is_some() && !self.path.as_path().exists() {
- self.file = None;
- }
-
- // Create the file if it doesn't exist yet
- if self.file.is_none() {
- let file = OpenOptions::new().append(true).create(true).open(&self.path);
-
- match file {
- Ok(file) => {
- self.file = Some(io::LineWriter::new(file));
- self.created.store(true, Ordering::Relaxed);
- let _ =
- writeln!(io::stdout(), "Created log file at \"{}\"", self.path.display());
- },
- Err(e) => {
- let _ = writeln!(io::stdout(), "Unable to create log file: {}", e);
- return Err(e);
- },
- }
- }
-
- Ok(self.file.as_mut().unwrap())
- }
-
- fn path(&self) -> &PathBuf {
- &self.path
- }
-}
-
-impl Write for OnDemandLogFile {
- fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
- self.file()?.write(buf)
- }
-
- fn flush(&mut self) -> Result<(), io::Error> {
- self.file()?.flush()
- }
-}
diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs
deleted file mode 100644
index db08245..0000000
--- a/alacritty/src/main.rs
+++ /dev/null
@@ -1,237 +0,0 @@
-// 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.
-//
-//! Alacritty - The GPU Enhanced Terminal
-#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)]
-#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
-#![cfg_attr(all(test, feature = "bench"), feature(test))]
-// With the default subsystem, 'console', windows creates an additional console
-// window for the program.
-// This is silently ignored on non-windows systems.
-// See https://msdn.microsoft.com/en-us/library/4cc7ya5b.aspx for more details.
-#![windows_subsystem = "windows"]
-
-#[cfg(target_os = "macos")]
-use std::env;
-use std::error::Error;
-use std::fs;
-use std::io::{self, Write};
-use std::sync::Arc;
-
-#[cfg(target_os = "macos")]
-use dirs;
-use glutin::event_loop::EventLoop as GlutinEventLoop;
-use log::info;
-#[cfg(windows)]
-use winapi::um::wincon::{AttachConsole, FreeConsole, ATTACH_PARENT_PROCESS};
-
-use alacritty_terminal::clipboard::Clipboard;
-use alacritty_terminal::event::Event;
-use alacritty_terminal::event_loop::{self, EventLoop, Msg};
-#[cfg(target_os = "macos")]
-use alacritty_terminal::locale;
-use alacritty_terminal::message_bar::MessageBuffer;
-use alacritty_terminal::panic;
-use alacritty_terminal::sync::FairMutex;
-use alacritty_terminal::term::Term;
-use alacritty_terminal::tty;
-
-mod cli;
-mod config;
-mod cursor;
-mod display;
-mod event;
-mod input;
-mod logging;
-mod renderer;
-mod url;
-mod window;
-
-mod gl {
- #![allow(clippy::all)]
- include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
-}
-
-use crate::cli::Options;
-use crate::config::monitor::Monitor;
-use crate::config::Config;
-use crate::display::Display;
-use crate::event::{EventProxy, Processor};
-
-fn main() {
- panic::attach_handler();
-
- // When linked with the windows subsystem windows won't automatically attach
- // to the console of the parent process, so we do it explicitly. This fails
- // silently if the parent has no console.
- #[cfg(windows)]
- unsafe {
- AttachConsole(ATTACH_PARENT_PROCESS);
- }
-
- // Load command line options
- let options = Options::new();
-
- // Setup glutin event loop
- let window_event_loop = GlutinEventLoop::<Event>::with_user_event();
-
- // Initialize the logger as soon as possible as to capture output from other subsystems
- let log_file = logging::initialize(&options, window_event_loop.create_proxy())
- .expect("Unable to initialize logger");
-
- // Load configuration file
- let config_path = options.config_path().or_else(config::installed_config);
- let config = config_path.map(config::load_from).unwrap_or_else(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")]
- env::set_current_dir(dirs::home_dir().unwrap()).unwrap();
- // Set locale
- #[cfg(target_os = "macos")]
- locale::set_locale_environment();
-
- // Store if log file should be deleted before moving config
- let persistent_logging = config.persistent_logging();
-
- // Run alacritty
- if let Err(err) = run(window_event_loop, config) {
- println!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", err);
- std::process::exit(1);
- }
-
- // Clean up logfile
- if let Some(log_file) = log_file {
- if !persistent_logging && fs::remove_file(&log_file).is_ok() {
- let _ = writeln!(io::stdout(), "Deleted log file at \"{}\"", log_file.display());
- }
- }
-}
-
-/// Run Alacritty
-///
-/// Creates a window, the terminal state, pty, I/O event loop, input processor,
-/// config change monitor, and runs the main display loop.
-fn run(window_event_loop: GlutinEventLoop<Event>, config: Config) -> Result<(), Box<dyn Error>> {
- info!("Welcome to Alacritty");
-
- match &config.config_path {
- Some(config_path) => info!("Configuration loaded from \"{}\"", config_path.display()),
- None => info!("No configuration file found"),
- }
-
- // Set environment variables
- tty::setup_env(&config);
-
- let event_proxy = EventProxy::new(window_event_loop.create_proxy());
-
- // Create a display
- //
- // The display manages a window and can draw the terminal.
- let display = Display::new(&config, &window_event_loop)?;
-
- info!("PTY Dimensions: {:?} x {:?}", display.size_info.lines(), display.size_info.cols());
-
- // Create new native clipboard
- #[cfg(not(any(target_os = "macos", windows)))]
- let clipboard = Clipboard::new(display.window.wayland_display());
- #[cfg(any(target_os = "macos", windows))]
- let clipboard = Clipboard::new();
-
- // Create the terminal
- //
- // This object contains all of the state about what's being displayed. It's
- // wrapped in a clonable mutex since both the I/O loop and display need to
- // access it.
- let terminal = Term::new(&config, &display.size_info, clipboard, event_proxy.clone());
- let terminal = Arc::new(FairMutex::new(terminal));
-
- // Create the pty
- //
- // 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.
- #[cfg(not(any(target_os = "macos", windows)))]
- let pty = tty::new(&config, &display.size_info, display.window.x11_window_id());
- #[cfg(any(target_os = "macos", windows))]
- let pty = tty::new(&config, &display.size_info, None);
-
- // Create the pseudoterminal I/O loop
- //
- // pty I/O is ran on another thread as to not occupy cycles used by the
- // renderer and input processing. Note that access to the terminal state is
- // synchronized since the I/O loop updates the state, and the display
- // consumes it periodically.
- let event_loop = EventLoop::new(Arc::clone(&terminal), event_proxy.clone(), pty, &config);
-
- // The event loop channel allows write requests from the event processor
- // to be sent to the pty loop and ultimately written to the pty.
- let loop_tx = event_loop.channel();
-
- // Create a config monitor when config was loaded from path
- //
- // The monitor watches the config file for changes and reloads it. Pending
- // config changes are processed in the main loop.
- if config.live_config_reload() {
- config.config_path.as_ref().map(|path| Monitor::new(path, event_proxy.clone()));
- }
-
- // Setup storage for message UI
- let message_buffer = MessageBuffer::new();
-
- // Event processor
- let mut processor =
- Processor::new(event_loop::Notifier(loop_tx.clone()), message_buffer, config, display);
-
- // Kick off the I/O thread
- let io_thread = event_loop.spawn();
-
- info!("Initialisation complete");
-
- // Start event loop and block until shutdown
- processor.run(terminal, window_event_loop);
-
- // This explicit drop is needed for Windows, ConPTY backend. Otherwise a deadlock can occur.
- // The cause:
- // - Drop for Conpty will deadlock if the conout pipe has already been dropped.
- // - The conout pipe is dropped when the io_thread is joined below (io_thread owns pty).
- // - Conpty is dropped when the last of processor and io_thread are dropped, because both of
- // them own an Arc<Conpty>.
- //
- // The fix is to ensure that processor is dropped first. That way, when io_thread (i.e. pty)
- // is dropped, it can ensure Conpty is dropped before the conout pipe in the pty drop order.
- //
- // FIXME: Change PTY API to enforce the correct drop order with the typesystem.
- drop(processor);
-
- // Shutdown PTY parser event loop
- loop_tx.send(Msg::Shutdown).expect("Error sending shutdown to pty event loop");
- io_thread.join().expect("join io thread");
-
- // FIXME patch notify library to have a shutdown method
- // config_reloader.join().ok();
-
- // Without explicitly detaching the console cmd won't redraw it's prompt
- #[cfg(windows)]
- unsafe {
- FreeConsole();
- }
-
- info!("Goodbye");
-
- Ok(())
-}
diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs
deleted file mode 100644
index a099b0d..0000000
--- a/alacritty/src/renderer/mod.rs
+++ /dev/null
@@ -1,1654 +0,0 @@
-// 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::collections::HashMap;
-use std::fs::File;
-use std::hash::BuildHasherDefault;
-use std::io::{self, Read};
-use std::mem::size_of;
-use std::path::PathBuf;
-use std::ptr;
-use std::sync::mpsc;
-use std::time::Duration;
-
-use fnv::FnvHasher;
-use font::{
- self, BitmapBuffer, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer,
-};
-use log::{error, info};
-use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
-
-use crate::cursor;
-use crate::gl;
-use crate::gl::types::*;
-use crate::renderer::rects::RenderRect;
-use alacritty_terminal::config::{self, Config, Delta, Font, StartupMode};
-use alacritty_terminal::index::{Column, Line};
-use alacritty_terminal::term::cell::{self, Flags};
-use alacritty_terminal::term::color::Rgb;
-use alacritty_terminal::term::{self, CursorKey, RenderableCell, RenderableCellContent, SizeInfo};
-use alacritty_terminal::util;
-use std::fmt::{self, Display, Formatter};
-
-pub mod rects;
-
-// Shader paths for live reload
-static TEXT_SHADER_F_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.f.glsl");
-static TEXT_SHADER_V_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.v.glsl");
-static RECT_SHADER_F_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.f.glsl");
-static RECT_SHADER_V_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.v.glsl");
-
-// Shader source which is used when live-shader-reload feature is disable
-static TEXT_SHADER_F: &str =
- include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.f.glsl"));
-static TEXT_SHADER_V: &str =
- include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.v.glsl"));
-static RECT_SHADER_F: &str =
- include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.f.glsl"));
-static RECT_SHADER_V: &str =
- include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.v.glsl"));
-
-/// `LoadGlyph` allows for copying a rasterized glyph into graphics memory
-pub trait LoadGlyph {
- /// Load the rasterized glyph into GPU memory
- fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph;
-
- /// Clear any state accumulated from previous loaded glyphs
- ///
- /// This can, for instance, be used to reset the texture Atlas.
- fn clear(&mut self);
-}
-
-enum Msg {
- ShaderReload,
-}
-
-#[derive(Debug)]
-pub enum Error {
- ShaderCreation(ShaderCreationError),
-}
-
-impl std::error::Error for Error {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- match self {
- Error::ShaderCreation(err) => err.source(),
- }
- }
-}
-
-impl Display for Error {
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- match self {
- Error::ShaderCreation(err) => {
- write!(f, "There was an error initializing the shaders: {}", err)
- },
- }
- }
-}
-
-impl From<ShaderCreationError> for Error {
- fn from(val: ShaderCreationError) -> Self {
- Error::ShaderCreation(val)
- }
-}
-
-/// Text drawing program
-///
-/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a".
-#[derive(Debug)]
-pub struct TextShaderProgram {
- // Program id
- id: GLuint,
-
- /// projection scale and offset uniform
- u_projection: GLint,
-
- /// Cell dimensions (pixels)
- u_cell_dim: GLint,
-
- /// Background pass flag
- ///
- /// Rendering is split into two passes; 1 for backgrounds, and one for text
- u_background: GLint,
-}
-
-/// Rectangle drawing program
-///
-/// Uniforms are prefixed with "u"
-#[derive(Debug)]
-pub struct RectShaderProgram {
- // Program id
- id: GLuint,
- /// Rectangle color
- u_color: GLint,
-}
-
-#[derive(Copy, Debug, Clone)]
-pub struct Glyph {
- tex_id: GLuint,
- colored: bool,
- top: f32,
- left: f32,
- width: f32,
- height: f32,
- uv_bot: f32,
- uv_left: f32,
- uv_width: f32,
- uv_height: f32,
-}
-
-/// Naïve glyph cache
-///
-/// Currently only keyed by `char`, and thus not possible to hold different
-/// representations of the same code point.
-pub struct GlyphCache {
- /// Cache of buffered glyphs
- cache: HashMap<GlyphKey, Glyph, BuildHasherDefault<FnvHasher>>,
-
- /// Cache of buffered cursor glyphs
- cursor_cache: HashMap<CursorKey, Glyph, BuildHasherDefault<FnvHasher>>,
-
- /// Rasterizer for loading new glyphs
- rasterizer: Rasterizer,
-
- /// regular font
- font_key: FontKey,
-
- /// bold font
- bold_key: FontKey,
-
- /// italic font
- italic_key: FontKey,
-
- /// bold italic font
- bold_italic_key: FontKey,
-
- /// font size
- font_size: font::Size,
-
- /// glyph offset
- glyph_offset: Delta<i8>,
-
- metrics: font::Metrics,
-}
-
-impl GlyphCache {
- pub fn new<L>(
- mut rasterizer: Rasterizer,
- font: &config::Font,
- loader: &mut L,
- ) -> Result<GlyphCache, font::Error>
- where
- L: LoadGlyph,
- {
- let (regular, bold, italic, bold_italic) = Self::compute_font_keys(font, &mut rasterizer)?;
-
- // 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 })?;
-
- let metrics = rasterizer.metrics(regular, font.size)?;
-
- let mut cache = Self {
- cache: HashMap::default(),
- cursor_cache: HashMap::default(),
- rasterizer,
- font_size: font.size,
- font_key: regular,
- bold_key: bold,
- italic_key: italic,
- bold_italic_key: bold_italic,
- glyph_offset: font.glyph_offset,
- metrics,
- };
-
- cache.load_glyphs_for_font(regular, loader);
- cache.load_glyphs_for_font(bold, loader);
- cache.load_glyphs_for_font(italic, loader);
- cache.load_glyphs_for_font(bold_italic, loader);
-
- Ok(cache)
- }
-
- fn load_glyphs_for_font<L: LoadGlyph>(&mut self, font: FontKey, loader: &mut L) {
- let size = self.font_size;
- for i in 32u8..=126u8 {
- self.get(GlyphKey { font_key: font, c: i as char, size }, loader);
- }
- }
-
- /// Computes font keys for (Regular, Bold, Italic, Bold Italic)
- fn compute_font_keys(
- font: &config::Font,
- rasterizer: &mut Rasterizer,
- ) -> Result<(FontKey, FontKey, FontKey, FontKey), font::Error> {
- let size = font.size;
-
- // Load regular font
- let regular_desc =
- Self::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal);
-
- let regular = rasterizer.load_font(®ular_desc, size)?;
-
- // helper to load a description if it is not the regular_desc
- let mut load_or_regular = |desc: FontDesc| {
- if desc == regular_desc {
- regular
- } else {
- rasterizer.load_font(&desc, size).unwrap_or_else(|_| regular)
- }
- };
-
- // Load bold font
- let bold_desc = Self::make_desc(&font.bold(), font::Slant::Normal, font::Weight::Bold);
-
- let bold = load_or_regular(bold_desc);
-
- // Load italic font
- let italic_desc =
- Self::make_desc(&font.italic(), font::Slant::Italic, font::Weight::Normal);
-
- let italic = load_or_regular(italic_desc);
-
- // Load bold italic font
- let bold_italic_desc =
- Self::make_desc(&font.bold_italic(), font::Slant::Italic, font::Weight::Bold);
-
- let bold_italic = load_or_regular(bold_italic_desc);
-
- Ok((regular, bold, italic, bold_italic))
- }
-
- fn make_desc(
- desc: &config::FontDescription,
- slant: font::Slant,
- weight: font::Weight,
- ) -> FontDesc {
- let style = if let Some(ref spec) = desc.style {
- font::Style::Specific(spec.to_owned())
- } else {
- font::Style::Description { slant, weight }
- };
- FontDesc::new(desc.family.clone(), style)
- }
-
- pub fn get<L>(&mut self, glyph_key: GlyphKey, loader: &mut L) -> &Glyph
- where
- L: LoadGlyph,
- {
- let glyph_offset = self.glyph_offset;
- let rasterizer = &mut self.rasterizer;
- let metrics = &self.metrics;
- self.cache.entry(glyph_key).or_insert_with(|| {
- let mut rasterized =
- rasterizer.get_glyph(glyph_key).unwrap_or_else(|_| Default::default());
-
- rasterized.left += i32::from(glyph_offset.x);
- rasterized.top += i32::from(glyph_offset.y);
- rasterized.top -= metrics.descent as i32;
-
- loader.load_glyph(&rasterized)
- })
- }
-
- pub fn update_font_size<L: LoadGlyph>(
- &mut self,
- font: config::Font,
- dpr: f64,
- loader: &mut L,
- ) -> Result<(), font::Error> {
- // Clear currently cached data in both GL and the registry
- loader.clear();
- self.cache = HashMap::default();
- self.cursor_cache = HashMap::default();
-
- // Update dpi scaling
- self.rasterizer.update_dpr(dpr as f32);
-
- // Recompute font keys
- let (regular, bold, italic, bold_italic) =
- Self::compute_font_keys(&font, &mut self.rasterizer)?;
-
- self.rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?;
- let metrics = self.rasterizer.metrics(regular, font.size)?;
-
- info!("Font size changed to {:?} with DPR of {}", font.size, dpr);
-
- self.font_size = font.size;
- self.font_key = regular;
- self.bold_key = bold;
- self.italic_key = italic;
- self.bold_italic_key = bold_italic;
- self.metrics = metrics;
-
- self.load_glyphs_for_font(regular, loader);
- self.load_glyphs_for_font(bold, loader);
- self.load_glyphs_for_font(italic, loader);
- self.load_glyphs_for_font(bold_italic, loader);
-
- Ok(())
- }
-
- pub fn font_metrics(&self) -> font::Metrics {
- self.metrics
- }
-
- // Calculate font metrics without access to a glyph cache
- pub fn static_metrics(font: Font, dpr: f64) -> Result<font::Metrics, font::Error> {
- let mut rasterizer = font::Rasterizer::new(dpr as f32, font.use_thin_strokes())?;
- let regular_desc =
- GlyphCache::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal);
- let regular = rasterizer.load_font(®ular_desc, font.size)?;
- rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?;
-
- rasterizer.metrics(regular, font.size)
- }
-
- pub fn calculate_dimensions<C>(
- config: &Config<C>,
- dpr: f64,
- cell_width: f32,
- cell_height: f32,
- ) -> Option<(u32, u32)> {
- let dimensions = config.window.dimensions;
-
- if dimensions.columns_u32() == 0
- || dimensions.lines_u32() == 0
- || config.window.startup_mode() != StartupMode::Windowed
- {
- return None;
- }
-
- 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();
- let grid_height = cell_height as u32 * dimensions.lines_u32();
-
- let width = padding_x.mul_add(2., f64::from(grid_width)).floor();
- let height = padding_y.mul_add(2., f64::from(grid_height)).floor();
-
- Some((width as u32, height as u32))
- }
-}
-
-#[derive(Debug)]
-#[repr(C)]
-struct InstanceData {
- // coords
- col: f32,
- row: f32,
- // glyph offset
- left: f32,
- top: f32,
- // glyph scale
- width: f32,
- height: f32,
- // uv offset
- uv_left: f32,
- uv_bot: f32,
- // uv scale
- uv_width: f32,
- uv_height: f32,
- // color
- r: f32,
- g: f32,
- b: f32,
- // background color
- bg_r: f32,
- bg_g: f32,
- bg_b: f32,
- bg_a: f32,
-}
-
-#[derive(Debug)]
-pub struct QuadRenderer {
- program: TextShaderProgram,
- rect_program: RectShaderProgram,
- vao: GLuint,
- ebo: GLuint,
- vbo_instance: GLuint,
- rect_vao: GLuint,
- rect_vbo: GLuint,
- atlas: Vec<Atlas>,
- current_atlas: usize,
- active_tex: GLuint,
- batch: Batch,
- rx: mpsc::Receiver<Msg>,
-}
-
-#[derive(Debug)]
-pub struct RenderApi<'a, C> {
- active_tex: &'a mut GLuint,
- batch: &'a mut Batch,
- atlas: &'a mut Vec<Atlas>,
- current_atlas: &'a mut usize,
- program: &'a mut TextShaderProgram,
- config: &'a Config<C>,
-}
-
-#[derive(Debug)]
-pub struct LoaderApi<'a> {
- active_tex: &'a mut GLuint,
- atlas: &'a mut Vec<Atlas>,
- current_atlas: &'a mut usize,
-}
-
-#[derive(Debug, Default)]
-pub struct Batch {
- tex: GLuint,
- instances: Vec<InstanceData>,
-}
-
-impl Batch {
- #[inline]
- pub fn new() -> Self {
- Self { tex: 0, instances: Vec::with_capacity(BATCH_MAX) }
- }
-
- pub fn add_item(&mut self, mut cell: RenderableCell, glyph: &Glyph) {
- if self.is_empty() {
- self.tex = glyph.tex_id;
- }
-
- if glyph.colored {
- // XXX Temporary workaround to prevent emojis being rendered with a wrong colors on, at
- // least, dark backgrounds. For more info see #1864.
- cell.fg.r = 255;
- cell.fg.g = 255;
- cell.fg.b = 255;
- }
-
- self.instances.push(InstanceData {
- col: cell.column.0 as f32,
- row: cell.line.0 as f32,
-
- top: glyph.top,
- left: glyph.left,
- width: glyph.width,
- height: glyph.height,
-
- uv_bot: glyph.uv_bot,
- uv_left: glyph.uv_left,
- uv_width: glyph.uv_width,
- uv_height: glyph.uv_height,
-
- r: f32::from(cell.fg.r),
- g: f32::from(cell.fg.g),
- b: f32::from(cell.fg.b),
-
- bg_r: f32::from(cell.bg.r),
- bg_g: f32::from(cell.bg.g),
- bg_b: f32::from(cell.bg.b),
- bg_a: cell.bg_alpha,
- });
- }
-
- #[inline]
- pub fn full(&self) -> bool {
- self.capacity() == self.len()
- }
-
- #[inline]
- pub fn len(&self) -> usize {
- self.instances.len()
- }
-
- #[inline]
- pub fn capacity(&self) -> usize {
- BATCH_MAX
- }
-
- #[inline]
- pub fn is_empty(&self) -> bool {
- self.len() == 0
- }
-
- #[inline]
- pub fn size(&self) -> usize {
- self.len() * size_of::<InstanceData>()
- }
-
- pub fn clear(&mut self) {
- self.tex = 0;
- self.instances.clear();
- }
-}
-
-/// Maximum items to be drawn in a batch.
-const BATCH_MAX: usize = 0x1_0000;
-const ATLAS_SIZE: i32 = 1024;
-
-impl QuadRenderer {
- pub fn new() -> Result<QuadRenderer, Error> {
- let program = TextShaderProgram::new()?;
- let rect_program = RectShaderProgram::new()?;
-
- let mut vao: GLuint = 0;
- let mut ebo: GLuint = 0;
-
- let mut vbo_instance: GLuint = 0;
-
- let mut rect_vao: GLuint = 0;
- let mut rect_vbo: GLuint = 0;
- let mut rect_ebo: GLuint = 0;
-
- unsafe {
- gl::Enable(gl::BLEND);
- gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
- gl::Enable(gl::MULTISAMPLE);
-
- // Disable depth mask, as the renderer never uses depth tests
- gl::DepthMask(gl::FALSE);
-
- gl::GenVertexArrays(1, &mut vao);
- gl::GenBuffers(1, &mut ebo);
- gl::GenBuffers(1, &mut vbo_instance);
- gl::BindVertexArray(vao);
-
- // ---------------------
- // Set up element buffer
- // ---------------------
- let indices: [u32; 6] = [0, 1, 3, 1, 2, 3];
-
- gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
- gl::BufferData(
- gl::ELEMENT_ARRAY_BUFFER,
- (6 * size_of::<u32>()) as isize,
- indices.as_ptr() as *const _,
- gl::STATIC_DRAW,
- );
-
- // ----------------------------
- // Setup vertex instance buffer
- // ----------------------------
- gl::BindBuffer(gl::ARRAY_BUFFER, vbo_instance);
- gl::BufferData(
- gl::ARRAY_BUFFER,
- (BATCH_MAX * size_of::<InstanceData>()) as isize,
- ptr::null(),
- gl::STREAM_DRAW,
- );
- // coords
- gl::VertexAttribPointer(
- 0,
- 2,
- gl::FLOAT,
- gl::FALSE,
- size_of::<InstanceData>() as i32,
- ptr::null(),
- );
- gl::EnableVertexAttribArray(0);
- gl::VertexAttribDivisor(0, 1);
- // glyphoffset
- gl::VertexAttribPointer(
- 1,
- 4,
- gl::FLOAT,
- gl::FALSE,
- size_of::<InstanceData>() as i32,
- (2 * size_of::<f32>()) as *const _,
- );
- gl::EnableVertexAttribArray(1);
- gl::VertexAttribDivisor(1, 1);
- // uv
- gl::VertexAttribPointer(
- 2,
- 4,
- gl::FLOAT,
- gl::FALSE,
- size_of::<InstanceData>() as i32,
- (6 * size_of::<f32>()) as *const _,
- );
- gl::EnableVertexAttribArray(2);
- gl::VertexAttribDivisor(2, 1);
- // color
- gl::VertexAttribPointer(
- 3,
- 3,
- gl::FLOAT,
- gl::FALSE,
- size_of::<InstanceData>() as i32,
- (10 * size_of::<f32>()) as *const _,
- );
- gl::EnableVertexAttribArray(3);
- gl::VertexAttribDivisor(3, 1);
- // color
- gl::VertexAttribPointer(
- 4,
- 4,
- gl::FLOAT,
- gl::FALSE,
- size_of::<InstanceData>() as i32,
- (13 * size_of::<f32>()) as *const _,
- );
- gl::EnableVertexAttribArray(4);
- gl::VertexAttribDivisor(4, 1);
-
- // Rectangle setup
- gl::GenVertexArrays(1, &mut rect_vao);
- gl::GenBuffers(1, &mut rect_vbo);
- gl::GenBuffers(1, &mut rect_ebo);
- gl::BindVertexArray(rect_vao);
- let indices: [i32; 6] = [0, 1, 3, 1, 2, 3];
- gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, rect_ebo);
- gl::BufferData(
- gl::ELEMENT_ARRAY_BUFFER,
- (size_of::<i32>() * indices.len()) as _,
- indices.as_ptr() as *const _,
- gl::STATIC_DRAW,
- );
-
- // Cleanup
- gl::BindVertexArray(0);
- gl::BindBuffer(gl::ARRAY_BUFFER, 0);
- gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
- }
-
- let (msg_tx, msg_rx) = mpsc::channel();
-
- if cfg!(feature = "live-shader-reload") {
- util::thread::spawn_named("live shader reload", move || {
- let (tx, rx) = std::sync::mpsc::channel();
- // The Duration argument is a debouncing period.
- let mut watcher =
- watcher(tx, Duration::from_millis(10)).expect("create file watcher");
- watcher
- .watch(TEXT_SHADER_F_PATH, RecursiveMode::NonRecursive)
- .expect("watch fragment shader");
- watcher
- .watch(TEXT_SHADER_V_PATH, RecursiveMode::NonRecursive)
- .expect("watch vertex shader");
-
- loop {
- let event = rx.recv().expect("watcher event");
-
- match event {
- DebouncedEvent::Rename(..) => continue,
- DebouncedEvent::Create(_)
- | DebouncedEvent::Write(_)
- | DebouncedEvent::Chmod(_) => {
- msg_tx.send(Msg::ShaderReload).expect("msg send ok");
- },
- _ => {},
- }
- }
- });
- }
-
- let mut renderer = Self {
- program,
- rect_program,
- vao,
- ebo,
- vbo_instance,
- rect_vao,
- rect_vbo,
- atlas: Vec::new(),
- current_atlas: 0,
- active_tex: 0,
- batch: Batch::new(),
- rx: msg_rx,
- };
-
- let atlas = Atlas::new(ATLAS_SIZE);
- renderer.atlas.push(atlas);
-
- Ok(renderer)
- }
-
- // Draw all rectangles simultaneously to prevent excessive program swaps
- pub fn draw_rects(&mut self, props: &term::SizeInfo, rects: Vec<RenderRect>) {
- // Swap to rectangle rendering program
- unsafe {
- // Swap program
- gl::UseProgram(self.rect_program.id);
-
- // Remove padding from viewport
- gl::Viewport(0, 0, props.width as i32, props.height as i32);
-
- // Change blending strategy
- gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::SRC_ALPHA, gl::ONE);
-
- // Setup data and buffers
- gl::BindVertexArray(self.rect_vao);
- gl::BindBuffer(gl::ARRAY_BUFFER, self.rect_vbo);
-
- // Position
- gl::VertexAttribPointer(
- 0,
- 2,
- gl::FLOAT,
- gl::FALSE,
- (size_of::<f32>() * 2) as _,
- ptr::null(),
- );
- gl::EnableVertexAttribArray(0);
- }
-
- // Draw all the rects
- for rect in rects {
- self.render_rect(&rect, props);
- }
-
- // Deactivate rectangle program again
- unsafe {
- // Reset blending strategy
- gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
-
- // Reset data and buffers
- gl::BindBuffer(gl::ARRAY_BUFFER, 0);
- gl::BindVertexArray(0);
-
- let padding_x = props.padding_x as i32;
- let padding_y = props.padding_y as i32;
- let width = props.width as i32;
- let height = props.height as i32;
- gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y);
-
- // Disable program
- gl::UseProgram(0);
- }
- }
-
- pub fn with_api<F, T, C>(&mut self, config: &Config<C>, props: &term::SizeInfo, func: F) -> T
- where
- F: FnOnce(RenderApi<'_, C>) -> T,
- {
- // Flush message queue
- if let Ok(Msg::ShaderReload) = self.rx.try_recv() {
- self.reload_shaders(props);
- }
- while let Ok(_) = self.rx.try_recv() {}
-
- unsafe {
- gl::UseProgram(self.program.id);
- self.program.set_term_uniforms(props);
-
- gl::BindVertexArray(self.vao);
- gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo);
- gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo_instance);
- gl::ActiveTexture(gl::TEXTURE0);
- }
-
- let res = func(RenderApi {
- active_tex: &mut self.active_tex,
- batch: &mut self.batch,
- atlas: &mut self.atlas,
- current_atlas: &mut self.current_atlas,
- program: &mut self.program,
- config,
- });
-
- unsafe {
- gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
- gl::BindBuffer(gl::ARRAY_BUFFER, 0);
- gl::BindVertexArray(0);
-
- gl::UseProgram(0);
- }
-
- res
- }
-
- pub fn with_loader<F, T>(&mut self, func: F) -> T
- where
- F: FnOnce(LoaderApi<'_>) -> T,
- {
- unsafe {
- gl::ActiveTexture(gl::TEXTURE0);
- }
-
- func(LoaderApi {
- active_tex: &mut self.active_tex,
- atlas: &mut self.atlas,
- current_atlas: &mut self.current_atlas,
- })
- }
-
- pub fn reload_shaders(&mut self, props: &term::SizeInfo) {
- info!("Reloading shaders...");
- let result = (TextShaderProgram::new(), RectShaderProgram::new());
- let (program, rect_program) = match result {
- (Ok(program), Ok(rect_program)) => {
- unsafe {
- gl::UseProgram(program.id);
- program.update_projection(
- props.width,
- props.height,
- props.padding_x,
- props.padding_y,
- );
- gl::UseProgram(0);
- }
-
- info!("... successfully reloaded shaders");
- (program, rect_program)
- },
- (Err(err), _) | (_, Err(err)) => {
- error!("{}", err);
- return;
- },
- };
-
- self.active_tex = 0;
- self.program = program;
- self.rect_program = rect_program;
- }
-
- pub fn resize(&mut self, size: &SizeInfo) {
- // viewport
- unsafe {
- gl::Viewport(
- size.padding_x as i32,
- size.padding_y as i32,
- size.width as i32 - 2 * size.padding_x as i32,
- size.height as i32 - 2 * size.padding_y as i32,
- );
-
- // update projection
- gl::UseProgram(self.program.id);
- self.program.update_projection(size.width, size.height, size.padding_x, size.padding_y);
- gl::UseProgram(0);
- }
- }
-
- // Render a rectangle
- //
- // This requires the rectangle program to be activated
- fn render_rect(&mut self, rect: &RenderRect, size: &term::SizeInfo) {
- // Do nothing when alpha is fully transparent
- if rect.alpha == 0. {
- return;
- }
-
- // Calculate rectangle position
- let center_x = size.width / 2.;
- let center_y = size.height / 2.;
- let x = (rect.x - center_x) / center_x;
- let y = -(rect.y - center_y) / center_y;
- let width = rect.width / center_x;
- let height = rect.height / center_y;
-
- unsafe {
- // Setup vertices
- let vertices: [f32; 8] = [x + width, y, x + width, y - height, x, y - height, x, y];
-
- // Load vertex data into array buffer
- gl::BufferData(
- gl::ARRAY_BUFFER,
- (size_of::<f32>() * vertices.len()) as _,
- vertices.as_ptr() as *const _,
- gl::STATIC_DRAW,
- );
-
- // Color
- self.rect_program.set_color(rect.color, rect.alpha);
-
- // Draw the rectangle
- gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, ptr::null());
- }
- }
-}
-
-impl<'a, C> RenderApi<'a, C> {
- pub fn clear(&self, color: Rgb) {
- unsafe {
- let alpha = self.config.background_opacity();
- gl::ClearColor(
- (f32::from(color.r) / 255.0).min(1.0) * alpha,
- (f32::from(color.g) / 255.0).min(1.0) * alpha,
- (f32::from(color.b) / 255.0).min(1.0) * alpha,
- alpha,
- );
- gl::Clear(gl::COLOR_BUFFER_BIT);
- }
- }
-
- fn render_batch(&mut self) {
- unsafe {
- gl::BufferSubData(
- gl::ARRAY_BUFFER,
- 0,
- self.batch.size() as isize,
- self.batch.instances.as_ptr() as *const _,
- );
- }
-
- // Bind texture if necessary
- if *self.active_tex != self.batch.tex {
- unsafe {
- gl::BindTexture(gl::TEXTURE_2D, self.batch.tex);
- }
- *self.active_tex = self.batch.tex;
- }
-
- unsafe {
- self.program.set_background_pass(true);
- gl::DrawElementsInstanced(
- gl::TRIANGLES,
- 6,
- gl::UNSIGNED_INT,
- ptr::null(),
- self.batch.len() as GLsizei,
- );
- self.program.set_background_pass(false);
- gl::DrawElementsInstanced(
- gl::TRIANGLES,
- 6,
- gl::UNSIGNED_INT,
- ptr::null(),
- self.batch.len() as GLsizei,
- );
- }
-
- self.batch.clear();
- }
-
- /// Render a string in a variable location. Used for printing the render timer, warnings and
- /// errors.
- pub fn render_string(
- &mut self,
- string: &str,
- line: Line,
- glyph_cache: &mut GlyphCache,
- color: Option<Rgb>,
- ) {
- let bg_alpha = color.map(|_| 1.0).unwrap_or(0.0);
- let col = Column(0);
-
- let cells = string
- .chars()
- .enumerate()
- .map(|(i, c)| RenderableCell {
- line,
- column: col + i,
- inner: RenderableCellContent::Chars({
- let mut chars = [' '; cell::MAX_ZEROWIDTH_CHARS + 1];
- chars[0] = c;
- chars
- }),
- bg: color.unwrap_or(Rgb { r: 0, g: 0, b: 0 }),
- fg: Rgb { r: 0, g: 0, b: 0 },
- flags: Flags::empty(),
- bg_alpha,
- })
- .collect::<Vec<_>>();
-
- for cell in cells {
- self.render_cell(cell, glyph_cache);
- }
- }
-
- #[inline]
- fn add_render_item(&mut self, cell: RenderableCell, glyph: &Glyph) {
- // Flush batch if tex changing
- if !self.batch.is_empty() && self.batch.tex != glyph.tex_id {
- self.render_batch();
- }
-
- self.batch.add_item(cell, glyph);
-
- // Render batch and clear if it's full
- if self.batch.full() {
- self.render_batch();
- }
- }
-
- pub fn render_cell(&mut self, cell: RenderableCell, glyph_cache: &mut GlyphCache) {
- let chars = match cell.inner {
- RenderableCellContent::Cursor(cursor_key) => {
- // Raw cell pixel buffers like cursors don't need to go through font lookup
- let metrics = glyph_cache.metrics;
- let glyph = glyph_cache.cursor_cache.entry(cursor_key).or_insert_with(|| {
- self.load_glyph(&cursor::get_cursor_glyph(
- cursor_key.style,
- metrics,
- self.config.font.offset.x,
- self.config.font.offset.y,
- cursor_key.is_wide,
- ))
- });
- self.add_render_item(cell, glyph);
- return;
- },
- RenderableCellContent::Chars(chars) => chars,
- };
-
- // Get font key for cell
- let font_key = match cell.flags & Flags::BOLD_ITALIC {
- Flags::BOLD_ITALIC => glyph_cache.bold_italic_key,
- Flags::ITALIC => glyph_cache.italic_key,
- Flags::BOLD => glyph_cache.bold_key,
- _ => glyph_cache.font_key,
- };
-
- // Don't render text of HIDDEN cells
- let mut chars = if cell.flags.contains(Flags::HIDDEN) {
- [' '; cell::MAX_ZEROWIDTH_CHARS + 1]
- } else {
- chars
- };
-
- // Render tabs as spaces in case the font doesn't support it
- if chars[0] == '\t' {
- chars[0] = ' ';
- }
-
- let mut glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, c: chars[0] };
-
- // Add cell to batch
- let glyph = glyph_cache.get(glyph_key, self);
- self.add_render_item(cell, glyph);
-
- // Render zero-width characters
- for c in (&chars[1..]).iter().filter(|c| **c != ' ') {
- glyph_key.c = *c;
- let mut glyph = *glyph_cache.get(glyph_key, self);
-
- // The metrics of zero-width characters are based on rendering
- // the character after the current cell, with the anchor at the
- // right side of the preceding character. Since we render the
- // zero-width characters inside the preceding character, the
- // anchor has been moved to the right by one cell.
- glyph.left += glyph_cache.metrics.average_advance as f32;
-
- self.add_render_item(cell, &glyph);
- }
- }
-}
-
-/// Load a glyph into a texture atlas
-///
-/// If the current atlas is full, a new one will be created.
-#[inline]
-fn load_glyph(
- active_tex: &mut GLuint,
- atlas: &mut Vec<Atlas>,
- current_atlas: &mut usize,
- rasterized: &RasterizedGlyph,
-) -> Glyph {
- // At least one atlas is guaranteed to be in the `self.atlas` list; thus
- // the unwrap.
- match atlas[*current_atlas].insert(rasterized, active_tex) {
- Ok(glyph) => glyph,
- Err(AtlasInsertError::Full) => {
- *current_atlas += 1;
- if *current_atlas == atlas.len() {
- let new = Atlas::new(ATLAS_SIZE);
- *active_tex = 0; // Atlas::new binds a texture. Ugh this is sloppy.
- atlas.push(new);
- }
- load_glyph(active_tex, atlas, current_atlas, rasterized)
- },
- Err(AtlasInsertError::GlyphTooLarge) => Glyph {
- tex_id: atlas[*current_atlas].id,
- colored: false,
- top: 0.0,
- left: 0.0,
- width: 0.0,
- height: 0.0,
- uv_bot: 0.0,
- uv_left: 0.0,
- uv_width: 0.0,
- uv_height: 0.0,
- },
- }
-}
-
-#[inline]
-fn clear_atlas(atlas: &mut Vec<Atlas>, current_atlas: &mut usize) {
- for atlas in atlas.iter_mut() {
- atlas.clear();
- }
- *current_atlas = 0;
-}
-
-impl<'a> LoadGlyph for LoaderApi<'a> {
- fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
- load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized)
- }
-
- fn clear(&mut self) {
- clear_atlas(self.atlas, self.current_atlas)
- }
-}
-
-impl<'a, C> LoadGlyph for RenderApi<'a, C> {
- fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
- load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized)
- }
-
- fn clear(&mut self) {
- clear_atlas(self.atlas, self.current_atlas)
- }
-}
-
-impl<'a, C> Drop for RenderApi<'a, C> {
- fn drop(&mut self) {
- if !self.batch.is_empty() {
- self.render_batch();
- }
- }
-}
-
-impl TextShaderProgram {
- pub fn new() -> Result<TextShaderProgram, ShaderCreationError> {
- let (vertex_src, fragment_src) = if cfg!(feature = "live-shader-reload") {
- (None, None)
- } else {
- (Some(TEXT_SHADER_V), Some(TEXT_SHADER_F))
- };
- let vertex_shader = create_shader(TEXT_SHADER_V_PATH, gl::VERTEX_SHADER, vertex_src)?;
- let fragment_shader = create_shader(TEXT_SHADER_F_PATH, gl::FRAGMENT_SHADER, fragment_src)?;
- let program = create_program(vertex_shader, fragment_shader)?;
-
- unsafe {
- gl::DeleteShader(fragment_shader);
- gl::DeleteShader(vertex_shader);
- gl::UseProgram(program);
- }
-
- macro_rules! cptr {
- ($thing:expr) => {
- $thing.as_ptr() as *const _
- };
- }
-
- macro_rules! assert_uniform_valid {
- ($uniform:expr) => {
- assert!($uniform != gl::INVALID_VALUE as i32);
- assert!($uniform != gl::INVALID_OPERATION as i32);
- };
- ( $( $uniform:expr ),* ) => {
- $( assert_uniform_valid!($uniform); )*
- };
- }
-
- // get uniform locations
- let (projection, cell_dim, background) = unsafe {
- (
- gl::GetUniformLocation(program, cptr!(b"projection\0")),
- gl::GetUniformLocation(program, cptr!(b"cellDim\0")),
- gl::GetUniformLocation(program, cptr!(b"backgroundPass\0")),
- )
- };
-
- assert_uniform_valid!(projection, cell_dim, background);
-
- let shader = Self {
- id: program,
- u_projection: projection,
- u_cell_dim: cell_dim,
- u_background: background,
- };
-
- unsafe {
- gl::UseProgram(0);
- }
-
- Ok(shader)
- }
-
- fn update_projection(&self, width: f32, height: f32, padding_x: f32, padding_y: f32) {
- // Bounds check
- if (width as u32) < (2 * padding_x as u32) || (height as u32) < (2 * padding_y as u32) {
- return;
- }
-
- // Compute scale and offset factors, from pixel to ndc space. Y is inverted
- // [0, width - 2 * padding_x] to [-1, 1]
- // [height - 2 * padding_y, 0] to [-1, 1]
- let scale_x = 2. / (width - 2. * padding_x);
- let scale_y = -2. / (height - 2. * padding_y);
- let offset_x = -1.;
- let offset_y = 1.;
-
- info!("Width: {}, Height: {}", width, height);
-
- unsafe {
- gl::Uniform4f(self.u_projection, offset_x, offset_y, scale_x, scale_y);
- }
- }
-
- fn set_term_uniforms(&self, props: &term::SizeInfo) {
- unsafe {
- gl::Uniform2f(self.u_cell_dim, props.cell_width, props.cell_height);
- }
- }
-
- fn set_background_pass(&self, background_pass: bool) {
- let value = if background_pass { 1 } else { 0 };
-
- unsafe {
- gl::Uniform1i(self.u_background, value);
- }
- }
-}
-
-impl Drop for TextShaderProgram {
- fn drop(&mut self) {
- unsafe {
- gl::DeleteProgram(self.id);
- }
- }
-}
-
-impl RectShaderProgram {
- pub fn new() -> Result<Self, ShaderCreationError> {
- let (vertex_src, fragment_src) = if cfg!(feature = "live-shader-reload") {
- (None, None)
- } else {
- (Some(RECT_SHADER_V), Some(RECT_SHADER_F))
- };
- let vertex_shader = create_shader(RECT_SHADER_V_PATH, gl::VERTEX_SHADER, vertex_src)?;
- let fragment_shader = create_shader(RECT_SHADER_F_PATH, gl::FRAGMENT_SHADER, fragment_src)?;
- let program = create_program(vertex_shader, fragment_shader)?;
-
- unsafe {
- gl::DeleteShader(fragment_shader);
- gl::DeleteShader(vertex_shader);
- gl::UseProgram(program);
- }
-
- // get uniform locations
- let u_color = unsafe { gl::GetUniformLocation(program, b"color\0".as_ptr() as *const _) };
-
- let shader = Self { id: program, u_color };
-
- unsafe { gl::UseProgram(0) }
-
- Ok(shader)
- }
-
- fn set_color(&self, color: Rgb, alpha: f32) {
- unsafe {
- gl::Uniform4f(
- self.u_color,
- f32::from(color.r) / 255.,
- f32::from(color.g) / 255.,
- f32::from(color.b) / 255.,
- alpha,
- );
- }
- }
-}
-
-impl Drop for RectShaderProgram {
- fn drop(&mut self) {
- unsafe {
- gl::DeleteProgram(self.id);
- }
- }
-}
-
-fn create_program(vertex: GLuint, fragment: GLuint) -> Result<GLuint, ShaderCreationError> {
- unsafe {
- let program = gl::CreateProgram();
- gl::AttachShader(program, vertex);
- gl::AttachShader(program, fragment);
- gl::LinkProgram(program);
-
- let mut success: GLint = 0;
- gl::GetProgramiv(program, gl::LINK_STATUS, &mut success);
-
- if success == i32::from(gl::TRUE) {
- Ok(program)
- } else {
- Err(ShaderCreationError::Link(get_program_info_log(program)))
- }
- }
-}
-
-fn create_shader(
- path: &str,
- kind: GLenum,
- source: Option<&'static str>,
-) -> Result<GLuint, ShaderCreationError> {
- let from_disk;
- let source = if let Some(src) = source {
- src
- } else {
- from_disk = read_file(path)?;
- &from_disk[..]
- };
-
- let len: [GLint; 1] = [source.len() as GLint];
-
- let shader = unsafe {
- let shader = gl::CreateShader(kind);
- gl::ShaderSource(shader, 1, &(source.as_ptr() as *const _), len.as_ptr());
- gl::CompileShader(shader);
- shader
- };
-
- let mut success: GLint = 0;
- unsafe {
- gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut success);
- }
-
- if success == GLint::from(gl::TRUE) {
- Ok(shader)
- } else {
- // Read log
- let log = get_shader_info_log(shader);
-
- // Cleanup
- unsafe {
- gl::DeleteShader(shader);
- }
-
- Err(ShaderCreationError::Compile(PathBuf::from(path), log))
- }
-}
-
-fn get_program_info_log(program: GLuint) -> String {
- // Get expected log length
- let mut max_length: GLint = 0;
- unsafe {
- gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut max_length);
- }
-
- // Read the info log
- let mut actual_length: GLint = 0;
- let mut buf: Vec<u8> = Vec::with_capacity(max_length as usize);
- unsafe {
- gl::GetProgramInfoLog(program, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _);
- }
-
- // Build a string
- unsafe {
- buf.set_len(actual_length as usize);
- }
-
- // XXX should we expect opengl to return garbage?
- String::from_utf8(buf).unwrap()
-}
-
-fn get_shader_info_log(shader: GLuint) -> String {
- // Get expected log length
- let mut max_length: GLint = 0;
- unsafe {
- gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut max_length);
- }
-
- // Read the info log
- let mut actual_length: GLint = 0;
- let mut buf: Vec<u8> = Vec::with_capacity(max_length as usize);
- unsafe {
- gl::GetShaderInfoLog(shader, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _);
- }
-
- // Build a string
- unsafe {
- buf.set_len(actual_length as usize);
- }
-
- // XXX should we expect opengl to return garbage?
- String::from_utf8(buf).unwrap()
-}
-
-fn read_file(path: &str) -> Result<String, io::Error> {
- let mut f = File::open(path)?;
- let mut buf = String::new();
- f.read_to_string(&mut buf)?;
-
- Ok(buf)
-}
-
-#[derive(Debug)]
-pub enum ShaderCreationError {
- /// Error reading file
- Io(io::Error),
-
- /// Error compiling shader
- Compile(PathBuf, String),
-
- /// Problem linking
- Link(String),
-}
-
-impl std::error::Error for ShaderCreationError {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- match self {
- ShaderCreationError::Io(err) => err.source(),
- _ => None,
- }
- }
-}
-
-impl Display for ShaderCreationError {
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- match self {
- ShaderCreationError::Io(err) => write!(f, "Couldn't read shader: {}", err),
- ShaderCreationError::Compile(path, log) => {
- write!(f, "Failed compiling shader at {}: {}", path.display(), log)
- },
- ShaderCreationError::Link(log) => write!(f, "Failed linking shader: {}", log),
- }
- }
-}
-
-impl From<io::Error> for ShaderCreationError {
- fn from(val: io::Error) -> Self {
- ShaderCreationError::Io(val)
- }
-}
-
-/// Manages a single texture atlas
-///
-/// The strategy for filling an atlas looks roughly like this:
-///
-/// ```text
-/// (width, height)
-/// ┌─────┬─────┬─────┬─────┬─────┐
-/// │ 10 │ │ │ │ │ <- Empty spaces; can be filled while
-/// │ │ │ │ │ │ glyph_height < height - row_baseline
-/// ├─────┼─────┼─────┼─────┼─────┤
-/// │ 5 │ 6 │ 7 │ 8 │ 9 │
-/// │ │ │ │ │ │
-/// ├─────┼─────┼─────┼─────┴─────┤ <- Row height is tallest glyph in row; this is
-/// │ 1 │ 2 │ 3 │ 4 │ used as the baseline for the following row.
-/// │ │ │ │ │ <- Row considered full when next glyph doesn't
-/// └─────┴─────┴─────┴───────────┘ fit in the row.
-/// (0, 0) x->
-/// ```
-#[derive(Debug)]
-struct Atlas {
- /// Texture id for this atlas
- id: GLuint,
-
- /// Width of atlas
- width: i32,
-
- /// Height of atlas
- height: i32,
-
- /// Left-most free pixel in a row.
- ///
- /// This is called the extent because it is the upper bound of used pixels
- /// in a row.
- row_extent: i32,
-
- /// Baseline for glyphs in the current row
- row_baseline: i32,
-
- /// Tallest glyph in current row
- ///
- /// This is used as the advance when end of row is reached
- row_tallest: i32,
-}
-
-/// Error that can happen when inserting a texture to the Atlas
-enum AtlasInsertError {
- /// Texture atlas is full
- Full,
-
- /// The glyph cannot fit within a single texture
- GlyphTooLarge,
-}
-
-impl Atlas {
- fn new(size: i32) -> Self {
- let mut id: GLuint = 0;
- unsafe {
- gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1);
- gl::GenTextures(1, &mut id);
- gl::BindTexture(gl::TEXTURE_2D, id);
- gl::TexImage2D(
- gl::TEXTURE_2D,
- 0,
- gl::RGB as i32,
- size,
- size,
- 0,
- gl::RGB,
- gl::UNSIGNED_BYTE,
- ptr::null(),
- );
-
- gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
- gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
- gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
- gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
-
- gl::BindTexture(gl::TEXTURE_2D, 0);
- }
-
- Self { id, width: size, height: size, row_extent: 0, row_baseline: 0, row_tallest: 0 }
- }
-
- pub fn clear(&mut self) {
- self.row_extent = 0;
- self.row_baseline = 0;
- self.row_tallest = 0;
- }
-
- /// Insert a RasterizedGlyph into the texture atlas
- pub fn insert(
- &mut self,
- glyph: &RasterizedGlyph,
- active_tex: &mut u32,
- ) -> Result<Glyph, AtlasInsertError> {
- if glyph.width > self.width || glyph.height > self.height {
- return Err(AtlasInsertError::GlyphTooLarge);
- }
-
- // If there's not enough room in current row, go onto next one
- if !self.room_in_row(glyph) {
- self.advance_row()?;
- }
-
- // If there's still not room, there's nothing that can be done here.
- if !self.room_in_row(glyph) {
- return Err(AtlasInsertError::Full);
- }
-
- // There appears to be room; load the glyph.
- Ok(self.insert_inner(glyph, active_tex))
- }
-
- /// Insert the glyph without checking for room
- ///
- /// Internal function for use once atlas has been checked for space. GL
- /// errors could still occur at this point if we were checking for them;
- /// hence, the Result.
- fn insert_inner(&mut self, glyph: &RasterizedGlyph, active_tex: &mut u32) -> Glyph {
- let offset_y = self.row_baseline;
- let offset_x = self.row_extent;
- let height = glyph.height as i32;
- let width = glyph.width as i32;
- let colored;
-
- unsafe {
- gl::BindTexture(gl::TEXTURE_2D, self.id);
-
- // Load data into OpenGL
- let (format, buf) = match &glyph.buf {
- BitmapBuffer::RGB(buf) => {
- colored = false;
- (gl::RGB, buf)
- },
- BitmapBuffer::RGBA(buf) => {
- colored = true;
- (gl::RGBA, buf)
- },
- };
-
- gl::TexSubImage2D(
- gl::TEXTURE_2D,
- 0,
- offset_x,
- offset_y,
- width,
- height,
- format,
- gl::UNSIGNED_BYTE,
- buf.as_ptr() as *const _,
- );
-
- gl::BindTexture(gl::TEXTURE_2D, 0);
- *active_tex = 0;
- }
-
- // Update Atlas state
- self.row_extent = offset_x + width;
- if height > self.row_tallest {
- self.row_tallest = height;
- }
-
- // Generate UV coordinates
- let uv_bot = offset_y as f32 / self.height as f32;
- let uv_left = offset_x as f32 / self.width as f32;
- let uv_height = height as f32 / self.height as f32;
- let uv_width = width as f32 / self.width as f32;
-
- Glyph {
- tex_id: self.id,
- colored,
- top: glyph.top as f32,
- width: width as f32,
- height: height as f32,
- left: glyph.left as f32,
- uv_bot,
- uv_left,
- uv_width,
- uv_height,
- }
- }
-
- /// Check if there's room in the current row for given glyph
- fn room_in_row(&self, raw: &RasterizedGlyph) -> bool {
- let next_extent = self.row_extent + raw.width as i32;
- let enough_width = next_extent <= self.width;
- let enough_height = (raw.height as i32) < (self.height - self.row_baseline);
-
- enough_width && enough_height
- }
-
- /// Mark current row as finished and prepare to insert into the next row
- fn advance_row(&mut self) -> Result<(), AtlasInsertError> {
- let advance_to = self.row_baseline + self.row_tallest;
- if self.height - advance_to <= 0 {
- return Err(AtlasInsertError::Full);
- }
-
- self.row_baseline = advance_to;
- self.row_extent = 0;
- self.row_tallest = 0;
-
- Ok(())
- }
-}
diff --git a/alacritty/src/renderer/rects.rs b/alacritty/src/renderer/rects.rs
deleted file mode 100644
index 796abe6..0000000
--- a/alacritty/src/renderer/rects.rs
+++ /dev/null
@@ -1,149 +0,0 @@
-// 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::collections::HashMap;
-
-use font::Metrics;
-
-use alacritty_terminal::index::{Column, Point};
-use alacritty_terminal::term::cell::Flags;
-use alacritty_terminal::term::color::Rgb;
-use alacritty_terminal::term::{RenderableCell, SizeInfo};
-
-#[derive(Debug, Copy, Clone)]
-pub struct RenderRect {
- pub x: f32,
- pub y: f32,
- pub width: f32,
- pub height: f32,
- pub color: Rgb,
- pub alpha: f32,
-}
-
-impl RenderRect {
- pub fn new(x: f32, y: f32, width: f32, height: f32, color: Rgb, alpha: f32) -> Self {
- RenderRect { x, y, width, height, color, alpha }
- }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub struct RenderLine {
- pub start: Point,
- pub end: Point,
- pub color: Rgb,
-}
-
-impl RenderLine {
- pub fn rects(&self, flag: Flags, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> {
- let mut rects = Vec::new();
-
- let mut start = self.start;
- while start.line < self.end.line {
- let mut end = start;
- end.col = size.cols() - 1;
- rects.push(Self::create_rect(metrics, size, flag, start, end, self.color));
-
- start.col = Column(0);
- start.line += 1;
- }
-
- rects.push(Self::create_rect(metrics, size, flag, start, self.end, self.color));
-
- rects
- }
-
- fn create_rect(
- metrics: &Metrics,
- size: &SizeInfo,
- flag: Flags,
- start: Point,
- end: Point,
- color: Rgb,
- ) -> RenderRect {
- let start_x = start.col.0 as f32 * size.cell_width;
- let end_x = (end.col.0 + 1) as f32 * size.cell_width;
- let width = end_x - start_x;
-
- let (position, mut height) = match flag {
- Flags::UNDERLINE => (metrics.underline_position, metrics.underline_thickness),
- Flags::STRIKEOUT => (metrics.strikeout_position, metrics.strikeout_thickness),
- _ => unimplemented!("Invalid flag for cell line drawing specified"),
- };
-
- // Make sure lines are always visible
- height = height.max(1.);
-
- let line_bottom = (start.line.0 as f32 + 1.) * size.cell_height;
- let baseline = line_bottom + metrics.descent;
-
- let mut y = (baseline - position - height / 2.).ceil();
- let max_y = line_bottom - height;
- if y > max_y {
- y = max_y;
- }
-
- RenderRect::new(start_x + size.padding_x, y + size.padding_y, width, height, color, 1.)
- }
-}
-
-/// Lines for underline and strikeout.
-#[derive(Default)]
-pub struct RenderLines {
- inner: HashMap<Flags, Vec<RenderLine>>,
-}
-
-impl RenderLines {
- pub fn new() -> Self {
- Self::default()
- }
-
- pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> {
- self.inner
- .iter()
- .map(|(flag, lines)| -> Vec<RenderRect> {
- lines.iter().map(|line| line.rects(*flag, metrics, size)).flatten().collect()
- })
- .flatten()
- .collect()
- }
-
- /// Update the stored lines with the next cell info.
- pub fn update(&mut self, cell: RenderableCell) {
- for flag in &[Flags::UNDERLINE, Flags::STRIKEOUT] {
- if !cell.flags.contains(*flag) {
- continue;
- }
-
- // Check if there's an active line
- if let Some(line) = self.inner.get_mut(flag).and_then(|lines| lines.last_mut()) {
- if cell.fg == line.color
- && cell.column == line.end.col + 1
- && cell.line == line.end.line
- {
- // Update the length of the line
- line.end = cell.into();
- continue;
- }
- }
-
- // Start new line if there currently is none
- let line = RenderLine { start: cell.into(), end: cell.into(), color: cell.fg };
- match self.inner.get_mut(flag) {
- Some(lines) => lines.push(line),
- None => {
- self.inner.insert(*flag, vec![line]);
- },
- }
- }
- }
-}
diff --git a/alacritty/src/url.rs b/alacritty/src/url.rs
deleted file mode 100644
index 4c5aed2..0000000
--- a/alacritty/src/url.rs
+++ /dev/null
@@ -1,252 +0,0 @@
-use std::cmp::min;
-use std::mem;
-
-use glutin::event::{ElementState, ModifiersState};
-use urlocator::{UrlLocation, UrlLocator};
-
-use font::Metrics;
-
-use alacritty_terminal::index::Point;
-use alacritty_terminal::term::cell::Flags;
-use alacritty_terminal::term::color::Rgb;
-use alacritty_terminal::term::{RenderableCell, RenderableCellContent, SizeInfo};
-
-use crate::config::Config;
-use crate::event::Mouse;
-use crate::renderer::rects::{RenderLine, RenderRect};
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct Url {
- lines: Vec<RenderLine>,
- end_offset: u16,
- num_cols: usize,
-}
-
-impl Url {
- pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> {
- let end = self.end();
- self.lines
- .iter()
- .filter(|line| line.start <= end)
- .map(|line| {
- let mut rect_line = *line;
- rect_line.end = min(line.end, end);
- rect_line.rects(Flags::UNDERLINE, metrics, size)
- })
- .flatten()
- .collect()
- }
-
- pub fn start(&self) -> Point {
- self.lines[0].start
- }
-
- pub fn end(&self) -> Point {
- self.lines[self.lines.len() - 1].end.sub(self.num_cols, self.end_offset as usize, false)
- }
-}
-
-pub struct Urls {
- locator: UrlLocator,
- urls: Vec<Url>,
- scheme_buffer: Vec<RenderableCell>,
- last_point: Option<Point>,
- state: UrlLocation,
-}
-
-impl Default for Urls {
- fn default() -> Self {
- Self {
- locator: UrlLocator::new(),
- scheme_buffer: Vec::new(),
- urls: Vec::new(),
- state: UrlLocation::Reset,
- last_point: None,
- }
- }
-}
-
-impl Urls {
- pub fn new() -> Self {
- Self::default()
- }
-
- // Update tracked URLs
- pub fn update(&mut self, num_cols: usize, cell: RenderableCell) {
- // Convert cell to character
- let c = match cell.inner {
- RenderableCellContent::Chars(chars) => chars[0],
- RenderableCellContent::Cursor(_) => return,
- };
-
- let point: Point = cell.into();
- let end = point;
-
- // Reset URL when empty cells have been skipped
- if point != Point::default() && Some(point.sub(num_cols, 1, false)) != self.last_point {
- self.reset();
- }
-
- self.last_point = Some(end);
-
- // Extend current state if a wide char spacer is encountered
- if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
- if let UrlLocation::Url(_, mut end_offset) = self.state {
- if end_offset != 0 {
- end_offset += 1;
- }
-
- self.extend_url(point, end, cell.fg, end_offset);
- }
-
- return;
- }
-
- // Advance parser
- let last_state = mem::replace(&mut self.state, self.locator.advance(c));
- match (self.state, last_state) {
- (UrlLocation::Url(_length, end_offset), UrlLocation::Scheme) => {
- // Create empty URL
- self.urls.push(Url { lines: Vec::new(), end_offset, num_cols });
-
- // Push schemes into URL
- for scheme_cell in self.scheme_buffer.split_off(0) {
- let point = scheme_cell.into();
- self.extend_url(point, point, scheme_cell.fg, end_offset);
- }
-
- // Push the new cell into URL
- self.extend_url(point, end, cell.fg, end_offset);
- },
- (UrlLocation::Url(_length, end_offset), UrlLocation::Url(..)) => {
- self.extend_url(point, end, cell.fg, end_offset);
- },
- (UrlLocation::Scheme, _) => self.scheme_buffer.push(cell),
- (UrlLocation::Reset, _) => self.reset(),
- _ => (),
- }
-
- // Reset at un-wrapped linebreak
- if cell.column.0 + 1 == num_cols && !cell.flags.contains(Flags::WRAPLINE) {
- self.reset();
- }
- }
-
- // Extend the last URL
- fn extend_url(&mut self, start: Point, end: Point, color: Rgb, end_offset: u16) {
- let url = self.urls.last_mut().unwrap();
-
- // If color changed, we need to insert a new line
- if url.lines.last().map(|last| last.color) == Some(color) {
- url.lines.last_mut().unwrap().end = end;
- } else {
- url.lines.push(RenderLine { color, start, end });
- }
-
- // Update excluded cells at the end of the URL
- url.end_offset = end_offset;
- }
-
- pub fn highlighted(
- &self,
- config: &Config,
- mouse: &Mouse,
- mods: ModifiersState,
- mouse_mode: bool,
- selection: bool,
- ) -> Option<Url> {
- // Require additional shift in mouse mode
- let mut required_mods = config.ui_config.mouse.url.mods();
- if mouse_mode {
- required_mods |= ModifiersState::SHIFT;
- }
-
- // Make sure all prerequisites for highlighting are met
- if selection
- || !mouse.inside_grid
- || config.ui_config.mouse.url.launcher.is_none()
- || required_mods != mods
- || mouse.left_button_state == ElementState::Pressed
- {
- return None;
- }
-
- for url in &self.urls {
- if (url.start()..=url.end()).contains(&Point::new(mouse.line, mouse.column)) {
- return Some(url.clone());
- }
- }
-
- None
- }
-
- fn reset(&mut self) {
- self.locator = UrlLocator::new();
- self.state = UrlLocation::Reset;
- self.scheme_buffer.clear();
- }
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- use alacritty_terminal::index::{Column, Line};
- use alacritty_terminal::term::cell::MAX_ZEROWIDTH_CHARS;
-
- fn text_to_cells(text: &str) -> Vec<RenderableCell> {
- text.chars()
- .enumerate()
- .map(|(i, c)| RenderableCell {
- inner: RenderableCellContent::Chars([c; MAX_ZEROWIDTH_CHARS + 1]),
- line: Line(0),
- column: Column(i),
- fg: Default::default(),
- bg: Default::default(),
- bg_alpha: 0.,
- flags: Flags::empty(),
- })
- .collect()
- }
-
- #[test]
- fn multi_color_url() {
- let mut input = text_to_cells("test https://example.org ing");
- let num_cols = input.len();
-
- input[10].fg = Rgb { r: 0xff, g: 0x00, b: 0xff };
-
- let mut urls = Urls::new();
-
- for cell in input {
- urls.update(num_cols, cell);
- }
-
- let url = urls.urls.first().unwrap();
- assert_eq!(url.start().col, Column(5));
- assert_eq!(url.end().col, Column(23));
- }
-
- #[test]
- fn multiple_urls() {
- let input = text_to_cells("test git:a git:b git:c ing");
- let num_cols = input.len();
-
- let mut urls = Urls::new();
-
- for cell in input {
- urls.update(num_cols, cell);
- }
-
- assert_eq!(urls.urls.len(), 3);
-
- assert_eq!(urls.urls[0].start().col, Column(5));
- assert_eq!(urls.urls[0].end().col, Column(9));
-
- assert_eq!(urls.urls[1].start().col, Column(11));
- assert_eq!(urls.urls[1].end().col, Column(15));
-
- assert_eq!(urls.urls[2].start().col, Column(17));
- assert_eq!(urls.urls[2].end().col, Column(21));
- }
-}
diff --git a/alacritty/src/window.rs b/alacritty/src/window.rs
deleted file mode 100644
index 4d1a8ea..0000000
--- a/alacritty/src/window.rs
+++ /dev/null
@@ -1,427 +0,0 @@
-// 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::convert::From;
-#[cfg(not(any(target_os = "macos", windows)))]
-use std::ffi::c_void;
-use std::fmt::{self, Display, Formatter};
-#[cfg(not(any(target_os = "macos", windows)))]
-use std::os::raw::c_ulong;
-
-use glutin::dpi::{PhysicalPosition, PhysicalSize};
-use glutin::event_loop::EventLoop;
-#[cfg(target_os = "macos")]
-use glutin::platform::macos::{RequestUserAttentionType, WindowBuilderExtMacOS, WindowExtMacOS};
-#[cfg(not(any(target_os = "macos", windows)))]
-use glutin::platform::unix::{EventLoopWindowTargetExtUnix, WindowBuilderExtUnix, WindowExtUnix};
-#[cfg(not(target_os = "macos"))]
-use glutin::window::Icon;
-use glutin::window::{CursorIcon, Fullscreen, Window as GlutinWindow, WindowBuilder, WindowId};
-use glutin::{self, ContextBuilder, PossiblyCurrent, WindowedContext};
-#[cfg(not(target_os = "macos"))]
-use image::ImageFormat;
-#[cfg(not(any(target_os = "macos", windows)))]
-use x11_dl::xlib::{Display as XDisplay, PropModeReplace, XErrorEvent, Xlib};
-
-use alacritty_terminal::config::{Decorations, StartupMode, WindowConfig};
-use alacritty_terminal::event::Event;
-#[cfg(not(windows))]
-use alacritty_terminal::term::{SizeInfo, Term};
-
-use crate::config::Config;
-use crate::gl;
-
-// It's required to be in this directory due to the `windows.rc` file
-#[cfg(not(target_os = "macos"))]
-static WINDOW_ICON: &[u8] = include_bytes!("../../extra/windows/alacritty.ico");
-
-/// Window errors
-#[derive(Debug)]
-pub enum Error {
- /// Error creating the window
- ContextCreation(glutin::CreationError),
-
- /// Error dealing with fonts
- Font(font::Error),
-
- /// Error manipulating the rendering context
- Context(glutin::ContextError),
-}
-
-/// Result of fallible operations concerning a Window.
-type Result<T> = std::result::Result<T, Error>;
-
-impl std::error::Error for Error {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- match self {
- Error::ContextCreation(err) => err.source(),
- Error::Context(err) => err.source(),
- Error::Font(err) => err.source(),
- }
- }
-}
-
-impl Display for Error {
- fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
- match self {
- Error::ContextCreation(err) => write!(f, "Error creating GL context; {}", err),
- Error::Context(err) => write!(f, "Error operating on render context; {}", err),
- Error::Font(err) => err.fmt(f),
- }
- }
-}
-
-impl From<glutin::CreationError> for Error {
- fn from(val: glutin::CreationError) -> Self {
- Error::ContextCreation(val)
- }
-}
-
-impl From<glutin::ContextError> for Error {
- fn from(val: glutin::ContextError) -> Self {
- Error::Context(val)
- }
-}
-
-impl From<font::Error> for Error {
- fn from(val: font::Error) -> Self {
- Error::Font(val)
- }
-}
-
-fn create_gl_window(
- mut window: WindowBuilder,
- event_loop: &EventLoop<Event>,
- srgb: bool,
- dimensions: Option<PhysicalSize<u32>>,
-) -> Result<WindowedContext<PossiblyCurrent>> {
- if let Some(dimensions) = dimensions {
- window = window.with_inner_size(dimensions);
- }
-
- let windowed_context = ContextBuilder::new()
- .with_srgb(srgb)
- .with_vsync(true)
- .with_hardware_acceleration(None)
- .build_windowed(window, event_loop)?;
-
- // Make the context current so OpenGL operations can run
- let windowed_context = unsafe { windowed_context.make_current().map_err(|(_, err)| err)? };
-
- Ok(windowed_context)
-}
-
-/// A window which can be used for displaying the terminal
-///
-/// Wraps the underlying windowing library to provide a stable API in Alacritty
-pub struct Window {
- windowed_context: WindowedContext<PossiblyCurrent>,
- current_mouse_cursor: CursorIcon,
- mouse_visible: bool,
-}
-
-impl Window {
- /// Create a new window
- ///
- /// This creates a window and fully initializes a window.
- pub fn new(
- event_loop: &EventLoop<Event>,
- config: &Config,
- size: Option<PhysicalSize<u32>>,
- ) -> Result<Window> {
- let window_builder = Window::get_platform_window(&config.window.title, &config.window);
- let windowed_context =
- create_gl_window(window_builder.clone(), &event_loop, false, size)
- .or_else(|_| create_gl_window(window_builder, &event_loop, true, size))?;
-
- // Text cursor
- let current_mouse_cursor = CursorIcon::Text;
- windowed_context.window().set_cursor_icon(current_mouse_cursor);
-
- // Set OpenGL symbol loader. This call MUST be after window.make_current on windows.
- gl::load_with(|symbol| windowed_context.get_proc_address(symbol) as *const _);
-
- // On X11, embed the window inside another if the parent ID has been set
- #[cfg(not(any(target_os = "macos", windows)))]
- {
- if event_loop.is_x11() {
- if let Some(parent_window_id) = config.window.embed {
- x_embed_window(windowed_context.window(), parent_window_id);
- }
- }
- }
-
- Ok(Self { current_mouse_cursor, mouse_visible: true, windowed_context })
- }
-
- pub fn set_inner_size(&mut self, size: PhysicalSize<u32>) {
- self.window().set_inner_size(size);
- }
-
- pub fn inner_size(&self) -> PhysicalSize<u32> {
- self.window().inner_size()
- }
-
- pub fn scale_factor(&self) -> f64 {
- self.window().scale_factor()
- }
-
- #[inline]
- pub fn set_visible(&self, visibility: bool) {
- self.window().set_visible(visibility);
- }
-
- /// Set the window title
- #[inline]
- pub fn set_title(&self, title: &str) {
- self.window().set_title(title);
- }
-
- #[inline]
- pub fn set_mouse_cursor(&mut self, cursor: CursorIcon) {
- if cursor != self.current_mouse_cursor {
- self.current_mouse_cursor = cursor;
- self.window().set_cursor_icon(cursor);
- }
- }
-
- /// Set mouse cursor visible
- pub fn set_mouse_visible(&mut self, visible: bool) {
- if visible != self.mouse_visible {
- self.mouse_visible = visible;
- self.window().set_cursor_visible(visible);
- }
- }
-
- #[cfg(not(any(target_os = "macos", windows)))]
- pub fn get_platform_window(title: &str, window_config: &WindowConfig) -> WindowBuilder {
- let decorations = match window_config.decorations {
- Decorations::None => false,
- _ => true,
- };
-
- let image = image::load_from_memory_with_format(WINDOW_ICON, ImageFormat::ICO)
- .expect("loading icon")
- .to_rgba();
- let (width, height) = image.dimensions();
- let icon = Icon::from_rgba(image.into_raw(), width, height);
-
- let class = &window_config.class;
-
- let mut builder = WindowBuilder::new()
- .with_title(title)
- .with_visible(false)
- .with_transparent(true)
- .with_decorations(decorations)
- .with_maximized(window_config.startup_mode() == StartupMode::Maximized)
- .with_window_icon(icon.ok())
- // X11
- .with_class(class.instance.clone(), class.general.clone())
- // Wayland
- .with_app_id(class.instance.clone());
-
- if let Some(ref val) = window_config.gtk_theme_variant {
- builder = builder.with_gtk_theme_variant(val.clone())
- }
-
- builder
- }
-
- #[cfg(windows)]
- pub fn get_platform_window(title: &str, window_config: &WindowConfig) -> WindowBuilder {
- let decorations = match window_config.decorations {
- Decorations::None => false,
- _ => true,
- };
-
- let image = image::load_from_memory_with_format(WINDOW_ICON, ImageFormat::ICO)
- .expect("loading icon")
- .to_rgba();
- let (width, height) = image.dimensions();
- let icon = Icon::from_rgba(image.into_raw(), width, height);
-
- WindowBuilder::new()
- .with_title(title)
- .with_visible(true)
- .with_decorations(decorations)
- .with_transparent(true)
- .with_maximized(window_config.startup_mode() == StartupMode::Maximized)
- .with_window_icon(icon.ok())
- }
-
- #[cfg(target_os = "macos")]
- pub fn get_platform_window(title: &str, window_config: &WindowConfig) -> WindowBuilder {
- let window = WindowBuilder::new()
- .with_title(title)
- .with_visible(false)
- .with_transparent(true)
- .with_maximized(window_config.startup_mode() == StartupMode::Maximized);
-
- match window_config.decorations {
- Decorations::Full => window,
- Decorations::Transparent => window
- .with_title_hidden(true)
- .with_titlebar_transparent(true)
- .with_fullsize_content_view(true),
- Decorations::Buttonless => window
- .with_title_hidden(true)
- .with_titlebar_buttons_hidden(true)
- .with_titlebar_transparent(true)
- .with_fullsize_content_view(true),
- Decorations::None => window.with_titlebar_hidden(true),
- }
- }
-
- #[cfg(not(any(target_os = "macos", windows)))]
- pub fn set_urgent(&self, is_urgent: bool) {
- self.window().set_urgent(is_urgent);
- }
-
- #[cfg(target_os = "macos")]
- pub fn set_urgent(&self, is_urgent: bool) {
- if !is_urgent {
- return;
- }
-
- self.window().request_user_attention(RequestUserAttentionType::Critical);
- }
-
- #[cfg(windows)]
- pub fn set_urgent(&self, _is_urgent: bool) {}
-
- pub fn set_outer_position(&self, pos: PhysicalPosition<u32>) {
- self.window().set_outer_position(pos);
- }
-
- #[cfg(not(any(target_os = "macos", windows)))]
- pub fn x11_window_id(&self) -> Option<usize> {
- self.window().xlib_window().map(|xlib_window| xlib_window as usize)
- }
-
- pub fn window_id(&self) -> WindowId {
- self.window().id()
- }
-
- #[cfg(not(any(target_os = "macos", windows)))]
- pub fn set_maximized(&self, maximized: bool) {
- self.window().set_maximized(maximized);
- }
-
- pub fn set_minimized(&self, minimized: bool) {
- self.window().set_minimized(minimized);
- }
-
- /// Toggle the window's fullscreen state
- pub fn toggle_fullscreen(&mut self) {
- self.set_fullscreen(self.window().fullscreen().is_none());
- }
-
- #[cfg(target_os = "macos")]
- pub fn toggle_simple_fullscreen(&mut self) {
- self.set_simple_fullscreen(!self.window().simple_fullscreen());
- }
-
- pub fn set_fullscreen(&mut self, fullscreen: bool) {
- #[cfg(macos)]
- {
- if self.window().simple_fullscreen() {
- return;
- }
- }
-
- if fullscreen {
- let current_monitor = self.window().current_monitor();
- self.window().set_fullscreen(Some(Fullscreen::Borderless(current_monitor)));
- } else {
- self.window().set_fullscreen(None);
- }
- }
-
- #[cfg(target_os = "macos")]
- pub fn set_simple_fullscreen(&mut self, simple_fullscreen: bool) {
- if self.window().fullscreen().is_some() {
- return;
- }
-
- self.window().set_simple_fullscreen(simple_fullscreen);
- }
-
- #[cfg(not(any(target_os = "macos", target_os = "windows")))]
- pub fn wayland_display(&self) -> Option<*mut c_void> {
- self.window().wayland_display()
- }
-
- /// Adjust the IME editor position according to the new location of the cursor
- #[cfg(not(windows))]
- pub fn update_ime_position<T>(&mut self, terminal: &Term<T>, size_info: &SizeInfo) {
- let point = terminal.cursor().point;
- let SizeInfo { cell_width, cell_height, padding_x, padding_y, .. } = size_info;
-
- let nspot_x = f64::from(padding_x + point.col.0 as f32 * cell_width);
- let nspot_y = f64::from(padding_y + (point.line.0 + 1) as f32 * cell_height);
-
- self.window().set_ime_position(PhysicalPosition::new(nspot_x, nspot_y));
- }
-
- pub fn swap_buffers(&self) {
- self.windowed_context.swap_buffers().expect("swap buffers");
- }
-
- pub fn resize(&self, size: PhysicalSize<u32>) {
- self.windowed_context.resize(size);
- }
-
- fn window(&self) -> &GlutinWindow {
- self.windowed_context.window()
- }
-}
-
-#[cfg(not(any(target_os = "macos", windows)))]
-fn x_embed_window(window: &GlutinWindow, parent_id: c_ulong) {
- let (xlib_display, xlib_window) = match (window.xlib_display(), window.xlib_window()) {
- (Some(display), Some(window)) => (display, window),
- _ => return,
- };
-
- let xlib = Xlib::open().expect("get xlib");
-
- unsafe {
- let atom = (xlib.XInternAtom)(xlib_display as *mut _, "_XEMBED".as_ptr() as *const _, 0);
- (xlib.XChangeProperty)(
- xlib_display as _,
- xlib_window as _,
- atom,
- atom,
- 32,
- PropModeReplace,
- [0, 1].as_ptr(),
- 2,
- );
-
- // Register new error handler
- let old_handler = (xlib.XSetErrorHandler)(Some(xembed_error_handler));
-
- // Check for the existence of the target before attempting reparenting
- (xlib.XReparentWindow)(xlib_display as _, xlib_window as _, parent_id, 0, 0);
-
- // Drain errors and restore original error handler
- (xlib.XSync)(xlib_display as _, 0);
- (xlib.XSetErrorHandler)(old_handler);
- }
-}
-
-#[cfg(not(any(target_os = "macos", windows)))]
-unsafe extern "C" fn xembed_error_handler(_: *mut XDisplay, _: *mut XErrorEvent) -> i32 {
- println!("Could not embed into specified window.");
- std::process::exit(1);
-}