From c1c4394f94936b1cfedddb4f54c37212f8f5e5cd Mon Sep 17 00:00:00 2001 From: filipriec Date: Sun, 13 Apr 2025 14:58:41 +0200 Subject: [PATCH] ... at the end of the dialog truncation --- Cargo.lock | 1 + client/Cargo.toml | 1 + client/src/components/common/dialog.rs | 47 ++++++++++++++++++++------ 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 547e403..c3f89a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -436,6 +436,7 @@ dependencies = [ "toml", "tonic", "tracing", + "unicode-segmentation", "unicode-width 0.2.0", ] diff --git a/client/Cargo.toml b/client/Cargo.toml index e09ac46..fb6467d 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -19,4 +19,5 @@ tokio = { version = "1.43.0", features = ["full", "macros"] } toml = "0.8.20" tonic = "0.12.3" tracing = "0.1.41" +unicode-segmentation = "1.12.0" unicode-width = "0.2.0" diff --git a/client/src/components/common/dialog.rs b/client/src/components/common/dialog.rs index 62fab00..385ae25 100644 --- a/client/src/components/common/dialog.rs +++ b/client/src/components/common/dialog.rs @@ -1,13 +1,14 @@ -// src/components/common/dialog.rs use crate::config::colors::themes::Theme; use ratatui::{ layout::{Constraint, Direction, Layout, Margin, Rect}, prelude::Alignment, style::{Modifier, Style}, text::{Line, Span, Text}, - widgets::{Block, BorderType, Borders, Paragraph, Clear}, // Added Clear + widgets::{Block, BorderType, Borders, Paragraph, Clear}, Frame, }; +use unicode_segmentation::UnicodeSegmentation; // For grapheme clusters +use unicode_width::UnicodeWidthStr; // For accurate width calculation pub fn render_dialog( f: &mut Frame, @@ -18,15 +19,16 @@ pub fn render_dialog( dialog_buttons: &[String], dialog_active_button_index: usize, ) { + // Calculate required height based on the actual number of lines in the message let message_lines: Vec<_> = dialog_message.lines().collect(); let message_height = message_lines.len() as u16; let button_row_height = if dialog_buttons.is_empty() { 0 } else { 3 }; let vertical_padding = 2; // Block borders (top/bottom) let inner_vertical_margin = 2; // Margin inside block (top/bottom) + // Calculate required height based on actual message lines let required_inner_height = message_height + button_row_height + inner_vertical_margin; - // Add block border height let required_total_height = required_inner_height + vertical_padding; // Use a fixed percentage width, clamped to min/max @@ -61,10 +63,10 @@ pub fn render_dialog( vertical: 1, // Top/Bottom padding inside border }); - // Layout for Message and Buttons + // Layout for Message and Buttons based on actual message height let mut constraints = vec![ // Allocate space for message, ensuring at least 1 line height - Constraint::Min(message_height.max(1)), + Constraint::Length(message_height.max(1)), // Use actual calculated height ]; if button_row_height > 0 { constraints.push(Constraint::Length(button_row_height)); @@ -76,15 +78,39 @@ pub fn render_dialog( .split(inner_area); // Render Message - let message_text = Text::from( + let available_width = inner_area.width as usize; + let ellipsis = "..."; + let ellipsis_width = UnicodeWidthStr::width(ellipsis); + + let processed_lines: Vec = message_lines .into_iter() - .map(|l| Line::from(Span::styled(l, Style::default().fg(theme.fg)))) - .collect::>(), - ); + .map(|line| { + let line_width = UnicodeWidthStr::width(line); + if line_width > available_width { + // Truncate with ellipsis + let mut truncated_len = 0; + let mut current_width = 0; + // Iterate over graphemes to handle multi-byte characters correctly + for (idx, grapheme) in line.grapheme_indices(true) { + let grapheme_width = UnicodeWidthStr::width(grapheme); + if current_width + grapheme_width > available_width.saturating_sub(ellipsis_width) { + break; // Stop before exceeding width needed for text + ellipsis + } + current_width += grapheme_width; + truncated_len = idx + grapheme.len(); // Store the byte index of the end of the last fitting grapheme + } + let truncated_line = format!("{}{}", &line[..truncated_len], ellipsis); + Line::from(Span::styled(truncated_line, Style::default().fg(theme.fg))) + } else { + // Line fits, use it as is + Line::from(Span::styled(line, Style::default().fg(theme.fg))) + } + }) + .collect(); let message_paragraph = - Paragraph::new(message_text).alignment(Alignment::Center); + Paragraph::new(Text::from(processed_lines)).alignment(Alignment::Center); // Render message in the first chunk f.render_widget(message_paragraph, chunks[0]); @@ -143,4 +169,3 @@ pub fn render_dialog( } } } -