1use super::Buffer;
4use crate::id::{BufferRevision, DisplayColumn, LineIndex};
5use crate::layout::{
6 LayoutCheckpoint, LineLayoutIndex, PreparedLineLayout, RopeGraphemes, INLINE_LAYOUT_BYTES,
7};
8use crate::InputEdit;
9use std::cell::RefCell;
10use std::sync::Arc;
11
12struct CachedLine {
13 line: LineIndex,
14 index: Arc<LineLayoutIndex>,
15 valid: usize,
16}
17#[derive(Default)]
18pub(super) struct LineLayouts {
19 entries: RefCell<Vec<CachedLine>>,
20}
21impl Buffer {
22 pub fn prepare_line_layout(
23 &self,
24 line: LineIndex,
25 tab: usize,
26 cancelled: impl Fn() -> bool,
27 ) -> Option<PreparedLineLayout> {
28 let text = self
29 .text()
30 .byte_slice(self.line_start(line)..self.line_end(line));
31 if text.len_bytes() <= INLINE_LAYOUT_BYTES {
32 return None;
33 }
34 LineLayoutIndex::build(text, tab, cancelled).map(|index| PreparedLineLayout {
35 line,
36 index: Arc::new(index),
37 })
38 }
39
40 pub fn install_line_layouts(
41 &mut self,
42 revision: BufferRevision,
43 layouts: &[PreparedLineLayout],
44 ) -> bool {
45 if revision != self.revision() {
46 return false;
47 }
48 for layout in layouts {
49 if layout.line.get() >= self.len_lines()
50 || layout.index.bytes != self.line_end(layout.line) - self.line_start(layout.line)
51 || layout.index.checkpoints.is_empty()
52 || layout.index.checkpoints[0].byte.get() != 0
53 || layout.index.checkpoints[0].cell.get() != 0
54 || layout.index.checkpoints.windows(2).any(|points| {
55 points[0].byte >= points[1].byte || points[0].cell > points[1].cell
56 })
57 || layout
58 .index
59 .checkpoints
60 .last()
61 .is_some_and(|point| point.byte.get() > layout.index.bytes)
62 {
63 return false;
64 }
65 }
66 let entries = self.line_layouts.entries.get_mut();
67 for layout in layouts {
68 let value = CachedLine {
69 line: layout.line,
70 index: layout.index.clone(),
71 valid: layout.index.checkpoints.len(),
72 };
73 if let Some(entry) = entries
74 .iter_mut()
75 .find(|entry| entry.line == layout.line && entry.index.tab == layout.index.tab)
76 {
77 *entry = value;
78 } else {
79 const CACHED_LINES: usize = 256;
80 if entries.len() == CACHED_LINES {
81 entries.remove(0);
82 }
83 entries.push(value);
84 }
85 }
86 true
87 }
88
89 pub(super) fn invalidate_line_layouts(&mut self, edit: &InputEdit) {
90 let same_line = edit.start_point.0 == edit.old_end_point.0
91 && edit.start_point.0 == edit.new_end_point.0;
92 self.line_layouts.entries.get_mut().retain_mut(|entry| {
93 if entry.line.get() < edit.start_point.0 {
94 return true;
95 }
96 if !same_line {
97 return false;
98 }
99 if entry.line.get() == edit.start_point.0 {
100 entry.valid = entry.index.prefix_before(edit.start_point.1, entry.valid);
101 }
102 true
103 });
104 }
105
106 pub fn layout_checkpoint(
107 &self,
108 line: LineIndex,
109 origin: DisplayColumn,
110 tab: usize,
111 ) -> Option<LayoutCheckpoint> {
112 let entries = self.line_layouts.entries.borrow();
113 if let Some(entry) = entries
114 .iter()
115 .find(|entry| entry.line == line && entry.index.tab == tab.max(1))
116 {
117 let checkpoint = entry.index.at_cell(origin, entry.valid);
118 if entry.valid == entry.index.checkpoints.len()
119 || origin.get().saturating_sub(checkpoint.cell.get()) <= INLINE_LAYOUT_BYTES
120 {
121 return Some(checkpoint);
122 }
123 }
124 (origin.get() == 0 || self.line_end(line) - self.line_start(line) <= INLINE_LAYOUT_BYTES)
125 .then_some(LayoutCheckpoint::default())
126 }
127
128 pub fn try_cell_col_with_tab(&self, offset: usize, tab: usize) -> Option<DisplayColumn> {
129 self.column_from_layout(offset, tab, true)
130 }
131
132 pub(super) fn column_from_layout(
133 &self,
134 offset: usize,
135 tab: usize,
136 bounded: bool,
137 ) -> Option<DisplayColumn> {
138 let offset = offset.min(self.len_bytes());
139 let line = LineIndex::new(self.line_of(offset));
140 let start = self.line_start(line);
141 let text = self.text().byte_slice(start..self.line_end(line));
142 let byte = offset.saturating_sub(start).min(text.len_bytes());
143 let (point, indexed) = self
144 .line_layouts
145 .entries
146 .borrow()
147 .iter()
148 .find(|entry| entry.line == line && entry.index.tab == tab.max(1))
149 .map_or((LayoutCheckpoint::default(), false), |entry| {
150 (
151 entry.index.at_byte(byte, entry.valid),
152 entry.valid == entry.index.checkpoints.len(),
153 )
154 });
155 if bounded && !indexed && byte.saturating_sub(point.byte.get()) > INLINE_LAYOUT_BYTES {
156 return None;
157 }
158 let mut end = point.cell;
159 for (span, cluster) in RopeGraphemes::from_checkpoint(text, tab, point) {
160 if byte < span.byte + cluster.len() {
161 return Some(span.cell);
162 }
163 end = span.cell + span.width;
164 }
165 Some(end)
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use crate::{id::ByteOffset, Range};
173
174 fn warm(buffer: &mut Buffer, line: usize, tab: usize) -> PreparedLineLayout {
175 let layout = buffer
176 .prepare_line_layout(LineIndex::new(line), tab, || false)
177 .unwrap();
178 assert!(buffer.install_line_layouts(buffer.revision(), std::slice::from_ref(&layout)));
179 layout
180 }
181
182 #[test]
183 fn indexed_unicode_cells_and_byte_offsets_match_the_frozen_line() {
184 let source = format!("{}\t็๐ฉโ๐ปe\u{301} tail", "ab".repeat(4096));
185 let mut buffer = Buffer::from_text(&source);
186 warm(&mut buffer, 0, 4);
187 let plain = Buffer::from_snapshot(buffer.snapshot());
188 for (byte, _) in source.char_indices().filter(|(byte, _)| *byte >= 8192) {
189 assert_eq!(
190 buffer.try_cell_col_with_tab(byte, 4),
191 Some(plain.cell_col_with_tab(byte, 4))
192 );
193 }
194 let origin = DisplayColumn::new(8196);
195 let checkpoint = buffer
196 .layout_checkpoint(LineIndex::new(0), origin, 4)
197 .unwrap();
198 let text = buffer.text().slice(..);
199 let indexed: Vec<_> = RopeGraphemes::from_checkpoint(text, 4, checkpoint)
200 .filter(|(span, _)| span.cell >= origin)
201 .map(|(span, text)| (span.byte, span.cell, text.into_owned()))
202 .collect();
203 let expected: Vec<_> = RopeGraphemes::new(text, 4)
204 .filter(|(span, _)| span.cell >= origin)
205 .map(|(span, text)| (span.byte, span.cell, text.into_owned()))
206 .collect();
207 assert_eq!(indexed, expected);
208 }
209
210 #[test]
211 fn edits_keep_only_proven_prefixes_and_reject_old_publications() {
212 let mut buffer = Buffer::from_text(&format!("{}\t็end", "a".repeat(8192)));
213 let old_revision = buffer.revision();
214 let old = warm(&mut buffer, 0, 4);
215 buffer.edit().insert(ByteOffset::new(8196), "Z").unwrap();
216 assert!(!buffer.install_line_layouts(old_revision, &[old]));
217 let byte = buffer.len_bytes();
218 let fresh = Buffer::from_snapshot(buffer.snapshot());
219 assert_eq!(
220 buffer.try_cell_col_with_tab(byte, 4),
221 Some(fresh.cell_col_with_tab(byte, 4))
222 );
223 buffer
224 .edit()
225 .replace(Range::charwise(0usize, 1usize), "\t")
226 .unwrap();
227 assert!(buffer.try_cell_col_with_tab(byte, 4).is_none());
229 warm(&mut buffer, 0, 4);
230 let fresh = Buffer::from_snapshot(buffer.snapshot());
231 assert_eq!(
232 buffer.try_cell_col_with_tab(byte, 4),
233 Some(fresh.cell_col_with_tab(byte, 4))
234 );
235 }
236}