Skip to main content

git_workty/commands/
pick.rs

1use crate::git::GitRepo;
2use crate::status::get_all_statuses;
3use crate::ui::{format_time, UiOptions};
4use crate::worktree::list_worktrees;
5use anyhow::{bail, Result};
6use console::Term;
7use dialoguer::FuzzySelect;
8use is_terminal::IsTerminal;
9
10pub fn execute(repo: &GitRepo, _opts: &UiOptions) -> Result<()> {
11    if !std::io::stdin().is_terminal() {
12        bail!("Cannot run interactive picker in non-TTY. Use `git workty go <name>` instead.");
13    }
14
15    let worktrees = list_worktrees(repo)?;
16    if worktrees.is_empty() {
17        bail!("No worktrees found");
18    }
19
20    // Get status for richer display
21    let statuses = get_all_statuses(repo, &worktrees);
22
23    // Find max name length for alignment
24    let max_name_len = statuses
25        .iter()
26        .map(|(wt, _)| wt.name().len())
27        .max()
28        .unwrap_or(10);
29
30    let items: Vec<String> = statuses
31        .iter()
32        .map(|(wt, status)| {
33            let name = format!("{:width$}", wt.name(), width = max_name_len);
34
35            // Dirty indicator
36            let dirty = if status.dirty_count > 0 {
37                format!("*{}", status.dirty_count)
38            } else {
39                " ".to_string()
40            };
41
42            // Time since last commit
43            let time = format_time(status.last_commit_time);
44
45            // Needs rebase indicator
46            let rebase = if status.needs_rebase() {
47                format!("R{}", status.behind_main.unwrap_or(0))
48            } else {
49                "  ".to_string()
50            };
51
52            format!("{}  {:>3}  {:>4}  {}", name, dirty, time, rebase)
53        })
54        .collect();
55
56    let selection = FuzzySelect::new()
57        .with_prompt("Select worktree")
58        .items(&items)
59        .default(0)
60        .interact_on_opt(&Term::stderr())?;
61
62    match selection {
63        Some(idx) => {
64            println!("{}", statuses[idx].0.path.display());
65            Ok(())
66        }
67        None => {
68            // User cancelled - exit with 130 (128 + SIGINT)
69            std::process::exit(130);
70        }
71    }
72}