Skip to content

Commit e49e1a4

Browse files
committed
adjusts spinners to be compatible with conduit
1 parent 58af0bc commit e49e1a4

File tree

5 files changed

+87
-51
lines changed

5 files changed

+87
-51
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/chat-cli-ui/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ serde.workspace = true
1919
serde_json.workspace = true
2020
chrono.workspace = true
2121
crossterm.workspace = true
22+
tokio.workspace = true
2223

2324
[target.'cfg(unix)'.dependencies]
2425
nix.workspace = true

crates/chat-cli-ui/src/conduit.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub enum ConduitError {
2121
pub struct ViewEnd {
2222
/// Used by the view to send input to the control
2323
// TODO: later on we will need replace this byte array with an actual event type from ACP
24-
pub sender: std::sync::mpsc::Sender<Vec<u8>>,
24+
pub sender: tokio::sync::mpsc::Sender<Vec<u8>>,
2525
/// To receive messages from control about state changes
2626
pub receiver: std::sync::mpsc::Receiver<Event>,
2727
}
@@ -61,7 +61,7 @@ pub struct DestinationStderr;
6161
#[derive(Clone, Debug)]
6262
pub struct DestinationStructuredOutput;
6363

64-
pub type InputReceiver = std::sync::mpsc::Receiver<Vec<u8>>;
64+
pub type InputReceiver = tokio::sync::mpsc::Receiver<Vec<u8>>;
6565

6666
/// This compliments the [ViewEnd]. It can be thought of as the "other end" of a pipe.
6767
/// The control would own this.
@@ -212,7 +212,7 @@ pub fn get_legacy_conduits() -> (
212212
ControlEnd<DestinationStdout>,
213213
) {
214214
let (state_tx, state_rx) = std::sync::mpsc::channel::<Event>();
215-
let (byte_tx, byte_rx) = std::sync::mpsc::channel::<Vec<u8>>();
215+
let (byte_tx, byte_rx) = tokio::sync::mpsc::channel::<Vec<u8>>(10);
216216

217217
(
218218
ViewEnd {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use crossterm::{cursor, execute, style, terminal};
2+
use indicatif::{ProgressBar, ProgressStyle};
3+
use tokio_util::sync::CancellationToken;
4+
5+
const SPINNER_CHARS: &str = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏";
6+
7+
pub struct Spinners {
8+
cancellation_token: CancellationToken,
9+
}
10+
11+
impl Spinners {
12+
pub fn new(message: String) -> Self {
13+
let pb = ProgressBar::new_spinner();
14+
pb.set_style(
15+
ProgressStyle::default_spinner()
16+
.tick_chars(SPINNER_CHARS)
17+
.template("{spinner:.green} {msg}")
18+
.unwrap(),
19+
);
20+
pb.set_message(message);
21+
let token = CancellationToken::new();
22+
let token_clone = token.clone();
23+
tokio::spawn(async move {
24+
loop {
25+
tokio::select! {
26+
_ = token_clone.cancelled() => {
27+
break;
28+
},
29+
_ = tokio::time::sleep(std::time::Duration::from_millis(100)) => {
30+
pb.tick();
31+
}
32+
}
33+
}
34+
35+
Ok::<(), Box<dyn std::error::Error + Send + Sync + 'static>>(())
36+
});
37+
38+
Self {
39+
cancellation_token: token,
40+
}
41+
}
42+
}
43+
44+
impl Drop for Spinners {
45+
fn drop(&mut self) {
46+
self.cancellation_token.cancel();
47+
let _ = execute!(
48+
std::io::stderr(),
49+
cursor::MoveToColumn(0),
50+
terminal::Clear(terminal::ClearType::CurrentLine),
51+
style::Print("\n"),
52+
);
53+
}
54+
}

crates/chat-cli/src/cli/chat/mod.rs

Lines changed: 28 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod cli;
22
mod consts;
33
pub mod context;
44
mod conversation;
5+
mod custom_spinner;
56
mod input_source;
67
mod message;
78
mod parse;
@@ -36,14 +37,14 @@ pub use conversation::ConversationState;
3637
use conversation::TokenWarningLevel;
3738
use crossterm::style::{Attribute, Color, Stylize};
3839
use crossterm::{cursor, execute, queue, style, terminal};
40+
use custom_spinner::Spinners;
3941
use eyre::{Report, Result, bail, eyre};
4042
use input_source::InputSource;
4143
use message::{AssistantMessage, AssistantToolUse, ToolUseResult, ToolUseResultBlock};
4244
use parse::{ParseState, interpret_markdown};
4345
use parser::{RecvErrorKind, RequestMetadata, SendMessageStream};
4446
use regex::Regex;
4547
use rmcp::model::PromptMessage;
46-
use spinners::{Spinner, Spinners};
4748
use thiserror::Error;
4849
use time::OffsetDateTime;
4950
use 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

10871083
impl 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

35923584
async 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>
36033599
where
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

Comments
 (0)