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
This commit is contained in:
Mikayla Maki 2024-10-23 00:14:43 -07:00 committed by GitHub
parent fabc14355c
commit d0bc84eb33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 318 additions and 109 deletions

View file

@ -22,7 +22,7 @@ pub use environment::EnvironmentErrorMessage;
pub mod search_history; pub mod search_history;
mod yarn; mod yarn;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Context as _, Result};
use buffer_store::{BufferStore, BufferStoreEvent}; use buffer_store::{BufferStore, BufferStoreEvent};
use client::{ use client::{
proto, Client, Collaborator, DevServerProjectId, PendingEntitySubscription, ProjectId, proto, Client, Collaborator, DevServerProjectId, PendingEntitySubscription, ProjectId,
@ -40,8 +40,8 @@ use futures::{
use git::{blame::Blame, repository::GitRepository}; use git::{blame::Blame, repository::GitRepository};
use gpui::{ use gpui::{
AnyModel, AppContext, AsyncAppContext, BorrowAppContext, Context, EventEmitter, Hsla, Model, AnyModel, AppContext, AsyncAppContext, BorrowAppContext, Context as _, EventEmitter, Hsla,
ModelContext, SharedString, Task, WeakModel, WindowContext, Model, ModelContext, SharedString, Task, WeakModel, WindowContext,
}; };
use itertools::Itertools; use itertools::Itertools;
use language::{ use language::{
@ -52,6 +52,7 @@ use language::{
}; };
use lsp::{ use lsp::{
CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServer, LanguageServerId, CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServer, LanguageServerId,
MessageActionItem,
}; };
use lsp_command::*; use lsp_command::*;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
@ -59,7 +60,10 @@ use parking_lot::{Mutex, RwLock};
pub use prettier_store::PrettierStore; pub use prettier_store::PrettierStore;
use project_settings::{ProjectSettings, SettingsObserver, SettingsObserverEvent}; use project_settings::{ProjectSettings, SettingsObserver, SettingsObserverEvent};
use remote::{SshConnectionOptions, SshRemoteClient}; 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::{SearchInputKind, SearchQuery, SearchResult};
use search_history::SearchHistory; use search_history::SearchHistory;
use settings::{InvalidSettingsError, Settings, SettingsLocation, SettingsStore}; 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_worktree);
ssh_proto.add_model_message_handler(Self::handle_update_project); ssh_proto.add_model_message_handler(Self::handle_update_project);
ssh_proto.add_model_message_handler(Self::handle_toast); 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_message_handler(Self::handle_hide_toast);
ssh_proto.add_model_request_handler(BufferStore::handle_update_buffer); ssh_proto.add_model_request_handler(BufferStore::handle_update_buffer);
BufferStore::init(&ssh_proto); BufferStore::init(&ssh_proto);
@ -1180,6 +1185,7 @@ impl Project {
cx: &mut gpui::TestAppContext, cx: &mut gpui::TestAppContext,
) -> Model<Project> { ) -> Model<Project> {
use clock::FakeSystemClock; use clock::FakeSystemClock;
use gpui::Context;
let languages = LanguageRegistry::test(cx.executor()); let languages = LanguageRegistry::test(cx.executor());
let clock = Arc::new(FakeSystemClock::default()); 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( async fn handle_hide_toast(
this: Model<Self>, this: Model<Self>,
envelope: TypedEnvelope<proto::HideToast>, 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,
}
}

View file

@ -67,13 +67,15 @@ impl Project {
} }
} }
fn ssh_command(&self, cx: &AppContext) -> Option<SshCommand> { fn ssh_details(&self, cx: &AppContext) -> Option<(String, SshCommand)> {
if let Some(args) = self if let Some(ssh_client) = &self.ssh_client {
.ssh_client let ssh_client = ssh_client.read(cx);
.as_ref() if let Some(args) = ssh_client.ssh_args() {
.and_then(|session| session.read(cx).ssh_args()) return Some((
{ ssh_client.connection_options().host.clone(),
return Some(SshCommand::Direct(args)); SshCommand::Direct(args),
));
}
} }
let dev_server_project_id = self.dev_server_project_id()?; let dev_server_project_id = self.dev_server_project_id()?;
@ -83,7 +85,7 @@ impl Project {
.ssh_connection_string .ssh_connection_string
.as_ref()? .as_ref()?
.to_string(); .to_string();
Some(SshCommand::DevServer(ssh_command)) Some(("".to_string(), SshCommand::DevServer(ssh_command)))
} }
pub fn create_terminal( 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; let mut settings_location = None;
if let Some(path) = path.as_ref() { if let Some(path) = path.as_ref() {
@ -127,7 +129,7 @@ impl Project {
// precedence. // precedence.
env.extend(settings.env.clone()); env.extend(settings.env.clone());
let local_path = if ssh_command.is_none() { let local_path = if ssh_details.is_none() {
path.clone() path.clone()
} else { } else {
None None
@ -144,8 +146,8 @@ impl Project {
self.python_activate_command(&python_venv_directory, settings); self.python_activate_command(&python_venv_directory, settings);
} }
match &ssh_command { match &ssh_details {
Some(ssh_command) => { Some((host, ssh_command)) => {
log::debug!("Connecting to a remote server: {ssh_command:?}"); log::debug!("Connecting to a remote server: {ssh_command:?}");
// Alacritty sets its terminfo to `alacritty`, this requiring hosts to have it installed // Alacritty sets its terminfo to `alacritty`, this requiring hosts to have it installed
@ -158,7 +160,14 @@ impl Project {
let (program, args) = let (program, args) =
wrap_for_ssh(ssh_command, None, path.as_deref(), env, None); wrap_for_ssh(ssh_command, None, path.as_deref(), env, None);
env = HashMap::default(); 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()), None => (None, settings.shell.clone()),
} }
@ -183,8 +192,8 @@ impl Project {
); );
} }
match &ssh_command { match &ssh_details {
Some(ssh_command) => { Some((host, ssh_command)) => {
log::debug!("Connecting to a remote server: {ssh_command:?}"); log::debug!("Connecting to a remote server: {ssh_command:?}");
env.entry("TERM".to_string()) env.entry("TERM".to_string())
.or_insert_with(|| "xterm-256color".to_string()); .or_insert_with(|| "xterm-256color".to_string());
@ -196,7 +205,14 @@ impl Project {
python_venv_directory, python_venv_directory,
); );
env = HashMap::default(); env = HashMap::default();
(task_state, Shell::WithArguments { program, args }) (
task_state,
Shell::WithArguments {
program,
args,
title_override: Some(format!("{} — Terminal", host).into()),
},
)
} }
None => { None => {
if let Some(venv_path) = &python_venv_directory { if let Some(venv_path) = &python_venv_directory {
@ -208,6 +224,7 @@ impl Project {
Shell::WithArguments { Shell::WithArguments {
program: spawn_task.command, program: spawn_task.command,
args: spawn_task.args, args: spawn_task.args,
title_override: None,
}, },
) )
} }

View file

@ -2939,7 +2939,7 @@ impl Render for ProjectPanel {
.key_binding(KeyBinding::for_action(&workspace::Open, cx)) .key_binding(KeyBinding::for_action(&workspace::Open, cx))
.on_click(cx.listener(|this, _, cx| { .on_click(cx.listener(|this, _, cx| {
this.workspace this.workspace
.update(cx, |workspace, cx| workspace.open(&workspace::Open, cx)) .update(cx, |_, cx| cx.dispatch_action(Box::new(workspace::Open)))
.log_err(); .log_err();
})), })),
) )

View file

@ -299,6 +299,9 @@ message Envelope {
GetPermalinkToLineResponse get_permalink_to_line_response = 265; GetPermalinkToLineResponse get_permalink_to_line_response = 265;
FlushBufferedMessages flush_buffered_messages = 267; FlushBufferedMessages flush_buffered_messages = 267;
LanguageServerPromptRequest language_server_prompt_request = 268;
LanguageServerPromptResponse language_server_prompt_response = 269; // current max
} }
reserved 87 to 88; reserved 87 to 88;
@ -2528,3 +2531,25 @@ message GetPermalinkToLineResponse {
message FlushBufferedMessages {} message FlushBufferedMessages {}
message FlushBufferedMessagesResponse {} 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;
}

View file

@ -373,6 +373,8 @@ messages!(
(GetPermalinkToLine, Foreground), (GetPermalinkToLine, Foreground),
(GetPermalinkToLineResponse, Foreground), (GetPermalinkToLineResponse, Foreground),
(FlushBufferedMessages, Foreground), (FlushBufferedMessages, Foreground),
(LanguageServerPromptRequest, Foreground),
(LanguageServerPromptResponse, Foreground),
); );
request_messages!( request_messages!(
@ -500,6 +502,7 @@ request_messages!(
(OpenServerSettings, OpenBufferResponse), (OpenServerSettings, OpenBufferResponse),
(GetPermalinkToLine, GetPermalinkToLineResponse), (GetPermalinkToLine, GetPermalinkToLineResponse),
(FlushBufferedMessages, Ack), (FlushBufferedMessages, Ack),
(LanguageServerPromptRequest, LanguageServerPromptResponse),
); );
entity_messages!( entity_messages!(
@ -577,6 +580,7 @@ entity_messages!(
HideToast, HideToast,
OpenServerSettings, OpenServerSettings,
GetPermalinkToLine, GetPermalinkToLine,
LanguageServerPromptRequest
); );
entity_messages!( entity_messages!(

View file

@ -386,6 +386,7 @@ impl RemoteServerProjects {
if !matches!(self.mode, Mode::Default(_) | Mode::ViewServerOptions(_, _)) { if !matches!(self.mode, Mode::Default(_) | Mode::ViewServerOptions(_, _)) {
return; return;
} }
self.selectable_items.next(cx); self.selectable_items.next(cx);
cx.notify(); cx.notify();
self.scroll_to_selected(cx); self.scroll_to_selected(cx);
@ -768,7 +769,7 @@ impl RemoteServerProjects {
}; };
let project = project.clone(); let project = project.clone();
let server = server.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 nickname = server.nickname.clone();
let result = open_ssh_project( let result = open_ssh_project(
server.into(), server.into(),
@ -789,6 +790,10 @@ impl RemoteServerProjects {
) )
.await .await
.ok(); .ok();
} else {
remote_server_projects
.update(&mut cx, |_, cx| cx.emit(DismissEvent))
.ok();
} }
}) })
.detach(); .detach();

View file

@ -1,13 +1,13 @@
use std::{path::PathBuf, sync::Arc, time::Duration}; use std::{path::PathBuf, sync::Arc, time::Duration};
use anyhow::Result; use anyhow::{anyhow, Result};
use auto_update::AutoUpdater; use auto_update::AutoUpdater;
use editor::Editor; use editor::Editor;
use futures::channel::oneshot; use futures::channel::oneshot;
use gpui::{ use gpui::{
percentage, Animation, AnimationExt, AnyWindowHandle, AsyncAppContext, DismissEvent, percentage, Animation, AnimationExt, AnyWindowHandle, AsyncAppContext, DismissEvent,
EventEmitter, FocusableView, ParentElement as _, PromptLevel, Render, SemanticVersion, EventEmitter, FocusableView, ParentElement as _, PromptLevel, Render, SemanticVersion,
SharedString, Task, TextStyleRefinement, Transformation, View, SharedString, Task, TextStyleRefinement, Transformation, View, WeakView,
}; };
use gpui::{AppContext, Model}; use gpui::{AppContext, Model};
@ -128,6 +128,14 @@ pub struct SshPrompt {
editor: View<Editor>, 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 struct SshConnectionModal {
pub(crate) prompt: View<SshPrompt>, pub(crate) prompt: View<SshPrompt>,
paths: Vec<PathBuf>, paths: Vec<PathBuf>,
@ -393,7 +401,7 @@ impl ModalView for SshConnectionModal {
#[derive(Clone)] #[derive(Clone)]
pub struct SshClientDelegate { pub struct SshClientDelegate {
window: AnyWindowHandle, window: AnyWindowHandle,
ui: View<SshPrompt>, ui: WeakView<SshPrompt>,
known_password: Option<String>, known_password: Option<String>,
} }
@ -493,7 +501,7 @@ impl SshClientDelegate {
) )
.await .await
.map_err(|e| { .map_err(|e| {
anyhow::anyhow!( anyhow!(
"failed to download remote server binary (os: {}, arch: {}): {}", "failed to download remote server binary (os: {}, arch: {}): {}",
platform.os, platform.os,
platform.arch, platform.arch,
@ -520,7 +528,7 @@ impl SshClientDelegate {
.output() .output()
.await?; .await?;
if !output.status.success() { if !output.status.success() {
Err(anyhow::anyhow!("failed to run command: {:?}", command))?; Err(anyhow!("failed to run command: {:?}", command))?;
} }
Ok(()) Ok(())
} }
@ -629,7 +637,7 @@ pub fn connect_over_ssh(
rx, rx,
Arc::new(SshClientDelegate { Arc::new(SshClientDelegate {
window, window,
ui, ui: ui.downgrade(),
known_password, known_password,
}), }),
cx, cx,
@ -686,7 +694,7 @@ pub async fn open_ssh_project(
Some(Arc::new(SshClientDelegate { Some(Arc::new(SshClientDelegate {
window: cx.window_handle(), window: cx.window_handle(),
ui, ui: ui.downgrade(),
known_password: connection_options.password.clone(), known_password: connection_options.password.clone(),
})) }))
} }

View file

@ -1,6 +1,6 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use fs::Fs; use fs::Fs;
use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext}; use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext, PromptLevel};
use http_client::HttpClient; use http_client::HttpClient;
use language::{proto::serialize_operation, Buffer, BufferEvent, LanguageRegistry}; use language::{proto::serialize_operation, Buffer, BufferEvent, LanguageRegistry};
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
@ -206,7 +206,7 @@ impl HeadlessProject {
&mut self, &mut self,
_lsp_store: Model<LspStore>, _lsp_store: Model<LspStore>,
event: &LspStoreEvent, event: &LspStoreEvent,
_cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) { ) {
match event { match event {
LspStoreEvent::LanguageServerUpdate { LspStoreEvent::LanguageServerUpdate {
@ -240,6 +240,29 @@ impl HeadlessProject {
}) })
.log_err(); .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 {}) 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 {},
),
}
}

View file

@ -269,5 +269,7 @@ pub enum Shell {
program: String, program: String,
/// The arguments to pass to the program. /// The arguments to pass to the program.
args: Vec<String>, args: Vec<String>,
/// An optional string to override the title of the terminal tab
title_override: Option<SharedString>,
}, },
} }

View file

@ -45,7 +45,7 @@ use smol::channel::{Receiver, Sender};
use task::{HideStrategy, Shell, TaskId}; use task::{HideStrategy, Shell, TaskId};
use terminal_settings::{AlternateScroll, CursorShape, TerminalSettings}; use terminal_settings::{AlternateScroll, CursorShape, TerminalSettings};
use theme::{ActiveTheme, Theme}; use theme::{ActiveTheme, Theme};
use util::truncate_and_trailoff; use util::{paths::home_dir, truncate_and_trailoff};
use std::{ use std::{
cmp::{self, min}, cmp::{self, min},
@ -60,7 +60,7 @@ use thiserror::Error;
use gpui::{ use gpui::{
actions, black, px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla, actions, black, px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla,
Keystroke, ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, 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}; 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 { pub fn fmt_shell(&self) -> String {
match &self.shell { match &self.shell {
Shell::System => "<system defined shell>".to_string(), Shell::System => "<system defined shell>".to_string(),
Shell::Program(s) => s.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(), release_channel::AppVersion::global(cx).to_string(),
); );
let mut terminal_title_override = None;
let pty_options = { let pty_options = {
let alac_shell = match shell.clone() { let alac_shell = match shell.clone() {
Shell::System => None, Shell::System => None,
Shell::Program(program) => { Shell::Program(program) => {
Some(alacritty_terminal::tty::Shell::new(program, Vec::new())) 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)) Some(alacritty_terminal::tty::Shell::new(program, args))
} }
}; };
alacritty_terminal::tty::Options { alacritty_terminal::tty::Options {
shell: alac_shell, shell: alac_shell,
working_directory: working_directory.clone(), working_directory: working_directory
.clone()
.or_else(|| Some(home_dir().to_path_buf())),
hold: false, hold: false,
env: env.into_iter().collect(), env: env.into_iter().collect(),
} }
@ -441,6 +452,7 @@ impl TerminalBuilder {
completion_tx, completion_tx,
term, term,
term_config: config, term_config: config,
title_override: terminal_title_override,
events: VecDeque::with_capacity(10), //Should never get this high. events: VecDeque::with_capacity(10), //Should never get this high.
last_content: Default::default(), last_content: Default::default(),
last_mouse: None, last_mouse: None,
@ -604,6 +616,7 @@ pub struct Terminal {
pub selection_head: Option<AlacPoint>, pub selection_head: Option<AlacPoint>,
pub breadcrumb_text: String, pub breadcrumb_text: String,
pub pty_info: PtyProcessInfo, pub pty_info: PtyProcessInfo,
title_override: Option<SharedString>,
scroll_px: Pixels, scroll_px: Pixels,
next_link_id: usize, next_link_id: usize,
selection_phase: SelectionPhase, selection_phase: SelectionPhase,
@ -1640,37 +1653,42 @@ impl Terminal {
} }
} }
None => self None => self
.pty_info .title_override
.current
.as_ref() .as_ref()
.map(|fpi| { .map(|title_override| title_override.to_string())
let process_file = fpi .unwrap_or_else(|| {
.cwd self.pty_info
.file_name() .current
.map(|name| name.to_string_lossy().to_string()) .as_ref()
.unwrap_or_default(); .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 argv = fpi.argv.clone();
let process_name = format!( let process_name = format!(
"{}{}", "{}{}",
fpi.name, fpi.name,
if !argv.is_empty() { if !argv.is_empty() {
format!(" {}", (argv[1..]).join(" ")) format!(" {}", (argv[1..]).join(" "))
} else { } else {
"".to_string() "".to_string()
} }
); );
let (process_file, process_name) = if truncate { let (process_file, process_name) = if truncate {
( (
truncate_and_trailoff(&process_file, MAX_CHARS), truncate_and_trailoff(&process_file, MAX_CHARS),
truncate_and_trailoff(&process_name, MAX_CHARS), truncate_and_trailoff(&process_name, MAX_CHARS),
) )
} else { } else {
(process_file, process_name) (process_file, process_name)
}; };
format!("{process_file}{process_name}") format!("{process_file}{process_name}")
}) })
.unwrap_or_else(|| "Terminal".to_string()), .unwrap_or_else(|| "Terminal".to_string())
}),
} }
} }

View file

@ -414,7 +414,7 @@ impl TerminalPanel {
} }
} }
Shell::Program(shell) => Some((shell, Vec::new())), Shell::Program(shell) => Some((shell, Vec::new())),
Shell::WithArguments { program, args } => Some((program, args)), Shell::WithArguments { program, args, .. } => Some((program, args)),
}) else { }) else {
return; return;
}; };

View file

@ -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( pub fn open_workspace_for_paths(
&mut self, &mut self,
replace_current_window: bool, replace_current_window: bool,
@ -4345,7 +4314,6 @@ impl Workspace {
.on_action(cx.listener(Self::send_keystrokes)) .on_action(cx.listener(Self::send_keystrokes))
.on_action(cx.listener(Self::add_folder_to_project)) .on_action(cx.listener(Self::add_folder_to_project))
.on_action(cx.listener(Self::follow_next_collaborator)) .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::close_window))
.on_action(cx.listener(Self::activate_pane_at_index)) .on_action(cx.listener(Self::activate_pane_at_index))
.on_action(cx.listener(|workspace, _: &Unfollow, cx| { .on_action(cx.listener(|workspace, _: &Unfollow, cx| {

View file

@ -18,8 +18,9 @@ use editor::ProposedChangesEditorToolbar;
use editor::{scroll::Autoscroll, Editor, MultiBuffer}; use editor::{scroll::Autoscroll, Editor, MultiBuffer};
use feature_flags::FeatureFlagAppExt; use feature_flags::FeatureFlagAppExt;
use gpui::{ use gpui::{
actions, point, px, AppContext, AsyncAppContext, Context, FocusableView, MenuItem, PromptLevel, actions, point, px, AppContext, AsyncAppContext, Context, FocusableView, MenuItem,
ReadGlobal, TitlebarOptions, View, ViewContext, VisualContext, WindowKind, WindowOptions, PathPromptOptions, PromptLevel, ReadGlobal, Task, TitlebarOptions, View, ViewContext,
VisualContext, WindowKind, WindowOptions,
}; };
pub use open_listener::*; pub use open_listener::*;
@ -27,9 +28,10 @@ use anyhow::Context as _;
use assets::Assets; use assets::Assets;
use futures::{channel::mpsc, select_biased, StreamExt}; use futures::{channel::mpsc, select_biased, StreamExt};
use outline_panel::OutlinePanel; use outline_panel::OutlinePanel;
use project::Item; use project::{DirectoryLister, Item};
use project_panel::ProjectPanel; use project_panel::ProjectPanel;
use quick_action_bar::QuickActionBar; use quick_action_bar::QuickActionBar;
use recent_projects::open_ssh_project;
use release_channel::{AppCommitSha, ReleaseChannel}; use release_channel::{AppCommitSha, ReleaseChannel};
use rope::Rope; use rope::Rope;
use search::project_search::ProjectSearchBar; use search::project_search::ProjectSearchBar;
@ -38,6 +40,7 @@ use settings::{
DEFAULT_KEYMAP_PATH, DEFAULT_KEYMAP_PATH,
}; };
use std::any::TypeId; use std::any::TypeId;
use std::path::PathBuf;
use std::{borrow::Cow, ops::Deref, path::Path, sync::Arc}; use std::{borrow::Cow, ops::Deref, path::Path, sync::Arc};
use theme::ActiveTheme; use theme::ActiveTheme;
use workspace::notifications::NotificationId; use workspace::notifications::NotificationId;
@ -296,6 +299,40 @@ pub fn initialize_workspace(
.register_action(move |_, _: &zed_actions::IncreaseBufferFontSize, cx| { .register_action(move |_, _: &zed_actions::IncreaseBufferFontSize, cx| {
theme::adjust_buffer_font_size(cx, |size| *size += px(1.0)) 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| { .register_action(move |_, _: &zed_actions::DecreaseBufferFontSize, cx| {
theme::adjust_buffer_font_size(cx, |size| *size -= px(1.0)) 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( fn open_project_settings_file(
workspace: &mut Workspace, workspace: &mut Workspace,
_: &OpenProjectSettings, _: &OpenProjectSettings,