822 lines · 27.6 KB · Rust
RawDownload
use anyhow::Context as _;use collections::HashSet;use fuzzy::StringMatchCandidate; use git::repository::Worktree as GitWorktree;use gpui::{    Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EventEmitter, FocusHandle,    Focusable, InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement,    PathPromptOptions, Render, SharedString, Styled, Subscription, Task, WeakEntity, Window,    actions, rems,};use picker::{Picker, PickerDelegate, PickerEditorPosition};use project::{    DirectoryLister,    git_store::Repository,    trusted_worktrees::{PathTrust, TrustedWorktrees},};use remote::{RemoteConnectionOptions, remote_client::ConnectionIdentifier};use remote_connection::{RemoteConnectionModal, connect};use std::{path::PathBuf, sync::Arc};use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, prelude::*};use util::ResultExt;use workspace::{ModalView, MultiWorkspace, Workspace, notifications::DetachAndPromptErr}; actions!(git, [WorktreeFromDefault, WorktreeFromDefaultOnWindow]); pub fn open(    workspace: &mut Workspace,    _: &zed_actions::git::Worktree,    window: &mut Window,    cx: &mut Context<Workspace>,) {    let repository = workspace.project().read(cx).active_repository(cx);    let workspace_handle = workspace.weak_handle();    workspace.toggle_modal(window, cx, |window, cx| {        WorktreeList::new(repository, workspace_handle, rems(34.), window, cx)    })} pub fn create_embedded(    repository: Option<Entity<Repository>>,    workspace: WeakEntity<Workspace>,    width: Rems,    window: &mut Window,    cx: &mut Context<WorktreeList>,) -> WorktreeList {    WorktreeList::new_embedded(repository, workspace, width, window, cx)} pub struct WorktreeList {    width: Rems,    pub picker: Entity<Picker<WorktreeListDelegate>>,    picker_focus_handle: FocusHandle,    _subscription: Option<Subscription>,    embedded: bool,} impl WorktreeList {    fn new(        repository: Option<Entity<Repository>>,        workspace: WeakEntity<Workspace>,        width: Rems,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let mut this = Self::new_inner(repository, workspace, width, false, window, cx);        this._subscription = Some(cx.subscribe(&this.picker, |_, _, _, cx| {            cx.emit(DismissEvent);        }));        this    }     fn new_inner(        repository: Option<Entity<Repository>>,        workspace: WeakEntity<Workspace>,        width: Rems,        embedded: bool,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let all_worktrees_request = repository            .clone()            .map(|repository| repository.update(cx, |repository, _| repository.worktrees()));         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 all_worktrees = all_worktrees_request                .context("No active repository")?                .await??;             let default_branch = default_branch_request                .context("No active repository")?                .await                .map(Result::ok)                .ok()                .flatten()                .flatten();             this.update_in(cx, |this, window, cx| {                this.picker.update(cx, |picker, cx| {                    picker.delegate.all_worktrees = Some(all_worktrees);                    picker.delegate.default_branch = default_branch;                    picker.refresh(window, cx);                })            })?;             anyhow::Ok(())        })        .detach_and_log_err(cx);         let delegate = WorktreeListDelegate::new(workspace, repository, window, 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(        repository: Option<Entity<Repository>>,        workspace: WeakEntity<Workspace>,        width: Rems,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let mut this = Self::new_inner(repository, workspace, 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_new_worktree(        &mut self,        replace_current_window: bool,        window: &mut Window,        cx: &mut Context<Self>,    ) {        self.picker.update(cx, |picker, cx| {            let ix = picker.delegate.selected_index();            let Some(entry) = picker.delegate.matches.get(ix) else {                return;            };            let Some(default_branch) = picker.delegate.default_branch.clone() else {                return;            };            if !entry.is_new {                return;            }            picker.delegate.create_worktree(                entry.worktree.branch(),                replace_current_window,                Some(default_branch.into()),                window,                cx,            );        })    }}impl ModalView for WorktreeList {}impl EventEmitter<DismissEvent> for WorktreeList {} impl Focusable for WorktreeList {    fn focus_handle(&self, _: &App) -> FocusHandle {        self.picker_focus_handle.clone()    }} impl Render for WorktreeList {    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {        v_flex()            .key_context("GitWorktreeSelector")            .w(self.width)            .on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))            .on_action(cx.listener(|this, _: &WorktreeFromDefault, w, cx| {                this.handle_new_worktree(false, w, cx)            }))            .on_action(cx.listener(|this, _: &WorktreeFromDefaultOnWindow, w, cx| {                this.handle_new_worktree(true, w, cx)            }))            .child(self.picker.clone())            .when(!self.embedded, |el| {                el.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)]struct WorktreeEntry {    worktree: GitWorktree,    positions: Vec<usize>,    is_new: bool,} pub struct WorktreeListDelegate {    matches: Vec<WorktreeEntry>,    all_worktrees: Option<Vec<GitWorktree>>,    workspace: WeakEntity<Workspace>,    repo: Option<Entity<Repository>>,    selected_index: usize,    last_query: String,    modifiers: Modifiers,    focus_handle: FocusHandle,    default_branch: Option<SharedString>,} impl WorktreeListDelegate {    fn new(        workspace: WeakEntity<Workspace>,        repo: Option<Entity<Repository>>,        _window: &mut Window,        cx: &mut Context<WorktreeList>,    ) -> Self {        Self {            matches: vec![],            all_worktrees: None,            workspace,            selected_index: 0,            repo,            last_query: Default::default(),            modifiers: Default::default(),            focus_handle: cx.focus_handle(),            default_branch: None,        }    }     fn create_worktree(        &self,        worktree_branch: &str,        replace_current_window: bool,        commit: Option<String>,        window: &mut Window,        cx: &mut Context<Picker<Self>>,    ) {        let Some(repo) = self.repo.clone() else {            return;        };         let worktree_path = self            .workspace            .clone()            .update(cx, |this, cx| {                this.prompt_for_open_path(                    PathPromptOptions {                        files: false,                        directories: true,                        multiple: false,                        prompt: Some("Select directory for new worktree".into()),                    },                    DirectoryLister::Project(this.project().clone()),                    window,                    cx,                )            })            .log_err();        let Some(worktree_path) = worktree_path else {            return;        };         let branch = worktree_branch.to_string();        let workspace = self.workspace.clone();        cx.spawn_in(window, async move |_, cx| {            let Some(paths) = worktree_path.await? else {                return anyhow::Ok(());            };            let path = paths.get(0).cloned().context("No path selected")?;             repo.update(cx, |repo, _| {                repo.create_worktree(branch.clone(), path.clone(), commit)            })            .await??;            let new_worktree_path = path.join(branch);             workspace.update(cx, |workspace, cx| {                if let Some(trusted_worktrees) = TrustedWorktrees::try_get_global(cx) {                    let repo_path = &repo.read(cx).snapshot().work_directory_abs_path;                    let project = workspace.project();                    if let Some((parent_worktree, _)) =                        project.read(cx).find_worktree(repo_path, cx)                    {                        let worktree_store = project.read(cx).worktree_store();                        trusted_worktrees.update(cx, |trusted_worktrees, cx| {                            if trusted_worktrees.can_trust(                                &worktree_store,                                parent_worktree.read(cx).id(),                                cx,                            ) {                                trusted_worktrees.trust(                                    &worktree_store,                                    HashSet::from_iter([PathTrust::AbsPath(                                        new_worktree_path.clone(),                                    )]),                                    cx,                                );                            }                        });                    }                }            })?;             let (connection_options, app_state, is_local) =                workspace.update(cx, |workspace, cx| {                    let project = workspace.project().clone();                    let connection_options = project.read(cx).remote_connection_options(cx);                    let app_state = workspace.app_state().clone();                    let is_local = project.read(cx).is_local();                    (connection_options, app_state, is_local)                })?;             if is_local {                workspace                    .update_in(cx, |workspace, window, cx| {                        workspace.open_workspace_for_paths(                            replace_current_window,                            vec![new_worktree_path],                            window,                            cx,                        )                    })?                    .await?;            } else if let Some(connection_options) = connection_options {                open_remote_worktree(                    connection_options,                    vec![new_worktree_path],                    app_state,                    workspace.clone(),                    replace_current_window,                    cx,                )                .await?;            }             anyhow::Ok(())        })        .detach_and_prompt_err("Failed to create worktree", window, cx, |e, _, _| {            Some(e.to_string())        });    }     fn open_worktree(        &self,        worktree_path: &PathBuf,        replace_current_window: bool,        window: &mut Window,        cx: &mut Context<Picker<Self>>,    ) {        let workspace = self.workspace.clone();        let path = worktree_path.clone();         let Some((connection_options, app_state, is_local)) = workspace            .update(cx, |workspace, cx| {                let project = workspace.project().clone();                let connection_options = project.read(cx).remote_connection_options(cx);                let app_state = workspace.app_state().clone();                let is_local = project.read(cx).is_local();                (connection_options, app_state, is_local)            })            .log_err()        else {            return;        };         if is_local {            let open_task = workspace.update(cx, |workspace, cx| {                workspace.open_workspace_for_paths(replace_current_window, vec![path], window, cx)            });            cx.spawn(async move |_, _| {                open_task?.await?;                anyhow::Ok(())            })            .detach_and_prompt_err(                "Failed to open worktree",                window,                cx,                |e, _, _| Some(e.to_string()),            );        } else if let Some(connection_options) = connection_options {            cx.spawn_in(window, async move |_, cx| {                open_remote_worktree(                    connection_options,                    vec![path],                    app_state,                    workspace,                    replace_current_window,                    cx,                )                .await            })            .detach_and_prompt_err(                "Failed to open worktree",                window,                cx,                |e, _, _| Some(e.to_string()),            );        }         cx.emit(DismissEvent);    }     fn base_branch<'a>(&'a self, cx: &'a mut Context<Picker<Self>>) -> Option<&'a str> {        self.repo            .as_ref()            .and_then(|repo| repo.read(cx).branch.as_ref().map(|b| b.name()))    }} async fn open_remote_worktree(    connection_options: RemoteConnectionOptions,    paths: Vec<PathBuf>,    app_state: Arc<workspace::AppState>,    workspace: WeakEntity<Workspace>,    replace_current_window: bool,    cx: &mut AsyncWindowContext,) -> anyhow::Result<()> {    let workspace_window = cx        .window_handle()        .downcast::<MultiWorkspace>()        .ok_or_else(|| anyhow::anyhow!("Window is not a Workspace window"))?;     let connect_task = workspace.update_in(cx, |workspace, window, cx| {        workspace.toggle_modal(window, cx, |window, cx| {            RemoteConnectionModal::new(&connection_options, Vec::new(), window, cx)        });         let prompt = workspace            .active_modal::<RemoteConnectionModal>(cx)            .expect("Modal just created")            .read(cx)            .prompt            .clone();         connect(            ConnectionIdentifier::setup(),            connection_options.clone(),            prompt,            window,            cx,        )        .prompt_err("Failed to connect", window, cx, |_, _, _| None)    })?;     let session = connect_task.await;     workspace        .update_in(cx, |workspace, _window, cx| {            if let Some(prompt) = workspace.active_modal::<RemoteConnectionModal>(cx) {                prompt.update(cx, |prompt, cx| prompt.finished(cx))            }        })        .ok();     let Some(Some(session)) = session else {        return Ok(());    };     let new_project: Entity<project::Project> = cx.update(|_, cx| {        project::Project::remote(            session,            app_state.client.clone(),            app_state.node_runtime.clone(),            app_state.user_store.clone(),            app_state.languages.clone(),            app_state.fs.clone(),            true,            cx,        )    })?;     let window_to_use = if replace_current_window {

Lines 1–500 of 822.