reactive_graph/client/repl/
hint.rs

1use clap::CommandFactory;
2use clap::Error;
3use clap::error::ContextKind;
4use clap::error::ContextValue;
5use clap::error::ErrorKind;
6use colored::Colorize;
7use rustyline::hint::Hint;
8
9use crate::client::repl::args::ReplArgs;
10use crate::client::repl::chars::CHAR_ENTER;
11use crate::client::repl::chars::CHAR_ERROR;
12use crate::client::repl::chars::CHAR_SUGGESTION;
13use crate::client::repl::longest_common_prefix;
14
15#[derive(Debug, Clone)]
16pub enum HinterMatchType {
17    Command(ReplArgs),
18    Error(ErrorKind, Vec<(ContextKind, ContextValue)>),
19}
20
21#[derive(Debug, Clone)]
22pub struct HinterMatch {
23    display: String,
24    completion: Option<String>,
25}
26
27impl HinterMatch {
28    pub fn new_from_args_and_command(args: Vec<String>, command: ReplArgs) -> Self {
29        HinterMatch::new(args, HinterMatchType::Command(command))
30    }
31
32    pub fn new_from_args_and_error(args: Vec<String>, error: Error) -> Self {
33        HinterMatch::new(args, HinterMatchType::Error(error.kind(), error.context().map(|(k, v)| (k, v.clone())).collect()))
34    }
35
36    pub fn new(args: Vec<String>, t: HinterMatchType) -> Self {
37        let last_arg = args.last().cloned().unwrap_or("".to_string());
38        let display = if args.len() < 2 {
39            let s: Vec<String> = ReplArgs::command().get_subcommands().map(|c| c.get_name().to_string()).collect();
40            format!("   {} {}", CHAR_SUGGESTION, s.join(" | "))
41        } else {
42            match &t {
43                HinterMatchType::Command(_command) => CHAR_ENTER.green().to_string(),
44                HinterMatchType::Error(error_kind, context) => {
45                    context
46                        .iter()
47                        .find(|(kind, _)| kind != &ContextKind::InvalidSubcommand && kind != &ContextKind::Usage && kind != &ContextKind::Custom)
48                        .and_then(|(kind, value)| match (kind, value) {
49                            (ContextKind::InvalidArg, ContextValue::String(s)) => Some(format!("   {CHAR_SUGGESTION} {s}").to_string()),
50                            (ContextKind::InvalidArg, ContextValue::Strings(s)) => Some(format!("   {CHAR_SUGGESTION} {}", s.join(" | "))),
51                            (ContextKind::SuggestedSubcommand, ContextValue::String(s)) => s.strip_prefix(&last_arg).map(|s| s.to_string()),
52                            (ContextKind::SuggestedSubcommand, ContextValue::Strings(s)) => {
53                                let mut s: Vec<String> = s.iter().filter_map(|s| s.strip_prefix(&last_arg).map(|s| s.to_string())).collect();
54                                // s.sort_by(|s1, s2| s1.len().cmp(&s2.len()));
55                                s.sort_by_key(|s| s.len());
56                                Some(s.join(" | "))
57                            }
58                            (ContextKind::ValidSubcommand, ContextValue::String(s)) => Some(format!("   {CHAR_SUGGESTION} {s}")),
59                            (ContextKind::ValidSubcommand, ContextValue::Strings(s)) => Some(format!("   {CHAR_SUGGESTION} {}", s.join(" | "))),
60                            (kind, value) => Some(format!(
61                                "   {} |{}| : {} = {} ({})",
62                                CHAR_ERROR,
63                                error_kind,
64                                kind,
65                                value,
66                                match value {
67                                    ContextValue::None => "none",
68                                    ContextValue::Bool(_) => "bool",
69                                    ContextValue::String(_) => "string",
70                                    ContextValue::Strings(_) => "strings",
71                                    ContextValue::StyledStr(_) => "styledstr",
72                                    ContextValue::StyledStrs(_) => "styledstrs",
73                                    ContextValue::Number(_) => "number",
74                                    _ => "unknown",
75                                }
76                            )),
77                        })
78                        .unwrap_or_default()
79                }
80            }
81        };
82
83        let mut error_info = String::new();
84
85        let mut completion_matches = 0;
86        let completion = match &t {
87            HinterMatchType::Command(_command) => None,
88            HinterMatchType::Error(error_kind, context) => context
89                .iter()
90                .find(|(kind, _)| kind != &ContextKind::InvalidSubcommand && kind != &ContextKind::Usage && kind != &ContextKind::Custom)
91                .map(|(k, v)| {
92                    error_info = format!("{} {} {}", error_info, CHAR_ERROR, error_kind.to_string().red());
93                    (k, v)
94                })
95                .and_then(|(_, value)| match value {
96                    ContextValue::String(s) => s.strip_prefix(&last_arg).map(|s| s.to_string()),
97                    ContextValue::Strings(s) => {
98                        let s: Vec<&String> = s.iter().filter(|s| s.starts_with(&last_arg)).collect();
99                        completion_matches = s.len();
100                        longest_common_prefix(&s).strip_prefix(&last_arg).map(|s| s.to_string())
101                    }
102                    _ => None,
103                }),
104        };
105
106        let display = if !error_info.is_empty() {
107            format!("{display}  {error_info}")
108        } else {
109            display
110        };
111
112        let completion = completion.map(|s| {
113            let spacer = if completion_matches < 2 { " " } else { "" };
114            format!("{s}{spacer}")
115        });
116
117        Self { display, completion }
118    }
119}
120
121impl Hint for HinterMatch {
122    fn display(&self) -> &str {
123        &self.display
124    }
125
126    fn completion(&self) -> Option<&str> {
127        self.completion.as_deref()
128    }
129}