diff --git a/docs/features.md b/docs/features.md
index d8672561..dec624b6 100644
--- a/docs/features.md
+++ b/docs/features.md
@@ -95,12 +95,13 @@ Keys are shown in macOS notation — on Windows and Linux, read ⌘ a
| | |
|---|---|
| ⌘ T · ⌘ W · ⌘ ⇧ T | new tab · close tab · reopen closed tab |
-| ⌘ 1…⌘ 9 · ⌃ ⇥ · ⌃ ⇧ ⇥ | jump to tab 1–9 · next tab · previous tab |
+| ⌘ 1…⌘ 9 | jump to tab 1–9 |
+| ⌃ ⇥ · ⌃ ⇧ ⇥ | hold to walk the switcher forwards · backwards; it commits when you let go |
| ⌘ D · ⌘ ⇧ D | split right · split down |
| ⌘ ] · ⌘ [ | next pane · previous pane |
| ⌘ ⌥ ←→↑↓ | focus the pane in that direction |
-| ⌘ ⏎ · ⌘ ⇧ ⏎ | toggle fullscreen · maximize / restore the pane |
-| ⌘ K | clear the screen and scrollback |
+| ⌘ ⏎ · ⌘ ⇧ ⏎ | toggle fullscreen · zoom pane |
+| ⌘ K | clear scrollback |
| ⌘ P | command palette |
| ⌘ F | search the scrollback |
| ⌃ R | fuzzy-search shell history |
diff --git a/docs/features.zh-CN.md b/docs/features.zh-CN.md
index 982524a0..ec93d185 100644
--- a/docs/features.zh-CN.md
+++ b/docs/features.zh-CN.md
@@ -92,12 +92,13 @@ Aider、Amp、OpenCode 等约 17 个)并在其外围加功能 —— 绝不包
| | |
|---|---|
| ⌘ T · ⌘ W · ⌘ ⇧ T | 新建标签页 · 关闭标签页 · 恢复关闭的标签页 |
-| ⌘ 1…⌘ 9 · ⌃ ⇥ · ⌃ ⇧ ⇥ | 跳到第 1–9 个标签页 · 下一个 · 上一个标签页 |
+| ⌘ 1…⌘ 9 | 跳到第 1–9 个标签页 |
+| ⌃ ⇥ · ⌃ ⇧ ⇥ | 按住不放在切换面板里向后 · 向前走,松手即切换 |
| ⌘ D · ⌘ ⇧ D | 向右分屏 · 向下分屏 |
| ⌘ ] · ⌘ [ | 下一个窗格 · 上一个窗格 |
| ⌘ ⌥ ←→↑↓ | 按方向切换焦点窗格 |
-| ⌘ ⏎ · ⌘ ⇧ ⏎ | 切换全屏 · 最大化 / 还原窗格 |
-| ⌘ K | 清屏并清空 scrollback |
+| ⌘ ⏎ · ⌘ ⇧ ⏎ | 切换全屏 · 缩放窗格 |
+| ⌘ K | 清除 scrollback |
| ⌘ P | 命令面板 |
| ⌘ F | 搜索 scrollback |
| ⌃ R | 模糊搜索 shell 历史 |
diff --git a/src/ui/app.rs b/src/ui/app.rs
index bf5c4880..0c38bd5a 100644
--- a/src/ui/app.rs
+++ b/src/ui/app.rs
@@ -2480,7 +2480,7 @@ impl Tty7App {
fn close_pane(&mut self, window: &mut Window, cx: &mut Context) {
if self.ssh_close_confirm.is_none() && self.focused_pane_is_warn_ssh(window, cx) {
self.ssh_close_confirm = Some(SshCloseKind::Pane);
- cx.notify();
+ self.ask_ssh_close(window, cx);
return;
}
self.ssh_close_confirm = None;
@@ -2732,7 +2732,7 @@ impl Tty7App {
let already_confirming = self.ssh_close_confirm == Some(SshCloseKind::Tab(index));
if !already_confirming && self.tab_has_warn_ssh(index, cx) {
self.ssh_close_confirm = Some(SshCloseKind::Tab(index));
- cx.notify();
+ self.ask_ssh_close(window, cx);
return;
}
self.ssh_close_confirm = None;
@@ -4449,6 +4449,32 @@ impl Tty7App {
.unwrap_or(false)
}
+ /// The app asks every other question of this class through the platform's
+ /// own dialog. This one used to be a bespoke in-app card with no scrim, no
+ /// Escape and no click-outside — the two buttons were the only way out.
+ fn ask_ssh_close(&mut self, window: &mut Window, cx: &mut Context) {
+ let answer = window.prompt(
+ PromptLevel::Warning,
+ t(crate::ui::i18n::L10nKey::CloseSshConnectionTitle),
+ Some(t(crate::ui::i18n::L10nKey::CloseSshConnectionBody)),
+ &[
+ t(crate::ui::i18n::L10nKey::Keep),
+ t(crate::ui::i18n::L10nKey::Close),
+ ],
+ cx,
+ );
+ cx.spawn_in(window, async move |this, cx| {
+ let close = matches!(answer.await, Ok(1));
+ let _ = this.update_in(cx, |this, window, cx| match close {
+ true => this.confirm_ssh_close(window, cx),
+ // Cancelled, or the window went away with the question open:
+ // either way the connection stays up.
+ false => this.cancel_ssh_close(cx),
+ });
+ })
+ .detach();
+ }
+
pub(crate) fn confirm_ssh_close(&mut self, window: &mut Window, cx: &mut Context) {
match self.ssh_close_confirm {
Some(SshCloseKind::Tab(i)) => self.close_tab(i, window, cx),
@@ -5204,9 +5230,6 @@ impl Render for Tty7App {
.when_some(self.render_remote_input_notice(cx), |this, el| {
this.child(el)
})
- .when_some(self.render_ssh_close_confirm_overlay(cx), |this, el| {
- this.child(el)
- })
.when_some(self.render_worktree_prompt_overlay(cx), |this, el| {
this.child(el)
});
diff --git a/src/ui/forwards.rs b/src/ui/forwards.rs
index 45ec7faa..3deb1d72 100644
--- a/src/ui/forwards.rs
+++ b/src/ui/forwards.rs
@@ -74,67 +74,6 @@ impl Tty7App {
)
}
- pub(crate) fn render_ssh_close_confirm_overlay(
- &self,
- cx: &mut Context,
- ) -> Option {
- self.ssh_close_confirm?;
- let theme = cx.theme();
- let card = v_flex()
- .w(px(360.))
- .gap_3()
- .p_4()
- .bg(theme.popover)
- .border_1()
- .border_color(theme.border)
- .rounded_lg()
- .shadow_lg()
- .occlude()
- .child(
- div()
- .font_weight(FontWeight::SEMIBOLD)
- .child(t(crate::ui::i18n::L10nKey::CloseSshConnectionTitle)),
- )
- .child(
- div()
- .text_sm()
- .text_color(theme.muted_foreground)
- .child(t(crate::ui::i18n::L10nKey::CloseSshConnectionBody)),
- )
- .child(
- h_flex()
- .justify_end()
- .gap_2()
- .child(
- Button::new("ssh-close-cancel")
- .label(t(crate::ui::i18n::L10nKey::Keep))
- .small()
- .on_click(
- cx.listener(|this, _, _window, cx| this.cancel_ssh_close(cx)),
- ),
- )
- .child(
- Button::new("ssh-close-confirm")
- .label(t(crate::ui::i18n::L10nKey::Close))
- .primary()
- .small()
- .on_click(cx.listener(|this, _, window, cx| {
- this.confirm_ssh_close(window, cx)
- })),
- ),
- );
- Some(
- div()
- .absolute()
- .inset_0()
- .flex()
- .items_center()
- .justify_center()
- .child(card)
- .into_any_element(),
- )
- }
-
pub(crate) fn forwards_section(
&self,
pane_id: Option,
diff --git a/src/ui/i18n/en.rs b/src/ui/i18n/en.rs
index f8767753..b4d9438e 100644
--- a/src/ui/i18n/en.rs
+++ b/src/ui/i18n/en.rs
@@ -981,6 +981,7 @@ pub fn translate_en(key: L10nKey) -> &'static str {
L10nKey::SwitcherRename => "Rename…",
L10nKey::SwitcherPickAWorkspace => "Pick a workspace to see its tabs",
L10nKey::SwitcherNoTabs => "No tabs in this workspace",
+ L10nKey::SwitcherNoTabMatch => "No tab matches.",
L10nKey::SwitcherTabsAfterOpening => "Open this workspace to see its tabs",
L10nKey::SwitcherTabCount => "{n} tabs",
L10nKey::SwitcherTabCountOne => "1 tab",
diff --git a/src/ui/i18n/ja.rs b/src/ui/i18n/ja.rs
index 2641042d..91139b56 100644
--- a/src/ui/i18n/ja.rs
+++ b/src/ui/i18n/ja.rs
@@ -776,8 +776,8 @@ pub fn translate_ja(key: L10nKey) -> Option<&'static str> {
L10nKey::ForwardLocal => "ローカル",
L10nKey::ForwardRemote => "リモート",
L10nKey::ForwardDynamic => "ダイナミック",
- L10nKey::ForwardBindLabel => "bind",
- L10nKey::ForwardToLabel => "to",
+ L10nKey::ForwardBindLabel => "バインド",
+ L10nKey::ForwardToLabel => "転送先",
L10nKey::ForwardSocksLabel => "SOCKS",
L10nKey::ForwardAdd => "追加",
L10nKey::FileTreePlaceholderFileName => "ファイル名",
@@ -1012,6 +1012,7 @@ pub fn translate_ja(key: L10nKey) -> Option<&'static str> {
L10nKey::SwitcherRename => "名前を変更…",
L10nKey::SwitcherPickAWorkspace => "ワークスペースを選ぶとタブが表示されます",
L10nKey::SwitcherNoTabs => "このワークスペースにタブはありません",
+ L10nKey::SwitcherNoTabMatch => "一致するタブがありません。",
L10nKey::SwitcherTabsAfterOpening => "このワークスペースを開くとタブが表示されます",
L10nKey::SwitcherTabCount => "{n} 個のタブ",
L10nKey::SwitcherTabCountOne => "1 個のタブ",
diff --git a/src/ui/i18n/mod.rs b/src/ui/i18n/mod.rs
index add96591..0212b0c3 100644
--- a/src/ui/i18n/mod.rs
+++ b/src/ui/i18n/mod.rs
@@ -806,6 +806,7 @@ pub enum L10nKey {
SwitcherRename,
SwitcherPickAWorkspace,
SwitcherNoTabs,
+ SwitcherNoTabMatch,
SwitcherTabsAfterOpening,
SwitcherTabCount,
SwitcherTabCountOne,
@@ -1822,6 +1823,7 @@ mod tests {
L10nKey::SwitcherRename,
L10nKey::SwitcherPickAWorkspace,
L10nKey::SwitcherNoTabs,
+ L10nKey::SwitcherNoTabMatch,
L10nKey::SwitcherTabsAfterOpening,
L10nKey::SwitcherTabCount,
L10nKey::SwitcherTabCountOne,
diff --git a/src/ui/i18n/zh.rs b/src/ui/i18n/zh.rs
index cd02070f..c8aa3dd1 100644
--- a/src/ui/i18n/zh.rs
+++ b/src/ui/i18n/zh.rs
@@ -930,6 +930,7 @@ pub fn translate_zh(key: L10nKey) -> Option<&'static str> {
L10nKey::SwitcherRename => "重命名…",
L10nKey::SwitcherPickAWorkspace => "选一个工作区查看它的标签页",
L10nKey::SwitcherNoTabs => "这个工作区没有标签页",
+ L10nKey::SwitcherNoTabMatch => "没有匹配的标签页。",
L10nKey::SwitcherTabsAfterOpening => "打开这个工作区后才能看到它的标签页",
L10nKey::SwitcherTabCount => "{n} 个标签页",
L10nKey::SwitcherTabCountOne => "1 个标签页",
diff --git a/src/ui/settings.rs b/src/ui/settings.rs
index 3a893306..581a7686 100644
--- a/src/ui/settings.rs
+++ b/src/ui/settings.rs
@@ -2906,7 +2906,16 @@ impl Tty7App {
h_flex()
.gap_1()
.items_center()
- .child(div().w(px(104.)).child(Input::new(host).xsmall()))
+ // Was pinned at 104px, which does not hold
+ // `ip-10-0-3-217.eu-west-1.compute.internal`. Same floor, but
+ // the field now takes a share of the row's slack instead of
+ // handing all of it to the free-text description beside it.
+ .child(
+ div()
+ .flex_1()
+ .min_w(px(104.))
+ .child(Input::new(host).xsmall()),
+ )
.child(div().text_xs().text_color(muted).child(":"))
.child(div().w(px(58.)).child(Input::new(port).xsmall()))
};
@@ -2937,10 +2946,15 @@ impl Tty7App {
}
},
))
- .child(endpoint(&row.bind_host, &row.bind_port))
- .child(div().text_xs().text_color(muted).child("→"))
.child(
div()
+ .flex_1()
+ .child(endpoint(&row.bind_host, &row.bind_port)),
+ )
+ .child(div().flex_shrink_0().text_xs().text_color(muted).child("→"))
+ .child(
+ div()
+ .flex_1()
.opacity(if needs_target { 1.0 } else { 0.35 })
.child(endpoint(&row.target_host, &row.target_port)),
)
diff --git a/src/ui/switcher.rs b/src/ui/switcher.rs
index 8658ae13..2a29558a 100644
--- a/src/ui/switcher.rs
+++ b/src/ui/switcher.rs
@@ -1921,7 +1921,7 @@ impl Tty7App {
.unwrap_or_default();
let hits = visible_tabs(row, &query);
if hits.is_empty() {
- return note(t(L10nKey::SwitcherNoMatch).to_string());
+ return note(t(L10nKey::SwitcherNoTabMatch).to_string());
}
let sf = rungs(cx);