mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-19 16:28:28 +00:00
feat use tab on windows instead of shift+tab
This commit is contained in:
parent
7935482c3a
commit
3296785b23
16 changed files with 167 additions and 138 deletions
|
|
@ -20,7 +20,7 @@ Qwen Code offers three distinct permission modes that allow you to flexibly cont
|
|||
|
||||
> [!tip]
|
||||
>
|
||||
> You can quickly cycle through modes during a session using **Shift+Tab**. The terminal status bar shows your current mode, so you always know what permissions Qwen Code has.
|
||||
> You can quickly cycle through modes during a session using **Shift+Tab** (or **Tab** on Windows). The terminal status bar shows your current mode, so you always know what permissions Qwen Code has.
|
||||
|
||||
## 1. Use Plan Mode for safe code analysis
|
||||
|
||||
|
|
@ -36,9 +36,9 @@ Plan Mode instructs Qwen Code to create a plan by analyzing the codebase with **
|
|||
|
||||
**Turn on Plan Mode during a session**
|
||||
|
||||
You can switch into Plan Mode during a session using **Shift+Tab** to cycle through permission modes.
|
||||
You can switch into Plan Mode during a session using **Shift+Tab** (or **Tab** on Windows) to cycle through permission modes.
|
||||
|
||||
If you are in Normal Mode, **Shift+Tab** first switches into `auto-edits` Mode, indicated by `⏵⏵ accept edits on` at the bottom of the terminal. A subsequent **Shift+Tab** will switch into Plan Mode, indicated by `⏸ plan mode`.
|
||||
If you are in Normal Mode, **Shift+Tab** (or **Tab** on Windows) first switches into `auto-edits` Mode, indicated by `⏵⏵ accept edits on` at the bottom of the terminal. A subsequent **Shift+Tab** (or **Tab** on Windows) will switch into Plan Mode, indicated by `⏸ plan mode`.
|
||||
|
||||
**Start a new session in Plan Mode**
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ Default Mode is the standard way to work with Qwen Code. In this mode, you maint
|
|||
|
||||
**Turn on Default Mode during a session**
|
||||
|
||||
You can switch into Default Mode during a session using **Shift+Tab** to cycle through permission modes. If you're in any other mode, pressing **Shift+Tab** will eventually cycle back to Default Mode, indicated by the absence of any mode indicator at the bottom of the terminal.
|
||||
You can switch into Default Mode during a session using **Shift+Tab** (or **Tab** on Windows) to cycle through permission modes. If you're in any other mode, pressing **Shift+Tab** (or **Tab** on Windows) will eventually cycle back to Default Mode, indicated by the absence of any mode indicator at the bottom of the terminal.
|
||||
|
||||
**Start a new session in Default Mode**
|
||||
|
||||
|
|
@ -164,7 +164,7 @@ Auto-Edit Mode instructs Qwen Code to automatically approve file edits while req
|
|||
/approval-mode auto-edit
|
||||
|
||||
# Or use keyboard shortcut
|
||||
Shift+Tab # Switch from other modes
|
||||
Shift+Tab (or Tab on Windows) # Switch from other modes
|
||||
```
|
||||
|
||||
### Workflow Example
|
||||
|
|
@ -235,7 +235,7 @@ qwen --prompt "Run the test suite, fix all failing tests, then commit changes"
|
|||
|
||||
### Keyboard Shortcut Switching
|
||||
|
||||
During a Qwen Code session, use **Shift+Tab** to quickly cycle through the three modes:
|
||||
During a Qwen Code session, use **Shift+Tab** (or **Tab** on Windows) to quickly cycle through the three modes:
|
||||
|
||||
```
|
||||
Default Mode → Auto-Edit Mode → YOLO Mode → Plan Mode → Default Mode
|
||||
|
|
|
|||
|
|
@ -4,16 +4,16 @@ This document lists the available keyboard shortcuts in Qwen Code.
|
|||
|
||||
## General
|
||||
|
||||
| Shortcut | Description |
|
||||
| ----------- | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Esc` | Close dialogs and suggestions. |
|
||||
| `Ctrl+C` | Cancel the ongoing request and clear the input. Press twice to exit the application. |
|
||||
| `Ctrl+D` | Exit the application if the input is empty. Press twice to confirm. |
|
||||
| `Ctrl+L` | Clear the screen. |
|
||||
| `Ctrl+O` | Toggle the display of the debug console. |
|
||||
| `Ctrl+S` | Allows long responses to print fully, disabling truncation. Use your terminal's scrollback to view the entire output. |
|
||||
| `Ctrl+T` | Toggle the display of tool descriptions. |
|
||||
| `Shift+Tab` | Cycle approval modes (`plan` → `default` → `auto-edit` → `yolo`). |
|
||||
| Shortcut | Description |
|
||||
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Esc` | Close dialogs and suggestions. |
|
||||
| `Ctrl+C` | Cancel the ongoing request and clear the input. Press twice to exit the application. |
|
||||
| `Ctrl+D` | Exit the application if the input is empty. Press twice to confirm. |
|
||||
| `Ctrl+L` | Clear the screen. |
|
||||
| `Ctrl+O` | Toggle the display of the debug console. |
|
||||
| `Ctrl+S` | Allows long responses to print fully, disabling truncation. Use your terminal's scrollback to view the entire output. |
|
||||
| `Ctrl+T` | Toggle the display of tool descriptions. |
|
||||
| `Shift+Tab` (`Tab` on Windows) | Cycle approval modes (`plan` → `default` → `auto-edit` → `yolo`) |
|
||||
|
||||
## Input Prompt
|
||||
|
||||
|
|
|
|||
3
package-lock.json
generated
3
package-lock.json
generated
|
|
@ -3879,6 +3879,7 @@
|
|||
"version": "2.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.4.9.tgz",
|
||||
"integrity": "sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
|
|
@ -17348,7 +17349,6 @@
|
|||
"@iarna/toml": "^2.2.5",
|
||||
"@modelcontextprotocol/sdk": "^1.25.1",
|
||||
"@qwen-code/qwen-code-core": "file:../core",
|
||||
"@types/prompts": "^2.4.9",
|
||||
"@types/update-notifier": "^6.0.8",
|
||||
"ansi-regex": "^6.2.2",
|
||||
"command-exists": "^1.2.9",
|
||||
|
|
@ -17392,6 +17392,7 @@
|
|||
"@types/diff": "^7.0.2",
|
||||
"@types/dotenv": "^6.1.1",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/prompts": "^2.4.9",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@types/semver": "^7.7.0",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export default {
|
|||
'auto-accept edits': 'Änderungen automatisch akzeptieren',
|
||||
'Accepting edits': 'Änderungen werden akzeptiert',
|
||||
'(shift + tab to cycle)': '(Umschalt + Tab zum Wechseln)',
|
||||
'(tab to cycle)': '(Tab zum Wechseln)',
|
||||
'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).':
|
||||
'Shell-Befehle über {{symbol}} ausführen (z.B. {{example1}}) oder natürliche Sprache verwenden (z.B. {{example2}}).',
|
||||
'!': '!',
|
||||
|
|
@ -1358,4 +1359,8 @@ export default {
|
|||
'Erweiterungsseite wird im Browser geöffnet: {{url}}',
|
||||
'Failed to open browser. Check out the extensions gallery at {{url}}':
|
||||
'Browser konnte nicht geöffnet werden. Besuchen Sie die Erweiterungsgalerie unter {{url}}',
|
||||
'You can switch permission mode quickly with Shift+Tab or /approval-mode.':
|
||||
'Sie können den Berechtigungsmodus schnell mit Shift+Tab oder /approval-mode wechseln.',
|
||||
'You can switch permission mode quickly with Tab or /approval-mode.':
|
||||
'Sie können den Berechtigungsmodus schnell mit Tab oder /approval-mode wechseln.',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export default {
|
|||
'auto-accept edits': 'auto-accept edits',
|
||||
'Accepting edits': 'Accepting edits',
|
||||
'(shift + tab to cycle)': '(shift + tab to cycle)',
|
||||
'(tab to cycle)': '(tab to cycle)',
|
||||
'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).':
|
||||
'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).',
|
||||
'!': '!',
|
||||
|
|
@ -1091,6 +1092,8 @@ export default {
|
|||
'You can resume a previous conversation by running qwen --continue or qwen --resume.',
|
||||
'You can switch permission mode quickly with Shift+Tab or /approval-mode.':
|
||||
'You can switch permission mode quickly with Shift+Tab or /approval-mode.',
|
||||
'You can switch permission mode quickly with Tab or /approval-mode.':
|
||||
'You can switch permission mode quickly with Tab or /approval-mode.',
|
||||
|
||||
// ============================================================================
|
||||
// Exit Screen / Stats
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export default {
|
|||
'auto-accept edits': 'Режим принятия правок',
|
||||
'Accepting edits': 'Принятие правок',
|
||||
'(shift + tab to cycle)': '(shift + tab для переключения)',
|
||||
'(tab to cycle)': '(Tab для переключения)',
|
||||
'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).':
|
||||
'Выполняйте команды терминала через {{symbol}} (например, {{example1}}) или используйте естественный язык (например, {{example2}}).',
|
||||
'!': '!',
|
||||
|
|
@ -1363,4 +1364,8 @@ export default {
|
|||
'Открываем страницу расширений в браузере: {{url}}',
|
||||
'Failed to open browser. Check out the extensions gallery at {{url}}':
|
||||
'Не удалось открыть браузер. Посетите галерею расширений по адресу {{url}}',
|
||||
'You can switch permission mode quickly with Shift+Tab or /approval-mode.':
|
||||
'Вы можете быстро переключать режим разрешений с помощью Shift+Tab или /approval-mode.',
|
||||
'You can switch permission mode quickly with Tab or /approval-mode.':
|
||||
'Вы можете быстро переключать режим разрешений с помощью Tab или /approval-mode.',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export default {
|
|||
'auto-accept edits': '自动接受编辑',
|
||||
'Accepting edits': '接受编辑',
|
||||
'(shift + tab to cycle)': '(shift + tab 切换)',
|
||||
'(tab to cycle)': '(按 tab 切换)',
|
||||
'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).':
|
||||
'通过 {{symbol}} 执行 shell 命令(例如,{{example1}})或使用自然语言(例如,{{example2}})',
|
||||
'!': '!',
|
||||
|
|
@ -1031,6 +1032,8 @@ export default {
|
|||
'运行 qwen --continue 或 qwen --resume 可继续之前的会话。',
|
||||
'You can switch permission mode quickly with Shift+Tab or /approval-mode.':
|
||||
'按 Shift+Tab 或输入 /approval-mode 可快速切换权限模式。',
|
||||
'You can switch permission mode quickly with Tab or /approval-mode.':
|
||||
'按 Tab 或输入 /approval-mode 可快速切换权限模式。',
|
||||
|
||||
// ============================================================================
|
||||
// Exit Screen / Stats
|
||||
|
|
|
|||
|
|
@ -21,21 +21,26 @@ export const AutoAcceptIndicator: React.FC<AutoAcceptIndicatorProps> = ({
|
|||
let textContent = '';
|
||||
let subText = '';
|
||||
|
||||
const cycleText =
|
||||
process.platform === 'win32'
|
||||
? ` ${t('(tab to cycle)')}`
|
||||
: ` ${t('(shift + tab to cycle)')}`;
|
||||
|
||||
switch (approvalMode) {
|
||||
case ApprovalMode.PLAN:
|
||||
textColor = theme.status.success;
|
||||
textContent = t('plan mode');
|
||||
subText = ` ${t('(shift + tab to cycle)')}`;
|
||||
subText = cycleText;
|
||||
break;
|
||||
case ApprovalMode.AUTO_EDIT:
|
||||
textColor = theme.status.warning;
|
||||
textContent = t('auto-accept edits');
|
||||
subText = ` ${t('(shift + tab to cycle)')}`;
|
||||
subText = cycleText;
|
||||
break;
|
||||
case ApprovalMode.YOLO:
|
||||
textColor = theme.status.error;
|
||||
textContent = t('YOLO mode');
|
||||
subText = ` ${t('(shift + tab to cycle)')}`;
|
||||
subText = cycleText;
|
||||
break;
|
||||
case ApprovalMode.DEFAULT:
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -46,6 +46,18 @@ const mockCommands: readonly SlashCommand[] = [
|
|||
];
|
||||
|
||||
describe('Help Component', () => {
|
||||
it('should render platform-specific keyboard shortcuts', () => {
|
||||
const { lastFrame } = render(<Help commands={mockCommands} />);
|
||||
const output = lastFrame();
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
expect(output).toContain('Tab');
|
||||
expect(output).not.toContain('Shift+Tab');
|
||||
} else {
|
||||
expect(output).toContain('Shift+Tab');
|
||||
}
|
||||
});
|
||||
|
||||
it('should not render hidden commands', () => {
|
||||
const { lastFrame } = render(<Help commands={mockCommands} />);
|
||||
const output = lastFrame();
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ export const Help: React.FC<Help> = ({ commands, width }) => (
|
|||
</Text>
|
||||
<Text color={theme.text.primary}>
|
||||
<Text bold color={theme.text.accent}>
|
||||
Shift+Tab
|
||||
{process.platform === 'win32' ? 'Tab' : 'Shift+Tab'}
|
||||
</Text>{' '}
|
||||
- {t('Cycle approval modes')}
|
||||
</Text>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,10 @@ const getShortcuts = (): Shortcut[] => [
|
|||
{ key: '/', description: t('for commands') },
|
||||
{ key: '@', description: t('for file paths') },
|
||||
{ key: 'esc esc', description: t('to clear input') },
|
||||
{ key: 'shift+tab', description: t('to cycle approvals') },
|
||||
{
|
||||
key: process.platform === 'win32' ? 'tab' : 'shift+tab',
|
||||
description: t('to cycle approvals'),
|
||||
},
|
||||
{ key: 'ctrl+c', description: t('to quit') },
|
||||
{ key: getNewlineKey(), description: t('for newline') + ' ⏎' },
|
||||
{ key: 'ctrl+l', description: t('to clear screen') },
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ const startupTips = [
|
|||
'You can run any shell commands from Qwen Code using ! (e.g. !ls).',
|
||||
'Type / to open the command popup; Tab autocompletes slash commands and saved prompts.',
|
||||
'You can resume a previous conversation by running qwen --continue or qwen --resume.',
|
||||
'You can switch permission mode quickly with Shift+Tab or /approval-mode.',
|
||||
process.platform === 'win32'
|
||||
? 'You can switch permission mode quickly with Tab or /approval-mode.'
|
||||
: 'You can switch permission mode quickly with Shift+Tab or /approval-mode.',
|
||||
] as const;
|
||||
|
||||
export const Tips: React.FC = () => {
|
||||
|
|
|
|||
|
|
@ -229,33 +229,6 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not process kitty sequences when kitty protocol is disabled', async () => {
|
||||
const keyHandler = vi.fn();
|
||||
|
||||
const { result } = renderHook(() => useKeypressContext(), {
|
||||
wrapper: ({ children }) =>
|
||||
wrapper({ children, kittyProtocolEnabled: false }),
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.subscribe(keyHandler);
|
||||
});
|
||||
|
||||
// Send kitty protocol sequence for numpad enter
|
||||
act(() => {
|
||||
stdin.sendKittySequence(`\x1b[57414u`);
|
||||
});
|
||||
|
||||
// When kitty protocol is disabled, the sequence should be passed through
|
||||
// as individual keypresses, not recognized as a single enter key
|
||||
expect(keyHandler).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'return',
|
||||
kittyProtocol: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Escape key handling', () => {
|
||||
|
|
@ -1256,13 +1229,13 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||
});
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
'[DEBUG] Kitty buffer accumulating:',
|
||||
'[DEBUG] CSI buffer accumulating:',
|
||||
expect.stringContaining('\x1b[27u'),
|
||||
);
|
||||
const parsedCall = consoleLogSpy.mock.calls.find(
|
||||
(args) =>
|
||||
typeof args[0] === 'string' &&
|
||||
args[0].includes('[DEBUG] Kitty sequence parsed successfully'),
|
||||
args[0].includes('[DEBUG] CSI sequence parsed successfully'),
|
||||
);
|
||||
expect(parsedCall).toBeTruthy();
|
||||
expect(parsedCall?.[1]).toEqual(expect.stringContaining('\x1b[27u'));
|
||||
|
|
@ -1293,7 +1266,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||
});
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
'[DEBUG] Kitty buffer overflow, clearing:',
|
||||
'[DEBUG] CSI buffer overflow, clearing:',
|
||||
expect.any(String),
|
||||
);
|
||||
});
|
||||
|
|
@ -1384,13 +1357,13 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||
|
||||
// Verify debug logging for accumulation
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
'[DEBUG] Kitty buffer accumulating:',
|
||||
'[DEBUG] CSI buffer accumulating:',
|
||||
sequence,
|
||||
);
|
||||
|
||||
// Verify warning for char codes
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
'Kitty sequence buffer has char codes:',
|
||||
'CSI sequence buffer has char codes:',
|
||||
[27, 91, 49, 50],
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -508,95 +508,97 @@ export function KeypressProvider({
|
|||
return;
|
||||
}
|
||||
|
||||
if (kittyProtocolEnabled) {
|
||||
if (
|
||||
kittySequenceBuffer ||
|
||||
(key.sequence.startsWith(`${ESC}[`) &&
|
||||
!key.sequence.startsWith(PASTE_MODE_PREFIX) &&
|
||||
!key.sequence.startsWith(PASTE_MODE_SUFFIX) &&
|
||||
!key.sequence.startsWith(FOCUS_IN) &&
|
||||
!key.sequence.startsWith(FOCUS_OUT))
|
||||
) {
|
||||
kittySequenceBuffer += key.sequence;
|
||||
// Parse CSI sequences for both Kitty protocol and legacy terminals
|
||||
// This ensures Shift+Tab and other special keys work correctly even when
|
||||
// Kitty protocol is not available (e.g., Windows PowerShell)
|
||||
if (
|
||||
kittySequenceBuffer ||
|
||||
(key.sequence &&
|
||||
key.sequence.startsWith(`${ESC}[`) &&
|
||||
!key.sequence.startsWith(PASTE_MODE_PREFIX) &&
|
||||
!key.sequence.startsWith(PASTE_MODE_SUFFIX) &&
|
||||
!key.sequence.startsWith(FOCUS_IN) &&
|
||||
!key.sequence.startsWith(FOCUS_OUT))
|
||||
) {
|
||||
kittySequenceBuffer += key.sequence;
|
||||
|
||||
if (debugKeystrokeLogging) {
|
||||
console.log('[DEBUG] CSI buffer accumulating:', kittySequenceBuffer);
|
||||
}
|
||||
|
||||
// Try to peel off as many complete sequences as are available at the
|
||||
// start of the buffer. This handles batched inputs cleanly. If the
|
||||
// prefix is incomplete or invalid, skip to the next CSI introducer
|
||||
// (ESC[) so that a following valid sequence can still be parsed.
|
||||
let parsedAny = false;
|
||||
while (kittySequenceBuffer) {
|
||||
const parsed = parseKittyPrefix(kittySequenceBuffer);
|
||||
if (!parsed) {
|
||||
// Look for the next potential CSI start beyond index 0
|
||||
const nextStart = kittySequenceBuffer.indexOf(`${ESC}[`, 1);
|
||||
if (nextStart > 0) {
|
||||
if (debugKeystrokeLogging) {
|
||||
console.log(
|
||||
'[DEBUG] Skipping incomplete/invalid CSI prefix:',
|
||||
kittySequenceBuffer.slice(0, nextStart),
|
||||
);
|
||||
}
|
||||
kittySequenceBuffer = kittySequenceBuffer.slice(nextStart);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (debugKeystrokeLogging) {
|
||||
const parsedSequence = kittySequenceBuffer.slice(0, parsed.length);
|
||||
if (kittySequenceBuffer.length > parsed.length) {
|
||||
console.log(
|
||||
'[DEBUG] CSI sequence parsed successfully (prefix):',
|
||||
parsedSequence,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
'[DEBUG] CSI sequence parsed successfully:',
|
||||
parsedSequence,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Consume the parsed prefix and broadcast it.
|
||||
kittySequenceBuffer = kittySequenceBuffer.slice(parsed.length);
|
||||
broadcast(parsed.key);
|
||||
parsedAny = true;
|
||||
}
|
||||
if (parsedAny) return;
|
||||
|
||||
if (config?.getDebugMode() || debugKeystrokeLogging) {
|
||||
const codes = Array.from(kittySequenceBuffer).map((ch) =>
|
||||
ch.charCodeAt(0),
|
||||
);
|
||||
console.warn('CSI sequence buffer has char codes:', codes);
|
||||
}
|
||||
|
||||
if (
|
||||
kittyProtocolEnabled &&
|
||||
kittySequenceBuffer.length > MAX_KITTY_SEQUENCE_LENGTH
|
||||
) {
|
||||
if (debugKeystrokeLogging) {
|
||||
console.log(
|
||||
'[DEBUG] Kitty buffer accumulating:',
|
||||
'[DEBUG] CSI buffer overflow, clearing:',
|
||||
kittySequenceBuffer,
|
||||
);
|
||||
}
|
||||
|
||||
// Try to peel off as many complete sequences as are available at the
|
||||
// start of the buffer. This handles batched inputs cleanly. If the
|
||||
// prefix is incomplete or invalid, skip to the next CSI introducer
|
||||
// (ESC[) so that a following valid sequence can still be parsed.
|
||||
let parsedAny = false;
|
||||
while (kittySequenceBuffer) {
|
||||
const parsed = parseKittyPrefix(kittySequenceBuffer);
|
||||
if (!parsed) {
|
||||
// Look for the next potential CSI start beyond index 0
|
||||
const nextStart = kittySequenceBuffer.indexOf(`${ESC}[`, 1);
|
||||
if (nextStart > 0) {
|
||||
if (debugKeystrokeLogging) {
|
||||
console.log(
|
||||
'[DEBUG] Skipping incomplete/invalid CSI prefix:',
|
||||
kittySequenceBuffer.slice(0, nextStart),
|
||||
);
|
||||
}
|
||||
kittySequenceBuffer = kittySequenceBuffer.slice(nextStart);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (debugKeystrokeLogging) {
|
||||
const parsedSequence = kittySequenceBuffer.slice(
|
||||
0,
|
||||
parsed.length,
|
||||
);
|
||||
if (kittySequenceBuffer.length > parsed.length) {
|
||||
console.log(
|
||||
'[DEBUG] Kitty sequence parsed successfully (prefix):',
|
||||
parsedSequence,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
'[DEBUG] Kitty sequence parsed successfully:',
|
||||
parsedSequence,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Consume the parsed prefix and broadcast it.
|
||||
kittySequenceBuffer = kittySequenceBuffer.slice(parsed.length);
|
||||
broadcast(parsed.key);
|
||||
parsedAny = true;
|
||||
}
|
||||
if (parsedAny) return;
|
||||
|
||||
if (config?.getDebugMode() || debugKeystrokeLogging) {
|
||||
const codes = Array.from(kittySequenceBuffer).map((ch) =>
|
||||
ch.charCodeAt(0),
|
||||
if (config) {
|
||||
const event = new KittySequenceOverflowEvent(
|
||||
kittySequenceBuffer.length,
|
||||
kittySequenceBuffer,
|
||||
);
|
||||
console.warn('Kitty sequence buffer has char codes:', codes);
|
||||
}
|
||||
|
||||
if (kittySequenceBuffer.length > MAX_KITTY_SEQUENCE_LENGTH) {
|
||||
if (debugKeystrokeLogging) {
|
||||
console.log(
|
||||
'[DEBUG] Kitty buffer overflow, clearing:',
|
||||
kittySequenceBuffer,
|
||||
);
|
||||
}
|
||||
if (config) {
|
||||
const event = new KittySequenceOverflowEvent(
|
||||
kittySequenceBuffer.length,
|
||||
kittySequenceBuffer,
|
||||
);
|
||||
logKittySequenceOverflow(config, event);
|
||||
}
|
||||
kittySequenceBuffer = '';
|
||||
} else {
|
||||
return;
|
||||
logKittySequenceOverflow(config, event);
|
||||
}
|
||||
kittySequenceBuffer = '';
|
||||
} else if (!kittyProtocolEnabled) {
|
||||
// For non-Kitty terminals, clear the buffer to avoid accumulation
|
||||
kittySequenceBuffer = '';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -240,7 +240,13 @@ describe('useAutoAcceptIndicator', () => {
|
|||
shift: false,
|
||||
} as Key);
|
||||
});
|
||||
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
||||
if (process.platform === 'win32') {
|
||||
// On Windows, Tab alone toggles approval mode
|
||||
expect(mockConfigInstance.setApprovalMode).toHaveBeenCalled();
|
||||
mockConfigInstance.setApprovalMode.mockClear();
|
||||
} else {
|
||||
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
|
||||
}
|
||||
|
||||
act(() => {
|
||||
capturedUseKeypressHandler({
|
||||
|
|
|
|||
|
|
@ -36,7 +36,16 @@ export function useAutoAcceptIndicator({
|
|||
useKeypress(
|
||||
(key) => {
|
||||
// Handle Shift+Tab to cycle through all modes
|
||||
if (key.shift && key.name === 'tab') {
|
||||
// On Windows, Shift+Tab is indistinguishable from Tab (\t) in some terminals,
|
||||
// so we allow Tab to switch modes as well to support the shortcut.
|
||||
const isShiftTab = key.shift && key.name === 'tab';
|
||||
const isWindowsTab =
|
||||
process.platform === 'win32' &&
|
||||
key.name === 'tab' &&
|
||||
!key.ctrl &&
|
||||
!key.meta;
|
||||
|
||||
if (isShiftTab || isWindowsTab) {
|
||||
const currentMode = config.getApprovalMode();
|
||||
const currentIndex = APPROVAL_MODES.indexOf(currentMode);
|
||||
const nextIndex =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue