feat(cli) Scrollbar for input prompt (#21992)

This commit is contained in:
Jacob Richman
2026-04-03 15:10:04 -07:00
committed by GitHub
parent 893ae4d29a
commit d5a5995281
6 changed files with 439 additions and 196 deletions
@@ -2907,6 +2907,25 @@ export function useTextBuffer({
const [scrollRowState, setScrollRowState] = useState<number>(0);
const { height } = viewport;
const totalVisualLines = visualLines.length;
const maxScrollStart = Math.max(0, totalVisualLines - height);
let newVisualScrollRow = scrollRowState;
if (visualCursor[0] < scrollRowState) {
newVisualScrollRow = visualCursor[0];
} else if (visualCursor[0] >= scrollRowState + height) {
newVisualScrollRow = visualCursor[0] - height + 1;
}
newVisualScrollRow = clamp(newVisualScrollRow, 0, maxScrollStart);
if (newVisualScrollRow !== scrollRowState) {
setScrollRowState(newVisualScrollRow);
}
const actualScrollRowState = newVisualScrollRow;
useEffect(() => {
if (onChange) {
onChange(text);
@@ -2920,28 +2939,6 @@ export function useTextBuffer({
});
}, [viewport.width, viewport.height]);
// Update visual scroll (vertical)
useEffect(() => {
const { height } = viewport;
const totalVisualLines = visualLines.length;
const maxScrollStart = Math.max(0, totalVisualLines - height);
let newVisualScrollRow = scrollRowState;
if (visualCursor[0] < scrollRowState) {
newVisualScrollRow = visualCursor[0];
} else if (visualCursor[0] >= scrollRowState + height) {
newVisualScrollRow = visualCursor[0] - height + 1;
}
// When the number of visual lines shrinks (e.g., after widening the viewport),
// ensure scroll never starts beyond the last valid start so we can render a full window.
newVisualScrollRow = clamp(newVisualScrollRow, 0, maxScrollStart);
if (newVisualScrollRow !== scrollRowState) {
setScrollRowState(newVisualScrollRow);
}
}, [visualCursor, scrollRowState, viewport, visualLines.length]);
const insert = useCallback(
(ch: string, { paste = false }: { paste?: boolean } = {}): void => {
if (typeof ch !== 'string') {
@@ -3495,10 +3492,10 @@ export function useTextBuffer({
const visualScrollRow = useMemo(() => {
const totalVisualLines = visualLines.length;
return Math.min(
scrollRowState,
actualScrollRowState,
Math.max(0, totalVisualLines - viewport.height),
);
}, [visualLines.length, scrollRowState, viewport.height]);
}, [visualLines.length, actualScrollRowState, viewport.height]);
const renderedVisualLines = useMemo(
() => visualLines.slice(visualScrollRow, visualScrollRow + viewport.height),
@@ -3694,6 +3691,7 @@ export function useTextBuffer({
viewportVisualLines: renderedVisualLines,
visualCursor,
visualScrollRow,
viewportHeight: viewport.height,
visualToLogicalMap,
transformedToLogicalMaps,
visualToTransformedMap,
@@ -3799,6 +3797,7 @@ export function useTextBuffer({
renderedVisualLines,
visualCursor,
visualScrollRow,
viewport.height,
visualToLogicalMap,
transformedToLogicalMaps,
visualToTransformedMap,
@@ -3914,6 +3913,7 @@ export interface TextBuffer {
viewportVisualLines: string[]; // The subset of visual lines to be rendered based on visualScrollRow and viewport.height
visualCursor: [number, number]; // Visual cursor [row, col] relative to the start of all visualLines
visualScrollRow: number; // Scroll position for visual lines (index of the first visible visual line)
viewportHeight: number; // The maximum height of the viewport
/**
* For each visual line (by absolute index in allVisualLines) provides a tuple
* [logicalLineIndex, startColInLogical] that maps where that visual line