mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
78570d4188
Removes ~600 lines of unreferenced code surfaced by deadcode + manual
audit (none of it reachable from production code paths or test setup):
- internal/models/pool.go: ProviderPool was never wired into kitsetup
or the agent; the global pool singleton had zero callers.
- internal/ui/debug_logger.go: CLIDebugLogger was unreachable; debug
routing goes through internal/tools/buffered_logger.go instead.
- internal/ui/tool_approval_input.go: tea.Model never instantiated;
approvals are handled inline in model.go.
- internal/ui/cli.go: DisplayAssistantMessage / DisplayCancellation /
GetDebugLogger had zero callers (the *WithModel variant is what
event_handler.go uses).
- internal/ui/style/enhanced.go: Style{Card,Header,Subheader,Muted,
Success,Error,Warning,Info} + Create{Separator,ProgressBar} — none
used. CreateBadge stays (used by model.go).
- internal/ui/style/themes.go: RefreshThemeRegistry — never called.
- internal/ui/block_renderer.go: With{FullWidth,MarginTop,Padding{Left,
Right},Background,Foreground,Width} — option helpers nobody calls.
- internal/ui/render/blocks.go: UserBlock, ToolBlock — replaced by
inline rendering elsewhere; the test for UserBlock was rewritten to
directly exercise HighlightFileTokens (which is what the test really
cared about).
- internal/ui/commands/commands.go: GetAllCommandNames — no callers.
- internal/ui/message_items.go: NewTextMessageItem,
NewSystemMessageItem + the entire SystemMessageItem type — model.go
uses NewStyledMessageItem instead.
- internal/prompts/loader.go: Deduplicate — the loader does dedup
internally; standalone helper was unused.
- internal/models/cache_options.go: mergeProviderOptions + its
test-only consumer.
- internal/extensions/installer.go: Installer.GetInstalledPackages —
intended for a 'kit ext list' command that was never built.
- internal/extensions/manifest.go: saveManifestToScope,
saveManifestToPath, GetGlobalManifest, GetProjectManifest,
addEntryToManifest, removeEntryFromManifest — package-level
duplicates of *Installer methods. Tests rewritten to exercise the
live Installer methods instead, which fixes a latent path-resolution
inconsistency between manifestPathForScope and Installer.manifestPath
(the former hard-coded paths, the latter respects projectGitRoot).
- internal/extensions/subagent.go: SpawnSubagent + helpers
(generateSubagentID, findKitBinary, subagentJSONOutput). The
subprocess-spawn implementation is unreachable; production code
routes through kit.Kit.Subagent (in-process). Types
(SubagentConfig/Result/Handle/etc.) and the SubagentHandle methods
remain because they are exposed to extensions via Yaegi symbols and
the Context.SpawnSubagent field.
- cmd/root.go: LoadConfigWithEnvSubstitution — one-line wrapper around
kit.LoadConfigWithEnvSubstitution with zero callers.
go test -race ./... passes.
291 lines
11 KiB
Go
291 lines
11 KiB
Go
package style
|
|
|
|
import (
|
|
"fmt"
|
|
"image/color"
|
|
"os"
|
|
"strings"
|
|
|
|
"charm.land/lipgloss/v2"
|
|
)
|
|
|
|
// Enhanced styling utilities and theme definitions
|
|
|
|
// isDarkBg caches the terminal background detection result at package init.
|
|
var isDarkBg = lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
|
|
|
|
// AdaptiveColor picks between a light-mode and dark-mode hex color string
|
|
// based on the detected terminal background. This replaces the old
|
|
// lipgloss.AdaptiveColor{Light: ..., Dark: ...} pattern from v1.
|
|
func AdaptiveColor(light, dark string) color.Color {
|
|
if isDarkBg {
|
|
return lipgloss.Color(dark)
|
|
}
|
|
return lipgloss.Color(light)
|
|
}
|
|
|
|
// Global theme instance
|
|
var currentTheme = DefaultTheme()
|
|
|
|
// GetTheme returns the currently active UI theme. The theme controls all color
|
|
// and styling decisions throughout the application's interface.
|
|
func GetTheme() Theme {
|
|
return currentTheme
|
|
}
|
|
|
|
// SetTheme updates the global UI theme, affecting all subsequent rendering
|
|
// operations. This allows runtime theme switching for different visual preferences.
|
|
// It also invalidates the markdownTypographyCache so the next call to
|
|
// GetMarkdownTypography picks up the new theme.
|
|
func SetTheme(theme Theme) {
|
|
currentTheme = theme
|
|
markdownTypographyCache = nil // invalidate cached renderer; colors may have changed
|
|
styleCache = nil // invalidate cached styles; colors may have changed
|
|
}
|
|
|
|
// CachedStyles holds pre-built lipgloss styles that are reused across
|
|
// render frames. Invalidated by SetTheme, lazily rebuilt on next access.
|
|
// Only accessed from BubbleTea's single-threaded Update/View cycle.
|
|
type CachedStyles struct {
|
|
// render/blocks.go
|
|
FileTokenAccent lipgloss.Style // Foreground(Accent).Bold(true)
|
|
Muted lipgloss.Style // Foreground(Muted)
|
|
VeryMuted lipgloss.Style // Foreground(VeryMuted)
|
|
Accent lipgloss.Style // Foreground(Accent)
|
|
MarginBottom1 lipgloss.Style // MarginBottom(1)
|
|
|
|
// stream.go - spinner phases
|
|
SpinnerBright lipgloss.Style // Foreground(Primary)
|
|
SpinnerMed lipgloss.Style // Foreground(Muted)
|
|
SpinnerDim lipgloss.Style // Foreground(VeryMuted)
|
|
SpinnerOff lipgloss.Style // Foreground(MutedBorder)
|
|
|
|
// message_items.go - bash output
|
|
BashHeader lipgloss.Style // Foreground(Muted).Italic(true)
|
|
BashStderr lipgloss.Style // Foreground(Error)
|
|
|
|
// render/blocks.go - tool block
|
|
ToolSuccess lipgloss.Style // Foreground(Success)
|
|
ToolError lipgloss.Style // Foreground(Error)
|
|
ToolInfo lipgloss.Style // Foreground(Info).Bold(true)
|
|
ToolMuted lipgloss.Style // Foreground(Muted)
|
|
|
|
// common
|
|
ErrorFg lipgloss.Style // Foreground(Error)
|
|
TextBold lipgloss.Style // Foreground(Text).Bold(true)
|
|
}
|
|
|
|
var styleCache *CachedStyles
|
|
|
|
// GetCachedStyles returns the pre-built style cache, creating it lazily
|
|
// from the current theme. Invalidated by SetTheme.
|
|
func GetCachedStyles() *CachedStyles {
|
|
if styleCache != nil {
|
|
return styleCache
|
|
}
|
|
theme := GetTheme()
|
|
styleCache = &CachedStyles{
|
|
FileTokenAccent: lipgloss.NewStyle().Foreground(theme.Accent).Bold(true),
|
|
Muted: lipgloss.NewStyle().Foreground(theme.Muted),
|
|
VeryMuted: lipgloss.NewStyle().Foreground(theme.VeryMuted),
|
|
Accent: lipgloss.NewStyle().Foreground(theme.Accent),
|
|
MarginBottom1: lipgloss.NewStyle().MarginBottom(1),
|
|
SpinnerBright: lipgloss.NewStyle().Foreground(theme.Primary),
|
|
SpinnerMed: lipgloss.NewStyle().Foreground(theme.Muted),
|
|
SpinnerDim: lipgloss.NewStyle().Foreground(theme.VeryMuted),
|
|
SpinnerOff: lipgloss.NewStyle().Foreground(theme.MutedBorder),
|
|
BashHeader: lipgloss.NewStyle().Foreground(theme.Muted).Italic(true),
|
|
BashStderr: lipgloss.NewStyle().Foreground(theme.Error),
|
|
ToolSuccess: lipgloss.NewStyle().Foreground(theme.Success),
|
|
ToolError: lipgloss.NewStyle().Foreground(theme.Error),
|
|
ToolInfo: lipgloss.NewStyle().Foreground(theme.Info).Bold(true),
|
|
ToolMuted: lipgloss.NewStyle().Foreground(theme.Muted),
|
|
ErrorFg: lipgloss.NewStyle().Foreground(theme.Error),
|
|
TextBold: lipgloss.NewStyle().Foreground(theme.Text).Bold(true),
|
|
}
|
|
return styleCache
|
|
}
|
|
|
|
// MarkdownThemeColors defines colors for markdown rendering and syntax highlighting.
|
|
type MarkdownThemeColors struct {
|
|
Text color.Color
|
|
Muted color.Color
|
|
Heading color.Color
|
|
Emph color.Color
|
|
Strong color.Color
|
|
Link color.Color
|
|
Code color.Color
|
|
Error color.Color
|
|
Keyword color.Color
|
|
String color.Color
|
|
Number color.Color
|
|
Comment color.Color
|
|
}
|
|
|
|
// Theme defines a comprehensive color scheme for the application's UI, supporting
|
|
// both light and dark terminal modes through adaptive colors. Inspired by the
|
|
// Knight Rider KITT aesthetic — scanner reds, amber dashboard glows, and dark
|
|
// cockpit tones.
|
|
type Theme struct {
|
|
Primary color.Color
|
|
Secondary color.Color
|
|
Success color.Color
|
|
Warning color.Color
|
|
Error color.Color
|
|
Info color.Color
|
|
Text color.Color
|
|
Muted color.Color
|
|
VeryMuted color.Color
|
|
Background color.Color
|
|
Border color.Color
|
|
MutedBorder color.Color
|
|
System color.Color
|
|
Tool color.Color
|
|
Accent color.Color
|
|
Highlight color.Color
|
|
|
|
// Diff block backgrounds
|
|
DiffInsertBg color.Color // Green-tinted bg for added lines
|
|
DiffDeleteBg color.Color // Red-tinted bg for removed lines
|
|
DiffEqualBg color.Color // Neutral bg for context lines
|
|
DiffMissingBg color.Color // Empty-cell bg when sides are uneven
|
|
|
|
// Code/output block backgrounds
|
|
CodeBg color.Color // Background for code blocks (Read tool)
|
|
GutterBg color.Color // Line-number gutter background
|
|
WriteBg color.Color // Green-tinted bg for Write tool content
|
|
|
|
// Markdown rendering and syntax highlighting colors
|
|
Markdown MarkdownThemeColors
|
|
}
|
|
|
|
// DefaultTheme creates and returns the default KIT theme inspired by the
|
|
// Knight Rider KITT aesthetic — scanner reds, amber dashboard glows, and a
|
|
// dark cockpit. No blues or bright greens; everything stays in the warm
|
|
// red/amber/gray family of KITT's instrument panel.
|
|
func DefaultTheme() Theme {
|
|
return Theme{
|
|
Primary: AdaptiveColor("#CC1100", "#FF2200"), // KITT scanner red
|
|
Secondary: AdaptiveColor("#CC6600", "#FF8800"), // Amber dashboard glow
|
|
Success: AdaptiveColor("#998800", "#CCAA00"), // Warm gold — system OK
|
|
Warning: AdaptiveColor("#CC8800", "#FFB800"), // Amber caution light
|
|
Error: AdaptiveColor("#CC0000", "#FF3333"), // Alert red
|
|
Info: AdaptiveColor("#BB6600", "#DD8833"), // Warm amber readout
|
|
Text: AdaptiveColor("#1A1A1A", "#E0E0E0"), // Console text
|
|
Muted: AdaptiveColor("#707070", "#808080"), // Dimmed readout
|
|
VeryMuted: AdaptiveColor("#A0A0A0", "#505050"), // Inactive element
|
|
Background: AdaptiveColor("#F0F0F0", "#0D0D0D"), // Cockpit interior
|
|
Border: AdaptiveColor("#B0B0B0", "#3A3A3A"), // Panel edge
|
|
MutedBorder: AdaptiveColor("#D0D0D0", "#222222"), // Subtle divider
|
|
System: AdaptiveColor("#CC6600", "#FF8800"), // Amber system status
|
|
Tool: AdaptiveColor("#CC6600", "#FF8800"), // Amber instrument
|
|
Accent: AdaptiveColor("#DD2222", "#FF4444"), // Secondary scanner glow
|
|
Highlight: AdaptiveColor("#FFF0F0", "#1A1010"), // Red-tinted mantle
|
|
|
|
// Diff backgrounds
|
|
DiffInsertBg: AdaptiveColor("#F0E8D0", "#2A2410"), // Warm amber tint (added)
|
|
DiffDeleteBg: AdaptiveColor("#F5D5D5", "#2E1A1A"), // Red tint (removed)
|
|
DiffEqualBg: AdaptiveColor("#E8E8E8", "#161616"), // Neutral
|
|
DiffMissingBg: AdaptiveColor("#E0E0E0", "#111111"), // Darker neutral
|
|
|
|
// Code & output backgrounds
|
|
CodeBg: AdaptiveColor("#E8E8E8", "#161616"), // Matches DiffEqualBg
|
|
GutterBg: AdaptiveColor("#E0E0E0", "#111111"), // Slightly darker
|
|
WriteBg: AdaptiveColor("#F0E8D0", "#2A2410"), // Warm amber tint
|
|
|
|
// Markdown & syntax highlighting — all warm tones
|
|
Markdown: MarkdownThemeColors{
|
|
Text: AdaptiveColor("#1A1A1A", "#E0E0E0"), // Console text
|
|
Muted: AdaptiveColor("#707070", "#808080"), // Dimmed readout
|
|
Heading: AdaptiveColor("#CC1100", "#FF4444"), // Scanner red accent
|
|
Emph: AdaptiveColor("#CC8800", "#FFB800"), // Amber emphasis
|
|
Strong: AdaptiveColor("#1A1A1A", "#E0E0E0"), // Bright text
|
|
Link: AdaptiveColor("#CC4400", "#FF7744"), // Warm orange link
|
|
Code: AdaptiveColor("#333333", "#CCCCCC"), // Inline code
|
|
Error: AdaptiveColor("#CC0000", "#FF3333"), // Alert red
|
|
Keyword: AdaptiveColor("#CC3300", "#FF6644"), // Orange-red keyword
|
|
String: AdaptiveColor("#BB7700", "#DDAA33"), // Amber string
|
|
Number: AdaptiveColor("#CC8800", "#FFB800"), // Amber number
|
|
Comment: AdaptiveColor("#909090", "#606060"), // Dark gray comment
|
|
},
|
|
}
|
|
}
|
|
|
|
// IsDarkBackground returns the cached terminal background detection result.
|
|
func IsDarkBackground() bool {
|
|
return isDarkBg
|
|
}
|
|
|
|
// CreateBadge generates a styled badge or label with inverted colors (text on
|
|
// colored background) for highlighting important tags, statuses, or categories.
|
|
func CreateBadge(text string, c color.Color) string {
|
|
return lipgloss.NewStyle().
|
|
Foreground(AdaptiveColor("#FFFFFF", "#000000")).
|
|
Background(c).
|
|
Padding(0, 1).
|
|
Bold(true).
|
|
Render(text)
|
|
}
|
|
|
|
// interpolateColor blends between two colors based on position (0.0 to 1.0)
|
|
// using linear RGB channel interpolation.
|
|
func interpolateColor(a, b color.Color, pos float64) color.Color {
|
|
r1, g1, b1, _ := a.RGBA()
|
|
r2, g2, b2, _ := b.RGBA()
|
|
|
|
r := uint8(float64(r1>>8)*(1-pos) + float64(r2>>8)*pos)
|
|
g := uint8(float64(g1>>8)*(1-pos) + float64(g2>>8)*pos)
|
|
bl := uint8(float64(b1>>8)*(1-pos) + float64(b2>>8)*pos)
|
|
|
|
return lipgloss.Color(fmt.Sprintf("#%02x%02x%02x", r, g, bl))
|
|
}
|
|
|
|
// ApplyGradient applies a color gradient from colorA to colorB across the text.
|
|
// Uses ~8 color stops for performance rather than per-character coloring.
|
|
func ApplyGradient(text string, colorA, colorB color.Color) string {
|
|
runes := []rune(text)
|
|
if len(runes) == 0 {
|
|
return text
|
|
}
|
|
|
|
const maxStops = 8
|
|
segmentSize := max(len(runes)/maxStops, 1)
|
|
|
|
var result strings.Builder
|
|
for i := 0; i < len(runes); i += segmentSize {
|
|
end := min(i+segmentSize, len(runes))
|
|
|
|
pos := float64(i) / float64(len(runes))
|
|
c := interpolateColor(colorA, colorB, pos)
|
|
style := lipgloss.NewStyle().Foreground(c)
|
|
result.WriteString(style.Render(string(runes[i:end])))
|
|
}
|
|
|
|
return result.String()
|
|
}
|
|
|
|
// KitBanner returns the KIT ASCII art title with KITT scanner lights,
|
|
// rendered with a KITT red gradient.
|
|
func KitBanner() string {
|
|
kittDark := lipgloss.Color("#8B0000")
|
|
kittBright := lipgloss.Color("#FF2200")
|
|
lines := []string{
|
|
" ██╗ ██╗ ██╗ ████████╗",
|
|
" ██║ ██╔╝ ██║ ╚══██╔══╝",
|
|
" █████╔╝ ██║ ██║",
|
|
" ██╔═██╗ ██║ ██║",
|
|
" ██║ ██╗ ██║ ██║",
|
|
" ╚═╝ ╚═╝ ╚═╝ ╚═╝",
|
|
"░░ ░░ ░░ ▒▒ ▒▒ ▓▓ ▓▓ ████ ▓▓ ▓▓ ▒▒ ▒▒ ░░ ░░ ░░",
|
|
}
|
|
|
|
var result strings.Builder
|
|
for i, line := range lines {
|
|
if i > 0 {
|
|
result.WriteString("\n")
|
|
}
|
|
result.WriteString(ApplyGradient(line, kittDark, kittBright))
|
|
}
|
|
return result.String()
|
|
}
|