1+ import { EditorState } from "@codemirror/state" ;
12import { oneDark } from "@codemirror/theme-one-dark" ;
23import aura , { config as auraConfig } from "./aura" ;
34import dracula , { config as draculaConfig } from "./dracula" ;
@@ -32,17 +33,93 @@ const oneDarkConfig = {
3233} ;
3334
3435const themes = new Map ( ) ;
36+ const warnedInvalidThemes = new Set ( ) ;
37+
38+ function normalizeExtensions ( value , target = [ ] ) {
39+ if ( Array . isArray ( value ) ) {
40+ value . forEach ( ( item ) => normalizeExtensions ( item , target ) ) ;
41+ return target ;
42+ }
43+
44+ if ( value !== null && value !== undefined ) {
45+ target . push ( value ) ;
46+ }
47+
48+ return target ;
49+ }
50+
51+ function toExtensionGetter ( getExtension ) {
52+ if ( typeof getExtension === "function" ) {
53+ return ( ) => normalizeExtensions ( getExtension ( ) ) ;
54+ }
55+
56+ return ( ) => normalizeExtensions ( getExtension ) ;
57+ }
58+
59+ function logInvalidThemeOnce ( themeId , error , reason = "" ) {
60+ if ( warnedInvalidThemes . has ( themeId ) ) return ;
61+ warnedInvalidThemes . add ( themeId ) ;
62+ const message = reason
63+ ? `[editorThemes] Theme '${ themeId } ' is invalid: ${ reason } `
64+ : `[editorThemes] Theme '${ themeId } ' is invalid.` ;
65+ console . error ( message , error ) ;
66+ }
67+
68+ function validateThemeExtensions ( themeId , extensions ) {
69+ if ( ! extensions . length ) {
70+ logInvalidThemeOnce ( themeId , null , "no extensions were returned" ) ;
71+ return false ;
72+ }
73+
74+ try {
75+ // Validate against Acode's own CodeMirror instance.
76+ EditorState . create ( { doc : "" , extensions } ) ;
77+ return true ;
78+ } catch ( error ) {
79+ logInvalidThemeOnce ( themeId , error ) ;
80+ return false ;
81+ }
82+ }
83+
84+ function resolveThemeEntryExtensions ( theme , fallbackExtensions ) {
85+ const fallback = fallbackExtensions . length
86+ ? [ ...fallbackExtensions ]
87+ : [ oneDark ] ;
88+
89+ if ( ! theme ) return fallback ;
90+
91+ try {
92+ const resolved = normalizeExtensions ( theme . getExtension ?. ( ) ) ;
93+ if ( ! validateThemeExtensions ( theme . id , resolved ) ) {
94+ return fallback ;
95+ }
96+ return resolved ;
97+ } catch ( error ) {
98+ logInvalidThemeOnce ( theme . id , error ) ;
99+ return fallback ;
100+ }
101+ }
35102
36103export function addTheme ( id , caption , isDark , getExtension , config = null ) {
37- const key = String ( id ) . toLowerCase ( ) ;
38- if ( themes . has ( key ) ) return ;
39- themes . set ( key , {
104+ const key = String ( id || "" )
105+ . trim ( )
106+ . toLowerCase ( ) ;
107+ if ( ! key || themes . has ( key ) ) return false ;
108+
109+ const theme = {
40110 id : key ,
41111 caption : caption || id ,
42112 isDark : ! ! isDark ,
43- getExtension,
113+ getExtension : toExtensionGetter ( getExtension ) ,
44114 config : config || null ,
45- } ) ;
115+ } ;
116+
117+ if ( ! validateThemeExtensions ( key , theme . getExtension ( ) ) ) {
118+ return false ;
119+ }
120+
121+ themes . set ( key , theme ) ;
122+ return true ;
46123}
47124
48125export function getThemes ( ) {
@@ -60,6 +137,13 @@ export function getThemeConfig(id) {
60137 return theme ?. config || oneDarkConfig ;
61138}
62139
140+ export function getThemeExtensions ( id , fallback = [ oneDark ] ) {
141+ const fallbackExtensions = normalizeExtensions ( fallback ) ;
142+ const theme =
143+ getThemeById ( id ) || getThemeById ( String ( id || "" ) . replace ( / - / g, "_" ) ) ;
144+ return resolveThemeEntryExtensions ( theme , fallbackExtensions ) ;
145+ }
146+
63147export function removeTheme ( id ) {
64148 if ( ! id ) return ;
65149 themes . delete ( String ( id ) . toLowerCase ( ) ) ;
@@ -142,6 +226,7 @@ export default {
142226 getThemes,
143227 getThemeById,
144228 getThemeConfig,
229+ getThemeExtensions,
145230 addTheme,
146231 removeTheme,
147232} ;
0 commit comments