Skip to content

Commit 8bd2d2d

Browse files
author
Test User
committed
style: restore list UI to v0.4.0 table format
1 parent 592b976 commit 8bd2d2d

1 file changed

Lines changed: 298 additions & 32 deletions

File tree

src/commands/list.rs

Lines changed: 298 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
use anyhow::Result;
22
use colored::*;
33

4-
use crate::constants::{section_header, WARNING_NO_WORKTREES};
4+
use crate::constants::{
5+
section_header, CURRENT_MARKER, ICON_CURRENT_WORKTREE, ICON_OTHER_WORKTREE, MODIFIED_STATUS_NO,
6+
MODIFIED_STATUS_YES, TABLE_HEADER_BRANCH, TABLE_HEADER_MODIFIED, TABLE_HEADER_NAME,
7+
TABLE_HEADER_PATH, TABLE_SEPARATOR, WARNING_NO_WORKTREES,
8+
};
59
use crate::git::{GitWorktreeManager, WorktreeInfo};
10+
use crate::repository_info::get_repository_info;
611
use crate::ui::{DialoguerUI, UserInterface};
712
use crate::utils::press_any_key_to_continue;
813

@@ -92,56 +97,98 @@ pub fn list_worktrees() -> Result<()> {
9297
pub fn list_worktrees_with_ui(manager: &GitWorktreeManager, _ui: &dyn UserInterface) -> Result<()> {
9398
let worktrees = manager.list_worktrees()?;
9499

95-
println!();
96-
let header = section_header("Worktrees");
97-
println!("{header}");
98-
println!();
99-
100100
if worktrees.is_empty() {
101+
println!();
101102
let msg = WARNING_NO_WORKTREES.yellow();
102103
println!("{msg}");
103104
println!();
104105
press_any_key_to_continue()?;
105106
return Ok(());
106107
}
107108

108-
// Print table header
109+
// Sort worktrees: current first, then alphabetically
110+
let mut sorted_worktrees = worktrees;
111+
sorted_worktrees.sort_by(|a, b| {
112+
if a.is_current && !b.is_current {
113+
std::cmp::Ordering::Less
114+
} else if !a.is_current && b.is_current {
115+
std::cmp::Ordering::Greater
116+
} else {
117+
a.name.cmp(&b.name)
118+
}
119+
});
120+
121+
// Print header
122+
println!();
123+
let header = section_header("Worktrees");
124+
println!("{header}");
125+
println!();
126+
127+
// Display repository info
128+
let repo_info = get_repository_info();
129+
println!("Repository: {}", repo_info.bright_cyan());
130+
println!();
131+
132+
// Calculate column widths
133+
let max_name_len = sorted_worktrees
134+
.iter()
135+
.map(|w| w.name.len())
136+
.max()
137+
.unwrap_or(0)
138+
.max(10);
139+
let max_branch_len = sorted_worktrees
140+
.iter()
141+
.map(|w| w.branch.len())
142+
.max()
143+
.unwrap_or(0)
144+
.max(10)
145+
+ 10; // Extra space for [current] marker
146+
147+
println!();
109148
println!(
110-
" {:<27} {:<37} {} {}",
111-
"Name".bold(),
112-
"Branch".bold(),
113-
"Modified".bold(),
114-
"Path".bold()
149+
" {:<name_width$} {:<branch_width$} {:<8} {}",
150+
TABLE_HEADER_NAME.bold(),
151+
TABLE_HEADER_BRANCH.bold(),
152+
TABLE_HEADER_MODIFIED.bold(),
153+
TABLE_HEADER_PATH.bold(),
154+
name_width = max_name_len,
155+
branch_width = max_branch_len
115156
);
116157
println!(
117-
" {} {} {} {}",
118-
"-".repeat(27).dimmed(),
119-
"-".repeat(37).dimmed(),
120-
"-".repeat(8).dimmed(),
121-
"-".repeat(40).dimmed()
158+
" {TABLE_SEPARATOR:-<max_name_len$} {TABLE_SEPARATOR:-<max_branch_len$} {TABLE_SEPARATOR:-<8} {TABLE_SEPARATOR:-<40}"
122159
);
123160

124161
// Display worktrees in table format
125-
for worktree in &worktrees {
126-
let current_indicator = if worktree.is_current { "▸ " } else { " " };
127-
128-
let name = if worktree.is_current {
129-
worktree.name.bright_white().bold().to_string()
162+
for worktree in &sorted_worktrees {
163+
let icon = if worktree.is_current {
164+
ICON_CURRENT_WORKTREE.bright_green().bold()
130165
} else {
131-
worktree.name.clone()
166+
ICON_OTHER_WORKTREE.bright_blue()
167+
};
168+
let branch_display = if worktree.is_current {
169+
format!("{} {}", worktree.branch, CURRENT_MARKER).bright_green()
170+
} else {
171+
worktree.branch.yellow()
172+
};
173+
let modified = if worktree.has_changes {
174+
MODIFIED_STATUS_YES.bright_yellow()
175+
} else {
176+
MODIFIED_STATUS_NO.bright_black()
132177
};
133-
134-
let branch = &worktree.branch;
135-
let modified = if worktree.has_changes { "Yes" } else { "No" };
136-
let path = worktree.path.display();
137178

138179
println!(
139-
"{}{:<27} {:<37} {:<8} {}",
140-
current_indicator.green(),
141-
name,
142-
branch.yellow(),
180+
"{} {:<name_width$} {:<branch_width$} {:<8} {}",
181+
icon,
182+
if worktree.is_current {
183+
worktree.name.bright_green().bold()
184+
} else {
185+
worktree.name.normal()
186+
},
187+
branch_display,
143188
modified,
144-
path.to_string().dimmed()
189+
worktree.path.display().to_string().dimmed(),
190+
name_width = max_name_len,
191+
branch_width = max_branch_len
145192
);
146193
}
147194

@@ -408,4 +455,223 @@ mod tests {
408455
Some(no_match_filter)
409456
));
410457
}
458+
459+
// Tests to protect the current table display implementation
460+
#[test]
461+
fn test_table_display_current_worktree_first() {
462+
// Create test worktrees with one being current
463+
let worktree1 = WorktreeInfo {
464+
name: "zebra".to_string(),
465+
path: PathBuf::from("/tmp/zebra"),
466+
branch: "zebra".to_string(),
467+
is_current: false,
468+
has_changes: false,
469+
last_commit: None,
470+
ahead_behind: None,
471+
is_locked: false,
472+
};
473+
let worktree2 = WorktreeInfo {
474+
name: "alpha".to_string(),
475+
path: PathBuf::from("/tmp/alpha"),
476+
branch: "alpha".to_string(),
477+
is_current: true,
478+
has_changes: false,
479+
last_commit: None,
480+
ahead_behind: None,
481+
is_locked: false,
482+
};
483+
let worktree3 = WorktreeInfo {
484+
name: "beta".to_string(),
485+
path: PathBuf::from("/tmp/beta"),
486+
branch: "beta".to_string(),
487+
is_current: false,
488+
has_changes: false,
489+
last_commit: None,
490+
ahead_behind: None,
491+
is_locked: false,
492+
};
493+
494+
let mut worktrees = vec![worktree1, worktree2, worktree3];
495+
496+
// Apply the same sorting logic as the main function
497+
worktrees.sort_by(|a, b| {
498+
if a.is_current && !b.is_current {
499+
std::cmp::Ordering::Less
500+
} else if !a.is_current && b.is_current {
501+
std::cmp::Ordering::Greater
502+
} else {
503+
a.name.cmp(&b.name)
504+
}
505+
});
506+
507+
// Current worktree should be first
508+
assert_eq!(worktrees[0].name, "alpha");
509+
assert!(worktrees[0].is_current);
510+
// Others should be alphabetically sorted
511+
assert_eq!(worktrees[1].name, "beta");
512+
assert_eq!(worktrees[2].name, "zebra");
513+
}
514+
515+
#[test]
516+
fn test_table_display_column_width_calculation() {
517+
let worktrees = vec![
518+
WorktreeInfo {
519+
name: "short".to_string(),
520+
path: PathBuf::from("/tmp/short"),
521+
branch: "main".to_string(),
522+
is_current: false,
523+
has_changes: false,
524+
last_commit: None,
525+
ahead_behind: None,
526+
is_locked: false,
527+
},
528+
WorktreeInfo {
529+
name: "very-long-worktree-name".to_string(),
530+
path: PathBuf::from("/tmp/very-long-worktree-name"),
531+
branch: "feature-with-very-long-branch-name".to_string(),
532+
is_current: true,
533+
has_changes: false,
534+
last_commit: None,
535+
ahead_behind: None,
536+
is_locked: false,
537+
},
538+
];
539+
540+
let max_name_len = worktrees
541+
.iter()
542+
.map(|w| w.name.len())
543+
.max()
544+
.unwrap_or(0)
545+
.max(10);
546+
let max_branch_len = worktrees
547+
.iter()
548+
.map(|w| w.branch.len())
549+
.max()
550+
.unwrap_or(0)
551+
.max(10)
552+
+ 10; // Extra space for [current] marker
553+
554+
assert_eq!(max_name_len, "very-long-worktree-name".len());
555+
assert_eq!(
556+
max_branch_len,
557+
"feature-with-very-long-branch-name".len() + 10
558+
);
559+
}
560+
561+
#[test]
562+
fn test_table_display_icon_selection() {
563+
let current_worktree = WorktreeInfo {
564+
name: "current".to_string(),
565+
path: PathBuf::from("/tmp/current"),
566+
branch: "main".to_string(),
567+
is_current: true,
568+
has_changes: false,
569+
last_commit: None,
570+
ahead_behind: None,
571+
is_locked: false,
572+
};
573+
let other_worktree = WorktreeInfo {
574+
name: "other".to_string(),
575+
path: PathBuf::from("/tmp/other"),
576+
branch: "feature".to_string(),
577+
is_current: false,
578+
has_changes: false,
579+
last_commit: None,
580+
ahead_behind: None,
581+
is_locked: false,
582+
};
583+
584+
// Test icon selection logic
585+
let current_icon = if current_worktree.is_current {
586+
"▸"
587+
} else {
588+
" "
589+
};
590+
let other_icon = if other_worktree.is_current {
591+
"▸"
592+
} else {
593+
" "
594+
};
595+
596+
assert_eq!(current_icon, "▸");
597+
assert_eq!(other_icon, " ");
598+
}
599+
600+
#[test]
601+
fn test_table_display_branch_formatting() {
602+
let current_worktree = WorktreeInfo {
603+
name: "current".to_string(),
604+
path: PathBuf::from("/tmp/current"),
605+
branch: "main".to_string(),
606+
is_current: true,
607+
has_changes: false,
608+
last_commit: None,
609+
ahead_behind: None,
610+
is_locked: false,
611+
};
612+
let other_worktree = WorktreeInfo {
613+
name: "other".to_string(),
614+
path: PathBuf::from("/tmp/other"),
615+
branch: "feature".to_string(),
616+
is_current: false,
617+
has_changes: false,
618+
last_commit: None,
619+
ahead_behind: None,
620+
is_locked: false,
621+
};
622+
623+
// Test branch display formatting
624+
let current_branch_display = if current_worktree.is_current {
625+
format!("{} [current]", current_worktree.branch)
626+
} else {
627+
current_worktree.branch.clone()
628+
};
629+
let other_branch_display = if other_worktree.is_current {
630+
format!("{} [current]", other_worktree.branch)
631+
} else {
632+
other_worktree.branch.clone()
633+
};
634+
635+
assert_eq!(current_branch_display, "main [current]");
636+
assert_eq!(other_branch_display, "feature");
637+
}
638+
639+
#[test]
640+
fn test_table_display_modified_status() {
641+
let clean_worktree = WorktreeInfo {
642+
name: "clean".to_string(),
643+
path: PathBuf::from("/tmp/clean"),
644+
branch: "main".to_string(),
645+
is_current: false,
646+
has_changes: false,
647+
last_commit: None,
648+
ahead_behind: None,
649+
is_locked: false,
650+
};
651+
let dirty_worktree = WorktreeInfo {
652+
name: "dirty".to_string(),
653+
path: PathBuf::from("/tmp/dirty"),
654+
branch: "feature".to_string(),
655+
is_current: false,
656+
has_changes: true,
657+
last_commit: None,
658+
ahead_behind: None,
659+
is_locked: false,
660+
};
661+
662+
// Test modified status display
663+
let clean_modified = if clean_worktree.has_changes {
664+
"Yes"
665+
} else {
666+
"No"
667+
};
668+
let dirty_modified = if dirty_worktree.has_changes {
669+
"Yes"
670+
} else {
671+
"No"
672+
};
673+
674+
assert_eq!(clean_modified, "No");
675+
assert_eq!(dirty_modified, "Yes");
676+
}
411677
}

0 commit comments

Comments
 (0)