limb 0.1.0

A focused CLI for git worktree management
Documentation
//! Implements `limb pick`. Interactive TUI worktree picker.

use anyhow::Result;

use crate::context::Context;
use crate::tui::app::{Entry, Mode};
use crate::tui::theme::{Theme, ThemeKind};
use crate::{tui, worktree};

/// Runs `limb pick`.
///
/// Dispatches on whether `projects.roots` is configured: empty → single
/// repo picker; non-empty → cross-repo picker scanning every root. Prints
/// the selected worktree's path to stdout (consumed by the shell wrapper
/// to `cd` the parent shell).
///
/// # Errors
///
/// Returns an error if no selectable worktrees exist, the terminal isn't
/// a TTY, or the TUI itself fails. Cancellation via `Esc` returns
/// [`crate::error::Canceled`] which exits `130`.
pub fn run(ctx: &Context) -> Result<()> {
    let global = ctx.global()?;
    let (entries, mode) = if global.projects_roots.is_empty() {
        single_repo(ctx)?
    } else {
        cross_repo(&global.projects_roots)
    };

    let selectable: Vec<_> = entries.into_iter().filter(|e| !e.worktree.bare).collect();
    if selectable.is_empty() {
        anyhow::bail!("no selectable worktrees; try: `limb add <name>` to create one");
    }

    let theme_kind = ThemeKind::from_name(&global.ui_theme);
    let theme = Theme::resolve(theme_kind, ctx.no_color);
    let path = tui::run_picker(selectable, mode, theme)?;
    println!("{}", path.display());
    Ok(())
}

fn single_repo(ctx: &Context) -> Result<(Vec<Entry>, Mode)> {
    let repo = ctx.repo()?;
    let rows = worktree::list_with_status(&repo)?;
    let entries = rows
        .into_iter()
        .map(|s| Entry {
            repo: None,
            worktree: s.worktree,
            dirty: s.dirty_files,
            upstream: s.upstream,
        })
        .collect();
    Ok((entries, Mode::SingleRepo))
}

fn cross_repo(roots: &[std::path::PathBuf]) -> (Vec<Entry>, Mode) {
    let rows = worktree::list_all_with_status(roots);
    let entries = rows
        .into_iter()
        .map(|s| Entry {
            repo: Some(s.repo),
            worktree: s.status.worktree,
            dirty: s.status.dirty_files,
            upstream: s.status.upstream,
        })
        .collect();
    (entries, Mode::CrossRepo)
}