Skip to main content

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;