@@ -97,6 +97,30 @@ impl PasteState {
9797 }
9898}
9999
100+ /// Shared state to track when Enter was pressed with unclosed backticks
101+ #[ derive( Clone , Debug ) ]
102+ pub struct MultilineHintState {
103+ inner : Arc < Mutex < bool > > ,
104+ }
105+
106+ impl MultilineHintState {
107+ pub fn new ( ) -> Self {
108+ Self {
109+ inner : Arc :: new ( Mutex :: new ( false ) ) ,
110+ }
111+ }
112+
113+ pub fn set ( & self , value : bool ) {
114+ let mut inner = self . inner . lock ( ) . unwrap ( ) ;
115+ * inner = value;
116+ }
117+
118+ pub fn get ( & self ) -> bool {
119+ let inner = self . inner . lock ( ) . unwrap ( ) ;
120+ * inner
121+ }
122+ }
123+
100124pub const COMMANDS : & [ & str ] = & [
101125 "/clear" ,
102126 "/help" ,
@@ -330,15 +354,23 @@ pub struct ChatHinter {
330354 history_hints_enabled : bool ,
331355 history_path : PathBuf ,
332356 available_commands : Vec < & ' static str > ,
357+ /// Shared state to track when to show multiline hint
358+ multiline_hint_state : MultilineHintState ,
333359}
334360
335361impl ChatHinter {
336362 /// Creates a new ChatHinter instance
337- pub fn new ( history_hints_enabled : bool , history_path : PathBuf , available_commands : Vec < & ' static str > ) -> Self {
363+ pub fn new (
364+ history_hints_enabled : bool ,
365+ history_path : PathBuf ,
366+ available_commands : Vec < & ' static str > ,
367+ multiline_hint_state : MultilineHintState ,
368+ ) -> Self {
338369 Self {
339370 history_hints_enabled,
340371 history_path,
341372 available_commands,
373+ multiline_hint_state,
342374 }
343375 }
344376
@@ -353,6 +385,16 @@ impl ChatHinter {
353385 return None ;
354386 }
355387
388+ // Check if we should show the multiline hint (after Enter was pressed with unclosed backticks)
389+ if self . multiline_hint_state . get ( ) && line. contains ( "```" ) {
390+ let triple_backtick_count = line. matches ( "```" ) . count ( ) ;
391+ if triple_backtick_count % 2 == 1 {
392+ // Clear the state after showing the hint once
393+ self . multiline_hint_state . set ( false ) ;
394+ return Some ( "in multiline mode, waiting for closing backticks ```" . to_string ( ) ) ;
395+ }
396+ }
397+
356398 // If line starts with a slash, try to find a command hint
357399 if line. starts_with ( '/' ) {
358400 return self
@@ -396,7 +438,15 @@ impl RustylineHinter for ChatHinter {
396438}
397439
398440/// Custom validator for multi-line input
399- pub struct MultiLineValidator ;
441+ pub struct MultiLineValidator {
442+ multiline_hint_state : MultilineHintState ,
443+ }
444+
445+ impl MultiLineValidator {
446+ pub fn new ( multiline_hint_state : MultilineHintState ) -> Self {
447+ Self { multiline_hint_state }
448+ }
449+ }
400450
401451impl Validator for MultiLineValidator {
402452 fn validate ( & self , os : & mut ValidationContext < ' _ > ) -> rustyline:: Result < ValidationResult > {
@@ -408,7 +458,9 @@ impl Validator for MultiLineValidator {
408458 let triple_backtick_count = input. matches ( "```" ) . count ( ) ;
409459
410460 // If we have an odd number of ```, we're in an incomplete code block
461+ // When user presses Enter, set the state to show the hint on next render
411462 if triple_backtick_count % 2 == 1 {
463+ self . multiline_hint_state . set ( true ) ;
412464 return Ok ( ValidationResult :: Incomplete ) ;
413465 }
414466 }
@@ -550,6 +602,43 @@ impl rustyline::ConditionalEventHandler for PasteImageHandler {
550602 }
551603}
552604
605+ /// Handler for right arrow key that prevents completing hints when in multiline mode
606+ ///
607+ /// This handler intercepts the right arrow key press to prevent accidentally completing
608+ /// status hints (like "in multiline mode, waiting for closing backticks ```") that appear
609+ /// when the user presses Enter with unclosed triple backticks.
610+ ///
611+ /// When unclosed backticks are detected (odd count of ```), pressing right arrow will:
612+ /// - Just move the cursor forward one character (normal behavior)
613+ /// - NOT complete/accept the hint text
614+ ///
615+ /// When no unclosed backticks exist, it returns None to allow default behavior.
616+ struct RightArrowHandler ;
617+
618+ impl rustyline:: ConditionalEventHandler for RightArrowHandler {
619+ fn handle (
620+ & self ,
621+ _evt : & rustyline:: Event ,
622+ _n : rustyline:: RepeatCount ,
623+ _positive : bool ,
624+ ctx : & rustyline:: EventContext < ' _ > ,
625+ ) -> Option < Cmd > {
626+ let line = ctx. line ( ) ;
627+
628+ // Check if we're in multiline mode with unclosed backticks
629+ if line. contains ( "```" ) {
630+ let triple_backtick_count = line. matches ( "```" ) . count ( ) ;
631+ if triple_backtick_count % 2 == 1 {
632+ // We're in multiline mode - don't complete the hint
633+ // Just move the cursor forward instead
634+ return Some ( Cmd :: Move ( rustyline:: Movement :: ForwardChar ( 1 ) ) ) ;
635+ }
636+ }
637+
638+ None
639+ }
640+ }
641+
553642pub fn rl (
554643 os : & Os ,
555644 sender : PromptQuerySender ,
@@ -577,10 +666,18 @@ pub fn rl(
577666 // Generate available commands based on enabled experiments
578667 let available_commands = get_available_commands ( os) ;
579668
669+ // Create shared state for multiline hint
670+ let multiline_hint_state = MultilineHintState :: new ( ) ;
671+
580672 let h = ChatHelper {
581673 completer : ChatCompleter :: new ( sender, receiver, available_commands. clone ( ) ) ,
582- hinter : ChatHinter :: new ( history_hints_enabled, history_path, available_commands) ,
583- validator : MultiLineValidator ,
674+ hinter : ChatHinter :: new (
675+ history_hints_enabled,
676+ history_path,
677+ available_commands,
678+ multiline_hint_state. clone ( ) ,
679+ ) ,
680+ validator : MultiLineValidator :: new ( multiline_hint_state) ,
584681 } ;
585682
586683 let mut rl = Editor :: with_config ( config) ?;
@@ -643,6 +740,12 @@ pub fn rl(
643740 EventHandler :: Conditional ( Box :: new ( PasteImageHandler :: new ( paste_state) ) ) ,
644741 ) ;
645742
743+ // Override right arrow key to prevent completing multiline status hints
744+ rl. bind_sequence (
745+ KeyEvent ( KeyCode :: Right , Modifiers :: empty ( ) ) ,
746+ EventHandler :: Conditional ( Box :: new ( RightArrowHandler ) ) ,
747+ ) ;
748+
646749 Ok ( rl)
647750}
648751
@@ -712,14 +815,15 @@ mod tests {
712815 // Create a mock Os for testing
713816 let mock_os = crate :: os:: Os :: new ( ) . await . unwrap ( ) ;
714817 let available_commands = get_available_commands ( & mock_os) ;
818+ let multiline_hint_state = MultilineHintState :: new ( ) ;
715819 let helper = ChatHelper {
716820 completer : ChatCompleter :: new (
717821 prompt_request_sender,
718822 prompt_response_receiver,
719823 available_commands. clone ( ) ,
720824 ) ,
721- hinter : ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands) ,
722- validator : MultiLineValidator ,
825+ hinter : ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands, multiline_hint_state . clone ( ) ) ,
826+ validator : MultiLineValidator :: new ( multiline_hint_state ) ,
723827 } ;
724828
725829 // Test basic prompt highlighting
@@ -736,14 +840,15 @@ mod tests {
736840 // Create a mock Os for testing
737841 let mock_os = crate :: os:: Os :: new ( ) . await . unwrap ( ) ;
738842 let available_commands = get_available_commands ( & mock_os) ;
843+ let multiline_hint_state = MultilineHintState :: new ( ) ;
739844 let helper = ChatHelper {
740845 completer : ChatCompleter :: new (
741846 prompt_request_sender,
742847 prompt_response_receiver,
743848 available_commands. clone ( ) ,
744849 ) ,
745- hinter : ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands) ,
746- validator : MultiLineValidator ,
850+ hinter : ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands, multiline_hint_state . clone ( ) ) ,
851+ validator : MultiLineValidator :: new ( multiline_hint_state ) ,
747852 } ;
748853
749854 // Test warning prompt highlighting
@@ -763,14 +868,15 @@ mod tests {
763868 // Create a mock Os for testing
764869 let mock_os = crate :: os:: Os :: new ( ) . await . unwrap ( ) ;
765870 let available_commands = get_available_commands ( & mock_os) ;
871+ let multiline_hint_state = MultilineHintState :: new ( ) ;
766872 let helper = ChatHelper {
767873 completer : ChatCompleter :: new (
768874 prompt_request_sender,
769875 prompt_response_receiver,
770876 available_commands. clone ( ) ,
771877 ) ,
772- hinter : ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands) ,
773- validator : MultiLineValidator ,
878+ hinter : ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands, multiline_hint_state . clone ( ) ) ,
879+ validator : MultiLineValidator :: new ( multiline_hint_state ) ,
774880 } ;
775881
776882 // Test profile prompt highlighting
@@ -790,14 +896,15 @@ mod tests {
790896 // Create a mock Os for testing
791897 let mock_os = crate :: os:: Os :: new ( ) . await . unwrap ( ) ;
792898 let available_commands = get_available_commands ( & mock_os) ;
899+ let multiline_hint_state = MultilineHintState :: new ( ) ;
793900 let helper = ChatHelper {
794901 completer : ChatCompleter :: new (
795902 prompt_request_sender,
796903 prompt_response_receiver,
797904 available_commands. clone ( ) ,
798905 ) ,
799- hinter : ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands) ,
800- validator : MultiLineValidator ,
906+ hinter : ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands, multiline_hint_state . clone ( ) ) ,
907+ validator : MultiLineValidator :: new ( multiline_hint_state ) ,
801908 } ;
802909
803910 // Test profile + warning prompt highlighting
@@ -822,14 +929,15 @@ mod tests {
822929 // Create a mock Os for testing
823930 let mock_os = crate :: os:: Os :: new ( ) . await . unwrap ( ) ;
824931 let available_commands = get_available_commands ( & mock_os) ;
932+ let multiline_hint_state = MultilineHintState :: new ( ) ;
825933 let helper = ChatHelper {
826934 completer : ChatCompleter :: new (
827935 prompt_request_sender,
828936 prompt_response_receiver,
829937 available_commands. clone ( ) ,
830938 ) ,
831- hinter : ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands) ,
832- validator : MultiLineValidator ,
939+ hinter : ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands, multiline_hint_state . clone ( ) ) ,
940+ validator : MultiLineValidator :: new ( multiline_hint_state ) ,
833941 } ;
834942
835943 // Test invalid prompt format (should return as-is)
@@ -846,14 +954,15 @@ mod tests {
846954 // Create a mock Os for testing
847955 let mock_os = crate :: os:: Os :: new ( ) . await . unwrap ( ) ;
848956 let available_commands = get_available_commands ( & mock_os) ;
957+ let multiline_hint_state = MultilineHintState :: new ( ) ;
849958 let helper = ChatHelper {
850959 completer : ChatCompleter :: new (
851960 prompt_request_sender,
852961 prompt_response_receiver,
853962 available_commands. clone ( ) ,
854963 ) ,
855- hinter : ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands) ,
856- validator : MultiLineValidator ,
964+ hinter : ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands, multiline_hint_state . clone ( ) ) ,
965+ validator : MultiLineValidator :: new ( multiline_hint_state ) ,
857966 } ;
858967
859968 // Test tangent mode prompt highlighting - ↯ yellow, > magenta
@@ -872,14 +981,15 @@ mod tests {
872981 // Create a mock Os for testing
873982 let mock_os = crate :: os:: Os :: new ( ) . await . unwrap ( ) ;
874983 let available_commands = get_available_commands ( & mock_os) ;
984+ let multiline_hint_state = MultilineHintState :: new ( ) ;
875985 let helper = ChatHelper {
876986 completer : ChatCompleter :: new (
877987 prompt_request_sender,
878988 prompt_response_receiver,
879989 available_commands. clone ( ) ,
880990 ) ,
881- hinter : ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands) ,
882- validator : MultiLineValidator ,
991+ hinter : ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands, multiline_hint_state . clone ( ) ) ,
992+ validator : MultiLineValidator :: new ( multiline_hint_state ) ,
883993 } ;
884994
885995 // Test tangent mode with warning - ↯ yellow, ! red, > magenta
@@ -903,14 +1013,15 @@ mod tests {
9031013 // Create a mock Os for testing
9041014 let mock_os = crate :: os:: Os :: new ( ) . await . unwrap ( ) ;
9051015 let available_commands = get_available_commands ( & mock_os) ;
1016+ let multiline_hint_state = MultilineHintState :: new ( ) ;
9061017 let helper = ChatHelper {
9071018 completer : ChatCompleter :: new (
9081019 prompt_request_sender,
9091020 prompt_response_receiver,
9101021 available_commands. clone ( ) ,
9111022 ) ,
912- hinter : ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands) ,
913- validator : MultiLineValidator ,
1023+ hinter : ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands, multiline_hint_state . clone ( ) ) ,
1024+ validator : MultiLineValidator :: new ( multiline_hint_state ) ,
9141025 } ;
9151026
9161027 // Test profile with tangent mode - [dev] cyan, ↯ yellow, > magenta
@@ -931,7 +1042,8 @@ mod tests {
9311042 // Create a mock Os for testing
9321043 let mock_os = crate :: os:: Os :: new ( ) . await . unwrap ( ) ;
9331044 let available_commands = get_available_commands ( & mock_os) ;
934- let hinter = ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands) ;
1045+ let multiline_hint_state = MultilineHintState :: new ( ) ;
1046+ let hinter = ChatHinter :: new ( true , PathBuf :: new ( ) , available_commands, multiline_hint_state) ;
9351047
9361048 // Test hint for a command
9371049 let line = "/he" ;
@@ -964,7 +1076,8 @@ mod tests {
9641076 // Create a mock Os for testing
9651077 let mock_os = crate :: os:: Os :: new ( ) . await . unwrap ( ) ;
9661078 let available_commands = get_available_commands ( & mock_os) ;
967- let hinter = ChatHinter :: new ( false , PathBuf :: new ( ) , available_commands) ;
1079+ let multiline_hint_state = MultilineHintState :: new ( ) ;
1080+ let hinter = ChatHinter :: new ( false , PathBuf :: new ( ) , available_commands, multiline_hint_state) ;
9681081
9691082 // Test hint from history - should be None since history hints are disabled
9701083 let line = "How" ;
0 commit comments