@@ -98,6 +98,7 @@ impl CommandPopup {
9898 /// to narrow down the list of available commands.
9999 pub ( crate ) fn on_composer_text_change ( & mut self , text : String ) {
100100 let first_line = text. lines ( ) . next ( ) . unwrap_or ( "" ) ;
101+ let previous_filter = self . command_filter . clone ( ) ;
101102
102103 if let Some ( stripped) = first_line. strip_prefix ( '/' ) {
103104 // Extract the *first* token (sequence of non-whitespace
@@ -118,6 +119,9 @@ impl CommandPopup {
118119
119120 // Reset or clamp selected index based on new filtered list.
120121 let matches_len = self . filtered_items ( ) . len ( ) ;
122+ if self . command_filter != previous_filter {
123+ self . state . reset ( ) ;
124+ }
121125 self . state . clamp_selection ( matches_len) ;
122126 self . state
123127 . ensure_visible ( matches_len, MAX_POPUP_ROWS . min ( matches_len) ) ;
@@ -407,6 +411,26 @@ mod tests {
407411 ) ;
408412 }
409413
414+ #[ test]
415+ fn changing_filter_resets_selection_after_scrolling ( ) {
416+ let mut popup = CommandPopup :: new ( CommandPopupFlags :: default ( ) , Vec :: new ( ) ) ;
417+ popup. on_composer_text_change ( "/" . to_string ( ) ) ;
418+ for _ in 0 ..20 {
419+ popup. move_down ( ) ;
420+ }
421+
422+ popup. on_composer_text_change ( "/st" . to_string ( ) ) ;
423+
424+ match popup. selected_item ( ) {
425+ Some ( CommandItem :: Builtin ( cmd) ) => assert_eq ! ( cmd. command( ) , "status" ) ,
426+ Some ( CommandItem :: ServiceTier ( command) ) => {
427+ panic ! ( "expected status command, got service tier {command:?}" )
428+ }
429+ None => panic ! ( "expected a selected command for '/st'" ) ,
430+ }
431+ assert_eq ! ( popup. state. scroll_top, 0 ) ;
432+ }
433+
410434 #[ test]
411435 fn quit_hidden_in_empty_filter_but_shown_for_prefix ( ) {
412436 let mut popup = CommandPopup :: new ( CommandPopupFlags :: default ( ) , Vec :: new ( ) ) ;
0 commit comments