@@ -13,7 +13,10 @@ use rustyline::highlight::{
1313 Highlighter ,
1414} ;
1515use rustyline:: hint:: Hinter as RustylineHinter ;
16- use rustyline:: history:: DefaultHistory ;
16+ use rustyline:: history:: {
17+ FileHistory ,
18+ SearchDirection ,
19+ } ;
1720use rustyline:: validate:: {
1821 ValidationContext ,
1922 ValidationResult ,
@@ -45,6 +48,8 @@ use super::tool_manager::{
4548use crate :: database:: settings:: Setting ;
4649use crate :: os:: Os ;
4750
51+ pub const CLI_HISTORY_PATH : & str = ".aws/amazonq/.cli_bash_history" ;
52+
4853pub const COMMANDS : & [ & str ] = & [
4954 "/clear" ,
5055 "/help" ,
@@ -262,31 +267,18 @@ impl Completer for ChatCompleter {
262267
263268/// Custom hinter that provides shadowtext suggestions
264269pub struct ChatHinter {
265- /// Command history for providing suggestions based on past commands
266- history : Vec < String > ,
267270 /// Whether history-based hints are enabled
268271 history_hints_enabled : bool ,
269272}
270273
271274impl ChatHinter {
272275 /// Creates a new ChatHinter instance
273276 pub fn new ( history_hints_enabled : bool ) -> Self {
274- Self {
275- history : Vec :: new ( ) ,
276- history_hints_enabled,
277- }
277+ Self { history_hints_enabled }
278278 }
279279
280- /// Updates the history with a new command
281- pub fn update_history ( & mut self , command : & str ) {
282- let command = command. trim ( ) ;
283- if !command. is_empty ( ) && !command. contains ( '\n' ) && !command. contains ( '\r' ) {
284- self . history . push ( command. to_string ( ) ) ;
285- }
286- }
287-
288- /// Finds the best hint for the current input
289- fn find_hint ( & self , line : & str ) -> Option < String > {
280+ /// Finds the best hint for the current input using rustyline's history
281+ fn find_hint ( & self , line : & str , ctx : & Context < ' _ > ) -> Option < String > {
290282 // If line is empty, no hint
291283 if line. is_empty ( ) {
292284 return None ;
@@ -300,13 +292,20 @@ impl ChatHinter {
300292 . map ( |cmd| cmd[ line. len ( ) ..] . to_string ( ) ) ;
301293 }
302294
303- // Try to find a hint from history if history hints are enabled
295+ // Try to find a hint from rustyline's history if history hints are enabled
304296 if self . history_hints_enabled {
305- return self . history
306- . iter ( )
307- . rev ( ) // Start from most recent
308- . find ( |cmd| cmd. starts_with ( line) && cmd. len ( ) > line. len ( ) )
309- . map ( |cmd| cmd[ line. len ( ) ..] . to_string ( ) ) ;
297+ let history = ctx. history ( ) ;
298+ let history_len = history. len ( ) ;
299+ if history_len == 0 {
300+ return None ;
301+ }
302+
303+ if let Ok ( Some ( search_result) ) = history. starts_with ( line, history_len - 1 , SearchDirection :: Reverse ) {
304+ let entry = search_result. entry . to_string ( ) ;
305+ if entry. len ( ) > line. len ( ) {
306+ return Some ( entry[ line. len ( ) ..] . to_string ( ) ) ;
307+ }
308+ }
310309 }
311310
312311 None
@@ -316,13 +315,13 @@ impl ChatHinter {
316315impl RustylineHinter for ChatHinter {
317316 type Hint = String ;
318317
319- fn hint ( & self , line : & str , pos : usize , _ctx : & Context < ' _ > ) -> Option < Self :: Hint > {
318+ fn hint ( & self , line : & str , pos : usize , ctx : & Context < ' _ > ) -> Option < Self :: Hint > {
320319 // Only provide hints when cursor is at the end of the line
321320 if pos < line. len ( ) {
322321 return None ;
323322 }
324323
325- self . find_hint ( line)
324+ self . find_hint ( line, ctx )
326325 }
327326}
328327
@@ -362,13 +361,6 @@ pub struct ChatHelper {
362361 validator : MultiLineValidator ,
363362}
364363
365- impl ChatHelper {
366- /// Updates the history of the ChatHinter with a new command
367- pub fn update_hinter_history ( & mut self , command : & str ) {
368- self . hinter . update_history ( command) ;
369- }
370- }
371-
372364impl Validator for ChatHelper {
373365 fn validate ( & self , os : & mut ValidationContext < ' _ > ) -> rustyline:: Result < ValidationResult > {
374366 self . validator . validate ( os)
@@ -426,7 +418,7 @@ pub fn rl(
426418 os : & Os ,
427419 sender : PromptQuerySender ,
428420 receiver : PromptQueryResponseReceiver ,
429- ) -> Result < Editor < ChatHelper , DefaultHistory > > {
421+ ) -> Result < Editor < ChatHelper , FileHistory > > {
430422 let edit_mode = match os. database . settings . get_string ( Setting :: ChatEditMode ) . as_deref ( ) {
431423 Some ( "vi" | "vim" ) => EditMode :: Vi ,
432424 _ => EditMode :: Emacs ,
@@ -437,7 +429,6 @@ pub fn rl(
437429 . edit_mode ( edit_mode)
438430 . build ( ) ;
439431
440- // Default to disabled if setting doesn't exist
441432 let history_hints_enabled = os
442433 . database
443434 . settings
@@ -452,6 +443,17 @@ pub fn rl(
452443 let mut rl = Editor :: with_config ( config) ?;
453444 rl. set_helper ( Some ( h) ) ;
454445
446+ // Load history from ~/.aws/amazonq/cli_history
447+ let history_path = dirs:: home_dir ( )
448+ . ok_or_else ( || eyre:: eyre!( "Could not find home directory" ) ) ?
449+ . join ( CLI_HISTORY_PATH ) ;
450+
451+ if let Err ( e) = rl. load_history ( & history_path) {
452+ if !matches ! ( e, ReadlineError :: Io ( ref io_err) if io_err. kind( ) == std:: io:: ErrorKind :: NotFound ) {
453+ eprintln ! ( "Warning: Failed to load history: {}" , e) ;
454+ }
455+ }
456+
455457 // Add custom keybinding for Alt+Enter to insert a newline
456458 rl. bind_sequence (
457459 KeyEvent ( KeyCode :: Enter , Modifiers :: ALT ) ,
@@ -487,6 +489,7 @@ pub fn rl(
487489mod tests {
488490 use crossterm:: style:: Stylize ;
489491 use rustyline:: highlight:: Highlighter ;
492+ use rustyline:: history:: DefaultHistory ;
490493
491494 use super :: * ;
492495
@@ -694,11 +697,7 @@ mod tests {
694697
695698 #[ test]
696699 fn test_chat_hinter_history_hint_disabled ( ) {
697- let mut hinter = ChatHinter :: new ( false ) ;
698-
699- // Add some history
700- hinter. update_history ( "Hello, world!" ) ;
701- hinter. update_history ( "How are you?" ) ;
700+ let hinter = ChatHinter :: new ( false ) ;
702701
703702 // Test hint from history - should be None since history hints are disabled
704703 let line = "How" ;
0 commit comments