command_autocomplete/
router.rs

1use crate::connection::{ConnRequest, ResponseError, SendError, Transport};
2use crate::types::{CompleteParams, CompleteResult, Error, ShutdownResult};
3use clap::Args;
4use serde::{Deserialize, Serialize};
5use std::path::{Path, PathBuf};
6use std::process::Stdio;
7
8#[derive(Debug, Args)]
9pub struct RouterArgs {
10    /// The configuration path for available completers.
11    config: Option<PathBuf>,
12    // TODO: add a config
13}
14
15#[derive(Clone, Debug, Deserialize, Serialize)]
16pub struct Completer {
17    command: String,
18    args: Vec<String>,
19}
20
21#[derive(Clone, Debug, Deserialize, Serialize)]
22pub struct Command {
23    name: String,
24    completer: Completer,
25}
26
27#[derive(Clone, Debug, Default, Deserialize, Serialize)]
28pub struct Config {
29    pub command: Vec<Command>,
30}
31
32pub fn run_router(_args: RouterArgs) -> anyhow::Result<()> {
33    let content = std::fs::read_to_string(
34        Path::new(&std::env::var("HOME")?).join(".config/command-autocomplete/completers.toml"),
35    );
36    let config = match content {
37        Ok(content) => toml::from_str(&content)?,
38        // TODO: check the error, only return default on not found
39        Err(_) => Config::default(),
40    };
41    let (transport, join_handle) = Transport::stdio();
42    {
43        let (_, receiver) = crate::connection::new_connection(transport);
44        let mut router = Router::new(config);
45        while let Some(req) = receiver.next_request() {
46            match router.handle_request(req) {
47                Ok(LoopAction::Continue) => continue,
48                Ok(LoopAction::Stop) => break,
49                Err(_) => {
50                    log::warn!("the connection closed unexpectedly, stopping the receving loop");
51                    break;
52                }
53            };
54        }
55    }
56    join_handle.join()?;
57    Ok(())
58}
59
60struct Router {
61    config: Config,
62}
63
64enum LoopAction {
65    Continue,
66    Stop,
67}
68
69impl Router {
70    fn new(config: Config) -> Self {
71        Router { config }
72    }
73
74    fn handle_request(&mut self, req: ConnRequest) -> Result<LoopAction, SendError> {
75        match req.inner().method.as_str() {
76            "complete" => match serde_json::from_value(req.inner().params.clone()) {
77                Ok(params) => {
78                    req.reply(self.handle_complete_request(params))?;
79                }
80                Err(err) => {
81                    req.reply_err(Error::invalid_request(format!(
82                        "invalid params for complete request: {err}"
83                    )))?;
84                }
85            },
86            "shutdown" => {
87                req.reply_ok(ShutdownResult {})?;
88                return Ok(LoopAction::Stop);
89            }
90            _ => {
91                let method = req.inner().method.clone();
92                req.reply_err(Error {
93                    code: "UNKNOWN_REQUEST".to_string(),
94                    message: format!("method {} is not recognized", method),
95                })?;
96            }
97        }
98        Ok(LoopAction::Continue)
99    }
100
101    fn completer(&self, params: &CompleteParams) -> Option<std::process::Command> {
102        if params.args.is_empty() {
103            return None;
104        }
105        for command in &self.config.command {
106            if command.name != params.args[0] {
107                continue;
108            }
109            let mut cmd = std::process::Command::new(&command.completer.command);
110            cmd.args(&command.completer.args);
111            return Some(cmd);
112        }
113        None
114    }
115
116    fn handle_complete_request(&mut self, params: CompleteParams) -> Result<CompleteResult, Error> {
117        let Some(mut command) = self.completer(&params) else {
118            if !params.args.is_empty() {
119                log::info!("completer for command {} not found", params.args[0]);
120            }
121            // TODO: handle this better
122            return Ok(CompleteResult { values: vec![] });
123        };
124        log::debug!("starting external completer: {:?}", command);
125
126        // TODO: unwrap
127        let mut child = command
128            .stdin(Stdio::piped())
129            .stdout(Stdio::piped())
130            .spawn()
131            .map_err(|e| Error::internal(format!("failed to start the completer: {e}")))?;
132
133        // TODO: unwrap
134        let stdin = child.stdin.take().ok_or_else(|| {
135            Error::internal("stdin missing in started process, this should never happen")
136        })?;
137        let stdout = child.stdout.take().ok_or_else(|| {
138            Error::internal("stdout missing in started process, this should never happen")
139        })?;
140
141        let (transport, join_handle) = Transport::raw(stdout, stdin);
142        let (sender, receiver) = crate::connection::new_connection(transport);
143
144        let recv_join_handle = std::thread::spawn(move || {
145            // ensuring we read the response
146            while let Some(req) = receiver.next_request() {
147                let r = req.reply_err(Error::invalid_request("no requests expected"));
148                if r.is_err() {
149                    break;
150                }
151            }
152            log::debug!("receiver finished");
153        });
154        // TODO: unwrap
155        log::debug!("sending complete request to sub process");
156        let res = sender.send::<CompleteResult>("complete", params).unwrap();
157        log::debug!("waiting for complete response");
158        let res = res.wait().map_err(|e| match e {
159            ResponseError::Err(e) => e,
160            ResponseError::ChannelClosed => {
161                Error::internal("subprocess closed connection before providing completions")
162            }
163            ResponseError::DeserializationError(err) => Error::internal(format!(
164                "subprocess returned response that failed deserialization, error: {err}"
165            )),
166        });
167        log::debug!("received response: {:?}", res.is_ok());
168
169        // TODO: handle unwrap
170        sender.shutdown().unwrap().wait().unwrap();
171
172        join_handle.join().unwrap();
173        recv_join_handle.join().unwrap();
174        child.wait().unwrap();
175
176        // TODO: exit cleanly
177        res
178    }
179}