diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 015d21db..55462660 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -21,6 +21,8 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Run tests run: cargo test --workspace --verbose + - name: Run mt tests + run: cargo test --workspace --verbose --features multithread lint: runs-on: ubuntu-latest diff --git a/CHANGES.md b/CHANGES.md index 1a823f74..a4c84ede 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ * `attributes.value_as::` is now available on `Attributes` * Performance improvements * New template function: `len` + * `List` now has `clear` and `retain` * 0.2.11 * FEATURE: ranges * `padding` function diff --git a/Cargo.toml b/Cargo.toml index f20ef105..2a42fa1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,26 +14,19 @@ publish = true rust-version = "1.88" [dependencies] -anathema-backend = { workspace = true } -anathema-default-widgets = { workspace = true } +anathema-core = { workspace = true } anathema-geometry = { workspace = true } -anathema-runtime = { workspace = true } anathema-state = { workspace = true } anathema-state-derive = { workspace = true } anathema-store = { workspace = true } -anathema-templates = { workspace = true } -anathema-value-resolver = { workspace = true } +anathema-strings = { workspace = true } anathema-widgets = { workspace = true } -[dev-dependencies] -# anathema-debug = { path = "anathema-debug" } -anathema-testutils = { path = "anathema-testutils" } - [features] default = [] -profile = ["anathema-runtime/profile", "anathema-widgets/profile", "anathema-backend/profile", "anathema-value-resolver/profile"] +profile = [] +multithread = ["anathema-core/multithread", "anathema-state/multithread"] serde = ["anathema-state/serde", "anathema-store/serde"] -# filelog = ["anathema-debug/filelog", "anathema-widgets/filelog", "anathema-runtime/filelog"] [lints] workspace = true @@ -48,29 +41,23 @@ crossterm = "0.28.1" unicode-width = "0.1.11" flume = "0.11.0" notify = "6.1.1" -anathema-default-widgets = { path = "./anathema-default-widgets", version = "0.2.12-beta" } -anathema-backend = { path = "./anathema-backend", version = "0.2.12-beta" } -anathema-runtime = { path = "./anathema-runtime", version = "0.2.12-beta" } +anathema-core = { path = "./anathema-core", version = "0.2.12-beta" } +anathema-geometry = { path = "./anathema-geometry", version = "0.2.12-beta" } anathema-state = { path = "./anathema-state", version = "0.2.12-beta" } anathema-state-derive = { path = "./anathema-state-derive", version = "0.2.12-beta" } anathema-store = { path = "./anathema-store", version = "0.2.12-beta" } -anathema-templates = { path = "./anathema-templates", version = "0.2.12-beta" } +anathema-strings = { path = "./anathema-strings", version = "0.2.12-beta" } anathema-widgets = { path = "./anathema-widgets", version = "0.2.12-beta" } -anathema-geometry = { path = "./anathema-geometry", version = "0.2.12-beta" } -anathema-value-resolver = { path = "./anathema-value-resolver", version = "0.2.12-beta" } [workspace] members = [ - "anathema-backend", - "anathema-runtime", - "anathema-default-widgets", + "anathema-core", "anathema-geometry", "anathema-state", "anathema-state-derive", "anathema-store", - "anathema-templates", + "anathema-strings", "anathema-widgets", - "anathema-value-resolver", ] [workspace.lints.rust] diff --git a/anathema-backend/Cargo.toml b/anathema-backend/Cargo.toml deleted file mode 100644 index 7721c76a..00000000 --- a/anathema-backend/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "anathema-backend" -license = "MIT" -description = "Various backends for Anathema" -version.workspace = true -edition.workspace = true -documentation = "https://togglebyte.github.io/anathema-guide/" -homepage = "https://github.com/togglebyte/anathema" -repository = "https://github.com/togglebyte/anathema" - -[dependencies] -anathema-geometry = { workspace = true } -anathema-state = { workspace = true } -anathema-store = { workspace = true } -anathema-templates = { workspace = true } -anathema-value-resolver = { workspace = true } -anathema-widgets = { workspace = true } -crossterm = { workspace = true } -unicode-width = { workspace = true } -bitflags = { workspace = true } -puffin = { version = "0.19.1", optional = true } - -[features] -default = [] -profile = ["puffin"] - -[lints] -workspace = true diff --git a/anathema-backend/src/lib.rs b/anathema-backend/src/lib.rs deleted file mode 100644 index 6fe3cd0b..00000000 --- a/anathema-backend/src/lib.rs +++ /dev/null @@ -1,210 +0,0 @@ -use std::ops::ControlFlow; -use std::time::Duration; - -use anathema_geometry::{Pos, Size}; -use anathema_value_resolver::{AttributeStorage, Scope}; -use anathema_widgets::components::events::Event; -use anathema_widgets::error::Result; -use anathema_widgets::layout::{Constraints, LayoutCtx, LayoutFilter, PositionFilter, Viewport}; -use anathema_widgets::paint::PaintFilter; -use anathema_widgets::{ - DirtyWidgets, GlyphMap, Layout, LayoutForEach, PaintChildren, PositionChildren, WidgetTreeView, -}; - -pub mod testing; -pub mod tui; - -pub trait Backend { - fn size(&self) -> Size; - - fn next_event(&mut self, timeout: Duration) -> Option; - - fn resize(&mut self, new_size: Size, glyph_map: &mut GlyphMap); - - /// Paint the widgets - fn paint<'bp>( - &mut self, - glyph_map: &mut GlyphMap, - widgets: PaintChildren<'_, 'bp>, - attribute_storage: &AttributeStorage<'bp>, - ); - - /// Called by the runtime at the end of the frame. - fn render(&mut self, glyph_map: &mut GlyphMap); - - /// Clear is called immediately after `render` is called. - fn clear(&mut self); - - /// Finalizes the backend. This is called when the runtime starts. - fn finalize(&mut self) {} -} - -// TODO: rename this. -// This does layout, position and paint and should have -// a less silly name -pub struct WidgetCycle<'rt, 'bp, T> { - backend: &'rt mut T, - tree: WidgetTreeView<'rt, 'bp>, - constraints: Constraints, -} - -impl<'rt, 'bp, T: Backend> WidgetCycle<'rt, 'bp, T> { - pub fn new(backend: &'rt mut T, tree: WidgetTreeView<'rt, 'bp>, constraints: Constraints) -> Self { - Self { - backend, - tree, - constraints, - } - } - - fn fixed(&mut self, ctx: &mut LayoutCtx<'_, 'bp>) -> Result<()> { - // ----------------------------------------------------------------------------- - // - Position - - // ----------------------------------------------------------------------------- - self.position(ctx.attribute_storage, *ctx.viewport, PositionFilter::fixed()); - - // ----------------------------------------------------------------------------- - // - Paint - - // ----------------------------------------------------------------------------- - self.paint(ctx, PaintFilter::fixed()); - - Ok(()) - } - - fn floating(&mut self, ctx: &mut LayoutCtx<'_, 'bp>) -> Result<()> { - // ----------------------------------------------------------------------------- - // - Position - - // ----------------------------------------------------------------------------- - self.position(ctx.attribute_storage, *ctx.viewport, PositionFilter::floating()); - - // ----------------------------------------------------------------------------- - // - Paint - - // ----------------------------------------------------------------------------- - self.paint(ctx, PaintFilter::floating()); - - Ok(()) - } - - pub fn run( - &mut self, - ctx: &mut LayoutCtx<'_, 'bp>, - force_layout: bool, - dirty_widgets: &mut DirtyWidgets, - ) -> Result<()> { - // ----------------------------------------------------------------------------- - // - Layout - - // ----------------------------------------------------------------------------- - self.layout(ctx, LayoutFilter, dirty_widgets, force_layout)?; - - // ----------------------------------------------------------------------------- - // - Position and paint - - // ----------------------------------------------------------------------------- - self.fixed(ctx)?; - self.floating(ctx)?; - Ok(()) - } - - fn layout( - &mut self, - ctx: &mut LayoutCtx<'_, 'bp>, - filter: LayoutFilter, - dirty_widgets: &mut DirtyWidgets, - force_layout: bool, - ) -> Result<()> { - #[cfg(feature = "profile")] - puffin::profile_function!(); - - let mut tree = self.tree.view(); - - if force_layout { - // Perform a layout across the entire tree - let scope = Scope::root(); - let mut for_each = LayoutForEach::new(tree, &scope, filter); - let constraints = self.constraints; - _ = for_each.each(ctx, |ctx, widget, children| { - _ = widget.layout(children, constraints, ctx)?; - Ok(ControlFlow::Break(())) - })?; - return Ok(()); - } - - // If a widget has changed, mark the parent as dirty - - // Layout only changed widgets. - // These are the parents of changed widgets. - // - // Investigate the possibility of attaching an offset as existing widgets don't need - // to reflow unless the constraint has changed. - // - // This means `additional_widgets` needs to store (key, offset) where offset can be None - // - // If this is going to work we need to consider `expand` and `spacer` - // - // Since widgets can be made by anyone and they are always guaranteed to give - // access to all their children this might not be a possibility. - // - // parent - // widget 0 - // widget 1 - // widget 2 | <- if this changes, only reflow this, three and four - // widget 3 |-- reflow - // widget 4 | - - // TODO: make `additional_widgets` a scratch buffer part of `DirtyWidgets`. - // Also ensure that it tracks last id as well - // ... and removes it when done! - let mut additional_widgets = vec![]; - - loop { - for widget_id in dirty_widgets.drain() { - if !tree.contains(widget_id) { - continue; - } - tree.with_value_mut(widget_id, |_, widget, children| { - let scope = Scope::root(); - let mut children = LayoutForEach::new(children, &scope, filter); - children.parent_element = Some(widget_id); - let parent_id = widget.parent_widget; - let anathema_widgets::WidgetKind::Element(widget) = &mut widget.kind else { return }; - - let constraints = widget.constraints(); - if let Ok(Layout::Changed(_)) = widget.layout(children, constraints, ctx) { - // write into scratch buffer - if let Some(id) = parent_id { - additional_widgets.push(id); - } - } - }); - } - - // merge the scratch if it's not empty - - if additional_widgets.is_empty() { - break; - } - - dirty_widgets.inner.append(&mut additional_widgets); - } - - Ok(()) - } - - fn position(&mut self, attributes: &AttributeStorage<'bp>, viewport: Viewport, filter: PositionFilter) { - #[cfg(feature = "profile")] - puffin::profile_function!(); - - let mut for_each = PositionChildren::new(self.tree.view(), attributes, filter); - _ = for_each.each(|widget, children| { - widget.position(children, Pos::ZERO, attributes, viewport); - ControlFlow::Break(()) - }); - } - - fn paint(&mut self, ctx: &mut LayoutCtx<'_, 'bp>, filter: PaintFilter) { - #[cfg(feature = "profile")] - puffin::profile_function!(); - - let for_each = PaintChildren::new(self.tree.view(), ctx.attribute_storage, filter); - self.backend.paint(ctx.glyph_map, for_each, ctx.attribute_storage); - } -} diff --git a/anathema-backend/src/testing/events.rs b/anathema-backend/src/testing/events.rs deleted file mode 100644 index 44971ad2..00000000 --- a/anathema-backend/src/testing/events.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::collections::VecDeque; - -use anathema_geometry::Size; -use anathema_widgets::components::events::{Event, KeyEvent}; - -pub struct EventsMut<'a> { - event_queue: &'a mut VecDeque>, -} - -impl EventsMut<'_> { - pub fn next(self) -> Self { - self.event_queue.push_back(None); - self - } - - pub fn next_frames(self, count: usize) -> Self { - for _ in 0..count { - self.event_queue.push_back(None); - } - self - } - - pub fn resize(self, new_size: impl Into) -> Self { - let size = new_size.into(); - self.event_queue.push_back(Some(Event::Resize(size))); - self - } - - pub fn stop(self) -> Self { - self.event_queue.push_back(Some(Event::Stop)); - self - } - - pub fn press(self, event: KeyEvent) -> Self { - self.event_queue.push_back(Some(Event::Key(event))); - self - } -} - -#[derive(Debug)] -pub struct Events { - event_queue: VecDeque>, -} - -impl Events { - pub(super) fn new() -> Self { - Self { - event_queue: VecDeque::new(), - } - } - - pub(super) fn mut_ref(&mut self) -> EventsMut<'_> { - EventsMut { - event_queue: &mut self.event_queue, - } - } - - pub(super) fn pop(&mut self) -> Option { - self.event_queue.pop_front().flatten() - } -} diff --git a/anathema-backend/src/testing/mod.rs b/anathema-backend/src/testing/mod.rs deleted file mode 100644 index d4f566ae..00000000 --- a/anathema-backend/src/testing/mod.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::time::Duration; - -use anathema_geometry::Size; -use anathema_value_resolver::AttributeStorage; -use anathema_widgets::components::events::Event; -use anathema_widgets::paint::{Glyph, paint}; -use anathema_widgets::{GlyphMap, PaintChildren}; -use surface::TestSurface; - -use crate::Backend; - -mod events; -mod surface; - -#[derive(Debug)] -/// This is used for testing -pub struct GlyphRef<'a> { - inner: Option<&'a Glyph>, -} - -impl<'a> GlyphRef<'a> { - pub fn is_char(&self, rhs: char) -> bool { - let Some(glyph) = self.inner else { return false }; - match glyph { - Glyph::Single(lhs, _) => *lhs == rhs, - Glyph::Cluster(_glyph_index, _) => todo!(), - } - } -} - -#[derive(Debug)] -pub struct TestBackend { - surface: TestSurface, - events: events::Events, -} - -impl TestBackend { - pub fn new(size: impl Into) -> Self { - let size = size.into(); - Self { - surface: TestSurface::new(size), - events: events::Events::new(), - } - } - - pub fn at(&self, x: usize, y: usize) -> GlyphRef<'_> { - GlyphRef { - inner: self.surface.get(x, y), - } - } - - pub fn line(&self, index: usize) -> String { - let glyphs = self.surface.line(index); - glyphs - .iter() - .filter_map(|g| match g { - Glyph::Single(c, _) => Some(c), - Glyph::Cluster(_, _) => None, - }) - .collect::() - .trim() - .to_string() - } - - pub fn events(&mut self) -> events::EventsMut<'_> { - self.events.mut_ref() - } -} - -impl Backend for TestBackend { - fn size(&self) -> Size { - self.surface.size - } - - fn next_event(&mut self, _timeout: Duration) -> Option { - self.events.pop() - } - - fn resize(&mut self, new_size: Size, glyph_map: &mut GlyphMap) { - self.surface.resize(new_size, glyph_map); - } - - fn paint<'bp>( - &mut self, - glyph_map: &mut GlyphMap, - widgets: PaintChildren<'_, 'bp>, - attribute_storage: &AttributeStorage<'bp>, - ) { - self.surface.clear(); - paint(&mut self.surface, glyph_map, widgets, attribute_storage); - } - - fn render(&mut self, _glyph_map: &mut GlyphMap) { - // this does nothing here as everything written to the test buffer - } - - fn clear(&mut self) {} -} diff --git a/anathema-backend/src/testing/surface.rs b/anathema-backend/src/testing/surface.rs deleted file mode 100644 index 045f95e8..00000000 --- a/anathema-backend/src/testing/surface.rs +++ /dev/null @@ -1,69 +0,0 @@ -use anathema_geometry::{Pos, Size}; -use anathema_value_resolver::Attributes; -use anathema_widgets::paint::Glyph; -use anathema_widgets::{GlyphMap, Style, WidgetRenderer}; - -#[derive(Debug)] -pub struct TestSurface { - pub(super) size: Size, - lines: Vec, - prev_lines: Vec, -} - -impl TestSurface { - pub fn new(size: Size) -> Self { - let len = size.width * size.height; - let lines = vec![Glyph::space(); len as usize]; - Self { - size, - lines, - prev_lines: vec![], - } - } - - pub(crate) fn resize(&mut self, new_size: Size, _glyph_map: &mut GlyphMap) { - self.size = new_size; - // TODO: this should pop and truncate the lines to fit the new size - } - - // Get from previous lines as the clear function has most likely - // been called by the time this is relevant - pub(crate) fn get(&self, x: usize, y: usize) -> Option<&Glyph> { - let index = y * self.size.width as usize + x; - self.prev_lines.get(index) - } - - // Look at the old lines as the surface has been cleared - // by the time we can inspect the lines - pub(crate) fn line(&self, index: usize) -> &[Glyph] { - let from = index * self.size.width as usize; - let to = from + self.size.width as usize; - &self.prev_lines[from..to] - } - - pub(crate) fn clear(&mut self) { - let len = self.size.width * self.size.height; - let mut lines = vec![Glyph::space(); len as usize]; - std::mem::swap(&mut lines, &mut self.lines); - std::mem::swap(&mut lines, &mut self.prev_lines); - } -} - -impl WidgetRenderer for TestSurface { - fn draw_glyph(&mut self, mut glyph: Glyph, local_pos: Pos) { - let index = self.size.width as i32 * local_pos.y + local_pos.x; - let index = index as usize; - std::mem::swap(&mut glyph, &mut self.lines[index]); - } - - fn set_attributes(&mut self, attribs: &Attributes<'_>, local_pos: Pos) { - let style = Style::from_cell_attribs(attribs); - self.set_style(style, local_pos) - } - - fn set_style(&mut self, _style: Style, _local_pos: Pos) {} - - fn size(&self) -> Size { - self.size - } -} diff --git a/anathema-backend/src/tui/buffer.rs b/anathema-backend/src/tui/buffer.rs deleted file mode 100644 index 3d4773ff..00000000 --- a/anathema-backend/src/tui/buffer.rs +++ /dev/null @@ -1,453 +0,0 @@ -#![deny(missing_docs)] -use std::io::{Result, Write}; -use std::ops::Index; - -use anathema_geometry::Size; -use anathema_widgets::Style; -use anathema_widgets::paint::{Glyph, GlyphMap}; -use crossterm::style::Print; -use crossterm::{QueueableCommand, cursor}; - -use super::LocalPos; -use super::style::write_style; - -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Cell { - pub(crate) style: Style, - pub(crate) state: CellState, -} - -impl Cell { - pub(crate) fn empty() -> Self { - // It's important to reset the colours as there - // might be residual colors from previous draw. - Self { - style: Style::reset(), - state: CellState::Empty, - } - } - - pub(crate) fn reset() -> Self { - Self { - style: Style::reset(), - state: CellState::Occupied(Glyph::space()), - } - } - - fn continuation(style: Style) -> Self { - Self { - style, - state: CellState::Continuation, - } - } - - pub(crate) fn new(glyph: Glyph, style: Style) -> Self { - Self { - style, - state: CellState::Occupied(glyph), - } - } -} - -/// Represent the state of a cell inside a [`Buffer`]. -#[derive(Copy, Clone, PartialEq)] -pub(crate) enum CellState { - /// Empty - Empty, - /// Occupied by a certain character - Occupied(Glyph), - /// A continuation means this cell is part of another cell - /// representing a value that spans more than two chars, e.g 💖 - Continuation, -} - -impl std::fmt::Debug for CellState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - CellState::Empty => write!(f, ""), - CellState::Occupied(Glyph::Single(glyph, _width)) => write!(f, "{glyph}"), - CellState::Occupied(Glyph::Cluster(index, width)) => write!(f, ""), - CellState::Continuation => write!(f, ""), - } - } -} - -/// A buffer contains a list of cells representing characters that can be rendered. -/// This doesn't necessarily have to be `stdout`, it can be anything that implements -/// [`std::io::Write`] -/// -/// The [`crate::Screen`] holds two `Buffer`s, used to only render the difference between frames. -/// -/// The [`crate::Screen`] writes all the chars and their styles to the buffer, which works like a -/// grid. -#[derive(Debug, Clone)] -pub struct Buffer { - size: Size, - pub(crate) inner: Box<[Cell]>, -} - -impl Buffer { - /// Crate a new `Buffer` with a given size. - pub fn new(size: impl Into) -> Self { - let size = size.into(); - Self { - inner: vec![Cell::empty(); (size.width * size.height) as usize].into_boxed_slice(), - size, - } - } - - /// Create a new buffer with reset cells - pub(crate) fn reset(size: impl Into) -> Self { - let size = size.into(); - Self { - inner: vec![Cell::reset(); (size.width * size.height) as usize].into_boxed_slice(), - size, - } - } - - /// The size of the `Buffer` - pub fn size(&self) -> Size { - self.size - } - - /// Resize the buffer, truncating what doesn't fit but keeps what does. - pub fn resize(&mut self, size: Size) { - let mut new_buf = Buffer::new(size); - for (y, line) in self.cell_lines().enumerate() { - if y >= size.height as usize { - break; - } - - for (x, cell) in line.iter().enumerate() { - if x >= size.width as usize { - break; - } - - let pos = LocalPos::new(x as u16, y as u16); - new_buf.put(*cell, pos); - } - } - - self.size = size; - self.inner = new_buf.inner; - } - - /// Put a character with a style at a given position. - pub fn put_glyph(&mut self, glyph: Glyph, pos: LocalPos) { - assert!( - pos.x < self.size.width && pos.y < self.size.height, - "position out of bounds" - ); - - let style = match self.get(pos) { - Some((_, style)) => *style, - None => Style::new(), - }; - - let cell = Cell::new(glyph, style); - self.put(cell, pos); - } - - /// Update the attributes at a given cell. - /// If there is no character at that cell, then write an empty space into it - pub fn update_cell(&mut self, style: Style, pos: LocalPos) { - if pos.x >= self.size.width || pos.y >= self.size.height { - return; - } - - let index = pos.to_index(self.size.width); - let cell = &mut self.inner[index]; - - if let fg @ Some(_) = style.fg { - cell.style.fg = fg; - } - - if let bg @ Some(_) = style.bg { - cell.style.bg = bg; - } - - cell.style.attributes |= style.attributes; - } - - /// Get a reference to a `char` and [`Style`] at a given position inside the buffer. - pub fn get(&self, pos: LocalPos) -> Option<(&Glyph, &Style)> { - if pos.x >= self.size.width || pos.y >= self.size.height { - return None; - } - - let index = pos.to_index(self.size.width); - let cell = self.inner.get(index)?; - match &cell.state { - CellState::Occupied(c) => Some((c, &cell.style)), - _ => None, - } - } - - /// Get a mutable reference to a `char` and [`Style`] at a given position inside the buffer. - pub fn get_mut(&mut self, pos: LocalPos) -> Option<(&mut Glyph, &mut Style)> { - if pos.x >= self.size.width || pos.y >= self.size.height { - return None; - } - - let index = pos.to_index(self.size.width); - let cell = self.inner.get_mut(index)?; - match &mut cell.state { - CellState::Occupied(c) => Some((c, &mut cell.style)), - _ => None, - } - } - - pub(super) fn reset_cell(&mut self, pos: LocalPos) { - let index = pos.to_index(self.size.width); - self.inner[index] = Cell::empty(); - } - - fn put(&mut self, mut cell: Cell, pos: LocalPos) { - let index = pos.to_index(self.size.width); - - if let CellState::Occupied(c) = cell.state { - // If this is a unicode char that is wider than one cell, - // add a continuation cell if it fits, this way if we overwrite it - // we can set the continuation cell to `Empty`. - if pos.x < self.size.width && c.width() >= 2 { - self.put(Cell::continuation(cell.style), LocalPos::new(pos.x + 1, pos.y)); - } - } - - let current = &mut self.inner[index]; - cell.style.merge(current.style); - - match (&mut current.state, cell.state) { - // Merge the styles - (CellState::Occupied(current_char), CellState::Occupied(new_char)) => { - *current_char = new_char; - current.style.attributes |= cell.style.attributes; - - if let Some(col) = cell.style.fg { - current.style.fg = Some(col); - } - - if let Some(col) = cell.style.bg { - current.style.bg = Some(col); - } - } - _ => *current = cell, - } - } - - pub(super) fn cell_lines(&mut self) -> impl Iterator { - self.inner.chunks_mut(self.size.width as usize) - } -} - -impl Index<(usize, usize)> for Buffer { - type Output = Cell; - - fn index(&self, pos: (usize, usize)) -> &Self::Output { - let pos = LocalPos::from(pos); - let index = pos.to_index(self.size.width); - &self.inner[index] - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub(crate) enum Change { - Remove, - Continuation, - Insert(Glyph), -} - -impl Change { - fn width(self) -> usize { - match self { - Self::Remove => 1, - Self::Continuation => 0, - Self::Insert(c) => c.width(), - } - } -} - -pub(crate) fn diff( - old: &mut Buffer, - new: &mut Buffer, - changes: &mut Vec<(LocalPos, Option