use super::{BufferId, Cursor, Position, rope::Rope};
#[derive(Debug, Clone)]
pub struct BufferSnapshot {
pub id: BufferId,
text: Rope,
pub cursor: Cursor,
pub file_path: Option<String>,
pub modified: bool,
}
impl BufferSnapshot {
#[must_use]
pub fn from_buffer(buffer: &super::Buffer, cursor: Cursor) -> Self {
Self {
id: buffer.id(),
text: buffer.clone_rope(),
cursor,
file_path: buffer.file_path().map(String::from),
modified: buffer.is_modified(),
}
}
#[must_use]
pub fn new(
id: BufferId,
lines: &[String],
cursor: Cursor,
file_path: Option<String>,
modified: bool,
) -> Self {
let content = lines.join("\n");
let text = if content.is_empty() {
Rope::new()
} else {
Rope::from_str(&content)
};
Self {
id,
text,
cursor,
file_path,
modified,
}
}
#[must_use]
pub fn line_count(&self) -> usize {
self.text.line_count()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.text.is_empty()
}
#[must_use]
pub fn line(&self, idx: usize) -> Option<&str> {
self.text.line(idx)
}
#[must_use]
pub fn line_len(&self, idx: usize) -> Option<usize> {
self.text.line_len(idx)
}
#[must_use]
pub fn lines(&self) -> Vec<String> {
(0..self.text.line_count())
.filter_map(|i| self.text.line(i).map(String::from))
.collect()
}
#[must_use]
pub fn content(&self) -> String {
self.text.content()
}
#[must_use]
pub fn text_in_range(&self, start: Position, end: Position) -> String {
if self.text.is_empty() {
return String::new();
}
let (start, end) = if start <= end {
(start, end)
} else {
(end, start)
};
let line_count = self.text.line_count();
let start_line = start.line.min(line_count - 1);
let end_line = end.line.min(line_count - 1);
if start_line == end_line {
let line = self.text.line(start_line).unwrap_or("");
let chars: Vec<char> = line.chars().collect();
let start_col = start.column.min(chars.len());
let end_col = end.column.min(chars.len());
return chars[start_col..end_col].iter().collect();
}
let mut result = String::new();
for line_idx in start_line..=end_line {
let line = self.text.line(line_idx).unwrap_or("");
let chars: Vec<char> = line.chars().collect();
if line_idx == start_line {
let start_col = start.column.min(chars.len());
result.extend(&chars[start_col..]);
result.push('\n');
} else if line_idx == end_line {
let end_col = end.column.min(chars.len());
result.extend(&chars[..end_col]);
} else {
result.push_str(line);
result.push('\n');
}
}
result
}
#[must_use]
pub const fn position(&self) -> Position {
self.cursor.position
}
#[must_use]
pub fn is_valid_position(&self, pos: Position) -> bool {
if pos.line >= self.text.line_count() {
return false;
}
self.line(pos.line)
.is_some_and(|line| pos.column <= line.chars().count())
}
}