Detailed changes
@@ -567,19 +567,12 @@ impl ThreadMetadataStore {
PathList::new(&paths)
};
- let main_worktree_paths = {
- let project = thread_ref.project().read(cx);
- let mut main_paths: Vec<Arc<Path>> = Vec::new();
- for repo in project.repositories(cx).values() {
- let snapshot = repo.read(cx).snapshot();
- if snapshot.is_linked_worktree() {
- main_paths.push(snapshot.original_repo_abs_path.clone());
- }
- }
- main_paths.sort();
- main_paths.dedup();
- PathList::new(&main_paths)
- };
+ let main_worktree_paths = thread_ref
+ .project()
+ .read(cx)
+ .project_group_key(cx)
+ .path_list()
+ .clone();
// Threads without a folder path (e.g. started in an empty
// window) are archived by default so they don't get lost,
@@ -163,6 +163,7 @@ struct WorktreeInfo {
name: SharedString,
full_path: SharedString,
highlight_positions: Vec<usize>,
+ kind: ui::WorktreeKind,
}
#[derive(Clone)]
@@ -307,23 +308,25 @@ fn workspace_path_list(workspace: &Entity<Workspace>, cx: &App) -> PathList {
/// Derives worktree display info from a thread's stored path list.
///
-/// For each path in the thread's `folder_paths` that is not one of the
-/// group's main paths (i.e. it's a git linked worktree), produces a
-/// [`WorktreeInfo`] with the short worktree name and full path.
+/// For each path in the thread's `folder_paths`, produces a
+/// [`WorktreeInfo`] with a short display name, full path, and whether
+/// the worktree is the main checkout or a linked git worktree.
fn worktree_info_from_thread_paths(
folder_paths: &PathList,
group_key: &project::ProjectGroupKey,
-) -> Vec<WorktreeInfo> {
+) -> impl Iterator<Item = WorktreeInfo> {
let main_paths = group_key.path_list().paths();
- folder_paths
- .paths()
- .iter()
- .filter_map(|path| {
- if main_paths.iter().any(|mp| mp.as_path() == path.as_path()) {
- return None;
- }
- // Find the main path whose file name matches this linked
- // worktree's file name, falling back to the first main path.
+ folder_paths.paths().iter().filter_map(|path| {
+ let is_main = main_paths.iter().any(|mp| mp.as_path() == path.as_path());
+ if is_main {
+ let name = path.file_name()?.to_string_lossy().to_string();
+ Some(WorktreeInfo {
+ name: SharedString::from(name),
+ full_path: SharedString::from(path.display().to_string()),
+ highlight_positions: Vec::new(),
+ kind: ui::WorktreeKind::Main,
+ })
+ } else {
let main_path = main_paths
.iter()
.find(|mp| mp.file_name() == path.file_name())
@@ -332,9 +335,10 @@ fn worktree_info_from_thread_paths(
name: linked_worktree_short_name(main_path, path).unwrap_or_default(),
full_path: SharedString::from(path.display().to_string()),
highlight_positions: Vec::new(),
+ kind: ui::WorktreeKind::Linked,
})
- })
- .collect()
+ }
+ })
}
/// The sidebar re-derives its entire entry list from scratch on every
@@ -851,7 +855,8 @@ impl Sidebar {
workspace: ThreadEntryWorkspace|
-> ThreadEntry {
let (icon, icon_from_external_svg) = resolve_agent_icon(&row.agent_id);
- let worktrees = worktree_info_from_thread_paths(&row.folder_paths, &group_key);
+ let worktrees: Vec<WorktreeInfo> =
+ worktree_info_from_thread_paths(&row.folder_paths, &group_key).collect();
ThreadEntry {
metadata: row,
icon,
@@ -1059,7 +1064,9 @@ impl Sidebar {
if let Some(ActiveEntry::Draft(draft_ws)) = &self.active_entry {
let ws_path_list = workspace_path_list(draft_ws, cx);
let worktrees = worktree_info_from_thread_paths(&ws_path_list, &group_key);
- entries.push(ListEntry::DraftThread { worktrees });
+ entries.push(ListEntry::DraftThread {
+ worktrees: worktrees.collect(),
+ });
}
}
@@ -1073,7 +1080,8 @@ impl Sidebar {
&& active_workspace.as_ref().is_some_and(|active_ws| {
let ws_path_list = workspace_path_list(active_ws, cx);
let has_linked_worktrees =
- !worktree_info_from_thread_paths(&ws_path_list, &group_key).is_empty();
+ worktree_info_from_thread_paths(&ws_path_list, &group_key)
+ .any(|wt| wt.kind == ui::WorktreeKind::Linked);
if !has_linked_worktrees {
return false;
}
@@ -1102,6 +1110,7 @@ impl Sidebar {
&workspace_path_list(ws, cx),
&group_key,
)
+ .collect()
})
.unwrap_or_default()
} else {
@@ -2545,6 +2554,7 @@ impl Sidebar {
name: wt.name.clone(),
full_path: wt.full_path.clone(),
highlight_positions: Vec::new(),
+ kind: wt.kind,
})
.collect(),
diff_stats: thread.diff_stats,
@@ -2817,6 +2827,7 @@ impl Sidebar {
name: wt.name.clone(),
full_path: wt.full_path.clone(),
highlight_positions: wt.highlight_positions.clone(),
+ kind: wt.kind,
})
.collect(),
)
@@ -3095,6 +3106,7 @@ impl Sidebar {
name: wt.name.clone(),
full_path: wt.full_path.clone(),
highlight_positions: wt.highlight_positions.clone(),
+ kind: wt.kind,
})
.collect(),
)
@@ -3132,6 +3144,7 @@ impl Sidebar {
name: wt.name.clone(),
full_path: wt.full_path.clone(),
highlight_positions: wt.highlight_positions.clone(),
+ kind: wt.kind,
})
.collect(),
)
@@ -191,6 +191,25 @@ fn focus_sidebar(sidebar: &Entity<Sidebar>, cx: &mut gpui::VisualTestContext) {
cx.run_until_parked();
}
+fn format_linked_worktree_chips(worktrees: &[WorktreeInfo]) -> String {
+ let mut seen = Vec::new();
+ let mut chips = Vec::new();
+ for wt in worktrees {
+ if wt.kind == ui::WorktreeKind::Main {
+ continue;
+ }
+ if !seen.contains(&wt.name) {
+ seen.push(wt.name.clone());
+ chips.push(format!("{{{}}}", wt.name));
+ }
+ }
+ if chips.is_empty() {
+ String::new()
+ } else {
+ format!(" {}", chips.join(", "))
+ }
+}
+
fn visible_entries_as_strings(
sidebar: &Entity<Sidebar>,
cx: &mut gpui::VisualTestContext,
@@ -238,23 +257,8 @@ fn visible_entries_as_strings(
} else {
""
};
- let worktree = if thread.worktrees.is_empty() {
- String::new()
- } else {
- let mut seen = Vec::new();
- let mut chips = Vec::new();
- for wt in &thread.worktrees {
- if !seen.contains(&wt.name) {
- seen.push(wt.name.clone());
- chips.push(format!("{{{}}}", wt.name));
- }
- }
- format!(" {}", chips.join(", "))
- };
- format!(
- " {}{}{}{}{}{}",
- title, worktree, active, status_str, notified, selected
- )
+ let worktree = format_linked_worktree_chips(&thread.worktrees);
+ format!(" {title}{worktree}{active}{status_str}{notified}{selected}")
}
ListEntry::ViewMore {
is_fully_expanded, ..
@@ -266,35 +270,11 @@ fn visible_entries_as_strings(
}
}
ListEntry::DraftThread { worktrees, .. } => {
- let worktree = if worktrees.is_empty() {
- String::new()
- } else {
- let mut seen = Vec::new();
- let mut chips = Vec::new();
- for wt in worktrees {
- if !seen.contains(&wt.name) {
- seen.push(wt.name.clone());
- chips.push(format!("{{{}}}", wt.name));
- }
- }
- format!(" {}", chips.join(", "))
- };
+ let worktree = format_linked_worktree_chips(worktrees);
format!(" [~ Draft{}]{}", worktree, selected)
}
ListEntry::NewThread { worktrees, .. } => {
- let worktree = if worktrees.is_empty() {
- String::new()
- } else {
- let mut seen = Vec::new();
- let mut chips = Vec::new();
- for wt in worktrees {
- if !seen.contains(&wt.name) {
- seen.push(wt.name.clone());
- chips.push(format!("{{{}}}", wt.name));
- }
- }
- format!(" {}", chips.join(", "))
- };
+ let worktree = format_linked_worktree_chips(worktrees);
format!(" [+ New Thread{}]{}", worktree, selected)
}
}
@@ -16,11 +16,19 @@ pub enum AgentThreadStatus {
Error,
}
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub enum WorktreeKind {
+ #[default]
+ Main,
+ Linked,
+}
+
#[derive(Clone)]
pub struct ThreadItemWorktreeInfo {
pub name: SharedString,
pub full_path: SharedString,
pub highlight_positions: Vec<usize>,
+ pub kind: WorktreeKind,
}
#[derive(IntoElement, RegisterComponent)]
@@ -359,7 +367,10 @@ impl RenderOnce for ThreadItem {
let has_project_name = self.project_name.is_some();
let has_project_paths = project_paths.is_some();
- let has_worktree = !self.worktrees.is_empty();
+ let has_worktree = self
+ .worktrees
+ .iter()
+ .any(|wt| wt.kind == WorktreeKind::Linked);
let has_timestamp = !self.timestamp.is_empty();
let timestamp = self.timestamp;
@@ -449,6 +460,10 @@ impl RenderOnce for ThreadItem {
continue;
}
+ if wt.kind == WorktreeKind::Main {
+ continue;
+ }
+
let chip_index = seen_names.len();
seen_names.push(wt.name.clone());
@@ -624,6 +639,7 @@ impl Component for ThreadItem {
name: "link-agent-panel".into(),
full_path: "link-agent-panel".into(),
highlight_positions: Vec::new(),
+ kind: WorktreeKind::Linked,
}]),
)
.into_any_element(),
@@ -650,6 +666,7 @@ impl Component for ThreadItem {
name: "my-project".into(),
full_path: "my-project".into(),
highlight_positions: Vec::new(),
+ kind: WorktreeKind::Linked,
}])
.added(42)
.removed(17)
@@ -729,6 +746,7 @@ impl Component for ThreadItem {
name: "my-project-name".into(),
full_path: "my-project-name".into(),
highlight_positions: vec![3, 4, 5, 6, 7, 8, 9, 10, 11],
+ kind: WorktreeKind::Linked,
}]),
)
.into_any_element(),