| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- package util
- import (
- "math"
- "os"
- "strings"
- "time"
- "github.com/charmbracelet/lipgloss/v2"
- "github.com/charmbracelet/lipgloss/v2/compat"
- "github.com/sst/opencode/internal/styles"
- )
- var (
- shimmerStart = time.Now()
- trueColorSupport = hasTrueColor()
- )
- // Shimmer renders text with a moving foreground highlight.
- // bg is the background color, dim is the base text color, bright is the highlight color.
- func Shimmer(s string, bg compat.AdaptiveColor, _ compat.AdaptiveColor, _ compat.AdaptiveColor) string {
- if s == "" {
- return ""
- }
- runes := []rune(s)
- n := len(runes)
- if n == 0 {
- return s
- }
- pad := 10
- period := float64(n + pad*2)
- sweep := 2.5
- elapsed := time.Since(shimmerStart).Seconds()
- pos := (math.Mod(elapsed, sweep) / sweep) * period
- half := 2.0
- type seg struct {
- useHex bool
- hex string
- bold bool
- faint bool
- text string
- }
- segs := make([]seg, 0, n/4)
- useHex := trueColorSupport
- for i, r := range runes {
- ip := float64(i + pad)
- dist := math.Abs(ip - pos)
- bold := false
- faint := true
- hex := ""
- if dist <= half {
- // Simple 3-level brightness based on distance
- if dist <= half/3 {
- // Center: brightest
- bold = true
- faint = false
- if useHex {
- hex = "#ffffff"
- }
- } else {
- // Edge: medium bright
- bold = false
- faint = false
- if useHex {
- hex = "#cccccc"
- }
- }
- }
- if len(segs) == 0 ||
- segs[len(segs)-1].useHex != useHex ||
- segs[len(segs)-1].hex != hex ||
- segs[len(segs)-1].bold != bold ||
- segs[len(segs)-1].faint != faint {
- segs = append(segs, seg{useHex: useHex, hex: hex, bold: bold, faint: faint, text: string(r)})
- } else {
- segs[len(segs)-1].text += string(r)
- }
- }
- baseStyle := styles.NewStyle().Background(bg)
- var b strings.Builder
- b.Grow(len(s) * 2)
- for _, g := range segs {
- st := baseStyle
- if g.useHex && g.hex != "" {
- c := compat.AdaptiveColor{Dark: lipgloss.Color(g.hex), Light: lipgloss.Color(g.hex)}
- st = st.Foreground(c)
- }
- if g.bold {
- st = st.Bold(true)
- }
- if g.faint {
- st = st.Faint(true)
- }
- b.WriteString(st.Render(g.text))
- }
- return b.String()
- }
- func hasTrueColor() bool {
- c := strings.ToLower(os.Getenv("COLORTERM"))
- return strings.Contains(c, "truecolor") || strings.Contains(c, "24bit")
- }
- func rgbHex(r, g, b int) string {
- if r < 0 {
- r = 0
- }
- if r > 255 {
- r = 255
- }
- if g < 0 {
- g = 0
- }
- if g > 255 {
- g = 255
- }
- if b < 0 {
- b = 0
- }
- if b > 255 {
- b = 255
- }
- return "#" + hex2(r) + hex2(g) + hex2(b)
- }
- func hex2(v int) string {
- const digits = "0123456789abcdef"
- return string([]byte{digits[(v>>4)&0xF], digits[v&0xF]})
- }
|