1394 lines · 54.8 KB · Rust
RawDownload
use std::collections::BTreeSet;use std::path::PathBuf;use std::sync::Arc; use editor::Editor;use file_finder::OpenPathDelegate;use futures::channel::oneshot;use futures::future::Shared;use futures::FutureExt;use gpui::canvas;use gpui::ClipboardItem;use gpui::Task;use gpui::WeakView;use gpui::{    AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,    PromptLevel, ScrollHandle, View, ViewContext,};use picker::Picker;use project::Project;use remote::ssh_session::ConnectionIdentifier;use remote::SshConnectionOptions;use remote::SshRemoteClient;use settings::update_settings_file;use settings::Settings;use ui::Navigable;use ui::NavigableEntry;use ui::{    prelude::*, IconButtonShape, List, ListItem, ListSeparator, Modal, ModalHeader, Scrollbar,    ScrollbarState, Section, Tooltip,};use util::ResultExt;use workspace::notifications::NotificationId;use workspace::OpenOptions;use workspace::Toast;use workspace::{notifications::DetachAndPromptErr, ModalView, Workspace}; use crate::ssh_connections::connect_over_ssh;use crate::ssh_connections::open_ssh_project;use crate::ssh_connections::RemoteSettingsContent;use crate::ssh_connections::SshConnection;use crate::ssh_connections::SshConnectionHeader;use crate::ssh_connections::SshConnectionModal;use crate::ssh_connections::SshProject;use crate::ssh_connections::SshPrompt;use crate::ssh_connections::SshSettings;use crate::OpenRemote; mod navigation_base {}pub struct RemoteServerProjects {    mode: Mode,    focus_handle: FocusHandle,    workspace: WeakView<Workspace>,    retained_connections: Vec<Model<SshRemoteClient>>,} struct CreateRemoteServer {    address_editor: View<Editor>,    address_error: Option<SharedString>,    ssh_prompt: Option<View<SshPrompt>>,    _creating: Option<Task<Option<()>>>,} impl CreateRemoteServer {    fn new(cx: &mut WindowContext) -> Self {        let address_editor = cx.new_view(Editor::single_line);        address_editor.update(cx, |this, cx| {            this.focus_handle(cx).focus(cx);        });        Self {            address_editor,            address_error: None,            ssh_prompt: None,            _creating: None,        }    }} struct ProjectPicker {    connection_string: SharedString,    nickname: Option<SharedString>,    picker: View<Picker<OpenPathDelegate>>,    _path_task: Shared<Task<Option<()>>>,} struct EditNicknameState {    index: usize,    editor: View<Editor>,} impl EditNicknameState {    fn new(index: usize, cx: &mut WindowContext) -> Self {        let this = Self {            index,            editor: cx.new_view(Editor::single_line),        };        let starting_text = SshSettings::get_global(cx)            .ssh_connections()            .nth(index)            .and_then(|state| state.nickname.clone())            .filter(|text| !text.is_empty());        this.editor.update(cx, |this, cx| {            this.set_placeholder_text("Add a nickname for this server", cx);            if let Some(starting_text) = starting_text {                this.set_text(starting_text, cx);            }        });        this.editor.focus_handle(cx).focus(cx);        this    }} impl FocusableView for ProjectPicker {    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {        self.picker.focus_handle(cx)    }} impl ProjectPicker {    fn new(        ix: usize,        connection: SshConnectionOptions,        project: Model<Project>,        workspace: WeakView<Workspace>,        cx: &mut ViewContext<RemoteServerProjects>,    ) -> View<Self> {        let (tx, rx) = oneshot::channel();        let lister = project::DirectoryLister::Project(project.clone());        let query = lister.default_query(cx);        let delegate = file_finder::OpenPathDelegate::new(tx, lister);         let picker = cx.new_view(|cx| {            let picker = Picker::uniform_list(delegate, cx)                .width(rems(34.))                .modal(false);            picker.set_query(query, cx);            picker        });        let connection_string = connection.connection_string().into();        let nickname = connection.nickname.clone().map(|nick| nick.into());        let _path_task = cx            .spawn({                let workspace = workspace.clone();                move |this, mut cx| async move {                    let Ok(Some(paths)) = rx.await else {                        workspace                            .update(&mut cx, |workspace, cx| {                                let weak = cx.view().downgrade();                                workspace                                    .toggle_modal(cx, |cx| RemoteServerProjects::new(cx, weak));                            })                            .log_err()?;                        return None;                    };                     let app_state = workspace                        .update(&mut cx, |workspace, _| workspace.app_state().clone())                        .ok()?;                    let options = cx                        .update(|cx| (app_state.build_window_options)(None, cx))                        .log_err()?;                     cx.open_window(options, |cx| {                        cx.activate_window();                         let fs = app_state.fs.clone();                        update_settings_file::<SshSettings>(fs, cx, {                            let paths = paths                                .iter()                                .map(|path| path.to_string_lossy().to_string())                                .collect();                            move |setting, _| {                                if let Some(server) = setting                                    .ssh_connections                                    .as_mut()                                    .and_then(|connections| connections.get_mut(ix))                                {                                    server.projects.insert(SshProject { paths });                                }                            }                        });                         let tasks = paths                            .into_iter()                            .map(|path| {                                project.update(cx, |project, cx| {                                    project.find_or_create_worktree(&path, true, cx)                                })                            })                            .collect::<Vec<_>>();                        cx.spawn(|_| async move {                            for task in tasks {                                task.await?;                            }                            Ok(())                        })                        .detach_and_prompt_err(                            "Failed to open path",                            cx,                            |_, _| None,                        );                         cx.new_view(|cx| {                            let workspace =                                Workspace::new(None, project.clone(), app_state.clone(), cx);                             workspace                                .client()                                .telemetry()                                .report_app_event("create ssh project".to_string());                             workspace                        })                    })                    .log_err();                    this.update(&mut cx, |_, cx| {                        cx.emit(DismissEvent);                    })                    .ok();                    Some(())                }            })            .shared();        cx.new_view(|_| Self {            _path_task,            picker,            connection_string,            nickname,        })    }} impl gpui::Render for ProjectPicker {    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {        v_flex()            .child(                SshConnectionHeader {                    connection_string: self.connection_string.clone(),                    paths: Default::default(),                    nickname: self.nickname.clone(),                }                .render(cx),            )            .child(                div()                    .border_t_1()                    .border_color(cx.theme().colors().border_variant)                    .child(self.picker.clone()),            )    }} #[derive(Clone)]struct ProjectEntry {    open_folder: NavigableEntry,    projects: Vec<(NavigableEntry, SshProject)>,    configure: NavigableEntry,    connection: SshConnection,} #[derive(Clone)]struct DefaultState {    scrollbar: ScrollbarState,    add_new_server: NavigableEntry,    servers: Vec<ProjectEntry>,}impl DefaultState {    fn new(cx: &WindowContext) -> Self {        let handle = ScrollHandle::new();        let scrollbar = ScrollbarState::new(handle.clone());        let add_new_server = NavigableEntry::new(&handle, cx);        let servers = SshSettings::get_global(cx)            .ssh_connections()            .map(|connection| {                let open_folder = NavigableEntry::new(&handle, cx);                let configure = NavigableEntry::new(&handle, cx);                let projects = connection                    .projects                    .iter()                    .map(|project| (NavigableEntry::new(&handle, cx), project.clone()))                    .collect();                ProjectEntry {                    open_folder,                    configure,                    projects,                    connection,                }            })            .collect();        Self {            scrollbar,            add_new_server,            servers,        }    }} #[derive(Clone)]struct ViewServerOptionsState {    server_index: usize,    connection: SshConnection,    entries: [NavigableEntry; 4],}enum Mode {    Default(DefaultState),    ViewServerOptions(ViewServerOptionsState),    EditNickname(EditNicknameState),    ProjectPicker(View<ProjectPicker>),    CreateRemoteServer(CreateRemoteServer),} impl Mode {    fn default_mode(cx: &WindowContext) -> Self {        Self::Default(DefaultState::new(cx))    }}impl RemoteServerProjects {    pub fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {        workspace.register_action(|workspace, _: &OpenRemote, cx| {            let handle = cx.view().downgrade();            workspace.toggle_modal(cx, |cx| Self::new(cx, handle))        });    }     pub fn open(workspace: View<Workspace>, cx: &mut WindowContext) {        workspace.update(cx, |workspace, cx| {            let handle = cx.view().downgrade();            workspace.toggle_modal(cx, |cx| Self::new(cx, handle))        })    }     pub fn new(cx: &mut ViewContext<Self>, workspace: WeakView<Workspace>) -> Self {        let focus_handle = cx.focus_handle();         let mut base_style = cx.text_style();        base_style.refine(&gpui::TextStyleRefinement {            color: Some(cx.theme().colors().editor_foreground),            ..Default::default()        });         Self {            mode: Mode::default_mode(cx),            focus_handle,            workspace,            retained_connections: Vec::new(),        }    }     pub fn project_picker(        ix: usize,        connection_options: remote::SshConnectionOptions,        project: Model<Project>,        cx: &mut ViewContext<Self>,        workspace: WeakView<Workspace>,    ) -> Self {        let mut this = Self::new(cx, workspace.clone());        this.mode = Mode::ProjectPicker(ProjectPicker::new(            ix,            connection_options,            project,            workspace,            cx,        ));        cx.notify();         this    }     fn create_ssh_server(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {        let input = get_text(&editor, cx);        if input.is_empty() {            return;        }         let connection_options = match SshConnectionOptions::parse_command_line(&input) {            Ok(c) => c,            Err(e) => {                self.mode = Mode::CreateRemoteServer(CreateRemoteServer {                    address_editor: editor,                    address_error: Some(format!("could not parse: {:?}", e).into()),                    ssh_prompt: None,                    _creating: None,                });                return;            }        };        let ssh_prompt = cx.new_view(|cx| SshPrompt::new(&connection_options, cx));         let connection = connect_over_ssh(            ConnectionIdentifier::setup(),            connection_options.clone(),            ssh_prompt.clone(),            cx,        )        .prompt_err("Failed to connect", cx, |_, _| None);         let address_editor = editor.clone();        let creating = cx.spawn(move |this, mut cx| async move {            match connection.await {                Some(Some(client)) => this                    .update(&mut cx, |this, cx| {                        let _ = this.workspace.update(cx, |workspace, _| {                            workspace                                .client()                                .telemetry()                                .report_app_event("create ssh server".to_string())                        });                        this.retained_connections.push(client);                        this.add_ssh_server(connection_options, cx);                        this.mode = Mode::default_mode(cx);                        cx.notify()                    })                    .log_err(),                _ => this                    .update(&mut cx, |this, cx| {                        address_editor.update(cx, |this, _| {                            this.set_read_only(false);                        });                        this.mode = Mode::CreateRemoteServer(CreateRemoteServer {                            address_editor,                            address_error: None,                            ssh_prompt: None,                            _creating: None,                        });                        cx.notify()                    })                    .log_err(),            };            None        });         editor.update(cx, |this, _| {            this.set_read_only(true);        });        self.mode = Mode::CreateRemoteServer(CreateRemoteServer {            address_editor: editor,            address_error: None,            ssh_prompt: Some(ssh_prompt.clone()),            _creating: Some(creating),        });    }     fn view_server_options(        &mut self,        (server_index, connection): (usize, SshConnection),        cx: &mut ViewContext<Self>,    ) {        self.mode = Mode::ViewServerOptions(ViewServerOptionsState {            server_index,            connection,            entries: std::array::from_fn(|_| NavigableEntry::focusable(cx)),        });        self.focus_handle(cx).focus(cx);        cx.notify();    }     fn create_ssh_project(        &mut self,        ix: usize,        ssh_connection: SshConnection,        cx: &mut ViewContext<Self>,    ) {        let Some(workspace) = self.workspace.upgrade() else {            return;        };         let connection_options = ssh_connection.into();        workspace.update(cx, |_, cx| {            cx.defer(move |workspace, cx| {                workspace.toggle_modal(cx, |cx| {                    SshConnectionModal::new(&connection_options, Vec::new(), cx)                });                let prompt = workspace                    .active_modal::<SshConnectionModal>(cx)                    .unwrap()                    .read(cx)                    .prompt                    .clone();                 let connect = connect_over_ssh(                    ConnectionIdentifier::setup(),                    connection_options.clone(),                    prompt,                    cx,                )                .prompt_err("Failed to connect", cx, |_, _| None);                 cx.spawn(move |workspace, mut cx| async move {                    let session = connect.await;                     workspace                        .update(&mut cx, |workspace, cx| {                            if let Some(prompt) = workspace.active_modal::<SshConnectionModal>(cx) {                                prompt.update(cx, |prompt, cx| prompt.finished(cx))                            }                        })                        .ok();                     let Some(Some(session)) = session else {                        workspace                            .update(&mut cx, |workspace, cx| {

Lines 1–500 of 1394.