From 371cefc1ca08e0d166027d9a83400a48ed690d56 Mon Sep 17 00:00:00 2001 From: sungdark Date: Sun, 29 Mar 2026 06:02:43 +0000 Subject: [PATCH] fix(model-picker): resolve legend overlap and UTF-8 caret misalignment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix two bugs in cortex-tui model picker (issue #40176): A) Legend/help row overlap (narrow terminals): render_help_bar now checks that help_x > legend_right + 1 before rendering the shortcut string. When the terminal is too narrow the legend (* / +) renders alone — no garbled collision. B) Search caret uses byte length instead of display width: render_search_bar now uses UnicodeWidthStr::width() instead of .len() so CJK characters and emoji don't misplace the caret. Both fixes are conservative; existing behaviour is preserved when there is enough terminal space. --- src/cortex-tui/src/widgets/model_picker.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/cortex-tui/src/widgets/model_picker.rs b/src/cortex-tui/src/widgets/model_picker.rs index ca21c7cd7..f2bd17ad3 100644 --- a/src/cortex-tui/src/widgets/model_picker.rs +++ b/src/cortex-tui/src/widgets/model_picker.rs @@ -23,6 +23,7 @@ use ratatui::widgets::{ Block, Borders, Clear, Scrollbar, ScrollbarOrientation, ScrollbarState, Widget, }; use unicode_segmentation::UnicodeSegmentation; +use unicode_width::UnicodeWidthStr; // ============================================================ // MODEL ITEM @@ -347,8 +348,9 @@ impl ModelPicker<'_> { buf.set_string(x + 3, y, &display_query, query_style); - // Cursor - let cursor_x = x + 3 + self.state.search_query.len() as u16; + // Cursor — use display width (UnicodeWidthStr) not byte length, + // so CJK/emoji characters don't misalign the caret. + let cursor_x = x + 3 + UnicodeWidthStr::width(&self.state.search_query) as u16; if cursor_x < search_inner.right() { buf[(cursor_x, y)].set_bg(CYAN_PRIMARY); } @@ -480,14 +482,19 @@ impl ModelPicker<'_> { let y = area.y; let x = area.x + 1; - // Legend + // Legend — occupies columns x..x+21 ("* current " = 10, "+ popular" = 9) buf.set_string(x, y, "* current ", Style::default().fg(GREEN)); buf.set_string(x + 11, y, "+ popular", Style::default().fg(ORANGE)); - // Help + // Help — guard against overlap with legend when terminal is narrow. + // Legend ends at legend_right = x + 21. Require help_x > legend_right + 1 gap. + let legend_right = x + 21; let help = "[Enter] select [Esc] cancel [Ctrl+L] clear search"; let help_x = area.right().saturating_sub(help.len() as u16 + 1); - buf.set_string(help_x, y, help, Style::default().fg(TEXT_MUTED)); + if help_x > legend_right + 1 { + buf.set_string(help_x, y, help, Style::default().fg(TEXT_MUTED)); + } + // When width is insufficient the legend renders alone — no garbled overlap. } }