This commit is contained in:
2026-08-11 13:30:09 +07:00
parent e77cfe29d9
commit 6f381c68c3
8 changed files with 261 additions and 9 deletions
@@ -4,6 +4,7 @@
use gpui::prelude::*;
use gpui::{AnyElement, Context, Entity, SharedString, WeakEntity, div, px};
use gpui_component::button::{Button, ButtonVariants};
use gpui_component::clipboard::Clipboard;
use gpui_component::list::ListItem;
use gpui_component::spinner::Spinner;
@@ -196,6 +197,23 @@ impl RepoDetailView {
placeholder("No README found", cx)
};
// Latest commit for the current pane: the selected file, or the README
// while nothing is selected. Computed after the body above, which
// needs `&mut self`.
let commit = self
.selected_file
.as_ref()
.and_then(|path| self.commits.get(path.as_ref()))
.or_else(|| {
if self.selected_file.is_none() {
self.readme_name
.as_ref()
.and_then(|name| self.commits.get(name.as_ref()))
} else {
None
}
});
v_flex()
.flex_1()
.min_w_0()
@@ -204,6 +222,7 @@ impl RepoDetailView {
h_flex()
.px_3()
.h_9()
.gap_2()
.bg(cx.theme().muted)
.border_b(px(1.))
.border_color(cx.theme().border)
@@ -215,7 +234,28 @@ impl RepoDetailView {
.text_ellipsis()
.whitespace_nowrap()
.child(pane_title),
),
)
.when_some(commit, |this, commit| {
this.child(
h_flex()
.flex_1()
.gap_1()
.child(
Button::new("commit")
.xsmall()
.text()
.label(commit.id.clone()),
)
.child(
div()
.max_w(px(250.))
.text_xs()
.text_ellipsis()
.whitespace_nowrap()
.child(commit.summary.clone()),
),
)
}),
)
.child(div().id("repo-content").flex_1().min_h_0().child(body))
}
@@ -14,6 +14,7 @@ use gpui_component::menu::PopupMenuItem;
use gpui_component::tree::TreeState;
use gpui_component::{ActiveTheme, IconName, StyledExt, h_flex, v_flex};
use signed_core::Announcement;
use signed_git::FileCommit;
use signed_state::{GitStore, RepoStore};
mod browser;
@@ -43,6 +44,10 @@ pub struct RepoDetailView {
files: HashMap<String, FileContent>,
/// Reads in flight, to avoid duplicate loads.
loading_files: HashSet<String>,
/// Latest commit touching a previewed file (or the README), keyed by path.
commits: HashMap<String, FileCommit>,
/// Commit queries in flight, to avoid duplicate loads.
loading_commits: HashSet<String>,
/// A clone/fetch is in flight.
loading: bool,
error: Option<SharedString>,
@@ -71,6 +76,8 @@ impl RepoDetailView {
selected_file: None,
files: HashMap::new(),
loading_files: HashSet::new(),
commits: HashMap::new(),
loading_commits: HashSet::new(),
loading: true,
error: None,
focus_handle: cx.focus_handle(),
@@ -113,6 +120,7 @@ impl RepoDetailView {
});
if let Some((path, bytes)) = readme_path.zip(readme) {
this.readme_name = Some(path.to_string_lossy().into());
this.load_commit(&path.to_string_lossy(), cx);
if let Ok(text) = String::from_utf8(bytes) {
this.set_markdown(None, &text, cx);
}
@@ -165,6 +173,7 @@ impl RepoDetailView {
self.loading_files.insert(path.to_string());
let path = path.to_string();
self.load_commit(&path, cx);
let task = cx.spawn(async move |this, cx| {
let path_for_read = path.clone();
@@ -222,6 +231,41 @@ impl RepoDetailView {
self.tasks.push(task);
}
/// Query the latest commit touching `path` on a background task and cache
/// it in [`Self::commits`], for the file header in the content column.
fn load_commit(&mut self, path: &str, cx: &mut Context<Self>) {
if self.commits.contains_key(path) || self.loading_commits.contains(path) {
return;
}
let Some(worktree) = self.worktree.clone() else {
return;
};
self.loading_commits.insert(path.to_string());
let path = path.to_string();
let task = cx.spawn(async move |this, cx| {
let path_for_query = path.clone();
let result = cx
.background_spawn(async move {
signed_git::worktree_last_commit(&worktree, Path::new(&path_for_query))
})
.await;
this.update(cx, |this, cx| {
this.loading_commits.remove(&path);
if let Ok(Some(commit)) = result {
this.commits.insert(path, commit);
}
cx.notify();
})?;
Ok(())
});
self.tasks.push(task);
}
}
impl Panel for RepoDetailView {
+15 -6
View File
@@ -21,23 +21,23 @@ pub struct Workspace {
impl Workspace {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let dock =
cx.new(|cx| DockArea::new("dock", Some(1), window, cx).panel_style(PanelStyle::TabBar));
let style = PanelStyle::TabBar;
let dock = cx.new(|cx| DockArea::new("dock", Some(1), window, cx).panel_style(style));
let weak_dock = dock.downgrade();
let sidebar = cx.new(|cx| SidebarPanel::new(weak_dock.clone(), cx));
let weak_sidebar = sidebar.downgrade();
dock.update(cx, |dock_area, cx| {
dock_area.set_left_dock(
DockItem::panel(Arc::new(sidebar.clone())),
Some(px(260.)),
DockItem::panel(Arc::new(sidebar)),
Some(px(240.)),
true,
window,
cx,
);
});
sidebar.update(cx, |sidebar, cx| sidebar.open_explore(window, cx));
let backend = Backend::global(cx);
let connected = backend.read(cx).is_connected();
let sync_progress = backend.read(cx).sync_progress();
@@ -86,6 +86,15 @@ impl Workspace {
passphrase_dialog::open(window, cx);
}
// Open the explore panel after the sidebar has been initialized.
cx.defer_in(window, move |_, window, cx| {
weak_sidebar
.update(cx, |this, cx| {
this.open_explore(window, cx);
})
.ok();
});
Self {
dock,
status,