Skip to content

Commit a7ec012

Browse files
committed
feat(cli) Scrollbar for input prompt (#21992)
1 parent 7311e24 commit a7ec012

File tree

7 files changed

+61
-20
lines changed

7 files changed

+61
-20
lines changed

packages/cli/src/test-utils/render.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,8 @@ const baseMockUiState = {
513513
activePtyId: undefined,
514514
backgroundTasks: new Map(),
515515
backgroundTaskHeight: 0,
516+
copyModeEnabled: false,
517+
mouseMode: true,
516518
quota: {
517519
userTier: undefined,
518520
stats: undefined,

packages/cli/src/ui/AppContainer.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2012,6 +2012,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
20122012

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

2015+
const isSelectionMode = isAlternateBuffer && !mouseMode;
2016+
20152017
useKeypress(
20162018
(key: Key) => {
20172019
if (
@@ -2026,13 +2028,16 @@ Logging in with Google... Restarting Gemini CLI to continue.
20262028
}
20272029

20282030
setCopyModeEnabled(false);
2029-
if (mouseMode) {
2031+
2032+
if (isSelectionMode) {
2033+
setMouseMode(true);
2034+
} else if (mouseMode) {
20302035
enableMouseEvents();
20312036
}
20322037
return true;
20332038
},
20342039
{
2035-
isActive: copyModeEnabled,
2040+
isActive: copyModeEnabled || isSelectionMode,
20362041
// We need to receive keypresses first so they do not bubble to other
20372042
// handlers.
20382043
priority: KeypressPriority.Critical,

packages/cli/src/ui/components/CopyModeWarning.test.tsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,66 @@ import { render } from '../../test-utils/render.js';
88
import { CopyModeWarning } from './CopyModeWarning.js';
99
import { describe, it, expect, vi, beforeEach } from 'vitest';
1010
import { useUIState, type UIState } from '../contexts/UIStateContext.js';
11+
import { useConfig } from '../contexts/ConfigContext.js';
12+
import type { Config } from '@google/gemini-cli-core';
1113

1214
vi.mock('../contexts/UIStateContext.js');
15+
vi.mock('../contexts/ConfigContext.js');
1316

1417
describe('CopyModeWarning', () => {
1518
const mockUseUIState = vi.mocked(useUIState);
19+
const mockUseConfig = vi.mocked(useConfig);
1620

1721
beforeEach(() => {
1822
vi.clearAllMocks();
23+
mockUseConfig.mockReturnValue({
24+
getUseAlternateBuffer: () => false,
25+
} as unknown as Config);
1926
});
2027

21-
it('renders nothing when copy mode is disabled', async () => {
28+
it('renders nothing when copy mode is disabled and not in alternate buffer', async () => {
2229
mockUseUIState.mockReturnValue({
2330
copyModeEnabled: false,
31+
mouseMode: true,
2432
} as unknown as UIState);
2533
const { lastFrame, unmount } = await render(<CopyModeWarning />);
2634
expect(lastFrame({ allowEmpty: true })).toBe('');
2735
unmount();
2836
});
2937

38+
it('renders nothing when copy mode is disabled and mouse mode is disabled but not in alternate buffer', async () => {
39+
mockUseUIState.mockReturnValue({
40+
copyModeEnabled: false,
41+
mouseMode: false,
42+
} as unknown as UIState);
43+
mockUseConfig.mockReturnValue({
44+
getUseAlternateBuffer: () => false,
45+
} as unknown as Config);
46+
const { lastFrame, unmount } = await render(<CopyModeWarning />);
47+
expect(lastFrame({ allowEmpty: true })).toBe('');
48+
unmount();
49+
});
50+
3051
it('renders warning when copy mode is enabled', async () => {
3152
mockUseUIState.mockReturnValue({
3253
copyModeEnabled: true,
54+
mouseMode: true,
55+
} as unknown as UIState);
56+
const { lastFrame, unmount } = await render(<CopyModeWarning />);
57+
expect(lastFrame()).toContain('In Copy Mode');
58+
expect(lastFrame()).toContain('Use Page Up/Down to scroll');
59+
expect(lastFrame()).toContain('Press Ctrl+S or any other key to exit');
60+
unmount();
61+
});
62+
63+
it('renders warning when in alternate buffer and mouse mode is disabled', async () => {
64+
mockUseUIState.mockReturnValue({
65+
copyModeEnabled: false,
66+
mouseMode: false,
3367
} as unknown as UIState);
68+
mockUseConfig.mockReturnValue({
69+
getUseAlternateBuffer: () => true,
70+
} as unknown as Config);
3471
const { lastFrame, unmount } = await render(<CopyModeWarning />);
3572
expect(lastFrame()).toContain('In Copy Mode');
3673
expect(lastFrame()).toContain('Use Page Up/Down to scroll');

packages/cli/src/ui/components/CopyModeWarning.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,20 @@
77
import type React from 'react';
88
import { Box, Text } from 'ink';
99
import { useUIState } from '../contexts/UIStateContext.js';
10+
import { useConfig } from '../contexts/ConfigContext.js';
1011
import { theme } from '../semantic-colors.js';
1112

1213
export const CopyModeWarning: React.FC = () => {
13-
const { copyModeEnabled } = useUIState();
14+
const { copyModeEnabled, mouseMode } = useUIState();
15+
const config = useConfig();
16+
const isTrueAlternateBuffer = config.getUseAlternateBuffer();
17+
18+
const isSelectionMode = isTrueAlternateBuffer && !mouseMode;
19+
const showWarning = copyModeEnabled || isSelectionMode;
1420

1521
return (
1622
<Box height={1}>
17-
{copyModeEnabled && (
23+
{showWarning && (
1824
<Text color={theme.status.warning}>
1925
In Copy Mode. Use Page Up/Down to scroll. Press Ctrl+S or any other
2026
key to exit.

packages/cli/src/ui/components/shared/ScrollableList.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ interface ScrollableListProps<T> extends VirtualizedListProps<T> {
3333
width?: string | number;
3434
scrollbar?: boolean;
3535
stableScrollback?: boolean;
36-
copyModeEnabled?: boolean;
3736
isStatic?: boolean;
3837
fixedItemHeight?: boolean;
3938
targetScrollIndex?: number;

packages/cli/src/ui/components/shared/VirtualizedList.test.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,9 +316,8 @@ describe('<VirtualizedList />', () => {
316316
unmount();
317317
});
318318

319-
it('renders correctly in copyModeEnabled when scrolled', async () => {
319+
it('renders correctly with scrollbar={false} when scrolled', async () => {
320320
const longData = Array.from({ length: 100 }, (_, i) => `Item ${i}`);
321-
// Use copy mode
322321
const { lastFrame, unmount } = await render(
323322
<Box height={10} width={100}>
324323
<VirtualizedList
@@ -331,7 +330,7 @@ describe('<VirtualizedList />', () => {
331330
keyExtractor={(item) => item}
332331
estimatedItemHeight={() => 1}
333332
initialScrollIndex={50}
334-
copyModeEnabled={true}
333+
scrollbar={false}
335334
/>
336335
</Box>,
337336
);

packages/cli/src/ui/components/shared/VirtualizedList.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ export type VirtualizedListProps<T> = {
3939
overflowToBackbuffer?: boolean;
4040
scrollbar?: boolean;
4141
stableScrollback?: boolean;
42-
copyModeEnabled?: boolean;
4342
fixedItemHeight?: boolean;
4443
containerHeight?: number;
4544
};
@@ -133,7 +132,6 @@ function VirtualizedList<T>(
133132
overflowToBackbuffer,
134133
scrollbar = true,
135134
stableScrollback,
136-
copyModeEnabled = false,
137135
fixedItemHeight = false,
138136
} = props;
139137
const dataRef = useRef(data);
@@ -696,25 +694,20 @@ function VirtualizedList<T>(
696694
return (
697695
<Box
698696
ref={containerRefCallback}
699-
overflowY={copyModeEnabled ? 'hidden' : 'scroll'}
697+
overflowY="scroll"
700698
overflowX="hidden"
701-
scrollTop={copyModeEnabled ? 0 : scrollTop}
699+
scrollTop={scrollTop}
702700
scrollbarThumbColor={props.scrollbarThumbColor ?? theme.text.secondary}
703701
backgroundColor={props.backgroundColor}
704702
width="100%"
705703
height="100%"
706704
flexDirection="column"
707-
paddingRight={copyModeEnabled ? 0 : 1}
705+
paddingRight={1}
708706
overflowToBackbuffer={overflowToBackbuffer}
709707
scrollbar={scrollbar}
710708
stableScrollback={stableScrollback}
711709
>
712-
<Box
713-
flexShrink={0}
714-
width="100%"
715-
flexDirection="column"
716-
marginTop={copyModeEnabled ? -actualScrollTop : 0}
717-
>
710+
<Box flexShrink={0} width="100%" flexDirection="column">
718711
<Box height={topSpacerHeight} flexShrink={0} />
719712
{renderedItems}
720713
<Box height={bottomSpacerHeight} flexShrink={0} />

0 commit comments

Comments
 (0)