summaryrefslogtreecommitdiffhomepage
path: root/packages/tui/internal/util
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-11-02 18:43:17 -0500
committerDax Raad <[email protected]>2025-11-02 18:43:33 -0500
commitf68374ad2223ddc213bdea9519ca6a699819ee0e (patch)
tree04f0fe21b8e12cd62d7274961bb0cff64f966f40 /packages/tui/internal/util
parent5e86c9b7916f75c7ad227b80eab18c7c54fc8ffe (diff)
downloadopencode-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.go154
-rw-r--r--packages/tui/internal/util/color.go115
-rw-r--r--packages/tui/internal/util/concurrency.go40
-rw-r--r--packages/tui/internal/util/concurrency_test.go23
-rw-r--r--packages/tui/internal/util/file.go113
-rw-r--r--packages/tui/internal/util/ide.go31
-rw-r--r--packages/tui/internal/util/shimmer.go138
-rw-r--r--packages/tui/internal/util/util.go71
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 ""
-}