1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! Glob occurrence order reconstruction from CLI arguments.
use super::Cli;
impl Cli {
pub(crate) fn combined_globs(&self) -> Vec<String> {
// ripgrep's `-g`/`--include`/`--exclude` are positional with
// last-match-wins. The stored Vec fields (each `ArgAction::Append`) lose
// occurrence order across sources, so an exclude would always win over a
// later `--glob`/`--include`. Reconstruct true CLI-occurrence order from
// `env::args_os()`; on any count mismatch (programmatic construction, or
// globs after a `--`) fall back to field order rather than drop a filter.
let total = self.glob.len() + self.include.len() + self.exclude.len();
if total == 0 {
return Vec::new();
}
if let Some(ordered) = self.globs_in_argv_order() {
return ordered;
}
let mut globs = self.glob.clone();
globs.extend(self.include.iter().cloned());
globs.extend(self.exclude.iter().map(|glob| format!("!{glob}")));
globs
}
/// Collect glob specs in true occurrence order, returning `None` unless the
/// per-source counts exactly match the stored fields. Stops at a bare `--`.
fn globs_in_argv_order(&self) -> Option<Vec<String>> {
let mut counts = [0usize; 3]; // [glob, include, exclude]
let mut ordered: Vec<String> = Vec::new();
let mut pending: Option<usize> = None; // index into counts/LONG
const LONG: [&str; 3] = ["--glob", "--include", "--exclude"];
const VALUE_SHORTS: &[char] = &[
'e', 'm', 'r', 'A', 'B', 'C', 't', 'T', 'M', 'd', 'f', 'E', 'j',
];
const VALUE_LONGS: &[&str] = &[
"--regexp",
"--max-count",
"--replace",
"--after-context",
"--before-context",
"--context",
"--type",
"--type-not",
"--max-columns",
"--max-depth",
"--ignore-file",
"--sort",
"--sortr",
"--max-filesize",
"--encoding",
"--engine",
"--dfa-size-limit",
"--regex-size-limit",
"--file",
"--type-add",
"--type-clear",
"--iglob",
"--threads",
"--pre",
"--pre-glob",
"--color",
"--colors",
"--context-separator",
];
let emit = |i: usize, v: &str, counts: &mut [usize; 3], out: &mut Vec<String>| {
counts[i] += 1;
out.push(if i == 2 {
format!("!{v}")
} else {
v.to_string()
});
};
let mut skip_next = false;
for raw in std::env::args_os().skip(1) {
let arg = raw.to_str()?;
if skip_next {
skip_next = false;
continue;
}
if arg == "--" {
break;
}
if let Some(i) = pending.take() {
emit(i, arg, &mut counts, &mut ordered);
continue;
}
if let Some((name, val)) = arg.split_once('=') {
if let Some(i) = LONG.iter().position(|l| *l == name) {
emit(i, val, &mut counts, &mut ordered);
continue;
}
if VALUE_LONGS.contains(&name) {
continue;
}
}
if VALUE_LONGS.contains(&arg) {
skip_next = true;
continue;
}
if let Some(i) = LONG.iter().position(|l| *l == arg) {
pending = Some(i);
continue;
}
// Short `-g` in any bundle position. clap accepts `-g val`, `-gval`,
// `-g=val`, and `-ngval` (g bundled after other short flags), and
// strips a single leading `=` from the attached value (clap_builder
// parser.rs: `v.strip_prefix("=")`). Mirror that here: find `g`
// (it takes a value, so it ends the bundle) and strip the `=`, else
// `-g=foo` would emit `=foo` and `-ng val` would skip counting the
// glob, both forcing a silent field-order fallback.
if let Some(rest) = arg
.strip_prefix('-')
.filter(|s| !s.is_empty() && !s.starts_with('-'))
{
for (k, b) in rest.bytes().enumerate() {
let c = b as char;
if c == 'g' {
// k sits on an ASCII 'g' (a char boundary), so k+1 is too.
let after = &rest[k + 1..];
let val = after.strip_prefix('=').unwrap_or(after);
if val.is_empty() {
pending = Some(0);
} else {
emit(0, val, &mut counts, &mut ordered);
}
break;
} else if VALUE_SHORTS.contains(&c) {
let after = &rest[k + 1..];
if after.is_empty() {
skip_next = true;
}
break;
}
}
}
}
(counts[0] == self.glob.len()
&& counts[1] == self.include.len()
&& counts[2] == self.exclude.len())
.then_some(ordered)
}
}