mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 00:02:23 +00:00
fix(ssh): give focus back when the auth sheet closes
The sheet focuses an input it owns. Dismissing it dropped that input and focused nothing, so the window was left with focus on an element that had stopped rendering: chords still reached the window handlers, but typed characters went nowhere until the user clicked a pane. `submit_ssh_prompt` comes through the same path as cancel, which is where this bites — after a successful login the pane is connected and waiting, and the first thing anyone does is type. Hand focus back once nothing else is asking, the way the switcher, the settings page, the palette, the diff overlay and the worktree prompt all already do. The branch is guarded on there being no next prompt, so a server that asks twice still walks the chain: verified against sshd on localhost, where cancelling the password sheet raises the 2FA sheet with its field focused and typing lands in it.
This commit is contained in:
@@ -489,6 +489,15 @@ impl Tty7App {
|
||||
self.on_auth_prompt_ready(view, window, cx);
|
||||
}
|
||||
}
|
||||
if self.ssh_prompt.model.is_none() {
|
||||
// Nothing else is asking, so the sheet stops rendering and the
|
||||
// focus it held goes with it. Every other overlay hands focus back
|
||||
// on the way out; this one left it on an element that was no
|
||||
// longer there, and `submit_ssh_prompt` comes through here too —
|
||||
// so after a successful login, with the pane connected and
|
||||
// waiting, the next keystroke went nowhere until the user clicked.
|
||||
self.focus_active(window, cx);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -1016,3 +1025,80 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod focus_tests {
|
||||
use super::*;
|
||||
use crate::core::config::Config;
|
||||
use crate::core::session::Session;
|
||||
use gpui::{TestAppContext, VisualTestContext, WindowHandle};
|
||||
|
||||
fn harness(cx: &mut TestAppContext) -> (WindowHandle<Tty7App>, VisualTestContext) {
|
||||
cx.executor().allow_parking();
|
||||
cx.update(|cx| {
|
||||
gpui_component::init(cx);
|
||||
cx.set_global(Config::default());
|
||||
});
|
||||
let window = cx.add_window(|window, cx| {
|
||||
Tty7App::with_session(None, Some(Session::default()), window, cx)
|
||||
});
|
||||
window
|
||||
.update(cx, |_, window, _| window.activate_window())
|
||||
.unwrap();
|
||||
cx.background_executor.run_until_parked();
|
||||
let vcx = VisualTestContext::from_window(window.into(), cx);
|
||||
(window, vcx)
|
||||
}
|
||||
|
||||
/// The sheet takes focus into an input it owns, and dismissing it drops
|
||||
/// that input. Every other overlay in the app hands focus back on the way
|
||||
/// out; this one left it on a handle whose element had stopped rendering.
|
||||
/// `submit_ssh_prompt` shares the path, so the same thing happened after a
|
||||
/// *successful* login, where the pane is alive and waiting — and the next
|
||||
/// keystroke went nowhere until the user clicked.
|
||||
#[gpui::test]
|
||||
fn dismissing_the_last_prompt_hands_focus_back(cx: &mut TestAppContext) {
|
||||
let (window, _vcx) = harness(cx);
|
||||
|
||||
let pane_focus = window
|
||||
.update(cx, |app, window, cx| {
|
||||
// The host-key sheet is the one model with no text fields, so
|
||||
// it stands in for a raised sheet without needing an
|
||||
// `InputState` — which this harness cannot build, its window
|
||||
// root being the app rather than gpui-component's `Root`.
|
||||
app.ssh_prompt.model = Some(PromptModel::HostKeyUnknown {
|
||||
host: "example".into(),
|
||||
port: 22,
|
||||
algorithm: "ssh-ed25519".into(),
|
||||
fingerprint: "SHA256:zzz".into(),
|
||||
});
|
||||
window.focus(&app.ssh_prompt.focus_handle, cx);
|
||||
// A headless harness has no live pane, so `focus_active`
|
||||
// falls through to the home screen's handle. Which target it
|
||||
// picks is not what is under test — that it picks one is.
|
||||
app.home_focus.clone()
|
||||
})
|
||||
.expect("the app window stays open");
|
||||
cx.background_executor.run_until_parked();
|
||||
|
||||
// Sanity: the sheet holds focus while it is up.
|
||||
assert!(
|
||||
!window
|
||||
.update(cx, |_, window, _| pane_focus.is_focused(window))
|
||||
.unwrap(),
|
||||
"the sheet should hold focus while it is up"
|
||||
);
|
||||
|
||||
window
|
||||
.update(cx, |app, window, cx| app.cancel_ssh_prompt(window, cx))
|
||||
.expect("the app window stays open");
|
||||
cx.background_executor.run_until_parked();
|
||||
|
||||
assert!(
|
||||
window
|
||||
.update(cx, |_, window, _| pane_focus.is_focused(window))
|
||||
.unwrap(),
|
||||
"dismissing the last prompt must hand focus back to the app"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user