miv_editor/app/editor/
gap_buffer.rs1use std::{cmp::max, fmt, str};
2
3pub struct GapBuffer {
4 pub buffer: Vec<char>,
5 pub gap_start: usize,
6 pub gap_length: usize,
7}
8
9impl GapBuffer {
10 pub fn with_data(data: &str) -> Self {
11 let mut buffer: Vec<char> = data.chars().collect();
12 let target_capacity = max(
13 if buffer.len().is_power_of_two() {
14 (buffer.len() + 1).next_power_of_two()
15 } else {
16 buffer.len().next_power_of_two()
17 },
18 16,
19 );
20
21 let gap_size = target_capacity - buffer.len();
22 let gap_start = buffer.len();
23
24 buffer.append(&mut vec!['_'; gap_size]);
25
26 let gap_length = buffer.len() - data.len();
27
28 let mut gb = Self {
29 buffer,
30 gap_start,
31 gap_length,
32 };
33 gb.move_gap(0);
34 gb
35 }
36
37 pub fn get_text_as_chars(&self) -> Vec<char> {
38 let left = &self.buffer[..self.gap_start];
39 let right = &self.buffer[self.tail_start()..];
40 [left, right].concat()
41 }
42
43 pub fn get_text_as_bytes(&self) -> Vec<u8> {
44 let left = &self.buffer[..self.gap_start];
45 let right = &self.buffer[self.tail_start()..];
46 [left, right]
47 .concat()
48 .into_iter()
49 .collect::<String>()
50 .into_bytes()
51 }
52
53 pub fn get_text_as_string(&self) -> String {
54 let left = &self.buffer[..self.gap_start];
55 let right = &self.buffer[self.tail_start()..];
56 [left, right].concat().into_iter().collect::<String>()
57 }
58
59 pub fn insert_at(&mut self, data: &str, at: usize) {
60 self.move_gap(at);
61 self.insert(data)
62 }
63
64 pub fn delete_at(&mut self, num_to_delete: usize, at: usize) {
65 let new_tail_end = self.gap_length + num_to_delete + at;
67 assert!(new_tail_end <= self.buffer.len());
68
69 self.move_gap(at);
70 self.gap_length += num_to_delete;
71 }
72
73 pub fn get_at(&self, at: usize) -> char {
74 if at < self.gap_start {
75 self.buffer[at]
76 } else {
77 self.buffer[self.tail_start() + at - self.gap_start]
78 }
79 }
80
81 fn move_gap(&mut self, pos: usize) {
82 assert!(pos <= self.data_length());
85
86 let position_delta: isize = pos as isize - self.gap_start as isize;
87 match position_delta {
88 num if num > 0 => {
90 let chunk_to_bump =
91 self.tail_start()..(self.tail_start() + position_delta.unsigned_abs());
92
93 self.buffer.copy_within(chunk_to_bump, self.gap_start);
94
95 self.gap_start = pos;
96 }
97 num if num < 0 => {
99 let chunk_to_bump = pos..self.gap_start;
100 let new_tail_start = self.tail_start() - position_delta.unsigned_abs();
101 self.buffer.copy_within(chunk_to_bump, new_tail_start);
102 self.gap_start = pos;
103 }
104 _ => {}
106 }
107 }
108
109 fn insert(&mut self, data: &str) {
110 let slice_to_insert: Vec<char> = data.chars().collect();
111
112 if slice_to_insert.len() > self.gap_length {
113 self.grow(slice_to_insert.len() + 1024);
114 }
115
116 let body_slice: &mut [char] =
117 &mut self.buffer[self.gap_start..(self.gap_start + slice_to_insert.len())];
118 body_slice.copy_from_slice(&slice_to_insert);
119 self.gap_start += slice_to_insert.len();
120 self.gap_length -= slice_to_insert.len();
121 }
122
123 fn grow(&mut self, amount: usize) {
124 let tail_range = self.tail_start()..self.buffer.len();
125 let tail_size = tail_range.len();
126 self.buffer.resize(self.buffer.len() + amount, '_');
127 let new_tail_start = self.buffer.len() - tail_size;
128 self.buffer.copy_within(tail_range, new_tail_start);
129 self.gap_length += amount;
130 }
131
132 fn tail_start(&self) -> usize {
133 self.gap_start + self.gap_length
134 }
135
136 pub fn data_length(&self) -> usize {
137 self.buffer.len() - self.gap_length
138 }
139}
140
141impl fmt::Debug for GapBuffer {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 writeln!(f, " ")?;
144 writeln!(f, " Gap Start: {}", self.gap_start)?;
145 writeln!(f, " Gap Length: {}", self.gap_length)?;
146 writeln!(f, " Tail Start: {}", self.tail_start())?;
147 writeln!(f, " Data Length: {}", self.data_length())?;
148 writeln!(f, " Buffer Length: {}", self.buffer.len())?;
149 let display_buf: Vec<char> = self
150 .buffer
151 .clone()
152 .into_iter()
153 .enumerate()
154 .filter_map(|(idx, el)| {
155 if idx < self.gap_start || idx > self.tail_start() - 1 {
156 return Some(el);
157 } else if idx == self.gap_start {
158 return Some('[');
159 } else if idx == self.tail_start() - 1 {
160 return Some(']');
161 } else if idx == self.tail_start() - 2 || idx == self.gap_start + 1 {
162 return Some('~');
163 }
164 None
165 })
166 .collect();
167 writeln!(
168 f,
169 " Data: {}",
170 display_buf.iter().collect::<String>()
171 )
172 }
173}
174
175#[cfg(test)]
176mod miv_gap_buffer_tests {
177 use super::*;
178
179 #[test]
181 fn creation() {
182 let gb = GapBuffer::with_data("Hello World");
183 assert_eq!(&gb.get_text_as_string(), "Hello World");
184 assert_eq!(gb.gap_length, 5);
185 assert_eq!(gb.gap_start, 0);
186 }
187
188 #[test]
189 fn creation_multiline() {
190 let multiline = r"1
1912
1923";
193 let gb = GapBuffer::with_data(multiline);
194 assert_eq!(&gb.get_text_as_string(), "1\n2\n3");
195 assert_eq!(gb.gap_length, 11);
196 assert_eq!(gb.gap_start, 0);
197 }
198
199 #[test]
202 fn move_gap_within_bounds() {
203 let mut gb = GapBuffer::with_data("Hello World");
204 assert_eq!(&gb.get_text_as_string(), "Hello World");
205 assert_eq!(gb.gap_length, 5);
206 assert_eq!(gb.gap_start, 0);
207 dbg!(&gb.tail_start());
208 gb.move_gap(11);
209 assert_eq!(&gb.get_text_as_string(), "Hello World");
210 assert_eq!(gb.gap_length, 5);
211 assert_eq!(gb.gap_start, 11);
212 gb.move_gap(5);
213 assert_eq!(&gb.get_text_as_string(), "Hello World");
214 assert_eq!(gb.gap_length, 5);
215 assert_eq!(gb.gap_start, 5);
216 }
217
218 #[test]
221 #[should_panic]
222 fn move_gap_out_of_bounds() {
223 let mut gb = GapBuffer::with_data("Hello World");
224 gb.move_gap(12);
225 }
226
227 #[test]
229 fn insert_multi_character_string_smaller_than_gap_at_end() {
230 let mut gb = GapBuffer::with_data("Hello World");
231 let original_buffer_length = gb.buffer.len();
232 gb.move_gap(gb.data_length());
233 gb.insert("!!!");
234 assert_eq!(&gb.get_text_as_string(), "Hello World!!!");
235 assert_eq!(gb.gap_length, 2);
237 assert_eq!(gb.gap_start, 14);
238 assert_eq!(gb.buffer.len(), original_buffer_length);
240 }
241
242 #[test]
243 fn insert_multi_character_string_smaller_than_gap_at_start() {
244 let mut gb = GapBuffer::with_data("Hello World");
245 let original_buffer_length = gb.buffer.len();
246 dbg!(&gb);
247 gb.move_gap(0);
248 gb.insert("Hi, ");
249 assert_eq!(&gb.get_text_as_string(), "Hi, Hello World");
250 assert_eq!(gb.gap_length, 1);
252 assert_eq!(gb.gap_start, 4);
253 assert_eq!(gb.buffer.len(), original_buffer_length);
255 }
256
257 #[test]
258 fn insert_multi_character_string_larger_than_gap_at_start() {
259 let mut gb = GapBuffer::with_data("Hello World");
260 let original_buffer_length = gb.buffer.len();
261 let str_to_insert = "Hi there, ";
262 gb.move_gap(0);
263 gb.insert(str_to_insert);
264 assert_eq!(&gb.get_text_as_string(), "Hi there, Hello World");
265 assert_eq!(gb.gap_length, 1029);
266 assert_eq!(gb.gap_start, 10);
267 assert_eq!(
269 gb.buffer.len(),
270 original_buffer_length + str_to_insert.len() + 1024
271 );
272 }
273
274 #[test]
275 fn delete_5_at_start() {
276 let mut gb = GapBuffer::with_data("Hello World");
277 gb.delete_at(5, 0);
278 assert_eq!(&gb.get_text_as_string(), " World");
279 assert_eq!(gb.gap_length, 10);
280 assert_eq!(gb.gap_start, 0);
281 }
282
283 #[test]
284 #[should_panic]
285 fn attempt_delete_5_after_data() {
286 let mut gb = GapBuffer::with_data("Hello World");
287 gb.delete_at(5, 11);
288 assert_eq!(&gb.get_text_as_string(), " World");
289 assert_eq!(gb.gap_length, 10);
290 assert_eq!(gb.gap_start, 0);
291 }
292}