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);