use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; use bitflags::bitflags; use serde::{Deserialize, Serialize}; use crate::ansi::{Color, NamedColor}; use crate::grid::{self, GridCell}; use crate::index::Column; bitflags! { #[derive(Serialize, Deserialize)] pub struct Flags: u16 { const INVERSE = 0b0000_0000_0000_0001; const BOLD = 0b0000_0000_0000_0010; const ITALIC = 0b0000_0000_0000_0100; const BOLD_ITALIC = 0b0000_0000_0000_0110; const UNDERLINE = 0b0000_0000_0000_1000; const WRAPLINE = 0b0000_0000_0001_0000; const WIDE_CHAR = 0b0000_0000_0010_0000; const WIDE_CHAR_SPACER = 0b0000_0000_0100_0000; const DIM = 0b0000_0000_1000_0000; const DIM_BOLD = 0b0000_0000_1000_0010; const HIDDEN = 0b0000_0001_0000_0000; const STRIKEOUT = 0b0000_0010_0000_0000; const LEADING_WIDE_CHAR_SPACER = 0b0000_0100_0000_0000; const DOUBLE_UNDERLINE = 0b0000_1000_0000_0000; const UNDERCURL = 0b0001_0000_0000_0000; const DOTTED_UNDERLINE = 0b0010_0000_0000_0000; const DASHED_UNDERLINE = 0b0100_0000_0000_0000; const ALL_UNDERLINES = Self::UNDERLINE.bits | Self::DOUBLE_UNDERLINE.bits | Self::UNDERCURL.bits | Self::DOTTED_UNDERLINE.bits | Self::DASHED_UNDERLINE.bits; } } /// Counter for hyperlinks without explicit ID. static HYPERLINK_ID_SUFFIX: AtomicU32 = AtomicU32::new(0); #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] pub struct Hyperlink { inner: Arc, } impl Hyperlink { pub fn new(id: Option, uri: T) -> Self { let inner = Arc::new(HyperlinkInner::new(id, uri)); Self { inner } } pub fn id(&self) -> &str { &self.inner.id } pub fn uri(&self) -> &str { &self.inner.uri } } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] struct HyperlinkInner { /// Identifier for the given hyperlink. id: String, /// Resource identifier of the hyperlink. uri: String, } impl HyperlinkInner { pub fn new(id: Option, uri: T) -> Self { let id = match id { Some(id) => id.to_string(), None => { let mut id = HYPERLINK_ID_SUFFIX.fetch_add(1, Ordering::Relaxed).to_string(); id.push_str("_alacritty"); id }, }; Self { id, uri: uri.to_string() } } } /// Trait for determining if a reset should be performed. pub trait ResetDiscriminant { /// Value based on which equality for the reset will be determined. fn discriminant(&self) -> T; } impl ResetDiscriminant for T { fn discriminant(&self) -> T { *self } } impl ResetDiscriminant for Cell { fn discriminant(&self) -> Color { self.bg } } /// Dynamically allocated cell content. /// /// This storage is reserved for cell attributes which are rarely set. This allows reducing the /// allocation required ahead of time for every cell, with some additional overhead when the extra /// storage is actually required. #[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)] pub struct CellExtra { zerowidth: Vec, underline_color: Option, hyperlink: Option, } /// Content and attributes of a single cell in the terminal grid. #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] pub struct Cell { pub c: char, pub fg: Color, pub bg: Color, pub flags: Flags, pub extra: Option>, } impl Default for Cell { #[inline] fn default() -> Cell { Cell { c: ' ', bg: Color::Named(NamedColor::Background), fg: Color::Named(NamedColor::Foreground), flags: Flags::empty(), extra: None, } } } impl Cell { /// Zerowidth characters stored in this cell. #[inline] pub fn zerowidth(&self) -> Option<&[char]> { self.extra.as_ref().map(|extra| extra.zerowidth.as_slice()) } /// Write a new zerowidth character to this cell. #[inline] pub fn push_zerowidth(&mut self, character: char) { let extra = self.extra.get_or_insert(Default::default()); Arc::make_mut(extra).zerowidth.push(character); } /// Remove all wide char data from a cell. #[inline(never)] pub fn clear_wide(&mut self) { self.flags.remove(Flags::WIDE_CHAR); if let Some(extra) = self.extra.as_mut() { Arc::make_mut(extra).zerowidth = Vec::new(); } self.c = ' '; } /// Set underline color on the cell. pub fn set_underline_color(&mut self, color: Option) { // If we reset color and we don't have zerowidth we should drop extra storage. if color.is_none() && self .extra .as_ref() .map_or(true, |extra| !extra.zerowidth.is_empty() || extra.hyperlink.is_some()) { self.extra = None; } else { let extra = self.extra.get_or_insert(Default::default()); Arc::make_mut(extra).underline_color = color; } } /// Underline color stored in this cell. #[inline] pub fn underline_color(&self) -> Option { self.extra.as_ref()?.underline_color } /// Set hyperlink. pub fn set_hyperlink(&mut self, hyperlink: Option) { let should_drop = hyperlink.is_none() && self.extra.as_ref().map_or(true, |extra| { !extra.zerowidth.is_empty() || extra.underline_color.is_some() }); if should_drop { self.extra = None; } else { let extra = self.extra.get_or_insert(Default::default()); Arc::make_mut(extra).hyperlink = hyperlink; } } /// Hyperlink stored in this cell. #[inline] pub fn hyperlink(&self) -> Option { self.extra.as_ref()?.hyperlink.clone() } } impl GridCell for Cell { #[inline] fn is_empty(&self) -> bool { (self.c == ' ' || self.c == '\t') && self.bg == Color::Named(NamedColor::Background) && self.fg == Color::Named(NamedColor::Foreground) && !self.flags.intersects( Flags::INVERSE | Flags::ALL_UNDERLINES | Flags::STRIKEOUT | Flags::WRAPLINE | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER, ) && self.extra.as_ref().map(|extra| extra.zerowidth.is_empty()) != Some(false) } #[inline] fn flags(&self) -> &Flags { &self.flags } #[inline] fn flags_mut(&mut self) -> &mut Flags { &mut self.flags } #[inline] fn reset(&mut self, template: &Self) { *self = Cell { bg: template.bg, ..Cell::default() }; } } impl From for Cell { #[inline] fn from(color: Color) -> Self { Self { bg: color, ..Cell::default() } } } /// Get the length of occupied cells in a line. pub trait LineLength { /// Calculate the occupied line length. fn line_length(&self) -> Column; } impl LineLength for grid::Row { fn line_length(&self) -> Column { let mut length = Column(0); if self[Column(self.len() - 1)].flags.contains(Flags::WRAPLINE) { return Column(self.len()); } for (index, cell) in self[..].iter().rev().enumerate() { if cell.c != ' ' || cell.extra.as_ref().map(|extra| extra.zerowidth.is_empty()) == Some(false) { length = Column(self.len() - index); break; } } length } } #[cfg(test)] mod tests { use super::*; use std::mem; use crate::grid::Row; use crate::index::Column; #[test] fn cell_size_is_below_cap() { // Expected cell size on 64-bit architectures. const EXPECTED_CELL_SIZE: usize = 24; // Ensure that cell size isn't growning by accident. assert!(mem::size_of::() <= EXPECTED_CELL_SIZE); } #[test] fn line_length_works() { let mut row = Row::::new(10); row[Column(5)].c = 'a'; assert_eq!(row.line_length(), Column(6)); } #[test] fn line_length_works_with_wrapline() { let mut row = Row::::new(10); row[Column(9)].flags.insert(super::Flags::WRAPLINE); assert_eq!(row.line_length(), Column(10)); } }