From 78953e4f7e89d0e90b8e1981bf76b03df16eded3 Mon Sep 17 00:00:00 2001 From: Richard Steinmetz Date: Thu, 8 Apr 2021 22:29:47 +0200 Subject: Fix automatic scrolling on resize --- alacritty_terminal/src/grid/resize.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/alacritty_terminal/src/grid/resize.rs b/alacritty_terminal/src/grid/resize.rs index 10bc51f9..882c0c90 100644 --- a/alacritty_terminal/src/grid/resize.rs +++ b/alacritty_terminal/src/grid/resize.rs @@ -187,7 +187,7 @@ impl Grid { cursor_line_delta += line_delta.0 as usize; } else if row.is_clear() { - if i <= self.display_offset { + if i < self.display_offset { // Since we removed a line, rotate down the viewport. self.display_offset = self.display_offset.saturating_sub(1); } @@ -355,7 +355,7 @@ impl Grid { } row = Row::from_vec(wrapped, occ); - if i <= self.display_offset { + if i < self.display_offset { // Since we added a new line, rotate up the viewport. self.display_offset += 1; } -- cgit From 40bcdb11335cc49d4d42694b953be746cb383cb9 Mon Sep 17 00:00:00 2001 From: Kam Kudla Date: Thu, 8 Apr 2021 14:01:47 -0700 Subject: Add hide other windows binding on macOS Fixes #3697. --- CHANGELOG.md | 1 + alacritty.yml | 39 ++++++++++++++++++++------------------- alacritty/src/config/bindings.rs | 5 +++++ alacritty/src/input.rs | 2 ++ 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39b3ebad..2fa883bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - IME composition preview not appearing on Windows - Synchronized terminal updates using `DCS = 1 s ST`/`DCS = 2 s ST` - Regex terminal hints ([see features.md](./docs/features.md#hints)) +- macOS keybinding (cmd+alt+H) hiding all windows other than Alacritty ### Fixed diff --git a/alacritty.yml b/alacritty.yml index 3df946a9..6cca5b76 100644 --- a/alacritty.yml +++ b/alacritty.yml @@ -821,25 +821,26 @@ #- { key: Return, mods: Alt, action: ToggleFullscreen } # (macOS only) - #- { key: K, mods: Command, mode: ~Vi|~Search, chars: "\x0c" } - #- { key: K, mods: Command, mode: ~Vi|~Search, action: ClearHistory } - #- { key: Key0, mods: Command, action: ResetFontSize } - #- { key: Equals, mods: Command, action: IncreaseFontSize } - #- { key: Plus, mods: Command, action: IncreaseFontSize } - #- { key: NumpadAdd, mods: Command, action: IncreaseFontSize } - #- { key: Minus, mods: Command, action: DecreaseFontSize } - #- { key: NumpadSubtract, mods: Command, action: DecreaseFontSize } - #- { key: V, mods: Command, action: Paste } - #- { key: C, mods: Command, action: Copy } - #- { key: C, mods: Command, mode: Vi|~Search, action: ClearSelection } - #- { key: H, mods: Command, action: Hide } - #- { key: M, mods: Command, action: Minimize } - #- { key: Q, mods: Command, action: Quit } - #- { key: W, mods: Command, action: Quit } - #- { key: N, mods: Command, action: SpawnNewInstance } - #- { key: F, mods: Command|Control, action: ToggleFullscreen } - #- { key: F, mods: Command, mode: ~Search, action: SearchForward } - #- { key: B, mods: Command, mode: ~Search, action: SearchBackward } + #- { key: K, mods: Command, mode: ~Vi|~Search, chars: "\x0c" } + #- { key: K, mods: Command, mode: ~Vi|~Search, action: ClearHistory } + #- { key: Key0, mods: Command, action: ResetFontSize } + #- { key: Equals, mods: Command, action: IncreaseFontSize } + #- { key: Plus, mods: Command, action: IncreaseFontSize } + #- { key: NumpadAdd, mods: Command, action: IncreaseFontSize } + #- { key: Minus, mods: Command, action: DecreaseFontSize } + #- { key: NumpadSubtract, mods: Command, action: DecreaseFontSize } + #- { key: V, mods: Command, action: Paste } + #- { key: C, mods: Command, action: Copy } + #- { key: C, mods: Command, mode: Vi|~Search, action: ClearSelection } + #- { key: H, mods: Command, action: Hide } + #- { key: H, mods: Command|Alt, action: HideOtherApplications } + #- { key: M, mods: Command, action: Minimize } + #- { key: Q, mods: Command, action: Quit } + #- { key: W, mods: Command, action: Quit } + #- { key: N, mods: Command, action: SpawnNewInstance } + #- { key: F, mods: Command|Control, action: ToggleFullscreen } + #- { key: F, mods: Command, mode: ~Search, action: SearchForward } + #- { key: B, mods: Command, mode: ~Search, action: SearchBackward } #debug: # Display the time it takes to redraw each frame. diff --git a/alacritty/src/config/bindings.rs b/alacritty/src/config/bindings.rs index 732875db..91cc09d6 100644 --- a/alacritty/src/config/bindings.rs +++ b/alacritty/src/config/bindings.rs @@ -161,6 +161,10 @@ pub enum Action { /// Hide the Alacritty window. Hide, + /// Hide all windows other than Alacritty on macOS. + #[cfg(target_os = "macos")] + HideOtherApplications, + /// Minimize the Alacritty window. Minimize, @@ -685,6 +689,7 @@ pub fn platform_key_bindings() -> Vec { C, ModifiersState::LOGO; Action::Copy; C, ModifiersState::LOGO, +BindingMode::VI, ~BindingMode::SEARCH; Action::ClearSelection; H, ModifiersState::LOGO; Action::Hide; + H, ModifiersState::LOGO | ModifiersState::ALT; Action::HideOtherApplications; M, ModifiersState::LOGO; Action::Minimize; Q, ModifiersState::LOGO; Action::Quit; W, ModifiersState::LOGO; Action::Quit; diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index a66511cf..38a346d9 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -255,6 +255,8 @@ impl Execute for Action { Action::ToggleSimpleFullscreen => ctx.window_mut().toggle_simple_fullscreen(), #[cfg(target_os = "macos")] Action::Hide => ctx.event_loop().hide_application(), + #[cfg(target_os = "macos")] + Action::HideOtherApplications => ctx.event_loop().hide_other_applications(), #[cfg(not(target_os = "macos"))] Action::Hide => ctx.window().set_visible(false), Action::Minimize => ctx.window().set_minimized(true), -- cgit From 96fc9ecc9a62c8a766da745d05fbe60e6c2e1efe Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 13 Apr 2021 03:24:42 +0000 Subject: Add vi/mouse hint highlighting support This patch removes the old url highlighting code and replaces it with a new implementation making use of hints as sources for finding matches in the terminal. --- Cargo.lock | 9 +- alacritty.yml | 58 +++---- alacritty/Cargo.toml | 1 - alacritty/src/config/bindings.rs | 2 +- alacritty/src/config/mouse.rs | 38 ----- alacritty/src/config/ui_config.rs | 99 +++++++++--- alacritty/src/display/content.rs | 15 +- alacritty/src/display/hint.rs | 179 ++++++++++++++++++++- alacritty/src/display/mod.rs | 122 +++++++++----- alacritty/src/event.rs | 127 ++++++--------- alacritty/src/input.rs | 186 ++++++++-------------- alacritty/src/main.rs | 1 - alacritty/src/url.rs | 288 ---------------------------------- alacritty_terminal/src/term/search.rs | 4 +- docs/features.md | 12 +- 15 files changed, 494 insertions(+), 647 deletions(-) delete mode 100644 alacritty/src/url.rs diff --git a/Cargo.lock b/Cargo.lock index 505b64d2..5b77d602 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "ab_glyph_rasterizer" version = "0.1.4" @@ -40,7 +42,6 @@ dependencies = [ "serde_yaml", "time", "unicode-width", - "urlocator", "wayland-client", "winapi 0.3.9", "x11-dl", @@ -1664,12 +1665,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" -[[package]] -name = "urlocator" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e39a4f106dafb0a748b951494667a44e62b55fd7942b4fc12706d63cc535a0" - [[package]] name = "utf8parse" version = "0.2.0" diff --git a/alacritty.yml b/alacritty.yml index 6cca5b76..840c4ed9 100644 --- a/alacritty.yml +++ b/alacritty.yml @@ -445,29 +445,6 @@ # If this is `true`, the cursor is temporarily hidden when typing. #hide_when_typing: false - #url: - # URL launcher - # - # This program is executed when clicking on a text which is recognized as a - # URL. The URL is always added to the command as the last parameter. - # - # When set to `launcher: None`, URL launching will be disabled completely. - # - # Default: - # - (macOS) open - # - (Linux/BSD) xdg-open - # - (Windows) cmd /c start "" - #launcher: - # program: xdg-open - # args: [] - - # URL modifiers - # - # These are the modifiers that need to be held down for opening URLs when - # clicking on them. The available modifiers are documented in the key - # binding section. - #modifiers: None - # Regex hints # # Terminal hints can be used to find text in the visible part of the terminal @@ -478,10 +455,18 @@ # List with all available hints # - # Each hint takes a `regex`, `binding` and either a `command` or an `action`. + # Each hint must have a `regex` and either an `action` or a `command` field. + # The fields `mouse`, `binding` and `post_processing` are optional. + # + # The fields `command`, `binding.key`, `binding.mods` and `mouse.mods` accept + # the same values as they do in the `key_bindings` section. + # + # The `mouse.enabled` field controls if the hint should be underlined while + # the mouse with all `mouse.mods` keys held or the vi mode cursor is above it. # - # The fields `command`, `binding.key` and `binding.mods` accept the same - # values as they do in the `key_bindings` section. + # If the `post_processing` field is set to `true`, heuristics will be used to + # shorten the match if there are characters likely not to be part of the hint + # (e.g. a trailing `.`). This is most useful for URIs. # # Values for `action`: # - Copy @@ -490,16 +475,17 @@ # Paste the hint's text to the terminal or search. # - Select # Select the hint's text. - # - # Example - # - # enabled: - # - regex: "alacritty/alacritty#\\d*" - # command: firefox - # binding: - # key: G - # mods: Control|Shift - #enabled: [] + #enabled: + # - regex: "(mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)\ + # [^\u0000-\u001F\u007F-\u009F<>\" {-}\\^⟨⟩`]+" + # command: xdg-open + # post_processing: true + # mouse: + # enabled: true + # mods: None + # binding: + # key: U + # mods: Control|Shift # Mouse bindings # diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index febfbdc7..b2fcbe9d 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -29,7 +29,6 @@ glutin = { version = "0.26.0", default-features = false, features = ["serde"] } notify = "4" parking_lot = "0.11.0" crossfont = { version = "0.2.0", features = ["force_system_fontconfig"] } -urlocator = "0.1.3" copypasta = { version = "0.7.0", default-features = false } libc = "0.2" unicode-width = "0.1" diff --git a/alacritty/src/config/bindings.rs b/alacritty/src/config/bindings.rs index 91cc09d6..4e7c2fcb 100644 --- a/alacritty/src/config/bindings.rs +++ b/alacritty/src/config/bindings.rs @@ -1162,7 +1162,7 @@ impl<'a> de::Deserialize<'a> for ModsWrapper { type Value = ModsWrapper; fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("a subset of Shift|Control|Super|Command|Alt|Option") + f.write_str("None or a subset of Shift|Control|Super|Command|Alt|Option") } fn visit_str(self, value: &str) -> Result diff --git a/alacritty/src/config/mouse.rs b/alacritty/src/config/mouse.rs index 2aa7557c..aed1ab04 100644 --- a/alacritty/src/config/mouse.rs +++ b/alacritty/src/config/mouse.rs @@ -1,50 +1,12 @@ use std::time::Duration; -use glutin::event::ModifiersState; - use alacritty_config_derive::ConfigDeserialize; -use alacritty_terminal::config::Program; - -use crate::config::bindings::ModsWrapper; #[derive(ConfigDeserialize, Default, Clone, Debug, PartialEq, Eq)] pub struct Mouse { pub double_click: ClickHandler, pub triple_click: ClickHandler, pub hide_when_typing: bool, - pub url: Url, -} - -#[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)] -pub struct Url { - /// Program for opening links. - pub launcher: Option, - - /// Modifier used to open links. - modifiers: ModsWrapper, -} - -impl Url { - pub fn mods(&self) -> ModifiersState { - self.modifiers.into_inner() - } -} - -impl Default for Url { - fn default() -> Url { - Url { - #[cfg(not(any(target_os = "macos", windows)))] - launcher: Some(Program::Just(String::from("xdg-open"))), - #[cfg(target_os = "macos")] - launcher: Some(Program::Just(String::from("open"))), - #[cfg(windows)] - launcher: Some(Program::WithArgs { - program: String::from("cmd"), - args: vec!["/c".to_string(), "start".to_string(), "".to_string()], - }), - modifiers: Default::default(), - } - } } #[derive(ConfigDeserialize, Clone, Debug, PartialEq, Eq)] diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs index 3cd2ad88..ff013d57 100644 --- a/alacritty/src/config/ui_config.rs +++ b/alacritty/src/config/ui_config.rs @@ -21,6 +21,11 @@ use crate::config::font::Font; use crate::config::mouse::Mouse; use crate::config::window::WindowConfig; +/// Regex used for the default URL hint. +#[rustfmt::skip] +const URL_REGEX: &str = "(mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)\ + [^\u{0000}-\u{001F}\u{007F}-\u{009F}<>\" {-}\\^⟨⟩`]+"; + #[derive(ConfigDeserialize, Debug, PartialEq)] pub struct UiConfig { /// Font configuration. @@ -90,13 +95,18 @@ impl Default for UiConfig { impl UiConfig { /// Generate key bindings for all keyboard hints. pub fn generate_hint_bindings(&mut self) { - for hint in self.hints.enabled.drain(..) { + for hint in &self.hints.enabled { + let binding = match hint.binding { + Some(binding) => binding, + None => continue, + }; + let binding = KeyBinding { - trigger: hint.binding.key, - mods: hint.binding.mods.0, + trigger: binding.key, + mods: binding.mods.0, mode: BindingMode::empty(), notmode: BindingMode::empty(), - action: Action::Hint(hint), + action: Action::Hint(hint.clone()), }; self.key_bindings.0.push(binding); @@ -197,13 +207,42 @@ pub struct Delta { } /// Regex terminal hints. -#[derive(ConfigDeserialize, Default, Debug, PartialEq, Eq)] +#[derive(ConfigDeserialize, Debug, PartialEq, Eq)] pub struct Hints { /// Characters for the hint labels. alphabet: HintsAlphabet, /// All configured terminal hints. - enabled: Vec, + pub enabled: Vec, +} + +impl Default for Hints { + fn default() -> Self { + // Add URL hint by default when no other hint is present. + let pattern = LazyRegexVariant::Pattern(String::from(URL_REGEX)); + let regex = LazyRegex(Rc::new(RefCell::new(pattern))); + + #[cfg(not(any(target_os = "macos", windows)))] + let action = HintAction::Command(Program::Just(String::from("xdg-open"))); + #[cfg(target_os = "macos")] + let action = HintAction::Command(Program::Just(String::from("open"))); + #[cfg(windows)] + let action = HintAction::Command(Program::WithArgs { + program: String::from("cmd"), + args: vec!["/c".to_string(), "start".to_string(), "".to_string()], + }); + + Self { + enabled: vec![Hint { + regex, + action, + post_processing: true, + mouse: Some(HintMouse { enabled: true, mods: Default::default() }), + binding: Default::default(), + }], + alphabet: Default::default(), + } + } } impl Hints { @@ -271,33 +310,51 @@ pub enum HintAction { /// Hint configuration. #[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub struct Hint { + /// Regex for finding matches. + pub regex: LazyRegex, + /// Action executed when this hint is triggered. #[serde(flatten)] pub action: HintAction, - /// Regex for finding matches. - pub regex: LazyRegex, + /// Hint text post processing. + #[serde(default)] + pub post_processing: bool, + + /// Hint mouse highlighting. + pub mouse: Option, /// Binding required to search for this hint. - binding: HintBinding, + binding: Option, } /// Binding for triggering a keyboard hint. #[derive(Deserialize, Copy, Clone, Debug, PartialEq, Eq)] pub struct HintBinding { pub key: Key, + #[serde(default)] + pub mods: ModsWrapper, +} + +/// Hint mouse highlighting. +#[derive(ConfigDeserialize, Default, Copy, Clone, Debug, PartialEq, Eq)] +pub struct HintMouse { + /// Hint mouse highlighting availability. + pub enabled: bool, + + /// Required mouse modifiers for hint highlighting. pub mods: ModsWrapper, } /// Lazy regex with interior mutability. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct LazyRegex(Rc>); impl LazyRegex { /// Execute a function with the compiled regex DFAs as parameter. - pub fn with_compiled(&self, f: F) -> T + pub fn with_compiled(&self, mut f: F) -> T where - F: Fn(&RegexSearch) -> T, + F: FnMut(&RegexSearch) -> T, { f(self.0.borrow_mut().compiled()) } @@ -313,14 +370,6 @@ impl<'de> Deserialize<'de> for LazyRegex { } } -/// Implement placeholder to allow derive upstream, since we never need it for this struct itself. -impl PartialEq for LazyRegex { - fn eq(&self, _other: &Self) -> bool { - false - } -} -impl Eq for LazyRegex {} - /// Regex which is compiled on demand, to avoid expensive computations at startup. #[derive(Clone, Debug)] pub enum LazyRegexVariant { @@ -357,3 +406,13 @@ impl LazyRegexVariant { } } } + +impl PartialEq for LazyRegexVariant { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Pattern(regex), Self::Pattern(other_regex)) => regex == other_regex, + _ => false, + } + } +} +impl Eq for LazyRegexVariant {} diff --git a/alacritty/src/display/content.rs b/alacritty/src/display/content.rs index e356f1f3..23a2f8a4 100644 --- a/alacritty/src/display/content.rs +++ b/alacritty/src/display/content.rs @@ -18,15 +18,12 @@ use alacritty_terminal::term::{ use crate::config::ui_config::UiConfig; use crate::display::color::{List, DIM_FACTOR}; use crate::display::hint::HintState; -use crate::display::Display; +use crate::display::{self, Display, MAX_SEARCH_LINES}; use crate::event::SearchState; /// Minimum contrast between a fixed cursor color and the cell's background. pub const MIN_CURSOR_CONTRAST: f64 = 1.5; -/// Maximum number of linewraps followed outside of the viewport during search highlighting. -const MAX_SEARCH_LINES: usize = 100; - /// Renderable terminal content. /// /// This provides the terminal cursor and an iterator over all non-empty cells. @@ -138,8 +135,8 @@ impl<'a> RenderableContent<'a> { // Convert cursor point to viewport position. let cursor_point = self.terminal_cursor.point; - let line = (cursor_point.line + self.terminal_content.display_offset as i32).0 as usize; - let point = Point::new(line, cursor_point.column); + let display_offset = self.terminal_content.display_offset; + let point = display::point_to_viewport(display_offset, cursor_point).unwrap(); Some(RenderableCursor { shape: self.terminal_cursor.shape, @@ -258,8 +255,8 @@ impl RenderableCell { // Convert cell point to viewport position. let cell_point = cell.point; - let line = (cell_point.line + content.terminal_content.display_offset as i32).0 as usize; - let point = Point::new(line, cell_point.column); + let display_offset = content.terminal_content.display_offset; + let point = display::point_to_viewport(display_offset, cell_point).unwrap(); RenderableCell { zerowidth: cell.zerowidth().map(|zerowidth| zerowidth.to_vec()), @@ -441,7 +438,7 @@ impl<'a> From<&'a HintState> for Hint<'a> { /// Wrapper for finding visible regex matches. #[derive(Default, Clone)] -pub struct RegexMatches(Vec>); +pub struct RegexMatches(pub Vec>); impl RegexMatches { /// Find all visible matches. diff --git a/alacritty/src/display/hint.rs b/alacritty/src/display/hint.rs index 2a5e9c65..f9ab90d4 100644 --- a/alacritty/src/display/hint.rs +++ b/alacritty/src/display/hint.rs @@ -1,8 +1,16 @@ -use alacritty_terminal::term::search::Match; +use std::cmp::{max, min}; + +use glutin::event::ModifiersState; + +use alacritty_terminal::grid::BidirectionalIterator; +use alacritty_terminal::index::{Boundary, Point}; +use alacritty_terminal::term::search::{Match, RegexSearch}; use alacritty_terminal::term::Term; use crate::config::ui_config::{Hint, HintAction}; +use crate::config::Config; use crate::display::content::RegexMatches; +use crate::display::MAX_SEARCH_LINES; /// Percentage of characters in the hints alphabet used for the last character. const HINT_SPLIT_PERCENTAGE: f32 = 0.5; @@ -63,7 +71,20 @@ impl HintState { }; // Find visible matches. - self.matches = hint.regex.with_compiled(|regex| RegexMatches::new(term, regex)); + self.matches.0 = hint.regex.with_compiled(|regex| { + let mut matches = RegexMatches::new(term, regex); + + // Apply post-processing and search for sub-matches if necessary. + if hint.post_processing { + matches + .drain(..) + .map(|rm| HintPostProcessor::new(term, regex, rm).collect::>()) + .flatten() + .collect() + } else { + matches.0 + } + }); // Cancel highlight with no visible matches. if self.matches.is_empty() { @@ -144,6 +165,7 @@ impl HintState { } /// Hint match which was selected by the user. +#[derive(Clone)] pub struct HintMatch { /// Action for handling the text. pub action: HintAction, @@ -217,6 +239,159 @@ impl HintLabels { } } +/// Check if there is a hint highlighted at the specified point. +pub fn highlighted_at( + term: &Term, + config: &Config, + point: Point, + mouse_mods: ModifiersState, +) -> Option { + config.ui_config.hints.enabled.iter().find_map(|hint| { + // Check if all required modifiers are pressed. + if hint.mouse.map_or(true, |mouse| !mouse.enabled || !mouse_mods.contains(mouse.mods.0)) { + return None; + } + + hint.regex.with_compiled(|regex| { + // Setup search boundaries. + let mut start = term.line_search_left(point); + start.line = max(start.line, point.line - MAX_SEARCH_LINES); + let mut end = term.line_search_right(point); + end.line = min(end.line, point.line + MAX_SEARCH_LINES); + + // Function to verify if the specified point is inside the match. + let at_point = |rm: &Match| *rm.start() <= point && *rm.end() >= point; + + // Check if there's any match at the specified point. + let regex_match = term.regex_search_right(regex, start, end).filter(at_point)?; + + // Apply post-processing and search for sub-matches if necessary. + let regex_match = if hint.post_processing { + HintPostProcessor::new(term, regex, regex_match).find(at_point) + } else { + Some(regex_match) + }; + + regex_match.map(|bounds| HintMatch { action: hint.action.clone(), bounds }) + }) + }) +} + +/// Iterator over all post-processed matches inside an existing hint match. +struct HintPostProcessor<'a, T> { + /// Regex search DFAs. + regex: &'a RegexSearch, + + /// Terminal reference. + term: &'a Term, + + /// Next hint match in the iterator. + next_match: Option, + + /// Start point for the next search. + start: Point, + + /// End point for the hint match iterator. + end: Point, +} + +impl<'a, T> HintPostProcessor<'a, T> { + /// Create a new iterator for an unprocessed match. + fn new(term: &'a Term, regex: &'a RegexSearch, regex_match: Match) -> Self { + let end = *regex_match.end(); + let mut post_processor = Self { next_match: None, start: end, end, term, regex }; + + // Post-process the first hint match. + let next_match = post_processor.hint_post_processing(®ex_match); + post_processor.start = next_match.end().add(term, Boundary::Grid, 1); + post_processor.next_match = Some(next_match); + + post_processor + } + + /// Apply some hint post processing heuristics. + /// + /// This will check the end of the hint and make it shorter if certain characters are determined + /// to be unlikely to be intentionally part of the hint. + /// + /// This is most useful for identifying URLs appropriately. + fn hint_post_processing(&self, regex_match: &Match) -> Match { + let mut iter = self.term.grid().iter_from(*regex_match.start()); + + let mut c = iter.cell().c; + + // Truncate uneven number of brackets. + let end = *regex_match.end(); + let mut open_parents = 0; + let mut open_brackets = 0; + loop { + match c { + '(' => open_parents += 1, + '[' => open_brackets += 1, + ')' => { + if open_parents == 0 { + iter.prev(); + break; + } else { + open_parents -= 1; + } + }, + ']' => { + if open_brackets == 0 { + iter.prev(); + break; + } else { + open_brackets -= 1; + } + }, + _ => (), + } + + if iter.point() == end { + break; + } + + match iter.next() { + Some(indexed) => c = indexed.cell.c, + None => break, + } + } + + // Truncate trailing characters which are likely to be delimiters. + let start = *regex_match.start(); + while iter.point() != start { + if !matches!(c, '.' | ',' | ':' | ';' | '?' | '!' | '(' | '[' | '\'') { + break; + } + + match iter.prev() { + Some(indexed) => c = indexed.cell.c, + None => break, + } + } + + start..=iter.point() + } +} + +impl<'a, T> Iterator for HintPostProcessor<'a, T> { + type Item = Match; + + fn next(&mut self) -> Option { + let next_match = self.next_match.take()?; + + if self.start <= self.end { + if let Some(rm) = self.term.regex_search_right(self.regex, self.start, self.end) { + let regex_match = self.hint_post_processing(&rm); + self.start = regex_match.end().add(self.term, Boundary::Grid, 1); + self.next_match = Some(regex_match); + } + } + + Some(next_match) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs index 78220b59..6e40e35c 100644 --- a/alacritty/src/display/mod.rs +++ b/alacritty/src/display/mod.rs @@ -2,6 +2,7 @@ //! GPU drawing. use std::cmp::min; +use std::convert::TryFrom; use std::f64; use std::fmt::{self, Formatter}; #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] @@ -27,6 +28,7 @@ use alacritty_terminal::event::{EventListener, OnResize}; use alacritty_terminal::grid::Dimensions as _; use alacritty_terminal::index::{Column, Direction, Line, Point}; use alacritty_terminal::selection::Selection; +use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::{SizeInfo, Term, TermMode, MIN_COLUMNS, MIN_SCREEN_LINES}; use crate::config::font::Font; @@ -38,14 +40,13 @@ use crate::display::bell::VisualBell; use crate::display::color::List; use crate::display::content::RenderableContent; use crate::display::cursor::IntoRects; -use crate::display::hint::HintState; +use crate::display::hint::{HintMatch, HintState}; use crate::display::meter::Meter; use crate::display::window::Window; use crate::event::{Mouse, SearchState}; use crate::message_bar::{MessageBuffer, MessageType}; use crate::renderer::rects::{RenderLines, RenderRect}; use crate::renderer::{self, GlyphCache, QuadRenderer}; -use crate::url::{Url, Urls}; pub mod content; pub mod cursor; @@ -58,7 +59,13 @@ mod meter; #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] mod wayland_theme; +/// Maximum number of linewraps followed outside of the viewport during search highlighting. +pub const MAX_SEARCH_LINES: usize = 100; + +/// Label for the forward terminal search bar. const FORWARD_SEARCH_LABEL: &str = "Search: "; + +/// Label for the backward terminal search bar. const BACKWARD_SEARCH_LABEL: &str = "Backward Search: "; #[derive(Debug)] @@ -164,10 +171,12 @@ impl DisplayUpdate { pub struct Display { pub size_info: SizeInfo, pub window: Window, - pub urls: Urls, - /// Currently highlighted URL. - pub highlighted_url: Option, + /// Hint highlighted by the mouse. + pub highlighted_hint: Option, + + /// Hint highlighted by the vi mode cursor. + pub vi_highlighted_hint: Option, #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] pub wayland_event_queue: Option, @@ -331,8 +340,8 @@ impl Display { hint_state, meter: Meter::new(), size_info, - urls: Urls::new(), - highlighted_url: None, + highlighted_hint: None, + vi_highlighted_hint: None, #[cfg(not(any(target_os = "macos", windows)))] is_x11, #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] @@ -473,8 +482,6 @@ impl Display { terminal: MutexGuard<'_, Term>, message_buffer: &MessageBuffer, config: &Config, - mouse: &Mouse, - mods: ModifiersState, search_state: &SearchState, ) { // Collect renderable content before the terminal is dropped. @@ -492,10 +499,6 @@ impl Display { let metrics = self.glyph_cache.font_metrics(); let size_info = self.size_info; - let selection = !terminal.selection.as_ref().map(Selection::is_empty).unwrap_or(true); - let mouse_mode = terminal.mode().intersects(TermMode::MOUSE_MODE) - && !terminal.mode().contains(TermMode::VI); - let vi_mode = terminal.mode().contains(TermMode::VI); let vi_mode_cursor = if vi_mode { Some(terminal.vi_mode_cursor) } else { None }; @@ -507,18 +510,24 @@ impl Display { }); let mut lines = RenderLines::new(); - let mut urls = Urls::new(); // Draw grid. { let _sampler = self.meter.sampler(); let glyph_cache = &mut self.glyph_cache; + let highlighted_hint = &self.highlighted_hint; + let vi_highlighted_hint = &self.vi_highlighted_hint; self.renderer.with_api(&config.ui_config, &size_info, |mut api| { // Iterate over all non-empty cells in the grid. - for cell in grid_cells { - // Update URL underlines. - urls.update(&size_info, &cell); + for mut cell in grid_cells { + // Underline hints hovered by mouse or vi mode cursor. + let point = viewport_to_point(display_offset, cell.point); + if highlighted_hint.as_ref().map_or(false, |h| h.bounds.contains(&point)) + || vi_highlighted_hint.as_ref().map_or(false, |h| h.bounds.contains(&point)) + { + cell.flags.insert(Flags::UNDERLINE); + } // Update underline/strikeout. lines.update(&cell); @@ -531,33 +540,9 @@ impl Display { let mut rects = lines.rects(&metrics, &size_info); - // Update visible URLs. - self.urls = urls; - if let Some(url) = self.urls.highlighted(config, mouse, mods, mouse_mode, selection) { - rects.append(&mut url.rects(&metrics, &size_info)); - - self.window.set_mouse_cursor(CursorIcon::Hand); - - self.highlighted_url = Some(url); - } else if self.highlighted_url.is_some() { - self.highlighted_url = None; - - if mouse_mode { - self.window.set_mouse_cursor(CursorIcon::Default); - } else { - self.window.set_mouse_cursor(CursorIcon::Text); - } - } - if let Some(vi_mode_cursor) = vi_mode_cursor { - // Highlight URLs at the vi mode cursor position. - let vi_point = vi_mode_cursor.point; - let line = (vi_point.line + display_offset).0 as usize; - if let Some(url) = self.urls.find_at(Point::new(line, vi_point.column)) { - rects.append(&mut url.rects(&metrics, &size_info)); - } - // Indicate vi mode by showing the cursor's position in the top right corner. + let vi_point = vi_mode_cursor.point; let line = (-vi_point.line.0 + size_info.bottommost_line().0) as usize; self.draw_line_indicator(config, &size_info, total_lines, Some(vi_point), line); } else if search_state.regex().is_some() { @@ -671,6 +656,47 @@ impl Display { self.colors = List::from(&config.ui_config.colors); } + /// Update the mouse/vi mode cursor hint highlighting. + pub fn update_highlighted_hints( + &mut self, + term: &Term, + config: &Config, + mouse: &Mouse, + modifiers: ModifiersState, + ) { + // Update vi mode cursor hint. + if term.mode().contains(TermMode::VI) { + let mods = ModifiersState::all(); + let point = term.vi_mode_cursor.point; + self.vi_highlighted_hint = hint::highlighted_at(&term, config, point, mods); + } else { + self.vi_highlighted_hint = None; + } + + // Abort if mouse highlighting conditions are not met. + if !mouse.inside_text_area || !term.selection.as_ref().map_or(true, Selection::is_empty) { + self.highlighted_hint = None; + return; + } + + // Find highlighted hint at mouse position. + let point = viewport_to_point(term.grid().display_offset(), mouse.point); + let highlighted_hint = hint::highlighted_at(&term, config, point, modifiers); + + // Update cursor shape. + if highlighted_hint.is_some() { + self.window.set_mouse_cursor(CursorIcon::Hand); + } else if self.highlighted_hint.is_some() { + if term.mode().intersects(TermMode::MOUSE_MODE) && !term.mode().contains(TermMode::VI) { + self.window.set_mouse_cursor(CursorIcon::Default); + } else { + self.window.set_mouse_cursor(CursorIcon::Text); + } + } + + self.highlighted_hint = highlighted_hint; + } + /// Format search regex to account for the cursor and fullwidth characters. fn format_search(size_info: &SizeInfo, search_regex: &str, search_label: &str) -> String { // Add spacers for wide chars. @@ -782,6 +808,18 @@ impl Display { } } +/// Convert a terminal point to a viewport relative point. +pub fn point_to_viewport(display_offset: usize, point: Point) -> Option> { + let viewport_line = point.line.0 + display_offset as i32; + usize::try_from(viewport_line).ok().map(|line| Point::new(line, point.column)) +} + +/// Convert a viewport relative point to a terminal point. +pub fn viewport_to_point(display_offset: usize, point: Point) -> Point { + let line = Line(point.line as i32) - display_offset; + Point::new(line, point.column) +} + /// Calculate the cell dimensions based on font metrics. /// /// This will return a tuple of the cell width and height. diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index 341f398a..33b15928 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -44,15 +44,14 @@ use crate::clipboard::Clipboard; use crate::config::ui_config::{HintAction, HintInternalAction}; use crate::config::{self, Config}; use crate::daemon::start_daemon; -use crate::display::hint::{HintMatch, HintState}; +use crate::display::hint::HintMatch; use crate::display::window::Window; -use crate::display::{Display, DisplayUpdate}; +use crate::display::{self, Display, DisplayUpdate}; use crate::input::{self, ActionContext as _, FONT_SIZE_STEP}; #[cfg(target_os = "macos")] use crate::macos; use crate::message_bar::{Message, MessageBuffer}; use crate::scheduler::{Scheduler, TimerId}; -use crate::url::{Url, Urls}; /// Duration after the last user input until an unlimited search is performed. pub const TYPING_SEARCH_DELAY: Duration = Duration::from_millis(500); @@ -213,9 +212,8 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon } else if self.mouse().left_button_state == ElementState::Pressed || self.mouse().right_button_state == ElementState::Pressed { - let point = self.mouse().point; - let line = Line(point.line as i32) - self.terminal.grid().display_offset(); - let point = Point::new(line, point.column); + let display_offset = self.terminal.grid().display_offset(); + let point = display::viewport_to_point(display_offset, self.mouse().point); self.update_selection(point, self.mouse().cell_side); } @@ -322,13 +320,13 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon } #[inline] - fn window(&self) -> &Window { - &self.display.window + fn window(&mut self) -> &mut Window { + &mut self.display.window } #[inline] - fn window_mut(&mut self) -> &mut Window { - &mut self.display.window + fn display(&mut self) -> &mut Display { + &mut self.display } #[inline] @@ -385,30 +383,6 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon start_daemon(&alacritty, &args); } - /// Spawn URL launcher when clicking on URLs. - fn launch_url(&self, url: Url) { - if self.mouse.block_url_launcher { - return; - } - - if let Some(ref launcher) = self.config.ui_config.mouse.url.launcher { - let display_offset = self.terminal.grid().display_offset(); - let start = url.start(); - let start = Point::new(Line(start.line as i32 - display_offset as i32), start.column); - let end = url.end(); - let end = Point::new(Line(end.line as i32 - display_offset as i32), end.column); - - let mut args = launcher.args().to_vec(); - args.push(self.terminal.bounds_to_string(start, end)); - - start_daemon(launcher.program(), &args); - } - } - - fn highlighted_url(&self) -> Option<&Url> { - self.display.highlighted_url.as_ref() - } - fn change_font_size(&mut self, delta: f32) { *self.font_size = max(*self.font_size + delta, Size::new(FONT_SIZE_STEP)); let font = self.config.ui_config.font.clone().with_size(*self.font_size); @@ -645,42 +619,43 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon } } - fn hint_state(&mut self) -> &mut HintState { - &mut self.display.hint_state - } - /// Process a new character for keyboard hints. fn hint_input(&mut self, c: char) { - let action = self.display.hint_state.keyboard_input(self.terminal, c); + if let Some(hint) = self.display.hint_state.keyboard_input(self.terminal, c) { + self.mouse.block_hint_launcher = false; + self.trigger_hint(&hint); + } *self.dirty = true; + } - let HintMatch { action, bounds } = match action { - Some(action) => action, - None => return, - }; + /// Trigger a hint action. + fn trigger_hint(&mut self, hint: &HintMatch) { + if self.mouse.block_hint_launcher { + return; + } - match action { + match &hint.action { // Launch an external program. HintAction::Command(command) => { - let text = self.terminal.bounds_to_string(*bounds.start(), *bounds.end()); + let text = self.terminal.bounds_to_string(*hint.bounds.start(), *hint.bounds.end()); let mut args = command.args().to_vec(); args.push(text); start_daemon(command.program(), &args); }, // Copy the text to the clipboard. HintAction::Action(HintInternalAction::Copy) => { - let text = self.terminal.bounds_to_string(*bounds.start(), *bounds.end()); + let text = self.terminal.bounds_to_string(*hint.bounds.start(), *hint.bounds.end()); self.clipboard.store(ClipboardType::Clipboard, text); }, // Write the text to the PTY/search. HintAction::Action(HintInternalAction::Paste) => { - let text = self.terminal.bounds_to_string(*bounds.start(), *bounds.end()); + let text = self.terminal.bounds_to_string(*hint.bounds.start(), *hint.bounds.end()); self.paste(&text); }, // Select the text. HintAction::Action(HintInternalAction::Select) => { - self.start_selection(SelectionType::Simple, *bounds.start(), Side::Left); - self.update_selection(*bounds.end(), Side::Right); + self.start_selection(SelectionType::Simple, *hint.bounds.start(), Side::Left); + self.update_selection(*hint.bounds.end(), Side::Right); }, } } @@ -731,10 +706,6 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon self.event_loop } - fn urls(&self) -> &Urls { - &self.display.urls - } - fn clipboard_mut(&mut self) -> &mut Clipboard { self.clipboard } @@ -908,7 +879,8 @@ pub struct Mouse { pub scroll_px: f64, pub cell_side: Side, pub lines_scrolled: f32, - pub block_url_launcher: bool, + pub block_hint_launcher: bool, + pub hint_highlight_dirty: bool, pub inside_text_area: bool, pub point: Point, pub x: usize, @@ -925,7 +897,8 @@ impl Default for Mouse { right_button_state: ElementState::Released, click_state: ClickState::None, cell_side: Side::Left, - block_url_launcher: Default::default(), + hint_highlight_dirty: Default::default(), + block_hint_launcher: Default::default(), inside_text_area: Default::default(), lines_scrolled: Default::default(), scroll_px: Default::default(), @@ -1115,6 +1088,17 @@ impl Processor { return; } + if self.dirty || self.mouse.hint_highlight_dirty { + self.display.update_highlighted_hints( + &terminal, + &self.config, + &self.mouse, + self.modifiers, + ); + self.mouse.hint_highlight_dirty = false; + self.dirty = true; + } + if self.dirty { self.dirty = false; @@ -1127,14 +1111,7 @@ impl Processor { } // Redraw screen. - self.display.draw( - terminal, - &self.message_buffer, - &self.config, - &self.mouse, - self.modifiers, - &self.search_state, - ); + self.display.draw(terminal, &self.message_buffer, &self.config, &self.search_state); } }); @@ -1165,7 +1142,7 @@ impl Processor { // Resize to event's dimensions, since no resize event is emitted on Wayland. display_update_pending.set_dimensions(PhysicalSize::new(width, height)); - processor.ctx.window_mut().dpr = scale_factor; + processor.ctx.window().dpr = scale_factor; *processor.ctx.dirty = true; }, Event::Message(message) => { @@ -1184,7 +1161,7 @@ impl Processor { TerminalEvent::Title(title) => { let ui_config = &processor.ctx.config.ui_config; if ui_config.window.dynamic_title { - processor.ctx.window_mut().set_title(&title); + processor.ctx.window().set_title(&title); } }, TerminalEvent::ResetTitle => { @@ -1198,7 +1175,7 @@ impl Processor { // Set window urgency. if processor.ctx.terminal.mode().contains(TermMode::URGENCY_HINTS) { let focused = processor.ctx.terminal.is_focused; - processor.ctx.window_mut().set_urgent(!focused); + processor.ctx.window().set_urgent(!focused); } // Ring visual bell. @@ -1251,16 +1228,16 @@ impl Processor { }, WindowEvent::ReceivedCharacter(c) => processor.received_char(c), WindowEvent::MouseInput { state, button, .. } => { - processor.ctx.window_mut().set_mouse_visible(true); + processor.ctx.window().set_mouse_visible(true); processor.mouse_input(state, button); *processor.ctx.dirty = true; }, WindowEvent::CursorMoved { position, .. } => { - processor.ctx.window_mut().set_mouse_visible(true); + processor.ctx.window().set_mouse_visible(true); processor.mouse_moved(position); }, WindowEvent::MouseWheel { delta, phase, .. } => { - processor.ctx.window_mut().set_mouse_visible(true); + processor.ctx.window().set_mouse_visible(true); processor.mouse_wheel_input(delta, phase); }, WindowEvent::Focused(is_focused) => { @@ -1269,9 +1246,9 @@ impl Processor { *processor.ctx.dirty = true; if is_focused { - processor.ctx.window_mut().set_urgent(false); + processor.ctx.window().set_urgent(false); } else { - processor.ctx.window_mut().set_mouse_visible(true); + processor.ctx.window().set_mouse_visible(true); } processor.ctx.update_cursor_blinking(); @@ -1285,7 +1262,7 @@ impl Processor { WindowEvent::CursorLeft { .. } => { processor.ctx.mouse.inside_text_area = false; - if processor.ctx.highlighted_url().is_some() { + if processor.ctx.display().highlighted_hint.is_some() { *processor.ctx.dirty = true; } }, @@ -1382,12 +1359,12 @@ impl Processor { if !config.ui_config.window.dynamic_title || processor.ctx.config.ui_config.window.title != config.ui_config.window.title { - processor.ctx.window_mut().set_title(&config.ui_config.window.title); + processor.ctx.window().set_title(&config.ui_config.window.title); } #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] if processor.ctx.event_loop.is_wayland() { - processor.ctx.window_mut().set_wayland_theme(&config.ui_config.colors); + processor.ctx.window().set_wayland_theme(&config.ui_config.colors); } // Set subpixel anti-aliasing. @@ -1396,7 +1373,7 @@ impl Processor { // Disable shadows for transparent windows on macOS. #[cfg(target_os = "macos")] - processor.ctx.window_mut().set_has_shadow(config.ui_config.background_opacity() >= 1.0); + processor.ctx.window().set_has_shadow(config.ui_config.background_opacity() >= 1.0); // Update hint keys. processor.ctx.display.hint_state.update_alphabet(config.ui_config.hints.alphabet()); diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 38a346d9..7dd47803 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -22,7 +22,7 @@ use glutin::window::CursorIcon; use alacritty_terminal::ansi::{ClearMode, Handler}; use alacritty_terminal::event::EventListener; use alacritty_terminal::grid::{Dimensions, Scroll}; -use alacritty_terminal::index::{Boundary, Column, Direction, Line, Point, Side}; +use alacritty_terminal::index::{Boundary, Column, Direction, Point, Side}; use alacritty_terminal::selection::SelectionType; use alacritty_terminal::term::search::Match; use alacritty_terminal::term::{ClipboardType, SizeInfo, Term, TermMode}; @@ -31,12 +31,12 @@ use alacritty_terminal::vi_mode::ViMotion; use crate::clipboard::Clipboard; use crate::config::{Action, BindingMode, Config, Key, SearchAction, ViAction}; use crate::daemon::start_daemon; -use crate::display::hint::HintState; +use crate::display::hint::HintMatch; use crate::display::window::Window; +use crate::display::{self, Display}; use crate::event::{ClickState, Event, Mouse, TYPING_SEARCH_DELAY}; use crate::message_bar::{self, Message}; use crate::scheduler::{Scheduler, TimerId}; -use crate::url::{Url, Urls}; /// Font size change interval. pub const FONT_SIZE_STEP: f32 = 0.5; @@ -75,8 +75,8 @@ pub trait ActionContext { fn suppress_chars(&mut self) -> &mut bool; fn modifiers(&mut self) -> &mut ModifiersState; fn scroll(&mut self, _scroll: Scroll) {} - fn window(&self) -> &Window; - fn window_mut(&mut self) -> &mut Window; + fn window(&mut self) -> &mut Window; + fn display(&mut self) -> &mut Display; fn terminal(&self) -> &Term; fn terminal_mut(&mut self) -> &mut Term; fn spawn_new_instance(&mut self) {} @@ -86,9 +86,6 @@ pub trait ActionContext { fn message(&self) -> Option<&Message>; fn config(&self) -> &Config; fn event_loop(&self) -> &EventLoopWindowTarget; - fn urls(&self) -> &Urls; - fn launch_url(&self, _url: Url) {} - fn highlighted_url(&self) -> Option<&Url>; fn mouse_mode(&self) -> bool; fn clipboard_mut(&mut self) -> &mut Clipboard; fn scheduler_mut(&mut self) -> &mut Scheduler; @@ -105,8 +102,8 @@ pub trait ActionContext { fn search_active(&self) -> bool; fn on_typing_start(&mut self) {} fn toggle_vi_mode(&mut self) {} - fn hint_state(&mut self) -> &mut HintState; fn hint_input(&mut self, _character: char) {} + fn trigger_hint(&mut self, _hint: &HintMatch) {} fn paste(&mut self, _text: &str) {} } @@ -142,7 +139,7 @@ impl Execute for Action { }, Action::Command(program) => start_daemon(program.program(), program.args()), Action::Hint(hint) => { - ctx.hint_state().start(hint.clone()); + ctx.display().hint_state.start(hint.clone()); ctx.mark_dirty(); }, Action::ToggleViMode => ctx.toggle_vi_mode(), @@ -164,12 +161,12 @@ impl Execute for Action { Self::toggle_selection(ctx, SelectionType::Semantic); }, Action::ViAction(ViAction::Open) => { - ctx.mouse_mut().block_url_launcher = false; - let vi_point = ctx.terminal().vi_mode_cursor.point; - let line = (vi_point.line + ctx.terminal().grid().display_offset()).0 as usize; - if let Some(url) = ctx.urls().find_at(Point::new(line, vi_point.column)) { - ctx.launch_url(url); + let hint = ctx.display().vi_highlighted_hint.take(); + if let Some(hint) = &hint { + ctx.mouse_mut().block_hint_launcher = false; + ctx.trigger_hint(hint); } + ctx.display().vi_highlighted_hint = hint; }, Action::ViAction(ViAction::SearchNext) => { let terminal = ctx.terminal(); @@ -250,9 +247,9 @@ impl Execute for Action { let text = ctx.clipboard_mut().load(ClipboardType::Selection); ctx.paste(&text); }, - Action::ToggleFullscreen => ctx.window_mut().toggle_fullscreen(), + Action::ToggleFullscreen => ctx.window().toggle_fullscreen(), #[cfg(target_os = "macos")] - Action::ToggleSimpleFullscreen => ctx.window_mut().toggle_simple_fullscreen(), + Action::ToggleSimpleFullscreen => ctx.window().toggle_simple_fullscreen(), #[cfg(target_os = "macos")] Action::Hide => ctx.event_loop().hide_application(), #[cfg(target_os = "macos")] @@ -327,25 +324,6 @@ impl Execute for Action { } } -#[derive(Debug, Clone, PartialEq)] -pub enum MouseState { - Url(Url), - MessageBar, - MessageBarButton, - Mouse, - Text, -} - -impl From for CursorIcon { - fn from(mouse_state: MouseState) -> CursorIcon { - match mouse_state { - MouseState::Url(_) | MouseState::MessageBarButton => CursorIcon::Hand, - MouseState::Text => CursorIcon::Text, - _ => CursorIcon::Default, - } - } -} - impl> Processor { pub fn new(ctx: A) -> Self { Self { ctx, _phantom: Default::default() } @@ -375,11 +353,6 @@ impl> Processor { let cell_changed = point != self.ctx.mouse().point; - // Update mouse state and check for URL change. - let mouse_state = self.mouse_state(); - self.update_url_state(&mouse_state); - self.ctx.window_mut().set_mouse_cursor(mouse_state.into()); - // If the mouse hasn't changed cells, do nothing. if !cell_changed && self.ctx.mouse().cell_side == cell_side @@ -392,13 +365,20 @@ impl> Processor { self.ctx.mouse_mut().cell_side = cell_side; self.ctx.mouse_mut().point = point; + // Update mouse state and check for URL change. + let mouse_state = self.cursor_state(); + self.ctx.window().set_mouse_cursor(mouse_state); + + // Prompt hint highlight update. + self.ctx.mouse_mut().hint_highlight_dirty = true; + // Don't launch URLs if mouse has moved. - self.ctx.mouse_mut().block_url_launcher = true; + self.ctx.mouse_mut().block_hint_launcher = true; if (lmb_pressed || rmb_pressed) && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode()) { - let line = Line(point.line as i32) - self.ctx.terminal().grid().display_offset(); - let point = Point::new(line, point.column); + let display_offset = self.ctx.terminal().grid().display_offset(); + let point = display::viewport_to_point(display_offset, point); self.ctx.update_selection(point, cell_side); } else if cell_changed && point.line < self.ctx.terminal().screen_lines() @@ -562,10 +542,8 @@ impl> Processor { }; // Load mouse point, treating message bar and padding as the closest cell. - let point = self.ctx.mouse().point; let display_offset = self.ctx.terminal().grid().display_offset(); - let absolute_line = Line(point.line as i32) - display_offset; - let point = Point::new(absolute_line, point.column); + let point = display::viewport_to_point(display_offset, self.ctx.mouse().point); match button { MouseButton::Left => self.on_left_click(point), @@ -619,7 +597,7 @@ impl> Processor { match self.ctx.mouse().click_state { ClickState::Click => { // Don't launch URLs if this click cleared the selection. - self.ctx.mouse_mut().block_url_launcher = !self.ctx.selection_is_empty(); + self.ctx.mouse_mut().block_hint_launcher = !self.ctx.selection_is_empty(); self.ctx.clear_selection(); @@ -631,11 +609,11 @@ impl> Processor { } }, ClickState::DoubleClick => { - self.ctx.mouse_mut().block_url_launcher = true; + self.ctx.mouse_mut().block_hint_launcher = true; self.ctx.start_selection(SelectionType::Semantic, point, side); }, ClickState::TripleClick => { - self.ctx.mouse_mut().block_url_launcher = true; + self.ctx.mouse_mut().block_hint_launcher = true; self.ctx.start_selection(SelectionType::Lines, point, side); }, ClickState::None => (), @@ -658,10 +636,15 @@ impl> Processor { }; self.mouse_report(code, ElementState::Released); return; - } else if let (MouseButton::Left, MouseState::Url(url)) = (button, self.mouse_state()) { - self.ctx.launch_url(url); } + // Trigger hints highlighted by the mouse. + let hint = self.ctx.display().highlighted_hint.take(); + if let Some(hint) = hint.as_ref().filter(|_| button == MouseButton::Left) { + self.ctx.trigger_hint(hint); + } + self.ctx.display().highlighted_hint = hint; + self.ctx.scheduler_mut().unschedule(TimerId::SelectionScrolling); } @@ -748,7 +731,7 @@ impl> Processor { } // Skip normal mouse events if the message bar has been clicked. - if self.message_bar_mouse_state() == Some(MouseState::MessageBarButton) + if self.message_bar_cursor_state() == Some(CursorIcon::Hand) && state == ElementState::Pressed { let size = self.ctx.size_info(); @@ -773,7 +756,7 @@ impl> Processor { }, }; - self.ctx.window_mut().set_mouse_cursor(new_icon); + self.ctx.window().set_mouse_cursor(new_icon); } else { match state { ElementState::Pressed => { @@ -788,7 +771,7 @@ impl> Processor { /// Process key input. pub fn key_input(&mut self, input: KeyboardInput) { // All key bindings are disabled while a hint is being selected. - if self.ctx.hint_state().active() { + if self.ctx.display().hint_state.active() { *self.ctx.suppress_chars() = false; return; } @@ -813,17 +796,19 @@ impl> Processor { pub fn modifiers_input(&mut self, modifiers: ModifiersState) { *self.ctx.modifiers() = modifiers; + // Prompt hint highlight update. + self.ctx.mouse_mut().hint_highlight_dirty = true; + // Update mouse state and check for URL change. - let mouse_state = self.mouse_state(); - self.update_url_state(&mouse_state); - self.ctx.window_mut().set_mouse_cursor(mouse_state.into()); + let mouse_state = self.cursor_state(); + self.ctx.window().set_mouse_cursor(mouse_state); } /// Reset mouse cursor based on modifier and terminal state. #[inline] pub fn reset_mouse_cursor(&mut self) { - let mouse_state = self.mouse_state(); - self.ctx.window_mut().set_mouse_cursor(mouse_state.into()); + let mouse_state = self.cursor_state(); + self.ctx.window().set_mouse_cursor(mouse_state); } /// Process a received character. @@ -831,7 +816,7 @@ impl> Processor { let suppress_chars = *self.ctx.suppress_chars(); // Handle hint selection over anything else. - if self.ctx.hint_state().active() && !suppress_chars { + if self.ctx.display().hint_state.active() && !suppress_chars { self.ctx.hint_input(c); return; } @@ -925,8 +910,8 @@ impl> Processor { } } - /// Check mouse state in relation to the message bar. - fn message_bar_mouse_state(&self) -> Option { + /// Check mouse icon state in relation to the message bar. + fn message_bar_cursor_state(&self) -> Option { // Since search is above the message bar, the button is offset by search's height. let search_height = if self.ctx.search_active() { 1 } else { 0 }; @@ -941,53 +926,30 @@ impl> Processor { } else if mouse.y <= terminal_end + size.cell_height() as usize && mouse.point.column + message_bar::CLOSE_BUTTON_TEXT.len() >= size.columns() { - Some(MouseState::MessageBarButton) + Some(CursorIcon::Hand) } else { - Some(MouseState::MessageBar) - } - } - - /// Trigger redraw when URL highlight changed. - #[inline] - fn update_url_state(&mut self, mouse_state: &MouseState) { - let highlighted_url = self.ctx.highlighted_url(); - if let MouseState::Url(url) = mouse_state { - if Some(url) != highlighted_url { - self.ctx.mark_dirty(); - } - } else if highlighted_url.is_some() { - self.ctx.mark_dirty(); + Some(CursorIcon::Default) } } - /// Location of the mouse cursor. - fn mouse_state(&mut self) -> MouseState { - // Check message bar before URL to ignore URLs in the message bar. - if let Some(mouse_state) = self.message_bar_mouse_state() { - return mouse_state; - } - - let mouse_mode = self.ctx.mouse_mode(); - - // Check for URL at mouse cursor. - let mods = *self.ctx.modifiers(); - let highlighted_url = self.ctx.urls().highlighted( - self.ctx.config(), - self.ctx.mouse(), - mods, - mouse_mode, - !self.ctx.selection_is_empty(), - ); - - if let Some(url) = highlighted_url { - return MouseState::Url(url); - } + /// Icon state of the cursor. + fn cursor_state(&mut self) -> CursorIcon { + // Define function to check if mouse is on top of a hint. + let display_offset = self.ctx.terminal().grid().display_offset(); + let mouse_point = self.ctx.mouse().point; + let hint_highlighted = |hint: &HintMatch| { + let point = display::viewport_to_point(display_offset, mouse_point); + hint.bounds.contains(&point) + }; - // Check mouse mode if location is not special. - if !self.ctx.modifiers().shift() && mouse_mode { - MouseState::Mouse + if let Some(mouse_state) = self.message_bar_cursor_state() { + mouse_state + } else if self.ctx.display().highlighted_hint.as_ref().map_or(false, hint_highlighted) { + CursorIcon::Hand + } else if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() { + CursorIcon::Default } else { - MouseState::Text + CursorIcon::Text } } @@ -1129,11 +1091,11 @@ mod tests { &mut self.modifiers } - fn window(&self) -> &Window { + fn window(&mut self) -> &mut Window { unimplemented!(); } - fn window_mut(&mut self) -> &mut Window { + fn display(&mut self) -> &mut Display { unimplemented!(); } @@ -1157,21 +1119,9 @@ mod tests { unimplemented!(); } - fn urls(&self) -> &Urls { - unimplemented!(); - } - - fn highlighted_url(&self) -> Option<&Url> { - unimplemented!(); - } - fn scheduler_mut(&mut self) -> &mut Scheduler { unimplemented!(); } - - fn hint_state(&mut self) -> &mut HintState { - unimplemented!(); - } } macro_rules! test_clickstate { diff --git a/alacritty/src/main.rs b/alacritty/src/main.rs index 0914aee4..c68f3cfb 100644 --- a/alacritty/src/main.rs +++ b/alacritty/src/main.rs @@ -45,7 +45,6 @@ mod message_bar; mod panic; mod renderer; mod scheduler; -mod url; mod gl { #![allow(clippy::all)] diff --git a/alacritty/src/url.rs b/alacritty/src/url.rs deleted file mode 100644 index add4a1aa..00000000 --- a/alacritty/src/url.rs +++ /dev/null @@ -1,288 +0,0 @@ -use std::cmp::min; -use std::mem; - -use crossfont::Metrics; -use glutin::event::{ElementState, ModifiersState}; -use urlocator::{UrlLocation, UrlLocator}; - -use alacritty_terminal::grid::Dimensions; -use alacritty_terminal::index::{Boundary, Column, Line, Point}; -use alacritty_terminal::term::cell::Flags; -use alacritty_terminal::term::color::Rgb; -use alacritty_terminal::term::SizeInfo; - -use crate::config::Config; -use crate::display::content::RenderableCell; -use crate::event::Mouse; -use crate::renderer::rects::{RenderLine, RenderRect}; - -#[derive(Clone, Debug, PartialEq)] -pub struct Url { - lines: Vec, - end_offset: u16, - size: SizeInfo, -} - -impl Url { - /// Rectangles required for underlining the URL. - pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec { - let end = self.end(); - self.lines - .iter() - .filter(|line| line.start <= end) - .map(|line| { - let mut rect_line = *line; - rect_line.end = min(line.end, end); - rect_line.rects(Flags::UNDERLINE, metrics, size) - }) - .flatten() - .collect() - } - - /// Viewport start point of the URL. - pub fn start(&self) -> Point { - self.lines[0].start - } - - /// Viewport end point of the URL. - pub fn end(&self) -> Point { - let end = self.lines[self.lines.len() - 1].end; - - // Convert to Point to make use of the grid clamping logic. - let mut end = Point::new(Line(end.line as i32), end.column); - end = end.sub(&self.size, Boundary::Cursor, self.end_offset as usize); - - Point::new(end.line.0 as usize, end.column) - } -} - -pub struct Urls { - locator: UrlLocator, - urls: Vec, - scheme_buffer: Vec<(Point, Rgb)>, - next_point: Point, - state: UrlLocation, -} - -impl Default for Urls { - fn default() -> Self { - Self { - locator: UrlLocator::new(), - scheme_buffer: Vec::new(), - urls: Vec::new(), - state: UrlLocation::Reset, - next_point: Point::new(0, Column(0)), - } - } -} - -impl Urls { - pub fn new() -> Self { - Self::default() - } - - // Update tracked URLs. - pub fn update(&mut self, size: &SizeInfo, cell: &RenderableCell) { - let point = cell.point; - let mut end = point; - - // Include the following wide char spacer. - if cell.flags.contains(Flags::WIDE_CHAR) { - end.column += 1; - } - - // Reset URL when empty cells have been skipped. - if point != Point::new(0, Column(0)) && point != self.next_point { - self.reset(); - } - - self.next_point = if end.column.0 + 1 == size.columns() { - Point::new(end.line + 1, Column(0)) - } else { - Point::new(end.line, end.column + 1) - }; - - // Extend current state if a leading wide char spacer is encountered. - if cell.flags.intersects(Flags::LEADING_WIDE_CHAR_SPACER) { - if let UrlLocation::Url(_, mut end_offset) = self.state { - if end_offset != 0 { - end_offset += 1; - } - - self.extend_url(point, end, cell.fg, end_offset); - } - - return; - } - - // Advance parser. - let last_state = mem::replace(&mut self.state, self.locator.advance(cell.character)); - match (self.state, last_state) { - (UrlLocation::Url(_length, end_offset), UrlLocation::Scheme) => { - // Create empty URL. - self.urls.push(Url { lines: Vec::new(), end_offset, size: *size }); - - // Push schemes into URL. - for (scheme_point, scheme_fg) in self.scheme_buffer.split_off(0) { - self.extend_url(scheme_point, scheme_point, scheme_fg, end_offset); - } - - // Push the new cell into URL. - self.extend_url(point, end, cell.fg, end_offset); - }, - (UrlLocation::Url(_length, end_offset), UrlLocation::Url(..)) => { - self.extend_url(point, end, cell.fg, end_offset); - }, - (UrlLocation::Scheme, _) => self.scheme_buffer.push((cell.point, cell.fg)), - (UrlLocation::Reset, _) => self.reset(), - _ => (), - } - - // Reset at un-wrapped linebreak. - if cell.point.column.0 + 1 == size.columns() && !cell.flags.contains(Flags::WRAPLINE) { - self.reset(); - } - } - - /// Extend the last URL. - fn extend_url(&mut self, start: Point, end: Point, color: Rgb, end_offset: u16) { - let url = self.urls.last_mut().unwrap(); - - // If color changed, we need to insert a new line. - if url.lines.last().map(|last| last.color) == Some(color) { - url.lines.last_mut().unwrap().end = end; - } else { - url.lines.push(RenderLine { start, end, color }); - } - - // Update excluded cells at the end of the URL. - url.end_offset = end_offset; - } - - /// Find URL below the mouse cursor. - pub fn highlighted( - &self, - config: &Config, - mouse: &Mouse, - mods: ModifiersState, - mouse_mode: bool, - selection: bool, - ) -> Option { - // Require additional shift in mouse mode. - let mut required_mods = config.ui_config.mouse.url.mods(); - if mouse_mode { - required_mods |= ModifiersState::SHIFT; - } - - // Make sure all prerequisites for highlighting are met. - if selection - || !mouse.inside_text_area - || config.ui_config.mouse.url.launcher.is_none() - || required_mods != mods - || mouse.left_button_state == ElementState::Pressed - { - return None; - } - - self.find_at(mouse.point) - } - - /// Find URL at location. - pub fn find_at(&self, point: Point) -> Option { - for url in &self.urls { - if (url.start()..=url.end()).contains(&point) { - return Some(url.clone()); - } - } - None - } - - fn reset(&mut self) { - self.locator = UrlLocator::new(); - self.state = UrlLocation::Reset; - self.scheme_buffer.clear(); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use alacritty_terminal::index::Column; - - fn text_to_cells(text: &str) -> Vec { - text.chars() - .enumerate() - .map(|(i, character)| RenderableCell { - character, - zerowidth: None, - point: Point::new(0, Column(i)), - fg: Default::default(), - bg: Default::default(), - bg_alpha: 0., - flags: Flags::empty(), - }) - .collect() - } - - #[test] - fn multi_color_url() { - let mut input = text_to_cells("test https://example.org ing"); - let size = SizeInfo::new(input.len() as f32, 1., 1.0, 1.0, 0.0, 0.0, false); - - input[10].fg = Rgb { r: 0xff, g: 0x00, b: 0xff }; - - let mut urls = Urls::new(); - - for cell in input { - urls.update(&size, &cell); - } - - let url = urls.urls.first().unwrap(); - assert_eq!(url.start().column, Column(5)); - assert_eq!(url.end().column, Column(23)); - } - - #[test] - fn multiple_urls() { - let input = text_to_cells("test git:a git:b git:c ing"); - let size = SizeInfo::new(input.len() as f32, 1., 1.0, 1.0, 0.0, 0.0, false); - - let mut urls = Urls::new(); - - for cell in input { - urls.update(&size, &cell); - } - - assert_eq!(urls.urls.len(), 3); - - assert_eq!(urls.urls[0].start().column, Column(5)); - assert_eq!(urls.urls[0].end().column, Column(9)); - - assert_eq!(urls.urls[1].start().column, Column(11)); - assert_eq!(urls.urls[1].end().column, Column(15)); - - assert_eq!(urls.urls[2].start().column, Column(17)); - assert_eq!(urls.urls[2].end().column, Column(21)); - } - - #[test] - fn wide_urls() { - let input = text_to_cells("test https://こんにちは (http:여보세요) ing"); - let size = SizeInfo::new(input.len() as f32 + 9., 1., 1.0, 1.0, 0.0, 0.0, false); - - let mut urls = Urls::new(); - - for cell in input { - urls.update(&size, &cell); - } - - assert_eq!(urls.urls.len(), 2); - - assert_eq!(urls.urls[0].start().column, Column(5)); - assert_eq!(urls.urls[0].end().column, Column(17)); - - assert_eq!(urls.urls[1].start().column, Column(20)); - assert_eq!(urls.urls[1].end().column, Column(28)); - } -} diff --git a/alacritty_terminal/src/term/search.rs b/alacritty_terminal/src/term/search.rs index 638df670..93345e4f 100644 --- a/alacritty_terminal/src/term/search.rs +++ b/alacritty_terminal/src/term/search.rs @@ -82,7 +82,7 @@ impl Term { // Limit maximum number of lines searched. end = match max_lines { Some(max_lines) => { - let line = (start.line + max_lines).grid_clamp(self, Boundary::Grid); + let line = (start.line + max_lines).grid_clamp(self, Boundary::None); Point::new(line, self.last_column()) }, _ => end.sub(self, Boundary::None, 1), @@ -121,7 +121,7 @@ impl Term { // Limit maximum number of lines searched. end = match max_lines { Some(max_lines) => { - let line = (start.line - max_lines).grid_clamp(self, Boundary::Grid); + let line = (start.line - max_lines).grid_clamp(self, Boundary::None); Point::new(line, Column(0)) }, _ => end.add(self, Boundary::None, 1), diff --git a/docs/features.md b/docs/features.md index 3aa87aab..fd9a9ad2 100644 --- a/docs/features.md +++ b/docs/features.md @@ -27,13 +27,6 @@ line (Shift v) and block selection (Ctrl v). You can also toggle between them while the selection is still active. -### Opening URLs - -While in vi mode you can open URLs using the Enter key. If some text -is recognized as a URL, it will be underlined once you move the vi cursor above -it. The program used to open these URLs can be changed in the [configuration -file]. - ## Search Search allows you to find anything in Alacritty's scrollback buffer. You can @@ -61,6 +54,11 @@ start vi mode. They consist of a regex that detects these text elements and then either feeds them to an external application or triggers one of Alacritty's built-in actions. +Hints can also be triggered using the mouse or vi mode cursor. If a hint is +enabled for mouse interaction and recognized as such, it will be underlined when +the mouse or vi mode cursor is on top of it. Using the left mouse button or +Enter key in vi mode will then trigger the hint. + Hints can be configured in the `hints` and `colors.hints` sections in the Alacritty configuration file. -- cgit From 7f31275e7443667387d9b886c6eeee52c2e29196 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Wed, 14 Apr 2021 01:05:20 +0000 Subject: Fix URL highlight in mouse mode without shift This resolves a regression introduced in 96fc9ec where URLs would get highlighted on mouse hover while mouse mode is active even when the shift modifier was not held down. --- alacritty/src/display/hint.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/alacritty/src/display/hint.rs b/alacritty/src/display/hint.rs index f9ab90d4..6ab68f10 100644 --- a/alacritty/src/display/hint.rs +++ b/alacritty/src/display/hint.rs @@ -5,7 +5,7 @@ use glutin::event::ModifiersState; use alacritty_terminal::grid::BidirectionalIterator; use alacritty_terminal::index::{Boundary, Point}; use alacritty_terminal::term::search::{Match, RegexSearch}; -use alacritty_terminal::term::Term; +use alacritty_terminal::term::{Term, TermMode}; use crate::config::ui_config::{Hint, HintAction}; use crate::config::Config; @@ -246,9 +246,16 @@ pub fn highlighted_at( point: Point, mouse_mods: ModifiersState, ) -> Option { + let mouse_mode = term.mode().intersects(TermMode::MOUSE_MODE); + config.ui_config.hints.enabled.iter().find_map(|hint| { // Check if all required modifiers are pressed. - if hint.mouse.map_or(true, |mouse| !mouse.enabled || !mouse_mods.contains(mouse.mods.0)) { + let highlight = hint.mouse.map_or(false, |mouse| { + mouse.enabled + && mouse_mods.contains(mouse.mods.0) + && (!mouse_mode || mouse_mods.contains(ModifiersState::SHIFT)) + }); + if !highlight { return None; } -- cgit From 37f638fc425c357a044ab50a86dcbf1d87cea49a Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Wed, 14 Apr 2021 03:47:10 +0000 Subject: Fix hint label with start above viewport Fixes #4960. --- alacritty/src/display/content.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/alacritty/src/display/content.rs b/alacritty/src/display/content.rs index 23a2f8a4..1d06c349 100644 --- a/alacritty/src/display/content.rs +++ b/alacritty/src/display/content.rs @@ -216,10 +216,12 @@ impl RenderableCell { .selection .map_or(false, |selection| selection.contains_cell(&cell, content.terminal_cursor)); + let display_offset = content.terminal_content.display_offset; + let viewport_start = Point::new(Line(-(display_offset as i32)), Column(0)); + let colors = &content.config.ui_config.colors; let mut character = cell.c; - let colors = &content.config.ui_config.colors; - if let Some((c, is_first)) = content.hint.advance(cell.point) { + if let Some((c, is_first)) = content.hint.advance(viewport_start, cell.point) { let (config_fg, config_bg) = if is_first { (colors.hints.start.foreground, colors.hints.start.background) } else { @@ -255,7 +257,6 @@ impl RenderableCell { // Convert cell point to viewport position. let cell_point = cell.point; - let display_offset = content.terminal_content.display_offset; let point = display::point_to_viewport(display_offset, cell_point).unwrap(); RenderableCell { @@ -406,7 +407,7 @@ impl<'a> Hint<'a> { /// this position will be returned. /// /// The tuple's [`bool`] will be `true` when the character is the first for this hint. - fn advance(&mut self, point: Point) -> Option<(char, bool)> { + fn advance(&mut self, viewport_start: Point, point: Point) -> Option<(char, bool)> { // Check if we're within a match at all. if !self.regex.advance(point) { return None; @@ -417,7 +418,7 @@ impl<'a> Hint<'a> { .regex .matches .get(self.regex.index) - .map(|regex_match| regex_match.start()) + .map(|regex_match| max(*regex_match.start(), viewport_start)) .filter(|start| start.line == point.line)?; // Position within the hint label. -- cgit From 05917b27405f797bca817fa68305c08b74897997 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Wed, 14 Apr 2021 19:39:35 +0000 Subject: Fix initial vi cursor position while in scrollback Fixes #4968. --- CHANGELOG.md | 4 ++++ alacritty_terminal/src/term/mod.rs | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fa883bc..ba70bd7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Regex terminal hints ([see features.md](./docs/features.md#hints)) - macOS keybinding (cmd+alt+H) hiding all windows other than Alacritty +### Changed + +- The vi mode cursor is now created in the top-left if the terminal cursor is invisible + ### Fixed - Alacritty failing to start on X11 with invalid DPI reported by XRandr diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index 8d0fc0f8..c4425916 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -612,8 +612,15 @@ impl Term { self.mode ^= TermMode::VI; if self.mode.contains(TermMode::VI) { - // Reset vi mode cursor position to match primary cursor. - self.vi_mode_cursor = ViModeCursor::new(self.grid.cursor.point); + let display_offset = self.grid.display_offset() as i32; + if self.grid.cursor.point.line > self.bottommost_line() - display_offset { + // Move cursor to top-left if terminal cursor is not visible. + let point = Point::new(Line(-display_offset), Column(0)); + self.vi_mode_cursor = ViModeCursor::new(point); + } else { + // Reset vi mode cursor position to match primary cursor. + self.vi_mode_cursor = ViModeCursor::new(self.grid.cursor.point); + } } // Update UI about cursor blinking state changes. -- cgit From 1f46bb7b925e5a6ce2dc4e5eeb88449d4cb8ebc5 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Thu, 15 Apr 2021 21:25:49 +0000 Subject: Add hint action for moving the vi cursor Fixes #4319. --- alacritty.yml | 2 ++ alacritty/src/config/ui_config.rs | 2 ++ alacritty/src/event.rs | 9 +++++++++ 3 files changed, 13 insertions(+) diff --git a/alacritty.yml b/alacritty.yml index 840c4ed9..8c3a1ab0 100644 --- a/alacritty.yml +++ b/alacritty.yml @@ -475,6 +475,8 @@ # Paste the hint's text to the terminal or search. # - Select # Select the hint's text. + # - MoveViModeCursor + # Move the vi mode cursor to the beginning of the hint. #enabled: # - regex: "(mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)\ # [^\u0000-\u001F\u007F-\u009F<>\" {-}\\^⟨⟩`]+" diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs index ff013d57..135d12c8 100644 --- a/alacritty/src/config/ui_config.rs +++ b/alacritty/src/config/ui_config.rs @@ -293,6 +293,8 @@ pub enum HintInternalAction { Paste, /// Select the text matching the hint. Select, + /// Move the vi mode cursor to the beginning of the hint. + MoveViModeCursor, } /// Actions for hint bindings. diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index 33b15928..3b24de0f 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -657,6 +657,15 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon self.start_selection(SelectionType::Simple, *hint.bounds.start(), Side::Left); self.update_selection(*hint.bounds.end(), Side::Right); }, + // Move the vi mode cursor. + HintAction::Action(HintInternalAction::MoveViModeCursor) => { + // Enter vi mode if we're not in it already. + if !self.terminal.mode().contains(TermMode::VI) { + self.terminal.toggle_vi_mode(); + } + + self.terminal.vi_goto_point(*hint.bounds.start()); + }, } } -- cgit From 9cb55621f77a8eeca3738013ef9b1274b7a97e96 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Thu, 15 Apr 2021 22:16:31 +0000 Subject: Fix mouse point crash on resize This resolves an issue with Alacritty crashing after a resize, due to the last cached mouse point being out of bounds. Instead of caching the mouse point, it is now computed on demand to make sure it can never be invalid. Fixes #4977. --- alacritty/src/display/mod.rs | 2 +- alacritty/src/event.rs | 27 +++++++--- alacritty/src/input.rs | 119 ++++++++++++++++++++----------------------- 3 files changed, 76 insertions(+), 72 deletions(-) diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs index 6e40e35c..810d20f3 100644 --- a/alacritty/src/display/mod.rs +++ b/alacritty/src/display/mod.rs @@ -680,7 +680,7 @@ impl Display { } // Find highlighted hint at mouse position. - let point = viewport_to_point(term.grid().display_offset(), mouse.point); + let point = mouse.point(&self.size_info, term.grid().display_offset()); let highlighted_hint = hint::highlighted_at(&term, config, point, modifiers); // Update cursor shape. diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index 3b24de0f..e61354b8 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -209,12 +209,12 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon && self.terminal.selection.as_ref().map(|s| s.is_empty()) != Some(true) { self.update_selection(self.terminal.vi_mode_cursor.point, Side::Right); - } else if self.mouse().left_button_state == ElementState::Pressed - || self.mouse().right_button_state == ElementState::Pressed + } else if self.mouse.left_button_state == ElementState::Pressed + || self.mouse.right_button_state == ElementState::Pressed { let display_offset = self.terminal.grid().display_offset(); - let point = display::viewport_to_point(display_offset, self.mouse().point); - self.update_selection(point, self.mouse().cell_side); + let point = self.mouse.point(&self.size_info(), display_offset); + self.update_selection(point, self.mouse.cell_side); } *self.dirty = true; @@ -891,7 +891,6 @@ pub struct Mouse { pub block_hint_launcher: bool, pub hint_highlight_dirty: bool, pub inside_text_area: bool, - pub point: Point, pub x: usize, pub y: usize, } @@ -911,13 +910,29 @@ impl Default for Mouse { inside_text_area: Default::default(), lines_scrolled: Default::default(), scroll_px: Default::default(), - point: Default::default(), x: Default::default(), y: Default::default(), } } } +impl Mouse { + /// Convert mouse pixel coordinates to viewport point. + /// + /// If the coordinates are outside of the terminal grid, like positions inside the padding, the + /// coordinates will be clamped to the closest grid coordinates. + #[inline] + pub fn point(&self, size: &SizeInfo, display_offset: usize) -> Point { + let col = self.x.saturating_sub(size.padding_x() as usize) / (size.cell_width() as usize); + let col = min(Column(col), size.last_column()); + + let line = self.y.saturating_sub(size.padding_y() as usize) / (size.cell_height() as usize); + let line = min(line, size.bottommost_line().0 as usize); + + display::viewport_to_point(display_offset, Point::new(line, col)) + } +} + /// The event processor. /// /// Stores some state from received events and dispatches actions when they are diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 7dd47803..0fd2b224 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -33,7 +33,7 @@ use crate::config::{Action, BindingMode, Config, Key, SearchAction, ViAction}; use crate::daemon::start_daemon; use crate::display::hint::HintMatch; use crate::display::window::Window; -use crate::display::{self, Display}; +use crate::display::Display; use crate::event::{ClickState, Event, Mouse, TYPING_SEARCH_DELAY}; use crate::message_bar::{self, Message}; use crate::scheduler::{Scheduler, TimerId}; @@ -341,17 +341,19 @@ impl> Processor { self.update_selection_scrolling(y); } + let display_offset = self.ctx.terminal().grid().display_offset(); + let old_point = self.ctx.mouse().point(&size_info, display_offset); + let x = min(max(x, 0), size_info.width() as i32 - 1) as usize; let y = min(max(y, 0), size_info.height() as i32 - 1) as usize; - self.ctx.mouse_mut().x = x; self.ctx.mouse_mut().y = y; let inside_text_area = size_info.contains_point(x, y); - let point = self.coords_to_point(x, y); let cell_side = self.cell_side(x); - let cell_changed = point != self.ctx.mouse().point; + let point = self.ctx.mouse().point(&size_info, display_offset); + let cell_changed = old_point != point; // If the mouse hasn't changed cells, do nothing. if !cell_changed @@ -363,7 +365,6 @@ impl> Processor { self.ctx.mouse_mut().inside_text_area = inside_text_area; self.ctx.mouse_mut().cell_side = cell_side; - self.ctx.mouse_mut().point = point; // Update mouse state and check for URL change. let mouse_state = self.cursor_state(); @@ -377,11 +378,8 @@ impl> Processor { if (lmb_pressed || rmb_pressed) && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode()) { - let display_offset = self.ctx.terminal().grid().display_offset(); - let point = display::viewport_to_point(display_offset, point); self.ctx.update_selection(point, cell_side); } else if cell_changed - && point.line < self.ctx.terminal().screen_lines() && self.ctx.terminal().mode().intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) { if lmb_pressed { @@ -396,23 +394,6 @@ impl> Processor { } } - /// Convert window space pixels to terminal grid coordinates. - /// - /// If the coordinates are outside of the terminal grid, like positions inside the padding, the - /// coordinates will be clamped to the closest grid coordinates. - #[inline] - fn coords_to_point(&self, x: usize, y: usize) -> Point { - let size = self.ctx.size_info(); - - let column = x.saturating_sub(size.padding_x() as usize) / (size.cell_width() as usize); - let column = min(Column(column), size.last_column()); - - let line = y.saturating_sub(size.padding_y() as usize) / (size.cell_height() as usize); - let line = min(line, size.bottommost_line().0 as usize); - - Point::new(line, column) - } - /// Check which side of a cell an X coordinate lies on. fn cell_side(&self, x: usize) -> Side { let size_info = self.ctx.size_info(); @@ -435,13 +416,45 @@ impl> Processor { } } - fn normal_mouse_report(&mut self, button: u8) { - let Point { line, column } = self.ctx.mouse().point; + fn mouse_report(&mut self, button: u8, state: ElementState) { + let display_offset = self.ctx.terminal().grid().display_offset(); + let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset); + + // Assure the mouse point is not in the scrollback. + if point.line >= 0 { + return; + } + + // Calculate modifiers value. + let mut mods = 0; + let modifiers = self.ctx.modifiers(); + if modifiers.shift() { + mods += 4; + } + if modifiers.alt() { + mods += 8; + } + if modifiers.ctrl() { + mods += 16; + } + + // Report mouse events. + if self.ctx.terminal().mode().contains(TermMode::SGR_MOUSE) { + self.sgr_mouse_report(point, button + mods, state); + } else if let ElementState::Released = state { + self.normal_mouse_report(point, 3 + mods); + } else { + self.normal_mouse_report(point, button + mods); + } + } + + fn normal_mouse_report(&mut self, point: Point, button: u8) { + let Point { line, column } = point; let utf8 = self.ctx.terminal().mode().contains(TermMode::UTF8_MOUSE); let max_point = if utf8 { 2015 } else { 223 }; - if line >= max_point || column >= Column(max_point) { + if line >= max_point || column >= max_point { return; } @@ -461,49 +474,24 @@ impl> Processor { } if utf8 && line >= 95 { - msg.append(&mut mouse_pos_encode(line)); + msg.append(&mut mouse_pos_encode(line.0 as usize)); } else { - msg.push(32 + 1 + line as u8); + msg.push(32 + 1 + line.0 as u8); } self.ctx.write_to_pty(msg); } - fn sgr_mouse_report(&mut self, button: u8, state: ElementState) { - let Point { line, column } = self.ctx.mouse().point; + fn sgr_mouse_report(&mut self, point: Point, button: u8, state: ElementState) { let c = match state { ElementState::Pressed => 'M', ElementState::Released => 'm', }; - let msg = format!("\x1b[<{};{};{}{}", button, column + 1, line + 1, c); + let msg = format!("\x1b[<{};{};{}{}", button, point.column + 1, point.line + 1, c); self.ctx.write_to_pty(msg.into_bytes()); } - fn mouse_report(&mut self, button: u8, state: ElementState) { - // Calculate modifiers value. - let mut mods = 0; - let modifiers = self.ctx.modifiers(); - if modifiers.shift() { - mods += 4; - } - if modifiers.alt() { - mods += 8; - } - if modifiers.ctrl() { - mods += 16; - } - - // Report mouse events. - if self.ctx.terminal().mode().contains(TermMode::SGR_MOUSE) { - self.sgr_mouse_report(button + mods, state); - } else if let ElementState::Released = state { - self.normal_mouse_report(3 + mods); - } else { - self.normal_mouse_report(button + mods); - } - } - fn on_mouse_press(&mut self, button: MouseButton) { // Handle mouse mode. if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() { @@ -543,7 +531,7 @@ impl> Processor { // Load mouse point, treating message bar and padding as the closest cell. let display_offset = self.ctx.terminal().grid().display_offset(); - let point = display::viewport_to_point(display_offset, self.ctx.mouse().point); + let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset); match button { MouseButton::Left => self.on_left_click(point), @@ -921,10 +909,13 @@ impl> Processor { + size.cell_height() as usize * (size.screen_lines() + search_height); let mouse = self.ctx.mouse(); + let display_offset = self.ctx.terminal().grid().display_offset(); + let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset); + if self.ctx.message().is_none() || (mouse.y <= terminal_end) { None } else if mouse.y <= terminal_end + size.cell_height() as usize - && mouse.point.column + message_bar::CLOSE_BUTTON_TEXT.len() >= size.columns() + && point.column + message_bar::CLOSE_BUTTON_TEXT.len() >= size.columns() { Some(CursorIcon::Hand) } else { @@ -934,13 +925,11 @@ impl> Processor { /// Icon state of the cursor. fn cursor_state(&mut self) -> CursorIcon { - // Define function to check if mouse is on top of a hint. let display_offset = self.ctx.terminal().grid().display_offset(); - let mouse_point = self.ctx.mouse().point; - let hint_highlighted = |hint: &HintMatch| { - let point = display::viewport_to_point(display_offset, mouse_point); - hint.bounds.contains(&point) - }; + let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset); + + // Function to check if mouse is on top of a hint. + let hint_highlighted = |hint: &HintMatch| hint.bounds.contains(&point); if let Some(mouse_state) = self.message_bar_cursor_state() { mouse_state -- cgit From 504b38cbd14c58b518d5f91dc1bf27311c5fe507 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Fri, 16 Apr 2021 20:23:54 +0000 Subject: Fix mouse reports The patch 9cb5562 has introduced a regression which would abort if a mouse report's line was bigger than zero, which is the exact opposite of when a mouse report is required. Fixes #4980. --- alacritty/src/input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 0fd2b224..977a6e5d 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -421,7 +421,7 @@ impl> Processor { let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset); // Assure the mouse point is not in the scrollback. - if point.line >= 0 { + if point.line < 0 { return; } -- cgit From 33d4b833dca46b775f485d0547f9f8804849fe8a Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 17 Apr 2021 01:53:21 +0000 Subject: Update dependencies This includes a bump to VTE resolving an issue with invalid intermediates when transitioning from DCS to ESC sequences. This should however not be noticeable with any existing escape sequences. Fixes #4827. --- Cargo.lock | 194 ++++++++++++++++++------------------------ alacritty_terminal/Cargo.toml | 2 +- 2 files changed, 86 insertions(+), 110 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b77d602..8c1a43c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,8 +73,8 @@ dependencies = [ "mio", "mio-anonymous-pipes", "mio-extras", - "miow 0.3.6", - "nix 0.19.1", + "miow 0.3.7", + "nix 0.20.0", "parking_lot", "regex-automata", "serde", @@ -174,9 +174,9 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "byteorder" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "calloop" @@ -421,14 +421,13 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae8f328835f8f5a6ceb6a7842a7f2d0c03692adb5c889347235d59194731fe3" +checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" dependencies = [ "autocfg", "cfg-if 1.0.0", "lazy_static", - "loom", ] [[package]] @@ -530,7 +529,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b11f15d1e3268f140f68d390637d5e76d849782d971ae7063e0da69fe9709a76" dependencies = [ - "libloading", + "libloading 0.6.7", +] + +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading 0.7.0", ] [[package]] @@ -541,9 +549,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "dtoa" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" [[package]] name = "dwrote" @@ -588,7 +596,7 @@ checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.5", + "redox_syscall 0.2.6", "winapi 0.3.9", ] @@ -697,19 +705,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -[[package]] -name = "generator" -version = "0.6.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9fed24fd1e18827652b4d55652899a1e9da8e54d91624dc3437a5bc3a9f9a9c" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "winapi 0.3.9", -] - [[package]] name = "getrandom" version = "0.1.16" @@ -748,7 +743,7 @@ dependencies = [ "glutin_glx_sys", "glutin_wgl_sys", "lazy_static", - "libloading", + "libloading 0.6.7", "log", "objc", "osmesa-sys", @@ -905,9 +900,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.86" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" +checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" [[package]] name = "libloading" @@ -919,6 +914,16 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "libloading" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +dependencies = [ + "cfg-if 1.0.0", + "winapi 0.3.9", +] + [[package]] name = "linked-hash-map" version = "0.5.4" @@ -927,9 +932,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "lock_api" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176" dependencies = [ "scopeguard", ] @@ -944,17 +949,6 @@ dependencies = [ "serde", ] -[[package]] -name = "loom" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d44c73b4636e497b4917eb21c33539efa3816741a2d3ff26c6316f1b529481a4" -dependencies = [ - "cfg-if 1.0.0", - "generator", - "scoped-tls", -] - [[package]] name = "malloc_buf" version = "0.0.6" @@ -1020,7 +1014,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c274c3c52dcd1d78c5d7ed841eca1e9ea2db8353f3b8ec25789cc62c471aaf" dependencies = [ "mio", - "miow 0.3.6", + "miow 0.3.7", "spsc-buffer", "winapi 0.3.9", ] @@ -1051,11 +1045,10 @@ dependencies = [ [[package]] name = "miow" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" dependencies = [ - "socket2", "winapi 0.3.9", ] @@ -1129,9 +1122,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.19.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" +checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" dependencies = [ "bitflags", "cc", @@ -1151,9 +1144,9 @@ dependencies = [ [[package]] name = "notify" -version = "4.0.15" +version = "4.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd" +checksum = "2599080e87c9bd051ddb11b10074f4da7b1223298df65d4c2ec5bcf309af1533" dependencies = [ "bitflags", "filetime", @@ -1220,9 +1213,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.5.2" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" [[package]] name = "osmesa-sys" @@ -1262,7 +1255,7 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.2.5", + "redox_syscall 0.2.6", "smallvec", "winapi 0.3.9", ] @@ -1301,9 +1294,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" dependencies = [ "unicode-xid", ] @@ -1334,9 +1327,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041" dependencies = [ "bitflags", ] @@ -1364,9 +1357,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.22" +version = "0.6.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" +checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" [[package]] name = "rust-argon2" @@ -1390,12 +1383,6 @@ dependencies = [ "owned_ttf_parser", ] -[[package]] -name = "rustversion" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd" - [[package]] name = "ryu" version = "1.0.5" @@ -1425,18 +1412,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.123" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" +checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.123" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" +checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" dependencies = [ "proc-macro2", "quote", @@ -1445,9 +1432,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.62" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" dependencies = [ "itoa", "ryu", @@ -1531,14 +1518,14 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "smithay-client-toolkit" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "316e13a3eb853ce7bf72ad3530dc186cb2005c57c521ef5f4ada5ee4eed74de6" +checksum = "4750c76fd5d3ac95fa3ed80fe667d6a3d8590a960e5b575b98eea93339a80b80" dependencies = [ "andrew", "bitflags", "calloop", - "dlib", + "dlib 0.4.2", "lazy_static", "log", "memmap2", @@ -1558,17 +1545,6 @@ dependencies = [ "wayland-client", ] -[[package]] -name = "socket2" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "winapi 0.3.9", -] - [[package]] name = "spsc-buffer" version = "0.1.1" @@ -1589,9 +1565,9 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" [[package]] name = "syn" -version = "1.0.60" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" dependencies = [ "proc-macro2", "quote", @@ -1679,9 +1655,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "vswhom" @@ -1705,9 +1681,9 @@ dependencies = [ [[package]] name = "vte" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2df25fed2855d2fbcbdf1016c69a6ac070fa1aabc8b5d7aedaab8703dce0d2d6" +checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" dependencies = [ "utf8parse", "vte_generate_state_changes", @@ -1725,9 +1701,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", "winapi 0.3.9", @@ -1748,14 +1724,14 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wayland-client" -version = "0.28.3" +version = "0.28.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbdbe01d03b2267809f3ed99495b37395387fde789e0f2ebb78e8b43f75b6d7" +checksum = "06ca44d86554b85cf449f1557edc6cc7da935cc748c8e4bf1c507cbd43bae02c" dependencies = [ "bitflags", "downcast-rs", "libc", - "nix 0.18.0", + "nix 0.20.0", "scoped-tls", "wayland-commons", "wayland-scanner", @@ -1764,11 +1740,11 @@ dependencies = [ [[package]] name = "wayland-commons" -version = "0.28.3" +version = "0.28.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "480450f76717edd64ad04a4426280d737fc3d10a236b982df7b1aee19f0e2d56" +checksum = "8bd75ae380325dbcff2707f0cd9869827ea1d2d6d534cff076858d3f0460fd5a" dependencies = [ - "nix 0.18.0", + "nix 0.20.0", "once_cell", "smallvec", "wayland-sys", @@ -1776,20 +1752,20 @@ dependencies = [ [[package]] name = "wayland-cursor" -version = "0.28.3" +version = "0.28.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6eb122c160223a7660feeaf949d0100281d1279acaaed3720eb3c9894496e5f" +checksum = "b37e5455ec72f5de555ec39b5c3704036ac07c2ecd50d0bffe02d5fe2d4e65ab" dependencies = [ - "nix 0.18.0", + "nix 0.20.0", "wayland-client", "xcursor", ] [[package]] name = "wayland-egl" -version = "0.28.3" +version = "0.28.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c653507447113c967a1aeee413699acb42d96d6302ec967c6d51930eae8aa7f5" +checksum = "9461a67930ec16da7a4fd8b50e9ffa23f4417240b43ec84008bd1b2c94421c94" dependencies = [ "wayland-client", "wayland-sys", @@ -1797,9 +1773,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.28.3" +version = "0.28.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "319a82b4d3054dd25acc32d9aee0f84fa95b63bc983fffe4703b6b8d47e01a30" +checksum = "95df3317872bcf9eec096c864b69aa4769a1d5d6291a5b513f8ba0af0efbd52c" dependencies = [ "bitflags", "wayland-client", @@ -1809,9 +1785,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.28.3" +version = "0.28.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7010ba5767b3fcd350decc59055390b4ebe6bd1b9279a9feb1f1888987f1133d" +checksum = "389d680d7bd67512dc9c37f39560224327038deb0f0e8d33f870900441b68720" dependencies = [ "proc-macro2", "quote", @@ -1820,11 +1796,11 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.28.3" +version = "0.28.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6793834e0c35d11fd96a97297abe03d37be627e1847da52e17d7e0e3b51cc099" +checksum = "2907bd297eef464a95ba9349ea771611771aa285b932526c633dc94d5400a8e2" dependencies = [ - "dlib", + "dlib 0.5.0", "lazy_static", "pkg-config", ] diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml index c29362e7..c6947009 100644 --- a/alacritty_terminal/Cargo.toml +++ b/alacritty_terminal/Cargo.toml @@ -28,7 +28,7 @@ regex-automata = "0.1.9" dirs = "3.0.1" [target.'cfg(unix)'.dependencies] -nix = "0.19.0" +nix = "0.20.0" signal-hook = { version = "0.1", features = ["mio-support"] } [target.'cfg(windows)'.dependencies] -- cgit From a312e415951fcb9156572f124b42f68c09f60ae9 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 17 Apr 2021 19:28:23 +0000 Subject: Fix selection flooding Wayland connection This resolves an issue where an excessive clipboard update frequency would cause the Wayland display server to ignore necessary selection updates. Instead of copying the selection to the clipboard during the selection process, it is now only copied once the mouse button is released. Fixes #4953. --- alacritty/src/event.rs | 7 ++++--- alacritty/src/input.rs | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index e61354b8..612a0cc0 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -206,7 +206,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon // Update selection. if self.terminal.mode().contains(TermMode::VI) - && self.terminal.selection.as_ref().map(|s| s.is_empty()) != Some(true) + && self.terminal.selection.as_ref().map_or(true, |s| !s.is_empty()) { self.update_selection(self.terminal.vi_mode_cursor.point, Side::Right); } else if self.mouse.left_button_state == ElementState::Pressed @@ -216,6 +216,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon let point = self.mouse.point(&self.size_info(), display_offset); self.update_selection(point, self.mouse.cell_side); } + self.copy_selection(ClipboardType::Selection); *self.dirty = true; } @@ -262,8 +263,6 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon self.terminal.selection = Some(selection); *self.dirty = true; - - self.copy_selection(ClipboardType::Selection); } fn start_selection(&mut self, ty: SelectionType, point: Point, side: Side) { @@ -461,6 +460,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon let end = *focused_match.end(); self.start_selection(SelectionType::Simple, start, Side::Left); self.update_selection(end, Side::Right); + self.copy_selection(ClipboardType::Selection); } self.search_state.dfas = None; @@ -656,6 +656,7 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext for ActionCon HintAction::Action(HintInternalAction::Select) => { self.start_selection(SelectionType::Simple, *hint.bounds.start(), Side::Left); self.update_selection(*hint.bounds.end(), Side::Right); + self.copy_selection(ClipboardType::Selection); }, // Move the vi mode cursor. HintAction::Action(HintInternalAction::MoveViModeCursor) => { diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 977a6e5d..3559b85e 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -634,6 +634,9 @@ impl> Processor { self.ctx.display().highlighted_hint = hint; self.ctx.scheduler_mut().unschedule(TimerId::SelectionScrolling); + + // Copy selection on release, to prevent flooding the display server. + self.ctx.copy_selection(ClipboardType::Selection); } pub fn mouse_wheel_input(&mut self, delta: MouseScrollDelta, phase: TouchPhase) { -- cgit From 28abb1f9c78ab316126bdf94e2ca12f034f1d8fd Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 17 Apr 2021 23:20:13 +0000 Subject: Fix out of order terminal query responses This forces all responses made to the PTY through the indirection of the UI event loop, making sure that the writes to the PTY are in the same order as the original requests. This just delays all escape sequences by forcing them through the event loop, ideally all responses which are not asynchronous (like a clipboard read) would be made immediately. However since some escapes require feedback from the UI to mutable structures like the config (e.g. color query escapes), this would require additional locking. Fixes #4872. --- alacritty/src/event.rs | 1 + alacritty_terminal/src/ansi.rs | 89 +++++++++++++++--------------------- alacritty_terminal/src/event.rs | 4 ++ alacritty_terminal/src/event_loop.rs | 4 +- alacritty_terminal/src/term/mod.rs | 29 +++++++----- alacritty_terminal/tests/ref.rs | 4 +- 6 files changed, 64 insertions(+), 67 deletions(-) diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs index 612a0cc0..a895514f 100644 --- a/alacritty/src/event.rs +++ b/alacritty/src/event.rs @@ -1222,6 +1222,7 @@ impl Processor { let text = format(processor.ctx.display.colors[index]); processor.ctx.write_to_pty(text.into_bytes()); }, + TerminalEvent::PtyWrite(text) => processor.ctx.write_to_pty(text.into_bytes()), TerminalEvent::MouseCursorDirty => processor.reset_mouse_cursor(), TerminalEvent::Exit => (), TerminalEvent::CursorBlinkingChange(_) => { diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs index 44c92d3e..55492d36 100644 --- a/alacritty_terminal/src/ansi.rs +++ b/alacritty_terminal/src/ansi.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use std::time::{Duration, Instant}; -use std::{io, iter, str}; +use std::{iter, str}; use log::{debug, trace}; use serde::{Deserialize, Serialize}; @@ -153,29 +153,27 @@ impl Processor { /// Process a new byte from the PTY. #[inline] - pub fn advance(&mut self, handler: &mut H, byte: u8, writer: &mut W) + pub fn advance(&mut self, handler: &mut H, byte: u8) where H: Handler, - W: io::Write, { if self.state.sync_state.timeout.is_none() { - let mut performer = Performer::new(&mut self.state, handler, writer); + let mut performer = Performer::new(&mut self.state, handler); self.parser.advance(&mut performer, byte); } else { - self.advance_sync(handler, byte, writer); + self.advance_sync(handler, byte); } } /// End a synchronized update. - pub fn stop_sync(&mut self, handler: &mut H, writer: &mut W) + pub fn stop_sync(&mut self, handler: &mut H) where H: Handler, - W: io::Write, { // Process all synchronized bytes. for i in 0..self.state.sync_state.buffer.len() { let byte = self.state.sync_state.buffer[i]; - let mut performer = Performer::new(&mut self.state, handler, writer); + let mut performer = Performer::new(&mut self.state, handler); self.parser.advance(&mut performer, byte); } @@ -198,16 +196,15 @@ impl Processor { /// Process a new byte during a synchronized update. #[cold] - fn advance_sync(&mut self, handler: &mut H, byte: u8, writer: &mut W) + fn advance_sync(&mut self, handler: &mut H, byte: u8) where H: Handler, - W: io::Write, { self.state.sync_state.buffer.push(byte); // Handle sync DCS escape sequences. match self.state.sync_state.pending_dcs { - Some(_) => self.advance_sync_dcs_end(handler, byte, writer), + Some(_) => self.advance_sync_dcs_end(handler, byte), None => self.advance_sync_dcs_start(), } } @@ -228,10 +225,9 @@ impl Processor { } /// Parse the DCS termination sequence for synchronized updates. - fn advance_sync_dcs_end(&mut self, handler: &mut H, byte: u8, writer: &mut W) + fn advance_sync_dcs_end(&mut self, handler: &mut H, byte: u8) where H: Handler, - W: io::Write, { match byte { // Ignore DCS passthrough characters. @@ -243,7 +239,7 @@ impl Processor { Some(Dcs::SyncStart) => { self.state.sync_state.timeout = Some(Instant::now() + SYNC_UPDATE_TIMEOUT); }, - Some(Dcs::SyncEnd) => self.stop_sync(handler, writer), + Some(Dcs::SyncEnd) => self.stop_sync(handler), None => (), }, } @@ -254,21 +250,16 @@ impl Processor { /// /// Processor creates a Performer when running advance and passes the Performer /// to `vte::Parser`. -struct Performer<'a, H: Handler, W: io::Write> { +struct Performer<'a, H: Handler> { state: &'a mut ProcessorState, handler: &'a mut H, - writer: &'a mut W, } -impl<'a, H: Handler + 'a, W: io::Write> Performer<'a, H, W> { +impl<'a, H: Handler + 'a> Performer<'a, H> { /// Create a performer. #[inline] - pub fn new<'b>( - state: &'b mut ProcessorState, - handler: &'b mut H, - writer: &'b mut W, - ) -> Performer<'b, H, W> { - Performer { state, handler, writer } + pub fn new<'b>(state: &'b mut ProcessorState, handler: &'b mut H) -> Performer<'b, H> { + Performer { state, handler } } } @@ -308,10 +299,10 @@ pub trait Handler { fn move_down(&mut self, _: usize) {} /// Identify the terminal (should write back to the pty stream). - fn identify_terminal(&mut self, _: &mut W, _intermediate: Option) {} + fn identify_terminal(&mut self, _intermediate: Option) {} /// Report device status. - fn device_status(&mut self, _: &mut W, _: usize) {} + fn device_status(&mut self, _: usize) {} /// Move cursor forward `cols`. fn move_forward(&mut self, _: Column) {} @@ -461,10 +452,10 @@ pub trait Handler { fn pop_title(&mut self) {} /// Report text area size in pixels. - fn text_area_size_pixels(&mut self, _: &mut W) {} + fn text_area_size_pixels(&mut self) {} /// Report text area size in characters. - fn text_area_size_chars(&mut self, _: &mut W) {} + fn text_area_size_chars(&mut self) {} } /// Terminal cursor configuration. @@ -883,10 +874,9 @@ impl StandardCharset { } } -impl<'a, H, W> vte::Perform for Performer<'a, H, W> +impl<'a, H> vte::Perform for Performer<'a, H> where H: Handler + 'a, - W: io::Write + 'a, { #[inline] fn print(&mut self, c: char) { @@ -1115,7 +1105,6 @@ where let mut params_iter = params.iter(); let handler = &mut self.handler; - let writer = &mut self.writer; let mut next_param_or = |default: u16| { params_iter.next().map(|param| param[0]).filter(|¶m| param != 0).unwrap_or(default) @@ -1136,7 +1125,7 @@ where }, ('C', []) | ('a', []) => handler.move_forward(Column(next_param_or(1) as usize)), ('c', intermediates) if next_param_or(0) == 0 => { - handler.identify_terminal(writer, intermediates.get(0).map(|&i| i as char)) + handler.identify_terminal(intermediates.get(0).map(|&i| i as char)) }, ('D', []) => handler.move_backward(Column(next_param_or(1) as usize)), ('d', []) => handler.goto_line(Line(next_param_or(1) as i32 - 1)), @@ -1218,7 +1207,7 @@ where } } }, - ('n', []) => handler.device_status(writer, next_param_or(0) as usize), + ('n', []) => handler.device_status(next_param_or(0) as usize), ('P', []) => handler.delete_chars(next_param_or(1) as usize), ('q', [b' ']) => { // DECSCUSR (CSI Ps SP q) -- Set Cursor Style. @@ -1249,8 +1238,8 @@ where ('s', []) => handler.save_cursor_position(), ('T', []) => handler.scroll_down(next_param_or(1) as usize), ('t', []) => match next_param_or(1) as usize { - 14 => handler.text_area_size_pixels(writer), - 18 => handler.text_area_size_chars(writer), + 14 => handler.text_area_size_pixels(), + 18 => handler.text_area_size_chars(), 22 => handler.push_title(), 23 => handler.pop_title(), _ => unhandled!(), @@ -1298,7 +1287,7 @@ where }, (b'H', []) => self.handler.set_horizontal_tabstop(), (b'M', []) => self.handler.reverse_index(), - (b'Z', []) => self.handler.identify_terminal(self.writer, None), + (b'Z', []) => self.handler.identify_terminal(None), (b'c', []) => self.handler.reset_state(), (b'0', intermediates) => { configure_charset!(StandardCharset::SpecialCharacterAndLineDrawing, intermediates) @@ -1494,11 +1483,9 @@ pub mod C0 { // Byte sequences used in these tests are recording of pty stdout. #[cfg(test)] mod tests { - use super::{ - parse_number, xparse_color, Attr, CharsetIndex, Color, Handler, Processor, StandardCharset, - }; + use super::*; + use crate::term::color::Rgb; - use std::io; struct MockHandler { index: CharsetIndex, @@ -1521,7 +1508,7 @@ mod tests { self.index = index; } - fn identify_terminal(&mut self, _: &mut W, _intermediate: Option) { + fn identify_terminal(&mut self, _intermediate: Option) { self.identity_reported = true; } @@ -1549,7 +1536,7 @@ mod tests { let mut handler = MockHandler::default(); for byte in BYTES { - parser.advance(&mut handler, *byte, &mut io::sink()); + parser.advance(&mut handler, *byte); } assert_eq!(handler.attr, Some(Attr::Bold)); @@ -1563,7 +1550,7 @@ mod tests { let mut handler = MockHandler::default(); for byte in bytes { - parser.advance(&mut handler, *byte, &mut io::sink()); + parser.advance(&mut handler, *byte); } assert!(!handler.identity_reported); @@ -1572,7 +1559,7 @@ mod tests { let bytes: &[u8] = &[0x1b, b'[', b'c']; for byte in bytes { - parser.advance(&mut handler, *byte, &mut io::sink()); + parser.advance(&mut handler, *byte); } assert!(handler.identity_reported); @@ -1581,7 +1568,7 @@ mod tests { let bytes: &[u8] = &[0x1b, b'[', b'0', b'c']; for byte in bytes { - parser.advance(&mut handler, *byte, &mut io::sink()); + parser.advance(&mut handler, *byte); } assert!(handler.identity_reported); @@ -1595,7 +1582,7 @@ mod tests { let mut handler = MockHandler::default(); for byte in bytes { - parser.advance(&mut handler, *byte, &mut io::sink()); + parser.advance(&mut handler, *byte); } assert!(handler.identity_reported); @@ -1607,7 +1594,7 @@ mod tests { let mut handler = MockHandler::default(); for byte in bytes { - parser.advance(&mut handler, *byte, &mut io::sink()); + parser.advance(&mut handler, *byte); } assert!(!handler.identity_reported); @@ -1625,7 +1612,7 @@ mod tests { let mut handler = MockHandler::default(); for byte in BYTES { - parser.advance(&mut handler, *byte, &mut io::sink()); + parser.advance(&mut handler, *byte); } let spec = Rgb { r: 128, g: 66, b: 255 }; @@ -1656,7 +1643,7 @@ mod tests { let mut parser = Processor::new(); for byte in BYTES { - parser.advance(&mut handler, *byte, &mut io::sink()); + parser.advance(&mut handler, *byte); } } @@ -1667,7 +1654,7 @@ mod tests { let mut handler = MockHandler::default(); for byte in BYTES { - parser.advance(&mut handler, *byte, &mut io::sink()); + parser.advance(&mut handler, *byte); } assert_eq!(handler.index, CharsetIndex::G0); @@ -1681,14 +1668,14 @@ mod tests { let mut handler = MockHandler::default(); for byte in &BYTES[..3] { - parser.advance(&mut handler, *byte, &mut io::sink()); + parser.advance(&mut handler, *byte); } assert_eq!(handler.index, CharsetIndex::G1); assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing); let mut handler = MockHandler::default(); - parser.advance(&mut handler, BYTES[3], &mut io::sink()); + parser.advance(&mut handler, BYTES[3]); assert_eq!(handler.index, CharsetIndex::G1); } diff --git a/alacritty_terminal/src/event.rs b/alacritty_terminal/src/event.rs index 70d16127..fac7a56a 100644 --- a/alacritty_terminal/src/event.rs +++ b/alacritty_terminal/src/event.rs @@ -35,6 +35,9 @@ pub enum Event { /// expected escape sequence format. ColorRequest(usize, Arc String + Sync + Send + 'static>), + /// Write some text to the PTY. + PtyWrite(String), + /// Cursor blinking state has changed. CursorBlinkingChange(bool), @@ -57,6 +60,7 @@ impl Debug for Event { Event::ClipboardStore(ty, text) => write!(f, "ClipboardStore({:?}, {})", ty, text), Event::ClipboardLoad(ty, _) => write!(f, "ClipboardLoad({:?})", ty), Event::ColorRequest(index, _) => write!(f, "ColorRequest({})", index), + Event::PtyWrite(text) => write!(f, "PtyWrite({})", text), Event::Wakeup => write!(f, "Wakeup"), Event::Bell => write!(f, "Bell"), Event::Exit => write!(f, "Exit"), diff --git a/alacritty_terminal/src/event_loop.rs b/alacritty_terminal/src/event_loop.rs index c3224dfe..098ee896 100644 --- a/alacritty_terminal/src/event_loop.rs +++ b/alacritty_terminal/src/event_loop.rs @@ -240,7 +240,7 @@ where // Run the parser. for byte in &buf[..got] { - state.parser.advance(&mut **terminal, *byte, &mut self.pty.writer()); + state.parser.advance(&mut **terminal, *byte); } // Exit if we've processed enough bytes. @@ -334,7 +334,7 @@ where // Handle synchronized update timeout. if events.is_empty() { - state.parser.stop_sync(&mut *self.terminal.lock(), &mut self.pty.writer()); + state.parser.stop_sync(&mut *self.terminal.lock()); self.event_proxy.send_event(Event::Wakeup); continue; } diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index c4425916..199fd207 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -3,7 +3,7 @@ use std::cmp::{max, min}; use std::ops::{Index, IndexMut, Range}; use std::sync::Arc; -use std::{io, mem, ptr, str}; +use std::{mem, ptr, str}; use bitflags::bitflags; use log::{debug, trace}; @@ -968,32 +968,35 @@ impl Handler for Term { } #[inline] - fn identify_terminal(&mut self, writer: &mut W, intermediate: Option) { + fn identify_terminal(&mut self, intermediate: Option) { match intermediate { None => { trace!("Reporting primary device attributes"); - let _ = writer.write_all(b"\x1b[?6c"); + let text = String::from("\x1b[?6c"); + self.event_proxy.send_event(Event::PtyWrite(text)); }, Some('>') => { trace!("Reporting secondary device attributes"); let version = version_number(env!("CARGO_PKG_VERSION")); - let _ = writer.write_all(format!("\x1b[>0;{};1c", version).as_bytes()); + let text = format!("\x1b[>0;{};1c", version); + self.event_proxy.send_event(Event::PtyWrite(text)); }, _ => debug!("Unsupported device attributes intermediate"), } } #[inline] - fn device_status(&mut self, writer: &mut W, arg: usize) { + fn device_status(&mut self, arg: usize) { trace!("Reporting device status: {}", arg); match arg { 5 => { - let _ = writer.write_all(b"\x1b[0n"); + let text = String::from("\x1b[0n"); + self.event_proxy.send_event(Event::PtyWrite(text)); }, 6 => { let pos = self.grid.cursor.point; - let response = format!("\x1b[{};{}R", pos.line + 1, pos.column + 1); - let _ = writer.write_all(response.as_bytes()); + let text = format!("\x1b[{};{}R", pos.line + 1, pos.column + 1); + self.event_proxy.send_event(Event::PtyWrite(text)); }, _ => debug!("unknown device status query: {}", arg), }; @@ -1687,15 +1690,17 @@ impl Handler for Term { } #[inline] - fn text_area_size_pixels(&mut self, writer: &mut W) { + fn text_area_size_pixels(&mut self) { let width = self.cell_width * self.columns(); let height = self.cell_height * self.screen_lines(); - let _ = write!(writer, "\x1b[4;{};{}t", height, width); + let text = format!("\x1b[4;{};{}t", height, width); + self.event_proxy.send_event(Event::PtyWrite(text)); } #[inline] - fn text_area_size_chars(&mut self, writer: &mut W) { - let _ = write!(writer, "\x1b[8;{};{}t", self.screen_lines(), self.columns()); + fn text_area_size_chars(&mut self) { + let text = format!("\x1b[8;{};{}t", self.screen_lines(), self.columns()); + self.event_proxy.send_event(Event::PtyWrite(text)); } } diff --git a/alacritty_terminal/tests/ref.rs b/alacritty_terminal/tests/ref.rs index e229b6d2..a9968736 100644 --- a/alacritty_terminal/tests/ref.rs +++ b/alacritty_terminal/tests/ref.rs @@ -2,7 +2,7 @@ use serde::Deserialize; use serde_json as json; use std::fs::{self, File}; -use std::io::{self, Read}; +use std::io::Read; use std::path::Path; use alacritty_terminal::ansi; @@ -108,7 +108,7 @@ fn ref_test(dir: &Path) { let mut parser = ansi::Processor::new(); for byte in recording { - parser.advance(&mut terminal, byte, &mut io::sink()); + parser.advance(&mut terminal, byte); } // Truncate invisible lines from the grid. -- cgit