.
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use assets::CustomIconName;
|
||||
use gpui::prelude::*;
|
||||
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 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 {
|
||||
fn ready_suggestion(&self, cx: &App) -> Option<CheckoutStatus> {
|
||||
let store = self.store.read(cx);
|
||||
@@ -23,10 +68,7 @@ impl RepoDetailView {
|
||||
let statuses = CheckoutsStore::global(cx).read(cx).ready_statuses_of(addr);
|
||||
|
||||
'status: for status in statuses {
|
||||
if self
|
||||
.banner_dismissed
|
||||
.contains(&(status.path.clone(), status.branch.clone()))
|
||||
{
|
||||
if self.banners.dismissal(&status) {
|
||||
continue;
|
||||
}
|
||||
for pr in &store.pull_requests {
|
||||
@@ -55,17 +97,15 @@ impl RepoDetailView {
|
||||
|
||||
let statuses = CheckoutsStore::global(cx).read(cx).push_statuses_of(addr);
|
||||
|
||||
statuses.into_iter().find(|status| {
|
||||
!self
|
||||
.banner_dismissed
|
||||
.contains(&(status.path.clone(), status.branch.clone()))
|
||||
})
|
||||
statuses
|
||||
.into_iter()
|
||||
.find(|status| !self.banners.dismissal(status))
|
||||
}
|
||||
|
||||
pub(super) fn render_push_banner(&self, cx: &Context<Self>) -> Option<AnyElement> {
|
||||
let status = self.push_suggestion(cx)?;
|
||||
let key = (status.path.clone(), status.branch.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.
|
||||
let pushing = self.store.read(cx).pushing;
|
||||
|
||||
@@ -98,7 +138,7 @@ impl RepoDetailView {
|
||||
.text_xs()
|
||||
.font_semibold()
|
||||
.font_family(cx.theme().mono_font_family.clone())
|
||||
.child(status.branch),
|
||||
.child(branch),
|
||||
)
|
||||
.child("has")
|
||||
.child(
|
||||
@@ -138,7 +178,7 @@ impl RepoDetailView {
|
||||
.ghost()
|
||||
.disabled(pushing)
|
||||
.on_click(cx.listener(move |this, _ev, _window, cx| {
|
||||
this.banner_dismissed.insert(key.clone());
|
||||
this.banners.dismiss(&status);
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
@@ -213,7 +253,8 @@ impl RepoDetailView {
|
||||
|
||||
pub(super) fn render_ready_banner(&self, cx: &Context<Self>) -> Option<AnyElement> {
|
||||
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 {
|
||||
SharedString::from("1 commit")
|
||||
@@ -244,7 +285,7 @@ impl RepoDetailView {
|
||||
.text_xs()
|
||||
.font_semibold()
|
||||
.font_family(cx.theme().mono_font_family.clone())
|
||||
.child(status.branch),
|
||||
.child(branch),
|
||||
)
|
||||
.child("is")
|
||||
.child(
|
||||
@@ -270,7 +311,7 @@ impl RepoDetailView {
|
||||
.text_xs()
|
||||
.font_semibold()
|
||||
.font_family(cx.theme().mono_font_family.clone())
|
||||
.child(status.base),
|
||||
.child(base),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@@ -298,7 +339,7 @@ impl RepoDetailView {
|
||||
.small()
|
||||
.ghost()
|
||||
.on_click(cx.listener(move |this, _ev, _window, cx| {
|
||||
this.banner_dismissed.insert(key.clone());
|
||||
this.banners.dismiss(&status);
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
|
||||
@@ -475,7 +475,7 @@ impl RepoDetailView {
|
||||
|
||||
fn render_header_tabs(&self, cx: &mut Context<Self>) -> AnyElement {
|
||||
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()
|
||||
.items_center()
|
||||
@@ -580,7 +580,7 @@ impl RepoDetailView {
|
||||
)
|
||||
.child(
|
||||
div().w(px(120.)).child(
|
||||
Combobox::new(&self.branch_select)
|
||||
Combobox::new(&self.refs.branch_select)
|
||||
.placeholder("Branch")
|
||||
.appearance(false)
|
||||
.menu_width(px(200.))
|
||||
@@ -594,7 +594,7 @@ impl RepoDetailView {
|
||||
)
|
||||
.child(
|
||||
div().w(px(120.)).child(
|
||||
Combobox::new(&self.tag_select)
|
||||
Combobox::new(&self.refs.tag_select)
|
||||
.placeholder("Tag")
|
||||
.appearance(false)
|
||||
.menu_width(px(200.))
|
||||
|
||||
@@ -3,9 +3,7 @@ use std::path::{Path, PathBuf};
|
||||
use anyhow::Error;
|
||||
use gix::Repository;
|
||||
use gpui::prelude::*;
|
||||
use gpui::{Context, Entity, PathPromptOptions, SharedString, Window};
|
||||
use gpui_component::combobox::ComboboxState;
|
||||
use gpui_component::searchable_list::SearchableVec;
|
||||
use gpui::{Context, PathPromptOptions, SharedString, Window};
|
||||
use nostr::prelude::Url;
|
||||
use signed_git::FileCommit;
|
||||
use signed_state::GitStore;
|
||||
@@ -185,25 +183,17 @@ impl RepoDetailView {
|
||||
let branches: Vec<SharedString> = branches.iter().map(Into::into).collect();
|
||||
let tags: Vec<SharedString> = tags.iter().map(Into::into).collect();
|
||||
|
||||
let branches_changed = Self::sync_ref_selector(
|
||||
&this.branch_select,
|
||||
&mut this.ref_branches,
|
||||
let branches_changed = this.refs.set_branches(
|
||||
branches,
|
||||
current_branch.map(Into::into),
|
||||
window,
|
||||
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 current_head_commit = this.head_commit.as_ref().map(|c| &c.id);
|
||||
|
||||
let head_changed = new_head_commit != current_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 tags: Vec<SharedString> = tags.into_iter().map(Into::into).collect();
|
||||
|
||||
Self::sync_ref_selector(
|
||||
&self.branch_select,
|
||||
&mut self.ref_branches,
|
||||
branches,
|
||||
current_branch.map(Into::into),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
self.refs
|
||||
.set_branches(branches, current_branch.map(Into::into), window, cx);
|
||||
|
||||
Self::sync_ref_selector(&self.tag_select, &mut self.ref_tags, tags, None, 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
|
||||
self.refs.set_tags(tags, window, cx);
|
||||
}
|
||||
|
||||
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 tree = build_tree_items(&entries);
|
||||
let readme_path = signed_git::find_readme(repo)?;
|
||||
|
||||
let readme = match &readme_path {
|
||||
Some(path) => signed_git::worktree_read(repo, path)?,
|
||||
None => None,
|
||||
};
|
||||
|
||||
let worktree = repo.workdir().map(Path::to_path_buf);
|
||||
// Ref listing is auxiliary UI.
|
||||
// A broken ref must not prevent the explorer from loading.
|
||||
// Failures degrade to empty selectors.
|
||||
let head_commit = signed_git::head_commit(repo).unwrap_or(None);
|
||||
|
||||
let (branches, tags, current_branch) = match &worktree {
|
||||
Some(_) => (
|
||||
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),
|
||||
};
|
||||
let head_commit = signed_git::head_commit(repo).unwrap_or(None);
|
||||
|
||||
Ok(RepoData {
|
||||
tree,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Error;
|
||||
@@ -9,13 +8,12 @@ use gpui::{
|
||||
SharedString, Subscription, Task, WeakEntity, Window, div,
|
||||
};
|
||||
use gpui_component::alert::Alert;
|
||||
use gpui_component::combobox::{ComboboxEvent, ComboboxState};
|
||||
use gpui_component::searchable_list::SearchableVec;
|
||||
use gpui_component::combobox::ComboboxEvent;
|
||||
use gpui_component::spinner::Spinner;
|
||||
use gpui_component::{ActiveTheme, Sizable, v_flex};
|
||||
use signed_core::{Announcement, RepoAddr};
|
||||
use signed_git::FileCommit;
|
||||
use signed_state::{CheckoutStatus, CheckoutsStore, RepoStore};
|
||||
use signed_state::{CheckoutsStore, RepoStore};
|
||||
|
||||
mod about;
|
||||
mod actions;
|
||||
@@ -30,8 +28,10 @@ mod store;
|
||||
|
||||
pub(crate) use actions::{RepoItem, open_repo_item, open_repo_panel};
|
||||
|
||||
use self::banners::Banners;
|
||||
use self::files::RepoFilesView;
|
||||
use self::history::RepoHistoryView;
|
||||
use self::refs::RefSwitcher;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum RefKind {
|
||||
@@ -41,10 +41,6 @@ enum RefKind {
|
||||
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)]
|
||||
#[action(namespace = repo, no_json)]
|
||||
pub(crate) enum RepoAction {
|
||||
@@ -74,38 +70,15 @@ pub struct RepoDetailView {
|
||||
loading: bool,
|
||||
error: Option<SharedString>,
|
||||
head_commit: Option<FileCommit>,
|
||||
branch_select: Entity<ComboboxState<SearchableVec<SharedString>>>,
|
||||
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,
|
||||
refs: RefSwitcher,
|
||||
/// In-flight loads with an older generation are discarded when they complete.
|
||||
ref_generation: u64,
|
||||
tasks: Vec<Task<Result<(), Error>>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
/// `(path, branch)` ready-suggestions dismissed by the user, per panel.
|
||||
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>,
|
||||
banners: Banners,
|
||||
}
|
||||
|
||||
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(
|
||||
dock_area: WeakEntity<DockArea>,
|
||||
addr: RepoAddr,
|
||||
@@ -136,42 +109,31 @@ impl RepoDetailView {
|
||||
let checkouts = CheckoutsStore::global(cx);
|
||||
let files = cx.new(RepoFilesView::new);
|
||||
let history = cx.new(|_cx| RepoHistoryView::new(store.clone(), dock_area.clone()));
|
||||
|
||||
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 refs = RefSwitcher::new(window, cx);
|
||||
|
||||
let mut subscriptions = vec![
|
||||
cx.subscribe_in(&branch_select, window, |this, _state, event, window, cx| {
|
||||
if let ComboboxEvent::Change(values) = event
|
||||
&& let Some(name) = values.first()
|
||||
{
|
||||
this.switch_ref(RefKind::Branch, name.clone(), window, cx);
|
||||
}
|
||||
}),
|
||||
cx.subscribe_in(&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);
|
||||
}
|
||||
}),
|
||||
cx.subscribe_in(
|
||||
&refs.branch_select,
|
||||
window,
|
||||
|this, _state, event, window, cx| {
|
||||
if let ComboboxEvent::Change(values) = event
|
||||
&& let Some(name) = values.first()
|
||||
{
|
||||
this.switch_ref(RefKind::Branch, 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.
|
||||
@@ -197,18 +159,10 @@ impl RepoDetailView {
|
||||
loading: true,
|
||||
error: None,
|
||||
head_commit: None,
|
||||
branch_select,
|
||||
tag_select,
|
||||
ref_branches: Vec::new(),
|
||||
ref_tags: Vec::new(),
|
||||
switching_ref: false,
|
||||
refs,
|
||||
ref_generation: 0,
|
||||
tasks: Vec::new(),
|
||||
banner_dismissed: HashSet::new(),
|
||||
ready_requested: false,
|
||||
ready_head: None,
|
||||
ready_statuses: Vec::new(),
|
||||
push_statuses: Vec::new(),
|
||||
banners: Banners::default(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
_subscriptions: subscriptions,
|
||||
};
|
||||
|
||||
@@ -1,12 +1,123 @@
|
||||
use anyhow::Error;
|
||||
use gpui::prelude::*;
|
||||
use gpui::{Context, Entity, SharedString, Window};
|
||||
use gpui::{App, Context, Entity, SharedString, Window};
|
||||
use gpui_component::combobox::ComboboxState;
|
||||
use gpui_component::searchable_list::SearchableVec;
|
||||
|
||||
use super::{RefKind, RepoDetailView};
|
||||
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 {
|
||||
pub(super) fn switch_ref<T>(
|
||||
&mut self,
|
||||
@@ -17,7 +128,7 @@ impl RepoDetailView {
|
||||
) where
|
||||
T: Into<SharedString>,
|
||||
{
|
||||
if self.switching_ref {
|
||||
if self.refs.switching_ref {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -26,21 +137,23 @@ impl RepoDetailView {
|
||||
};
|
||||
|
||||
let name = name.into();
|
||||
let previous_branch = self.branch_select.read(cx).selected_value();
|
||||
let previous_tag = self.tag_select.read(cx).selected_value();
|
||||
let previous_branch = self.refs.branch_select.read(cx).selected_value();
|
||||
let previous_tag = self.refs.tag_select.read(cx).selected_value();
|
||||
|
||||
match kind {
|
||||
RefKind::Branch => {
|
||||
self.tag_select
|
||||
self.refs
|
||||
.tag_select
|
||||
.update(cx, |state, cx| state.clear_selection(cx));
|
||||
}
|
||||
RefKind::Tag => {
|
||||
self.branch_select
|
||||
self.refs
|
||||
.branch_select
|
||||
.update(cx, |state, cx| state.clear_selection(cx));
|
||||
}
|
||||
}
|
||||
|
||||
self.switching_ref = true;
|
||||
self.refs.switching_ref = true;
|
||||
self.ref_generation += 1;
|
||||
cx.notify();
|
||||
|
||||
@@ -64,9 +177,19 @@ impl RepoDetailView {
|
||||
Ok(()) => this.reload_worktree(cx),
|
||||
Err(error) => {
|
||||
this.error = Some(format!("Failed to check out {name}: {error}").into());
|
||||
this.switching_ref = false;
|
||||
this.restore_selection(&this.branch_select, &previous_branch, window, cx);
|
||||
this.restore_selection(&this.tag_select, &previous_tag, window, cx);
|
||||
this.refs.switching_ref = false;
|
||||
this.refs.restore_selection(
|
||||
&this.refs.branch_select,
|
||||
&previous_branch,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
this.refs.restore_selection(
|
||||
&this.refs.tag_select,
|
||||
&previous_tag,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
@@ -78,19 +201,6 @@ impl RepoDetailView {
|
||||
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>) {
|
||||
let Some(worktree) = self.worktree.clone() else {
|
||||
return;
|
||||
@@ -108,7 +218,8 @@ impl RepoDetailView {
|
||||
.await;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.switching_ref = false;
|
||||
this.refs.switching_ref = false;
|
||||
|
||||
match result {
|
||||
Ok((snapshot, tree, paths)) => {
|
||||
this.head_commit = snapshot.head_commit;
|
||||
@@ -130,6 +241,7 @@ impl RepoDetailView {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
|
||||
@@ -47,13 +47,13 @@ impl RepoDetailView {
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
self.ready_requested = true;
|
||||
self.ready_head = head.clone();
|
||||
self.banners.mark_ready_requested(head.clone());
|
||||
|
||||
let backend = Backend::global(cx);
|
||||
let checkout = CheckoutsStore::global(cx);
|
||||
@@ -83,9 +83,6 @@ impl RepoDetailView {
|
||||
let ready_statuses = checkouts.ready_statuses_of(&addr);
|
||||
let push_statuses = checkouts.push_statuses_of(&addr);
|
||||
|
||||
let changed = ready_statuses != self.ready_statuses || push_statuses != self.push_statuses;
|
||||
self.ready_statuses = ready_statuses;
|
||||
self.push_statuses = push_statuses;
|
||||
changed
|
||||
self.banners.set_statuses(ready_statuses, push_statuses)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user