use std::borrow::Cow;
#[derive(Debug, Clone)]
pub(super) struct Line {
start: usize,
end: usize, has_newline: bool, }
impl Line {
fn new(start: usize, end: usize, has_newline: bool) -> Self {
Self {
start,
end,
has_newline,
}
}
fn empty() -> Self {
Self::new(0, 0, false)
}
pub(super) fn start(&self) -> usize {
self.start
}
pub(super) fn end(&self) -> usize {
self.end
}
pub(super) fn has_newline(&self) -> bool {
self.has_newline
}
fn as_str<'a>(&self, buffer: &'a str) -> &'a str {
&buffer[self.start..self.end]
}
pub(super) fn end_with_newline(&self) -> usize {
if self.has_newline {
self.end + 1
} else {
self.end
}
}
}
#[derive(Debug, Clone)]
pub(super) struct LineBuffer {
text: String,
lines: Vec<Line>,
pending_cr: bool,
}
impl Default for LineBuffer {
fn default() -> Self {
Self::new()
}
}
impl LineBuffer {
pub(super) fn new() -> Self {
Self {
text: String::new(),
lines: vec![Line::empty()],
pending_cr: false,
}
}
pub(super) fn as_str(&self) -> &str {
&self.text
}
pub(super) fn len(&self) -> usize {
self.text.len()
}
pub(super) fn is_empty(&self) -> bool {
self.text.is_empty()
}
pub(super) fn line_count(&self) -> usize {
self.lines.len()
}
pub(super) fn has_pending_cr(&self) -> bool {
self.pending_cr
}
pub(super) fn line(&self, line_index: usize) -> Option<&Line> {
self.lines.get(line_index)
}
pub(super) fn line_str(&self, line_index: usize) -> &str {
self.lines[line_index].as_str(&self.text)
}
pub(super) fn line_start(&self, line_index: usize) -> Option<usize> {
self.line(line_index).map(Line::start)
}
pub(super) fn line_end_with_newline(&self, line_index: usize) -> Option<usize> {
self.line(line_index).map(Line::end_with_newline)
}
pub(super) fn line_has_newline(&self, line_index: usize) -> bool {
self.line(line_index).is_some_and(Line::has_newline)
}
pub(super) fn normalize_newlines_cow<'a>(&mut self, chunk: &'a str) -> Cow<'a, str> {
if !chunk.contains('\r') && !self.pending_cr {
return Cow::Borrowed(chunk);
}
let mut out = String::with_capacity(chunk.len() + 1);
let mut chars = chunk.chars().peekable();
if self.pending_cr {
if chars.peek() == Some(&'\n') {
chars.next();
}
out.push('\n');
self.pending_cr = false;
}
while let Some(c) = chars.next() {
if c != '\r' {
out.push(c);
continue;
}
if chars.peek() == Some(&'\n') {
chars.next();
out.push('\n');
continue;
}
if chars.peek().is_none() {
self.pending_cr = true;
continue;
}
out.push('\n');
}
Cow::Owned(out)
}
pub(super) fn append_normalized(&mut self, chunk: &str) {
let start_offset = self.text.len();
self.text.push_str(chunk);
if self.lines.is_empty() {
self.lines.push(Line::empty());
}
let last_index = self.lines.len() - 1;
self.lines[last_index].end = self.text.len();
let bytes = self.text.as_bytes();
let mut i = start_offset;
while i < bytes.len() {
if bytes[i] == b'\n' {
let last = self.lines.len() - 1;
self.lines[last].end = i;
self.lines[last].has_newline = true;
let next_start = i + 1;
self.lines.push(Line::new(next_start, bytes.len(), false));
}
i += 1;
}
}
pub(super) fn flush_pending_cr_at_eof(&mut self) {
if !self.pending_cr {
return;
}
self.append_normalized("\n");
self.pending_cr = false;
}
pub(super) fn compact_from(&mut self, mut keep_from: usize) -> usize {
if keep_from == 0 || keep_from > self.text.len() {
return 0;
}
while keep_from < self.text.len() && !self.text.is_char_boundary(keep_from) {
keep_from += 1;
}
if keep_from >= self.text.len() {
self.text.clear();
} else {
self.text = self.text[keep_from..].to_string();
}
self.rebuild_lines_from_buffer();
keep_from
}
pub(super) fn reset(&mut self) {
self.text.clear();
self.lines.clear();
self.lines.push(Line::empty());
self.pending_cr = false;
}
fn rebuild_lines_from_buffer(&mut self) {
self.lines.clear();
self.lines.push(Line::new(0, self.text.len(), false));
if self.text.is_empty() {
return;
}
let bytes = self.text.as_bytes();
let mut i = 0usize;
while i < bytes.len() {
if bytes[i] == b'\n' {
let last = self.lines.len() - 1;
self.lines[last].end = i;
self.lines[last].has_newline = true;
let next_start = i + 1;
self.lines.push(Line::new(next_start, bytes.len(), false));
}
i += 1;
}
}
}