diff options
| author | Dax Raad <[email protected]> | 2025-11-02 18:43:17 -0500 |
|---|---|---|
| committer | Dax Raad <[email protected]> | 2025-11-02 18:43:33 -0500 |
| commit | f68374ad2223ddc213bdea9519ca6a699819ee0e (patch) | |
| tree | 04f0fe21b8e12cd62d7274961bb0cff64f966f40 /packages/tui/internal/util | |
| parent | 5e86c9b7916f75c7ad227b80eab18c7c54fc8ffe (diff) | |
| download | opencode-f68374ad2223ddc213bdea9519ca6a699819ee0e.tar.gz opencode-f68374ad2223ddc213bdea9519ca6a699819ee0e.zip | |
DELETE GO BUBBLETEA CRAP HOORAY
Diffstat (limited to 'packages/tui/internal/util')
| -rw-r--r-- | packages/tui/internal/util/apilogger.go | 154 | ||||
| -rw-r--r-- | packages/tui/internal/util/color.go | 115 | ||||
| -rw-r--r-- | packages/tui/internal/util/concurrency.go | 40 | ||||
| -rw-r--r-- | packages/tui/internal/util/concurrency_test.go | 23 | ||||
| -rw-r--r-- | packages/tui/internal/util/file.go | 113 | ||||
| -rw-r--r-- | packages/tui/internal/util/ide.go | 31 | ||||
| -rw-r--r-- | packages/tui/internal/util/shimmer.go | 138 | ||||
| -rw-r--r-- | packages/tui/internal/util/util.go | 71 |
8 files changed, 0 insertions, 685 deletions
diff --git a/packages/tui/internal/util/apilogger.go b/packages/tui/internal/util/apilogger.go deleted file mode 100644 index 8e872e63a..000000000 --- a/packages/tui/internal/util/apilogger.go +++ /dev/null @@ -1,154 +0,0 @@ -package util - -import ( - "context" - "fmt" - "log/slog" - "reflect" - "sync" - - opencode "github.com/sst/opencode-sdk-go" -) - -func sanitizeValue(val any) any { - if val == nil { - return nil - } - - if err, ok := val.(error); ok { - return err.Error() - } - - v := reflect.ValueOf(val) - if v.Kind() == reflect.Interface && !v.IsNil() { - return fmt.Sprintf("%T", val) - } - - return val -} - -type APILogHandler struct { - client *opencode.Client - service string - level slog.Level - attrs []slog.Attr - groups []string - mu sync.Mutex - queue chan opencode.AppLogParams -} - -func NewAPILogHandler(ctx context.Context, client *opencode.Client, service string, level slog.Level) *APILogHandler { - result := &APILogHandler{ - client: client, - service: service, - level: level, - attrs: make([]slog.Attr, 0), - groups: make([]string, 0), - queue: make(chan opencode.AppLogParams, 100_000), - } - go func() { - for { - select { - case <-ctx.Done(): - return - case params := <-result.queue: - _, err := client.App.Log(context.Background(), params) - if err != nil { - slog.Error("Failed to log to API", "error", err) - } - } - } - }() - return result -} - -func (h *APILogHandler) Enabled(_ context.Context, level slog.Level) bool { - return level >= h.level -} - -func (h *APILogHandler) Handle(ctx context.Context, r slog.Record) error { - var apiLevel opencode.AppLogParamsLevel - switch r.Level { - case slog.LevelDebug: - apiLevel = opencode.AppLogParamsLevelDebug - case slog.LevelInfo: - apiLevel = opencode.AppLogParamsLevelInfo - case slog.LevelWarn: - apiLevel = opencode.AppLogParamsLevelWarn - case slog.LevelError: - apiLevel = opencode.AppLogParamsLevelError - default: - apiLevel = opencode.AppLogParamsLevelInfo - } - - extra := make(map[string]any) - - h.mu.Lock() - for _, attr := range h.attrs { - val := attr.Value.Any() - extra[attr.Key] = sanitizeValue(val) - } - h.mu.Unlock() - - r.Attrs(func(attr slog.Attr) bool { - val := attr.Value.Any() - extra[attr.Key] = sanitizeValue(val) - return true - }) - - params := opencode.AppLogParams{ - Service: opencode.F(h.service), - Level: opencode.F(apiLevel), - Message: opencode.F(r.Message), - } - - if len(extra) > 0 { - params.Extra = opencode.F(extra) - } - - h.queue <- params - - return nil -} - -// WithAttrs returns a new Handler whose attributes consist of -// both the receiver's attributes and the arguments. -func (h *APILogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { - h.mu.Lock() - defer h.mu.Unlock() - - newHandler := &APILogHandler{ - client: h.client, - service: h.service, - level: h.level, - attrs: make([]slog.Attr, len(h.attrs)+len(attrs)), - groups: make([]string, len(h.groups)), - } - - copy(newHandler.attrs, h.attrs) - copy(newHandler.attrs[len(h.attrs):], attrs) - copy(newHandler.groups, h.groups) - - return newHandler -} - -// WithGroup returns a new Handler with the given group appended to -// the receiver's existing groups. -func (h *APILogHandler) WithGroup(name string) slog.Handler { - h.mu.Lock() - defer h.mu.Unlock() - - newHandler := &APILogHandler{ - client: h.client, - service: h.service, - level: h.level, - attrs: make([]slog.Attr, len(h.attrs)), - groups: make([]string, len(h.groups)+1), - } - - copy(newHandler.attrs, h.attrs) - copy(newHandler.groups, h.groups) - newHandler.groups[len(h.groups)] = name - - return newHandler -} diff --git a/packages/tui/internal/util/color.go b/packages/tui/internal/util/color.go deleted file mode 100644 index b387ca655..000000000 --- a/packages/tui/internal/util/color.go +++ /dev/null @@ -1,115 +0,0 @@ -package util - -import ( - "regexp" - "strings" - - "github.com/charmbracelet/lipgloss/v2/compat" - "github.com/sst/opencode/internal/theme" -) - -var csiRE *regexp.Regexp - -func init() { - csiRE = regexp.MustCompile(`\x1b\[([0-9;]+)m`) -} - -var targetFGMap = map[string]string{ - "0;0;0": "\x1b[30m", // Black - "128;0;0": "\x1b[31m", // Red - "0;128;0": "\x1b[32m", // Green - "128;128;0": "\x1b[33m", // Yellow - "0;0;128": "\x1b[34m", // Blue - "128;0;128": "\x1b[35m", // Magenta - "0;128;128": "\x1b[36m", // Cyan - "192;192;192": "\x1b[37m", // White (light grey) - "128;128;128": "\x1b[90m", // Bright Black (dark grey) - "255;0;0": "\x1b[91m", // Bright Red - "0;255;0": "\x1b[92m", // Bright Green - "255;255;0": "\x1b[93m", // Bright Yellow - "0;0;255": "\x1b[94m", // Bright Blue - "255;0;255": "\x1b[95m", // Bright Magenta - "0;255;255": "\x1b[96m", // Bright Cyan - "255;255;255": "\x1b[97m", // Bright White -} - -var targetBGMap = map[string]string{ - "0;0;0": "\x1b[40m", - "128;0;0": "\x1b[41m", - "0;128;0": "\x1b[42m", - "128;128;0": "\x1b[43m", - "0;0;128": "\x1b[44m", - "128;0;128": "\x1b[45m", - "0;128;128": "\x1b[46m", - "192;192;192": "\x1b[47m", - "128;128;128": "\x1b[100m", - "255;0;0": "\x1b[101m", - "0;255;0": "\x1b[102m", - "255;255;0": "\x1b[103m", - "0;0;255": "\x1b[104m", - "255;0;255": "\x1b[105m", - "0;255;255": "\x1b[106m", - "255;255;255": "\x1b[107m", -} - -func ConvertRGBToAnsi16Colors(s string) string { - return csiRE.ReplaceAllStringFunc(s, func(seq string) string { - params := strings.Split(csiRE.FindStringSubmatch(seq)[1], ";") - out := make([]string, 0, len(params)) - - for i := 0; i < len(params); { - // Detect “38 | 48 ; 2 ; r ; g ; b ( ; alpha? )” - if (params[i] == "38" || params[i] == "48") && - i+4 < len(params) && - params[i+1] == "2" { - - key := strings.Join(params[i+2:i+5], ";") - var repl string - if params[i] == "38" { - repl = targetFGMap[key] - } else { - repl = targetBGMap[key] - } - - if repl != "" { // exact RGB hit - out = append(out, repl[2:len(repl)-1]) - i += 5 // skip 38/48;2;r;g;b - - // if i == len(params)-1 && looksLikeByte(params[i]) { - // i++ // swallow the alpha byte - // } - continue - } - } - // Normal token — keep verbatim. - out = append(out, params[i]) - i++ - } - - return "\x1b[" + strings.Join(out, ";") + "m" - }) -} - -// func looksLikeByte(tok string) bool { -// v, err := strconv.Atoi(tok) -// return err == nil && v >= 0 && v <= 255 -// } - -// GetAgentColor returns the color for a given agent index, matching the status bar colors -func GetAgentColor(agentIndex int) compat.AdaptiveColor { - t := theme.CurrentTheme() - agentColors := []compat.AdaptiveColor{ - t.TextMuted(), - t.Secondary(), - t.Accent(), - t.Success(), - t.Warning(), - t.Primary(), - t.Error(), - } - - if agentIndex >= 0 && agentIndex < len(agentColors) { - return agentColors[agentIndex] - } - return t.Secondary() // default fallback -} diff --git a/packages/tui/internal/util/concurrency.go b/packages/tui/internal/util/concurrency.go deleted file mode 100644 index d24c7f974..000000000 --- a/packages/tui/internal/util/concurrency.go +++ /dev/null @@ -1,40 +0,0 @@ -package util - -import ( - "strings" -) - -func mapParallel[in, out any](items []in, fn func(in) out) chan out { - mapChans := make([]chan out, 0, len(items)) - - for _, v := range items { - ch := make(chan out) - mapChans = append(mapChans, ch) - go func() { - defer close(ch) - ch <- fn(v) - }() - } - - resultChan := make(chan out) - - go func() { - defer close(resultChan) - for _, ch := range mapChans { - v := <-ch - resultChan <- v - } - }() - - return resultChan -} - -// WriteStringsPar allows to iterate over a list and compute strings in parallel, -// yet write them in order. -func WriteStringsPar[a any](sb *strings.Builder, items []a, fn func(a) string) { - ch := mapParallel(items, fn) - - for v := range ch { - sb.WriteString(v) - } -} diff --git a/packages/tui/internal/util/concurrency_test.go b/packages/tui/internal/util/concurrency_test.go deleted file mode 100644 index 6512882f5..000000000 --- a/packages/tui/internal/util/concurrency_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package util_test - -import ( - "strconv" - "strings" - "testing" - "time" - - "github.com/sst/opencode/internal/util" -) - -func TestWriteStringsPar(t *testing.T) { - items := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} - sb := strings.Builder{} - util.WriteStringsPar(&sb, items, func(i int) string { - // sleep for the inverse duration so that later items finish first - time.Sleep(time.Duration(10-i) * time.Millisecond) - return strconv.Itoa(i) - }) - if sb.String() != "0123456789" { - t.Fatalf("expected 0123456789, got %s", sb.String()) - } -} diff --git a/packages/tui/internal/util/file.go b/packages/tui/internal/util/file.go deleted file mode 100644 index 050b96343..000000000 --- a/packages/tui/internal/util/file.go +++ /dev/null @@ -1,113 +0,0 @@ -package util - -import ( - "fmt" - "path/filepath" - "regexp" - "strings" - "unicode" - - "github.com/charmbracelet/lipgloss/v2/compat" - "github.com/charmbracelet/x/ansi" - "github.com/sst/opencode/internal/styles" - "github.com/sst/opencode/internal/theme" -) - -var RootPath string -var CwdPath string - -type fileRenderer struct { - filename string - content string - height int -} - -type fileRenderingOption func(*fileRenderer) - -func WithTruncate(height int) fileRenderingOption { - return func(c *fileRenderer) { - c.height = height - } -} - -func RenderFile( - filename string, - content string, - width int, - options ...fileRenderingOption) string { - t := theme.CurrentTheme() - renderer := &fileRenderer{ - filename: filename, - content: content, - } - for _, option := range options { - option(renderer) - } - - lines := []string{} - for line := range strings.SplitSeq(content, "\n") { - line = strings.TrimRightFunc(line, unicode.IsSpace) - line = strings.ReplaceAll(line, "\t", " ") - lines = append(lines, line) - } - content = strings.Join(lines, "\n") - - if renderer.height > 0 { - content = TruncateHeight(content, renderer.height) - } - content = fmt.Sprintf("```%s\n%s\n```", Extension(renderer.filename), content) - content = ToMarkdown(content, width, t.BackgroundPanel()) - return content -} - -func TruncateHeight(content string, height int) string { - lines := strings.Split(content, "\n") - if len(lines) > height { - return strings.Join(lines[:height], "\n") - } - return content -} - -func Relative(path string) string { - path = strings.TrimPrefix(path, CwdPath+"/") - return strings.TrimPrefix(path, RootPath+"/") -} - -func Extension(path string) string { - ext := filepath.Ext(path) - if ext == "" { - ext = "" - } else { - ext = strings.ToLower(ext[1:]) - } - return ext -} - -func ToMarkdown(content string, width int, backgroundColor compat.AdaptiveColor) string { - r := styles.GetMarkdownRenderer(width-6, backgroundColor) - content = strings.ReplaceAll(content, RootPath+"/", "") - hyphenRegex := regexp.MustCompile(`-([^ \-|]|$)`) - content = hyphenRegex.ReplaceAllString(content, "\u2011$1") - rendered, _ := r.Render(content) - lines := strings.Split(rendered, "\n") - - if len(lines) > 0 { - firstLine := lines[0] - cleaned := ansi.Strip(firstLine) - nospace := strings.ReplaceAll(cleaned, " ", "") - if nospace == "" { - lines = lines[1:] - } - if len(lines) > 0 { - lastLine := lines[len(lines)-1] - cleaned = ansi.Strip(lastLine) - nospace = strings.ReplaceAll(cleaned, " ", "") - if nospace == "" { - lines = lines[:len(lines)-1] - } - } - } - content = strings.Join(lines, "\n") - content = strings.ReplaceAll(content, "\u2011", "-") - return strings.TrimSuffix(content, "\n") -} diff --git a/packages/tui/internal/util/ide.go b/packages/tui/internal/util/ide.go deleted file mode 100644 index 7b3832f9b..000000000 --- a/packages/tui/internal/util/ide.go +++ /dev/null @@ -1,31 +0,0 @@ -package util - -import ( - "os" - "strings" -) - -var SUPPORTED_IDES = []struct { - Search string - ShortName string -}{ - {"Windsurf", "Windsurf"}, - {"Visual Studio Code", "vscode"}, - {"Cursor", "Cursor"}, - {"VSCodium", "VSCodium"}, -} - -func IsVSCode() bool { - return os.Getenv("OPENCODE_CALLER") == "vscode" -} - -func Ide() string { - for _, ide := range SUPPORTED_IDES { - if strings.Contains(os.Getenv("GIT_ASKPASS"), ide.Search) { - return ide.ShortName - } - } - - return "unknown" -} - diff --git a/packages/tui/internal/util/shimmer.go b/packages/tui/internal/util/shimmer.go deleted file mode 100644 index b6ba0db64..000000000 --- a/packages/tui/internal/util/shimmer.go +++ /dev/null @@ -1,138 +0,0 @@ -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]}) -} diff --git a/packages/tui/internal/util/util.go b/packages/tui/internal/util/util.go deleted file mode 100644 index b49d2e292..000000000 --- a/packages/tui/internal/util/util.go +++ /dev/null @@ -1,71 +0,0 @@ -package util - -import ( - "log/slog" - "os" - "os/exec" - "runtime" - "strings" - "time" - - tea "github.com/charmbracelet/bubbletea/v2" -) - -func CmdHandler(msg tea.Msg) tea.Cmd { - return func() tea.Msg { - return msg - } -} - -func Clamp(v, low, high int) int { - // Swap if needed to ensure low <= high - if high < low { - low, high = high, low - } - return min(high, max(low, v)) -} - -func IsWsl() bool { - // Check for WSL environment variables - if os.Getenv("WSL_DISTRO_NAME") != "" { - return true - } - - // Check /proc/version for WSL signature - if data, err := os.ReadFile("/proc/version"); err == nil { - version := strings.ToLower(string(data)) - return strings.Contains(version, "microsoft") || strings.Contains(version, "wsl") - } - - return false -} - -func Measure(tag string) func(...any) { - startTime := time.Now() - return func(args ...any) { - args = append(args, []any{"timeTakenMs", time.Since(startTime).Milliseconds()}...) - slog.Debug(tag, args...) - } -} - -func GetEditor() string { - if editor := os.Getenv("VISUAL"); editor != "" { - return editor - } - if editor := os.Getenv("EDITOR"); editor != "" { - return editor - } - - commonEditors := []string{"vim", "nvim", "zed", "code", "cursor", "vi", "nano"} - if runtime.GOOS == "windows" { - commonEditors = []string{"vim", "nvim", "zed", "code.cmd", "cursor.cmd", "notepad.exe", "vi", "nano"} - } - - for _, editor := range commonEditors { - if _, err := exec.LookPath(editor); err == nil { - return editor - } - } - - return "" -} |
