@@ -31,7 +31,7 @@ import {
3131} from "@/plugin/shared"
3232import { PluginMeta } from "@/plugin/meta"
3333import { installPlugin as installModulePlugin , patchPluginConfig , readPluginManifest } from "@/plugin/install"
34- import { addTheme , hasTheme } from "../context/theme"
34+ import { hasTheme , upsertTheme } from "../context/theme"
3535import { Global } from "@/global"
3636import { Filesystem } from "@/util/filesystem"
3737import { Process } from "@/util/process"
@@ -49,7 +49,8 @@ type PluginLoad = {
4949 source : PluginSource | "internal"
5050 id : string
5151 module : TuiPluginModule
52- install_theme : TuiTheme [ "install" ]
52+ theme_meta : TuiConfig . PluginMeta
53+ theme_root : string
5354}
5455
5556type Api = HostPluginApi
@@ -64,6 +65,7 @@ type PluginEntry = {
6465 id : string
6566 load : PluginLoad
6667 meta : TuiPluginMeta
68+ themes : Record < string , PluginMeta . Theme >
6769 plugin : TuiPlugin
6870 options : Config . PluginOptions | undefined
6971 enabled : boolean
@@ -143,12 +145,54 @@ function resolveRoot(root: string) {
143145 return path . resolve ( process . cwd ( ) , root )
144146}
145147
146- function createThemeInstaller ( meta : TuiConfig . PluginMeta , root : string , spec : string ) : TuiTheme [ "install" ] {
148+ function createThemeInstaller (
149+ meta : TuiConfig . PluginMeta ,
150+ root : string ,
151+ spec : string ,
152+ plugin : PluginEntry ,
153+ ) : TuiTheme [ "install" ] {
147154 return async ( file ) => {
148155 const raw = file . startsWith ( "file://" ) ? fileURLToPath ( file ) : file
149156 const src = path . isAbsolute ( raw ) ? raw : path . resolve ( root , raw )
150- const theme = path . basename ( src , path . extname ( src ) )
151- if ( hasTheme ( theme ) ) return
157+ const name = path . basename ( src , path . extname ( src ) )
158+ const source_dir = path . dirname ( meta . source )
159+ const local_dir =
160+ path . basename ( source_dir ) === ".opencode"
161+ ? path . join ( source_dir , "themes" )
162+ : path . join ( source_dir , ".opencode" , "themes" )
163+ const dest_dir = meta . scope === "local" ? local_dir : path . join ( Global . Path . config , "themes" )
164+ const dest = path . join ( dest_dir , `${ name } .json` )
165+ const stat = await Filesystem . statAsync ( src )
166+ const mtime = stat ? Math . floor ( typeof stat . mtimeMs === "bigint" ? Number ( stat . mtimeMs ) : stat . mtimeMs ) : undefined
167+ const size = stat ? ( typeof stat . size === "bigint" ? Number ( stat . size ) : stat . size ) : undefined
168+ const exists = hasTheme ( name )
169+ const prev = plugin . themes [ name ]
170+
171+ if ( exists ) {
172+ if ( plugin . meta . state !== "updated" ) return
173+ if ( ! prev ) {
174+ if ( await Filesystem . exists ( dest ) ) {
175+ plugin . themes [ name ] = {
176+ src,
177+ dest,
178+ mtime,
179+ size,
180+ }
181+ await PluginMeta . setTheme ( plugin . id , name , plugin . themes [ name ] ! ) . catch ( ( error ) => {
182+ log . warn ( "failed to track tui plugin theme" , {
183+ path : spec ,
184+ id : plugin . id ,
185+ theme : src ,
186+ dest,
187+ error,
188+ } )
189+ } )
190+ }
191+ return
192+ }
193+ if ( prev . dest !== dest ) return
194+ if ( prev . mtime === mtime && prev . size === size ) return
195+ }
152196
153197 const text = await Filesystem . readText ( src ) . catch ( ( error ) => {
154198 log . warn ( "failed to read tui plugin theme" , { path : spec , theme : src , error } )
@@ -170,20 +214,28 @@ function createThemeInstaller(meta: TuiConfig.PluginMeta, root: string, spec: st
170214 return
171215 }
172216
173- const source_dir = path . dirname ( meta . source )
174- const local_dir =
175- path . basename ( source_dir ) === ".opencode"
176- ? path . join ( source_dir , "themes" )
177- : path . join ( source_dir , ".opencode" , "themes" )
178- const dest_dir = meta . scope === "local" ? local_dir : path . join ( Global . Path . config , "themes" )
179- const dest = path . join ( dest_dir , `${ theme } .json` )
180- if ( ! ( await Filesystem . exists ( dest ) ) ) {
217+ if ( exists || ! ( await Filesystem . exists ( dest ) ) ) {
181218 await Filesystem . write ( dest , text ) . catch ( ( error ) => {
182219 log . warn ( "failed to persist tui plugin theme" , { path : spec , theme : src , dest, error } )
183220 } )
184221 }
185222
186- addTheme ( theme , data )
223+ upsertTheme ( name , data )
224+ plugin . themes [ name ] = {
225+ src,
226+ dest,
227+ mtime,
228+ size,
229+ }
230+ await PluginMeta . setTheme ( plugin . id , name , plugin . themes [ name ] ! ) . catch ( ( error ) => {
231+ log . warn ( "failed to track tui plugin theme" , {
232+ path : spec ,
233+ id : plugin . id ,
234+ theme : src ,
235+ dest,
236+ error,
237+ } )
238+ } )
187239 }
188240}
189241
@@ -222,7 +274,6 @@ async function loadExternalPlugin(
222274 }
223275
224276 const root = resolveRoot ( source === "file" ? spec : target )
225- const install_theme = createThemeInstaller ( meta , root , spec )
226277 const entry = await resolvePluginEntrypoint ( spec , target , "tui" ) . catch ( ( error ) => {
227278 fail ( "failed to resolve tui plugin entry" , { path : spec , target, retry, error } )
228279 return
@@ -253,7 +304,8 @@ async function loadExternalPlugin(
253304 source,
254305 id,
255306 module : mod ,
256- install_theme,
307+ theme_meta : meta ,
308+ theme_root : root ,
257309 }
258310}
259311
@@ -297,14 +349,11 @@ function loadInternalPlugin(item: InternalTuiPlugin): PluginLoad {
297349 source : "internal" ,
298350 id : item . id ,
299351 module : item ,
300- install_theme : createThemeInstaller (
301- {
302- scope : "global" ,
303- source : target ,
304- } ,
305- process . cwd ( ) ,
306- spec ,
307- ) ,
352+ theme_meta : {
353+ scope : "global" ,
354+ source : target ,
355+ } ,
356+ theme_root : process . cwd ( ) ,
308357 }
309358}
310359
@@ -436,7 +485,7 @@ async function activatePluginEntry(state: RuntimeState, plugin: PluginEntry, per
436485 if ( plugin . scope ) return true
437486
438487 const scope = createPluginScope ( plugin . load , plugin . id )
439- const api = pluginApi ( state , plugin . load , scope , plugin . id )
488+ const api = pluginApi ( state , plugin , scope , plugin . id )
440489 const ok = await Promise . resolve ( )
441490 . then ( async ( ) => {
442491 await plugin . plugin ( api , plugin . options , plugin . meta )
@@ -479,9 +528,10 @@ async function deactivatePluginById(state: RuntimeState | undefined, id: string,
479528 return deactivatePluginEntry ( state , plugin , persist )
480529}
481530
482- function pluginApi ( runtime : RuntimeState , load : PluginLoad , scope : PluginScope , base : string ) : TuiPluginApi {
531+ function pluginApi ( runtime : RuntimeState , plugin : PluginEntry , scope : PluginScope , base : string ) : TuiPluginApi {
483532 const api = runtime . api
484533 const host = runtime . slots
534+ const load = plugin . load
485535 const command : TuiPluginApi [ "command" ] = {
486536 register ( cb ) {
487537 return scope . track ( api . command . register ( cb ) )
@@ -504,7 +554,7 @@ function pluginApi(runtime: RuntimeState, load: PluginLoad, scope: PluginScope,
504554 }
505555
506556 const theme : TuiPluginApi [ "theme" ] = Object . assign ( Object . create ( api . theme ) , {
507- install : load . install_theme ,
557+ install : createThemeInstaller ( load . theme_meta , load . theme_root , load . spec , plugin ) ,
508558 } )
509559
510560 const event : TuiPluginApi [ "event" ] = {
@@ -563,13 +613,14 @@ function pluginApi(runtime: RuntimeState, load: PluginLoad, scope: PluginScope,
563613 }
564614}
565615
566- function collectPluginEntries ( load : PluginLoad , meta : TuiPluginMeta ) {
616+ function collectPluginEntries ( load : PluginLoad , meta : TuiPluginMeta , themes : Record < string , PluginMeta . Theme > = { } ) {
567617 const options = load . item ? Config . pluginOptions ( load . item ) : undefined
568618 return [
569619 {
570620 id : load . id ,
571621 load,
572622 meta,
623+ themes,
573624 plugin : load . module . tui ,
574625 options,
575626 enabled : true ,
@@ -661,7 +712,8 @@ async function addExternalPluginEntries(state: RuntimeState, ready: PluginLoad[]
661712 }
662713
663714 const row = createMeta ( entry . source , entry . spec , entry . target , hit , entry . id )
664- for ( const plugin of collectPluginEntries ( entry , row ) ) {
715+ const themes = hit ?. entry . themes ? { ...hit . entry . themes } : { }
716+ for ( const plugin of collectPluginEntries ( entry , row , themes ) ) {
665717 if ( ! addPluginEntry ( state , plugin ) ) {
666718 ok = false
667719 continue
0 commit comments