ssh remoting: Fix cmd-o (#18308)

Release Notes:

- ssh-remoting: Cmd-O now correctly opens files on the remote host

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
Conrad Irwin 2024-09-24 16:23:08 -06:00 committed by GitHub
parent fdb03d3058
commit d33600525e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 80 additions and 87 deletions

View file

@ -357,9 +357,6 @@ impl ContextStore {
let Some(project_id) = project.remote_id() else { let Some(project_id) = project.remote_id() else {
return Task::ready(Err(anyhow!("project was not remote"))); 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 replica_id = project.replica_id();
let capability = project.capability(); let capability = project.capability();
@ -488,9 +485,6 @@ impl ContextStore {
let Some(project_id) = project.remote_id() else { let Some(project_id) = project.remote_id() else {
return Task::ready(Err(anyhow!("project was not remote"))); 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) { if let Some(context) = self.loaded_context_for_id(&context_id, cx) {
return Task::ready(Ok(context)); return Task::ready(Ok(context));

View file

@ -298,8 +298,7 @@ impl RandomizedTest for ProjectCollaborationTest {
continue; continue;
}; };
let project_root_name = root_name_for_project(&project, cx); let project_root_name = root_name_for_project(&project, cx);
let is_local = let is_local = project.read_with(cx, |project, _| project.is_local());
project.read_with(cx, |project, _| project.is_local_or_ssh());
let worktree = project.read_with(cx, |project, cx| { let worktree = project.read_with(cx, |project, cx| {
project project
.worktrees(cx) .worktrees(cx)
@ -335,7 +334,7 @@ impl RandomizedTest for ProjectCollaborationTest {
continue; continue;
}; };
let project_root_name = root_name_for_project(&project, cx); 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) { match rng.gen_range(0..100_u32) {
// Manipulate an existing buffer // Manipulate an existing buffer
@ -1256,7 +1255,7 @@ impl RandomizedTest for ProjectCollaborationTest {
let buffers = client.buffers().clone(); let buffers = client.buffers().clone();
for (guest_project, guest_buffers) in &buffers { for (guest_project, guest_buffers) in &buffers {
let project_id = if guest_project.read_with(client_cx, |project, _| { 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; continue;
} else { } else {
@ -1560,9 +1559,7 @@ async fn ensure_project_shared(
let first_root_name = root_name_for_project(project, cx); let first_root_name = root_name_for_project(project, cx);
let active_call = cx.read(ActiveCall::global); let active_call = cx.read(ActiveCall::global);
if active_call.read_with(cx, |call, _| call.room().is_some()) if active_call.read_with(cx, |call, _| call.room().is_some())
&& project.read_with(cx, |project, _| { && project.read_with(cx, |project, _| project.is_local() && !project.is_shared())
project.is_local_or_ssh() && !project.is_shared()
})
{ {
match active_call match active_call
.update(cx, |call, cx| call.share_project(project.clone(), cx)) .update(cx, |call, cx| call.share_project(project.clone(), cx))

View file

@ -11819,7 +11819,7 @@ impl Editor {
.filter_map(|buffer| { .filter_map(|buffer| {
let buffer = buffer.read(cx); let buffer = buffer.read(cx);
let language = buffer.language()?; let language = buffer.language()?;
if project.is_local_or_ssh() if project.is_local()
&& project.language_servers_for_buffer(buffer, cx).count() == 0 && project.language_servers_for_buffer(buffer, cx).count() == 0
{ {
None None

View file

@ -18,8 +18,7 @@ use regex::Regex;
use serde_derive::Serialize; use serde_derive::Serialize;
use ui::{prelude::*, Button, ButtonStyle, IconPosition, Tooltip}; use ui::{prelude::*, Button, ButtonStyle, IconPosition, Tooltip};
use util::ResultExt; use util::ResultExt;
use workspace::notifications::NotificationId; use workspace::{DismissDecision, ModalView, Workspace};
use workspace::{DismissDecision, ModalView, Toast, Workspace};
use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedRepo}; use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedRepo};
@ -120,44 +119,34 @@ impl FeedbackModal {
pub fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) { pub fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
let _handle = cx.view().downgrade(); let _handle = cx.view().downgrade();
workspace.register_action(move |workspace, _: &GiveFeedback, cx| { workspace.register_action(move |workspace, _: &GiveFeedback, cx| {
let markdown = workspace workspace
.app_state() .with_local_workspace(cx, |workspace, cx| {
.languages let markdown = workspace
.language_for_name("Markdown"); .app_state()
.languages
.language_for_name("Markdown");
let project = workspace.project().clone(); let project = workspace.project().clone();
let is_local_project = project.read(cx).is_local_or_ssh();
if !is_local_project { let system_specs = SystemSpecs::new(cx);
struct FeedbackInRemoteProject; 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( workspace.update(&mut cx, |workspace, cx| {
Toast::new( workspace.toggle_modal(cx, move |cx| {
NotificationId::unique::<FeedbackInRemoteProject>(), FeedbackModal::new(system_specs, project, buffer, cx)
"You can only submit feedback in your own project.", });
), })?;
cx,
);
return;
}
let system_specs = SystemSpecs::new(cx); anyhow::Ok(())
cx.spawn(|workspace, mut cx| async move { })
let markdown = markdown.await.log_err(); .detach_and_log_err(cx);
let buffer = project.update(&mut cx, |project, cx| { })
project.create_local_buffer("", markdown, cx) .detach_and_log_err(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);
}); });
} }

View file

@ -884,7 +884,7 @@ impl PickerDelegate for FileFinderDelegate {
project project
.worktree_for_id(history_item.project.worktree_id, cx) .worktree_for_id(history_item.project.worktree_id, cx)
.is_some() .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(), self.currently_opened_path.as_ref(),
None, None,

View file

@ -184,7 +184,7 @@ pub fn init(cx: &mut AppContext) {
cx.observe_new_views(move |workspace: &mut Workspace, cx| { cx.observe_new_views(move |workspace: &mut Workspace, cx| {
let project = workspace.project(); let project = workspace.project();
if project.read(cx).is_local_or_ssh() { if project.read(cx).is_local() {
log_store.update(cx, |store, cx| { log_store.update(cx, |store, cx| {
store.add_project(project, cx); store.add_project(project, cx);
}); });
@ -193,7 +193,7 @@ pub fn init(cx: &mut AppContext) {
let log_store = log_store.clone(); let log_store = log_store.clone();
workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, cx| { workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, cx| {
let project = workspace.project().read(cx); let project = workspace.project().read(cx);
if project.is_local_or_ssh() { if project.is_local() {
workspace.add_item_to_active_pane( workspace.add_item_to_active_pane(
Box::new(cx.new_view(|cx| { Box::new(cx.new_view(|cx| {
LspLogView::new(workspace.project().clone(), log_store.clone(), cx) LspLogView::new(workspace.project().clone(), log_store.clone(), cx)

View file

@ -3909,7 +3909,7 @@ impl Render for OutlinePanel {
.when(project.is_local(), |el| { .when(project.is_local(), |el| {
el.on_action(cx.listener(Self::reveal_in_finder)) 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)) el.on_action(cx.listener(Self::open_in_terminal))
}) })
.on_mouse_down( .on_mouse_down(

View file

@ -487,7 +487,7 @@ impl DirectoryLister {
pub fn is_local(&self, cx: &AppContext) -> bool { pub fn is_local(&self, cx: &AppContext) -> bool {
match self { match self {
DirectoryLister::Local(_) => true, 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 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 { let Some(id) = self.dev_server_project_id else {
return false; return false;
}; };
@ -1213,10 +1219,6 @@ impl Project {
} }
pub fn ssh_connection_string(&self, cx: &ModelContext<Self>) -> Option<SharedString> { pub fn ssh_connection_string(&self, cx: &ModelContext<Self>) -> Option<SharedString> {
if self.is_local_or_ssh() {
return None;
}
let dev_server_id = self.dev_server_project_id()?; let dev_server_id = self.dev_server_project_id()?;
dev_server_projects::Store::global(cx) dev_server_projects::Store::global(cx)
.read(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 { pub fn is_via_ssh(&self) -> bool {
match &self.client_state { match &self.client_state {
ProjectClientState::Local | ProjectClientState::Shared { .. } => { ProjectClientState::Local | ProjectClientState::Shared { .. } => {
@ -1735,7 +1730,7 @@ impl Project {
) -> Task<Result<Model<Buffer>>> { ) -> Task<Result<Model<Buffer>>> {
if let Some(buffer) = self.buffer_for_id(id, cx) { if let Some(buffer) = self.buffer_for_id(id, cx) {
Task::ready(Ok(buffer)) 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))) Task::ready(Err(anyhow!("buffer {} does not exist", id)))
} else if let Some(project_id) = self.remote_id() { } else if let Some(project_id) = self.remote_id() {
let request = self.client.request(proto::OpenBufferById { let request = self.client.request(proto::OpenBufferById {
@ -1857,7 +1852,7 @@ impl Project {
let mut changes = rx.ready_chunks(MAX_BATCH_SIZE); let mut changes = rx.ready_chunks(MAX_BATCH_SIZE);
while let Some(changes) = changes.next().await { 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 { for change in changes {
match change { match change {
@ -2001,7 +1996,7 @@ impl Project {
language_server_id, language_server_id,
message, message,
} => { } => {
if self.is_local_or_ssh() { if self.is_local() {
self.enqueue_buffer_ordered_message( self.enqueue_buffer_ordered_message(
BufferOrderedMessage::LanguageServerUpdate { BufferOrderedMessage::LanguageServerUpdate {
language_server_id: *language_server_id, language_server_id: *language_server_id,
@ -3039,8 +3034,19 @@ impl Project {
query: String, query: String,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<PathBuf>>> { ) -> Task<Result<Vec<PathBuf>>> {
if self.is_local_or_ssh() { if self.is_local() {
DirectoryLister::Local(self.fs.clone()).list_directory(query, cx) 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| { } else if let Some(dev_server) = self.dev_server_project_id().and_then(|id| {
dev_server_projects::Store::global(cx) dev_server_projects::Store::global(cx)
.read(cx) .read(cx)
@ -3317,7 +3323,7 @@ impl Project {
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
if this.is_local_or_ssh() { if this.is_local() || this.is_via_ssh() {
this.unshare(cx)?; this.unshare(cx)?;
} else { } else {
this.disconnected_from_host(cx); this.disconnected_from_host(cx);
@ -3995,7 +4001,7 @@ impl Project {
location: Location, location: Location,
cx: &mut ModelContext<'_, Project>, cx: &mut ModelContext<'_, Project>,
) -> Task<Option<TaskContext>> { ) -> Task<Option<TaskContext>> {
if self.is_local_or_ssh() { if self.is_local() {
let (worktree_id, worktree_abs_path) = if let Some(worktree) = self.task_worktree(cx) { let (worktree_id, worktree_abs_path) = if let Some(worktree) = self.task_worktree(cx) {
( (
Some(worktree.read(cx).id()), Some(worktree.read(cx).id()),
@ -4081,7 +4087,7 @@ impl Project {
location: Option<Location>, location: Option<Location>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<(TaskSourceKind, TaskTemplate)>>> { ) -> Task<Result<Vec<(TaskSourceKind, TaskTemplate)>>> {
if self.is_local_or_ssh() { if self.is_local() {
let (file, language) = location let (file, language) = location
.map(|location| { .map(|location| {
let buffer = location.buffer.read(cx); let buffer = location.buffer.read(cx);

View file

@ -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)) el.on_action(cx.listener(Self::reveal_in_finder))
.on_action(cx.listener(Self::open_system)) .on_action(cx.listener(Self::open_system))
.on_action(cx.listener(Self::open_in_terminal)) .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( .on_mouse_down(
MouseButton::Right, MouseButton::Right,
cx.listener(move |this, event: &MouseDownEvent, cx| { cx.listener(move |this, event: &MouseDownEvent, cx| {

View file

@ -94,7 +94,7 @@ fn toggle_modal(workspace: &mut Workspace, cx: &mut ViewContext<'_, Workspace>)
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {
if workspace.project().update(cx, |project, 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| { workspace.toggle_modal(cx, |cx| {
TasksModal::new(project, task_context, workspace_handle, cx) TasksModal::new(project, task_context, workspace_handle, cx)

View file

@ -225,7 +225,7 @@ impl PickerDelegate for TasksModalDelegate {
if project.is_via_collab() && ssh_connection_string.is_none() { if project.is_via_collab() && ssh_connection_string.is_none() {
Task::ready((Vec::new(), Vec::new())) Task::ready((Vec::new(), Vec::new()))
} else { } else {
let remote_templates = if project.is_local_or_ssh() { let remote_templates = if project.is_local() {
None None
} else { } else {
project project

View file

@ -144,7 +144,7 @@ impl TerminalPanel {
cx.subscribe(&pane, Self::handle_pane_event), cx.subscribe(&pane, Self::handle_pane_event),
]; ];
let project = workspace.project().read(cx); 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 { let this = Self {
pane, pane,
fs: workspace.app_state().fs.clone(), fs: workspace.app_state().fs.clone(),

View file

@ -284,14 +284,14 @@ impl TitleBar {
let room = room.read(cx); let room = room.read(cx);
let project = self.project.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_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_muted = room.is_muted();
let is_deafened = room.is_deafened().unwrap_or(false); let is_deafened = room.is_deafened().unwrap_or(false);
let is_screen_sharing = room.is_screen_sharing(); let is_screen_sharing = room.is_screen_sharing();
let can_use_microphone = room.can_use_microphone(); 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 { let platform_supported = match self.platform_style {
PlatformStyle::Mac => true, PlatformStyle::Mac => true,
PlatformStyle::Linux | PlatformStyle::Windows => false, PlatformStyle::Linux | PlatformStyle::Windows => false,
@ -299,7 +299,7 @@ impl TitleBar {
let mut children = Vec::new(); let mut children = Vec::new();
if (is_local || is_dev_server_project) && can_share_projects { if can_share_projects {
children.push( children.push(
Button::new( Button::new(
"toggle_sharing", "toggle_sharing",

View file

@ -1891,7 +1891,11 @@ impl Workspace {
directories: true, directories: true,
multiple: 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, cx,
); );
@ -3956,7 +3960,7 @@ impl Workspace {
fn local_paths(&self, cx: &AppContext) -> Option<Vec<Arc<Path>>> { fn local_paths(&self, cx: &AppContext) -> Option<Vec<Arc<Path>>> {
let project = self.project().read(cx); let project = self.project().read(cx);
if project.is_local_or_ssh() { if project.is_local() {
Some( Some(
project project
.visible_worktrees(cx) .visible_worktrees(cx)
@ -5160,7 +5164,7 @@ async fn join_channel_internal(
return None; 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| { && project.visible_worktrees(cx).any(|tree| {
tree.read(cx) tree.read(cx)
.root_entry() .root_entry()
@ -5314,7 +5318,7 @@ pub fn local_workspace_windows(cx: &AppContext) -> Vec<WindowHandle<Workspace>>
.filter(|workspace| { .filter(|workspace| {
workspace workspace
.read(cx) .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() .collect()
} }

View file

@ -230,7 +230,7 @@ pub fn initialize_workspace(
let project = workspace.project().clone(); let project = workspace.project().clone();
if project.update(cx, |project, cx| { 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| { project.update(cx, |project, cx| {
let fs = app_state.fs.clone(); let fs = app_state.fs.clone();