use crate::connection::{ConnRequest, ResponseError, SendError, Transport};
use crate::types::{CompleteParams, CompleteResult, Error, ShutdownResult};
use clap::Args;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::process::Stdio;
#[derive(Debug, Args)]
pub struct RouterArgs {
config: Option<PathBuf>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Completer {
command: String,
args: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Command {
name: String,
completer: Completer,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Config {
pub command: Vec<Command>,
}
pub fn run_router(_args: RouterArgs) -> anyhow::Result<()> {
let content = std::fs::read_to_string(
Path::new(&std::env::var("HOME")?).join(".config/command-autocomplete/completers.toml"),
);
let config = match content {
Ok(content) => toml::from_str(&content)?,
Err(_) => Config::default(),
};
let (transport, join_handle) = Transport::stdio();
{
let (_, receiver) = crate::connection::new_connection(transport);
let mut router = Router::new(config);
while let Some(req) = receiver.next_request() {
match router.handle_request(req) {
Ok(LoopAction::Continue) => continue,
Ok(LoopAction::Stop) => break,
Err(_) => {
log::warn!("the connection closed unexpectedly, stopping the receving loop");
break;
}
};
}
}
join_handle.join()?;
Ok(())
}
struct Router {
config: Config,
}
enum LoopAction {
Continue,
Stop,
}
impl Router {
fn new(config: Config) -> Self {
Router { config }
}
fn handle_request(&mut self, req: ConnRequest) -> Result<LoopAction, SendError> {
match req.inner().method.as_str() {
"complete" => match serde_json::from_value(req.inner().params.clone()) {
Ok(params) => {
req.reply(self.handle_complete_request(params))?;
}
Err(err) => {
req.reply_err(Error::invalid_request(format!(
"invalid params for complete request: {err}"
)))?;
}
},
"shutdown" => {
req.reply_ok(ShutdownResult {})?;
return Ok(LoopAction::Stop);
}
_ => {
let method = req.inner().method.clone();
req.reply_err(Error {
code: "UNKNOWN_REQUEST".to_string(),
message: format!("method {} is not recognized", method),
})?;
}
}
Ok(LoopAction::Continue)
}
fn completer(&self, params: &CompleteParams) -> Option<std::process::Command> {
if params.args.is_empty() {
return None;
}
for command in &self.config.command {
if command.name != params.args[0] {
continue;
}
let mut cmd = std::process::Command::new(&command.completer.command);
cmd.args(&command.completer.args);
return Some(cmd);
}
None
}
fn handle_complete_request(&mut self, params: CompleteParams) -> Result<CompleteResult, Error> {
let Some(mut command) = self.completer(¶ms) else {
if !params.args.is_empty() {
log::info!("completer for command {} not found", params.args[0]);
}
return Ok(CompleteResult { values: vec![] });
};
log::debug!("starting external completer: {:?}", command);
let mut child = command
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.map_err(|e| Error::internal(format!("failed to start the completer: {e}")))?;
let stdin = child.stdin.take().ok_or_else(|| {
Error::internal("stdin missing in started process, this should never happen")
})?;
let stdout = child.stdout.take().ok_or_else(|| {
Error::internal("stdout missing in started process, this should never happen")
})?;
let (transport, join_handle) = Transport::raw(stdout, stdin);
let (sender, receiver) = crate::connection::new_connection(transport);
let recv_join_handle = std::thread::spawn(move || {
while let Some(req) = receiver.next_request() {
let r = req.reply_err(Error::invalid_request("no requests expected"));
if r.is_err() {
break;
}
}
log::debug!("receiver finished");
});
log::debug!("sending complete request to sub process");
let res = sender.send::<CompleteResult>("complete", params).unwrap();
log::debug!("waiting for complete response");
let res = res.wait().map_err(|e| match e {
ResponseError::Err(e) => e,
ResponseError::ChannelClosed => {
Error::internal("subprocess closed connection before providing completions")
}
ResponseError::DeserializationError(err) => Error::internal(format!(
"subprocess returned response that failed deserialization, error: {err}"
)),
});
log::debug!("received response: {:?}", res.is_ok());
sender.shutdown().unwrap().wait().unwrap();
join_handle.join().unwrap();
recv_join_handle.join().unwrap();
child.wait().unwrap();
res
}
}