1207 lines · 48.2 KB · Rust
RawDownload
use std::time::Duration; use anyhow::anyhow;use anyhow::Context;use dev_server_projects::{DevServer, DevServerId, DevServerProject, DevServerProjectId};use editor::Editor;use feature_flags::FeatureFlagAppExt;use feature_flags::FeatureFlagViewExt;use gpui::AsyncWindowContext;use gpui::Subscription;use gpui::Task;use gpui::WeakView;use gpui::{    percentage, Animation, AnimationExt, AnyElement, AppContext, DismissEvent, EventEmitter,    FocusHandle, FocusableView, Model, ScrollHandle, Transformation, View, ViewContext,};use markdown::Markdown;use markdown::MarkdownStyle;use rpc::proto::RegenerateDevServerTokenResponse;use rpc::{    proto::{CreateDevServerResponse, DevServerStatus},    ErrorCode, ErrorExt,};use task::RevealStrategy;use task::SpawnInTerminal;use task::TerminalWorkDir;use terminal_view::terminal_panel::TerminalPanel;use ui::ElevationIndex;use ui::Section;use ui::{    prelude::*, Indicator, List, ListHeader, ListItem, Modal, ModalFooter, ModalHeader,    RadioWithLabel, Tooltip,};use ui_input::{FieldLabelLayout, TextField};use util::ResultExt;use workspace::{notifications::DetachAndPromptErr, AppState, ModalView, Workspace, WORKSPACE_DB}; use crate::open_dev_server_project;use crate::OpenRemote; pub struct DevServerProjects {    mode: Mode,    focus_handle: FocusHandle,    scroll_handle: ScrollHandle,    dev_server_store: Model<dev_server_projects::Store>,    workspace: WeakView<Workspace>,    project_path_input: View<Editor>,    dev_server_name_input: View<TextField>,    markdown: View<Markdown>,    _dev_server_subscription: Subscription,} #[derive(Default)]struct CreateDevServer {    creating: Option<Task<()>>,    dev_server_id: Option<DevServerId>,    access_token: Option<String>,    manual_setup: bool,} struct CreateDevServerProject {    dev_server_id: DevServerId,    creating: bool,    _opening: Option<Subscription>,} enum Mode {    Default(Option<CreateDevServerProject>),    CreateDevServer(CreateDevServer),} impl DevServerProjects {    pub fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {        cx.observe_flag::<feature_flags::Remoting, _>(|enabled, workspace, _| {            if enabled {                Self::register_open_remote_action(workspace);            }        })        .detach();         if cx.has_flag::<feature_flags::Remoting>() {            Self::register_open_remote_action(workspace);        }    }     fn register_open_remote_action(workspace: &mut 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 project_path_input = cx.new_view(|cx| {            let mut editor = Editor::single_line(cx);            editor.set_placeholder_text("Project path (~/work/zed, /workspace/zed, …)", cx);            editor        });        let dev_server_name_input = cx.new_view(|cx| {            TextField::new(cx, "Name", "192.168.0.1").with_label(FieldLabelLayout::Hidden)        });         let focus_handle = cx.focus_handle();        let dev_server_store = dev_server_projects::Store::global(cx);         let subscription = cx.observe(&dev_server_store, |_, _, cx| {            cx.notify();        });         let markdown_style = MarkdownStyle {            code_block: gpui::TextStyleRefinement {                font_family: Some("Zed Plex Mono".into()),                color: Some(cx.theme().colors().editor_foreground),                background_color: Some(cx.theme().colors().editor_background),                ..Default::default()            },            inline_code: Default::default(),            block_quote: Default::default(),            link: gpui::TextStyleRefinement {                color: Some(Color::Accent.color(cx)),                ..Default::default()            },            rule_color: Default::default(),            block_quote_border_color: Default::default(),            syntax: cx.theme().syntax().clone(),            selection_background_color: cx.theme().players().local().selection,        };        let markdown = cx.new_view(|cx| Markdown::new("".to_string(), markdown_style, None, cx));         Self {            mode: Mode::Default(None),            focus_handle,            scroll_handle: ScrollHandle::new(),            dev_server_store,            project_path_input,            dev_server_name_input,            markdown,            workspace,            _dev_server_subscription: subscription,        }    }     pub fn create_dev_server_project(        &mut self,        dev_server_id: DevServerId,        cx: &mut ViewContext<Self>,    ) {        let mut path = self.project_path_input.read(cx).text(cx).trim().to_string();         if path == "" {            return;        }         if !path.starts_with('/') && !path.starts_with('~') {            path = format!("~/{}", path);        }         if self            .dev_server_store            .read(cx)            .projects_for_server(dev_server_id)            .iter()            .any(|p| p.path == path)        {            cx.spawn(|_, mut cx| async move {                cx.prompt(                    gpui::PromptLevel::Critical,                    "Failed to create project",                    Some(&format!(                        "Project {} already exists for this dev server.",                        path                    )),                    &["Ok"],                )                .await            })            .detach_and_log_err(cx);            return;        }         let create = {            let path = path.clone();            self.dev_server_store.update(cx, |store, cx| {                store.create_dev_server_project(dev_server_id, path, cx)            })        };         cx.spawn(|this, mut cx| async move {            let result = create.await;            this.update(&mut cx, |this, cx| {                if let Ok(result) = &result {                    if let Some(dev_server_project_id) =                        result.dev_server_project.as_ref().map(|p| p.id)                    {                        let subscription =                            cx.observe(&this.dev_server_store, move |this, store, cx| {                                if let Some(project_id) = store                                    .read(cx)                                    .dev_server_project(DevServerProjectId(dev_server_project_id))                                    .and_then(|p| p.project_id)                                {                                    this.project_path_input.update(cx, |editor, cx| {                                        editor.set_text("", cx);                                    });                                    this.mode = Mode::Default(None);                                    if let Some(app_state) = AppState::global(cx).upgrade() {                                        workspace::join_dev_server_project(                                            DevServerProjectId(dev_server_project_id),                                            project_id,                                            app_state,                                            None,                                            cx,                                        )                                        .detach_and_prompt_err(                                            "Could not join project",                                            cx,                                            |_, _| None,                                        )                                    }                                }                            });                         this.mode = Mode::Default(Some(CreateDevServerProject {                            dev_server_id,                            creating: true,                            _opening: Some(subscription),                        }));                    }                } else {                    this.mode = Mode::Default(Some(CreateDevServerProject {                        dev_server_id,                        creating: false,                        _opening: None,                    }));                }            })            .log_err();            result        })        .detach_and_prompt_err("Failed to create project", cx, move |e, _| {            match e.error_code() {                ErrorCode::DevServerOffline => Some(                    "The dev server is offline. Please log in and check it is connected."                        .to_string(),                ),                ErrorCode::DevServerProjectPathDoesNotExist => {                    Some(format!("The path `{}` does not exist on the server.", path))                }                _ => None,            }        });         self.mode = Mode::Default(Some(CreateDevServerProject {            dev_server_id,            creating: true,            _opening: None,        }));    }     pub fn create_or_update_dev_server(        &mut self,        manual_setup: bool,        existing_id: Option<DevServerId>,        access_token: Option<String>,        cx: &mut ViewContext<Self>,    ) {        let name = get_text(&self.dev_server_name_input, cx);        if name.is_empty() {            return;        }         let ssh_connection_string = if manual_setup {            None        } else if name.contains(' ') {            Some(name.clone())        } else {            Some(format!("ssh {}", name))        };         let dev_server = self.dev_server_store.update(cx, {            let access_token = access_token.clone();            |store, cx| {                let ssh_connection_string = ssh_connection_string.clone();                if let Some(dev_server_id) = existing_id {                    let rename = store.rename_dev_server(                        dev_server_id,                        name.clone(),                        ssh_connection_string,                        cx,                    );                    let token = if let Some(access_token) = access_token {                        Task::ready(Ok(RegenerateDevServerTokenResponse {                            dev_server_id: dev_server_id.0,                            access_token,                        }))                    } else {                        store.regenerate_dev_server_token(dev_server_id, cx)                    };                    cx.spawn(|_, _| async move {                        rename.await?;                        let response = token.await?;                        Ok(CreateDevServerResponse {                            dev_server_id: dev_server_id.0,                            name,                            access_token: response.access_token,                        })                    })                } else {                    store.create_dev_server(name, ssh_connection_string.clone(), cx)                }            }        });         let workspace = self.workspace.clone();        let store = dev_server_projects::Store::global(cx);         let task = cx            .spawn({                |this, mut cx| async move {                    let result = dev_server.await;                     match result {                        Ok(dev_server) => {                            if let Some(ssh_connection_string) = ssh_connection_string {                                this.update(&mut cx, |this, cx| {                                    if let Mode::CreateDevServer(CreateDevServer {                                        access_token,                                        dev_server_id,                                        ..                                    }) = &mut this.mode                                    {                                        access_token.replace(dev_server.access_token.clone());                                        dev_server_id                                            .replace(DevServerId(dev_server.dev_server_id));                                    }                                    cx.notify();                                })?;                                 spawn_ssh_task(                                    workspace                                        .upgrade()                                        .ok_or_else(|| anyhow!("workspace dropped"))?,                                    store,                                    DevServerId(dev_server.dev_server_id),                                    ssh_connection_string,                                    dev_server.access_token.clone(),                                    &mut cx,                                )                                .await                                .log_err();                            }                             this.update(&mut cx, |this, cx| {                                this.focus_handle.focus(cx);                                this.mode = Mode::CreateDevServer(CreateDevServer {                                    creating: None,                                    dev_server_id: Some(DevServerId(dev_server.dev_server_id)),                                    access_token: Some(dev_server.access_token),                                    manual_setup,                                });                                cx.notify();                            })?;                            Ok(())                        }                        Err(e) => {                            this.update(&mut cx, |this, cx| {                                this.mode = Mode::CreateDevServer(CreateDevServer {                                    creating: None,                                    dev_server_id: existing_id,                                    access_token: None,                                    manual_setup,                                });                                cx.notify()                            })                            .log_err();                             return Err(e);                        }                    }                }            })            .prompt_err("Failed to create server", cx, |_, _| None);         self.mode = Mode::CreateDevServer(CreateDevServer {            creating: Some(task),            dev_server_id: existing_id,            access_token,            manual_setup,        });        cx.notify()    }     fn delete_dev_server(&mut self, id: DevServerId, cx: &mut ViewContext<Self>) {        let store = self.dev_server_store.read(cx);        let prompt = if store.projects_for_server(id).is_empty()            && store                .dev_server(id)                .is_some_and(|server| server.status == DevServerStatus::Offline)        {            None        } else {            Some(cx.prompt(                gpui::PromptLevel::Warning,                "Are you sure?",                Some("This will delete the dev server and all of its remote projects."),                &["Delete", "Cancel"],            ))        };         cx.spawn(|this, mut cx| async move {            if let Some(prompt) = prompt {                if prompt.await? != 0 {                    return Ok(());                }            }             let project_ids: Vec<DevServerProjectId> = this.update(&mut cx, |this, cx| {                this.dev_server_store.update(cx, |store, _| {                    store                        .projects_for_server(id)                        .into_iter()                        .map(|project| project.id)                        .collect()                })            })?;             this.update(&mut cx, |this, cx| {                this.dev_server_store                    .update(cx, |store, cx| store.delete_dev_server(id, cx))            })?            .await?;             for id in project_ids {                WORKSPACE_DB                    .delete_workspace_by_dev_server_project_id(id)                    .await                    .log_err();            }            Ok(())        })        .detach_and_prompt_err("Failed to delete dev server", cx, |_, _| None);    }     fn delete_dev_server_project(        &mut self,        id: DevServerProjectId,        path: &str,        cx: &mut ViewContext<Self>,    ) {        let answer = cx.prompt(            gpui::PromptLevel::Warning,            format!("Delete \"{}\"?", path).as_str(),            Some("This will delete the remote project. You can always re-add it later."),            &["Delete", "Cancel"],        );         cx.spawn(|this, mut cx| async move {            let answer = answer.await?;             if answer != 0 {                return Ok(());            }             this.update(&mut cx, |this, cx| {                this.dev_server_store                    .update(cx, |store, cx| store.delete_dev_server_project(id, cx))            })?            .await?;             WORKSPACE_DB                .delete_workspace_by_dev_server_project_id(id)                .await                .log_err();             Ok(())        })        .detach_and_prompt_err("Failed to delete dev server project", cx, |_, _| None);    }     fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {        match &self.mode {            Mode::Default(None) => {}            Mode::Default(Some(create_project)) => {                self.create_dev_server_project(create_project.dev_server_id, cx);            }            Mode::CreateDevServer(state) => {                if state.creating.is_none() || state.dev_server_id.is_some() {                    self.create_or_update_dev_server(                        state.manual_setup,                        state.dev_server_id,                        state.access_token.clone(),                        cx,                    );

Lines 1–500 of 1207.