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:
parent
fabc14355c
commit
d0bc84eb33
13 changed files with 318 additions and 109 deletions
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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| {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue