use std::time::Duration; use dev_server_projects::{DevServer, DevServerId, DevServerProject, DevServerProjectId};use editor::Editor;use feature_flags::FeatureFlagAppExt;use feature_flags::FeatureFlagViewExt;use gpui::Subscription;use gpui::{ percentage, Action, Animation, AnimationExt, AnyElement, AppContext, ClipboardItem, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, ScrollHandle, Transformation, View, ViewContext,};use markdown::Markdown;use markdown::MarkdownStyle;use rpc::{ proto::{CreateDevServerResponse, DevServerStatus, RegenerateDevServerTokenResponse}, ErrorCode, ErrorExt,};use ui::CheckboxWithLabel;use ui::{prelude::*, Indicator, List, ListHeader, ListItem, ModalContent, ModalHeader, Tooltip};use ui_text_field::{FieldLabelLayout, TextField};use util::ResultExt;use workspace::{notifications::DetachAndPromptErr, AppState, ModalView, Workspace, WORKSPACE_DB}; use crate::OpenRemote; pub struct DevServerProjects { mode: Mode, focus_handle: FocusHandle, scroll_handle: ScrollHandle, dev_server_store: Model<dev_server_projects::Store>, project_path_input: View<Editor>, dev_server_name_input: View<TextField>, use_server_name_in_ssh: Selection, rename_dev_server_input: View<TextField>, markdown: View<Markdown>, _dev_server_subscription: Subscription,} #[derive(Default, Clone)]struct CreateDevServer { creating: bool, dev_server: Option<CreateDevServerResponse>, // ssh_connection_string: Option<String>,} #[derive(Clone)]struct EditDevServer { dev_server_id: DevServerId, state: EditDevServerState,} #[derive(Clone, PartialEq)]enum EditDevServerState { Default, RenamingDevServer, RegeneratingToken, RegeneratedToken(RegenerateDevServerTokenResponse),} struct CreateDevServerProject { dev_server_id: DevServerId, creating: bool, _opening: Option<Subscription>,} enum Mode { Default(Option<CreateDevServerProject>), CreateDevServer(CreateDevServer), EditDevServer(EditDevServer),} 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| { workspace.toggle_modal(cx, |cx| Self::new(cx)) }); } pub fn open(workspace: View<Workspace>, cx: &mut WindowContext) { workspace.update(cx, |workspace, cx| { workspace.toggle_modal(cx, |cx| Self::new(cx)) }) } pub fn new(cx: &mut ViewContext<Self>) -> 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", "").with_label(FieldLabelLayout::Stacked)); let rename_dev_server_input = cx.new_view(|cx| TextField::new(cx, "Name", "").with_label(FieldLabelLayout::Stacked)); 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 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: 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, rename_dev_server_input, markdown, use_server_name_in_ssh: Selection::Unselected, _dev_server_subscription: subscription, } } pub fn create_dev_server_project( &mut self, dev_server_id: DevServerId, cx: &mut ViewContext<Self>, ) { let path = self.project_path_input.read(cx).text(cx).trim().to_string(); if path == "" { return; } 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( 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_dev_server(&mut self, cx: &mut ViewContext<Self>) { let name = get_text(&self.dev_server_name_input, cx); if name.is_empty() { return; } let ssh_connection_string = if self.use_server_name_in_ssh == Selection::Selected { Some(name.clone()) } else { None }; let dev_server = self.dev_server_store.update(cx, |store, cx| { store.create_dev_server(name, ssh_connection_string, cx) }); cx.spawn(|this, mut cx| async move { let result = dev_server.await; this.update(&mut cx, |this, cx| match &result { Ok(dev_server) => { this.focus_handle.focus(cx); this.mode = Mode::CreateDevServer(CreateDevServer { creating: false, dev_server: Some(dev_server.clone()), }); } Err(_) => { this.mode = Mode::CreateDevServer(Default::default()); } }) .log_err(); result }) .detach_and_prompt_err("Failed to create server", cx, |_, _| None); self.mode = Mode::CreateDevServer(CreateDevServer { creating: true, dev_server: None, }); cx.notify() } fn rename_dev_server(&mut self, id: DevServerId, cx: &mut ViewContext<Self>) { let name = get_text(&self.rename_dev_server_input, cx); let Some(dev_server) = self.dev_server_store.read(cx).dev_server(id) else { return; }; if name.is_empty() || dev_server.name == name { return; } let request = self .dev_server_store .update(cx, |store, cx| store.rename_dev_server(id, name, cx)); self.mode = Mode::EditDevServer(EditDevServer { dev_server_id: id, state: EditDevServerState::RenamingDevServer, }); cx.spawn(|this, mut cx| async move { request.await?; this.update(&mut cx, move |this, cx| { this.mode = Mode::EditDevServer(EditDevServer { dev_server_id: id, state: EditDevServerState::Default, }); cx.notify(); }) }) .detach_and_prompt_err("Failed to rename dev server", cx, |_, _| None); } fn refresh_dev_server_token(&mut self, id: DevServerId, cx: &mut ViewContext<Self>) { let answer = cx.prompt( gpui::PromptLevel::Warning, "Are you sure?", Some("This will invalidate the existing dev server token."), &["Generate", "Cancel"], ); cx.spawn(|this, mut cx| async move { let answer = answer.await?; if answer != 0 { return Ok(()); } let response = this .update(&mut cx, move |this, cx| { let request = this .dev_server_store .update(cx, |store, cx| store.regenerate_dev_server_token(id, cx)); this.mode = Mode::EditDevServer(EditDevServer { dev_server_id: id, state: EditDevServerState::RegeneratingToken, }); cx.notify(); request })? .await?; this.update(&mut cx, move |this, cx| { this.mode = Mode::EditDevServer(EditDevServer { dev_server_id: id, state: EditDevServerState::RegeneratedToken(response), }); cx.notify(); }) .log_err(); Ok(()) }) .detach_and_prompt_err("Failed to delete dev server", cx, |_, _| None); } fn delete_dev_server(&mut self, id: DevServerId, cx: &mut ViewContext<Self>) { let answer = 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 { let answer = answer.await?; if answer != 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 && state.dev_server.is_none() { self.create_dev_server(cx); } } Mode::EditDevServer(edit_dev_server) => { if self .rename_dev_server_input .read(cx) .editor() .read(cx) .is_focused(cx) { self.rename_dev_server(edit_dev_server.dev_server_id, cx); } } } } fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) { match self.mode { Mode::Default(None) => cx.emit(DismissEvent), _ => { self.mode = Mode::Default(None); self.focus_handle(cx).focus(cx); cx.notify(); } } } fn render_dev_server( &mut self, dev_server: &DevServer, create_project: Option<bool>, cx: &mut ViewContext<Self>, ) -> impl IntoElement { let dev_server_id = dev_server.id; let status = dev_server.status; let dev_server_name = dev_server.name.clone(); v_flex() .w_full()Lines 1–500 of 1097. Load more
use std::time::Duration; use dev_server_projects::{DevServer, DevServerId, DevServerProject, DevServerProjectId}; use editor::Editor; use feature_flags::FeatureFlagAppExt; use feature_flags::FeatureFlagViewExt; use gpui::Subscription; use gpui::{ percentage, Action, Animation, AnimationExt, AnyElement, AppContext, ClipboardItem, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, ScrollHandle, Transformation, View, ViewContext, }; use markdown::Markdown; use markdown::MarkdownStyle; use rpc::{ proto::{CreateDevServerResponse, DevServerStatus, RegenerateDevServerTokenResponse}, ErrorCode, ErrorExt, }; use ui::CheckboxWithLabel; use ui::{prelude::*, Indicator, List, ListHeader, ListItem, ModalContent, ModalHeader, Tooltip}; use ui_text_field::{FieldLabelLayout, TextField}; use util::ResultExt; use workspace::{notifications::DetachAndPromptErr, AppState, ModalView, Workspace, WORKSPACE_DB}; use crate::OpenRemote; pub struct DevServerProjects { mode: Mode, focus_handle: FocusHandle, scroll_handle: ScrollHandle, dev_server_store: Model<dev_server_projects::Store>, project_path_input: View<Editor>, dev_server_name_input: View<TextField>, use_server_name_in_ssh: Selection, rename_dev_server_input: View<TextField>, markdown: View<Markdown>, _dev_server_subscription: Subscription, } #[derive(Default, Clone)] struct CreateDevServer { creating: bool, dev_server: Option<CreateDevServerResponse>, // ssh_connection_string: Option<String>, } #[derive(Clone)] struct EditDevServer { dev_server_id: DevServerId, state: EditDevServerState, } #[derive(Clone, PartialEq)] enum EditDevServerState { Default, RenamingDevServer, RegeneratingToken, RegeneratedToken(RegenerateDevServerTokenResponse), } struct CreateDevServerProject { dev_server_id: DevServerId, creating: bool, _opening: Option<Subscription>, } enum Mode { Default(Option<CreateDevServerProject>), CreateDevServer(CreateDevServer), EditDevServer(EditDevServer), } 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| { workspace.toggle_modal(cx, |cx| Self::new(cx)) }); } pub fn open(workspace: View<Workspace>, cx: &mut WindowContext) { workspace.update(cx, |workspace, cx| { workspace.toggle_modal(cx, |cx| Self::new(cx)) }) } pub fn new(cx: &mut ViewContext<Self>) -> 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", "").with_label(FieldLabelLayout::Stacked)); let rename_dev_server_input = cx.new_view(|cx| TextField::new(cx, "Name", "").with_label(FieldLabelLayout::Stacked)); 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 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: 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, rename_dev_server_input, markdown, use_server_name_in_ssh: Selection::Unselected, _dev_server_subscription: subscription, } } pub fn create_dev_server_project( &mut self, dev_server_id: DevServerId, cx: &mut ViewContext<Self>, ) { let path = self.project_path_input.read(cx).text(cx).trim().to_string(); if path == "" { return; } 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( 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_dev_server(&mut self, cx: &mut ViewContext<Self>) { let name = get_text(&self.dev_server_name_input, cx); if name.is_empty() { return; } let ssh_connection_string = if self.use_server_name_in_ssh == Selection::Selected { Some(name.clone()) } else { None }; let dev_server = self.dev_server_store.update(cx, |store, cx| { store.create_dev_server(name, ssh_connection_string, cx) }); cx.spawn(|this, mut cx| async move { let result = dev_server.await; this.update(&mut cx, |this, cx| match &result { Ok(dev_server) => { this.focus_handle.focus(cx); this.mode = Mode::CreateDevServer(CreateDevServer { creating: false, dev_server: Some(dev_server.clone()), }); } Err(_) => { this.mode = Mode::CreateDevServer(Default::default()); } }) .log_err(); result }) .detach_and_prompt_err("Failed to create server", cx, |_, _| None); self.mode = Mode::CreateDevServer(CreateDevServer { creating: true, dev_server: None, }); cx.notify() } fn rename_dev_server(&mut self, id: DevServerId, cx: &mut ViewContext<Self>) { let name = get_text(&self.rename_dev_server_input, cx); let Some(dev_server) = self.dev_server_store.read(cx).dev_server(id) else { return; }; if name.is_empty() || dev_server.name == name { return; } let request = self .dev_server_store .update(cx, |store, cx| store.rename_dev_server(id, name, cx)); self.mode = Mode::EditDevServer(EditDevServer { dev_server_id: id, state: EditDevServerState::RenamingDevServer, }); cx.spawn(|this, mut cx| async move { request.await?; this.update(&mut cx, move |this, cx| { this.mode = Mode::EditDevServer(EditDevServer { dev_server_id: id, state: EditDevServerState::Default, }); cx.notify(); }) }) .detach_and_prompt_err("Failed to rename dev server", cx, |_, _| None); } fn refresh_dev_server_token(&mut self, id: DevServerId, cx: &mut ViewContext<Self>) { let answer = cx.prompt( gpui::PromptLevel::Warning, "Are you sure?", Some("This will invalidate the existing dev server token."), &["Generate", "Cancel"], ); cx.spawn(|this, mut cx| async move { let answer = answer.await?; if answer != 0 { return Ok(()); } let response = this .update(&mut cx, move |this, cx| { let request = this .dev_server_store .update(cx, |store, cx| store.regenerate_dev_server_token(id, cx)); this.mode = Mode::EditDevServer(EditDevServer { dev_server_id: id, state: EditDevServerState::RegeneratingToken, }); cx.notify(); request })? .await?; this.update(&mut cx, move |this, cx| { this.mode = Mode::EditDevServer(EditDevServer { dev_server_id: id, state: EditDevServerState::RegeneratedToken(response), }); cx.notify(); }) .log_err(); Ok(()) }) .detach_and_prompt_err("Failed to delete dev server", cx, |_, _| None); } fn delete_dev_server(&mut self, id: DevServerId, cx: &mut ViewContext<Self>) { let answer = 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 { let answer = answer.await?; if answer != 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 && state.dev_server.is_none() { self.create_dev_server(cx); } } Mode::EditDevServer(edit_dev_server) => { if self .rename_dev_server_input .read(cx) .editor() .read(cx) .is_focused(cx) { self.rename_dev_server(edit_dev_server.dev_server_id, cx); } } } } fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) { match self.mode { Mode::Default(None) => cx.emit(DismissEvent), _ => { self.mode = Mode::Default(None); self.focus_handle(cx).focus(cx); cx.notify(); } } } fn render_dev_server( &mut self, dev_server: &DevServer, create_project: Option<bool>, cx: &mut ViewContext<Self>, ) -> impl IntoElement { let dev_server_id = dev_server.id; let status = dev_server.status; let dev_server_name = dev_server.name.clone(); v_flex() .w_full()