@@ -269,6 +269,7 @@ struct App {
269269 input_saved : String ,
270270
271271 at_autocomplete_selected : usize ,
272+ slash_autocomplete_selected : usize ,
272273
273274 update_rx : Option < mpsc:: Receiver < String > > ,
274275
@@ -502,6 +503,7 @@ fn main() -> io::Result<()> {
502503 input_history_index : None ,
503504 input_saved : String :: new ( ) ,
504505 at_autocomplete_selected : 0 ,
506+ slash_autocomplete_selected : 0 ,
505507 update_rx : None ,
506508 suggestions : Vec :: new ( ) ,
507509 suggestions_selected : 0 ,
@@ -1148,7 +1150,78 @@ fn input_has_at_prefix(input: &str, cursor: usize) -> bool {
11481150 active_at_query ( input, cursor) . is_some ( )
11491151}
11501152
1153+ fn active_slash_query ( input : & str , cursor : usize ) -> Option < String > {
1154+ let chars: Vec < char > = input. chars ( ) . collect ( ) ;
1155+ let end = cursor. min ( chars. len ( ) ) ;
1156+ let mut start = end;
1157+ while start > 0 && chars[ start - 1 ] != ' ' {
1158+ start -= 1 ;
1159+ }
1160+ if start < end && chars[ start] == '/' {
1161+ Some ( chars[ start + 1 ..end] . iter ( ) . collect ( ) )
1162+ } else {
1163+ None
1164+ }
1165+ }
1166+
1167+ fn slash_completions ( input : & str , cursor : usize ) -> Vec < ( & ' static str , & ' static str ) > {
1168+ let query = match active_slash_query ( input, cursor) {
1169+ Some ( q) => q,
1170+ None => return Vec :: new ( ) ,
1171+ } ;
1172+ let all_commands: & [ ( & str , & str ) ] = & [
1173+ ( "clear" , "Clear AI chat history" ) ,
1174+ ( "buckets" , "List all buckets" ) ,
1175+ ( "bucket add" , "Add a new bucket" ) ,
1176+ ( "bucket rename" , "Rename a bucket" ) ,
1177+ ( "organize" , "AI restructures all tasks" ) ,
1178+ ( "exit" , "Quit the app" ) ,
1179+ ] ;
1180+ let query_lower = query. to_lowercase ( ) ;
1181+ all_commands
1182+ . iter ( )
1183+ . filter ( |( cmd, _) | cmd. starts_with ( & query_lower) || query_lower. is_empty ( ) )
1184+ . copied ( )
1185+ . collect ( )
1186+ }
1187+
11511188fn handle_input_key ( app : & mut App , key : KeyEvent ) -> io:: Result < bool > {
1189+ // / command autocomplete interception.
1190+ let slash_comps = slash_completions ( & app. input , app. input_cursor ) ;
1191+ if !slash_comps. is_empty ( ) {
1192+ match key. code {
1193+ KeyCode :: Up => {
1194+ if app. slash_autocomplete_selected == 0 {
1195+ app. slash_autocomplete_selected = slash_comps. len ( ) - 1 ;
1196+ } else {
1197+ app. slash_autocomplete_selected -= 1 ;
1198+ }
1199+ return Ok ( false ) ;
1200+ }
1201+ KeyCode :: Down => {
1202+ app. slash_autocomplete_selected =
1203+ ( app. slash_autocomplete_selected + 1 ) % slash_comps. len ( ) ;
1204+ return Ok ( false ) ;
1205+ }
1206+ KeyCode :: Enter | KeyCode :: Tab => {
1207+ let sel = app
1208+ . slash_autocomplete_selected
1209+ . min ( slash_comps. len ( ) . saturating_sub ( 1 ) ) ;
1210+ let ( cmd, _) = slash_comps[ sel] ;
1211+ let replacement = format ! ( "/{}" , cmd) ;
1212+ let ( new_input, new_cursor) =
1213+ replace_at_token ( & app. input , app. input_cursor , & replacement) ;
1214+ app. input = new_input;
1215+ app. input_cursor = new_cursor;
1216+ app. slash_autocomplete_selected = 0 ;
1217+ return Ok ( false ) ;
1218+ }
1219+ _ => {
1220+ app. slash_autocomplete_selected = 0 ;
1221+ }
1222+ }
1223+ }
1224+
11521225 // @ autocomplete interception.
11531226 let completions = at_completions ( & app. tasks , & app. input , app. input_cursor ) ;
11541227 if !completions. is_empty ( ) {
@@ -4665,6 +4738,41 @@ fn render_input_bar(stdout: &mut Stdout, app: &App, cols: u16, rows: u16) -> io:
46654738 }
46664739 }
46674740 }
4741+ } else {
4742+ let slash_comps = slash_completions ( & app. input , app. input_cursor ) ;
4743+ if !slash_comps. is_empty ( ) {
4744+ const MAX_SHOW : usize = 8 ;
4745+ let show = slash_comps. len ( ) . min ( MAX_SHOW ) ;
4746+ let sel = app
4747+ . slash_autocomplete_selected
4748+ . min ( slash_comps. len ( ) . saturating_sub ( 1 ) ) ;
4749+ let scroll = if sel >= show { sel - show + 1 } else { 0 } ;
4750+ for ( draw_i, ( cmd, desc) ) in slash_comps. iter ( ) . enumerate ( ) . skip ( scroll) . take ( show)
4751+ {
4752+ let row_from_bottom = show - ( draw_i - scroll) - 1 ;
4753+ let y_row = y_sep_top - 1 - row_from_bottom as u16 ;
4754+ let label = format ! ( " /{} — {}" , cmd, desc) ;
4755+ let padded = pad_to_width ( & clamp_text ( & label, content_width) , content_width) ;
4756+ queue ! ( stdout, MoveTo ( x, y_row) ) ?;
4757+ if draw_i == sel {
4758+ queue ! (
4759+ stdout,
4760+ SetForegroundColor ( Color :: Black ) ,
4761+ SetBackgroundColor ( Color :: White ) ,
4762+ Print ( & padded) ,
4763+ ResetColor
4764+ ) ?;
4765+ } else {
4766+ queue ! (
4767+ stdout,
4768+ SetForegroundColor ( Color :: White ) ,
4769+ SetBackgroundColor ( Color :: DarkGrey ) ,
4770+ Print ( & padded) ,
4771+ ResetColor
4772+ ) ?;
4773+ }
4774+ }
4775+ }
46684776 }
46694777 }
46704778
0 commit comments