Sidebar and git cleanups (#52431)

A few minor cleanups in sidebar.rs and some related areas.

Release notes:

- N/A
This commit is contained in:
Eric Holk authored and GitHub committed 2026-03-25 17:16:51 +00:00
1 parent d434ac4446
commit edc8bafa3c
2 files changed
+114 -87

No files matched your search

+17
View File
@@ -3706,6 +3706,23 @@ impl RepositorySnapshot {
}
}
/// The main worktree is the original checkout that other worktrees were
/// created from.
///
/// For example, if you had both `~/code/zed` and `~/code/worktrees/zed-2`,
/// then `~/code/zed` is the main worktree and `~/code/worktrees/zed-2` is a linked worktree.
pub fn is_main_worktree(&self) -> bool {
self.work_directory_abs_path == self.original_repo_abs_path
}
/// Returns true if this repository is a linked worktree, that is, one that
/// was created from another worktree.
///
/// This is by definition the opposite of [`Self::is_main_worktree`].
pub fn is_linked_worktree(&self) -> bool {
!self.is_main_worktree()
}
pub fn linked_worktrees(&self) -> &[GitWorktree] {
&self.linked_worktrees
}
+97 -87
View File
@@ -190,21 +190,17 @@ fn fuzzy_match_positions(query: &str, candidate: &str) -> Option<Vec<usize>> {
fn root_repository_snapshots(
workspace: &Entity<Workspace>,
cx: &App,
) -> Vec<project::git_store::RepositorySnapshot> {
) -> impl Iterator<Item = project::git_store::RepositorySnapshot> {
let path_list = workspace_path_list(workspace, cx);
let project = workspace.read(cx).project().read(cx);
project
.repositories(cx)
.values()
.filter_map(|repo| {
let snapshot = repo.read(cx).snapshot();
let is_root = path_list
.paths()
.iter()
.any(|p| p.as_path() == snapshot.work_directory_abs_path.as_ref());
is_root.then_some(snapshot)
})
.collect()
project.repositories(cx).values().filter_map(move |repo| {
let snapshot = repo.read(cx).snapshot();
let is_root = path_list
.paths()
.iter()
.any(|p| p.as_path() == snapshot.work_directory_abs_path.as_ref());
is_root.then_some(snapshot)
})
}
fn workspace_path_list(workspace: &Entity<Workspace>, cx: &App) -> PathList {
@@ -544,59 +540,6 @@ impl Sidebar {
result
}
fn all_thread_infos_for_workspace(
workspace: &Entity<Workspace>,
cx: &App,
) -> Vec<ActiveThreadInfo> {
let Some(agent_panel) = workspace.read(cx).panel::<AgentPanel>(cx) else {
return Vec::new();
};
let agent_panel_ref = agent_panel.read(cx);
agent_panel_ref
.parent_threads(cx)
.into_iter()
.map(|thread_view| {
let thread_view_ref = thread_view.read(cx);
let thread = thread_view_ref.thread.read(cx);
let icon = thread_view_ref.agent_icon;
let icon_from_external_svg = thread_view_ref.agent_icon_from_external_svg.clone();
let title = thread
.title()
.unwrap_or_else(|| DEFAULT_THREAD_TITLE.into());
let is_native = thread_view_ref.as_native_thread(cx).is_some();
let is_title_generating = is_native && thread.has_provisional_title();
let session_id = thread.session_id().clone();
let is_background = agent_panel_ref.is_background_thread(&session_id);
let status = if thread.is_waiting_for_confirmation() {
AgentThreadStatus::WaitingForConfirmation
} else if thread.had_error() {
AgentThreadStatus::Error
} else {
match thread.status() {
ThreadStatus::Generating => AgentThreadStatus::Running,
ThreadStatus::Idle => AgentThreadStatus::Completed,
}
};
let diff_stats = thread.action_log().read(cx).diff_stats(cx);
ActiveThreadInfo {
session_id,
title,
status,
icon,
icon_from_external_svg,
is_background,
is_title_generating,
diff_stats,
}
})
.collect()
}
/// When modifying this thread, aim for a single forward pass over workspaces
/// and threads plus an O(T log T) sort. Avoid adding extra scans over the data.
fn rebuild_contents(&mut self, cx: &App) {
@@ -686,7 +629,7 @@ impl Sidebar {
for (i, workspace) in workspaces.iter().enumerate() {
for snapshot in root_repository_snapshots(workspace, cx) {
if snapshot.work_directory_abs_path == snapshot.original_repo_abs_path {
if snapshot.is_main_worktree() {
main_repo_workspace
.entry(snapshot.work_directory_abs_path.clone())
.or_insert(i);
@@ -773,7 +716,7 @@ impl Sidebar {
.is_some_and(|(main_idx, _)| *main_idx == ws_index)
});
let mut live_infos = Self::all_thread_infos_for_workspace(workspace, cx);
let mut live_infos: Vec<_> = all_thread_infos_for_workspace(workspace, cx).collect();
let mut threads: Vec<ThreadEntry> = Vec::new();
let mut has_running_threads = false;
@@ -831,7 +774,7 @@ impl Sidebar {
let mut linked_worktree_queries: Vec<(PathList, SharedString, Arc<Path>)> =
Vec::new();
for snapshot in root_repository_snapshots(workspace, cx) {
if snapshot.work_directory_abs_path != snapshot.original_repo_abs_path {
if snapshot.is_linked_worktree() {
continue;
}
@@ -852,17 +795,16 @@ impl Sidebar {
for (worktree_path_list, worktree_name, worktree_path) in
&linked_worktree_queries
{
let target_workspace =
match absorbed_workspace_by_path.get(worktree_path.as_ref()) {
Some(&idx) => {
live_infos.extend(Self::all_thread_infos_for_workspace(
&workspaces[idx],
cx,
));
ThreadEntryWorkspace::Open(workspaces[idx].clone())
}
None => ThreadEntryWorkspace::Closed(worktree_path_list.clone()),
};
let target_workspace = match absorbed_workspace_by_path
.get(worktree_path.as_ref())
{
Some(&idx) => {
live_infos
.extend(all_thread_infos_for_workspace(&workspaces[idx], cx));
ThreadEntryWorkspace::Open(workspaces[idx].clone())
}
None => ThreadEntryWorkspace::Closed(worktree_path_list.clone()),
};
let worktree_rows: Vec<_> = thread_store
.read(cx)
@@ -1721,7 +1663,7 @@ impl Sidebar {
let mut known_worktree_paths: HashSet<std::path::PathBuf> = HashSet::new();
for workspace in &workspaces {
for snapshot in root_repository_snapshots(workspace, cx) {
if snapshot.work_directory_abs_path != snapshot.original_repo_abs_path {
if snapshot.is_linked_worktree() {
continue;
}
for git_worktree in snapshot.linked_worktrees() {
@@ -1740,12 +1682,10 @@ impl Sidebar {
if path_list.paths().len() != 1 {
continue;
}
let should_prune = root_repository_snapshots(workspace, cx)
.iter()
.any(|snapshot| {
snapshot.work_directory_abs_path != snapshot.original_repo_abs_path
&& !known_worktree_paths.contains(snapshot.work_directory_abs_path.as_ref())
});
let should_prune = root_repository_snapshots(workspace, cx).any(|snapshot| {
snapshot.is_linked_worktree()
&& !known_worktree_paths.contains(snapshot.work_directory_abs_path.as_ref())
});
if should_prune {
to_remove.push(workspace.clone());
}
@@ -3217,6 +3157,76 @@ impl Render for Sidebar {
}
}
fn all_thread_infos_for_workspace(
workspace: &Entity<Workspace>,
cx: &App,
) -> impl Iterator<Item = ActiveThreadInfo> {
enum ThreadInfoIterator<T: Iterator<Item = ActiveThreadInfo>> {
Empty,
Threads(T),
}
impl<T: Iterator<Item = ActiveThreadInfo>> Iterator for ThreadInfoIterator<T> {
type Item = ActiveThreadInfo;
fn next(&mut self) -> Option<Self::Item> {
match self {
ThreadInfoIterator::Empty => None,
ThreadInfoIterator::Threads(threads) => threads.next(),
}
}
}
let Some(agent_panel) = workspace.read(cx).panel::<AgentPanel>(cx) else {
return ThreadInfoIterator::Empty;
};
let agent_panel = agent_panel.read(cx);
let threads = agent_panel
.parent_threads(cx)
.into_iter()
.map(|thread_view| {
let thread_view_ref = thread_view.read(cx);
let thread = thread_view_ref.thread.read(cx);
let icon = thread_view_ref.agent_icon;
let icon_from_external_svg = thread_view_ref.agent_icon_from_external_svg.clone();
let title = thread
.title()
.unwrap_or_else(|| DEFAULT_THREAD_TITLE.into());
let is_native = thread_view_ref.as_native_thread(cx).is_some();
let is_title_generating = is_native && thread.has_provisional_title();
let session_id = thread.session_id().clone();
let is_background = agent_panel.is_background_thread(&session_id);
let status = if thread.is_waiting_for_confirmation() {
AgentThreadStatus::WaitingForConfirmation
} else if thread.had_error() {
AgentThreadStatus::Error
} else {
match thread.status() {
ThreadStatus::Generating => AgentThreadStatus::Running,
ThreadStatus::Idle => AgentThreadStatus::Completed,
}
};
let diff_stats = thread.action_log().read(cx).diff_stats(cx);
ActiveThreadInfo {
session_id,
title,
status,
icon,
icon_from_external_svg,
is_background,
is_title_generating,
diff_stats,
}
});
ThreadInfoIterator::Threads(threads)
}
#[cfg(test)]
mod tests {
use super::*;