mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 17:37:40 +00:00
* feat(editor): choose a code editor theme independently of the app theme * fix(editor): paint the diff overview ruler with the editor theme * chore(i18n): add editor theme strings to the English catalog * fix(editor): editor-theme settings search and Orca line tints in a separate editor theme Searching 'editor theme' now opens the Interface section that holds the rows. Diff comment-range bands and the conflict base-section tint use the editor theme's line-number color, so they stay visible when the app's tokens don't contrast with a differently based editor background. * fix(editor): an editor-only theme paints the diff colors it would paint as the app theme Main's diff tokens reach Monaco only where a theme painted them, and omitted tints come from main.css fallbacks. An editor-only theme now reads its tokens through the same fallbacks, so its diff colors match the ones it paints as the app theme.
162 lines
5.6 KiB
TypeScript
162 lines
5.6 KiB
TypeScript
import type { GlobalSettings } from './global-settings-types'
|
|
import type { CodeEditorThemeChoice } from './plugin-appearance-settings-types'
|
|
import type { TerminalPluginPalette } from './terminal-color-overrides'
|
|
import {
|
|
hasUsableTerminalThemeColors,
|
|
normalizeTerminalColorOverrides
|
|
} from './terminal-custom-themes'
|
|
|
|
type PluginThemeSettings = Pick<
|
|
GlobalSettings,
|
|
| 'pluginAppTheme'
|
|
| 'pluginAppThemeLight'
|
|
| 'pluginAppThemeDark'
|
|
| 'editorThemeLight'
|
|
| 'editorThemeDark'
|
|
| 'pluginIconTheme'
|
|
| 'themeBeforePlugin'
|
|
| 'terminalPluginPalettes'
|
|
>
|
|
|
|
/** The setting that holds the plugin app theme for one light/dark mode. */
|
|
export function pluginAppThemeSlotUpdate(
|
|
base: 'light' | 'dark',
|
|
id: `plugin:${string}` | null
|
|
): Pick<GlobalSettings, 'pluginAppThemeLight'> | Pick<GlobalSettings, 'pluginAppThemeDark'> {
|
|
return base === 'dark' ? { pluginAppThemeDark: id } : { pluginAppThemeLight: id }
|
|
}
|
|
|
|
/** The setting that holds the code editor theme for one light/dark mode. */
|
|
export function editorThemeSlotUpdate(
|
|
base: 'light' | 'dark',
|
|
choice: CodeEditorThemeChoice | null
|
|
): Pick<GlobalSettings, 'editorThemeLight'> | Pick<GlobalSettings, 'editorThemeDark'> {
|
|
return base === 'dark' ? { editorThemeDark: choice } : { editorThemeLight: choice }
|
|
}
|
|
|
|
// Two terminal slots (dark, light) can each hold one plugin palette.
|
|
const MAX_TERMINAL_PLUGIN_PALETTES = 2
|
|
const PLUGIN_CONTENT_ID =
|
|
/^plugin:[a-z0-9][a-z0-9-]{0,63}\.[a-z0-9][a-z0-9-]{0,63}\/[a-z0-9][a-z0-9-]{0,63}$/
|
|
|
|
function contentId(value: unknown): `plugin:${string}` | null {
|
|
return typeof value === 'string' && PLUGIN_CONTENT_ID.test(value)
|
|
? `plugin:${value.slice('plugin:'.length)}`
|
|
: null
|
|
}
|
|
|
|
function editorThemeChoice(value: unknown): CodeEditorThemeChoice | null {
|
|
return value === 'light' || value === 'dark' ? value : contentId(value)
|
|
}
|
|
|
|
function palettes(value: unknown): TerminalPluginPalette[] {
|
|
if (!Array.isArray(value)) {
|
|
return []
|
|
}
|
|
const result: TerminalPluginPalette[] = []
|
|
for (const entry of value) {
|
|
const id =
|
|
typeof entry === 'object' && entry !== null && 'id' in entry ? contentId(entry.id) : null
|
|
const colors = normalizeTerminalColorOverrides(
|
|
typeof entry === 'object' && entry !== null && 'colors' in entry ? entry.colors : undefined
|
|
)
|
|
if (id && hasUsableTerminalThemeColors(colors) && !result.some((p) => p.id === id)) {
|
|
result.push({ id, colors })
|
|
}
|
|
}
|
|
return result.slice(0, MAX_TERMINAL_PLUGIN_PALETTES)
|
|
}
|
|
|
|
/** Unvalidated plugin theme keys as a settings write or load carries them. */
|
|
export type PluginThemeSettingsInput = { [Key in keyof PluginThemeSettings]?: unknown }
|
|
|
|
/** Bounds plugin theme settings on every write and load; only keys present in `input` return. */
|
|
export function normalizePluginThemeSettings(
|
|
input: PluginThemeSettingsInput
|
|
): Partial<PluginThemeSettings> {
|
|
const result: Partial<PluginThemeSettings> = {}
|
|
if ('pluginAppTheme' in input) {
|
|
result.pluginAppTheme = contentId(input.pluginAppTheme)
|
|
}
|
|
if ('pluginAppThemeLight' in input) {
|
|
result.pluginAppThemeLight = contentId(input.pluginAppThemeLight)
|
|
}
|
|
if ('pluginAppThemeDark' in input) {
|
|
result.pluginAppThemeDark = contentId(input.pluginAppThemeDark)
|
|
}
|
|
if ('editorThemeLight' in input) {
|
|
result.editorThemeLight = editorThemeChoice(input.editorThemeLight)
|
|
}
|
|
if ('editorThemeDark' in input) {
|
|
result.editorThemeDark = editorThemeChoice(input.editorThemeDark)
|
|
}
|
|
if ('pluginIconTheme' in input) {
|
|
result.pluginIconTheme = contentId(input.pluginIconTheme)
|
|
}
|
|
if ('themeBeforePlugin' in input) {
|
|
const theme = input.themeBeforePlugin
|
|
result.themeBeforePlugin =
|
|
theme === 'system' || theme === 'dark' || theme === 'light' ? theme : null
|
|
}
|
|
if ('terminalPluginPalettes' in input) {
|
|
result.terminalPluginPalettes = palettes(input.terminalPluginPalettes)
|
|
}
|
|
return result
|
|
}
|
|
|
|
function ownedBy(id: string | null | undefined, pluginKey: string): boolean {
|
|
return typeof id === 'string' && id.startsWith(`plugin:${pluginKey}/`)
|
|
}
|
|
|
|
/** Forgets an uninstalled plugin's theme choices so a reinstall does not quietly reapply them. */
|
|
export function pluginThemeSettingsAfterRemoval(
|
|
settings: Pick<
|
|
GlobalSettings,
|
|
| 'theme'
|
|
| 'terminalThemeDark'
|
|
| 'terminalThemeLight'
|
|
| 'pluginAppTheme'
|
|
| 'pluginAppThemeLight'
|
|
| 'pluginAppThemeDark'
|
|
| 'editorThemeLight'
|
|
| 'editorThemeDark'
|
|
| 'pluginIconTheme'
|
|
| 'themeBeforePlugin'
|
|
| 'terminalPluginPalettes'
|
|
>,
|
|
pluginKey: string
|
|
): Partial<GlobalSettings> {
|
|
const update: Partial<GlobalSettings> = {}
|
|
if (ownedBy(settings.pluginAppTheme, pluginKey)) {
|
|
update.pluginAppTheme = null
|
|
update.themeBeforePlugin = null
|
|
update.theme = settings.themeBeforePlugin ?? settings.theme
|
|
}
|
|
if (ownedBy(settings.pluginAppThemeLight, pluginKey)) {
|
|
update.pluginAppThemeLight = null
|
|
}
|
|
if (ownedBy(settings.pluginAppThemeDark, pluginKey)) {
|
|
update.pluginAppThemeDark = null
|
|
}
|
|
if (ownedBy(settings.editorThemeLight, pluginKey)) {
|
|
update.editorThemeLight = null
|
|
}
|
|
if (ownedBy(settings.editorThemeDark, pluginKey)) {
|
|
update.editorThemeDark = null
|
|
}
|
|
if (ownedBy(settings.pluginIconTheme, pluginKey)) {
|
|
update.pluginIconTheme = null
|
|
}
|
|
if (ownedBy(settings.terminalThemeDark, pluginKey)) {
|
|
update.terminalThemeDark = ''
|
|
}
|
|
if (ownedBy(settings.terminalThemeLight, pluginKey)) {
|
|
update.terminalThemeLight = ''
|
|
}
|
|
const kept = (settings.terminalPluginPalettes ?? []).filter((p) => !ownedBy(p.id, pluginKey))
|
|
if (kept.length !== (settings.terminalPluginPalettes ?? []).length) {
|
|
update.terminalPluginPalettes = kept
|
|
}
|
|
return update
|
|
}
|