mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 10:27:33 +00:00
fix(terminal): scope terminal color overrides to their light/dark theme slot (#36581)
This commit is contained in:
@@ -278,6 +278,40 @@ it('migrates a shipped split chat/terminal profile using terminal choices while
|
||||
})
|
||||
})
|
||||
|
||||
describe('pre-#10561 shared terminal color overrides', () => {
|
||||
const overrides = { background: '#101010' }
|
||||
const legacy = { terminalColorOverridesSplit: undefined, terminalColorOverrides: overrides }
|
||||
|
||||
it.each([
|
||||
{ theme: 'system', terminalUseSeparateLightTheme: true },
|
||||
{ theme: 'light', terminalUseSeparateLightTheme: true },
|
||||
{ theme: 'system', terminalUseSeparateLightTheme: undefined }
|
||||
])('keep painting both modes when the light slot could show them (%o)', (appearance) => {
|
||||
const normalized = normalizeLegacyProfile({ ...legacy, ...appearance })
|
||||
expect(normalized.terminalColorOverrides).toEqual(overrides)
|
||||
expect(normalized.terminalColorOverridesLight).toEqual(overrides)
|
||||
expect(normalized.terminalColorOverridesSplit).toBe(true)
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ theme: 'dark', terminalUseSeparateLightTheme: true },
|
||||
{ theme: 'light', terminalUseSeparateLightTheme: false }
|
||||
])('stay on the dark slot otherwise (%o)', (appearance) => {
|
||||
const normalized = normalizeLegacyProfile({ ...legacy, ...appearance })
|
||||
expect(normalized.terminalColorOverrides).toEqual(overrides)
|
||||
expect(normalized.terminalColorOverridesLight).toBeUndefined()
|
||||
})
|
||||
|
||||
it('never re-split once migrated, so a cleared light slot stays clear', () => {
|
||||
const normalized = normalizeLegacyProfile({
|
||||
terminalColorOverrides: overrides,
|
||||
theme: 'light',
|
||||
terminalUseSeparateLightTheme: true
|
||||
})
|
||||
expect(normalized.terminalColorOverridesLight).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('terminal default shell', () => {
|
||||
it('trims a hand-edited value and treats whitespace-only as unset', () => {
|
||||
expect(
|
||||
|
||||
@@ -36,6 +36,7 @@ export function normalizeLoadedGlobalSettings(
|
||||
migratedOptionAsAlt,
|
||||
migratedFloatingTerminalEnabled,
|
||||
migratedOsc52Clipboard,
|
||||
migratedTerminalColorOverrides,
|
||||
migratedFloatingTerminalCwd,
|
||||
migratedFloatingTerminalTrustedCwds
|
||||
} = terminal
|
||||
@@ -111,6 +112,7 @@ export function normalizeLoadedGlobalSettings(
|
||||
localAccountRuntime: migratedLocalAccountRuntime,
|
||||
localAccountRuntimeDefaultedToAutoForAllUsers: true,
|
||||
...migratedOsc52Clipboard,
|
||||
...migratedTerminalColorOverrides,
|
||||
floatingTerminalEnabled: migratedFloatingTerminalEnabled,
|
||||
floatingTerminalDefaultedForAllUsers: true,
|
||||
floatingTerminalCwd: migratedFloatingTerminalCwd,
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
normalizeOsc52ClipboardDefaultOn,
|
||||
osc52ClipboardDefaultOnOverridesPersistedOff
|
||||
} from '../../../shared/osc52-clipboard-settings'
|
||||
import { splitLegacyTerminalColorOverrides } from '../../../shared/terminal-color-overrides'
|
||||
import {
|
||||
migrateTerminalScrollbackRows,
|
||||
migrateTerminalTuiScrollSensitivityDefault
|
||||
@@ -47,6 +48,7 @@ export type PreparedLoadedTerminalSettings = {
|
||||
GlobalSettings,
|
||||
'terminalAllowOsc52Clipboard' | 'terminalAllowOsc52ClipboardDefaultedOnForAllUsers'
|
||||
>
|
||||
migratedTerminalColorOverrides: ReturnType<typeof splitLegacyTerminalColorOverrides>
|
||||
migratedFloatingTerminalCwd: GlobalSettings['floatingTerminalCwd']
|
||||
migratedFloatingTerminalTrustedCwds: GlobalSettings['floatingTerminalTrustedCwds']
|
||||
osc52ClipboardNoticePending: boolean
|
||||
@@ -135,6 +137,10 @@ export function prepareLoadedTerminalSettings(
|
||||
if (parsed.settings?.terminalAllowOsc52ClipboardDefaultedOnForAllUsers !== true) {
|
||||
markNeedsSave()
|
||||
}
|
||||
const migratedTerminalColorOverrides = splitLegacyTerminalColorOverrides(parsed.settings)
|
||||
if (parsed.settings?.terminalColorOverridesSplit !== true) {
|
||||
markNeedsSave()
|
||||
}
|
||||
const floatingTerminalCwdMigrated =
|
||||
parsed.settings?.floatingTerminalCwdMigratedToAppWorkspace === true
|
||||
// Repair the old empty notes-directory migration; terminal cwd and notes now resolve separately.
|
||||
@@ -182,6 +188,7 @@ export function prepareLoadedTerminalSettings(
|
||||
migratedOptionAsAlt,
|
||||
migratedFloatingTerminalEnabled,
|
||||
migratedOsc52Clipboard,
|
||||
migratedTerminalColorOverrides,
|
||||
migratedFloatingTerminalCwd,
|
||||
migratedFloatingTerminalTrustedCwds,
|
||||
osc52ClipboardNoticePending
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import { syncPreviewTerminalLigatures } from './preview-terminal-ligatures'
|
||||
import type { DashboardCardTerminalInput } from '../../../../shared/dashboard-snapshot'
|
||||
import { terminalPreviewUnavailableMessage } from './terminal-preview-unavailable-message'
|
||||
import { getBuiltinTheme, resolveEffectiveTerminalAppearance } from '@/lib/terminal-theme'
|
||||
import { resolveEffectiveTerminalAppearance } from '@/lib/terminal-theme'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useAppStore } from '@/store'
|
||||
import { createPreviewGridClaim } from './preview-grid-claim'
|
||||
@@ -65,10 +65,7 @@ export function AgentTerminalPreview({
|
||||
return { terminalTheme: null, terminalMode: 'dark' as const }
|
||||
}
|
||||
const appearance = resolveEffectiveTerminalAppearance(settings, systemPrefersDark)
|
||||
const theme = composeActiveTerminalTheme(
|
||||
appearance.theme ?? getBuiltinTheme(appearance.themeName),
|
||||
settings
|
||||
)
|
||||
const theme = composeActiveTerminalTheme(appearance.theme, settings)
|
||||
return { terminalTheme: theme, terminalMode: appearance.mode }
|
||||
}, [settings, systemPrefersDark])
|
||||
// Settings arrive as cloned snapshots; compare theme values before reconnecting.
|
||||
|
||||
@@ -4,6 +4,7 @@ import { resolve } from 'node:path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createGlobalSettingsFixture } from '../../../../shared/global-settings-test-fixture'
|
||||
import * as terminalThemeSelection from '../../../../shared/terminal-theme-selection'
|
||||
import { TERMINAL_COLOR_OVERRIDES_SETTING } from '../../../../shared/terminal-color-overrides'
|
||||
import { nativeChatAppearanceStyle } from './native-chat-appearance-style'
|
||||
|
||||
const css = readFileSync(resolve('src/renderer/src/assets/main.css'), 'utf8')
|
||||
@@ -31,7 +32,7 @@ function createChat(appScheme: 'light' | 'dark', terminalScheme: 'light' | 'dark
|
||||
const style = nativeChatAppearanceStyle(
|
||||
createGlobalSettingsFixture({
|
||||
theme: appScheme,
|
||||
terminalColorOverrides:
|
||||
[TERMINAL_COLOR_OVERRIDES_SETTING[appScheme]]:
|
||||
terminalScheme === 'light'
|
||||
? { background: '#ffffff', foreground: '#000000' }
|
||||
: { background: '#000000', foreground: '#ffffff' },
|
||||
|
||||
@@ -46,6 +46,7 @@ export function selectNativeChatAppearanceSettings(
|
||||
terminalUseSeparateLightTheme: settings?.terminalUseSeparateLightTheme,
|
||||
terminalCustomThemes: settings?.terminalCustomThemes,
|
||||
terminalColorOverrides: settings?.terminalColorOverrides,
|
||||
terminalColorOverridesLight: settings?.terminalColorOverridesLight,
|
||||
terminalFontFamily: settings?.terminalFontFamily,
|
||||
terminalFontSize: settings?.nativeChatAppearance?.matchTerminalInterface
|
||||
? settings.terminalFontSize
|
||||
@@ -107,7 +108,8 @@ export function nativeChatAppearanceStyle(
|
||||
terminalThemeLight: settings?.terminalThemeLight ?? '',
|
||||
terminalUseSeparateLightTheme: settings?.terminalUseSeparateLightTheme ?? false,
|
||||
terminalCustomThemes: settings?.terminalCustomThemes,
|
||||
terminalColorOverrides: settings?.terminalColorOverrides
|
||||
terminalColorOverrides: settings?.terminalColorOverrides,
|
||||
terminalColorOverridesLight: settings?.terminalColorOverridesLight
|
||||
},
|
||||
systemPrefersDark
|
||||
)
|
||||
|
||||
@@ -10,6 +10,8 @@ import type { GhosttyImportPreview, GlobalSettings } from '../../../../shared/gl
|
||||
import type { DiscoveryStatusEmitted } from '../../../../shared/onboarding-state-types'
|
||||
import { i18n, translate } from '@/i18n/i18n'
|
||||
import { ChromePreview } from './theme-chrome-preview'
|
||||
import { withImportedTerminalColorOverrides } from '../../../../shared/terminal-theme-selection'
|
||||
import { getSystemPrefersDark } from '@/lib/terminal-theme'
|
||||
|
||||
type ThemeStepProps = {
|
||||
theme: GlobalSettings['theme']
|
||||
@@ -128,17 +130,9 @@ export function ThemeStep({ theme, onThemeChange, settings, updateSettings }: Th
|
||||
track('onboarding_ghostty_import_failed', { reason: 'empty_diff' })
|
||||
return
|
||||
}
|
||||
await updateSettings({
|
||||
...resolved.diff,
|
||||
...(resolved.diff.terminalColorOverrides
|
||||
? {
|
||||
terminalColorOverrides: {
|
||||
...settings.terminalColorOverrides,
|
||||
...resolved.diff.terminalColorOverrides
|
||||
}
|
||||
}
|
||||
: {})
|
||||
})
|
||||
await updateSettings(
|
||||
withImportedTerminalColorOverrides(settings, resolved.diff, getSystemPrefersDark())
|
||||
)
|
||||
// Sync the controller’s local theme so Continue cannot overwrite the imported theme.
|
||||
if (resolved.diff.theme && mountedRef.current) {
|
||||
onThemeChange(resolved.diff.theme)
|
||||
|
||||
@@ -11,7 +11,7 @@ import { translate } from '@/i18n/i18n'
|
||||
import { useAppStore } from '@/store'
|
||||
import { useEffectiveMacOptionAsAlt } from '@/lib/keyboard-layout/use-effective-mac-option-as-alt'
|
||||
import { composeActiveTerminalTheme } from '@/components/terminal-pane/terminal-appearance'
|
||||
import { getBuiltinTheme, resolveEffectiveTerminalAppearance } from '@/lib/terminal-theme'
|
||||
import { resolveEffectiveTerminalAppearance } from '@/lib/terminal-theme'
|
||||
import { useSystemPrefersDark } from '@/components/terminal-pane/use-system-prefers-dark'
|
||||
import { resolveTerminalMinimumContrastRatio } from '../../../../shared/terminal-contrast-correction'
|
||||
import { syncPreviewTerminalLigatures } from '@/components/dashboard-popout/preview-terminal-ligatures'
|
||||
@@ -71,10 +71,7 @@ function resolveThemeColors(
|
||||
}
|
||||
const appearance = resolveEffectiveTerminalAppearance(settings, systemPrefersDark)
|
||||
return {
|
||||
theme: composeActiveTerminalTheme(
|
||||
appearance.theme ?? getBuiltinTheme(appearance.themeName),
|
||||
settings
|
||||
),
|
||||
theme: composeActiveTerminalTheme(appearance.theme, settings),
|
||||
mode: appearance.mode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,8 @@ it.each([6, 60])('prepares only the clicked terminal palette among %i choices',
|
||||
terminalUseSeparateLightTheme: true,
|
||||
terminalPluginPalettes: [{ id: candidate.id, colors: candidate.terminal }]
|
||||
})
|
||||
expect(update).toHaveProperty('terminalColorOverrides', undefined)
|
||||
// The light slot now has its own theme, so the dark slot's overrides stay for dark mode.
|
||||
expect(update).not.toHaveProperty('terminalColorOverrides')
|
||||
expect(update).not.toHaveProperty('theme')
|
||||
expect(settings().terminalThemeLight).toBe(before.terminalThemeLight)
|
||||
expect(
|
||||
|
||||
@@ -5,6 +5,10 @@ import type { PluginTerminalThemeRegistration } from '../../../../../shared/plug
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { resolveDocumentTheme } from '@/lib/document-theme'
|
||||
import { pluginAppThemeSlotUpdate } from '../../../../../shared/plugin-theme-settings'
|
||||
import {
|
||||
hasTerminalColorOverrides,
|
||||
TERMINAL_COLOR_OVERRIDES_SETTING
|
||||
} from '../../../../../shared/terminal-color-overrides'
|
||||
import { withTerminalPluginPalettes } from '@/lib/terminal-plugin-palettes'
|
||||
import { useAppStore } from '@/store'
|
||||
import { PLUGIN_ACTION_TOAST_DURATION_MS } from './plugin-action-toast'
|
||||
@@ -62,13 +66,14 @@ export function terminalThemeUpdate(
|
||||
dark: boolean,
|
||||
terminalThemes: readonly PluginTerminalThemeRegistration[]
|
||||
): Partial<GlobalSettings> {
|
||||
const slot = terminalSlot(theme.mode, dark) === 'terminalThemeLight' ? 'light' : 'dark'
|
||||
return withTerminalPluginPalettes(
|
||||
settings,
|
||||
{
|
||||
...terminalThemeSlotUpdate(theme, dark),
|
||||
// A selected theme must replace overrides or they mask its palette.
|
||||
...(Object.keys(settings.terminalColorOverrides ?? {}).length > 0
|
||||
? { terminalColorOverrides: undefined }
|
||||
// A selected theme must replace its slot's overrides or they mask its palette.
|
||||
...(hasTerminalColorOverrides(settings, slot)
|
||||
? { [TERMINAL_COLOR_OVERRIDES_SETTING[slot]]: undefined }
|
||||
: {})
|
||||
},
|
||||
terminalThemes
|
||||
@@ -125,6 +130,7 @@ export async function applyPluginThemesNow(pluginKey: string): Promise<boolean>
|
||||
terminalThemeLight: settings.terminalThemeLight,
|
||||
terminalUseSeparateLightTheme: settings.terminalUseSeparateLightTheme,
|
||||
terminalColorOverrides: settings.terminalColorOverrides,
|
||||
terminalColorOverridesLight: settings.terminalColorOverridesLight,
|
||||
terminalPluginPalettes: settings.terminalPluginPalettes ?? []
|
||||
}
|
||||
let next: Partial<GlobalSettings> = {}
|
||||
|
||||
@@ -158,7 +158,10 @@ export function TerminalSurfacePreview({ settings }: { settings: GlobalSettings
|
||||
const systemPrefersDark = useSystemPrefersDark()
|
||||
const customThemes = JSON.stringify(settings.terminalCustomThemes)
|
||||
const pluginPalettes = JSON.stringify(settings.terminalPluginPalettes)
|
||||
const colorOverrides = JSON.stringify(settings.terminalColorOverrides)
|
||||
const colorOverrides = JSON.stringify([
|
||||
settings.terminalColorOverrides,
|
||||
settings.terminalColorOverridesLight
|
||||
])
|
||||
const theme = useMemo(
|
||||
() =>
|
||||
composeActiveTerminalTheme(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { useState, type ReactNode } from 'react'
|
||||
import type { GlobalSettings } from '../../../../shared/global-settings-types'
|
||||
import type { TerminalThemeSlot } from '../../../../shared/terminal-color-overrides'
|
||||
import {
|
||||
matchesSettingsSearch,
|
||||
normalizeSettingsSearchQuery,
|
||||
@@ -49,8 +50,6 @@ type TerminalAppearanceSectionProps = {
|
||||
forceVisiblePrimary?: boolean
|
||||
}
|
||||
|
||||
type TerminalThemeTarget = 'dark' | 'light'
|
||||
|
||||
function scoreThemeTargetIntent(searchQuery: string, entries: SettingsSearchEntry[]): number {
|
||||
// Infer dark/light target from labels/aliases, not incidental description mentions.
|
||||
return scoreSettingsSearch(
|
||||
@@ -62,7 +61,7 @@ function scoreThemeTargetIntent(searchQuery: string, entries: SettingsSearchEntr
|
||||
function getPreferredThemeTarget(
|
||||
darkThemeSearchScore: number,
|
||||
lightThemeSearchScore: number
|
||||
): TerminalThemeTarget | undefined {
|
||||
): TerminalThemeSlot | undefined {
|
||||
if (darkThemeSearchScore === lightThemeSearchScore) {
|
||||
return undefined
|
||||
}
|
||||
@@ -127,47 +126,58 @@ export function TerminalAppearanceSection({
|
||||
showDesktopThemeImports && (!isSearching || forceVisiblePrimary || ghosttyImportMatches)
|
||||
const showTypographyAdvancedDisclosure = !isSearching || typographyMatches
|
||||
|
||||
const advancedGroups = [
|
||||
cursorMatches
|
||||
? {
|
||||
key: 'cursor',
|
||||
node: (
|
||||
<TerminalCursorAppearanceSection settings={settings} updateSettings={updateSettings} />
|
||||
)
|
||||
}
|
||||
: null,
|
||||
paneMatches
|
||||
? {
|
||||
key: 'pane',
|
||||
node: (
|
||||
<TerminalPaneAppearanceSection settings={settings} updateSettings={updateSettings} />
|
||||
)
|
||||
}
|
||||
: null,
|
||||
windowMatches
|
||||
? {
|
||||
key: 'window',
|
||||
node: <TerminalWindowSection settings={settings} updateSettings={updateSettings} />
|
||||
}
|
||||
: null
|
||||
].filter((group): group is { key: string; node: React.JSX.Element } => group !== null)
|
||||
const showAdvancedDisclosure = !isSearching || advancedGroups.length > 0
|
||||
const previewAdvancedContent = showAdvancedDisclosure ? (
|
||||
<AppearanceAdvancedDisclosure
|
||||
showTopBorder={false}
|
||||
className="mt-0 pt-2"
|
||||
contentClassName="ml-4 pt-4"
|
||||
>
|
||||
{advancedGroups.map((group, index) => (
|
||||
<div
|
||||
key={group.key}
|
||||
className={index > 0 ? 'mt-2 border-t border-border/60 pt-4' : undefined}
|
||||
>
|
||||
{group.node}
|
||||
</div>
|
||||
))}
|
||||
</AppearanceAdvancedDisclosure>
|
||||
) : null
|
||||
const advancedGroups = (colorOverrideSlot: TerminalThemeSlot) =>
|
||||
[
|
||||
cursorMatches
|
||||
? {
|
||||
key: 'cursor',
|
||||
node: (
|
||||
<TerminalCursorAppearanceSection
|
||||
settings={settings}
|
||||
updateSettings={updateSettings}
|
||||
/>
|
||||
)
|
||||
}
|
||||
: null,
|
||||
paneMatches
|
||||
? {
|
||||
key: 'pane',
|
||||
node: (
|
||||
<TerminalPaneAppearanceSection settings={settings} updateSettings={updateSettings} />
|
||||
)
|
||||
}
|
||||
: null,
|
||||
windowMatches
|
||||
? {
|
||||
key: 'window',
|
||||
node: (
|
||||
<TerminalWindowSection
|
||||
settings={settings}
|
||||
updateSettings={updateSettings}
|
||||
colorOverrideSlot={colorOverrideSlot}
|
||||
/>
|
||||
)
|
||||
}
|
||||
: null
|
||||
].filter((group): group is { key: string; node: React.JSX.Element } => group !== null)
|
||||
const showAdvancedDisclosure = !isSearching || cursorMatches || paneMatches || windowMatches
|
||||
const renderPreviewAdvancedContent = (colorOverrideSlot: TerminalThemeSlot): ReactNode =>
|
||||
showAdvancedDisclosure ? (
|
||||
<AppearanceAdvancedDisclosure
|
||||
showTopBorder={false}
|
||||
className="mt-0 pt-2"
|
||||
contentClassName="ml-4 pt-4"
|
||||
>
|
||||
{advancedGroups(colorOverrideSlot).map((group, index) => (
|
||||
<div
|
||||
key={group.key}
|
||||
className={index > 0 ? 'mt-2 border-t border-border/60 pt-4' : undefined}
|
||||
>
|
||||
{group.node}
|
||||
</div>
|
||||
))}
|
||||
</AppearanceAdvancedDisclosure>
|
||||
) : null
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
@@ -260,7 +270,7 @@ export function TerminalAppearanceSection({
|
||||
warpThemes={warpThemes}
|
||||
showThemeImport={showDesktopThemeImports}
|
||||
preferredTarget={preferredThemeTarget}
|
||||
advancedContent={previewAdvancedContent}
|
||||
renderAdvancedContent={renderPreviewAdvancedContent}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -90,6 +90,8 @@ export function TerminalSettingsPreview({
|
||||
settings.terminalUseSeparateLightTheme,
|
||||
settings.terminalDividerColorDark,
|
||||
settings.terminalDividerColorLight,
|
||||
settings.terminalColorOverrides,
|
||||
settings.terminalColorOverridesLight,
|
||||
systemPrefersDark
|
||||
]
|
||||
)
|
||||
@@ -98,12 +100,7 @@ export function TerminalSettingsPreview({
|
||||
const composedTheme = useMemo(
|
||||
() => composeActiveTerminalTheme(appearance.theme, settings),
|
||||
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
appearance,
|
||||
settings.terminalColorOverrides,
|
||||
settings.terminalBackgroundOpacity,
|
||||
settings.terminalCursorOpacity
|
||||
]
|
||||
[appearance, settings.terminalBackgroundOpacity, settings.terminalCursorOpacity]
|
||||
)
|
||||
|
||||
const dividerThicknessPx = clampNumber(settings.terminalDividerThicknessPx, 1, 32)
|
||||
|
||||
@@ -234,12 +234,13 @@ describe('TerminalThemeCatalogSection', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('clears imported color overrides when selecting a light theme', () => {
|
||||
it('clears only the light slot overrides when selecting a light theme', () => {
|
||||
const updateSettings = vi.fn()
|
||||
const element = renderCatalog(
|
||||
makeSettings({
|
||||
terminalUseSeparateLightTheme: true,
|
||||
terminalColorOverrides: { background: '#111111' }
|
||||
terminalColorOverrides: { background: '#111111' },
|
||||
terminalColorOverridesLight: { background: '#eeeeee' }
|
||||
}),
|
||||
updateSettings,
|
||||
'light'
|
||||
@@ -251,7 +252,7 @@ describe('TerminalThemeCatalogSection', () => {
|
||||
|
||||
expect(updateSettings.mock.calls[0]?.[0]).toStrictEqual({
|
||||
terminalThemeLight: 'GitHub Light',
|
||||
terminalColorOverrides: undefined
|
||||
terminalColorOverridesLight: undefined
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@ import { usePluginTerminalThemes } from '@/store/plugin-themes'
|
||||
import { withTerminalPluginPalettes } from '@/lib/terminal-plugin-palettes'
|
||||
import { useState, type Dispatch, type ReactNode, type SetStateAction } from 'react'
|
||||
import type { GlobalSettings } from '../../../../shared/global-settings-types'
|
||||
import {
|
||||
hasTerminalColorOverrides,
|
||||
TERMINAL_COLOR_OVERRIDES_SETTING,
|
||||
type TerminalThemeSlot
|
||||
} from '../../../../shared/terminal-color-overrides'
|
||||
import {
|
||||
ColorField,
|
||||
SettingsSegmentedControl,
|
||||
@@ -23,11 +28,9 @@ import {
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
type TerminalThemeTarget = 'dark' | 'light'
|
||||
let lastEditedTerminalThemeTarget: TerminalThemeSlot | null = null
|
||||
|
||||
let lastEditedTerminalThemeTarget: TerminalThemeTarget | null = null
|
||||
|
||||
function rememberTerminalThemeTarget(target: TerminalThemeTarget): void {
|
||||
function rememberTerminalThemeTarget(target: TerminalThemeSlot): void {
|
||||
lastEditedTerminalThemeTarget = target
|
||||
}
|
||||
|
||||
@@ -43,8 +46,8 @@ function isCustomizedTheme(themeName: string, defaultThemeName: string): boolean
|
||||
function getInitialTerminalThemeTarget(
|
||||
settings: GlobalSettings,
|
||||
systemPrefersDark: boolean,
|
||||
preferredTarget?: TerminalThemeTarget
|
||||
): TerminalThemeTarget {
|
||||
preferredTarget?: TerminalThemeSlot
|
||||
): TerminalThemeSlot {
|
||||
if (preferredTarget) {
|
||||
return preferredTarget
|
||||
}
|
||||
@@ -73,8 +76,8 @@ type TerminalThemeCatalogSectionProps = {
|
||||
importedHighlightSignal: number
|
||||
warpThemes: UseWarpThemeImportReturn
|
||||
showThemeImport: boolean
|
||||
preferredTarget?: TerminalThemeTarget
|
||||
advancedContent?: ReactNode
|
||||
preferredTarget?: TerminalThemeSlot
|
||||
renderAdvancedContent?: (colorOverrideSlot: TerminalThemeSlot) => ReactNode
|
||||
}
|
||||
|
||||
export function TerminalThemeCatalogSection({
|
||||
@@ -88,12 +91,12 @@ export function TerminalThemeCatalogSection({
|
||||
warpThemes,
|
||||
showThemeImport,
|
||||
preferredTarget,
|
||||
advancedContent
|
||||
renderAdvancedContent
|
||||
}: TerminalThemeCatalogSectionProps): React.JSX.Element {
|
||||
const [target, setTargetState] = useState<TerminalThemeTarget>(() =>
|
||||
const [target, setTargetState] = useState<TerminalThemeSlot>(() =>
|
||||
getInitialTerminalThemeTarget(settings, systemPrefersDark, preferredTarget)
|
||||
)
|
||||
const setTarget = (nextTarget: TerminalThemeTarget): void => {
|
||||
const setTarget = (nextTarget: TerminalThemeSlot): void => {
|
||||
rememberTerminalThemeTarget(nextTarget)
|
||||
setTargetState(nextTarget)
|
||||
}
|
||||
@@ -103,6 +106,8 @@ export function TerminalThemeCatalogSection({
|
||||
const matchDarkMode = !settings.terminalUseSeparateLightTheme
|
||||
const lightModeMatchesDark = isLightTarget && matchDarkMode
|
||||
const showCustomControls = !lightModeMatchesDark
|
||||
// Light mode that matches dark paints the dark slot, so its overrides are the ones the preview shows.
|
||||
const advancedContent = renderAdvancedContent?.(lightModeMatchesDark ? 'dark' : target)
|
||||
const selectedTheme = isLightTarget ? settings.terminalThemeLight : settings.terminalThemeDark
|
||||
const pickerTitle = isLightTarget
|
||||
? translate('auto.components.settings.TerminalThemeSections.8273bc75d7', 'Light Theme')
|
||||
@@ -254,15 +259,15 @@ export function TerminalThemeCatalogSection({
|
||||
onQueryChange={setThemeSearch}
|
||||
onSelectTheme={(theme) => {
|
||||
rememberTerminalThemeTarget(target)
|
||||
// A selected theme must replace overrides or they mask the picker.
|
||||
const hasColorOverrides =
|
||||
Object.keys(settings.terminalColorOverrides ?? {}).length > 0
|
||||
// A selected theme must replace its slot's overrides or they mask the picker.
|
||||
updateSettings(
|
||||
withTerminalPluginPalettes(
|
||||
settings,
|
||||
{
|
||||
[isLightTarget ? 'terminalThemeLight' : 'terminalThemeDark']: theme,
|
||||
...(hasColorOverrides ? { terminalColorOverrides: undefined } : {})
|
||||
...(hasTerminalColorOverrides(settings, target)
|
||||
? { [TERMINAL_COLOR_OVERRIDES_SETTING[target]]: undefined }
|
||||
: {})
|
||||
},
|
||||
pluginTerminalThemes
|
||||
)
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { RotateCw } from 'lucide-react'
|
||||
import type { GlobalSettings } from '../../../../shared/global-settings-types'
|
||||
import {
|
||||
TERMINAL_COLOR_OVERRIDES_SETTING,
|
||||
terminalColorOverridesForSlot,
|
||||
type TerminalThemeSlot
|
||||
} from '../../../../shared/terminal-color-overrides'
|
||||
import { Button } from '../ui/button'
|
||||
import { Label } from '../ui/label'
|
||||
import { Switch } from '../ui/switch'
|
||||
@@ -10,18 +15,23 @@ import { clampNumber } from '@/lib/terminal-theme'
|
||||
import { useMountedRef } from '@/hooks/useMountedRef'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
|
||||
import { COLOR_OVERRIDE_GROUPS } from './terminal-window-color-groups'
|
||||
|
||||
type TerminalWindowSectionProps = {
|
||||
settings: GlobalSettings
|
||||
updateSettings: (updates: Partial<GlobalSettings>) => void
|
||||
/** Theme slot the Color Overrides editor writes, following the theme catalog's Dark/Light target. */
|
||||
colorOverrideSlot: TerminalThemeSlot
|
||||
}
|
||||
|
||||
import { COLOR_OVERRIDE_GROUPS } from './terminal-window-color-groups'
|
||||
|
||||
export function TerminalWindowSection({
|
||||
settings,
|
||||
updateSettings
|
||||
updateSettings,
|
||||
colorOverrideSlot
|
||||
}: TerminalWindowSectionProps): React.JSX.Element {
|
||||
const [colorOverridesExpanded, setColorOverridesExpanded] = useState(false)
|
||||
const colorOverridesSetting = TERMINAL_COLOR_OVERRIDES_SETTING[colorOverrideSlot]
|
||||
const colorOverrides = terminalColorOverridesForSlot(settings, colorOverrideSlot)
|
||||
// Blur is a startup window option on macOS/Windows; compare against mount state to request restart only after changes.
|
||||
const blurAtMountRef = useRef<boolean>(settings.windowBackgroundBlur ?? false)
|
||||
const blurPendingRestart = (settings.windowBackgroundBlur ?? false) !== blurAtMountRef.current
|
||||
@@ -283,6 +293,17 @@ export function TerminalWindowSection({
|
||||
}`}
|
||||
>
|
||||
<div className="min-h-0 space-y-4">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{colorOverrideSlot === 'light'
|
||||
? translate(
|
||||
'auto.components.settings.TerminalWindowSection.color_overrides_light_description',
|
||||
'Override individual colors of the light terminal theme.'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.settings.TerminalWindowSection.color_overrides_dark_description',
|
||||
'Override individual colors of the dark terminal theme.'
|
||||
)}
|
||||
</p>
|
||||
{COLOR_OVERRIDE_GROUPS.map((group) => (
|
||||
<div key={group.label} className="space-y-2">
|
||||
<p className="text-xs font-semibold text-muted-foreground">{group.label}</p>
|
||||
@@ -292,12 +313,12 @@ export function TerminalWindowSection({
|
||||
key={item.key}
|
||||
label={item.label}
|
||||
description={item.description}
|
||||
value={settings.terminalColorOverrides?.[item.key] ?? ''}
|
||||
value={colorOverrides?.[item.key] ?? ''}
|
||||
fallback=""
|
||||
onChange={(value) =>
|
||||
updateSettings({
|
||||
terminalColorOverrides: {
|
||||
...settings.terminalColorOverrides,
|
||||
[colorOverridesSetting]: {
|
||||
...colorOverrides,
|
||||
[item.key]: value || undefined
|
||||
}
|
||||
})
|
||||
@@ -310,12 +331,17 @@ export function TerminalWindowSection({
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => updateSettings({ terminalColorOverrides: undefined })}
|
||||
onClick={() => updateSettings({ [colorOverridesSetting]: undefined })}
|
||||
>
|
||||
{translate(
|
||||
'auto.components.settings.TerminalWindowSection.03c855d15f',
|
||||
'Reset all color overrides'
|
||||
)}
|
||||
{colorOverrideSlot === 'light'
|
||||
? translate(
|
||||
'auto.components.settings.TerminalWindowSection.reset_light_color_overrides',
|
||||
'Reset light color overrides'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.settings.TerminalWindowSection.reset_dark_color_overrides',
|
||||
'Reset dark color overrides'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useState } from 'react'
|
||||
import type { GhosttyImportPreview, GlobalSettings } from '../../../../shared/global-settings-types'
|
||||
import { useMountedRef } from '../../hooks/useMountedRef'
|
||||
import { withImportedTerminalColorOverrides } from '../../../../shared/terminal-theme-selection'
|
||||
import { getSystemPrefersDark } from '@/lib/terminal-theme'
|
||||
|
||||
export type UseGhosttyImportReturn = {
|
||||
open: boolean
|
||||
@@ -49,17 +51,11 @@ export function useGhosttyImport(
|
||||
if (applied || !preview?.found || Object.keys(preview.diff).length === 0 || !settings) {
|
||||
return
|
||||
}
|
||||
const merged = {
|
||||
...preview.diff,
|
||||
...(preview.diff.terminalColorOverrides
|
||||
? {
|
||||
terminalColorOverrides: {
|
||||
...settings.terminalColorOverrides,
|
||||
...preview.diff.terminalColorOverrides
|
||||
}
|
||||
}
|
||||
: {})
|
||||
}
|
||||
const merged = withImportedTerminalColorOverrides(
|
||||
settings,
|
||||
preview.diff,
|
||||
getSystemPrefersDark()
|
||||
)
|
||||
setApplyError(null)
|
||||
try {
|
||||
// Await persistence before marking applied so rejected IPC cannot report success.
|
||||
|
||||
@@ -5,7 +5,6 @@ import type { GlobalSettings } from '../../../../shared/global-settings-types'
|
||||
describe('composeActiveTerminalTheme', () => {
|
||||
function settingsWith(partial: Partial<GlobalSettings>): GlobalSettings {
|
||||
return {
|
||||
terminalColorOverrides: undefined,
|
||||
terminalCursorOpacity: undefined,
|
||||
terminalBackgroundOpacity: undefined,
|
||||
...partial
|
||||
@@ -38,16 +37,6 @@ describe('composeActiveTerminalTheme', () => {
|
||||
expect(result!.scrollbarSliderBackground).toBe('rgba(1, 2, 3, 0.4)')
|
||||
})
|
||||
|
||||
it('layers terminalColorOverrides on top of the base theme', () => {
|
||||
const base = { background: '#101010', foreground: '#fafafa' }
|
||||
const result = composeActiveTerminalTheme(
|
||||
base,
|
||||
settingsWith({ terminalColorOverrides: { foreground: '#00ff00' } })
|
||||
)
|
||||
expect(result!.foreground).toBe('#00ff00')
|
||||
expect(result!.background).toBe('#101010')
|
||||
})
|
||||
|
||||
it('applies background opacity by converting the hex background to rgba', () => {
|
||||
const base = { background: '#112233' }
|
||||
const result = composeActiveTerminalTheme(
|
||||
|
||||
@@ -3,11 +3,7 @@ import type { PaneManager } from '@/lib/pane-manager/pane-manager'
|
||||
import type { GlobalSettings } from '../../../../shared/global-settings-types'
|
||||
import { resolveTerminalFontWeights } from '../../../../shared/terminal-fonts'
|
||||
import { resolveTerminalLigaturesEnabled } from '../../../../shared/terminal-ligatures'
|
||||
import {
|
||||
getBuiltinTheme,
|
||||
resolvePaneStyleOptions,
|
||||
resolveEffectiveTerminalAppearance
|
||||
} from '@/lib/terminal-theme'
|
||||
import { resolvePaneStyleOptions, resolveEffectiveTerminalAppearance } from '@/lib/terminal-theme'
|
||||
import { buildFontFamily } from '@/lib/monospace-font-family'
|
||||
import { safeFit, safeFitAndThen } from '@/lib/pane-manager/pane-tree-ops'
|
||||
import { canApplyPaneMetricOptions } from '@/lib/pane-manager/pane-fit'
|
||||
@@ -50,13 +46,11 @@ export function isHexColor(value: string): boolean {
|
||||
return HEX_COLOR_RE.test(value)
|
||||
}
|
||||
|
||||
// Keep pure so settings preview can compose the same theme without PaneManager.
|
||||
// Keep pure so settings preview can compose the same theme without PaneManager. baseTheme is the resolved palette
|
||||
// (resolveEffectiveTerminalAppearance), which already carries the slot's color overrides.
|
||||
export function composeActiveTerminalTheme(
|
||||
baseTheme: ITheme | null,
|
||||
settings: Pick<
|
||||
GlobalSettings,
|
||||
'terminalColorOverrides' | 'terminalBackgroundOpacity' | 'terminalCursorOpacity'
|
||||
>
|
||||
settings: Pick<GlobalSettings, 'terminalBackgroundOpacity' | 'terminalCursorOpacity'>
|
||||
): ITheme | null {
|
||||
if (!baseTheme) {
|
||||
return null
|
||||
@@ -70,9 +64,6 @@ export function composeActiveTerminalTheme(
|
||||
scrollbarSliderActiveBackground: 'rgba(180, 180, 185, 0.8)',
|
||||
...baseTheme
|
||||
}
|
||||
if (settings.terminalColorOverrides) {
|
||||
theme = { ...theme, ...settings.terminalColorOverrides }
|
||||
}
|
||||
// Why: convert the hex background to rgba so xterm honors the opacity when allowTransparency is set.
|
||||
if (settings.terminalBackgroundOpacity !== undefined && theme.background) {
|
||||
theme = {
|
||||
@@ -100,8 +91,7 @@ export function publishTerminalViewAttributesAtAppStart(
|
||||
return false
|
||||
}
|
||||
const appearance = resolveEffectiveTerminalAppearance(settings, systemPrefersDark)
|
||||
const baseTheme: ITheme | null = appearance.theme ?? getBuiltinTheme(appearance.themeName)
|
||||
const theme = composeActiveTerminalTheme(baseTheme, settings)
|
||||
const theme = composeActiveTerminalTheme(appearance.theme, settings)
|
||||
return send !== undefined
|
||||
? publishTerminalViewAttributes(theme, appearance.mode, settings, send)
|
||||
: publishTerminalViewAttributes(theme, appearance.mode, settings)
|
||||
@@ -144,8 +134,7 @@ export function applyTerminalAppearance(
|
||||
): void {
|
||||
const appearance = resolveEffectiveTerminalAppearance(settings, systemPrefersDark)
|
||||
const paneStyles = resolvePaneStyleOptions(settings)
|
||||
const baseTheme: ITheme | null = appearance.theme ?? getBuiltinTheme(appearance.themeName)
|
||||
const theme = composeActiveTerminalTheme(baseTheme, settings)
|
||||
const theme = composeActiveTerminalTheme(appearance.theme, settings)
|
||||
publishTerminalViewAttributes(theme, appearance.mode, settings)
|
||||
const paneBackground = theme?.background ?? '#000000'
|
||||
|
||||
|
||||
@@ -59,8 +59,7 @@ export function useTerminalPaneProjection(controller: TerminalPaneMobileControll
|
||||
const effectiveAppearance = settings
|
||||
? resolveEffectiveTerminalAppearance(settings, systemPrefersDark)
|
||||
: null
|
||||
const terminalBackground =
|
||||
settings?.terminalColorOverrides?.background ?? effectiveAppearance?.theme?.background
|
||||
const terminalBackground = effectiveAppearance?.theme?.background
|
||||
const titleUsesLightSurface = isTerminalBackgroundLight(terminalBackground, {
|
||||
appSurface: effectiveAppearance?.mode,
|
||||
backgroundOpacity: settings?.terminalBackgroundOpacity
|
||||
|
||||
@@ -9324,7 +9324,6 @@
|
||||
},
|
||||
"TerminalWindowSection": {
|
||||
"1705318506": "ANSI-Farbe Magenta",
|
||||
"03c855d15f": "Alle Farbanpassungen zurücksetzen",
|
||||
"63f8d9336e": "Farbanpassungen",
|
||||
"e86e09b5c7": "Einzelne Terminalfarben anpassen.",
|
||||
"1d1920dc8a": "Den Mauszeiger beim Tippen im Terminal ausblenden.",
|
||||
@@ -9391,7 +9390,11 @@
|
||||
"cf37ff69f6": "Basis",
|
||||
"8abdab9f7c": "Jetzt neu starten",
|
||||
"907131d741": "Wird neu gestartet…",
|
||||
"605a35d600": "Wird vom Terminal-Renderer noch nicht angewendet — xterm.js hat keine Farboption für fetten Text. Ein gespeicherter Wert bleibt erhalten."
|
||||
"605a35d600": "Wird vom Terminal-Renderer noch nicht angewendet — xterm.js hat keine Farboption für fetten Text. Ein gespeicherter Wert bleibt erhalten.",
|
||||
"color_overrides_dark_description": "Einzelne Farben des dunklen Terminal-Themes anpassen.",
|
||||
"color_overrides_light_description": "Einzelne Farben des hellen Terminal-Themes anpassen.",
|
||||
"reset_dark_color_overrides": "Farbanpassungen des dunklen Themes zurücksetzen",
|
||||
"reset_light_color_overrides": "Farbanpassungen des hellen Themes zurücksetzen"
|
||||
},
|
||||
"UIZoomControl": {
|
||||
"c2c64b24d0": "Zurücksetzen"
|
||||
|
||||
@@ -9404,7 +9404,6 @@
|
||||
},
|
||||
"TerminalWindowSection": {
|
||||
"1705318506": "ANSI magenta color",
|
||||
"03c855d15f": "Reset all color overrides",
|
||||
"63f8d9336e": "Color Overrides",
|
||||
"e86e09b5c7": "Override individual terminal colors.",
|
||||
"1d1920dc8a": "Hide the mouse cursor when typing in the terminal.",
|
||||
@@ -9471,7 +9470,11 @@
|
||||
"cf37ff69f6": "Base",
|
||||
"8abdab9f7c": "Restart now",
|
||||
"907131d741": "Restarting…",
|
||||
"605a35d600": "Not applied by the terminal renderer yet — xterm.js has no bold color slot. A saved value is preserved."
|
||||
"605a35d600": "Not applied by the terminal renderer yet — xterm.js has no bold color slot. A saved value is preserved.",
|
||||
"color_overrides_dark_description": "Override individual colors of the dark terminal theme.",
|
||||
"color_overrides_light_description": "Override individual colors of the light terminal theme.",
|
||||
"reset_dark_color_overrides": "Reset dark color overrides",
|
||||
"reset_light_color_overrides": "Reset light color overrides"
|
||||
},
|
||||
"UIZoomControl": {
|
||||
"c2c64b24d0": "Reset"
|
||||
|
||||
@@ -8930,7 +8930,6 @@
|
||||
},
|
||||
"TerminalWindowSection": {
|
||||
"1705318506": "Color magenta ANSI",
|
||||
"03c855d15f": "Restablecer todas las anulaciones de color",
|
||||
"63f8d9336e": "Anulaciones de color",
|
||||
"e86e09b5c7": "Anula colores individuales de la terminal.",
|
||||
"1d1920dc8a": "Oculta el cursor del mouse al escribir en la terminal.",
|
||||
@@ -8997,7 +8996,11 @@
|
||||
"cf37ff69f6": "Base",
|
||||
"8abdab9f7c": "Reiniciar ahora",
|
||||
"907131d741": "Reiniciando…",
|
||||
"605a35d600": "El renderizador de la terminal aún no aplica este valor: xterm.js no tiene un ajuste de color para el texto en negrita. Se conserva el valor guardado."
|
||||
"605a35d600": "El renderizador de la terminal aún no aplica este valor: xterm.js no tiene un ajuste de color para el texto en negrita. Se conserva el valor guardado.",
|
||||
"color_overrides_dark_description": "Anula colores individuales del tema oscuro de la terminal.",
|
||||
"color_overrides_light_description": "Anula colores individuales del tema claro de la terminal.",
|
||||
"reset_dark_color_overrides": "Restablecer las anulaciones de color del tema oscuro",
|
||||
"reset_light_color_overrides": "Restablecer las anulaciones de color del tema claro"
|
||||
},
|
||||
"UIZoomControl": {
|
||||
"c2c64b24d0": "Restablecer"
|
||||
|
||||
@@ -9200,7 +9200,6 @@
|
||||
},
|
||||
"TerminalWindowSection": {
|
||||
"1705318506": "Couleur ANSI magenta",
|
||||
"03c855d15f": "Réinitialiser toutes les surcharges de couleurs",
|
||||
"63f8d9336e": "Surcharges de couleurs",
|
||||
"e86e09b5c7": "Remplacer individuellement les couleurs du terminal.",
|
||||
"1d1920dc8a": "Masquer le curseur de la souris pendant la saisie dans le terminal.",
|
||||
@@ -9267,7 +9266,11 @@
|
||||
"cf37ff69f6": "Base",
|
||||
"8abdab9f7c": "Redémarrer maintenant",
|
||||
"907131d741": "Redémarrage…",
|
||||
"605a35d600": "Non encore appliqué par le moteur de rendu du terminal — xterm.js n'a pas d'emplacement dédié aux couleurs en gras. Une valeur enregistrée est conservée."
|
||||
"605a35d600": "Non encore appliqué par le moteur de rendu du terminal — xterm.js n'a pas d'emplacement dédié aux couleurs en gras. Une valeur enregistrée est conservée.",
|
||||
"color_overrides_dark_description": "Remplacer individuellement les couleurs du thème de terminal sombre.",
|
||||
"color_overrides_light_description": "Remplacer individuellement les couleurs du thème de terminal clair.",
|
||||
"reset_dark_color_overrides": "Réinitialiser les surcharges de couleurs du thème sombre",
|
||||
"reset_light_color_overrides": "Réinitialiser les surcharges de couleurs du thème clair"
|
||||
},
|
||||
"UIZoomControl": {
|
||||
"c2c64b24d0": "Réinitialiser"
|
||||
|
||||
@@ -8672,7 +8672,6 @@
|
||||
},
|
||||
"TerminalWindowSection": {
|
||||
"1705318506": "ANSIマゼンタ色",
|
||||
"03c855d15f": "すべての色のオーバーライドをリセット",
|
||||
"63f8d9336e": "色のオーバーライド",
|
||||
"e86e09b5c7": "個々のターミナルの色をオーバーライドします。",
|
||||
"1d1920dc8a": "ターミナルに入力するときにマウスカーソルを非表示にします。",
|
||||
@@ -8739,7 +8738,11 @@
|
||||
"cf37ff69f6": "ベース",
|
||||
"8abdab9f7c": "今すぐ再起動",
|
||||
"907131d741": "再起動中…",
|
||||
"605a35d600": "ターミナルのレンダラーではまだ適用されません。xterm.js に太字用の色スロットがないためです。保存した値は保持されます。"
|
||||
"605a35d600": "ターミナルのレンダラーではまだ適用されません。xterm.js に太字用の色スロットがないためです。保存した値は保持されます。",
|
||||
"color_overrides_dark_description": "ダークターミナルテーマの個々の色をオーバーライドします。",
|
||||
"color_overrides_light_description": "ライトターミナルテーマの個々の色をオーバーライドします。",
|
||||
"reset_dark_color_overrides": "ダークテーマの色のオーバーライドをリセット",
|
||||
"reset_light_color_overrides": "ライトテーマの色のオーバーライドをリセット"
|
||||
},
|
||||
"UIZoomControl": {
|
||||
"c2c64b24d0": "リセット"
|
||||
|
||||
@@ -8635,7 +8635,6 @@
|
||||
},
|
||||
"TerminalWindowSection": {
|
||||
"1705318506": "ANSI 마젠타색",
|
||||
"03c855d15f": "모든 색상 재정의 재설정",
|
||||
"63f8d9336e": "색상 재정의",
|
||||
"e86e09b5c7": "개별 터미널 색상을 재정의합니다.",
|
||||
"1d1920dc8a": "terminal에 입력할 때 마우스 커서를 숨깁니다.",
|
||||
@@ -8702,7 +8701,11 @@
|
||||
"cf37ff69f6": "기본",
|
||||
"8abdab9f7c": "지금 재시작",
|
||||
"907131d741": "다시 시작하는 중…",
|
||||
"605a35d600": "아직 터미널 렌더러에는 적용되지 않습니다. xterm.js에 굵은 글씨 색상 슬롯이 없기 때문입니다. 저장된 값은 유지됩니다."
|
||||
"605a35d600": "아직 터미널 렌더러에는 적용되지 않습니다. xterm.js에 굵은 글씨 색상 슬롯이 없기 때문입니다. 저장된 값은 유지됩니다.",
|
||||
"color_overrides_dark_description": "다크 터미널 테마의 개별 색상을 재정의합니다.",
|
||||
"color_overrides_light_description": "라이트 터미널 테마의 개별 색상을 재정의합니다.",
|
||||
"reset_dark_color_overrides": "다크 테마 색상 재정의 재설정",
|
||||
"reset_light_color_overrides": "라이트 테마 색상 재정의 재설정"
|
||||
},
|
||||
"UIZoomControl": {
|
||||
"c2c64b24d0": "재설정"
|
||||
|
||||
@@ -5177,7 +5177,6 @@
|
||||
},
|
||||
"TerminalWindowSection": {
|
||||
"1705318506": "Cor magenta ANSI",
|
||||
"03c855d15f": "Redefinir todas as substituições de cor",
|
||||
"63f8d9336e": "Substituições de Cor",
|
||||
"e86e09b5c7": "Substitui cores individuais do terminal.",
|
||||
"1d1920dc8a": "Oculta o cursor do mouse ao digitar no terminal.",
|
||||
@@ -5244,7 +5243,11 @@
|
||||
"cf37ff69f6": "Base",
|
||||
"8abdab9f7c": "Reiniciar agora",
|
||||
"907131d741": "Reiniciando…",
|
||||
"605a35d600": "Ainda não aplicado pelo renderizador do terminal — o xterm.js não tem um slot de cor para negrito. Um valor salvo é preservado."
|
||||
"605a35d600": "Ainda não aplicado pelo renderizador do terminal — o xterm.js não tem um slot de cor para negrito. Um valor salvo é preservado.",
|
||||
"color_overrides_dark_description": "Substitui cores individuais do tema escuro do terminal.",
|
||||
"color_overrides_light_description": "Substitui cores individuais do tema claro do terminal.",
|
||||
"reset_dark_color_overrides": "Redefinir as substituições de cor do tema escuro",
|
||||
"reset_light_color_overrides": "Redefinir as substituições de cor do tema claro"
|
||||
},
|
||||
"agents": {
|
||||
"search": {
|
||||
|
||||
@@ -9682,7 +9682,6 @@
|
||||
},
|
||||
"TerminalWindowSection": {
|
||||
"1705318506": "Пурпурный цвет ANSI",
|
||||
"03c855d15f": "Сбросить все переопределения цветов",
|
||||
"63f8d9336e": "Переопределения цветов",
|
||||
"e86e09b5c7": "Измените отдельные цвета терминала.",
|
||||
"1d1920dc8a": "Скрывать указатель мыши при вводе в терминале.",
|
||||
@@ -9749,7 +9748,11 @@
|
||||
"cf37ff69f6": "Основные",
|
||||
"8abdab9f7c": "Перезапустить сейчас",
|
||||
"907131d741": "Перезапуск…",
|
||||
"605a35d600": "Пока не применяется при отрисовке терминала: в xterm.js нет отдельного цвета жирного текста. Сохранённое значение сохраняется."
|
||||
"605a35d600": "Пока не применяется при отрисовке терминала: в xterm.js нет отдельного цвета жирного текста. Сохранённое значение сохраняется.",
|
||||
"color_overrides_dark_description": "Измените отдельные цвета тёмной темы терминала.",
|
||||
"color_overrides_light_description": "Измените отдельные цвета светлой темы терминала.",
|
||||
"reset_dark_color_overrides": "Сбросить переопределения цветов тёмной темы",
|
||||
"reset_light_color_overrides": "Сбросить переопределения цветов светлой темы"
|
||||
},
|
||||
"UIZoomControl": {
|
||||
"c2c64b24d0": "Сбросить"
|
||||
|
||||
@@ -9310,7 +9310,6 @@
|
||||
},
|
||||
"TerminalWindowSection": {
|
||||
"1705318506": "ANSI eflatun rengi",
|
||||
"03c855d15f": "Tüm renk geçersiz kılmalarını sıfırla",
|
||||
"63f8d9336e": "Renk Geçersiz Kılmaları",
|
||||
"e86e09b5c7": "Tek tek terminal renklerini geçersiz kılın.",
|
||||
"1d1920dc8a": "Terminalde yazarken fare imlecini gizleyin.",
|
||||
@@ -9377,7 +9376,11 @@
|
||||
"cf37ff69f6": "Temel",
|
||||
"8abdab9f7c": "Şimdi yeniden başlat",
|
||||
"907131d741": "Yeniden başlatılıyor…",
|
||||
"605a35d600": "Terminal render motoru tarafından henüz uygulanmıyor; xterm.js'de kalın renk yuvası yok. Kaydedilen değer korunur."
|
||||
"605a35d600": "Terminal render motoru tarafından henüz uygulanmıyor; xterm.js'de kalın renk yuvası yok. Kaydedilen değer korunur.",
|
||||
"color_overrides_dark_description": "Koyu terminal temasının renklerini tek tek geçersiz kılın.",
|
||||
"color_overrides_light_description": "Açık terminal temasının renklerini tek tek geçersiz kılın.",
|
||||
"reset_dark_color_overrides": "Koyu tema renk geçersiz kılmalarını sıfırla",
|
||||
"reset_light_color_overrides": "Açık tema renk geçersiz kılmalarını sıfırla"
|
||||
},
|
||||
"UIZoomControl": {
|
||||
"c2c64b24d0": "Sıfırla"
|
||||
|
||||
@@ -9147,7 +9147,6 @@
|
||||
},
|
||||
"TerminalWindowSection": {
|
||||
"1705318506": "ANSI 洋紅色",
|
||||
"03c855d15f": "重設所有顏色覆寫設定",
|
||||
"63f8d9336e": "顏色覆寫設定",
|
||||
"e86e09b5c7": "覆寫個別終端機顏色。",
|
||||
"1d1920dc8a": "在終端機中輸入時隱藏滑鼠游標。",
|
||||
@@ -9214,7 +9213,11 @@
|
||||
"cf37ff69f6": "基本",
|
||||
"8abdab9f7c": "立即重新啟動",
|
||||
"907131d741": "正在重新啟動...",
|
||||
"605a35d600": "終端機算繪器尚未套用此設定,因為 xterm.js 沒有粗體顏色欄位。已儲存的值會保留。"
|
||||
"605a35d600": "終端機算繪器尚未套用此設定,因為 xterm.js 沒有粗體顏色欄位。已儲存的值會保留。",
|
||||
"color_overrides_dark_description": "覆寫深色終端機主題的個別顏色。",
|
||||
"color_overrides_light_description": "覆寫淺色終端機主題的個別顏色。",
|
||||
"reset_dark_color_overrides": "重設深色主題顏色覆寫設定",
|
||||
"reset_light_color_overrides": "重設淺色主題顏色覆寫設定"
|
||||
},
|
||||
"UIZoomControl": {
|
||||
"c2c64b24d0": "重設"
|
||||
|
||||
@@ -8635,7 +8635,6 @@
|
||||
},
|
||||
"TerminalWindowSection": {
|
||||
"1705318506": "ANSI 洋红色",
|
||||
"03c855d15f": "重置所有颜色覆盖",
|
||||
"63f8d9336e": "颜色覆盖",
|
||||
"e86e09b5c7": "覆盖各个终端颜色。",
|
||||
"1d1920dc8a": "在终端中输入时隐藏鼠标光标。",
|
||||
@@ -8702,7 +8701,11 @@
|
||||
"cf37ff69f6": "根据",
|
||||
"8abdab9f7c": "立即重启",
|
||||
"907131d741": "正在重新启动...",
|
||||
"605a35d600": "终端渲染器尚未应用此项 — xterm.js 没有粗体颜色槽位。已保存的值会被保留。"
|
||||
"605a35d600": "终端渲染器尚未应用此项 — xterm.js 没有粗体颜色槽位。已保存的值会被保留。",
|
||||
"color_overrides_dark_description": "覆盖深色终端主题的各个颜色。",
|
||||
"color_overrides_light_description": "覆盖浅色终端主题的各个颜色。",
|
||||
"reset_dark_color_overrides": "重置深色主题颜色覆盖",
|
||||
"reset_light_color_overrides": "重置浅色主题颜色覆盖"
|
||||
},
|
||||
"UIZoomControl": {
|
||||
"c2c64b24d0": "重置"
|
||||
|
||||
@@ -19,6 +19,7 @@ type LeftSidebarAppearanceSettings = Pick<
|
||||
| 'terminalCustomThemes'
|
||||
| 'terminalDividerColorLight'
|
||||
| 'terminalColorOverrides'
|
||||
| 'terminalColorOverridesLight'
|
||||
| 'terminalBackgroundOpacity'
|
||||
>
|
||||
|
||||
@@ -93,11 +94,10 @@ function resolveTerminalSurfaceVariables(
|
||||
): LeftSidebarStyleVariables {
|
||||
const appearance = resolveEffectiveTerminalAppearance(settings, systemPrefersDark)
|
||||
const background = applyAlpha(
|
||||
settings.terminalColorOverrides?.background ?? appearance.theme?.background ?? '#000000',
|
||||
appearance.theme?.background ?? '#000000',
|
||||
settings.terminalBackgroundOpacity
|
||||
)
|
||||
const foreground =
|
||||
settings.terminalColorOverrides?.foreground ?? appearance.theme?.foreground ?? '#fafafa'
|
||||
const foreground = appearance.theme?.foreground ?? '#fafafa'
|
||||
return buildSurfaceVariables({ background, foreground, overrideTextTokens: true })
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,9 @@ import {
|
||||
DEFAULT_TERMINAL_THEME_DARK,
|
||||
DEFAULT_TERMINAL_THEME_LIGHT,
|
||||
lookupTerminalTheme,
|
||||
type TerminalThemeLookupSettings,
|
||||
selectTerminalTheme
|
||||
resolveTerminalPalette,
|
||||
type TerminalPaletteSettings,
|
||||
type TerminalThemeLookupSettings
|
||||
} from '../../../shared/terminal-theme-selection'
|
||||
|
||||
export const BUILTIN_TERMINAL_THEME_NAMES = getThemeNames()
|
||||
@@ -58,20 +59,6 @@ export function getTerminalTheme(
|
||||
return lookupTerminalTheme(settings, selection)
|
||||
}
|
||||
|
||||
function getTerminalThemePreview(
|
||||
name: string,
|
||||
settings?: TerminalThemeLookupSettings,
|
||||
fallbackMode: 'dark' | 'light' = 'dark'
|
||||
): ITheme | null {
|
||||
const theme = getTerminalTheme(settings, name)
|
||||
if (theme) {
|
||||
return theme
|
||||
}
|
||||
return getTheme(
|
||||
fallbackMode === 'light' ? DEFAULT_TERMINAL_THEME_LIGHT : DEFAULT_TERMINAL_THEME_DARK
|
||||
)
|
||||
}
|
||||
|
||||
export function getAvailableTerminalThemeOptions(
|
||||
settings: Pick<GlobalSettings, 'terminalCustomThemes' | 'terminalPluginPalettes'>,
|
||||
pluginThemes: readonly PluginTerminalThemeRegistration[] = []
|
||||
@@ -116,24 +103,23 @@ export function resolveEffectiveTerminalAppearance(
|
||||
| 'terminalPluginPalettes'
|
||||
| 'terminalDividerColorLight'
|
||||
> &
|
||||
TerminalThemeLookupSettings,
|
||||
TerminalPaletteSettings,
|
||||
systemPrefersDark = getSystemPrefersDark()
|
||||
): EffectiveTerminalAppearance {
|
||||
const {
|
||||
mode: sourceTheme,
|
||||
useLightVariant,
|
||||
themeName
|
||||
} = selectTerminalTheme(settings, systemPrefersDark)
|
||||
const { mode, useLightVariant, themeName, theme } = resolveTerminalPalette(
|
||||
settings,
|
||||
systemPrefersDark
|
||||
)
|
||||
const dividerColor = useLightVariant
|
||||
? normalizeColor(settings.terminalDividerColorLight, DEFAULT_TERMINAL_DIVIDER_LIGHT)
|
||||
: normalizeColor(settings.terminalDividerColorDark, DEFAULT_TERMINAL_DIVIDER_DARK)
|
||||
|
||||
return {
|
||||
mode: sourceTheme,
|
||||
mode,
|
||||
sourceTheme: settings.theme,
|
||||
themeName,
|
||||
dividerColor,
|
||||
theme: getTerminalThemePreview(themeName, settings, useLightVariant ? 'light' : 'dark'),
|
||||
theme,
|
||||
systemPrefersDark
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,7 +536,7 @@ describe('buildMobileSessionTabSnapshots', () => {
|
||||
...getDefaultSettings('/tmp'),
|
||||
theme: 'light',
|
||||
terminalUseSeparateLightTheme: true,
|
||||
terminalColorOverrides: {
|
||||
terminalColorOverridesLight: {
|
||||
background: '#f8f8f8',
|
||||
foreground: '#101010',
|
||||
cursor: '#202020'
|
||||
|
||||
@@ -30,9 +30,8 @@ export function resolveMobileTerminalTheme(
|
||||
return undefined
|
||||
}
|
||||
const appearance = resolveEffectiveTerminalAppearance(settings, systemPrefersDark)
|
||||
const resolvedTheme = appearance.theme
|
||||
? { ...appearance.theme, ...settings.terminalColorOverrides }
|
||||
: undefined
|
||||
// Copy: the opacity writes below must not mutate the shared theme catalog entry.
|
||||
const resolvedTheme = appearance.theme ? { ...appearance.theme } : undefined
|
||||
if (!resolvedTheme) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import type { PairedUiState, PairingLocalUiField } from '../../../../shared/pair
|
||||
import type { PersistedUIState } from '../../../../shared/persisted-ui-state-types'
|
||||
import { normalizeStatusBarUsageMode } from '../../../../shared/status-bar-usage-mode'
|
||||
import { normalizeTerminalCustomThemes } from '../../../../shared/terminal-custom-themes'
|
||||
import { splitLegacyTerminalColorOverrides } from '../../../../shared/terminal-color-overrides'
|
||||
import {
|
||||
DEFAULT_TUI_AGENT_ARGS,
|
||||
DEFAULT_TUI_AGENT_ENV,
|
||||
@@ -165,6 +166,7 @@ export function migrateStoredWebSettings(
|
||||
...normalizeTerminalCursorStyleDefault(stored),
|
||||
...normalizeOsc52ClipboardDefaultOn(stored),
|
||||
terminalCustomThemes: normalizeTerminalCustomThemes(stored.terminalCustomThemes),
|
||||
...splitLegacyTerminalColorOverrides(stored),
|
||||
...migratePortugueseUiLanguageOnLoad(stored)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ export function getStoredSettings(): GlobalSettings {
|
||||
stored.terminalAllowOsc52ClipboardDefaultedOnForAllUsers !==
|
||||
migratedStored.terminalAllowOsc52ClipboardDefaultedOnForAllUsers ||
|
||||
stored.terminalCustomThemes !== migratedStored.terminalCustomThemes ||
|
||||
stored.terminalColorOverridesSplit !== migratedStored.terminalColorOverridesSplit ||
|
||||
stored.uiLanguage !== migratedStored.uiLanguage ||
|
||||
needsChatUiChoiceCarry(stored) ||
|
||||
stored.uiLanguagePortugueseMigrationApplied !== true)
|
||||
|
||||
@@ -85,6 +85,7 @@ export function buildDefaultSettings(args: {
|
||||
terminalThemeDark: 'Ghostty Default Style Dark',
|
||||
terminalDividerColorDark: '#3f3f46',
|
||||
terminalUseSeparateLightTheme: true,
|
||||
terminalColorOverridesSplit: true,
|
||||
terminalThemeLight: 'Builtin Tango Light',
|
||||
terminalCustomThemes: [],
|
||||
terminalDividerColorLight: '#d4d4d8',
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { PluginUpdatePolicySettings } from './plugins/catalog/plugin-update
|
||||
import type { PluginSystemSettings } from './plugins/plugin-system-settings'
|
||||
import type { WorktreeDeletionPreferences } from './worktree-deletion-preferences'
|
||||
import type { AgentPermissionGlobalSettings } from './tui-agent-permission-choice'
|
||||
import type { TerminalColorOverrideGlobalSettings } from './terminal-color-overrides'
|
||||
|
||||
/** Settings groups owned by their feature modules and composed into GlobalSettings. */
|
||||
export type GlobalSettingsBase = ClaudeAgentViewSettings &
|
||||
@@ -23,4 +24,5 @@ export type GlobalSettingsBase = ClaudeAgentViewSettings &
|
||||
PluginAppearanceSettings &
|
||||
PluginFileTypeSuggestionSettings &
|
||||
PluginUpdatePolicySettings &
|
||||
PluginSystemSettings
|
||||
PluginSystemSettings &
|
||||
TerminalColorOverrideGlobalSettings
|
||||
|
||||
@@ -29,7 +29,6 @@ import type {
|
||||
} from './managed-account-types'
|
||||
import type { NotificationSettings } from './notification-settings-types'
|
||||
import type { CtrlTabOrderMode } from './tab-types'
|
||||
import type { TerminalColorOverrides } from './terminal-color-overrides'
|
||||
import type { TerminalQuickCommand } from './terminal-quick-command-types'
|
||||
import type { TuiAgent } from './tui-agent'
|
||||
import type { ZcodePlanSite } from './zcode-plan-sites'
|
||||
@@ -136,7 +135,6 @@ export type GlobalSettings = GlobalSettingsBase & {
|
||||
* background-luminance-gated floor (3 dark / 4.5 light); 1 disables contrast correction so TUIs
|
||||
* that rely on deliberately low contrast (Powerline seams, dimmed secondary text) render as sent. */
|
||||
terminalMinimumContrastRatio?: number
|
||||
terminalColorOverrides?: TerminalColorOverrides
|
||||
terminalPaddingX?: number
|
||||
terminalPaddingY?: number
|
||||
terminalMouseHideWhileTyping?: boolean
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { ITheme } from '@xterm/xterm'
|
||||
import type { GlobalSettings } from './global-settings-types'
|
||||
|
||||
export type TerminalColorOverrides = {
|
||||
foreground?: string
|
||||
background?: string
|
||||
@@ -30,3 +33,74 @@ export type TerminalPluginPalette = {
|
||||
id: `plugin:${string}`
|
||||
colors: TerminalColorOverrides
|
||||
}
|
||||
|
||||
/** Terminal theme slot (the Dark/Light picker) an override bag tweaks; light mode paints the dark slot unless it has its own theme. */
|
||||
export type TerminalThemeSlot = 'dark' | 'light'
|
||||
|
||||
export type TerminalColorOverrideGlobalSettings = {
|
||||
/** Dark slot's overrides; light mode uses them too unless terminalUseSeparateLightTheme. */
|
||||
terminalColorOverrides?: TerminalColorOverrides
|
||||
/** Light slot's overrides (#10561); older clients ignore them. */
|
||||
terminalColorOverridesLight?: TerminalColorOverrides
|
||||
/** One-shot migration guard for splitting the legacy shared bag per slot. */
|
||||
terminalColorOverridesSplit?: boolean
|
||||
}
|
||||
|
||||
// Why the dark slot keeps the original key: older clients keep applying it in both modes, the pre-split behavior.
|
||||
export const TERMINAL_COLOR_OVERRIDES_SETTING = {
|
||||
dark: 'terminalColorOverrides',
|
||||
light: 'terminalColorOverridesLight'
|
||||
} as const satisfies Record<TerminalThemeSlot, keyof TerminalColorOverrideGlobalSettings>
|
||||
|
||||
export type TerminalColorOverrideSettings = Pick<
|
||||
TerminalColorOverrideGlobalSettings,
|
||||
'terminalColorOverrides' | 'terminalColorOverridesLight'
|
||||
>
|
||||
|
||||
export function terminalColorOverridesForSlot(
|
||||
settings: TerminalColorOverrideSettings,
|
||||
slot: TerminalThemeSlot
|
||||
): TerminalColorOverrides | undefined {
|
||||
return settings[TERMINAL_COLOR_OVERRIDES_SETTING[slot]]
|
||||
}
|
||||
|
||||
export function hasTerminalColorOverrides(
|
||||
settings: TerminalColorOverrideSettings,
|
||||
slot: TerminalThemeSlot
|
||||
): boolean {
|
||||
return Object.values(terminalColorOverridesForSlot(settings, slot) ?? {}).some(Boolean)
|
||||
}
|
||||
|
||||
/** Cleared fields stay undefined in memory until reload; they must not blank the theme's color. */
|
||||
export function withTerminalColorOverrides(
|
||||
theme: ITheme,
|
||||
overrides: TerminalColorOverrides | undefined
|
||||
): ITheme {
|
||||
const painted = Object.entries(overrides ?? {}).filter(
|
||||
([, value]) => typeof value === 'string' && value.length > 0
|
||||
)
|
||||
return painted.length === 0 ? theme : { ...theme, ...Object.fromEntries(painted) }
|
||||
}
|
||||
|
||||
type LegacyTerminalColorOverrideSettings = TerminalColorOverrideGlobalSettings &
|
||||
Partial<Pick<GlobalSettings, 'theme' | 'terminalUseSeparateLightTheme'>>
|
||||
|
||||
/** One-shot split of the pre-#10561 shared bag. It stays on the dark slot; when the light slot could have shown it
|
||||
* (own light theme, app not pinned dark) it is copied there too, so nothing changes on upgrade and older clients,
|
||||
* which only read the dark key, keep their behavior. A missing separate-light flag means its default, on. */
|
||||
export function splitLegacyTerminalColorOverrides(
|
||||
settings: LegacyTerminalColorOverrideSettings | undefined
|
||||
): TerminalColorOverrideGlobalSettings {
|
||||
const shownInLight =
|
||||
settings?.terminalColorOverridesSplit !== true &&
|
||||
settings?.theme !== 'dark' &&
|
||||
settings?.terminalUseSeparateLightTheme !== false &&
|
||||
settings?.terminalColorOverridesLight === undefined
|
||||
return {
|
||||
terminalColorOverrides: settings?.terminalColorOverrides,
|
||||
terminalColorOverridesLight: shownInLight
|
||||
? settings?.terminalColorOverrides
|
||||
: settings?.terminalColorOverridesLight,
|
||||
terminalColorOverridesSplit: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { resolveConfiguredTerminalColors } from './terminal-theme-selection'
|
||||
import {
|
||||
resolveConfiguredTerminalColors,
|
||||
resolveTerminalPalette,
|
||||
withImportedTerminalColorOverrides
|
||||
} from './terminal-theme-selection'
|
||||
import { TERMINAL_THEME_CATALOG } from './terminal-themes'
|
||||
|
||||
const base = {
|
||||
@@ -45,6 +49,50 @@ describe('the terminal colours a host configures', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('terminal colour overrides per theme slot (#10561)', () => {
|
||||
const settings = {
|
||||
...base,
|
||||
terminalColorOverrides: { background: '#101010' },
|
||||
terminalColorOverridesLight: { foreground: '#202020' }
|
||||
}
|
||||
|
||||
it('paints each mode with only its own slot overrides', () => {
|
||||
expect(resolveTerminalPalette(settings, true).theme).toMatchObject({
|
||||
foreground: '#ffffff',
|
||||
background: '#101010'
|
||||
})
|
||||
expect(resolveTerminalPalette(settings, false).theme).toMatchObject({
|
||||
foreground: '#202020',
|
||||
background: '#ffffff'
|
||||
})
|
||||
})
|
||||
|
||||
it('lets light mode that matches dark share the dark slot overrides', () => {
|
||||
expect(
|
||||
resolveTerminalPalette({ ...settings, terminalUseSeparateLightTheme: false }, false).theme
|
||||
).toMatchObject({ foreground: '#ffffff', background: '#101010' })
|
||||
})
|
||||
|
||||
it('merges an imported palette into the slot on screen once the import applies', () => {
|
||||
const imported = { terminalColorOverrides: { red: '#ff0000' } }
|
||||
expect(withImportedTerminalColorOverrides(settings, imported, false)).toEqual({
|
||||
terminalColorOverridesLight: { foreground: '#202020', red: '#ff0000' }
|
||||
})
|
||||
expect(
|
||||
withImportedTerminalColorOverrides(settings, { ...imported, theme: 'dark' }, false)
|
||||
).toEqual({ theme: 'dark', terminalColorOverrides: { background: '#101010', red: '#ff0000' } })
|
||||
})
|
||||
|
||||
it('keeps the theme colour for a field cleared in memory', () => {
|
||||
expect(
|
||||
resolveConfiguredTerminalColors(
|
||||
{ ...base, terminalColorOverrides: { background: undefined } },
|
||||
true
|
||||
)
|
||||
).toEqual({ foreground: '#ffffff', background: '#282c34' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('plugin terminal palettes', () => {
|
||||
const id = 'plugin:samples.dusk/dusk' as const
|
||||
const colors = { background: '#1b1430', foreground: '#e8e0ff', red: '#ff6b8b' }
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import type { ITheme } from '@xterm/xterm'
|
||||
import type { GlobalSettings } from './global-settings-types'
|
||||
import { isPluginContentSwitchedOff } from './plugins/plugin-system-settings'
|
||||
import {
|
||||
TERMINAL_COLOR_OVERRIDES_SETTING,
|
||||
terminalColorOverridesForSlot,
|
||||
withTerminalColorOverrides,
|
||||
type TerminalColorOverrideSettings
|
||||
} from './terminal-color-overrides'
|
||||
import {
|
||||
hasUsableTerminalThemeColors,
|
||||
normalizeTerminalColorOverrides,
|
||||
@@ -86,26 +93,51 @@ function pluginPalette(palettes: unknown, selection: string): TerminalThemeMap[s
|
||||
return hasUsableTerminalThemeColors(colors) ? colors : null
|
||||
}
|
||||
|
||||
function themeColors(
|
||||
selection: string,
|
||||
settings: TerminalThemeLookupSettings
|
||||
): TerminalOscColorQueryReplyColors | null {
|
||||
const theme = lookupTerminalTheme(settings, selection)
|
||||
return theme ? { foreground: theme.foreground, background: theme.background } : null
|
||||
export type TerminalPaletteSettings = TerminalThemeSelectionSettings &
|
||||
TerminalThemeLookupSettings &
|
||||
TerminalColorOverrideSettings
|
||||
|
||||
export type TerminalPalette = TerminalThemeSelection & { theme: ITheme | null }
|
||||
|
||||
/** The one place a mode's terminal palette is composed: its slot's theme (or that slot's default) plus the slot's overrides. */
|
||||
export function resolveTerminalPalette(
|
||||
settings: TerminalPaletteSettings,
|
||||
systemPrefersDark: boolean
|
||||
): TerminalPalette {
|
||||
const selection = selectTerminalTheme(settings, systemPrefersDark)
|
||||
const slot = selection.useLightVariant ? 'light' : 'dark'
|
||||
const base =
|
||||
lookupTerminalTheme(settings, selection.themeName) ??
|
||||
lookupTerminalTheme(
|
||||
settings,
|
||||
selection.useLightVariant ? DEFAULT_TERMINAL_THEME_LIGHT : DEFAULT_TERMINAL_THEME_DARK
|
||||
)
|
||||
return {
|
||||
...selection,
|
||||
theme: base && withTerminalColorOverrides(base, terminalColorOverridesForSlot(settings, slot))
|
||||
}
|
||||
}
|
||||
|
||||
/** An imported palette (Ghostty) merges into the slot painting the terminal once the import applies, so it shows. */
|
||||
export function withImportedTerminalColorOverrides(
|
||||
settings: TerminalThemeSelectionSettings & TerminalColorOverrideSettings,
|
||||
diff: Partial<GlobalSettings>,
|
||||
systemPrefersDark: boolean
|
||||
): Partial<GlobalSettings> {
|
||||
const { terminalColorOverrides: imported, ...rest } = diff
|
||||
if (!imported) {
|
||||
return diff
|
||||
}
|
||||
const { useLightVariant } = selectTerminalTheme({ ...settings, ...rest }, systemPrefersDark)
|
||||
const setting = TERMINAL_COLOR_OVERRIDES_SETTING[useLightVariant ? 'light' : 'dark']
|
||||
return { ...rest, [setting]: { ...settings[setting], ...imported } }
|
||||
}
|
||||
|
||||
/** Answer OSC 10/11 from saved settings without a renderer; queries report opaque theme colors, ignoring opacity. */
|
||||
export function resolveConfiguredTerminalColors(
|
||||
settings: TerminalThemeSelectionSettings &
|
||||
TerminalThemeLookupSettings &
|
||||
Pick<GlobalSettings, 'terminalColorOverrides'>,
|
||||
settings: TerminalPaletteSettings,
|
||||
systemPrefersDark: boolean
|
||||
): TerminalOscColorQueryReplyColors {
|
||||
const { themeName, useLightVariant } = selectTerminalTheme(settings, systemPrefersDark)
|
||||
const fallback = useLightVariant ? DEFAULT_TERMINAL_THEME_LIGHT : DEFAULT_TERMINAL_THEME_DARK
|
||||
const base: TerminalOscColorQueryReplyColors =
|
||||
themeColors(themeName, settings) ?? themeColors(fallback, settings) ?? {}
|
||||
const foreground = settings.terminalColorOverrides?.foreground ?? base.foreground
|
||||
const background = settings.terminalColorOverrides?.background ?? base.background
|
||||
const { foreground, background } = resolveTerminalPalette(settings, systemPrefersDark).theme ?? {}
|
||||
return { ...(foreground ? { foreground } : {}), ...(background ? { background } : {}) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user