use std::cmp::{max, min};
pub trait TextBuffer {
fn line_count(&self) -> usize;
fn get_line(&self, line_idx: usize) -> Option<String>;
fn line_len(&self, line_idx: usize) -> usize {
self.get_line(line_idx)
.map(|s| s.chars().count())
.unwrap_or(0)
}
fn all_lines(&self) -> Vec<String>;
fn insert_at(&mut self, row: usize, col: usize, text: &str);
fn delete_at(&mut self, row: usize, col: usize);
fn backspace_at(&mut self, row: usize, col: usize);
}
#[derive(Clone)]
pub struct GapBuffer {
buffer: Vec<char>,
gap_start: usize,
gap_end: usize,
}
impl GapBuffer {
pub fn new() -> Self {
let initial_capacity = 1024;
let buffer = vec!['\0'; initial_capacity];
Self {
buffer,
gap_start: 0,
gap_end: initial_capacity,
}
}
pub fn from_text(text: &str) -> Self {
let chars: Vec<char> = text.chars().collect();
let text_len = chars.len();
let capacity = max(1024, text_len * 2);
let mut buffer = vec!['\0'; capacity];
for (i, &ch) in chars.iter().enumerate() {
buffer[i] = ch;
}
Self {
buffer,
gap_start: text_len,
gap_end: capacity,
}
}
pub fn from_lines(lines: Vec<String>) -> Self {
Self::from_text(&lines.join("\n"))
}
pub fn len(&self) -> usize {
self.buffer.len() - self.gap_size()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn gap_size(&self) -> usize {
self.gap_end - self.gap_start
}
pub fn move_gap_to(&mut self, position: usize) {
let position = min(position, self.len());
if position < self.gap_start {
let move_count = self.gap_start - position;
for i in (0..move_count).rev() {
self.buffer[self.gap_end - 1 - i] = self.buffer[self.gap_start - 1 - i];
}
self.gap_end -= move_count;
self.gap_start -= move_count;
} else if position > self.gap_start {
let move_count = position - self.gap_start;
for i in 0..move_count {
if self.gap_end + i < self.buffer.len() {
self.buffer[self.gap_start + i] = self.buffer[self.gap_end + i];
}
}
self.gap_start += move_count;
self.gap_end += move_count;
}
}
pub fn insert_char(&mut self, position: usize, ch: char) {
self.move_gap_to(position);
if self.gap_start >= self.gap_end {
self.grow_gap();
}
self.buffer[self.gap_start] = ch;
self.gap_start += 1;
}
pub fn insert(&mut self, position: usize, text: &str) {
self.move_gap_to(position);
for ch in text.chars() {
if self.gap_start >= self.gap_end {
self.grow_gap();
}
self.buffer[self.gap_start] = ch;
self.gap_start += 1;
}
}
pub fn delete_backward(&mut self, position: usize) {
if position > 0 {
self.move_gap_to(position);
self.gap_start -= 1;
}
}
pub fn delete_forward(&mut self, position: usize) {
self.move_gap_to(position);
if self.gap_end < self.buffer.len() {
self.gap_end += 1;
}
}
pub fn delete_range(&mut self, start: usize, end: usize) {
if start >= end {
return;
}
let start = min(start, self.len());
let end = min(end, self.len());
self.move_gap_to(start);
let delete_count = end - start;
self.gap_end = min(self.gap_end + delete_count, self.buffer.len());
}
fn grow_gap(&mut self) {
let old_capacity = self.buffer.len();
let new_capacity = old_capacity * 2;
let additional_capacity = new_capacity - old_capacity;
let mut new_buffer = vec!['\0'; new_capacity];
new_buffer[..self.gap_start].copy_from_slice(&self.buffer[..self.gap_start]);
let after_gap_start = self.gap_end;
let after_gap_count = old_capacity - self.gap_end;
let new_gap_end = self.gap_start + self.gap_size() + additional_capacity;
new_buffer[new_gap_end..new_gap_end + after_gap_count]
.copy_from_slice(&self.buffer[after_gap_start..after_gap_start + after_gap_count]);
self.buffer = new_buffer;
self.gap_end = new_gap_end;
}
pub fn to_lines(&self) -> Vec<String> {
let text = self.to_string();
if text.is_empty() {
vec![String::new()]
} else {
let lines: Vec<String> = text.split('\n').map(|s| s.to_string()).collect();
if lines.is_empty() {
vec![String::new()]
} else {
lines
}
}
}
pub fn to_lines_with_endings(&self) -> Vec<String> {
let mut lines = self.to_lines();
let last = lines.len().saturating_sub(1);
for line in lines.iter_mut().take(last) {
line.push('\n');
}
lines
}
pub fn cursor_to_position(&self, row: usize, col: usize) -> usize {
let text = self.to_string();
let mut current_row = 0;
let mut current_col = 0;
let mut char_index = 0;
for ch in text.chars() {
if current_row == row && current_col == col {
return char_index;
}
if ch == '\n' {
if current_row == row {
return char_index; }
current_row += 1;
current_col = 0;
} else {
current_col += 1;
}
char_index += 1;
}
char_index
}
pub fn position_to_cursor(&self, position: usize) -> (usize, usize) {
let text = self.to_string();
let char_count = text.chars().count();
let position = min(position, char_count);
let mut row = 0;
let mut col = 0;
for (char_index, ch) in text.chars().enumerate() {
if char_index >= position {
break;
}
if ch == '\n' {
row += 1;
col = 0;
} else {
col += 1;
}
}
(row, col)
}
}
impl std::fmt::Display for GapBuffer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use std::fmt::Write as _;
for &ch in &self.buffer[..self.gap_start] {
f.write_char(ch)?;
}
for &ch in &self.buffer[self.gap_end..] {
if ch != '\0' {
f.write_char(ch)?;
}
}
Ok(())
}
}
impl Default for GapBuffer {
fn default() -> Self {
Self::new()
}
}
impl TextBuffer for GapBuffer {
fn line_count(&self) -> usize {
let text = self.to_string();
if text.is_empty() {
1
} else {
text.chars().filter(|&c| c == '\n').count() + 1
}
}
fn get_line(&self, line_idx: usize) -> Option<String> {
let lines = self.to_lines();
lines.get(line_idx).cloned()
}
fn all_lines(&self) -> Vec<String> {
self.to_lines()
}
fn line_len(&self, line_idx: usize) -> usize {
let lines = self.to_lines();
lines.get(line_idx).map(|s| s.chars().count()).unwrap_or(0)
}
fn insert_at(&mut self, row: usize, col: usize, text: &str) {
let position = self.cursor_to_position(row, col);
self.insert(position, text);
}
fn delete_at(&mut self, row: usize, col: usize) {
let position = self.cursor_to_position(row, col);
self.delete_forward(position);
}
fn backspace_at(&mut self, row: usize, col: usize) {
let position = self.cursor_to_position(row, col);
if position > 0 {
self.delete_backward(position);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_line_splitting_with_newlines() {
let buffer = GapBuffer::from_text("Hello\nWorld\nTest");
let lines = buffer.to_lines();
assert_eq!(lines.len(), 3);
assert_eq!(lines[0], "Hello");
assert_eq!(lines[1], "World");
assert_eq!(lines[2], "Test");
}
#[test]
fn test_cursor_position_conversion() {
let buffer = GapBuffer::from_text("Hello\nWorld");
assert_eq!(buffer.cursor_to_position(0, 0), 0);
assert_eq!(buffer.cursor_to_position(0, 5), 5);
assert_eq!(buffer.cursor_to_position(1, 0), 6);
assert_eq!(buffer.cursor_to_position(1, 5), 11);
assert_eq!(buffer.position_to_cursor(0), (0, 0));
assert_eq!(buffer.position_to_cursor(5), (0, 5));
assert_eq!(buffer.position_to_cursor(6), (1, 0));
}
#[test]
fn test_newline_insertion() {
let mut buffer = GapBuffer::from_text("HelloWorld");
buffer.insert(5, "\n");
let lines = buffer.to_lines();
assert_eq!(lines.len(), 2);
assert_eq!(lines[0], "Hello");
assert_eq!(lines[1], "World");
assert_eq!(buffer.to_string(), "Hello\nWorld");
}
#[test]
fn test_new_empty_buffer() {
let buffer = GapBuffer::new();
assert_eq!(buffer.len(), 0);
assert_eq!(buffer.to_string(), "");
}
#[test]
fn test_from_text() {
let buffer = GapBuffer::from_text("Hello, World!");
assert_eq!(buffer.len(), 13);
assert_eq!(buffer.to_string(), "Hello, World!");
}
#[test]
fn test_from_lines() {
let lines = vec!["Hello".to_string(), "World".to_string()];
let buffer = GapBuffer::from_lines(lines);
assert_eq!(buffer.to_string(), "Hello\nWorld");
}
#[test]
fn test_insert_char() {
let mut buffer = GapBuffer::from_text("Hello");
buffer.insert_char(5, '!');
assert_eq!(buffer.to_string(), "Hello!");
buffer.insert_char(0, '>');
assert_eq!(buffer.to_string(), ">Hello!");
buffer.insert_char(3, '<');
assert_eq!(buffer.to_string(), ">He<llo!");
}
#[test]
fn test_insert_text_various_positions() {
let mut buffer = GapBuffer::from_text("Hello");
buffer.insert(5, " World");
assert_eq!(buffer.to_string(), "Hello World");
buffer.insert(0, "Say ");
assert_eq!(buffer.to_string(), "Say Hello World");
buffer.insert(9, " Beautiful");
assert_eq!(buffer.to_string(), "Say Hello Beautiful World");
}
#[test]
fn test_insert_multiline() {
let mut buffer = GapBuffer::from_text("Line1");
buffer.insert(5, "\nLine2\nLine3");
assert_eq!(buffer.to_string(), "Line1\nLine2\nLine3");
let lines = buffer.to_lines();
assert_eq!(lines.len(), 3);
assert_eq!(lines[0], "Line1");
assert_eq!(lines[1], "Line2");
assert_eq!(lines[2], "Line3");
}
#[test]
fn test_delete_backward() {
let mut buffer = GapBuffer::from_text("Hello World");
buffer.delete_backward(11);
assert_eq!(buffer.to_string(), "Hello Worl");
buffer.delete_backward(6);
assert_eq!(buffer.to_string(), "HelloWorl");
buffer.delete_backward(1);
assert_eq!(buffer.to_string(), "elloWorl");
}
#[test]
fn test_delete_forward() {
let mut buffer = GapBuffer::from_text("Hello World");
buffer.delete_forward(0);
assert_eq!(buffer.to_string(), "ello World");
buffer.delete_forward(4);
assert_eq!(buffer.to_string(), "elloWorld");
buffer.delete_forward(8);
assert_eq!(buffer.to_string(), "elloWorl");
}
#[test]
fn test_delete_range() {
let mut buffer = GapBuffer::from_text("Hello World");
buffer.delete_range(5, 11);
assert_eq!(buffer.to_string(), "Hello");
let mut buffer = GapBuffer::from_text("Hello World");
buffer.delete_range(0, 6);
assert_eq!(buffer.to_string(), "World");
let mut buffer = GapBuffer::from_text("Hello World");
buffer.delete_range(2, 8);
assert_eq!(buffer.to_string(), "Herld");
}
#[test]
fn test_delete_range_edge_cases() {
let mut buffer = GapBuffer::from_text("Hello");
buffer.delete_range(0, 5);
assert_eq!(buffer.to_string(), "");
let mut buffer = GapBuffer::from_text("Hello");
buffer.delete_range(3, 2);
assert_eq!(buffer.to_string(), "Hello");
}
#[test]
fn test_move_gap_to() {
let mut buffer = GapBuffer::from_text("Hello World");
let initial_text = buffer.to_string();
buffer.move_gap_to(5);
assert_eq!(buffer.to_string(), initial_text);
buffer.move_gap_to(0);
assert_eq!(buffer.to_string(), initial_text);
buffer.move_gap_to(11);
assert_eq!(buffer.to_string(), initial_text);
}
#[test]
fn test_gap_movement_with_operations() {
let mut buffer = GapBuffer::from_text("ABC");
buffer.insert(0, "1");
assert_eq!(buffer.to_string(), "1ABC");
buffer.insert(4, "2");
assert_eq!(buffer.to_string(), "1ABC2");
buffer.insert(2, "X");
assert_eq!(buffer.to_string(), "1AXBC2");
}
#[test]
fn test_cursor_to_position() {
let buffer = GapBuffer::from_text("Line1\nLine2\nLine3");
assert_eq!(buffer.cursor_to_position(0, 0), 0);
assert_eq!(buffer.cursor_to_position(0, 5), 5);
assert_eq!(buffer.cursor_to_position(1, 0), 6);
assert_eq!(buffer.cursor_to_position(1, 5), 11);
assert_eq!(buffer.cursor_to_position(2, 0), 12);
assert_eq!(buffer.cursor_to_position(0, 100), 5);
}
#[test]
fn test_position_to_cursor() {
let buffer = GapBuffer::from_text("Line1\nLine2\nLine3");
assert_eq!(buffer.position_to_cursor(0), (0, 0));
assert_eq!(buffer.position_to_cursor(5), (0, 5));
assert_eq!(buffer.position_to_cursor(6), (1, 0));
assert_eq!(buffer.position_to_cursor(11), (1, 5));
assert_eq!(buffer.position_to_cursor(12), (2, 0));
assert_eq!(buffer.position_to_cursor(100), (2, 5));
}
#[test]
fn test_cursor_position_roundtrip() {
let buffer = GapBuffer::from_text("Hello\nWorld\n!");
for row in 0..3 {
for col in 0..6 {
let pos = buffer.cursor_to_position(row, col);
let (r, c) = buffer.position_to_cursor(pos);
if col <= buffer.line_len(row) {
assert_eq!((r, c), (row, col.min(buffer.line_len(row))));
}
}
}
}
#[test]
fn test_to_lines() {
let buffer = GapBuffer::from_text("Line1\nLine2\nLine3");
let lines = buffer.to_lines();
assert_eq!(lines, vec!["Line1", "Line2", "Line3"]);
let empty_buffer = GapBuffer::new();
let empty_lines = empty_buffer.to_lines();
assert_eq!(empty_lines, vec![""]);
}
#[test]
fn test_to_lines_with_endings() {
let buffer = GapBuffer::from_text("Line1\nLine2\nLine3");
assert_eq!(
buffer.to_lines_with_endings(),
vec!["Line1\n", "Line2\n", "Line3"]
);
let buffer = GapBuffer::from_text("Line1\nLine2\n");
assert_eq!(
buffer.to_lines_with_endings(),
vec!["Line1\n", "Line2\n", ""]
);
let buffer = GapBuffer::from_text("no newline at all");
assert_eq!(buffer.to_lines_with_endings(), vec!["no newline at all"]);
let buffer = GapBuffer::new();
assert_eq!(buffer.to_lines_with_endings(), vec![""]);
}
#[test]
fn test_to_lines_with_endings_reproduces_the_buffer() {
for text in [
"",
"one line",
"Line1\nLine2\nLine3",
"Line1\nLine2\n",
"\n\n\n",
"Hello δΈη\nπ emoji\n",
] {
let buffer = GapBuffer::from_text(text);
let with_endings = buffer.to_lines_with_endings();
assert_eq!(
with_endings.len(),
buffer.to_lines().len(),
"row count diverged for {text:?}"
);
assert_eq!(
with_endings.concat(),
buffer.to_string(),
"round trip failed for {text:?}"
);
}
}
#[test]
fn test_line_count() {
let buffer = GapBuffer::from_text("");
assert_eq!(buffer.line_count(), 1);
let buffer = GapBuffer::from_text("Hello");
assert_eq!(buffer.line_count(), 1);
let buffer = GapBuffer::from_text("Hello\nWorld");
assert_eq!(buffer.line_count(), 2);
let buffer = GapBuffer::from_text("Line1\nLine2\nLine3");
assert_eq!(buffer.line_count(), 3);
let buffer = GapBuffer::from_text("Line1\nLine2\n");
assert_eq!(buffer.line_count(), 3);
}
#[test]
fn test_line_len() {
let buffer = GapBuffer::from_text("Hello\nWorld!\n");
assert_eq!(buffer.line_len(0), 5);
assert_eq!(buffer.line_len(1), 6);
assert_eq!(buffer.line_len(2), 0);
}
#[test]
fn test_insert_at() {
let mut buffer = GapBuffer::from_text("Hello\nWorld");
buffer.insert_at(0, 5, "!");
assert_eq!(buffer.to_string(), "Hello!\nWorld");
buffer.insert_at(1, 5, "!");
assert_eq!(buffer.to_string(), "Hello!\nWorld!");
buffer.insert_at(1, 0, "> ");
assert_eq!(buffer.to_string(), "Hello!\n> World!");
}
#[test]
fn test_delete_at() {
let mut buffer = GapBuffer::from_text("Hello\nWorld");
buffer.delete_at(0, 4);
assert_eq!(buffer.to_string(), "Hell\nWorld");
buffer.delete_at(1, 0);
assert_eq!(buffer.to_string(), "Hell\norld");
}
#[test]
fn test_backspace_at() {
let mut buffer = GapBuffer::from_text("Hello\nWorld");
buffer.backspace_at(0, 5);
assert_eq!(buffer.to_string(), "Hell\nWorld");
buffer.backspace_at(1, 1);
assert_eq!(buffer.to_string(), "Hell\norld");
buffer.backspace_at(1, 0);
assert_eq!(buffer.to_string(), "Hellorld");
}
#[test]
fn test_grow_gap() {
let mut buffer = GapBuffer::from_text("Hi");
let mut long_text = String::new();
for _ in 0..1000 {
long_text.push('x');
}
buffer.insert(2, &long_text);
assert_eq!(buffer.to_string(), format!("Hi{}", long_text));
}
#[test]
fn test_gap_movement_preserves_text() {
let mut buffer = GapBuffer::from_text("The quick brown fox jumps over the lazy dog");
let original = buffer.to_string();
for i in 0..original.len() {
buffer.move_gap_to(i);
assert_eq!(buffer.to_string(), original);
}
buffer.insert(10, "[INSERT]");
assert_eq!(
buffer.to_string(),
"The quick [INSERT]brown fox jumps over the lazy dog"
);
buffer.delete_range(10, 18);
assert_eq!(buffer.to_string(), original);
}
#[test]
fn test_large_buffer_with_gap_movement() {
let mut text = String::new();
for i in 0..100 {
text.push_str(&format!("Line {}\n", i));
}
let mut buffer = GapBuffer::from_text(&text);
buffer.insert(0, "START\n");
buffer.insert(buffer.len(), "\nEND");
buffer.insert(buffer.len() / 2, "\nMIDDLE\n");
let result = buffer.to_string();
assert!(result.starts_with("START\n"));
assert!(result.ends_with("\nEND"));
assert!(result.contains("\nMIDDLE\n"));
}
#[test]
fn test_empty_buffer_operations() {
let mut buffer = GapBuffer::new();
buffer.insert(0, "Hello");
assert_eq!(buffer.to_string(), "Hello");
buffer.delete_range(0, 5);
assert_eq!(buffer.to_string(), "");
buffer.insert(0, "World");
assert_eq!(buffer.to_string(), "World");
buffer.delete_backward(5);
assert_eq!(buffer.to_string(), "Worl");
}
#[test]
fn test_newline_handling() {
let mut buffer = GapBuffer::new();
buffer.insert(0, "Line1");
buffer.insert(5, "\n");
buffer.insert(6, "Line2");
buffer.insert(11, "\n");
buffer.insert(12, "Line3");
let lines = buffer.to_lines();
assert_eq!(lines.len(), 3);
assert_eq!(lines[0], "Line1");
assert_eq!(lines[1], "Line2");
assert_eq!(lines[2], "Line3");
buffer.delete_at(0, 5); assert_eq!(buffer.to_string(), "Line1Line2\nLine3");
buffer.delete_forward(10); assert_eq!(buffer.to_string(), "Line1Line2Line3");
}
#[test]
fn test_stress_random_operations() {
let mut buffer = GapBuffer::from_text("Initial");
for i in 0..50 {
let pos = i % (buffer.len() + 1);
buffer.insert(pos, &format!("{}", i % 10));
if buffer.len() > 10 && i % 3 == 0 {
let del_pos = (i * 7) % buffer.len();
buffer.delete_forward(del_pos);
}
if buffer.len() > 5 && i % 5 == 0 {
let del_pos = (i * 3) % buffer.len();
buffer.delete_backward(del_pos);
}
}
let text = buffer.to_string();
let reconstructed = GapBuffer::from_text(&text);
assert_eq!(reconstructed.to_string(), text);
}
#[test]
fn test_large_text() {
let mut large_text = String::new();
for i in 0..1000 {
large_text.push_str(&format!(
"Line {}: This is a test line with some content.\n",
i
));
}
let buffer = GapBuffer::from_text(&large_text);
assert_eq!(buffer.to_string(), large_text);
let lines = buffer.to_lines();
assert_eq!(lines.len(), 1001);
assert_eq!(buffer.line_count(), 1001);
assert!(buffer.get_line(500).is_some());
}
#[test]
fn test_unicode_characters() {
let mut buffer = GapBuffer::from_text("Hello δΈη");
assert_eq!(buffer.to_string(), "Hello δΈη");
buffer.insert(6, "π ");
assert_eq!(buffer.to_string(), "Hello π δΈη");
buffer.insert_char(0, 'π');
assert_eq!(buffer.to_string(), "πHello π δΈη");
let mut buffer = GapBuffer::from_text("π¨ππͺ");
buffer.delete_forward(0);
assert_eq!(buffer.to_string(), "ππͺ");
}
#[test]
fn test_sequential_edits() {
let mut buffer = GapBuffer::new();
let text = "The quick brown fox";
for ch in text.chars() {
buffer.insert_char(buffer.len(), ch);
}
assert_eq!(buffer.to_string(), text);
buffer.delete_backward(19); buffer.delete_backward(18); buffer.delete_backward(17); buffer.insert(16, "cat");
assert_eq!(buffer.to_string(), "The quick brown cat");
buffer.insert(buffer.len(), " jumped over the lazy dog");
assert_eq!(
buffer.to_string(),
"The quick brown cat jumped over the lazy dog"
);
}
}