use miette::IntoDiagnostic;
use portable_pty::PtySize;
use r3bl_tui::{core::pty::{ControlSequence, CursorKeyMode, PtyCommandBuilder,
PtyInputEvent, PtyReadWriteOutputEvent},
set_mimalloc_in_main};
use tokio::time::{Duration, sleep};
const YELLOW: &str = "\x1b[93m";
const GREEN: &str = "\x1b[92m";
const BLUE: &str = "\x1b[94m";
const CYAN: &str = "\x1b[96m";
const RED: &str = "\x1b[91m";
const RESET: &str = "\x1b[0m";
#[allow(clippy::too_many_lines)]
async fn run_python_repl_demo() -> miette::Result<()> {
println!("{YELLOW}🐍 Starting Python REPL session...{RESET}\n");
let mut session = PtyCommandBuilder::new("python3")
.args(["-u", "-i"]) .spawn_read_write(PtySize::default())?;
let output_handle = tokio::spawn(async move {
let mut buffer = String::new();
while let Some(event) = session.output_event_receiver_half.recv().await {
match event {
PtyReadWriteOutputEvent::Output(data) => {
let text = String::from_utf8_lossy(&data);
buffer.push_str(&text);
for line in text.lines() {
if line.starts_with(">>>") || line.starts_with("...") {
print!("{CYAN}{line}{RESET}");
} else if line.contains("Error") || line.contains("Traceback") {
print!("{RED}{line}{RESET}");
} else {
print!("{GREEN}{line}{RESET}");
}
println!();
}
}
PtyReadWriteOutputEvent::Exit(status) => {
println!("\n{YELLOW}Python exited with status: {status:?}{RESET}");
break;
}
_ => {}
}
}
buffer
});
sleep(Duration::from_millis(500)).await;
println!("{BLUE}📝 Sending: Basic arithmetic{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine("2 + 2".into()))
.unwrap();
sleep(Duration::from_millis(200)).await;
println!("\n{BLUE}📝 Sending: Variable assignment{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine("name = 'PTY Demo'".into()))
.unwrap();
sleep(Duration::from_millis(200)).await;
println!("{BLUE}📝 Sending: Print variable{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine(
"print(f'Hello from {name}!')".into(),
))
.unwrap();
sleep(Duration::from_millis(200)).await;
println!("\n{BLUE}📝 Sending: Create a list{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine("numbers = [1, 2, 3, 4, 5]".into()))
.unwrap();
sleep(Duration::from_millis(200)).await;
println!("{BLUE}📝 Sending: List comprehension{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine("[x**2 for x in numbers]".into()))
.unwrap();
sleep(Duration::from_millis(200)).await;
println!("\n{BLUE}📝 Sending: Define a function{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine("def greet(name):".into()))
.unwrap();
sleep(Duration::from_millis(100)).await;
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine(
" return f'Hello, {name}!'".into(),
))
.unwrap();
sleep(Duration::from_millis(100)).await;
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine(String::new()))
.unwrap(); sleep(Duration::from_millis(200)).await;
println!("{BLUE}📝 Sending: Call the function{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine("greet('PTY User')".into()))
.unwrap();
sleep(Duration::from_millis(200)).await;
println!("\n{BLUE}📝 Sending: Intentional error to show error handling{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine("1 / 0".into()))
.unwrap();
sleep(Duration::from_millis(200)).await;
println!("\n{BLUE}📝 Sending: Import a module{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine("import sys".into()))
.unwrap();
sleep(Duration::from_millis(200)).await;
println!("{BLUE}📝 Sending: Check Python version{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine("sys.version".into()))
.unwrap();
sleep(Duration::from_millis(200)).await;
println!("\n{BLUE}📝 Sending: Exit command (Ctrl-D){RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::SendControl(
ControlSequence::CtrlD,
CursorKeyMode::default(),
))
.unwrap();
let final_output = output_handle.await.into_diagnostic()?;
println!(
"\n{YELLOW}═══════════════════════════════════════════════════════════{RESET}"
);
println!("{GREEN}✅ Python REPL session completed successfully!{RESET}");
println!(
"{YELLOW}Total output captured: {} bytes{RESET}",
final_output.len()
);
Ok(())
}
async fn run_shell_demo() -> miette::Result<()> {
println!("\n{YELLOW}🐚 Starting shell session demo...{RESET}\n");
let mut session = PtyCommandBuilder::new("sh")
.args(["-i"]) .spawn_read_write(PtySize::default())?;
let output_handle = tokio::spawn(async move {
while let Some(event) = session.output_event_receiver_half.recv().await {
match event {
PtyReadWriteOutputEvent::Output(data) => {
print!("{CYAN}{}{RESET}", String::from_utf8_lossy(&data));
}
PtyReadWriteOutputEvent::Exit(status) => {
println!("\n{YELLOW}Shell exited with status: {status:?}{RESET}");
break;
}
_ => {}
}
}
});
sleep(Duration::from_millis(500)).await;
println!("{BLUE}📝 Sending: pwd{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine("pwd".into()))
.unwrap();
sleep(Duration::from_millis(200)).await;
println!("\n{BLUE}📝 Sending: echo command{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine(
"echo 'Hello from PTY shell!'".into(),
))
.unwrap();
sleep(Duration::from_millis(200)).await;
println!("\n{BLUE}📝 Sending: List files{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine("ls -la | head -5".into()))
.unwrap();
sleep(Duration::from_millis(200)).await;
println!("\n{BLUE}📝 Sending: Environment variable{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine("echo \"Home: $HOME\"".into()))
.unwrap();
sleep(Duration::from_millis(200)).await;
println!("\n{BLUE}📝 Sending: Start a long command and interrupt it{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine(
"sleep 10 && echo 'This should not print'".into(),
))
.unwrap();
sleep(Duration::from_millis(500)).await;
println!("{BLUE}📝 Sending: Ctrl-C to interrupt{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::SendControl(
ControlSequence::CtrlC,
CursorKeyMode::default(),
))
.unwrap();
sleep(Duration::from_millis(200)).await;
println!("\n{BLUE}📝 Sending: exit{RESET}");
session
.input_event_ch_tx_half
.send(PtyInputEvent::WriteLine("exit".into()))
.unwrap();
output_handle.await.into_diagnostic()?;
println!("\n{GREEN}✅ Shell session completed successfully!{RESET}");
Ok(())
}
#[tokio::main]
async fn main() -> miette::Result<()> {
set_mimalloc_in_main!();
println!(
"\
{YELLOW}╔═══════════════════════════════════════════════════════════════╗\n\
{YELLOW}║ Demo: Bidirectional PTY Communication (Read-Write) ║\n\
{YELLOW}╚═══════════════════════════════════════════════════════════════╝{RESET}"
);
println!(
"\n{CYAN}This demo shows how to interact with child processes through PTY.{RESET}"
);
println!("{CYAN}We'll demonstrate both Python REPL and shell interactions.{RESET}\n");
println!("{YELLOW}▶ Demo 1: Python REPL Interaction{RESET}");
println!(
"{YELLOW}═══════════════════════════════════════════════════════════{RESET}"
);
run_python_repl_demo().await?;
println!("\n{YELLOW}▶ Demo 2: Shell Session with Command Interruption{RESET}");
println!(
"{YELLOW}═══════════════════════════════════════════════════════════{RESET}"
);
run_shell_demo().await?;
println!(
"\n{GREEN}✨ Demo complete! The spawn_read_write() API successfully demonstrated:\n\
{GREEN} • Sending commands to child processes\n\
{GREEN} • Receiving and processing output\n\
{GREEN} • Handling control characters (Ctrl-C, Ctrl-D)\n\
{GREEN} • Managing interactive sessions{RESET}"
);
Ok(())
}