Refactor TUI line wrapping: implement manual wrapping for consistency, preserve cursor styles, and handle wide characters.
This commit is contained in:
@@ -101,12 +101,32 @@ fn render_editable_textarea(
|
|||||||
render_lines.push(Line::default());
|
render_lines.push(Line::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut paragraph = Paragraph::new(render_lines).style(base_style);
|
// If wrapping is enabled, we need to manually wrap the lines
|
||||||
|
// For now, we'll convert to plain text, wrap, and lose styling
|
||||||
|
// This ensures consistency with cursor calculation
|
||||||
if wrap_lines {
|
if wrap_lines {
|
||||||
paragraph = paragraph.wrap(Wrap { trim: false });
|
let content_width = inner.width as usize;
|
||||||
|
let mut wrapped_lines: Vec<Line> = Vec::new();
|
||||||
|
|
||||||
|
for (row_idx, line) in render_lines.iter().enumerate() {
|
||||||
|
let line_text = line.to_string();
|
||||||
|
let segments = wrap_line_segments(&line_text, content_width);
|
||||||
|
|
||||||
|
for (seg_idx, segment) in segments.into_iter().enumerate() {
|
||||||
|
// For the line with the cursor, preserve the cursor line style
|
||||||
|
if row_idx == cursor.0 && seg_idx == 0 {
|
||||||
|
wrapped_lines.push(Line::from(segment).patch_style(cursor_line_style));
|
||||||
|
} else {
|
||||||
|
wrapped_lines.push(Line::from(segment));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render_lines = wrapped_lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut paragraph = Paragraph::new(render_lines).style(base_style);
|
||||||
|
|
||||||
let metrics = compute_cursor_metrics(lines_slice, cursor, mask_char, inner, wrap_lines);
|
let metrics = compute_cursor_metrics(lines_slice, cursor, mask_char, inner, wrap_lines);
|
||||||
|
|
||||||
if let Some(ref metrics) = metrics {
|
if let Some(ref metrics) = metrics {
|
||||||
@@ -307,15 +327,48 @@ fn wrap_line_segments(line: &str, width: usize) -> Vec<String> {
|
|||||||
return vec![String::new()];
|
return vec![String::new()];
|
||||||
}
|
}
|
||||||
|
|
||||||
let wrapped = wrap(line, Options::new(width).break_words(false));
|
if line.is_empty() {
|
||||||
if wrapped.is_empty() {
|
return vec![String::new()];
|
||||||
vec![String::new()]
|
|
||||||
} else {
|
|
||||||
wrapped
|
|
||||||
.into_iter()
|
|
||||||
.map(|segment| segment.into_owned())
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manual wrapping that preserves all characters including spaces
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let mut current = String::new();
|
||||||
|
let mut current_width = 0usize;
|
||||||
|
|
||||||
|
for ch in line.chars() {
|
||||||
|
let ch_width = UnicodeWidthStr::width(ch.to_string().as_str());
|
||||||
|
|
||||||
|
// If adding this character would exceed width, wrap to next line
|
||||||
|
if current_width + ch_width > width {
|
||||||
|
if !current.is_empty() {
|
||||||
|
result.push(current);
|
||||||
|
current = String::new();
|
||||||
|
current_width = 0;
|
||||||
|
}
|
||||||
|
// If even a single character is too wide, add it anyway to avoid infinite loop
|
||||||
|
if ch_width > width {
|
||||||
|
current.push(ch);
|
||||||
|
result.push(current);
|
||||||
|
current = String::new();
|
||||||
|
current_width = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current.push(ch);
|
||||||
|
current_width += ch_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !current.is_empty() {
|
||||||
|
result.push(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.is_empty() {
|
||||||
|
result.push(String::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_header(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) {
|
fn render_header(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) {
|
||||||
@@ -498,6 +551,7 @@ fn render_input(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) {
|
|||||||
if matches!(app.mode(), InputMode::Editing) {
|
if matches!(app.mode(), InputMode::Editing) {
|
||||||
let mut textarea = app.textarea().clone();
|
let mut textarea = app.textarea().clone();
|
||||||
textarea.set_block(input_block.clone());
|
textarea.set_block(input_block.clone());
|
||||||
|
textarea.set_hard_tab_indent(false);
|
||||||
render_editable_textarea(frame, area, &mut textarea, true);
|
render_editable_textarea(frame, area, &mut textarea, true);
|
||||||
} else {
|
} else {
|
||||||
// In non-editing mode, show the current input buffer content as read-only
|
// In non-editing mode, show the current input buffer content as read-only
|
||||||
|
|||||||
Reference in New Issue
Block a user