mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-21 16:02:20 +00:00
fix(ui): one confirmation idiom, and copy that names what it is looking at
- The SSH close confirmation was the app's only bespoke in-app dialog: no scrim, no Escape, no click-outside, no focus — the two buttons were the only way out, and an on_key_down there could never have fired because nothing inside the card is focusable. It now asks through the same platform prompt as the other eleven confirmations, which gets Escape and dismissal from the OS. - The switcher's tab column reported "No workspace or machine matches." That is the other column's noun. - ja left "bind" and "to" in English inside a form whose other labels are Japanese; zh had already translated both. - The bind and target host fields in an SSH forward rule were pinned at 104px, which does not hold an EC2 internal hostname. Same floor, but they now take a share of the row's slack instead of handing all of it to the free-text description beside them. - docs/features.md still described ⌃⇥ as "next tab"; since the two-column switcher landed it holds a panel open and commits on release. Also drops two more names for actions the app calls Zoom Pane and Clear Scrollback.
This commit is contained in:
+4
-3
@@ -95,12 +95,13 @@ Keys are shown in macOS notation — on Windows and Linux, read <kbd>⌘</kbd> a
|
||||
| | |
|
||||
|---|---|
|
||||
| <kbd>⌘ T</kbd> · <kbd>⌘ W</kbd> · <kbd>⌘ ⇧ T</kbd> | new tab · close tab · reopen closed tab |
|
||||
| <kbd>⌘ 1</kbd>…<kbd>⌘ 9</kbd> · <kbd>⌃ ⇥</kbd> · <kbd>⌃ ⇧ ⇥</kbd> | jump to tab 1–9 · next tab · previous tab |
|
||||
| <kbd>⌘ 1</kbd>…<kbd>⌘ 9</kbd> | jump to tab 1–9 |
|
||||
| <kbd>⌃ ⇥</kbd> · <kbd>⌃ ⇧ ⇥</kbd> | hold to walk the switcher forwards · backwards; it commits when you let go |
|
||||
| <kbd>⌘ D</kbd> · <kbd>⌘ ⇧ D</kbd> | split right · split down |
|
||||
| <kbd>⌘ ]</kbd> · <kbd>⌘ [</kbd> | next pane · previous pane |
|
||||
| <kbd>⌘ ⌥ ←→↑↓</kbd> | focus the pane in that direction |
|
||||
| <kbd>⌘ ⏎</kbd> · <kbd>⌘ ⇧ ⏎</kbd> | toggle fullscreen · maximize / restore the pane |
|
||||
| <kbd>⌘ K</kbd> | clear the screen and scrollback |
|
||||
| <kbd>⌘ ⏎</kbd> · <kbd>⌘ ⇧ ⏎</kbd> | toggle fullscreen · zoom pane |
|
||||
| <kbd>⌘ K</kbd> | clear scrollback |
|
||||
| <kbd>⌘ P</kbd> | command palette |
|
||||
| <kbd>⌘ F</kbd> | search the scrollback |
|
||||
| <kbd>⌃ R</kbd> | fuzzy-search shell history |
|
||||
|
||||
@@ -92,12 +92,13 @@ Aider、Amp、OpenCode 等约 17 个)并在其外围加功能 —— 绝不包
|
||||
| | |
|
||||
|---|---|
|
||||
| <kbd>⌘ T</kbd> · <kbd>⌘ W</kbd> · <kbd>⌘ ⇧ T</kbd> | 新建标签页 · 关闭标签页 · 恢复关闭的标签页 |
|
||||
| <kbd>⌘ 1</kbd>…<kbd>⌘ 9</kbd> · <kbd>⌃ ⇥</kbd> · <kbd>⌃ ⇧ ⇥</kbd> | 跳到第 1–9 个标签页 · 下一个 · 上一个标签页 |
|
||||
| <kbd>⌘ 1</kbd>…<kbd>⌘ 9</kbd> | 跳到第 1–9 个标签页 |
|
||||
| <kbd>⌃ ⇥</kbd> · <kbd>⌃ ⇧ ⇥</kbd> | 按住不放在切换面板里向后 · 向前走,松手即切换 |
|
||||
| <kbd>⌘ D</kbd> · <kbd>⌘ ⇧ D</kbd> | 向右分屏 · 向下分屏 |
|
||||
| <kbd>⌘ ]</kbd> · <kbd>⌘ [</kbd> | 下一个窗格 · 上一个窗格 |
|
||||
| <kbd>⌘ ⌥ ←→↑↓</kbd> | 按方向切换焦点窗格 |
|
||||
| <kbd>⌘ ⏎</kbd> · <kbd>⌘ ⇧ ⏎</kbd> | 切换全屏 · 最大化 / 还原窗格 |
|
||||
| <kbd>⌘ K</kbd> | 清屏并清空 scrollback |
|
||||
| <kbd>⌘ ⏎</kbd> · <kbd>⌘ ⇧ ⏎</kbd> | 切换全屏 · 缩放窗格 |
|
||||
| <kbd>⌘ K</kbd> | 清除 scrollback |
|
||||
| <kbd>⌘ P</kbd> | 命令面板 |
|
||||
| <kbd>⌘ F</kbd> | 搜索 scrollback |
|
||||
| <kbd>⌃ R</kbd> | 模糊搜索 shell 历史 |
|
||||
|
||||
+28
-5
@@ -2480,7 +2480,7 @@ impl Tty7App {
|
||||
fn close_pane(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
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<Self>) {
|
||||
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<Self>) {
|
||||
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)
|
||||
});
|
||||
|
||||
@@ -74,67 +74,6 @@ impl Tty7App {
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn render_ssh_close_confirm_overlay(
|
||||
&self,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<AnyElement> {
|
||||
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<u64>,
|
||||
|
||||
@@ -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",
|
||||
|
||||
+3
-2
@@ -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 個のタブ",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 个标签页",
|
||||
|
||||
+17
-3
@@ -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)),
|
||||
)
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user