Replace shell-based statusline with native Go for ~4x speedup. Parses JSON natively, detects git status, checks gitea process, and formats colored output with proper terminal width handling.
203 lines
4.6 KiB
Go
203 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
const statuslineWidthOffset = 7
|
|
|
|
// Input JSON structure from Claude Code
|
|
type StatusInput struct {
|
|
Model struct {
|
|
DisplayName string `json:"display_name"`
|
|
} `json:"model"`
|
|
Workspace struct {
|
|
CurrentDir string `json:"current_dir"`
|
|
} `json:"workspace"`
|
|
ContextWindow struct {
|
|
ContextWindowSize int `json:"context_window_size"`
|
|
CurrentUsage *struct {
|
|
InputTokens int `json:"input_tokens"`
|
|
CacheCreationTokens int `json:"cache_creation_input_tokens"`
|
|
CacheReadInputTokens int `json:"cache_read_input_tokens"`
|
|
} `json:"current_usage"`
|
|
} `json:"context_window"`
|
|
}
|
|
|
|
// ANSI color codes
|
|
const (
|
|
reset = "\033[0m"
|
|
bold = "\033[1m"
|
|
red = "\033[31m"
|
|
green = "\033[32m"
|
|
yellow = "\033[33m"
|
|
magenta = "\033[35m"
|
|
cyan = "\033[36m"
|
|
boldGreen = "\033[1;32m"
|
|
)
|
|
|
|
func main() {
|
|
// Read JSON from stdin
|
|
reader := bufio.NewReader(os.Stdin)
|
|
var input strings.Builder
|
|
for {
|
|
line, err := reader.ReadString('\n')
|
|
input.WriteString(line)
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
var data StatusInput
|
|
if err := json.Unmarshal([]byte(input.String()), &data); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error parsing JSON: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Calculate context info
|
|
contextInfo := formatContextInfo(data.ContextWindow.ContextWindowSize, data.ContextWindow.CurrentUsage)
|
|
|
|
// Get directory name
|
|
dirName := filepath.Base(data.Workspace.CurrentDir)
|
|
|
|
// Check gitea status
|
|
giteaStatus := getGiteaStatus()
|
|
|
|
// Get git info
|
|
gitInfo := getGitInfo(data.Workspace.CurrentDir)
|
|
|
|
// Build left part
|
|
left := fmt.Sprintf("%s %s%s%s %s➜%s %s%s%s%s",
|
|
giteaStatus,
|
|
magenta, data.Model.DisplayName, reset,
|
|
boldGreen, reset,
|
|
cyan, dirName, reset,
|
|
gitInfo)
|
|
|
|
// Build right part
|
|
right := fmt.Sprintf("%s%s%s", yellow, contextInfo, reset)
|
|
|
|
// Calculate visible lengths (strip ANSI)
|
|
leftVisible := stripANSI(left)
|
|
rightVisible := stripANSI(right)
|
|
|
|
// Get terminal width
|
|
termWidth := getTerminalWidth() - statuslineWidthOffset
|
|
|
|
// Calculate padding
|
|
padding := termWidth - len(leftVisible) - len(rightVisible)
|
|
if padding < 1 {
|
|
padding = 1
|
|
}
|
|
|
|
// Output with padding
|
|
fmt.Printf("%s%s%s", left, strings.Repeat(" ", padding), right)
|
|
}
|
|
|
|
func formatContextInfo(contextSize int, usage *struct {
|
|
InputTokens int `json:"input_tokens"`
|
|
CacheCreationTokens int `json:"cache_creation_input_tokens"`
|
|
CacheReadInputTokens int `json:"cache_read_input_tokens"`
|
|
}) string {
|
|
totalK := contextSize / 1000
|
|
|
|
if usage == nil {
|
|
return fmt.Sprintf("0/%dk", totalK)
|
|
}
|
|
|
|
currentTokens := usage.InputTokens + usage.CacheCreationTokens + usage.CacheReadInputTokens
|
|
currentK := currentTokens / 1000
|
|
return fmt.Sprintf("%dk/%dk", currentK, totalK)
|
|
}
|
|
|
|
func getGiteaStatus() string {
|
|
// Check if gitea process is running using pgrep
|
|
cmd := exec.Command("pgrep", "-x", "gitea")
|
|
if err := cmd.Run(); err == nil {
|
|
return green + "●" + reset
|
|
}
|
|
return red + "●" + reset
|
|
}
|
|
|
|
func getGitInfo(cwd string) string {
|
|
// Change to the directory
|
|
oldDir, _ := os.Getwd()
|
|
if err := os.Chdir(cwd); err != nil {
|
|
return ""
|
|
}
|
|
defer os.Chdir(oldDir)
|
|
|
|
// Check if we're in a git repo
|
|
cmd := exec.Command("git", "rev-parse", "--git-dir")
|
|
cmd.Stderr = nil
|
|
if err := cmd.Run(); err != nil {
|
|
return ""
|
|
}
|
|
|
|
// Get branch name
|
|
branch := ""
|
|
cmd = exec.Command("git", "symbolic-ref", "--short", "HEAD")
|
|
cmd.Stderr = nil
|
|
if out, err := cmd.Output(); err == nil {
|
|
branch = strings.TrimSpace(string(out))
|
|
} else {
|
|
// Fallback to short SHA
|
|
cmd = exec.Command("git", "rev-parse", "--short", "HEAD")
|
|
cmd.Stderr = nil
|
|
if out, err := cmd.Output(); err == nil {
|
|
branch = strings.TrimSpace(string(out))
|
|
}
|
|
}
|
|
|
|
if branch == "" {
|
|
return ""
|
|
}
|
|
|
|
// Check if working tree is dirty
|
|
isDirty := false
|
|
|
|
// Check unstaged changes
|
|
cmd = exec.Command("git", "diff", "--no-ext-diff", "--quiet", "--exit-code", "--no-optional-locks")
|
|
cmd.Stderr = nil
|
|
if err := cmd.Run(); err != nil {
|
|
isDirty = true
|
|
}
|
|
|
|
// Check staged changes
|
|
if !isDirty {
|
|
cmd = exec.Command("git", "diff-index", "--cached", "--quiet", "HEAD", "--no-optional-locks")
|
|
cmd.Stderr = nil
|
|
if err := cmd.Run(); err != nil {
|
|
isDirty = true
|
|
}
|
|
}
|
|
|
|
if isDirty {
|
|
return fmt.Sprintf(" git:(%s) ✗", branch)
|
|
}
|
|
return fmt.Sprintf(" git:(%s)", branch)
|
|
}
|
|
|
|
func getTerminalWidth() int {
|
|
ws, err := unix.IoctlGetWinsize(int(os.Stdout.Fd()), unix.TIOCGWINSZ)
|
|
if err != nil {
|
|
return 80
|
|
}
|
|
return int(ws.Col)
|
|
}
|
|
|
|
var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*m`)
|
|
|
|
func stripANSI(s string) string {
|
|
return ansiRegex.ReplaceAllString(s, "")
|
|
}
|