3460 lines · 123.5 KB · Rust
RawDownload
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.