This commit is contained in:
2026-09-13 15:54:17 +07:00
parent b59f6a95de
commit 93d96ca9ee
6 changed files with 242 additions and 188 deletions
+57 -16
View File
@@ -1,3 +1,6 @@
use std::collections::HashSet;
use std::path::PathBuf;
use assets::CustomIconName; use assets::CustomIconName;
use gpui::prelude::*; use gpui::prelude::*;
use gpui::{AnyElement, App, Context, SharedString, div, transparent_white}; use gpui::{AnyElement, App, Context, SharedString, div, transparent_white};
@@ -10,6 +13,48 @@ use signed_state::{Backend, CheckoutStatus, CheckoutsStore, pr_proposes_checkout
use super::RepoDetailView; use super::RepoDetailView;
use crate::views::pull_requests::new::open_new_pull_panel; use crate::views::pull_requests::new::open_new_pull_panel;
#[derive(Default)]
pub(super) struct Banners {
dismissed: HashSet<(PathBuf, String)>,
ready_requested: bool,
/// Re-requested only when the announced HEAD or the base default changes.
ready_head: Option<String>,
ready_statuses: Vec<CheckoutStatus>,
push_statuses: Vec<CheckoutStatus>,
}
impl Banners {
pub(super) fn dismissal(&self, status: &CheckoutStatus) -> bool {
self.dismissed
.contains(&(status.path.clone(), status.branch.clone()))
}
pub(super) fn dismiss(&mut self, status: &CheckoutStatus) {
self.dismissed
.insert((status.path.clone(), status.branch.clone()));
}
pub(super) fn ready_requested_at(&self) -> (bool, &Option<String>) {
(self.ready_requested, &self.ready_head)
}
pub(super) fn mark_ready_requested(&mut self, head: Option<String>) {
self.ready_requested = true;
self.ready_head = head;
}
pub(super) fn set_statuses(
&mut self,
ready: Vec<CheckoutStatus>,
push: Vec<CheckoutStatus>,
) -> bool {
let changed = ready != self.ready_statuses || push != self.push_statuses;
self.ready_statuses = ready;
self.push_statuses = push;
changed
}
}
impl RepoDetailView { impl RepoDetailView {
fn ready_suggestion(&self, cx: &App) -> Option<CheckoutStatus> { fn ready_suggestion(&self, cx: &App) -> Option<CheckoutStatus> {
let store = self.store.read(cx); let store = self.store.read(cx);
@@ -23,10 +68,7 @@ impl RepoDetailView {
let statuses = CheckoutsStore::global(cx).read(cx).ready_statuses_of(addr); let statuses = CheckoutsStore::global(cx).read(cx).ready_statuses_of(addr);
'status: for status in statuses { 'status: for status in statuses {
if self if self.banners.dismissal(&status) {
.banner_dismissed
.contains(&(status.path.clone(), status.branch.clone()))
{
continue; continue;
} }
for pr in &store.pull_requests { for pr in &store.pull_requests {
@@ -55,17 +97,15 @@ impl RepoDetailView {
let statuses = CheckoutsStore::global(cx).read(cx).push_statuses_of(addr); let statuses = CheckoutsStore::global(cx).read(cx).push_statuses_of(addr);
statuses.into_iter().find(|status| { statuses
!self .into_iter()
.banner_dismissed .find(|status| !self.banners.dismissal(status))
.contains(&(status.path.clone(), status.branch.clone()))
})
} }
pub(super) fn render_push_banner(&self, cx: &Context<Self>) -> Option<AnyElement> { pub(super) fn render_push_banner(&self, cx: &Context<Self>) -> Option<AnyElement> {
let status = self.push_suggestion(cx)?; let status = self.push_suggestion(cx)?;
let key = (status.path.clone(), status.branch.clone());
let path = status.path.clone(); let path = status.path.clone();
let branch = status.branch.clone();
// The push busy flag lives on the store; it disables the banner's triggers. // The push busy flag lives on the store; it disables the banner's triggers.
let pushing = self.store.read(cx).pushing; let pushing = self.store.read(cx).pushing;
@@ -98,7 +138,7 @@ impl RepoDetailView {
.text_xs() .text_xs()
.font_semibold() .font_semibold()
.font_family(cx.theme().mono_font_family.clone()) .font_family(cx.theme().mono_font_family.clone())
.child(status.branch), .child(branch),
) )
.child("has") .child("has")
.child( .child(
@@ -138,7 +178,7 @@ impl RepoDetailView {
.ghost() .ghost()
.disabled(pushing) .disabled(pushing)
.on_click(cx.listener(move |this, _ev, _window, cx| { .on_click(cx.listener(move |this, _ev, _window, cx| {
this.banner_dismissed.insert(key.clone()); this.banners.dismiss(&status);
cx.notify(); cx.notify();
})), })),
), ),
@@ -213,7 +253,8 @@ impl RepoDetailView {
pub(super) fn render_ready_banner(&self, cx: &Context<Self>) -> Option<AnyElement> { pub(super) fn render_ready_banner(&self, cx: &Context<Self>) -> Option<AnyElement> {
let status = self.ready_suggestion(cx)?; let status = self.ready_suggestion(cx)?;
let key = (status.path.clone(), status.branch.clone()); let branch = status.branch.clone();
let base = status.base.clone();
let commits = if status.ahead == 1 { let commits = if status.ahead == 1 {
SharedString::from("1 commit") SharedString::from("1 commit")
@@ -244,7 +285,7 @@ impl RepoDetailView {
.text_xs() .text_xs()
.font_semibold() .font_semibold()
.font_family(cx.theme().mono_font_family.clone()) .font_family(cx.theme().mono_font_family.clone())
.child(status.branch), .child(branch),
) )
.child("is") .child("is")
.child( .child(
@@ -270,7 +311,7 @@ impl RepoDetailView {
.text_xs() .text_xs()
.font_semibold() .font_semibold()
.font_family(cx.theme().mono_font_family.clone()) .font_family(cx.theme().mono_font_family.clone())
.child(status.base), .child(base),
), ),
) )
.child( .child(
@@ -298,7 +339,7 @@ impl RepoDetailView {
.small() .small()
.ghost() .ghost()
.on_click(cx.listener(move |this, _ev, _window, cx| { .on_click(cx.listener(move |this, _ev, _window, cx| {
this.banner_dismissed.insert(key.clone()); this.banners.dismiss(&status);
cx.notify(); cx.notify();
})), })),
), ),
+3 -3
View File
@@ -475,7 +475,7 @@ impl RepoDetailView {
fn render_header_tabs(&self, cx: &mut Context<Self>) -> AnyElement { fn render_header_tabs(&self, cx: &mut Context<Self>) -> AnyElement {
let commits_count = self.history.read(cx).commit_count(); let commits_count = self.history.read(cx).commit_count();
let worktree_empty = self.switching_ref || self.worktree.is_none(); let worktree_empty = self.refs.switching_ref || self.worktree.is_none();
h_flex() h_flex()
.items_center() .items_center()
@@ -580,7 +580,7 @@ impl RepoDetailView {
) )
.child( .child(
div().w(px(120.)).child( div().w(px(120.)).child(
Combobox::new(&self.branch_select) Combobox::new(&self.refs.branch_select)
.placeholder("Branch") .placeholder("Branch")
.appearance(false) .appearance(false)
.menu_width(px(200.)) .menu_width(px(200.))
@@ -594,7 +594,7 @@ impl RepoDetailView {
) )
.child( .child(
div().w(px(120.)).child( div().w(px(120.)).child(
Combobox::new(&self.tag_select) Combobox::new(&self.refs.tag_select)
.placeholder("Tag") .placeholder("Tag")
.appearance(false) .appearance(false)
.menu_width(px(200.)) .menu_width(px(200.))
+11 -61
View File
@@ -3,9 +3,7 @@ use std::path::{Path, PathBuf};
use anyhow::Error; use anyhow::Error;
use gix::Repository; use gix::Repository;
use gpui::prelude::*; use gpui::prelude::*;
use gpui::{Context, Entity, PathPromptOptions, SharedString, Window}; use gpui::{Context, PathPromptOptions, SharedString, Window};
use gpui_component::combobox::ComboboxState;
use gpui_component::searchable_list::SearchableVec;
use nostr::prelude::Url; use nostr::prelude::Url;
use signed_git::FileCommit; use signed_git::FileCommit;
use signed_state::GitStore; use signed_state::GitStore;
@@ -185,25 +183,17 @@ impl RepoDetailView {
let branches: Vec<SharedString> = branches.iter().map(Into::into).collect(); let branches: Vec<SharedString> = branches.iter().map(Into::into).collect();
let tags: Vec<SharedString> = tags.iter().map(Into::into).collect(); let tags: Vec<SharedString> = tags.iter().map(Into::into).collect();
let branches_changed = Self::sync_ref_selector( let branches_changed = this.refs.set_branches(
&this.branch_select,
&mut this.ref_branches,
branches, branches,
current_branch.map(Into::into), current_branch.map(Into::into),
window, window,
cx, cx,
); );
let tags_changed = Self::sync_ref_selector(
&this.tag_select,
&mut this.ref_tags,
tags,
None,
window,
cx,
);
let tags_changed = this.refs.set_tags(tags, window, cx);
let new_head_commit = head_commit.as_ref().map(|c| &c.id); let new_head_commit = head_commit.as_ref().map(|c| &c.id);
let current_head_commit = this.head_commit.as_ref().map(|c| &c.id); let current_head_commit = this.head_commit.as_ref().map(|c| &c.id);
let head_changed = new_head_commit != current_head_commit; let head_changed = new_head_commit != current_head_commit;
this.head_commit = head_commit; this.head_commit = head_commit;
@@ -263,50 +253,10 @@ impl RepoDetailView {
let branches: Vec<SharedString> = branches.into_iter().map(Into::into).collect(); let branches: Vec<SharedString> = branches.into_iter().map(Into::into).collect();
let tags: Vec<SharedString> = tags.into_iter().map(Into::into).collect(); let tags: Vec<SharedString> = tags.into_iter().map(Into::into).collect();
Self::sync_ref_selector( self.refs
&self.branch_select, .set_branches(branches, current_branch.map(Into::into), window, cx);
&mut self.ref_branches,
branches,
current_branch.map(Into::into),
window,
cx,
);
Self::sync_ref_selector(&self.tag_select, &mut self.ref_tags, tags, None, window, cx); self.refs.set_tags(tags, window, cx);
}
/// Point a ref selector at `items`, selecting `selected` when given.
fn sync_ref_selector(
select: &Entity<ComboboxState<SearchableVec<SharedString>>>,
cached: &mut Vec<SharedString>,
items: Vec<SharedString>,
selected: Option<SharedString>,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {
let items_changed = *cached != items;
let selection_changed = selected
.as_ref()
.is_some_and(|value| select.read(cx).selected_value().as_ref() != Some(value));
if !items_changed && !selection_changed {
return false;
}
select.update(cx, |state, cx| {
if items_changed {
state.set_items(SearchableVec::from(items.clone()), window, cx);
}
if let Some(value) = selected
&& (items_changed || selection_changed)
{
state.set_selected_values(std::slice::from_ref(&value), window, cx);
}
});
*cached = items;
true
} }
pub(super) fn clone_to_folder(&mut self, window: &mut Window, cx: &mut Context<Self>) { pub(super) fn clone_to_folder(&mut self, window: &mut Window, cx: &mut Context<Self>) {
@@ -378,14 +328,15 @@ fn load_repo_data(repo: &Repository) -> Result<RepoData, Error> {
let entries = signed_git::worktree_entries(repo)?; let entries = signed_git::worktree_entries(repo)?;
let tree = build_tree_items(&entries); let tree = build_tree_items(&entries);
let readme_path = signed_git::find_readme(repo)?; let readme_path = signed_git::find_readme(repo)?;
let readme = match &readme_path { let readme = match &readme_path {
Some(path) => signed_git::worktree_read(repo, path)?, Some(path) => signed_git::worktree_read(repo, path)?,
None => None, None => None,
}; };
let worktree = repo.workdir().map(Path::to_path_buf); let worktree = repo.workdir().map(Path::to_path_buf);
// Ref listing is auxiliary UI. let head_commit = signed_git::head_commit(repo).unwrap_or(None);
// A broken ref must not prevent the explorer from loading.
// Failures degrade to empty selectors.
let (branches, tags, current_branch) = match &worktree { let (branches, tags, current_branch) = match &worktree {
Some(_) => ( Some(_) => (
signed_git::repo_branches(repo).unwrap_or_default(), signed_git::repo_branches(repo).unwrap_or_default(),
@@ -394,7 +345,6 @@ fn load_repo_data(repo: &Repository) -> Result<RepoData, Error> {
), ),
None => (Vec::new(), Vec::new(), None), None => (Vec::new(), Vec::new(), None),
}; };
let head_commit = signed_git::head_commit(repo).unwrap_or(None);
Ok(RepoData { Ok(RepoData {
tree, tree,
+31 -77
View File
@@ -1,4 +1,3 @@
use std::collections::HashSet;
use std::path::PathBuf; use std::path::PathBuf;
use anyhow::Error; use anyhow::Error;
@@ -9,13 +8,12 @@ use gpui::{
SharedString, Subscription, Task, WeakEntity, Window, div, SharedString, Subscription, Task, WeakEntity, Window, div,
}; };
use gpui_component::alert::Alert; use gpui_component::alert::Alert;
use gpui_component::combobox::{ComboboxEvent, ComboboxState}; use gpui_component::combobox::ComboboxEvent;
use gpui_component::searchable_list::SearchableVec;
use gpui_component::spinner::Spinner; use gpui_component::spinner::Spinner;
use gpui_component::{ActiveTheme, Sizable, v_flex}; use gpui_component::{ActiveTheme, Sizable, v_flex};
use signed_core::{Announcement, RepoAddr}; use signed_core::{Announcement, RepoAddr};
use signed_git::FileCommit; use signed_git::FileCommit;
use signed_state::{CheckoutStatus, CheckoutsStore, RepoStore}; use signed_state::{CheckoutsStore, RepoStore};
mod about; mod about;
mod actions; mod actions;
@@ -30,8 +28,10 @@ mod store;
pub(crate) use actions::{RepoItem, open_repo_item, open_repo_panel}; pub(crate) use actions::{RepoItem, open_repo_item, open_repo_panel};
use self::banners::Banners;
use self::files::RepoFilesView; use self::files::RepoFilesView;
use self::history::RepoHistoryView; use self::history::RepoHistoryView;
use self::refs::RefSwitcher;
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
enum RefKind { enum RefKind {
@@ -41,10 +41,6 @@ enum RefKind {
Tag, Tag,
} }
/// Header actions dispatched by the header dropdown menus.
///
/// `pub(crate)` because the pull-request list panel shares this action set,
/// offering the New-PR and Send-patch actions in its own dropdown.
#[derive(Clone, Action, PartialEq, Eq)] #[derive(Clone, Action, PartialEq, Eq)]
#[action(namespace = repo, no_json)] #[action(namespace = repo, no_json)]
pub(crate) enum RepoAction { pub(crate) enum RepoAction {
@@ -74,38 +70,15 @@ pub struct RepoDetailView {
loading: bool, loading: bool,
error: Option<SharedString>, error: Option<SharedString>,
head_commit: Option<FileCommit>, head_commit: Option<FileCommit>,
branch_select: Entity<ComboboxState<SearchableVec<SharedString>>>, refs: RefSwitcher,
tag_select: Entity<ComboboxState<SearchableVec<SharedString>>>,
/// Branch names currently in `branch_select`, for cheap no-op detection.
ref_branches: Vec<SharedString>,
/// Tag names currently in `tag_select`, for cheap no-op detection.
ref_tags: Vec<SharedString>,
switching_ref: bool,
/// In-flight loads with an older generation are discarded when they complete. /// In-flight loads with an older generation are discarded when they complete.
ref_generation: u64, ref_generation: u64,
tasks: Vec<Task<Result<(), Error>>>, tasks: Vec<Task<Result<(), Error>>>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
/// `(path, branch)` ready-suggestions dismissed by the user, per panel. banners: Banners,
banner_dismissed: HashSet<(PathBuf, String)>,
/// Whether the ready statuses were requested at all.
ready_requested: bool,
/// The announced HEAD they were last requested with. Re-requested only when
/// the HEAD, the base default, changes, e.g. when the store's first refresh
/// lands.
ready_head: Option<String>,
/// The global checkouts store's ready-to-contribute statuses of this
/// repository, last seen when they drove a render.
///
/// The store notifies on any recompute pass; the observer re-renders this
/// panel only when these slices changed.
ready_statuses: Vec<CheckoutStatus>,
push_statuses: Vec<CheckoutStatus>,
} }
impl RepoDetailView { impl RepoDetailView {
/// `hint` is an announcement already in hand. It seeds the store's relays
/// and the explorer's clone URLs; without it the panel waits for the store
/// to load the announcement from the local database.
pub fn new( pub fn new(
dock_area: WeakEntity<DockArea>, dock_area: WeakEntity<DockArea>,
addr: RepoAddr, addr: RepoAddr,
@@ -136,42 +109,31 @@ impl RepoDetailView {
let checkouts = CheckoutsStore::global(cx); let checkouts = CheckoutsStore::global(cx);
let files = cx.new(RepoFilesView::new); let files = cx.new(RepoFilesView::new);
let history = cx.new(|_cx| RepoHistoryView::new(store.clone(), dock_area.clone())); let history = cx.new(|_cx| RepoHistoryView::new(store.clone(), dock_area.clone()));
let refs = RefSwitcher::new(window, cx);
let branch_select = cx.new(|cx| {
ComboboxState::new(
SearchableVec::new(Vec::<SharedString>::new()),
Vec::new(),
window,
cx,
)
.searchable(true)
});
let tag_select = cx.new(|cx| {
ComboboxState::new(
SearchableVec::new(Vec::<SharedString>::new()),
Vec::new(),
window,
cx,
)
.searchable(true)
});
let mut subscriptions = vec![ let mut subscriptions = vec![
cx.subscribe_in(&branch_select, window, |this, _state, event, window, cx| { cx.subscribe_in(
if let ComboboxEvent::Change(values) = event &refs.branch_select,
&& let Some(name) = values.first() window,
{ |this, _state, event, window, cx| {
this.switch_ref(RefKind::Branch, name.clone(), window, cx); if let ComboboxEvent::Change(values) = event
} && let Some(name) = values.first()
}), {
cx.subscribe_in(&tag_select, window, |this, _state, event, window, cx| { this.switch_ref(RefKind::Branch, name.clone(), window, cx);
if let ComboboxEvent::Change(values) = event }
&& let Some(name) = values.first() },
{ ),
this.switch_ref(RefKind::Tag, name.clone(), window, cx); cx.subscribe_in(
} &refs.tag_select,
}), window,
|this, _state, event, window, cx| {
if let ComboboxEvent::Change(values) = event
&& let Some(name) = values.first()
{
this.switch_ref(RefKind::Tag, name.clone(), window, cx);
}
},
),
]; ];
// The ready-to-contribute and ready-to-push banners are driven by the global checkouts store. // The ready-to-contribute and ready-to-push banners are driven by the global checkouts store.
@@ -197,18 +159,10 @@ impl RepoDetailView {
loading: true, loading: true,
error: None, error: None,
head_commit: None, head_commit: None,
branch_select, refs,
tag_select,
ref_branches: Vec::new(),
ref_tags: Vec::new(),
switching_ref: false,
ref_generation: 0, ref_generation: 0,
tasks: Vec::new(), tasks: Vec::new(),
banner_dismissed: HashSet::new(), banners: Banners::default(),
ready_requested: false,
ready_head: None,
ready_statuses: Vec::new(),
push_statuses: Vec::new(),
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
_subscriptions: subscriptions, _subscriptions: subscriptions,
}; };
+136 -24
View File
@@ -1,12 +1,123 @@
use anyhow::Error; use anyhow::Error;
use gpui::prelude::*; use gpui::prelude::*;
use gpui::{Context, Entity, SharedString, Window}; use gpui::{App, Context, Entity, SharedString, Window};
use gpui_component::combobox::ComboboxState; use gpui_component::combobox::ComboboxState;
use gpui_component::searchable_list::SearchableVec; use gpui_component::searchable_list::SearchableVec;
use super::{RefKind, RepoDetailView}; use super::{RefKind, RepoDetailView};
use crate::views::tree::{build_tree_items, sorted_worktree_paths}; use crate::views::tree::{build_tree_items, sorted_worktree_paths};
pub(super) struct RefSwitcher {
pub(super) branch_select: Entity<ComboboxState<SearchableVec<SharedString>>>,
pub(super) tag_select: Entity<ComboboxState<SearchableVec<SharedString>>>,
ref_branches: Vec<SharedString>,
ref_tags: Vec<SharedString>,
pub(super) switching_ref: bool,
}
impl RefSwitcher {
pub(super) fn new(window: &mut Window, cx: &mut App) -> Self {
let branch_select = cx.new(|cx| {
ComboboxState::new(
SearchableVec::new(Vec::<SharedString>::new()),
Vec::new(),
window,
cx,
)
.searchable(true)
});
let tag_select = cx.new(|cx| {
ComboboxState::new(
SearchableVec::new(Vec::<SharedString>::new()),
Vec::new(),
window,
cx,
)
.searchable(true)
});
Self {
branch_select,
tag_select,
ref_branches: Vec::new(),
ref_tags: Vec::new(),
switching_ref: false,
}
}
pub(super) fn set_branches(
&mut self,
branches: Vec<SharedString>,
selected: Option<SharedString>,
window: &mut Window,
cx: &mut App,
) -> bool {
sync_selector(
&self.branch_select,
&mut self.ref_branches,
branches,
selected,
window,
cx,
)
}
pub(super) fn set_tags(
&mut self,
tags: Vec<SharedString>,
window: &mut Window,
cx: &mut App,
) -> bool {
sync_selector(&self.tag_select, &mut self.ref_tags, tags, None, window, cx)
}
fn restore_selection(
&self,
select: &Entity<ComboboxState<SearchableVec<SharedString>>>,
previous: &Option<SharedString>,
window: &mut Window,
cx: &mut App,
) {
select.update(cx, |state, cx| match previous {
Some(value) => state.set_selected_values(std::slice::from_ref(value), window, cx),
None => state.clear_selection(cx),
});
}
}
fn sync_selector(
select: &Entity<ComboboxState<SearchableVec<SharedString>>>,
cached: &mut Vec<SharedString>,
items: Vec<SharedString>,
selected: Option<SharedString>,
window: &mut Window,
cx: &mut App,
) -> bool {
let items_changed = *cached != items;
let selection_changed = selected
.as_ref()
.is_some_and(|value| select.read(cx).selected_value().as_ref() != Some(value));
if !items_changed && !selection_changed {
return false;
}
select.update(cx, |state, cx| {
if items_changed {
state.set_items(SearchableVec::from(items.clone()), window, cx);
}
if let Some(value) = selected
&& (items_changed || selection_changed)
{
state.set_selected_values(std::slice::from_ref(&value), window, cx);
}
});
*cached = items;
true
}
impl RepoDetailView { impl RepoDetailView {
pub(super) fn switch_ref<T>( pub(super) fn switch_ref<T>(
&mut self, &mut self,
@@ -17,7 +128,7 @@ impl RepoDetailView {
) where ) where
T: Into<SharedString>, T: Into<SharedString>,
{ {
if self.switching_ref { if self.refs.switching_ref {
return; return;
} }
@@ -26,21 +137,23 @@ impl RepoDetailView {
}; };
let name = name.into(); let name = name.into();
let previous_branch = self.branch_select.read(cx).selected_value(); let previous_branch = self.refs.branch_select.read(cx).selected_value();
let previous_tag = self.tag_select.read(cx).selected_value(); let previous_tag = self.refs.tag_select.read(cx).selected_value();
match kind { match kind {
RefKind::Branch => { RefKind::Branch => {
self.tag_select self.refs
.tag_select
.update(cx, |state, cx| state.clear_selection(cx)); .update(cx, |state, cx| state.clear_selection(cx));
} }
RefKind::Tag => { RefKind::Tag => {
self.branch_select self.refs
.branch_select
.update(cx, |state, cx| state.clear_selection(cx)); .update(cx, |state, cx| state.clear_selection(cx));
} }
} }
self.switching_ref = true; self.refs.switching_ref = true;
self.ref_generation += 1; self.ref_generation += 1;
cx.notify(); cx.notify();
@@ -64,9 +177,19 @@ impl RepoDetailView {
Ok(()) => this.reload_worktree(cx), Ok(()) => this.reload_worktree(cx),
Err(error) => { Err(error) => {
this.error = Some(format!("Failed to check out {name}: {error}").into()); this.error = Some(format!("Failed to check out {name}: {error}").into());
this.switching_ref = false; this.refs.switching_ref = false;
this.restore_selection(&this.branch_select, &previous_branch, window, cx); this.refs.restore_selection(
this.restore_selection(&this.tag_select, &previous_tag, window, cx); &this.refs.branch_select,
&previous_branch,
window,
cx,
);
this.refs.restore_selection(
&this.refs.tag_select,
&previous_tag,
window,
cx,
);
} }
} }
cx.notify(); cx.notify();
@@ -78,19 +201,6 @@ impl RepoDetailView {
self.tasks.push(task); self.tasks.push(task);
} }
fn restore_selection(
&self,
select: &Entity<ComboboxState<SearchableVec<SharedString>>>,
previous: &Option<SharedString>,
window: &mut Window,
cx: &mut Context<Self>,
) {
select.update(cx, |state, cx| match previous {
Some(value) => state.set_selected_values(std::slice::from_ref(value), window, cx),
None => state.clear_selection(cx),
});
}
fn reload_worktree(&mut self, cx: &mut Context<Self>) { fn reload_worktree(&mut self, cx: &mut Context<Self>) {
let Some(worktree) = self.worktree.clone() else { let Some(worktree) = self.worktree.clone() else {
return; return;
@@ -108,7 +218,8 @@ impl RepoDetailView {
.await; .await;
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.switching_ref = false; this.refs.switching_ref = false;
match result { match result {
Ok((snapshot, tree, paths)) => { Ok((snapshot, tree, paths)) => {
this.head_commit = snapshot.head_commit; this.head_commit = snapshot.head_commit;
@@ -130,6 +241,7 @@ impl RepoDetailView {
}); });
} }
} }
cx.notify(); cx.notify();
})?; })?;
+4 -7
View File
@@ -47,13 +47,13 @@ impl RepoDetailView {
}; };
let head = self.store.read(cx).head.clone(); let head = self.store.read(cx).head.clone();
let (requested, requested_head) = self.banners.ready_requested_at();
if self.ready_requested && self.ready_head == head { if requested && requested_head == &head {
return; return;
} }
self.ready_requested = true; self.banners.mark_ready_requested(head.clone());
self.ready_head = head.clone();
let backend = Backend::global(cx); let backend = Backend::global(cx);
let checkout = CheckoutsStore::global(cx); let checkout = CheckoutsStore::global(cx);
@@ -83,9 +83,6 @@ impl RepoDetailView {
let ready_statuses = checkouts.ready_statuses_of(&addr); let ready_statuses = checkouts.ready_statuses_of(&addr);
let push_statuses = checkouts.push_statuses_of(&addr); let push_statuses = checkouts.push_statuses_of(&addr);
let changed = ready_statuses != self.ready_statuses || push_statuses != self.push_statuses; self.banners.set_statuses(ready_statuses, push_statuses)
self.ready_statuses = ready_statuses;
self.push_statuses = push_statuses;
changed
} }
} }