From 237cdc4fb2e540addef8fe3ca629d9ad2f33f418 Mon Sep 17 00:00:00 2001 From: akirco Date: Thu, 9 Apr 2026 11:36:06 +0800 Subject: [PATCH] fix: proper unicode support for input handling --- Cargo.lock | 1 + Cargo.toml | 6 +++- src/main.rs | 80 +++++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 68 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 480e52e..abdcc62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,6 +196,7 @@ dependencies = [ "serde_json", "tokio", "toml", + "unicode-width", "windows-sys 0.59.0", ] diff --git a/Cargo.toml b/Cargo.toml index cfebdb2..a91613e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,12 @@ repository = "https://github.com/64bit/commandOK" [dependencies] ratatui = "0.30" +unicode-width = "0.2.2" crossterm = "0.29" -reqwest = { version = "0.13", default-features = false, features = ["json", "rustls"] } +reqwest = { version = "0.13", default-features = false, features = [ + "json", + "rustls", +] } tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/src/main.rs b/src/main.rs index 7fa6512..091aa41 100644 --- a/src/main.rs +++ b/src/main.rs @@ -150,6 +150,56 @@ impl App { history: History::load(), } } + + fn move_cursor_left(&mut self) { + self.cursor = self.cursor.saturating_sub(1); + } + + fn move_cursor_right(&mut self) { + if self.cursor < self.input.chars().count() { + self.cursor += 1; + } + } + + fn enter_char(&mut self, c: char) { + let idx = self.byte_index(); + self.input.insert(idx, c); + self.move_cursor_right(); + } + + fn byte_index(&self) -> usize { + self.input + .char_indices() + .map(|(i, _)| i) + .nth(self.cursor) + .unwrap_or(self.input.len()) + } + + fn cursor_visual_width(&self) -> usize { + use unicode_width::UnicodeWidthStr; + let byte_idx = self.byte_index(); + UnicodeWidthStr::width(&self.input[..byte_idx]) + } + + fn delete_char(&mut self) { + if self.cursor != 0 { + let current_index = self.cursor; + let from_left_to_current_index = current_index - 1; + let before_char_to_delete = self.input.chars().take(from_left_to_current_index); + let after_char_to_delete = self.input.chars().skip(current_index); + self.input = before_char_to_delete.chain(after_char_to_delete).collect(); + self.move_cursor_left(); + } + } + + fn delete_forward_char(&mut self) { + if self.cursor < self.input.chars().count() { + let current_index = self.cursor; + let before_char_to_delete = self.input.chars().take(current_index); + let after_char_to_delete = self.input.chars().skip(current_index + 1); + self.input = before_char_to_delete.chain(after_char_to_delete).collect(); + } + } } // --------------------------------------------------------------------------- @@ -197,7 +247,7 @@ fn render(f: &mut Frame, app: &App, provider_label: &str) { let (color, content, hint, show_cursor) = match &app.mode { Mode::Input => { let line = Line::from(vec![ - Span::styled("> ", Style::default().fg(Color::Cyan).bold()), + Span::styled("❱ ", Style::default().fg(Color::Cyan).bold()), Span::raw(&app.input), ]); ( @@ -264,7 +314,7 @@ fn render(f: &mut Frame, app: &App, provider_label: &str) { f.render_widget(Paragraph::new(content).block(block), area); if show_cursor { - let x = area.x + 1 + 2 + app.cursor as u16; + let x = area.x + 1 + 2 + app.cursor_visual_width() as u16; f.set_cursor_position((x, area.y + 1)); } } @@ -382,13 +432,13 @@ async fn main() -> Result<(), Box> { KeyCode::Up => { if let Some(prev) = app.history.prev(&app.input) { app.input = prev.to_string(); - app.cursor = app.input.len(); + app.cursor = app.input.chars().count(); } } KeyCode::Down => { if let Some(next) = app.history.next() { app.input = next.to_string(); - app.cursor = app.input.len(); + app.cursor = app.input.chars().count(); } } KeyCode::Char(c) => { @@ -398,25 +448,19 @@ async fn main() -> Result<(), Box> { app.cursor = 0; } } else { - app.input.insert(app.cursor, c); - app.cursor += 1; + app.enter_char(c); } } KeyCode::Backspace => { - if app.cursor > 0 { - app.cursor -= 1; - app.input.remove(app.cursor); - } - } - KeyCode::Delete if app.cursor < app.input.len() => { - app.input.remove(app.cursor); + app.delete_char(); } - KeyCode::Left => app.cursor = app.cursor.saturating_sub(1), - KeyCode::Right if app.cursor < app.input.len() => { - app.cursor += 1; + KeyCode::Delete => { + app.delete_forward_char(); } + KeyCode::Left => app.move_cursor_left(), + KeyCode::Right => app.move_cursor_right(), KeyCode::Home => app.cursor = 0, - KeyCode::End => app.cursor = app.input.len(), + KeyCode::End => app.cursor = app.input.chars().count(), _ => {} }, Mode::Loading | Mode::Streaming(_) => { @@ -431,7 +475,7 @@ async fn main() -> Result<(), Box> { } KeyCode::Char('e') => { app.mode = Mode::Input; - app.cursor = app.input.len(); + app.cursor = app.input.chars().count(); app.history.reset_nav(); } KeyCode::Esc => app.quit = true,