Make closing the SSH modal equivalent to cancelling the SSH connection task (#19568)
TODO: - [x] Fix focus not being on the modal initially Release Notes: - N/A --------- Co-authored-by: Trace <violet.white.batt@gmail.com>
This commit is contained in:
parent
f951410ef0
commit
33197608ed
5 changed files with 108 additions and 56 deletions
|
@ -547,7 +547,7 @@ impl RemoteServerProjects {
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
let Some(session) = session else {
|
let Some(Some(session)) = session else {
|
||||||
workspace
|
workspace
|
||||||
.update(&mut cx, |workspace, cx| {
|
.update(&mut cx, |workspace, cx| {
|
||||||
let weak = cx.view().downgrade();
|
let weak = cx.view().downgrade();
|
||||||
|
@ -1071,7 +1071,7 @@ impl RemoteServerProjects {
|
||||||
);
|
);
|
||||||
|
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
if confirmation.await.ok() == Some(0) {
|
if confirmation.await.ok() == Some(1) {
|
||||||
remote_servers
|
remote_servers
|
||||||
.update(&mut cx, |this, cx| {
|
.update(&mut cx, |this, cx| {
|
||||||
this.delete_ssh_server(index, cx);
|
this.delete_ssh_server(index, cx);
|
||||||
|
|
|
@ -124,6 +124,7 @@ pub struct SshPrompt {
|
||||||
nickname: Option<SharedString>,
|
nickname: Option<SharedString>,
|
||||||
status_message: Option<SharedString>,
|
status_message: Option<SharedString>,
|
||||||
prompt: Option<(View<Markdown>, oneshot::Sender<Result<String>>)>,
|
prompt: Option<(View<Markdown>, oneshot::Sender<Result<String>>)>,
|
||||||
|
cancellation: Option<oneshot::Sender<()>>,
|
||||||
editor: View<Editor>,
|
editor: View<Editor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,12 +143,17 @@ impl SshPrompt {
|
||||||
Self {
|
Self {
|
||||||
connection_string,
|
connection_string,
|
||||||
nickname,
|
nickname,
|
||||||
status_message: None,
|
|
||||||
prompt: None,
|
|
||||||
editor: cx.new_view(Editor::single_line),
|
editor: cx.new_view(Editor::single_line),
|
||||||
|
status_message: None,
|
||||||
|
cancellation: None,
|
||||||
|
prompt: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_cancellation_tx(&mut self, tx: oneshot::Sender<()>) {
|
||||||
|
self.cancellation = Some(tx);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_prompt(
|
pub fn set_prompt(
|
||||||
&mut self,
|
&mut self,
|
||||||
prompt: String,
|
prompt: String,
|
||||||
|
@ -270,7 +276,13 @@ impl SshConnectionModal {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
||||||
cx.emit(DismissEvent);
|
if let Some(tx) = self
|
||||||
|
.prompt
|
||||||
|
.update(cx, |prompt, _cx| prompt.cancellation.take())
|
||||||
|
{
|
||||||
|
tx.send(()).ok();
|
||||||
|
}
|
||||||
|
self.finished(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,6 +334,7 @@ impl Render for SshConnectionModal {
|
||||||
.w(rems(34.))
|
.w(rems(34.))
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(theme.colors().border)
|
.border_color(theme.colors().border)
|
||||||
|
.key_context("SshConnectionModal")
|
||||||
.track_focus(&self.focus_handle(cx))
|
.track_focus(&self.focus_handle(cx))
|
||||||
.on_action(cx.listener(Self::dismiss))
|
.on_action(cx.listener(Self::dismiss))
|
||||||
.on_action(cx.listener(Self::confirm))
|
.on_action(cx.listener(Self::confirm))
|
||||||
|
@ -486,7 +499,11 @@ impl SshClientDelegate {
|
||||||
use smol::process::{Command, Stdio};
|
use smol::process::{Command, Stdio};
|
||||||
|
|
||||||
async fn run_cmd(command: &mut Command) -> Result<()> {
|
async fn run_cmd(command: &mut Command) -> Result<()> {
|
||||||
let output = command.stderr(Stdio::inherit()).output().await?;
|
let output = command
|
||||||
|
.kill_on_drop(true)
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
Err(anyhow::anyhow!("failed to run command: {:?}", command))?;
|
Err(anyhow::anyhow!("failed to run command: {:?}", command))?;
|
||||||
}
|
}
|
||||||
|
@ -585,13 +602,16 @@ pub fn connect_over_ssh(
|
||||||
connection_options: SshConnectionOptions,
|
connection_options: SshConnectionOptions,
|
||||||
ui: View<SshPrompt>,
|
ui: View<SshPrompt>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<Model<SshRemoteClient>>> {
|
) -> Task<Result<Option<Model<SshRemoteClient>>>> {
|
||||||
let window = cx.window_handle();
|
let window = cx.window_handle();
|
||||||
let known_password = connection_options.password.clone();
|
let known_password = connection_options.password.clone();
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
ui.update(cx, |ui, _cx| ui.set_cancellation_tx(tx));
|
||||||
|
|
||||||
remote::SshRemoteClient::new(
|
remote::SshRemoteClient::new(
|
||||||
unique_identifier,
|
unique_identifier,
|
||||||
connection_options,
|
connection_options,
|
||||||
|
rx,
|
||||||
Arc::new(SshClientDelegate {
|
Arc::new(SshClientDelegate {
|
||||||
window,
|
window,
|
||||||
ui,
|
ui,
|
||||||
|
@ -628,6 +648,7 @@ pub async fn open_ssh_project(
|
||||||
};
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
let (cancel_tx, cancel_rx) = oneshot::channel();
|
||||||
let delegate = window.update(cx, {
|
let delegate = window.update(cx, {
|
||||||
let connection_options = connection_options.clone();
|
let connection_options = connection_options.clone();
|
||||||
let nickname = nickname.clone();
|
let nickname = nickname.clone();
|
||||||
|
@ -636,26 +657,33 @@ pub async fn open_ssh_project(
|
||||||
workspace.toggle_modal(cx, |cx| {
|
workspace.toggle_modal(cx, |cx| {
|
||||||
SshConnectionModal::new(&connection_options, nickname.clone(), cx)
|
SshConnectionModal::new(&connection_options, nickname.clone(), cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
let ui = workspace
|
let ui = workspace
|
||||||
.active_modal::<SshConnectionModal>(cx)
|
.active_modal::<SshConnectionModal>(cx)?
|
||||||
.unwrap()
|
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.prompt
|
.prompt
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
Arc::new(SshClientDelegate {
|
ui.update(cx, |ui, _cx| {
|
||||||
|
ui.set_cancellation_tx(cancel_tx);
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(Arc::new(SshClientDelegate {
|
||||||
window: cx.window_handle(),
|
window: cx.window_handle(),
|
||||||
ui,
|
ui,
|
||||||
known_password: connection_options.password.clone(),
|
known_password: connection_options.password.clone(),
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let Some(delegate) = delegate else { break };
|
||||||
|
|
||||||
let did_open_ssh_project = cx
|
let did_open_ssh_project = cx
|
||||||
.update(|cx| {
|
.update(|cx| {
|
||||||
workspace::open_ssh_project(
|
workspace::open_ssh_project(
|
||||||
window,
|
window,
|
||||||
connection_options.clone(),
|
connection_options.clone(),
|
||||||
|
cancel_rx,
|
||||||
delegate.clone(),
|
delegate.clone(),
|
||||||
app_state.clone(),
|
app_state.clone(),
|
||||||
paths.clone(),
|
paths.clone(),
|
||||||
|
|
|
@ -14,7 +14,7 @@ use futures::{
|
||||||
oneshot,
|
oneshot,
|
||||||
},
|
},
|
||||||
future::BoxFuture,
|
future::BoxFuture,
|
||||||
select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,
|
select, select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, SemanticVersion, Task,
|
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, SemanticVersion, Task,
|
||||||
|
@ -455,54 +455,65 @@ impl SshRemoteClient {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
unique_identifier: String,
|
unique_identifier: String,
|
||||||
connection_options: SshConnectionOptions,
|
connection_options: SshConnectionOptions,
|
||||||
|
cancellation: oneshot::Receiver<()>,
|
||||||
delegate: Arc<dyn SshClientDelegate>,
|
delegate: Arc<dyn SshClientDelegate>,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> Task<Result<Model<Self>>> {
|
) -> Task<Result<Option<Model<Self>>>> {
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
let (outgoing_tx, outgoing_rx) = mpsc::unbounded::<Envelope>();
|
let success = Box::pin(async move {
|
||||||
let (incoming_tx, incoming_rx) = mpsc::unbounded::<Envelope>();
|
let (outgoing_tx, outgoing_rx) = mpsc::unbounded::<Envelope>();
|
||||||
let (connection_activity_tx, connection_activity_rx) = mpsc::channel::<()>(1);
|
let (incoming_tx, incoming_rx) = mpsc::unbounded::<Envelope>();
|
||||||
|
let (connection_activity_tx, connection_activity_rx) = mpsc::channel::<()>(1);
|
||||||
|
|
||||||
let client =
|
let client =
|
||||||
cx.update(|cx| ChannelClient::new(incoming_rx, outgoing_tx, cx, "client"))?;
|
cx.update(|cx| ChannelClient::new(incoming_rx, outgoing_tx, cx, "client"))?;
|
||||||
let this = cx.new_model(|_| Self {
|
let this = cx.new_model(|_| Self {
|
||||||
client: client.clone(),
|
client: client.clone(),
|
||||||
unique_identifier: unique_identifier.clone(),
|
unique_identifier: unique_identifier.clone(),
|
||||||
connection_options: connection_options.clone(),
|
connection_options: connection_options.clone(),
|
||||||
state: Arc::new(Mutex::new(Some(State::Connecting))),
|
state: Arc::new(Mutex::new(Some(State::Connecting))),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let (ssh_connection, io_task) = Self::establish_connection(
|
let (ssh_connection, io_task) = Self::establish_connection(
|
||||||
unique_identifier,
|
unique_identifier,
|
||||||
false,
|
false,
|
||||||
connection_options,
|
connection_options,
|
||||||
incoming_tx,
|
incoming_tx,
|
||||||
outgoing_rx,
|
outgoing_rx,
|
||||||
connection_activity_tx,
|
connection_activity_tx,
|
||||||
delegate.clone(),
|
delegate.clone(),
|
||||||
&mut cx,
|
&mut cx,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let multiplex_task = Self::monitor(this.downgrade(), io_task, &cx);
|
let multiplex_task = Self::monitor(this.downgrade(), io_task, &cx);
|
||||||
|
|
||||||
if let Err(error) = client.ping(HEARTBEAT_TIMEOUT).await {
|
if let Err(error) = client.ping(HEARTBEAT_TIMEOUT).await {
|
||||||
log::error!("failed to establish connection: {}", error);
|
log::error!("failed to establish connection: {}", error);
|
||||||
return Err(error);
|
return Err(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
let heartbeat_task =
|
||||||
|
Self::heartbeat(this.downgrade(), connection_activity_rx, &mut cx);
|
||||||
|
|
||||||
|
this.update(&mut cx, |this, _| {
|
||||||
|
*this.state.lock() = Some(State::Connected {
|
||||||
|
ssh_connection,
|
||||||
|
delegate,
|
||||||
|
multiplex_task,
|
||||||
|
heartbeat_task,
|
||||||
|
});
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Some(this))
|
||||||
|
});
|
||||||
|
|
||||||
|
select! {
|
||||||
|
_ = cancellation.fuse() => {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
result = success.fuse() => result
|
||||||
}
|
}
|
||||||
|
|
||||||
let heartbeat_task = Self::heartbeat(this.downgrade(), connection_activity_rx, &mut cx);
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, _| {
|
|
||||||
*this.state.lock() = Some(State::Connected {
|
|
||||||
ssh_connection,
|
|
||||||
delegate,
|
|
||||||
multiplex_task,
|
|
||||||
heartbeat_task,
|
|
||||||
});
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(this)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1128,6 +1139,7 @@ impl SshRemoteClient {
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub async fn fake_client(port: u16, client_cx: &mut gpui::TestAppContext) -> Model<Self> {
|
pub async fn fake_client(port: u16, client_cx: &mut gpui::TestAppContext) -> Model<Self> {
|
||||||
|
let (_tx, rx) = oneshot::channel();
|
||||||
client_cx
|
client_cx
|
||||||
.update(|cx| {
|
.update(|cx| {
|
||||||
Self::new(
|
Self::new(
|
||||||
|
@ -1137,12 +1149,14 @@ impl SshRemoteClient {
|
||||||
port: Some(port),
|
port: Some(port),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
rx,
|
||||||
Arc::new(fake::Delegate),
|
Arc::new(fake::Delegate),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5534,6 +5534,7 @@ pub fn join_hosted_project(
|
||||||
pub fn open_ssh_project(
|
pub fn open_ssh_project(
|
||||||
window: WindowHandle<Workspace>,
|
window: WindowHandle<Workspace>,
|
||||||
connection_options: SshConnectionOptions,
|
connection_options: SshConnectionOptions,
|
||||||
|
cancel_rx: oneshot::Receiver<()>,
|
||||||
delegate: Arc<dyn SshClientDelegate>,
|
delegate: Arc<dyn SshClientDelegate>,
|
||||||
app_state: Arc<AppState>,
|
app_state: Arc<AppState>,
|
||||||
paths: Vec<PathBuf>,
|
paths: Vec<PathBuf>,
|
||||||
|
@ -5555,11 +5556,21 @@ pub fn open_ssh_project(
|
||||||
workspace_id.0
|
workspace_id.0
|
||||||
);
|
);
|
||||||
|
|
||||||
let session = cx
|
let session = match cx
|
||||||
.update(|cx| {
|
.update(|cx| {
|
||||||
remote::SshRemoteClient::new(unique_identifier, connection_options, delegate, cx)
|
remote::SshRemoteClient::new(
|
||||||
|
unique_identifier,
|
||||||
|
connection_options,
|
||||||
|
cancel_rx,
|
||||||
|
delegate,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
})?
|
})?
|
||||||
.await?;
|
.await?
|
||||||
|
{
|
||||||
|
Some(result) => result,
|
||||||
|
None => return Ok(()),
|
||||||
|
};
|
||||||
|
|
||||||
let project = cx.update(|cx| {
|
let project = cx.update(|cx| {
|
||||||
project::Project::ssh(
|
project::Project::ssh(
|
||||||
|
|
|
@ -274,7 +274,6 @@ pub fn initialize_workspace(
|
||||||
workspace.add_panel(channels_panel, cx);
|
workspace.add_panel(channels_panel, cx);
|
||||||
workspace.add_panel(chat_panel, cx);
|
workspace.add_panel(chat_panel, cx);
|
||||||
workspace.add_panel(notification_panel, cx);
|
workspace.add_panel(notification_panel, cx);
|
||||||
cx.focus_self();
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue