1use lattice_core::BufferId;
27use lattice_protocol::Position;
28
29pub const DEFAULT_PANE_BUFFER_HISTORY_SIZE: usize = 100;
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct PaneHistoryEntry {
47 pub buffer: BufferId,
48 pub cursor: Position,
49 pub scroll: u32,
50}
51
52impl PaneHistoryEntry {
53 pub fn new(buffer: BufferId, cursor: Position, scroll: u32) -> Self {
54 Self {
55 buffer,
56 cursor,
57 scroll,
58 }
59 }
60
61 pub fn at_origin(buffer: BufferId) -> Self {
64 Self::new(buffer, Position::ZERO, 0)
65 }
66}
67
68#[derive(Debug, Clone, Default, PartialEq, Eq)]
74pub struct PaneBufferHistory {
75 entries: Vec<PaneHistoryEntry>,
76 cursor: usize,
77}
78
79impl PaneBufferHistory {
80 pub fn seeded(entry: PaneHistoryEntry) -> Self {
83 Self {
84 entries: vec![entry],
85 cursor: 0,
86 }
87 }
88
89 pub fn entries(&self) -> &[PaneHistoryEntry] {
90 &self.entries
91 }
92
93 pub fn cursor(&self) -> usize {
94 self.cursor
95 }
96
97 pub fn is_empty(&self) -> bool {
98 self.entries.is_empty()
99 }
100
101 pub fn len(&self) -> usize {
102 self.entries.len()
103 }
104
105 pub fn current(&self) -> Option<&PaneHistoryEntry> {
107 self.entries.get(self.cursor)
108 }
109
110 pub fn update_current_position(&mut self, cursor: Position, scroll: u32) {
114 if let Some(entry) = self.entries.get_mut(self.cursor) {
115 entry.cursor = cursor;
116 entry.scroll = scroll;
117 }
118 }
119
120 pub fn repoint_current(&mut self, buffer: BufferId) {
129 if let Some(entry) = self.entries.get_mut(self.cursor) {
130 entry.buffer = buffer;
131 }
132 }
133
134 pub fn push(&mut self, entry: PaneHistoryEntry, cap: usize) {
144 if self.current().map(|c| c.buffer) == Some(entry.buffer) {
145 return;
146 }
147 if !self.entries.is_empty() {
150 self.entries.truncate(self.cursor + 1);
151 }
152 self.entries.push(entry);
153 self.cursor = self.entries.len() - 1;
154 self.evict(cap);
155 }
156
157 fn evict(&mut self, cap: usize) {
159 let cap = cap.max(1);
162 if self.entries.len() <= cap {
163 return;
164 }
165 let overflow = self.entries.len() - cap;
166 self.entries.drain(..overflow);
167 self.cursor = self.cursor.saturating_sub(overflow);
168 }
169
170 pub fn back(&mut self) -> Option<PaneHistoryEntry> {
179 if self.cursor == 0 {
180 return None;
181 }
182 self.cursor -= 1;
183 self.entries.get(self.cursor).copied()
184 }
185
186 pub fn forward(&mut self) -> Option<PaneHistoryEntry> {
189 if self.cursor + 1 >= self.entries.len() {
190 return None;
191 }
192 self.cursor += 1;
193 self.entries.get(self.cursor).copied()
194 }
195
196 pub fn jump_to(&mut self, index: usize) -> Option<PaneHistoryEntry> {
200 if index >= self.entries.len() {
201 return None;
202 }
203 self.cursor = index;
204 self.entries.get(index).copied()
205 }
206
207 pub fn retain_live<F>(&mut self, still_live: F)
214 where
215 F: Fn(BufferId) -> bool,
216 {
217 if self.entries.is_empty() {
218 return;
219 }
220 let surviving_before = self.entries[..=self.cursor.min(self.entries.len() - 1)]
224 .iter()
225 .filter(|e| still_live(e.buffer))
226 .count();
227 self.entries.retain(|e| still_live(e.buffer));
228 self.cursor = surviving_before
229 .saturating_sub(1)
230 .min(self.entries.len().saturating_sub(1));
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 fn buf(n: u32) -> BufferId {
239 BufferId(n)
240 }
241
242 fn entry(n: u32) -> PaneHistoryEntry {
243 PaneHistoryEntry::at_origin(buf(n))
244 }
245
246 const CAP: usize = 100;
247
248 fn trail(ids: &[u32]) -> PaneBufferHistory {
249 let mut h = PaneBufferHistory::default();
250 for &n in ids {
251 h.push(entry(n), CAP);
252 }
253 h
254 }
255
256 fn buffers(h: &PaneBufferHistory) -> Vec<u32> {
257 h.entries().iter().map(|e| e.buffer.0).collect()
258 }
259
260 #[test]
261 fn a_fresh_history_is_empty() {
262 let h = PaneBufferHistory::default();
263 assert!(h.is_empty());
264 assert_eq!(h.current(), None);
265 }
266
267 #[test]
268 fn seeding_gives_one_entry_at_the_cursor() {
269 let h = PaneBufferHistory::seeded(entry(1));
270 assert_eq!(buffers(&h), vec![1]);
271 assert_eq!(h.cursor(), 0);
272 assert_eq!(h.current().map(|e| e.buffer), Some(buf(1)));
273 }
274
275 #[test]
276 fn push_appends_and_advances_the_cursor() {
277 let h = trail(&[1, 2, 3]);
278 assert_eq!(buffers(&h), vec![1, 2, 3]);
279 assert_eq!(h.cursor(), 2);
280 }
281
282 #[test]
283 fn pushing_the_current_buffer_again_is_ignored() {
284 let mut h = trail(&[1, 2]);
287 h.push(entry(2), CAP);
288 assert_eq!(buffers(&h), vec![1, 2]);
289 assert_eq!(h.cursor(), 1);
290 }
291
292 #[test]
293 fn non_adjacent_repeats_are_kept() {
294 let h = trail(&[1, 2, 1]);
296 assert_eq!(buffers(&h), vec![1, 2, 1]);
297 }
298
299 #[test]
300 fn back_and_forward_round_trip() {
301 let mut h = trail(&[1, 2, 3]);
302 assert_eq!(h.back().map(|e| e.buffer), Some(buf(2)));
303 assert_eq!(h.back().map(|e| e.buffer), Some(buf(1)));
304 assert_eq!(h.forward().map(|e| e.buffer), Some(buf(2)));
305 assert_eq!(h.forward().map(|e| e.buffer), Some(buf(3)));
306 }
307
308 #[test]
309 fn back_at_the_oldest_entry_returns_none_and_does_not_wrap() {
310 let mut h = trail(&[1, 2]);
311 h.back();
312 assert_eq!(h.cursor(), 0);
313 assert_eq!(h.back(), None, "must not wrap to the newest entry");
314 assert_eq!(h.cursor(), 0, "a refused back must not move the cursor");
315 }
316
317 #[test]
318 fn forward_at_the_newest_entry_returns_none_and_does_not_wrap() {
319 let mut h = trail(&[1, 2]);
320 assert_eq!(h.forward(), None);
321 assert_eq!(h.cursor(), 1);
322 }
323
324 #[test]
325 fn walking_does_not_record() {
326 let mut h = trail(&[1, 2, 3]);
330 let before = buffers(&h);
331 h.back();
332 h.back();
333 assert_eq!(buffers(&h), before, "a walk must not alter the entries");
334 }
335
336 #[test]
337 fn visiting_while_walked_back_truncates_the_forward_tail() {
338 let mut h = trail(&[1, 2, 3]);
340 h.back();
341 h.push(entry(4), CAP);
342 assert_eq!(buffers(&h), vec![1, 2, 4]);
343 assert_eq!(h.cursor(), 2);
344 assert_eq!(h.forward(), None, "the truncated tail must not survive");
345 }
346
347 #[test]
348 fn update_current_position_records_the_outgoing_cursor() {
349 let mut h = trail(&[1, 2]);
350 h.back();
351 h.update_current_position(Position::new(12, 3), 7);
352 h.forward();
353 let back = h.back().expect("entry 1 exists");
354 assert_eq!(back.cursor, Position::new(12, 3));
355 assert_eq!(back.scroll, 7);
356 }
357
358 #[test]
359 fn eviction_drops_oldest_and_keeps_the_cursor_on_the_same_entry() {
360 let mut h = PaneBufferHistory::default();
361 for n in 1..=5 {
362 h.push(entry(n), 3);
363 }
364 assert_eq!(buffers(&h), vec![3, 4, 5], "cap of 3 keeps the newest 3");
365 assert_eq!(
366 h.current().map(|e| e.buffer),
367 Some(buf(5)),
368 "the cursor must still point at the entry it pointed at before eviction",
369 );
370 }
371
372 #[test]
373 fn a_zero_cap_is_treated_as_one_rather_than_underflowing() {
374 let mut h = PaneBufferHistory::default();
377 h.push(entry(1), 0);
378 h.push(entry(2), 0);
379 assert_eq!(buffers(&h), vec![2]);
380 assert_eq!(h.cursor(), 0);
381 assert_eq!(h.current().map(|e| e.buffer), Some(buf(2)));
382 }
383
384 #[test]
385 fn jump_to_moves_without_recording() {
386 let mut h = trail(&[1, 2, 3]);
389 let before = buffers(&h);
390 assert_eq!(h.jump_to(0).map(|e| e.buffer), Some(buf(1)));
391 assert_eq!(buffers(&h), before);
392 assert_eq!(h.cursor(), 0);
393 assert_eq!(h.forward().map(|e| e.buffer), Some(buf(2)));
394 }
395
396 #[test]
397 fn jump_to_out_of_range_is_a_no_op() {
398 let mut h = trail(&[1, 2]);
399 assert_eq!(h.jump_to(9), None);
400 assert_eq!(h.cursor(), 1);
401 }
402
403 #[test]
404 fn retain_live_drops_deleted_buffers() {
405 let mut h = trail(&[1, 2, 3]);
406 h.retain_live(|b| b != buf(2));
407 assert_eq!(buffers(&h), vec![1, 3]);
408 }
409
410 #[test]
411 fn retain_live_keeps_the_cursor_on_the_nearest_survivor() {
412 let mut h = trail(&[1, 2, 3, 4]);
413 h.back(); h.retain_live(|b| b != buf(2));
415 assert_eq!(buffers(&h), vec![1, 3, 4]);
417 assert_eq!(h.current().map(|e| e.buffer), Some(buf(3)));
418 }
419
420 #[test]
421 fn retain_live_can_empty_the_history_without_panicking() {
422 let mut h = trail(&[1, 2]);
423 h.retain_live(|_| false);
424 assert!(h.is_empty());
425 assert_eq!(h.current(), None);
426 assert_eq!(h.back(), None);
427 assert_eq!(h.forward(), None);
428 }
429}