summaryrefslogtreecommitdiff
path: root/alacritty_terminal/src
diff options
context:
space:
mode:
authorChristian Duerr <contact@christianduerr.com>2019-10-05 02:29:26 +0200
committerGitHub <noreply@github.com>2019-10-05 02:29:26 +0200
commit729eef0c933831bccfeac6a355bdb410787fbe5f (patch)
tree35cdf2e6427ad18bc53efbab4cab34a0af2054d7 /alacritty_terminal/src
parentb0c6fdff763f7271506d26d7e768e6377fdc691b (diff)
downloadalacritty-729eef0c933831bccfeac6a355bdb410787fbe5f.tar.gz
alacritty-729eef0c933831bccfeac6a355bdb410787fbe5f.zip
Update to winit/glutin EventLoop 2.0
This takes the latest glutin master to port Alacritty to the EventLoop 2.0 rework. This changes a big part of the event loop handling by pushing the event loop in a separate thread from the renderer and running both in parallel. Fixes #2796. Fixes #2694. Fixes #2643. Fixes #2625. Fixes #2618. Fixes #2601. Fixes #2564. Fixes #2456. Fixes #2438. Fixes #2334. Fixes #2254. Fixes #2217. Fixes #1789. Fixes #1750. Fixes #1125.
Diffstat (limited to 'alacritty_terminal/src')
-rw-r--r--alacritty_terminal/src/ansi.rs17
-rw-r--r--alacritty_terminal/src/clipboard.rs2
-rw-r--r--alacritty_terminal/src/config/bindings.rs1023
-rw-r--r--alacritty_terminal/src/config/colors.rs18
-rw-r--r--alacritty_terminal/src/config/debug.rs11
-rw-r--r--alacritty_terminal/src/config/font.rs10
-rw-r--r--alacritty_terminal/src/config/mod.rs86
-rw-r--r--alacritty_terminal/src/config/monitor.rs79
-rw-r--r--alacritty_terminal/src/config/mouse.rs108
-rw-r--r--alacritty_terminal/src/config/scrolling.rs12
-rw-r--r--alacritty_terminal/src/config/test.rs22
-rw-r--r--alacritty_terminal/src/config/visual_bell.rs4
-rw-r--r--alacritty_terminal/src/config/window.rs6
-rw-r--r--alacritty_terminal/src/cursor.rs2
-rw-r--r--alacritty_terminal/src/display.rs603
-rw-r--r--alacritty_terminal/src/event.rs561
-rw-r--r--alacritty_terminal/src/event_loop.rs103
-rw-r--r--alacritty_terminal/src/grid/mod.rs2
-rw-r--r--alacritty_terminal/src/grid/row.rs2
-rw-r--r--alacritty_terminal/src/grid/storage.rs1
-rw-r--r--alacritty_terminal/src/index.rs6
-rw-r--r--alacritty_terminal/src/input.rs1538
-rw-r--r--alacritty_terminal/src/lib.rs12
-rw-r--r--alacritty_terminal/src/macros.rs21
-rw-r--r--alacritty_terminal/src/message_bar.rs148
-rw-r--r--alacritty_terminal/src/meter.rs1
-rw-r--r--alacritty_terminal/src/renderer/mod.rs122
-rw-r--r--alacritty_terminal/src/renderer/rects.rs2
-rw-r--r--alacritty_terminal/src/selection.rs15
-rw-r--r--alacritty_terminal/src/term/cell.rs2
-rw-r--r--alacritty_terminal/src/term/color.rs3
-rw-r--r--alacritty_terminal/src/term/mod.rs398
-rw-r--r--alacritty_terminal/src/tty/mod.rs2
-rw-r--r--alacritty_terminal/src/tty/unix.rs38
-rw-r--r--alacritty_terminal/src/tty/windows/conpty.rs9
-rw-r--r--alacritty_terminal/src/tty/windows/mod.rs5
-rw-r--r--alacritty_terminal/src/tty/windows/winpty.rs8
-rw-r--r--alacritty_terminal/src/url.rs2
-rw-r--r--alacritty_terminal/src/window.rs473
39 files changed, 431 insertions, 5046 deletions
diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs
index 3bdef32d..b34c1cd9 100644
--- a/alacritty_terminal/src/ansi.rs
+++ b/alacritty_terminal/src/ansi.rs
@@ -16,11 +16,13 @@
use std::io;
use std::str;
-use crate::index::{Column, Contains, Line};
use base64;
-use glutin::MouseCursor;
+use log::{debug, trace};
+use serde::{Deserialize, Serialize};
+
use vte;
+use crate::index::{Column, Contains, Line};
use crate::term::color::Rgb;
// Parse colors in XParseColor format
@@ -104,7 +106,7 @@ struct ProcessorState {
/// Processor creates a Performer when running advance and passes the Performer
/// to `vte::Parser`.
struct Performer<'a, H: Handler + TermInfo, W: io::Write> {
- _state: &'a mut ProcessorState,
+ state: &'a mut ProcessorState,
handler: &'a mut H,
writer: &'a mut W,
}
@@ -117,7 +119,7 @@ impl<'a, H: Handler + TermInfo + 'a, W: io::Write> Performer<'a, H, W> {
handler: &'b mut H,
writer: &'b mut W,
) -> Performer<'b, H, W> {
- Performer { _state: state, handler, writer }
+ Performer { state, handler, writer }
}
}
@@ -157,9 +159,6 @@ pub trait Handler {
/// OSC to set window title
fn set_title(&mut self, _: &str) {}
- /// Set the window's mouse cursor
- fn set_mouse_cursor(&mut self, _: MouseCursor) {}
-
/// Set the cursor style
fn set_cursor_style(&mut self, _: Option<CursorStyle>) {}
@@ -686,7 +685,7 @@ where
#[inline]
fn print(&mut self, c: char) {
self.handler.input(c);
- self._state.preceding_char = Some(c);
+ self.state.preceding_char = Some(c);
}
#[inline]
@@ -919,7 +918,7 @@ where
handler.move_up(Line(arg_or_default!(idx: 0, default: 1) as usize));
},
('b', None) => {
- if let Some(c) = self._state.preceding_char {
+ if let Some(c) = self.state.preceding_char {
for _ in 0..arg_or_default!(idx: 0, default: 1) {
handler.input(c);
}
diff --git a/alacritty_terminal/src/clipboard.rs b/alacritty_terminal/src/clipboard.rs
index dc826481..6f6a41be 100644
--- a/alacritty_terminal/src/clipboard.rs
+++ b/alacritty_terminal/src/clipboard.rs
@@ -15,6 +15,8 @@
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
use std::ffi::c_void;
+use log::{debug, warn};
+
use copypasta::nop_clipboard::NopClipboardContext;
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
use copypasta::wayland_clipboard;
diff --git a/alacritty_terminal/src/config/bindings.rs b/alacritty_terminal/src/config/bindings.rs
deleted file mode 100644
index 00090bbf..00000000
--- a/alacritty_terminal/src/config/bindings.rs
+++ /dev/null
@@ -1,1023 +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::fmt;
-use std::str::FromStr;
-
-use glutin::{ModifiersState, MouseButton};
-use serde::de::Error as SerdeError;
-use serde::de::{self, MapAccess, Unexpected, Visitor};
-use serde::{Deserialize, Deserializer};
-
-use crate::input::{Action, Binding, KeyBinding, MouseBinding};
-use crate::term::TermMode;
-
-macro_rules! bindings {
- (
- $ty:ident;
- $(
- $key:path
- $(,[$($mod:ident: $enabled:expr),*])*
- $(,+$mode:expr)*
- $(,~$notmode:expr)*
- ;$action:expr
- );*
- $(;)*
- ) => {{
- let mut v = Vec::new();
-
- $(
- let mut _mods = ModifiersState {
- $($($mod: $enabled),*,)*
- ..Default::default()
- };
- 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;
- Key::Paste; Action::Paste;
- Key::Copy; Action::Copy;
- Key::L, [ctrl: true]; Action::ClearLogNotice;
- Key::L, [ctrl: true]; Action::Esc("\x0c".into());
- Key::PageUp, [shift: true], ~TermMode::ALT_SCREEN; Action::ScrollPageUp;
- Key::PageDown, [shift: true], ~TermMode::ALT_SCREEN; Action::ScrollPageDown;
- Key::Home, [shift: true], ~TermMode::ALT_SCREEN; Action::ScrollToTop;
- Key::End, [shift: true], ~TermMode::ALT_SCREEN; Action::ScrollToBottom;
- Key::Home, +TermMode::APP_CURSOR; Action::Esc("\x1bOH".into());
- Key::Home, ~TermMode::APP_CURSOR; Action::Esc("\x1b[H".into());
- Key::Home, [shift: true], +TermMode::ALT_SCREEN; Action::Esc("\x1b[1;2H".into());
- Key::End, +TermMode::APP_CURSOR; Action::Esc("\x1bOF".into());
- Key::End, ~TermMode::APP_CURSOR; Action::Esc("\x1b[F".into());
- Key::End, [shift: true], +TermMode::ALT_SCREEN; Action::Esc("\x1b[1;2F".into());
- Key::PageUp; Action::Esc("\x1b[5~".into());
- Key::PageUp, [shift: true], +TermMode::ALT_SCREEN; Action::Esc("\x1b[5;2~".into());
- Key::PageDown; Action::Esc("\x1b[6~".into());
- Key::PageDown, [shift: true], +TermMode::ALT_SCREEN; Action::Esc("\x1b[6;2~".into());
- Key::Tab, [shift: true]; Action::Esc("\x1b[Z".into());
- Key::Back; Action::Esc("\x7f".into());
- Key::Back, [alt: true]; Action::Esc("\x1b\x7f".into());
- Key::Insert; Action::Esc("\x1b[2~".into());
- Key::Delete; Action::Esc("\x1b[3~".into());
- Key::Up, +TermMode::APP_CURSOR; Action::Esc("\x1bOA".into());
- Key::Up, ~TermMode::APP_CURSOR; Action::Esc("\x1b[A".into());
- Key::Down, +TermMode::APP_CURSOR; Action::Esc("\x1bOB".into());
- Key::Down, ~TermMode::APP_CURSOR; Action::Esc("\x1b[B".into());
- Key::Right, +TermMode::APP_CURSOR; Action::Esc("\x1bOC".into());
- Key::Right, ~TermMode::APP_CURSOR; Action::Esc("\x1b[C".into());
- Key::Left, +TermMode::APP_CURSOR; Action::Esc("\x1bOD".into());
- Key::Left, ~TermMode::APP_CURSOR; Action::Esc("\x1b[D".into());
- Key::F1; Action::Esc("\x1bOP".into());
- Key::F2; Action::Esc("\x1bOQ".into());
- Key::F3; Action::Esc("\x1bOR".into());
- Key::F4; Action::Esc("\x1bOS".into());
- Key::F5; Action::Esc("\x1b[15~".into());
- Key::F6; Action::Esc("\x1b[17~".into());
- Key::F7; Action::Esc("\x1b[18~".into());
- Key::F8; Action::Esc("\x1b[19~".into());
- Key::F9; Action::Esc("\x1b[20~".into());
- Key::F10; Action::Esc("\x1b[21~".into());
- Key::F11; Action::Esc("\x1b[23~".into());
- Key::F12; Action::Esc("\x1b[24~".into());
- Key::F13; Action::Esc("\x1b[25~".into());
- Key::F14; Action::Esc("\x1b[26~".into());
- Key::F15; Action::Esc("\x1b[28~".into());
- Key::F16; Action::Esc("\x1b[29~".into());
- Key::F17; Action::Esc("\x1b[31~".into());
- Key::F18; Action::Esc("\x1b[32~".into());
- Key::F19; Action::Esc("\x1b[33~".into());
- Key::F20; Action::Esc("\x1b[34~".into());
- Key::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 modifiers = vec![
- ModifiersState { shift: true, ..ModifiersState::default() },
- ModifiersState { alt: true, ..ModifiersState::default() },
- ModifiersState { shift: true, alt: true, ..ModifiersState::default() },
- ModifiersState { ctrl: true, ..ModifiersState::default() },
- ModifiersState { shift: true, ctrl: true, ..ModifiersState::default() },
- ModifiersState { alt: true, ctrl: true, ..ModifiersState::default() },
- ModifiersState { shift: true, alt: true, ctrl: true, ..ModifiersState::default() },
- ];
-
- for (index, mods) in modifiers.iter().enumerate() {
- let modifiers_code = index + 2;
- bindings.extend(bindings!(
- KeyBinding;
- Key::Up, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[1;{}A", modifiers_code));
- Key::Down, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[1;{}B", modifiers_code));
- Key::Right, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[1;{}C", modifiers_code));
- Key::Left, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[1;{}D", modifiers_code));
- Key::F1, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[1;{}P", modifiers_code));
- Key::F2, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[1;{}Q", modifiers_code));
- Key::F3, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[1;{}R", modifiers_code));
- Key::F4, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[1;{}S", modifiers_code));
- Key::F5, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[15;{}~", modifiers_code));
- Key::F6, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[17;{}~", modifiers_code));
- Key::F7, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[18;{}~", modifiers_code));
- Key::F8, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[19;{}~", modifiers_code));
- Key::F9, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[20;{}~", modifiers_code));
- Key::F10, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[21;{}~", modifiers_code));
- Key::F11, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[23;{}~", modifiers_code));
- Key::F12, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[24;{}~", modifiers_code));
- Key::F13, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[25;{}~", modifiers_code));
- Key::F14, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[26;{}~", modifiers_code));
- Key::F15, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[28;{}~", modifiers_code));
- Key::F16, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[29;{}~", modifiers_code));
- Key::F17, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[31;{}~", modifiers_code));
- Key::F18, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[32;{}~", modifiers_code));
- Key::F19, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[33;{}~", modifiers_code));
- Key::F20, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- 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;
- Key::PageUp, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[5;{}~", modifiers_code));
- Key::PageDown, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[6;{}~", modifiers_code));
- Key::End, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- Action::Esc(format!("\x1b[1;{}F", modifiers_code));
- Key::Home, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl];
- 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;
- Key::V, [ctrl: true, shift: true]; Action::Paste;
- Key::C, [ctrl: true, shift: true]; Action::Copy;
- Key::Insert, [shift: true]; Action::PasteSelection;
- Key::Key0, [ctrl: true]; Action::ResetFontSize;
- Key::Equals, [ctrl: true]; Action::IncreaseFontSize;
- Key::Add, [ctrl: true]; Action::IncreaseFontSize;
- Key::Subtract, [ctrl: true]; Action::DecreaseFontSize;
- Key::Minus, [ctrl: true]; 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;
- Key::Return, [alt: true]; Action::ToggleFullscreen;
- );
- bindings.extend(common_keybindings());
- bindings
-}
-
-#[cfg(all(target_os = "macos", not(test)))]
-pub fn platform_key_bindings() -> Vec<KeyBinding> {
- bindings!(
- KeyBinding;
- Key::Key0, [logo: true]; Action::ResetFontSize;
- Key::Equals, [logo: true]; Action::IncreaseFontSize;
- Key::Add, [logo: true]; Action::IncreaseFontSize;
- Key::Minus, [logo: true]; Action::DecreaseFontSize;
- Key::F, [ctrl: true, logo: true]; Action::ToggleFullscreen;
- Key::K, [logo: true]; Action::ClearHistory;
- Key::K, [logo: true]; Action::Esc("\x0c".into());
- Key::V, [logo: true]; Action::Paste;
- Key::C, [logo: true]; Action::Copy;
- Key::H, [logo: true]; Action::Hide;
- Key::Q, [logo: true]; Action::Quit;
- Key::W, [logo: true]; 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(Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
-pub enum Key {
- Scancode(u32),
- Key1,
- Key2,
- Key3,
- Key4,
- Key5,
- Key6,
- Key7,
- Key8,
- Key9,
- Key0,
- A,
- B,
- C,
- D,
- E,
- F,
- G,
- H,
- I,
- J,
- K,
- L,
- M,
- N,
- O,
- P,
- Q,
- R,
- S,
- T,
- U,
- V,
- W,
- X,
- Y,
- Z,
- Escape,
- F1,
- F2,
- F3,
- F4,
- F5,
- F6,
- F7,
- F8,
- F9,
- F10,
- F11,
- F12,
- F13,
- F14,
- F15,
- F16,
- F17,
- F18,
- F19,
- F20,
- F21,
- F22,
- F23,
- F24,
- Snapshot,
- Scroll,
- Pause,
- Insert,
- Home,
- Delete,
- End,
- PageDown,
- PageUp,
- Left,
- Up,
- Right,
- Down,
- Back,
- Return,
- Space,
- Compose,
- Numlock,
- Numpad0,
- Numpad1,
- Numpad2,
- Numpad3,
- Numpad4,
- Numpad5,
- Numpad6,
- Numpad7,
- Numpad8,
- Numpad9,
- AbntC1,
- AbntC2,
- Add,
- Apostrophe,
- Apps,
- At,
- Ax,
- Backslash,
- Calculator,
- Capital,
- Colon,
- Comma,
- Convert,
- Decimal,
- Divide,
- Equals,
- Grave,
- Kana,
- Kanji,
- LAlt,
- LBracket,
- LControl,
- LShift,
- LWin,
- Mail,
- MediaSelect,
- MediaStop,
- Minus,
- Multiply,
- Mute,
- MyComputer,
- NavigateForward,
- NavigateBackward,
- NextTrack,
- NoConvert,
- NumpadComma,
- NumpadEnter,
- NumpadEquals,
- OEM102,
- Period,
- PlayPause,
- Power,
- PrevTrack,
- RAlt,
- RBracket,
- RControl,
- RShift,
- RWin,
- Semicolon,
- Slash,
- Sleep,
- Stop,
- Subtract,
- Sysrq,
- Tab,
- Underline,
- Unlabeled,
- VolumeDown,
- VolumeUp,
- Wake,
- WebBack,
- WebFavorites,
- WebForward,
- WebHome,
- WebRefresh,
- WebSearch,
- WebStop,
- Yen,
- Caret,
- Copy,
- Paste,
- Cut,
-}
-
-impl Key {
- pub fn from_glutin_input(key: ::glutin::VirtualKeyCode) -> Self {
- use glutin::VirtualKeyCode::*;
- // Thank you, vim macros and regex!
- match key {
- Key1 => Key::Key1,
- Key2 => Key::Key2,
- Key3 => Key::Key3,
- Key4 => Key::Key4,
- Key5 => Key::Key5,
- Key6 => Key::Key6,
- Key7 => Key::Key7,
- Key8 => Key::Key8,
- Key9 => Key::Key9,
- Key0 => Key::Key0,
- A => Key::A,
- B => Key::B,
- C => Key::C,
- D => Key::D,
- E => Key::E,
- F => Key::F,
- G => Key::G,
- H => Key::H,
- I => Key::I,
- J => Key::J,
- K => Key::K,
- L => Key::L,
- M => Key::M,
- N => Key::N,
- O => Key::O,
- P => Key::P,
- Q => Key::Q,
- R => Key::R,
- S => Key::S,
- T => Key::T,
- U => Key::U,
- V => Key::V,
- W => Key::W,
- X => Key::X,
- Y => Key::Y,
- Z => Key::Z,
- Escape => Key::Escape,
- F1 => Key::F1,
- F2 => Key::F2,
- F3 => Key::F3,
- F4 => Key::F4,
- F5 => Key::F5,
- F6 => Key::F6,
- F7 => Key::F7,
- F8 => Key::F8,
- F9 => Key::F9,
- F10 => Key::F10,
- F11 => Key::F11,
- F12 => Key::F12,
- F13 => Key::F13,
- F14 => Key::F14,
- F15 => Key::F15,
- F16 => Key::F16,
- F17 => Key::F17,
- F18 => Key::F18,
- F19 => Key::F19,
- F20 => Key::F20,
- F21 => Key::F21,
- F22 => Key::F22,
- F23 => Key::F23,
- F24 => Key::F24,
- Snapshot => Key::Snapshot,
- Scroll => Key::Scroll,
- Pause => Key::Pause,
- Insert => Key::Insert,
- Home => Key::Home,
- Delete => Key::Delete,
- End => Key::End,
- PageDown => Key::PageDown,
- PageUp => Key::PageUp,
- Left => Key::Left,
- Up => Key::Up,
- Right => Key::Right,
- Down => Key::Down,
- Back => Key::Back,
- Return => Key::Return,
- Space => Key::Space,
- Compose => Key::Compose,
- Numlock => Key::Numlock,
- Numpad0 => Key::Numpad0,
- Numpad1 => Key::Numpad1,
- Numpad2 => Key::Numpad2,
- Numpad3 => Key::Numpad3,
- Numpad4 => Key::Numpad4,
- Numpad5 => Key::Numpad5,
- Numpad6 => Key::Numpad6,
- Numpad7 => Key::Numpad7,
- Numpad8 => Key::Numpad8,
- Numpad9 => Key::Numpad9,
- AbntC1 => Key::AbntC1,
- AbntC2 => Key::AbntC2,
- Add => Key::Add,
- Apostrophe => Key::Apostrophe,
- Apps => Key::Apps,
- At => Key::At,
- Ax => Key::Ax,
- Backslash => Key::Backslash,
- Calculator => Key::Calculator,
- Capital => Key::Capital,
- Colon => Key::Colon,
- Comma => Key::Comma,
- Convert => Key::Convert,
- Decimal => Key::Decimal,
- Divide => Key::Divide,
- Equals => Key::Equals,
- Grave => Key::Grave,
- Kana => Key::Kana,
- Kanji => Key::Kanji,
- LAlt => Key::LAlt,
- LBracket => Key::LBracket,
- LControl => Key::LControl,
- LShift => Key::LShift,
- LWin => Key::LWin,
- Mail => Key::Mail,
- MediaSelect => Key::MediaSelect,
- MediaStop => Key::MediaStop,
- Minus => Key::Minus,
- Multiply => Key::Multiply,
- Mute => Key::Mute,
- MyComputer => Key::MyComputer,
- NavigateForward => Key::NavigateForward,
- NavigateBackward => Key::NavigateBackward,
- NextTrack => Key::NextTrack,
- NoConvert => Key::NoConvert,
- NumpadComma => Key::NumpadComma,
- NumpadEnter => Key::NumpadEnter,
- NumpadEquals => Key::NumpadEquals,
- OEM102 => Key::OEM102,
- Period => Key::Period,
- PlayPause => Key::PlayPause,
- Power => Key::Power,
- PrevTrack => Key::PrevTrack,
- RAlt => Key::RAlt,
- RBracket => Key::RBracket,
- RControl => Key::RControl,
- RShift => Key::RShift,
- RWin => Key::RWin,
- Semicolon => Key::Semicolon,
- Slash => Key::Slash,
- Sleep => Key::Sleep,
- Stop => Key::Stop,
- Subtract => Key::Subtract,
- Sysrq => Key::Sysrq,
- Tab => Key::Tab,
- Underline => Key::Underline,
- Unlabeled => Key::Unlabeled,
- VolumeDown => Key::VolumeDown,
- VolumeUp => Key::VolumeUp,
- Wake => Key::Wake,
- WebBack => Key::WebBack,
- WebFavorites => Key::WebFavorites,
- WebForward => Key::WebForward,
- WebHome => Key::WebHome,
- WebRefresh => Key::WebRefresh,
- WebSearch => Key::WebSearch,
- WebStop => Key::WebStop,
- Yen => Key::Yen,
- Caret => Key::Caret,
- Copy => Key::Copy,
- Paste => Key::Paste,
- Cut => Key::Cut,
- }
- }
-}
-
-struct ModeWrapper {
- pub mode: TermMode,
- pub not_mode: TermMode,
-}
-
-impl<'a> Deserialize<'a> for ModeWrapper {
- fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
- where
- D: Deserializer<'a>,
- {
- struct ModeVisitor;
-
- impl<'a> Visitor<'a> for ModeVisitor {
- type Value = ModeWrapper;
-
- fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.write_str("Combination of AppCursor | AppKeypad, possibly with negation (~)")
- }
-
- fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModeWrapper, E>
- where
- E: de::Error,
- {
- let mut res = ModeWrapper { mode: TermMode::empty(), not_mode: TermMode::empty() };
-
- for modifier in value.split('|') {
- match modifier.trim().to_lowercase().as_str() {
- "appcursor" => res.mode |= TermMode::APP_CURSOR,
- "~appcursor" => res.not_mode |= TermMode::APP_CURSOR,
- "appkeypad" => res.mode |= TermMode::APP_KEYPAD,
- "~appkeypad" => res.not_mode |= TermMode::APP_KEYPAD,
- "~alt" => res.not_mode |= TermMode::ALT_SCREEN,
- "alt" => res.mode |= TermMode::ALT_SCREEN,
- _ => error!("Unknown mode {:?}", modifier),
- }
- }
-
- Ok(res)
- }
- }
- deserializer.deserialize_str(ModeVisitor)
- }
-}
-
-struct MouseButtonWrapper(MouseButton);
-
-impl MouseButtonWrapper {
- fn into_inner(self) -> MouseButton {
- self.0
- }
-}
-
-impl<'a> Deserialize<'a> for MouseButtonWrapper {
- fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
- where
- D: Deserializer<'a>,
- {
- struct MouseButtonVisitor;
-
- impl<'a> Visitor<'a> for MouseButtonVisitor {
- type Value = MouseButtonWrapper;
-
- fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.write_str("Left, Right, Middle, or a number")
- }
-
- fn visit_str<E>(self, value: &str) -> ::std::result::Result<MouseButtonWrapper, E>
- where
- E: de::Error,
- {
- match value {
- "Left" => Ok(MouseButtonWrapper(MouseButton::Left)),
- "Right" => Ok(MouseButtonWrapper(MouseButton::Right)),
- "Middle" => Ok(MouseButtonWrapper(MouseButton::Middle)),
- _ => {
- if let Ok(index) = u8::from_str(value) {
- Ok(MouseButtonWrapper(MouseButton::Other(index)))
- } else {
- Err(E::invalid_value(Unexpected::Str(value), &self))
- }
- },
- }
- }
- }
-
- deserializer.deserialize_str(MouseButtonVisitor)
- }
-}
-
-/// Bindings are deserialized into a `RawBinding` before being parsed as a
-/// `KeyBinding` or `MouseBinding`.
-#[derive(PartialEq, Eq)]
-struct RawBinding {
- key: Option<Key>,
- mouse: Option<MouseButton>,
- mods: ModifiersState,
- mode: TermMode,
- notmode: TermMode,
- action: Action,
-}
-
-impl RawBinding {
- fn into_mouse_binding(self) -> ::std::result::Result<MouseBinding, Self> {
- if let Some(mouse) = self.mouse {
- Ok(Binding {
- trigger: mouse,
- mods: self.mods,
- action: self.action,
- mode: self.mode,
- notmode: self.notmode,
- })
- } else {
- Err(self)
- }
- }
-
- fn into_key_binding(self) -> ::std::result::Result<KeyBinding, Self> {
- if let Some(key) = self.key {
- Ok(KeyBinding {
- trigger: key,
- mods: self.mods,
- action: self.action,
- mode: self.mode,
- notmode: self.notmode,
- })
- } else {
- Err(self)
- }
- }
-}
-
-impl<'a> Deserialize<'a> for RawBinding {
- fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
- where
- D: Deserializer<'a>,
- {
- enum Field {
- Key,
- Mods,
- Mode,
- Action,
- Chars,
- Mouse,
- Command,
- }
-
- impl<'a> Deserialize<'a> for Field {
- fn deserialize<D>(deserializer: D) -> ::std::result::Result<Field, D::Error>
- where
- D: Deserializer<'a>,
- {
- struct FieldVisitor;
-
- static FIELDS: &[&str] =
- &["key", "mods", "mode", "action", "chars", "mouse", "command"];
-
- impl<'a> Visitor<'a> for FieldVisitor {
- type Value = Field;
-
- fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.write_str("binding fields")
- }
-
- fn visit_str<E>(self, value: &str) -> ::std::result::Result<Field, E>
- where
- E: de::Error,
- {
- match value {
- "key" => Ok(Field::Key),
- "mods" => Ok(Field::Mods),
- "mode" => Ok(Field::Mode),
- "action" => Ok(Field::Action),
- "chars" => Ok(Field::Chars),
- "mouse" => Ok(Field::Mouse),
- "command" => Ok(Field::Command),
- _ => Err(E::unknown_field(value, FIELDS)),
- }
- }
- }
-
- deserializer.deserialize_str(FieldVisitor)
- }
- }
-
- struct RawBindingVisitor;
- impl<'a> Visitor<'a> for RawBindingVisitor {
- type Value = RawBinding;
-
- fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.write_str("binding specification")
- }
-
- fn visit_map<V>(self, mut map: V) -> ::std::result::Result<RawBinding, V::Error>
- where
- V: MapAccess<'a>,
- {
- let mut mods: Option<ModifiersState> = None;
- let mut key: Option<Key> = None;
- let mut chars: Option<String> = None;
- let mut action: Option<crate::input::Action> = None;
- let mut mode: Option<TermMode> = None;
- let mut not_mode: Option<TermMode> = None;
- let mut mouse: Option<MouseButton> = None;
- let mut command: Option<CommandWrapper> = None;
-
- use ::serde::de::Error;
-
- while let Some(struct_key) = map.next_key::<Field>()? {
- match struct_key {
- Field::Key => {
- if key.is_some() {
- return Err(<V::Error as Error>::duplicate_field("key"));
- }
-
- let val = map.next_value::<serde_yaml::Value>()?;
- if val.is_u64() {
- let scancode = val.as_u64().unwrap();
- if scancode > u64::from(::std::u32::MAX) {
- return Err(<V::Error as Error>::custom(format!(
- "Invalid key binding, scancode too big: {}",
- scancode
- )));
- }
- key = Some(Key::Scancode(scancode as u32));
- } else {
- let k = Key::deserialize(val).map_err(V::Error::custom)?;
- key = Some(k);
- }
- },
- Field::Mods => {
- if mods.is_some() {
- return Err(<V::Error as Error>::duplicate_field("mods"));
- }
-
- mods = Some(map.next_value::<ModsWrapper>()?.into_inner());
- },
- Field::Mode => {
- if mode.is_some() {
- return Err(<V::Error as Error>::duplicate_field("mode"));
- }
-
- let mode_deserializer = map.next_value::<ModeWrapper>()?;
- mode = Some(mode_deserializer.mode);
- not_mode = Some(mode_deserializer.not_mode);
- },
- Field::Action => {
- if action.is_some() {
- return Err(<V::Error as Error>::duplicate_field("action"));
- }
-
- action = Some(map.next_value::<Action>()?);
- },
- Field::Chars => {
- if chars.is_some() {
- return Err(<V::Error as Error>::duplicate_field("chars"));
- }
-
- chars = Some(map.next_value()?);
- },
- Field::Mouse => {
- if chars.is_some() {
- return Err(<V::Error as Error>::duplicate_field("mouse"));
- }
-
- mouse = Some(map.next_value::<MouseButtonWrapper>()?.into_inner());
- },
- Field::Command => {
- if command.is_some() {
- return Err(<V::Error as Error>::duplicate_field("command"));
- }
-
- command = Some(map.next_value::<CommandWrapper>()?);
- },
- }
- }
-
- let action = match (action, chars, command) {
- (Some(action), None, None) => action,
- (None, Some(chars), None) => Action::Esc(chars),
- (None, None, Some(cmd)) => match cmd {
- CommandWrapper::Just(program) => Action::Command(program, vec![]),
- CommandWrapper::WithArgs { program, args } => {
- Action::Command(program, args)
- },
- },
- (None, None, None) => {
- return Err(V::Error::custom("must specify chars, action or command"));
- },
- _ => {
- return Err(V::Error::custom("must specify only chars, action or command"))
- },
- };
-
- let mode = mode.unwrap_or_else(TermMode::empty);
- let not_mode = not_mode.unwrap_or_else(TermMode::empty);
- let mods = mods.unwrap_or_else(ModifiersState::default);
-
- if mouse.is_none() && key.is_none() {
- return Err(V::Error::custom("bindings require mouse button or key"));
- }
-
- Ok(RawBinding { mode, notmode: not_mode, action, key, mouse, mods })
- }
- }
-
- const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"];
-
- deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor)
- }
-}
-
-impl<'a> Deserialize<'a> for MouseBinding {
- fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
- where
- D: Deserializer<'a>,
- {
- let raw = RawBinding::deserialize(deserializer)?;
- raw.into_mouse_binding().map_err(|_| D::Error::custom("expected mouse binding"))
- }
-}
-
-impl<'a> Deserialize<'a> for KeyBinding {
- fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
- where
- D: Deserializer<'a>,
- {
- let raw = RawBinding::deserialize(deserializer)?;
- raw.into_key_binding().map_err(|_| D::Error::custom("expected key binding"))
- }
-}
-
-#[serde(untagged)]
-#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
-pub enum CommandWrapper {
- Just(String),
- WithArgs {
- program: String,
- #[serde(default)]
- args: Vec<String>,
- },
-}
-
-impl CommandWrapper {
- pub fn program(&self) -> &str {
- match self {
- CommandWrapper::Just(program) => program,
- CommandWrapper::WithArgs { program, .. } => program,
- }
- }
-
- pub fn args(&self) -> &[String] {
- match self {
- CommandWrapper::Just(_) => &[],
- CommandWrapper::WithArgs { args, .. } => args,
- }
- }
-}
-
-/// Newtype for implementing deserialize on glutin Mods
-///
-/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the
-/// impl below.
-#[derive(Debug, Copy, Clone, Hash, Default, Eq, PartialEq)]
-pub struct ModsWrapper(ModifiersState);
-
-impl ModsWrapper {
- pub fn into_inner(self) -> ModifiersState {
- self.0
- }
-}
-
-impl<'a> de::Deserialize<'a> for ModsWrapper {
- fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
- where
- D: de::Deserializer<'a>,
- {
- struct ModsVisitor;
-
- impl<'a> Visitor<'a> for ModsVisitor {
- type Value = ModsWrapper;
-
- fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.write_str("Some subset of Command|Shift|Super|Alt|Option|Control")
- }
-
- fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModsWrapper, E>
- where
- E: de::Error,
- {
- let mut res = ModifiersState::default();
- for modifier in value.split('|') {
- match modifier.trim().to_lowercase().as_str() {
- "command" | "super" => res.logo = true,
- "shift" => res.shift = true,
- "alt" | "option" => res.alt = true,
- "control" => res.ctrl = true,
- "none" => (),
- _ => error!("Unknown modifier {:?}", modifier),
- }
- }
-
- Ok(ModsWrapper(res))
- }
- }
-
- deserializer.deserialize_str(ModsVisitor)
- }
-}
diff --git a/alacritty_terminal/src/config/colors.rs b/alacritty_terminal/src/config/colors.rs
index a9e7a6de..35c03684 100644
--- a/alacritty_terminal/src/config/colors.rs
+++ b/alacritty_terminal/src/config/colors.rs
@@ -1,10 +1,11 @@
+use log::error;
use serde::{Deserialize, Deserializer};
-use crate::config::failure_default;
+use crate::config::{failure_default, LOG_TARGET_CONFIG};
use crate::term::color::Rgb;
#[serde(default)]
-#[derive(Deserialize, Debug, Default, PartialEq, Eq)]
+#[derive(Deserialize, Clone, Debug, Default, PartialEq, Eq)]
pub struct Colors {
#[serde(deserialize_with = "failure_default")]
pub primary: PrimaryColors,
@@ -33,7 +34,7 @@ impl Colors {
}
#[serde(default)]
-#[derive(Deserialize, Default, Debug, PartialEq, Eq)]
+#[derive(Deserialize, Clone, Default, Debug, PartialEq, Eq)]
pub struct IndexedColor {
#[serde(deserialize_with = "deserialize_color_index")]
pub index: u8,
@@ -50,6 +51,7 @@ where
Ok(index) => {
if index < 16 {
error!(
+ target: LOG_TARGET_CONFIG,
"Problem with config: indexed_color's index is {}, but a value bigger than 15 \
was expected; ignoring setting",
index
@@ -62,7 +64,7 @@ where
}
},
Err(err) => {
- error!("Problem with config: {}; ignoring setting", err);
+ error!(target: LOG_TARGET_CONFIG, "Problem with config: {}; ignoring setting", err);
// Return value out of range to ignore this color
Ok(0)
@@ -89,7 +91,7 @@ pub struct SelectionColors {
}
#[serde(default)]
-#[derive(Deserialize, Debug, PartialEq, Eq)]
+#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct PrimaryColors {
#[serde(default = "default_background", deserialize_with = "failure_default")]
pub background: Rgb,
@@ -121,7 +123,7 @@ fn default_foreground() -> Rgb {
}
/// The 8-colors sections of config
-#[derive(Deserialize, Debug, PartialEq, Eq)]
+#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct AnsiColors {
#[serde(deserialize_with = "failure_default")]
pub black: Rgb,
@@ -141,7 +143,7 @@ pub struct AnsiColors {
pub white: Rgb,
}
-#[derive(Deserialize, Debug, PartialEq, Eq)]
+#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
struct NormalColors(AnsiColors);
impl Default for NormalColors {
@@ -159,7 +161,7 @@ impl Default for NormalColors {
}
}
-#[derive(Deserialize, Debug, PartialEq, Eq)]
+#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
struct BrightColors(AnsiColors);
impl Default for BrightColors {
diff --git a/alacritty_terminal/src/config/debug.rs b/alacritty_terminal/src/config/debug.rs
index b7d1144f..f3489693 100644
--- a/alacritty_terminal/src/config/debug.rs
+++ b/alacritty_terminal/src/config/debug.rs
@@ -1,7 +1,7 @@
-use log::LevelFilter;
-use serde::Deserializer;
+use log::{error, LevelFilter};
+use serde::{Deserialize, Deserializer};
-use crate::config::failure_default;
+use crate::config::{failure_default, LOG_TARGET_CONFIG};
/// Debugging options
#[serde(default)]
@@ -54,7 +54,10 @@ where
"debug" => LevelFilter::Debug,
"trace" => LevelFilter::Trace,
level => {
- error!("Problem with config: invalid log level {}; using level Warn", level);
+ error!(
+ target: LOG_TARGET_CONFIG,
+ "Problem with config: invalid log level {}; using level Warn", level
+ );
default_log_level()
},
})
diff --git a/alacritty_terminal/src/config/font.rs b/alacritty_terminal/src/config/font.rs
index 6148c982..a8a76c15 100644
--- a/alacritty_terminal/src/config/font.rs
+++ b/alacritty_terminal/src/config/font.rs
@@ -1,12 +1,13 @@
use std::fmt;
use font::Size;
+use log::error;
use serde::de::Visitor;
use serde::{Deserialize, Deserializer};
#[cfg(target_os = "macos")]
use crate::config::DefaultTrueBool;
-use crate::config::{failure_default, Delta};
+use crate::config::{failure_default, Delta, LOG_TARGET_CONFIG};
/// Font config
///
@@ -202,7 +203,12 @@ impl DeserializeSize for Size {
Ok(size) => Ok(size),
Err(err) => {
let size = default_font_size();
- error!("Problem with config: {}; using size {}", err, size.as_f32_pts());
+ error!(
+ target: LOG_TARGET_CONFIG,
+ "Problem with config: {}; using size {}",
+ err,
+ size.as_f32_pts()
+ );
Ok(size)
},
}
diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs
index ac945e9b..cd900373 100644
--- a/alacritty_terminal/src/config/mod.rs
+++ b/alacritty_terminal/src/config/mod.rs
@@ -17,42 +17,38 @@ use std::collections::HashMap;
use std::fmt::Display;
use std::path::PathBuf;
+use log::error;
+use serde::de::DeserializeOwned;
use serde::{Deserialize, Deserializer};
use serde_yaml::Value;
-mod bindings;
mod colors;
mod debug;
mod font;
-mod monitor;
-mod mouse;
mod scrolling;
-#[cfg(test)]
-mod test;
mod visual_bell;
mod window;
use crate::ansi::{Color, CursorStyle, NamedColor};
-use crate::input::{Binding, KeyBinding, MouseBinding};
-pub use crate::config::bindings::Key;
pub use crate::config::colors::Colors;
pub use crate::config::debug::Debug;
pub use crate::config::font::{Font, FontDescription};
-pub use crate::config::monitor::Monitor;
-pub use crate::config::mouse::{ClickHandler, Mouse};
pub use crate::config::scrolling::Scrolling;
pub use crate::config::visual_bell::{VisualBellAnimation, VisualBellConfig};
-pub use crate::config::window::{Decorations, Dimensions, StartupMode, WindowConfig};
+pub use crate::config::window::{Decorations, Dimensions, StartupMode, WindowConfig, DEFAULT_NAME};
use crate::term::color::Rgb;
pub static DEFAULT_ALACRITTY_CONFIG: &str =
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../alacritty.yml"));
+pub const LOG_TARGET_CONFIG: &str = "alacritty_config";
const MAX_SCROLLBACK_LINES: u32 = 100_000;
+pub type MockConfig = Config<HashMap<String, serde_yaml::Value>>;
+
/// Top-level config type
#[derive(Debug, PartialEq, Deserialize)]
-pub struct Config {
+pub struct Config<T> {
/// Pixel padding
#[serde(default, deserialize_with = "failure_default")]
pub padding: Option<Delta<u8>>,
@@ -80,20 +76,9 @@ pub struct Config {
#[serde(default, deserialize_with = "failure_default")]
pub window: WindowConfig,
- /// 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>,
-
#[serde(default, deserialize_with = "failure_default")]
pub selection: Selection,
- #[serde(default, deserialize_with = "failure_default")]
- pub mouse: Mouse,
-
/// Path to a shell program to run on startup
#[serde(default, deserialize_with = "from_string_or_deserialize")]
pub shell: Option<Shell<'static>>,
@@ -144,6 +129,10 @@ pub struct Config {
#[serde(default, deserialize_with = "failure_default")]
pub debug: Debug,
+ /// Additional configuration options not directly required by the terminal
+ #[serde(flatten)]
+ pub ui_config: T,
+
// TODO: DEPRECATED
#[serde(default, deserialize_with = "failure_default")]
pub render_timer: Option<bool>,
@@ -153,13 +142,13 @@ pub struct Config {
pub persistent_logging: Option<bool>,
}
-impl Default for Config {
+impl<T: DeserializeOwned> Default for Config<T> {
fn default() -> Self {
serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("default config is invalid")
}
}
-impl Config {
+impl<T> Config<T> {
pub fn tabspaces(&self) -> usize {
self.tabspaces.0
}
@@ -236,49 +225,6 @@ impl Config {
}
}
-fn default_key_bindings() -> Vec<KeyBinding> {
- bindings::default_key_bindings()
-}
-
-fn default_mouse_bindings() -> Vec<MouseBinding> {
- bindings::default_mouse_bindings()
-}
-
-fn deserialize_key_bindings<'a, D>(deserializer: D) -> 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 + std::hash::Hash + std::fmt::Debug,
- 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)
-}
-
#[serde(default)]
#[derive(Deserialize, Default, Clone, Debug, PartialEq, Eq)]
pub struct Selection {
@@ -324,7 +270,7 @@ impl Cursor {
}
}
-#[derive(Debug, Deserialize, PartialEq, Eq)]
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct Shell<'a> {
pub program: Cow<'a, str>,
@@ -397,7 +343,7 @@ impl<'a> Deserialize<'a> for Alpha {
}
}
-#[derive(Deserialize, Debug, PartialEq, Eq)]
+#[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
struct Tabspaces(usize);
impl Default for Tabspaces {
@@ -420,7 +366,7 @@ where
T: Default,
E: Display,
{
- error!("Problem with config: {}; using default value", err);
+ error!(target: LOG_TARGET_CONFIG, "Problem with config: {}; using default value", err);
T::default()
}
diff --git a/alacritty_terminal/src/config/monitor.rs b/alacritty_terminal/src/config/monitor.rs
deleted file mode 100644
index 6d2ab41a..00000000
--- a/alacritty_terminal/src/config/monitor.rs
+++ /dev/null
@@ -1,79 +0,0 @@
-use std::path::PathBuf;
-use std::sync::mpsc;
-use std::time::Duration;
-
-use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
-
-pub struct Monitor {
- _thread: ::std::thread::JoinHandle<()>,
- rx: mpsc::Receiver<PathBuf>,
-}
-
-pub trait OnConfigReload {
- fn on_config_reload(&mut self);
-}
-
-impl OnConfigReload for crate::display::Notifier {
- fn on_config_reload(&mut self) {
- self.notify();
- }
-}
-
-impl Monitor {
- /// Get pending config changes
- pub fn pending(&self) -> Option<PathBuf> {
- let mut config = None;
- while let Ok(new) = self.rx.try_recv() {
- config = Some(new);
- }
-
- config
- }
-
- pub fn new<H, P>(path: P, mut handler: H) -> Monitor
- where
- H: OnConfigReload + Send + 'static,
- P: Into<PathBuf>,
- {
- let path = path.into();
-
- let (config_tx, config_rx) = mpsc::channel();
-
- Monitor {
- _thread: crate::util::thread::spawn_named("config watcher", move || {
- let (tx, rx) = mpsc::channel();
- // The Duration argument is a debouncing period.
- let mut watcher =
- watcher(tx, Duration::from_millis(10)).expect("Unable to spawn file watcher");
- let config_path = ::std::fs::canonicalize(path).expect("canonicalize config path");
-
- // Get directory of config
- let mut parent = config_path.clone();
- parent.pop();
-
- // Watch directory
- watcher
- .watch(&parent, RecursiveMode::NonRecursive)
- .expect("watch alacritty.yml dir");
-
- loop {
- match rx.recv().expect("watcher event") {
- DebouncedEvent::Rename(..) => continue,
- DebouncedEvent::Write(path)
- | DebouncedEvent::Create(path)
- | DebouncedEvent::Chmod(path) => {
- if path != config_path {
- continue;
- }
-
- let _ = config_tx.send(path);
- handler.on_config_reload();
- },
- _ => {},
- }
- }
- }),
- rx: config_rx,
- }
- }
-}
diff --git a/alacritty_terminal/src/config/mouse.rs b/alacritty_terminal/src/config/mouse.rs
deleted file mode 100644
index 7a04cbe7..00000000
--- a/alacritty_terminal/src/config/mouse.rs
+++ /dev/null
@@ -1,108 +0,0 @@
-use std::time::Duration;
-
-use glutin::ModifiersState;
-use serde::{Deserialize, Deserializer};
-
-use crate::config::bindings::{CommandWrapper, ModsWrapper};
-use crate::config::failure_default;
-
-#[serde(default)]
-#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq)]
-pub struct Mouse {
- #[serde(deserialize_with = "failure_default")]
- pub double_click: ClickHandler,
- #[serde(deserialize_with = "failure_default")]
- pub triple_click: ClickHandler,
- #[serde(deserialize_with = "failure_default")]
- pub hide_when_typing: bool,
- #[serde(deserialize_with = "failure_default")]
- pub url: Url,
-}
-
-#[serde(default)]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
-pub struct Url {
- // Program for opening links
- #[serde(deserialize_with = "deserialize_launcher")]
- pub launcher: Option<CommandWrapper>,
-
- // Modifier used to open links
- #[serde(deserialize_with = "failure_default")]
- modifiers: ModsWrapper,
-}
-
-impl Url {
- pub fn mods(&self) -> ModifiersState {
- self.modifiers.into_inner()
- }
-}
-
-fn deserialize_launcher<'a, D>(
- deserializer: D,
-) -> ::std::result::Result<Option<CommandWrapper>, D::Error>
-where
- D: Deserializer<'a>,
-{
- let default = Url::default().launcher;
-
- // Deserialize to generic value
- let val = serde_yaml::Value::deserialize(deserializer)?;
-
- // Accept `None` to disable the launcher
- if val.as_str().filter(|v| v.to_lowercase() == "none").is_some() {
- return Ok(None);
- }
-
- match <Option<CommandWrapper>>::deserialize(val) {
- Ok(launcher) => Ok(launcher),
- Err(err) => {
- error!("Problem with config: {}; using {}", err, default.clone().unwrap().program());
- Ok(default)
- },
- }
-}
-
-impl Default for Url {
- fn default() -> Url {
- Url {
- #[cfg(not(any(target_os = "macos", windows)))]
- launcher: Some(CommandWrapper::Just(String::from("xdg-open"))),
- #[cfg(target_os = "macos")]
- launcher: Some(CommandWrapper::Just(String::from("open"))),
- #[cfg(windows)]
- launcher: Some(CommandWrapper::Just(String::from("explorer"))),
- modifiers: Default::default(),
- }
- }
-}
-
-#[serde(default)]
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
-pub struct ClickHandler {
- #[serde(deserialize_with = "deserialize_duration_ms")]
- pub threshold: Duration,
-}
-
-impl Default for ClickHandler {
- fn default() -> Self {
- ClickHandler { threshold: default_threshold_ms() }
- }
-}
-
-fn default_threshold_ms() -> Duration {
- Duration::from_millis(300)
-}
-
-fn deserialize_duration_ms<'a, D>(deserializer: D) -> ::std::result::Result<Duration, D::Error>
-where
- D: Deserializer<'a>,
-{
- let value = serde_yaml::Value::deserialize(deserializer)?;
- match u64::deserialize(value) {
- Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)),
- Err(err) => {
- error!("Problem with config: {}; using default value", err);
- Ok(default_threshold_ms())
- },
- }
-}
diff --git a/alacritty_terminal/src/config/scrolling.rs b/alacritty_terminal/src/config/scrolling.rs
index d62b102f..8471fcd7 100644
--- a/alacritty_terminal/src/config/scrolling.rs
+++ b/alacritty_terminal/src/config/scrolling.rs
@@ -1,6 +1,7 @@
+use log::error;
use serde::{Deserialize, Deserializer};
-use crate::config::{failure_default, MAX_SCROLLBACK_LINES};
+use crate::config::{failure_default, LOG_TARGET_CONFIG, MAX_SCROLLBACK_LINES};
/// Struct for scrolling related settings
#[serde(default)]
@@ -63,9 +64,11 @@ impl<'de> Deserialize<'de> for ScrollingHistory {
Ok(lines) => {
if lines > MAX_SCROLLBACK_LINES {
error!(
+ target: LOG_TARGET_CONFIG,
"Problem with config: scrollback size is {}, but expected a maximum of \
{}; using {1} instead",
- lines, MAX_SCROLLBACK_LINES,
+ lines,
+ MAX_SCROLLBACK_LINES,
);
Ok(ScrollingHistory(MAX_SCROLLBACK_LINES))
} else {
@@ -73,7 +76,10 @@ impl<'de> Deserialize<'de> for ScrollingHistory {
}
},
Err(err) => {
- error!("Problem with config: {}; using default value", err);
+ error!(
+ target: LOG_TARGET_CONFIG,
+ "Problem with config: {}; using default value", err
+ );
Ok(Default::default())
},
}
diff --git a/alacritty_terminal/src/config/test.rs b/alacritty_terminal/src/config/test.rs
deleted file mode 100644
index e7890922..00000000
--- a/alacritty_terminal/src/config/test.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-use crate::config::{Config, DEFAULT_ALACRITTY_CONFIG};
-
-#[test]
-fn parse_config() {
- let config: Config =
- ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config");
-
- // Sanity check that mouse bindings are being parsed
- assert!(!config.mouse_bindings.is_empty());
-
- // Sanity check that key bindings are being parsed
- assert!(!config.key_bindings.is_empty());
-}
-
-#[test]
-fn default_match_empty() {
- let default = Config::default();
-
- let empty = serde_yaml::from_str("key: val\n").unwrap();
-
- assert_eq!(default, empty);
-}
diff --git a/alacritty_terminal/src/config/visual_bell.rs b/alacritty_terminal/src/config/visual_bell.rs
index 3a31b24a..8981c929 100644
--- a/alacritty_terminal/src/config/visual_bell.rs
+++ b/alacritty_terminal/src/config/visual_bell.rs
@@ -1,10 +1,12 @@
use std::time::Duration;
+use serde::Deserialize;
+
use crate::config::failure_default;
use crate::term::color::Rgb;
#[serde(default)]
-#[derive(Debug, Deserialize, PartialEq, Eq)]
+#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct VisualBellConfig {
/// Visual bell animation function
#[serde(deserialize_with = "failure_default")]
diff --git a/alacritty_terminal/src/config/window.rs b/alacritty_terminal/src/config/window.rs
index 7ca90a5b..f470f936 100644
--- a/alacritty_terminal/src/config/window.rs
+++ b/alacritty_terminal/src/config/window.rs
@@ -1,8 +1,12 @@
+use serde::Deserialize;
+
use crate::config::{
failure_default, from_string_or_deserialize, option_explicit_none, Delta, FromString,
};
use crate::index::{Column, Line};
-use crate::window::DEFAULT_NAME;
+
+/// Default Alacritty name, used for window title and class.
+pub const DEFAULT_NAME: &str = "Alacritty";
#[serde(default)]
#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)]
diff --git a/alacritty_terminal/src/cursor.rs b/alacritty_terminal/src/cursor.rs
index 93f2bd30..d1df14e2 100644
--- a/alacritty_terminal/src/cursor.rs
+++ b/alacritty_terminal/src/cursor.rs
@@ -16,6 +16,8 @@
use std::cmp;
+use serde::Deserialize;
+
use font::{Metrics, RasterizedGlyph};
use crate::ansi::CursorStyle;
diff --git a/alacritty_terminal/src/display.rs b/alacritty_terminal/src/display.rs
deleted file mode 100644
index bdd34460..00000000
--- a/alacritty_terminal/src/display.rs
+++ /dev/null
@@ -1,603 +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;
-#[cfg(not(any(target_os = "macos", target_os = "windows")))]
-use std::ffi::c_void;
-use std::sync::mpsc;
-
-use glutin::dpi::{PhysicalPosition, PhysicalSize};
-use glutin::EventsLoop;
-use parking_lot::MutexGuard;
-
-use crate::config::{Config, StartupMode};
-use crate::index::Line;
-use crate::message_bar::Message;
-use crate::meter::Meter;
-use crate::renderer::rects::{RenderLines, RenderRect};
-use crate::renderer::{self, GlyphCache, QuadRenderer};
-use crate::sync::FairMutex;
-use crate::term::color::Rgb;
-use crate::term::{RenderableCell, SizeInfo, Term};
-use crate::window::{self, Window};
-use font::{self, Rasterize};
-
-#[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),
-}
-
-impl ::std::error::Error for Error {
- fn cause(&self) -> Option<&dyn (::std::error::Error)> {
- match *self {
- Error::Window(ref err) => Some(err),
- Error::Font(ref err) => Some(err),
- Error::Render(ref err) => Some(err),
- }
- }
-
- fn description(&self) -> &str {
- match *self {
- Error::Window(ref err) => err.description(),
- Error::Font(ref err) => err.description(),
- Error::Render(ref err) => err.description(),
- }
- }
-}
-
-impl ::std::fmt::Display for Error {
- fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
- match *self {
- Error::Window(ref err) => err.fmt(f),
- Error::Font(ref err) => err.fmt(f),
- Error::Render(ref err) => err.fmt(f),
- }
- }
-}
-
-impl From<window::Error> for Error {
- fn from(val: window::Error) -> Error {
- Error::Window(val)
- }
-}
-
-impl From<font::Error> for Error {
- fn from(val: font::Error) -> Error {
- Error::Font(val)
- }
-}
-
-impl From<renderer::Error> for Error {
- fn from(val: renderer::Error) -> Error {
- Error::Render(val)
- }
-}
-
-/// The display wraps a window, font rasterizer, and GPU renderer
-pub struct Display {
- window: Window,
- renderer: QuadRenderer,
- glyph_cache: GlyphCache,
- render_timer: bool,
- rx: mpsc::Receiver<PhysicalSize>,
- tx: mpsc::Sender<PhysicalSize>,
- meter: Meter,
- font_size: font::Size,
- size_info: SizeInfo,
- last_message: Option<Message>,
-}
-
-/// Can wakeup the render loop from other threads
-pub struct Notifier(window::Proxy);
-
-/// Types that are interested in when the display is resized
-pub trait OnResize {
- fn on_resize(&mut self, size: &SizeInfo);
-}
-
-impl Notifier {
- pub fn notify(&self) {
- self.0.wakeup_event_loop();
- }
-}
-
-impl Display {
- pub fn notifier(&self) -> Notifier {
- Notifier(self.window.create_window_proxy())
- }
-
- pub fn update_config(&mut self, config: &Config) {
- self.render_timer = config.render_timer();
- }
-
- /// Get size info about the display
- pub fn size(&self) -> &SizeInfo {
- &self.size_info
- }
-
- pub fn new(config: &Config) -> Result<Display, Error> {
- // Extract some properties from config
- let render_timer = config.render_timer();
-
- // Guess DPR based on first monitor
- let event_loop = EventsLoop::new();
- let estimated_dpr =
- event_loop.get_available_monitors().next().map(|m| m.get_hidpi_factor()).unwrap_or(1.);
-
- // Guess the target window dimensions
- let metrics = GlyphCache::static_metrics(config, estimated_dpr as f32)?;
- let (cell_width, cell_height) = Self::compute_cell_size(config, &metrics);
- let dimensions = Self::calculate_dimensions(config, 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 logical = dimensions.map(|d| PhysicalSize::new(d.0, d.1).to_logical(estimated_dpr));
- let mut window = Window::new(event_loop, &config, logical)?;
-
- let dpr = window.hidpi_factor();
- info!("Device pixel ratio: {}", dpr);
-
- // get window properties for initializing the other subsystems
- let mut viewport_size =
- window.inner_size_pixels().expect("glutin returns window size").to_physical(dpr);
-
- // 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 = f64::from(config.window.padding.x) * dpr;
- let mut padding_y = f64::from(config.window.padding.y) * dpr;
-
- if let Some((width, height)) =
- Self::calculate_dimensions(config, dpr, cell_width, cell_height)
- {
- if dimensions == Some((width, height)) {
- info!("Estimated DPR correctly, skipping resize");
- } else {
- viewport_size = PhysicalSize::new(width, height);
- window.set_inner_size(viewport_size.to_logical(dpr));
- }
- } else if config.window.dynamic_padding {
- // Make sure additional padding is spread evenly
- let cw = f64::from(cell_width);
- let ch = f64::from(cell_height);
- padding_x = padding_x + (viewport_size.width - 2. * padding_x) % cw / 2.;
- padding_y = padding_y + (viewport_size.height - 2. * padding_y) % ch / 2.;
- }
-
- padding_x = padding_x.floor();
- padding_y = padding_y.floor();
-
- // Update OpenGL projection
- renderer.resize(viewport_size, padding_x as f32, padding_y as f32);
-
- info!("Cell Size: {} x {}", cell_width, cell_height);
- info!("Padding: {} x {}", padding_x, padding_y);
-
- let size_info = SizeInfo {
- dpr,
- width: viewport_size.width as f32,
- height: viewport_size.height as f32,
- cell_width: cell_width as f32,
- cell_height: cell_height as f32,
- padding_x: padding_x as f32,
- padding_y: padding_y as f32,
- };
-
- // Channel for resize events
- //
- // macOS has a callback for getting resize events, the channel is used
- // to queue resize events until the next draw call. Unfortunately, it
- // seems that the event loop is blocked until the window is done
- // resizing. If any drawing were to happen during a resize, it would
- // need to be in the callback.
- let (tx, rx) = mpsc::channel();
-
- // 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.
- window.swap_buffers()?;
-
- window.show();
-
- // Set window position
- //
- // TODO: replace `set_position` with `with_position` once available
- // Upstream issue: https://github.com/tomaka/winit/issues/806
- if let Some(position) = config.window.position {
- let physical = PhysicalPosition::from((position.x, position.y));
- let logical = physical.to_logical(window.hidpi_factor());
- window.set_position(logical);
- }
-
- #[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 if window.is_x11() => window.set_maximized(true),
- _ => (),
- }
-
- Ok(Display {
- window,
- renderer,
- glyph_cache,
- render_timer,
- tx,
- rx,
- meter: Meter::new(),
- font_size: config.font.size,
- size_info,
- last_message: None,
- })
- }
-
- fn calculate_dimensions(
- config: &Config,
- dpr: f64,
- cell_width: f32,
- cell_height: f32,
- ) -> Option<(f64, f64)> {
- 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 = (f64::from(grid_width) + 2. * padding_x).floor();
- let height = (f64::from(grid_height) + 2. * padding_y).floor();
-
- Some((width, height))
- }
-
- 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 = ::std::time::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) = Self::compute_cell_size(config, &glyph_cache.font_metrics());
-
- Ok((glyph_cache, cw, ch))
- }
-
- pub fn update_glyph_cache(&mut self, config: &Config) {
- let cache = &mut self.glyph_cache;
- let dpr = self.size_info.dpr;
- let size = self.font_size;
-
- self.renderer.with_loader(|mut api| {
- let _ = cache.update_font_size(&config.font, size, dpr, &mut api);
- });
-
- let (cw, ch) = Self::compute_cell_size(config, &cache.font_metrics());
- self.size_info.cell_width = cw;
- self.size_info.cell_height = ch;
- }
-
- 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);
- (
- f32::max(1., ((metrics.average_advance + offset_x) as f32).floor()),
- f32::max(1., ((metrics.line_height + offset_y) as f32).floor()),
- )
- }
-
- #[inline]
- pub fn resize_channel(&self) -> mpsc::Sender<PhysicalSize> {
- self.tx.clone()
- }
-
- pub fn window(&mut self) -> &mut Window {
- &mut self.window
- }
-
- /// Process pending resize events
- pub fn handle_resize(
- &mut self,
- terminal: &mut MutexGuard<'_, Term>,
- config: &Config,
- pty_resize_handle: &mut dyn OnResize,
- processor_resize_handle: &mut dyn OnResize,
- ) {
- let previous_cols = self.size_info.cols();
- let previous_lines = self.size_info.lines();
-
- // Resize events new_size and are handled outside the poll_events
- // iterator. This has the effect of coalescing multiple resize
- // events into one.
- let mut new_size = None;
-
- // Take most recent resize event, if any
- while let Ok(size) = self.rx.try_recv() {
- new_size = Some(size);
- }
-
- // Update the DPR
- let dpr = self.window.hidpi_factor();
-
- // Font size/DPI factor modification detected
- let font_changed =
- terminal.font_size != self.font_size || (dpr - self.size_info.dpr).abs() > f64::EPSILON;
-
- // Skip resize if nothing changed
- if let Some(new_size) = new_size {
- if !font_changed
- && (new_size.width - f64::from(self.size_info.width)).abs() < f64::EPSILON
- && (new_size.height - f64::from(self.size_info.height)).abs() < f64::EPSILON
- {
- return;
- }
- }
-
- // Message bar update detected
- let message_bar_changed = self.last_message != terminal.message_buffer_mut().message();
-
- if font_changed || message_bar_changed {
- if new_size == None {
- // Force a resize to refresh things
- new_size = Some(PhysicalSize::new(
- f64::from(self.size_info.width) / self.size_info.dpr * dpr,
- f64::from(self.size_info.height) / self.size_info.dpr * dpr,
- ));
- }
-
- self.font_size = terminal.font_size;
- self.last_message = terminal.message_buffer_mut().message();
- self.size_info.dpr = dpr;
- }
-
- if font_changed {
- self.update_glyph_cache(config);
- }
-
- if let Some(psize) = new_size.take() {
- let width = psize.width as f32;
- let height = psize.height as f32;
- let cell_width = self.size_info.cell_width;
- let cell_height = self.size_info.cell_height;
-
- self.size_info.width = width;
- self.size_info.height = height;
-
- let mut padding_x = f32::from(config.window.padding.x) * dpr as f32;
- let mut padding_y = f32::from(config.window.padding.y) * dpr as f32;
-
- if config.window.dynamic_padding {
- padding_x = padding_x + ((width - 2. * padding_x) % cell_width) / 2.;
- padding_y = padding_y + ((height - 2. * padding_y) % cell_height) / 2.;
- }
-
- self.size_info.padding_x = padding_x.floor();
- self.size_info.padding_y = padding_y.floor();
-
- let size = &self.size_info;
- terminal.resize(size);
- processor_resize_handle.on_resize(size);
-
- // Subtract message bar lines for pty size
- let mut pty_size = *size;
- if let Some(message) = terminal.message_buffer_mut().message() {
- pty_size.height -= pty_size.cell_height * message.text(&size).len() as f32;
- }
-
- if message_bar_changed
- || previous_cols != pty_size.cols()
- || previous_lines != pty_size.lines()
- {
- pty_resize_handle.on_resize(&pty_size);
- }
-
- self.window.resize(psize);
- self.renderer.resize(psize, self.size_info.padding_x, self.size_info.padding_y);
- }
- }
-
- /// 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(&mut self, terminal: &FairMutex<Term>, config: &Config) {
- let mut terminal = terminal.lock();
- let size_info = *terminal.size_info();
- let visual_bell_intensity = terminal.visual_bell.intensity();
- let background_color = terminal.background_color();
- let metrics = self.glyph_cache.font_metrics();
-
- let window_focused = self.window.is_focused;
- let grid_cells: Vec<RenderableCell> =
- terminal.renderable_cells(config, window_focused).collect();
-
- // Get message from terminal to ignore modifications after lock is dropped
- let message_buffer = terminal.message_buffer_mut().message();
-
- // Clear dirty flag
- terminal.dirty = !terminal.visual_bell.completed();
-
- if let Some(title) = terminal.get_next_title() {
- self.window.set_title(&title);
- }
-
- if let Some(mouse_cursor) = terminal.get_next_mouse_cursor() {
- self.window.set_mouse_cursor(mouse_cursor);
- }
-
- if let Some(is_urgent) = terminal.next_is_urgent.take() {
- // We don't need to set the urgent flag if we already have the
- // user's attention.
- if !is_urgent || !self.window.is_focused {
- self.window.set_urgent(is_urgent);
- }
- }
-
- // Clear when terminal mutex isn't held. Mesa for
- // some reason takes a long time to call glClear(). The driver descends
- // into xcb_connect_to_fd() which ends up calling __poll_nocancel()
- // which blocks for a while.
- //
- // By keeping this outside of the critical region, the Mesa bug is
- // worked around to some extent. Since this doesn't actually address the
- // issue of glClear being slow, less time is available for input
- // handling and rendering.
- drop(terminal);
-
- self.renderer.with_api(config, &size_info, |api| {
- api.clear(background_color);
- });
-
- {
- let glyph_cache = &mut self.glyph_cache;
- let mut lines = RenderLines::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 underline/strikeout
- lines.update(&cell);
-
- // Draw the cell
- api.render_cell(cell, glyph_cache);
- }
- });
- }
-
- let mut rects = lines.into_rects(&metrics, &size_info);
-
- if let Some(message) = message_buffer {
- 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.padding_y + size_info.cell_height * start_line as f32;
- rects.push(RenderRect::new(
- 0.,
- y,
- size_info.width,
- size_info.height - y,
- message.color(),
- ));
-
- // Draw rectangles including the new background
- self.renderer.draw_rects(config, &size_info, visual_bell_intensity, 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(config, &size_info, visual_bell_intensity, rects);
- }
-
- // Draw render timer
- if self.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().expect("swap buffers");
- }
-
- pub fn get_window_id(&self) -> Option<usize> {
- self.window.get_window_id()
- }
-
- /// Adjust the IME editor position according to the new location of the cursor
- pub fn update_ime_position(&mut self, terminal: &Term) {
- let point = terminal.cursor().point;
- let SizeInfo { cell_width: cw, cell_height: ch, padding_x: px, padding_y: py, .. } =
- *terminal.size_info();
-
- let dpr = self.window().hidpi_factor();
- let nspot_x = f64::from(px + point.col.0 as f32 * cw);
- let nspot_y = f64::from(py + (point.line.0 + 1) as f32 * ch);
-
- self.window().set_ime_spot(PhysicalPosition::from((nspot_x, nspot_y)).to_logical(dpr));
- }
-
- #[cfg(not(any(target_os = "macos", target_os = "windows")))]
- pub fn get_wayland_display(&self) -> Option<*mut c_void> {
- self.window.get_wayland_display()
- }
-}
diff --git a/alacritty_terminal/src/event.rs b/alacritty_terminal/src/event.rs
index 9d2aa78c..2d43e9dd 100644
--- a/alacritty_terminal/src/event.rs
+++ b/alacritty_terminal/src/event.rs
@@ -1,28 +1,19 @@
-//! Process window events
use std::borrow::Cow;
-use std::env;
-#[cfg(unix)]
-use std::fs;
-use std::sync::mpsc;
-use std::time::Instant;
+use std::path::PathBuf;
-use glutin::dpi::PhysicalSize;
-use glutin::{self, ElementState, Event, MouseButton};
-use parking_lot::MutexGuard;
+use crate::message_bar::Message;
+use crate::term::SizeInfo;
-use crate::clipboard::ClipboardType;
-use crate::config::{self, Config, StartupMode};
-use crate::display::OnResize;
-use crate::grid::Scroll;
-use crate::index::{Column, Line, Point, Side};
-use crate::input::{self, KeyBinding, Modifiers, MouseBinding};
-use crate::selection::Selection;
-use crate::sync::FairMutex;
-use crate::term::{SizeInfo, Term};
-#[cfg(unix)]
-use crate::tty;
-use crate::util::{limit, start_daemon};
-use crate::window::Window;
+#[derive(Clone, Debug, PartialEq)]
+pub enum Event {
+ ConfigReload(PathBuf),
+ MouseCursorDirty,
+ Message(Message),
+ Title(String),
+ Wakeup,
+ Urgent,
+ Exit,
+}
/// Byte sequences are sent to a `Notify` in response to some events
pub trait Notify {
@@ -32,526 +23,12 @@ pub trait Notify {
fn notify<B: Into<Cow<'static, [u8]>>>(&mut self, _: B);
}
-pub struct ActionContext<'a, N> {
- pub notifier: &'a mut N,
- pub terminal: &'a mut Term,
- 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 Modifiers,
- pub window_changes: &'a mut WindowChanges,
+/// Types that are interested in when the display is resized
+pub trait OnResize {
+ fn on_resize(&mut self, size: &SizeInfo);
}
-impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> {
- 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> {
- self.terminal.pixels_to_coords(self.mouse.x as usize, self.mouse.y as usize)
- }
-
- #[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 Modifiers {
- &mut self.modifiers
- }
-
- #[inline]
- fn hide_window(&mut self) {
- self.window_changes.hide = true;
- }
-
- #[inline]
- fn terminal(&self) -> &Term {
- self.terminal
- }
-
- #[inline]
- fn terminal_mut(&mut self) -> &mut Term {
- 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 toggle_fullscreen(&mut self) {
- self.window_changes.toggle_fullscreen();
- }
-
- #[cfg(target_os = "macos")]
- fn toggle_simple_fullscreen(&mut self) {
- self.window_changes.toggle_simple_fullscreen()
- }
-}
-
-/// The ActionContext can't really have direct access to the Window
-/// with the current design. Event handlers that want to change the
-/// window must set these flags instead. The processor will trigger
-/// the actual changes.
-#[derive(Default)]
-pub struct WindowChanges {
- pub hide: bool,
- pub toggle_fullscreen: bool,
- #[cfg(target_os = "macos")]
- pub toggle_simple_fullscreen: bool,
-}
-
-impl WindowChanges {
- fn clear(&mut self) {
- *self = WindowChanges::default();
- }
-
- fn toggle_fullscreen(&mut self) {
- self.toggle_fullscreen = !self.toggle_fullscreen;
- }
-
- #[cfg(target_os = "macos")]
- fn toggle_simple_fullscreen(&mut self) {
- self.toggle_simple_fullscreen = !self.toggle_simple_fullscreen;
- }
-}
-
-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,
-}
-
-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),
- }
- }
-}
-
-/// The event processor
-///
-/// Stores some state from received events and dispatches actions when they are
-/// triggered.
-pub struct Processor<N> {
- key_bindings: Vec<KeyBinding>,
- mouse_bindings: Vec<MouseBinding>,
- mouse_config: config::Mouse,
- scrolling_config: config::Scrolling,
- print_events: bool,
- wait_for_event: bool,
- notifier: N,
- mouse: Mouse,
- resize_tx: mpsc::Sender<PhysicalSize>,
- size_info: SizeInfo,
- hide_mouse_when_typing: bool,
- hide_mouse: bool,
- received_count: usize,
- suppress_chars: bool,
- modifiers: Modifiers,
- pending_events: Vec<Event>,
- window_changes: WindowChanges,
- save_to_clipboard: bool,
- alt_send_esc: bool,
- is_fullscreen: bool,
- is_simple_fullscreen: bool,
-}
-
-/// Notify that the terminal was resized
-///
-/// Currently this just forwards the notice to the input processor.
-impl<N> OnResize for Processor<N> {
- fn on_resize(&mut self, size: &SizeInfo) {
- self.size_info = size.to_owned();
- }
-}
-
-impl<N: Notify> 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,
- resize_tx: mpsc::Sender<PhysicalSize>,
- config: &Config,
- size_info: SizeInfo,
- ) -> Processor<N> {
- Processor {
- key_bindings: config.key_bindings.to_vec(),
- mouse_bindings: config.mouse_bindings.to_vec(),
- mouse_config: config.mouse.to_owned(),
- scrolling_config: config.scrolling,
- print_events: config.debug.print_events,
- wait_for_event: true,
- notifier,
- resize_tx,
- mouse: Default::default(),
- size_info,
- hide_mouse_when_typing: config.mouse.hide_when_typing,
- hide_mouse: false,
- received_count: 0,
- suppress_chars: false,
- modifiers: Default::default(),
- pending_events: Vec::with_capacity(4),
- window_changes: Default::default(),
- save_to_clipboard: config.selection.save_to_clipboard,
- alt_send_esc: config.alt_send_esc(),
- is_fullscreen: config.window.startup_mode() == StartupMode::Fullscreen,
- #[cfg(target_os = "macos")]
- is_simple_fullscreen: config.window.startup_mode() == StartupMode::SimpleFullscreen,
- #[cfg(not(target_os = "macos"))]
- is_simple_fullscreen: false,
- }
- }
-
- /// Handle events from glutin
- ///
- /// Doesn't take self mutably due to borrow checking. Kinda uggo but w/e.
- fn handle_event<'a>(
- processor: &mut input::Processor<'a, ActionContext<'a, N>>,
- event: Event,
- resize_tx: &mpsc::Sender<PhysicalSize>,
- hide_mouse: &mut bool,
- window_is_focused: &mut bool,
- ) {
- match event {
- // Pass on device events
- Event::DeviceEvent { .. } | Event::Suspended { .. } => (),
- Event::WindowEvent { event, .. } => {
- use glutin::WindowEvent::*;
- match event {
- CloseRequested => processor.ctx.terminal.exit(),
- Resized(lsize) => {
- // Resize events are emitted via glutin/winit with logical sizes
- // However the terminal, window and renderer use physical sizes
- // so a conversion must be done here
- resize_tx
- .send(lsize.to_physical(processor.ctx.size_info.dpr))
- .expect("send new size");
- processor.ctx.terminal.dirty = true;
- },
- KeyboardInput { input, .. } => {
- processor.process_key(input);
- if input.state == ElementState::Pressed {
- // Hide cursor while typing
- *hide_mouse = true;
- }
- },
- ReceivedCharacter(c) => {
- processor.received_char(c);
- },
- MouseInput { state, button, modifiers, .. } => {
- if !cfg!(target_os = "macos") || *window_is_focused {
- *hide_mouse = false;
- processor.mouse_input(state, button, modifiers);
- processor.ctx.terminal.dirty = true;
- }
- },
- CursorMoved { position: lpos, modifiers, .. } => {
- let (x, y) = lpos.to_physical(processor.ctx.size_info.dpr).into();
- let x: i32 = limit(x, 0, processor.ctx.size_info.width as i32);
- let y: i32 = limit(y, 0, processor.ctx.size_info.height as i32);
-
- *hide_mouse = false;
- processor.mouse_moved(x as usize, y as usize, modifiers);
- },
- MouseWheel { delta, phase, modifiers, .. } => {
- *hide_mouse = false;
- processor.on_mouse_wheel(delta, phase, modifiers);
- },
- Refresh => {
- processor.ctx.terminal.dirty = true;
- },
- Focused(is_focused) => {
- *window_is_focused = is_focused;
-
- if is_focused {
- processor.ctx.terminal.next_is_urgent = Some(false);
- processor.ctx.terminal.dirty = true;
- } else {
- processor.ctx.terminal.dirty = true;
- *hide_mouse = false;
- }
-
- processor.on_focus_change(is_focused);
- },
- DroppedFile(path) => {
- use crate::input::ActionContext;
- let path: String = path.to_string_lossy().into();
- processor.ctx.write_to_pty(path.into_bytes());
- },
- HiDpiFactorChanged(new_dpr) => {
- processor.ctx.size_info.dpr = new_dpr;
- processor.ctx.terminal.dirty = true;
- },
- CursorLeft { .. } => processor.ctx.terminal.reset_url_highlight(),
- _ => (),
- }
- },
- Event::Awakened => {
- processor.ctx.terminal.dirty = true;
- },
- }
- }
-
- /// Process events. When `wait_for_event` is set, this method is guaranteed
- /// to process at least one event.
- pub fn process_events<'a>(
- &mut self,
- term: &'a FairMutex<Term>,
- window: &mut Window,
- ) -> MutexGuard<'a, Term> {
- // Terminal is lazily initialized the first time an event is returned
- // from the blocking WaitEventsIterator. Otherwise, the pty reader would
- // be blocked the entire time we wait for input!
- let mut terminal;
-
- self.pending_events.clear();
-
- {
- // Ditto on lazy initialization for context and processor.
- let context;
- let mut processor: input::Processor<'_, ActionContext<'_, N>>;
-
- let print_events = self.print_events;
-
- let resize_tx = &self.resize_tx;
-
- if self.wait_for_event {
- // A Vec is used here since wait_events can potentially yield
- // multiple events before the interrupt is handled. For example,
- // Resize and Moved events.
- let pending_events = &mut self.pending_events;
- window.wait_events(|e| {
- pending_events.push(e);
- glutin::ControlFlow::Break
- });
- }
-
- terminal = term.lock();
-
- context = ActionContext {
- terminal: &mut terminal,
- notifier: &mut self.notifier,
- mouse: &mut self.mouse,
- size_info: &mut self.size_info,
- received_count: &mut self.received_count,
- suppress_chars: &mut self.suppress_chars,
- modifiers: &mut self.modifiers,
- window_changes: &mut self.window_changes,
- };
-
- processor = input::Processor {
- ctx: context,
- scrolling_config: &self.scrolling_config,
- mouse_config: &self.mouse_config,
- key_bindings: &self.key_bindings[..],
- mouse_bindings: &self.mouse_bindings[..],
- save_to_clipboard: self.save_to_clipboard,
- alt_send_esc: self.alt_send_esc,
- };
-
- let mut window_is_focused = window.is_focused;
-
- // Scope needed to that hide_mouse isn't borrowed after the scope
- // ends.
- {
- let hide_mouse = &mut self.hide_mouse;
- let mut process = |event| {
- if print_events {
- info!("glutin event: {:?}", event);
- }
- Processor::handle_event(
- &mut processor,
- event,
- resize_tx,
- hide_mouse,
- &mut window_is_focused,
- );
- };
-
- for event in self.pending_events.drain(..) {
- process(event);
- }
-
- window.poll_events(process);
- }
-
- if self.hide_mouse_when_typing {
- window.set_mouse_visible(!self.hide_mouse);
- }
-
- window.is_focused = window_is_focused;
- }
-
- if self.window_changes.hide {
- window.hide();
- }
-
- #[cfg(target_os = "macos")]
- {
- if self.window_changes.toggle_simple_fullscreen && !self.is_fullscreen {
- window.set_simple_fullscreen(!self.is_simple_fullscreen);
- self.is_simple_fullscreen = !self.is_simple_fullscreen;
- }
- }
-
- if self.window_changes.toggle_fullscreen && !self.is_simple_fullscreen {
- window.set_fullscreen(!self.is_fullscreen);
- self.is_fullscreen = !self.is_fullscreen;
- }
-
- self.window_changes.clear();
- self.wait_for_event = !terminal.dirty;
-
- terminal
- }
-
- pub fn update_config(&mut self, config: &Config) {
- self.key_bindings = config.key_bindings.to_vec();
- self.mouse_bindings = config.mouse_bindings.to_vec();
- self.mouse_config = config.mouse.to_owned();
- self.save_to_clipboard = config.selection.save_to_clipboard;
- self.alt_send_esc = config.alt_send_esc();
- }
+/// Event Loop for notifying the renderer about terminal events
+pub trait EventListener {
+ fn send_event(&self, event: Event);
}
diff --git a/alacritty_terminal/src/event_loop.rs b/alacritty_terminal/src/event_loop.rs
index 275d752c..ed78d79e 100644
--- a/alacritty_terminal/src/event_loop.rs
+++ b/alacritty_terminal/src/event_loop.rs
@@ -6,20 +6,22 @@ use std::io::{self, ErrorKind, Read, Write};
use std::marker::Send;
use std::sync::Arc;
-use mio::{self, Events, PollOpt, Ready};
-use mio_extras::channel::{self, Receiver, Sender};
-
+use log::error;
#[cfg(not(windows))]
use mio::unix::UnixReady;
+use mio::{self, Events, PollOpt, Ready};
+use mio_extras::channel::{self, Receiver, Sender};
use crate::ansi;
-use crate::display;
-use crate::event;
+use crate::event::{self, Event, EventListener};
use crate::sync::FairMutex;
use crate::term::Term;
use crate::tty;
use crate::util::thread;
+/// Max bytes to read from the PTY
+const MAX_READ: usize = 0x10_000;
+
/// Messages that may be sent to the `EventLoop`
#[derive(Debug)]
pub enum Msg {
@@ -34,13 +36,13 @@ pub enum Msg {
///
/// Handles all the pty I/O and runs the pty parser which updates terminal
/// state.
-pub struct EventLoop<T: tty::EventedPty> {
+pub struct EventLoop<T: tty::EventedPty, U: EventListener> {
poll: mio::Poll,
pty: T,
rx: Receiver<Msg>,
tx: Sender<Msg>,
- terminal: Arc<FairMutex<Term>>,
- display: display::Notifier,
+ terminal: Arc<FairMutex<Term<U>>>,
+ event_proxy: U,
ref_test: bool,
}
@@ -50,26 +52,6 @@ struct Writing {
written: usize,
}
-/// Indicates the result of draining the mio channel
-#[derive(Debug)]
-enum DrainResult {
- /// At least one new item was received
- ReceivedItem,
- /// Nothing was available to receive
- Empty,
- /// A shutdown message was received
- Shutdown,
-}
-
-impl DrainResult {
- pub fn is_shutdown(&self) -> bool {
- match *self {
- DrainResult::Shutdown => true,
- _ => false,
- }
- }
-}
-
/// All of the mutable state needed to run the event loop
///
/// Contains list of items to write, current write state, etc. Anything that
@@ -155,17 +137,18 @@ impl Writing {
}
}
-impl<T> EventLoop<T>
+impl<T, U> EventLoop<T, U>
where
T: tty::EventedPty + Send + 'static,
+ U: EventListener + Send + 'static,
{
/// Create a new event loop
pub fn new(
- terminal: Arc<FairMutex<Term>>,
- display: display::Notifier,
+ terminal: Arc<FairMutex<Term<U>>>,
+ event_proxy: U,
pty: T,
ref_test: bool,
- ) -> EventLoop<T> {
+ ) -> EventLoop<T, U> {
let (tx, rx) = channel::channel();
EventLoop {
poll: mio::Poll::new().expect("create mio Poll"),
@@ -173,7 +156,7 @@ where
tx,
rx,
terminal,
- display,
+ event_proxy,
ref_test,
}
}
@@ -184,33 +167,22 @@ where
// Drain the channel
//
- // Returns a `DrainResult` indicating the result of receiving from the channel
- //
- fn drain_recv_channel(&self, state: &mut State) -> DrainResult {
- let mut received_item = false;
+ // Returns `false` when a shutdown message was received.
+ fn drain_recv_channel(&self, state: &mut State) -> bool {
while let Ok(msg) = self.rx.try_recv() {
- received_item = true;
match msg {
- Msg::Input(input) => {
- state.write_list.push_back(input);
- },
- Msg::Shutdown => {
- return DrainResult::Shutdown;
- },
+ Msg::Input(input) => state.write_list.push_back(input),
+ Msg::Shutdown => return false,
}
}
- if received_item {
- DrainResult::ReceivedItem
- } else {
- DrainResult::Empty
- }
+ true
}
// Returns a `bool` indicating whether or not the event loop should continue running
#[inline]
fn channel_event(&mut self, token: mio::Token, state: &mut State) -> bool {
- if self.drain_recv_channel(state).is_shutdown() {
+ if !self.drain_recv_channel(state) {
return false;
}
@@ -231,13 +203,9 @@ where
where
X: Write,
{
- const MAX_READ: usize = 0x1_0000;
let mut processed = 0;
let mut terminal = None;
- // Flag to keep track if wakeup has already been sent
- let mut send_wakeup = false;
-
loop {
match self.pty.reader().read(&mut buf[..]) {
Ok(0) => break,
@@ -255,14 +223,10 @@ where
// Get reference to terminal. Lock is acquired on initial
// iteration and held until there's no bytes left to parse
// or we've reached MAX_READ.
- let terminal = if terminal.is_none() {
+ if terminal.is_none() {
terminal = Some(self.terminal.lock());
- let terminal = terminal.as_mut().unwrap();
- send_wakeup = !terminal.dirty;
- terminal
- } else {
- terminal.as_mut().unwrap()
- };
+ }
+ let terminal = terminal.as_mut().unwrap();
// Run the parser
for byte in &buf[..got] {
@@ -283,13 +247,8 @@ where
}
}
- // Only request a draw if one hasn't already been requested.
- if let Some(mut terminal) = terminal {
- if send_wakeup {
- self.display.notify();
- terminal.dirty = true;
- }
- }
+ // Queue terminal redraw
+ self.event_proxy.send_event(Event::Wakeup);
Ok(())
}
@@ -326,10 +285,10 @@ where
Ok(())
}
- pub fn spawn(mut self, state: Option<State>) -> thread::JoinHandle<(Self, State)> {
+ pub fn spawn(mut self) -> thread::JoinHandle<(Self, State)> {
thread::spawn_named("pty reader", move || {
- let mut state = state.unwrap_or_else(Default::default);
- let mut buf = [0u8; 0x1000];
+ let mut state = State::default();
+ let mut buf = [0u8; MAX_READ];
let mut tokens = (0..).map(Into::into);
@@ -369,7 +328,7 @@ where
token if token == self.pty.child_event_token() => {
if let Some(tty::ChildEvent::Exited) = self.pty.next_child_event() {
self.terminal.lock().exit();
- self.display.notify();
+ self.event_proxy.send_event(Event::Wakeup);
break 'event_loop;
}
},
diff --git a/alacritty_terminal/src/grid/mod.rs b/alacritty_terminal/src/grid/mod.rs
index 01c17152..5f5b84fe 100644
--- a/alacritty_terminal/src/grid/mod.rs
+++ b/alacritty_terminal/src/grid/mod.rs
@@ -17,6 +17,8 @@
use std::cmp::{max, min, Ordering};
use std::ops::{Deref, Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo};
+use serde::{Deserialize, Serialize};
+
use crate::index::{self, Column, IndexRange, Line, Point};
use crate::selection::Selection;
diff --git a/alacritty_terminal/src/grid/row.rs b/alacritty_terminal/src/grid/row.rs
index f82e7eaa..058583f2 100644
--- a/alacritty_terminal/src/grid/row.rs
+++ b/alacritty_terminal/src/grid/row.rs
@@ -19,6 +19,8 @@ use std::ops::{Index, IndexMut};
use std::ops::{Range, RangeFrom, RangeFull, RangeTo, RangeToInclusive};
use std::slice;
+use serde::{Deserialize, Serialize};
+
use crate::grid::GridCell;
use crate::index::Column;
diff --git a/alacritty_terminal/src/grid/storage.rs b/alacritty_terminal/src/grid/storage.rs
index e04d01cd..2004cece 100644
--- a/alacritty_terminal/src/grid/storage.rs
+++ b/alacritty_terminal/src/grid/storage.rs
@@ -14,6 +14,7 @@
use std::ops::{Index, IndexMut};
use std::vec::Drain;
+use serde::{Deserialize, Serialize};
use static_assertions::assert_eq_size;
use super::Row;
diff --git a/alacritty_terminal/src/index.rs b/alacritty_terminal/src/index.rs
index 7d1a8e91..0a100e18 100644
--- a/alacritty_terminal/src/index.rs
+++ b/alacritty_terminal/src/index.rs
@@ -19,6 +19,8 @@ use std::cmp::{Ord, Ordering};
use std::fmt;
use std::ops::{self, Add, AddAssign, Deref, Range, RangeInclusive, Sub, SubAssign};
+use serde::{Deserialize, Serialize};
+
use crate::term::RenderableCell;
/// The side of a cell
@@ -77,8 +79,8 @@ impl From<Point> for Point<usize> {
}
}
-impl From<&RenderableCell> for Point<Line> {
- fn from(cell: &RenderableCell) -> Self {
+impl From<RenderableCell> for Point<Line> {
+ fn from(cell: RenderableCell) -> Self {
Point::new(cell.line, cell.column)
}
}
diff --git a/alacritty_terminal/src/input.rs b/alacritty_terminal/src/input.rs
deleted file mode 100644
index e268bf62..00000000
--- a/alacritty_terminal/src/input.rs
+++ /dev/null
@@ -1,1538 +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 crate::url::Url;
-use std::borrow::Cow;
-use std::mem;
-use std::time::Instant;
-
-use glutin::{
- ElementState, KeyboardInput, ModifiersState, MouseButton, MouseCursor, MouseScrollDelta,
- TouchPhase, VirtualKeyCode,
-};
-
-use crate::ansi::{ClearMode, Handler};
-use crate::clipboard::ClipboardType;
-use crate::config::{self, Key};
-use crate::event::{ClickState, Mouse};
-use crate::grid::Scroll;
-use crate::index::{Column, Line, Point, Side};
-use crate::message_bar::{self, Message};
-use crate::term::mode::TermMode;
-use crate::term::{SizeInfo, Term};
-use crate::util::start_daemon;
-
-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, A: 'a> {
- pub key_bindings: &'a [KeyBinding],
- pub mouse_bindings: &'a [MouseBinding],
- pub mouse_config: &'a config::Mouse,
- pub scrolling_config: &'a config::Scrolling,
- pub ctx: A,
- pub save_to_clipboard: bool,
- pub alt_send_esc: bool,
-}
-
-pub trait ActionContext {
- 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 Modifiers;
- fn scroll(&mut self, scroll: Scroll);
- fn hide_window(&mut self);
- fn terminal(&self) -> &Term;
- fn terminal_mut(&mut self) -> &mut Term;
- fn spawn_new_instance(&mut self);
- fn toggle_fullscreen(&mut self);
- #[cfg(target_os = "macos")]
- fn toggle_simple_fullscreen(&mut self);
-}
-
-#[derive(Debug, Default, Copy, Clone)]
-pub struct Modifiers {
- mods: ModifiersState,
- lshift: bool,
- rshift: bool,
-}
-
-impl Modifiers {
- pub fn update(&mut self, input: KeyboardInput) {
- match input.virtual_keycode {
- Some(VirtualKeyCode::LShift) => self.lshift = input.state == ElementState::Pressed,
- Some(VirtualKeyCode::RShift) => self.rshift = input.state == ElementState::Pressed,
- _ => (),
- }
-
- self.mods = input.modifiers;
- }
-
- pub fn shift(self) -> bool {
- self.lshift || self.rshift
- }
-
- pub fn ctrl(self) -> bool {
- self.mods.ctrl
- }
-
- pub fn logo(self) -> bool {
- self.mods.logo
- }
-
- pub fn alt(self) -> bool {
- self.mods.alt
- }
-}
-
-impl From<&mut Modifiers> for ModifiersState {
- fn from(mods: &mut Modifiers) -> ModifiersState {
- ModifiersState { shift: mods.shift(), ..mods.mods }
- }
-}
-
-/// 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::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]
- fn is_triggered_by(
- &self,
- mode: TermMode,
- mods: ModifiersState,
- input: &T,
- relaxed: bool,
- ) -> 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_match(mods, relaxed)
- }
-
- #[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))
- }
-}
-
-impl<T> Binding<T> {
- /// Execute the action associate with this binding
- #[inline]
- fn execute<A: ActionContext>(&self, ctx: &mut A, mouse_mode: bool) {
- self.action.execute(ctx, mouse_mode)
- }
-
- /// Check that two mods descriptions for equivalence
- #[inline]
- fn mods_match(&self, mods: ModifiersState, relaxed: bool) -> bool {
- if relaxed {
- self.mods.relaxed_eq(mods)
- } else {
- self.mods == mods
- }
- }
-}
-
-#[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,
-
- /// 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 Action {
- #[inline]
- fn execute<A: ActionContext>(&self, ctx: &mut A, mouse_mode: bool) {
- match *self {
- Action::Esc(ref s) => {
- 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);
- self.paste(ctx, &text);
- },
- Action::PasteSelection => {
- // Only paste if mouse events are not captured by an application
- if !mouse_mode {
- let text = ctx.terminal_mut().clipboard().load(ClipboardType::Selection);
- self.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.toggle_fullscreen();
- },
- #[cfg(target_os = "macos")]
- Action::ToggleSimpleFullscreen => {
- ctx.toggle_simple_fullscreen();
- },
- Action::Hide => {
- ctx.hide_window();
- },
- Action::Quit => {
- ctx.terminal_mut().exit();
- },
- Action::IncreaseFontSize => {
- ctx.terminal_mut().change_font_size(FONT_SIZE_STEP);
- },
- Action::DecreaseFontSize => {
- ctx.terminal_mut().change_font_size(-FONT_SIZE_STEP);
- },
- Action::ResetFontSize => {
- ctx.terminal_mut().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.terminal_mut().message_buffer_mut().pop();
- },
- Action::SpawnNewInstance => {
- ctx.spawn_new_instance();
- },
- Action::ReceiveChar | Action::None => (),
- }
- }
-
- fn paste<A: ActionContext>(&self, 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());
- }
- }
-}
-
-trait RelaxedEq<T: ?Sized = Self> {
- fn relaxed_eq(&self, other: T) -> bool;
-}
-
-impl RelaxedEq for ModifiersState {
- // Make sure that modifiers in the config are always present,
- // but ignore surplus modifiers.
- fn relaxed_eq(&self, other: Self) -> bool {
- (!self.logo || other.logo)
- && (!self.alt || other.alt)
- && (!self.ctrl || other.ctrl)
- && (!self.shift || other.shift)
- }
-}
-
-impl From<&'static str> for Action {
- fn from(s: &'static str) -> Action {
- Action::Esc(s.into())
- }
-}
-
-#[derive(Debug, Copy, Clone, PartialEq)]
-pub enum MouseState {
- Url(Url),
- MessageBar,
- MessageBarButton,
- Mouse,
- Text,
-}
-
-impl<'a, A: ActionContext + 'a> Processor<'a, A> {
- fn mouse_state(&mut self, point: Point, mods: ModifiersState) -> MouseState {
- let mouse_mode =
- TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG | TermMode::MOUSE_REPORT_CLICK;
-
- // Check message bar before URL to ignore URLs in the message bar
- if let Some(message) = self.message_at_point(Some(point)) {
- if self.message_close_at_point(point, message) {
- return MouseState::MessageBarButton;
- } else {
- return MouseState::MessageBar;
- }
- }
-
- // Check for URL at point with required modifiers held
- if self.mouse_config.url.mods().relaxed_eq(mods)
- && (!self.ctx.terminal().mode().intersects(mouse_mode) || mods.shift)
- && self.mouse_config.url.launcher.is_some()
- && self.ctx.selection_is_empty()
- && self.ctx.mouse().left_button_state != ElementState::Pressed
- {
- let buffer_point = self.ctx.terminal().visible_to_buffer(point);
- if let Some(url) =
- self.ctx.terminal().urls().drain(..).find(|url| url.contains(buffer_point))
- {
- return MouseState::Url(url);
- }
- }
-
- if self.ctx.terminal().mode().intersects(mouse_mode) && !self.ctx.modifiers().shift() {
- MouseState::Mouse
- } else {
- MouseState::Text
- }
- }
-
- #[inline]
- pub fn mouse_moved(&mut self, x: usize, y: usize, modifiers: ModifiersState) {
- self.ctx.mouse_mut().x = x;
- self.ctx.mouse_mut().y = y;
-
- let size_info = self.ctx.size_info();
- let point = size_info.pixels_to_coords(x, y);
-
- let cell_side = self.get_mouse_side();
- let prev_side = mem::replace(&mut self.ctx.mouse_mut().cell_side, cell_side);
- let prev_line = mem::replace(&mut self.ctx.mouse_mut().line, point.line);
- let prev_col = mem::replace(&mut self.ctx.mouse_mut().column, point.col);
-
- let motion_mode = TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG;
- let report_mode = TermMode::MOUSE_REPORT_CLICK | motion_mode;
-
- let cell_changed =
- prev_line != self.ctx.mouse().line || prev_col != self.ctx.mouse().column;
-
- // If the mouse hasn't changed cells, do nothing
- if !cell_changed && prev_side == cell_side {
- return;
- }
-
- // Don't launch URLs if mouse has moved
- self.ctx.mouse_mut().block_url_launcher = true;
-
- let mouse_state = self.mouse_state(point, modifiers);
- self.update_mouse_cursor(mouse_state);
- match mouse_state {
- MouseState::Url(url) => {
- let url_bounds = url.linear_bounds(self.ctx.terminal());
- self.ctx.terminal_mut().set_url_highlight(url_bounds);
- },
- MouseState::MessageBar | MouseState::MessageBarButton => {
- self.ctx.terminal_mut().reset_url_highlight();
- return;
- },
- _ => self.ctx.terminal_mut().reset_url_highlight(),
- }
-
- if self.ctx.mouse().left_button_state == ElementState::Pressed
- && (modifiers.shift || !self.ctx.terminal().mode().intersects(report_mode))
- {
- self.ctx.update_selection(Point { line: point.line, col: point.col }, cell_side);
- } else if self.ctx.terminal().mode().intersects(motion_mode)
- && size_info.contains_point(x, y, false)
- && cell_changed
- {
- if self.ctx.mouse().left_button_state == ElementState::Pressed {
- self.mouse_report(32, ElementState::Pressed, modifiers);
- } else if self.ctx.mouse().middle_button_state == ElementState::Pressed {
- self.mouse_report(33, ElementState::Pressed, modifiers);
- } else if self.ctx.mouse().right_button_state == ElementState::Pressed {
- self.mouse_report(34, ElementState::Pressed, modifiers);
- } else if self.ctx.terminal().mode().contains(TermMode::MOUSE_MOTION) {
- self.mouse_report(35, ElementState::Pressed, modifiers);
- }
- }
- }
-
- 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
- }
- }
-
- pub fn normal_mouse_report(&mut self, button: u8) {
- let (line, column) = (self.ctx.mouse().line, self.ctx.mouse().column);
-
- if line < Line(223) && column < Column(223) {
- let msg = vec![
- b'\x1b',
- b'[',
- b'M',
- 32 + button,
- 32 + 1 + column.0 as u8,
- 32 + 1 + line.0 as u8,
- ];
-
- self.ctx.write_to_pty(msg);
- }
- }
-
- pub 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());
- }
-
- pub fn mouse_report(&mut self, button: u8, state: ElementState, modifiers: ModifiersState) {
- // Calculate modifiers value
- let mut mods = 0;
- 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);
- }
- }
-
- pub fn on_mouse_double_click(&mut self, button: MouseButton, point: Option<Point>) {
- if let (Some(point), true) = (point, button == MouseButton::Left) {
- self.ctx.semantic_selection(point);
- }
- }
-
- pub fn on_mouse_triple_click(&mut self, button: MouseButton, point: Option<Point>) {
- if let (Some(point), true) = (point, button == MouseButton::Left) {
- self.ctx.line_selection(point);
- }
- }
-
- pub fn on_mouse_press(
- &mut self,
- button: MouseButton,
- modifiers: ModifiersState,
- point: Option<Point>,
- ) {
- 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;
-
- self.ctx.mouse_mut().click_state = match self.ctx.mouse().click_state {
- ClickState::Click
- if !button_changed && elapsed < self.mouse_config.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.mouse_config.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 let Some(point) = point {
- if modifiers.ctrl {
- self.ctx.block_selection(point, side);
- } else {
- self.ctx.simple_selection(point, side);
- }
- }
-
- let report_modes =
- TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION;
- if !modifiers.shift && self.ctx.terminal().mode().intersects(report_modes) {
- 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, modifiers);
- return;
- }
-
- ClickState::Click
- },
- };
- }
-
- pub fn on_mouse_release(
- &mut self,
- button: MouseButton,
- modifiers: ModifiersState,
- point: Option<Point>,
- ) {
- let report_modes =
- TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION;
- if !modifiers.shift && self.ctx.terminal().mode().intersects(report_modes) {
- 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, modifiers);
- return;
- } else if let (Some(point), true) = (point, button == MouseButton::Left) {
- let mouse_state = self.mouse_state(point, modifiers);
- self.update_mouse_cursor(mouse_state);
- if let MouseState::Url(url) = mouse_state {
- let url_bounds = url.linear_bounds(self.ctx.terminal());
- self.ctx.terminal_mut().set_url_highlight(url_bounds);
- 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.mouse_config.url.launcher {
- let mut args = launcher.args().to_vec();
- args.push(self.ctx.terminal().url_to_string(url));
-
- 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 on_mouse_wheel(
- &mut self,
- delta: MouseScrollDelta,
- phase: TouchPhase,
- modifiers: ModifiersState,
- ) {
- match delta {
- MouseScrollDelta::LineDelta(_columns, lines) => {
- let new_scroll_px = lines * self.ctx.size_info().cell_height;
- self.scroll_terminal(modifiers, 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(modifiers, lpos.y as i32);
- },
- _ => (),
- }
- },
- }
- }
-
- fn scroll_terminal(&mut self, modifiers: ModifiersState, new_scroll_px: i32) {
- let mouse_modes =
- TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION;
- let height = self.ctx.size_info().cell_height as i32;
-
- // Make sure the new and deprecated setting are both allowed
- let faux_multiplier = self.scrolling_config.faux_multiplier() as usize;
-
- if self.ctx.terminal().mode().intersects(mouse_modes) {
- self.ctx.mouse_mut().scroll_px += new_scroll_px;
-
- 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, modifiers);
- }
- } else if self.ctx.terminal().mode().contains(TermMode::ALT_SCREEN)
- && faux_multiplier > 0
- && !modifiers.shift
- {
- self.ctx.mouse_mut().scroll_px += new_scroll_px * faux_multiplier as i32;
-
- 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.scrolling_config.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,
- modifiers: ModifiersState,
- ) {
- 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,
- _ => (),
- }
-
- let point = self.ctx.mouse_coords();
-
- // Skip normal mouse events if the message bar has been clicked
- if let Some(message) = self.message_at_point(point) {
- // Message should never be `Some` if point is `None`
- debug_assert!(point.is_some());
- self.on_message_bar_click(state, point.unwrap(), message, modifiers);
- } else {
- match state {
- ElementState::Pressed => {
- self.process_mouse_bindings(modifiers, button);
- self.on_mouse_press(button, modifiers, point);
- },
- ElementState::Released => self.on_mouse_release(button, modifiers, point),
- }
- }
-
- self.ctx.mouse_mut().last_button = button;
- }
-
- /// Process key input.
- pub fn process_key(&mut self, input: KeyboardInput) {
- self.ctx.modifiers().update(input);
-
- // Update mouse cursor for temporarily disabling mouse mode
- if input.virtual_keycode == Some(VirtualKeyCode::LShift)
- || input.virtual_keycode == Some(VirtualKeyCode::RShift)
- {
- if let Some(point) = self.ctx.mouse_coords() {
- let mods = self.ctx.modifiers().into();
- let mouse_state = self.mouse_state(point, mods);
- self.update_mouse_cursor(mouse_state);
- }
- }
-
- match input.state {
- ElementState::Pressed => {
- *self.ctx.received_count() = 0;
- self.process_key_bindings(input);
- },
- ElementState::Released => *self.ctx.suppress_chars() = false,
- }
- }
-
- /// 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.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;
- }
-
- /// 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 mode = *self.ctx.terminal().mode();
-
- *self.ctx.suppress_chars() = self
- .key_bindings
- .iter()
- .filter(|binding| {
- let key = match (binding.trigger, input.virtual_keycode) {
- (Key::Scancode(_), _) => Key::Scancode(input.scancode),
- (_, Some(key)) => Key::from_glutin_input(key),
- _ => return false,
- };
-
- binding.is_triggered_by(mode, input.modifiers, &key, false)
- })
- .fold(None, |suppress_chars, binding| {
- // Binding was triggered; run the action
- binding.execute(&mut self.ctx, false);
-
- // Don't suppress when there has been a `ReceiveChar` action
- Some(suppress_chars.unwrap_or(true) && binding.action != Action::ReceiveChar)
- })
- // Don't suppress char if no bindings were triggered
- .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, mods: ModifiersState, button: MouseButton) {
- for binding in self.mouse_bindings {
- if binding.is_triggered_by(*self.ctx.terminal().mode(), mods, &button, true) {
- // binding was triggered; run the action
- let mouse_mode = !mods.shift
- && self.ctx.terminal().mode().intersects(
- TermMode::MOUSE_REPORT_CLICK
- | TermMode::MOUSE_DRAG
- | TermMode::MOUSE_MOTION,
- );
- binding.execute(&mut self.ctx, mouse_mode);
- }
- }
- }
-
- /// Return the message bar's message if there is some at the specified point
- fn message_at_point(&mut self, point: Option<Point>) -> Option<Message> {
- if let (Some(point), Some(message)) =
- (point, self.ctx.terminal_mut().message_buffer_mut().message())
- {
- let size = self.ctx.size_info();
- if point.line.0 >= size.lines().saturating_sub(message.text(&size).len()) {
- return Some(message);
- }
- }
-
- None
- }
-
- /// Whether the point is over the message bar's close button
- fn message_close_at_point(&self, point: Point, message: Message) -> bool {
- let size = self.ctx.size_info();
- point.col + message_bar::CLOSE_BUTTON_TEXT.len() >= size.cols()
- && point.line == size.lines() - message.text(&size).len()
- }
-
- /// Handle clicks on the message bar.
- fn on_message_bar_click(
- &mut self,
- button_state: ElementState,
- point: Point,
- message: Message,
- mods: ModifiersState,
- ) {
- match button_state {
- ElementState::Released => self.copy_selection(),
- ElementState::Pressed => {
- if self.message_close_at_point(point, message) {
- let mouse_state = self.mouse_state(point, mods);
- self.update_mouse_cursor(mouse_state);
- self.ctx.terminal_mut().message_buffer_mut().pop();
- }
-
- self.ctx.clear_selection();
- },
- }
- }
-
- /// Copy text selection.
- fn copy_selection(&mut self) {
- if self.save_to_clipboard {
- self.ctx.copy_selection(ClipboardType::Clipboard);
- }
- self.ctx.copy_selection(ClipboardType::Selection);
- }
-
- #[inline]
- fn update_mouse_cursor(&mut self, mouse_state: MouseState) {
- let mouse_cursor = match mouse_state {
- MouseState::Url(_) | MouseState::MessageBarButton => MouseCursor::Hand,
- MouseState::Text => MouseCursor::Text,
- _ => MouseCursor::Default,
- };
-
- self.ctx.terminal_mut().set_mouse_cursor(mouse_cursor);
- }
-}
-
-#[cfg(test)]
-mod tests {
- use std::borrow::Cow;
- use std::time::Duration;
-
- use glutin::{ElementState, Event, ModifiersState, MouseButton, VirtualKeyCode, WindowEvent};
-
- use crate::clipboard::{Clipboard, ClipboardType};
- use crate::config::{self, ClickHandler, Config};
- use crate::event::{ClickState, Mouse, WindowChanges};
- use crate::grid::Scroll;
- use crate::index::{Point, Side};
- use crate::message_bar::MessageBuffer;
- use crate::selection::Selection;
- use crate::term::{SizeInfo, Term, TermMode};
-
- use super::{Action, Binding, Modifiers, Processor};
-
- const KEY: VirtualKeyCode = VirtualKeyCode::Key0;
-
- 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 { alt: true, logo: true, ctrl: true, shift: true };
- let mut subset_mods = MockBinding::default();
- subset_mods.mods = ModifiersState { alt: true, logo: false, ctrl: false, shift: false };
-
- 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, true));
- assert!(!binding.is_triggered_by(mode, mods, &32, true));
- }
-
- #[test]
- fn binding_trigger_mods() {
- let mut binding = MockBinding::default();
- binding.mods = ModifiersState { alt: true, logo: true, ctrl: false, shift: false };
-
- let superset_mods = ModifiersState { alt: true, logo: true, ctrl: true, shift: true };
- let subset_mods = ModifiersState { alt: false, logo: false, ctrl: false, shift: false };
-
- let t = binding.trigger;
- let mode = binding.mode;
-
- assert!(binding.is_triggered_by(mode, binding.mods, &t, true));
- assert!(binding.is_triggered_by(mode, binding.mods, &t, false));
-
- assert!(binding.is_triggered_by(mode, superset_mods, &t, true));
- assert!(!binding.is_triggered_by(mode, superset_mods, &t, false));
-
- assert!(!binding.is_triggered_by(mode, subset_mods, &t, true));
- assert!(!binding.is_triggered_by(mode, subset_mods, &t, false));
- }
-
- #[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, true));
- assert!(binding.is_triggered_by(TermMode::ALT_SCREEN, mods, &t, true));
- assert!(binding.is_triggered_by(TermMode::ALT_SCREEN | TermMode::INSERT, mods, &t, true));
- }
-
- #[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, true));
- assert!(!binding.is_triggered_by(TermMode::ALT_SCREEN, mods, &t, true));
- assert!(!binding.is_triggered_by(TermMode::ALT_SCREEN | TermMode::INSERT, mods, &t, true));
- }
-
- #[derive(PartialEq)]
- enum MultiClick {
- DoubleClick,
- TripleClick,
- None,
- }
-
- struct ActionContext<'a> {
- pub terminal: &'a mut Term,
- pub selection: &'a mut Option<Selection>,
- pub size_info: &'a SizeInfo,
- pub mouse: &'a mut Mouse,
- pub last_action: MultiClick,
- pub received_count: usize,
- pub suppress_chars: bool,
- pub modifiers: Modifiers,
- pub window_changes: &'a mut WindowChanges,
- }
-
- impl<'a> super::ActionContext for ActionContext<'a> {
- 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 hide_window(&mut self) {}
-
- fn spawn_new_instance(&mut self) {}
-
- fn toggle_fullscreen(&mut self) {}
-
- #[cfg(target_os = "macos")]
- fn toggle_simple_fullscreen(&mut self) {}
-
- fn terminal(&self) -> &Term {
- &self.terminal
- }
-
- fn terminal_mut(&mut self) -> &mut Term {
- &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> {
- self.terminal.pixels_to_coords(self.mouse.x as usize, self.mouse.y as usize)
- }
-
- #[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 Modifiers {
- &mut self.modifiers
- }
- }
-
- 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 config = Config::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(&config, size, MessageBuffer::new(), Clipboard::new_nop());
-
- let mut mouse = Mouse::default();
- mouse.click_state = $initial_state;
- mouse.last_button = $initial_button;
-
- let mut selection = None;
-
- 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(),
- window_changes: &mut WindowChanges::default(),
- };
-
- let mut processor = Processor {
- ctx: context,
- mouse_config: &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(),
- },
- scrolling_config: &config::Scrolling::default(),
- key_bindings: &config.key_bindings[..],
- mouse_bindings: &config.mouse_bindings[..],
- save_to_clipboard: config.selection.save_to_clipboard,
- alt_send_esc: config.alt_send_esc(),
- };
-
- if let Event::WindowEvent { event: WindowEvent::MouseInput { state, button, modifiers, .. }, .. } = $input {
- processor.mouse_input(state, button, modifiers);
- };
-
- 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, false));
- } else {
- assert!(!$binding.is_triggered_by($mode, $mods, &KEY, false));
- }
- }
- }
- }
-
- 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: true, ctrl: false, alt: false, logo: false }, action: Action::from("\x1b[1;2D"), mode: TermMode::NONE, notmode: TermMode::NONE },
- triggers: true,
- mode: TermMode::NONE,
- mods: ModifiersState { shift: true, ctrl: false, alt: false, logo: false }
- }
-
- test_process_binding! {
- name: process_binding_nomode_nomod_require_shift,
- binding: Binding { trigger: KEY, mods: ModifiersState { shift: true, ctrl: false, alt: false, logo: false }, action: Action::from("\x1b[1;2D"), mode: TermMode::NONE, notmode: TermMode::NONE },
- triggers: false,
- mode: TermMode::NONE,
- mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }
- }
-
- test_process_binding! {
- name: process_binding_nomode_controlmod,
- binding: Binding { trigger: KEY, mods: ModifiersState { ctrl: true, shift: false, alt: false, logo: false }, action: Action::from("\x1b[1;5D"), mode: TermMode::NONE, notmode: TermMode::NONE },
- triggers: true,
- mode: TermMode::NONE,
- mods: ModifiersState { ctrl: true, shift: false, alt: false, logo: false }
- }
-
- test_process_binding! {
- name: process_binding_nomode_nomod_require_not_appcursor,
- binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1b[D"), mode: TermMode::NONE, notmode: TermMode::APP_CURSOR },
- triggers: true,
- mode: TermMode::NONE,
- mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }
- }
-
- test_process_binding! {
- name: process_binding_appcursormode_nomod_require_appcursor,
- binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE },
- triggers: true,
- mode: TermMode::APP_CURSOR,
- mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }
- }
-
- test_process_binding! {
- name: process_binding_nomode_nomod_require_appcursor,
- binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE },
- triggers: false,
- mode: TermMode::NONE,
- mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }
- }
-
- test_process_binding! {
- name: process_binding_appcursormode_appkeypadmode_nomod_require_appcursor,
- binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE },
- triggers: true,
- mode: TermMode::APP_CURSOR | TermMode::APP_KEYPAD,
- mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }
- }
-
- test_process_binding! {
- name: process_binding_fail_with_extra_mods,
- binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: true }, action: Action::from("arst"), mode: TermMode::NONE, notmode: TermMode::NONE },
- triggers: false,
- mode: TermMode::NONE,
- mods: ModifiersState { shift: false, ctrl: false, alt: true, logo: true }
- }
-}
diff --git a/alacritty_terminal/src/lib.rs b/alacritty_terminal/src/lib.rs
index 0bada535..dc26046d 100644
--- a/alacritty_terminal/src/lib.rs
+++ b/alacritty_terminal/src/lib.rs
@@ -17,27 +17,18 @@
#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
#![cfg_attr(all(test, feature = "bench"), feature(test))]
-#[macro_use]
-extern crate log;
-#[macro_use]
-extern crate serde_derive;
-
#[cfg(target_os = "macos")]
#[macro_use]
extern crate objc;
-#[macro_use]
-pub mod macros;
pub mod ansi;
pub mod clipboard;
pub mod config;
mod cursor;
-pub mod display;
pub mod event;
pub mod event_loop;
pub mod grid;
pub mod index;
-pub mod input;
pub mod locale;
pub mod message_bar;
pub mod meter;
@@ -47,9 +38,8 @@ pub mod selection;
pub mod sync;
pub mod term;
pub mod tty;
-mod url;
+pub mod url;
pub mod util;
-pub mod window;
pub use crate::grid::Grid;
pub use crate::term::Term;
diff --git a/alacritty_terminal/src/macros.rs b/alacritty_terminal/src/macros.rs
deleted file mode 100644
index 519f8b6a..00000000
--- a/alacritty_terminal/src/macros.rs
+++ /dev/null
@@ -1,21 +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.
-
-#[macro_export]
-macro_rules! die {
- ($($arg:tt)*) => {{
- error!($($arg)*);
- ::std::process::exit(1);
- }}
-}
diff --git a/alacritty_terminal/src/message_bar.rs b/alacritty_terminal/src/message_bar.rs
index 8883dcb0..1382684d 100644
--- a/alacritty_terminal/src/message_bar.rs
+++ b/alacritty_terminal/src/message_bar.rs
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crossbeam_channel::{Receiver, Sender};
+use std::collections::VecDeque;
use crate::term::color::Rgb;
use crate::term::SizeInfo;
@@ -22,21 +22,21 @@ const CLOSE_BUTTON_PADDING: usize = 1;
const MIN_FREE_LINES: usize = 3;
const TRUNCATED_MESSAGE: &str = "[MESSAGE TRUNCATED]";
-/// Message for display in the MessageBuffer
+/// Message for display in the MessageBuffer.
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Message {
text: String,
color: Rgb,
- topic: Option<String>,
+ target: Option<String>,
}
impl Message {
- /// Create a new message
+ /// Create a new message.
pub fn new(text: String, color: Rgb) -> Message {
- Message { text, color, topic: None }
+ Message { text, color, target: None }
}
- /// Formatted message text lines
+ /// Formatted message text lines.
pub fn text(&self, size_info: &SizeInfo) -> Vec<String> {
let num_cols = size_info.cols().0;
let max_lines = size_info.lines().saturating_sub(MIN_FREE_LINES);
@@ -92,25 +92,25 @@ impl Message {
lines
}
- /// Message color
+ /// Message color.
#[inline]
pub fn color(&self) -> Rgb {
self.color
}
- /// Message topic
+ /// Message target.
#[inline]
- pub fn topic(&self) -> Option<&String> {
- self.topic.as_ref()
+ pub fn target(&self) -> Option<&String> {
+ self.target.as_ref()
}
- /// Update the message topic
+ /// Update the message target.
#[inline]
- pub fn set_topic(&mut self, topic: String) {
- self.topic = Some(topic);
+ pub fn set_target(&mut self, target: String) {
+ self.target = Some(target);
}
- /// Right-pad text to fit a specific number of columns
+ /// Right-pad text to fit a specific number of columns.
#[inline]
fn pad_text(mut text: String, num_cols: usize) -> String {
let padding_len = num_cols.saturating_sub(text.len());
@@ -119,82 +119,56 @@ impl Message {
}
}
-/// Storage for message bar
-#[derive(Debug)]
+/// Storage for message bar.
+#[derive(Debug, Default)]
pub struct MessageBuffer {
- current: Option<Message>,
- messages: Receiver<Message>,
- tx: Sender<Message>,
+ messages: VecDeque<Message>,
}
impl MessageBuffer {
- /// Create new message buffer
+ /// Create new message buffer.
pub fn new() -> MessageBuffer {
- let (tx, messages) = crossbeam_channel::unbounded();
- MessageBuffer { current: None, messages, tx }
+ MessageBuffer { messages: VecDeque::new() }
}
- /// Check if there are any messages queued
+ /// Check if there are any messages queued.
#[inline]
pub fn is_empty(&self) -> bool {
- self.current.is_none()
+ self.messages.is_empty()
}
- /// Current message
+ /// Current message.
#[inline]
- pub fn message(&mut self) -> Option<Message> {
- if let Some(current) = &self.current {
- Some(current.clone())
- } else {
- self.current = self.messages.try_recv().ok();
- self.current.clone()
- }
- }
-
- /// Channel for adding new messages
- #[inline]
- pub fn tx(&self) -> Sender<Message> {
- self.tx.clone()
+ pub fn message(&self) -> Option<&Message> {
+ self.messages.front()
}
- /// Remove the currently visible message
+ /// Remove the currently visible message.
#[inline]
pub fn pop(&mut self) {
+ // Remove the message itself
+ let msg = self.messages.pop_front();
+
// Remove all duplicates
- for msg in self
- .messages
- .try_iter()
- .take(self.messages.len())
- .filter(|m| Some(m) != self.current.as_ref())
- {
- let _ = self.tx.send(msg);
+ if let Some(msg) = msg {
+ self.messages = self.messages.drain(..).filter(|m| m != &msg).collect();
}
-
- // Remove the message itself
- self.current = self.messages.try_recv().ok();
}
- /// Remove all messages with a specific topic
+ /// Remove all messages with a specific target.
#[inline]
- pub fn remove_topic(&mut self, topic: &str) {
- // Filter messages currently pending
- for msg in self
+ pub fn remove_target(&mut self, target: &str) {
+ self.messages = self
.messages
- .try_iter()
- .take(self.messages.len())
- .filter(|m| m.topic().map(String::as_str) != Some(topic))
- {
- let _ = self.tx.send(msg);
- }
-
- // Remove the currently active message
- self.current = self.messages.try_recv().ok();
+ .drain(..)
+ .filter(|m| m.target().map(String::as_str) != Some(target))
+ .collect();
}
-}
-impl Default for MessageBuffer {
- fn default() -> MessageBuffer {
- MessageBuffer::new()
+ /// Add a new message to the queue.
+ #[inline]
+ pub fn push(&mut self, message: Message) {
+ self.messages.push_back(message);
}
}
@@ -207,7 +181,7 @@ mod test {
fn appends_close_button() {
let input = "a";
let mut message_buffer = MessageBuffer::new();
- message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap();
+ message_buffer.push(Message::new(input.into(), color::RED));
let size = SizeInfo {
width: 7.,
height: 10.,
@@ -227,7 +201,7 @@ mod test {
fn multiline_close_button_first_line() {
let input = "fo\nbar";
let mut message_buffer = MessageBuffer::new();
- message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap();
+ message_buffer.push(Message::new(input.into(), color::RED));
let size = SizeInfo {
width: 6.,
height: 10.,
@@ -247,7 +221,7 @@ mod test {
fn splits_on_newline() {
let input = "a\nb";
let mut message_buffer = MessageBuffer::new();
- message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap();
+ message_buffer.push(Message::new(input.into(), color::RED));
let size = SizeInfo {
width: 6.,
height: 10.,
@@ -267,7 +241,7 @@ mod test {
fn splits_on_length() {
let input = "foobar1";
let mut message_buffer = MessageBuffer::new();
- message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap();
+ message_buffer.push(Message::new(input.into(), color::RED));
let size = SizeInfo {
width: 6.,
height: 10.,
@@ -287,7 +261,7 @@ mod test {
fn empty_with_shortterm() {
let input = "foobar";
let mut message_buffer = MessageBuffer::new();
- message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap();
+ message_buffer.push(Message::new(input.into(), color::RED));
let size = SizeInfo {
width: 6.,
height: 0.,
@@ -307,7 +281,7 @@ mod test {
fn truncates_long_messages() {
let input = "hahahahahahahahahahaha truncate this because it's too long for the term";
let mut message_buffer = MessageBuffer::new();
- message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap();
+ message_buffer.push(Message::new(input.into(), color::RED));
let size = SizeInfo {
width: 22.,
height: (MIN_FREE_LINES + 2) as f32,
@@ -330,7 +304,7 @@ mod test {
fn hide_button_when_too_narrow() {
let input = "ha";
let mut message_buffer = MessageBuffer::new();
- message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap();
+ message_buffer.push(Message::new(input.into(), color::RED));
let size = SizeInfo {
width: 2.,
height: 10.,
@@ -350,7 +324,7 @@ mod test {
fn hide_truncated_when_too_narrow() {
let input = "hahahahahahahahaha";
let mut message_buffer = MessageBuffer::new();
- message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap();
+ message_buffer.push(Message::new(input.into(), color::RED));
let size = SizeInfo {
width: 2.,
height: (MIN_FREE_LINES + 2) as f32,
@@ -370,7 +344,7 @@ mod test {
fn add_newline_for_button() {
let input = "test";
let mut message_buffer = MessageBuffer::new();
- message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap();
+ message_buffer.push(Message::new(input.into(), color::RED));
let size = SizeInfo {
width: 5.,
height: 10.,
@@ -387,17 +361,17 @@ mod test {
}
#[test]
- fn remove_topic() {
+ fn remove_target() {
let mut message_buffer = MessageBuffer::new();
for i in 0..10 {
let mut msg = Message::new(i.to_string(), color::RED);
if i % 2 == 0 && i < 5 {
- msg.set_topic("topic".into());
+ msg.set_target("target".into());
}
- message_buffer.tx().send(msg).unwrap();
+ message_buffer.push(msg);
}
- message_buffer.remove_topic("topic");
+ message_buffer.remove_target("target");
// Count number of messages
let mut num_messages = 0;
@@ -413,22 +387,22 @@ mod test {
fn pop() {
let mut message_buffer = MessageBuffer::new();
let one = Message::new(String::from("one"), color::RED);
- message_buffer.tx().send(one.clone()).unwrap();
+ message_buffer.push(one.clone());
let two = Message::new(String::from("two"), color::YELLOW);
- message_buffer.tx().send(two.clone()).unwrap();
+ message_buffer.push(two.clone());
- assert_eq!(message_buffer.message(), Some(one));
+ assert_eq!(message_buffer.message(), Some(&one));
message_buffer.pop();
- assert_eq!(message_buffer.message(), Some(two));
+ assert_eq!(message_buffer.message(), Some(&two));
}
#[test]
fn wrap_on_words() {
let input = "a\nbc defg";
let mut message_buffer = MessageBuffer::new();
- message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap();
+ message_buffer.push(Message::new(input.into(), color::RED));
let size = SizeInfo {
width: 5.,
height: 10.,
@@ -453,10 +427,10 @@ mod test {
let mut message_buffer = MessageBuffer::new();
for _ in 0..10 {
let msg = Message::new(String::from("test"), color::RED);
- message_buffer.tx().send(msg).unwrap();
+ message_buffer.push(msg);
}
- message_buffer.tx().send(Message::new(String::from("other"), color::RED)).unwrap();
- message_buffer.tx().send(Message::new(String::from("test"), color::YELLOW)).unwrap();
+ message_buffer.push(Message::new(String::from("other"), color::RED));
+ message_buffer.push(Message::new(String::from("test"), color::YELLOW));
let _ = message_buffer.message();
message_buffer.pop();
diff --git a/alacritty_terminal/src/meter.rs b/alacritty_terminal/src/meter.rs
index 19d7fe70..686dd859 100644
--- a/alacritty_terminal/src/meter.rs
+++ b/alacritty_terminal/src/meter.rs
@@ -30,6 +30,7 @@
//! // Get the moving average. The meter tracks a fixed number of samples, and
//! // the average won't mean much until it's filled up at least once.
//! println!("Average time: {}", meter.average());
+//! ```
use std::time::{Duration, Instant};
diff --git a/alacritty_terminal/src/renderer/mod.rs b/alacritty_terminal/src/renderer/mod.rs
index 4aae8536..51c0d0f0 100644
--- a/alacritty_terminal/src/renderer/mod.rs
+++ b/alacritty_terminal/src/renderer/mod.rs
@@ -23,10 +23,10 @@ use std::time::Duration;
use fnv::FnvHasher;
use font::{self, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer};
-use glutin::dpi::PhysicalSize;
+use log::{error, info};
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
-use crate::config::{self, Config, Delta};
+use crate::config::{self, Config, Delta, Font, StartupMode};
use crate::cursor::{get_cursor_glyph, CursorKey};
use crate::gl;
use crate::gl::types::*;
@@ -34,7 +34,9 @@ use crate::index::{Column, Line};
use crate::renderer::rects::RenderRect;
use crate::term::cell::{self, Flags};
use crate::term::color::Rgb;
+use crate::term::SizeInfo;
use crate::term::{self, RenderableCell, RenderableCellContent};
+use crate::util;
pub mod rects;
@@ -284,12 +286,6 @@ impl GlyphCache {
FontDesc::new(desc.family.clone(), style)
}
- pub fn font_metrics(&self) -> font::Metrics {
- self.rasterizer
- .metrics(self.font_key, self.font_size)
- .expect("metrics load since font is loaded at glyph cache creation")
- }
-
pub fn get<'a, L>(&'a mut self, glyph_key: GlyphKey, loader: &mut L) -> &'a Glyph
where
L: LoadGlyph,
@@ -311,8 +307,7 @@ impl GlyphCache {
pub fn update_font_size<L: LoadGlyph>(
&mut self,
- font: &config::Font,
- size: font::Size,
+ font: config::Font,
dpr: f64,
loader: &mut L,
) -> Result<(), font::Error> {
@@ -325,12 +320,11 @@ impl GlyphCache {
self.rasterizer.update_dpr(dpr as f32);
// Recompute font keys
- let font = font.to_owned().with_size(size);
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, size)?;
+ let metrics = self.rasterizer.metrics(regular, font.size)?;
info!("Font size changed to {:?} with DPR of {}", font.size, dpr);
@@ -349,13 +343,15 @@ impl GlyphCache {
Ok(())
}
- // Calculate font metrics without access to a glyph cache
- //
- // This should only be used *before* OpenGL is initialized and the glyph cache can be filled.
- pub fn static_metrics(config: &Config, dpr: f32) -> Result<font::Metrics, font::Error> {
- let font = config.font.clone();
+ pub fn font_metrics(&self) -> font::Metrics {
+ self.rasterizer
+ .metrics(self.font_key, self.font_size)
+ .expect("metrics load since font is loaded at glyph cache creation")
+ }
- let mut rasterizer = font::Rasterizer::new(dpr, config.font.use_thin_strokes())?;
+ // 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(&regular_desc, font.size)?;
@@ -363,6 +359,34 @@ impl GlyphCache {
rasterizer.metrics(regular, font.size)
}
+
+ pub fn calculate_dimensions<C>(
+ config: &Config<C>,
+ dpr: f64,
+ cell_width: f32,
+ cell_height: f32,
+ ) -> Option<(f64, f64)> {
+ 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 = (f64::from(grid_width) + 2. * padding_x).floor();
+ let height = (f64::from(grid_height) + 2. * padding_y).floor();
+
+ Some((width, height))
+ }
}
#[derive(Debug)]
@@ -411,13 +435,13 @@ pub struct QuadRenderer {
}
#[derive(Debug)]
-pub struct RenderApi<'a> {
+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,
+ config: &'a Config<C>,
}
#[derive(Debug)]
@@ -445,7 +469,7 @@ impl Batch {
Batch { tex: 0, instances: Vec::with_capacity(BATCH_MAX) }
}
- pub fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph) {
+ pub fn add_item(&mut self, cell: RenderableCell, glyph: &Glyph) {
if self.is_empty() {
self.tex = glyph.tex_id;
}
@@ -639,7 +663,7 @@ impl QuadRenderer {
let (msg_tx, msg_rx) = mpsc::channel();
if cfg!(feature = "live-shader-reload") {
- ::std::thread::spawn(move || {
+ 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 =
@@ -691,8 +715,8 @@ impl QuadRenderer {
// Draw all rectangles simultaneously to prevent excessive program swaps
pub fn draw_rects(
&mut self,
- config: &Config,
props: &term::SizeInfo,
+ visual_bell_color: Rgb,
visual_bell_intensity: f64,
cell_line_rects: Vec<RenderRect>,
) {
@@ -724,8 +748,7 @@ impl QuadRenderer {
}
// Draw visual bell
- let color = config.visual_bell.color;
- let rect = RenderRect::new(0., 0., props.width, props.height, color);
+ let rect = RenderRect::new(0., 0., props.width, props.height, visual_bell_color);
self.render_rect(&rect, visual_bell_intensity as f32, props);
// Draw underlines and strikeouts
@@ -753,9 +776,9 @@ impl QuadRenderer {
}
}
- pub fn with_api<F, T>(&mut self, config: &Config, props: &term::SizeInfo, func: F) -> T
+ pub fn with_api<F, T, C>(&mut self, config: &Config<C>, props: &term::SizeInfo, func: F) -> T
where
- F: FnOnce(RenderApi<'_>) -> T,
+ F: FnOnce(RenderApi<'_, C>) -> T,
{
// Flush message queue
if let Ok(Msg::ShaderReload) = self.rx.try_recv() {
@@ -838,25 +861,19 @@ impl QuadRenderer {
self.rect_program = rect_program;
}
- pub fn resize(&mut self, size: PhysicalSize, padding_x: f32, padding_y: f32) {
- let (width, height): (u32, u32) = size.into();
-
+ pub fn resize(&mut self, size: &SizeInfo) {
// viewport
unsafe {
- let width = width as i32;
- let height = height as i32;
- let padding_x = padding_x as i32;
- let padding_y = padding_y as i32;
- gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y);
+ 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(
- width as f32,
- height as f32,
- padding_x as f32,
- padding_y as f32,
- );
+ self.program.update_projection(size.width, size.height, size.padding_x, size.padding_y);
gl::UseProgram(0);
}
}
@@ -899,10 +916,10 @@ impl QuadRenderer {
}
}
-impl<'a> RenderApi<'a> {
+impl<'a, C> RenderApi<'a, C> {
pub fn clear(&self, color: Rgb) {
- let alpha = self.config.background_opacity();
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,
@@ -989,7 +1006,7 @@ impl<'a> RenderApi<'a> {
}
#[inline]
- fn add_render_item(&mut self, cell: &RenderableCell, glyph: &Glyph) {
+ 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();
@@ -1009,18 +1026,15 @@ impl<'a> RenderApi<'a> {
// 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(|| {
- let offset_x = self.config.font.offset.x;
- let offset_y = self.config.font.offset.y;
-
self.load_glyph(&get_cursor_glyph(
cursor_key.style,
metrics,
- offset_x,
- offset_y,
+ self.config.font.offset.x,
+ self.config.font.offset.y,
cursor_key.is_wide,
))
});
- self.add_render_item(&cell, &glyph);
+ self.add_render_item(cell, &glyph);
return;
},
RenderableCellContent::Chars(chars) => chars,
@@ -1050,7 +1064,7 @@ impl<'a> RenderApi<'a> {
// Add cell to batch
let glyph = glyph_cache.get(glyph_key, self);
- self.add_render_item(&cell, glyph);
+ self.add_render_item(cell, glyph);
// Render zero-width characters
for c in (&chars[1..]).iter().filter(|c| **c != ' ') {
@@ -1064,7 +1078,7 @@ impl<'a> RenderApi<'a> {
// 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);
+ self.add_render_item(cell, &glyph);
}
}
}
@@ -1124,7 +1138,7 @@ impl<'a> LoadGlyph for LoaderApi<'a> {
}
}
-impl<'a> LoadGlyph for RenderApi<'a> {
+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)
}
@@ -1134,7 +1148,7 @@ impl<'a> LoadGlyph for RenderApi<'a> {
}
}
-impl<'a> Drop for RenderApi<'a> {
+impl<'a, C> Drop for RenderApi<'a, C> {
fn drop(&mut self) {
if !self.batch.is_empty() {
self.render_batch();
diff --git a/alacritty_terminal/src/renderer/rects.rs b/alacritty_terminal/src/renderer/rects.rs
index 72139e3d..dd72f673 100644
--- a/alacritty_terminal/src/renderer/rects.rs
+++ b/alacritty_terminal/src/renderer/rects.rs
@@ -91,7 +91,7 @@ impl RenderLines {
}
/// Update the stored lines with the next cell info.
- pub fn update(&mut self, cell: &RenderableCell) {
+ pub fn update(&mut self, cell: RenderableCell) {
for flag in &[Flags::UNDERLINE, Flags::STRIKEOUT] {
if !cell.flags.contains(*flag) {
continue;
diff --git a/alacritty_terminal/src/selection.rs b/alacritty_terminal/src/selection.rs
index ffe35e08..29934e5a 100644
--- a/alacritty_terminal/src/selection.rs
+++ b/alacritty_terminal/src/selection.rs
@@ -39,6 +39,7 @@ use crate::term::{Search, Term};
/// [`simple`]: enum.Selection.html#method.simple
/// [`semantic`]: enum.Selection.html#method.semantic
/// [`lines`]: enum.Selection.html#method.lines
+/// [`update`]: enum.Selection.html#method.update
#[derive(Debug, Clone, PartialEq)]
pub enum Selection {
Simple {
@@ -164,7 +165,7 @@ impl Selection {
}
}
- pub fn to_span(&self, term: &Term) -> Option<Span> {
+ pub fn to_span<T>(&self, term: &Term<T>) -> Option<Span> {
// Get both sides of the selection
let (mut start, mut end) = match *self {
Selection::Simple { ref region } | Selection::Block { ref region } => {
@@ -405,13 +406,19 @@ mod test {
use super::{Selection, Span};
use crate::clipboard::Clipboard;
+ use crate::config::MockConfig;
+ use crate::event::{Event, EventListener};
use crate::grid::Grid;
use crate::index::{Column, Line, Point, Side};
- use crate::message_bar::MessageBuffer;
use crate::term::cell::{Cell, Flags};
use crate::term::{SizeInfo, Term};
- fn term(width: usize, height: usize) -> Term {
+ struct Mock;
+ impl EventListener for Mock {
+ fn send_event(&self, _event: Event) {}
+ }
+
+ fn term(width: usize, height: usize) -> Term<Mock> {
let size = SizeInfo {
width: width as f32,
height: height as f32,
@@ -421,7 +428,7 @@ mod test {
padding_y: 0.0,
dpr: 1.0,
};
- Term::new(&Default::default(), size, MessageBuffer::new(), Clipboard::new_nop())
+ Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock)
}
/// Test case of single cell selection
diff --git a/alacritty_terminal/src/term/cell.rs b/alacritty_terminal/src/term/cell.rs
index 7a759777..234805f9 100644
--- a/alacritty_terminal/src/term/cell.rs
+++ b/alacritty_terminal/src/term/cell.rs
@@ -13,6 +13,8 @@
// limitations under the License.
use bitflags::bitflags;
+use serde::{Deserialize, Serialize};
+
use crate::ansi::{Color, NamedColor};
use crate::grid::{self, GridCell};
use crate::index::Column;
diff --git a/alacritty_terminal/src/term/color.rs b/alacritty_terminal/src/term/color.rs
index f2db335a..e9f0a26d 100644
--- a/alacritty_terminal/src/term/color.rs
+++ b/alacritty_terminal/src/term/color.rs
@@ -2,8 +2,9 @@ use std::fmt;
use std::ops::{Index, IndexMut, Mul};
use std::str::FromStr;
+use log::{error, trace};
use serde::de::Visitor;
-use serde::{Deserialize, Deserializer};
+use serde::{Deserialize, Deserializer, Serialize};
use crate::ansi;
use crate::config::Colors;
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs
index 58d06318..4e51d734 100644
--- a/alacritty_terminal/src/term/mod.rs
+++ b/alacritty_terminal/src/term/mod.rs
@@ -18,9 +18,9 @@ use std::ops::{Index, IndexMut, Range, RangeInclusive};
use std::time::{Duration, Instant};
use std::{io, mem, ptr};
-use font::{self, Size};
-use glutin::MouseCursor;
+use log::{debug, trace};
use rfind_url::{Parser, ParserState};
+use serde::{Deserialize, Serialize};
use unicode_width::UnicodeWidthChar;
use crate::ansi::{
@@ -29,19 +29,17 @@ use crate::ansi::{
use crate::clipboard::{Clipboard, ClipboardType};
use crate::config::{Config, VisualBellAnimation};
use crate::cursor::CursorKey;
+use crate::event::{Event, EventListener};
use crate::grid::{
BidirectionalIterator, DisplayIter, Grid, GridCell, IndexRegion, Indexed, Scroll,
};
use crate::index::{self, Column, Contains, IndexRange, Line, Linear, Point};
-use crate::input::FONT_SIZE_STEP;
-use crate::message_bar::MessageBuffer;
use crate::selection::{self, Selection, SelectionRange, Span};
use crate::term::cell::{Cell, Flags, LineLength};
use crate::term::color::Rgb;
-use crate::url::Url;
-
#[cfg(windows)]
use crate::tty;
+use crate::url::Url;
pub mod cell;
pub mod color;
@@ -62,7 +60,7 @@ pub trait Search {
fn bracket_search(&self, _: Point<usize>) -> Option<Point<usize>>;
}
-impl Search for Term {
+impl<T> Search for Term<T> {
fn semantic_search_left(&self, mut point: Point<usize>) -> Point<usize> {
// Limit the starting point to the last line in the history
point.line = min(point.line, self.grid.len() - 1);
@@ -151,7 +149,7 @@ impl Search for Term {
}
}
-impl selection::Dimensions for Term {
+impl<T> selection::Dimensions for Term<T> {
fn dimensions(&self) -> Point {
let line = if self.mode.contains(TermMode::ALT_SCREEN) {
self.grid.num_lines()
@@ -170,30 +168,30 @@ impl selection::Dimensions for Term {
///
/// This manages the cursor during a render. The cursor location is inverted to
/// draw it, and reverted after drawing to maintain state.
-pub struct RenderableCellsIter<'a> {
+pub struct RenderableCellsIter<'a, C> {
inner: DisplayIter<'a, Cell>,
grid: &'a Grid<Cell>,
cursor: &'a Point,
cursor_offset: usize,
cursor_key: Option<CursorKey>,
cursor_style: CursorStyle,
- config: &'a Config,
+ config: &'a Config<C>,
colors: &'a color::List,
selection: Option<SelectionRange>,
url_highlight: &'a Option<RangeInclusive<index::Linear>>,
}
-impl<'a> RenderableCellsIter<'a> {
+impl<'a, C> RenderableCellsIter<'a, C> {
/// Create the renderable cells iterator
///
/// The cursor and terminal mode are required for properly displaying the
/// cursor.
- fn new<'b>(
- term: &'b Term,
- config: &'b Config,
+ fn new<'b, T>(
+ term: &'b Term<T>,
+ config: &'b Config<C>,
selection: Option<Span>,
mut cursor_style: CursorStyle,
- ) -> RenderableCellsIter<'b> {
+ ) -> RenderableCellsIter<'b, C> {
let grid = &term.grid;
let cursor_offset = grid.line_to_offset(term.cursor.point.line);
@@ -250,13 +248,13 @@ impl<'a> RenderableCellsIter<'a> {
}
}
-#[derive(Clone, Debug)]
+#[derive(Copy, Clone, Debug)]
pub enum RenderableCellContent {
Chars([char; cell::MAX_ZEROWIDTH_CHARS + 1]),
Cursor(CursorKey),
}
-#[derive(Clone, Debug)]
+#[derive(Copy, Clone, Debug)]
pub struct RenderableCell {
/// A _Display_ line (not necessarily an _Active_ line)
pub line: Line,
@@ -269,7 +267,12 @@ pub struct RenderableCell {
}
impl RenderableCell {
- fn new(config: &Config, colors: &color::List, cell: Indexed<Cell>, selected: bool) -> Self {
+ fn new<C>(
+ config: &Config<C>,
+ colors: &color::List,
+ cell: Indexed<Cell>,
+ selected: bool,
+ ) -> Self {
// Lookup RGB values
let mut fg_rgb = Self::compute_fg_rgb(config, colors, cell.fg, cell.flags);
let mut bg_rgb = Self::compute_bg_rgb(colors, cell.bg);
@@ -309,7 +312,12 @@ impl RenderableCell {
}
}
- fn compute_fg_rgb(config: &Config, colors: &color::List, fg: Color, flags: cell::Flags) -> Rgb {
+ fn compute_fg_rgb<C>(
+ config: &Config<C>,
+ colors: &color::List,
+ fg: Color,
+ flags: cell::Flags,
+ ) -> Rgb {
match fg {
Color::Spec(rgb) => rgb,
Color::Named(ansi) => {
@@ -365,7 +373,7 @@ impl RenderableCell {
}
}
-impl<'a> Iterator for RenderableCellsIter<'a> {
+impl<'a, C> Iterator for RenderableCellsIter<'a, C> {
type Item = RenderableCell;
/// Gets the next renderable cell
@@ -573,7 +581,7 @@ fn cubic_bezier(p0: f64, p1: f64, p2: f64, p3: f64, x: f64) -> f64 {
}
impl VisualBell {
- pub fn new(config: &Config) -> VisualBell {
+ pub fn new<C>(config: &Config<C>) -> VisualBell {
let visual_bell_config = &config.visual_bell;
VisualBell {
animation: visual_bell_config.animation,
@@ -668,14 +676,14 @@ impl VisualBell {
}
}
- pub fn update_config(&mut self, config: &Config) {
+ pub fn update_config<C>(&mut self, config: &Config<C>) {
let visual_bell_config = &config.visual_bell;
self.animation = visual_bell_config.animation;
self.duration = visual_bell_config.duration();
}
}
-pub struct Term {
+pub struct Term<T> {
/// The grid
grid: Grid<Cell>,
@@ -686,14 +694,6 @@ pub struct Term {
/// arrays. Without it we would have to sanitize cursor.col every time we used it.
input_needs_wrap: bool,
- /// Got a request to set title; it's buffered here until next draw.
- ///
- /// Would be nice to avoid the allocation...
- next_title: Option<String>,
-
- /// Got a request to set the mouse cursor; it's buffered here until the next draw
- next_mouse_cursor: Option<MouseCursor>,
-
/// Alternate grid
alt_grid: Grid<Cell>,
@@ -716,17 +716,9 @@ pub struct Term {
/// Scroll region
scroll_region: Range<Line>,
- /// Font size
- pub font_size: Size,
- original_font_size: Size,
-
- /// Size
- size_info: SizeInfo,
-
pub dirty: bool,
pub visual_bell: VisualBell,
- pub next_is_urgent: Option<bool>,
/// Saved cursor from main grid
cursor_save: Cursor,
@@ -760,18 +752,18 @@ pub struct Term {
/// Automatically scroll to bottom when new lines are added
auto_scroll: bool,
- /// Buffer to store messages for the message bar
- message_buffer: MessageBuffer,
-
- /// Hint that Alacritty should be closed
- should_exit: bool,
-
/// Clipboard access coupled to the active window
clipboard: Clipboard,
+
+ /// Proxy for sending events to the event loop
+ event_proxy: T,
+
+ /// Terminal focus
+ pub is_focused: bool,
}
/// Terminal size info
-#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
pub struct SizeInfo {
/// Terminal window width
pub width: f32,
@@ -829,7 +821,7 @@ impl SizeInfo {
}
}
-impl Term {
+impl<T> Term<T> {
pub fn selection(&self) -> &Option<Selection> {
&self.grid.selection
}
@@ -839,29 +831,22 @@ impl Term {
}
#[inline]
- pub fn get_next_title(&mut self) -> Option<String> {
- self.next_title.take()
- }
-
- #[inline]
- pub fn scroll_display(&mut self, scroll: Scroll) {
- self.set_mouse_cursor(MouseCursor::Text);
+ pub fn scroll_display(&mut self, scroll: Scroll)
+ where
+ T: EventListener,
+ {
+ self.event_proxy.send_event(Event::MouseCursorDirty);
self.grid.scroll_display(scroll);
self.reset_url_highlight();
self.dirty = true;
}
- #[inline]
- pub fn get_next_mouse_cursor(&mut self) -> Option<MouseCursor> {
- self.next_mouse_cursor.take()
- }
-
- pub fn new(
- config: &Config,
- size: SizeInfo,
- message_buffer: MessageBuffer,
+ pub fn new<C>(
+ config: &Config<C>,
+ size: &SizeInfo,
clipboard: Clipboard,
- ) -> Term {
+ event_proxy: T,
+ ) -> Term<T> {
let num_cols = size.cols();
let num_lines = size.lines();
@@ -877,17 +862,12 @@ impl Term {
let colors = color::List::from(&config.colors);
Term {
- next_title: None,
- next_mouse_cursor: None,
dirty: false,
visual_bell: VisualBell::new(config),
- next_is_urgent: None,
input_needs_wrap: false,
grid,
alt_grid: alt,
alt: false,
- font_size: config.font.size,
- original_font_size: config.font.size,
active_charset: Default::default(),
cursor: Default::default(),
cursor_save: Default::default(),
@@ -895,7 +875,6 @@ impl Term {
tabs,
mode: Default::default(),
scroll_region,
- size_info: size,
colors,
color_modified: [false; color::COUNT],
original_colors: colors,
@@ -905,25 +884,13 @@ impl Term {
dynamic_title: config.dynamic_title(),
tabspaces,
auto_scroll: config.scrolling.auto_scroll,
- message_buffer,
- should_exit: false,
clipboard,
+ event_proxy,
+ is_focused: true,
}
}
- pub fn change_font_size(&mut self, delta: f32) {
- // Saturating addition with minimum font size FONT_SIZE_STEP
- let new_size = self.font_size + Size::new(delta);
- self.font_size = max(new_size, Size::new(FONT_SIZE_STEP));
- self.dirty = true;
- }
-
- pub fn reset_font_size(&mut self) {
- self.font_size = self.original_font_size;
- self.dirty = true;
- }
-
- pub fn update_config(&mut self, config: &Config) {
+ pub fn update_config<C>(&mut self, config: &Config<C>) {
self.semantic_escape_chars = config.selection.semantic_escape_chars().to_owned();
self.original_colors.fill_named(&config.colors);
self.original_colors.fill_cube(&config.colors);
@@ -938,16 +905,6 @@ impl Term {
self.dynamic_title = config.dynamic_title();
self.auto_scroll = config.scrolling.auto_scroll;
self.grid.update_history(config.scrolling.history() as usize, &self.cursor.template);
-
- if self.original_font_size == self.font_size {
- self.font_size = config.font.size;
- }
- self.original_font_size = config.font.size;
- }
-
- #[inline]
- pub fn needs_draw(&self) -> bool {
- self.dirty
}
pub fn selection_to_string(&self) -> Option<String> {
@@ -1072,21 +1029,6 @@ impl Term {
self.grid.buffer_to_visible(point)
}
- /// Convert the given pixel values to a grid coordinate
- ///
- /// The mouse coordinates are expected to be relative to the top left. The
- /// line and column returned are also relative to the top left.
- ///
- /// Returns None if the coordinates are outside the window,
- /// padding pixels are considered inside the window
- pub fn pixels_to_coords(&self, x: usize, y: usize) -> Option<Point> {
- if self.size_info.contains_point(x, y, true) {
- Some(self.size_info.pixels_to_coords(x, y))
- } else {
- None
- }
- }
-
/// Access to the raw grid data structure
///
/// This is a bit of a hack; when the window is closed, the event processor
@@ -1106,14 +1048,10 @@ impl Term {
/// A renderable cell is any cell which has content other than the default
/// background color. Cells with an alternate background color are
/// considered renderable as are cells with any text content.
- pub fn renderable_cells<'b>(
- &'b self,
- config: &'b Config,
- window_focused: bool,
- ) -> RenderableCellsIter<'_> {
+ pub fn renderable_cells<'b, C>(&'b self, config: &'b Config<C>) -> RenderableCellsIter<'_, C> {
let selection = self.grid.selection.as_ref().and_then(|s| s.to_span(self));
- let cursor = if window_focused || !config.cursor.unfocused_hollow() {
+ let cursor = if self.is_focused || !config.cursor.unfocused_hollow() {
self.cursor_style.unwrap_or(self.default_cursor_style)
} else {
CursorStyle::HollowBlock
@@ -1124,11 +1062,9 @@ impl Term {
/// Resize terminal to new dimensions
pub fn resize(&mut self, size: &SizeInfo) {
- debug!("Resizing terminal");
-
// Bounds check; lots of math assumes width and height are > 0
- if size.width as usize <= 2 * self.size_info.padding_x as usize
- || size.height as usize <= 2 * self.size_info.padding_y as usize
+ if size.width as usize <= 2 * size.padding_x as usize
+ || size.height as usize <= 2 * size.padding_y as usize
{
return;
}
@@ -1138,12 +1074,6 @@ impl Term {
let mut num_cols = size.cols();
let mut num_lines = size.lines();
- if let Some(message) = self.message_buffer.message() {
- num_lines -= message.text(size).len();
- }
-
- self.size_info = *size;
-
if old_cols == num_cols && old_lines == num_lines {
debug!("Term::resize dimensions unchanged");
return;
@@ -1211,11 +1141,6 @@ impl Term {
}
#[inline]
- pub fn size_info(&self) -> &SizeInfo {
- &self.size_info
- }
-
- #[inline]
pub fn mode(&self) -> &TermMode {
&self.mode
}
@@ -1266,7 +1191,10 @@ impl Term {
self.grid.scroll_up(&(origin..self.scroll_region.end), lines, &template);
}
- fn deccolm(&mut self) {
+ fn deccolm(&mut self)
+ where
+ T: EventListener,
+ {
// Setting 132 column font makes no sense, but run the other side effects
// Clear scrolling region
self.set_scrolling_region(1, self.grid.num_lines().0);
@@ -1282,23 +1210,11 @@ impl Term {
}
#[inline]
- pub fn message_buffer_mut(&mut self) -> &mut MessageBuffer {
- &mut self.message_buffer
- }
-
- #[inline]
- pub fn message_buffer(&self) -> &MessageBuffer {
- &self.message_buffer
- }
-
- #[inline]
- pub fn exit(&mut self) {
- self.should_exit = true;
- }
-
- #[inline]
- pub fn should_exit(&self) -> bool {
- self.should_exit
+ pub fn exit(&mut self)
+ where
+ T: EventListener,
+ {
+ self.event_proxy.send_event(Event::Exit);
}
#[inline]
@@ -1389,7 +1305,7 @@ impl Term {
}
}
-impl TermInfo for Term {
+impl<T> TermInfo for Term<T> {
#[inline]
fn lines(&self) -> Line {
self.grid.num_lines()
@@ -1401,33 +1317,33 @@ impl TermInfo for Term {
}
}
-impl ansi::Handler for Term {
- /// Set the window title
+impl<T: EventListener> ansi::Handler for Term<T> {
#[inline]
+ #[cfg(not(windows))]
fn set_title(&mut self, title: &str) {
if self.dynamic_title {
- self.next_title = Some(title.to_owned());
-
- #[cfg(windows)]
- {
- // cmd.exe in winpty: winpty incorrectly sets the title to ' ' instead of
- // 'Alacritty' - thus we have to substitute this back to get equivalent
- // behaviour as conpty.
- //
- // The starts_with check is necessary because other shells e.g. bash set a
- // different title and don't need Alacritty prepended.
- if !tty::is_conpty() && title.starts_with(' ') {
- self.next_title = Some(format!("Alacritty {}", title.trim()));
- }
- }
+ self.event_proxy.send_event(Event::Title(title.to_owned()));
}
}
- /// Set the mouse cursor
#[inline]
- fn set_mouse_cursor(&mut self, cursor: MouseCursor) {
- self.next_mouse_cursor = Some(cursor);
- self.dirty = true;
+ #[cfg(windows)]
+ fn set_title(&mut self, title: &str) {
+ if self.dynamic_title {
+ // cmd.exe in winpty: winpty incorrectly sets the title to ' ' instead of
+ // 'Alacritty' - thus we have to substitute this back to get equivalent
+ // behaviour as conpty.
+ //
+ // The starts_with check is necessary because other shells e.g. bash set a
+ // different title and don't need Alacritty prepended.
+ let title = if !tty::is_conpty() && title.starts_with(' ') {
+ format!("Alacritty {}", title.trim())
+ } else {
+ title.to_owned()
+ };
+
+ self.event_proxy.send_event(Event::Title(title));
+ }
}
/// A character to be displayed
@@ -1554,11 +1470,11 @@ impl ansi::Handler for Term {
fn insert_blank(&mut self, count: Column) {
// Ensure inserting within terminal bounds
- let count = min(count, self.size_info.cols() - self.cursor.point.col);
+ let count = min(count, self.grid.num_cols() - self.cursor.point.col);
let source = self.cursor.point.col;
let destination = self.cursor.point.col + count;
- let num_cells = (self.size_info.cols() - destination).0;
+ let num_cells = (self.grid.num_cols() - destination).0;
let line = &mut self.grid[self.cursor.point.line];
@@ -1703,7 +1619,7 @@ impl ansi::Handler for Term {
fn bell(&mut self) {
trace!("Bell");
self.visual_bell.ring();
- self.next_is_urgent = Some(true);
+ self.event_proxy.send_event(Event::Urgent);
}
#[inline]
@@ -1794,12 +1710,14 @@ impl ansi::Handler for Term {
#[inline]
fn delete_chars(&mut self, count: Column) {
+ let cols = self.grid.num_cols();
+
// Ensure deleting within terminal bounds
- let count = min(count, self.size_info.cols());
+ let count = min(count, cols);
let start = self.cursor.point.col;
- let end = min(start + count, self.grid.num_cols() - 1);
- let n = (self.size_info.cols() - end).0;
+ let end = min(start + count, cols - 1);
+ let n = (cols - end).0;
let line = &mut self.grid[self.cursor.point.line];
@@ -1813,7 +1731,7 @@ impl ansi::Handler for Term {
// Clear last `count` cells in line. If deleting 1 char, need to delete
// 1 cell.
let template = self.cursor.template;
- let end = self.size_info.cols() - count;
+ let end = cols - count;
for c in &mut line[end..] {
c.reset(&template);
}
@@ -1983,13 +1901,9 @@ impl ansi::Handler for Term {
self.swap_alt();
}
self.input_needs_wrap = false;
- self.next_title = None;
- self.next_mouse_cursor = None;
self.cursor = Default::default();
self.active_charset = Default::default();
self.mode = Default::default();
- self.font_size = self.original_font_size;
- self.next_is_urgent = None;
self.cursor_save = Default::default();
self.cursor_save_alt = Default::default();
self.colors = self.original_colors;
@@ -2061,15 +1975,15 @@ impl ansi::Handler for Term {
ansi::Mode::CursorKeys => self.mode.insert(TermMode::APP_CURSOR),
ansi::Mode::ReportMouseClicks => {
self.mode.insert(TermMode::MOUSE_REPORT_CLICK);
- self.set_mouse_cursor(MouseCursor::Default);
+ self.event_proxy.send_event(Event::MouseCursorDirty);
},
ansi::Mode::ReportCellMouseMotion => {
self.mode.insert(TermMode::MOUSE_DRAG);
- self.set_mouse_cursor(MouseCursor::Default);
+ self.event_proxy.send_event(Event::MouseCursorDirty);
},
ansi::Mode::ReportAllMouseMotion => {
self.mode.insert(TermMode::MOUSE_MOTION);
- self.set_mouse_cursor(MouseCursor::Default);
+ self.event_proxy.send_event(Event::MouseCursorDirty);
},
ansi::Mode::ReportFocusInOut => self.mode.insert(TermMode::FOCUS_IN_OUT),
ansi::Mode::BracketedPaste => self.mode.insert(TermMode::BRACKETED_PASTE),
@@ -2101,15 +2015,15 @@ impl ansi::Handler for Term {
ansi::Mode::CursorKeys => self.mode.remove(TermMode::APP_CURSOR),
ansi::Mode::ReportMouseClicks => {
self.mode.remove(TermMode::MOUSE_REPORT_CLICK);
- self.set_mouse_cursor(MouseCursor::Text);
+ self.event_proxy.send_event(Event::MouseCursorDirty);
},
ansi::Mode::ReportCellMouseMotion => {
self.mode.remove(TermMode::MOUSE_DRAG);
- self.set_mouse_cursor(MouseCursor::Text);
+ self.event_proxy.send_event(Event::MouseCursorDirty);
},
ansi::Mode::ReportAllMouseMotion => {
self.mode.remove(TermMode::MOUSE_MOTION);
- self.set_mouse_cursor(MouseCursor::Text);
+ self.event_proxy.send_event(Event::MouseCursorDirty);
},
ansi::Mode::ReportFocusInOut => self.mode.remove(TermMode::FOCUS_IN_OUT),
ansi::Mode::BracketedPaste => self.mode.remove(TermMode::BRACKETED_PASTE),
@@ -2215,19 +2129,22 @@ impl IndexMut<Column> for TabStops {
mod tests {
use std::mem;
- use font::Size;
use serde_json;
use crate::ansi::{self, CharsetIndex, Handler, StandardCharset};
use crate::clipboard::Clipboard;
- use crate::config::Config;
+ use crate::config::MockConfig;
+ use crate::event::{Event, EventListener};
use crate::grid::{Grid, Scroll};
use crate::index::{Column, Line, Point, Side};
- use crate::input::FONT_SIZE_STEP;
- use crate::message_bar::MessageBuffer;
use crate::selection::Selection;
use crate::term::{cell, Cell, SizeInfo, Term};
+ struct Mock;
+ impl EventListener for Mock {
+ fn send_event(&self, _event: Event) {}
+ }
+
#[test]
fn semantic_selection_works() {
let size = SizeInfo {
@@ -2239,8 +2156,7 @@ mod tests {
padding_y: 0.0,
dpr: 1.0,
};
- let mut term =
- Term::new(&Default::default(), size, MessageBuffer::new(), Clipboard::new_nop());
+ let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock);
let mut grid: Grid<Cell> = Grid::new(Line(3), Column(5), 0, Cell::default());
for i in 0..5 {
for j in 0..2 {
@@ -2284,8 +2200,7 @@ mod tests {
padding_y: 0.0,
dpr: 1.0,
};
- let mut term =
- Term::new(&Default::default(), size, MessageBuffer::new(), Clipboard::new_nop());
+ let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock);
let mut grid: Grid<Cell> = Grid::new(Line(1), Column(5), 0, Cell::default());
for i in 0..5 {
grid[Line(0)][Column(i)].c = 'a';
@@ -2310,8 +2225,7 @@ mod tests {
padding_y: 0.0,
dpr: 1.0,
};
- let mut term =
- Term::new(&Default::default(), size, MessageBuffer::new(), Clipboard::new_nop());
+ let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock);
let mut grid: Grid<Cell> = Grid::new(Line(3), Column(3), 0, Cell::default());
for l in 0..3 {
if l != 1 {
@@ -2355,8 +2269,7 @@ mod tests {
padding_y: 0.0,
dpr: 1.0,
};
- let mut term =
- Term::new(&Default::default(), size, MessageBuffer::new(), Clipboard::new_nop());
+ let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock);
let cursor = Point::new(Line(0), Column(0));
term.configure_charset(CharsetIndex::G0, StandardCharset::SpecialCharacterAndLineDrawing);
term.input('a');
@@ -2364,75 +2277,6 @@ mod tests {
assert_eq!(term.grid()[&cursor].c, '▒');
}
- fn change_font_size_works(font_size: f32) {
- 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 config: Config = Default::default();
- let mut term: Term = Term::new(&config, size, MessageBuffer::new(), Clipboard::new_nop());
- term.change_font_size(font_size);
-
- let expected_font_size: Size = config.font.size + Size::new(font_size);
- assert_eq!(term.font_size, expected_font_size);
- }
-
- #[test]
- fn increase_font_size_works() {
- change_font_size_works(10.0);
- }
-
- #[test]
- fn decrease_font_size_works() {
- change_font_size_works(-10.0);
- }
-
- #[test]
- fn prevent_font_below_threshold_works() {
- 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 config: Config = Default::default();
- let mut term: Term = Term::new(&config, size, MessageBuffer::new(), Clipboard::new_nop());
-
- term.change_font_size(-100.0);
-
- let expected_font_size: Size = Size::new(FONT_SIZE_STEP);
- assert_eq!(term.font_size, expected_font_size);
- }
-
- #[test]
- fn reset_font_size_works() {
- 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 config: Config = Default::default();
- let mut term: Term = Term::new(&config, size, MessageBuffer::new(), Clipboard::new_nop());
-
- term.change_font_size(10.0);
- term.reset_font_size();
-
- let expected_font_size: Size = config.font.size;
- assert_eq!(term.font_size, expected_font_size);
- }
-
#[test]
fn clear_saved_lines() {
let size = SizeInfo {
@@ -2444,8 +2288,7 @@ mod tests {
padding_y: 0.0,
dpr: 1.0,
};
- let config: Config = Default::default();
- let mut term: Term = Term::new(&config, size, MessageBuffer::new(), Clipboard::new_nop());
+ let mut term = Term::new(&MockConfig::default(), &size, Clipboard::new_nop(), Mock);
// Add one line of scrollback
term.grid.scroll_up(&(Line(0)..Line(1)), Line(1), &Cell::default());
@@ -2471,13 +2314,18 @@ mod benches {
use std::path::Path;
use crate::clipboard::Clipboard;
- use crate::config::Config;
+ use crate::config::MockConfig;
+ use crate::event::{Event, EventListener};
use crate::grid::Grid;
- use crate::message_bar::MessageBuffer;
use super::cell::Cell;
use super::{SizeInfo, Term};
+ struct Mock;
+ impl EventListener for Mock {
+ fn send_event(&self, _event: Event) {}
+ }
+
fn read_string<P>(path: P) -> String
where
P: AsRef<Path>,
@@ -2512,13 +2360,13 @@ mod benches {
let mut grid: Grid<Cell> = json::from_str(&serialized_grid).unwrap();
let size: SizeInfo = json::from_str(&serialized_size).unwrap();
- let config = Config::default();
+ let config = MockConfig::default();
- let mut terminal = Term::new(&config, size, MessageBuffer::new(), Clipboard::new_nop());
+ let mut terminal = Term::new(&config, &size, Clipboard::new_nop(), Mock);
mem::swap(&mut terminal.grid, &mut grid);
b.iter(|| {
- let iter = terminal.renderable_cells(&config, false);
+ let iter = terminal.renderable_cells(&config);
for cell in iter {
test::black_box(cell);
}
diff --git a/alacritty_terminal/src/tty/mod.rs b/alacritty_terminal/src/tty/mod.rs
index 16ae96ce..7150a42d 100644
--- a/alacritty_terminal/src/tty/mod.rs
+++ b/alacritty_terminal/src/tty/mod.rs
@@ -77,7 +77,7 @@ pub trait EventedPty: EventedReadWrite {
}
// Setup environment variables
-pub fn setup_env(config: &Config) {
+pub fn setup_env<C>(config: &Config<C>) {
// Default to 'alacritty' terminfo if it is available, otherwise
// default to 'xterm-256color'. May be overridden by user's config
// below.
diff --git a/alacritty_terminal/src/tty/unix.rs b/alacritty_terminal/src/tty/unix.rs
index 77b5db43..a2a277a6 100644
--- a/alacritty_terminal/src/tty/unix.rs
+++ b/alacritty_terminal/src/tty/unix.rs
@@ -15,12 +15,13 @@
//! tty related functionality
use crate::config::{Config, Shell};
-use crate::display::OnResize;
+use crate::event::OnResize;
use crate::term::SizeInfo;
use crate::tty::{ChildEvent, EventedPty, EventedReadWrite};
use mio;
use libc::{self, c_int, pid_t, winsize, TIOCSCTTY};
+use log::error;
use nix::pty::openpty;
use signal_hook::{self as sighook, iterator::Signals};
@@ -42,6 +43,13 @@ use std::sync::atomic::{AtomicUsize, Ordering};
/// Necessary to put this in static storage for `sigchld` to have access
static PID: AtomicUsize = AtomicUsize::new(0);
+macro_rules! die {
+ ($($arg:tt)*) => {{
+ error!($($arg)*);
+ ::std::process::exit(1);
+ }}
+}
+
pub fn child_pid() -> pid_t {
PID.load(Ordering::Relaxed) as pid_t
}
@@ -133,24 +141,8 @@ pub struct Pty {
signals_token: mio::Token,
}
-impl Pty {
- /// Resize the pty
- ///
- /// Tells the kernel that the window size changed with the new pixel
- /// dimensions and line/column counts.
- pub fn resize<T: ToWinsize>(&self, size: &T) {
- let win = size.to_winsize();
-
- let res = unsafe { libc::ioctl(self.fd.as_raw_fd(), libc::TIOCSWINSZ, &win as *const _) };
-
- if res < 0 {
- die!("ioctl TIOCSWINSZ failed: {}", io::Error::last_os_error());
- }
- }
-}
-
/// Create a new tty and return a handle to interact with it.
-pub fn new<T: ToWinsize>(config: &Config, size: &T, window_id: Option<usize>) -> Pty {
+pub fn new<C>(config: &Config<C>, size: &SizeInfo, window_id: Option<usize>) -> Pty {
let win_size = size.to_winsize();
let mut buf = [0; 1024];
let pw = get_pw_entry(&mut buf);
@@ -241,12 +233,10 @@ pub fn new<T: ToWinsize>(config: &Config, size: &T, window_id: Option<usize>) ->
signals,
signals_token: mio::Token::from(0),
};
- pty.resize(size);
+ pty.fd.as_raw_fd().on_resize(size);
pty
},
- Err(err) => {
- die!("Failed to spawn command: {}", err);
- },
+ Err(err) => die!("Failed to spawn command: {}", err),
}
}
@@ -365,6 +355,10 @@ impl<'a> ToWinsize for &'a SizeInfo {
}
impl OnResize for i32 {
+ /// Resize the pty
+ ///
+ /// Tells the kernel that the window size changed with the new pixel
+ /// dimensions and line/column counts.
fn on_resize(&mut self, size: &SizeInfo) {
let win = size.to_winsize();
diff --git a/alacritty_terminal/src/tty/windows/conpty.rs b/alacritty_terminal/src/tty/windows/conpty.rs
index bd602c35..fe49b4dc 100644
--- a/alacritty_terminal/src/tty/windows/conpty.rs
+++ b/alacritty_terminal/src/tty/windows/conpty.rs
@@ -22,6 +22,7 @@ use std::ptr;
use std::sync::Arc;
use dunce::canonicalize;
+use log::info;
use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite};
use miow;
use widestring::U16CString;
@@ -38,7 +39,7 @@ use winapi::um::winbase::{EXTENDED_STARTUPINFO_PRESENT, STARTF_USESTDHANDLES, ST
use winapi::um::wincontypes::{COORD, HPCON};
use crate::config::{Config, Shell};
-use crate::display::OnResize;
+use crate::event::OnResize;
use crate::term::SizeInfo;
/// Dynamically-loaded Pseudoconsole API from kernel32.dll
@@ -98,7 +99,11 @@ impl Drop for Conpty {
unsafe impl Send for Conpty {}
unsafe impl Sync for Conpty {}
-pub fn new<'a>(config: &Config, size: &SizeInfo, _window_id: Option<usize>) -> Option<Pty<'a>> {
+pub fn new<'a, C>(
+ config: &Config<C>,
+ size: &SizeInfo,
+ _window_id: Option<usize>,
+) -> Option<Pty<'a>> {
if !config.enable_experimental_conpty_backend {
return None;
}
diff --git a/alacritty_terminal/src/tty/windows/mod.rs b/alacritty_terminal/src/tty/windows/mod.rs
index 7537d331..922dde26 100644
--- a/alacritty_terminal/src/tty/windows/mod.rs
+++ b/alacritty_terminal/src/tty/windows/mod.rs
@@ -20,12 +20,13 @@ use mio::{self, Evented, Poll, PollOpt, Ready, Token};
use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite};
use mio_named_pipes::NamedPipe;
+use log::info;
use winapi::shared::winerror::WAIT_TIMEOUT;
use winapi::um::synchapi::WaitForSingleObject;
use winapi::um::winbase::WAIT_OBJECT_0;
use crate::config::Config;
-use crate::display::OnResize;
+use crate::event::OnResize;
use crate::term::SizeInfo;
use crate::tty::{EventedPty, EventedReadWrite};
@@ -83,7 +84,7 @@ impl<'a> Pty<'a> {
}
}
-pub fn new<'a>(config: &Config, size: &SizeInfo, window_id: Option<usize>) -> Pty<'a> {
+pub fn new<'a, C>(config: &Config<C>, size: &SizeInfo, window_id: Option<usize>) -> Pty<'a> {
if let Some(pty) = conpty::new(config, size, window_id) {
info!("Using Conpty agent");
IS_CONPTY.store(true, Ordering::Relaxed);
diff --git a/alacritty_terminal/src/tty/windows/winpty.rs b/alacritty_terminal/src/tty/windows/winpty.rs
index 8795ca6d..db397ad9 100644
--- a/alacritty_terminal/src/tty/windows/winpty.rs
+++ b/alacritty_terminal/src/tty/windows/winpty.rs
@@ -22,13 +22,13 @@ use std::sync::Arc;
use std::u16;
use dunce::canonicalize;
+use log::info;
use mio_named_pipes::NamedPipe;
use winapi::um::winbase::FILE_FLAG_OVERLAPPED;
-use winpty::Config as WinptyConfig;
-use winpty::{ConfigFlags, MouseMode, SpawnConfig, SpawnFlags, Winpty};
+use winpty::{Config as WinptyConfig, ConfigFlags, MouseMode, SpawnConfig, SpawnFlags, Winpty};
use crate::config::{Config, Shell};
-use crate::display::OnResize;
+use crate::event::OnResize;
use crate::term::SizeInfo;
// We store a raw pointer because we need mutable access to call
@@ -75,7 +75,7 @@ impl<'a> Drop for Agent<'a> {
/// This is a placeholder value until we see how often long responses happen
const AGENT_TIMEOUT: u32 = 10000;
-pub fn new<'a>(config: &Config, size: &SizeInfo, _window_id: Option<usize>) -> Pty<'a> {
+pub fn new<'a, C>(config: &Config<C>, size: &SizeInfo, _window_id: Option<usize>) -> Pty<'a> {
// Create config
let mut wconfig = WinptyConfig::new(ConfigFlags::empty()).unwrap();
diff --git a/alacritty_terminal/src/url.rs b/alacritty_terminal/src/url.rs
index 292f8358..9e8ecd4b 100644
--- a/alacritty_terminal/src/url.rs
+++ b/alacritty_terminal/src/url.rs
@@ -29,7 +29,7 @@ impl Url {
}
/// Convert URLs bounding points to linear indices
- pub fn linear_bounds(&self, terminal: &Term) -> RangeInclusive<Linear> {
+ pub fn linear_bounds<T>(&self, terminal: &Term<T>) -> RangeInclusive<Linear> {
let mut start = self.start;
let mut end = self.end;
diff --git a/alacritty_terminal/src/window.rs b/alacritty_terminal/src/window.rs
deleted file mode 100644
index f18fda3a..00000000
--- a/alacritty_terminal/src/window.rs
+++ /dev/null
@@ -1,473 +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::Display;
-
-use glutin::dpi::{LogicalPosition, LogicalSize, PhysicalSize};
-#[cfg(target_os = "macos")]
-use glutin::os::macos::WindowExt;
-#[cfg(not(any(target_os = "macos", windows)))]
-use glutin::os::unix::{EventsLoopExt, WindowExt};
-#[cfg(not(target_os = "macos"))]
-use glutin::Icon;
-#[cfg(not(any(target_os = "macos", windows)))]
-use glutin::Window as GlutinWindow;
-use glutin::{
- self, ContextBuilder, ControlFlow, Event, EventsLoop, MouseCursor, PossiblyCurrent,
- WindowBuilder,
-};
-#[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 crate::config::{Config, Decorations, StartupMode, WindowConfig};
-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");
-
-/// Default Alacritty name, used for window title and class.
-pub const DEFAULT_NAME: &str = "Alacritty";
-
-/// Window errors
-#[derive(Debug)]
-pub enum Error {
- /// Error creating the window
- ContextCreation(glutin::CreationError),
-
- /// Error manipulating the rendering context
- Context(glutin::ContextError),
-}
-
-/// Result of fallible operations concerning a Window.
-type Result<T> = ::std::result::Result<T, Error>;
-
-/// 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 {
- event_loop: EventsLoop,
- windowed_context: glutin::WindowedContext<PossiblyCurrent>,
- mouse_visible: bool,
-
- /// Keep track of the current mouse cursor to avoid unnecessarily changing it
- current_mouse_cursor: MouseCursor,
-
- /// Whether or not the window is the focused window.
- pub is_focused: bool,
-}
-
-/// Threadsafe APIs for the window
-pub struct Proxy {
- inner: glutin::EventsLoopProxy,
-}
-
-/// Information about where the window is being displayed
-///
-/// Useful for subsystems like the font rasterized which depend on DPI and scale
-/// factor.
-pub struct DeviceProperties {
- /// Scale factor for pixels <-> points.
- ///
- /// This will be 1. on standard displays and may have a different value on
- /// hidpi displays.
- pub scale_factor: f64,
-}
-
-impl ::std::error::Error for Error {
- fn cause(&self) -> Option<&dyn (::std::error::Error)> {
- match *self {
- Error::ContextCreation(ref err) => Some(err),
- Error::Context(ref err) => Some(err),
- }
- }
-
- fn description(&self) -> &str {
- match *self {
- Error::ContextCreation(ref _err) => "Error creating gl context",
- Error::Context(ref _err) => "Error operating on render context",
- }
- }
-}
-
-impl Display for Error {
- fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
- match *self {
- Error::ContextCreation(ref err) => write!(f, "Error creating GL context; {}", err),
- Error::Context(ref err) => write!(f, "Error operating on render context; {}", err),
- }
- }
-}
-
-impl From<glutin::CreationError> for Error {
- fn from(val: glutin::CreationError) -> Error {
- Error::ContextCreation(val)
- }
-}
-
-impl From<glutin::ContextError> for Error {
- fn from(val: glutin::ContextError) -> Error {
- Error::Context(val)
- }
-}
-
-fn create_gl_window(
- mut window: WindowBuilder,
- event_loop: &EventsLoop,
- srgb: bool,
- dimensions: Option<LogicalSize>,
-) -> Result<glutin::WindowedContext<PossiblyCurrent>> {
- if let Some(dimensions) = dimensions {
- window = window.with_dimensions(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(|(_, e)| e)? };
-
- Ok(windowed_context)
-}
-
-impl Window {
- /// Create a new window
- ///
- /// This creates a window and fully initializes a window.
- pub fn new(
- event_loop: EventsLoop,
- config: &Config,
- dimensions: Option<LogicalSize>,
- ) -> Result<Window> {
- let title = config.window.title.as_ref().map_or(DEFAULT_NAME, |t| t);
-
- let window_builder = Window::get_platform_window(title, &config.window);
- let windowed_context =
- create_gl_window(window_builder.clone(), &event_loop, false, dimensions)
- .or_else(|_| create_gl_window(window_builder, &event_loop, true, dimensions))?;
- let window = windowed_context.window();
-
- // Text cursor
- window.set_cursor(MouseCursor::Text);
-
- // 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(window, parent_window_id);
- }
- }
- }
-
- let window = Window {
- event_loop,
- current_mouse_cursor: MouseCursor::Default,
- windowed_context,
- mouse_visible: true,
- is_focused: false,
- };
-
- Ok(window)
- }
-
- /// Get some properties about the device
- ///
- /// Some window properties are provided since subsystems like font
- /// rasterization depend on DPI and scale factor.
- pub fn device_properties(&self) -> DeviceProperties {
- DeviceProperties { scale_factor: self.window().get_hidpi_factor() }
- }
-
- pub fn inner_size_pixels(&self) -> Option<LogicalSize> {
- self.window().get_inner_size()
- }
-
- pub fn set_inner_size(&mut self, size: LogicalSize) {
- self.window().set_inner_size(size);
- }
-
- #[inline]
- pub fn hidpi_factor(&self) -> f64 {
- self.window().get_hidpi_factor()
- }
-
- #[inline]
- pub fn create_window_proxy(&self) -> Proxy {
- Proxy { inner: self.event_loop.create_proxy() }
- }
-
- #[inline]
- pub fn swap_buffers(&self) -> Result<()> {
- self.windowed_context.swap_buffers().map_err(From::from)
- }
-
- /// Poll for any available events
- #[inline]
- pub fn poll_events<F>(&mut self, func: F)
- where
- F: FnMut(Event),
- {
- self.event_loop.poll_events(func);
- }
-
- #[inline]
- pub fn resize(&self, size: PhysicalSize) {
- self.windowed_context.resize(size);
- }
-
- /// Show window
- #[inline]
- pub fn show(&self) {
- self.window().show();
- }
-
- /// Block waiting for events
- #[inline]
- pub fn wait_events<F>(&mut self, func: F)
- where
- F: FnMut(Event) -> ControlFlow,
- {
- self.event_loop.run_forever(func);
- }
-
- /// 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: MouseCursor) {
- if cursor != self.current_mouse_cursor {
- self.current_mouse_cursor = cursor;
- self.window().set_cursor(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().hide_cursor(!visible);
- }
- }
-
- #[cfg(not(any(target_os = "macos", windows)))]
- pub fn get_platform_window(title: &str, window_config: &WindowConfig) -> WindowBuilder {
- use glutin::os::unix::WindowBuilderExt;
-
- let decorations = match window_config.decorations {
- Decorations::None => false,
- _ => true,
- };
-
- let icon = Icon::from_bytes_with_format(WINDOW_ICON, ImageFormat::ICO);
-
- let class = &window_config.class;
-
- let mut builder = WindowBuilder::new()
- .with_title(title)
- .with_visibility(false)
- .with_transparency(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 icon = Icon::from_bytes_with_format(WINDOW_ICON, ImageFormat::ICO);
-
- WindowBuilder::new()
- .with_title(title)
- .with_visibility(cfg!(windows))
- .with_decorations(decorations)
- .with_transparency(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 {
- use glutin::os::macos::WindowBuilderExt;
-
- let window = WindowBuilder::new()
- .with_title(title)
- .with_visibility(false)
- .with_transparency(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) {
- self.window().request_user_attention(is_urgent);
- }
-
- #[cfg(windows)]
- pub fn set_urgent(&self, _is_urgent: bool) {}
-
- pub fn set_ime_spot(&self, pos: LogicalPosition) {
- self.window().set_ime_spot(pos);
- }
-
- pub fn set_position(&self, pos: LogicalPosition) {
- self.window().set_position(pos);
- }
-
- #[cfg(not(any(target_os = "macos", target_os = "windows")))]
- pub fn get_window_id(&self) -> Option<usize> {
- match self.window().get_xlib_window() {
- Some(xlib_window) => Some(xlib_window as usize),
- None => None,
- }
- }
-
- #[cfg(not(any(target_os = "macos", target_os = "windows")))]
- pub fn is_x11(&self) -> bool {
- self.event_loop.is_x11()
- }
-
- #[cfg(any(target_os = "macos", target_os = "windows"))]
- pub fn get_window_id(&self) -> Option<usize> {
- None
- }
-
- /// Hide the window
- pub fn hide(&self) {
- self.window().hide();
- }
-
- /// Fullscreens the window on the current monitor.
- pub fn set_fullscreen(&self, fullscreen: bool) {
- let glutin_window = self.window();
- if fullscreen {
- let current_monitor = glutin_window.get_current_monitor();
- glutin_window.set_fullscreen(Some(current_monitor));
- } else {
- glutin_window.set_fullscreen(None);
- }
- }
-
- pub fn set_maximized(&self, maximized: bool) {
- self.window().set_maximized(maximized);
- }
-
- #[cfg(target_os = "macos")]
- pub fn set_simple_fullscreen(&self, fullscreen: bool) {
- self.window().set_simple_fullscreen(fullscreen);
- }
-
- #[cfg(not(any(target_os = "macos", target_os = "windows")))]
- pub fn get_wayland_display(&self) -> Option<*mut c_void> {
- self.window().get_wayland_display()
- }
-
- fn window(&self) -> &glutin::Window {
- self.windowed_context.window()
- }
-}
-
-#[cfg(not(any(target_os = "macos", windows)))]
-fn x_embed_window(window: &GlutinWindow, parent_id: u64) {
- let (xlib_display, xlib_window) = match (window.get_xlib_display(), window.get_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 {
- die!("Could not embed into specified window.");
-}
-
-impl Proxy {
- /// Wakes up the event loop of the window
- ///
- /// This is useful for triggering a draw when the renderer would otherwise
- /// be waiting on user input.
- pub fn wakeup_event_loop(&self) {
- self.inner.wakeup().unwrap();
- }
-}