diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsx
index be086541967..92b8a4956fb 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsx
@@ -3,12 +3,13 @@ import {
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
+ ContextMenuShortcut,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from "@superset/ui/context-menu";
-import { Columns2, MoveRight, Plus, Rows2, X } from "lucide-react";
+import { Columns2, Eraser, MoveRight, Plus, Rows2, X } from "lucide-react";
import type { ReactNode } from "react";
import type { Tab } from "renderer/stores/tabs/types";
@@ -17,6 +18,7 @@ interface TabContentContextMenuProps {
onSplitHorizontal: () => void;
onSplitVertical: () => void;
onClosePane: () => void;
+ onClearTerminal: () => void;
currentTabId: string;
availableTabs: Tab[];
onMoveToTab: (tabId: string) => void;
@@ -28,6 +30,7 @@ export function TabContentContextMenu({
onSplitHorizontal,
onSplitVertical,
onClosePane,
+ onClearTerminal,
currentTabId,
availableTabs,
onMoveToTab,
@@ -48,6 +51,11 @@ export function TabContentContextMenu({
Split Vertically
+
+
+ Clear Terminal
+ ⌘K
+
@@ -73,7 +81,7 @@ export function TabContentContextMenu({
- Close Pane
+ Close Terminal
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/TabPane.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/TabPane.tsx
index 4b9d15705b0..792dc7110cd 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/TabPane.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/TabPane.tsx
@@ -7,6 +7,7 @@ import {
registerPaneRef,
unregisterPaneRef,
} from "renderer/stores/tabs/pane-refs";
+import { useTerminalCallbacksStore } from "renderer/stores/tabs/terminal-callbacks";
import type { Pane, Tab } from "renderer/stores/tabs/types";
import { TabContentContextMenu } from "../TabContentContextMenu";
import { Terminal } from "../Terminal";
@@ -110,6 +111,11 @@ export function TabPane({
splitPaneAuto(tabId, paneId, { width, height }, path);
};
+ const getClearCallback = useTerminalCallbacksStore((s) => s.getClearCallback);
+ const handleClearTerminal = () => {
+ getClearCallback(paneId)?.();
+ };
+
const splitIcon =
splitOrientation === "vertical" ? (
@@ -147,6 +153,7 @@ export function TabPane({
onSplitHorizontal={() => splitPaneHorizontal(tabId, paneId, path)}
onSplitVertical={() => splitPaneVertical(tabId, paneId, path)}
onClosePane={() => removePane(paneId)}
+ onClearTerminal={handleClearTerminal}
currentTabId={tabId}
availableTabs={availableTabs}
onMoveToTab={onMoveToTab}
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
index 04ee96146ea..3e55aada271 100644
--- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
+++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
@@ -7,6 +7,7 @@ import { useEffect, useRef, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { trpc } from "renderer/lib/trpc";
import { useTabsStore } from "renderer/stores/tabs/store";
+import { useTerminalCallbacksStore } from "renderer/stores/tabs/terminal-callbacks";
import { useTerminalTheme } from "renderer/stores/theme";
import { HOTKEYS } from "shared/hotkeys";
import { sanitizeForTitle } from "./commandBuffer";
@@ -80,6 +81,13 @@ export const Terminal = ({ tabId, workspaceId }: TerminalProps) => {
detachRef.current = detachMutation.mutate;
clearScrollbackRef.current = clearScrollbackMutation.mutate;
+ const registerClearCallbackRef = useRef(
+ useTerminalCallbacksStore.getState().registerClearCallback,
+ );
+ const unregisterClearCallbackRef = useRef(
+ useTerminalCallbacksStore.getState().unregisterClearCallback,
+ );
+
const parentTabIdRef = useRef(parentTabId);
parentTabIdRef.current = parentTabId;
@@ -293,18 +301,23 @@ export const Terminal = ({ tabId, workspaceId }: TerminalProps) => {
const inputDisposable = xterm.onData(handleTerminalInput);
const keyDisposable = xterm.onKey(handleKeyPress);
+ const handleClear = () => {
+ xterm.clear();
+ clearScrollbackRef.current({ paneId });
+ };
+
const cleanupKeyboard = setupKeyboardHandler(xterm, {
onShiftEnter: () => {
if (!isExitedRef.current) {
writeRef.current({ paneId, data: "\\\n" });
}
},
- onClear: () => {
- xterm.clear();
- clearScrollbackRef.current({ paneId });
- },
+ onClear: handleClear,
});
+ // Register clear callback for context menu access
+ registerClearCallbackRef.current(paneId, handleClear);
+
const cleanupFocus = setupFocusListener(xterm, () =>
handleTerminalFocusRef.current(),
);
@@ -331,6 +344,7 @@ export const Terminal = ({ tabId, workspaceId }: TerminalProps) => {
cleanupResize();
cleanupPaste();
cleanupQuerySuppression();
+ unregisterClearCallbackRef.current(paneId);
debouncedSetTabAutoTitleRef.current?.cancel?.();
// Detach instead of kill to keep PTY running for reattachment
detachRef.current({ paneId });
diff --git a/apps/desktop/src/renderer/stores/tabs/terminal-callbacks.ts b/apps/desktop/src/renderer/stores/tabs/terminal-callbacks.ts
new file mode 100644
index 00000000000..eb69d7cb78e
--- /dev/null
+++ b/apps/desktop/src/renderer/stores/tabs/terminal-callbacks.ts
@@ -0,0 +1,34 @@
+import { create } from "zustand";
+
+interface TerminalCallbacksState {
+ clearCallbacks: Map void>;
+ registerClearCallback: (paneId: string, callback: () => void) => void;
+ unregisterClearCallback: (paneId: string) => void;
+ getClearCallback: (paneId: string) => (() => void) | undefined;
+}
+
+export const useTerminalCallbacksStore = create()(
+ (set, get) => ({
+ clearCallbacks: new Map(),
+
+ registerClearCallback: (paneId, callback) => {
+ set((state) => {
+ const newCallbacks = new Map(state.clearCallbacks);
+ newCallbacks.set(paneId, callback);
+ return { clearCallbacks: newCallbacks };
+ });
+ },
+
+ unregisterClearCallback: (paneId) => {
+ set((state) => {
+ const newCallbacks = new Map(state.clearCallbacks);
+ newCallbacks.delete(paneId);
+ return { clearCallbacks: newCallbacks };
+ });
+ },
+
+ getClearCallback: (paneId) => {
+ return get().clearCallbacks.get(paneId);
+ },
+ }),
+);