use anyhow::Context as _;use editor::Editor;use fuzzy_nucleo::StringMatchCandidate; use collections::{HashMap, HashSet};use git::repository::{Branch, delete_branch_flag};use git::{GitHostingProviderRegistry, parse_git_remote_url};use gpui::http_client::Url;use gpui::{ Action, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Global, InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement, PromptLevel, Render, SharedString, Styled, Subscription, Task, TaskExt, WeakEntity, Window, actions, rems,};use picker::{Picker, PickerDelegate, PickerEditorPosition};use project::git_store::{Repository, RepositoryEvent};use project::project_settings::ProjectSettings;use settings::Settings; use std::sync::Arc;use time::OffsetDateTime;use ui::{ Banner, ContextMenu, Divider, HighlightedLabel, Indicator, KeyBinding, ListItem, ListItemSpacing, ListSubHeader, PopoverMenu, PopoverMenuHandle, Severity, Tooltip, prelude::*,};use ui_input::ErasedEditor;use util::ResultExt;use workspace::notifications::DetachAndPromptErr;use workspace::{ModalView, Workspace}; use crate::{branch_picker, git_panel::show_error_toast}; actions!( branch_picker, [ /// Deletes the selected git branch or remote. DeleteBranch, /// Force deletes the selected git branch or remote. ForceDeleteBranch, /// Show all branches. ShowAllBranches, /// Show only local branches. ShowLocalBranches, /// Show only remote branches. ShowRemoteBranches, /// Cycle through branch filters. CycleBranchFilter, /// Toggles the branch filter menu. ToggleFilterMenu ]); pub fn checkout_branch( workspace: &mut Workspace, _: &zed_actions::git::CheckoutBranch, window: &mut Window, cx: &mut Context<Workspace>,) { open(workspace, &zed_actions::git::Branch, window, cx);} pub fn switch( workspace: &mut Workspace, _: &zed_actions::git::Switch, window: &mut Window, cx: &mut Context<Workspace>,) { open(workspace, &zed_actions::git::Branch, window, cx);} pub fn open( workspace: &mut Workspace, _: &zed_actions::git::Branch, window: &mut Window, cx: &mut Context<Workspace>,) { let workspace_handle = workspace.weak_handle(); let repository = workspace.project().read(cx).active_repository(cx); workspace.toggle_modal(window, cx, |window, cx| { BranchList::new( workspace_handle, repository, BranchListStyle::Modal, rems(34.), window, cx, ) })} pub fn popover( workspace: WeakEntity<Workspace>, modal_style: bool, repository: Option<Entity<Repository>>, window: &mut Window, cx: &mut App,) -> Entity<BranchList> { let (style, width) = if modal_style { (BranchListStyle::Modal, rems(34.)) } else { (BranchListStyle::Popover, rems(20.)) }; cx.new(|cx| { let list = BranchList::new(workspace, repository, style, width, window, cx); list.focus_handle(cx).focus(window, cx); list })} pub fn select_popover( workspace: WeakEntity<Workspace>, repository: Option<Entity<Repository>>, selected_branch: Option<SharedString>, on_select: SelectBranchCallback, window: &mut Window, cx: &mut App,) -> Entity<BranchList> { cx.new(|cx| { let list = BranchList::new_select( workspace, repository, BranchListStyle::Popover, rems(20.), selected_branch, on_select, window, cx, ); list.focus_handle(cx).focus(window, cx); list })} pub fn select_modal( workspace: WeakEntity<Workspace>, repository: Option<Entity<Repository>>, selected_branch: Option<SharedString>, on_select: SelectBranchCallback, window: &mut Window, cx: &mut Context<BranchList>,) -> BranchList { let list = BranchList::new_select( workspace, repository, BranchListStyle::Modal, rems(34.), selected_branch, on_select, window, cx, ); list.focus_handle(cx).focus(window, cx); list} pub type SelectBranchCallback = Arc<dyn Fn(Branch, &mut Window, &mut App)>; pub fn create_embedded( workspace: WeakEntity<Workspace>, repository: Option<Entity<Repository>>, width: Rems, show_footer: bool, window: &mut Window, cx: &mut Context<BranchList>,) -> BranchList { BranchList::new_embedded(workspace, repository, width, show_footer, window, cx)} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]enum BranchListStyle { Modal, Popover,} pub struct BranchList { pub picker: Entity<Picker<BranchListDelegate>>, picker_focus_handle: FocusHandle, _subscriptions: Vec<Subscription>, embedded: bool,} impl BranchList { fn new( workspace: WeakEntity<Workspace>, repository: Option<Entity<Repository>>, style: BranchListStyle, width: Rems, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let mut this = Self::new_inner(workspace, repository, style, width, false, window, cx); this._subscriptions .push(cx.subscribe(&this.picker, |this, _, _, cx| { if !this.branch_filter_menu_open(cx) { cx.emit(DismissEvent); } })); this } fn new_inner( workspace: WeakEntity<Workspace>, repository: Option<Entity<Repository>>, style: BranchListStyle, width: Rems, embedded: bool, window: &mut Window, cx: &mut Context<Self>, ) -> Self { Self::new_inner_with_behavior( workspace, repository, style, width, embedded, BranchSelectionBehavior::Checkout, window, cx, ) } fn new_select( workspace: WeakEntity<Workspace>, repository: Option<Entity<Repository>>, style: BranchListStyle, width: Rems, selected_branch: Option<SharedString>, on_select: SelectBranchCallback, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let mut this = Self::new_inner_with_behavior( workspace, repository, style, width, false, BranchSelectionBehavior::Select { selected_branch, on_select, }, window, cx, ); this._subscriptions .push(cx.subscribe(&this.picker, |this, _, _, cx| { if !this.branch_filter_menu_open(cx) { cx.emit(DismissEvent); } })); this } fn new_inner_with_behavior( workspace: WeakEntity<Workspace>, repository: Option<Entity<Repository>>, style: BranchListStyle, width: Rems, embedded: bool, branch_selection_behavior: BranchSelectionBehavior, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let all_branches = repository .as_ref() .map(|repo| { process_branches( &repo.read(cx).branch_list, !branch_selection_behavior.is_select_only(), ) }) .unwrap_or_default(); let branch_list_error = repository .as_ref() .and_then(|repo| repo.read(cx).branch_list_error.clone()); let default_branch_request = repository.clone().map(|repository| { repository.update(cx, |repository, _| repository.default_branch(false)) }); let remote_urls_request = repository .clone() .map(|repository| repository.update(cx, |repository, _| repository.remote_urls())); let mut delegate = BranchListDelegate::new( workspace, repository.clone(), style, branch_selection_behavior, cx, ); delegate.all_branches = all_branches; delegate.branch_list_error = branch_list_error; let picker = cx.new(|cx| { Picker::list(delegate, window, cx) .initial_width(width) .show_scrollbar(true) .when(embedded, |picker| picker.embedded()) }); let picker_focus_handle = picker.focus_handle(cx); picker.update(cx, |picker, _| { picker.delegate.focus_handle = picker_focus_handle.clone(); picker.delegate.show_footer = !embedded && !picker.delegate.is_select_only(); }); let mut subscriptions = Vec::new(); if let Some(repo) = &repository { subscriptions.push(cx.subscribe_in( repo, window, move |this, repo, event, window, cx| { if matches!(event, RepositoryEvent::BranchListChanged) { let snapshot = repo.read(cx); let branch_list = snapshot.branch_list.clone(); let branch_list_error = snapshot.branch_list_error.clone(); this.picker.update(cx, |picker, cx| { picker.delegate.restore_selected_branch = picker .delegate .matches .get(picker.delegate.selected_index) .and_then(|entry| entry.as_branch().map(|b| b.ref_name.clone())); picker.delegate.all_branches = process_branches( &branch_list, !picker.delegate.branch_selection_behavior.is_select_only(), ); picker.delegate.branch_list_error = branch_list_error; picker.refresh(window, cx); }); } }, )); } // Fetch default branch asynchronously since it requires a git operation cx.spawn_in(window, async move |this, cx| { let default_branch = default_branch_request .context("No active repository")? .await .map(Result::ok) .ok() .flatten() .flatten(); let _ = this.update_in(cx, |this, _window, cx| { this.picker.update(cx, |picker, _cx| { picker.delegate.default_branch = default_branch; }); }); anyhow::Ok(()) }) .detach_and_log_err(cx); cx.spawn(async move |this, cx| { let remote_urls = remote_urls_request .context("No active repository")? .await??; let remote_provider_icons = cx.update(|cx| remote_provider_icons(&remote_urls, cx)); this.update(cx, |this, cx| { this.picker.update(cx, |picker, cx| { picker.delegate.remote_provider_icons = remote_provider_icons; cx.notify(); }); })?; anyhow::Ok(()) }) .detach_and_log_err(cx); Self { picker, picker_focus_handle, _subscriptions: subscriptions, embedded, } } fn new_embedded( workspace: WeakEntity<Workspace>, repository: Option<Entity<Repository>>, width: Rems, show_footer: bool, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let mut this = Self::new_inner( workspace, repository, BranchListStyle::Modal, width, true, window, cx, ); this.picker.update(cx, |picker, _| { picker.delegate.show_footer = show_footer; }); this._subscriptions .push(cx.subscribe(&this.picker, |_, _, _, cx| { cx.emit(DismissEvent); })); this } pub fn handle_modifiers_changed( &mut self, ev: &ModifiersChangedEvent, _: &mut Window, cx: &mut Context<Self>, ) { self.picker.update(cx, |picker, cx| { picker.delegate.modifiers = ev.modifiers; cx.notify(); }) } pub fn handle_delete( &mut self, _: &branch_picker::DeleteBranch, window: &mut Window, cx: &mut Context<Self>, ) { self.picker.update(cx, |picker, cx| { if picker.delegate.is_select_only() { return; } picker .delegate .delete_at(picker.delegate.selected_index, false, window, cx) }) } pub fn handle_force_delete( &mut self, _: &branch_picker::ForceDeleteBranch, window: &mut Window, cx: &mut Context<Self>, ) { self.picker.update(cx, |picker, cx| { if picker.delegate.is_select_only() { return; } picker .delegate .delete_at(picker.delegate.selected_index, true, window, cx) }) } pub(crate) fn set_branch_filter( &mut self, branch_filter: BranchFilter, window: &mut Window, cx: &mut Context<Self>, ) { cx.set_global(GlobalBranchFilter(branch_filter)); self.picker.update(cx, |picker, cx| { if picker.delegate.branch_filter == branch_filter { return; } picker.delegate.branch_filter = branch_filter; picker.update_matches(picker.query(cx), window, cx); picker.refresh_placeholder(window, cx); cx.notify(); }); } pub(crate) fn branch_filter_menu_open(&self, cx: &App) -> bool { self.picker .read(cx) .delegate .branch_filter_menu_handle .is_deployed() } pub(crate) fn cycle_branch_filter(&mut self, window: &mut Window, cx: &mut Context<Self>) { let branch_filter = self.picker.read(cx).delegate.branch_filter.next(); self.set_branch_filter(branch_filter, window, cx); }}impl ModalView for BranchList {}impl EventEmitter<DismissEvent> for BranchList {} impl Focusable for BranchList { fn focus_handle(&self, _cx: &App) -> FocusHandle { self.picker_focus_handle.clone() }} impl Render for BranchList { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { v_flex() .key_context("GitBranchSelector") .on_modifiers_changed(cx.listener(Self::handle_modifiers_changed)) .on_action(cx.listener(Self::handle_delete)) .on_action(cx.listener(Self::handle_force_delete)) .on_action( cx.listener(|this, _: &branch_picker::ShowAllBranches, window, cx| { this.set_branch_filter(BranchFilter::All, window, cx);Lines 1–500 of 3460. Load more
use anyhow::Context as _; use editor::Editor; use fuzzy_nucleo::StringMatchCandidate; use collections::{HashMap, HashSet}; use git::repository::{Branch, delete_branch_flag}; use git::{GitHostingProviderRegistry, parse_git_remote_url}; use gpui::http_client::Url; use gpui::{ Action, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Global, InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement, PromptLevel, Render, SharedString, Styled, Subscription, Task, TaskExt, WeakEntity, Window, actions, rems, }; use picker::{Picker, PickerDelegate, PickerEditorPosition}; use project::git_store::{Repository, RepositoryEvent}; use project::project_settings::ProjectSettings; use settings::Settings; use std::sync::Arc; use time::OffsetDateTime; use ui::{ Banner, ContextMenu, Divider, HighlightedLabel, Indicator, KeyBinding, ListItem, ListItemSpacing, ListSubHeader, PopoverMenu, PopoverMenuHandle, Severity, Tooltip, prelude::*, }; use ui_input::ErasedEditor; use util::ResultExt; use workspace::notifications::DetachAndPromptErr; use workspace::{ModalView, Workspace}; use crate::{branch_picker, git_panel::show_error_toast}; actions!( branch_picker, [ /// Deletes the selected git branch or remote. DeleteBranch, /// Force deletes the selected git branch or remote. ForceDeleteBranch, /// Show all branches. ShowAllBranches, /// Show only local branches. ShowLocalBranches, /// Show only remote branches. ShowRemoteBranches, /// Cycle through branch filters. CycleBranchFilter, /// Toggles the branch filter menu. ToggleFilterMenu ] ); pub fn checkout_branch( workspace: &mut Workspace, _: &zed_actions::git::CheckoutBranch, window: &mut Window, cx: &mut Context<Workspace>, ) { open(workspace, &zed_actions::git::Branch, window, cx); } pub fn switch( workspace: &mut Workspace, _: &zed_actions::git::Switch, window: &mut Window, cx: &mut Context<Workspace>, ) { open(workspace, &zed_actions::git::Branch, window, cx); } pub fn open( workspace: &mut Workspace, _: &zed_actions::git::Branch, window: &mut Window, cx: &mut Context<Workspace>, ) { let workspace_handle = workspace.weak_handle(); let repository = workspace.project().read(cx).active_repository(cx); workspace.toggle_modal(window, cx, |window, cx| { BranchList::new( workspace_handle, repository, BranchListStyle::Modal, rems(34.), window, cx, ) }) } pub fn popover( workspace: WeakEntity<Workspace>, modal_style: bool, repository: Option<Entity<Repository>>, window: &mut Window, cx: &mut App, ) -> Entity<BranchList> { let (style, width) = if modal_style { (BranchListStyle::Modal, rems(34.)) } else { (BranchListStyle::Popover, rems(20.)) }; cx.new(|cx| { let list = BranchList::new(workspace, repository, style, width, window, cx); list.focus_handle(cx).focus(window, cx); list }) } pub fn select_popover( workspace: WeakEntity<Workspace>, repository: Option<Entity<Repository>>, selected_branch: Option<SharedString>, on_select: SelectBranchCallback, window: &mut Window, cx: &mut App, ) -> Entity<BranchList> { cx.new(|cx| { let list = BranchList::new_select( workspace, repository, BranchListStyle::Popover, rems(20.), selected_branch, on_select, window, cx, ); list.focus_handle(cx).focus(window, cx); list }) } pub fn select_modal( workspace: WeakEntity<Workspace>, repository: Option<Entity<Repository>>, selected_branch: Option<SharedString>, on_select: SelectBranchCallback, window: &mut Window, cx: &mut Context<BranchList>, ) -> BranchList { let list = BranchList::new_select( workspace, repository, BranchListStyle::Modal, rems(34.), selected_branch, on_select, window, cx, ); list.focus_handle(cx).focus(window, cx); list } pub type SelectBranchCallback = Arc<dyn Fn(Branch, &mut Window, &mut App)>; pub fn create_embedded( workspace: WeakEntity<Workspace>, repository: Option<Entity<Repository>>, width: Rems, show_footer: bool, window: &mut Window, cx: &mut Context<BranchList>, ) -> BranchList { BranchList::new_embedded(workspace, repository, width, show_footer, window, cx) } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum BranchListStyle { Modal, Popover, } pub struct BranchList { pub picker: Entity<Picker<BranchListDelegate>>, picker_focus_handle: FocusHandle, _subscriptions: Vec<Subscription>, embedded: bool, } impl BranchList { fn new( workspace: WeakEntity<Workspace>, repository: Option<Entity<Repository>>, style: BranchListStyle, width: Rems, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let mut this = Self::new_inner(workspace, repository, style, width, false, window, cx); this._subscriptions .push(cx.subscribe(&this.picker, |this, _, _, cx| { if !this.branch_filter_menu_open(cx) { cx.emit(DismissEvent); } })); this } fn new_inner( workspace: WeakEntity<Workspace>, repository: Option<Entity<Repository>>, style: BranchListStyle, width: Rems, embedded: bool, window: &mut Window, cx: &mut Context<Self>, ) -> Self { Self::new_inner_with_behavior( workspace, repository, style, width, embedded, BranchSelectionBehavior::Checkout, window, cx, ) } fn new_select( workspace: WeakEntity<Workspace>, repository: Option<Entity<Repository>>, style: BranchListStyle, width: Rems, selected_branch: Option<SharedString>, on_select: SelectBranchCallback, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let mut this = Self::new_inner_with_behavior( workspace, repository, style, width, false, BranchSelectionBehavior::Select { selected_branch, on_select, }, window, cx, ); this._subscriptions .push(cx.subscribe(&this.picker, |this, _, _, cx| { if !this.branch_filter_menu_open(cx) { cx.emit(DismissEvent); } })); this } fn new_inner_with_behavior( workspace: WeakEntity<Workspace>, repository: Option<Entity<Repository>>, style: BranchListStyle, width: Rems, embedded: bool, branch_selection_behavior: BranchSelectionBehavior, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let all_branches = repository .as_ref() .map(|repo| { process_branches( &repo.read(cx).branch_list, !branch_selection_behavior.is_select_only(), ) }) .unwrap_or_default(); let branch_list_error = repository .as_ref() .and_then(|repo| repo.read(cx).branch_list_error.clone()); let default_branch_request = repository.clone().map(|repository| { repository.update(cx, |repository, _| repository.default_branch(false)) }); let remote_urls_request = repository .clone() .map(|repository| repository.update(cx, |repository, _| repository.remote_urls())); let mut delegate = BranchListDelegate::new( workspace, repository.clone(), style, branch_selection_behavior, cx, ); delegate.all_branches = all_branches; delegate.branch_list_error = branch_list_error; let picker = cx.new(|cx| { Picker::list(delegate, window, cx) .initial_width(width) .show_scrollbar(true) .when(embedded, |picker| picker.embedded()) }); let picker_focus_handle = picker.focus_handle(cx); picker.update(cx, |picker, _| { picker.delegate.focus_handle = picker_focus_handle.clone(); picker.delegate.show_footer = !embedded && !picker.delegate.is_select_only(); }); let mut subscriptions = Vec::new(); if let Some(repo) = &repository { subscriptions.push(cx.subscribe_in( repo, window, move |this, repo, event, window, cx| { if matches!(event, RepositoryEvent::BranchListChanged) { let snapshot = repo.read(cx); let branch_list = snapshot.branch_list.clone(); let branch_list_error = snapshot.branch_list_error.clone(); this.picker.update(cx, |picker, cx| { picker.delegate.restore_selected_branch = picker .delegate .matches .get(picker.delegate.selected_index) .and_then(|entry| entry.as_branch().map(|b| b.ref_name.clone())); picker.delegate.all_branches = process_branches( &branch_list, !picker.delegate.branch_selection_behavior.is_select_only(), ); picker.delegate.branch_list_error = branch_list_error; picker.refresh(window, cx); }); } }, )); } // Fetch default branch asynchronously since it requires a git operation cx.spawn_in(window, async move |this, cx| { let default_branch = default_branch_request .context("No active repository")? .await .map(Result::ok) .ok() .flatten() .flatten(); let _ = this.update_in(cx, |this, _window, cx| { this.picker.update(cx, |picker, _cx| { picker.delegate.default_branch = default_branch; }); }); anyhow::Ok(()) }) .detach_and_log_err(cx); cx.spawn(async move |this, cx| { let remote_urls = remote_urls_request .context("No active repository")? .await??; let remote_provider_icons = cx.update(|cx| remote_provider_icons(&remote_urls, cx)); this.update(cx, |this, cx| { this.picker.update(cx, |picker, cx| { picker.delegate.remote_provider_icons = remote_provider_icons; cx.notify(); }); })?; anyhow::Ok(()) }) .detach_and_log_err(cx); Self { picker, picker_focus_handle, _subscriptions: subscriptions, embedded, } } fn new_embedded( workspace: WeakEntity<Workspace>, repository: Option<Entity<Repository>>, width: Rems, show_footer: bool, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let mut this = Self::new_inner( workspace, repository, BranchListStyle::Modal, width, true, window, cx, ); this.picker.update(cx, |picker, _| { picker.delegate.show_footer = show_footer; }); this._subscriptions .push(cx.subscribe(&this.picker, |_, _, _, cx| { cx.emit(DismissEvent); })); this } pub fn handle_modifiers_changed( &mut self, ev: &ModifiersChangedEvent, _: &mut Window, cx: &mut Context<Self>, ) { self.picker.update(cx, |picker, cx| { picker.delegate.modifiers = ev.modifiers; cx.notify(); }) } pub fn handle_delete( &mut self, _: &branch_picker::DeleteBranch, window: &mut Window, cx: &mut Context<Self>, ) { self.picker.update(cx, |picker, cx| { if picker.delegate.is_select_only() { return; } picker .delegate .delete_at(picker.delegate.selected_index, false, window, cx) }) } pub fn handle_force_delete( &mut self, _: &branch_picker::ForceDeleteBranch, window: &mut Window, cx: &mut Context<Self>, ) { self.picker.update(cx, |picker, cx| { if picker.delegate.is_select_only() { return; } picker .delegate .delete_at(picker.delegate.selected_index, true, window, cx) }) } pub(crate) fn set_branch_filter( &mut self, branch_filter: BranchFilter, window: &mut Window, cx: &mut Context<Self>, ) { cx.set_global(GlobalBranchFilter(branch_filter)); self.picker.update(cx, |picker, cx| { if picker.delegate.branch_filter == branch_filter { return; } picker.delegate.branch_filter = branch_filter; picker.update_matches(picker.query(cx), window, cx); picker.refresh_placeholder(window, cx); cx.notify(); }); } pub(crate) fn branch_filter_menu_open(&self, cx: &App) -> bool { self.picker .read(cx) .delegate .branch_filter_menu_handle .is_deployed() } pub(crate) fn cycle_branch_filter(&mut self, window: &mut Window, cx: &mut Context<Self>) { let branch_filter = self.picker.read(cx).delegate.branch_filter.next(); self.set_branch_filter(branch_filter, window, cx); } } impl ModalView for BranchList {} impl EventEmitter<DismissEvent> for BranchList {} impl Focusable for BranchList { fn focus_handle(&self, _cx: &App) -> FocusHandle { self.picker_focus_handle.clone() } } impl Render for BranchList { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { v_flex() .key_context("GitBranchSelector") .on_modifiers_changed(cx.listener(Self::handle_modifiers_changed)) .on_action(cx.listener(Self::handle_delete)) .on_action(cx.listener(Self::handle_force_delete)) .on_action( cx.listener(|this, _: &branch_picker::ShowAllBranches, window, cx| { this.set_branch_filter(BranchFilter::All, window, cx);