use super::ParserState;
use crate::util::text::{display_width, wrap_text};
use ratatui::{
style::{Modifier, Style},
text::Span,
};
impl<'a> ParserState<'a> {
pub(crate) fn handle_text_event(&mut self, text: &str) {
if self.image_url.is_some() {
self.image_alt.push_str(text);
return;
}
if self.in_code_block {
self.code_block_content.push_str(text);
return;
}
if self.in_table {
self.current_cell.push_str(text);
return;
}
let style = *self.style_stack.last().unwrap_or(&self.base_style);
let text_str = text.replace('\u{200B}', "");
let effective_prefix_w = if self.in_blockquote { 2 } else { 0 };
let full_line_w = self.content_width.saturating_sub(effective_prefix_w);
let existing_w: usize = self
.current_spans
.iter()
.map(|s| display_width(&s.content))
.sum();
let content_w_on_line = existing_w.saturating_sub(effective_prefix_w);
let wrap_w = full_line_w.saturating_sub(content_w_on_line);
let min_useful_w = full_line_w / 4;
let wrap_w = if wrap_w < min_useful_w.max(4) && !self.current_spans.is_empty() {
self.flush_line();
if self.in_blockquote {
self.current_spans.push(Span::styled(
"| ".to_string(),
Style::default()
.fg(self.theme.md_blockquote_bar)
.bg(self.theme.md_blockquote_bg)
.add_modifier(Modifier::BOLD),
));
}
full_line_w
} else {
wrap_w
};
let link_style = Style::default()
.fg(self.theme.md_link)
.add_modifier(Modifier::UNDERLINED);
let in_link = self.link_url.is_some();
let segments: Vec<Span<'static>> = if in_link {
text_str
.split('\n')
.enumerate()
.flat_map(|(i, line)| {
let mut v = Vec::new();
if i > 0 {
v.push(Span::raw("\n"));
}
v.push(Span::styled(line.to_string(), style));
v
})
.collect()
} else {
text_str
.split('\n')
.enumerate()
.flat_map(|(i, line)| {
let mut v: Vec<Span<'static>> = Vec::new();
if i > 0 {
v.push(Span::raw("\n"));
}
v.extend(split_text_with_urls(line, style, link_style));
v
})
.collect()
};
let mut cur_line_w = content_w_on_line;
let mut first_seg = true;
for seg in &segments {
if seg.content.as_ref() == "\n" {
self.flush_line();
if self.in_blockquote {
self.current_spans.push(Span::styled(
"| ".to_string(),
Style::default()
.fg(self.theme.md_blockquote_bar)
.bg(self.theme.md_blockquote_bg)
.add_modifier(Modifier::BOLD),
));
cur_line_w = 0;
} else {
cur_line_w = 0;
}
first_seg = false;
continue;
}
let seg_text = seg.content.to_string();
let seg_style = seg.style;
let seg_w = display_width(&seg_text);
let avail = if first_seg {
wrap_w
} else {
full_line_w.saturating_sub(cur_line_w)
};
first_seg = false;
if seg_w <= avail {
self.current_spans.push(Span::styled(seg_text, seg_style));
cur_line_w += seg_w;
} else {
let first_wrap_w = avail;
let first_wrapped = wrap_text(&seg_text, first_wrap_w.max(1));
self.current_spans
.push(Span::styled(first_wrapped[0].clone(), seg_style));
if first_wrapped.len() > 1 {
let rest: String = first_wrapped[1..].join("");
self.flush_line();
if self.in_blockquote {
self.current_spans.push(Span::styled(
"| ".to_string(),
Style::default()
.fg(self.theme.md_blockquote_bar)
.bg(self.theme.md_blockquote_bg)
.add_modifier(Modifier::BOLD),
));
}
let rest_wrapped = wrap_text(&rest, full_line_w.max(1));
for (j, wl) in rest_wrapped.iter().enumerate() {
if j > 0 {
self.flush_line();
if self.in_blockquote {
self.current_spans.push(Span::styled(
"| ".to_string(),
Style::default()
.fg(self.theme.md_blockquote_bar)
.bg(self.theme.md_blockquote_bg)
.add_modifier(Modifier::BOLD),
));
}
}
self.current_spans.push(Span::styled(wl.clone(), seg_style));
}
cur_line_w = display_width(rest_wrapped.last().unwrap_or(&String::new()));
} else {
cur_line_w = display_width(&first_wrapped[0]);
}
}
}
}
}
pub(crate) fn split_text_with_urls<'a>(
text: &str,
normal_style: Style,
link_style: Style,
) -> Vec<Span<'a>> {
let mut spans = Vec::new();
let mut remaining = text;
while !remaining.is_empty() {
let url_start = remaining
.find("https://")
.or_else(|| remaining.find("http://"));
match url_start {
Some(start) => {
if start > 0 {
spans.push(Span::styled(remaining[..start].to_string(), normal_style));
}
let url_part = &remaining[start..];
let url_end = url_part
.char_indices()
.find(|(i, c)| {
if *i < 8 {
return false;
}
c.is_whitespace()
|| *c == '>'
|| *c == ')'
|| *c == ']'
|| ('\u{4E00}'..='\u{9FFF}').contains(c) || ('\u{3000}'..='\u{303F}').contains(c) || ('\u{FF00}'..='\u{FFEF}').contains(c) || matches!(
*c,
',' | '。' | ';'
| ':'
| '!'
| '?'
| '、'
| '\u{201C}'
| '\u{201D}'
| '\u{2018}'
| '\u{2019}'
)
})
.map(|(i, _)| i)
.unwrap_or(url_part.len());
let url = url_part[..url_end].trim_end_matches(['.', ',', ';', ':', '!', '?']);
let url_len = url.len();
spans.push(Span::styled(url.to_string(), link_style));
if url_len < url_end {
spans.push(Span::styled(
url_part[url_len..url_end].to_string(),
normal_style,
));
}
remaining = &remaining[start + url_end..];
}
None => {
spans.push(Span::styled(remaining.to_string(), normal_style));
break;
}
}
}
spans
}