mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-13 19:20:06 +00:00
feat: add extension API for programmatic theme registration and switching
Add RegisterTheme, SetTheme, and ListThemes to the extension Context, allowing extensions to create custom themes at runtime and switch between them. Uses ThemeColor/ThemeColorConfig concrete structs (no interfaces) for Yaegi safety. Include neon-theme.go example extension demonstrating the API.
This commit is contained in:
+22
@@ -936,6 +936,28 @@ func runNormalMode(ctx context.Context) error {
|
||||
SetActiveTools: func(names []string) {
|
||||
kitInstance.SetExtensionActiveTools(names)
|
||||
},
|
||||
RegisterTheme: func(name string, config extensions.ThemeColorConfig) {
|
||||
tc := func(c extensions.ThemeColor) [2]string { return [2]string{c.Light, c.Dark} }
|
||||
ui.RegisterThemeFromConfig(name,
|
||||
tc(config.Primary), tc(config.Secondary),
|
||||
tc(config.Success), tc(config.Warning),
|
||||
tc(config.Error), tc(config.Info),
|
||||
tc(config.Text), tc(config.Muted),
|
||||
tc(config.VeryMuted), tc(config.Background),
|
||||
tc(config.Border), tc(config.MutedBorder),
|
||||
tc(config.System), tc(config.Tool),
|
||||
tc(config.Accent), tc(config.Highlight),
|
||||
tc(config.MdHeading), tc(config.MdLink),
|
||||
tc(config.MdKeyword), tc(config.MdString),
|
||||
tc(config.MdNumber), tc(config.MdComment),
|
||||
)
|
||||
},
|
||||
SetTheme: func(name string) error {
|
||||
return ui.ApplyTheme(name)
|
||||
},
|
||||
ListThemes: func() []string {
|
||||
return ui.ListThemes()
|
||||
},
|
||||
ShowOverlay: func(config extensions.OverlayConfig) extensions.OverlayResult {
|
||||
ch := make(chan app.OverlayResponse, 1)
|
||||
appInstance.SendOverlayRequest(app.OverlayRequestEvent{
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
//go:build ignore
|
||||
|
||||
package main
|
||||
|
||||
import "kit/ext"
|
||||
|
||||
// Init registers a "neon" theme and a /neon slash command to apply it.
|
||||
// Demonstrates how extensions can create and set themes programmatically.
|
||||
//
|
||||
// Usage: kit -e examples/extensions/neon-theme.go
|
||||
func Init(api ext.API) {
|
||||
api.OnSessionStart(func(_ ext.SessionStartEvent, ctx ext.Context) {
|
||||
// Register a cyberpunk neon theme at startup.
|
||||
ctx.RegisterTheme("neon", ext.ThemeColorConfig{
|
||||
Primary: ext.ThemeColor{Light: "#CC00FF", Dark: "#FF00FF"},
|
||||
Secondary: ext.ThemeColor{Light: "#0088CC", Dark: "#00FFFF"},
|
||||
Success: ext.ThemeColor{Light: "#00CC44", Dark: "#00FF66"},
|
||||
Warning: ext.ThemeColor{Light: "#CCAA00", Dark: "#FFFF00"},
|
||||
Error: ext.ThemeColor{Light: "#CC0033", Dark: "#FF0055"},
|
||||
Info: ext.ThemeColor{Light: "#0088CC", Dark: "#00CCFF"},
|
||||
Text: ext.ThemeColor{Light: "#111111", Dark: "#F0F0F0"},
|
||||
Background: ext.ThemeColor{Light: "#F0F0F0", Dark: "#0A0A14"},
|
||||
MdKeyword: ext.ThemeColor{Light: "#CC00FF", Dark: "#FF00FF"},
|
||||
MdString: ext.ThemeColor{Light: "#00CC44", Dark: "#00FF66"},
|
||||
MdComment: ext.ThemeColor{Light: "#888888", Dark: "#555555"},
|
||||
})
|
||||
|
||||
ctx.PrintInfo("Neon theme registered! Use /theme neon to activate.")
|
||||
})
|
||||
|
||||
// Also register a /neon slash command as a shortcut.
|
||||
api.RegisterCommand(ext.CommandDef{
|
||||
Name: "neon",
|
||||
Description: "Switch to the neon cyberpunk theme",
|
||||
Execute: func(args string, ctx ext.Context) (string, error) {
|
||||
if err := ctx.SetTheme("neon"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "Neon theme activated!", nil
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -485,6 +485,36 @@ type Context struct {
|
||||
// ctx.RenderMessage("build-status", "All 42 tests passed.")
|
||||
RenderMessage func(rendererName string, content string)
|
||||
|
||||
// RegisterTheme adds a named theme to the runtime theme registry.
|
||||
// If a theme with the same name already exists it is replaced.
|
||||
// The theme becomes available via /theme and ctx.SetTheme().
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// ctx.RegisterTheme("neon", ext.ThemeColorConfig{
|
||||
// Primary: ext.ThemeColor{Dark: "#FF00FF"},
|
||||
// Secondary: ext.ThemeColor{Dark: "#00FFFF"},
|
||||
// Success: ext.ThemeColor{Dark: "#00FF00"},
|
||||
// Warning: ext.ThemeColor{Dark: "#FFFF00"},
|
||||
// Error: ext.ThemeColor{Dark: "#FF0000"},
|
||||
// Info: ext.ThemeColor{Dark: "#00FFFF"},
|
||||
// Text: ext.ThemeColor{Dark: "#FFFFFF"},
|
||||
// Background: ext.ThemeColor{Dark: "#000000"},
|
||||
// })
|
||||
RegisterTheme func(name string, config ThemeColorConfig)
|
||||
|
||||
// SetTheme switches the active color theme by name. The name must
|
||||
// match a built-in theme, a user/project theme file, or a theme
|
||||
// registered via RegisterTheme. Returns an error if not found.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// err := ctx.SetTheme("neon")
|
||||
SetTheme func(name string) error
|
||||
|
||||
// ListThemes returns the names of all available themes.
|
||||
ListThemes func() []string
|
||||
|
||||
// ReloadExtensions hot-reloads all extensions from disk. Existing
|
||||
// extensions receive a SessionShutdown event, then new code is loaded
|
||||
// and receives a SessionStart event. Event handlers, commands,
|
||||
@@ -1723,3 +1753,44 @@ type BeforeCompactResult struct {
|
||||
}
|
||||
|
||||
func (BeforeCompactResult) isResult() {}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Theme types (exposed to Yaegi — concrete structs, string hex colors)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ThemeColor is an adaptive color pair with light and dark hex values.
|
||||
// Either field may be empty to inherit from the default theme.
|
||||
type ThemeColor struct {
|
||||
Light string
|
||||
Dark string
|
||||
}
|
||||
|
||||
// ThemeColorConfig defines a complete color theme that extensions can register
|
||||
// programmatically via ctx.RegisterTheme(). Uses plain hex strings (not
|
||||
// color.Color) so the type is safe to pass across the Yaegi boundary.
|
||||
type ThemeColorConfig struct {
|
||||
Primary ThemeColor
|
||||
Secondary ThemeColor
|
||||
Success ThemeColor
|
||||
Warning ThemeColor
|
||||
Error ThemeColor
|
||||
Info ThemeColor
|
||||
Text ThemeColor
|
||||
Muted ThemeColor
|
||||
VeryMuted ThemeColor
|
||||
Background ThemeColor
|
||||
Border ThemeColor
|
||||
MutedBorder ThemeColor
|
||||
System ThemeColor
|
||||
Tool ThemeColor
|
||||
Accent ThemeColor
|
||||
Highlight ThemeColor
|
||||
|
||||
// Markdown/syntax highlighting overrides.
|
||||
MdHeading ThemeColor
|
||||
MdLink ThemeColor
|
||||
MdKeyword ThemeColor
|
||||
MdString ThemeColor
|
||||
MdNumber ThemeColor
|
||||
MdComment ThemeColor
|
||||
}
|
||||
|
||||
@@ -119,6 +119,10 @@ func Symbols() interp.Exports {
|
||||
"SubagentHandle": reflect.ValueOf((*SubagentHandle)(nil)),
|
||||
"SubagentEvent": reflect.ValueOf((*SubagentEvent)(nil)),
|
||||
|
||||
// Theme types
|
||||
"ThemeColor": reflect.ValueOf((*ThemeColor)(nil)),
|
||||
"ThemeColorConfig": reflect.ValueOf((*ThemeColorConfig)(nil)),
|
||||
|
||||
// Event structs
|
||||
"ToolCallEvent": reflect.ValueOf((*ToolCallEvent)(nil)),
|
||||
"ToolCallResult": reflect.ValueOf((*ToolCallResult)(nil)),
|
||||
|
||||
@@ -454,6 +454,37 @@ func RefreshThemeRegistry() {
|
||||
initThemeRegistry()
|
||||
}
|
||||
|
||||
// RegisterThemeFromConfig adds a theme to the runtime registry from an
|
||||
// extension's ThemeColorConfig (string hex pairs). Replaces any existing
|
||||
// entry with the same name. The theme is immediately available via
|
||||
// ListThemes, LoadThemeByName, and ApplyTheme.
|
||||
func RegisterThemeFromConfig(name string, primary, secondary, success, warning, error_, info, text, muted, veryMuted, background, border, mutedBorder, system, tool, accent, highlight, mdHeading, mdLink, mdKeyword, mdString, mdNumber, mdComment [2]string) {
|
||||
if themeRegistry == nil {
|
||||
initThemeRegistry()
|
||||
}
|
||||
t := makeTheme(presetColors{
|
||||
primary: primary, secondary: secondary,
|
||||
success: success, warning: warning,
|
||||
error_: error_, info: info,
|
||||
text: text, muted: muted,
|
||||
veryMuted: veryMuted, background: background,
|
||||
border: border, mutedBorder: mutedBorder,
|
||||
system: system, tool: tool,
|
||||
accent: accent, highlight: highlight,
|
||||
mdHeading: mdHeading, mdLink: mdLink,
|
||||
mdKeyword: mdKeyword, mdString: mdString,
|
||||
mdNumber: mdNumber, mdComment: mdComment,
|
||||
})
|
||||
removeFromRegistry(name)
|
||||
themeRegistry = append(themeRegistry, ThemeEntry{
|
||||
Name: name,
|
||||
Source: "extension",
|
||||
theme: t,
|
||||
loaded: true,
|
||||
})
|
||||
sortRegistry()
|
||||
}
|
||||
|
||||
// ActiveThemeName returns the name of the currently active theme by comparing
|
||||
// against known entries. Returns "custom" if no match is found.
|
||||
func ActiveThemeName() string {
|
||||
|
||||
Reference in New Issue
Block a user