diff options
author | Christian Duerr <contact@christianduerr.com> | 2024-11-02 20:05:51 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-02 20:05:51 +0000 |
commit | fd745a9f4cb3ba81623167c9d1117747353db33a (patch) | |
tree | effc8817d4d024dd80477fce123bcd2335b95874 /alacritty/src/display | |
parent | 39ea7271e32ad88280191d8040d6f8feafe4307a (diff) | |
download | r-alacritty-fd745a9f4cb3ba81623167c9d1117747353db33a.tar.gz r-alacritty-fd745a9f4cb3ba81623167c9d1117747353db33a.tar.bz2 r-alacritty-fd745a9f4cb3ba81623167c9d1117747353db33a.zip |
Fix racing condition in hint triggering
This fixes an issue with hints where it was possible that the terminal
content of highlighted hints changed between the highlighted hint update
and the activation of the hint.
This patch always validates the hint's text content against the hint
itself to ensure that the content is still valid for the original hint
which triggered the highlight.
Closes #8277.
Diffstat (limited to 'alacritty/src/display')
-rw-r--r-- | alacritty/src/display/color.rs | 6 | ||||
-rw-r--r-- | alacritty/src/display/content.rs | 6 | ||||
-rw-r--r-- | alacritty/src/display/damage.rs | 2 | ||||
-rw-r--r-- | alacritty/src/display/hint.rs | 52 | ||||
-rw-r--r-- | alacritty/src/display/meter.rs | 2 |
5 files changed, 45 insertions, 23 deletions
diff --git a/alacritty/src/display/color.rs b/alacritty/src/display/color.rs index 669bf502..2e854a3f 100644 --- a/alacritty/src/display/color.rs +++ b/alacritty/src/display/color.rs @@ -18,7 +18,7 @@ pub const DIM_FACTOR: f32 = 0.66; #[derive(Copy, Clone)] pub struct List([Rgb; COUNT]); -impl<'a> From<&'a Colors> for List { +impl From<&'_ Colors> for List { fn from(colors: &Colors) -> List { // Type inference fails without this annotation. let mut list = List([Rgb::default(); COUNT]); @@ -235,7 +235,7 @@ impl<'de> Deserialize<'de> for Rgb { b: u8, } - impl<'a> Visitor<'a> for RgbVisitor { + impl Visitor<'_> for RgbVisitor { type Value = Rgb; fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result { @@ -331,7 +331,7 @@ impl<'de> Deserialize<'de> for CellRgb { const EXPECTING: &str = "CellForeground, CellBackground, or hex color like #ff00ff"; struct CellRgbVisitor; - impl<'a> Visitor<'a> for CellRgbVisitor { + impl Visitor<'_> for CellRgbVisitor { type Value = CellRgb; fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result { diff --git a/alacritty/src/display/content.rs b/alacritty/src/display/content.rs index 2fbbdec4..edd2709f 100644 --- a/alacritty/src/display/content.rs +++ b/alacritty/src/display/content.rs @@ -150,7 +150,7 @@ impl<'a> RenderableContent<'a> { } } -impl<'a> Iterator for RenderableContent<'a> { +impl Iterator for RenderableContent<'_> { type Item = RenderableCell; /// Gets the next renderable cell. @@ -453,7 +453,7 @@ struct Hint<'a> { labels: &'a Vec<Vec<char>>, } -impl<'a> Hint<'a> { +impl Hint<'_> { /// Advance the hint iterator. /// /// If the point is within a hint, the keyboard shortcut character that should be displayed at @@ -543,7 +543,7 @@ impl<'a> HintMatches<'a> { } } -impl<'a> Deref for HintMatches<'a> { +impl Deref for HintMatches<'_> { type Target = [Match]; fn deref(&self) -> &Self::Target { diff --git a/alacritty/src/display/damage.rs b/alacritty/src/display/damage.rs index fc6aef39..b0736375 100644 --- a/alacritty/src/display/damage.rs +++ b/alacritty/src/display/damage.rs @@ -251,7 +251,7 @@ impl<'a> RenderDamageIterator<'a> { } } -impl<'a> Iterator for RenderDamageIterator<'a> { +impl Iterator for RenderDamageIterator<'_> { type Item = Rect; fn next(&mut self) -> Option<Rect> { diff --git a/alacritty/src/display/hint.rs b/alacritty/src/display/hint.rs index a01a1d03..3f10b4e5 100644 --- a/alacritty/src/display/hint.rs +++ b/alacritty/src/display/hint.rs @@ -1,6 +1,8 @@ +use std::borrow::Cow; use std::cmp::Reverse; use std::collections::HashSet; use std::iter; +use std::rc::Rc; use ahash::RandomState; use winit::keyboard::ModifiersState; @@ -23,7 +25,7 @@ const HINT_SPLIT_PERCENTAGE: f32 = 0.5; /// Keyboard regex hint state. pub struct HintState { /// Hint currently in use. - hint: Option<Hint>, + hint: Option<Rc<Hint>>, /// Alphabet for hint labels. alphabet: String, @@ -56,7 +58,7 @@ impl HintState { } /// Start the hint selection process. - pub fn start(&mut self, hint: Hint) { + pub fn start(&mut self, hint: Rc<Hint>) { self.hint = Some(hint); } @@ -150,7 +152,7 @@ impl HintState { // Check if the selected label is fully matched. if label.len() == 1 { let bounds = self.matches[index].clone(); - let action = hint.action.clone(); + let hint = hint.clone(); // Exit hint mode unless it requires explicit dismissal. if hint.persist { @@ -161,7 +163,7 @@ impl HintState { // Hyperlinks take precedence over regex matches. let hyperlink = term.grid()[*bounds.start()].hyperlink(); - Some(HintMatch { action, bounds, hyperlink }) + Some(HintMatch { bounds, hyperlink, hint }) } else { // Store character to preserve the selection. self.keys.push(c); @@ -192,13 +194,14 @@ impl HintState { /// Hint match which was selected by the user. #[derive(PartialEq, Eq, Debug, Clone)] pub struct HintMatch { - /// Action for handling the text. - action: HintAction, - /// Terminal range matching the hint. bounds: Match, + /// OSC 8 hyperlink. hyperlink: Option<Hyperlink>, + + /// Hint which triggered this match. + hint: Rc<Hint>, } impl HintMatch { @@ -210,7 +213,7 @@ impl HintMatch { #[inline] pub fn action(&self) -> &HintAction { - &self.action + &self.hint.action } #[inline] @@ -221,6 +224,29 @@ impl HintMatch { pub fn hyperlink(&self) -> Option<&Hyperlink> { self.hyperlink.as_ref() } + + /// Get the text content of the hint match. + /// + /// This will always revalidate the hint text, to account for terminal content + /// changes since the [`HintMatch`] was constructed. The text of the hint might + /// be different from its original value, but it will **always** be a valid + /// match for this hint. + pub fn text<T>(&self, term: &Term<T>) -> Option<Cow<'_, str>> { + // Revalidate hyperlink match. + if let Some(hyperlink) = &self.hyperlink { + let (validated, bounds) = hyperlink_at(term, *self.bounds.start())?; + return (&validated == hyperlink && bounds == self.bounds) + .then(|| hyperlink.uri().into()); + } + + // Revalidate regex match. + let regex = self.hint.content.regex.as_ref()?; + let bounds = regex.with_compiled(|regex| { + regex_match_at(term, *self.bounds.start(), regex, self.hint.post_processing) + })??; + (bounds == self.bounds) + .then(|| term.bounds_to_string(*bounds.start(), *bounds.end()).into()) + } } /// Generator for creating new hint labels. @@ -382,18 +408,14 @@ pub fn highlighted_at<T>( if let Some((hyperlink, bounds)) = hint.content.hyperlinks.then(|| hyperlink_at(term, point)).flatten() { - return Some(HintMatch { - bounds, - action: hint.action.clone(), - hyperlink: Some(hyperlink), - }); + return Some(HintMatch { bounds, hyperlink: Some(hyperlink), hint: hint.clone() }); } let bounds = hint.content.regex.as_ref().and_then(|regex| { regex.with_compiled(|regex| regex_match_at(term, point, regex, hint.post_processing)) }); if let Some(bounds) = bounds.flatten() { - return Some(HintMatch { bounds, action: hint.action.clone(), hyperlink: None }); + return Some(HintMatch { bounds, hint: hint.clone(), hyperlink: None }); } None @@ -554,7 +576,7 @@ impl<'a, T> HintPostProcessor<'a, T> { } } -impl<'a, T> Iterator for HintPostProcessor<'a, T> { +impl<T> Iterator for HintPostProcessor<'_, T> { type Item = Match; fn next(&mut self) -> Option<Self::Item> { diff --git a/alacritty/src/display/meter.rs b/alacritty/src/display/meter.rs index e263100f..20cbbd1b 100644 --- a/alacritty/src/display/meter.rs +++ b/alacritty/src/display/meter.rs @@ -57,7 +57,7 @@ impl<'a> Sampler<'a> { } } -impl<'a> Drop for Sampler<'a> { +impl Drop for Sampler<'_> { fn drop(&mut self) { self.meter.add_sample(self.alive_duration()); } |