diff --git a/crates/workspace/src/views/repo_list.rs b/crates/workspace/src/views/repo_list.rs index 126669a..c3dec27 100644 --- a/crates/workspace/src/views/repo_list.rs +++ b/crates/workspace/src/views/repo_list.rs @@ -1,3 +1,4 @@ +use std::fmt::Display; use std::rc::Rc; use assets::CustomIconName; @@ -5,7 +6,7 @@ use dock::{BasePanel, DockArea, Panel, PanelEvent}; use gpui::prelude::*; use gpui::{ AnyElement, App, Context, Entity, EventEmitter, FocusHandle, Focusable, Pixels, Render, - SharedString, Size, Subscription, WeakEntity, Window, div, px, size, + SharedString, Size, Subscription, WeakEntity, Window, div, px, relative, size, }; use gpui_component::input::{Input, InputEvent, InputState}; use gpui_component::scroll::Scrollbar; @@ -38,6 +39,22 @@ enum RepoFilter { Recent, } +impl AsRef for RepoFilter { + fn as_ref(&self) -> &str { + match self { + RepoFilter::All => "all", + RepoFilter::Popular => "popular", + RepoFilter::Recent => "recent", + } + } +} + +impl Display for RepoFilter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_ref()) + } +} + impl RepoFilter { /// Indices into the store's `announcements` this filter includes, in display order. /// @@ -49,6 +66,7 @@ impl RepoFilter { // Narrow by the search query first. // Recent then limits the matches and Popular ranks them. let query = query.trim().to_lowercase(); + if !query.is_empty() { indices.retain(|&ix| { let announcement = &announcements[ix]; @@ -125,7 +143,11 @@ impl RepoListView { this.rebuild_rows(cx); }); - let mut this = Self { + cx.defer_in(window, |this, _window, cx| { + this.rebuild_rows(cx); + }); + + Self { store, dock_area, focus_handle: cx.focus_handle(), @@ -137,14 +159,7 @@ impl RepoListView { search, _search_subscription: search_subscription, _subscription: subscription, - }; - - // Seed the rows right away. - // The store may already hold announcements from before the panel opened. - // The first render must not depend on a later store update. - this.rebuild_rows(cx); - - this + } } /// Rebuild [`Self::visible`] and [`Self::item_sizes`] from the store. @@ -154,10 +169,12 @@ impl RepoListView { let filter = self.filter; let query = self.search.read(cx).value(); let store = self.store.read(cx); + self.visible = filter.visible(store, &query); // Each virtual list row holds `COLUMNS` repo cards. let rows = self.visible.len().div_ceil(COLUMNS); + if self.repo_len != rows { self.repo_len = rows; self.item_sizes = Rc::new(vec![size(px(0.), px(CARD_HEIGHT)); rows]); @@ -172,7 +189,7 @@ impl RepoListView { window: &mut Window, cx: &mut Context, ) { - open_repo_panel(&self.dock_area, announcement, window, &mut *cx); + open_repo_panel(&self.dock_area, announcement, window, cx); } fn render_card( @@ -308,6 +325,22 @@ impl RepoListView { .into_any_element() } + fn render_filter(&self, filter: RepoFilter, label: T, cx: &mut Context) -> AnyElement + where + T: Into, + { + let active = self.filter == filter; + + SegmentButton::new(filter.to_string(), label) + .icon(Icon::new(filter.icon_name())) + .selected(active) + .on_click(cx.listener(move |this, _event, _window, cx| { + this.filter = filter; + this.rebuild_rows(cx); + })) + .into_any_element() + } + fn render_header(&self, count: usize, cx: &mut Context) -> AnyElement { h_flex() .px_4() @@ -315,18 +348,24 @@ impl RepoListView { .w_full() .gap_3() .child( - h_flex() - .gap_1() - .text_xs() - .child(div().font_semibold().child("Repositories")) + v_flex() + .gap_0p5() .child( div() - .w_10() .min_w_0() .truncate() .text_ellipsis() + .font_semibold() + .text_xs() + .line_height(relative(1.2)) + .child("Repositories"), + ) + .child( + div() + .text_size(px(10.)) .text_color(cx.theme().muted_foreground) - .child(SharedString::from(format!("({count})"))), + .line_height(relative(1.2)) + .child(SharedString::from(format!("Total: {count}"))), ), ) .child( @@ -342,31 +381,12 @@ impl RepoListView { .child( h_flex() .gap_1() - .child(self.filter_button(RepoFilter::All, "All", cx)) - .child(self.filter_button(RepoFilter::Popular, "Popular", cx)) - .child(self.filter_button(RepoFilter::Recent, "Recent", cx)), + .child(self.render_filter(RepoFilter::All, "All", cx)) + .child(self.render_filter(RepoFilter::Popular, "Popular", cx)) + .child(self.render_filter(RepoFilter::Recent, "Recent", cx)), ) .into_any_element() } - - /// One segmented header filter button, like the issues list's status filter buttons. - fn filter_button( - &self, - filter: RepoFilter, - label: &'static str, - cx: &mut Context, - ) -> AnyElement { - let active = self.filter == filter; - - SegmentButton::new(label, label) - .icon(Icon::new(filter.icon_name())) - .selected(active) - .on_click(cx.listener(move |this, _event, _window, cx| { - this.filter = filter; - this.rebuild_rows(cx); - })) - .into_any_element() - } } impl BasePanel for RepoListView { diff --git a/crates/workspace/src/views/sidebar/mod.rs b/crates/workspace/src/views/sidebar/mod.rs index 303471f..950e790 100644 --- a/crates/workspace/src/views/sidebar/mod.rs +++ b/crates/workspace/src/views/sidebar/mod.rs @@ -164,12 +164,12 @@ impl SidebarPanel { /// Recompute the badge counts from the global checkouts store's ready-to-push statuses fn refresh_unpushed(&mut self, cx: &mut Context) -> bool { - let checkouts = CheckoutsStore::global(cx).read(cx); + let checkouts = CheckoutsStore::global(cx); let mut unpushed = HashMap::with_capacity(self.announcements.len()); for announcement in self.announcements.iter() { let addr = announcement.addr(); - let count = checkouts.unpushed(&addr); + let count = checkouts.read(cx).unpushed(&addr); if count > 0 { unpushed.insert(addr, count); } @@ -202,9 +202,11 @@ impl SidebarPanel { let panel = cx.new(|cx| InboxView::new(self.dock_area.clone(), cx)); self.inbox = Some(panel.downgrade()); - let _ = self.dock_area.update(cx, |dock_area, cx| { - add_center_panel(dock_area, panel_handle(panel), window, cx); - }); + self.dock_area + .update(cx, |dock_area, cx| { + add_center_panel(dock_area, panel_handle(panel), window, cx); + }) + .ok(); } /// Open the Explore repository list panel in the dock area's center. @@ -221,9 +223,11 @@ impl SidebarPanel { let panel = cx.new(|cx| RepoListView::new(self.dock_area.clone(), window, cx)); self.explore = Some(panel.downgrade()); - let _ = self.dock_area.update(cx, |dock_area, cx| { - add_center_panel(dock_area, panel_handle(panel), window, cx); - }); + self.dock_area + .update(cx, |dock_area, cx| { + add_center_panel(dock_area, panel_handle(panel), window, cx); + }) + .ok(); } /// Show the Onboarding dialog. @@ -599,9 +603,9 @@ impl Render for SidebarPanel { } v_flex() + .image_cache(gpui::retain_all("sidebar")) .size_full() .justify_between() - .image_cache(gpui::retain_all("sidebar")) .bg(cx.theme().sidebar) .text_color(cx.theme().sidebar_foreground) .child(