Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "@superset/ui/context-menu";
import type { ReactNode } from "react";
import {
LuArrowDownToLine,
LuColumns2,
LuEraser,
LuMoveRight,
Expand All @@ -27,6 +28,7 @@ interface TabContentContextMenuProps {
onSplitVertical: () => void;
onClosePane: () => void;
onClearTerminal: () => void;
onScrollToBottom: () => void;
currentTabId: string;
availableTabs: Tab[];
onMoveToTab: (tabId: string) => void;
Expand All @@ -39,6 +41,7 @@ export function TabContentContextMenu({
onSplitVertical,
onClosePane,
onClearTerminal,
onScrollToBottom,
currentTabId,
availableTabs,
onMoveToTab,
Expand All @@ -48,6 +51,8 @@ export function TabContentContextMenu({
const targetTabs = availableTabs.filter((t) => t.id !== currentTabId);
const clearShortcut = useHotkeyText("CLEAR_TERMINAL");
const showClearShortcut = clearShortcut !== "Unassigned";
const scrollToBottomShortcut = useHotkeyText("SCROLL_TO_BOTTOM");
const showScrollToBottomShortcut = scrollToBottomShortcut !== "Unassigned";

return (
<ContextMenu>
Expand All @@ -68,6 +73,13 @@ export function TabContentContextMenu({
<ContextMenuShortcut>{clearShortcut}</ContextMenuShortcut>
)}
</ContextMenuItem>
<ContextMenuItem onSelect={onScrollToBottom}>
<LuArrowDownToLine className="size-4" />
Scroll to Bottom
{showScrollToBottomShortcut && (
<ContextMenuShortcut>{scrollToBottomShortcut}</ContextMenuShortcut>
)}
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuSub>
<ContextMenuSubTrigger className="gap-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ export function TabPane({
}: TabPaneProps) {
const terminalContainerRef = useRef<HTMLDivElement>(null);
const getClearCallback = useTerminalCallbacksStore((s) => s.getClearCallback);
const getScrollToBottomCallback = useTerminalCallbacksStore(
(s) => s.getScrollToBottomCallback,
);

useEffect(() => {
const container = terminalContainerRef.current;
Expand All @@ -74,6 +77,10 @@ export function TabPane({
getClearCallback(paneId)?.();
};

const handleScrollToBottom = () => {
getScrollToBottomCallback(paneId)?.();
};

return (
<BasePaneWindow
paneId={paneId}
Expand Down Expand Up @@ -106,6 +113,7 @@ export function TabPane({
onSplitVertical={() => splitPaneVertical(tabId, paneId, path)}
onClosePane={() => removePane(paneId)}
onClearTerminal={handleClearTerminal}
onScrollToBottom={handleScrollToBottom}
currentTabId={tabId}
availableTabs={availableTabs}
onMoveToTab={onMoveToTab}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ export const Terminal = ({ tabId, workspaceId }: TerminalProps) => {
const unregisterClearCallbackRef = useRef(
useTerminalCallbacksStore.getState().unregisterClearCallback,
);
const registerScrollToBottomCallbackRef = useRef(
useTerminalCallbacksStore.getState().registerScrollToBottomCallback,
);
const unregisterScrollToBottomCallbackRef = useRef(
useTerminalCallbacksStore.getState().unregisterScrollToBottomCallback,
);

const parentTabIdRef = useRef(parentTabId);
parentTabIdRef.current = parentTabId;
Expand Down Expand Up @@ -275,6 +281,15 @@ export const Terminal = ({ tabId, workspaceId }: TerminalProps) => {
[isFocused],
);

useAppHotkey(
"SCROLL_TO_BOTTOM",
() => {
xtermRef.current?.scrollToBottom();
},
{ enabled: isFocused, preventDefault: true },
[isFocused],
);

useEffect(() => {
const container = terminalRef.current;
if (!container) return;
Expand Down Expand Up @@ -462,6 +477,10 @@ export const Terminal = ({ tabId, workspaceId }: TerminalProps) => {
clearScrollbackRef.current({ paneId });
};

const handleScrollToBottom = () => {
xterm.scrollToBottom();
};

const handleWrite = (data: string) => {
if (!isExitedRef.current) {
writeRef.current({ paneId, data });
Expand All @@ -481,6 +500,9 @@ export const Terminal = ({ tabId, workspaceId }: TerminalProps) => {
// Register clear callback for context menu access
registerClearCallbackRef.current(paneId, handleClear);

// Register scroll to bottom callback for context menu access
registerScrollToBottomCallbackRef.current(paneId, handleScrollToBottom);

const cleanupFocus = setupFocusListener(xterm, () =>
handleTerminalFocusRef.current(),
);
Expand Down Expand Up @@ -510,6 +532,7 @@ export const Terminal = ({ tabId, workspaceId }: TerminalProps) => {
cleanupPaste();
cleanupQuerySuppression();
unregisterClearCallbackRef.current(paneId);
unregisterScrollToBottomCallbackRef.current(paneId);
debouncedSetTabAutoTitleRef.current?.cancel?.();
// Detach instead of kill to keep PTY running for reattachment
detachRef.current({ paneId });
Expand Down
28 changes: 28 additions & 0 deletions apps/desktop/src/renderer/stores/tabs/terminal-callbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@ import { create } from "zustand";

interface TerminalCallbacksState {
clearCallbacks: Map<string, () => void>;
scrollToBottomCallbacks: Map<string, () => void>;
registerClearCallback: (paneId: string, callback: () => void) => void;
unregisterClearCallback: (paneId: string) => void;
getClearCallback: (paneId: string) => (() => void) | undefined;
registerScrollToBottomCallback: (
paneId: string,
callback: () => void,
) => void;
unregisterScrollToBottomCallback: (paneId: string) => void;
getScrollToBottomCallback: (paneId: string) => (() => void) | undefined;
}

export const useTerminalCallbacksStore = create<TerminalCallbacksState>()(
(set, get) => ({
clearCallbacks: new Map(),
scrollToBottomCallbacks: new Map(),

registerClearCallback: (paneId, callback) => {
set((state) => {
Expand All @@ -30,5 +38,25 @@ export const useTerminalCallbacksStore = create<TerminalCallbacksState>()(
getClearCallback: (paneId) => {
return get().clearCallbacks.get(paneId);
},

registerScrollToBottomCallback: (paneId, callback) => {
set((state) => {
const newCallbacks = new Map(state.scrollToBottomCallbacks);
newCallbacks.set(paneId, callback);
return { scrollToBottomCallbacks: newCallbacks };
});
},

unregisterScrollToBottomCallback: (paneId) => {
set((state) => {
const newCallbacks = new Map(state.scrollToBottomCallbacks);
newCallbacks.delete(paneId);
return { scrollToBottomCallbacks: newCallbacks };
});
},

getScrollToBottomCallback: (paneId) => {
return get().scrollToBottomCallbacks.get(paneId);
},
}),
);
6 changes: 6 additions & 0 deletions apps/desktop/src/shared/hotkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,12 @@ export const HOTKEYS = {
label: "Clear Terminal",
category: "Terminal",
}),
SCROLL_TO_BOTTOM: defineHotkey({
keys: "meta+shift+down",
label: "Scroll to Bottom",
category: "Terminal",
description: "Scroll the active terminal to the bottom",
}),
PREV_TERMINAL: defineHotkey({
keys: "meta+left",
label: "Previous Terminal",
Expand Down
Loading