diff --git a/src/view/chat.rs b/src/view/chat.rs index e1a41de..23cf229 100644 --- a/src/view/chat.rs +++ b/src/view/chat.rs @@ -18,7 +18,7 @@ use ratatui::layout::Rect; use ratatui::style::{Color, Style, Modifier}; use ratatui::text::{Line, Span}; -use ratatui::widgets::{Block, BorderType, Borders, Paragraph, Wrap}; +use ratatui::widgets::{Block, BorderType, Borders, Paragraph}; use ratatui::Frame; use super::theme::Theme; 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) .block(block) - .style(Style::default().bg(Theme::BG)) - .wrap(Wrap { trim: false }); + .style(Style::default().bg(Theme::BG)); frame.render_widget(paragraph, area); } diff --git a/src/view/markdown.rs b/src/view/markdown.rs index 2245a8e..f705d6a 100644 --- a/src/view/markdown.rs +++ b/src/view/markdown.rs @@ -120,8 +120,9 @@ pub fn render_markdown(text: &str, width: u16) -> Vec> { pulldown_cmark::Event::Text(text) => { let s = text.to_string(); if in_code_block { + let indented = format!(" {}", s.replace('\n', "\n ")); spans.push(Span::styled( - format!(" {s}"), + indented, Style::default().fg(Theme::ACCENT_TEAL).bg(Theme::CODE_BG), )); } else if in_heading { @@ -164,23 +165,54 @@ pub fn render_markdown(text: &str, width: u16) -> Vec> { let mut line_len = 0; let effective_width = (width as usize).saturating_sub(2); // leave margin - for span in &spans { + for span in spans { let style = span.style; - let s = span.content.clone(); - let text_str = s.as_ref(); - let remaining = text_str.len(); - - if line_len + remaining > effective_width && line_len > 0 { - spans_out.push(Span::raw("\n")); - line_len = 0; + let text = span.content.as_ref(); + + let mut current = String::new(); + let mut tokens = Vec::new(); + for c in text.chars() { + if c == ' ' { + 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); + } } - - spans_out.push(Span::styled(text_str.to_string(), style)); - - if text_str.contains('\n') { - line_len = text_str.split('\n').next_back().unwrap_or("").len(); - } else { - line_len += remaining; + if !current.is_empty() { tokens.push(current); } + + for token in tokens { + if token == "\n" { + spans_out.push(Span::styled("\n", style)); + line_len = 0; + } 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;