@brimveyn/aimux-config 0.4.4 → 0.4.6

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 +46 -131
  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
@@ -168,7 +168,8 @@ export interface GitPaneState {
168
168
  visible: boolean
169
169
  mode: 'embedded' | 'pane'
170
170
  position: 'top' | 'bottom' | 'left' | 'right'
171
- ratio: number
171
+ paneRatio: number
172
+ embeddedRatio: number
172
173
  diffModeRatio: number
173
174
  fileListMode: GitFileListMode
174
175
  treeCompaction: boolean
@@ -257,6 +258,7 @@ export interface ModalSessionPicker extends ModalBase {
257
258
  }
258
259
  export interface ModalSessionName extends ModalBase {
259
260
  type: 'session-name'
261
+ returnToSessionPicker: boolean
260
262
  }
261
263
  export interface ModalRenameTab extends ModalBase {
262
264
  type: 'rename-tab'
@@ -357,7 +359,12 @@ export type ModalAction =
357
359
  | { type: 'open-help-modal'; scope?: ModeId }
358
360
  | { type: 'open-split-picker'; direction: SplitDirection }
359
361
  | { type: 'open-session-picker' }
360
- | { type: 'open-session-name-modal'; sessionTargetId?: string; initialName?: string }
362
+ | {
363
+ type: 'open-session-name-modal'
364
+ sessionTargetId?: string
365
+ initialName?: string
366
+ returnToSessionPicker?: boolean
367
+ }
361
368
  | { type: 'close-modal' }
362
369
  | { type: 'move-modal-selection'; delta: number }
363
370
  | { type: 'update-command-edit'; char: string }
@@ -437,10 +444,12 @@ export type LayoutAction =
437
444
  export type UIAction =
438
445
  | { type: 'toggle-sidebar' }
439
446
  | { type: 'resize-sidebar'; delta: number }
447
+ | { type: 'set-sidebar-width'; width: number }
440
448
  | { type: 'set-focus-mode'; focusMode: FocusMode }
441
449
  | { type: 'set-terminal-size'; cols: number; rows: number }
442
450
  | { type: 'toggle-git-pane' }
443
451
  | { type: 'resize-git-pane'; delta: number }
452
+ | { type: 'set-git-pane-ratio'; target: 'pane' | 'embedded'; ratio: number }
444
453
  | { type: 'resize-git-diff-pane'; delta: number }
445
454
  | { type: 'set-git-pane-mode'; mode: 'embedded' | 'pane' }
446
455
  | { type: 'set-git-pane-position'; position: 'top' | 'bottom' | 'left' | 'right' }
@@ -532,7 +541,7 @@ export type SideEffect =
532
541
  | { type: 'apply-theme'; action: 'confirm' }
533
542
  | { type: 'apply-theme'; action: 'preview'; delta: 1 | -1 }
534
543
  | { type: 'rename-session'; sessionId: string; name: string }
535
- | { type: 'split-pane'; direction: SplitDirection }
544
+ | { type: 'split-pane'; direction: SplitDirection; sourceTabId?: string }
536
545
  | { type: 'confirm-split' }
537
546
  | { type: 'scroll-git-diff'; delta: number }
538
547
  | { type: 'persist-git-diff-mode-ratio'; ratio: number }
@@ -546,6 +555,7 @@ export type SideEffect =
546
555
  | { type: 'git-push' }
547
556
  | { type: 'confirm-update-selection' }
548
557
  | { type: 'switch-session-by-index'; index: number }
558
+ | { type: 'delete-session'; sessionId: string }
549
559
  | { type: 'toggle-transparent' }
550
560
 
551
561
  // ─── Key input / KeyResult / ModeContext ──────────────────────────────────────
@@ -571,138 +581,40 @@ export interface ModeContext {
571
581
 
572
582
  // ─── Themes ───────────────────────────────────────────────────────────────────
573
583
 
574
- import type { BundledTheme } from 'shiki'
575
-
576
- export type ThemeId = BundledTheme | (string & {})
577
-
578
584
  /**
579
- * VSCode workbench color keys aimux's UI depends on. The build-time normalizer
580
- * guarantees every `Theme` has all of these populated.
585
+ * Compact opencode-style semantic palette. Required tokens cover every UI role;
586
+ * optional tokens fall back via helpers in `palette-utils.ts`.
581
587
  */
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 {
588
+ export interface AimuxPalette {
589
+ // required
590
+ neutral: string
591
+ ink: string
592
+ primary: string
593
+ success: string
594
+ warning: string
595
+ error: string
596
+ info: string
597
+ // optional
598
+ accent?: string
599
+ interactive?: string
600
+ diffAdd?: string
601
+ diffDelete?: string
602
+ }
603
+
604
+ export type ThemeMode = 'light' | 'dark'
605
+ // eslint-disable-next-line typescript/no-redundant-type-constituents
606
+ export type ThemeId = 'aimux-light' | 'aimux-dark' | (string & {})
607
+
608
+ export interface AimuxTheme {
659
609
  name: string
610
+ mode: ThemeMode
660
611
  displayName: string
661
- type: 'dark' | 'light'
612
+ palette: AimuxPalette
662
613
  fg: string
663
614
  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
615
  }
691
616
 
692
- export interface NamedTheme extends Theme {
693
- displayName: string
694
- }
695
-
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
- }
617
+ export type Theme = AimuxTheme
706
618
 
707
619
  // ─── Backend config (stub) ────────────────────────────────────────────────────
708
620
 
@@ -816,9 +728,13 @@ export interface GitPanePaneConfig extends GitPaneBaseConfig {
816
728
 
817
729
  export type GitPaneConfig = GitPaneEmbeddedConfig | GitPanePaneConfig
818
730
 
731
+ export interface AimuxThemeConfig {
732
+ mode?: ThemeMode
733
+ paletteOverrides?: Partial<AimuxPalette>
734
+ }
735
+
819
736
  export interface AimuxUserConfig {
820
- theme?: ThemeId
821
- themes?: Record<string, NamedThemeDefinition | UserThemeInput>
737
+ theme?: AimuxThemeConfig
822
738
  keymaps?: (k: KeymapBuilderApi) => KeymapBuilderApi
823
739
  backends?: Record<string, BackendConfig>
824
740
  sidebar?: SidebarConfig
@@ -851,8 +767,7 @@ export interface ResolvedKeymapConfig {
851
767
  }
852
768
 
853
769
  export interface ResolvedConfig {
854
- theme: ThemeId | undefined
855
- themes: Record<string, NamedThemeDefinition | UserThemeInput>
770
+ theme: AimuxThemeConfig | undefined
856
771
  keymaps: ResolvedKeymapConfig
857
772
  backends: Record<string, BackendConfig>
858
773
  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
- }