mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-16 22:36:48 -07:00
fix(cli): handle tmux false positive background detection (#27572)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com>
This commit is contained in:
@@ -117,6 +117,51 @@ describe('TerminalCapabilityManager', () => {
|
||||
expect(manager.getTerminalBackgroundColor()).toBe('#00ff00');
|
||||
});
|
||||
|
||||
it('should ignore #ffffff in tmux as it is a common false positive', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
vi.spyOn(manager, 'isTmux').mockReturnValue(true);
|
||||
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
// Simulate OSC 11 response for white
|
||||
stdin.emit('data', Buffer.from('\x1b]11;rgb:ffff/ffff/ffff\x1b\\'));
|
||||
// Complete detection with DA1
|
||||
stdin.emit('data', Buffer.from('\x1b[?62c'));
|
||||
|
||||
await promise;
|
||||
expect(manager.getTerminalBackgroundColor()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not ignore #ffffff when NOT in tmux', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
vi.spyOn(manager, 'isTmux').mockReturnValue(false);
|
||||
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
// Simulate OSC 11 response for white
|
||||
stdin.emit('data', Buffer.from('\x1b]11;rgb:ffff/ffff/ffff\x1b\\'));
|
||||
// Complete detection with DA1
|
||||
stdin.emit('data', Buffer.from('\x1b[?62c'));
|
||||
|
||||
await promise;
|
||||
expect(manager.getTerminalBackgroundColor()).toBe('#ffffff');
|
||||
});
|
||||
|
||||
it('should NOT ignore other colors in tmux', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
vi.stubEnv('TMUX', '1');
|
||||
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
// Simulate OSC 11 response for grey
|
||||
stdin.emit('data', Buffer.from('\x1b]11;rgb:8888/8888/8888\x1b\\'));
|
||||
// Complete detection with DA1
|
||||
stdin.emit('data', Buffer.from('\x1b[?62c'));
|
||||
|
||||
await promise;
|
||||
expect(manager.getTerminalBackgroundColor()).toBe('#888888');
|
||||
});
|
||||
|
||||
it('should detect Terminal Name', async () => {
|
||||
const manager = TerminalCapabilityManager.getInstance();
|
||||
const promise = manager.detectCapabilities();
|
||||
|
||||
@@ -161,9 +161,21 @@ export class TerminalCapabilityManager {
|
||||
match[2],
|
||||
match[3],
|
||||
);
|
||||
debugLogger.log(
|
||||
`Detected terminal background color: ${this.terminalBackgroundColor}`,
|
||||
);
|
||||
|
||||
// Heuristic: tmux 3.5+ may report #ffffff when it doesn't know the
|
||||
// actual host terminal color (e.g. over mosh). We ignore this specific
|
||||
// fallback value to prevent blinding the user with a light theme in a
|
||||
// likely dark terminal.
|
||||
if (this.terminalBackgroundColor === '#ffffff' && this.isTmux()) {
|
||||
debugLogger.log(
|
||||
'Ignored #ffffff background in tmux (common false positive over mosh).',
|
||||
);
|
||||
this.terminalBackgroundColor = undefined;
|
||||
} else {
|
||||
debugLogger.log(
|
||||
`Detected terminal background color: ${this.terminalBackgroundColor}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { setupTerminalAndTheme } from './terminalTheme.js';
|
||||
import { terminalCapabilityManager } from '../ui/utils/terminalCapabilityManager.js';
|
||||
import { themeManager } from '../ui/themes/theme-manager.js';
|
||||
import { coreEvents, type Config } from '@google/gemini-cli-core';
|
||||
import type { LoadedSettings } from '../config/settings.js';
|
||||
import type { Theme } from '../ui/themes/theme.js';
|
||||
|
||||
vi.mock('../ui/utils/terminalCapabilityManager.js', () => ({
|
||||
terminalCapabilityManager: {
|
||||
detectCapabilities: vi.fn(),
|
||||
getTerminalBackgroundColor: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../ui/themes/theme-manager.js', () => ({
|
||||
themeManager: {
|
||||
loadCustomThemes: vi.fn(),
|
||||
setActiveTheme: vi.fn(),
|
||||
getActiveTheme: vi.fn(),
|
||||
setTerminalBackground: vi.fn(),
|
||||
isThemeCompatible: vi.fn(),
|
||||
getAllThemes: vi.fn().mockReturnValue([]),
|
||||
},
|
||||
DEFAULT_THEME: { name: 'Default Dark' },
|
||||
}));
|
||||
|
||||
vi.mock('@google/gemini-cli-core', () => ({
|
||||
coreEvents: {
|
||||
emitFeedback: vi.fn(),
|
||||
},
|
||||
debugLogger: {
|
||||
warn: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('setupTerminalAndTheme', () => {
|
||||
let mockConfig: Config;
|
||||
let mockSettings: LoadedSettings;
|
||||
const originalIsTTY = process.stdin.isTTY;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
mockConfig = {
|
||||
isInteractive: vi.fn().mockReturnValue(true),
|
||||
setTerminalBackground: vi.fn(),
|
||||
} as Partial<Config> as Config;
|
||||
mockSettings = {
|
||||
merged: {
|
||||
ui: {
|
||||
customThemes: {},
|
||||
theme: 'Dracula',
|
||||
autoThemeSwitching: true,
|
||||
},
|
||||
},
|
||||
} as Partial<LoadedSettings> as LoadedSettings;
|
||||
|
||||
// Mock process.stdin.isTTY
|
||||
Object.defineProperty(process.stdin, 'isTTY', {
|
||||
value: true,
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(process.stdin, 'isTTY', {
|
||||
value: originalIsTTY,
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit warning when theme is incompatible and autoThemeSwitching is enabled', async () => {
|
||||
vi.mocked(
|
||||
terminalCapabilityManager.getTerminalBackgroundColor,
|
||||
).mockReturnValue('#ffffff'); // Light
|
||||
vi.mocked(themeManager.setActiveTheme).mockReturnValue(true);
|
||||
vi.mocked(themeManager.getActiveTheme).mockReturnValue({
|
||||
name: 'Dracula',
|
||||
type: 'dark',
|
||||
} as Theme);
|
||||
vi.mocked(themeManager.isThemeCompatible).mockReturnValue(false);
|
||||
|
||||
await setupTerminalAndTheme(mockConfig, mockSettings);
|
||||
|
||||
expect(coreEvents.emitFeedback).toHaveBeenCalledWith(
|
||||
'warning',
|
||||
expect.stringContaining(
|
||||
"Theme 'Dracula' (dark) might look incorrect on your light terminal background",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should NOT emit warning when theme is incompatible but autoThemeSwitching is DISABLED', async () => {
|
||||
mockSettings.merged.ui.autoThemeSwitching = false;
|
||||
vi.mocked(
|
||||
terminalCapabilityManager.getTerminalBackgroundColor,
|
||||
).mockReturnValue('#ffffff'); // Light
|
||||
vi.mocked(themeManager.setActiveTheme).mockReturnValue(true);
|
||||
vi.mocked(themeManager.getActiveTheme).mockReturnValue({
|
||||
name: 'Dracula',
|
||||
type: 'dark',
|
||||
} as Theme);
|
||||
vi.mocked(themeManager.isThemeCompatible).mockReturnValue(false);
|
||||
|
||||
await setupTerminalAndTheme(mockConfig, mockSettings);
|
||||
|
||||
expect(coreEvents.emitFeedback).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -56,7 +56,10 @@ export async function setupTerminalAndTheme(
|
||||
config.setTerminalBackground(terminalBackground);
|
||||
themeManager.setTerminalBackground(terminalBackground);
|
||||
|
||||
if (terminalBackground !== undefined) {
|
||||
if (
|
||||
terminalBackground !== undefined &&
|
||||
(settings.merged.ui.autoThemeSwitching ?? true)
|
||||
) {
|
||||
const currentTheme = themeManager.getActiveTheme();
|
||||
if (!themeManager.isThemeCompatible(currentTheme, terminalBackground)) {
|
||||
const backgroundType =
|
||||
|
||||
Reference in New Issue
Block a user