use super::inline_emission;
use crate::options::ParserOptions;
use crate::parser::inlines::sink::MarkerInjectingSink;
use rowan::GreenNodeBuilder;
#[derive(Debug, Default, Clone)]
pub(crate) struct TextBuffer {
lines: Vec<String>,
}
impl TextBuffer {
pub(crate) fn new() -> Self {
Self { lines: Vec::new() }
}
pub(crate) fn push_line(&mut self, text: impl Into<String>) {
self.lines.push(text.into());
}
pub(crate) fn get_accumulated_text(&self) -> String {
self.lines.concat()
}
pub(crate) fn clear(&mut self) {
self.lines.clear();
}
pub(crate) fn is_empty(&self) -> bool {
self.lines.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_buffer_is_empty() {
let buffer = TextBuffer::new();
assert!(buffer.is_empty());
assert!(buffer.is_empty());
assert_eq!(buffer.get_accumulated_text(), "");
}
#[test]
fn test_push_single_line() {
let mut buffer = TextBuffer::new();
buffer.push_line("Hello, world!");
assert!(!buffer.is_empty());
assert_eq!(buffer.get_accumulated_text(), "Hello, world!");
}
#[test]
fn test_push_multiple_lines() {
let mut buffer = TextBuffer::new();
buffer.push_line("Line 1\n");
buffer.push_line("Line 2\n");
buffer.push_line("Line 3");
assert_eq!(buffer.get_accumulated_text(), "Line 1\nLine 2\nLine 3");
}
#[test]
fn test_clear_buffer() {
let mut buffer = TextBuffer::new();
buffer.push_line("Line 1");
buffer.push_line("Line 2");
buffer.clear();
assert!(buffer.is_empty());
assert_eq!(buffer.get_accumulated_text(), "");
}
#[test]
fn test_reuse_after_clear() {
let mut buffer = TextBuffer::new();
buffer.push_line("First paragraph\n");
buffer.push_line("continues here");
assert_eq!(
buffer.get_accumulated_text(),
"First paragraph\ncontinues here"
);
buffer.clear();
buffer.push_line("Second paragraph\n");
buffer.push_line("also continues");
assert_eq!(
buffer.get_accumulated_text(),
"Second paragraph\nalso continues"
);
}
#[test]
fn test_empty_lines() {
let mut buffer = TextBuffer::new();
buffer.push_line("\n");
buffer.push_line("Non-empty\n");
buffer.push_line("");
assert!(!buffer.is_empty());
assert_eq!(buffer.get_accumulated_text(), "\nNon-empty\n");
}
#[test]
fn test_whitespace_preserved() {
let mut buffer = TextBuffer::new();
buffer.push_line(" Leading spaces\n");
buffer.push_line("Trailing spaces \n");
buffer.push_line("\tTab at start");
assert_eq!(
buffer.get_accumulated_text(),
" Leading spaces\nTrailing spaces \n\tTab at start"
);
}
#[test]
fn test_default_is_empty() {
let buffer = TextBuffer::default();
assert!(buffer.is_empty());
assert_eq!(buffer.get_accumulated_text(), "");
}
}
#[derive(Debug, Clone)]
pub(crate) enum ParagraphSegment {
Text(String),
BlockquoteMarker {
leading_spaces: usize,
has_trailing_space: bool,
},
}
#[derive(Debug, Default, Clone)]
pub(crate) struct ParagraphBuffer {
segments: Vec<ParagraphSegment>,
}
impl ParagraphBuffer {
pub(crate) fn new() -> Self {
Self {
segments: Vec::new(),
}
}
pub(crate) fn push_text(&mut self, text: &str) {
if text.is_empty() {
return;
}
match self.segments.last_mut() {
Some(ParagraphSegment::Text(existing)) => {
existing.push_str(text);
}
_ => {
self.segments.push(ParagraphSegment::Text(text.to_string()));
}
}
}
pub(crate) fn push_marker(&mut self, leading_spaces: usize, has_trailing_space: bool) {
self.segments.push(ParagraphSegment::BlockquoteMarker {
leading_spaces,
has_trailing_space,
});
}
pub(crate) fn get_text_for_parsing(&self) -> String {
let mut result = String::new();
for segment in &self.segments {
if let ParagraphSegment::Text(text) = segment {
result.push_str(text);
}
}
result
}
fn get_marker_positions(&self) -> Vec<(usize, usize, bool)> {
let mut positions = Vec::new();
let mut byte_offset = 0;
for segment in &self.segments {
match segment {
ParagraphSegment::Text(text) => {
byte_offset += text.len();
}
ParagraphSegment::BlockquoteMarker {
leading_spaces,
has_trailing_space,
} => {
positions.push((byte_offset, *leading_spaces, *has_trailing_space));
}
}
}
positions
}
pub(crate) fn emit_with_inlines(
&self,
builder: &mut GreenNodeBuilder<'static>,
config: &ParserOptions,
suppress_footnote_refs: bool,
) {
let text = self.get_text_for_parsing();
if text.is_empty() && self.segments.is_empty() {
return;
}
let marker_positions = self.get_marker_positions();
if marker_positions.is_empty() {
inline_emission::emit_inlines(builder, &text, config, suppress_footnote_refs);
} else {
self.emit_with_markers(
builder,
&text,
&marker_positions,
config,
suppress_footnote_refs,
);
}
}
fn emit_with_markers(
&self,
builder: &mut GreenNodeBuilder<'static>,
text: &str,
marker_positions: &[(usize, usize, bool)],
config: &ParserOptions,
suppress_footnote_refs: bool,
) {
let mut sink = MarkerInjectingSink::new(builder, marker_positions);
inline_emission::emit_inlines(&mut sink, text, config, suppress_footnote_refs);
sink.finish();
}
pub(crate) fn is_empty(&self) -> bool {
self.segments.is_empty()
}
}
#[cfg(test)]
mod paragraph_buffer_tests {
use super::*;
#[test]
fn test_new_buffer_is_empty() {
let buffer = ParagraphBuffer::new();
assert!(buffer.is_empty());
assert_eq!(buffer.get_text_for_parsing(), "");
}
#[test]
fn test_push_text_single() {
let mut buffer = ParagraphBuffer::new();
buffer.push_text("Hello, world!");
assert!(!buffer.is_empty());
assert_eq!(buffer.get_text_for_parsing(), "Hello, world!");
}
#[test]
fn test_push_text_concatenates() {
let mut buffer = ParagraphBuffer::new();
buffer.push_text("Hello");
buffer.push_text(", ");
buffer.push_text("world!");
assert_eq!(buffer.get_text_for_parsing(), "Hello, world!");
assert_eq!(buffer.segments.len(), 1);
}
#[test]
fn test_push_marker_separates_text() {
let mut buffer = ParagraphBuffer::new();
buffer.push_text("Line 1\n");
buffer.push_marker(0, true);
buffer.push_text("Line 2\n");
assert_eq!(buffer.segments.len(), 3);
assert_eq!(buffer.get_text_for_parsing(), "Line 1\nLine 2\n");
}
#[test]
fn test_marker_positions() {
let mut buffer = ParagraphBuffer::new();
buffer.push_text("Line 1\n"); buffer.push_marker(0, true);
buffer.push_text("Line 2\n");
let positions = buffer.get_marker_positions();
assert_eq!(positions.len(), 1);
assert_eq!(positions[0], (7, 0, true)); }
#[test]
fn test_multiple_markers() {
let mut buffer = ParagraphBuffer::new();
buffer.push_text("A\n"); buffer.push_marker(0, true);
buffer.push_text("B\n"); buffer.push_marker(1, false);
buffer.push_text("C");
let positions = buffer.get_marker_positions();
assert_eq!(positions.len(), 2);
assert_eq!(positions[0], (2, 0, true)); assert_eq!(positions[1], (4, 1, false)); }
#[test]
fn test_empty_text_ignored() {
let mut buffer = ParagraphBuffer::new();
buffer.push_text("");
assert!(buffer.is_empty());
}
}