use ad_client::{Client, EventFilter, Outcome, Source};
use std::{
env,
fs::File,
io::{self, copy, Write},
process::exit,
thread::spawn,
};
use subprocess::{Popen, PopenConfig, Redirection};
const PROMPT: &str = "% ";
fn main() -> io::Result<()> {
let mut client = match Client::new() {
Ok(client) => client,
Err(e) => {
eprintln!("unable to connect to ad\n{e}");
exit(1);
}
};
client.open_in_new_window("+win")?;
let buffer_id = client.current_buffer()?;
let mut env_vars: Vec<(String, String)> = env::vars().collect();
env_vars.push(("prompt".into(), PROMPT.into()));
let mut child = Popen::create(
&["rc", "-i"],
PopenConfig {
stdin: Redirection::Pipe,
stdout: Redirection::Pipe,
stderr: Redirection::Merge,
env: Some(
env_vars
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect(),
),
..Default::default()
},
)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
let stdin = child.stdin.take().unwrap();
let mut stdout = child.stdout.take().unwrap();
let mut w = client.body_writer(&buffer_id)?;
spawn(move || {
_ = copy(&mut stdout, &mut w);
});
client.run_event_filter(
&buffer_id,
Filter {
child,
buffer_id: buffer_id.clone(),
stdin,
},
)
}
struct Filter {
child: Popen,
buffer_id: String,
stdin: File,
}
impl Drop for Filter {
fn drop(&mut self) {
_ = self.child.kill();
}
}
impl Filter {
fn clear_buffer(&mut self, client: &mut Client) -> io::Result<()> {
client.write_xaddr(&self.buffer_id, ",")?;
client.write_xdot(&self.buffer_id, PROMPT)?;
client.write_addr(&self.buffer_id, "$")?;
client.ctl("mark-clean", "")?;
Ok(())
}
fn send_input(&mut self, input: &str, client: &mut Client) -> io::Result<Outcome> {
match input.trim() {
"clear" => {
self.clear_buffer(client)?;
return Ok(Outcome::Handled);
}
"exit" => {
client.ctl("db!", "")?;
self.child.kill()?;
exit(0);
}
_ => (),
}
self.stdin.write_all(input.as_bytes())?;
if !input.ends_with("\n") {
self.stdin.write_all(b"\n")?;
}
Ok(Outcome::Handled)
}
}
impl EventFilter for Filter {
fn handle_insert(
&mut self,
src: Source,
_from: usize,
_to: usize,
txt: &str,
client: &mut Client,
) -> io::Result<Outcome> {
client.ctl("mark-clean", "")?;
if src == Source::Fsys {
client.write_addr(&self.buffer_id, "$")?;
return Ok(Outcome::Handled);
}
if txt == "\n" {
client.write_xaddr(&self.buffer_id, "$")?;
let xaddr = client.read_xaddr(&self.buffer_id)?;
let addr = client.read_addr(&self.buffer_id)?;
if xaddr == addr {
client.write_xaddr(&self.buffer_id, "$-1")?;
let raw = client.read_xdot(&self.buffer_id)?;
return self.send_input(strip_prompt(&raw), client);
}
}
Ok(Outcome::Handled)
}
fn handle_delete(
&mut self,
_src: Source,
_from: usize,
_to: usize,
client: &mut Client,
) -> io::Result<Outcome> {
client.ctl("mark-clean", "")?;
Ok(Outcome::Handled)
}
fn handle_execute(
&mut self,
_src: Source,
_from: usize,
_to: usize,
txt: &str,
client: &mut Client,
) -> io::Result<Outcome> {
let s = strip_prompt(txt).trim();
client.append_to_body(&self.buffer_id, &format!("\n{PROMPT}{s}\n"))?;
let outcome = self.send_input(s, client)?;
Ok(outcome)
}
}
#[inline]
fn strip_prompt(s: &str) -> &str {
s.strip_prefix(PROMPT).unwrap_or(s)
}