mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 08:02:24 +00:00
fix(switcher): stop opening context menus with every row greyed out
Two menus in the switcher could open with nothing to press and no word
about why, which reads as broken rather than as not-yet.
A remote workspace this client has never adopted has no local id, and
all four row verbs address a workspace by id — so right-clicking one
greyed the whole menu. A machine reached over WSL or stdio that has
never connected has no home directory for New Workspace, no live link
for Disconnect, and no Restart Server, which is offered to SSH alone;
its menu greyed out the same way.
Both now say what would make the verbs work, in the register the tab
pane already uses for adopted rows ("Open this workspace to see its
tabs"). The machine case gets a named predicate so the one state that
reaches it stays pinned while the verbs move around.
This commit is contained in:
@@ -1054,6 +1054,8 @@ pub fn translate_en(key: L10nKey) -> &'static str {
|
||||
L10nKey::SwitcherNoTabs => "No tabs in this workspace.",
|
||||
L10nKey::SwitcherNoTabMatch => "No tab matches.",
|
||||
L10nKey::SwitcherTabsAfterOpening => "Open this workspace to see its tabs.",
|
||||
L10nKey::SwitcherOpenToManage => "Open this workspace to rename or stop it.",
|
||||
L10nKey::SwitcherConnectToUse => "Connect to this machine to open a workspace on it.",
|
||||
L10nKey::SwitcherTabCount => "{n} tabs",
|
||||
L10nKey::SwitcherTabCountOne => "1 tab",
|
||||
L10nKey::SwitcherActiveTab => "active",
|
||||
|
||||
@@ -1082,6 +1082,8 @@ pub fn translate_ja(key: L10nKey) -> Option<&'static str> {
|
||||
L10nKey::SwitcherNoTabs => "このワークスペースにタブはありません",
|
||||
L10nKey::SwitcherNoTabMatch => "一致するタブがありません",
|
||||
L10nKey::SwitcherTabsAfterOpening => "このワークスペースを開くとタブが表示されます",
|
||||
L10nKey::SwitcherOpenToManage => "このワークスペースを開くと名前の変更や停止ができます",
|
||||
L10nKey::SwitcherConnectToUse => "このマシンに接続するとワークスペースを作成できます",
|
||||
L10nKey::SwitcherTabCount => "{n} 個のタブ",
|
||||
L10nKey::SwitcherTabCountOne => "1 個のタブ",
|
||||
L10nKey::SwitcherActiveTab => "アクティブ",
|
||||
|
||||
@@ -872,6 +872,8 @@ l10n_keys! {
|
||||
SwitcherNoTabs,
|
||||
SwitcherNoTabMatch,
|
||||
SwitcherTabsAfterOpening,
|
||||
SwitcherOpenToManage,
|
||||
SwitcherConnectToUse,
|
||||
SwitcherTabCount,
|
||||
SwitcherTabCountOne,
|
||||
SwitcherActiveTab,
|
||||
|
||||
@@ -994,6 +994,8 @@ pub fn translate_zh(key: L10nKey) -> Option<&'static str> {
|
||||
L10nKey::SwitcherNoTabs => "这个工作区没有标签页。",
|
||||
L10nKey::SwitcherNoTabMatch => "没有匹配的标签页。",
|
||||
L10nKey::SwitcherTabsAfterOpening => "打开这个工作区后才能看到它的标签页。",
|
||||
L10nKey::SwitcherOpenToManage => "打开这个工作区后才能重命名或停止它。",
|
||||
L10nKey::SwitcherConnectToUse => "连接这台机器后才能在上面新建工作区。",
|
||||
L10nKey::SwitcherTabCount => "{n} 个标签页",
|
||||
L10nKey::SwitcherTabCountOne => "1 个标签页",
|
||||
L10nKey::SwitcherActiveTab => "当前",
|
||||
|
||||
+102
-22
@@ -2327,6 +2327,21 @@ impl RowRef {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a machine's menu would open with every verb greyed out.
|
||||
///
|
||||
/// New Workspace needs the machine's home directory, which only a link that
|
||||
/// has come up once supplies; Disconnect needs a live link; Restart Server is
|
||||
/// offered to SSH alone. A WSL or stdio machine that has never connected fails
|
||||
/// all three, and the menu opened with every row greyed and nothing to say for
|
||||
/// itself. One line about the link beats three dead verbs.
|
||||
fn group_menu_is_empty_handed(group: &GroupRef) -> bool {
|
||||
let Some(target) = group.target.as_ref() else {
|
||||
// A local machine can always take a new workspace.
|
||||
return false;
|
||||
};
|
||||
group.home.is_none() && group.link != Link::Connected && !target.is_ssh()
|
||||
}
|
||||
|
||||
fn group_menu(
|
||||
menu: gpui_component::menu::PopupMenu,
|
||||
group: &GroupRef,
|
||||
@@ -2335,6 +2350,9 @@ fn group_menu(
|
||||
let (a1, a2, a3) = (app.clone(), app.clone(), app);
|
||||
let gref = group.clone();
|
||||
let can_create = group.target.is_none() || group.home.is_some();
|
||||
if group_menu_is_empty_handed(group) {
|
||||
return menu.item(PopupMenuItem::label(t(L10nKey::SwitcherConnectToUse)));
|
||||
}
|
||||
let menu = menu.item(
|
||||
PopupMenuItem::new(t(L10nKey::AppMenuNewWorkspace))
|
||||
.disabled(!can_create)
|
||||
@@ -2375,27 +2393,31 @@ fn row_menu(
|
||||
let (a1, a2, a3, a4) = (app.clone(), app.clone(), app.clone(), app);
|
||||
let (id, adopt) = (row.id, row.adopt.is_some());
|
||||
let stoppable = row.live;
|
||||
// Every verb below addresses a workspace by its local id, and a remote
|
||||
// this client has never adopted has none yet. That greyed all four out at
|
||||
// once: the menu opened with nothing to press and no word about why, which
|
||||
// reads as broken rather than as not-yet. Say what would make them work,
|
||||
// the way the tab pane already says it for the same rows.
|
||||
if adopt {
|
||||
return menu.item(PopupMenuItem::label(t(L10nKey::SwitcherOpenToManage)));
|
||||
}
|
||||
menu.item(
|
||||
PopupMenuItem::new(t(L10nKey::SwitcherRename))
|
||||
.disabled(adopt)
|
||||
.on_click(move |_, window, cx| {
|
||||
let _ = a1.update(cx, |this, cx| this.switcher_rename(id, window, cx));
|
||||
}),
|
||||
PopupMenuItem::new(t(L10nKey::SwitcherRename)).on_click(move |_, window, cx| {
|
||||
let _ = a1.update(cx, |this, cx| this.switcher_rename(id, window, cx));
|
||||
}),
|
||||
)
|
||||
.item(
|
||||
PopupMenuItem::new(t(L10nKey::SwitcherOpenInNewWindow))
|
||||
.disabled(adopt)
|
||||
.on_click(move |_, window, cx| {
|
||||
let _ = a2.update(cx, |this, cx| {
|
||||
this.close_switcher(window, cx);
|
||||
crate::ui::windows::open(cx, Some(id));
|
||||
});
|
||||
}),
|
||||
PopupMenuItem::new(t(L10nKey::SwitcherOpenInNewWindow)).on_click(move |_, window, cx| {
|
||||
let _ = a2.update(cx, |this, cx| {
|
||||
this.close_switcher(window, cx);
|
||||
crate::ui::windows::open(cx, Some(id));
|
||||
});
|
||||
}),
|
||||
)
|
||||
.separator()
|
||||
.item(
|
||||
PopupMenuItem::new(t(L10nKey::AppMenuStopWorkspace))
|
||||
.disabled(adopt || !stoppable)
|
||||
.disabled(!stoppable)
|
||||
.on_click(move |_, window, cx| {
|
||||
let _ = a3.update(cx, |this, cx| {
|
||||
this.close_switcher(window, cx);
|
||||
@@ -2404,14 +2426,12 @@ fn row_menu(
|
||||
}),
|
||||
)
|
||||
.item(
|
||||
PopupMenuItem::new(t(L10nKey::AppMenuDeleteWorkspace))
|
||||
.disabled(adopt)
|
||||
.on_click(move |_, window, cx| {
|
||||
let _ = a4.update(cx, |this, cx| {
|
||||
this.close_switcher(window, cx);
|
||||
this.delete_workspace(id, window, cx);
|
||||
});
|
||||
}),
|
||||
PopupMenuItem::new(t(L10nKey::AppMenuDeleteWorkspace)).on_click(move |_, window, cx| {
|
||||
let _ = a4.update(cx, |this, cx| {
|
||||
this.close_switcher(window, cx);
|
||||
this.delete_workspace(id, window, cx);
|
||||
});
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2500,6 +2520,66 @@ fn glyph_col(w: f32, child: impl IntoElement) -> impl IntoElement {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn group_ref(target: Option<RemoteTarget>, home: Option<&str>, link: Link) -> GroupRef {
|
||||
GroupRef {
|
||||
key: "k".into(),
|
||||
label: "l".into(),
|
||||
target,
|
||||
home: home.map(PathBuf::from),
|
||||
link,
|
||||
}
|
||||
}
|
||||
|
||||
/// A menu that opens with every row greyed and no word about why reads as
|
||||
/// broken rather than as not-yet, so the one state that reaches it has to
|
||||
/// stay pinned as the verbs and their conditions move around.
|
||||
#[test]
|
||||
fn only_an_unconnected_non_ssh_machine_has_nothing_to_offer() {
|
||||
let wsl = || {
|
||||
Some(RemoteTarget::Wsl {
|
||||
distro: "Ubuntu".into(),
|
||||
})
|
||||
};
|
||||
let ssh = || RemoteTarget::direct("me", "host", 22);
|
||||
|
||||
// The case: never connected, so no home, and not SSH, so no restart.
|
||||
assert!(group_menu_is_empty_handed(&group_ref(
|
||||
wsl(),
|
||||
None,
|
||||
Link::Offline
|
||||
)));
|
||||
assert!(group_menu_is_empty_handed(&group_ref(
|
||||
wsl(),
|
||||
None,
|
||||
Link::Failed
|
||||
)));
|
||||
|
||||
// A home from an earlier link still allows a new workspace.
|
||||
assert!(!group_menu_is_empty_handed(&group_ref(
|
||||
wsl(),
|
||||
Some("/home/me"),
|
||||
Link::Offline
|
||||
)));
|
||||
// A live link still allows Disconnect.
|
||||
assert!(!group_menu_is_empty_handed(&group_ref(
|
||||
wsl(),
|
||||
None,
|
||||
Link::Connected
|
||||
)));
|
||||
// SSH always keeps Restart Server.
|
||||
assert!(!group_menu_is_empty_handed(&group_ref(
|
||||
Some(ssh()),
|
||||
None,
|
||||
Link::Offline
|
||||
)));
|
||||
// This machine is never short of verbs.
|
||||
assert!(!group_menu_is_empty_handed(&group_ref(
|
||||
None,
|
||||
None,
|
||||
Link::Local
|
||||
)));
|
||||
}
|
||||
|
||||
fn tab(label: &str, path: &str) -> TabRow {
|
||||
TabRow {
|
||||
id: TabId::new(),
|
||||
|
||||
Reference in New Issue
Block a user