Skip to main content

strop_core/layout/
index.rs

1//! Sparse byte/cell checkpoints. Built on a worker for long lines, then shared
2//! with the UI; a seek replays at most one checkpoint interval, not the prefix.
3use super::RopeGraphemes;
4use crate::id::{ByteOffset, DisplayColumn, LineIndex};
5use ropey::RopeSlice;
6use std::sync::Arc;
7
8pub const INLINE_LAYOUT_BYTES: usize = 4096;
9const CHECKPOINT_GRAPHEMES: usize = 128;
10
11#[derive(Debug, Clone, Copy, Default, serde::Serialize, serde::Deserialize)]
12pub struct LayoutCheckpoint {
13    pub byte: ByteOffset,
14    pub cell: DisplayColumn,
15}
16#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
17pub struct LineLayoutIndex {
18    pub(crate) checkpoints: Vec<LayoutCheckpoint>,
19    pub(crate) bytes: usize,
20    pub(crate) tab: usize,
21}
22#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
23pub struct PreparedLineLayout {
24    pub line: LineIndex,
25    pub index: Arc<LineLayoutIndex>,
26}
27impl LineLayoutIndex {
28    pub fn build(text: RopeSlice<'_>, tab: usize, cancelled: impl Fn() -> bool) -> Option<Self> {
29        let mut checkpoints = vec![LayoutCheckpoint::default()];
30        let mut end = DisplayColumn::new(0);
31        for (count, (span, _)) in RopeGraphemes::new(text, tab).enumerate() {
32            if count % CHECKPOINT_GRAPHEMES == 0 {
33                if cancelled() {
34                    return None;
35                }
36                if span.byte != 0 {
37                    checkpoints.push(LayoutCheckpoint {
38                        byte: ByteOffset::new(span.byte),
39                        cell: span.cell,
40                    });
41                }
42            }
43            end = span.cell + span.width;
44        }
45        if cancelled() {
46            return None;
47        }
48        if checkpoints
49            .last()
50            .is_some_and(|point| point.byte.get() != text.len_bytes())
51        {
52            checkpoints.push(LayoutCheckpoint {
53                byte: ByteOffset::new(text.len_bytes()),
54                cell: end,
55            });
56        }
57        Some(Self {
58            checkpoints,
59            bytes: text.len_bytes(),
60            tab: tab.max(1),
61        })
62    }
63    pub(crate) fn at_byte(&self, byte: usize, valid: usize) -> LayoutCheckpoint {
64        let points = &self.checkpoints[..valid];
65        points[points
66            .partition_point(|point| point.byte.get() <= byte)
67            .saturating_sub(1)]
68    }
69    pub(crate) fn at_cell(&self, cell: DisplayColumn, valid: usize) -> LayoutCheckpoint {
70        let points = &self.checkpoints[..valid];
71        points[points
72            .partition_point(|point| point.cell <= cell)
73            .saturating_sub(1)]
74    }
75    pub(crate) fn prefix_before(&self, byte: usize, valid: usize) -> usize {
76        self.checkpoints[..valid]
77            .partition_point(|point| point.byte.get() < byte)
78            .max(1)
79    }
80}