1use capnp::{
2 message::{self, ReaderOptions},
3 serialize_packed as capn_serialize,
4};
5use serde::{Deserialize, Serialize};
6use shellac_capnp::{request::Reader as RequestReader, response::Builder as ResponseBuilder};
7use std::{
8 convert::TryInto,
9 fmt,
10 io::{self, BufRead, Write},
11};
12
13#[allow(dead_code)]
15mod shellac_capnp {
16 include!(concat!(env!("OUT_DIR"), "/shellac_capnp.rs"));
17}
18
19#[derive(Debug)]
21pub enum Error {
22 WordOutOfRange { word: u16, argv_len: u32 },
24 Capnp(capnp::Error),
26 NotInSchema(capnp::NotInSchema),
28}
29
30impl From<capnp::NotInSchema> for Error {
31 fn from(cause: capnp::NotInSchema) -> Self { Error::NotInSchema(cause) }
32}
33
34impl From<capnp::Error> for Error {
35 fn from(cause: capnp::Error) -> Self { Error::Capnp(cause) }
36}
37
38impl std::error::Error for Error {}
39impl fmt::Display for Error {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 Error::WordOutOfRange { word, argv_len } => write!(
43 f,
44 "the word {} can't be autocompleted because it is out of bound for argc = {}",
45 word, argv_len
46 ),
47 Error::Capnp(e) => write!(f, "{}", e),
48 Error::NotInSchema(e) => write!(f, "{}", e),
49 }
50 }
51}
52
53#[derive(Default, Clone, Debug, Hash, PartialEq, Eq, Deserialize)]
55pub struct AutocompRequest {
56 argv: Vec<String>,
57 word: u16,
58}
59
60#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize)]
62pub struct Reply<T> {
63 pub choices: Vec<Suggestion<T>>,
64}
65
66#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize)]
68pub enum SuggestionType<T> {
69 Literal(T),
71 Command { command: Vec<T>, prefix: T },
74}
75
76#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize)]
78pub struct Suggestion<T> {
79 suggestion: SuggestionType<T>,
81 description: T,
84}
85
86impl AutocompRequest {
87 pub fn new(argv: Vec<String>, word: u16) -> Self {
90 if word as usize >= argv.len() {
91 eprintln!(
92 "Word {} is out of bound for argv '{:?}' in ShellAC autocompletion request",
93 word, argv
94 );
95 panic!();
96 }
97 Self { argv, word }
98 }
99}
100
101impl<T> Suggestion<T> {
102 pub const fn new(suggestion: SuggestionType<T>, description: T) -> Self {
104 Self { suggestion, description }
105 }
106
107 pub const fn description(&self) -> &T { &self.description }
109
110 pub const fn suggestion(&self) -> &SuggestionType<T> { &self.suggestion }
112}
113
114type Out<'a, E> = std::iter::Map<
116 capnp::traits::ListIter<
117 capnp::struct_list::Reader<'a, shellac_capnp::suggestion::Owned>,
118 shellac_capnp::suggestion::Reader<'a>,
119 >,
120 fn(shellac_capnp::suggestion::Reader<'a>) -> Result<(SuggestionType<&'a str>, &'a str), E>,
121>;
122
123fn convert<T: From<capnp::Error> + From<capnp::NotInSchema>>(
124 choice: shellac_capnp::suggestion::Reader,
125) -> Result<(SuggestionType<&str>, &str), T> {
126 Ok((
127 match choice.get_arg().which()? {
128 shellac_capnp::suggestion::arg::Which::Literal(lit) => SuggestionType::Literal(lit?),
129 shellac_capnp::suggestion::arg::Which::Command(cmd) => {
130 let cmd = cmd?;
131 let prefix = cmd.get_prefix()?;
132 let command = cmd.get_args()?.iter().collect::<Result<Vec<_>, _>>()?;
133 SuggestionType::Command { command, prefix }
134 }
135 },
136 choice.get_description()?,
137 ))
138}
139
140pub fn read_reply<R, T, E, F>(reader: &mut R, f: F) -> Result<T, E>
153where
154 E: From<capnp::Error> + From<capnp::NotInSchema>,
155 R: BufRead,
156 F: FnOnce(Out<'_, E>) -> Result<T, E>,
157{
158 let request = capnp::serialize_packed::read_message(reader, ReaderOptions::default())?;
159
160 let choices = request.get_root::<shellac_capnp::response::Reader>()?.get_choices()?;
161 f(choices.iter().map(convert))
162}
163
164pub fn write_request<W: Write>(writer: &mut W, input: &AutocompRequest) -> Result<(), io::Error> {
176 let mut message = capnp::message::Builder::new_default();
177 let mut output = message.init_root::<shellac_capnp::request::Builder>();
178 output.set_word(input.word);
179
180 let len = input.argv.len().try_into().expect("Too many output choices");
181 let mut reply_argv = output.init_argv(len);
182 for (i, arg) in input.argv.iter().enumerate() {
183 reply_argv.reborrow().set(i as u32, arg);
184 }
185
186 capnp::serialize_packed::write_message(writer, &message)
187}
188
189pub fn write_reply<'a, W: Write, T: AsRef<str> + 'a, I: IntoIterator<Item = &'a Suggestion<T>>>(
192 writer: &mut W,
193 choices: I,
194) -> Result<(), io::Error>
195where
196 I::IntoIter: ExactSizeIterator,
197{
198 let mut message = message::Builder::new_default();
199 let reply = message.init_root::<ResponseBuilder>();
200
201 let choices = choices.into_iter();
202 let mut reply_choices =
203 reply.init_choices(choices.len().try_into().expect("Too many output choices"));
204 for (i, choice) in choices.enumerate() {
205 let mut reply_choice = reply_choices.reborrow().get(i as u32);
206 match choice.suggestion() {
207 SuggestionType::Literal(lit) => {
208 reply_choice.reborrow().init_arg().set_literal(lit.as_ref())
209 }
210 SuggestionType::Command { command, prefix } => {
211 let mut builder = reply_choice.reborrow().init_arg().init_command();
212 builder.set_prefix(prefix.as_ref());
213 let mut args = builder.init_args(command.len() as u32);
214 for (i, arg) in command.iter().enumerate() {
215 args.set(i as u32, arg.as_ref());
216 }
217 }
218 }
219 reply_choice.set_description(choice.description().as_ref());
220 }
221
222 capn_serialize::write_message(writer, &message)
223}
224
225pub fn read_request<
227 'a,
228 R: BufRead + 'a,
229 T,
230 E: From<Error>,
231 F: FnOnce(u16, capnp::text_list::Reader<'_>, &RequestReader) -> Result<T, E>,
232>(
233 reader: &mut R,
234 f: F,
235) -> Result<T, E> {
236 let request =
237 capn_serialize::read_message(reader, ReaderOptions::default()).map_err(Into::into)?;
238 let request = request.get_root::<RequestReader>().map_err(Into::into)?;
239
240 let argv = request.get_argv().map_err(Into::into)?;
241 let word = request.get_word();
242
243 if u32::from(word) > argv.len() {
244 Err(Error::WordOutOfRange { word, argv_len: argv.len() }.into())
245 } else {
246 f(word, argv, &request)
247 }
248}