use super::inline_emission;
use crate::config::Config;
use crate::syntax::{SyntaxKind, SyntaxNode, SyntaxToken};
use rowan::{GreenNodeBuilder, NodeOrToken};
#[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: &Config,
) {
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);
} else {
self.emit_with_markers(builder, &text, &marker_positions, config);
}
}
fn emit_with_markers(
&self,
builder: &mut GreenNodeBuilder<'static>,
text: &str,
marker_positions: &[(usize, usize, bool)],
config: &Config,
) {
let mut temp_builder = GreenNodeBuilder::new();
temp_builder.start_node(SyntaxKind::HEADING_CONTENT.into());
inline_emission::emit_inlines(&mut temp_builder, text, config);
temp_builder.finish_node();
let inline_root = SyntaxNode::new_root(temp_builder.finish());
struct MarkerEmitter<'a> {
marker_positions: &'a [(usize, usize, bool)],
idx: usize,
offset: usize,
}
impl<'a> MarkerEmitter<'a> {
fn emit_markers_at_current(&mut self, builder: &mut GreenNodeBuilder<'static>) {
while let Some(&(byte_offset, leading_spaces, has_trailing_space)) =
self.marker_positions.get(self.idx)
&& byte_offset == self.offset
{
if leading_spaces > 0 {
builder.token(SyntaxKind::WHITESPACE.into(), &" ".repeat(leading_spaces));
}
builder.token(SyntaxKind::BLOCK_QUOTE_MARKER.into(), ">");
if has_trailing_space {
builder.token(SyntaxKind::WHITESPACE.into(), " ");
}
self.idx += 1;
}
}
fn emit_token_with_markers(
&mut self,
builder: &mut GreenNodeBuilder<'static>,
token: &SyntaxToken,
) {
let kind = token.kind();
let token_text = token.text();
let mut start = 0;
while start < token_text.len() {
self.emit_markers_at_current(builder);
let remaining = token_text.len() - start;
let next_marker_offset = self
.marker_positions
.get(self.idx)
.map(|(byte_offset, _, _)| *byte_offset);
if let Some(next) = next_marker_offset
&& next > self.offset
&& next < self.offset + remaining
{
let split_len = next - self.offset;
let end = start + split_len;
if end > start {
builder.token(kind.into(), &token_text[start..end]);
self.offset += split_len;
start = end;
continue;
}
}
builder.token(kind.into(), &token_text[start..]);
self.offset += remaining;
break;
}
}
fn emit_element(
&mut self,
builder: &mut GreenNodeBuilder<'static>,
el: NodeOrToken<SyntaxNode, SyntaxToken>,
) {
match el {
NodeOrToken::Node(n) => {
builder.start_node(n.kind().into());
for child in n.children_with_tokens() {
self.emit_element(builder, child);
}
builder.finish_node();
}
NodeOrToken::Token(t) => self.emit_token_with_markers(builder, &t),
}
}
}
let mut emitter = MarkerEmitter {
marker_positions,
idx: 0,
offset: 0,
};
for el in inline_root.children_with_tokens() {
emitter.emit_element(builder, el);
}
emitter.emit_markers_at_current(builder);
}
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());
}
}