fix(workspace): suppress pane focus when moving tabs between panes
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
|||||||
type ReactNode,
|
type ReactNode,
|
||||||
type SetStateAction,
|
type SetStateAction,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
import { useStableEvent } from "@/hooks/use-stable-event";
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
DragOverlay,
|
DragOverlay,
|
||||||
@@ -714,6 +715,7 @@ function SplitPaneView({
|
|||||||
}: SplitPaneViewProps) {
|
}: SplitPaneViewProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const paneRef = useRef<View | null>(null);
|
const paneRef = useRef<View | null>(null);
|
||||||
|
const stableOnFocusPane = useStableEvent(onFocusPane);
|
||||||
const paneState = useMemo(
|
const paneState = useMemo(
|
||||||
() =>
|
() =>
|
||||||
deriveWorkspacePaneState({
|
deriveWorkspacePaneState({
|
||||||
@@ -743,7 +745,7 @@ function SplitPaneView({
|
|||||||
tab: activeTabDescriptor,
|
tab: activeTabDescriptor,
|
||||||
})
|
})
|
||||||
: null,
|
: null,
|
||||||
[activeTabDescriptor, buildPaneContentModel, pane.id],
|
[activeTabDescriptor, buildPaneContentModel, isFocused, pane.id],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -764,14 +766,14 @@ function SplitPaneView({
|
|||||||
if (!shouldFocusPaneFromEventTarget(event.target)) {
|
if (!shouldFocusPaneFromEventTarget(event.target)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onFocusPane(pane.id);
|
stableOnFocusPane(pane.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePaneFocusIn = (event: FocusEvent) => {
|
const handlePaneFocusIn = (event: FocusEvent) => {
|
||||||
if (!shouldFocusPaneFromEventTarget(event.target)) {
|
if (!shouldFocusPaneFromEventTarget(event.target)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onFocusPane(pane.id);
|
stableOnFocusPane(pane.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
paneElement.addEventListener("pointerdown", handlePanePointerDown, true);
|
paneElement.addEventListener("pointerdown", handlePanePointerDown, true);
|
||||||
@@ -781,7 +783,7 @@ function SplitPaneView({
|
|||||||
paneElement.removeEventListener("pointerdown", handlePanePointerDown, true);
|
paneElement.removeEventListener("pointerdown", handlePanePointerDown, true);
|
||||||
paneElement.removeEventListener("focusin", handlePaneFocusIn, true);
|
paneElement.removeEventListener("focusin", handlePaneFocusIn, true);
|
||||||
};
|
};
|
||||||
}, [onFocusPane, pane.id]);
|
}, [stableOnFocusPane, pane.id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View ref={paneRef} collapsable={false} style={styles.pane}>
|
<View ref={paneRef} collapsable={false} style={styles.pane}>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ interface TerminalPaneProps {
|
|||||||
serverId: string;
|
serverId: string;
|
||||||
cwd: string;
|
cwd: string;
|
||||||
terminalId: string;
|
terminalId: string;
|
||||||
|
isPaneFocused: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TERMINAL_REFIT_DELAYS_MS = [0, 48, 144, 320];
|
const TERMINAL_REFIT_DELAYS_MS = [0, 48, 144, 320];
|
||||||
@@ -82,6 +83,7 @@ export function TerminalPane({
|
|||||||
serverId,
|
serverId,
|
||||||
cwd,
|
cwd,
|
||||||
terminalId,
|
terminalId,
|
||||||
|
isPaneFocused,
|
||||||
}: TerminalPaneProps) {
|
}: TerminalPaneProps) {
|
||||||
const isScreenFocused = useIsFocused();
|
const isScreenFocused = useIsFocused();
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
@@ -127,7 +129,7 @@ export function TerminalPane({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isMobile || !isScreenFocused || !terminalId) {
|
if (isMobile || !isScreenFocused || !isPaneFocused || !terminalId) {
|
||||||
lastAutoFocusKeyRef.current = null;
|
lastAutoFocusKeyRef.current = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -139,7 +141,7 @@ export function TerminalPane({
|
|||||||
|
|
||||||
lastAutoFocusKeyRef.current = nextFocusKey;
|
lastAutoFocusKeyRef.current = nextFocusKey;
|
||||||
requestTerminalFocus();
|
requestTerminalFocus();
|
||||||
}, [isMobile, isScreenFocused, requestTerminalFocus, scopeKey, terminalId]);
|
}, [isMobile, isPaneFocused, isScreenFocused, requestTerminalFocus, scopeKey, terminalId]);
|
||||||
|
|
||||||
const clearKeyboardRefitTimeouts = useCallback(() => {
|
const clearKeyboardRefitTimeouts = useCallback(() => {
|
||||||
if (keyboardRefitTimeoutsRef.current.length === 0) {
|
if (keyboardRefitTimeoutsRef.current.length === 0) {
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
|
|||||||
id: "workspace-pane-split-right-cmd-backslash",
|
id: "workspace-pane-split-right-cmd-backslash",
|
||||||
action: "workspace.pane.split.right",
|
action: "workspace.pane.split.right",
|
||||||
combo: "Cmd+\\",
|
combo: "Cmd+\\",
|
||||||
when: { mac: true, terminal: false, commandCenter: false },
|
when: { mac: true, commandCenter: false },
|
||||||
help: {
|
help: {
|
||||||
id: "workspace-pane-split-right",
|
id: "workspace-pane-split-right",
|
||||||
section: "tabs-panes",
|
section: "tabs-panes",
|
||||||
@@ -451,7 +451,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
|
|||||||
id: "workspace-pane-split-down-cmd-shift-backslash",
|
id: "workspace-pane-split-down-cmd-shift-backslash",
|
||||||
action: "workspace.pane.split.down",
|
action: "workspace.pane.split.down",
|
||||||
combo: "Cmd+Shift+\\",
|
combo: "Cmd+Shift+\\",
|
||||||
when: { mac: true, terminal: false, commandCenter: false },
|
when: { mac: true, commandCenter: false },
|
||||||
help: {
|
help: {
|
||||||
id: "workspace-pane-split-down",
|
id: "workspace-pane-split-down",
|
||||||
section: "tabs-panes",
|
section: "tabs-panes",
|
||||||
@@ -463,7 +463,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
|
|||||||
id: "workspace-pane-focus-left-cmd-shift-left",
|
id: "workspace-pane-focus-left-cmd-shift-left",
|
||||||
action: "workspace.pane.focus.left",
|
action: "workspace.pane.focus.left",
|
||||||
combo: "Cmd+Shift+ArrowLeft",
|
combo: "Cmd+Shift+ArrowLeft",
|
||||||
when: { mac: true, terminal: false, commandCenter: false },
|
when: { mac: true, commandCenter: false },
|
||||||
help: {
|
help: {
|
||||||
id: "workspace-pane-focus-left",
|
id: "workspace-pane-focus-left",
|
||||||
section: "tabs-panes",
|
section: "tabs-panes",
|
||||||
@@ -475,7 +475,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
|
|||||||
id: "workspace-pane-focus-right-cmd-shift-right",
|
id: "workspace-pane-focus-right-cmd-shift-right",
|
||||||
action: "workspace.pane.focus.right",
|
action: "workspace.pane.focus.right",
|
||||||
combo: "Cmd+Shift+ArrowRight",
|
combo: "Cmd+Shift+ArrowRight",
|
||||||
when: { mac: true, terminal: false, commandCenter: false },
|
when: { mac: true, commandCenter: false },
|
||||||
help: {
|
help: {
|
||||||
id: "workspace-pane-focus-right",
|
id: "workspace-pane-focus-right",
|
||||||
section: "tabs-panes",
|
section: "tabs-panes",
|
||||||
@@ -487,7 +487,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
|
|||||||
id: "workspace-pane-focus-up-cmd-shift-up",
|
id: "workspace-pane-focus-up-cmd-shift-up",
|
||||||
action: "workspace.pane.focus.up",
|
action: "workspace.pane.focus.up",
|
||||||
combo: "Cmd+Shift+ArrowUp",
|
combo: "Cmd+Shift+ArrowUp",
|
||||||
when: { mac: true, terminal: false, commandCenter: false },
|
when: { mac: true, commandCenter: false },
|
||||||
help: {
|
help: {
|
||||||
id: "workspace-pane-focus-up",
|
id: "workspace-pane-focus-up",
|
||||||
section: "tabs-panes",
|
section: "tabs-panes",
|
||||||
@@ -499,7 +499,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
|
|||||||
id: "workspace-pane-focus-down-cmd-shift-down",
|
id: "workspace-pane-focus-down-cmd-shift-down",
|
||||||
action: "workspace.pane.focus.down",
|
action: "workspace.pane.focus.down",
|
||||||
combo: "Cmd+Shift+ArrowDown",
|
combo: "Cmd+Shift+ArrowDown",
|
||||||
when: { mac: true, terminal: false, commandCenter: false },
|
when: { mac: true, commandCenter: false },
|
||||||
help: {
|
help: {
|
||||||
id: "workspace-pane-focus-down",
|
id: "workspace-pane-focus-down",
|
||||||
section: "tabs-panes",
|
section: "tabs-panes",
|
||||||
@@ -511,7 +511,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
|
|||||||
id: "workspace-pane-move-tab-left-cmd-shift-alt-left",
|
id: "workspace-pane-move-tab-left-cmd-shift-alt-left",
|
||||||
action: "workspace.pane.move-tab.left",
|
action: "workspace.pane.move-tab.left",
|
||||||
combo: "Cmd+Alt+Shift+ArrowLeft",
|
combo: "Cmd+Alt+Shift+ArrowLeft",
|
||||||
when: { mac: true, terminal: false, commandCenter: false },
|
when: { mac: true, commandCenter: false },
|
||||||
help: {
|
help: {
|
||||||
id: "workspace-pane-move-tab-left",
|
id: "workspace-pane-move-tab-left",
|
||||||
section: "tabs-panes",
|
section: "tabs-panes",
|
||||||
@@ -523,7 +523,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
|
|||||||
id: "workspace-pane-move-tab-right-cmd-shift-alt-right",
|
id: "workspace-pane-move-tab-right-cmd-shift-alt-right",
|
||||||
action: "workspace.pane.move-tab.right",
|
action: "workspace.pane.move-tab.right",
|
||||||
combo: "Cmd+Alt+Shift+ArrowRight",
|
combo: "Cmd+Alt+Shift+ArrowRight",
|
||||||
when: { mac: true, terminal: false, commandCenter: false },
|
when: { mac: true, commandCenter: false },
|
||||||
help: {
|
help: {
|
||||||
id: "workspace-pane-move-tab-right",
|
id: "workspace-pane-move-tab-right",
|
||||||
section: "tabs-panes",
|
section: "tabs-panes",
|
||||||
@@ -535,7 +535,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
|
|||||||
id: "workspace-pane-move-tab-up-cmd-shift-alt-up",
|
id: "workspace-pane-move-tab-up-cmd-shift-alt-up",
|
||||||
action: "workspace.pane.move-tab.up",
|
action: "workspace.pane.move-tab.up",
|
||||||
combo: "Cmd+Alt+Shift+ArrowUp",
|
combo: "Cmd+Alt+Shift+ArrowUp",
|
||||||
when: { mac: true, terminal: false, commandCenter: false },
|
when: { mac: true, commandCenter: false },
|
||||||
help: {
|
help: {
|
||||||
id: "workspace-pane-move-tab-up",
|
id: "workspace-pane-move-tab-up",
|
||||||
section: "tabs-panes",
|
section: "tabs-panes",
|
||||||
@@ -547,7 +547,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
|
|||||||
id: "workspace-pane-move-tab-down-cmd-shift-alt-down",
|
id: "workspace-pane-move-tab-down-cmd-shift-alt-down",
|
||||||
action: "workspace.pane.move-tab.down",
|
action: "workspace.pane.move-tab.down",
|
||||||
combo: "Cmd+Alt+Shift+ArrowDown",
|
combo: "Cmd+Alt+Shift+ArrowDown",
|
||||||
when: { mac: true, terminal: false, commandCenter: false },
|
when: { mac: true, commandCenter: false },
|
||||||
help: {
|
help: {
|
||||||
id: "workspace-pane-move-tab-down",
|
id: "workspace-pane-move-tab-down",
|
||||||
section: "tabs-panes",
|
section: "tabs-panes",
|
||||||
@@ -559,7 +559,7 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
|
|||||||
id: "workspace-pane-close-cmd-shift-w",
|
id: "workspace-pane-close-cmd-shift-w",
|
||||||
action: "workspace.pane.close",
|
action: "workspace.pane.close",
|
||||||
combo: "Cmd+Shift+W",
|
combo: "Cmd+Shift+W",
|
||||||
when: { mac: true, terminal: false, commandCenter: false },
|
when: { mac: true, commandCenter: false },
|
||||||
help: {
|
help: {
|
||||||
id: "workspace-pane-close",
|
id: "workspace-pane-close",
|
||||||
section: "tabs-panes",
|
section: "tabs-panes",
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ function useTerminalPanelDescriptor(
|
|||||||
|
|
||||||
function TerminalPanel() {
|
function TerminalPanel() {
|
||||||
const isFocused = useIsFocused();
|
const isFocused = useIsFocused();
|
||||||
const { serverId, workspaceId, target } = usePaneContext();
|
const { serverId, workspaceId, target, isPaneFocused } = usePaneContext();
|
||||||
invariant(target.kind === "terminal", "TerminalPanel requires terminal target");
|
invariant(target.kind === "terminal", "TerminalPanel requires terminal target");
|
||||||
|
|
||||||
if (!isFocused) {
|
if (!isFocused) {
|
||||||
@@ -61,6 +61,7 @@ function TerminalPanel() {
|
|||||||
serverId={serverId}
|
serverId={serverId}
|
||||||
cwd={workspaceId}
|
cwd={workspaceId}
|
||||||
terminalId={target.terminalId}
|
terminalId={target.terminalId}
|
||||||
|
isPaneFocused={isPaneFocused}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ import type { ListTerminalsResponse } from "@server/shared/messages";
|
|||||||
import { upsertTerminalListEntry } from "@/utils/terminal-list";
|
import { upsertTerminalListEntry } from "@/utils/terminal-list";
|
||||||
import { confirmDialog } from "@/utils/confirm-dialog";
|
import { confirmDialog } from "@/utils/confirm-dialog";
|
||||||
import { useArchiveAgent } from "@/hooks/use-archive-agent";
|
import { useArchiveAgent } from "@/hooks/use-archive-agent";
|
||||||
|
import { useStableEvent } from "@/hooks/use-stable-event";
|
||||||
import { buildProviderCommand } from "@/utils/provider-command-templates";
|
import { buildProviderCommand } from "@/utils/provider-command-templates";
|
||||||
import { generateDraftId } from "@/stores/draft-keys";
|
import { generateDraftId } from "@/stores/draft-keys";
|
||||||
import {
|
import {
|
||||||
@@ -740,6 +741,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
|
|||||||
const splitWorkspacePaneEmpty = useWorkspaceLayoutStore((state) => state.splitPaneEmpty);
|
const splitWorkspacePaneEmpty = useWorkspaceLayoutStore((state) => state.splitPaneEmpty);
|
||||||
const moveWorkspaceTabToPane = useWorkspaceLayoutStore((state) => state.moveTabToPane);
|
const moveWorkspaceTabToPane = useWorkspaceLayoutStore((state) => state.moveTabToPane);
|
||||||
const focusWorkspacePane = useWorkspaceLayoutStore((state) => state.focusPane);
|
const focusWorkspacePane = useWorkspaceLayoutStore((state) => state.focusPane);
|
||||||
|
const paneFocusSuppressedRef = useRef(false);
|
||||||
const resizeWorkspaceSplit = useWorkspaceLayoutStore((state) => state.resizeSplit);
|
const resizeWorkspaceSplit = useWorkspaceLayoutStore((state) => state.resizeSplit);
|
||||||
const reorderWorkspaceTabsInPane = useWorkspaceLayoutStore((state) => state.reorderTabsInPane);
|
const reorderWorkspaceTabsInPane = useWorkspaceLayoutStore((state) => state.reorderTabsInPane);
|
||||||
const pinnedAgentIds = useWorkspaceLayoutStore((state) =>
|
const pinnedAgentIds = useWorkspaceLayoutStore((state) =>
|
||||||
@@ -1556,7 +1558,11 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
|
|||||||
const activePaneTabId = focusedPaneTabState.activeTabId;
|
const activePaneTabId = focusedPaneTabState.activeTabId;
|
||||||
const adjacentPaneId = findAdjacentPane(workspaceLayout.root, focusedPane.id, direction);
|
const adjacentPaneId = findAdjacentPane(workspaceLayout.root, focusedPane.id, direction);
|
||||||
if (activePaneTabId && adjacentPaneId) {
|
if (activePaneTabId && adjacentPaneId) {
|
||||||
|
paneFocusSuppressedRef.current = true;
|
||||||
moveWorkspaceTabToPane(persistenceKey, activePaneTabId, adjacentPaneId);
|
moveWorkspaceTabToPane(persistenceKey, activePaneTabId, adjacentPaneId);
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
paneFocusSuppressedRef.current = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -1790,15 +1796,12 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
|
|||||||
[buildPaneContentModel],
|
[buildPaneContentModel],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFocusPane = useCallback(
|
const handleFocusPane = useStableEvent(function handleFocusPane(paneId: string) {
|
||||||
function handleFocusPane(paneId: string) {
|
if (!persistenceKey || paneFocusSuppressedRef.current) {
|
||||||
if (!persistenceKey) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
focusWorkspacePane(persistenceKey, paneId);
|
||||||
focusWorkspacePane(persistenceKey, paneId);
|
});
|
||||||
},
|
|
||||||
[focusWorkspacePane, persistenceKey],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSplitPane = useCallback(
|
const handleSplitPane = useCallback(
|
||||||
function handleSplitPane(input: {
|
function handleSplitPane(input: {
|
||||||
|
|||||||
Reference in New Issue
Block a user