@@ -2,6 +2,7 @@ pub mod cli;
22mod consts;
33pub mod context;
44mod conversation;
5+ mod custom_spinner;
56mod input_source;
67mod message;
78mod parse;
@@ -36,14 +37,14 @@ pub use conversation::ConversationState;
3637use conversation:: TokenWarningLevel ;
3738use crossterm:: style:: { Attribute , Color , Stylize } ;
3839use crossterm:: { cursor, execute, queue, style, terminal} ;
40+ use custom_spinner:: Spinners ;
3941use eyre:: { Report , Result , bail, eyre} ;
4042use input_source:: InputSource ;
4143use message:: { AssistantMessage , AssistantToolUse , ToolUseResult , ToolUseResultBlock } ;
4244use parse:: { ParseState , interpret_markdown} ;
4345use parser:: { RecvErrorKind , RequestMetadata , SendMessageStream } ;
4446use regex:: Regex ;
4547use rmcp:: model:: PromptMessage ;
46- use spinners:: { Spinner , Spinners } ;
4748use thiserror:: Error ;
4849use time:: OffsetDateTime ;
4950use token_counter:: TokenCounter ;
@@ -169,7 +170,6 @@ impl ChatArgs {
169170 }
170171 }
171172
172- let stdout = std:: io:: stdout ( ) ;
173173 let mut stderr = std:: io:: stderr ( ) ;
174174
175175 let args: Vec < String > = std:: env:: args ( ) . collect ( ) ;
@@ -336,8 +336,6 @@ impl ChatArgs {
336336
337337 ChatSession :: new (
338338 os,
339- stdout,
340- stderr,
341339 & conversation_id,
342340 agents,
343341 input,
@@ -540,7 +538,7 @@ pub struct ChatSession {
540538 input_source : InputSource ,
541539 /// Width of the terminal, required for [ParseState].
542540 terminal_width_provider : fn ( ) -> Option < usize > ,
543- spinner : Option < Spinner > ,
541+ spinner : Option < Spinners > ,
544542 /// [ConversationState].
545543 conversation : ConversationState ,
546544 /// Tool uses requested by the model that are actively being handled.
@@ -572,8 +570,6 @@ impl ChatSession {
572570 #[ allow( clippy:: too_many_arguments) ]
573571 pub async fn new (
574572 os : & mut Os ,
575- stdout : std:: io:: Stdout ,
576- mut stderr : std:: io:: Stderr ,
577573 conversation_id : & str ,
578574 mut agents : Agents ,
579575 mut input : Option < String > ,
@@ -594,7 +590,7 @@ impl ChatSession {
594590 . and_then ( |cwd| os. database . get_conversation_by_path ( cwd) . ok ( ) )
595591 . flatten ( ) ;
596592
597- let ( view_end, _byte_receiver, control_end_stderr, control_end_stdout) = get_legacy_conduits ( ) ;
593+ let ( view_end, _byte_receiver, mut control_end_stderr, control_end_stdout) = get_legacy_conduits ( ) ;
598594
599595 tokio:: task:: spawn_blocking ( move || {
600596 let stderr = std:: io:: stderr ( ) ;
@@ -619,7 +615,7 @@ impl ChatSession {
619615 if let Some ( profile) = cs. current_profile ( ) {
620616 if agents. switch ( profile) . is_err ( ) {
621617 execute ! (
622- stderr ,
618+ control_end_stderr ,
623619 style:: SetForegroundColor ( Color :: Red ) ,
624620 style:: Print ( "Error" ) ,
625621 style:: ResetColor ,
@@ -1086,10 +1082,6 @@ impl ChatSession {
10861082
10871083impl Drop for ChatSession {
10881084 fn drop ( & mut self ) {
1089- if let Some ( spinner) = & mut self . spinner {
1090- spinner. stop ( ) ;
1091- }
1092-
10931085 execute ! (
10941086 self . stderr,
10951087 cursor:: MoveToColumn ( 0 ) ,
@@ -1386,7 +1378,7 @@ impl ChatSession {
13861378
13871379 if self . interactive {
13881380 execute ! ( self . stderr, cursor:: Hide , style:: Print ( "\n " ) ) ?;
1389- self . spinner = Some ( Spinner :: new ( Spinners :: Dots , "Creating summary..." . to_string ( ) ) ) ;
1381+ self . spinner = Some ( Spinners :: new ( "Creating summary..." . to_string ( ) ) ) ;
13901382 }
13911383
13921384 let mut response = match self
@@ -1681,10 +1673,10 @@ impl ChatSession {
16811673
16821674 if self . interactive {
16831675 execute ! ( self . stderr, cursor:: Hide , style:: Print ( "\n " ) ) ?;
1684- self . spinner = Some ( Spinner :: new (
1685- Spinners :: Dots ,
1686- format ! ( "Generating agent config for '{}'..." , agent_name) ,
1687- ) ) ;
1676+ self . spinner = Some ( Spinners :: new ( format ! (
1677+ "Generating agent config for '{}'..." ,
1678+ agent_name
1679+ ) ) ) ;
16881680 }
16891681
16901682 let mut response = match self
@@ -2132,7 +2124,7 @@ impl ChatSession {
21322124 queue ! ( self . stderr, cursor:: Hide ) ?;
21332125
21342126 if self . interactive {
2135- self . spinner = Some ( Spinner :: new ( Spinners :: Dots , "Thinking..." . to_owned ( ) ) ) ;
2127+ self . spinner = Some ( Spinners :: new ( "Thinking..." . to_owned ( ) ) ) ;
21362128 }
21372129
21382130 Ok ( ChatState :: HandleResponseStream ( conv_state) )
@@ -2537,7 +2529,7 @@ impl ChatSession {
25372529 execute ! ( self . stderr, cursor:: Hide ) ?;
25382530 execute ! ( self . stderr, style:: Print ( "\n " ) , style:: SetAttribute ( Attribute :: Reset ) ) ?;
25392531 if self . interactive {
2540- self . spinner = Some ( Spinner :: new ( Spinners :: Dots , "Thinking..." . to_string ( ) ) ) ;
2532+ self . spinner = Some ( Spinners :: new ( "Thinking..." . to_string ( ) ) ) ;
25412533 }
25422534
25432535 self . send_chat_telemetry ( os, TelemetryResult :: Succeeded , None , None , None , false )
@@ -2685,7 +2677,7 @@ impl ChatSession {
26852677 ) ;
26862678
26872679 execute ! ( self . stderr, cursor:: Hide ) ?;
2688- self . spinner = Some ( Spinner :: new ( Spinners :: Dots , "Dividing up the work..." . to_string ( ) ) ) ;
2680+ self . spinner = Some ( Spinners :: new ( "Dividing up the work..." . to_string ( ) ) ) ;
26892681
26902682 // For stream timeouts, we'll tell the model to try and split its response into
26912683 // smaller chunks.
@@ -2859,7 +2851,7 @@ impl ChatSession {
28592851 if tool_name_being_recvd. is_some ( ) {
28602852 queue ! ( self . stderr, cursor:: Hide ) ?;
28612853 if self . interactive {
2862- self . spinner = Some ( Spinner :: new ( Spinners :: Dots , "Thinking..." . to_string ( ) ) ) ;
2854+ self . spinner = Some ( Spinners :: new ( "Thinking..." . to_string ( ) ) ) ;
28632855 }
28642856 }
28652857
@@ -3176,7 +3168,7 @@ impl ChatSession {
31763168 }
31773169
31783170 if self . interactive {
3179- self . spinner = Some ( Spinner :: new ( Spinners :: Dots , "Thinking..." . to_owned ( ) ) ) ;
3171+ self . spinner = Some ( Spinners :: new ( "Thinking..." . to_owned ( ) ) ) ;
31803172 }
31813173
31823174 Ok ( ChatState :: HandleResponseStream (
@@ -3591,32 +3583,34 @@ async fn get_subscription_status(os: &mut Os) -> Result<ActualSubscriptionStatus
35913583
35923584async fn get_subscription_status_with_spinner (
35933585 os : & mut Os ,
3594- output : & mut impl Write ,
3586+ output : & mut ( impl Write + Clone + Send + Sync + ' static ) ,
35953587) -> Result < ActualSubscriptionStatus > {
35963588 return with_spinner ( output, "Checking subscription status..." , || async {
35973589 get_subscription_status ( os) . await
35983590 } )
35993591 . await ;
36003592}
36013593
3602- pub async fn with_spinner < T , E , F , Fut > ( output : & mut impl std:: io:: Write , spinner_text : & str , f : F ) -> Result < T , E >
3594+ pub async fn with_spinner < T , E , F , Fut , S : std:: io:: Write + Clone + Send + Sync + ' static > (
3595+ output : & mut S ,
3596+ spinner_text : & str ,
3597+ f : F ,
3598+ ) -> Result < T , E >
36033599where
36043600 F : FnOnce ( ) -> Fut ,
36053601 Fut : std:: future:: Future < Output = Result < T , E > > ,
36063602{
36073603 queue ! ( output, cursor:: Hide , ) . ok ( ) ;
3608- let spinner = Some ( Spinner :: new ( Spinners :: Dots , spinner_text. to_owned ( ) ) ) ;
3604+ let spinner = Spinners :: new ( spinner_text. to_owned ( ) ) ;
36093605
36103606 let result = f ( ) . await ;
36113607
3612- if let Some ( mut s) = spinner {
3613- s. stop ( ) ;
3614- let _ = queue ! (
3615- output,
3616- terminal:: Clear ( terminal:: ClearType :: CurrentLine ) ,
3617- cursor:: MoveToColumn ( 0 ) ,
3618- ) ;
3619- }
3608+ drop ( spinner) ;
3609+ let _ = queue ! (
3610+ output,
3611+ terminal:: Clear ( terminal:: ClearType :: CurrentLine ) ,
3612+ cursor:: MoveToColumn ( 0 ) ,
3613+ ) ;
36203614
36213615 result
36223616}
@@ -3727,8 +3721,6 @@ mod tests {
37273721 . expect ( "Tools failed to load" ) ;
37283722 ChatSession :: new (
37293723 & mut os,
3730- std:: io:: stdout ( ) ,
3731- std:: io:: stderr ( ) ,
37323724 "fake_conv_id" ,
37333725 agents,
37343726 None ,
@@ -3857,8 +3849,6 @@ mod tests {
38573849 . expect ( "Tools failed to load" ) ;
38583850 ChatSession :: new (
38593851 & mut os,
3860- std:: io:: stdout ( ) ,
3861- std:: io:: stderr ( ) ,
38623852 "fake_conv_id" ,
38633853 agents,
38643854 None ,
@@ -3964,8 +3954,6 @@ mod tests {
39643954 . expect ( "Tools failed to load" ) ;
39653955 ChatSession :: new (
39663956 & mut os,
3967- std:: io:: stdout ( ) ,
3968- std:: io:: stderr ( ) ,
39693957 "fake_conv_id" ,
39703958 agents,
39713959 None ,
@@ -4042,8 +4030,6 @@ mod tests {
40424030 . expect ( "Tools failed to load" ) ;
40434031 ChatSession :: new (
40444032 & mut os,
4045- std:: io:: stdout ( ) ,
4046- std:: io:: stderr ( ) ,
40474033 "fake_conv_id" ,
40484034 agents,
40494035 None ,
@@ -4100,8 +4086,6 @@ mod tests {
41004086 . expect ( "Tools failed to load" ) ;
41014087 ChatSession :: new (
41024088 & mut os,
4103- std:: io:: stdout ( ) ,
4104- std:: io:: stderr ( ) ,
41054089 "fake_conv_id" ,
41064090 agents,
41074091 None ,
@@ -4208,8 +4192,6 @@ mod tests {
42084192 // Test that PreToolUse hook runs
42094193 ChatSession :: new (
42104194 & mut os,
4211- std:: io:: stdout ( ) ,
4212- std:: io:: stderr ( ) ,
42134195 "fake_conv_id" ,
42144196 agents,
42154197 None , // No initial input
@@ -4344,8 +4326,6 @@ mod tests {
43444326 // Run chat session - hook should block tool execution
43454327 let result = ChatSession :: new (
43464328 & mut os,
4347- std:: io:: stdout ( ) ,
4348- std:: io:: stderr ( ) ,
43494329 "test_conv_id" ,
43504330 agents,
43514331 None ,
0 commit comments