#[allow(unused_imports)]
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use clap_complete::engine::{ArgValueCompleter, CompletionCandidate};
use std::ffi::OsStr;
use std::process::Command;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
#[arg(long, value_name = "WHEN", global = true, ignore_case = true)]
pub color: Option<crate::color::ColorMode>,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
Add {
branch: String,
#[arg(add = ArgValueCompleter::new(list_git_refs))]
start_point: Option<String>,
#[arg(long, conflicts_with = "no_tmux")]
tmux: bool,
#[arg(long, conflicts_with = "tmux")]
no_tmux: bool,
},
Create {
branch: String,
#[arg(add = ArgValueCompleter::new(list_git_refs))]
start_point: Option<String>,
},
Ls {
#[arg(long)]
show_path: bool,
},
Rm {
#[arg(num_args = 0.., value_name = "TARGET", add = ArgValueCompleter::new(list_git_worktrees))]
targets: Vec<String>,
},
Cd {
#[arg(add = ArgValueCompleter::new(list_git_worktrees))]
name: Option<String>,
},
Init {
#[arg(long, conflicts_with = "local")]
global: bool,
#[arg(long, conflicts_with = "global")]
local: bool,
#[arg(short, long)]
force: bool,
},
Completion {
shell: String,
},
ShellInit {
shell: String,
},
}
#[must_use]
pub fn list_git_refs(current: &OsStr) -> Vec<CompletionCandidate> {
let output = Command::new("git")
.args([
"for-each-ref",
"--format=%(refname:short)%09%(symref)",
"refs/heads",
"refs/remotes",
"refs/tags",
])
.output();
let Ok(output) = output else {
return Vec::new();
};
if !output.status.success() {
return Vec::new();
}
let prefix = current.to_string_lossy();
String::from_utf8_lossy(&output.stdout)
.lines()
.filter_map(|line| {
let parts: Vec<&str> = line.split('\t').collect();
let refname = parts.first()?.trim();
let symref = parts.get(1).map_or("", |s| s.trim());
if !symref.is_empty() {
return None;
}
if !refname.starts_with(&*prefix) {
return None;
}
Some(CompletionCandidate::new(refname))
})
.collect()
}
#[must_use]
#[allow(dead_code)] pub fn list_git_branches(current: &OsStr) -> Vec<CompletionCandidate> {
let output = Command::new("git")
.args([
"for-each-ref",
"--format=%(refname:short)%09%(symref)",
"refs/heads",
"refs/remotes",
])
.output();
let Ok(output) = output else {
return Vec::new();
};
if !output.status.success() {
return Vec::new();
}
let prefix = current.to_string_lossy();
String::from_utf8_lossy(&output.stdout)
.lines()
.filter_map(|line| {
let parts: Vec<&str> = line.split('\t').collect();
let refname = parts.first()?.trim();
let symref = parts.get(1).map_or("", |s| s.trim());
if !symref.is_empty() {
return None;
}
if !refname.starts_with(&*prefix) {
return None;
}
Some(CompletionCandidate::new(refname))
})
.collect()
}
pub fn list_git_worktrees(current: &OsStr) -> Vec<CompletionCandidate> {
let output = Command::new("git")
.args(["worktree", "list", "--porcelain"])
.output();
let Ok(output) = output else {
return Vec::new();
};
if !output.status.success() {
return Vec::new();
}
let prefix = current.to_string_lossy();
let stdout = String::from_utf8_lossy(&output.stdout);
let mut candidates = Vec::new();
if "@".starts_with(&*prefix) {
candidates.push(CompletionCandidate::new("@"));
}
candidates.extend(
parse_worktree_list(&stdout)
.into_iter()
.filter(|name| name.starts_with(&*prefix))
.map(CompletionCandidate::new),
);
candidates
}
#[must_use]
pub fn parse_worktree_list(output: &str) -> Vec<String> {
let mut branches = Vec::new();
let mut worktree_index = 0;
let mut current_branch: Option<String> = None;
for line in output.lines() {
if line.starts_with("worktree ") {
if let Some(branch) = current_branch.take() {
if worktree_index > 0 {
branches.push(branch);
}
}
worktree_index += 1;
} else if line.starts_with("branch ") {
if let Some(branch_ref) = line.strip_prefix("branch ") {
let branch = branch_ref.strip_prefix("refs/heads/").unwrap_or(branch_ref);
current_branch = Some(branch.to_string());
}
} else if line.is_empty() {
if let Some(branch) = current_branch.take() {
if worktree_index > 1 {
branches.push(branch);
}
}
}
}
if let Some(branch) = current_branch {
if worktree_index > 1 {
branches.push(branch);
}
}
branches
}