feat(ssh-terminal): add a duplicate tab action (#1510)

This commit is contained in:
ZacharyZcR authored and GitHub committed 2026-09-30 09:46:50 -05:00
1 parent 55d7c8837b
commit fef33142b3
4 files changed
+55 -2

No files matched your search

+2 -1
View File
@@ -79,7 +79,8 @@
"clipboardReadFailed": "Failed to read from clipboard. Make sure clipboard permissions are granted.", "clipboardReadFailed": "Failed to read from clipboard. Make sure clipboard permissions are granted.",
"clipboardWriteFailed": "Failed to copy to clipboard. Make sure the page is served over HTTPS or localhost.", "clipboardWriteFailed": "Failed to copy to clipboard. Make sure the page is served over HTTPS or localhost.",
"connected": "Connected", "connected": "Connected",
"totpCodeLabel": "Verification Code" "totpCodeLabel": "Verification Code",
"duplicateTab": "Duplicate tab"
}, },
"terminalToolbar": { "terminalToolbar": {
"detachTmux": "Detach tmux", "detachTmux": "Detach tmux",
@@ -3,6 +3,7 @@ import "@xterm/xterm/css/xterm.css";
import { lazy, Suspense, type ComponentType } from "react"; import { lazy, Suspense, type ComponentType } from "react";
import { import {
Braces, Braces,
Copy,
Hammer, Hammer,
History, History,
Laptop, Laptop,
@@ -12,6 +13,7 @@ import {
import type { import type {
PanelProps, PanelProps,
PluginHostRecord, PluginHostRecord,
PluginTabRecord,
StandaloneViewProps, StandaloneViewProps,
TabProps, TabProps,
TermixApp, TermixApp,
@@ -117,6 +119,26 @@ export function activate(app: TermixApp): void {
}, },
); );
app.registerAction(
"terminal.duplicateTab",
((_handle: unknown, tab?: PluginTabRecord) => {
if (tab?.type === "terminal" && tab.host) {
app.tabs.openTab(tab.host, "terminal", { forceNewTab: true });
}
}) as never,
{ permission: "use" },
);
app.registerSlotContribution("tab.menu", {
actionId: "terminal.duplicateTab",
titleKey: "terminal.duplicateTab",
icon: Copy,
kind: "button",
when: ({ tab }) => {
const target = tab as PluginTabRecord | undefined;
return target?.type === "terminal" && !!target.host;
},
});
app.registerHostAction({ app.registerHostAction({
id: "terminal", id: "terminal",
titleKey: "nav.terminal", titleKey: "nav.terminal",
@@ -63,6 +63,36 @@ describe(`${manifest.id} activate`, () => {
); );
}); });
it("duplicates only the SSH connection configuration into a fresh tab", async () => {
rendered = await renderWithApp(plugin, { manifest, locales });
const host = { id: 7, name: "server", ip: "192.0.2.1", port: 22 };
expect(rendered.registered.slot("tab.menu")).toContain(
"terminal.duplicateTab",
);
await rendered.app.invokeAction("terminal.duplicateTab", undefined, {
id: "old-tab",
type: "terminal",
host,
instanceId: "old-instance",
data: { initialPath: "/tmp", joinSharedSessionId: "shared-session" },
});
expect(rendered.shellCalls).toEqual([
{
method: "openTab",
args: [
{ ...host, pluginSettings: {} },
"terminal",
{ forceNewTab: true },
],
},
]);
await rendered.app.invokeAction("terminal.duplicateTab", undefined, {
id: "local",
type: "local-terminal",
});
expect(rendered.shellCalls).toHaveLength(1);
});
it("owns the host editor's Terminal tab in the SSH group", async () => { it("owns the host editor's Terminal tab in the SSH group", async () => {
rendered = await renderWithApp(plugin, { manifest, locales }); rendered = await renderWithApp(plugin, { manifest, locales });
expect(rendered.registered.hostEditorSections()).toEqual(["terminal"]); expect(rendered.registered.hostEditorSections()).toEqual(["terminal"]);
+1 -1
View File
@@ -713,7 +713,7 @@ export function TabBar({
<ActionSlot <ActionSlot
slotId="tab.menu" slotId="tab.menu"
when={{ tab: ctxTab, handle: ctxTab.terminalRef?.current }} when={{ tab: ctxTab, handle: ctxTab.terminalRef?.current }}
context={() => [ctxTab.terminalRef?.current]} context={() => [ctxTab.terminalRef?.current, ctxTab]}
renderItem={(contribution, invoke) => ( renderItem={(contribution, invoke) => (
<button <button
className="flex items-center gap-2 w-full px-3 py-1.5 text-xs text-left hover:bg-accent hover:text-accent-foreground" className="flex items-center gap-2 w-full px-3 py-1.5 text-xs text-left hover:bg-accent hover:text-accent-foreground"