Skip to content
Open
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
2 changes: 2 additions & 0 deletions packages/cli/src/test-utils/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,8 @@ const baseMockUiState = {
activePtyId: undefined,
backgroundTasks: new Map(),
backgroundTaskHeight: 0,
copyModeEnabled: false,
mouseMode: true,
quota: {
userTier: undefined,
stats: undefined,
Expand Down
9 changes: 7 additions & 2 deletions packages/cli/src/ui/AppContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2012,6 +2012,8 @@ Logging in with Google... Restarting Gemini CLI to continue.

useKeypress(handleGlobalKeypress, { isActive: true, priority: true });

const isSelectionMode = isAlternateBuffer && !mouseMode;

useKeypress(
(key: Key) => {
if (
Expand All @@ -2026,13 +2028,16 @@ Logging in with Google... Restarting Gemini CLI to continue.
}

setCopyModeEnabled(false);
if (mouseMode) {

if (isSelectionMode) {
setMouseMode(true);
} else if (mouseMode) {
enableMouseEvents();
}
return true;
},
{
isActive: copyModeEnabled,
isActive: copyModeEnabled || isSelectionMode,
// We need to receive keypresses first so they do not bubble to other
// handlers.
priority: KeypressPriority.Critical,
Expand Down
39 changes: 38 additions & 1 deletion packages/cli/src/ui/components/CopyModeWarning.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,66 @@ import { render } from '../../test-utils/render.js';
import { CopyModeWarning } from './CopyModeWarning.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useUIState, type UIState } from '../contexts/UIStateContext.js';
import { useConfig } from '../contexts/ConfigContext.js';
import type { Config } from '@google/gemini-cli-core';

vi.mock('../contexts/UIStateContext.js');
vi.mock('../contexts/ConfigContext.js');

describe('CopyModeWarning', () => {
const mockUseUIState = vi.mocked(useUIState);
const mockUseConfig = vi.mocked(useConfig);

beforeEach(() => {
vi.clearAllMocks();
mockUseConfig.mockReturnValue({
getUseAlternateBuffer: () => false,
} as unknown as Config);
});

it('renders nothing when copy mode is disabled', async () => {
it('renders nothing when copy mode is disabled and not in alternate buffer', async () => {
mockUseUIState.mockReturnValue({
copyModeEnabled: false,
mouseMode: true,
} as unknown as UIState);
const { lastFrame, unmount } = await render(<CopyModeWarning />);
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});

it('renders nothing when copy mode is disabled and mouse mode is disabled but not in alternate buffer', async () => {
mockUseUIState.mockReturnValue({
copyModeEnabled: false,
mouseMode: false,
} as unknown as UIState);
mockUseConfig.mockReturnValue({
getUseAlternateBuffer: () => false,
} as unknown as Config);
const { lastFrame, unmount } = await render(<CopyModeWarning />);
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});

it('renders warning when copy mode is enabled', async () => {
mockUseUIState.mockReturnValue({
copyModeEnabled: true,
mouseMode: true,
} as unknown as UIState);
const { lastFrame, unmount } = await render(<CopyModeWarning />);
expect(lastFrame()).toContain('In Copy Mode');
expect(lastFrame()).toContain('Use Page Up/Down to scroll');
expect(lastFrame()).toContain('Press Ctrl+S or any other key to exit');
unmount();
});

it('renders warning when in alternate buffer and mouse mode is disabled', async () => {
mockUseUIState.mockReturnValue({
copyModeEnabled: false,
mouseMode: false,
} as unknown as UIState);
mockUseConfig.mockReturnValue({
getUseAlternateBuffer: () => true,
} as unknown as Config);
const { lastFrame, unmount } = await render(<CopyModeWarning />);
expect(lastFrame()).toContain('In Copy Mode');
expect(lastFrame()).toContain('Use Page Up/Down to scroll');
Expand Down
10 changes: 8 additions & 2 deletions packages/cli/src/ui/components/CopyModeWarning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@
import type React from 'react';
import { Box, Text } from 'ink';
import { useUIState } from '../contexts/UIStateContext.js';
import { useConfig } from '../contexts/ConfigContext.js';
import { theme } from '../semantic-colors.js';

export const CopyModeWarning: React.FC = () => {
const { copyModeEnabled } = useUIState();
const { copyModeEnabled, mouseMode } = useUIState();
const config = useConfig();
const isTrueAlternateBuffer = config.getUseAlternateBuffer();

const isSelectionMode = isTrueAlternateBuffer && !mouseMode;
const showWarning = copyModeEnabled || isSelectionMode;

return (
<Box height={1}>
{copyModeEnabled && (
{showWarning && (
<Text color={theme.status.warning}>
In Copy Mode. Use Page Up/Down to scroll. Press Ctrl+S or any other
key to exit.
Expand Down
1 change: 0 additions & 1 deletion packages/cli/src/ui/components/shared/ScrollableList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ interface ScrollableListProps<T> extends VirtualizedListProps<T> {
width?: string | number;
scrollbar?: boolean;
stableScrollback?: boolean;
copyModeEnabled?: boolean;
isStatic?: boolean;
fixedItemHeight?: boolean;
targetScrollIndex?: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,9 +316,8 @@ describe('<VirtualizedList />', () => {
unmount();
});

it('renders correctly in copyModeEnabled when scrolled', async () => {
it('renders correctly with scrollbar={false} when scrolled', async () => {
const longData = Array.from({ length: 100 }, (_, i) => `Item ${i}`);
// Use copy mode
const { lastFrame, unmount } = await render(
<Box height={10} width={100}>
<VirtualizedList
Expand All @@ -331,7 +330,7 @@ describe('<VirtualizedList />', () => {
keyExtractor={(item) => item}
estimatedItemHeight={() => 1}
initialScrollIndex={50}
copyModeEnabled={true}
scrollbar={false}
/>
</Box>,
);
Expand Down
15 changes: 4 additions & 11 deletions packages/cli/src/ui/components/shared/VirtualizedList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export type VirtualizedListProps<T> = {
overflowToBackbuffer?: boolean;
scrollbar?: boolean;
stableScrollback?: boolean;
copyModeEnabled?: boolean;
fixedItemHeight?: boolean;
containerHeight?: number;
};
Expand Down Expand Up @@ -133,7 +132,6 @@ function VirtualizedList<T>(
overflowToBackbuffer,
scrollbar = true,
stableScrollback,
copyModeEnabled = false,
fixedItemHeight = false,
} = props;
const dataRef = useRef(data);
Expand Down Expand Up @@ -696,25 +694,20 @@ function VirtualizedList<T>(
return (
<Box
ref={containerRefCallback}
overflowY={copyModeEnabled ? 'hidden' : 'scroll'}
overflowY="scroll"
overflowX="hidden"
scrollTop={copyModeEnabled ? 0 : scrollTop}
scrollTop={scrollTop}
scrollbarThumbColor={props.scrollbarThumbColor ?? theme.text.secondary}
backgroundColor={props.backgroundColor}
width="100%"
height="100%"
flexDirection="column"
paddingRight={copyModeEnabled ? 0 : 1}
paddingRight={1}
overflowToBackbuffer={overflowToBackbuffer}
scrollbar={scrollbar}
stableScrollback={stableScrollback}
>
<Box
flexShrink={0}
width="100%"
flexDirection="column"
marginTop={copyModeEnabled ? -actualScrollTop : 0}
>
<Box flexShrink={0} width="100%" flexDirection="column">
<Box height={topSpacerHeight} flexShrink={0} />
{renderedItems}
<Box height={bottomSpacerHeight} flexShrink={0} />
Expand Down
Loading