From 089aec8b8dad56518a2a0d16afa2b7f9f0ed9fe1 Mon Sep 17 00:00:00 2001 From: Jacob Richman Date: Fri, 20 Feb 2026 13:06:35 -0800 Subject: [PATCH] feat(cli): make JetBrains warning more specific (#19687) --- packages/cli/src/gemini.tsx | 8 +- packages/cli/src/utils/userStartupWarnings.ts | 7 +- packages/core/src/utils/compatibility.test.ts | 243 ++++++++++++------ packages/core/src/utils/compatibility.ts | 23 +- 4 files changed, 194 insertions(+), 87 deletions(-) diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index dc3d64046b..8dbff89128 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -671,6 +671,10 @@ export async function main() { } let input = config.getQuestion(); + const useAlternateBuffer = shouldEnterAlternateScreen( + isAlternateBufferEnabled(settings), + config.getScreenReader(), + ); const rawStartupWarnings = await getStartupWarnings(); const startupWarnings: StartupWarning[] = [ ...rawStartupWarnings.map((message) => ({ @@ -678,7 +682,9 @@ export async function main() { message, priority: WarningPriority.High, })), - ...(await getUserStartupWarnings(settings.merged)), + ...(await getUserStartupWarnings(settings.merged, undefined, { + isAlternateBuffer: useAlternateBuffer, + })), ]; // Handle --resume flag diff --git a/packages/cli/src/utils/userStartupWarnings.ts b/packages/cli/src/utils/userStartupWarnings.ts index da9623acb6..6174e6c420 100644 --- a/packages/cli/src/utils/userStartupWarnings.ts +++ b/packages/cli/src/utils/userStartupWarnings.ts @@ -88,6 +88,7 @@ const WARNING_CHECKS: readonly WarningCheck[] = [ export async function getUserStartupWarnings( settings: Settings, workspaceRoot: string = process.cwd(), + options?: { isAlternateBuffer?: boolean }, ): Promise { const results = await Promise.all( WARNING_CHECKS.map(async (check) => { @@ -105,7 +106,11 @@ export async function getUserStartupWarnings( const warnings = results.filter((w): w is StartupWarning => w !== null); if (settings.ui?.showCompatibilityWarnings !== false) { - warnings.push(...getCompatibilityWarnings()); + warnings.push( + ...getCompatibilityWarnings({ + isAlternateBuffer: options?.isAlternateBuffer, + }), + ); } return warnings; diff --git a/packages/core/src/utils/compatibility.test.ts b/packages/core/src/utils/compatibility.test.ts index c7819578f1..faf0dd579d 100644 --- a/packages/core/src/utils/compatibility.test.ts +++ b/packages/core/src/utils/compatibility.test.ts @@ -12,6 +12,7 @@ import { supports256Colors, supportsTrueColor, getCompatibilityWarnings, + WarningPriority, } from './compatibility.js'; vi.mock('node:os', () => ({ @@ -31,83 +32,128 @@ describe('compatibility', () => { }); describe('isWindows10', () => { - it('should return true for Windows 10 (build < 22000)', () => { - vi.mocked(os.platform).mockReturnValue('win32'); - vi.mocked(os.release).mockReturnValue('10.0.19041'); - expect(isWindows10()).toBe(true); - }); - - it('should return false for Windows 11 (build >= 22000)', () => { - vi.mocked(os.platform).mockReturnValue('win32'); - vi.mocked(os.release).mockReturnValue('10.0.22000'); - expect(isWindows10()).toBe(false); - }); - - it('should return false for non-Windows platforms', () => { - vi.mocked(os.platform).mockReturnValue('darwin'); - vi.mocked(os.release).mockReturnValue('20.6.0'); - expect(isWindows10()).toBe(false); - }); + it.each<{ + platform: NodeJS.Platform; + release: string; + expected: boolean; + desc: string; + }>([ + { + platform: 'win32', + release: '10.0.19041', + expected: true, + desc: 'Windows 10 (build < 22000)', + }, + { + platform: 'win32', + release: '10.0.22000', + expected: false, + desc: 'Windows 11 (build >= 22000)', + }, + { + platform: 'darwin', + release: '20.6.0', + expected: false, + desc: 'non-Windows platforms', + }, + ])( + 'should return $expected for $desc', + ({ platform, release, expected }) => { + vi.mocked(os.platform).mockReturnValue(platform); + vi.mocked(os.release).mockReturnValue(release); + expect(isWindows10()).toBe(expected); + }, + ); }); describe('isJetBrainsTerminal', () => { - it('should return true when TERMINAL_EMULATOR is JetBrains-JediTerm', () => { - vi.stubEnv('TERMINAL_EMULATOR', 'JetBrains-JediTerm'); - expect(isJetBrainsTerminal()).toBe(true); - }); - - it('should return false for other terminals', () => { - vi.stubEnv('TERMINAL_EMULATOR', 'something-else'); - expect(isJetBrainsTerminal()).toBe(false); - }); - - it('should return false when TERMINAL_EMULATOR is not set', () => { - vi.stubEnv('TERMINAL_EMULATOR', ''); - expect(isJetBrainsTerminal()).toBe(false); + it.each<{ env: string; expected: boolean; desc: string }>([ + { + env: 'JetBrains-JediTerm', + expected: true, + desc: 'TERMINAL_EMULATOR is JetBrains-JediTerm', + }, + { env: 'something-else', expected: false, desc: 'other terminals' }, + { env: '', expected: false, desc: 'TERMINAL_EMULATOR is not set' }, + ])('should return $expected when $desc', ({ env, expected }) => { + vi.stubEnv('TERMINAL_EMULATOR', env); + expect(isJetBrainsTerminal()).toBe(expected); }); }); describe('supports256Colors', () => { - it('should return true when getColorDepth returns >= 8', () => { - process.stdout.getColorDepth = vi.fn().mockReturnValue(8); - expect(supports256Colors()).toBe(true); - }); - - it('should return true when TERM contains 256color', () => { - process.stdout.getColorDepth = vi.fn().mockReturnValue(4); - vi.stubEnv('TERM', 'xterm-256color'); - expect(supports256Colors()).toBe(true); - }); - - it('should return false when 256 colors are not supported', () => { - process.stdout.getColorDepth = vi.fn().mockReturnValue(4); - vi.stubEnv('TERM', 'xterm'); - expect(supports256Colors()).toBe(false); + it.each<{ + depth: number; + term?: string; + expected: boolean; + desc: string; + }>([ + { + depth: 8, + term: undefined, + expected: true, + desc: 'getColorDepth returns >= 8', + }, + { + depth: 4, + term: 'xterm-256color', + expected: true, + desc: 'TERM contains 256color', + }, + { + depth: 4, + term: 'xterm', + expected: false, + desc: '256 colors are not supported', + }, + ])('should return $expected when $desc', ({ depth, term, expected }) => { + process.stdout.getColorDepth = vi.fn().mockReturnValue(depth); + if (term !== undefined) { + vi.stubEnv('TERM', term); + } + expect(supports256Colors()).toBe(expected); }); }); describe('supportsTrueColor', () => { - it('should return true when COLORTERM is truecolor', () => { - vi.stubEnv('COLORTERM', 'truecolor'); - expect(supportsTrueColor()).toBe(true); - }); - - it('should return true when COLORTERM is 24bit', () => { - vi.stubEnv('COLORTERM', '24bit'); - expect(supportsTrueColor()).toBe(true); - }); - - it('should return true when getColorDepth returns >= 24', () => { - vi.stubEnv('COLORTERM', ''); - process.stdout.getColorDepth = vi.fn().mockReturnValue(24); - expect(supportsTrueColor()).toBe(true); - }); - - it('should return false when true color is not supported', () => { - vi.stubEnv('COLORTERM', ''); - process.stdout.getColorDepth = vi.fn().mockReturnValue(8); - expect(supportsTrueColor()).toBe(false); - }); + it.each<{ + colorterm: string; + depth: number; + expected: boolean; + desc: string; + }>([ + { + colorterm: 'truecolor', + depth: 8, + expected: true, + desc: 'COLORTERM is truecolor', + }, + { + colorterm: '24bit', + depth: 8, + expected: true, + desc: 'COLORTERM is 24bit', + }, + { + colorterm: '', + depth: 24, + expected: true, + desc: 'getColorDepth returns >= 24', + }, + { + colorterm: '', + depth: 8, + expected: false, + desc: 'true color is not supported', + }, + ])( + 'should return $expected when $desc', + ({ colorterm, depth, expected }) => { + vi.stubEnv('COLORTERM', colorterm); + process.stdout.getColorDepth = vi.fn().mockReturnValue(depth); + expect(supportsTrueColor()).toBe(expected); + }, + ); }); describe('getCompatibilityWarnings', () => { @@ -131,17 +177,58 @@ describe('compatibility', () => { ); }); - it('should return JetBrains warning when detected', () => { + it.each<{ + platform: NodeJS.Platform; + release: string; + externalTerminal: string; + desc: string; + }>([ + { + platform: 'darwin', + release: '20.6.0', + externalTerminal: 'iTerm2 or Ghostty', + desc: 'macOS', + }, + { + platform: 'win32', + release: '10.0.22000', + externalTerminal: 'Windows Terminal', + desc: 'Windows', + }, // Valid Windows 11 release to not trigger the Windows 10 warning + { + platform: 'linux', + release: '5.10.0', + externalTerminal: 'Ghostty', + desc: 'Linux', + }, + ])( + 'should return JetBrains warning when detected and in alternate buffer ($desc)', + ({ platform, release, externalTerminal }) => { + vi.mocked(os.platform).mockReturnValue(platform); + vi.mocked(os.release).mockReturnValue(release); + vi.stubEnv('TERMINAL_EMULATOR', 'JetBrains-JediTerm'); + + const warnings = getCompatibilityWarnings({ isAlternateBuffer: true }); + expect(warnings).toContainEqual( + expect.objectContaining({ + id: 'jetbrains-terminal', + message: expect.stringContaining( + `Warning: JetBrains mouse scrolling is unreliable. Disabling alternate buffer mode in settings or using an external terminal (e.g., ${externalTerminal}) is recommended.`, + ), + priority: WarningPriority.High, + }), + ); + }, + ); + + it('should not return JetBrains warning when detected but NOT in alternate buffer', () => { vi.mocked(os.platform).mockReturnValue('darwin'); vi.stubEnv('TERMINAL_EMULATOR', 'JetBrains-JediTerm'); - const warnings = getCompatibilityWarnings(); - expect(warnings).toContainEqual( - expect.objectContaining({ - id: 'jetbrains-terminal', - message: expect.stringContaining('JetBrains terminal detected'), - }), - ); + const warnings = getCompatibilityWarnings({ isAlternateBuffer: false }); + expect( + warnings.find((w) => w.id === 'jetbrains-terminal'), + ).toBeUndefined(); }); it('should return 256-color warning when 256 colors are not supported', () => { @@ -156,7 +243,7 @@ describe('compatibility', () => { expect.objectContaining({ id: '256-color', message: expect.stringContaining('256-color support not detected'), - priority: 'high', + priority: WarningPriority.High, }), ); // Should NOT show true-color warning if 256-color warning is shown @@ -177,7 +264,7 @@ describe('compatibility', () => { message: expect.stringContaining( 'True color (24-bit) support not detected', ), - priority: 'low', + priority: WarningPriority.Low, }), ); }); @@ -201,10 +288,10 @@ describe('compatibility', () => { vi.stubEnv('TERM_PROGRAM', 'xterm'); process.stdout.getColorDepth = vi.fn().mockReturnValue(8); - const warnings = getCompatibilityWarnings(); + const warnings = getCompatibilityWarnings({ isAlternateBuffer: true }); expect(warnings).toHaveLength(3); expect(warnings[0].message).toContain('Windows 10 detected'); - expect(warnings[1].message).toContain('JetBrains terminal detected'); + expect(warnings[1].message).toContain('JetBrains'); expect(warnings[2].message).toContain( 'True color (24-bit) support not detected', ); diff --git a/packages/core/src/utils/compatibility.ts b/packages/core/src/utils/compatibility.ts index 8099351ad0..15b2ae24b4 100644 --- a/packages/core/src/utils/compatibility.ts +++ b/packages/core/src/utils/compatibility.ts @@ -75,9 +75,6 @@ export function supportsTrueColor(): boolean { return false; } -/** - * Returns a list of compatibility warnings based on the current environment. - */ export enum WarningPriority { Low = 'low', High = 'high', @@ -89,7 +86,12 @@ export interface StartupWarning { priority: WarningPriority; } -export function getCompatibilityWarnings(): StartupWarning[] { +/** + * Returns a list of compatibility warnings based on the current environment. + */ +export function getCompatibilityWarnings(options?: { + isAlternateBuffer?: boolean; +}): StartupWarning[] { const warnings: StartupWarning[] = []; if (isWindows10()) { @@ -101,11 +103,18 @@ export function getCompatibilityWarnings(): StartupWarning[] { }); } - if (isJetBrainsTerminal()) { + if (isJetBrainsTerminal() && options?.isAlternateBuffer) { + const platformTerminals: Partial> = { + win32: 'Windows Terminal', + darwin: 'iTerm2 or Ghostty', + linux: 'Ghostty', + }; + const suggestion = platformTerminals[os.platform()]; + const suggestedTerminals = suggestion ? ` (e.g., ${suggestion})` : ''; + warnings.push({ id: 'jetbrains-terminal', - message: - 'Warning: JetBrains terminal detected. You may experience rendering or scrolling issues. Using an external terminal (e.g., Windows Terminal, iTerm2) is recommended.', + message: `Warning: JetBrains mouse scrolling is unreliable. Disabling alternate buffer mode in settings or using an external terminal${suggestedTerminals} is recommended.`, priority: WarningPriority.High, }); }