diff --git a/cli/src/utils/__tests__/text-layout.test.ts b/cli/src/utils/__tests__/text-layout.test.ts index a4554d98ca..9ff8c20fdc 100644 --- a/cli/src/utils/__tests__/text-layout.test.ts +++ b/cli/src/utils/__tests__/text-layout.test.ts @@ -1,6 +1,6 @@ import { describe, test, expect } from 'bun:test' -import { computeInputLayoutMetrics } from '../text-layout' +import { computeInputLayoutMetrics, getLastNVisualLines } from '../text-layout' describe('computeInputLayoutMetrics', () => { test('single-line content keeps height at 1 without gutter', () => { @@ -80,3 +80,39 @@ describe('computeInputLayoutMetrics', () => { expect(metrics.gutterEnabled).toBe(false) }) }) + +describe('getLastNVisualLines', () => { + test('returns empty array when n or cols <= 0 or text is empty', () => { + expect(getLastNVisualLines('', 40, 5)).toEqual({ lines: [], hasMore: false }) + expect(getLastNVisualLines('hello', 0, 5)).toEqual({ lines: [], hasMore: false }) + expect(getLastNVisualLines('hello', 40, 0)).toEqual({ lines: [], hasMore: false }) + }) + + test('returns all lines without hasMore when line count <= n', () => { + const text = 'line 1\nline 2\nline 3' + const result = getLastNVisualLines(text, 40, 5) + expect(result.lines).toEqual(['line 1', 'line 2', 'line 3']) + expect(result.hasMore).toBe(false) + }) + + test('returns only last n lines with hasMore = true when line count > n', () => { + const text = 'line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7' + const result = getLastNVisualLines(text, 40, 3) + expect(result.lines).toEqual(['line 5', 'line 6', 'line 7']) + expect(result.hasMore).toBe(true) + }) + + test('correctly bounds array when text has hundreds of wrapped lines', () => { + const text = Array.from({ length: 100 }, (_, i) => `Line ${i}`).join('\n') + const result = getLastNVisualLines(text, 40, 4) + expect(result.lines).toEqual(['Line 96', 'Line 97', 'Line 98', 'Line 99']) + expect(result.hasMore).toBe(true) + }) + + test('wraps long lines exceeding column width', () => { + const text = 'supercalifragilisticexpialidocious' + const result = getLastNVisualLines(text, 10, 2) + expect(result.lines.length).toBe(2) + expect(result.hasMore).toBe(true) + }) +}) diff --git a/cli/src/utils/text-layout.ts b/cli/src/utils/text-layout.ts index d7f17472b9..d4afdfa96e 100644 --- a/cli/src/utils/text-layout.ts +++ b/cli/src/utils/text-layout.ts @@ -62,11 +62,16 @@ export function getLastNVisualLines(text: string, cols: number, n: number): { li const lines: string[] = [] if (!text) return { lines, hasMore: false } + let hasMore = false const tokens = text.split(/(\s+)/) let current = '' let currentWidth = 0 const pushLine = () => { + if (lines.length === n) { + hasMore = true + lines.shift() + } lines.push(current) current = '' currentWidth = 0 @@ -104,10 +109,8 @@ export function getLastNVisualLines(text: string, cols: number, n: number): { li appendSegment(token) } - if (current.length > 0 || lines.length === 0) pushLine() - const hasMore = lines.length > n - const lastLines = lines.slice(-n) - return { lines: lastLines, hasMore } + if (current.length > 0 || (!hasMore && lines.length === 0)) pushLine() + return { lines, hasMore } } export function computeInputLayoutMetrics({