1#[derive(Default, Clone, Debug)]
14pub struct Slot {
15 pub text: String,
16 pub linewise: bool,
17}
18
19impl Slot {
20 fn new(text: String, linewise: bool) -> Self {
21 Self { text, linewise }
22 }
23}
24
25#[derive(Default, Debug)]
26pub struct Registers {
27 pub unnamed: Slot,
29 pub yank_zero: Slot,
31 pub delete_ring: [Slot; 9],
33 pub named: [Slot; 26],
35 pub clip: Slot,
41}
42
43impl Registers {
44 pub fn record_yank(&mut self, text: String, linewise: bool, target: Option<char>) {
47 let slot = Slot::new(text, linewise);
48 self.unnamed = slot.clone();
49 self.yank_zero = slot.clone();
50 if let Some(c) = target {
51 self.write_named(c, slot);
52 }
53 }
54
55 pub fn record_delete(&mut self, text: String, linewise: bool, target: Option<char>) {
60 if text.is_empty() {
61 return;
62 }
63 let slot = Slot::new(text, linewise);
64 self.unnamed = slot.clone();
65 for i in (1..9).rev() {
66 self.delete_ring[i] = self.delete_ring[i - 1].clone();
67 }
68 self.delete_ring[0] = slot.clone();
69 if let Some(c) = target {
70 self.write_named(c, slot);
71 }
72 }
73
74 pub fn read(&self, reg: char) -> Option<&Slot> {
77 match reg {
78 '"' => Some(&self.unnamed),
79 '0' => Some(&self.yank_zero),
80 '1'..='9' => Some(&self.delete_ring[(reg as u8 - b'1') as usize]),
81 'a'..='z' => Some(&self.named[(reg as u8 - b'a') as usize]),
82 'A'..='Z' => Some(&self.named[(reg.to_ascii_lowercase() as u8 - b'a') as usize]),
83 '+' | '*' => Some(&self.clip),
84 _ => None,
85 }
86 }
87
88 pub fn set_clipboard(&mut self, text: String, linewise: bool) {
91 self.clip = Slot::new(text, linewise);
92 }
93
94 fn write_named(&mut self, c: char, slot: Slot) {
95 if c.is_ascii_lowercase() {
96 self.named[(c as u8 - b'a') as usize] = slot;
97 } else if c.is_ascii_uppercase() {
98 let idx = (c.to_ascii_lowercase() as u8 - b'a') as usize;
99 let cur = &mut self.named[idx];
100 cur.text.push_str(&slot.text);
101 cur.linewise = slot.linewise || cur.linewise;
102 } else if c == '+' || c == '*' {
103 self.clip = slot;
104 }
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn yank_writes_unnamed_and_zero() {
114 let mut r = Registers::default();
115 r.record_yank("foo".into(), false, None);
116 assert_eq!(r.read('"').unwrap().text, "foo");
117 assert_eq!(r.read('0').unwrap().text, "foo");
118 }
119
120 #[test]
121 fn delete_rotates_ring_and_skips_zero() {
122 let mut r = Registers::default();
123 r.record_yank("kept".into(), false, None);
124 r.record_delete("d1".into(), false, None);
125 r.record_delete("d2".into(), false, None);
126 assert_eq!(r.read('1').unwrap().text, "d2");
128 assert_eq!(r.read('2').unwrap().text, "d1");
129 assert_eq!(r.read('0').unwrap().text, "kept");
131 assert_eq!(r.read('"').unwrap().text, "d2");
133 }
134
135 #[test]
136 fn named_lowercase_overwrites_uppercase_appends() {
137 let mut r = Registers::default();
138 r.record_yank("hello ".into(), false, Some('a'));
139 r.record_yank("world".into(), false, Some('A'));
140 assert_eq!(r.read('a').unwrap().text, "hello world");
141 assert_eq!(r.read('A').unwrap().text, "hello world");
143 }
144
145 #[test]
146 fn empty_delete_is_dropped() {
147 let mut r = Registers::default();
148 r.record_delete("first".into(), false, None);
149 r.record_delete(String::new(), false, None);
150 assert_eq!(r.read('1').unwrap().text, "first");
151 assert!(r.read('2').unwrap().text.is_empty());
152 }
153
154 #[test]
155 fn unknown_selector_returns_none() {
156 let r = Registers::default();
157 assert!(r.read('?').is_none());
158 assert!(r.read('!').is_none());
159 }
160
161 #[test]
162 fn plus_and_star_alias_clipboard_slot() {
163 let mut r = Registers::default();
164 r.set_clipboard("payload".into(), false);
165 assert_eq!(r.read('+').unwrap().text, "payload");
166 assert_eq!(r.read('*').unwrap().text, "payload");
167 }
168
169 #[test]
170 fn yank_to_plus_writes_clipboard_slot() {
171 let mut r = Registers::default();
172 r.record_yank("hi".into(), false, Some('+'));
173 assert_eq!(r.read('+').unwrap().text, "hi");
174 assert_eq!(r.read('"').unwrap().text, "hi");
176 }
177}