miv_editor/app/editor/
gap_buffer.rs

1use 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        // If delete would mean tail is out of bounds we need to panic
66        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 that we are only moving the gap within the data
83        // of our buffer excluding the gap.
84        assert!(pos <= self.data_length());
85
86        let position_delta: isize = pos as isize - self.gap_start as isize;
87        match position_delta {
88            // Moving the "cursor" (gap) right
89            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            // Moving the "cursor" (gap) left
98            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            // Moving the "cursor" to where it already is (noop)
105            _ => {}
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    // More or less a smoke test to make sure we can create
180    #[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    // Check happy path movement logic
200    // I.e. moving to a valid position
201    #[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    // Check unhappy path movement logic
219    // I.e. moving to a position outside the data
220    #[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    // Check insertion
228    #[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 gap changed as expected, length of addition was 3
236        assert_eq!(gb.gap_length, 2);
237        assert_eq!(gb.gap_start, 14);
238        // Make sure buffer stays the same length
239        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 gap changed as expected, length of addition was 4
251        assert_eq!(gb.gap_length, 1);
252        assert_eq!(gb.gap_start, 4);
253        // Make sure buffer stays the same length
254        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        // Make sure buffer stays the same length
268        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}