mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-24 12:04:56 -07:00
fix(cli): hide scrollbars when in alternate buffer copy mode (#18354)
Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
@@ -89,6 +89,8 @@ describe('MainContent', () => {
|
|||||||
historyRemountKey: 0,
|
historyRemountKey: 0,
|
||||||
bannerData: { defaultText: '', warningText: '' },
|
bannerData: { defaultText: '', warningText: '' },
|
||||||
bannerVisible: false,
|
bannerVisible: false,
|
||||||
|
copyModeEnabled: false,
|
||||||
|
terminalWidth: 100,
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -173,6 +175,7 @@ describe('MainContent', () => {
|
|||||||
vi.mocked(useAlternateBuffer).mockReturnValue(isAlternateBuffer);
|
vi.mocked(useAlternateBuffer).mockReturnValue(isAlternateBuffer);
|
||||||
const ptyId = 123;
|
const ptyId = 123;
|
||||||
const uiState = {
|
const uiState = {
|
||||||
|
...defaultMockUiState,
|
||||||
history: [],
|
history: [],
|
||||||
pendingHistoryItems: [
|
pendingHistoryItems: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -107,9 +107,9 @@ ShowMoreLines"
|
|||||||
exports[`MainContent > does not constrain height in alternate buffer mode 1`] = `
|
exports[`MainContent > does not constrain height in alternate buffer mode 1`] = `
|
||||||
"ScrollableList
|
"ScrollableList
|
||||||
AppHeader
|
AppHeader
|
||||||
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
> Hello
|
> Hello
|
||||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||||
✦ Hi there
|
✦ Hi there
|
||||||
ShowMoreLines
|
ShowMoreLines
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ import { MouseProvider } from '../../contexts/MouseContext.js';
|
|||||||
import { describe, it, expect, vi } from 'vitest';
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
import { waitFor } from '../../../test-utils/async.js';
|
import { waitFor } from '../../../test-utils/async.js';
|
||||||
|
|
||||||
|
vi.mock('../../contexts/UIStateContext.js', () => ({
|
||||||
|
useUIState: vi.fn(() => ({
|
||||||
|
copyModeEnabled: false,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
// Mock useStdout to provide a fixed size for testing
|
// Mock useStdout to provide a fixed size for testing
|
||||||
vi.mock('ink', async (importOriginal) => {
|
vi.mock('ink', async (importOriginal) => {
|
||||||
const actual = await importOriginal<typeof import('ink')>();
|
const actual = await importOriginal<typeof import('ink')>();
|
||||||
|
|||||||
@@ -16,6 +16,13 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import type { UIState } from '../../contexts/UIStateContext.js';
|
||||||
|
|
||||||
|
vi.mock('../../contexts/UIStateContext.js', () => ({
|
||||||
|
useUIState: vi.fn(() => ({
|
||||||
|
copyModeEnabled: false,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
@@ -324,4 +331,39 @@ describe('<VirtualizedList />', () => {
|
|||||||
|
|
||||||
expect(ref.current?.getScrollState().scrollTop).toBe(4);
|
expect(ref.current?.getScrollState().scrollTop).toBe(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders correctly in copyModeEnabled when scrolled', async () => {
|
||||||
|
const { useUIState } = await import('../../contexts/UIStateContext.js');
|
||||||
|
vi.mocked(useUIState).mockReturnValue({
|
||||||
|
copyModeEnabled: true,
|
||||||
|
} as Partial<UIState> as UIState);
|
||||||
|
|
||||||
|
const longData = Array.from({ length: 100 }, (_, i) => `Item ${i}`);
|
||||||
|
// Use copy mode
|
||||||
|
const { lastFrame } = render(
|
||||||
|
<Box height={10} width={100}>
|
||||||
|
<VirtualizedList
|
||||||
|
data={longData}
|
||||||
|
renderItem={({ item }) => (
|
||||||
|
<Box height={1}>
|
||||||
|
<Text>{item}</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
keyExtractor={(item) => item}
|
||||||
|
estimatedItemHeight={() => 1}
|
||||||
|
initialScrollIndex={50}
|
||||||
|
/>
|
||||||
|
</Box>,
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
await delay(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Item 50 should be visible
|
||||||
|
expect(lastFrame()).toContain('Item 50');
|
||||||
|
// And surrounding items
|
||||||
|
expect(lastFrame()).toContain('Item 59');
|
||||||
|
// But far away items should not be (ensures we are actually scrolled)
|
||||||
|
expect(lastFrame()).not.toContain('Item 0');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { theme } from '../../semantic-colors.js';
|
import { theme } from '../../semantic-colors.js';
|
||||||
import { useBatchedScroll } from '../../hooks/useBatchedScroll.js';
|
import { useBatchedScroll } from '../../hooks/useBatchedScroll.js';
|
||||||
|
import { useUIState } from '../../contexts/UIStateContext.js';
|
||||||
|
|
||||||
import { type DOMElement, measureElement, Box } from 'ink';
|
import { type DOMElement, measureElement, Box } from 'ink';
|
||||||
|
|
||||||
@@ -78,6 +79,7 @@ function VirtualizedList<T>(
|
|||||||
initialScrollIndex,
|
initialScrollIndex,
|
||||||
initialScrollOffsetInIndex,
|
initialScrollOffsetInIndex,
|
||||||
} = props;
|
} = props;
|
||||||
|
const { copyModeEnabled } = useUIState();
|
||||||
const dataRef = useRef(data);
|
const dataRef = useRef(data);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dataRef.current = data;
|
dataRef.current = data;
|
||||||
@@ -474,16 +476,21 @@ function VirtualizedList<T>(
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
overflowY="scroll"
|
overflowY={copyModeEnabled ? 'hidden' : 'scroll'}
|
||||||
overflowX="hidden"
|
overflowX="hidden"
|
||||||
scrollTop={scrollTop}
|
scrollTop={copyModeEnabled ? 0 : scrollTop}
|
||||||
scrollbarThumbColor={props.scrollbarThumbColor ?? theme.text.secondary}
|
scrollbarThumbColor={props.scrollbarThumbColor ?? theme.text.secondary}
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
paddingRight={1}
|
paddingRight={copyModeEnabled ? 0 : 1}
|
||||||
>
|
>
|
||||||
<Box flexShrink={0} width="100%" flexDirection="column">
|
<Box
|
||||||
|
flexShrink={0}
|
||||||
|
width="100%"
|
||||||
|
flexDirection="column"
|
||||||
|
marginTop={copyModeEnabled ? -scrollTop : 0}
|
||||||
|
>
|
||||||
<Box height={topSpacerHeight} flexShrink={0} />
|
<Box height={topSpacerHeight} flexShrink={0} />
|
||||||
{renderedItems}
|
{renderedItems}
|
||||||
<Box height={bottomSpacerHeight} flexShrink={0} />
|
<Box height={bottomSpacerHeight} flexShrink={0} />
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ export const DefaultAppLayout: React.FC = () => {
|
|||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
width={uiState.terminalWidth}
|
width={uiState.terminalWidth}
|
||||||
height={isAlternateBuffer ? terminalHeight : undefined}
|
height={isAlternateBuffer ? terminalHeight : undefined}
|
||||||
paddingBottom={isAlternateBuffer ? 1 : undefined}
|
paddingBottom={
|
||||||
|
isAlternateBuffer && !uiState.copyModeEnabled ? 1 : undefined
|
||||||
|
}
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
flexGrow={0}
|
flexGrow={0}
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
|
|||||||
Reference in New Issue
Block a user