use crate::widgets::markdown_widget::foundation::elements::constants::{
get_link_icon, CHECKBOX_CHECKED, CHECKBOX_TODO, CHECKBOX_UNCHECKED,
};
use crate::widgets::markdown_widget::foundation::elements::enums::{CheckboxState, TextSegment};
use crate::widgets::markdown_widget::foundation::elements::methods::helpers::wrap_text;
use crate::widgets::markdown_widget::foundation::elements::MarkdownElement;
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
pub fn render(
_element: &MarkdownElement,
segments: &[TextSegment],
width: usize,
app_theme: Option<&crate::services::theme::AppTheme>,
) -> Vec<Line<'static>> {
let plain_text = segments_to_plain_text(segments);
let wrapped = wrap_text(&plain_text, width);
wrapped
.into_iter()
.map(|line_text| {
let spans = render_line_with_segments(&line_text, segments, app_theme);
Line::from(spans)
})
.collect()
}
fn render_line_with_segments(
line_text: &str,
segments: &[TextSegment],
app_theme: Option<&crate::services::theme::AppTheme>,
) -> Vec<Span<'static>> {
if line_text.is_empty() {
return vec![Span::raw("")];
}
let full_text = segments_to_plain_text(segments);
let line_start = full_text.find(line_text).unwrap_or(0);
let line_end = line_start + line_text.len();
let mut spans = Vec::new();
let mut char_pos = 0;
let code_color = app_theme
.map(|t| t.markdown.code)
.unwrap_or(Color::Rgb(230, 180, 100));
let link_color = app_theme
.map(|t| t.markdown.link_text)
.unwrap_or(Color::Rgb(100, 200, 100));
let emph_color = app_theme.map(|t| t.markdown.emph).unwrap_or(Color::Reset);
let strong_color = app_theme.map(|t| t.markdown.strong).unwrap_or(Color::Reset);
for segment in segments {
let (text, style) = match segment {
TextSegment::Plain(t) => (t.clone(), Style::default()),
TextSegment::Bold(t) => (
t.clone(),
Style::default()
.fg(strong_color)
.add_modifier(Modifier::BOLD),
),
TextSegment::Italic(t) => (
t.clone(),
Style::default()
.fg(emph_color)
.add_modifier(Modifier::ITALIC),
),
TextSegment::BoldItalic(t) => (
t.clone(),
Style::default()
.fg(strong_color)
.add_modifier(Modifier::BOLD)
.add_modifier(Modifier::ITALIC),
),
TextSegment::InlineCode(t) => (
t.clone(),
Style::default().bg(Color::Rgb(60, 60, 60)).fg(code_color),
),
TextSegment::Link {
text,
url,
is_autolink,
bold,
italic,
show_icon,
} => {
let full_text = if *show_icon {
let icon = get_link_icon(url);
format!("{}{}", icon, text)
} else {
text.clone()
};
let mut style = if *is_autolink {
Style::default()
.fg(Color::Rgb(100, 150, 255))
.add_modifier(Modifier::ITALIC)
.add_modifier(Modifier::UNDERLINED)
} else {
Style::default().fg(link_color)
};
if *bold {
style = style.add_modifier(Modifier::BOLD);
}
if *italic && !*is_autolink {
style = style.add_modifier(Modifier::ITALIC);
}
(full_text, style)
}
TextSegment::Strikethrough(t) => (
t.clone(),
Style::default()
.fg(Color::Rgb(150, 150, 150))
.add_modifier(Modifier::CROSSED_OUT),
),
TextSegment::Html(t) => (t.clone(), Style::default()),
TextSegment::Checkbox(state) => {
let (icon, color) = match state {
CheckboxState::Unchecked => (CHECKBOX_UNCHECKED, Color::Rgb(150, 150, 150)),
CheckboxState::Checked => (CHECKBOX_CHECKED, Color::Rgb(100, 200, 100)),
CheckboxState::Todo => (CHECKBOX_TODO, Color::Rgb(200, 150, 50)),
};
(format!("{} ", icon), Style::default().fg(color))
}
};
let seg_start = char_pos;
let seg_end = char_pos + text.len();
char_pos = seg_end;
if seg_end <= line_start || seg_start >= line_end {
continue; }
let overlap_start = seg_start.max(line_start);
let overlap_end = seg_end.min(line_end);
let local_start = overlap_start - seg_start;
let local_end = overlap_end - seg_start;
if local_start < text.len() && local_end <= text.len() {
let slice = &text[local_start..local_end];
if !slice.is_empty() {
spans.push(Span::styled(slice.to_string(), style));
}
}
}
if spans.is_empty() {
vec![Span::raw(line_text.to_string())]
} else {
spans
}
}
fn segments_to_plain_text(segments: &[TextSegment]) -> String {
segments
.iter()
.map(|seg| match seg {
TextSegment::Plain(text) => text.clone(),
TextSegment::Bold(text) => text.clone(),
TextSegment::Italic(text) => text.clone(),
TextSegment::BoldItalic(text) => text.clone(),
TextSegment::InlineCode(text) => text.clone(),
TextSegment::Link {
text,
url,
show_icon,
..
} => {
if *show_icon {
let icon = get_link_icon(url);
format!("{}{}", icon, text)
} else {
text.clone()
}
}
TextSegment::Strikethrough(text) => text.clone(),
TextSegment::Html(content) => content.clone(),
TextSegment::Checkbox(_) => String::new(), })
.collect::<Vec<_>>()
.join("")
}