sidebar: Show worktree labels for agent terminals (#56412)
Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - N/A Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
This commit is contained in:
1 parent
552a5c0ae7
commit
f48d72686b
3 files changed
+122
-18
No files matched your search
@@ -237,6 +237,7 @@ struct TerminalEntry {
|
||||
id: TerminalId,
|
||||
title: SharedString,
|
||||
workspace: Entity<Workspace>,
|
||||
worktrees: Vec<ThreadItemWorktreeInfo>,
|
||||
created_at: DateTime<Utc>,
|
||||
has_notification: bool,
|
||||
highlight_positions: Vec<usize>,
|
||||
@@ -543,6 +544,16 @@ fn apply_worktree_label_mode(
|
||||
worktrees
|
||||
}
|
||||
|
||||
fn terminal_worktree_info<S: std::hash::BuildHasher>(
|
||||
workspace: &Entity<Workspace>,
|
||||
branch_by_path: &HashMap<PathBuf, SharedString, S>,
|
||||
cx: &App,
|
||||
) -> Vec<ThreadItemWorktreeInfo> {
|
||||
let project = workspace.read(cx).project().clone();
|
||||
let worktree_paths = project.read(cx).worktree_paths(cx);
|
||||
worktree_info_from_thread_paths(&worktree_paths, branch_by_path)
|
||||
}
|
||||
|
||||
/// Shows a [`RemoteConnectionModal`] on the given workspace and establishes
|
||||
/// an SSH connection. Suitable for passing to
|
||||
/// [`MultiWorkspace::find_or_create_workspace`] as the `connect_remote`
|
||||
@@ -1196,7 +1207,9 @@ impl Sidebar {
|
||||
let group_workspaces = &group.workspaces;
|
||||
let terminals: Vec<TerminalEntry> = group_workspaces
|
||||
.iter()
|
||||
.flat_map(|workspace| terminal_entries_for_workspace(workspace, cx))
|
||||
.flat_map(|workspace| {
|
||||
terminal_entries_for_workspace(workspace, &branch_by_path, cx)
|
||||
})
|
||||
.collect();
|
||||
current_terminal_ids.extend(terminals.iter().map(|terminal| terminal.id));
|
||||
notified_terminals.extend(
|
||||
@@ -1483,7 +1496,17 @@ impl Sidebar {
|
||||
terminal.highlight_positions = positions;
|
||||
terminal_matched = true;
|
||||
}
|
||||
if workspace_matched || terminal_matched {
|
||||
let mut worktree_matched = false;
|
||||
for worktree in &mut terminal.worktrees {
|
||||
let Some(name) = worktree.worktree_name.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
if let Some(positions) = fuzzy_match_positions(&query, name) {
|
||||
worktree.highlight_positions = positions;
|
||||
worktree_matched = true;
|
||||
}
|
||||
}
|
||||
if workspace_matched || terminal_matched || worktree_matched {
|
||||
matched_terminals.push(terminal);
|
||||
}
|
||||
}
|
||||
@@ -4036,6 +4059,15 @@ impl Sidebar {
|
||||
title: terminal.title.clone(),
|
||||
workspace: terminal.workspace.clone(),
|
||||
project_name: current_header_label.clone(),
|
||||
worktrees: terminal
|
||||
.worktrees
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|mut wt| {
|
||||
wt.highlight_positions = Vec::new();
|
||||
wt
|
||||
})
|
||||
.collect(),
|
||||
created_at: terminal.created_at,
|
||||
notified: self.contents.is_terminal_notified(terminal.id),
|
||||
timestamp,
|
||||
@@ -4459,10 +4491,15 @@ impl Sidebar {
|
||||
let terminal_id = terminal.id;
|
||||
let workspace = terminal.workspace.clone();
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
let worktrees = apply_worktree_label_mode(
|
||||
terminal.worktrees.clone(),
|
||||
cx.flag_value::<AgentThreadWorktreeLabelFlag>(),
|
||||
);
|
||||
|
||||
ThreadItem::new(id, terminal.title.clone())
|
||||
.base_bg(sidebar_bg)
|
||||
.icon(IconName::Terminal)
|
||||
.worktrees(worktrees)
|
||||
.timestamp(timestamp)
|
||||
.notified(terminal.has_notification)
|
||||
.highlight_positions(terminal.highlight_positions.clone())
|
||||
@@ -5579,8 +5616,9 @@ impl Render for Sidebar {
|
||||
}
|
||||
}
|
||||
|
||||
fn terminal_entries_for_workspace(
|
||||
fn terminal_entries_for_workspace<S: std::hash::BuildHasher>(
|
||||
workspace: &Entity<Workspace>,
|
||||
branch_by_path: &HashMap<PathBuf, SharedString, S>,
|
||||
cx: &App,
|
||||
) -> impl Iterator<Item = TerminalEntry> {
|
||||
if !cx.has_flag::<AgentPanelTerminalFeatureFlag>() {
|
||||
@@ -5589,19 +5627,17 @@ fn terminal_entries_for_workspace(
|
||||
let Some(agent_panel) = workspace.read(cx).panel::<AgentPanel>(cx) else {
|
||||
return None.into_iter().flatten();
|
||||
};
|
||||
let terminals =
|
||||
agent_panel
|
||||
.read(cx)
|
||||
.terminals(cx)
|
||||
.into_iter()
|
||||
.map(|terminal: AgentPanelTerminalInfo| TerminalEntry {
|
||||
id: terminal.id,
|
||||
title: terminal.title,
|
||||
workspace: workspace.clone(),
|
||||
created_at: terminal.created_at,
|
||||
has_notification: terminal.has_notification,
|
||||
highlight_positions: Vec::new(),
|
||||
});
|
||||
let terminals = agent_panel.read(cx).terminals(cx).into_iter().map(
|
||||
move |terminal: AgentPanelTerminalInfo| TerminalEntry {
|
||||
id: terminal.id,
|
||||
title: terminal.title,
|
||||
workspace: workspace.clone(),
|
||||
worktrees: terminal_worktree_info(workspace, branch_by_path, cx),
|
||||
created_at: terminal.created_at,
|
||||
has_notification: terminal.has_notification,
|
||||
highlight_positions: Vec::new(),
|
||||
},
|
||||
);
|
||||
|
||||
Some(terminals).into_iter().flatten()
|
||||
}
|
||||
|
||||
@@ -530,7 +530,8 @@ fn visible_entries_as_strings(
|
||||
}
|
||||
ListEntry::Terminal(terminal) => {
|
||||
let title = &terminal.title;
|
||||
format!(" {title}{selected}")
|
||||
let worktree = format_linked_worktree_chips(&terminal.worktrees);
|
||||
format!(" {title}{worktree}{selected}")
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1469,6 +1470,72 @@ async fn test_agent_panel_terminals_appear_in_sidebar_and_search(cx: &mut TestAp
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_agent_panel_terminal_shows_project_and_linked_worktree(cx: &mut TestAppContext) {
|
||||
agent_ui::test_support::init_test(cx);
|
||||
cx.update(|cx| {
|
||||
cx.set_global(agent_ui::MaxIdleRetainedThreads(1));
|
||||
ThreadStore::init_global(cx);
|
||||
ThreadMetadataStore::init_global(cx);
|
||||
language_model::LanguageModelRegistry::test(cx);
|
||||
prompt_store::init(cx);
|
||||
});
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/project", serde_json::json!({ ".git": {}, "src": {} }))
|
||||
.await;
|
||||
fs.add_linked_worktree_for_repo(
|
||||
Path::new("/project/.git"),
|
||||
false,
|
||||
git::repository::Worktree {
|
||||
path: PathBuf::from("/wt-feature-a"),
|
||||
ref_name: Some("refs/heads/feature-a".into()),
|
||||
sha: "aaa".into(),
|
||||
is_main: false,
|
||||
is_bare: false,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
cx.update(|cx| <dyn fs::Fs>::set_global(fs.clone(), cx));
|
||||
|
||||
let main_project = project::Project::test(fs.clone(), ["/project".as_ref()], cx).await;
|
||||
let worktree_project = project::Project::test(fs.clone(), ["/wt-feature-a".as_ref()], cx).await;
|
||||
|
||||
main_project
|
||||
.update(cx, |project, cx| project.git_scans_complete(cx))
|
||||
.await;
|
||||
worktree_project
|
||||
.update(cx, |project, cx| project.git_scans_complete(cx))
|
||||
.await;
|
||||
|
||||
enable_agent_panel_terminal(cx);
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(main_project.clone(), window, cx));
|
||||
let sidebar = setup_sidebar(&multi_workspace, cx);
|
||||
let worktree_workspace = multi_workspace.update_in(cx, |multi_workspace, window, cx| {
|
||||
multi_workspace.test_add_workspace(worktree_project.clone(), window, cx)
|
||||
});
|
||||
let panel = add_agent_panel(&worktree_workspace, cx);
|
||||
|
||||
panel
|
||||
.update_in(cx, |panel, window, cx| {
|
||||
panel.insert_test_terminal("Dev Server", true, window, cx)
|
||||
})
|
||||
.expect("test terminal should be inserted");
|
||||
cx.run_until_parked();
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&sidebar, cx),
|
||||
vec!["v [project]", " Dev Server {wt-feature-a}"]
|
||||
);
|
||||
|
||||
type_in_search(&sidebar, "wt-feature-a", cx);
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&sidebar, cx),
|
||||
vec!["v [project]", " Dev Server {wt-feature-a} <== selected"]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_agent_panel_terminal_notifications_update_sidebar(cx: &mut TestAppContext) {
|
||||
let project = init_test_project_with_agent_panel("/my-project", cx).await;
|
||||
|
||||
@@ -32,6 +32,7 @@ pub(crate) struct ThreadSwitcherTerminalEntry {
|
||||
pub title: SharedString,
|
||||
pub workspace: Entity<Workspace>,
|
||||
pub project_name: Option<SharedString>,
|
||||
pub worktrees: Vec<ThreadItemWorktreeInfo>,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub notified: bool,
|
||||
pub timestamp: SharedString,
|
||||
@@ -118,7 +119,7 @@ impl ThreadSwitcherEntry {
|
||||
fn worktrees(&self) -> Vec<ThreadItemWorktreeInfo> {
|
||||
match self {
|
||||
Self::Thread(entry) => entry.worktrees.clone(),
|
||||
Self::Terminal(_) => Vec::new(),
|
||||
Self::Terminal(entry) => entry.worktrees.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user