Detailed changes
@@ -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<Project> {
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<Self>,
+ envelope: TypedEnvelope<proto::LanguageServerPromptRequest>,
+ mut cx: AsyncAppContext,
+ ) -> Result<proto::LanguageServerPromptResponse> {
+ 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<Self>,
envelope: TypedEnvelope<proto::HideToast>,
@@ -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,
+ }
+}
@@ -67,13 +67,15 @@ impl Project {
}
}
- fn ssh_command(&self, cx: &AppContext) -> Option<SshCommand> {
- 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,
},
)
}
@@ -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();
})),
)
@@ -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;
+}
@@ -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!(
@@ -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();
@@ -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<Editor>,
}
+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<SshPrompt>,
paths: Vec<PathBuf>,
@@ -393,7 +401,7 @@ impl ModalView for SshConnectionModal {
#[derive(Clone)]
pub struct SshClientDelegate {
window: AnyWindowHandle,
- ui: View<SshPrompt>,
+ ui: WeakView<SshPrompt>,
known_password: Option<String>,
}
@@ -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(),
}))
}
@@ -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<LspStore>,
event: &LspStoreEvent,
- _cx: &mut ModelContext<Self>,
+ cx: &mut ModelContext<Self>,
) {
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 {},
+ ),
+ }
+}
@@ -269,5 +269,7 @@ pub enum Shell {
program: String,
/// The arguments to pass to the program.
args: Vec<String>,
+ /// An optional string to override the title of the terminal tab
+ title_override: Option<SharedString>,
},
}
@@ -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 => "<system shell>".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 => "<system defined shell>".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<AlacPoint>,
pub breadcrumb_text: String,
pub pty_info: PtyProcessInfo,
+ title_override: Option<SharedString>,
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())
+ }),
}
}
@@ -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;
};
@@ -1877,37 +1877,6 @@ impl Workspace {
})
}
- pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
- 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| {
@@ -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<PathBuf>,
+ cx: &mut ViewContext<Workspace>,
+) -> Task<anyhow::Result<()>> {
+ 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,