From a4fd6e342057ad9876d512f260ec6e0873bb3524 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Thu, 15 Jan 2026 00:02:35 -0800 Subject: [PATCH] fix --- codex-rs/tui/src/bottom_pane/chat_composer.rs | 40 ++++++++++++++++++- .../tui2/src/bottom_pane/chat_composer.rs | 40 ++++++++++++++++++- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 0e6c887523..387450c904 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -124,11 +124,25 @@ impl SlashPopupContext { /// Return the prefix used to filter slash commands. fn popup_prefix(&self) -> &str { - let prefix_end = if self.cursor_on_first_line() { - self.cursor.max(1).min(self.first_line_end) + let mut prefix_end = if self.cursor_on_first_line() { + let cursor = if self.cursor == 0 && self.text.starts_with('/') { + 1 + } else { + self.cursor + }; + cursor.min(self.first_line_end) } else { self.first_line_end }; + if prefix_end < self.text.len() && !self.text.is_char_boundary(prefix_end) { + prefix_end = self + .text + .char_indices() + .map(|(i, _)| i) + .take_while(|&i| i <= prefix_end) + .last() + .unwrap_or(0); + } &self.text[..prefix_end] } } @@ -4535,6 +4549,28 @@ mod tests { ); } + #[test] + fn slash_popup_ignores_non_ascii_prefix_at_start() { + use tokio::sync::mpsc::unbounded_channel; + + let (tx, _rx) = unbounded_channel::(); + let sender = AppEventSender::new(tx); + let mut composer = ChatComposer::new( + true, + sender, + false, + "Ask Codex to do anything".to_string(), + false, + ); + + composer.set_text_content("あ".to_string()); + + assert!( + matches!(composer.active_popup, ActivePopup::None), + "non-ASCII prefix should not activate slash popup" + ); + } + #[test] fn slash_popup_activated_for_prefix_with_args() { let (tx, _rx) = unbounded_channel::(); diff --git a/codex-rs/tui2/src/bottom_pane/chat_composer.rs b/codex-rs/tui2/src/bottom_pane/chat_composer.rs index c76fabb21b..f32a4327b3 100644 --- a/codex-rs/tui2/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui2/src/bottom_pane/chat_composer.rs @@ -127,11 +127,25 @@ impl SlashPopupContext { /// Return the prefix used to filter slash commands. fn popup_prefix(&self) -> &str { - let prefix_end = if self.cursor_on_first_line() { - self.cursor.max(1).min(self.first_line_end) + let mut prefix_end = if self.cursor_on_first_line() { + let cursor = if self.cursor == 0 && self.text.starts_with('/') { + 1 + } else { + self.cursor + }; + cursor.min(self.first_line_end) } else { self.first_line_end }; + if prefix_end < self.text.len() && !self.text.is_char_boundary(prefix_end) { + prefix_end = self + .text + .char_indices() + .map(|(i, _)| i) + .take_while(|&i| i <= prefix_end) + .last() + .unwrap_or(0); + } &self.text[..prefix_end] } } @@ -4393,6 +4407,28 @@ mod tests { ); } + #[test] + fn slash_popup_ignores_non_ascii_prefix_at_start() { + use tokio::sync::mpsc::unbounded_channel; + + let (tx, _rx) = unbounded_channel::(); + let sender = AppEventSender::new(tx); + let mut composer = ChatComposer::new( + true, + sender, + false, + "Ask Codex to do anything".to_string(), + false, + ); + + composer.set_text_content("あ".to_string()); + + assert!( + matches!(composer.active_popup, ActivePopup::None), + "non-ASCII prefix should not activate slash popup" + ); + } + #[test] fn slash_popup_activated_for_prefix_with_args() { use tokio::sync::mpsc::unbounded_channel;