1use crate::{
18 args::{Arg, State},
19 complete_shell::{render_bash, render_fish, render_simple, render_test, render_zsh},
20 item::ShortLong,
21 parsers::NamedArg,
22 Doc, ShellComp,
23};
24use std::ffi::OsStr;
25
26#[derive(Clone, Debug)]
27pub(crate) struct Complete {
28 comps: Vec<Comp>,
30 pub(crate) output_rev: usize,
31
32 pub(crate) no_pos_ahead: bool,
35}
36
37impl Complete {
38 pub(crate) fn new(output_rev: usize) -> Self {
39 Self {
40 comps: Vec::new(),
41 output_rev,
42 no_pos_ahead: false,
43 }
44 }
45}
46
47impl State {
48 pub(crate) fn push_flag(&mut self, named: &NamedArg) {
50 let depth = self.depth();
51 if let Some(comp) = self.comp_mut() {
52 if let Ok(name) = ShortLong::try_from(named) {
53 comp.comps.push(Comp::Flag {
54 extra: CompExtra {
55 depth,
56 group: None,
57 help: named.help.as_ref().and_then(Doc::to_completion),
58 },
59 name,
60 });
61 }
62 }
63 }
64
65 pub(crate) fn push_argument(&mut self, named: &NamedArg, metavar: &'static str) {
67 let depth = self.depth();
68 if let Some(comp) = self.comp_mut() {
69 if let Ok(name) = ShortLong::try_from(named) {
70 comp.comps.push(Comp::Argument {
71 extra: CompExtra {
72 depth,
73 group: None,
74 help: named.help.as_ref().and_then(Doc::to_completion),
75 },
76 metavar,
77 name,
78 });
79 }
80 }
81 }
82
83 pub(crate) fn push_metavar(
88 &mut self,
89 meta: &'static str,
90 help: &Option<Doc>,
91 is_argument: bool,
92 ) {
93 let depth = self.depth();
94 if let Some(comp) = self.comp_mut() {
95 let extra = CompExtra {
96 depth,
97 group: None,
98 help: help.as_ref().and_then(Doc::to_completion),
99 };
100
101 comp.comps.push(Comp::Metavariable {
102 extra,
103 meta,
104 is_argument,
105 });
106 }
107 }
108
109 pub(crate) fn push_command(
111 &mut self,
112 name: &'static str,
113 short: Option<char>,
114 help: &Option<Doc>,
115 ) {
116 let depth = self.depth();
117 if let Some(comp) = self.comp_mut() {
118 comp.comps.push(Comp::Command {
119 extra: CompExtra {
120 depth,
121 group: None,
122 help: help.as_ref().and_then(Doc::to_completion),
123 },
124 name,
125 short,
126 });
127 }
128 }
129
130 pub(crate) fn clear_comps(&mut self) {
132 if let Some(comp) = self.comp_mut() {
133 comp.comps.clear();
134 }
135 }
136
137 pub(crate) fn push_pos_sep(&mut self) {
141 let depth = self.depth();
142 if let Some(comp) = self.comp_mut() {
143 comp.comps.push(Comp::Value {
144 extra: CompExtra {
145 depth,
146 group: None,
147 help: Some("Positional only items after this token".to_owned()),
148 },
149 body: "--".to_owned(),
150 is_argument: false,
151 });
152 }
153 }
154
155 pub(crate) fn push_with_group(&mut self, group: &Option<String>, comps: &mut Vec<Comp>) {
157 if let Some(comp) = self.comp_mut() {
158 for mut item in comps.drain(..) {
159 if let Some(group) = group.as_ref() {
160 item.set_group(group.clone());
161 }
162 comp.comps.push(item);
163 }
164 }
165 }
166}
167
168impl Complete {
169 pub(crate) fn push_shell(&mut self, op: ShellComp, is_argument: bool, depth: usize) {
170 self.comps.push(Comp::Shell {
171 extra: CompExtra {
172 depth,
173 group: None,
174 help: None,
175 },
176 script: op,
177 is_argument,
178 });
179 }
180
181 pub(crate) fn push_value(
182 &mut self,
183 body: String,
184 help: Option<String>,
185 group: Option<String>,
186 depth: usize,
187 is_argument: bool,
188 ) {
189 self.comps.push(Comp::Value {
190 body,
191 is_argument,
192 extra: CompExtra { depth, group, help },
193 });
194 }
195
196 pub(crate) fn push_comp(&mut self, comp: Comp) {
197 self.comps.push(comp);
198 }
199
200 pub(crate) fn extend_comps(&mut self, comps: Vec<Comp>) {
201 self.comps.extend(comps);
202 }
203
204 pub(crate) fn drain_comps(&mut self) -> std::vec::Drain<'_, Comp> {
205 self.comps.drain(0..)
206 }
207
208 pub(crate) fn swap_comps(&mut self, other: &mut Vec<Comp>) {
209 std::mem::swap(other, &mut self.comps);
210 }
211}
212
213#[derive(Clone, Debug)]
214pub(crate) struct CompExtra {
215 pub(crate) depth: usize,
217
218 pub(crate) group: Option<String>,
220
221 pub(crate) help: Option<String>,
223}
224
225#[derive(Clone, Debug)]
226pub(crate) enum Comp {
227 Flag { extra: CompExtra, name: ShortLong },
229
230 Argument {
232 extra: CompExtra,
233 name: ShortLong,
234 metavar: &'static str,
235 },
236
237 Command {
239 extra: CompExtra,
240 name: &'static str,
241 short: Option<char>,
242 },
243
244 Value {
246 extra: CompExtra,
247 body: String,
248 is_argument: bool,
252 },
253
254 Metavariable {
255 extra: CompExtra,
256 meta: &'static str,
257 is_argument: bool,
259 },
260
261 Shell {
262 extra: CompExtra,
263 script: ShellComp,
264 is_argument: bool,
266 },
267}
268
269impl Comp {
270 fn depth(&self) -> usize {
272 match self {
273 Comp::Command { extra, .. }
274 | Comp::Value { extra, .. }
275 | Comp::Flag { extra, .. }
276 | Comp::Shell { extra, .. }
277 | Comp::Metavariable { extra, .. }
278 | Comp::Argument { extra, .. } => extra.depth,
279 }
280 }
281
282 pub(crate) fn is_metavar(&self) -> Option<bool> {
286 if let Comp::Metavariable { is_argument, .. } = self {
287 Some(*is_argument)
288 } else {
289 None
290 }
291 }
292
293 pub(crate) fn set_group(&mut self, group: String) {
294 let extra = match self {
295 Comp::Flag { extra, .. }
296 | Comp::Argument { extra, .. }
297 | Comp::Command { extra, .. }
298 | Comp::Value { extra, .. }
299 | Comp::Shell { extra, .. }
300 | Comp::Metavariable { extra, .. } => extra,
301 };
302 if extra.group.is_none() {
303 extra.group = Some(group);
304 }
305 }
306}
307
308#[derive(Debug)]
309pub(crate) struct ShowComp<'a> {
310 pub(crate) subst: String,
312
313 pub(crate) pretty: String,
315
316 pub(crate) extra: &'a CompExtra,
317}
318
319impl std::fmt::Display for ShowComp<'_> {
320 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321 if let (Some(help), true) = (&self.extra.help, self.subst.is_empty()) {
322 write!(f, "{}: {}", self.pretty, help)
323 } else if let Some(help) = &self.extra.help {
324 write!(f, "{:24} -- {}", self.pretty, help)
325 } else {
326 write!(f, "{}", self.pretty)
327 }
328 }
329}
330
331impl Arg {
332 fn and_os_string(&self) -> Option<(&Self, &OsStr)> {
333 match self {
334 Arg::Short(_, _, s) => {
335 if s.is_empty() {
336 None
337 } else {
338 Some((self, s))
339 }
340 }
341 Arg::Long(_, _, s) | Arg::ArgWord(s) | Arg::Word(s) | Arg::PosWord(s) => {
342 Some((self, s))
343 }
344 }
345 }
346}
347
348fn pair_to_os_string<'a>(pair: (&'a Arg, &'a OsStr)) -> Option<(&'a Arg, &'a str)> {
349 Some((pair.0, pair.1.to_str()?))
350}
351
352#[derive(Debug, Copy, Clone)]
356enum Prefix<'a> {
357 NA,
358 Short(char),
359 Long(&'a str),
360}
361
362impl State {
363 pub(crate) fn check_complete(&self) -> Option<String> {
369 let comp = self.comp_ref()?;
370
371 let mut items = self
372 .items
373 .iter()
374 .rev()
375 .filter_map(Arg::and_os_string)
376 .filter_map(pair_to_os_string);
377
378 let (cur, lit) = items.next()?;
382
383 let preceeding = items.next();
388 let (pos_only, full_lit) = match preceeding {
389 Some((Arg::Short(_, true, _os) | Arg::Long(_, true, _os), full_lit)) => {
390 (false, full_lit)
391 }
392 Some((Arg::PosWord(_), _)) => (true, lit),
393 _ => (false, lit),
394 };
395
396 let is_named = match cur {
397 Arg::Short(_, _, _) | Arg::Long(_, _, _) => true,
398 Arg::ArgWord(_) | Arg::Word(_) | Arg::PosWord(_) => false,
399 };
400
401 let prefix = match preceeding {
402 Some((Arg::Short(s, true, _os), _lit)) => Prefix::Short(*s),
403 Some((Arg::Long(l, true, _os), _lit)) => Prefix::Long(l.as_str()),
404 _ => Prefix::NA,
405 };
406
407 let (items, shell) = comp.complete(lit, pos_only, is_named, prefix);
408
409 Some(match comp.output_rev {
410 0 => render_test(&items, &shell, full_lit),
411 1 => render_simple(&items), 7 => render_zsh(&items, &shell, full_lit),
413 8 => render_bash(&items, &shell, full_lit),
414 9 => render_fish(&items, &shell, full_lit, self.path[0].as_str()),
415 unk => {
416 #[cfg(debug_assertions)]
417 {
418 eprintln!("Unsupported output revision {}, you need to genenerate your shell completion files for the app", unk);
419 std::process::exit(1);
420 }
421 #[cfg(not(debug_assertions))]
422 {
423 std::process::exit(0);
424 }
425 }
426 }.unwrap())
427 }
428}
429
430fn preferred_name(name: ShortLong) -> String {
432 match name {
433 ShortLong::Short(s) => format!("-{}", s),
434 ShortLong::Long(l) | ShortLong::Both(_, l) => format!("--{}", l),
435 }
436}
437
438fn arg_matches(arg: &str, name: ShortLong) -> Option<String> {
440 if arg.is_empty() || arg == "-" {
442 return Some(preferred_name(name));
443 }
444
445 let mut can_match = false;
446
447 match name {
450 ShortLong::Long(_) => {}
451 ShortLong::Short(s) | ShortLong::Both(s, _) => {
452 can_match |= arg
453 .strip_prefix('-')
454 .and_then(|a| a.strip_prefix(s))
455 .map_or(false, str::is_empty);
456 }
457 }
458
459 match name {
461 ShortLong::Short(_) => {}
462 ShortLong::Long(l) | ShortLong::Both(_, l) => {
463 can_match |= arg.strip_prefix("--").map_or(false, |s| l.starts_with(s));
464 }
465 }
466
467 if can_match {
468 Some(preferred_name(name))
469 } else {
470 None
471 }
472}
473fn cmd_matches(arg: &str, name: &'static str, short: Option<char>) -> Option<&'static str> {
474 if name.starts_with(arg)
476 || short.map_or(false, |s| {
477 arg.strip_prefix(s).map_or(false, str::is_empty)
479 })
480 {
481 Some(name)
482 } else {
483 None
484 }
485}
486
487impl Comp {
488 fn only_value(&self) -> bool {
490 match self {
491 Comp::Flag { .. } | Comp::Argument { .. } | Comp::Command { .. } => false,
492 Comp::Metavariable { is_argument, .. }
493 | Comp::Value { is_argument, .. }
494 | Comp::Shell { is_argument, .. } => *is_argument,
495 }
496 }
497 fn is_pos(&self) -> bool {
498 match self {
499 Comp::Flag { .. } | Comp::Argument { .. } | Comp::Command { .. } => false,
500 Comp::Value { is_argument, .. } => !is_argument,
501 Comp::Metavariable { .. } | Comp::Shell { .. } => true,
502 }
503 }
504}
505
506impl Complete {
507 fn complete(
508 &self,
509 arg: &str,
510 pos_only: bool,
511 is_named: bool,
512 prefix: Prefix,
513 ) -> (Vec<ShowComp<'_>>, Vec<ShellComp>) {
514 let mut items: Vec<ShowComp> = Vec::new();
515 let mut shell = Vec::new();
516 let max_depth = self.comps.iter().map(Comp::depth).max().unwrap_or(0);
517 let mut only_values = false;
518
519 for item in self
520 .comps
521 .iter()
522 .filter(|c| c.depth() == max_depth && (!pos_only || c.is_pos()))
523 {
524 match (only_values, item.only_value()) {
525 (true, true) | (false, false) => {}
526 (true, false) => continue,
527 (false, true) => {
528 only_values = true;
529 items.clear();
530 }
531 }
532
533 match item {
534 Comp::Command { name, short, extra } => {
535 if let Some(long) = cmd_matches(arg, name, *short) {
536 items.push(ShowComp {
537 subst: long.to_string(),
538 pretty: long.to_string(),
539 extra,
540 });
541 }
542 }
543
544 Comp::Flag { name, extra } => {
545 if let Some(long) = arg_matches(arg, *name) {
546 items.push(ShowComp {
547 pretty: long.clone(),
548 subst: long,
549 extra,
550 });
551 }
552 }
553
554 Comp::Argument {
555 name,
556 metavar,
557 extra,
558 } => {
559 if let Some(long) = arg_matches(arg, *name) {
560 items.push(ShowComp {
561 pretty: format!("{}={}", long, metavar),
562 subst: long,
563 extra,
564 });
565 }
566 }
567
568 Comp::Value {
569 body,
570 extra,
571 is_argument: _,
572 } => {
573 items.push(ShowComp {
574 pretty: body.clone(),
575 extra,
576 subst: match prefix {
577 Prefix::NA => body.clone(),
578 Prefix::Short(s) => format!("-{}={}", s, body),
579 Prefix::Long(l) => format!("--{}={}", l, body),
580 },
581 });
582 }
583
584 Comp::Metavariable {
585 extra,
586 meta,
587 is_argument,
588 } => {
589 if !is_argument && !pos_only && arg.starts_with('-') {
590 continue;
591 }
592 items.push(ShowComp {
593 subst: String::new(),
594 pretty: (*meta).to_string(),
595 extra,
596 });
597 }
598
599 Comp::Shell { script, .. } => {
600 if !is_named {
601 shell.push(*script);
602 }
603 }
604 }
605 }
606
607 (items, shell)
608 }
609}