//! Compact cells and rows, with bounded owned page storage. use crate::{ hyperlink::{self, Hyperlink, HyperlinkId}, ref_counted_set::{AddError, Layout as SetLayout}, style::{self, Style}, }; use ghostty_vt::color::Rgb; use std::collections::{HashMap, HashSet}; /// Default grapheme allocation request, in bytes. pub const GRAPHEME_BYTES_DEFAULT: u32 = 1024; /// Default string allocation request, in bytes. pub const STRING_BYTES_DEFAULT: u32 = 2048; /// Reference ABI size of a hyperlink set item. pub const HYPERLINK_ITEM_SIZE: usize = 48; /// Reference ABI size of a style set item. pub const STYLE_ITEM_SIZE: usize = 36; /// The standard page capacity (smaller grapheme region in tests, as in Zig). pub const STD_CAPACITY: Capacity = Capacity { cols: 215, rows: 215, styles: 128, hyperlink_bytes: 192, grapheme_bytes: if cfg!(test) { 512 } else { 8192 }, string_bytes: STRING_BYTES_DEFAULT, }; /// ContentTag field of the packed grid representation. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum ContentTag { /// Single codepoint. #[default] Codepoint = 0, /// First codepoint of a grapheme cluster. CodepointGrapheme = 1, /// Background-only palette color. BgColorPalette = 2, /// Background-only RGB color. BgColorRgb = 3, } /// Wide field of the packed grid representation. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum Wide { /// Single grid cell. #[default] Narrow = 0, /// Two-cell character. Wide = 1, /// Trailing half of a wide character. SpacerTail = 2, /// End-of-line spacer before a wrapped wide character. SpacerHead = 3, } /// SemanticContent field of the packed grid representation. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum SemanticContent { /// Ordinary command output. #[default] Output = 0, /// User input. Input = 1, /// Application prompt. Prompt = 2, } /// SemanticPrompt field of the packed grid representation. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum SemanticPrompt { /// No prompt cells. #[default] None = 0, /// Primary prompt row. Prompt = 1, /// Continuation prompt row. PromptContinuation = 2, } /// Eight-byte terminal cell; all-zero bits describe an empty cell. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, bytemuck::Zeroable)] #[repr(transparent)] pub struct Cell(u64); impl Cell { /// Construct an unstyled single-codepoint cell. pub fn init(cp: u32) -> Self { let mut c = Self::default(); c.set_codepoint(cp); c } fn bits(self, shift: u32, mask: u64) -> u64 { (self.0 >> shift) & mask } fn set_bits(&mut self, shift: u32, mask: u64, value: u64) { self.0 = (self.0 & !(mask << shift)) | ((value & mask) << shift); } /// Return the bit-packed representation. pub const fn raw(self) -> u64 { self.0 } /// True when every bit is zero. pub fn is_zero(self) -> bool { self.0 == 0 } /// Return the codepoint, or zero for background-only cells. pub fn codepoint(self) -> u32 { if (self.content_tag() as u8) < 2 { self.bits(2, 0x1fffff) as u32 } else { 0 } } /// Set the codepoint payload without changing the content tag. pub fn set_codepoint(&mut self, cp: u32) { self.set_bits(2, 0xffffff, u64::from(cp & 0x1fffff)); } /// Return the palette payload. pub fn color_palette(self) -> u8 { self.bits(2, 255) as u8 } /// Set the palette payload without changing the content tag. pub fn set_color_palette(&mut self, v: u8) { self.set_bits(2, 0xffffff, u64::from(v)); } /// Return the RGB payload. pub fn color_rgb(self) -> Rgb { Rgb { r: self.bits(2, 255) as u8, g: self.bits(10, 255) as u8, b: self.bits(18, 255) as u8, } } /// Set the RGB payload without changing the content tag. pub fn set_color_rgb(&mut self, v: Rgb) { self.set_bits( 2, 0xffffff, u64::from(v.r) | (u64::from(v.g) << 8) | (u64::from(v.b) << 16), ); } /// True when the cell contains a nonzero text codepoint. pub fn has_text(self) -> bool { self.codepoint() != 0 } /// True when the cell refers to extra grapheme codepoints. pub fn has_grapheme(self) -> bool { self.content_tag() == ContentTag::CodepointGrapheme } /// Width in grid cells, including one-cell spacers. pub fn grid_width(self) -> u8 { if self.wide() == Wide::Wide { 2 } else { 1 } } /// True when the cell has a nondefault style ID. pub fn has_styling(self) -> bool { self.style_id() != 0 } /// True for a blank narrow textual cell; style does not affect this test. pub fn is_empty(self) -> bool { (self.content_tag() as u8) < 2 && !self.has_text() && self.wide() == Wide::Narrow } /// True if any cell has text. pub fn has_text_any(cells: &[Self]) -> bool { cells.iter().any(|c| c.has_text()) } /// Read the content_tag field. pub fn content_tag(self) -> ContentTag { match self.bits(0, 3) { 0 => ContentTag::Codepoint, 1 => ContentTag::CodepointGrapheme, 2 => ContentTag::BgColorPalette, _ => ContentTag::BgColorRgb, } } /// Update the content_tag field. pub fn set_content_tag(&mut self, v: ContentTag) { self.set_bits(0, 3, v as u64); } /// Read the style_id field. pub fn style_id(self) -> u16 { self.bits(26, 65535) as u16 } /// Update the style_id field. pub fn set_style_id(&mut self, v: u16) { self.set_bits(26, 65535, v as u64); } /// Read the wide field. pub fn wide(self) -> Wide { match self.bits(42, 3) { 0 => Wide::Narrow, 1 => Wide::Wide, 2 => Wide::SpacerTail, _ => Wide::SpacerHead, } } /// Update the wide field. pub fn set_wide(&mut self, v: Wide) { self.set_bits(42, 3, v as u64); } /// Read the protected field. pub fn protected(self) -> bool { self.bits(44, 1) != 0 } /// Update the protected field. pub fn set_protected(&mut self, v: bool) { self.set_bits(44, 1, v as u64); } /// Read the hyperlink field. pub fn hyperlink(self) -> bool { self.bits(45, 1) != 0 } /// Update the hyperlink field. pub fn set_hyperlink(&mut self, v: bool) { self.set_bits(45, 1, v as u64); } /// Read the semantic_content field. pub fn semantic_content(self) -> SemanticContent { match self.bits(46, 3) { 0 => SemanticContent::Output, 1 => SemanticContent::Input, _ => SemanticContent::Prompt, } } /// Update the semantic_content field. pub fn set_semantic_content(&mut self, v: SemanticContent) { self.set_bits(46, 3, v as u64); } } /// Eight-byte row metadata. The low 32 bits hold its first cell index. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[repr(transparent)] pub struct Row(u64); impl Row { /// First cell index in the page's row-major storage. pub fn cells(self) -> usize { (self.0 & 0xffffffff) as usize } /// Set the row's first cell index. pub fn set_cells(&mut self, v: usize) { self.0 = (self.0 & !0xffffffff) | (v as u64 & 0xffffffff); } /// True if cells can have managed style, hyperlink, or grapheme data. pub fn managed_memory(self) -> bool { self.styled() || self.hyperlink() || self.grapheme() } /// Prompt-row classification. pub fn semantic_prompt(self) -> SemanticPrompt { match (self.0 >> 37) & 3 { 1 => SemanticPrompt::Prompt, 2 => SemanticPrompt::PromptContinuation, _ => SemanticPrompt::None, } } /// Set prompt-row classification. pub fn set_semantic_prompt(&mut self, v: SemanticPrompt) { self.0 = (self.0 & !(3 << 37)) | ((v as u64) << 37); } /// Read the wrap flag. pub fn wrap(self) -> bool { self.0 & (1 << 32) != 0 } /// Update the wrap flag. pub fn set_wrap(&mut self, v: bool) { self.0 = (self.0 & !(1 << 32)) | (u64::from(v) << 32); } /// Read the wrap_continuation flag. pub fn wrap_continuation(self) -> bool { self.0 & (1 << 33) != 0 } /// Update the wrap_continuation flag. pub fn set_wrap_continuation(&mut self, v: bool) { self.0 = (self.0 & !(1 << 33)) | (u64::from(v) << 33); } /// Read the grapheme flag. pub fn grapheme(self) -> bool { self.0 & (1 << 34) != 0 } /// Update the grapheme flag. pub fn set_grapheme(&mut self, v: bool) { self.0 = (self.0 & !(1 << 34)) | (u64::from(v) << 34); } /// Read the styled flag. pub fn styled(self) -> bool { self.0 & (1 << 35) != 0 } /// Update the styled flag. pub fn set_styled(&mut self, v: bool) { self.0 = (self.0 & !(1 << 35)) | (u64::from(v) << 35); } /// Read the hyperlink flag. pub fn hyperlink(self) -> bool { self.0 & (1 << 36) != 0 } /// Update the hyperlink flag. pub fn set_hyperlink(&mut self, v: bool) { self.0 = (self.0 & !(1 << 36)) | (u64::from(v) << 36); } /// Read the kitty_virtual_placeholder flag. pub fn kitty_virtual_placeholder(self) -> bool { self.0 & (1 << 39) != 0 } /// Update the kitty_virtual_placeholder flag. pub fn set_kitty_virtual_placeholder(&mut self, v: bool) { self.0 = (self.0 & !(1 << 39)) | (u64::from(v) << 39); } /// Read the dirty flag. pub fn dirty(self) -> bool { self.0 & (1 << 40) != 0 } /// Update the dirty flag. pub fn set_dirty(&mut self, v: bool) { self.0 = (self.0 & !(1 << 40)) | (u64::from(v) << 40); } } /// Current logical page dimensions. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct Size { /// Columns currently addressable. pub cols: u16, /// Rows currently addressable. pub rows: u16, } /// Fixed logical allocation limits for a page. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Capacity { /// Allocated columns. pub cols: u16, /// Allocated rows. pub rows: u16, /// Requested style hash-table slots. pub styles: u16, /// Requested hyperlink-set bytes, excluding strings and map. pub hyperlink_bytes: u16, /// Requested grapheme bytes. pub grapheme_bytes: u32, /// Requested string bytes. pub string_bytes: u32, } impl Default for Capacity { fn default() -> Self { Self { cols: 0, rows: 0, styles: 16, hyperlink_bytes: 192, grapheme_bytes: GRAPHEME_BYTES_DEFAULT, string_bytes: STRING_BYTES_DEFAULT, } } } /// Capacity adjustment that retains the original total byte allocation. #[derive(Debug, Default, Clone, Copy)] pub struct Adjustment { /// New column count; rows absorb the size change. pub cols: Option, } impl Capacity { fn available_bytes_for_grid(self) -> usize { let l = Page::layout(self); let mut end = l.total_size; for (bytes, align) in [ (l.hyperlink_map_layout.total_size, 4), (l.hyperlink_set_layout.total_size, 8), (l.string_alloc_layout.total_size, 8), (l.grapheme_map_layout.total_size, 8), (l.grapheme_alloc_layout.total_size, 8), (l.styles_layout.total_size, 8), ] { end = (end - bytes) & !(align - 1); } end } /// Maximum columns that still allow one row without enlarging the page. pub fn max_cols(self) -> Option { let bytes = self.available_bytes_for_grid(); (bytes > 8).then(|| ((bytes - 8) / 8).min(65535) as u16) } /// Adjust columns, retaining the same page size by changing row capacity. pub fn adjust(self, req: Adjustment) -> Result { let Some(cols) = req.cols else { return Ok(self); }; let rows = self.available_bytes_for_grid() / (8 + usize::from(cols) * 8); if rows == 0 { return Err(Error::OutOfMemory); } Ok(Self { cols, rows: rows.min(65535) as u16, ..self }) } } /// Logical bitmap allocator layout, preserving the reference size formula. #[derive(Debug, Default, Clone, Copy)] pub struct AllocLayout { /// Total bytes including the reference allocator's chunk multiplier. pub total_size: usize, /// Number of 64-bit chunk bitmaps. pub bitmap_count: usize, /// Start of allocation data. pub chunks_start: usize, } impl AllocLayout { fn new(cap: usize, chunk: usize) -> Self { let aligned = cap.next_multiple_of(chunk); let bitmap_count = (aligned / chunk).div_ceil(64); Self { total_size: bitmap_count * 8 + aligned * chunk, bitmap_count, chunks_start: bitmap_count * 8, } } } /// Logical offset hash-map layout. #[derive(Debug, Default, Clone, Copy)] pub struct MapLayout { /// Total bytes including the 16-byte header. pub total_size: usize, /// Maximum entries (no extra load-factor reduction). pub capacity: usize, } impl MapLayout { fn new(capacity: usize, value_size: usize, value_align: usize) -> Self { let keys_start = (16 + capacity).next_multiple_of(4); let vals_start = (keys_start + capacity * 4).next_multiple_of(value_align); Self { total_size: (vals_start + capacity * value_size).next_multiple_of(value_align.max(4)), capacity, } } } /// Complete logical page layout for the 64-bit Linux reference ABI. #[derive(Debug, Clone, Copy)] pub struct Layout { /// Page-aligned byte size. pub total_size: usize, /// Row array offset. pub rows_start: usize, /// Row array bytes. pub rows_size: usize, /// Cell array offset. pub cells_start: usize, /// Cell array bytes. pub cells_size: usize, /// Style set offset. pub styles_start: usize, /// Style set layout. pub styles_layout: SetLayout, /// Grapheme allocator offset. pub grapheme_alloc_start: usize, /// Grapheme allocator layout. pub grapheme_alloc_layout: AllocLayout, /// Grapheme map offset. pub grapheme_map_start: usize, /// Grapheme map layout. pub grapheme_map_layout: MapLayout, /// String allocator offset. pub string_alloc_start: usize, /// String allocator layout. pub string_alloc_layout: AllocLayout, /// Hyperlink set offset. pub hyperlink_set_start: usize, /// Hyperlink set layout. pub hyperlink_set_layout: SetLayout, /// Hyperlink map offset. pub hyperlink_map_start: usize, /// Hyperlink map layout. pub hyperlink_map_layout: MapLayout, /// Requested capacity. pub capacity: Capacity, } /// Recoverable page operation errors. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Error { /// Allocation or grid capacity exhausted. OutOfMemory, /// Cell or row is outside the logical page. InvalidCoordinate, /// A grid dimension is zero. InvalidDimensions, /// Style set needs more space. StyleSetOutOfMemory, /// Style IDs need compaction. StyleSetNeedsRehash, /// No contiguous grapheme allocation fits. GraphemeAllocOutOfMemory, /// Grapheme map capacity exhausted. GraphemeMapOutOfMemory, /// Hyperlink map capacity exhausted. HyperlinkMapOutOfMemory, /// Hyperlink set capacity exhausted. SetOutOfMemory, /// Hyperlink IDs need compaction. SetNeedsRehash, /// No contiguous string allocation fits. StringsOutOfMemory, /// Missing style or hyperlink ID. InvalidId, /// Reference count overflow or underflow. InvalidRefCount, /// Capacity cannot grow further. OutOfSpace, } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{self:?}") } } impl std::error::Error for Error {} impl From for Error { fn from(e: AddError) -> Self { match e { AddError::OutOfMemory => Self::StyleSetOutOfMemory, AddError::NeedsRehash => Self::StyleSetNeedsRehash, AddError::InvalidId => Self::InvalidId, AddError::InvalidRefCount => Self::InvalidRefCount, } } } /// Hasher for cell-index keys: indices are already well distributed, so a /// single multiply replaces SipHash on the print path. #[derive(Debug, Default, Clone, Copy)] struct IndexHasher(u64); impl std::hash::Hasher for IndexHasher { #[inline] fn finish(&self) -> u64 { self.0 } #[inline] fn write(&mut self, bytes: &[u8]) { for &b in bytes { self.0 = (self.0.rotate_left(5) ^ u64::from(b)).wrapping_mul(0x517c_c1b7_2722_0a95); } } #[inline] fn write_usize(&mut self, n: usize) { self.0 = (n as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15); } } type IndexMap = HashMap>; #[derive(Debug, Clone, Copy)] struct Allocation { start: usize, len: usize, } #[derive(Debug, Clone)] struct ChunkAlloc { /// Bitmap of used chunks (bit set = used), 64 chunks per word. used: Vec, /// Total number of chunks. chunks: usize, /// Every word below this index is completely used. first_free_word: usize, chunk: usize, } impl ChunkAlloc { fn new(layout: AllocLayout, chunk: usize) -> Result { let mut used = Vec::new(); used.try_reserve_exact(layout.bitmap_count) .map_err(|_| Error::OutOfMemory)?; used.resize(layout.bitmap_count, 0); Ok(Self { used, chunks: layout.bitmap_count * 64, first_free_word: 0, chunk, }) } /// First-fit allocation of `bytes` (rounded up to whole chunks): the /// lowest-addressed run of free chunks is used. fn alloc(&mut self, bytes: usize) -> Option { let n = bytes.div_ceil(self.chunk); if n == 0 { return Some(Allocation { start: 0, len: 0 }); } while self.first_free_word < self.used.len() && self.used[self.first_free_word] == u64::MAX { self.first_free_word += 1; } let mut run = 0usize; let mut run_start = 0usize; for w in self.first_free_word..self.used.len() { let word = self.used[w]; if word == u64::MAX { run = 0; continue; } if word == 0 { if run == 0 { run_start = w * 64; } run += 64; if run >= n { return Some(self.take(run_start, n)); } continue; } if n == 1 { let b = word.trailing_ones() as usize; return Some(self.take(w * 64 + b, 1)); } for b in 0..64 { if word >> b & 1 == 1 { run = 0; } else { if run == 0 { run_start = w * 64 + b; } run += 1; if run == n { return Some(self.take(run_start, n)); } } } } None } fn take(&mut self, start: usize, len: usize) -> Allocation { for i in start..start + len { self.used[i / 64] |= 1 << (i % 64); } Allocation { start, len } } fn free(&mut self, a: Allocation) { for i in a.start..a.start + a.len { self.used[i / 64] &= !(1 << (i % 64)); } self.first_free_word = self.first_free_word.min(a.start / 64); } fn reset(&mut self) { self.used.fill(0); self.first_free_word = 0; } } /// Page-owned rows, cells, and bounded auxiliary storage. #[derive(Debug, Clone)] pub struct Page { /// Logical dimensions; no greater than capacity. pub size: Size, /// Fixed logical allocation limits. pub capacity: Capacity, /// A full-page operation dirtied every row. pub dirty: bool, /// Row metadata in screen order. pub rows: Vec, /// Cells in row-major capacity order. pub cells: Vec, /// Shared nondefault cell styles. pub styles: style::Set, /// Shared hyperlink values. pub hyperlink_set: hyperlink::Set, // Graphemes share a page arena; copying a cell never allocates a Vec. grapheme_map: IndexMap>, grapheme_data: Vec, grapheme_allocations: IndexMap, grapheme_alloc: ChunkAlloc, hyperlink_map: hyperlink::Map, string_alloc: ChunkAlloc, link_allocations: HashMap; 2]>, pause_integrity_checks: usize, } /// A cell with its page-independent managed values, used when copying pages. #[derive(Debug, Clone)] pub(crate) struct OwnedCell { pub cell: Cell, pub style: Option