reovim_kernel/core/history.rs
1//! Clipboard history ring for numbered registers (0-255).
2//!
3//! Provides a LIFO ring buffer for tracking yank/delete history.
4//! Index `0` is the most recent entry, `255` is the oldest.
5//!
6//! # Capacity
7//!
8//! Default capacity is 256 entries (the full `u8` range), addressable via
9//! `Register::History(u8)`. Entries beyond capacity are discarded.
10//!
11//! # Per-Client Isolation (#515)
12//!
13//! Each client owns their own `HistoryRing`. Client A's yank/delete
14//! operations do not affect Client B's history.
15
16use std::collections::VecDeque;
17
18use super::register::RegisterContent;
19
20/// Default capacity for the history ring (registers 0-255).
21const DEFAULT_HISTORY_CAPACITY: usize = 256;
22
23/// LIFO ring buffer for yank/delete history (numbered registers 0-255).
24///
25/// # Example
26///
27/// ```
28/// use reovim_kernel::api::v1::*;
29///
30/// let mut ring = HistoryRing::new();
31/// ring.push(RegisterContent::characterwise("first"));
32/// ring.push(RegisterContent::characterwise("second"));
33///
34/// // Most recent is at index 0
35/// assert_eq!(ring.get(0).map(|r| r.text.as_str()), Some("second"));
36/// assert_eq!(ring.get(1).map(|r| r.text.as_str()), Some("first"));
37/// ```
38#[derive(Debug, Clone)]
39pub struct HistoryRing {
40 /// Entries in LIFO order (index 0 = most recent).
41 entries: VecDeque<RegisterContent>,
42 /// Maximum number of entries to keep.
43 capacity: usize,
44}
45
46impl HistoryRing {
47 /// Create a new history ring with default capacity (256).
48 #[must_use]
49 pub const fn new() -> Self {
50 Self {
51 entries: VecDeque::new(),
52 capacity: DEFAULT_HISTORY_CAPACITY,
53 }
54 }
55
56 /// Create a history ring with custom capacity.
57 #[must_use]
58 pub fn with_capacity(capacity: usize) -> Self {
59 Self {
60 entries: VecDeque::with_capacity(capacity),
61 capacity,
62 }
63 }
64
65 /// Push content to the front of the history.
66 ///
67 /// The new entry becomes register `0`. If the ring is full,
68 /// the oldest entry (register 9) is discarded.
69 pub fn push(&mut self, content: RegisterContent) {
70 self.entries.push_front(content);
71 if self.entries.len() > self.capacity {
72 self.entries.pop_back();
73 }
74 }
75
76 /// Get an entry by index (0 = most recent).
77 #[must_use]
78 pub fn get(&self, index: usize) -> Option<&RegisterContent> {
79 self.entries.get(index)
80 }
81
82 /// Get an entry by `u8` index (0 = most recent).
83 ///
84 /// This is the typed accessor for `Register::History(u8)`.
85 /// Returns `None` if the index is beyond the current history length.
86 #[must_use]
87 pub fn get_by_index(&self, index: u8) -> Option<&RegisterContent> {
88 self.entries.get(index as usize)
89 }
90
91 /// Get an entry by numbered register character ('0'-'9').
92 ///
93 /// Returns `None` if the character is not a digit or the index
94 /// is beyond the current history length.
95 #[must_use]
96 pub fn get_numbered(&self, n: char) -> Option<RegisterContent> {
97 if n.is_ascii_digit() {
98 let index = (n as u8 - b'0') as usize;
99 self.entries.get(index).cloned()
100 } else {
101 None
102 }
103 }
104
105 /// Get the number of entries currently in the ring.
106 #[must_use]
107 pub fn len(&self) -> usize {
108 self.entries.len()
109 }
110
111 /// Check if the ring is empty.
112 #[must_use]
113 pub fn is_empty(&self) -> bool {
114 self.entries.is_empty()
115 }
116
117 /// Get the maximum capacity of the ring.
118 #[must_use]
119 pub const fn capacity(&self) -> usize {
120 self.capacity
121 }
122
123 /// Iterate over entries in order (most recent first).
124 pub fn iter(&self) -> impl Iterator<Item = &RegisterContent> {
125 self.entries.iter()
126 }
127
128 /// Clear all entries.
129 pub fn clear(&mut self) {
130 self.entries.clear();
131 }
132}
133
134impl Default for HistoryRing {
135 fn default() -> Self {
136 Self::new()
137 }
138}
139
140#[cfg(test)]
141#[path = "tests/history.rs"]
142mod tests;