From d33600525ee372813293e803d6aa0f2fa7d50fcb Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 24 Sep 2024 16:23:08 -0600 Subject: [PATCH] ssh remoting: Fix cmd-o (#18308) Release Notes: - ssh-remoting: Cmd-O now correctly opens files on the remote host --------- Co-authored-by: Mikayla --- crates/assistant/src/context_store.rs | 6 -- .../random_project_collaboration_tests.rs | 11 ++-- crates/editor/src/editor.rs | 2 +- crates/feedback/src/feedback_modal.rs | 61 ++++++++----------- crates/file_finder/src/file_finder.rs | 2 +- crates/language_tools/src/lsp_log.rs | 4 +- crates/outline_panel/src/outline_panel.rs | 2 +- crates/project/src/project.rs | 46 ++++++++------ crates/project_panel/src/project_panel.rs | 5 +- crates/tasks_ui/src/lib.rs | 2 +- crates/tasks_ui/src/modal.rs | 2 +- crates/terminal_view/src/terminal_panel.rs | 2 +- crates/title_bar/src/collab.rs | 8 +-- crates/workspace/src/workspace.rs | 12 ++-- crates/zed/src/zed.rs | 2 +- 15 files changed, 80 insertions(+), 87 deletions(-) diff --git a/crates/assistant/src/context_store.rs b/crates/assistant/src/context_store.rs index f57a2fbca6..f4f03dda37 100644 --- a/crates/assistant/src/context_store.rs +++ b/crates/assistant/src/context_store.rs @@ -357,9 +357,6 @@ impl ContextStore { let Some(project_id) = project.remote_id() else { return Task::ready(Err(anyhow!("project was not remote"))); }; - if project.is_local_or_ssh() { - return Task::ready(Err(anyhow!("cannot create remote contexts as the host"))); - } let replica_id = project.replica_id(); let capability = project.capability(); @@ -488,9 +485,6 @@ impl ContextStore { let Some(project_id) = project.remote_id() else { return Task::ready(Err(anyhow!("project was not remote"))); }; - if project.is_local_or_ssh() { - return Task::ready(Err(anyhow!("cannot open remote contexts as the host"))); - } if let Some(context) = self.loaded_context_for_id(&context_id, cx) { return Task::ready(Ok(context)); diff --git a/crates/collab/src/tests/random_project_collaboration_tests.rs b/crates/collab/src/tests/random_project_collaboration_tests.rs index 831114ba1a..19d37f8786 100644 --- a/crates/collab/src/tests/random_project_collaboration_tests.rs +++ b/crates/collab/src/tests/random_project_collaboration_tests.rs @@ -298,8 +298,7 @@ impl RandomizedTest for ProjectCollaborationTest { continue; }; let project_root_name = root_name_for_project(&project, cx); - let is_local = - project.read_with(cx, |project, _| project.is_local_or_ssh()); + let is_local = project.read_with(cx, |project, _| project.is_local()); let worktree = project.read_with(cx, |project, cx| { project .worktrees(cx) @@ -335,7 +334,7 @@ impl RandomizedTest for ProjectCollaborationTest { continue; }; let project_root_name = root_name_for_project(&project, cx); - let is_local = project.read_with(cx, |project, _| project.is_local_or_ssh()); + let is_local = project.read_with(cx, |project, _| project.is_local()); match rng.gen_range(0..100_u32) { // Manipulate an existing buffer @@ -1256,7 +1255,7 @@ impl RandomizedTest for ProjectCollaborationTest { let buffers = client.buffers().clone(); for (guest_project, guest_buffers) in &buffers { let project_id = if guest_project.read_with(client_cx, |project, _| { - project.is_local_or_ssh() || project.is_disconnected() + project.is_local() || project.is_disconnected() }) { continue; } else { @@ -1560,9 +1559,7 @@ async fn ensure_project_shared( let first_root_name = root_name_for_project(project, cx); let active_call = cx.read(ActiveCall::global); if active_call.read_with(cx, |call, _| call.room().is_some()) - && project.read_with(cx, |project, _| { - project.is_local_or_ssh() && !project.is_shared() - }) + && project.read_with(cx, |project, _| project.is_local() && !project.is_shared()) { match active_call .update(cx, |call, cx| call.share_project(project.clone(), cx)) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 316d945ca4..b54889dc0d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -11819,7 +11819,7 @@ impl Editor { .filter_map(|buffer| { let buffer = buffer.read(cx); let language = buffer.language()?; - if project.is_local_or_ssh() + if project.is_local() && project.language_servers_for_buffer(buffer, cx).count() == 0 { None diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index a4a07ad2ad..4762b228d3 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -18,8 +18,7 @@ use regex::Regex; use serde_derive::Serialize; use ui::{prelude::*, Button, ButtonStyle, IconPosition, Tooltip}; use util::ResultExt; -use workspace::notifications::NotificationId; -use workspace::{DismissDecision, ModalView, Toast, Workspace}; +use workspace::{DismissDecision, ModalView, Workspace}; use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedRepo}; @@ -120,44 +119,34 @@ impl FeedbackModal { pub fn register(workspace: &mut Workspace, cx: &mut ViewContext) { let _handle = cx.view().downgrade(); workspace.register_action(move |workspace, _: &GiveFeedback, cx| { - let markdown = workspace - .app_state() - .languages - .language_for_name("Markdown"); + workspace + .with_local_workspace(cx, |workspace, cx| { + let markdown = workspace + .app_state() + .languages + .language_for_name("Markdown"); - let project = workspace.project().clone(); - let is_local_project = project.read(cx).is_local_or_ssh(); + let project = workspace.project().clone(); - if !is_local_project { - struct FeedbackInRemoteProject; + let system_specs = SystemSpecs::new(cx); + cx.spawn(|workspace, mut cx| async move { + let markdown = markdown.await.log_err(); + let buffer = project.update(&mut cx, |project, cx| { + project.create_local_buffer("", markdown, cx) + })?; + let system_specs = system_specs.await; - workspace.show_toast( - Toast::new( - NotificationId::unique::(), - "You can only submit feedback in your own project.", - ), - cx, - ); - return; - } + workspace.update(&mut cx, |workspace, cx| { + workspace.toggle_modal(cx, move |cx| { + FeedbackModal::new(system_specs, project, buffer, cx) + }); + })?; - let system_specs = SystemSpecs::new(cx); - cx.spawn(|workspace, mut cx| async move { - let markdown = markdown.await.log_err(); - let buffer = project.update(&mut cx, |project, cx| { - project.create_local_buffer("", markdown, cx) - })?; - let system_specs = system_specs.await; - - workspace.update(&mut cx, |workspace, cx| { - workspace.toggle_modal(cx, move |cx| { - FeedbackModal::new(system_specs, project, buffer, cx) - }); - })?; - - anyhow::Ok(()) - }) - .detach_and_log_err(cx); + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + }) + .detach_and_log_err(cx); }); } diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 4c3f92d3c1..726a8bcb5e 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -884,7 +884,7 @@ impl PickerDelegate for FileFinderDelegate { project .worktree_for_id(history_item.project.worktree_id, cx) .is_some() - || (project.is_local_or_ssh() && history_item.absolute.is_some()) + || (project.is_local() && history_item.absolute.is_some()) }), self.currently_opened_path.as_ref(), None, diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index bde5fe9b19..d8fe3aa518 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -184,7 +184,7 @@ pub fn init(cx: &mut AppContext) { cx.observe_new_views(move |workspace: &mut Workspace, cx| { let project = workspace.project(); - if project.read(cx).is_local_or_ssh() { + if project.read(cx).is_local() { log_store.update(cx, |store, cx| { store.add_project(project, cx); }); @@ -193,7 +193,7 @@ pub fn init(cx: &mut AppContext) { let log_store = log_store.clone(); workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, cx| { let project = workspace.project().read(cx); - if project.is_local_or_ssh() { + if project.is_local() { workspace.add_item_to_active_pane( Box::new(cx.new_view(|cx| { LspLogView::new(workspace.project().clone(), log_store.clone(), cx) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index da66ca4031..4944f770e7 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -3909,7 +3909,7 @@ impl Render for OutlinePanel { .when(project.is_local(), |el| { el.on_action(cx.listener(Self::reveal_in_finder)) }) - .when(project.is_local_or_ssh(), |el| { + .when(project.is_local() || project.is_via_ssh(), |el| { el.on_action(cx.listener(Self::open_in_terminal)) }) .on_mouse_down( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index fe4d2d6b01..5a9b235d91 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -487,7 +487,7 @@ impl DirectoryLister { pub fn is_local(&self, cx: &AppContext) -> bool { match self { DirectoryLister::Local(_) => true, - DirectoryLister::Project(project) => project.read(cx).is_local_or_ssh(), + DirectoryLister::Project(project) => project.read(cx).is_local(), } } @@ -1199,7 +1199,13 @@ impl Project { self.dev_server_project_id } - pub fn supports_remote_terminal(&self, cx: &AppContext) -> bool { + pub fn supports_terminal(&self, cx: &AppContext) -> bool { + if self.is_local() { + return true; + } + if self.is_via_ssh() { + return true; + } let Some(id) = self.dev_server_project_id else { return false; }; @@ -1213,10 +1219,6 @@ impl Project { } pub fn ssh_connection_string(&self, cx: &ModelContext) -> Option { - if self.is_local_or_ssh() { - return None; - } - let dev_server_id = self.dev_server_project_id()?; dev_server_projects::Store::global(cx) .read(cx) @@ -1643,13 +1645,6 @@ impl Project { } } - pub fn is_local_or_ssh(&self) -> bool { - match &self.client_state { - ProjectClientState::Local | ProjectClientState::Shared { .. } => true, - ProjectClientState::Remote { .. } => false, - } - } - pub fn is_via_ssh(&self) -> bool { match &self.client_state { ProjectClientState::Local | ProjectClientState::Shared { .. } => { @@ -1735,7 +1730,7 @@ impl Project { ) -> Task>> { if let Some(buffer) = self.buffer_for_id(id, cx) { Task::ready(Ok(buffer)) - } else if self.is_local_or_ssh() { + } else if self.is_local() || self.is_via_ssh() { Task::ready(Err(anyhow!("buffer {} does not exist", id))) } else if let Some(project_id) = self.remote_id() { let request = self.client.request(proto::OpenBufferById { @@ -1857,7 +1852,7 @@ impl Project { let mut changes = rx.ready_chunks(MAX_BATCH_SIZE); while let Some(changes) = changes.next().await { - let is_local = this.update(&mut cx, |this, _| this.is_local_or_ssh())?; + let is_local = this.update(&mut cx, |this, _| this.is_local())?; for change in changes { match change { @@ -2001,7 +1996,7 @@ impl Project { language_server_id, message, } => { - if self.is_local_or_ssh() { + if self.is_local() { self.enqueue_buffer_ordered_message( BufferOrderedMessage::LanguageServerUpdate { language_server_id: *language_server_id, @@ -3039,8 +3034,19 @@ impl Project { query: String, cx: &mut ModelContext, ) -> Task>> { - if self.is_local_or_ssh() { + if self.is_local() { DirectoryLister::Local(self.fs.clone()).list_directory(query, cx) + } else if let Some(session) = self.ssh_session.as_ref() { + let request = proto::ListRemoteDirectory { + dev_server_id: SSH_PROJECT_ID, + path: query, + }; + + let response = session.request(request); + cx.background_executor().spawn(async move { + let response = response.await?; + Ok(response.entries.into_iter().map(PathBuf::from).collect()) + }) } else if let Some(dev_server) = self.dev_server_project_id().and_then(|id| { dev_server_projects::Store::global(cx) .read(cx) @@ -3317,7 +3323,7 @@ impl Project { mut cx: AsyncAppContext, ) -> Result<()> { this.update(&mut cx, |this, cx| { - if this.is_local_or_ssh() { + if this.is_local() || this.is_via_ssh() { this.unshare(cx)?; } else { this.disconnected_from_host(cx); @@ -3995,7 +4001,7 @@ impl Project { location: Location, cx: &mut ModelContext<'_, Project>, ) -> Task> { - if self.is_local_or_ssh() { + if self.is_local() { let (worktree_id, worktree_abs_path) = if let Some(worktree) = self.task_worktree(cx) { ( Some(worktree.read(cx).id()), @@ -4081,7 +4087,7 @@ impl Project { location: Option, cx: &mut ModelContext, ) -> Task>> { - if self.is_local_or_ssh() { + if self.is_local() { let (file, language) = location .map(|location| { let buffer = location.buffer.read(cx); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 8e741134f0..6958bfb331 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -2722,11 +2722,14 @@ impl Render for ProjectPanel { } })) }) - .when(project.is_local_or_ssh(), |el| { + .when(project.is_local(), |el| { el.on_action(cx.listener(Self::reveal_in_finder)) .on_action(cx.listener(Self::open_system)) .on_action(cx.listener(Self::open_in_terminal)) }) + .when(project.is_via_ssh(), |el| { + el.on_action(cx.listener(Self::open_in_terminal)) + }) .on_mouse_down( MouseButton::Right, cx.listener(move |this, event: &MouseDownEvent, cx| { diff --git a/crates/tasks_ui/src/lib.rs b/crates/tasks_ui/src/lib.rs index 4ea4a8fa2c..fd14f9aaef 100644 --- a/crates/tasks_ui/src/lib.rs +++ b/crates/tasks_ui/src/lib.rs @@ -94,7 +94,7 @@ fn toggle_modal(workspace: &mut Workspace, cx: &mut ViewContext<'_, Workspace>) workspace .update(&mut cx, |workspace, cx| { if workspace.project().update(cx, |project, cx| { - project.is_local_or_ssh() || project.ssh_connection_string(cx).is_some() + project.is_local() || project.ssh_connection_string(cx).is_some() }) { workspace.toggle_modal(cx, |cx| { TasksModal::new(project, task_context, workspace_handle, cx) diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index 931a0b09c3..662e3f11fd 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -225,7 +225,7 @@ impl PickerDelegate for TasksModalDelegate { if project.is_via_collab() && ssh_connection_string.is_none() { Task::ready((Vec::new(), Vec::new())) } else { - let remote_templates = if project.is_local_or_ssh() { + let remote_templates = if project.is_local() { None } else { project diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index f745fbe348..72f8606fa2 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -144,7 +144,7 @@ impl TerminalPanel { cx.subscribe(&pane, Self::handle_pane_event), ]; let project = workspace.project().read(cx); - let enabled = project.is_local_or_ssh() || project.supports_remote_terminal(cx); + let enabled = project.supports_terminal(cx); let this = Self { pane, fs: workspace.app_state().fs.clone(), diff --git a/crates/title_bar/src/collab.rs b/crates/title_bar/src/collab.rs index 1f052e1a5e..e9f89643d5 100644 --- a/crates/title_bar/src/collab.rs +++ b/crates/title_bar/src/collab.rs @@ -284,14 +284,14 @@ impl TitleBar { let room = room.read(cx); let project = self.project.read(cx); - let is_local = project.is_local_or_ssh(); let is_dev_server_project = project.dev_server_project_id().is_some(); - let is_shared = (is_local || is_dev_server_project) && project.is_shared(); + let is_shared = project.is_shared(); let is_muted = room.is_muted(); let is_deafened = room.is_deafened().unwrap_or(false); let is_screen_sharing = room.is_screen_sharing(); let can_use_microphone = room.can_use_microphone(); - let can_share_projects = room.can_share_projects(); + let can_share_projects = room.can_share_projects() + && (is_dev_server_project || project.is_local() || project.is_via_ssh()); let platform_supported = match self.platform_style { PlatformStyle::Mac => true, PlatformStyle::Linux | PlatformStyle::Windows => false, @@ -299,7 +299,7 @@ impl TitleBar { let mut children = Vec::new(); - if (is_local || is_dev_server_project) && can_share_projects { + if can_share_projects { children.push( Button::new( "toggle_sharing", diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 98f793c234..4290e12105 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1891,7 +1891,11 @@ impl Workspace { directories: true, multiple: true, }, - DirectoryLister::Local(self.app_state.fs.clone()), + if self.project.read(cx).is_via_ssh() { + DirectoryLister::Project(self.project.clone()) + } else { + DirectoryLister::Local(self.app_state.fs.clone()) + }, cx, ); @@ -3956,7 +3960,7 @@ impl Workspace { fn local_paths(&self, cx: &AppContext) -> Option>> { let project = self.project().read(cx); - if project.is_local_or_ssh() { + if project.is_local() { Some( project .visible_worktrees(cx) @@ -5160,7 +5164,7 @@ async fn join_channel_internal( return None; } - if (project.is_local_or_ssh() || is_dev_server) + if (project.is_local() || project.is_via_ssh() || is_dev_server) && project.visible_worktrees(cx).any(|tree| { tree.read(cx) .root_entry() @@ -5314,7 +5318,7 @@ pub fn local_workspace_windows(cx: &AppContext) -> Vec> .filter(|workspace| { workspace .read(cx) - .is_ok_and(|workspace| workspace.project.read(cx).is_local_or_ssh()) + .is_ok_and(|workspace| workspace.project.read(cx).is_local()) }) .collect() } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 8f4f1af243..c631c01f99 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -230,7 +230,7 @@ pub fn initialize_workspace( let project = workspace.project().clone(); if project.update(cx, |project, cx| { - project.is_local_or_ssh() || project.ssh_connection_string(cx).is_some() + project.is_local() || project.is_via_ssh() || project.ssh_connection_string(cx).is_some() }) { project.update(cx, |project, cx| { let fs = app_state.fs.clone();