use std::path::Path;
pub fn extract_worktree_name(
session_name: &str,
window_name: &str,
window_prefix: &str,
path: &Path,
) -> (String, bool) {
if let Some(stripped) = window_name.strip_prefix(window_prefix) {
(stripped.to_string(), false)
} else if let Some(stripped) = session_name.strip_prefix(window_prefix) {
(stripped.to_string(), false)
} else {
derive_worktree_name_from_path(path)
}
}
fn derive_worktree_name_from_path(path: &Path) -> (String, bool) {
let components: Vec<_> = path
.components()
.filter_map(|c| c.as_os_str().to_str())
.collect();
for (i, comp) in components.iter().enumerate().rev() {
if comp.ends_with("__worktrees")
&& let Some(&name) = components.get(i + 1)
{
return (name.to_string(), false);
}
if *comp == ".worktrees"
&& let Some(&name) = components.get(i + 1)
{
return (name.to_string(), false);
}
}
("main".to_string(), true)
}
pub fn extract_project_name(path: &Path) -> String {
for ancestor in path.ancestors() {
let git_path = ancestor.join(".git");
if git_path.is_dir()
&& let Some(name) = ancestor.file_name()
{
return name.to_string_lossy().to_string();
}
if let Some(name) = ancestor.file_name() {
let name_str = name.to_string_lossy();
if name_str.ends_with("__worktrees") {
return name_str
.strip_suffix("__worktrees")
.unwrap_or(&name_str)
.to_string();
}
}
}
path.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| path.to_string_lossy().to_string())
}
pub fn strip_oc_title_prefix(mut title: &str) -> &str {
while let Some((prefix, rest)) = title.split_once('|') {
if prefix.trim() != "OC" {
break;
}
title = rest.trim();
}
title
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_extract_worktree_name_window_mode() {
let path = Path::new("/home/user/myproject__worktrees/fix-bug");
let (name, is_main) =
extract_worktree_name("main-session", "workmux:fix-bug", "workmux:", path);
assert_eq!(name, "fix-bug");
assert!(!is_main);
}
#[test]
fn test_extract_worktree_name_session_mode() {
let path = Path::new("/home/user/myproject__worktrees/feature-auth");
let (name, is_main) =
extract_worktree_name("workmux:feature-auth", "zsh", "workmux:", path);
assert_eq!(name, "feature-auth");
assert!(!is_main);
}
#[test]
fn test_extract_worktree_name_window_preferred_over_session() {
let path = Path::new("/home/user/myproject__worktrees/from-window");
let (name, is_main) = extract_worktree_name(
"workmux:from-session",
"workmux:from-window",
"workmux:",
path,
);
assert_eq!(name, "from-window");
assert!(!is_main);
}
#[test]
fn test_extract_worktree_name_path_fallback_sibling() {
let path = Path::new("/home/user/myproject__worktrees/fix-bug");
let (name, is_main) = extract_worktree_name("0", "zsh", "workmux:", path);
assert_eq!(name, "fix-bug");
assert!(!is_main);
}
#[test]
fn test_extract_worktree_name_path_fallback_subdir() {
let path = Path::new("/home/user/myproject/.worktrees/fix-bug");
let (name, is_main) = extract_worktree_name("0", "zsh", "workmux:", path);
assert_eq!(name, "fix-bug");
assert!(!is_main);
}
#[test]
fn test_extract_worktree_name_path_fallback_nested_cwd() {
let path = Path::new("/home/user/myproject__worktrees/fix-bug/src/lib");
let (name, is_main) = extract_worktree_name("0", "zsh", "workmux:", path);
assert_eq!(name, "fix-bug");
assert!(!is_main);
}
#[test]
fn test_extract_worktree_name_path_fallback_main() {
let path = Path::new("/home/user/myproject");
let (name, is_main) = extract_worktree_name("0", "zsh", "workmux:", path);
assert_eq!(name, "main");
assert!(is_main);
}
#[test]
fn test_extract_project_name_worktrees() {
let path = PathBuf::from("/home/user/myproject__worktrees/fix-bug");
assert_eq!(extract_project_name(&path), "myproject");
}
#[test]
fn test_extract_project_name_fallback() {
let path = PathBuf::from("/home/user/myproject");
assert_eq!(extract_project_name(&path), "myproject");
}
#[test]
fn test_extract_project_name_git_root() {
let temp = tempfile::TempDir::new().unwrap();
let project_dir = temp.path().join("myproject");
std::fs::create_dir_all(project_dir.join(".git")).unwrap();
std::fs::create_dir_all(project_dir.join(".worktrees").join("fix-bug")).unwrap();
let worktree_path = project_dir.join(".worktrees").join("fix-bug");
assert_eq!(extract_project_name(&worktree_path), "myproject");
}
#[test]
fn test_extract_project_name_git_file_skipped() {
let temp = tempfile::TempDir::new().unwrap();
let project_dir = temp.path().join("myproject");
let worktree_dir = project_dir.join(".worktrees").join("fix-bug");
std::fs::create_dir_all(&worktree_dir).unwrap();
std::fs::write(worktree_dir.join(".git"), "gitdir: /somewhere/else").unwrap();
std::fs::create_dir_all(project_dir.join(".git")).unwrap();
assert_eq!(extract_project_name(&worktree_dir), "myproject");
}
#[test]
fn test_strip_oc_title_prefix() {
assert_eq!(
strip_oc_title_prefix("OC | Investigating..."),
"Investigating..."
);
assert_eq!(
strip_oc_title_prefix("OC | OC | Investigating..."),
"Investigating..."
);
assert_eq!(
strip_oc_title_prefix("Claude Code | Investigating..."),
"Claude Code | Investigating..."
);
}
}