gpui_editor/
buffer.rs

1//! Gap buffer implementation for efficient text editing.
2//!
3//! This module provides a gap buffer data structure that efficiently handles
4//! text insertions and deletions at arbitrary positions. The gap buffer maintains
5//! a gap (unused space) at the cursor position, making insertions and deletions
6//! at that position O(1) operations.
7//!
8//! # Design
9//!
10//! The gap buffer uses a single contiguous array with a "gap" of unused space.
11//! When text is inserted or deleted, the gap moves to that position first,
12//! then the operation is performed by adjusting the gap boundaries.
13//!
14//! # Trade-offs
15//!
16//! - Insertions/deletions at cursor position: O(1)
17//! - Moving the gap: O(n) where n is the distance moved
18//! - Memory overhead: The gap size (typically grows as needed)
19//! - Line-based operations: Currently O(n) as they require string conversion
20
21use std::cmp::{max, min};
22
23/// A minimal text buffer trait that supports the features we have so far
24pub trait TextBuffer {
25    /// Get the total number of lines in the buffer
26    fn line_count(&self) -> usize;
27
28    /// Get a specific line by index (0-based)
29    fn get_line(&self, line_idx: usize) -> Option<String>;
30
31    /// Get the length of a specific line in characters
32    fn line_len(&self, line_idx: usize) -> usize {
33        self.get_line(line_idx).map(|s| s.len()).unwrap_or(0)
34    }
35
36    /// Get all lines (for now, while we're simple)
37    fn all_lines(&self) -> Vec<String>;
38
39    /// Insert text at a specific position (row, col)
40    fn insert_at(&mut self, row: usize, col: usize, text: &str);
41
42    /// Delete a character at a specific position (row, col)
43    fn delete_at(&mut self, row: usize, col: usize);
44
45    /// Delete backwards from a specific position (row, col)
46    fn backspace_at(&mut self, row: usize, col: usize);
47}
48
49/// Gap buffer implementation for efficient text editing.
50///
51/// The buffer maintains text as a vector of characters with a gap at the
52/// current editing position. This makes insertions and deletions at the
53/// cursor position very efficient (O(1)), at the cost of having to move
54/// the gap when editing at different positions.
55///
56/// # Example Structure
57///
58/// For text "Hello World" with gap after "Hello":
59/// ```text
60/// ['H', 'e', 'l', 'l', 'o', '\0', '\0', '\0', ' ', 'W', 'o', 'r', 'l', 'd']
61///                           ^gap_start      ^gap_end
62/// ```
63#[derive(Clone)]
64pub struct GapBuffer {
65    /// The buffer containing the text with a gap for efficient insertion
66    buffer: Vec<char>,
67    /// Start position of the gap (inclusive)
68    gap_start: usize,
69    /// End position of the gap (exclusive)
70    gap_end: usize,
71}
72
73impl GapBuffer {
74    /// Create a new empty gap buffer
75    pub fn new() -> Self {
76        let initial_capacity = 1024;
77        let buffer = vec!['\0'; initial_capacity];
78        Self {
79            buffer,
80            gap_start: 0,
81            gap_end: initial_capacity,
82        }
83    }
84
85    /// Create a gap buffer from text
86    pub fn from_text(text: &str) -> Self {
87        let chars: Vec<char> = text.chars().collect();
88        let text_len = chars.len();
89
90        // Add extra capacity for the gap
91        let capacity = max(1024, text_len * 2);
92        let mut buffer = vec!['\0'; capacity];
93
94        // Copy text to the beginning
95        for (i, &ch) in chars.iter().enumerate() {
96            buffer[i] = ch;
97        }
98
99        Self {
100            buffer,
101            gap_start: text_len,
102            gap_end: capacity,
103        }
104    }
105
106    /// Create a gap buffer from lines
107    pub fn from_lines(lines: Vec<String>) -> Self {
108        Self::from_text(&lines.join("\n"))
109    }
110
111    /// Get the length of the content (excluding the gap)
112    pub fn len(&self) -> usize {
113        self.buffer.len() - self.gap_size()
114    }
115
116    /// Get the size of the gap
117    fn gap_size(&self) -> usize {
118        self.gap_end - self.gap_start
119    }
120
121    /// Move the gap to a specific position in the text.
122    ///
123    /// This is the core operation that enables efficient editing. When the gap
124    /// is at the cursor position, insertions and deletions become O(1) operations.
125    ///
126    /// # Complexity
127    /// O(n) where n is the distance between the current gap position and the target position
128    pub fn move_gap_to(&mut self, position: usize) {
129        let position = min(position, self.len());
130
131        if position < self.gap_start {
132            // Move gap backward - move characters from before gap to after gap
133            let move_count = self.gap_start - position;
134
135            // Move characters right-to-left to avoid overwriting
136            for i in (0..move_count).rev() {
137                self.buffer[self.gap_end - 1 - i] = self.buffer[self.gap_start - 1 - i];
138            }
139
140            self.gap_end -= move_count;
141            self.gap_start -= move_count;
142        } else if position > self.gap_start {
143            // Move gap forward - move characters from after gap to before gap
144            let move_count = position - self.gap_start;
145
146            // Move characters left-to-right
147            for i in 0..move_count {
148                if self.gap_end + i < self.buffer.len() {
149                    self.buffer[self.gap_start + i] = self.buffer[self.gap_end + i];
150                }
151            }
152
153            self.gap_start += move_count;
154            self.gap_end += move_count;
155        }
156    }
157
158    /// Insert a character at the specified position.
159    ///
160    /// Moves the gap to the position first, then inserts the character
161    /// by placing it at gap_start and incrementing gap_start.
162    pub fn insert_char(&mut self, position: usize, ch: char) {
163        self.move_gap_to(position);
164        if self.gap_start >= self.gap_end {
165            self.grow_gap();
166        }
167        self.buffer[self.gap_start] = ch;
168        self.gap_start += 1;
169    }
170
171    /// Insert text at the specified position
172    pub fn insert(&mut self, position: usize, text: &str) {
173        self.move_gap_to(position);
174        for ch in text.chars() {
175            if self.gap_start >= self.gap_end {
176                self.grow_gap();
177            }
178            self.buffer[self.gap_start] = ch;
179            self.gap_start += 1;
180        }
181    }
182
183    /// Delete backward from the specified position
184    pub fn delete_backward(&mut self, position: usize) {
185        if position > 0 {
186            self.move_gap_to(position);
187            self.gap_start -= 1;
188        }
189    }
190
191    /// Delete forward from the specified position
192    pub fn delete_forward(&mut self, position: usize) {
193        self.move_gap_to(position);
194        if self.gap_end < self.buffer.len() {
195            self.gap_end += 1;
196        }
197    }
198
199    /// Delete a range of text
200    pub fn delete_range(&mut self, start: usize, end: usize) {
201        if start >= end {
202            return;
203        }
204
205        let start = min(start, self.len());
206        let end = min(end, self.len());
207
208        self.move_gap_to(start);
209        let delete_count = end - start;
210        self.gap_end = min(self.gap_end + delete_count, self.buffer.len());
211    }
212
213    /// Grow the gap when it becomes too small
214    fn grow_gap(&mut self) {
215        let old_capacity = self.buffer.len();
216        let new_capacity = old_capacity * 2;
217        let additional_capacity = new_capacity - old_capacity;
218
219        let mut new_buffer = vec!['\0'; new_capacity];
220
221        // Copy content before the gap
222        for i in 0..self.gap_start {
223            new_buffer[i] = self.buffer[i];
224        }
225
226        // Copy content after the gap
227        let after_gap_start = self.gap_end;
228        let after_gap_count = old_capacity - self.gap_end;
229        let new_gap_end = self.gap_start + self.gap_size() + additional_capacity;
230
231        for i in 0..after_gap_count {
232            new_buffer[new_gap_end + i] = self.buffer[after_gap_start + i];
233        }
234
235        self.buffer = new_buffer;
236        self.gap_end = new_gap_end;
237    }
238
239    /// Convert the buffer to a string.
240    ///
241    /// Reconstructs the text by concatenating the content before the gap
242    /// with the content after the gap, skipping the gap itself.
243    ///
244    /// # Complexity
245    /// O(n) where n is the length of the text
246    pub fn to_string(&self) -> String {
247        let mut result = String::with_capacity(self.len());
248
249        // Add content before the gap
250        for i in 0..self.gap_start {
251            result.push(self.buffer[i]);
252        }
253
254        // Add content after the gap
255        for i in self.gap_end..self.buffer.len() {
256            let ch = self.buffer[i];
257            if ch != '\0' {
258                result.push(ch);
259            }
260        }
261
262        result
263    }
264
265    /// Convert the buffer to lines
266    pub fn to_lines(&self) -> Vec<String> {
267        let text = self.to_string();
268        if text.is_empty() {
269            vec![String::new()]
270        } else {
271            let lines: Vec<String> = text.split('\n').map(|s| s.to_string()).collect();
272            if lines.is_empty() {
273                vec![String::new()]
274            } else {
275                lines
276            }
277        }
278    }
279
280    /// Convert cursor position (row, col) to buffer position.
281    ///
282    /// Translates a 2D cursor position (line and column) to a linear
283    /// position in the buffer. This is used to map editor cursor positions
284    /// to buffer positions for editing operations.
285    ///
286    /// # Returns
287    /// The character index in the buffer corresponding to the cursor position
288    pub fn cursor_to_position(&self, row: usize, col: usize) -> usize {
289        let text = self.to_string();
290        let mut current_row = 0;
291        let mut current_col = 0;
292
293        for (i, ch) in text.char_indices() {
294            if current_row == row && current_col == col {
295                return i;
296            }
297
298            if ch == '\n' {
299                if current_row == row {
300                    return i; // Return position at end of line if col is beyond line end
301                }
302                current_row += 1;
303                current_col = 0;
304            } else {
305                current_col += 1;
306            }
307        }
308
309        // Return end of text if position is beyond
310        text.len()
311    }
312
313    /// Convert buffer position to cursor position (row, col).
314    ///
315    /// Translates a linear buffer position to a 2D cursor position
316    /// (line and column). This is the inverse of `cursor_to_position`.
317    ///
318    /// # Returns
319    /// A tuple of (row, column) representing the cursor position
320    pub fn position_to_cursor(&self, position: usize) -> (usize, usize) {
321        let text = self.to_string();
322        let position = min(position, text.len());
323
324        let mut row = 0;
325        let mut col = 0;
326
327        for (i, ch) in text.char_indices() {
328            if i >= position {
329                break;
330            }
331            if ch == '\n' {
332                row += 1;
333                col = 0;
334            } else {
335                col += 1;
336            }
337        }
338
339        (row, col)
340    }
341}
342
343impl Default for GapBuffer {
344    fn default() -> Self {
345        Self::new()
346    }
347}
348
349impl TextBuffer for GapBuffer {
350    fn line_count(&self) -> usize {
351        let text = self.to_string();
352        if text.is_empty() {
353            1
354        } else {
355            text.chars().filter(|&c| c == '\n').count() + 1
356        }
357    }
358
359    fn get_line(&self, line_idx: usize) -> Option<String> {
360        let lines = self.to_lines();
361        lines.get(line_idx).cloned()
362    }
363
364    fn all_lines(&self) -> Vec<String> {
365        self.to_lines()
366    }
367
368    fn line_len(&self, line_idx: usize) -> usize {
369        let lines = self.to_lines();
370        lines.get(line_idx).map(|s| s.len()).unwrap_or(0)
371    }
372
373    fn insert_at(&mut self, row: usize, col: usize, text: &str) {
374        let position = self.cursor_to_position(row, col);
375        self.insert(position, text);
376    }
377
378    fn delete_at(&mut self, row: usize, col: usize) {
379        let position = self.cursor_to_position(row, col);
380        self.delete_forward(position);
381    }
382
383    fn backspace_at(&mut self, row: usize, col: usize) {
384        let position = self.cursor_to_position(row, col);
385        if position > 0 {
386            self.delete_backward(position);
387        }
388    }
389}
390
391#[cfg(test)]
392mod tests {
393    use super::*;
394
395    #[test]
396    fn test_line_splitting_with_newlines() {
397        let buffer = GapBuffer::from_text("Hello\nWorld\nTest");
398        let lines = buffer.to_lines();
399
400        assert_eq!(lines.len(), 3);
401        assert_eq!(lines[0], "Hello");
402        assert_eq!(lines[1], "World");
403        assert_eq!(lines[2], "Test");
404    }
405
406    #[test]
407    fn test_cursor_position_conversion() {
408        let buffer = GapBuffer::from_text("Hello\nWorld");
409
410        // Test cursor to position
411        assert_eq!(buffer.cursor_to_position(0, 0), 0);
412        assert_eq!(buffer.cursor_to_position(0, 5), 5);
413        assert_eq!(buffer.cursor_to_position(1, 0), 6);
414        assert_eq!(buffer.cursor_to_position(1, 5), 11);
415
416        // Test position to cursor
417        assert_eq!(buffer.position_to_cursor(0), (0, 0));
418        assert_eq!(buffer.position_to_cursor(5), (0, 5));
419        assert_eq!(buffer.position_to_cursor(6), (1, 0));
420    }
421
422    #[test]
423    fn test_newline_insertion() {
424        let mut buffer = GapBuffer::from_text("HelloWorld");
425        buffer.insert(5, "\n");
426
427        let lines = buffer.to_lines();
428        assert_eq!(lines.len(), 2);
429        assert_eq!(lines[0], "Hello");
430        assert_eq!(lines[1], "World");
431
432        assert_eq!(buffer.to_string(), "Hello\nWorld");
433    }
434
435    #[test]
436    fn test_new_empty_buffer() {
437        let buffer = GapBuffer::new();
438        assert_eq!(buffer.len(), 0);
439        assert_eq!(buffer.to_string(), "");
440    }
441
442    #[test]
443    fn test_from_text() {
444        let buffer = GapBuffer::from_text("Hello, World!");
445        assert_eq!(buffer.len(), 13);
446        assert_eq!(buffer.to_string(), "Hello, World!");
447    }
448
449    #[test]
450    fn test_from_lines() {
451        let lines = vec!["Hello".to_string(), "World".to_string()];
452        let buffer = GapBuffer::from_lines(lines);
453        assert_eq!(buffer.to_string(), "Hello\nWorld");
454    }
455
456    #[test]
457    fn test_insert_char() {
458        let mut buffer = GapBuffer::from_text("Hello");
459        buffer.insert_char(5, '!');
460        assert_eq!(buffer.to_string(), "Hello!");
461
462        buffer.insert_char(0, '>');
463        assert_eq!(buffer.to_string(), ">Hello!");
464
465        buffer.insert_char(3, '<');
466        assert_eq!(buffer.to_string(), ">He<llo!");
467    }
468
469    #[test]
470    fn test_insert_text_various_positions() {
471        let mut buffer = GapBuffer::from_text("Hello");
472
473        // Insert at end
474        buffer.insert(5, " World");
475        assert_eq!(buffer.to_string(), "Hello World");
476
477        // Insert at beginning
478        buffer.insert(0, "Say ");
479        assert_eq!(buffer.to_string(), "Say Hello World");
480
481        // Insert in middle
482        buffer.insert(9, " Beautiful");
483        assert_eq!(buffer.to_string(), "Say Hello Beautiful World");
484    }
485
486    #[test]
487    fn test_insert_multiline() {
488        let mut buffer = GapBuffer::from_text("Line1");
489        buffer.insert(5, "\nLine2\nLine3");
490        assert_eq!(buffer.to_string(), "Line1\nLine2\nLine3");
491
492        let lines = buffer.to_lines();
493        assert_eq!(lines.len(), 3);
494        assert_eq!(lines[0], "Line1");
495        assert_eq!(lines[1], "Line2");
496        assert_eq!(lines[2], "Line3");
497    }
498
499    #[test]
500    fn test_delete_backward() {
501        let mut buffer = GapBuffer::from_text("Hello World");
502
503        buffer.delete_backward(11);
504        assert_eq!(buffer.to_string(), "Hello Worl");
505
506        buffer.delete_backward(6);
507        assert_eq!(buffer.to_string(), "HelloWorl");
508
509        buffer.delete_backward(1);
510        assert_eq!(buffer.to_string(), "elloWorl");
511    }
512
513    #[test]
514    fn test_delete_forward() {
515        let mut buffer = GapBuffer::from_text("Hello World");
516
517        buffer.delete_forward(0);
518        assert_eq!(buffer.to_string(), "ello World");
519
520        buffer.delete_forward(4);
521        assert_eq!(buffer.to_string(), "elloWorld");
522
523        buffer.delete_forward(8);
524        assert_eq!(buffer.to_string(), "elloWorl");
525    }
526
527    #[test]
528    fn test_delete_range() {
529        let mut buffer = GapBuffer::from_text("Hello World");
530
531        buffer.delete_range(5, 11);
532        assert_eq!(buffer.to_string(), "Hello");
533
534        let mut buffer = GapBuffer::from_text("Hello World");
535        buffer.delete_range(0, 6);
536        assert_eq!(buffer.to_string(), "World");
537
538        let mut buffer = GapBuffer::from_text("Hello World");
539        buffer.delete_range(2, 8);
540        assert_eq!(buffer.to_string(), "Herld");
541    }
542
543    #[test]
544    fn test_delete_range_edge_cases() {
545        let mut buffer = GapBuffer::from_text("Hello");
546
547        // Delete everything
548        buffer.delete_range(0, 5);
549        assert_eq!(buffer.to_string(), "");
550
551        // Delete with inverted range (should do nothing)
552        let mut buffer = GapBuffer::from_text("Hello");
553        buffer.delete_range(3, 2);
554        assert_eq!(buffer.to_string(), "Hello");
555    }
556
557    #[test]
558    fn test_move_gap_to() {
559        let mut buffer = GapBuffer::from_text("Hello World");
560        let initial_text = buffer.to_string();
561
562        // Moving gap shouldn't change the text
563        buffer.move_gap_to(5);
564        assert_eq!(buffer.to_string(), initial_text);
565
566        buffer.move_gap_to(0);
567        assert_eq!(buffer.to_string(), initial_text);
568
569        buffer.move_gap_to(11);
570        assert_eq!(buffer.to_string(), initial_text);
571    }
572
573    #[test]
574    fn test_gap_movement_with_operations() {
575        let mut buffer = GapBuffer::from_text("ABC");
576
577        // Insert at beginning
578        buffer.insert(0, "1");
579        assert_eq!(buffer.to_string(), "1ABC");
580
581        // Insert at end
582        buffer.insert(4, "2");
583        assert_eq!(buffer.to_string(), "1ABC2");
584
585        // Insert in middle
586        buffer.insert(2, "X");
587        assert_eq!(buffer.to_string(), "1AXBC2");
588    }
589
590    #[test]
591    fn test_cursor_to_position() {
592        let buffer = GapBuffer::from_text("Line1\nLine2\nLine3");
593
594        assert_eq!(buffer.cursor_to_position(0, 0), 0);
595        assert_eq!(buffer.cursor_to_position(0, 5), 5);
596        assert_eq!(buffer.cursor_to_position(1, 0), 6);
597        assert_eq!(buffer.cursor_to_position(1, 5), 11);
598        assert_eq!(buffer.cursor_to_position(2, 0), 12);
599
600        // Out of bounds column
601        assert_eq!(buffer.cursor_to_position(0, 100), 5);
602    }
603
604    #[test]
605    fn test_position_to_cursor() {
606        let buffer = GapBuffer::from_text("Line1\nLine2\nLine3");
607
608        assert_eq!(buffer.position_to_cursor(0), (0, 0));
609        assert_eq!(buffer.position_to_cursor(5), (0, 5));
610        assert_eq!(buffer.position_to_cursor(6), (1, 0));
611        assert_eq!(buffer.position_to_cursor(11), (1, 5));
612        assert_eq!(buffer.position_to_cursor(12), (2, 0));
613
614        // Out of bounds position
615        assert_eq!(buffer.position_to_cursor(100), (2, 5));
616    }
617
618    #[test]
619    fn test_cursor_position_roundtrip() {
620        let buffer = GapBuffer::from_text("Hello\nWorld\n!");
621
622        for row in 0..3 {
623            for col in 0..6 {
624                let pos = buffer.cursor_to_position(row, col);
625                let (r, c) = buffer.position_to_cursor(pos);
626                if col <= buffer.line_len(row) {
627                    assert_eq!((r, c), (row, col.min(buffer.line_len(row))));
628                }
629            }
630        }
631    }
632
633    #[test]
634    fn test_to_lines() {
635        let buffer = GapBuffer::from_text("Line1\nLine2\nLine3");
636        let lines = buffer.to_lines();
637        assert_eq!(lines, vec!["Line1", "Line2", "Line3"]);
638
639        let empty_buffer = GapBuffer::new();
640        let empty_lines = empty_buffer.to_lines();
641        assert_eq!(empty_lines, vec![""]);
642    }
643
644    #[test]
645    fn test_line_count() {
646        let buffer = GapBuffer::from_text("");
647        assert_eq!(buffer.line_count(), 1);
648
649        let buffer = GapBuffer::from_text("Hello");
650        assert_eq!(buffer.line_count(), 1);
651
652        let buffer = GapBuffer::from_text("Hello\nWorld");
653        assert_eq!(buffer.line_count(), 2);
654
655        let buffer = GapBuffer::from_text("Line1\nLine2\nLine3");
656        assert_eq!(buffer.line_count(), 3);
657
658        let buffer = GapBuffer::from_text("Line1\nLine2\n");
659        assert_eq!(buffer.line_count(), 3);
660    }
661
662    #[test]
663    fn test_line_len() {
664        let buffer = GapBuffer::from_text("Hello\nWorld!\n");
665        assert_eq!(buffer.line_len(0), 5);
666        assert_eq!(buffer.line_len(1), 6);
667        assert_eq!(buffer.line_len(2), 0);
668    }
669
670    #[test]
671    fn test_insert_at() {
672        let mut buffer = GapBuffer::from_text("Hello\nWorld");
673
674        buffer.insert_at(0, 5, "!");
675        assert_eq!(buffer.to_string(), "Hello!\nWorld");
676
677        buffer.insert_at(1, 5, "!");
678        assert_eq!(buffer.to_string(), "Hello!\nWorld!");
679
680        buffer.insert_at(1, 0, "> ");
681        assert_eq!(buffer.to_string(), "Hello!\n> World!");
682    }
683
684    #[test]
685    fn test_delete_at() {
686        let mut buffer = GapBuffer::from_text("Hello\nWorld");
687
688        buffer.delete_at(0, 4);
689        assert_eq!(buffer.to_string(), "Hell\nWorld");
690
691        buffer.delete_at(1, 0);
692        assert_eq!(buffer.to_string(), "Hell\norld");
693    }
694
695    #[test]
696    fn test_backspace_at() {
697        let mut buffer = GapBuffer::from_text("Hello\nWorld");
698
699        buffer.backspace_at(0, 5);
700        assert_eq!(buffer.to_string(), "Hell\nWorld");
701
702        buffer.backspace_at(1, 1);
703        assert_eq!(buffer.to_string(), "Hell\norld");
704
705        // Backspace at beginning of line merges with previous
706        buffer.backspace_at(1, 0);
707        assert_eq!(buffer.to_string(), "Hellorld");
708    }
709
710    #[test]
711    fn test_grow_gap() {
712        let mut buffer = GapBuffer::from_text("Hi");
713        let mut long_text = String::new();
714        for _ in 0..1000 {
715            long_text.push('x');
716        }
717        buffer.insert(2, &long_text);
718        assert_eq!(buffer.to_string(), format!("Hi{}", long_text));
719    }
720
721    #[test]
722    fn test_gap_movement_preserves_text() {
723        let mut buffer = GapBuffer::from_text("The quick brown fox jumps over the lazy dog");
724        let original = buffer.to_string();
725
726        // Move gap to various positions
727        for i in 0..original.len() {
728            buffer.move_gap_to(i);
729            assert_eq!(buffer.to_string(), original);
730        }
731
732        // Perform operations at different positions
733        buffer.insert(10, "[INSERT]");
734        assert_eq!(
735            buffer.to_string(),
736            "The quick [INSERT]brown fox jumps over the lazy dog"
737        );
738
739        buffer.delete_range(10, 18);
740        assert_eq!(buffer.to_string(), original);
741    }
742
743    #[test]
744    fn test_large_buffer_with_gap_movement() {
745        let mut text = String::new();
746        for i in 0..100 {
747            text.push_str(&format!("Line {}\n", i));
748        }
749
750        let mut buffer = GapBuffer::from_text(&text);
751
752        // Insert at various positions
753        buffer.insert(0, "START\n");
754        buffer.insert(buffer.len(), "\nEND");
755        buffer.insert(buffer.len() / 2, "\nMIDDLE\n");
756
757        let result = buffer.to_string();
758        assert!(result.starts_with("START\n"));
759        assert!(result.ends_with("\nEND"));
760        assert!(result.contains("\nMIDDLE\n"));
761    }
762
763    #[test]
764    fn test_empty_buffer_operations() {
765        let mut buffer = GapBuffer::new();
766
767        buffer.insert(0, "Hello");
768        assert_eq!(buffer.to_string(), "Hello");
769
770        buffer.delete_range(0, 5);
771        assert_eq!(buffer.to_string(), "");
772
773        buffer.insert(0, "World");
774        assert_eq!(buffer.to_string(), "World");
775
776        buffer.delete_backward(5);
777        assert_eq!(buffer.to_string(), "Worl");
778    }
779
780    #[test]
781    fn test_newline_handling() {
782        let mut buffer = GapBuffer::new();
783
784        buffer.insert(0, "Line1");
785        buffer.insert(5, "\n");
786        buffer.insert(6, "Line2");
787        buffer.insert(11, "\n");
788        buffer.insert(12, "Line3");
789
790        let lines = buffer.to_lines();
791        assert_eq!(lines.len(), 3);
792        assert_eq!(lines[0], "Line1");
793        assert_eq!(lines[1], "Line2");
794        assert_eq!(lines[2], "Line3");
795
796        // Test deleting newlines
797        buffer.delete_at(0, 5); // Delete newline after Line1
798        assert_eq!(buffer.to_string(), "Line1Line2\nLine3");
799
800        buffer.delete_forward(10); // Delete newline before Line3
801        assert_eq!(buffer.to_string(), "Line1Line2Line3");
802    }
803
804    #[test]
805    fn test_stress_random_operations() {
806        let mut buffer = GapBuffer::from_text("Initial");
807
808        // Perform many random operations
809        for i in 0..50 {
810            let pos = i % (buffer.len() + 1);
811            buffer.insert(pos, &format!("{}", i % 10));
812
813            if buffer.len() > 10 && i % 3 == 0 {
814                let del_pos = (i * 7) % buffer.len();
815                buffer.delete_forward(del_pos);
816            }
817
818            if buffer.len() > 5 && i % 5 == 0 {
819                let del_pos = (i * 3) % buffer.len();
820                buffer.delete_backward(del_pos);
821            }
822        }
823
824        // Verify buffer integrity
825        let text = buffer.to_string();
826        let reconstructed = GapBuffer::from_text(&text);
827        assert_eq!(reconstructed.to_string(), text);
828    }
829
830    #[test]
831    fn test_large_text() {
832        let mut large_text = String::new();
833        for i in 0..1000 {
834            large_text.push_str(&format!(
835                "Line {}: This is a test line with some content.\n",
836                i
837            ));
838        }
839
840        let buffer = GapBuffer::from_text(&large_text);
841        assert_eq!(buffer.to_string(), large_text);
842
843        let lines = buffer.to_lines();
844        assert_eq!(lines.len(), 1001); // 1000 lines + 1 empty line at end
845
846        // Verify line operations
847        assert_eq!(buffer.line_count(), 1001);
848        assert!(buffer.get_line(500).is_some());
849    }
850
851    #[test]
852    fn test_unicode_characters() {
853        let mut buffer = GapBuffer::from_text("Hello δΈ–η•Œ");
854        assert_eq!(buffer.to_string(), "Hello δΈ–η•Œ");
855
856        buffer.insert(6, "🌍 ");
857        assert_eq!(buffer.to_string(), "Hello 🌍 δΈ–η•Œ");
858
859        buffer.insert_char(0, 'πŸ‘‹');
860        assert_eq!(buffer.to_string(), "πŸ‘‹Hello 🌍 δΈ–η•Œ");
861
862        // Test with emoji and complex unicode
863        let mut buffer = GapBuffer::from_text("🎨🎭πŸŽͺ");
864        buffer.delete_forward(0);
865        assert_eq!(buffer.to_string(), "🎭πŸŽͺ");
866    }
867
868    #[test]
869    fn test_sequential_edits() {
870        let mut buffer = GapBuffer::new();
871
872        // Simulate typing
873        let text = "The quick brown fox";
874        for ch in text.chars() {
875            buffer.insert_char(buffer.len(), ch);
876        }
877        assert_eq!(buffer.to_string(), text);
878
879        // Simulate corrections
880        buffer.delete_backward(19); // Delete 'x'
881        buffer.delete_backward(18); // Delete 'o'
882        buffer.delete_backward(17); // Delete 'f'
883        buffer.insert(16, "cat");
884        assert_eq!(buffer.to_string(), "The quick brown cat");
885
886        // Add more text
887        buffer.insert(buffer.len(), " jumped over the lazy dog");
888        assert_eq!(
889            buffer.to_string(),
890            "The quick brown cat jumped over the lazy dog"
891        );
892    }
893}