reactive_graph_client/client/repl/
hint.rs1use 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_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}