feat(plan): Extend Shift+Tab Mode Cycling to include Plan Mode (#17177)

This commit is contained in:
Adib234
2026-01-21 10:19:47 -05:00
committed by GitHub
parent b703c87a64
commit 0605e6e3e9
13 changed files with 163 additions and 100 deletions

View File

@@ -5,23 +5,32 @@
*/
import { render } from '../../test-utils/render.js';
import { AutoAcceptIndicator } from './AutoAcceptIndicator.js';
import { ApprovalModeIndicator } from './ApprovalModeIndicator.js';
import { describe, it, expect } from 'vitest';
import { ApprovalMode } from '@google/gemini-cli-core';
describe('AutoAcceptIndicator', () => {
describe('ApprovalModeIndicator', () => {
it('renders correctly for AUTO_EDIT mode', () => {
const { lastFrame } = render(
<AutoAcceptIndicator approvalMode={ApprovalMode.AUTO_EDIT} />,
<ApprovalModeIndicator approvalMode={ApprovalMode.AUTO_EDIT} />,
);
const output = lastFrame();
expect(output).toContain('accepting edits');
expect(output).toContain('(shift + tab to toggle)');
expect(output).toContain('(shift + tab to cycle)');
});
it('renders correctly for PLAN mode', () => {
const { lastFrame } = render(
<ApprovalModeIndicator approvalMode={ApprovalMode.PLAN} />,
);
const output = lastFrame();
expect(output).toContain('plan mode');
expect(output).toContain('(shift + tab to cycle)');
});
it('renders correctly for YOLO mode', () => {
const { lastFrame } = render(
<AutoAcceptIndicator approvalMode={ApprovalMode.YOLO} />,
<ApprovalModeIndicator approvalMode={ApprovalMode.YOLO} />,
);
const output = lastFrame();
expect(output).toContain('YOLO mode');
@@ -30,7 +39,7 @@ describe('AutoAcceptIndicator', () => {
it('renders nothing for DEFAULT mode', () => {
const { lastFrame } = render(
<AutoAcceptIndicator approvalMode={ApprovalMode.DEFAULT} />,
<ApprovalModeIndicator approvalMode={ApprovalMode.DEFAULT} />,
);
const output = lastFrame();
expect(output).not.toContain('accepting edits');

View File

@@ -9,11 +9,11 @@ import { Box, Text } from 'ink';
import { theme } from '../semantic-colors.js';
import { ApprovalMode } from '@google/gemini-cli-core';
interface AutoAcceptIndicatorProps {
interface ApprovalModeIndicatorProps {
approvalMode: ApprovalMode;
}
export const AutoAcceptIndicator: React.FC<AutoAcceptIndicatorProps> = ({
export const ApprovalModeIndicator: React.FC<ApprovalModeIndicatorProps> = ({
approvalMode,
}) => {
let textColor = '';
@@ -24,7 +24,12 @@ export const AutoAcceptIndicator: React.FC<AutoAcceptIndicatorProps> = ({
case ApprovalMode.AUTO_EDIT:
textColor = theme.status.warning;
textContent = 'accepting edits';
subText = ' (shift + tab to toggle)';
subText = ' (shift + tab to cycle)';
break;
case ApprovalMode.PLAN:
textColor = theme.status.success;
textContent = 'plan mode';
subText = ' (shift + tab to cycle)';
break;
case ApprovalMode.YOLO:
textColor = theme.status.error;

View File

@@ -41,8 +41,8 @@ vi.mock('./HookStatusDisplay.js', () => ({
HookStatusDisplay: () => <Text>HookStatusDisplay</Text>,
}));
vi.mock('./AutoAcceptIndicator.js', () => ({
AutoAcceptIndicator: () => <Text>AutoAcceptIndicator</Text>,
vi.mock('./ApprovalModeIndicator.js', () => ({
ApprovalModeIndicator: () => <Text>ApprovalModeIndicator</Text>,
}));
vi.mock('./ShellModeIndicator.js', () => ({
@@ -95,7 +95,7 @@ const createMockUIState = (overrides: Partial<UIState> = {}): UIState =>
({
streamingState: null,
contextFileNames: [],
showAutoAcceptIndicator: ApprovalMode.DEFAULT,
showApprovalModeIndicator: ApprovalMode.DEFAULT,
messageQueue: [],
showErrorDetails: false,
constrainHeight: false,
@@ -419,15 +419,15 @@ describe('Composer', () => {
expect(lastFrame()).not.toContain('InputPrompt');
});
it('shows AutoAcceptIndicator when approval mode is not default and shell mode is inactive', () => {
it('shows ApprovalModeIndicator when approval mode is not default and shell mode is inactive', () => {
const uiState = createMockUIState({
showAutoAcceptIndicator: ApprovalMode.YOLO,
showApprovalModeIndicator: ApprovalMode.YOLO,
shellModeActive: false,
});
const { lastFrame } = renderComposer(uiState);
expect(lastFrame()).toContain('AutoAcceptIndicator');
expect(lastFrame()).toContain('ApprovalModeIndicator');
});
it('shows ShellModeIndicator when shell mode is active', () => {

View File

@@ -8,7 +8,7 @@ import { useState } from 'react';
import { Box, useIsScreenReaderEnabled } from 'ink';
import { LoadingIndicator } from './LoadingIndicator.js';
import { StatusDisplay } from './StatusDisplay.js';
import { AutoAcceptIndicator } from './AutoAcceptIndicator.js';
import { ApprovalModeIndicator } from './ApprovalModeIndicator.js';
import { ShellModeIndicator } from './ShellModeIndicator.js';
import { DetailedMessagesDisplay } from './DetailedMessagesDisplay.js';
import { RawMarkdownIndicator } from './RawMarkdownIndicator.js';
@@ -42,7 +42,7 @@ export const Composer = () => {
const [suggestionsVisible, setSuggestionsVisible] = useState(false);
const isAlternateBuffer = useAlternateBuffer();
const { showAutoAcceptIndicator } = uiState;
const { showApprovalModeIndicator } = uiState;
const suggestionsPosition = isAlternateBuffer ? 'above' : 'below';
const hideContextSummary =
suggestionsVisible && suggestionsPosition === 'above';
@@ -92,9 +92,9 @@ export const Composer = () => {
<StatusDisplay hideContextSummary={hideContextSummary} />
</Box>
<Box paddingTop={isNarrow ? 1 : 0}>
{showAutoAcceptIndicator !== ApprovalMode.DEFAULT &&
{showApprovalModeIndicator !== ApprovalMode.DEFAULT &&
!uiState.shellModeActive && (
<AutoAcceptIndicator approvalMode={showAutoAcceptIndicator} />
<ApprovalModeIndicator approvalMode={showApprovalModeIndicator} />
)}
{uiState.shellModeActive && <ShellModeIndicator />}
{!uiState.renderMarkdown && <RawMarkdownIndicator />}
@@ -131,7 +131,7 @@ export const Composer = () => {
commandContext={uiState.commandContext}
shellModeActive={uiState.shellModeActive}
setShellModeActive={uiActions.setShellModeActive}
approvalMode={showAutoAcceptIndicator}
approvalMode={showApprovalModeIndicator}
onEscapePromptChange={uiActions.onEscapePromptChange}
focus={true}
vimHandleInput={uiActions.vimHandleInput}