1use std::{collections::BTreeMap, ops::Range};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub enum ValueType {
5 Path,
6}
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum QuoteStyle {
10 Single,
11 Double,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct CursorState {
16 pub token_stub: String,
17 pub raw_stub: String,
18 pub replace_range: Range<usize>,
19 pub quote_style: Option<QuoteStyle>,
20}
21
22impl CursorState {
23 pub fn new(
24 token_stub: impl Into<String>,
25 raw_stub: impl Into<String>,
26 replace_range: Range<usize>,
27 quote_style: Option<QuoteStyle>,
28 ) -> Self {
29 Self {
30 token_stub: token_stub.into(),
31 raw_stub: raw_stub.into(),
32 replace_range,
33 quote_style,
34 }
35 }
36
37 pub fn synthetic(token_stub: impl Into<String>) -> Self {
38 let token_stub = token_stub.into();
39 let len = token_stub.len();
40 Self {
41 raw_stub: token_stub.clone(),
42 token_stub,
43 replace_range: 0..len,
44 quote_style: None,
45 }
46 }
47}
48
49impl Default for CursorState {
50 fn default() -> Self {
51 Self::synthetic("")
52 }
53}
54
55#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
56pub enum ContextScope {
57 Global,
58 #[default]
59 Subtree,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct SuggestionEntry {
64 pub value: String,
66 pub meta: Option<String>,
68 pub display: Option<String>,
70 pub sort: Option<String>,
72}
73
74impl SuggestionEntry {
75 pub fn value(value: impl Into<String>) -> Self {
76 Self {
77 value: value.into(),
78 meta: None,
79 display: None,
80 sort: None,
81 }
82 }
83
84 pub fn meta(mut self, meta: impl Into<String>) -> Self {
85 self.meta = Some(meta.into());
86 self
87 }
88
89 pub fn display(mut self, display: impl Into<String>) -> Self {
90 self.display = Some(display.into());
91 self
92 }
93
94 pub fn sort(mut self, sort: impl Into<String>) -> Self {
95 self.sort = Some(sort.into());
96 self
97 }
98}
99
100impl From<&str> for SuggestionEntry {
101 fn from(value: &str) -> Self {
102 Self::value(value)
103 }
104}
105
106#[derive(Debug, Clone, Default, PartialEq, Eq)]
107pub struct OsVersions {
108 pub union: BTreeMap<String, Vec<SuggestionEntry>>,
109 pub by_provider: BTreeMap<String, BTreeMap<String, Vec<SuggestionEntry>>>,
110}
111
112#[derive(Debug, Clone, Default, PartialEq, Eq)]
113pub struct RequestHints {
114 pub keys: Vec<String>,
115 pub required: Vec<String>,
116 pub tiers: BTreeMap<String, Vec<String>>,
117 pub defaults: BTreeMap<String, String>,
118 pub choices: BTreeMap<String, Vec<String>>,
119}
120
121#[derive(Debug, Clone, Default, PartialEq, Eq)]
122pub struct RequestHintSet {
123 pub common: RequestHints,
124 pub by_provider: BTreeMap<String, RequestHints>,
125}
126
127#[derive(Debug, Clone, Default, PartialEq, Eq)]
128pub struct FlagHints {
129 pub common: Vec<String>,
130 pub by_provider: BTreeMap<String, Vec<String>>,
131 pub required_common: Vec<String>,
132 pub required_by_provider: BTreeMap<String, Vec<String>>,
133}
134
135#[derive(Debug, Clone, Default, PartialEq, Eq)]
136pub struct ArgNode {
137 pub name: Option<String>,
139 pub tooltip: Option<String>,
140 pub multi: bool,
141 pub value_type: Option<ValueType>,
142 pub suggestions: Vec<SuggestionEntry>,
143}
144
145impl ArgNode {
146 pub fn named(name: impl Into<String>) -> Self {
147 Self {
148 name: Some(name.into()),
149 ..Self::default()
150 }
151 }
152
153 pub fn tooltip(mut self, tooltip: impl Into<String>) -> Self {
154 self.tooltip = Some(tooltip.into());
155 self
156 }
157
158 pub fn multi(mut self) -> Self {
159 self.multi = true;
160 self
161 }
162
163 pub fn value_type(mut self, value_type: ValueType) -> Self {
164 self.value_type = Some(value_type);
165 self
166 }
167
168 pub fn suggestions(mut self, suggestions: impl IntoIterator<Item = SuggestionEntry>) -> Self {
169 self.suggestions = suggestions.into_iter().collect();
170 self
171 }
172}
173
174#[derive(Debug, Clone, Default, PartialEq, Eq)]
175pub struct FlagNode {
176 pub tooltip: Option<String>,
177 pub flag_only: bool,
178 pub multi: bool,
179 pub context_only: bool,
182 pub context_scope: ContextScope,
183 pub value_type: Option<ValueType>,
184 pub suggestions: Vec<SuggestionEntry>,
185 pub suggestions_by_provider: BTreeMap<String, Vec<SuggestionEntry>>,
186 pub os_provider_map: BTreeMap<String, Vec<String>>,
187 pub os_versions: Option<OsVersions>,
188 pub request_hints: Option<RequestHintSet>,
189 pub flag_hints: Option<FlagHints>,
190}
191
192impl FlagNode {
193 pub fn new() -> Self {
194 Self::default()
195 }
196
197 pub fn tooltip(mut self, tooltip: impl Into<String>) -> Self {
198 self.tooltip = Some(tooltip.into());
199 self
200 }
201
202 pub fn flag_only(mut self) -> Self {
203 self.flag_only = true;
204 self
205 }
206
207 pub fn multi(mut self) -> Self {
208 self.multi = true;
209 self
210 }
211
212 pub fn context_only(mut self, scope: ContextScope) -> Self {
213 self.context_only = true;
214 self.context_scope = scope;
215 self
216 }
217
218 pub fn value_type(mut self, value_type: ValueType) -> Self {
219 self.value_type = Some(value_type);
220 self
221 }
222
223 pub fn suggestions(mut self, suggestions: impl IntoIterator<Item = SuggestionEntry>) -> Self {
224 self.suggestions = suggestions.into_iter().collect();
225 self
226 }
227}
228
229#[derive(Debug, Clone, Default, PartialEq, Eq)]
230pub struct CompletionNode {
231 pub tooltip: Option<String>,
236 pub sort: Option<String>,
238 pub value_key: bool,
240 pub value_leaf: bool,
242 pub prefilled_flags: BTreeMap<String, Vec<String>>,
244 pub prefilled_positionals: Vec<String>,
246 pub children: BTreeMap<String, CompletionNode>,
247 pub flags: BTreeMap<String, FlagNode>,
248 pub args: Vec<ArgNode>,
249 pub flag_hints: Option<FlagHints>,
250}
251
252impl CompletionNode {
253 pub fn sort(mut self, sort: impl Into<String>) -> Self {
254 self.sort = Some(sort.into());
255 self
256 }
257
258 pub fn with_child(mut self, name: impl Into<String>, node: CompletionNode) -> Self {
259 self.children.insert(name.into(), node);
260 self
261 }
262
263 pub fn with_flag(mut self, name: impl Into<String>, node: FlagNode) -> Self {
264 self.flags.insert(name.into(), node);
265 self
266 }
267}
268
269#[derive(Debug, Clone, Default, PartialEq, Eq)]
270pub struct CompletionTree {
271 pub root: CompletionNode,
272 pub pipe_verbs: BTreeMap<String, String>,
275}
276
277#[derive(Debug, Clone, Default, PartialEq, Eq)]
278pub struct CommandLine {
279 pub(crate) head: Vec<String>,
284 pub(crate) tail: Vec<TailItem>,
285 pub(crate) flag_values: BTreeMap<String, Vec<String>>,
286 pub(crate) pipes: Vec<String>,
287 pub(crate) has_pipe: bool,
288}
289
290#[derive(Debug, Clone, Default, PartialEq, Eq)]
291pub struct FlagOccurrence {
292 pub name: String,
293 pub values: Vec<String>,
294}
295
296#[derive(Debug, Clone, PartialEq, Eq)]
297pub enum TailItem {
298 Flag(FlagOccurrence),
299 Positional(String),
300}
301
302impl CommandLine {
303 pub fn head(&self) -> &[String] {
304 &self.head
305 }
306
307 pub fn tail(&self) -> &[TailItem] {
308 &self.tail
309 }
310
311 pub fn pipes(&self) -> &[String] {
312 &self.pipes
313 }
314
315 pub fn has_pipe(&self) -> bool {
316 self.has_pipe
317 }
318
319 pub fn flag_values_map(&self) -> &BTreeMap<String, Vec<String>> {
320 &self.flag_values
321 }
322
323 pub fn flag_values(&self, name: &str) -> Option<&[String]> {
324 self.flag_values.get(name).map(Vec::as_slice)
325 }
326
327 pub fn has_flag(&self, name: &str) -> bool {
328 self.flag_values.contains_key(name)
329 }
330
331 pub fn flag_occurrences(&self) -> impl Iterator<Item = &FlagOccurrence> {
332 self.tail.iter().filter_map(|item| match item {
333 TailItem::Flag(flag) => Some(flag),
334 TailItem::Positional(_) => None,
335 })
336 }
337
338 pub fn last_flag_occurrence(&self) -> Option<&FlagOccurrence> {
339 self.flag_occurrences().last()
340 }
341
342 pub fn positional_args(&self) -> impl Iterator<Item = &String> {
343 self.tail.iter().filter_map(|item| match item {
344 TailItem::Positional(value) => Some(value),
345 TailItem::Flag(_) => None,
346 })
347 }
348
349 pub fn tail_len(&self) -> usize {
350 self.tail.len()
351 }
352
353 pub fn push_flag_occurrence(&mut self, occurrence: FlagOccurrence) {
354 self.flag_values
355 .entry(occurrence.name.clone())
356 .or_default()
357 .extend(occurrence.values.iter().cloned());
358 self.tail.push(TailItem::Flag(occurrence));
359 }
360
361 pub fn push_positional(&mut self, value: impl Into<String>) {
362 self.tail.push(TailItem::Positional(value.into()));
363 }
364
365 pub fn merge_flag_values(&mut self, name: impl Into<String>, values: Vec<String>) {
366 self.flag_values
367 .entry(name.into())
368 .or_default()
369 .extend(values);
370 }
371
372 pub fn prepend_positional_values(&mut self, values: impl IntoIterator<Item = String>) {
373 let mut values = values
374 .into_iter()
375 .filter(|value| !value.trim().is_empty())
376 .map(TailItem::Positional)
377 .collect::<Vec<_>>();
378 if values.is_empty() {
379 return;
380 }
381 values.extend(std::mem::take(&mut self.tail));
382 self.tail = values;
383 }
384
385 pub fn set_pipe(&mut self, pipes: Vec<String>) {
386 self.has_pipe = true;
387 self.pipes = pipes;
388 }
389
390 pub fn push_head(&mut self, segment: impl Into<String>) {
391 self.head.push(segment.into());
392 }
393}
394
395#[derive(Debug, Clone, Default, PartialEq, Eq)]
396pub struct ParsedLine {
397 pub safe_cursor: usize,
398 pub full_tokens: Vec<String>,
399 pub cursor_tokens: Vec<String>,
400 pub full_cmd: CommandLine,
401 pub cursor_cmd: CommandLine,
402}
403
404#[derive(Debug, Clone, Default, PartialEq, Eq)]
405pub struct CompletionAnalysis {
406 pub parsed: ParsedLine,
408 pub cursor: CursorState,
409 pub context: CompletionContext,
410}
411
412#[derive(Debug, Clone, Default, PartialEq, Eq)]
419pub struct CompletionContext {
420 pub matched_path: Vec<String>,
421 pub flag_scope_path: Vec<String>,
422 pub subcommand_context: bool,
423}
424
425#[derive(Debug, Clone, Copy, PartialEq, Eq)]
426pub enum MatchKind {
427 Pipe,
428 Flag,
429 Command,
430 Subcommand,
431 Value,
432}
433
434impl MatchKind {
435 pub fn as_str(self) -> &'static str {
436 match self {
437 Self::Pipe => "pipe",
438 Self::Flag => "flag",
439 Self::Command => "command",
440 Self::Subcommand => "subcommand",
441 Self::Value => "value",
442 }
443 }
444}
445
446#[derive(Debug, Clone, PartialEq, Eq)]
447pub struct Suggestion {
448 pub text: String,
449 pub meta: Option<String>,
450 pub display: Option<String>,
451 pub is_exact: bool,
452 pub sort: Option<String>,
453 pub match_score: u32,
454}
455
456impl Suggestion {
457 pub fn new(text: impl Into<String>) -> Self {
458 Self {
459 text: text.into(),
460 meta: None,
461 display: None,
462 is_exact: false,
463 sort: None,
464 match_score: u32::MAX,
465 }
466 }
467}
468
469#[derive(Debug, Clone, PartialEq, Eq)]
470pub enum SuggestionOutput {
471 Item(Suggestion),
472 PathSentinel,
473}
474
475#[cfg(test)]
476mod tests;