2009 lines · 74.2 KB · Rust
RawDownload
use anyhow::Context as _;use editor::Editor;use fuzzy::StringMatchCandidate; use collections::HashSet;use git::repository::Branch;use gpui::http_client::Url;use gpui::{    Action, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,    InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement, Render,    SharedString, Styled, Subscription, Task, WeakEntity, Window, actions, rems,};use picker::{Picker, PickerDelegate, PickerEditorPosition};use project::git_store::Repository;use project::project_settings::ProjectSettings;use settings::Settings;use std::sync::Arc;use time::OffsetDateTime;use ui::{    Divider, HighlightedLabel, KeyBinding, ListHeader, ListItem, ListItemSpacing, 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, resolve_active_repository}; actions!(    branch_picker,    [        /// Deletes the selected git branch or remote.        DeleteBranch,        /// Filter the list of remotes        FilterRemotes    ]); 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 = resolve_active_repository(workspace, 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 create_embedded(    workspace: WeakEntity<Workspace>,    repository: Option<Entity<Repository>>,    width: Rems,    window: &mut Window,    cx: &mut Context<BranchList>,) -> BranchList {    BranchList::new_embedded(workspace, repository, width, window, cx)} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]enum BranchListStyle {    Modal,    Popover,} pub struct BranchList {    width: Rems,    pub picker: Entity<Picker<BranchListDelegate>>,    picker_focus_handle: FocusHandle,    _subscription: Option<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._subscription = Some(cx.subscribe(&this.picker, |_, _, _, 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 {        let all_branches_request = repository            .clone()            .map(|repository| repository.update(cx, |repository, _| repository.branches()));         let default_branch_request = repository.clone().map(|repository| {            repository.update(cx, |repository, _| repository.default_branch(false))        });         cx.spawn_in(window, async move |this, cx| {            let mut all_branches = all_branches_request                .context("No active repository")?                .await??;            let default_branch = default_branch_request                .context("No active repository")?                .await                .map(Result::ok)                .ok()                .flatten()                .flatten();             let all_branches = cx                .background_spawn(async move {                    let remote_upstreams: HashSet<_> = all_branches                        .iter()                        .filter_map(|branch| {                            branch                                .upstream                                .as_ref()                                .filter(|upstream| upstream.is_remote())                                .map(|upstream| upstream.ref_name.clone())                        })                        .collect();                     all_branches.retain(|branch| !remote_upstreams.contains(&branch.ref_name));                     all_branches.sort_by_key(|branch| {                        (                            !branch.is_head, // Current branch (is_head=true) comes first                            branch                                .most_recent_commit                                .as_ref()                                .map(|commit| 0 - commit.commit_timestamp),                        )                    });                     all_branches                })                .await;             let _ = this.update_in(cx, |this, window, cx| {                this.picker.update(cx, |picker, cx| {                    picker.delegate.default_branch = default_branch;                    picker.delegate.all_branches = Some(all_branches);                    picker.refresh(window, cx);                })            });             anyhow::Ok(())        })        .detach_and_log_err(cx);         let delegate = BranchListDelegate::new(workspace, repository, style, cx);        let picker = cx.new(|cx| {            Picker::uniform_list(delegate, window, cx)                .show_scrollbar(true)                .modal(!embedded)        });        let picker_focus_handle = picker.focus_handle(cx);         picker.update(cx, |picker, _| {            picker.delegate.focus_handle = picker_focus_handle.clone();        });         Self {            picker,            picker_focus_handle,            width,            _subscription: None,            embedded,        }    }     fn new_embedded(        workspace: WeakEntity<Workspace>,        repository: Option<Entity<Repository>>,        width: Rems,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let mut this = Self::new_inner(            workspace,            repository,            BranchListStyle::Modal,            width,            true,            window,            cx,        );        this._subscription = Some(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, _| picker.delegate.modifiers = ev.modifiers)    }     pub fn handle_delete(        &mut self,        _: &branch_picker::DeleteBranch,        window: &mut Window,        cx: &mut Context<Self>,    ) {        self.picker.update(cx, |picker, cx| {            picker                .delegate                .delete_at(picker.delegate.selected_index, window, cx)        })    }     pub fn handle_filter(        &mut self,        _: &branch_picker::FilterRemotes,        window: &mut Window,        cx: &mut Context<Self>,    ) {        self.picker.update(cx, |picker, cx| {            picker.delegate.branch_filter = picker.delegate.branch_filter.invert();            picker.update_matches(picker.query(cx), window, cx);            picker.refresh_placeholder(window, cx);            cx.notify();        });    }}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")            .w(self.width)            .on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))            .on_action(cx.listener(Self::handle_delete))            .on_action(cx.listener(Self::handle_filter))            .child(self.picker.clone())            .when(!self.embedded, |this| {                this.on_mouse_down_out({                    cx.listener(move |this, _, window, cx| {                        this.picker.update(cx, |this, cx| {                            this.cancel(&Default::default(), window, cx);                        })                    })                })            })    }} #[derive(Debug, Clone, PartialEq)]enum Entry {    Branch {        branch: Branch,        positions: Vec<usize>,    },    NewUrl {        url: String,    },    NewBranch {        name: String,    },    NewRemoteName {        name: String,        url: SharedString,    },} impl Entry {    fn as_branch(&self) -> Option<&Branch> {        match self {            Entry::Branch { branch, .. } => Some(branch),            _ => None,        }    }     fn name(&self) -> &str {        match self {            Entry::Branch { branch, .. } => branch.name(),            Entry::NewUrl { url, .. } => url.as_str(),            Entry::NewBranch { name, .. } => name.as_str(),            Entry::NewRemoteName { name, .. } => name.as_str(),        }    }     #[cfg(test)]    fn is_new_url(&self) -> bool {        matches!(self, Self::NewUrl { .. })    }     #[cfg(test)]    fn is_new_branch(&self) -> bool {        matches!(self, Self::NewBranch { .. })    }} #[derive(Clone, Copy, PartialEq)]enum BranchFilter {    /// Show both local and remote branches.    All,    /// Only show remote branches.    Remote,} impl BranchFilter {    fn invert(&self) -> Self {        match self {            BranchFilter::All => BranchFilter::Remote,            BranchFilter::Remote => BranchFilter::All,        }    }} pub struct BranchListDelegate {    workspace: WeakEntity<Workspace>,    matches: Vec<Entry>,    all_branches: Option<Vec<Branch>>,    default_branch: Option<SharedString>,    repo: Option<Entity<Repository>>,    style: BranchListStyle,    selected_index: usize,    last_query: String,    modifiers: Modifiers,    branch_filter: BranchFilter,    state: PickerState,    focus_handle: FocusHandle,} #[derive(Debug)]enum PickerState {    /// When we display list of branches/remotes    List,    /// When we set an url to create a new remote    NewRemote,    /// When we confirm the new remote url (after NewRemote)    CreateRemote(SharedString),    /// When we set a new branch to create    NewBranch,} impl BranchListDelegate {    fn new(        workspace: WeakEntity<Workspace>,        repo: Option<Entity<Repository>>,        style: BranchListStyle,        cx: &mut Context<BranchList>,    ) -> Self {        Self {            workspace,            matches: vec![],            repo,            style,            all_branches: None,            default_branch: None,            selected_index: 0,            last_query: Default::default(),            modifiers: Default::default(),            branch_filter: BranchFilter::All,            state: PickerState::List,            focus_handle: cx.focus_handle(),        }    }     fn create_branch(        &self,        from_branch: Option<SharedString>,        new_branch_name: SharedString,        window: &mut Window,        cx: &mut Context<Picker<Self>>,    ) {        let Some(repo) = self.repo.clone() else {            return;        };        let new_branch_name = new_branch_name.to_string().replace(' ', "-");        let base_branch = from_branch.map(|b| b.to_string());        cx.spawn(async move |_, cx| {            repo.update(cx, |repo, _| {                repo.create_branch(new_branch_name, base_branch)            })            .await??;             Ok(())        })        .detach_and_prompt_err("Failed to create branch", window, cx, |e, _, _| {            Some(e.to_string())        });        cx.emit(DismissEvent);    }     fn create_remote(        &self,        remote_name: String,        remote_url: String,        window: &mut Window,        cx: &mut Context<Picker<Self>>,    ) {        let Some(repo) = self.repo.clone() else {            return;        };         let receiver = repo.update(cx, |repo, _| repo.create_remote(remote_name, remote_url));         cx.background_spawn(async move { receiver.await? })            .detach_and_prompt_err("Failed to create remote", window, cx, |e, _, _cx| {                Some(e.to_string())            });        cx.emit(DismissEvent);    }     fn delete_at(&self, idx: usize, window: &mut Window, cx: &mut Context<Picker<Self>>) {        let Some(entry) = self.matches.get(idx).cloned() else {            return;        };        let Some(repo) = self.repo.clone() else {            return;        };         let workspace = self.workspace.clone();         cx.spawn_in(window, async move |picker, cx| {            let mut is_remote = false;            let result = match &entry {                Entry::Branch { branch, .. } => match branch.remote_name() {                    Some(remote_name) => {                        is_remote = true;                        repo.update(cx, |repo, _| repo.remove_remote(remote_name.to_string()))                            .await?                    }                    None => {                        repo.update(cx, |repo, _| repo.delete_branch(branch.name().to_string()))                            .await?                    }

Lines 1–500 of 2009.