1use crate::config::ReplConfig;
6use crate::repl::runner::{ReplClientAdapter, ReplRunner};
7use crate::repl::stats::{
8 show_cache_from_snapshot, show_execution_from_snapshot, show_server_stats,
9 show_session_summary_from_snapshot, show_subprocess_stats,
10};
11use crate::repl::terminal::ReplTerminal;
12use anyhow::{Context, Result};
13use async_trait::async_trait;
14use oxur_repl::protocol::{
15 MessageId, Operation, OperationResult, ReplMode, Request, Response, SessionId,
16};
17use oxur_repl::transport::{TcpTransport, Transport};
18use std::sync::atomic::{AtomicU64, Ordering};
19
20struct TcpAdapter {
24 transport: TcpTransport,
25 session_id: SessionId,
26 msg_counter: AtomicU64,
27}
28
29impl TcpAdapter {
30 fn new(transport: TcpTransport, session_id: SessionId) -> Self {
31 Self { transport, session_id, msg_counter: AtomicU64::new(1000) }
32 }
33
34 fn next_message_id(&self) -> MessageId {
36 MessageId::new(self.msg_counter.fetch_add(1, Ordering::SeqCst))
37 }
38
39 async fn request_stats(&mut self, operation: Operation) -> Result<OperationResult> {
41 let request =
42 Request { id: self.next_message_id(), session_id: self.session_id.clone(), operation };
43 self.transport.send_request(&request).await?;
44 let response = self.transport.recv_response().await?;
45 Ok(response.result)
46 }
47}
48
49#[async_trait]
50impl ReplClientAdapter for TcpAdapter {
51 async fn send_eval(&mut self, request: Request) -> Result<()> {
52 self.transport.send_request(&request).await.context("Failed to send request")
53 }
54
55 async fn recv_response(&mut self) -> Result<Response> {
56 self.transport.recv_response().await.context("Failed to receive response")
57 }
58
59 async fn close(&mut self) -> Result<()> {
60 self.transport.close().await.context("Failed to close connection")
61 }
62
63 fn current_session(&self) -> &SessionId {
64 &self.session_id
65 }
66
67 async fn handle_special_command(&mut self, input: &str, color_enabled: bool) -> Option<String> {
68 if !input.starts_with("(stats") {
70 return None;
71 }
72
73 match input {
74 "(stats server)" => match self.request_stats(Operation::GetServerStats).await {
75 Ok(OperationResult::ServerStats { snapshot }) => {
76 Some(show_server_stats(&snapshot, color_enabled))
77 }
78 Ok(_) => Some("Unexpected response from server".to_string()),
79 Err(e) => Some(format!("Failed to get server stats: {}", e)),
80 },
81 "(stats subprocess)" => match self.request_stats(Operation::GetSubprocessStats).await {
82 Ok(OperationResult::SubprocessStats { snapshot }) => {
83 Some(show_subprocess_stats(&snapshot, color_enabled))
84 }
85 Ok(_) => Some("Unexpected response from server".to_string()),
86 Err(e) => Some(format!("Failed to get subprocess stats: {}", e)),
87 },
88 "(stats)" => match self.request_stats(Operation::GetSessionStats).await {
89 Ok(OperationResult::SessionStats { snapshot }) => {
90 Some(show_session_summary_from_snapshot(&snapshot, color_enabled))
91 }
92 Ok(_) => Some("Unexpected response from server".to_string()),
93 Err(e) => Some(format!("Failed to get session stats: {}", e)),
94 },
95 "(stats execution)" => match self.request_stats(Operation::GetSessionStats).await {
96 Ok(OperationResult::SessionStats { snapshot }) => {
97 Some(show_execution_from_snapshot(&snapshot, color_enabled))
98 }
99 Ok(_) => Some("Unexpected response from server".to_string()),
100 Err(e) => Some(format!("Failed to get session stats: {}", e)),
101 },
102 "(stats cache)" => match self.request_stats(Operation::GetSessionStats).await {
103 Ok(OperationResult::SessionStats { snapshot }) => {
104 Some(show_cache_from_snapshot(&snapshot, color_enabled))
105 }
106 Ok(_) => Some("Unexpected response from server".to_string()),
107 Err(e) => Some(format!("Failed to get session stats: {}", e)),
108 },
109 "(stats resources)" => {
110 Some("Resource stats not available in remote mode (requires local filesystem access)".to_string())
112 }
113 _ => {
114 Some(format!(
116 "Unknown stats command: {}. Try (help stats) for available commands.",
117 input
118 ))
119 }
120 }
121 }
122}
123
124pub async fn run(addr: &str, config: ReplConfig) -> Result<()> {
129 let system_metadata = oxur_repl::metadata::SystemMetadata::capture();
131
132 let transport = TcpTransport::connect(addr)
134 .await
135 .context(format!("Failed to connect to REPL server at {}", addr))?;
136
137 let session_id = SessionId::new(format!("connect-{}", std::process::id()));
139
140 let mut adapter = TcpAdapter::new(transport, session_id.clone());
142
143 let create_req = Request {
145 id: adapter.next_message_id(),
146 session_id: session_id.clone(),
147 operation: Operation::CreateSession { mode: ReplMode::Lisp },
148 };
149
150 adapter.send_eval(create_req).await.context("Failed to send create session request")?;
151 let _response = adapter.recv_response().await.context("Failed to receive create response")?;
152
153 let terminal = ReplTerminal::with_config(config.terminal, config.history)
155 .context("Failed to create terminal")?;
156
157 let mut runner = ReplRunner::new(terminal, session_id);
159
160 runner.print_banner(&system_metadata);
162 print_connection_info(addr, runner.terminal().config().color_enabled);
163
164 runner.run(&mut adapter).await?;
166 runner.finish(&mut adapter).await?;
167
168 Ok(())
169}
170
171fn print_connection_info(addr: &str, color_enabled: bool) {
173 if color_enabled {
174 println!("\x1b[36mConnected to {}\x1b[0m\n", addr);
175 } else {
176 println!("Connected to {}\n", addr);
177 }
178}