feat: Improve markdown rendering with enhanced line wrapping and indentation for code blocks

This commit is contained in:
asepharyana
2026-07-15 04:36:00 +07:00
parent 3020a0431c
commit b2e848d124
2 changed files with 50 additions and 19 deletions
+2 -3
View File
@@ -18,7 +18,7 @@
use ratatui::layout::Rect; use ratatui::layout::Rect;
use ratatui::style::{Color, Style, Modifier}; use ratatui::style::{Color, Style, Modifier};
use ratatui::text::{Line, Span}; use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, BorderType, Borders, Paragraph, Wrap}; use ratatui::widgets::{Block, BorderType, Borders, Paragraph};
use ratatui::Frame; use ratatui::Frame;
use super::theme::Theme; use super::theme::Theme;
use crate::dto::chat::message::Role; use crate::dto::chat::message::Role;
@@ -253,8 +253,7 @@ pub fn draw_chat(frame: &mut Frame, area: Rect, state: &crate::app::state::rest:
let paragraph = Paragraph::new(visible) let paragraph = Paragraph::new(visible)
.block(block) .block(block)
.style(Style::default().bg(Theme::BG)) .style(Style::default().bg(Theme::BG));
.wrap(Wrap { trim: false });
frame.render_widget(paragraph, area); frame.render_widget(paragraph, area);
} }
+48 -16
View File
@@ -120,8 +120,9 @@ pub fn render_markdown(text: &str, width: u16) -> Vec<Span<'static>> {
pulldown_cmark::Event::Text(text) => { pulldown_cmark::Event::Text(text) => {
let s = text.to_string(); let s = text.to_string();
if in_code_block { if in_code_block {
let indented = format!(" {}", s.replace('\n', "\n "));
spans.push(Span::styled( spans.push(Span::styled(
format!(" {s}"), indented,
Style::default().fg(Theme::ACCENT_TEAL).bg(Theme::CODE_BG), Style::default().fg(Theme::ACCENT_TEAL).bg(Theme::CODE_BG),
)); ));
} else if in_heading { } else if in_heading {
@@ -164,23 +165,54 @@ pub fn render_markdown(text: &str, width: u16) -> Vec<Span<'static>> {
let mut line_len = 0; let mut line_len = 0;
let effective_width = (width as usize).saturating_sub(2); // leave margin let effective_width = (width as usize).saturating_sub(2); // leave margin
for span in &spans { for span in spans {
let style = span.style; let style = span.style;
let s = span.content.clone(); let text = span.content.as_ref();
let text_str = s.as_ref();
let remaining = text_str.len(); let mut current = String::new();
let mut tokens = Vec::new();
if line_len + remaining > effective_width && line_len > 0 { for c in text.chars() {
spans_out.push(Span::raw("\n")); if c == ' ' {
line_len = 0; if !current.is_empty() { tokens.push(current.clone()); current.clear(); }
tokens.push(" ".to_string());
} else if c == '\n' {
if !current.is_empty() { tokens.push(current.clone()); current.clear(); }
tokens.push("\n".to_string());
} else {
current.push(c);
}
} }
if !current.is_empty() { tokens.push(current); }
spans_out.push(Span::styled(text_str.to_string(), style));
for token in tokens {
if text_str.contains('\n') { if token == "\n" {
line_len = text_str.split('\n').next_back().unwrap_or("").len(); spans_out.push(Span::styled("\n", style));
} else { line_len = 0;
line_len += remaining; } else if token == " " {
if line_len > 0 && line_len < effective_width {
spans_out.push(Span::styled(" ", style));
line_len += 1;
}
} else {
let token_len = token.chars().count();
if line_len + token_len > effective_width && line_len > 0 {
spans_out.push(Span::raw("\n"));
line_len = 0;
}
if token_len > effective_width {
for c in token.chars() {
if line_len >= effective_width {
spans_out.push(Span::raw("\n"));
line_len = 0;
}
spans_out.push(Span::styled(c.to_string(), style));
line_len += 1;
}
} else {
spans_out.push(Span::styled(token, style));
line_len += token_len;
}
}
} }
} }
spans = spans_out; spans = spans_out;