Skip to main content

tattoy_wezterm_cell/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2//! Model a cell in the terminal display
3use crate::color::{ColorAttribute, PaletteIndex};
4#[cfg(feature = "use_image")]
5use crate::image::ImageCell;
6use alloc::sync::Arc;
7use core::hash::{Hash, Hasher};
8use core::mem;
9use finl_unicode::grapheme_clusters::Graphemes;
10#[cfg(feature = "use_serde")]
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12pub use wezterm_char_props::emoji::Presentation;
13use wezterm_char_props::emoji_variation::WCWIDTH_TABLE;
14use wezterm_char_props::widechar_width::WcWidth;
15use wezterm_dynamic::{FromDynamic, ToDynamic};
16pub use wezterm_escape_parser::osc::Hyperlink;
17
18extern crate alloc;
19use crate::alloc::string::ToString;
20use alloc::boxed::Box;
21use alloc::vec::Vec;
22
23pub mod color;
24#[cfg(feature = "use_image")]
25pub mod image;
26
27#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
28#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
29enum SmallColor {
30    Default,
31    PaletteIndex(PaletteIndex),
32}
33
34impl Default for SmallColor {
35    fn default() -> Self {
36        Self::Default
37    }
38}
39
40impl Into<ColorAttribute> for SmallColor {
41    fn into(self) -> ColorAttribute {
42        match self {
43            Self::Default => ColorAttribute::Default,
44            Self::PaletteIndex(idx) => ColorAttribute::PaletteIndex(idx),
45        }
46    }
47}
48
49/// Holds the attributes for a cell.
50/// Most style attributes are stored internally as part of a bitfield
51/// to reduce per-cell overhead.
52/// The setter methods return a mutable self reference so that they can
53/// be chained together.
54#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
55#[derive(Clone, Eq, PartialEq)]
56pub struct CellAttributes {
57    attributes: u32,
58    /// The foreground color
59    foreground: SmallColor,
60    /// The background color
61    background: SmallColor,
62    /// Relatively rarely used attributes spill over to a heap
63    /// allocated struct in order to keep CellAttributes
64    /// smaller in the common case.
65    fat: Option<Box<FatAttributes>>,
66}
67
68impl core::fmt::Debug for CellAttributes {
69    fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
70        fmt.debug_struct("CellAttributes")
71            .field("attributes", &self.attributes)
72            .field("intensity", &self.intensity())
73            .field("underline", &self.underline())
74            .field("blink", &self.blink())
75            .field("italic", &self.italic())
76            .field("reverse", &self.reverse())
77            .field("strikethrough", &self.strikethrough())
78            .field("invisible", &self.invisible())
79            .field("wrapped", &self.wrapped())
80            .field("overline", &self.overline())
81            .field("semantic_type", &self.semantic_type())
82            .field("foreground", &self.foreground)
83            .field("background", &self.background)
84            .field("fat", &self.fat)
85            .finish()
86    }
87}
88
89#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
90#[derive(Debug, Default, Clone, Eq, PartialEq)]
91struct FatAttributes {
92    /// The hyperlink content, if any
93    hyperlink: Option<Arc<Hyperlink>>,
94    /// The image data, if any
95    #[cfg(feature = "use_image")]
96    image: Vec<Box<ImageCell>>,
97    /// The color of the underline.  If None, then
98    /// the foreground color is to be used
99    underline_color: ColorAttribute,
100    foreground: ColorAttribute,
101    background: ColorAttribute,
102}
103
104impl FatAttributes {
105    pub fn compute_shape_hash<H: Hasher>(&self, hasher: &mut H) {
106        if let Some(link) = &self.hyperlink {
107            link.compute_shape_hash(hasher);
108        }
109        #[cfg(feature = "use_image")]
110        for cell in &self.image {
111            cell.compute_shape_hash(hasher);
112        }
113        self.underline_color.hash(hasher);
114        self.foreground.hash(hasher);
115        self.background.hash(hasher);
116    }
117}
118
119/// Define getter and setter for the attributes bitfield.
120/// The first form is for a simple boolean value stored in
121/// a single bit.  The $bitnum parameter specifies which bit.
122/// The second form is for an integer value that occupies a range
123/// of bits.  The $bitmask and $bitshift parameters define how
124/// to transform from the stored bit value to the consumable
125/// value.
126macro_rules! bitfield {
127    ($getter:ident, $setter:ident, $bitnum:expr) => {
128        #[inline]
129        pub fn $getter(&self) -> bool {
130            (self.attributes & (1 << $bitnum)) == (1 << $bitnum)
131        }
132
133        #[inline]
134        pub fn $setter(&mut self, value: bool) -> &mut Self {
135            let attr_value = if value { 1 << $bitnum } else { 0 };
136            self.attributes = (self.attributes & !(1 << $bitnum)) | attr_value;
137            self
138        }
139    };
140
141    ($getter:ident, $setter:ident, $bitmask:expr, $bitshift:expr) => {
142        #[inline]
143        pub fn $getter(&self) -> u32 {
144            (self.attributes >> $bitshift) & $bitmask
145        }
146
147        #[inline]
148        pub fn $setter(&mut self, value: u32) -> &mut Self {
149            let clear = !($bitmask << $bitshift);
150            let attr_value = (value & $bitmask) << $bitshift;
151            self.attributes = (self.attributes & clear) | attr_value;
152            self
153        }
154    };
155
156    ($getter:ident, $setter:ident, $enum:ident, $bitmask:expr, $bitshift:expr) => {
157        #[inline]
158        pub fn $getter(&self) -> $enum {
159            unsafe { mem::transmute(((self.attributes >> $bitshift) & $bitmask) as u8) }
160        }
161
162        #[inline]
163        pub fn $setter(&mut self, value: $enum) -> &mut Self {
164            let value = value as u32;
165            let clear = !($bitmask << $bitshift);
166            let attr_value = (value & $bitmask) << $bitshift;
167            self.attributes = (self.attributes & clear) | attr_value;
168            self
169        }
170    };
171}
172
173/// Describes the semantic "type" of the cell.
174/// This categorizes cells into Output (from the actions the user is
175/// taking; this is the default if left unspecified),
176/// Input (that the user typed) and Prompt (effectively, "chrome" provided
177/// by the shell or application that the user is interacting with.
178#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
179#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromDynamic, ToDynamic)]
180#[repr(u8)]
181pub enum SemanticType {
182    Output = 0,
183    Input = 1,
184    Prompt = 2,
185}
186
187impl Default for SemanticType {
188    fn default() -> Self {
189        Self::Output
190    }
191}
192
193pub use wezterm_escape_parser::csi::{Blink, Intensity, Underline, VerticalAlign};
194
195impl Default for CellAttributes {
196    fn default() -> Self {
197        Self::blank()
198    }
199}
200
201impl CellAttributes {
202    bitfield!(intensity, set_intensity, Intensity, 0b11, 0);
203    bitfield!(underline, set_underline, Underline, 0b111, 2);
204    bitfield!(blink, set_blink, Blink, 0b11, 5);
205    bitfield!(italic, set_italic, 7);
206    bitfield!(reverse, set_reverse, 8);
207    bitfield!(strikethrough, set_strikethrough, 9);
208    bitfield!(invisible, set_invisible, 10);
209    bitfield!(wrapped, set_wrapped, 11);
210    bitfield!(overline, set_overline, 12);
211    bitfield!(semantic_type, set_semantic_type, SemanticType, 0b11, 13);
212    bitfield!(vertical_align, set_vertical_align, VerticalAlign, 0b11, 15);
213
214    pub const fn blank() -> Self {
215        Self {
216            attributes: 0,
217            foreground: SmallColor::Default,
218            background: SmallColor::Default,
219            fat: None,
220        }
221    }
222
223    /// Returns true if the attribute bits in both objects are equal.
224    /// This can be used to cheaply test whether the styles of the two
225    /// cells are the same, and is used by some `Renderer` implementations.
226    pub fn attribute_bits_equal(&self, other: &Self) -> bool {
227        self.attributes == other.attributes
228    }
229
230    pub fn compute_shape_hash<H: Hasher>(&self, hasher: &mut H) {
231        self.attributes.hash(hasher);
232        self.foreground.hash(hasher);
233        self.background.hash(hasher);
234        if let Some(fat) = &self.fat {
235            fat.compute_shape_hash(hasher);
236        }
237    }
238
239    /// Set the foreground color for the cell to that specified
240    pub fn set_foreground<C: Into<ColorAttribute>>(&mut self, foreground: C) -> &mut Self {
241        let foreground: ColorAttribute = foreground.into();
242        match foreground {
243            ColorAttribute::Default => {
244                self.foreground = SmallColor::Default;
245                if let Some(fat) = self.fat.as_mut() {
246                    fat.foreground = ColorAttribute::Default;
247                }
248                self.deallocate_fat_attributes_if_none();
249            }
250            ColorAttribute::PaletteIndex(idx) => {
251                self.foreground = SmallColor::PaletteIndex(idx);
252                if let Some(fat) = self.fat.as_mut() {
253                    fat.foreground = ColorAttribute::Default;
254                }
255                self.deallocate_fat_attributes_if_none();
256            }
257            foreground => {
258                self.foreground = SmallColor::Default;
259                self.allocate_fat_attributes();
260                self.fat.as_mut().unwrap().foreground = foreground;
261            }
262        }
263
264        self
265    }
266
267    pub fn foreground(&self) -> ColorAttribute {
268        if let Some(fat) = self.fat.as_ref() {
269            if fat.foreground != ColorAttribute::Default {
270                return fat.foreground;
271            }
272        }
273        self.foreground.into()
274    }
275
276    pub fn set_background<C: Into<ColorAttribute>>(&mut self, background: C) -> &mut Self {
277        let background: ColorAttribute = background.into();
278        match background {
279            ColorAttribute::Default => {
280                self.background = SmallColor::Default;
281                if let Some(fat) = self.fat.as_mut() {
282                    fat.background = ColorAttribute::Default;
283                }
284                self.deallocate_fat_attributes_if_none();
285            }
286            ColorAttribute::PaletteIndex(idx) => {
287                self.background = SmallColor::PaletteIndex(idx);
288                if let Some(fat) = self.fat.as_mut() {
289                    fat.background = ColorAttribute::Default;
290                }
291                self.deallocate_fat_attributes_if_none();
292            }
293            background => {
294                self.background = SmallColor::Default;
295                self.allocate_fat_attributes();
296                self.fat.as_mut().unwrap().background = background;
297            }
298        }
299
300        self
301    }
302
303    pub fn background(&self) -> ColorAttribute {
304        if let Some(fat) = self.fat.as_ref() {
305            if fat.background != ColorAttribute::Default {
306                return fat.background;
307            }
308        }
309        self.background.into()
310    }
311
312    /// Clear all attributes from a cell
313    pub fn clear(&mut self) {
314        *self = Self::blank();
315    }
316
317    fn allocate_fat_attributes(&mut self) {
318        if self.fat.is_none() {
319            self.fat.replace(Box::new(FatAttributes {
320                hyperlink: None,
321                #[cfg(feature = "use_image")]
322                image: vec![],
323                underline_color: ColorAttribute::Default,
324                foreground: ColorAttribute::Default,
325                background: ColorAttribute::Default,
326            }));
327        }
328    }
329
330    fn deallocate_fat_attributes_if_none(&mut self) {
331        let deallocate = self
332            .fat
333            .as_ref()
334            .map(|fat| {
335                #[cfg(feature = "use_image")]
336                {
337                    if !fat.image.is_empty() {
338                        return false;
339                    }
340                }
341                fat.hyperlink.is_none()
342                    && fat.underline_color == ColorAttribute::Default
343                    && fat.foreground == ColorAttribute::Default
344                    && fat.background == ColorAttribute::Default
345            })
346            .unwrap_or(false);
347        if deallocate {
348            self.fat.take();
349        }
350    }
351
352    pub fn set_hyperlink(&mut self, link: Option<Arc<Hyperlink>>) -> &mut Self {
353        if link.is_none() && self.fat.is_none() {
354            self
355        } else {
356            self.allocate_fat_attributes();
357            self.fat.as_mut().unwrap().hyperlink = link;
358            self.deallocate_fat_attributes_if_none();
359            self
360        }
361    }
362}
363
364#[cfg(feature = "use_image")]
365impl CellAttributes {
366    /// Assign a single image to a cell.
367    pub fn set_image(&mut self, image: Box<ImageCell>) -> &mut Self {
368        self.allocate_fat_attributes();
369        self.fat.as_mut().unwrap().image = vec![image];
370        self
371    }
372
373    /// Clear all images from a cell
374    pub fn clear_images(&mut self) -> &mut Self {
375        if let Some(fat) = self.fat.as_mut() {
376            fat.image.clear();
377        }
378        self.deallocate_fat_attributes_if_none();
379        self
380    }
381
382    pub fn detach_image_with_placement(&mut self, image_id: u32, placement_id: Option<u32>) {
383        if let Some(fat) = self.fat.as_mut() {
384            fat.image
385                .retain(|im| !im.matches_placement(image_id, placement_id));
386        }
387        self.deallocate_fat_attributes_if_none();
388    }
389
390    /// Add an image attachement, preserving any existing attachments.
391    /// The list of images is maintained in z-index order
392    pub fn attach_image(&mut self, image: Box<ImageCell>) -> &mut Self {
393        self.allocate_fat_attributes();
394        let fat = self.fat.as_mut().unwrap();
395        let z_index = image.z_index();
396        match fat
397            .image
398            .binary_search_by(|probe| probe.z_index().cmp(&z_index))
399        {
400            Ok(idx) | Err(idx) => fat.image.insert(idx, image),
401        }
402        self
403    }
404}
405
406impl CellAttributes {
407    pub fn set_underline_color<C: Into<ColorAttribute>>(
408        &mut self,
409        underline_color: C,
410    ) -> &mut Self {
411        let underline_color = underline_color.into();
412        if underline_color == ColorAttribute::Default && self.fat.is_none() {
413            self
414        } else {
415            self.allocate_fat_attributes();
416            self.fat.as_mut().unwrap().underline_color = underline_color;
417            self.deallocate_fat_attributes_if_none();
418            self
419        }
420    }
421
422    /// Clone the attributes, but exclude fancy extras such
423    /// as hyperlinks or future sprite things
424    pub fn clone_sgr_only(&self) -> Self {
425        let mut res = Self {
426            attributes: self.attributes,
427            foreground: self.foreground,
428            background: self.background,
429            fat: None,
430        };
431        if let Some(fat) = self.fat.as_ref() {
432            if fat.background != ColorAttribute::Default
433                || fat.foreground != ColorAttribute::Default
434            {
435                res.allocate_fat_attributes();
436                let new_fat = res.fat.as_mut().unwrap();
437                new_fat.foreground = fat.foreground;
438                new_fat.background = fat.background;
439            }
440        }
441        // Reset the semantic type; clone_sgr_only is used primarily
442        // to create a "blank" cell when clearing and we want that to
443        // be deterministically tagged as Output so that we have an
444        // easier time in get_semantic_zones.
445        res.set_semantic_type(SemanticType::default());
446        res.set_underline_color(self.underline_color());
447
448        // Turn off underline because it can have surprising results
449        // if underline is on, then we get CRLF and then SGR reset:
450        // If the CRLF causes a line to scroll, we'll call clone_sgr_only()
451        // to get a blank cell for the new line and it would be filled
452        // with underlines.
453        // clone_sgr_only() is primarily for preserving the background
454        // color when erasing rather than other attributes, so it should
455        // be fine to clear out the actual underline attribute.
456        // Let's extend this to other line attribute types as well.
457        // <https://github.com/wezterm/wezterm/issues/2489>
458        res.set_underline(Underline::None);
459        res.set_overline(false);
460        res.set_strikethrough(false);
461        res
462    }
463
464    pub fn hyperlink(&self) -> Option<&Arc<Hyperlink>> {
465        self.fat.as_ref().and_then(|fat| fat.hyperlink.as_ref())
466    }
467
468    /// Returns the list of attached images in z-index order.
469    /// Returns None if there are no attached images; will
470    /// never return Some(vec![]).
471    #[cfg(feature = "use_image")]
472    pub fn images(&self) -> Option<Vec<ImageCell>> {
473        let fat = self.fat.as_ref()?;
474        if fat.image.is_empty() {
475            return None;
476        }
477        Some(fat.image.iter().map(|im| im.as_ref().clone()).collect())
478    }
479
480    pub fn underline_color(&self) -> ColorAttribute {
481        self.fat
482            .as_ref()
483            .map(|fat| fat.underline_color)
484            .unwrap_or(ColorAttribute::Default)
485    }
486
487    pub fn apply_change(&mut self, change: &AttributeChange) {
488        use AttributeChange::*;
489        match change {
490            Intensity(value) => {
491                self.set_intensity(*value);
492            }
493            Underline(value) => {
494                self.set_underline(*value);
495            }
496            Italic(value) => {
497                self.set_italic(*value);
498            }
499            Blink(value) => {
500                self.set_blink(*value);
501            }
502            Reverse(value) => {
503                self.set_reverse(*value);
504            }
505            StrikeThrough(value) => {
506                self.set_strikethrough(*value);
507            }
508            Invisible(value) => {
509                self.set_invisible(*value);
510            }
511            Foreground(value) => {
512                self.set_foreground(*value);
513            }
514            Background(value) => {
515                self.set_background(*value);
516            }
517            Hyperlink(value) => {
518                self.set_hyperlink(value.clone());
519            }
520        }
521    }
522}
523
524#[cfg(feature = "use_serde")]
525fn deserialize_teenystring<'de, D>(deserializer: D) -> Result<TeenyString, D::Error>
526where
527    D: Deserializer<'de>,
528{
529    let text = String::deserialize(deserializer)?;
530    Ok(TeenyString::from_str(&text, None, None))
531}
532
533#[cfg(feature = "use_serde")]
534fn serialize_teenystring<S>(value: &TeenyString, serializer: S) -> Result<S::Ok, S::Error>
535where
536    S: Serializer,
537{
538    // unsafety: this is safe because the Cell constructor guarantees
539    // that the storage is valid utf8
540    let s = unsafe { core::str::from_utf8_unchecked(value.as_bytes()) };
541    s.serialize(serializer)
542}
543
544/// TeenyString encodes string storage in a single u64.
545/// The scheme is simple but effective: strings that encode into a
546/// byte slice that is 1 less byte than the machine word size can
547/// be encoded directly into the usize bits stored in the struct.
548/// A marker bit (LSB for big endian, MSB for little endian) is
549/// set to indicate that the string is stored inline.
550/// If the string is longer than this then a `Vec<u8>` is allocated
551/// from the heap and the usize holds its raw pointer address.
552///
553/// When the string is inlined, the next-MSB is used to short-cut
554/// calling grapheme_column_width; if it is set, then the TeenyString
555/// has length 2, otherwise, it has length 1 (we don't allow zero-length
556/// strings).
557struct TeenyString(u64);
558struct TeenyStringHeap {
559    bytes: Vec<u8>,
560    width: usize,
561}
562
563impl TeenyString {
564    const fn marker_mask() -> u64 {
565        if cfg!(target_endian = "little") {
566            0x80000000_00000000
567        } else {
568            0x1
569        }
570    }
571
572    const fn double_wide_mask() -> u64 {
573        if cfg!(target_endian = "little") {
574            0xc0000000_00000000
575        } else {
576            0x3
577        }
578    }
579
580    const fn is_marker_bit_set(word: u64) -> bool {
581        let mask = Self::marker_mask();
582        word & mask == mask
583    }
584
585    const fn is_double_width(word: u64) -> bool {
586        let mask = Self::double_wide_mask();
587        word & mask == mask
588    }
589
590    const fn set_marker_bit(word: u64, width: usize) -> u64 {
591        if width > 1 {
592            word | Self::double_wide_mask()
593        } else {
594            word | Self::marker_mask()
595        }
596    }
597
598    pub fn from_str(
599        s: &str,
600        width: Option<usize>,
601        unicode_version: Option<&UnicodeVersion>,
602    ) -> Self {
603        // De-fang the input text such that it has no special meaning
604        // to a terminal.  All control and movement characters are rewritten
605        // as a space.
606        let s = if s.is_empty() || s == "\r\n" {
607            " "
608        } else if s.len() == 1 {
609            let b = s.as_bytes()[0];
610            if b < 0x20 || b == 0x7f { " " } else { s }
611        } else {
612            s
613        };
614
615        let bytes = s.as_bytes();
616        let len = bytes.len();
617        let width = width.unwrap_or_else(|| grapheme_column_width(s, unicode_version));
618
619        if len < core::mem::size_of::<u64>() && width < 3 {
620            let mut word = 0u64;
621            unsafe {
622                core::ptr::copy_nonoverlapping(
623                    bytes.as_ptr(),
624                    &mut word as *mut u64 as *mut u8,
625                    len,
626                );
627            }
628            let word = Self::set_marker_bit(word as u64, width);
629            Self(word)
630        } else {
631            let vec = Box::new(TeenyStringHeap {
632                bytes: bytes.to_vec(),
633                width,
634            });
635            let ptr = Box::into_raw(vec);
636            Self(ptr as u64)
637        }
638    }
639
640    pub const fn space() -> Self {
641        Self(if cfg!(target_endian = "little") {
642            0x80000000_00000020
643        } else {
644            0x20000000_00000001
645        })
646    }
647
648    pub fn from_char(c: char) -> Self {
649        let mut bytes = [0u8; 8];
650        Self::from_str(c.encode_utf8(&mut bytes), None, None)
651    }
652
653    pub fn width(&self) -> usize {
654        if Self::is_marker_bit_set(self.0) {
655            if Self::is_double_width(self.0) { 2 } else { 1 }
656        } else {
657            let heap = self.0 as *const u64 as *const TeenyStringHeap;
658            unsafe { (*heap).width }
659        }
660    }
661
662    pub fn str(&self) -> &str {
663        // unsafety: this is safe because the constructor guarantees
664        // that the storage is valid utf8
665        unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
666    }
667
668    pub fn as_bytes(&self) -> &[u8] {
669        if Self::is_marker_bit_set(self.0) {
670            let bytes = &self.0 as *const u64 as *const u8;
671            let bytes =
672                unsafe { core::slice::from_raw_parts(bytes, core::mem::size_of::<u64>() - 1) };
673            let len = bytes
674                .iter()
675                .position(|&b| b == 0)
676                .unwrap_or(core::mem::size_of::<u64>() - 1);
677
678            &bytes[0..len]
679        } else {
680            let heap = self.0 as *const u64 as *const TeenyStringHeap;
681            unsafe { (*heap).bytes.as_slice() }
682        }
683    }
684}
685
686impl Drop for TeenyString {
687    fn drop(&mut self) {
688        if !Self::is_marker_bit_set(self.0) {
689            let vec = unsafe { Box::from_raw(self.0 as *mut usize as *mut TeenyStringHeap) };
690            drop(vec);
691        }
692    }
693}
694
695impl core::clone::Clone for TeenyString {
696    fn clone(&self) -> Self {
697        if Self::is_marker_bit_set(self.0) {
698            Self(self.0)
699        } else {
700            Self::from_str(self.str(), None, None)
701        }
702    }
703}
704
705impl core::cmp::PartialEq for TeenyString {
706    fn eq(&self, rhs: &Self) -> bool {
707        self.as_bytes().eq(rhs.as_bytes())
708    }
709}
710impl core::cmp::Eq for TeenyString {}
711
712/// Models the contents of a cell on the terminal display
713#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
714#[derive(Clone, Eq, PartialEq)]
715pub struct Cell {
716    #[cfg_attr(
717        feature = "use_serde",
718        serde(
719            deserialize_with = "deserialize_teenystring",
720            serialize_with = "serialize_teenystring"
721        )
722    )]
723    text: TeenyString,
724    attrs: CellAttributes,
725}
726
727impl core::fmt::Debug for Cell {
728    fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
729        fmt.debug_struct("Cell")
730            .field("text", &self.str())
731            .field("width", &self.width())
732            .field("attrs", &self.attrs)
733            .finish()
734    }
735}
736
737impl Default for Cell {
738    fn default() -> Self {
739        Self::blank()
740    }
741}
742
743impl Cell {
744    /// Create a new cell holding the specified character and with the
745    /// specified cell attributes.
746    /// All control and movement characters are rewritten as a space.
747    pub fn new(text: char, attrs: CellAttributes) -> Self {
748        let storage = TeenyString::from_char(text);
749        Self {
750            text: storage,
751            attrs,
752        }
753    }
754
755    pub const fn blank() -> Self {
756        Self {
757            text: TeenyString::space(),
758            attrs: CellAttributes::blank(),
759        }
760    }
761
762    pub const fn blank_with_attrs(attrs: CellAttributes) -> Self {
763        Self {
764            text: TeenyString::space(),
765            attrs,
766        }
767    }
768
769    /// Indicates whether this cell has text or emoji presentation.
770    /// The width already reflects that choice; this information
771    /// is also useful when selecting an appropriate font.
772    pub fn presentation(&self) -> Presentation {
773        match Presentation::for_grapheme(self.str()) {
774            (_, Some(variation)) => variation,
775            (presentation, None) => presentation,
776        }
777    }
778
779    /// Create a new cell holding the specified grapheme.
780    /// The grapheme is passed as a string slice and is intended to hold
781    /// double-width characters, or combining unicode sequences, that need
782    /// to be treated as a single logical "character" that can be cursored
783    /// over.  This function technically allows for an arbitrary string to
784    /// be passed but it should not be used to hold strings other than
785    /// graphemes.
786    pub fn new_grapheme(
787        text: &str,
788        attrs: CellAttributes,
789        unicode_version: Option<&UnicodeVersion>,
790    ) -> Self {
791        let storage = TeenyString::from_str(text, None, unicode_version);
792
793        Self {
794            text: storage,
795            attrs,
796        }
797    }
798
799    pub fn new_grapheme_with_width(text: &str, width: usize, attrs: CellAttributes) -> Self {
800        let storage = TeenyString::from_str(text, Some(width), None);
801        Self {
802            text: storage,
803            attrs,
804        }
805    }
806
807    /// Returns the textual content of the cell
808    pub fn str(&self) -> &str {
809        self.text.str()
810    }
811
812    /// Returns the number of cells visually occupied by this grapheme
813    pub fn width(&self) -> usize {
814        self.text.width()
815    }
816
817    /// Returns the attributes of the cell
818    pub fn attrs(&self) -> &CellAttributes {
819        &self.attrs
820    }
821
822    pub fn attrs_mut(&mut self) -> &mut CellAttributes {
823        &mut self.attrs
824    }
825}
826
827#[derive(Clone, Debug, Eq, PartialEq)]
828pub struct UnicodeVersion {
829    pub version: u8,
830    pub ambiguous_are_wide: bool,
831    #[cfg(feature = "std")]
832    pub cell_widths: Option<Arc<std::collections::HashMap<u32, u8>>>,
833}
834
835impl UnicodeVersion {
836    pub const fn new(version: u8) -> Self {
837        Self {
838            version,
839            ambiguous_are_wide: false,
840            #[cfg(feature = "std")]
841            cell_widths: None,
842        }
843    }
844
845    #[inline]
846    fn width(&self, c: WcWidth) -> usize {
847        // Special case for symbol fonts that are naughtly and use
848        // the unassigned range instead of the private use range.
849        // <https://github.com/wezterm/wezterm/issues/1864>
850        if c == WcWidth::Unassigned {
851            1
852        } else if c == WcWidth::Ambiguous && self.ambiguous_are_wide {
853            2
854        } else if self.version >= 9 {
855            c.width_unicode_9_or_later() as usize
856        } else {
857            c.width_unicode_8_or_earlier() as usize
858        }
859    }
860
861    #[inline]
862    fn wcwidth(&self, c: char) -> usize {
863        #[cfg(feature = "std")]
864        if let Some(ref cell_widths) = self.cell_widths {
865            if let Some(width) = cell_widths.get(&(c as u32)) {
866                return (*width).into();
867            }
868        }
869        self.width(WCWIDTH_TABLE.classify(c))
870    }
871
872    #[inline]
873    pub fn idx(&self) -> usize {
874        (if self.version > 9 { 2 } else { 0 }) | (if self.ambiguous_are_wide { 1 } else { 0 })
875    }
876}
877
878pub const LATEST_UNICODE_VERSION: UnicodeVersion = UnicodeVersion {
879    version: 14,
880    ambiguous_are_wide: false,
881    #[cfg(feature = "std")]
882    cell_widths: None,
883};
884
885/// Returns true if the char `c` has the unicode White_Space property
886pub fn is_white_space_char(c: char) -> bool {
887    wezterm_char_props::white_space::WHITE_SPACE.contains_u32(c as u32)
888}
889
890/// Returns true if the grapheme string `g` consists entirely of characters
891/// that have the unicode White_Space property.
892pub fn is_white_space_grapheme(g: &str) -> bool {
893    for c in g.chars() {
894        if !is_white_space_char(c) {
895            return false;
896        }
897    }
898    true
899}
900
901/// Returns the number of cells visually occupied by a sequence
902/// of graphemes.
903/// Calls through to `grapheme_column_width` for each grapheme
904/// and sums up the length.
905pub fn unicode_column_width(s: &str, version: Option<&UnicodeVersion>) -> usize {
906    Graphemes::new(s)
907        .map(|g| grapheme_column_width(g, version))
908        .sum()
909}
910
911/// Returns the number of cells visually occupied by a grapheme.
912/// The input string must be a single grapheme.
913///
914/// There are some frustrating dragons in the realm of terminal cell widths:
915///
916/// a) wcwidth and wcswidth are widely used by applications and may be
917///    several versions of unicode behind the current version
918/// b) The width of characters has and will change in the future.
919///    Unicode Version 8 -> 9 made some characters wider.
920///    Unicode 14 defines Emoji variation selectors that change the
921///    width depending on trailing context in the unicode sequence.
922///
923/// Differing opinions about the width leads to visual artifacts in
924/// text and and line editors, especially with respect to cursor placement.
925///
926/// There aren't any really great solutions to this problem, as a given
927/// terminal emulator may be fine locally but essentially breaks when
928/// ssh'ing into a remote system with a divergent wcwidth implementation.
929///
930/// This means that a global understanding of the unicode version that
931/// is in use isn't a good solution.
932///
933/// The approach that wezterm wants to take here is to define a
934/// configuration value that sets the starting level of unicode conformance,
935/// and to define an escape sequence that can push/pop a desired confirmance
936/// level onto a stack maintained by the terminal emulator.
937///
938/// The terminal emulator can then pass the unicode version through to
939/// the Cell that is used to hold a grapheme, and that per-Cell version
940/// can then be used to calculate width.
941pub fn grapheme_column_width(s: &str, version: Option<&UnicodeVersion>) -> usize {
942    let version = version.as_deref().unwrap_or(&LATEST_UNICODE_VERSION);
943
944    // Optimization: if there is a single byte we can directly cast
945    // that byte as a char which will be in the range 0.255.
946    // This takes ~1.5ns, and we can then look that up in the table
947    // which is valid for chars in the range 0-0xffff.
948    // That lookup also takes ~1.5ns, giving us a hot path latency
949    // of ~3-4ns for a grapheme string that is comprised of a single
950    // ASCII byte.
951    //
952    // Since we know this is a single ASCII char, we know that it
953    // cannot be a sequence with a variation selector, so we don't
954    // need to requested `Presentation` for it.
955    if s.len() == 1 {
956        return version.wcwidth(s.as_bytes()[0] as char);
957    }
958
959    // Slow path: `s.chars()` will dominate and pull up the minimum
960    // runtime to ~20ns
961    if version.version >= 14 {
962        // Lookup the grapheme to see if the presentation of
963        // the grapheme forces the width. We can bypass
964        // the WcWidth classification if that is true.
965        match Presentation::for_grapheme(s) {
966            (_, Some(Presentation::Emoji)) => return 2,
967            (_, Some(Presentation::Text)) => return 1,
968            (Presentation::Emoji, None) => return 2,
969            (Presentation::Text, None) => {}
970        }
971    }
972
973    // Otherwise, classify and sum up
974    let mut width = 0;
975    for c in s.chars() {
976        width += version.wcwidth(c);
977    }
978
979    width.min(2)
980}
981
982/// Models a change in the attributes of a cell in a stream of changes.
983/// Each variant specifies one of the possible attributes; the corresponding
984/// value holds the new value to be used for that attribute.
985#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
986#[derive(Debug, Clone, Eq, PartialEq, FromDynamic, ToDynamic)]
987pub enum AttributeChange {
988    Intensity(Intensity),
989    Underline(Underline),
990    Italic(bool),
991    Blink(Blink),
992    Reverse(bool),
993    StrikeThrough(bool),
994    Invisible(bool),
995    Foreground(ColorAttribute),
996    Background(ColorAttribute),
997    Hyperlink(Option<Arc<Hyperlink>>),
998}
999
1000#[cfg(test)]
1001mod test {
1002    use super::*;
1003
1004    #[test]
1005    fn teeny_string() {
1006        assert!(
1007            core::mem::size_of::<usize>() <= core::mem::size_of::<u64>(),
1008            "if a pointer doesn't fit in u64 then we need to change TeenyString"
1009        );
1010
1011        let s = TeenyString::from_char('a');
1012        assert_eq!(s.as_bytes(), &[b'a']);
1013
1014        let longer = TeenyString::from_str("hellothere", None, None);
1015        assert_eq!(longer.as_bytes(), b"hellothere");
1016
1017        assert_eq!(
1018            TeenyString::from_char(' ').as_bytes(),
1019            TeenyString::space().as_bytes()
1020        );
1021    }
1022
1023    #[test]
1024    #[cfg(target_pointer_width = "64")]
1025    fn memory_usage() {
1026        assert_eq!(core::mem::size_of::<crate::color::RgbColor>(), 4);
1027        assert_eq!(core::mem::size_of::<ColorAttribute>(), 20);
1028        assert_eq!(core::mem::size_of::<CellAttributes>(), 16);
1029        assert_eq!(core::mem::size_of::<Cell>(), 24);
1030        assert_eq!(core::mem::size_of::<Vec<u8>>(), 24);
1031        assert_eq!(core::mem::size_of::<char>(), 4);
1032        assert_eq!(core::mem::size_of::<TeenyString>(), 8);
1033    }
1034
1035    #[test]
1036    fn nerf_special() {
1037        for c in " \n\r\t".chars() {
1038            let cell = Cell::new(c, CellAttributes::default());
1039            assert_eq!(cell.str(), " ");
1040        }
1041
1042        for g in &["", " ", "\n", "\r", "\t", "\r\n"] {
1043            let cell = Cell::new_grapheme(g, CellAttributes::default(), None);
1044            assert_eq!(cell.str(), " ");
1045        }
1046    }
1047
1048    #[test]
1049    fn test_width() {
1050        let foot = "\u{1f9b6}";
1051        eprintln!("foot chars");
1052        for c in foot.chars() {
1053            eprintln!("char: {:?}", c);
1054        }
1055        assert_eq!(unicode_column_width(foot, None), 2, "{} should be 2", foot);
1056
1057        let women_holding_hands_dark_skin_tone_medium_light_skin_tone =
1058            "\u{1F469}\u{1F3FF}\u{200D}\u{1F91D}\u{200D}\u{1F469}\u{1F3FC}";
1059
1060        // Ensure that we can hold this longer grapheme sequence in the cell
1061        // and correctly return its string contents!
1062        let cell = Cell::new_grapheme(
1063            women_holding_hands_dark_skin_tone_medium_light_skin_tone,
1064            CellAttributes::default(),
1065            None,
1066        );
1067        assert_eq!(
1068            cell.str(),
1069            women_holding_hands_dark_skin_tone_medium_light_skin_tone
1070        );
1071        assert_eq!(
1072            cell.width(),
1073            2,
1074            "width of {} should be 2",
1075            women_holding_hands_dark_skin_tone_medium_light_skin_tone
1076        );
1077
1078        let deaf_man = "\u{1F9CF}\u{200D}\u{2642}\u{FE0F}";
1079        eprintln!("deaf_man chars");
1080        for c in deaf_man.chars() {
1081            eprintln!("char: {:?}", c);
1082        }
1083        assert_eq!(unicode_column_width(deaf_man, None), 2);
1084
1085        let man_dancing = "\u{1F57A}";
1086        assert_eq!(
1087            unicode_column_width(man_dancing, Some(&UnicodeVersion::new(9))),
1088            2
1089        );
1090        assert_eq!(
1091            unicode_column_width(man_dancing, Some(&UnicodeVersion::new(8))),
1092            2
1093        );
1094
1095        let raised_fist = "\u{270a}";
1096        assert_eq!(
1097            unicode_column_width(raised_fist, Some(&UnicodeVersion::new(9))),
1098            2
1099        );
1100        assert_eq!(
1101            unicode_column_width(raised_fist, Some(&UnicodeVersion::new(8))),
1102            1
1103        );
1104
1105        // This is a codepoint in the private use area
1106        let font_awesome_star = "\u{f005}";
1107        eprintln!("font_awesome_star {}", font_awesome_star.escape_debug());
1108        assert_eq!(unicode_column_width(font_awesome_star, None), 1);
1109
1110        let england_flag = "\u{1f3f4}\u{e0067}\u{e0062}\u{e0065}\u{e006e}\u{e0067}\u{e007f}";
1111        assert_eq!(unicode_column_width(england_flag, None), 2);
1112    }
1113
1114    #[test]
1115    fn issue_1161() {
1116        let x_ideographic_space_x = "x\u{3000}x";
1117        assert_eq!(unicode_column_width(x_ideographic_space_x, None), 4);
1118        assert_eq!(
1119            Graphemes::new(x_ideographic_space_x).collect::<Vec<_>>(),
1120            vec!["x".to_string(), "\u{3000}".to_string(), "x".to_string()],
1121        );
1122
1123        let c = Cell::new_grapheme("\u{3000}", CellAttributes::blank(), None);
1124        assert_eq!(c.width(), 2);
1125    }
1126
1127    #[test]
1128    fn issue_997() {
1129        let victory_hand = "\u{270c}";
1130        let victory_hand_text_presentation = "\u{270c}\u{fe0e}";
1131
1132        assert_eq!(
1133            unicode_column_width(victory_hand_text_presentation, None),
1134            1
1135        );
1136        assert_eq!(unicode_column_width(victory_hand, None), 1);
1137
1138        assert_eq!(
1139            Graphemes::new(victory_hand_text_presentation).collect::<Vec<_>>(),
1140            vec![victory_hand_text_presentation.to_string()]
1141        );
1142        assert_eq!(
1143            Graphemes::new(victory_hand).collect::<Vec<_>>(),
1144            vec![victory_hand.to_string()]
1145        );
1146
1147        let copyright_emoji_presentation = "\u{00A9}\u{FE0F}";
1148        assert_eq!(
1149            Graphemes::new(copyright_emoji_presentation).collect::<Vec<_>>(),
1150            vec![copyright_emoji_presentation.to_string()]
1151        );
1152        assert_eq!(unicode_column_width(copyright_emoji_presentation, None), 2);
1153        assert_eq!(
1154            unicode_column_width(copyright_emoji_presentation, Some(&UnicodeVersion::new(9))),
1155            1
1156        );
1157
1158        let copyright_text_presentation = "\u{00A9}";
1159        assert_eq!(
1160            Graphemes::new(copyright_text_presentation).collect::<Vec<_>>(),
1161            vec![copyright_text_presentation.to_string()]
1162        );
1163        assert_eq!(unicode_column_width(copyright_text_presentation, None), 1);
1164
1165        let raised_fist = "\u{270a}";
1166        // Not valid to have explicit Text presentation for raised fist
1167        let raised_fist_text = "\u{270a}\u{fe0e}";
1168        assert_eq!(
1169            Presentation::for_grapheme(raised_fist),
1170            (Presentation::Emoji, None)
1171        );
1172        assert_eq!(unicode_column_width(raised_fist, None), 2);
1173        assert_eq!(
1174            Presentation::for_grapheme(raised_fist_text),
1175            (Presentation::Emoji, None)
1176        );
1177        assert_eq!(unicode_column_width(raised_fist_text, None), 2);
1178
1179        assert_eq!(
1180            Graphemes::new(raised_fist_text).collect::<Vec<_>>(),
1181            vec![raised_fist_text.to_string()]
1182        );
1183        assert_eq!(
1184            Graphemes::new(raised_fist).collect::<Vec<_>>(),
1185            vec![raised_fist.to_string()]
1186        );
1187    }
1188
1189    #[test]
1190    fn issue_1573() {
1191        let sequence = "\u{1112}\u{1161}\u{11ab}";
1192        assert_eq!(unicode_column_width(sequence, None), 2);
1193        assert_eq!(grapheme_column_width(sequence, None), 2);
1194
1195        let sequence2 = core::str::from_utf8(b"\xe1\x84\x92\xe1\x85\xa1\xe1\x86\xab").unwrap();
1196        assert_eq!(unicode_column_width(sequence2, None), 2);
1197        assert_eq!(grapheme_column_width(sequence2, None), 2);
1198    }
1199
1200    // See <https://github.com/wezterm/wezterm/issues/6637>
1201    // We're not directly "fixing" that issue here in termwiz at this time
1202    // because it isn't clear that this cell module has enough context
1203    // to eg: decide that the width of U+2028 should be returned as 1.
1204    // That decision is made over in wezterm-term when processing
1205    // a sequence of graphemes. This test case is just making assertions
1206    // about the properties of a couple of problematic zero-width
1207    // characters.
1208    #[test]
1209    fn issue_6637() {
1210        // U+2028 is the unicode line separator. It is Non-printing White_Space.
1211        let sequence = "\u{2028}";
1212        // It has zero width
1213        assert_eq!(unicode_column_width(sequence, None), 0);
1214        assert_eq!(grapheme_column_width(sequence, None), 0);
1215        // it is white space
1216        assert!(is_white_space_grapheme(sequence));
1217
1218        // Just a couple of sanity checks for the white space function
1219        assert!(is_white_space_char(' '));
1220        assert!(!is_white_space_char('x'));
1221
1222        // U+2068 is a BIDI control character and is relevant here
1223        // due to <https://github.com/wezterm/wezterm/issues/1422>.
1224        // It is Non-Printing, non-White_Space
1225        assert!(!is_white_space_char('\u{2068}'));
1226    }
1227}