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 @@ -24,7 +24,7 @@ import { V2PresetBarItem } from "./components/V2PresetBarItem";

interface V2PresetsBarProps {
matchedPresets: V2TerminalPresetRow[];
executePreset: (preset: V2TerminalPresetRow) => void;
executePreset: (preset: V2TerminalPresetRow) => void | Promise<void>;
}

// Co-located to keep v2 self-contained. Mirrors the v1 array in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@ import type {
PaneViewerData,
TerminalPaneData,
} from "../../types";
import type { TerminalLauncher } from "../useV2TerminalLauncher";

export function useDefaultContextMenuActions(
paneRegistry: PaneRegistry<PaneViewerData>,
): ContextMenuActionConfig<PaneViewerData>[] {
export function useDefaultContextMenuActions({
paneRegistry,
launcher,
}: {
paneRegistry: PaneRegistry<PaneViewerData>;
launcher: TerminalLauncher;
}): ContextMenuActionConfig<PaneViewerData>[] {
const splitDownShortcut = useHotkeyDisplay("SPLIT_DOWN").text;
const splitRightShortcut = useHotkeyDisplay("SPLIT_RIGHT").text;
const splitWithChatShortcut = useHotkeyDisplay("SPLIT_WITH_CHAT").text;
Expand All @@ -43,12 +48,11 @@ export function useDefaultContextMenuActions(
icon: <LuRows2 />,
shortcut:
splitDownShortcut !== "Unassigned" ? splitDownShortcut : undefined,
onSelect: (ctx) => {
onSelect: async (ctx) => {
const terminalId = await launcher.create();
ctx.actions.split("down", {
kind: "terminal",
data: {
terminalId: crypto.randomUUID(),
} as TerminalPaneData,
data: { terminalId } as TerminalPaneData,
});
},
},
Expand All @@ -58,12 +62,11 @@ export function useDefaultContextMenuActions(
icon: <LuColumns2 />,
shortcut:
splitRightShortcut !== "Unassigned" ? splitRightShortcut : undefined,
onSelect: (ctx) => {
onSelect: async (ctx) => {
const terminalId = await launcher.create();
ctx.actions.split("right", {
kind: "terminal",
data: {
terminalId: crypto.randomUUID(),
} as TerminalPaneData,
data: { terminalId } as TerminalPaneData,
});
},
},
Expand Down Expand Up @@ -162,6 +165,7 @@ export function useDefaultContextMenuActions(
equalizePaneSplitsShortcut,
closePaneShortcut,
paneRegistry,
launcher,
],
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import { HiMiniXMark } from "react-icons/hi2";
import { TbLayoutColumns, TbLayoutRows } from "react-icons/tb";
import { HotkeyLabel } from "renderer/hotkeys";
import type { PaneViewerData, TerminalPaneData } from "../../types";
import type { TerminalLauncher } from "../useV2TerminalLauncher";

export function useDefaultPaneActions(): PaneActionConfig<PaneViewerData>[] {
export function useDefaultPaneActions({
launcher,
}: {
launcher: TerminalLauncher;
}): PaneActionConfig<PaneViewerData>[] {
return useMemo<PaneActionConfig<PaneViewerData>[]>(
() => [
{
Expand All @@ -17,14 +22,13 @@ export function useDefaultPaneActions(): PaneActionConfig<PaneViewerData>[] {
<TbLayoutColumns className="size-3.5" />
),
tooltip: <HotkeyLabel label="Split pane" id="SPLIT_AUTO" />,
onClick: (ctx) => {
onClick: async (ctx) => {
const position =
ctx.pane.parentDirection === "horizontal" ? "down" : "right";
const terminalId = await launcher.create();
ctx.actions.split(position, {
kind: "terminal",
data: {
terminalId: crypto.randomUUID(),
} as TerminalPaneData,
data: { terminalId } as TerminalPaneData,
});
},
},
Expand All @@ -35,6 +39,6 @@ export function useDefaultPaneActions(): PaneActionConfig<PaneViewerData>[] {
onClick: (ctx) => ctx.actions.close(),
},
],
[],
[launcher],
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { RendererContext } from "@superset/panes";
import { cn } from "@superset/ui/utils";
import { useWorkspaceClient, workspaceTrpc } from "@superset/workspace-client";
import { workspaceTrpc } from "@superset/workspace-client";
import "@xterm/xterm/css/xterm.css";
import {
useCallback,
Expand Down Expand Up @@ -33,8 +33,6 @@ import { openUrlInV2Workspace } from "renderer/routes/_authenticated/_dashboard/
import { useWorkspaceWsUrl } from "renderer/routes/_authenticated/_dashboard/v2-workspace/providers/WorkspaceTrpcProvider/WorkspaceTrpcProvider";
import { ScrollToBottomButton } from "renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/ScrollToBottomButton";
import { TerminalSearch } from "renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch";
import { useTheme } from "renderer/stores/theme";
import { resolveTerminalThemeType } from "renderer/stores/theme/utils";
import { useLinkClickHint } from "./hooks/useLinkClickHint";
import { type HoveredLink, useLinkHoverState } from "./hooks/useLinkHoverState";
import { useTerminalAppearance } from "./hooks/useTerminalAppearance";
Expand Down Expand Up @@ -64,45 +62,20 @@ export function TerminalPane({
const { hint, showHint } = useLinkClickHint();
const openInExternalEditor = useOpenInExternalEditor(workspaceId);
const paneData = ctx.pane.data as TerminalPaneData;
const paneDataRef = useRef(paneData);
paneDataRef.current = paneData;
const paneActionsRef = useRef(ctx.actions);
paneActionsRef.current = ctx.actions;
const { terminalId } = paneData;
const initialCommandRef = useRef(paneData.initialCommand);
const terminalInstanceId = ctx.pane.id;
const containerRef = useRef<HTMLDivElement | null>(null);
const activeTheme = useTheme();
const [isSearchOpen, setIsSearchOpen] = useState(false);

const appearance = useTerminalAppearance();
const appearanceRef = useRef(appearance);
appearanceRef.current = appearance;
const initialThemeTypeRef = useRef<
ReturnType<typeof resolveTerminalThemeType>
>(
resolveTerminalThemeType({
activeThemeType: activeTheme?.type,
}),
);
const { trpcClient } = useWorkspaceClient();
const trpcClientRef = useRef(trpcClient);
trpcClientRef.current = trpcClient;

const websocketUrl = useWorkspaceWsUrl(`/terminal/${terminalId}`);
const websocketUrlRef = useRef(websocketUrl);
websocketUrlRef.current = websocketUrl;
const workspaceIdRef = useRef(workspaceId);
workspaceIdRef.current = workspaceId;
const markInitialCommandAccepted = useCallback(() => {
initialCommandRef.current = undefined;
const currentPaneData = paneDataRef.current;
if (currentPaneData.initialCommand === undefined) return;
paneActionsRef.current.updateData({
...currentPaneData,
initialCommand: undefined,
} as PaneViewerData);
}, []);

const workspaceTrpcUtils = workspaceTrpc.useUtils();
const invalidateTerminalSessionsRef = useRef(
Expand Down Expand Up @@ -139,17 +112,17 @@ export function TerminalPane({
// is visible immediately, even on cold start. For a warm return
// (workspace switch) this reparents the wrapper from the parking
// container back into the live tree, preserving the buffer.
// 2. createSession() starts or adopts the server terminal using the
// measured dimensions and optional initial command.
// 3. connect() attaches the WebSocket to that terminalId. The socket is
// 2. connect() attaches the WebSocket to that terminalId. The socket is
// transport only; it does not carry creation-time intent.
// The pane never calls createSession — that's useV2TerminalLauncher's job,
// awaited at the call site before the pane is added to the store. By the
// time this effect runs, the host-service session already exists.
// Deps narrowed to the terminal identity so provider key remount churn
// (workspaceId/client briefly flipping while pane data catches up) doesn't
// re-run this effect. Mutable inputs are read through refs.
useEffect(() => {
const container = containerRef.current;
if (!container) return;
let cancelled = false;

terminalRuntimeRegistry.mount(
terminalId,
Expand All @@ -158,51 +131,16 @@ export function TerminalPane({
terminalInstanceId,
);

const dimensions = terminalRuntimeRegistry.getDimensions(
terminalRuntimeRegistry.connect(
terminalId,
websocketUrlRef.current,
terminalInstanceId,
);
const pendingInitialCommand = initialCommandRef.current?.trim()
? initialCommandRef.current
: undefined;

void (async () => {
try {
await trpcClientRef.current.terminal.createSession.mutate({
terminalId,
workspaceId: workspaceIdRef.current,
themeType: initialThemeTypeRef.current,
initialCommand: pendingInitialCommand,
cols: dimensions?.cols,
rows: dimensions?.rows,
});
if (cancelled) return;
if (pendingInitialCommand) markInitialCommandAccepted();
} catch (error) {
if (cancelled) return;
const message =
error instanceof Error
? error.message
: "Failed to create terminal session";
terminalRuntimeRegistry
.getTerminal(terminalId, terminalInstanceId)
?.writeln(`\r\n[terminal] ${message}`);
return;
}

if (cancelled) return;
terminalRuntimeRegistry.connect(
terminalId,
websocketUrlRef.current,
terminalInstanceId,
);
})();

return () => {
cancelled = true;
terminalRuntimeRegistry.detach(terminalId, terminalInstanceId);
};
}, [terminalId, terminalInstanceId, markInitialCommandAccepted]);
}, [terminalId, terminalInstanceId]);

const lastInvalidatedOpenSessionRef = useRef<string | null>(null);
useEffect(() => {
Expand Down
Loading
Loading