1#[derive(Clone, Debug)]
2pub enum Span {
3 Exact(usize), FirstArg,
5 End,
6 NthArg(usize), Rendered((String, usize, usize)),
8}
9
10impl Span {
11 pub fn render(&self, args: &[String], skip_first_n: usize) -> Self {
12 let mut rendered_args = Vec::with_capacity(args.len());
13 let mut arg_indices = vec![];
14
15 for (index, arg) in args.iter().enumerate() {
16 if !arg.starts_with("--") && index >= skip_first_n {
17 arg_indices.push(index);
18 }
19
20 if arg.contains(" ") || arg.contains("\"") || arg.contains("'") || arg.contains("\n") {
21 rendered_args.push(format!("{arg:?}"));
22 }
23
24 else {
25 rendered_args.push(arg.to_string());
26 }
27 }
28
29 let new_span = match self {
30 Span::Exact(n) => Span::Exact(*n),
31 Span::FirstArg => match arg_indices.get(0) {
32 Some(n) => Span::Exact(*n),
33 None => Span::End,
34 },
35 Span::NthArg(n) => match arg_indices.get(*n) {
36 Some(n) => Span::Exact(*n),
37 None => Span::End,
38 },
39 _ => self.clone(),
40 };
41 let selected_index = match new_span {
42 Span::Exact(n) => n,
43 _ => 0,
44 };
45 let mut joined_args = rendered_args.join(" ");
46 let (start, end) = if joined_args.is_empty() {
47 joined_args = String::from(" ");
48 (0, 1)
49 } else {
50 joined_args = format!("{joined_args} ");
52
53 match new_span {
54 Span::End => (joined_args.len() - 1, joined_args.len()),
55 _ => (
56 rendered_args[..selected_index].iter().map(|arg| arg.len()).sum::<usize>() + selected_index,
57 rendered_args[..(selected_index + 1)].iter().map(|arg| arg.len()).sum::<usize>() + selected_index,
58 ),
59 }
60 };
61
62 Span::Rendered((
63 joined_args,
64 start,
65 end,
66 ))
67 }
68
69 pub fn unwrap_rendered(&self) -> (String, usize, usize) {
70 match self {
71 Span::Rendered((span, start, end)) => (span.to_string(), *start, *end),
72 _ => panic!(),
73 }
74 }
75}
76
77pub fn underline_span(args: &str, start: usize, end: usize) -> String {
78 format!(
79 "{args}\n{}{}{}",
80 " ".repeat(start),
81 "^".repeat(end - start),
82 " ".repeat(args.len() - end),
83 )
84}