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
72 changes: 28 additions & 44 deletions apps/desktop/src/renderer/hotkeys/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,24 +101,6 @@ export const HOTKEYS_REGISTRY = {
label: "Switch to Workspace 9",
category: "Workspace",
},
PREV_WORKSPACE: {
key: {
mac: "meta+alt+up",
windows: "ctrl+shift+alt+up",
linux: "ctrl+shift+alt+up",
},
label: "Previous Workspace",
category: "Workspace",
},
NEXT_WORKSPACE: {
key: {
mac: "meta+alt+down",
windows: "ctrl+shift+alt+down",
linux: "ctrl+shift+alt+down",
},
label: "Next Workspace",
category: "Workspace",
},
CLOSE_WORKSPACE: {
key: {
mac: "meta+shift+backspace",
Expand Down Expand Up @@ -334,24 +316,6 @@ export const HOTKEYS_REGISTRY = {
category: "Terminal",
description: "Scroll the active terminal to the bottom",
},
PREV_TAB: {
key: {
mac: "meta+alt+left",
windows: "ctrl+shift+alt+left",
linux: "ctrl+shift+alt+left",
},
label: "Previous Tab",
category: "Terminal",
},
NEXT_TAB: {
key: {
mac: "meta+alt+right",
windows: "ctrl+shift+alt+right",
linux: "ctrl+shift+alt+right",
},
label: "Next Tab",
category: "Terminal",
},
PREV_TAB_ALT: {
key: {
mac: "ctrl+shift+tab",
Expand All @@ -366,25 +330,45 @@ export const HOTKEYS_REGISTRY = {
label: "Next Tab (Alt)",
category: "Terminal",
},
PREV_PANE: {
FOCUS_PANE_LEFT: {
key: {
mac: "meta+shift+left",
mac: "meta+alt+left",
windows: "ctrl+shift+alt+left",
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
linux: "ctrl+shift+alt+left",
},
label: "Previous Pane",
label: "Focus Pane Left",
category: "Terminal",
description: "Focus the previous pane in the current tab",
description: "Focus the pane to the left of the active pane",
},
NEXT_PANE: {
FOCUS_PANE_RIGHT: {
key: {
mac: "meta+shift+right",
mac: "meta+alt+right",
windows: "ctrl+shift+alt+right",
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
linux: "ctrl+shift+alt+right",
},
label: "Next Pane",
label: "Focus Pane Right",
category: "Terminal",
description: "Focus the pane to the right of the active pane",
},
FOCUS_PANE_UP: {
key: {
mac: "meta+alt+up",
windows: "ctrl+shift+alt+up",
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
linux: "ctrl+shift+alt+up",
},
label: "Focus Pane Up",
category: "Terminal",
description: "Focus the pane above the active pane",
},
FOCUS_PANE_DOWN: {
key: {
mac: "meta+alt+down",
windows: "ctrl+shift+alt+down",
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
linux: "ctrl+shift+alt+down",
},
label: "Focus Pane Down",
category: "Terminal",
description: "Focus the next pane in the current tab",
description: "Focus the pane below the active pane",
Comment on lines +363 to +371
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Resolve the Windows/Linux shortcut collision for pane-down focus.

At Line 366-367, FOCUS_PANE_DOWN uses ctrl+shift+alt+down, which is already assigned to SCROLL_TO_BOTTOM at Line 312-313. That makes the registry ambiguous and can leave one of the actions unreachable wherever both bindings are active. Please give one of these commands a distinct accelerator.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/hotkeys/registry.ts` around lines 363 - 371,
FOCUS_PANE_DOWN currently collides with SCROLL_TO_BOTTOM on Windows/Linux
because both use "ctrl+shift+alt+down"; open the hotkeys registry entry for
FOCUS_PANE_DOWN and change the windows/linux accelerator to a distinct combo
(for example "ctrl+alt+down" or "ctrl+shift+down") so it no longer matches
SCROLL_TO_BOTTOM, verify the new binding does not conflict with other entries,
and update any related documentation/tests that reference the old accelerator.

},
JUMP_TO_TAB_1: {
Comment on lines +333 to 373
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 ctrl+alt+arrow conflicts with Intel HD Graphics shortcuts on Windows

On Windows, Ctrl+Alt+Arrow (Left/Right/Up/Down) is the default hotkey for Intel HD / UHD Graphics screen-rotation shortcuts and is registered at the driver level. While users can disable this in the Intel Graphics Control Panel, it's a very common configuration that would silently swallow these keystrokes before the Electron app sees them, making the feature completely non-functional for a significant portion of Windows users.

The old PREV_TAB/NEXT_TAB used ctrl+shift+alt+left/right (three-modifier), which avoided this collision. Consider restoring the three-modifier combo for Windows/Linux:

Suggested change
FOCUS_PANE_LEFT: {
key: {
mac: "meta+alt+left",
windows: "ctrl+alt+left",
linux: "ctrl+alt+left",
},
label: "Focus Pane Left",
category: "Terminal",
description: "Focus the pane to the left of the active pane",
},
FOCUS_PANE_RIGHT: {
key: {
mac: "meta+alt+right",
windows: "ctrl+alt+right",
linux: "ctrl+alt+right",
},
label: "Focus Pane Right",
category: "Terminal",
description: "Focus the pane to the right of the active pane",
},
FOCUS_PANE_UP: {
key: {
mac: "meta+alt+up",
windows: "ctrl+alt+up",
linux: "ctrl+alt+up",
},
label: "Focus Pane Up",
category: "Terminal",
description: "Focus the pane above the active pane",
},
FOCUS_PANE_DOWN: {
key: {
mac: "meta+alt+down",
windows: "ctrl+alt+down",
linux: "ctrl+alt+down",
},
label: "Focus Pane Down",
category: "Terminal",
description: "Focus the pane below the active pane",
},
JUMP_TO_TAB_1: {
FOCUS_PANE_LEFT: {
key: {
mac: "meta+alt+left",
windows: "ctrl+shift+alt+left",
linux: "ctrl+shift+alt+left",
},
label: "Focus Pane Left",
category: "Terminal",
description: "Focus the pane to the left of the active pane",
},
FOCUS_PANE_RIGHT: {
key: {
mac: "meta+alt+right",
windows: "ctrl+shift+alt+right",
linux: "ctrl+shift+alt+right",
},
label: "Focus Pane Right",
category: "Terminal",
description: "Focus the pane to the right of the active pane",
},
FOCUS_PANE_UP: {
key: {
mac: "meta+alt+up",
windows: "ctrl+shift+alt+up",
linux: "ctrl+shift+alt+up",
},
label: "Focus Pane Up",
category: "Terminal",
description: "Focus the pane above the active pane",
},
FOCUS_PANE_DOWN: {
key: {
mac: "meta+alt+down",
windows: "ctrl+shift+alt+down",
linux: "ctrl+shift+alt+down",
},
label: "Focus Pane Down",
category: "Terminal",
description: "Focus the pane below the active pane",
},

Note: the old PREV_PANE/NEXT_PANE already use ctrl+shift+alt+left/right on Windows — you'd need to choose distinct combos (e.g., ctrl+shift+alt+up/down for vertical, or a different modifier set entirely).

key: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMatchRoute, useNavigate } from "@tanstack/react-router";
import { useNavigate } from "@tanstack/react-router";
import { useCallback, useMemo } from "react";
import { useHotkey } from "renderer/hotkeys";
import { navigateToV2Workspace } from "renderer/routes/_authenticated/_dashboard/utils/workspace-navigation";
Expand Down Expand Up @@ -48,33 +48,5 @@ export function useDashboardSidebarShortcuts(
useHotkey("JUMP_TO_WORKSPACE_8", () => switchToWorkspace(7));
useHotkey("JUMP_TO_WORKSPACE_9", () => switchToWorkspace(8));

// Prev/next workspace navigation (cycles)
const matchRoute = useMatchRoute();
const currentWorkspaceMatch = matchRoute({
to: "/v2-workspace/$workspaceId",
fuzzy: true,
});
const currentWorkspaceId =
currentWorkspaceMatch !== false ? currentWorkspaceMatch.workspaceId : null;

useHotkey("PREV_WORKSPACE", () => {
if (!currentWorkspaceId || flattenedWorkspaces.length === 0) return;
const index = flattenedWorkspaces.findIndex(
(w) => w.id === currentWorkspaceId,
);
const prevIndex = index <= 0 ? flattenedWorkspaces.length - 1 : index - 1;
navigateToV2Workspace(flattenedWorkspaces[prevIndex].id, navigate);
});

useHotkey("NEXT_WORKSPACE", () => {
if (!currentWorkspaceId || flattenedWorkspaces.length === 0) return;
const index = flattenedWorkspaces.findIndex(
(w) => w.id === currentWorkspaceId,
);
const nextIndex =
index >= flattenedWorkspaces.length - 1 || index === -1 ? 0 : index + 1;
navigateToV2Workspace(flattenedWorkspaces[nextIndex].id, navigate);
});

return workspaceShortcutLabels;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { WorkspaceStore } from "@superset/panes";
import {
type FocusDirection,
getSpatialNeighborPaneId,
type WorkspaceStore,
} from "@superset/panes";
import { useCallback } from "react";
import { useHotkey } from "renderer/hotkeys";
import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider";
Expand Down Expand Up @@ -83,23 +87,6 @@ export function useWorkspaceHotkeys({
}
});

useHotkey("PREV_TAB", () => {
const state = store.getState();
if (!state.activeTabId || state.tabs.length === 0) return;
const index = state.tabs.findIndex((t) => t.id === state.activeTabId);
const prevIndex = index <= 0 ? state.tabs.length - 1 : index - 1;
state.setActiveTab(state.tabs[prevIndex].id);
});

useHotkey("NEXT_TAB", () => {
const state = store.getState();
if (!state.activeTabId || state.tabs.length === 0) return;
const index = state.tabs.findIndex((t) => t.id === state.activeTabId);
const nextIndex =
index >= state.tabs.length - 1 || index === -1 ? 0 : index + 1;
state.setActiveTab(state.tabs[nextIndex].id);
});

useHotkey("PREV_TAB_ALT", () => {
const state = store.getState();
if (!state.activeTabId || state.tabs.length === 0) return;
Expand Down Expand Up @@ -138,25 +125,25 @@ export function useWorkspaceHotkeys({

// --- Pane management ---

useHotkey("PREV_PANE", () => {
const state = store.getState();
const tab = state.getActiveTab();
if (!tab || !tab.activePaneId) return;
const paneIds = Object.keys(tab.panes);
const index = paneIds.indexOf(tab.activePaneId);
const prevIndex = index <= 0 ? paneIds.length - 1 : index - 1;
state.setActivePane({ tabId: tab.id, paneId: paneIds[prevIndex] });
});
const moveFocusDirectional = useCallback(
(dir: FocusDirection) => {
const state = store.getState();
const tab = state.getActiveTab();
if (!tab || !tab.activePaneId) return;
const neighbor = getSpatialNeighborPaneId(
tab.layout,
tab.activePaneId,
dir,
);
if (neighbor) state.setActivePane({ tabId: tab.id, paneId: neighbor });
},
[store],
);

useHotkey("NEXT_PANE", () => {
const state = store.getState();
const tab = state.getActiveTab();
if (!tab || !tab.activePaneId) return;
const paneIds = Object.keys(tab.panes);
const index = paneIds.indexOf(tab.activePaneId);
const nextIndex = index >= paneIds.length - 1 ? 0 : index + 1;
state.setActivePane({ tabId: tab.id, paneId: paneIds[nextIndex] });
});
useHotkey("FOCUS_PANE_LEFT", () => moveFocusDirectional("left"));
useHotkey("FOCUS_PANE_RIGHT", () => moveFocusDirectional("right"));
useHotkey("FOCUS_PANE_UP", () => moveFocusDirectional("up"));
useHotkey("FOCUS_PANE_DOWN", () => moveFocusDirectional("down"));

useHotkey("SPLIT_AUTO", () => {
const state = store.getState();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ExternalApp } from "@superset/local-db";
import { createFileRoute, notFound, useNavigate } from "@tanstack/react-router";
import { createFileRoute, notFound } from "@tanstack/react-router";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useCopyToClipboard } from "renderer/hooks/useCopyToClipboard";
import { useFileOpenMode } from "renderer/hooks/useFileOpenMode";
Expand All @@ -8,7 +8,6 @@ import { electronTrpc } from "renderer/lib/electron-trpc";
import { electronTrpcClient as trpcClient } from "renderer/lib/trpc-client";
import { usePresets } from "renderer/react-query/presets";
import type { WorkspaceSearchParams } from "renderer/routes/_authenticated/_dashboard/utils/workspace-navigation";
import { navigateToWorkspace } from "renderer/routes/_authenticated/_dashboard/utils/workspace-navigation";
import { usePresetHotkeys } from "renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/hooks/usePresetHotkeys";
import { useWorkspaceRunCommand } from "renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/hooks/useWorkspaceRunCommand";
import { NotFound } from "renderer/routes/not-found";
Expand All @@ -35,8 +34,6 @@ import { useTabsWithPresets } from "renderer/stores/tabs/useTabsWithPresets";
import {
findPanePath,
getFirstPaneId,
getNextPaneId,
getPreviousPaneId,
resolveActiveTabIdForWorkspace,
} from "renderer/stores/tabs/utils";
import {
Expand Down Expand Up @@ -93,7 +90,6 @@ function WorkspacePage() {
worktreePath: workspace?.worktreePath,
enabled: Boolean(workspace?.worktreePath),
});
const navigate = useNavigate();
const routeNavigate = Route.useNavigate();
const { tabId: searchTabId, paneId: searchPaneId } = Route.useSearch();

Expand Down Expand Up @@ -228,20 +224,6 @@ function WorkspacePage() {
}
});

useHotkey("PREV_TAB", () => {
if (!activeTabId || tabs.length === 0) return;
const index = tabs.findIndex((t) => t.id === activeTabId);
const prevIndex = index <= 0 ? tabs.length - 1 : index - 1;
setActiveTab(workspaceId, tabs[prevIndex].id);
});

useHotkey("NEXT_TAB", () => {
if (!activeTabId || tabs.length === 0) return;
const index = tabs.findIndex((t) => t.id === activeTabId);
const nextIndex = index >= tabs.length - 1 || index === -1 ? 0 : index + 1;
setActiveTab(workspaceId, tabs[nextIndex].id);
});

useHotkey("PREV_TAB_ALT", () => {
if (!activeTabId || tabs.length === 0) return;
const index = tabs.findIndex((t) => t.id === activeTabId);
Expand Down Expand Up @@ -276,22 +258,6 @@ function WorkspacePage() {
useHotkey("JUMP_TO_TAB_8", () => switchToTab(7));
useHotkey("JUMP_TO_TAB_9", () => switchToTab(8));

useHotkey("PREV_PANE", () => {
if (!activeTabId || !activeTab?.layout || !focusedPaneId) return;
const prevPaneId = getPreviousPaneId(activeTab.layout, focusedPaneId);
if (prevPaneId) {
setFocusedPane(activeTabId, prevPaneId);
}
});

useHotkey("NEXT_PANE", () => {
if (!activeTabId || !activeTab?.layout || !focusedPaneId) return;
const nextPaneId = getNextPaneId(activeTab.layout, focusedPaneId);
if (nextPaneId) {
setFocusedPane(activeTabId, nextPaneId);
}
});

// Open in last used app shortcut
const projectId = workspace?.projectId;
const { data: defaultApp } = electronTrpc.projects.getDefaultApp.useQuery(
Expand Down Expand Up @@ -427,31 +393,6 @@ function WorkspacePage() {
}
});

// Navigate to previous workspace (⌘↑)
const getPreviousWorkspace =
electronTrpc.workspaces.getPreviousWorkspace.useQuery(
{ id: workspaceId },
{ enabled: !!workspaceId },
);
useHotkey("PREV_WORKSPACE", () => {
const prevWorkspaceId = getPreviousWorkspace.data;
if (prevWorkspaceId) {
navigateToWorkspace(prevWorkspaceId, navigate);
}
});

// Navigate to next workspace (⌘↓)
const getNextWorkspace = electronTrpc.workspaces.getNextWorkspace.useQuery(
{ id: workspaceId },
{ enabled: !!workspaceId },
);
useHotkey("NEXT_WORKSPACE", () => {
const nextWorkspaceId = getNextWorkspace.data;
if (nextWorkspaceId) {
navigateToWorkspace(nextWorkspaceId, navigate);
}
});

return (
<div className="flex-1 h-full flex flex-col overflow-hidden">
<div className="flex-1 min-h-0 flex overflow-hidden">
Expand Down
36 changes: 0 additions & 36 deletions apps/desktop/src/renderer/stores/tabs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,42 +506,6 @@ export const getFirstPaneId = (layout: MosaicNode<string>): string => {
return getFirstPaneId(layout.first);
};

/**
* Gets the next pane ID in visual order (left-to-right, top-to-bottom),
* wrapping around to the first if at the end.
*/
export const getNextPaneId = (
layout: MosaicNode<string>,
currentPaneId: string,
): string | null => {
const paneIds = getPaneIdsInVisualOrder(layout);
if (paneIds.length <= 1) return null;

const currentIndex = paneIds.indexOf(currentPaneId);
if (currentIndex === -1) return paneIds[0];

const nextIndex = (currentIndex + 1) % paneIds.length;
return paneIds[nextIndex];
};

/**
* Gets the previous pane ID in visual order (right-to-left, bottom-to-top),
* wrapping around to the last if at the beginning.
*/
export const getPreviousPaneId = (
layout: MosaicNode<string>,
currentPaneId: string,
): string | null => {
const paneIds = getPaneIdsInVisualOrder(layout);
if (paneIds.length <= 1) return null;

const currentIndex = paneIds.indexOf(currentPaneId);
if (currentIndex === -1) return paneIds[paneIds.length - 1];

const prevIndex = (currentIndex - 1 + paneIds.length) % paneIds.length;
return paneIds[prevIndex];
};

/**
* Gets the adjacent pane ID for focus fallback when a pane is closed.
* Prefers the next pane in visual order, falls back to previous if at the end.
Expand Down
3 changes: 3 additions & 0 deletions packages/panes/src/core/store/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
export type { FocusDirection } from "./utils";
export {
equalizeAllSplits,
findFirstPaneId,
findPaneInLayout,
findPanePath,
findSiblingPaneId,
generateId,
getNodeAtPath,
getOtherBranch,
getSpatialNeighborPaneId,
positionToDirection,
removePaneFromLayout,
replacePaneIdInLayout,
Expand Down
Loading
Loading