1use crate::buffer::Cell;
9
10const CHUNK_SIZE: usize = 64;
13
14#[derive(Debug, Clone)]
16struct Chunk {
17 lines: Vec<ChunkedLine>,
19}
20
21impl Chunk {
22 fn new() -> Self {
24 Self {
25 lines: Vec::with_capacity(CHUNK_SIZE),
26 }
27 }
28
29 const fn is_full(&self) -> bool {
31 self.lines.len() >= CHUNK_SIZE
32 }
33
34 const fn len(&self) -> usize {
36 self.lines.len()
37 }
38
39 #[allow(dead_code)]
41 const fn is_empty(&self) -> bool {
42 self.lines.is_empty()
43 }
44
45 fn push(&mut self, line: ChunkedLine) {
47 self.lines.push(line);
48 }
49
50 fn get(&self, index: usize) -> Option<&ChunkedLine> {
52 self.lines.get(index)
53 }
54
55 fn get_mut(&mut self, index: usize) -> Option<&mut ChunkedLine> {
57 self.lines.get_mut(index)
58 }
59}
60
61#[derive(Debug, Clone)]
63pub struct ChunkedLine {
64 pub content: Vec<Cell>,
66 pub wrapped: bool,
68}
69
70impl ChunkedLine {
71 pub const fn new(content: Vec<Cell>, wrapped: bool) -> Self {
73 Self { content, wrapped }
74 }
75
76 pub const fn empty() -> Self {
78 Self {
79 content: Vec::new(),
80 wrapped: false,
81 }
82 }
83
84 pub const fn len(&self) -> usize {
86 self.content.len()
87 }
88
89 pub const fn is_empty(&self) -> bool {
91 self.content.is_empty()
92 }
93}
94
95#[derive(Debug)]
103pub struct RopeBuffer {
104 chunks: Vec<Chunk>,
106 total_lines: usize,
108 max_lines: usize,
110 scroll_offset: usize,
112}
113
114impl RopeBuffer {
115 pub fn new(max_lines: usize) -> Self {
121 let mut buffer = Self {
122 chunks: Vec::new(),
123 total_lines: 0,
124 max_lines,
125 scroll_offset: 0,
126 };
127 buffer.push_line(ChunkedLine::empty());
129 buffer
130 }
131
132 pub fn unbounded() -> Self {
134 Self::new(0)
135 }
136
137 pub const fn len(&self) -> usize {
139 self.total_lines
140 }
141
142 pub const fn is_empty(&self) -> bool {
144 self.total_lines == 0
145 }
146
147 pub const fn chunk_count(&self) -> usize {
149 self.chunks.len()
150 }
151
152 pub fn get_line(&self, index: usize) -> Option<&ChunkedLine> {
154 if index >= self.total_lines {
155 return None;
156 }
157 let chunk_idx = index / CHUNK_SIZE;
158 let line_idx = index % CHUNK_SIZE;
159 self.chunks.get(chunk_idx)?.get(line_idx)
160 }
161
162 pub fn get_line_mut(&mut self, index: usize) -> Option<&mut ChunkedLine> {
164 if index >= self.total_lines {
165 return None;
166 }
167 let chunk_idx = index / CHUNK_SIZE;
168 let line_idx = index % CHUNK_SIZE;
169 self.chunks.get_mut(chunk_idx)?.get_mut(line_idx)
170 }
171
172 pub fn current_line(&self) -> Option<&ChunkedLine> {
174 if self.total_lines == 0 {
175 return None;
176 }
177 self.get_line(self.total_lines - 1)
178 }
179
180 pub fn current_line_mut(&mut self) -> Option<&mut ChunkedLine> {
182 if self.total_lines == 0 {
183 return None;
184 }
185 let idx = self.total_lines - 1;
186 self.get_line_mut(idx)
187 }
188
189 pub fn push_line(&mut self, line: ChunkedLine) {
191 if self.chunks.is_empty() || self.chunks.last().is_none_or(Chunk::is_full) {
193 self.chunks.push(Chunk::new());
194 }
195
196 if let Some(chunk) = self.chunks.last_mut() {
198 chunk.push(line);
199 self.total_lines += 1;
200 }
201
202 if self.max_lines > 0 && self.total_lines > self.max_lines {
204 self.trim_front();
205 }
206 }
207
208 pub fn newline(&mut self) {
210 self.push_line(ChunkedLine::empty());
211 }
212
213 pub fn append(&mut self, cells: impl Iterator<Item = Cell>) {
215 if let Some(line) = self.current_line_mut() {
216 line.content.extend(cells);
217 }
218 }
219
220 pub fn clear(&mut self) {
222 self.chunks.clear();
223 self.total_lines = 0;
224 self.scroll_offset = 0;
225 self.push_line(ChunkedLine::empty());
226 }
227
228 pub const fn scroll_offset(&self) -> usize {
230 self.scroll_offset
231 }
232
233 pub fn scroll_up(&mut self, lines: usize) {
235 let max_offset = self.total_lines.saturating_sub(1);
236 self.scroll_offset = (self.scroll_offset + lines).min(max_offset);
237 }
238
239 pub const fn scroll_down(&mut self, lines: usize) {
241 self.scroll_offset = self.scroll_offset.saturating_sub(lines);
242 }
243
244 pub const fn scroll_to_bottom(&mut self) {
246 self.scroll_offset = 0;
247 }
248
249 pub fn visible_lines(&self, viewport_height: usize) -> impl Iterator<Item = (usize, &ChunkedLine)> {
254 let end = self.total_lines.saturating_sub(self.scroll_offset);
255 let start = end.saturating_sub(viewport_height);
256
257 (start..end).filter_map(move |i| {
258 self.get_line(i).map(|line| (i, line))
259 })
260 }
261
262 fn trim_front(&mut self) {
264 while self.total_lines > self.max_lines && !self.chunks.is_empty() {
265 let removed_chunk = self.chunks.remove(0);
267 self.total_lines -= removed_chunk.len();
268
269 if self.scroll_offset > removed_chunk.len() {
271 self.scroll_offset -= removed_chunk.len();
272 } else {
273 self.scroll_offset = 0;
274 }
275 }
276 }
277
278 pub fn memory_stats(&self) -> RopeMemoryStats {
280 let mut total_cells = 0;
281 for chunk in &self.chunks {
282 for line in &chunk.lines {
283 total_cells += line.content.len();
284 }
285 }
286
287 RopeMemoryStats {
288 chunks: self.chunks.len(),
289 lines: self.total_lines,
290 cells: total_cells,
291 bytes_estimated: self.chunks.len() * std::mem::size_of::<Chunk>()
292 + self.total_lines * std::mem::size_of::<ChunkedLine>()
293 + total_cells * std::mem::size_of::<Cell>(),
294 }
295 }
296}
297
298#[derive(Debug, Clone, Copy)]
300pub struct RopeMemoryStats {
301 pub chunks: usize,
303 pub lines: usize,
305 pub cells: usize,
307 pub bytes_estimated: usize,
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 #[test]
316 fn test_rope_buffer_basic() {
317 let mut buffer = RopeBuffer::new(1000);
318
319 assert_eq!(buffer.len(), 1); buffer.append([Cell::new('H'), Cell::new('i')].into_iter());
322 assert_eq!(buffer.current_line().unwrap().len(), 2);
323
324 buffer.newline();
325 assert_eq!(buffer.len(), 2);
326 }
327
328 #[test]
329 fn test_rope_buffer_chunks() {
330 let mut buffer = RopeBuffer::unbounded();
331
332 for i in 0..200 {
334 buffer.newline();
335 buffer.append([Cell::new(char::from_u32(('a' as u32) + (i % 26)).unwrap())].into_iter());
336 }
337
338 assert!(buffer.chunk_count() > 1);
340 assert_eq!(buffer.len(), 201); }
342
343 #[test]
344 fn test_rope_buffer_max_lines() {
345 let mut buffer = RopeBuffer::new(100);
346
347 for _ in 0..200 {
348 buffer.newline();
349 }
350
351 assert!(buffer.len() <= 100 + CHUNK_SIZE);
354 }
355
356 #[test]
357 fn test_rope_buffer_scroll() {
358 let mut buffer = RopeBuffer::new(1000);
359
360 for _ in 0..50 {
361 buffer.newline();
362 }
363
364 assert_eq!(buffer.scroll_offset(), 0);
365
366 buffer.scroll_up(10);
367 assert_eq!(buffer.scroll_offset(), 10);
368
369 buffer.scroll_down(5);
370 assert_eq!(buffer.scroll_offset(), 5);
371
372 buffer.scroll_to_bottom();
373 assert_eq!(buffer.scroll_offset(), 0);
374 }
375
376 #[test]
377 fn test_rope_buffer_visible_lines() {
378 let mut buffer = RopeBuffer::new(1000);
379
380 for i in 0..20 {
381 buffer.append([Cell::new(char::from_u32('a' as u32 + i).unwrap())].into_iter());
382 buffer.newline();
383 }
384
385 let visible: Vec<_> = buffer.visible_lines(10).collect();
386 assert_eq!(visible.len(), 10);
387 }
388
389 #[test]
390 fn test_rope_buffer_memory_stats() {
391 let mut buffer = RopeBuffer::new(1000);
392
393 for _ in 0..100 {
394 buffer.append([Cell::new('x'); 80].into_iter());
395 buffer.newline();
396 }
397
398 let stats = buffer.memory_stats();
399 assert_eq!(stats.lines, 101);
400 assert_eq!(stats.cells, 8000);
401 assert!(stats.bytes_estimated > 0);
402 }
403}