2799 lines · 109.9 KB · Rust
RawDownload
use crate::{    dev_container::start_dev_container,    remote_connections::{        Connection, RemoteConnectionModal, RemoteConnectionPrompt, RemoteSettings, SshConnection,        SshConnectionHeader, connect, determine_paths_with_positions, open_remote_project,    },    ssh_config::parse_ssh_config_hosts,};use editor::Editor;use file_finder::OpenPathDelegate;use futures::{FutureExt, channel::oneshot, future::Shared, select};use gpui::{    AnyElement, App, ClickEvent, ClipboardItem, Context, DismissEvent, Entity, EventEmitter,    FocusHandle, Focusable, PromptLevel, ScrollHandle, Subscription, Task, WeakEntity, Window,    canvas,};use language::Point;use log::info;use paths::{global_ssh_config_file, user_ssh_config_file};use picker::Picker;use project::{Fs, Project};use remote::{    RemoteClient, RemoteConnectionOptions, SshConnectionOptions, WslConnectionOptions,    remote_client::ConnectionIdentifier,};use settings::{    RemoteProject, RemoteSettingsContent, Settings as _, SettingsStore, update_settings_file,    watch_config_file,};use smol::stream::StreamExt as _;use std::{    borrow::Cow,    collections::BTreeSet,    path::PathBuf,    rc::Rc,    sync::{        Arc,        atomic::{self, AtomicUsize},    },};use ui::{    CommonAnimationExt, IconButtonShape, KeyBinding, List, ListItem, ListSeparator, Modal,    ModalHeader, Navigable, NavigableEntry, Section, Tooltip, WithScrollbar, prelude::*,};use util::{    ResultExt,    paths::{PathStyle, RemotePathBuf},    rel_path::RelPath,};use workspace::{    ModalView, OpenOptions, Toast, Workspace,    notifications::{DetachAndPromptErr, NotificationId},    open_remote_project_with_existing_connection,}; pub struct RemoteServerProjects {    mode: Mode,    focus_handle: FocusHandle,    workspace: WeakEntity<Workspace>,    retained_connections: Vec<Entity<RemoteClient>>,    ssh_config_updates: Task<()>,    ssh_config_servers: BTreeSet<SharedString>,    create_new_window: bool,    _subscription: Subscription,} struct CreateRemoteServer {    address_editor: Entity<Editor>,    address_error: Option<SharedString>,    ssh_prompt: Option<Entity<RemoteConnectionPrompt>>,    _creating: Option<Task<Option<()>>>,} impl CreateRemoteServer {    fn new(window: &mut Window, cx: &mut App) -> Self {        let address_editor = cx.new(|cx| Editor::single_line(window, cx));        address_editor.update(cx, |this, cx| {            this.focus_handle(cx).focus(window, cx);        });        Self {            address_editor,            address_error: None,            ssh_prompt: None,            _creating: None,        }    }} #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]enum DevContainerCreationProgress {    Initial,    Creating,    Error(String),} #[derive(Clone)]struct CreateRemoteDevContainer {    // 3 Navigable Options    // - Create from devcontainer.json    // - Edit devcontainer.json    // - Go back    entries: [NavigableEntry; 3],    progress: DevContainerCreationProgress,} impl CreateRemoteDevContainer {    fn new(window: &mut Window, cx: &mut Context<RemoteServerProjects>) -> Self {        let entries = std::array::from_fn(|_| NavigableEntry::focusable(cx));        entries[0].focus_handle.focus(window, cx);        Self {            entries,            progress: DevContainerCreationProgress::Initial,        }    }     fn progress(&mut self, progress: DevContainerCreationProgress) -> Self {        self.progress = progress;        self.clone()    }} #[cfg(target_os = "windows")]struct AddWslDistro {    picker: Entity<Picker<crate::wsl_picker::WslPickerDelegate>>,    connection_prompt: Option<Entity<RemoteConnectionPrompt>>,    _creating: Option<Task<()>>,} #[cfg(target_os = "windows")]impl AddWslDistro {    fn new(window: &mut Window, cx: &mut Context<RemoteServerProjects>) -> Self {        use crate::wsl_picker::{WslDistroSelected, WslPickerDelegate, WslPickerDismissed};         let delegate = WslPickerDelegate::new();        let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false));         cx.subscribe_in(            &picker,            window,            |this, _, _: &WslDistroSelected, window, cx| {                this.confirm(&menu::Confirm, window, cx);            },        )        .detach();         cx.subscribe_in(            &picker,            window,            |this, _, _: &WslPickerDismissed, window, cx| {                this.cancel(&menu::Cancel, window, cx);            },        )        .detach();         AddWslDistro {            picker,            connection_prompt: None,            _creating: None,        }    }} enum ProjectPickerData {    Ssh {        connection_string: SharedString,        nickname: Option<SharedString>,    },    Wsl {        distro_name: SharedString,    },} struct ProjectPicker {    data: ProjectPickerData,    picker: Entity<Picker<OpenPathDelegate>>,    _path_task: Shared<Task<Option<()>>>,} struct EditNicknameState {    index: SshServerIndex,    editor: Entity<Editor>,} impl EditNicknameState {    fn new(index: SshServerIndex, window: &mut Window, cx: &mut App) -> Self {        let this = Self {            index,            editor: cx.new(|cx| Editor::single_line(window, cx)),        };        let starting_text = RemoteSettings::get_global(cx)            .ssh_connections()            .nth(index.0)            .and_then(|state| state.nickname)            .filter(|text| !text.is_empty());        this.editor.update(cx, |this, cx| {            this.set_placeholder_text("Add a nickname for this server", window, cx);            if let Some(starting_text) = starting_text {                this.set_text(starting_text, window, cx);            }        });        this.editor.focus_handle(cx).focus(window, cx);        this    }} impl Focusable for ProjectPicker {    fn focus_handle(&self, cx: &App) -> FocusHandle {        self.picker.focus_handle(cx)    }} impl ProjectPicker {    fn new(        create_new_window: bool,        index: ServerIndex,        connection: RemoteConnectionOptions,        project: Entity<Project>,        home_dir: RemotePathBuf,        workspace: WeakEntity<Workspace>,        window: &mut Window,        cx: &mut Context<RemoteServerProjects>,    ) -> Entity<Self> {        let (tx, rx) = oneshot::channel();        let lister = project::DirectoryLister::Project(project.clone());        let delegate = file_finder::OpenPathDelegate::new(tx, lister, false, cx);         let picker = cx.new(|cx| {            let picker = Picker::uniform_list(delegate, window, cx)                .width(rems(34.))                .modal(false);            picker.set_query(home_dir.to_string(), window, cx);            picker        });         let data = match &connection {            RemoteConnectionOptions::Ssh(connection) => ProjectPickerData::Ssh {                connection_string: connection.connection_string().into(),                nickname: connection.nickname.clone().map(|nick| nick.into()),            },            RemoteConnectionOptions::Wsl(connection) => ProjectPickerData::Wsl {                distro_name: connection.distro_name.clone().into(),            },            RemoteConnectionOptions::Docker(_) => ProjectPickerData::Ssh {                // Not implemented as a project picker at this time                connection_string: "".into(),                nickname: None,            },        };        let _path_task = cx            .spawn_in(window, {                let workspace = workspace;                async move |this, cx| {                    let Ok(Some(paths)) = rx.await else {                        workspace                            .update_in(cx, |workspace, window, cx| {                                let fs = workspace.project().read(cx).fs().clone();                                let weak = cx.entity().downgrade();                                workspace.toggle_modal(window, cx, |window, cx| {                                    RemoteServerProjects::new(                                        create_new_window,                                        fs,                                        window,                                        weak,                                        cx,                                    )                                });                            })                            .log_err()?;                        return None;                    };                     let app_state = workspace                        .read_with(cx, |workspace, _| workspace.app_state().clone())                        .ok()?;                     let remote_connection = project                        .read_with(cx, |project, cx| {                            project.remote_client()?.read(cx).connection()                        })                        .ok()??;                     let (paths, paths_with_positions) =                        determine_paths_with_positions(&remote_connection, paths).await;                     cx.update(|_, cx| {                        let fs = app_state.fs.clone();                        update_settings_file(fs, cx, {                            let paths = paths                                .iter()                                .map(|path| path.to_string_lossy().into_owned())                                .collect();                            move |settings, _| match index {                                ServerIndex::Ssh(index) => {                                    if let Some(server) = settings                                        .remote                                        .ssh_connections                                        .as_mut()                                        .and_then(|connections| connections.get_mut(index.0))                                    {                                        server.projects.insert(RemoteProject { paths });                                    };                                }                                ServerIndex::Wsl(index) => {                                    if let Some(server) = settings                                        .remote                                        .wsl_connections                                        .as_mut()                                        .and_then(|connections| connections.get_mut(index.0))                                    {                                        server.projects.insert(RemoteProject { paths });                                    };                                }                            }                        });                    })                    .log_err();                     let options = cx                        .update(|_, cx| (app_state.build_window_options)(None, cx))                        .log_err()?;                    let window = cx                        .open_window(options, |window, cx| {                            cx.new(|cx| {                                telemetry::event!("SSH Project Created");                                Workspace::new(None, project.clone(), app_state.clone(), window, cx)                            })                        })                        .log_err()?;                     let items = open_remote_project_with_existing_connection(                        connection, project, paths, app_state, window, cx,                    )                    .await                    .log_err();                     if let Some(items) = items {                        for (item, path) in items.into_iter().zip(paths_with_positions) {                            let Some(item) = item else {                                continue;                            };                            let Some(row) = path.row else {                                continue;                            };                            if let Some(active_editor) = item.downcast::<Editor>() {                                window                                    .update(cx, |_, window, cx| {                                        active_editor.update(cx, |editor, cx| {                                            let row = row.saturating_sub(1);                                            let col = path.column.unwrap_or(0).saturating_sub(1);                                            editor.go_to_singleton_buffer_point(                                                Point::new(row, col),                                                window,                                                cx,                                            );                                        });                                    })                                    .ok();                            }                        }                    }                     this.update(cx, |_, cx| {                        cx.emit(DismissEvent);                    })                    .ok();                    Some(())                }            })            .shared();        cx.new(|_| Self {            _path_task,            picker,            data,        })    }} impl gpui::Render for ProjectPicker {    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {        v_flex()            .child(match &self.data {                ProjectPickerData::Ssh {                    connection_string,                    nickname,                } => SshConnectionHeader {                    connection_string: connection_string.clone(),                    paths: Default::default(),                    nickname: nickname.clone(),                    is_wsl: false,                    is_devcontainer: false,                }                .render(window, cx),                ProjectPickerData::Wsl { distro_name } => SshConnectionHeader {                    connection_string: distro_name.clone(),                    paths: Default::default(),                    nickname: None,                    is_wsl: true,                    is_devcontainer: false,                }                .render(window, cx),            })            .child(                div()                    .border_t_1()                    .border_color(cx.theme().colors().border_variant)                    .child(self.picker.clone()),            )    }} #[repr(transparent)]#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]struct SshServerIndex(usize);impl std::fmt::Display for SshServerIndex {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        self.0.fmt(f)    }} #[repr(transparent)]#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]struct WslServerIndex(usize);impl std::fmt::Display for WslServerIndex {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        self.0.fmt(f)    }} #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]enum ServerIndex {    Ssh(SshServerIndex),    Wsl(WslServerIndex),}impl From<SshServerIndex> for ServerIndex {    fn from(index: SshServerIndex) -> Self {        Self::Ssh(index)    }}impl From<WslServerIndex> for ServerIndex {    fn from(index: WslServerIndex) -> Self {        Self::Wsl(index)    }} #[derive(Clone)]enum RemoteEntry {    Project {        open_folder: NavigableEntry,        projects: Vec<(NavigableEntry, RemoteProject)>,        configure: NavigableEntry,        connection: Connection,        index: ServerIndex,    },    SshConfig {        open_folder: NavigableEntry,        host: SharedString,    },} impl RemoteEntry {    fn is_from_zed(&self) -> bool {        matches!(self, Self::Project { .. })    }     fn connection(&self) -> Cow<'_, Connection> {        match self {            Self::Project { connection, .. } => Cow::Borrowed(connection),            Self::SshConfig { host, .. } => Cow::Owned(                SshConnection {                    host: host.clone(),                    ..SshConnection::default()                }                .into(),            ),        }    }} #[derive(Clone)]struct DefaultState {    scroll_handle: ScrollHandle,    add_new_server: NavigableEntry,    add_new_devcontainer: NavigableEntry,    add_new_wsl: NavigableEntry,    servers: Vec<RemoteEntry>,} impl DefaultState {    fn new(ssh_config_servers: &BTreeSet<SharedString>, cx: &mut App) -> Self {        let handle = ScrollHandle::new();        let add_new_server = NavigableEntry::new(&handle, cx);        let add_new_devcontainer = NavigableEntry::new(&handle, cx);        let add_new_wsl = NavigableEntry::new(&handle, cx);         let ssh_settings = RemoteSettings::get_global(cx);        let read_ssh_config = ssh_settings.read_ssh_config;         let ssh_servers = ssh_settings            .ssh_connections()            .enumerate()

Lines 1–500 of 2799.