Skip to content

Commit 8bff776

Browse files
authored
fix: add back shift-tab support (#1805)
Closes #1773
1 parent de92789 commit 8bff776

3 files changed

Lines changed: 77 additions & 24 deletions

File tree

apps/code/src/renderer/features/message-editor/components/PromptInput.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { SessionConfigOption } from "@agentclientprotocol/sdk";
33
import { ArrowUp, Stop } from "@phosphor-icons/react";
44
import { InputGroup, InputGroupAddon, InputGroupButton } from "@posthog/quill";
55
import { Flex, Text, Tooltip } from "@radix-ui/themes";
6+
import { cycleModeOption } from "@renderer/features/sessions/stores/sessionStore";
67
import { EditorContent } from "@tiptap/react";
78
import { hasOpenOverlay } from "@utils/overlay";
89
import { forwardRef, useCallback, useEffect, useImperativeHandle } from "react";
@@ -188,6 +189,27 @@ export const PromptInput = forwardRef<EditorHandle, PromptInputProps>(
188189
[isActiveSession, isLoading, onCancel],
189190
);
190191

192+
useHotkeys(
193+
"shift+tab",
194+
(e) => {
195+
if (!editor?.isFocused) return;
196+
if (hasOpenOverlay()) return;
197+
if (!modeOption || !onModeChange) return;
198+
const nextMode = cycleModeOption(modeOption, {
199+
allowBypassPermissions,
200+
});
201+
if (!nextMode) return;
202+
e.preventDefault();
203+
onModeChange(nextMode);
204+
},
205+
{
206+
enableOnFormTags: true,
207+
enableOnContentEditable: true,
208+
enabled: !disabled && !!modeOption && !!onModeChange,
209+
},
210+
[editor, modeOption, onModeChange, allowBypassPermissions, disabled],
211+
);
212+
191213
const handleContainerClick = useCallback(
192214
(e: React.MouseEvent) => {
193215
const target = e.target as HTMLElement;

apps/code/src/renderer/features/sessions/stores/sessionStore.test.ts

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,49 @@ function createModeOption(
1919
} as SessionConfigOption;
2020
}
2121

22-
describe("cycleModeOption", () => {
23-
it("cycles through auto-accept permissions for claude", () => {
24-
const option = createModeOption("plan", [
25-
"default",
26-
"acceptEdits",
27-
"plan",
28-
"bypassPermissions",
29-
]);
30-
31-
expect(cycleModeOption(option)).toBe("bypassPermissions");
32-
});
22+
const CLAUDE_MODES = ["default", "acceptEdits", "plan", "bypassPermissions"];
23+
const CODEX_MODES = ["read-only", "auto", "full-access"];
3324

34-
it("cycles through full access for codex", () => {
35-
const option = createModeOption("auto", [
36-
"read-only",
37-
"auto",
38-
"full-access",
39-
]);
25+
describe("cycleModeOption", () => {
26+
it.each([
27+
{
28+
name: "claude: advances to next mode when bypass allowed",
29+
values: CLAUDE_MODES,
30+
currentValue: "plan",
31+
allowBypassPermissions: true,
32+
expected: "bypassPermissions",
33+
},
34+
{
35+
name: "codex: advances to next mode when bypass allowed",
36+
values: CODEX_MODES,
37+
currentValue: "auto",
38+
allowBypassPermissions: true,
39+
expected: "full-access",
40+
},
41+
{
42+
name: "claude: skips bypassPermissions when not allowed",
43+
values: CLAUDE_MODES,
44+
currentValue: "acceptEdits",
45+
allowBypassPermissions: false,
46+
expected: "plan",
47+
},
48+
{
49+
name: "claude: wraps past bypassPermissions back to default",
50+
values: CLAUDE_MODES,
51+
currentValue: "plan",
52+
allowBypassPermissions: false,
53+
expected: "default",
54+
},
55+
{
56+
name: "codex: skips full-access when not allowed",
57+
values: CODEX_MODES,
58+
currentValue: "auto",
59+
allowBypassPermissions: false,
60+
expected: "read-only",
61+
},
62+
])("$name", ({ values, currentValue, allowBypassPermissions, expected }) => {
63+
const option = createModeOption(currentValue, values);
4064

41-
expect(cycleModeOption(option)).toBe("full-access");
65+
expect(cycleModeOption(option, { allowBypassPermissions })).toBe(expected);
4266
});
4367
});

apps/code/src/renderer/features/sessions/stores/sessionStore.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,19 +148,26 @@ export function getConfigOptionByCategory(
148148
*/
149149
export function cycleModeOption(
150150
modeOption: SessionConfigOption | undefined,
151+
options?: { allowBypassPermissions?: boolean },
151152
): string | undefined {
152153
if (!modeOption || modeOption.type !== "select") return undefined;
153154

154155
const allOptions = flattenSelectOptions(modeOption.options);
155-
if (allOptions.length === 0) return undefined;
156-
157-
const currentIndex = allOptions.findIndex(
156+
const filtered = options?.allowBypassPermissions
157+
? allOptions
158+
: allOptions.filter(
159+
(opt) =>
160+
opt.value !== "bypassPermissions" && opt.value !== "full-access",
161+
);
162+
if (filtered.length === 0) return undefined;
163+
164+
const currentIndex = filtered.findIndex(
158165
(opt) => opt.value === modeOption.currentValue,
159166
);
160-
if (currentIndex === -1) return allOptions[0]?.value;
167+
if (currentIndex === -1) return filtered[0]?.value;
161168

162-
const nextIndex = (currentIndex + 1) % allOptions.length;
163-
return allOptions[nextIndex]?.value;
169+
const nextIndex = (currentIndex + 1) % filtered.length;
170+
return filtered[nextIndex]?.value;
164171
}
165172

166173
/**

0 commit comments

Comments
 (0)