@brimveyn/aimux-config 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/package.json +1 -1
  2. package/src/actions.ts +11 -8
  3. package/src/defaults.ts +3 -2
  4. package/src/house-themes.ts +44 -302
  5. package/src/index.ts +20 -16
  6. package/src/palette-to-shiki.ts +134 -0
  7. package/src/palette-utils.ts +92 -0
  8. package/src/resolver.ts +0 -1
  9. package/src/themes/opencode.ts +1460 -0
  10. package/src/themes.ts +18 -88
  11. package/src/types.ts +42 -130
  12. package/src/normalize-theme.ts +0 -260
  13. package/src/themes/generated/andromeeda.ts +0 -346
  14. package/src/themes/generated/aurora-x.ts +0 -603
  15. package/src/themes/generated/ayu-dark.ts +0 -817
  16. package/src/themes/generated/ayu-light.ts +0 -817
  17. package/src/themes/generated/ayu-mirage.ts +0 -817
  18. package/src/themes/generated/catppuccin-frappe.ts +0 -2041
  19. package/src/themes/generated/catppuccin-latte.ts +0 -2041
  20. package/src/themes/generated/catppuccin-macchiato.ts +0 -2041
  21. package/src/themes/generated/catppuccin-mocha.ts +0 -2041
  22. package/src/themes/generated/dark-plus.ts +0 -614
  23. package/src/themes/generated/dracula-soft.ts +0 -947
  24. package/src/themes/generated/dracula.ts +0 -947
  25. package/src/themes/generated/everforest-dark.ts +0 -2246
  26. package/src/themes/generated/everforest-light.ts +0 -2246
  27. package/src/themes/generated/github-dark-default.ts +0 -605
  28. package/src/themes/generated/github-dark-dimmed.ts +0 -605
  29. package/src/themes/generated/github-dark-high-contrast.ts +0 -608
  30. package/src/themes/generated/github-dark.ts +0 -519
  31. package/src/themes/generated/github-light-default.ts +0 -602
  32. package/src/themes/generated/github-light-high-contrast.ts +0 -604
  33. package/src/themes/generated/github-light.ts +0 -515
  34. package/src/themes/generated/gruvbox-dark-hard.ts +0 -1124
  35. package/src/themes/generated/gruvbox-dark-medium.ts +0 -1124
  36. package/src/themes/generated/gruvbox-dark-soft.ts +0 -1124
  37. package/src/themes/generated/gruvbox-light-hard.ts +0 -1124
  38. package/src/themes/generated/gruvbox-light-medium.ts +0 -1124
  39. package/src/themes/generated/gruvbox-light-soft.ts +0 -1124
  40. package/src/themes/generated/horizon-bright.ts +0 -420
  41. package/src/themes/generated/horizon.ts +0 -418
  42. package/src/themes/generated/houston.ts +0 -1819
  43. package/src/themes/generated/index.ts +0 -140
  44. package/src/themes/generated/kanagawa-dragon.ts +0 -753
  45. package/src/themes/generated/kanagawa-lotus.ts +0 -753
  46. package/src/themes/generated/kanagawa-wave.ts +0 -753
  47. package/src/themes/generated/laserwave.ts +0 -489
  48. package/src/themes/generated/light-plus.ts +0 -639
  49. package/src/themes/generated/material-theme-darker.ts +0 -759
  50. package/src/themes/generated/material-theme-lighter.ts +0 -759
  51. package/src/themes/generated/material-theme-ocean.ts +0 -759
  52. package/src/themes/generated/material-theme-palenight.ts +0 -759
  53. package/src/themes/generated/material-theme.ts +0 -759
  54. package/src/themes/generated/min-dark.ts +0 -326
  55. package/src/themes/generated/min-light.ts +0 -331
  56. package/src/themes/generated/monokai.ts +0 -463
  57. package/src/themes/generated/night-owl-light.ts +0 -1526
  58. package/src/themes/generated/night-owl.ts +0 -1628
  59. package/src/themes/generated/nord.ts +0 -1277
  60. package/src/themes/generated/one-dark-pro.ts +0 -1920
  61. package/src/themes/generated/one-light.ts +0 -1599
  62. package/src/themes/generated/plastic.ts +0 -315
  63. package/src/themes/generated/poimandres.ts +0 -1233
  64. package/src/themes/generated/red.ts +0 -395
  65. package/src/themes/generated/rose-pine-dawn.ts +0 -696
  66. package/src/themes/generated/rose-pine-moon.ts +0 -696
  67. package/src/themes/generated/rose-pine.ts +0 -696
  68. package/src/themes/generated/slack-dark.ts +0 -594
  69. package/src/themes/generated/slack-ochin.ts +0 -461
  70. package/src/themes/generated/snazzy-light.ts +0 -1254
  71. package/src/themes/generated/solarized-dark.ts +0 -387
  72. package/src/themes/generated/solarized-light.ts +0 -379
  73. package/src/themes/generated/synthwave-84.ts +0 -751
  74. package/src/themes/generated/tokyo-night.ts +0 -1420
  75. package/src/themes/generated/vesper.ts +0 -578
  76. package/src/themes/generated/vitesse-black.ts +0 -629
  77. package/src/themes/generated/vitesse-dark.ts +0 -629
  78. package/src/themes/generated/vitesse-light.ts +0 -627
package/src/themes.ts CHANGED
@@ -1,100 +1,30 @@
1
- import type {
2
- NamedTheme,
3
- NamedThemeDefinition,
4
- Theme,
5
- ThemeColorMap,
6
- ThemeDefinition,
7
- ThemeId,
8
- UserThemeInput,
9
- } from './types'
1
+ import type { AimuxTheme, ThemeId } from './types'
10
2
 
11
3
  import { HOUSE_THEME_IDS, HOUSE_THEMES } from './house-themes'
12
- import { normalizeTheme } from './normalize-theme'
13
- import { GENERATED_THEME_IDS, GENERATED_THEMES } from './themes/generated'
4
+ import { OPENCODE_THEME_IDS, OPENCODE_THEMES } from './themes/opencode'
14
5
 
15
- export const THEMES: Record<string, Theme> = { ...GENERATED_THEMES, ...HOUSE_THEMES }
16
- export const THEME_IDS: ThemeId[] = [...GENERATED_THEME_IDS, ...HOUSE_THEME_IDS]
6
+ export const THEMES: Record<string, AimuxTheme> = { ...HOUSE_THEMES, ...OPENCODE_THEMES }
7
+ export const THEME_IDS: ThemeId[] = [...HOUSE_THEME_IDS, ...OPENCODE_THEME_IDS]
17
8
 
18
- const DEFAULT_THEME_ID: ThemeId = 'aimux'
9
+ const DEFAULT_THEME_ID: ThemeId = 'aimux-dark'
19
10
 
20
- const LEGACY_THEME_ALIASES: Record<string, ThemeId> = {
21
- 'everforest': 'everforest-dark',
22
- 'gruvbox-dark': 'gruvbox-dark-hard',
23
- 'kanagawa': 'kanagawa-wave',
24
- 'one-dark': 'one-dark-pro',
25
- }
26
-
27
- export function migrateThemeId(id: string | undefined): ThemeId {
28
- if (id !== undefined && id in THEMES) return id as ThemeId
29
- if (id !== undefined) {
30
- const alias = LEGACY_THEME_ALIASES[id]
31
- if (alias !== undefined) return alias
32
- }
33
- return DEFAULT_THEME_ID
34
- }
35
-
36
- export function isKnownThemeId(id: string): boolean {
37
- return id in THEMES
38
- }
39
-
40
- function isFullTheme(
41
- def: NamedTheme | NamedThemeDefinition | UserThemeInput
42
- ): def is NamedTheme | UserThemeInput {
43
- return 'settings' in def || 'fg' in def || 'bg' in def
11
+ const LEGACY_MODE_HINTS: Record<string, ThemeId> = {
12
+ 'aimux': 'aimux-dark',
13
+ 'aimux-dark': 'aimux-dark',
14
+ 'aimux-light': 'aimux-light',
44
15
  }
45
16
 
46
17
  /**
47
- * Merge user-declared themes into the registry. Both the full and palette
48
- * shortcut paths run through `normalizeTheme` so every registered theme has
49
- * all AIMUX_COLOR_KEYS populated same guarantees as shipped themes.
18
+ * Migrate a legacy persisted theme id (anything from the old wide registry)
19
+ * to the new light/dark identifier. Anything we don't recognize lands on the
20
+ * dark defaultmodes are the only knob now.
50
21
  */
51
- export function registerUserThemes(
52
- defs: Record<string, NamedTheme | NamedThemeDefinition | UserThemeInput> | undefined
53
- ): void {
54
- if (!defs) return
55
- const fallback = THEMES[DEFAULT_THEME_ID]
56
- if (!fallback) return
57
- for (const [id, def] of Object.entries(defs)) {
58
- if (isFullTheme(def)) {
59
- THEMES[id] = normalizeTheme({ ...def, name: def.name ?? id })
60
- } else {
61
- const base = def.base ? (THEMES[def.base] ?? fallback) : fallback
62
- const colors: Record<string, string> = { ...base.colors }
63
- for (const [k, v] of Object.entries(def.colors ?? {})) {
64
- if (typeof v === 'string') colors[k] = v
65
- }
66
- THEMES[id] = normalizeTheme({
67
- bg: base.bg,
68
- colors,
69
- displayName: def.name,
70
- fg: base.fg,
71
- name: id,
72
- settings: base.settings,
73
- type: base.type,
74
- })
75
- }
76
- if (!THEME_IDS.includes(id)) THEME_IDS.push(id)
77
- }
22
+ export function migrateThemeId(id: string | undefined): ThemeId {
23
+ if (id === undefined) return DEFAULT_THEME_ID
24
+ if (id in THEMES) return id as ThemeId
25
+ return LEGACY_MODE_HINTS[id] ?? DEFAULT_THEME_ID
78
26
  }
79
27
 
80
- export const themes = {
81
- /**
82
- * Palette shortcut. Patches VSCode color keys on top of an existing theme;
83
- * token settings inherit from the base unchanged.
84
- */
85
- define(name: string, base: ThemeId, overrides: Partial<ThemeColorMap>): NamedThemeDefinition {
86
- return { base, colors: overrides, name }
87
- },
88
- /** Unnamed variant of `define`. Prefer `define` in config. */
89
- extend(base: ThemeId, overrides: Partial<ThemeColorMap>): ThemeDefinition {
90
- return { base, colors: overrides }
91
- },
92
- /**
93
- * Passthrough for a raw Shiki / VSCode-shape theme. Missing
94
- * `AIMUX_COLOR_KEYS` are filled at registration via the same fallback chain
95
- * the shipped themes go through.
96
- */
97
- full(theme: UserThemeInput): UserThemeInput {
98
- return theme
99
- },
28
+ export function isKnownThemeId(id: string): id is ThemeId {
29
+ return id in THEMES
100
30
  }
package/src/types.ts CHANGED
@@ -257,6 +257,7 @@ export interface ModalSessionPicker extends ModalBase {
257
257
  }
258
258
  export interface ModalSessionName extends ModalBase {
259
259
  type: 'session-name'
260
+ returnToSessionPicker: boolean
260
261
  }
261
262
  export interface ModalRenameTab extends ModalBase {
262
263
  type: 'rename-tab'
@@ -357,7 +358,12 @@ export type ModalAction =
357
358
  | { type: 'open-help-modal'; scope?: ModeId }
358
359
  | { type: 'open-split-picker'; direction: SplitDirection }
359
360
  | { type: 'open-session-picker' }
360
- | { type: 'open-session-name-modal'; sessionTargetId?: string; initialName?: string }
361
+ | {
362
+ type: 'open-session-name-modal'
363
+ sessionTargetId?: string
364
+ initialName?: string
365
+ returnToSessionPicker?: boolean
366
+ }
361
367
  | { type: 'close-modal' }
362
368
  | { type: 'move-modal-selection'; delta: number }
363
369
  | { type: 'update-command-edit'; char: string }
@@ -532,7 +538,7 @@ export type SideEffect =
532
538
  | { type: 'apply-theme'; action: 'confirm' }
533
539
  | { type: 'apply-theme'; action: 'preview'; delta: 1 | -1 }
534
540
  | { type: 'rename-session'; sessionId: string; name: string }
535
- | { type: 'split-pane'; direction: SplitDirection }
541
+ | { type: 'split-pane'; direction: SplitDirection; sourceTabId?: string }
536
542
  | { type: 'confirm-split' }
537
543
  | { type: 'scroll-git-diff'; delta: number }
538
544
  | { type: 'persist-git-diff-mode-ratio'; ratio: number }
@@ -546,6 +552,7 @@ export type SideEffect =
546
552
  | { type: 'git-push' }
547
553
  | { type: 'confirm-update-selection' }
548
554
  | { type: 'switch-session-by-index'; index: number }
555
+ | { type: 'delete-session'; sessionId: string }
549
556
  | { type: 'toggle-transparent' }
550
557
 
551
558
  // ─── Key input / KeyResult / ModeContext ──────────────────────────────────────
@@ -571,138 +578,40 @@ export interface ModeContext {
571
578
 
572
579
  // ─── Themes ───────────────────────────────────────────────────────────────────
573
580
 
574
- import type { BundledTheme } from 'shiki'
575
-
576
- export type ThemeId = BundledTheme | (string & {})
577
-
578
581
  /**
579
- * VSCode workbench color keys aimux's UI depends on. The build-time normalizer
580
- * guarantees every `Theme` has all of these populated.
582
+ * Compact opencode-style semantic palette. Required tokens cover every UI role;
583
+ * optional tokens fall back via helpers in `palette-utils.ts`.
581
584
  */
582
- export const AIMUX_COLOR_KEYS = [
583
- // editor
584
- 'editor.background',
585
- 'editor.foreground',
586
- 'editor.lineHighlightBackground',
587
- 'editor.selectionBackground',
588
- 'editorLineNumber.foreground',
589
- 'editorCursor.foreground',
590
- // sidebar / panels
591
- 'sideBar.background',
592
- 'sideBar.foreground',
593
- 'sideBarSectionHeader.background',
594
- 'sideBarSectionHeader.foreground',
595
- 'editorGroupHeader.tabsBackground',
596
- 'editorWidget.background',
597
- 'panel.background',
598
- 'panel.border',
599
- // borders & focus
600
- 'focusBorder',
601
- 'editorGroup.border',
602
- 'contrastBorder',
603
- // lists
604
- 'list.activeSelectionBackground',
605
- 'list.activeSelectionForeground',
606
- 'list.hoverBackground',
607
- // text & links
608
- 'foreground',
609
- 'descriptionForeground',
610
- 'textLink.foreground',
611
- 'textLink.activeForeground',
612
- // status
613
- 'errorForeground',
614
- 'editorError.foreground',
615
- 'editorWarning.foreground',
616
- // terminal ANSI
617
- 'terminal.ansiRed',
618
- 'terminal.ansiGreen',
619
- 'terminal.ansiYellow',
620
- 'terminal.ansiBlue',
621
- 'terminal.ansiMagenta',
622
- 'terminal.ansiCyan',
623
- // git decorations
624
- 'gitDecoration.addedResourceForeground',
625
- 'gitDecoration.modifiedResourceForeground',
626
- 'gitDecoration.deletedResourceForeground',
627
- 'gitDecoration.untrackedResourceForeground',
628
- // diff editor
629
- 'diffEditor.insertedLineBackground',
630
- 'diffEditor.removedLineBackground',
631
- // input
632
- 'input.background',
633
- 'input.foreground',
634
- 'input.border',
635
- ] as const
636
-
637
- export type AimuxColorKey = (typeof AIMUX_COLOR_KEYS)[number]
638
-
639
- export type ThemeColorMap = Record<AimuxColorKey, string> & Record<string, string>
640
-
641
- export interface ThemeSettings {
642
- fontStyle?: string
643
- foreground?: string
644
- background?: string
645
- // Some bundled shiki themes include non-standard keys (e.g. `content`); allow passthrough.
646
- [key: string]: string | undefined
647
- }
648
-
649
- export interface ThemeTokenRule {
650
- name?: string
651
- scope?: string | string[]
652
- // `foreground` at the top level is used by a handful of shiki themes as a
653
- // shorthand for `settings.foreground` — accept both forms.
654
- foreground?: string
655
- settings?: ThemeSettings
656
- }
657
-
658
- export interface Theme {
585
+ export interface AimuxPalette {
586
+ // required
587
+ neutral: string
588
+ ink: string
589
+ primary: string
590
+ success: string
591
+ warning: string
592
+ error: string
593
+ info: string
594
+ // optional
595
+ accent?: string
596
+ interactive?: string
597
+ diffAdd?: string
598
+ diffDelete?: string
599
+ }
600
+
601
+ export type ThemeMode = 'light' | 'dark'
602
+ // eslint-disable-next-line typescript/no-redundant-type-constituents
603
+ export type ThemeId = 'aimux-light' | 'aimux-dark' | (string & {})
604
+
605
+ export interface AimuxTheme {
659
606
  name: string
607
+ mode: ThemeMode
660
608
  displayName: string
661
- type: 'dark' | 'light'
609
+ palette: AimuxPalette
662
610
  fg: string
663
611
  bg: string
664
- colors: ThemeColorMap
665
- settings: ThemeTokenRule[]
666
- tokenColors?: ThemeTokenRule[]
667
- colorReplacements?: Record<string, string>
668
- semanticHighlighting?: boolean
669
- semanticTokenColors?: Record<
670
- string,
671
- | string
672
- | {
673
- foreground?: string
674
- fontStyle?: string
675
- bold?: boolean
676
- italic?: boolean
677
- underline?: boolean
678
- }
679
- >
680
- }
681
-
682
- /** Partial-override shape for `themes.define(name, base, overrides)`. */
683
- export interface ThemeDefinition {
684
- base?: ThemeId
685
- colors: Partial<ThemeColorMap>
686
- }
687
-
688
- export interface NamedThemeDefinition extends ThemeDefinition {
689
- name: string
690
- }
691
-
692
- export interface NamedTheme extends Theme {
693
- displayName: string
694
612
  }
695
613
 
696
- /**
697
- * Input shape accepted by `themes.full(...)` — lets users paste a shiki /
698
- * VSCode theme JSON without typing every AIMUX_COLOR_KEY. `registerUserThemes`
699
- * runs it through `normalizeTheme` to produce a complete `Theme`.
700
- */
701
- export interface UserThemeInput extends Partial<Omit<Theme, 'colors' | 'name' | 'displayName'>> {
702
- name: string
703
- displayName: string
704
- colors?: Partial<ThemeColorMap> & Record<string, string | undefined>
705
- }
614
+ export type Theme = AimuxTheme
706
615
 
707
616
  // ─── Backend config (stub) ────────────────────────────────────────────────────
708
617
 
@@ -816,9 +725,13 @@ export interface GitPanePaneConfig extends GitPaneBaseConfig {
816
725
 
817
726
  export type GitPaneConfig = GitPaneEmbeddedConfig | GitPanePaneConfig
818
727
 
728
+ export interface AimuxThemeConfig {
729
+ mode?: ThemeMode
730
+ paletteOverrides?: Partial<AimuxPalette>
731
+ }
732
+
819
733
  export interface AimuxUserConfig {
820
- theme?: ThemeId
821
- themes?: Record<string, NamedThemeDefinition | UserThemeInput>
734
+ theme?: AimuxThemeConfig
822
735
  keymaps?: (k: KeymapBuilderApi) => KeymapBuilderApi
823
736
  backends?: Record<string, BackendConfig>
824
737
  sidebar?: SidebarConfig
@@ -851,8 +764,7 @@ export interface ResolvedKeymapConfig {
851
764
  }
852
765
 
853
766
  export interface ResolvedConfig {
854
- theme: ThemeId | undefined
855
- themes: Record<string, NamedThemeDefinition | UserThemeInput>
767
+ theme: AimuxThemeConfig | undefined
856
768
  keymaps: ResolvedKeymapConfig
857
769
  backends: Record<string, BackendConfig>
858
770
  sidebar: SidebarConfig
@@ -1,260 +0,0 @@
1
- // Normalize a partial theme input into a full aimux `Theme` where every
2
- // AIMUX_COLOR_KEY is guaranteed to have a value. Used at build time by the
3
- // generator script and at runtime by registerUserThemes — same code path, same
4
- // guarantees.
5
-
6
- import {
7
- AIMUX_COLOR_KEYS,
8
- type AimuxColorKey,
9
- type Theme,
10
- type ThemeColorMap,
11
- type ThemeTokenRule,
12
- } from './types'
13
-
14
- export interface NormalizeInput {
15
- name: string
16
- displayName?: string
17
- type?: 'dark' | 'light'
18
- fg?: string
19
- bg?: string
20
- colors?: Record<string, string | null | undefined>
21
- settings?: ThemeTokenRule[]
22
- tokenColors?: ThemeTokenRule[]
23
- colorReplacements?: Record<string, string>
24
- semanticHighlighting?: boolean
25
- semanticTokenColors?: Theme['semanticTokenColors']
26
- }
27
-
28
- function stripAlpha(hex: string | null | undefined): string {
29
- if (!hex || typeof hex !== 'string') return '#000000'
30
- if (hex.length === 9) return hex.slice(0, 7)
31
- if (hex.length === 5) return hex.slice(0, 4)
32
- return hex
33
- }
34
-
35
- function expand3(hex: string): string {
36
- if (hex.length === 4) {
37
- return `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`
38
- }
39
- return hex
40
- }
41
-
42
- function parseHex(hex: string): { r: number; g: number; b: number } {
43
- const clean = expand3(stripAlpha(hex))
44
- return {
45
- b: parseInt(clean.slice(5, 7), 16),
46
- g: parseInt(clean.slice(3, 5), 16),
47
- r: parseInt(clean.slice(1, 3), 16),
48
- }
49
- }
50
-
51
- function toHex({ b, g, r }: { b: number; g: number; r: number }): string {
52
- const clamp = (n: number): number => Math.max(0, Math.min(255, Math.round(n)))
53
- const h = (n: number): string => clamp(n).toString(16).padStart(2, '0')
54
- return `#${h(r)}${h(g)}${h(b)}`
55
- }
56
-
57
- export function shade(hex: string, amount: number): string {
58
- const { b, g, r } = parseHex(hex)
59
- const d = amount * 255
60
- return toHex({ b: b + d, g: g + d, r: r + d })
61
- }
62
-
63
- export function mix(a: string, b: string, t: number): string {
64
- const ca = parseHex(a)
65
- const cb = parseHex(b)
66
- return toHex({
67
- b: ca.b + (cb.b - ca.b) * t,
68
- g: ca.g + (cb.g - ca.g) * t,
69
- r: ca.r + (cb.r - ca.r) * t,
70
- })
71
- }
72
-
73
- function first(map: Record<string, string | null | undefined>, keys: string[]): string | undefined {
74
- for (const k of keys) {
75
- const v = map[k]
76
- if (v && typeof v === 'string') return stripAlpha(v)
77
- }
78
- return undefined
79
- }
80
-
81
- const FALLBACKS: Record<AimuxColorKey, string[]> = {
82
- 'contrastBorder': ['editorGroup.border'],
83
- 'descriptionForeground': ['editorLineNumber.foreground', 'foreground'],
84
- 'diffEditor.insertedLineBackground': [],
85
- 'diffEditor.removedLineBackground': [],
86
- 'editor.background': [],
87
- 'editor.foreground': [],
88
- 'editor.lineHighlightBackground': [],
89
- 'editor.selectionBackground': ['selection.background'],
90
- 'editorCursor.foreground': ['editor.foreground'],
91
- 'editorError.foreground': ['errorForeground', 'terminal.ansiRed'],
92
- 'editorGroup.border': ['contrastBorder', 'panel.border'],
93
- 'editorGroupHeader.tabsBackground': ['sideBar.background'],
94
- 'editorLineNumber.foreground': ['descriptionForeground'],
95
- 'editorWarning.foreground': ['terminal.ansiYellow'],
96
- 'editorWidget.background': ['sideBar.background', 'editor.background'],
97
- 'errorForeground': ['editorError.foreground', 'terminal.ansiRed'],
98
- 'focusBorder': ['textLink.foreground'],
99
- 'foreground': ['editor.foreground'],
100
- 'gitDecoration.addedResourceForeground': ['terminal.ansiGreen'],
101
- 'gitDecoration.deletedResourceForeground': ['terminal.ansiRed', 'editorError.foreground'],
102
- 'gitDecoration.modifiedResourceForeground': ['terminal.ansiYellow'],
103
- 'gitDecoration.untrackedResourceForeground': ['gitDecoration.addedResourceForeground'],
104
- 'input.background': ['editorWidget.background'],
105
- 'input.border': ['focusBorder', 'editorGroup.border'],
106
- 'input.foreground': ['editor.foreground'],
107
- 'list.activeSelectionBackground': ['editor.selectionBackground'],
108
- 'list.activeSelectionForeground': ['editor.foreground'],
109
- 'list.hoverBackground': ['list.activeSelectionBackground'],
110
- 'panel.background': ['editor.background'],
111
- 'panel.border': ['editorGroup.border', 'contrastBorder'],
112
- 'sideBar.background': ['editorGroupHeader.tabsBackground', 'panel.background'],
113
- 'sideBar.foreground': ['editor.foreground', 'foreground'],
114
- 'sideBarSectionHeader.background': ['sideBar.background'],
115
- 'sideBarSectionHeader.foreground': ['sideBar.foreground'],
116
- 'terminal.ansiBlue': ['textLink.foreground'],
117
- 'terminal.ansiCyan': [],
118
- 'terminal.ansiGreen': ['gitDecoration.addedResourceForeground'],
119
- 'terminal.ansiMagenta': [],
120
- 'terminal.ansiRed': ['editorError.foreground'],
121
- 'terminal.ansiYellow': ['editorWarning.foreground'],
122
- 'textLink.activeForeground': ['textLink.foreground'],
123
- 'textLink.foreground': ['editorLink.activeForeground', 'terminal.ansiBlue'],
124
- }
125
-
126
- function computeFallback(
127
- key: AimuxColorKey,
128
- bg: string,
129
- fg: string,
130
- resolved: ThemeColorMap
131
- ): string {
132
- switch (key) {
133
- case 'editor.background':
134
- return bg
135
- case 'editor.foreground':
136
- return fg
137
- case 'editor.lineHighlightBackground':
138
- return shade(bg, 0.03)
139
- case 'editor.selectionBackground':
140
- return shade(bg, 0.1)
141
- case 'editorCursor.foreground':
142
- return fg
143
- case 'editorLineNumber.foreground':
144
- return mix(fg, bg, 0.55)
145
- case 'sideBar.background':
146
- return shade(bg, -0.04)
147
- case 'sideBar.foreground':
148
- return fg
149
- case 'sideBarSectionHeader.background':
150
- return shade(bg, -0.02)
151
- case 'sideBarSectionHeader.foreground':
152
- return mix(fg, bg, 0.35)
153
- case 'editorGroupHeader.tabsBackground':
154
- return shade(bg, -0.04)
155
- case 'editorWidget.background':
156
- return shade(bg, -0.08)
157
- case 'panel.background':
158
- return bg
159
- case 'panel.border':
160
- return shade(bg, 0.15)
161
- case 'focusBorder':
162
- return resolved['textLink.foreground'] ?? fg
163
- case 'editorGroup.border':
164
- return shade(bg, 0.15)
165
- case 'contrastBorder':
166
- return shade(bg, 0.2)
167
- case 'list.activeSelectionBackground':
168
- return shade(bg, 0.1)
169
- case 'list.activeSelectionForeground':
170
- return fg
171
- case 'list.hoverBackground':
172
- return shade(bg, 0.05)
173
- case 'foreground':
174
- return fg
175
- case 'descriptionForeground':
176
- return mix(fg, bg, 0.55)
177
- case 'textLink.foreground':
178
- case 'textLink.activeForeground':
179
- return resolved['terminal.ansiBlue'] ?? fg
180
- case 'errorForeground':
181
- case 'editorError.foreground':
182
- return '#f38ba8'
183
- case 'editorWarning.foreground':
184
- return '#f6c177'
185
- case 'terminal.ansiRed':
186
- return '#f38ba8'
187
- case 'terminal.ansiGreen':
188
- return '#8bd5ca'
189
- case 'terminal.ansiYellow':
190
- return '#f6c177'
191
- case 'terminal.ansiBlue':
192
- return '#7cd1b8'
193
- case 'terminal.ansiMagenta':
194
- return resolved['textLink.activeForeground'] ?? '#c4a7e7'
195
- case 'terminal.ansiCyan':
196
- return resolved['terminal.ansiBlue'] ?? '#89dceb'
197
- case 'gitDecoration.addedResourceForeground':
198
- return resolved['terminal.ansiGreen'] ?? '#8bd5ca'
199
- case 'gitDecoration.modifiedResourceForeground':
200
- return resolved['terminal.ansiYellow'] ?? '#f6c177'
201
- case 'gitDecoration.deletedResourceForeground':
202
- return resolved['terminal.ansiRed'] ?? '#f38ba8'
203
- case 'gitDecoration.untrackedResourceForeground':
204
- return resolved['gitDecoration.addedResourceForeground'] ?? '#8bd5ca'
205
- case 'diffEditor.insertedLineBackground':
206
- return mix(bg, resolved['gitDecoration.addedResourceForeground'] ?? '#8bd5ca', 0.18)
207
- case 'diffEditor.removedLineBackground':
208
- return mix(bg, resolved['gitDecoration.deletedResourceForeground'] ?? '#f38ba8', 0.18)
209
- case 'input.background':
210
- return shade(bg, -0.04)
211
- case 'input.foreground':
212
- return fg
213
- case 'input.border':
214
- return shade(bg, 0.15)
215
- default:
216
- return fg
217
- }
218
- }
219
-
220
- export function normalizeTheme(raw: NormalizeInput): Theme {
221
- const srcColors = raw.colors ?? {}
222
- const bg = stripAlpha(srcColors['editor.background'] ?? raw.bg ?? '#000000')
223
- const fg = stripAlpha(srcColors['editor.foreground'] ?? raw.fg ?? '#ffffff')
224
-
225
- const resolved: ThemeColorMap = {} as ThemeColorMap
226
- for (const key of AIMUX_COLOR_KEYS) {
227
- const direct = srcColors[key]
228
- if (direct && typeof direct === 'string') {
229
- resolved[key] = stripAlpha(direct)
230
- continue
231
- }
232
- const alt = first(srcColors, FALLBACKS[key])
233
- if (alt) resolved[key] = alt
234
- }
235
- for (const key of AIMUX_COLOR_KEYS) {
236
- if (!resolved[key]) resolved[key] = computeFallback(key, bg, fg, resolved)
237
- }
238
- for (const [k, v] of Object.entries(srcColors)) {
239
- if (!resolved[k] && v && typeof v === 'string') resolved[k] = stripAlpha(v)
240
- }
241
-
242
- const out: Theme = {
243
- bg,
244
- colors: resolved,
245
- displayName: raw.displayName ?? raw.name,
246
- fg,
247
- name: raw.name,
248
- settings: raw.settings ?? [],
249
- type: raw.type ?? 'dark',
250
- }
251
- if (raw.colorReplacements && Object.keys(raw.colorReplacements).length > 0) {
252
- out.colorReplacements = raw.colorReplacements
253
- }
254
- if (raw.semanticHighlighting) out.semanticHighlighting = raw.semanticHighlighting
255
- if (raw.semanticTokenColors && Object.keys(raw.semanticTokenColors).length > 0) {
256
- out.semanticTokenColors = raw.semanticTokenColors
257
- }
258
- if (raw.tokenColors && raw.tokenColors !== raw.settings) out.tokenColors = raw.tokenColors
259
- return out
260
- }