From d0bc84eb33627620dee3bd5b784ffc2774a2287a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 23 Oct 2024 00:14:43 -0700 Subject: [PATCH] Fix remoting things (#19587) - Fixes modal closing when using the remote modal folder - Fixes a bug with local terminals where they could open in / instead of ~ - Fixes a bug where SSH connections would continue running after their window is closed - Hides SSH Terminal process details from Zed UI - Implement `cmd-o` for remote projects - Implement LanguageServerPromptRequest for remote LSPs Release Notes: - N/A --- crates/project/src/project.rs | 61 +++++++++- crates/project/src/terminals.rs | 49 ++++++--- crates/project_panel/src/project_panel.rs | 2 +- crates/proto/proto/zed.proto | 25 +++++ crates/proto/src/proto.rs | 4 + crates/recent_projects/src/remote_servers.rs | 7 +- crates/recent_projects/src/ssh_connections.rs | 22 ++-- crates/remote_server/src/headless_project.rs | 43 +++++++- crates/task/src/lib.rs | 2 + crates/terminal/src/terminal.rs | 104 ++++++++++-------- crates/terminal_view/src/terminal_panel.rs | 2 +- crates/workspace/src/workspace.rs | 32 ------ crates/zed/src/zed.rs | 76 ++++++++++++- 13 files changed, 319 insertions(+), 110 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 93452d3e8d0087bf59ad8af10ac09431469b7951..4a3eaf98ba7ba9848291d48d8cae580ee33e3765 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -22,7 +22,7 @@ pub use environment::EnvironmentErrorMessage; pub mod search_history; mod yarn; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context as _, Result}; use buffer_store::{BufferStore, BufferStoreEvent}; use client::{ proto, Client, Collaborator, DevServerProjectId, PendingEntitySubscription, ProjectId, @@ -40,8 +40,8 @@ use futures::{ use git::{blame::Blame, repository::GitRepository}; use gpui::{ - AnyModel, AppContext, AsyncAppContext, BorrowAppContext, Context, EventEmitter, Hsla, Model, - ModelContext, SharedString, Task, WeakModel, WindowContext, + AnyModel, AppContext, AsyncAppContext, BorrowAppContext, Context as _, EventEmitter, Hsla, + Model, ModelContext, SharedString, Task, WeakModel, WindowContext, }; use itertools::Itertools; use language::{ @@ -52,6 +52,7 @@ use language::{ }; use lsp::{ CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServer, LanguageServerId, + MessageActionItem, }; use lsp_command::*; use node_runtime::NodeRuntime; @@ -59,7 +60,10 @@ use parking_lot::{Mutex, RwLock}; pub use prettier_store::PrettierStore; use project_settings::{ProjectSettings, SettingsObserver, SettingsObserverEvent}; use remote::{SshConnectionOptions, SshRemoteClient}; -use rpc::{proto::SSH_PROJECT_ID, AnyProtoClient, ErrorCode}; +use rpc::{ + proto::{LanguageServerPromptResponse, SSH_PROJECT_ID}, + AnyProtoClient, ErrorCode, +}; use search::{SearchInputKind, SearchQuery, SearchResult}; use search_history::SearchHistory; use settings::{InvalidSettingsError, Settings, SettingsLocation, SettingsStore}; @@ -810,6 +814,7 @@ impl Project { ssh_proto.add_model_message_handler(Self::handle_update_worktree); ssh_proto.add_model_message_handler(Self::handle_update_project); ssh_proto.add_model_message_handler(Self::handle_toast); + ssh_proto.add_model_request_handler(Self::handle_language_server_prompt_request); ssh_proto.add_model_message_handler(Self::handle_hide_toast); ssh_proto.add_model_request_handler(BufferStore::handle_update_buffer); BufferStore::init(&ssh_proto); @@ -1180,6 +1185,7 @@ impl Project { cx: &mut gpui::TestAppContext, ) -> Model { use clock::FakeSystemClock; + use gpui::Context; let languages = LanguageRegistry::test(cx.executor()); let clock = Arc::new(FakeSystemClock::default()); @@ -3622,6 +3628,45 @@ impl Project { })? } + async fn handle_language_server_prompt_request( + this: Model, + envelope: TypedEnvelope, + mut cx: AsyncAppContext, + ) -> Result { + let (tx, mut rx) = smol::channel::bounded(1); + let actions: Vec<_> = envelope + .payload + .actions + .into_iter() + .map(|action| MessageActionItem { + title: action, + properties: Default::default(), + }) + .collect(); + this.update(&mut cx, |_, cx| { + cx.emit(Event::LanguageServerPrompt(LanguageServerPromptRequest { + level: proto_to_prompt(envelope.payload.level.context("Invalid prompt level")?), + message: envelope.payload.message, + actions: actions.clone(), + lsp_name: envelope.payload.lsp_name, + response_channel: tx, + })); + + anyhow::Ok(()) + })??; + + let answer = rx.next().await; + + Ok(LanguageServerPromptResponse { + action_response: answer.and_then(|answer| { + actions + .iter() + .position(|action| *action == answer) + .map(|index| index as u64) + }), + }) + } + async fn handle_hide_toast( this: Model, envelope: TypedEnvelope, @@ -4257,3 +4302,11 @@ pub fn sort_worktree_entries(entries: &mut [Entry]) { ) }); } + +fn proto_to_prompt(level: proto::language_server_prompt_request::Level) -> gpui::PromptLevel { + match level { + proto::language_server_prompt_request::Level::Info(_) => gpui::PromptLevel::Info, + proto::language_server_prompt_request::Level::Warning(_) => gpui::PromptLevel::Warning, + proto::language_server_prompt_request::Level::Critical(_) => gpui::PromptLevel::Critical, + } +} diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index 37b29c2a1d951b8defad84fe8df2b03015f6f745..3d1821ce666e0dce062446ab78182e22cd115b4e 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -67,13 +67,15 @@ impl Project { } } - fn ssh_command(&self, cx: &AppContext) -> Option { - if let Some(args) = self - .ssh_client - .as_ref() - .and_then(|session| session.read(cx).ssh_args()) - { - return Some(SshCommand::Direct(args)); + fn ssh_details(&self, cx: &AppContext) -> Option<(String, SshCommand)> { + if let Some(ssh_client) = &self.ssh_client { + let ssh_client = ssh_client.read(cx); + if let Some(args) = ssh_client.ssh_args() { + return Some(( + ssh_client.connection_options().host.clone(), + SshCommand::Direct(args), + )); + } } let dev_server_project_id = self.dev_server_project_id()?; @@ -83,7 +85,7 @@ impl Project { .ssh_connection_string .as_ref()? .to_string(); - Some(SshCommand::DevServer(ssh_command)) + Some(("".to_string(), SshCommand::DevServer(ssh_command))) } pub fn create_terminal( @@ -102,7 +104,7 @@ impl Project { } } }; - let ssh_command = self.ssh_command(cx); + let ssh_details = self.ssh_details(cx); let mut settings_location = None; if let Some(path) = path.as_ref() { @@ -127,7 +129,7 @@ impl Project { // precedence. env.extend(settings.env.clone()); - let local_path = if ssh_command.is_none() { + let local_path = if ssh_details.is_none() { path.clone() } else { None @@ -144,8 +146,8 @@ impl Project { self.python_activate_command(&python_venv_directory, settings); } - match &ssh_command { - Some(ssh_command) => { + match &ssh_details { + Some((host, ssh_command)) => { log::debug!("Connecting to a remote server: {ssh_command:?}"); // Alacritty sets its terminfo to `alacritty`, this requiring hosts to have it installed @@ -158,7 +160,14 @@ impl Project { let (program, args) = wrap_for_ssh(ssh_command, None, path.as_deref(), env, None); env = HashMap::default(); - (None, Shell::WithArguments { program, args }) + ( + None, + Shell::WithArguments { + program, + args, + title_override: Some(format!("{} — Terminal", host).into()), + }, + ) } None => (None, settings.shell.clone()), } @@ -183,8 +192,8 @@ impl Project { ); } - match &ssh_command { - Some(ssh_command) => { + match &ssh_details { + Some((host, ssh_command)) => { log::debug!("Connecting to a remote server: {ssh_command:?}"); env.entry("TERM".to_string()) .or_insert_with(|| "xterm-256color".to_string()); @@ -196,7 +205,14 @@ impl Project { python_venv_directory, ); env = HashMap::default(); - (task_state, Shell::WithArguments { program, args }) + ( + task_state, + Shell::WithArguments { + program, + args, + title_override: Some(format!("{} — Terminal", host).into()), + }, + ) } None => { if let Some(venv_path) = &python_venv_directory { @@ -208,6 +224,7 @@ impl Project { Shell::WithArguments { program: spawn_task.command, args: spawn_task.args, + title_override: None, }, ) } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 2f3a3162500a149ad9934b1f4eeca73b923fbe8b..491480936976e69910143a6528d8b5f8b161e0ed 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -2939,7 +2939,7 @@ impl Render for ProjectPanel { .key_binding(KeyBinding::for_action(&workspace::Open, cx)) .on_click(cx.listener(|this, _, cx| { this.workspace - .update(cx, |workspace, cx| workspace.open(&workspace::Open, cx)) + .update(cx, |_, cx| cx.dispatch_action(Box::new(workspace::Open))) .log_err(); })), ) diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index 7ba144a73a336eb5567be5c61cd5788a18d1f699..6539604c86f70b59c83347f984cc2028a1285555 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -299,6 +299,9 @@ message Envelope { GetPermalinkToLineResponse get_permalink_to_line_response = 265; FlushBufferedMessages flush_buffered_messages = 267; + + LanguageServerPromptRequest language_server_prompt_request = 268; + LanguageServerPromptResponse language_server_prompt_response = 269; // current max } reserved 87 to 88; @@ -2528,3 +2531,25 @@ message GetPermalinkToLineResponse { message FlushBufferedMessages {} message FlushBufferedMessagesResponse {} + +message LanguageServerPromptRequest { + uint64 project_id = 1; + + oneof level { + Info info = 2; + Warning warning = 3; + Critical critical = 4; + } + + message Info {} + message Warning {} + message Critical {} + + string message = 5; + repeated string actions = 6; + string lsp_name = 7; +} + +message LanguageServerPromptResponse { + optional uint64 action_response = 1; +} diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index 8179473feaa5f45735a9c699ada7c308cca04924..d60cd3cd87e4f1700a425b1cce8496187ddd604c 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -373,6 +373,8 @@ messages!( (GetPermalinkToLine, Foreground), (GetPermalinkToLineResponse, Foreground), (FlushBufferedMessages, Foreground), + (LanguageServerPromptRequest, Foreground), + (LanguageServerPromptResponse, Foreground), ); request_messages!( @@ -500,6 +502,7 @@ request_messages!( (OpenServerSettings, OpenBufferResponse), (GetPermalinkToLine, GetPermalinkToLineResponse), (FlushBufferedMessages, Ack), + (LanguageServerPromptRequest, LanguageServerPromptResponse), ); entity_messages!( @@ -577,6 +580,7 @@ entity_messages!( HideToast, OpenServerSettings, GetPermalinkToLine, + LanguageServerPromptRequest ); entity_messages!( diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index e1c20954cf5014688f318bd71a171542b03c6d62..30c5f27dfe0d18aa1001030bccc4b0e3634ed046 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -386,6 +386,7 @@ impl RemoteServerProjects { if !matches!(self.mode, Mode::Default(_) | Mode::ViewServerOptions(_, _)) { return; } + self.selectable_items.next(cx); cx.notify(); self.scroll_to_selected(cx); @@ -768,7 +769,7 @@ impl RemoteServerProjects { }; let project = project.clone(); let server = server.clone(); - cx.spawn(|_, mut cx| async move { + cx.spawn(|remote_server_projects, mut cx| async move { let nickname = server.nickname.clone(); let result = open_ssh_project( server.into(), @@ -789,6 +790,10 @@ impl RemoteServerProjects { ) .await .ok(); + } else { + remote_server_projects + .update(&mut cx, |_, cx| cx.emit(DismissEvent)) + .ok(); } }) .detach(); diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index 853f99303ed73f69d7956bfb1757f3d2f3a68be3..7c3798053d01c44c7e162c209c65ebaca11a0645 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -1,13 +1,13 @@ use std::{path::PathBuf, sync::Arc, time::Duration}; -use anyhow::Result; +use anyhow::{anyhow, Result}; use auto_update::AutoUpdater; use editor::Editor; use futures::channel::oneshot; use gpui::{ percentage, Animation, AnimationExt, AnyWindowHandle, AsyncAppContext, DismissEvent, EventEmitter, FocusableView, ParentElement as _, PromptLevel, Render, SemanticVersion, - SharedString, Task, TextStyleRefinement, Transformation, View, + SharedString, Task, TextStyleRefinement, Transformation, View, WeakView, }; use gpui::{AppContext, Model}; @@ -128,6 +128,14 @@ pub struct SshPrompt { editor: View, } +impl Drop for SshPrompt { + fn drop(&mut self) { + if let Some(cancel) = self.cancellation.take() { + cancel.send(()).ok(); + } + } +} + pub struct SshConnectionModal { pub(crate) prompt: View, paths: Vec, @@ -393,7 +401,7 @@ impl ModalView for SshConnectionModal { #[derive(Clone)] pub struct SshClientDelegate { window: AnyWindowHandle, - ui: View, + ui: WeakView, known_password: Option, } @@ -493,7 +501,7 @@ impl SshClientDelegate { ) .await .map_err(|e| { - anyhow::anyhow!( + anyhow!( "failed to download remote server binary (os: {}, arch: {}): {}", platform.os, platform.arch, @@ -520,7 +528,7 @@ impl SshClientDelegate { .output() .await?; if !output.status.success() { - Err(anyhow::anyhow!("failed to run command: {:?}", command))?; + Err(anyhow!("failed to run command: {:?}", command))?; } Ok(()) } @@ -629,7 +637,7 @@ pub fn connect_over_ssh( rx, Arc::new(SshClientDelegate { window, - ui, + ui: ui.downgrade(), known_password, }), cx, @@ -686,7 +694,7 @@ pub async fn open_ssh_project( Some(Arc::new(SshClientDelegate { window: cx.window_handle(), - ui, + ui: ui.downgrade(), known_password: connection_options.password.clone(), })) } diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 4536cc94638d858d218d6f12143f4499acaee77f..efdc886b82835543317d383be2ddb21c26858713 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Result}; use fs::Fs; -use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext}; +use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext, PromptLevel}; use http_client::HttpClient; use language::{proto::serialize_operation, Buffer, BufferEvent, LanguageRegistry}; use node_runtime::NodeRuntime; @@ -206,7 +206,7 @@ impl HeadlessProject { &mut self, _lsp_store: Model, event: &LspStoreEvent, - _cx: &mut ModelContext, + cx: &mut ModelContext, ) { match event { LspStoreEvent::LanguageServerUpdate { @@ -240,6 +240,29 @@ impl HeadlessProject { }) .log_err(); } + LspStoreEvent::LanguageServerPrompt(prompt) => { + let prompt = prompt.clone(); + let request = self.session.request(proto::LanguageServerPromptRequest { + project_id: SSH_PROJECT_ID, + actions: prompt + .actions + .iter() + .map(|action| action.title.to_string()) + .collect(), + level: Some(prompt_to_proto(&prompt)), + lsp_name: Default::default(), + message: Default::default(), + }); + cx.background_executor() + .spawn(async move { + let response = request.await?; + if let Some(action_response) = response.action_response { + prompt.respond(action_response as usize).await; + } + anyhow::Ok(()) + }) + .detach(); + } _ => {} } } @@ -540,3 +563,19 @@ impl HeadlessProject { Ok(proto::Ack {}) } } + +fn prompt_to_proto( + prompt: &project::LanguageServerPromptRequest, +) -> proto::language_server_prompt_request::Level { + match prompt.level { + PromptLevel::Info => proto::language_server_prompt_request::Level::Info( + proto::language_server_prompt_request::Info {}, + ), + PromptLevel::Warning => proto::language_server_prompt_request::Level::Warning( + proto::language_server_prompt_request::Warning {}, + ), + PromptLevel::Critical => proto::language_server_prompt_request::Level::Critical( + proto::language_server_prompt_request::Critical {}, + ), + } +} diff --git a/crates/task/src/lib.rs b/crates/task/src/lib.rs index 1687f8f6964e6d5b8731161bd3e82fa0ab4ba865..534b77b743427f81a5319a3d8ef0894f18b62513 100644 --- a/crates/task/src/lib.rs +++ b/crates/task/src/lib.rs @@ -269,5 +269,7 @@ pub enum Shell { program: String, /// The arguments to pass to the program. args: Vec, + /// An optional string to override the title of the terminal tab + title_override: Option, }, } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index d02da38d94ee6f18ba80de3ba55f51b32e6400ad..95f5555b7ef03219e03f05dbc4de0e9d745a6f2b 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -45,7 +45,7 @@ use smol::channel::{Receiver, Sender}; use task::{HideStrategy, Shell, TaskId}; use terminal_settings::{AlternateScroll, CursorShape, TerminalSettings}; use theme::{ActiveTheme, Theme}; -use util::truncate_and_trailoff; +use util::{paths::home_dir, truncate_and_trailoff}; use std::{ cmp::{self, min}, @@ -60,7 +60,7 @@ use thiserror::Error; use gpui::{ actions, black, px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla, Keystroke, ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, - Pixels, Point, Rgba, ScrollWheelEvent, Size, Task, TouchPhase, + Pixels, Point, Rgba, ScrollWheelEvent, SharedString, Size, Task, TouchPhase, }; use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str}; @@ -274,19 +274,21 @@ impl TerminalError { }) } - pub fn shell_to_string(&self) -> String { - match &self.shell { - Shell::System => "".to_string(), - Shell::Program(p) => p.to_string(), - Shell::WithArguments { program, args } => format!("{} {}", program, args.join(" ")), - } - } - pub fn fmt_shell(&self) -> String { match &self.shell { Shell::System => "".to_string(), Shell::Program(s) => s.to_string(), - Shell::WithArguments { program, args } => format!("{} {}", program, args.join(" ")), + Shell::WithArguments { + program, + args, + title_override, + } => { + if let Some(title_override) = title_override { + format!("{} {} ({})", program, args.join(" "), title_override) + } else { + format!("{} {}", program, args.join(" ")) + } + } } } } @@ -348,20 +350,29 @@ impl TerminalBuilder { release_channel::AppVersion::global(cx).to_string(), ); + let mut terminal_title_override = None; + let pty_options = { let alac_shell = match shell.clone() { Shell::System => None, Shell::Program(program) => { Some(alacritty_terminal::tty::Shell::new(program, Vec::new())) } - Shell::WithArguments { program, args } => { + Shell::WithArguments { + program, + args, + title_override, + } => { + terminal_title_override = title_override; Some(alacritty_terminal::tty::Shell::new(program, args)) } }; alacritty_terminal::tty::Options { shell: alac_shell, - working_directory: working_directory.clone(), + working_directory: working_directory + .clone() + .or_else(|| Some(home_dir().to_path_buf())), hold: false, env: env.into_iter().collect(), } @@ -441,6 +452,7 @@ impl TerminalBuilder { completion_tx, term, term_config: config, + title_override: terminal_title_override, events: VecDeque::with_capacity(10), //Should never get this high. last_content: Default::default(), last_mouse: None, @@ -604,6 +616,7 @@ pub struct Terminal { pub selection_head: Option, pub breadcrumb_text: String, pub pty_info: PtyProcessInfo, + title_override: Option, scroll_px: Pixels, next_link_id: usize, selection_phase: SelectionPhase, @@ -1640,37 +1653,42 @@ impl Terminal { } } None => self - .pty_info - .current + .title_override .as_ref() - .map(|fpi| { - let process_file = fpi - .cwd - .file_name() - .map(|name| name.to_string_lossy().to_string()) - .unwrap_or_default(); - - let argv = fpi.argv.clone(); - let process_name = format!( - "{}{}", - fpi.name, - if !argv.is_empty() { - format!(" {}", (argv[1..]).join(" ")) - } else { - "".to_string() - } - ); - let (process_file, process_name) = if truncate { - ( - truncate_and_trailoff(&process_file, MAX_CHARS), - truncate_and_trailoff(&process_name, MAX_CHARS), - ) - } else { - (process_file, process_name) - }; - format!("{process_file} — {process_name}") - }) - .unwrap_or_else(|| "Terminal".to_string()), + .map(|title_override| title_override.to_string()) + .unwrap_or_else(|| { + self.pty_info + .current + .as_ref() + .map(|fpi| { + let process_file = fpi + .cwd + .file_name() + .map(|name| name.to_string_lossy().to_string()) + .unwrap_or_default(); + + let argv = fpi.argv.clone(); + let process_name = format!( + "{}{}", + fpi.name, + if !argv.is_empty() { + format!(" {}", (argv[1..]).join(" ")) + } else { + "".to_string() + } + ); + let (process_file, process_name) = if truncate { + ( + truncate_and_trailoff(&process_file, MAX_CHARS), + truncate_and_trailoff(&process_name, MAX_CHARS), + ) + } else { + (process_file, process_name) + }; + format!("{process_file} — {process_name}") + }) + .unwrap_or_else(|| "Terminal".to_string()) + }), } } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index df6fdec3080efbf799b9d211c63c576f2a1a92ce..582d0d78c3fbf6e10e0a06eba2d6250467679a33 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -414,7 +414,7 @@ impl TerminalPanel { } } Shell::Program(shell) => Some((shell, Vec::new())), - Shell::WithArguments { program, args } => Some((program, args)), + Shell::WithArguments { program, args, .. } => Some((program, args)), }) else { return; }; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 24d643f287e36a7e9728820a652bc5c4f0b77853..cfe666b38e462301fbf5de6e354426088b576565 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1877,37 +1877,6 @@ impl Workspace { }) } - pub fn open(&mut self, _: &Open, cx: &mut ViewContext) { - self.client() - .telemetry() - .report_app_event("open project".to_string()); - let paths = self.prompt_for_open_path( - PathPromptOptions { - files: true, - directories: true, - multiple: true, - }, - DirectoryLister::Local(self.app_state.fs.clone()), - cx, - ); - - cx.spawn(|this, mut cx| async move { - let Some(paths) = paths.await.log_err().flatten() else { - return; - }; - - if let Some(task) = this - .update(&mut cx, |this, cx| { - this.open_workspace_for_paths(false, paths, cx) - }) - .log_err() - { - task.await.log_err(); - } - }) - .detach() - } - pub fn open_workspace_for_paths( &mut self, replace_current_window: bool, @@ -4345,7 +4314,6 @@ impl Workspace { .on_action(cx.listener(Self::send_keystrokes)) .on_action(cx.listener(Self::add_folder_to_project)) .on_action(cx.listener(Self::follow_next_collaborator)) - .on_action(cx.listener(Self::open)) .on_action(cx.listener(Self::close_window)) .on_action(cx.listener(Self::activate_pane_at_index)) .on_action(cx.listener(|workspace, _: &Unfollow, cx| { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 6bd927918ac0df9bdde176b0ad452222666643f6..7cfc8f40c918076b55fcaddc557b4c61073acac7 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -18,8 +18,9 @@ use editor::ProposedChangesEditorToolbar; use editor::{scroll::Autoscroll, Editor, MultiBuffer}; use feature_flags::FeatureFlagAppExt; use gpui::{ - actions, point, px, AppContext, AsyncAppContext, Context, FocusableView, MenuItem, PromptLevel, - ReadGlobal, TitlebarOptions, View, ViewContext, VisualContext, WindowKind, WindowOptions, + actions, point, px, AppContext, AsyncAppContext, Context, FocusableView, MenuItem, + PathPromptOptions, PromptLevel, ReadGlobal, Task, TitlebarOptions, View, ViewContext, + VisualContext, WindowKind, WindowOptions, }; pub use open_listener::*; @@ -27,9 +28,10 @@ use anyhow::Context as _; use assets::Assets; use futures::{channel::mpsc, select_biased, StreamExt}; use outline_panel::OutlinePanel; -use project::Item; +use project::{DirectoryLister, Item}; use project_panel::ProjectPanel; use quick_action_bar::QuickActionBar; +use recent_projects::open_ssh_project; use release_channel::{AppCommitSha, ReleaseChannel}; use rope::Rope; use search::project_search::ProjectSearchBar; @@ -38,6 +40,7 @@ use settings::{ DEFAULT_KEYMAP_PATH, }; use std::any::TypeId; +use std::path::PathBuf; use std::{borrow::Cow, ops::Deref, path::Path, sync::Arc}; use theme::ActiveTheme; use workspace::notifications::NotificationId; @@ -296,6 +299,40 @@ pub fn initialize_workspace( .register_action(move |_, _: &zed_actions::IncreaseBufferFontSize, cx| { theme::adjust_buffer_font_size(cx, |size| *size += px(1.0)) }) + .register_action(|workspace, _: &workspace::Open, cx| { + workspace.client() + .telemetry() + .report_app_event("open project".to_string()); + let paths = workspace.prompt_for_open_path( + PathPromptOptions { + files: true, + directories: true, + multiple: true, + }, + DirectoryLister::Project(workspace.project().clone()), + cx, + ); + + cx.spawn(|this, mut cx| async move { + let Some(paths) = paths.await.log_err().flatten() else { + return; + }; + + if let Some(task) = this + .update(&mut cx, |this, cx| { + if this.project().read(cx).is_local() { + this.open_workspace_for_paths(false, paths, cx) + } else { + open_new_ssh_project_from_project(this, paths, cx) + } + }) + .log_err() + { + task.await.log_err(); + } + }) + .detach() + }) .register_action(move |_, _: &zed_actions::DecreaseBufferFontSize, cx| { theme::adjust_buffer_font_size(cx, |size| *size -= px(1.0)) }) @@ -834,6 +871,39 @@ pub fn load_default_keymap(cx: &mut AppContext) { } } +pub fn open_new_ssh_project_from_project( + workspace: &mut Workspace, + paths: Vec, + cx: &mut ViewContext, +) -> Task> { + let app_state = workspace.app_state().clone(); + let Some(ssh_client) = workspace.project().read(cx).ssh_client() else { + return Task::ready(Err(anyhow::anyhow!("Not an ssh project"))); + }; + let connection_options = ssh_client.read(cx).connection_options(); + let nickname = recent_projects::SshSettings::get_global(cx).nickname_for( + &connection_options.host, + connection_options.port, + &connection_options.username, + ); + + cx.spawn(|_, mut cx| async move { + open_ssh_project( + connection_options, + paths, + app_state, + workspace::OpenOptions { + open_new_workspace: Some(true), + replace_window: None, + env: None, + }, + nickname, + &mut cx, + ) + .await + }) +} + fn open_project_settings_file( workspace: &mut Workspace, _: &OpenProjectSettings,