1#![cfg_attr(not(feature = "std"), no_std)]
2use 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#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
55#[derive(Clone, Eq, PartialEq)]
56pub struct CellAttributes {
57 attributes: u32,
58 foreground: SmallColor,
60 background: SmallColor,
62 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 hyperlink: Option<Arc<Hyperlink>>,
94 #[cfg(feature = "use_image")]
96 image: Vec<Box<ImageCell>>,
97 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
119macro_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#[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 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 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 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 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 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 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 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 res.set_semantic_type(SemanticType::default());
446 res.set_underline_color(self.underline_color());
447
448 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 #[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 let s = unsafe { core::str::from_utf8_unchecked(value.as_bytes()) };
541 s.serialize(serializer)
542}
543
544struct 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 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 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#[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 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 pub fn presentation(&self) -> Presentation {
773 match Presentation::for_grapheme(self.str()) {
774 (_, Some(variation)) => variation,
775 (presentation, None) => presentation,
776 }
777 }
778
779 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 pub fn str(&self) -> &str {
809 self.text.str()
810 }
811
812 pub fn width(&self) -> usize {
814 self.text.width()
815 }
816
817 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 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
885pub fn is_white_space_char(c: char) -> bool {
887 wezterm_char_props::white_space::WHITE_SPACE.contains_u32(c as u32)
888}
889
890pub 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
901pub 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
911pub fn grapheme_column_width(s: &str, version: Option<&UnicodeVersion>) -> usize {
942 let version = version.as_deref().unwrap_or(&LATEST_UNICODE_VERSION);
943
944 if s.len() == 1 {
956 return version.wcwidth(s.as_bytes()[0] as char);
957 }
958
959 if version.version >= 14 {
962 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 let mut width = 0;
975 for c in s.chars() {
976 width += version.wcwidth(c);
977 }
978
979 width.min(2)
980}
981
982#[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 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 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 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 #[test]
1209 fn issue_6637() {
1210 let sequence = "\u{2028}";
1212 assert_eq!(unicode_column_width(sequence, None), 0);
1214 assert_eq!(grapheme_column_width(sequence, None), 0);
1215 assert!(is_white_space_grapheme(sequence));
1217
1218 assert!(is_white_space_char(' '));
1220 assert!(!is_white_space_char('x'));
1221
1222 assert!(!is_white_space_char('\u{2068}'));
1226 }
1227}