diff --git a/plugins/ssh-terminal/src/frontend/terminal/Terminal.tsx b/plugins/ssh-terminal/src/frontend/terminal/Terminal.tsx index 53cc181d4..1d50b78bd 100644 --- a/plugins/ssh-terminal/src/frontend/terminal/Terminal.tsx +++ b/plugins/ssh-terminal/src/frontend/terminal/Terminal.tsx @@ -1487,13 +1487,7 @@ const TerminalInner = forwardRef( terminal?.modes?.applicationCursorKeysMode ?? false, // What the tab menu's share entry needs, read at call time. getShareTarget: () => slotApiRef.current?.getShareTarget() ?? null, - openFileManager: () => { - if (webSocketRef.current?.readyState === WebSocket.OPEN) { - webSocketRef.current.send(JSON.stringify({ type: "get_cwd" })); - } else { - onOpenFileManager?.("/"); - } - }, + openFileManager: () => openFilesAtCwd(), }), [isConnected, terminal], ); @@ -1502,6 +1496,14 @@ const TerminalInner = forwardRef( return getClientPreference("copyOnSelect") === "true"; } + function openFilesAtCwd() { + if (webSocketRef.current?.readyState === WebSocket.OPEN) { + webSocketRef.current.send(JSON.stringify({ type: "get_cwd" })); + } else { + onOpenFileManager?.("/"); + } + } + function attemptReconnection() { if ( isUnmountingRef.current || @@ -3713,6 +3715,7 @@ const TerminalInner = forwardRef( onPasteImage={() => void handleClipboardImage()} isFocused={isFocusedPane} slotApi={slotApi} + onOpenFiles={onOpenFileManager ? openFilesAtCwd : undefined} /> )} diff --git a/plugins/ssh-terminal/src/frontend/terminal/TerminalToolbar.tsx b/plugins/ssh-terminal/src/frontend/terminal/TerminalToolbar.tsx index fd78055e6..94bfa7e6d 100644 --- a/plugins/ssh-terminal/src/frontend/terminal/TerminalToolbar.tsx +++ b/plugins/ssh-terminal/src/frontend/terminal/TerminalToolbar.tsx @@ -90,6 +90,8 @@ interface TerminalToolbarProps { onUploadImage: (file: File) => void | Promise; onPasteImage: () => void | Promise; isFocused: boolean; + /** Opens the file manager at the shell's working directory. */ + onOpenFiles?: () => void; /** Hides every contributed action, e.g. while the toolbar is measuring. */ actionsEnabled?: boolean; /** Handed to contributed actions when they are invoked. */ @@ -108,6 +110,7 @@ export const TerminalToolbar: React.FC = ({ isFocused, actionsEnabled = true, slotApi, + onOpenFiles, }) => { const { t } = useTranslation(); // Drives both the measurement copy and the separator, so the toolbar sizes @@ -140,7 +143,9 @@ export const TerminalToolbar: React.FC = ({ }, [hostActions, host]); const openHostLink = (action: (typeof hostLinks)[number]) => { const record = host as unknown as PluginHostRecord; - if (action.run) action.run(record, tabs as unknown as ShellApi); + // The file manager opens where the shell is, as in 2.8. + if (action.tabType === "files" && onOpenFiles) onOpenFiles(); + else if (action.run) action.run(record, tabs as unknown as ShellApi); else if (action.tabType) tabs.openTab(record, action.tabType); }; const settings = useMemo(() => readToolbarSettings(host), [host]); diff --git a/plugins/ssh-terminal/tests/frontend/terminal/TerminalToolbar.test.tsx b/plugins/ssh-terminal/tests/frontend/terminal/TerminalToolbar.test.tsx index 91a8fbe76..9be3f98ef 100644 --- a/plugins/ssh-terminal/tests/frontend/terminal/TerminalToolbar.test.tsx +++ b/plugins/ssh-terminal/tests/frontend/terminal/TerminalToolbar.test.tsx @@ -149,6 +149,28 @@ afterEach(() => { vi.unstubAllGlobals(); }); +describe("toolbar host links", () => { + it("opens the file manager at the shell directory instead of a plain tab", () => { + const run = vi.fn(); + slots.hostActions = [ + { + id: "files", + kind: "open", + tabType: "files", + titleKey: "Files", + icon: () => null, + when: () => true, + run, + }, + ]; + const onOpenFiles = vi.fn(); + renderToolbar({ onOpenFiles }); + fireEvent.click(screen.getAllByRole("button", { name: "Files" })[0]); + expect(onOpenFiles).toHaveBeenCalledOnce(); + expect(run).not.toHaveBeenCalled(); + }); +}); + describe("responsive toolbar density", () => { it("enters below 110 percent and restores at 115 percent", () => { expect(getResponsiveToolbarDensity("labeled", "labeled", 1099, 1000)).toBe(