2022-07-10 17:11:28 +00:00
|
|
|
use std::sync::atomic::{AtomicU32, Ordering};
|
2022-03-16 16:27:55 +00:00
|
|
|
use std::sync::Arc;
|
2018-12-10 17:53:56 +00:00
|
|
|
|
2020-11-05 04:45:14 +00:00
|
|
|
use bitflags::bitflags;
|
2019-10-05 00:29:26 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
2019-03-30 16:48:36 +00:00
|
|
|
use crate::ansi::{Color, NamedColor};
|
2019-03-13 18:55:18 +00:00
|
|
|
use crate::grid::{self, GridCell};
|
2018-12-10 17:53:56 +00:00
|
|
|
use crate::index::Column;
|
2016-11-28 22:30:08 +00:00
|
|
|
|
|
|
|
bitflags! {
|
|
|
|
#[derive(Serialize, Deserialize)]
|
2018-12-09 15:28:22 +00:00
|
|
|
pub struct Flags: u16 {
|
2020-08-12 16:05:22 +00:00
|
|
|
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;
|
2022-02-08 17:47:31 +00:00
|
|
|
const UNDERCURL = 0b0001_0000_0000_0000;
|
2022-02-14 16:10:13 +00:00
|
|
|
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;
|
2016-11-28 22:30:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-10 17:11:28 +00:00
|
|
|
/// 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<HyperlinkInner>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Hyperlink {
|
|
|
|
pub fn new<T: ToString>(id: Option<T>, 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<T: ToString>(id: Option<T>, 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() }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-05 04:45:14 +00:00
|
|
|
/// Trait for determining if a reset should be performed.
|
|
|
|
pub trait ResetDiscriminant<T> {
|
|
|
|
/// Value based on which equality for the reset will be determined.
|
|
|
|
fn discriminant(&self) -> T;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: Copy> ResetDiscriminant<T> for T {
|
|
|
|
fn discriminant(&self) -> T {
|
|
|
|
*self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ResetDiscriminant<Color> 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)]
|
2022-03-16 16:27:55 +00:00
|
|
|
pub struct CellExtra {
|
2020-11-05 04:45:14 +00:00
|
|
|
zerowidth: Vec<char>,
|
2022-03-16 16:27:55 +00:00
|
|
|
|
|
|
|
underline_color: Option<Color>,
|
2022-07-10 17:11:28 +00:00
|
|
|
|
|
|
|
hyperlink: Option<Hyperlink>,
|
2018-12-09 15:28:22 +00:00
|
|
|
}
|
|
|
|
|
2020-11-05 04:45:14 +00:00
|
|
|
/// Content and attributes of a single cell in the terminal grid.
|
2022-03-16 16:27:55 +00:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
|
2016-11-28 22:30:08 +00:00
|
|
|
pub struct Cell {
|
|
|
|
pub c: char,
|
|
|
|
pub fg: Color,
|
|
|
|
pub bg: Color,
|
|
|
|
pub flags: Flags,
|
2022-03-16 16:27:55 +00:00
|
|
|
pub extra: Option<Arc<CellExtra>>,
|
2016-11-28 22:30:08 +00:00
|
|
|
}
|
|
|
|
|
2016-12-30 02:43:55 +00:00
|
|
|
impl Default for Cell {
|
2020-11-05 04:45:14 +00:00
|
|
|
#[inline]
|
2016-12-30 02:43:55 +00:00
|
|
|
fn default() -> Cell {
|
2020-11-05 04:45:14 +00:00
|
|
|
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]
|
2022-03-16 16:27:55 +00:00
|
|
|
pub fn push_zerowidth(&mut self, character: char) {
|
|
|
|
let extra = self.extra.get_or_insert(Default::default());
|
|
|
|
Arc::make_mut(extra).zerowidth.push(character);
|
2016-12-30 02:43:55 +00:00
|
|
|
}
|
2021-04-29 17:06:44 +00:00
|
|
|
|
|
|
|
/// Remove all wide char data from a cell.
|
|
|
|
#[inline(never)]
|
|
|
|
pub fn clear_wide(&mut self) {
|
|
|
|
self.flags.remove(Flags::WIDE_CHAR);
|
2022-03-16 16:27:55 +00:00
|
|
|
if let Some(extra) = self.extra.as_mut() {
|
|
|
|
Arc::make_mut(extra).zerowidth = Vec::new();
|
|
|
|
}
|
2021-04-29 17:06:44 +00:00
|
|
|
self.c = ' ';
|
|
|
|
}
|
2022-03-16 16:27:55 +00:00
|
|
|
|
|
|
|
/// Set underline color on the cell.
|
|
|
|
pub fn set_underline_color(&mut self, color: Option<Color>) {
|
|
|
|
// If we reset color and we don't have zerowidth we should drop extra storage.
|
2022-07-10 17:11:28 +00:00
|
|
|
if color.is_none()
|
|
|
|
&& self
|
|
|
|
.extra
|
|
|
|
.as_ref()
|
|
|
|
.map_or(true, |extra| !extra.zerowidth.is_empty() || extra.hyperlink.is_some())
|
2022-03-16 16:27:55 +00:00
|
|
|
{
|
|
|
|
self.extra = None;
|
2022-07-10 17:11:28 +00:00
|
|
|
} else {
|
|
|
|
let extra = self.extra.get_or_insert(Default::default());
|
|
|
|
Arc::make_mut(extra).underline_color = color;
|
2022-03-16 16:27:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Underline color stored in this cell.
|
|
|
|
#[inline]
|
|
|
|
pub fn underline_color(&self) -> Option<Color> {
|
|
|
|
self.extra.as_ref()?.underline_color
|
|
|
|
}
|
2022-07-10 17:11:28 +00:00
|
|
|
|
|
|
|
/// Set hyperlink.
|
|
|
|
pub fn set_hyperlink(&mut self, hyperlink: Option<Hyperlink>) {
|
|
|
|
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<Hyperlink> {
|
|
|
|
self.extra.as_ref()?.hyperlink.clone()
|
|
|
|
}
|
2016-12-30 02:43:55 +00:00
|
|
|
}
|
|
|
|
|
2019-03-13 18:55:18 +00:00
|
|
|
impl GridCell for Cell {
|
|
|
|
#[inline]
|
|
|
|
fn is_empty(&self) -> bool {
|
|
|
|
(self.c == ' ' || self.c == '\t')
|
|
|
|
&& self.bg == Color::Named(NamedColor::Background)
|
2019-07-10 21:17:20 +00:00
|
|
|
&& self.fg == Color::Named(NamedColor::Foreground)
|
2020-01-09 23:06:41 +00:00
|
|
|
&& !self.flags.intersects(
|
|
|
|
Flags::INVERSE
|
2022-02-14 16:10:13 +00:00
|
|
|
| Flags::ALL_UNDERLINES
|
2020-01-09 23:06:41 +00:00
|
|
|
| Flags::STRIKEOUT
|
|
|
|
| Flags::WRAPLINE
|
2020-07-09 21:45:22 +00:00
|
|
|
| Flags::WIDE_CHAR_SPACER
|
|
|
|
| Flags::LEADING_WIDE_CHAR_SPACER,
|
2020-01-09 23:06:41 +00:00
|
|
|
)
|
2020-11-05 04:45:14 +00:00
|
|
|
&& self.extra.as_ref().map(|extra| extra.zerowidth.is_empty()) != Some(false)
|
2019-03-13 18:55:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2020-01-09 23:06:41 +00:00
|
|
|
fn flags(&self) -> &Flags {
|
|
|
|
&self.flags
|
2019-03-13 18:55:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2020-01-09 23:06:41 +00:00
|
|
|
fn flags_mut(&mut self) -> &mut Flags {
|
|
|
|
&mut self.flags
|
2019-03-13 18:55:18 +00:00
|
|
|
}
|
2019-12-09 23:35:13 +00:00
|
|
|
|
|
|
|
#[inline]
|
2020-11-05 04:45:14 +00:00
|
|
|
fn reset(&mut self, template: &Self) {
|
|
|
|
*self = Cell { bg: template.bg, ..Cell::default() };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Color> for Cell {
|
|
|
|
#[inline]
|
|
|
|
fn from(color: Color) -> Self {
|
|
|
|
Self { bg: color, ..Cell::default() }
|
2019-12-09 23:35:13 +00:00
|
|
|
}
|
2019-03-13 18:55:18 +00:00
|
|
|
}
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
/// Get the length of occupied cells in a line.
|
2016-12-27 03:52:37 +00:00
|
|
|
pub trait LineLength {
|
2020-05-05 22:50:23 +00:00
|
|
|
/// Calculate the occupied line length.
|
2016-12-27 03:52:37 +00:00
|
|
|
fn line_length(&self) -> Column;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl LineLength for grid::Row<Cell> {
|
|
|
|
fn line_length(&self) -> Column {
|
|
|
|
let mut length = Column(0);
|
|
|
|
|
2017-10-12 01:52:23 +00:00
|
|
|
if self[Column(self.len() - 1)].flags.contains(Flags::WRAPLINE) {
|
2016-12-29 15:43:58 +00:00
|
|
|
return Column(self.len());
|
|
|
|
}
|
|
|
|
|
2016-12-27 03:52:37 +00:00
|
|
|
for (index, cell) in self[..].iter().rev().enumerate() {
|
2020-11-05 04:45:14 +00:00
|
|
|
if cell.c != ' '
|
|
|
|
|| cell.extra.as_ref().map(|extra| extra.zerowidth.is_empty()) == Some(false)
|
|
|
|
{
|
2016-12-27 03:52:37 +00:00
|
|
|
length = Column(self.len() - index);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
length
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2022-03-16 16:27:55 +00:00
|
|
|
use super::*;
|
|
|
|
|
|
|
|
use std::mem;
|
2016-12-27 03:52:37 +00:00
|
|
|
|
2018-12-10 17:53:56 +00:00
|
|
|
use crate::grid::Row;
|
|
|
|
use crate::index::Column;
|
2016-12-27 03:52:37 +00:00
|
|
|
|
2022-03-16 16:27:55 +00:00
|
|
|
#[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::<Cell>() <= EXPECTED_CELL_SIZE);
|
|
|
|
}
|
|
|
|
|
2016-12-27 03:52:37 +00:00
|
|
|
#[test]
|
|
|
|
fn line_length_works() {
|
2021-03-30 23:25:38 +00:00
|
|
|
let mut row = Row::<Cell>::new(10);
|
2016-12-27 03:52:37 +00:00
|
|
|
row[Column(5)].c = 'a';
|
|
|
|
|
|
|
|
assert_eq!(row.line_length(), Column(6));
|
|
|
|
}
|
2016-12-29 15:43:58 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn line_length_works_with_wrapline() {
|
2021-03-30 23:25:38 +00:00
|
|
|
let mut row = Row::<Cell>::new(10);
|
2017-10-12 01:52:23 +00:00
|
|
|
row[Column(9)].flags.insert(super::Flags::WRAPLINE);
|
2016-12-29 15:43:58 +00:00
|
|
|
|
|
|
|
assert_eq!(row.line_length(), Column(10));
|
|
|
|
}
|
2016-12-27 03:52:37 +00:00
|
|
|
}
|