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 @@ -80,7 +80,7 @@ export function UpdateRequiredPage({
</p>

{isError && (
<p className="text-sm text-destructive">
<p className="text-sm text-destructive select-text cursor-text break-words">
{updateStatus.error || "Update check failed. Please try again."}
</p>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function useHotkey(
useHotkeys(
keys ?? "",
(e, _h) => callbackRef.current(e),
{ enableOnFormTags: true, ...options },
{ enableOnFormTags: true, enableOnContentEditable: true, ...options },
[keys],
);
return formatHotkeyDisplay(keys, PLATFORM);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
import { eq } from "@tanstack/db";
import { useLiveQuery } from "@tanstack/react-db";
import {
LuPanelRight,
LuPanelRightClose,
Expand All @@ -9,8 +11,16 @@ import { useCollections } from "renderer/routes/_authenticated/providers/Collect

export function RightSidebarToggle({ workspaceId }: { workspaceId: string }) {
const collections = useCollections();
const localState = collections.v2WorkspaceLocalState.get(workspaceId);
const isOpen = localState?.rightSidebarOpen ?? false;
const { data: localStateRows = [] } = useLiveQuery(
(query) =>
query
.from({ v2WorkspaceLocalState: collections.v2WorkspaceLocalState })
.where(({ v2WorkspaceLocalState }) =>
eq(v2WorkspaceLocalState.workspaceId, workspaceId),
),
[collections, workspaceId],
);
const isOpen = localStateRows[0]?.rightSidebarOpen ?? false;

const toggle = () => {
collections.v2WorkspaceLocalState.update(workspaceId, (draft) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,9 @@ function PendingWorkspacePage() {
<div className="space-y-4">
<div className="flex items-start gap-2 text-sm text-destructive">
<HiExclamationTriangle className="size-4 mt-0.5 shrink-0" />
<span>{pending.error ?? "Failed to create workspace"}</span>
<span className="select-text cursor-text break-words">
{pending.error ?? "Failed to create workspace"}
</span>
</div>
<div className="flex gap-2">
<button
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { describe, expect, it } from "bun:test";
import {
shouldBubbleClipboardShortcut,
shouldSelectAllShortcut,
} from "./clipboardShortcuts";

function makeEvent(
overrides: Partial<{
code: string;
metaKey: boolean;
ctrlKey: boolean;
altKey: boolean;
shiftKey: boolean;
}>,
) {
return {
code: "KeyC",
metaKey: false,
ctrlKey: false,
altKey: false,
shiftKey: false,
...overrides,
};
}

describe("shouldBubbleClipboardShortcut", () => {
it("matches the VS Code terminal clipboard bindings", () => {
const cases = [
{
name: "macOS Cmd+V",
event: makeEvent({ code: "KeyV", metaKey: true }),
options: { isMac: true, isWindows: false, hasSelection: false },
expected: true,
},
{
name: "macOS Cmd+C with selection",
event: makeEvent({ code: "KeyC", metaKey: true }),
options: { isMac: true, isWindows: false, hasSelection: true },
expected: true,
},
{
name: "windows Ctrl+V",
event: makeEvent({ code: "KeyV", ctrlKey: true }),
options: { isMac: false, isWindows: true, hasSelection: false },
expected: true,
},
{
name: "windows Ctrl+Shift+V",
event: makeEvent({ code: "KeyV", ctrlKey: true, shiftKey: true }),
options: { isMac: false, isWindows: true, hasSelection: false },
expected: true,
},
{
name: "windows Ctrl+C with selection",
event: makeEvent({ code: "KeyC", ctrlKey: true }),
options: { isMac: false, isWindows: true, hasSelection: true },
expected: true,
},
{
name: "linux Ctrl+Shift+C with selection",
event: makeEvent({ code: "KeyC", ctrlKey: true, shiftKey: true }),
options: { isMac: false, isWindows: false, hasSelection: true },
expected: true,
},
{
name: "linux Ctrl+Shift+V",
event: makeEvent({ code: "KeyV", ctrlKey: true, shiftKey: true }),
options: { isMac: false, isWindows: false, hasSelection: false },
expected: true,
},
{
name: "linux Shift+Insert",
event: makeEvent({ code: "Insert", shiftKey: true }),
options: { isMac: false, isWindows: false, hasSelection: false },
expected: true,
},
{
name: "macOS Cmd+C without selection",
event: makeEvent({ code: "KeyC", metaKey: true }),
options: { isMac: true, isWindows: false, hasSelection: false },
expected: false,
},
{
name: "windows Ctrl+C without selection",
event: makeEvent({ code: "KeyC", ctrlKey: true }),
options: { isMac: false, isWindows: true, hasSelection: false },
expected: false,
},
{
name: "linux Ctrl+Shift+C without selection",
event: makeEvent({ code: "KeyC", ctrlKey: true, shiftKey: true }),
options: { isMac: false, isWindows: false, hasSelection: false },
expected: false,
},
{
name: "linux Ctrl+Insert stays with the PTY",
event: makeEvent({ code: "Insert", ctrlKey: true }),
options: { isMac: false, isWindows: false, hasSelection: false },
expected: false,
},
{
name: "macOS does not inherit linux fallback chords",
event: makeEvent({ code: "KeyV", ctrlKey: true, shiftKey: true }),
options: { isMac: true, isWindows: false, hasSelection: false },
expected: false,
},
{
name: "macOS Shift+Insert stays with the PTY",
event: makeEvent({ code: "Insert", shiftKey: true }),
options: { isMac: true, isWindows: false, hasSelection: false },
expected: false,
},
];

for (const { name, event, options, expected } of cases) {
expect(shouldBubbleClipboardShortcut(event, options), name).toBe(
expected,
);
}
});
});

describe("shouldSelectAllShortcut", () => {
it("matches only the VS Code macOS terminal select-all binding", () => {
const cases = [
{
name: "macOS Cmd+A",
event: makeEvent({ code: "KeyA", metaKey: true }),
isMac: true,
expected: true,
},
{
name: "windows Ctrl+A is not intercepted",
event: makeEvent({ code: "KeyA", ctrlKey: true }),
isMac: false,
expected: false,
},
{
name: "macOS Cmd+Shift+A is not intercepted",
event: makeEvent({ code: "KeyA", metaKey: true, shiftKey: true }),
isMac: true,
expected: false,
},
];

for (const { name, event, isMac, expected } of cases) {
expect(shouldSelectAllShortcut(event, isMac), name).toBe(expected);
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
export interface ClipboardShortcutEvent {
code: string;
metaKey: boolean;
ctrlKey: boolean;
altKey: boolean;
shiftKey: boolean;
}

export interface ClipboardShortcutOptions {
isMac: boolean;
isWindows: boolean;
hasSelection: boolean;
}

/** Match VS Code's macOS terminal `Cmd+A` binding. */
export function shouldSelectAllShortcut(
event: ClipboardShortcutEvent,
isMac: boolean,
): boolean {
return (
isMac &&
event.code === "KeyA" &&
event.metaKey &&
!event.ctrlKey &&
!event.altKey &&
!event.shiftKey
);
}

/**
* Mirror VS Code terminal clipboard bindings so host copy/paste can run before
* xterm's kitty keyboard handler turns the chord into CSI-u input.
*/
export function shouldBubbleClipboardShortcut(
event: ClipboardShortcutEvent,
options: ClipboardShortcutOptions,
): boolean {
const { isMac, isWindows, hasSelection } = options;

const onlyMeta =
event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey;
const onlyCtrl =
event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey;
const ctrlShiftOnly =
event.ctrlKey && event.shiftKey && !event.metaKey && !event.altKey;
const onlyShift =
event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey;

if (isMac && onlyMeta) {
return event.code === "KeyV" || (hasSelection && event.code === "KeyC");
}

if (isWindows) {
if (event.code === "KeyV" && (onlyCtrl || ctrlShiftOnly)) {
return true;
}

if (hasSelection && event.code === "KeyC" && (onlyCtrl || ctrlShiftOnly)) {
return true;
}

return false;
}

if (!isMac) {
return (
(event.code === "KeyV" && ctrlShiftOnly) ||
(event.code === "Insert" && onlyShift) ||
(hasSelection && event.code === "KeyC" && ctrlShiftOnly)
);
}

return false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import {
DEFAULT_THEME_ID,
getTerminalColors,
} from "shared/themes";
import {
shouldBubbleClipboardShortcut,
shouldSelectAllShortcut,
} from "./clipboardShortcuts";
import { TERMINAL_OPTIONS } from "./config";
import { suppressQueryResponses } from "./suppressQueryResponses";

Expand Down Expand Up @@ -640,6 +644,26 @@ export function setupKeyboardHandler(
return false;
}

if (shouldSelectAllShortcut(event, isMac)) {
if (event.type === "keydown") {
event.preventDefault();
xterm.selectAll();
}
return false;
}

// Mirror VS Code terminal clipboard bindings so host copy/paste happens
// before kitty CSI-u handling in xterm consumes the command chord.
if (
shouldBubbleClipboardShortcut(event, {
isMac,
isWindows,
hasSelection: xterm.hasSelection(),
})
) {
return false;
}

// Terminal-reserved chords (ctrl+c/d/z/s/q) always go to xterm
if (isTerminalReservedEvent(event)) return true;

Expand Down
Loading