in codex-rs/shell-command/src/parse_command.rs [2074:2499]
fn summarize_main_tokens(main_cmd: &[String]) -> ParsedCommand {
match main_cmd.split_first() {
Some((head, tail)) if matches!(head.as_str(), "ls" | "eza" | "exa") => {
let flags_with_vals: &[&str] = match head.as_str() {
"ls" => &[
"-I",
"-w",
"--block-size",
"--format",
"--time-style",
"--color",
"--quoting-style",
],
"eza" | "exa" => &[
"-I",
"--ignore-glob",
"--color",
"--sort",
"--time-style",
"--time",
],
_ => &[],
};
let path =
first_non_flag_operand(tail, flags_with_vals).map(|p| short_display_path(&p));
ParsedCommand::ListFiles {
cmd: shlex_join(main_cmd),
path,
}
}
Some((head, tail)) if head == "tree" => {
let path = first_non_flag_operand(
tail,
&["-L", "-P", "-I", "--charset", "--filelimit", "--sort"],
)
.map(|p| short_display_path(&p));
ParsedCommand::ListFiles {
cmd: shlex_join(main_cmd),
path,
}
}
Some((head, tail)) if head == "du" => {
let path = first_non_flag_operand(
tail,
&[
"-d",
"--max-depth",
"-B",
"--block-size",
"--exclude",
"--time-style",
],
)
.map(|p| short_display_path(&p));
ParsedCommand::ListFiles {
cmd: shlex_join(main_cmd),
path,
}
}
Some((head, tail)) if head == "rg" || head == "rga" || head == "ripgrep-all" => {
let args_no_connector = trim_at_connector(tail);
let has_files_flag = args_no_connector.iter().any(|a| a == "--files");
let candidates = skip_flag_values(
&args_no_connector,
&[
"-g",
"--glob",
"--iglob",
"-t",
"--type",
"--type-add",
"--type-not",
"-m",
"--max-count",
"-A",
"-B",
"-C",
"--context",
"--max-depth",
],
);
let non_flags: Vec<&String> = candidates
.into_iter()
.filter(|p| !p.starts_with('-'))
.collect();
if has_files_flag {
let path = non_flags.first().map(|s| short_display_path(s));
ParsedCommand::ListFiles {
cmd: shlex_join(main_cmd),
path,
}
} else {
let query = non_flags.first().cloned().map(String::from);
let path = non_flags.get(1).map(|s| short_display_path(s));
ParsedCommand::Search {
cmd: shlex_join(main_cmd),
query,
path,
}
}
}
Some((head, tail)) if head == "git" => match tail.split_first() {
Some((subcmd, sub_tail)) if subcmd == "grep" => parse_grep_like(main_cmd, sub_tail),
Some((subcmd, sub_tail)) if subcmd == "ls-files" => {
let path = first_non_flag_operand(
sub_tail,
&["--exclude", "--exclude-from", "--pathspec-from-file"],
)
.map(|p| short_display_path(&p));
ParsedCommand::ListFiles {
cmd: shlex_join(main_cmd),
path,
}
}
_ => ParsedCommand::Unknown {
cmd: shlex_join(main_cmd),
},
},
Some((head, tail)) if head == "fd" => {
let (query, path) = parse_fd_query_and_path(tail);
if query.is_some() {
ParsedCommand::Search {
cmd: shlex_join(main_cmd),
query,
path,
}
} else {
ParsedCommand::ListFiles {
cmd: shlex_join(main_cmd),
path,
}
}
}
Some((head, tail)) if head == "find" => {
// Basic find support: capture path and common name filter
let (query, path) = parse_find_query_and_path(tail);
if query.is_some() {
ParsedCommand::Search {
cmd: shlex_join(main_cmd),
query,
path,
}
} else {
ParsedCommand::ListFiles {
cmd: shlex_join(main_cmd),
path,
}
}
}
Some((head, tail)) if matches!(head.as_str(), "grep" | "egrep" | "fgrep") => {
parse_grep_like(main_cmd, tail)
}
Some((head, tail)) if matches!(head.as_str(), "ag" | "ack" | "pt") => {
let args_no_connector = trim_at_connector(tail);
let candidates = skip_flag_values(
&args_no_connector,
&[
"-G",
"-g",
"--file-search-regex",
"--ignore-dir",
"--ignore-file",
"--path-to-ignore",
],
);
let non_flags: Vec<&String> = candidates
.into_iter()
.filter(|p| !p.starts_with('-'))
.collect();
let query = non_flags.first().cloned().map(String::from);
let path = non_flags.get(1).map(|s| short_display_path(s));
ParsedCommand::Search {
cmd: shlex_join(main_cmd),
query,
path,
}
}
Some((head, tail)) if head == "cat" => {
if let Some(path) = single_non_flag_operand(tail, &[]) {
let name = short_display_path(&path);
ParsedCommand::Read {
cmd: shlex_join(main_cmd),
name,
path: PathBuf::from(path),
}
} else {
ParsedCommand::Unknown {
cmd: shlex_join(main_cmd),
}
}
}
Some((head, tail)) if matches!(head.as_str(), "bat" | "batcat") => {
if let Some(path) = single_non_flag_operand(
tail,
&[
"--theme",
"--language",
"--style",
"--terminal-width",
"--tabs",
"--line-range",
"--map-syntax",
],
) {
let name = short_display_path(&path);
ParsedCommand::Read {
cmd: shlex_join(main_cmd),
name,
path: PathBuf::from(path),
}
} else {
ParsedCommand::Unknown {
cmd: shlex_join(main_cmd),
}
}
}
Some((head, tail)) if head == "less" => {
if let Some(path) = single_non_flag_operand(
tail,
&[
"-p",
"-P",
"-x",
"-y",
"-z",
"-j",
"--pattern",
"--prompt",
"--tabs",
"--shift",
"--jump-target",
],
) {
let name = short_display_path(&path);
ParsedCommand::Read {
cmd: shlex_join(main_cmd),
name,
path: PathBuf::from(path),
}
} else {
ParsedCommand::Unknown {
cmd: shlex_join(main_cmd),
}
}
}
Some((head, tail)) if head == "more" => {
if let Some(path) = single_non_flag_operand(tail, &[]) {
let name = short_display_path(&path);
ParsedCommand::Read {
cmd: shlex_join(main_cmd),
name,
path: PathBuf::from(path),
}
} else {
ParsedCommand::Unknown {
cmd: shlex_join(main_cmd),
}
}
}
Some((head, tail)) if head == "head" => {
// Support `head -n 50 file` and `head -n50 file` forms.
let has_valid_n = match tail.split_first() {
Some((first, rest)) if first == "-n" => rest
.first()
.is_some_and(|n| n.chars().all(|c| c.is_ascii_digit())),
Some((first, _)) if first.starts_with("-n") => {
first[2..].chars().all(|c| c.is_ascii_digit())
}
_ => false,
};
if has_valid_n {
// Build candidates skipping the numeric value consumed by `-n` when separated.
let mut candidates: Vec<&String> = Vec::new();
let mut i = 0;
while i < tail.len() {
if i == 0 && tail[i] == "-n" && i + 1 < tail.len() {
let n = &tail[i + 1];
if n.chars().all(|c| c.is_ascii_digit()) {
i += 2;
continue;
}
}
candidates.push(&tail[i]);
i += 1;
}
if let Some(p) = candidates.into_iter().find(|p| !p.starts_with('-')) {
let path = p.clone();
let name = short_display_path(&path);
return ParsedCommand::Read {
cmd: shlex_join(main_cmd),
name,
path: PathBuf::from(path),
};
}
}
if let [path] = tail
&& !path.starts_with('-')
{
let name = short_display_path(path);
return ParsedCommand::Read {
cmd: shlex_join(main_cmd),
name,
path: PathBuf::from(path),
};
}
ParsedCommand::Unknown {
cmd: shlex_join(main_cmd),
}
}
Some((head, tail)) if head == "tail" => {
// Support `tail -n +10 file` and `tail -n+10 file` forms.
let has_valid_n = match tail.split_first() {
Some((first, rest)) if first == "-n" => rest.first().is_some_and(|n| {
let s = n.strip_prefix('+').unwrap_or(n);
!s.is_empty() && s.chars().all(|c| c.is_ascii_digit())
}),
Some((first, _)) if first.starts_with("-n") => {
let v = &first[2..];
let s = v.strip_prefix('+').unwrap_or(v);
!s.is_empty() && s.chars().all(|c| c.is_ascii_digit())
}
_ => false,
};
if has_valid_n {
// Build candidates skipping the numeric value consumed by `-n` when separated.
let mut candidates: Vec<&String> = Vec::new();
let mut i = 0;
while i < tail.len() {
if i == 0 && tail[i] == "-n" && i + 1 < tail.len() {
let n = &tail[i + 1];
let s = n.strip_prefix('+').unwrap_or(n);
if !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()) {
i += 2;
continue;
}
}
candidates.push(&tail[i]);
i += 1;
}
if let Some(p) = candidates.into_iter().find(|p| !p.starts_with('-')) {
let path = p.clone();
let name = short_display_path(&path);
return ParsedCommand::Read {
cmd: shlex_join(main_cmd),
name,
path: PathBuf::from(path),
};
}
}
if let [path] = tail
&& !path.starts_with('-')
{
let name = short_display_path(path);
return ParsedCommand::Read {
cmd: shlex_join(main_cmd),
name,
path: PathBuf::from(path),
};
}
ParsedCommand::Unknown {
cmd: shlex_join(main_cmd),
}
}
Some((head, tail)) if head == "awk" => {
if let Some(path) = awk_data_file_operand(tail) {
let name = short_display_path(&path);
ParsedCommand::Read {
cmd: shlex_join(main_cmd),
name,
path: PathBuf::from(path),
}
} else {
ParsedCommand::Unknown {
cmd: shlex_join(main_cmd),
}
}
}
Some((head, tail)) if head == "nl" => {
// Avoid treating option values as paths (e.g., nl -s " ").
let candidates = skip_flag_values(tail, &["-s", "-w", "-v", "-i", "-b"]);
if let Some(p) = candidates.into_iter().find(|p| !p.starts_with('-')) {
let path = p.clone();
let name = short_display_path(&path);
ParsedCommand::Read {
cmd: shlex_join(main_cmd),
name,
path: PathBuf::from(path),
}
} else {
ParsedCommand::Unknown {
cmd: shlex_join(main_cmd),
}
}
}
Some((head, tail)) if head == "sed" => {
if let Some(path) = sed_read_path(tail) {
let name = short_display_path(&path);
ParsedCommand::Read {
cmd: shlex_join(main_cmd),
name,
path: PathBuf::from(path),
}
} else {
ParsedCommand::Unknown {
cmd: shlex_join(main_cmd),
}
}
}
Some((head, tail)) if is_python_command(head) => {
if python_walks_files(tail) {
ParsedCommand::ListFiles {
cmd: shlex_join(main_cmd),
path: None,
}
} else {
ParsedCommand::Unknown {
cmd: shlex_join(main_cmd),
}
}
}
// Other commands
_ => ParsedCommand::Unknown {
cmd: shlex_join(main_cmd),
},
}
}