diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 92fae951a5e46a81bce4bd1b57e59dea936d6509..0bd1b46a5e909ff296a1fb359d3b586586cbc8f7 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -35,7 +35,7 @@ use git::{ StashApply, StashPop, TrashUntrackedFiles, UnstageAll, }; use gpui::{ - Action, AsyncApp, AsyncWindowContext, Bounds, ClickEvent, Corner, DismissEvent, Entity, + Action, AsyncApp, AsyncWindowContext, Bounds, ClickEvent, Corner, DismissEvent, Empty, Entity, EventEmitter, FocusHandle, Focusable, KeyContext, MouseButton, MouseDownEvent, Point, PromptLevel, ScrollStrategy, Subscription, Task, UniformListScrollHandle, WeakEntity, actions, anchored, deferred, point, size, uniform_list, @@ -5771,22 +5771,33 @@ impl RenderOnce for PanelRepoFooter { let repo_selector_trigger = Button::new("repo-selector", truncated_repo_name) .size(ButtonSize::None) - .label_size(LabelSize::Small) - .color(Color::Muted); + .label_size(LabelSize::Small); let repo_selector = PopoverMenu::new("repository-switcher") .menu({ let project = project; move |window, cx| { let project = project.clone()?; - Some(cx.new(|cx| RepositorySelector::new(project, rems(16.), window, cx))) + Some(cx.new(|cx| RepositorySelector::new(project, rems(20.), window, cx))) } }) .trigger_with_tooltip( - repo_selector_trigger.disabled(single_repo).truncate(true), - Tooltip::text("Switch Active Repository"), + repo_selector_trigger + .when(single_repo, |this| this.disabled(true).color(Color::Muted)) + .truncate(true), + move |_, cx| { + if single_repo { + cx.new(|_| Empty).into() + } else { + Tooltip::simple("Switch Active Repository", cx) + } + }, ) .anchor(Corner::BottomLeft) + .offset(gpui::Point { + x: px(0.0), + y: px(-2.0), + }) .into_any_element(); let branch_selector_button = Button::new("branch-selector", truncated_branch_name) diff --git a/crates/git_ui/src/repository_selector.rs b/crates/git_ui/src/repository_selector.rs index 5e60bebc4279df4bbf90a685ccffa957803253f7..463540de90ce204fba6af5b5d17a6a9e8f831fb8 100644 --- a/crates/git_ui/src/repository_selector.rs +++ b/crates/git_ui/src/repository_selector.rs @@ -1,3 +1,5 @@ +use crate::git_status_icon; +use git::status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode}; use gpui::{App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity}; use itertools::Itertools; use picker::{Picker, PickerDelegate, PickerEditorPosition}; @@ -38,7 +40,12 @@ impl RepositorySelector { let repository_entries = git_store.update(cx, |git_store, _cx| { let mut repos: Vec<_> = git_store.repositories().values().cloned().collect(); - repos.sort_by_key(|a| a.read(_cx).display_name()); + repos.sort_by(|a, b| { + a.read(_cx) + .display_name() + .to_lowercase() + .cmp(&b.read(_cx).display_name().to_lowercase()) + }); repos }); @@ -51,17 +58,24 @@ impl RepositorySelector { .cmp(&b.read(cx).display_name().len()) }); + let active_repository = git_store.read(cx).active_repository(); + let selected_index = active_repository + .as_ref() + .and_then(|active| filtered_repositories.iter().position(|repo| repo == active)) + .unwrap_or(0); let delegate = RepositorySelectorDelegate { repository_selector: cx.entity().downgrade(), repository_entries, filtered_repositories, - selected_index: 0, + active_repository, + selected_index, }; let picker = cx.new(|cx| { Picker::uniform_list(delegate, window, cx) .widest_item(widest_item_ix) .max_height(Some(rems(20.).into())) + .show_scrollbar(true) }); RepositorySelector { picker, width } @@ -122,6 +136,7 @@ pub struct RepositorySelectorDelegate { repository_selector: WeakEntity, repository_entries: Vec>, filtered_repositories: Vec>, + active_repository: Option>, selected_index: usize, } @@ -129,7 +144,15 @@ impl RepositorySelectorDelegate { pub fn update_repository_entries(&mut self, all_repositories: Vec>) { self.repository_entries = all_repositories.clone(); self.filtered_repositories = all_repositories; - self.selected_index = 0; + self.selected_index = self + .active_repository + .as_ref() + .and_then(|active| { + self.filtered_repositories + .iter() + .position(|repo| repo == active) + }) + .unwrap_or(0); } } @@ -193,9 +216,20 @@ impl PickerDelegate for RepositorySelectorDelegate { this.update_in(cx, |this, window, cx| { let mut sorted_repositories = filtered_repositories; - sorted_repositories.sort_by_key(|a| a.read(cx).display_name()); + sorted_repositories.sort_by(|a, b| { + a.read(cx) + .display_name() + .to_lowercase() + .cmp(&b.read(cx).display_name().to_lowercase()) + }); + let selected_index = this + .delegate + .active_repository + .as_ref() + .and_then(|active| sorted_repositories.iter().position(|repo| repo == active)) + .unwrap_or(0); this.delegate.filtered_repositories = sorted_repositories; - this.delegate.set_selected_index(0, window, cx); + this.delegate.set_selected_index(selected_index, window, cx); cx.notify(); }) .ok(); @@ -226,13 +260,56 @@ impl PickerDelegate for RepositorySelectorDelegate { cx: &mut Context>, ) -> Option { let repo_info = self.filtered_repositories.get(ix)?; - let display_name = repo_info.read(cx).display_name(); - Some( - ListItem::new(ix) - .inset(true) - .spacing(ListItemSpacing::Sparse) - .toggle_state(selected) - .child(Label::new(display_name)), - ) + let repo = repo_info.read(cx); + let display_name = repo.display_name(); + let summary = repo.status_summary(); + let is_active = self + .active_repository + .as_ref() + .is_some_and(|active| active == repo_info); + + let mut item = ListItem::new(ix) + .inset(true) + .spacing(ListItemSpacing::Sparse) + .toggle_state(selected) + .child( + h_flex() + .gap_1() + .child(Label::new(display_name)) + .when(is_active, |this| { + this.child( + Icon::new(IconName::Check) + .size(IconSize::Small) + .color(Color::Accent), + ) + }), + ); + + if summary.count > 0 { + let status = if summary.conflict > 0 { + FileStatus::Unmerged(UnmergedStatus { + first_head: UnmergedStatusCode::Updated, + second_head: UnmergedStatusCode::Updated, + }) + } else if summary.worktree.deleted > 0 || summary.index.deleted > 0 { + FileStatus::Tracked(TrackedStatus { + index_status: StatusCode::Deleted, + worktree_status: StatusCode::Unmodified, + }) + } else if summary.worktree.modified > 0 || summary.index.modified > 0 { + FileStatus::Tracked(TrackedStatus { + index_status: StatusCode::Modified, + worktree_status: StatusCode::Unmodified, + }) + } else { + FileStatus::Tracked(TrackedStatus { + index_status: StatusCode::Added, + worktree_status: StatusCode::Unmodified, + }) + }; + item = item.end_slot(div().pr_2().child(git_status_icon(status))); + } + + Some(item) } }