strop_core/layout/
index.rs1use 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}