feat(04-02): i3bar widget with per-session colors, workspace prefix, wait type and duration
- One i3bar block per session (individual colors: red/green/gray) - Workspace prefix: 10-vmux[W], 3-auth[? 3m] - Wait type markers: ⚡ permission, ? question, ! generic - Wait duration: [⚡ 30s], [? 3m], [? 1h15m] - Standalone mode only (separate bar, no i3status wrapping) - Removed isTerminal and i3status wrapping code
This commit is contained in:
189
i3bar.go
189
i3bar.go
@@ -1,13 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,65 +17,101 @@ type I3BarBlock struct {
|
|||||||
Markup string `json:"markup,omitempty"`
|
Markup string `json:"markup,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatI3BarBlocks builds the vmux status block for i3bar from session data.
|
// formatI3BarBlocks builds one i3bar block per session, each with its own color.
|
||||||
func formatI3BarBlocks(sessions []SessionInfo) []I3BarBlock {
|
// For Needs Input sessions: shows wait type icon and duration.
|
||||||
|
func formatI3BarBlocks(sessions []SessionInfo, now time.Time) []I3BarBlock {
|
||||||
if len(sessions) == 0 {
|
if len(sessions) == 0 {
|
||||||
return []I3BarBlock{{FullText: "vmux: no sessions", Color: "#00ff00", Name: "vmux"}}
|
return []I3BarBlock{{FullText: "vmux: no sessions", Color: "#888888", Name: "vmux"}}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasWaiting := false
|
blocks := make([]I3BarBlock, 0, len(sessions))
|
||||||
parts := make([]string, 0, len(sessions))
|
|
||||||
|
|
||||||
for _, s := range sessions {
|
for _, s := range sessions {
|
||||||
name := shortName(s)
|
name := shortName(s)
|
||||||
|
if s.Workspace != "" {
|
||||||
|
name = s.Workspace + "-" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
var marker, color string
|
||||||
switch s.State {
|
switch s.State {
|
||||||
case "Needs Input":
|
case "Needs Input":
|
||||||
parts = append(parts, name+"[!]")
|
marker = waitTypeMarker(s.WaitType)
|
||||||
hasWaiting = true
|
if s.WaitingSince != nil {
|
||||||
case "Working":
|
marker += " " + shortDuration(now.Sub(*s.WaitingSince))
|
||||||
parts = append(parts, name+"[W]")
|
|
||||||
case "Idle":
|
|
||||||
parts = append(parts, name+"[I]")
|
|
||||||
}
|
}
|
||||||
}
|
marker = "[" + marker + "]"
|
||||||
|
|
||||||
var text string
|
|
||||||
if !hasWaiting {
|
|
||||||
text = fmt.Sprintf("vmux: all working (%d)", len(sessions))
|
|
||||||
} else {
|
|
||||||
text = "vmux: " + strings.Join(parts, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
color := "#00ff00"
|
|
||||||
if hasWaiting {
|
|
||||||
color = "#ff0000"
|
color = "#ff0000"
|
||||||
|
case "Working":
|
||||||
|
marker = "[W]"
|
||||||
|
color = "#00ff00"
|
||||||
|
case "Idle":
|
||||||
|
marker = "[I]"
|
||||||
|
color = "#888888"
|
||||||
|
default:
|
||||||
|
marker = "[?]"
|
||||||
|
color = "#888888"
|
||||||
}
|
}
|
||||||
|
|
||||||
return []I3BarBlock{{FullText: text, Color: color, Name: "vmux"}}
|
blocks = append(blocks, I3BarBlock{
|
||||||
|
FullText: name + marker,
|
||||||
|
Color: color,
|
||||||
|
Name: "vmux-" + s.SessionID,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// queryVmuxBlock fetches sessions from daemon and returns the vmux i3bar block.
|
return blocks
|
||||||
// Returns a "daemon offline" block if the daemon is unreachable.
|
}
|
||||||
func queryVmuxBlock(sockPath string) I3BarBlock {
|
|
||||||
|
// waitTypeMarker returns a short icon for the wait type.
|
||||||
|
func waitTypeMarker(waitType string) string {
|
||||||
|
switch waitType {
|
||||||
|
case "permission":
|
||||||
|
return "⚡"
|
||||||
|
case "question":
|
||||||
|
return "?"
|
||||||
|
default:
|
||||||
|
return "!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortDuration formats a duration as compact string for i3bar.
|
||||||
|
func shortDuration(d time.Duration) string {
|
||||||
|
if d < time.Minute {
|
||||||
|
return fmt.Sprintf("%ds", int(d.Seconds()))
|
||||||
|
}
|
||||||
|
m := int(d.Minutes())
|
||||||
|
if m < 60 {
|
||||||
|
return fmt.Sprintf("%dm", m)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%dh%dm", m/60, m%60)
|
||||||
|
}
|
||||||
|
|
||||||
|
// queryVmuxBlocks fetches sessions from daemon and returns i3bar blocks.
|
||||||
|
func queryVmuxBlocks(sockPath string) []I3BarBlock {
|
||||||
client := NewClient(sockPath)
|
client := NewClient(sockPath)
|
||||||
resp, err := client.Send(Request{Action: "list"})
|
resp, err := client.Send(Request{Action: "list"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return I3BarBlock{FullText: "vmux: daemon offline", Color: "#888888", Name: "vmux"}
|
return []I3BarBlock{{FullText: "vmux: offline", Color: "#888888", Name: "vmux"}}
|
||||||
}
|
}
|
||||||
if !resp.OK {
|
if !resp.OK {
|
||||||
return I3BarBlock{FullText: "vmux: error", Color: "#888888", Name: "vmux"}
|
return []I3BarBlock{{FullText: "vmux: error", Color: "#888888", Name: "vmux"}}
|
||||||
}
|
}
|
||||||
blocks := formatI3BarBlocks(resp.Sessions)
|
return formatI3BarBlocks(resp.Sessions, time.Now())
|
||||||
return blocks[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeI3BarLine writes a JSON array of blocks as an i3bar protocol line.
|
// runI3Bar runs the i3bar protocol loop on stdout, polling the daemon every 2s.
|
||||||
// Uses os.Stdout.Write + Sync to avoid buffering issues.
|
func runI3Bar(sockPath string) {
|
||||||
func writeI3BarLine(blocks []I3BarBlock, first bool) {
|
os.Stdout.Write([]byte("{\"version\":1}\n"))
|
||||||
|
os.Stdout.Write([]byte("[\n"))
|
||||||
|
os.Stdout.Sync()
|
||||||
|
|
||||||
|
first := true
|
||||||
|
for {
|
||||||
|
blocks := queryVmuxBlocks(sockPath)
|
||||||
data, err := json.Marshal(blocks)
|
data, err := json.Marshal(blocks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("i3bar marshal error: %v", err)
|
log.Printf("i3bar marshal error: %v", err)
|
||||||
return
|
time.Sleep(2 * time.Second)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var line string
|
var line string
|
||||||
@@ -89,88 +122,8 @@ func writeI3BarLine(blocks []I3BarBlock, first bool) {
|
|||||||
}
|
}
|
||||||
os.Stdout.Write([]byte(line))
|
os.Stdout.Write([]byte(line))
|
||||||
os.Stdout.Sync()
|
os.Stdout.Sync()
|
||||||
}
|
|
||||||
|
|
||||||
// runI3Bar runs the i3bar protocol loop on stdout.
|
|
||||||
// If i3statusCmd is non-empty, it wraps i3status output and prepends vmux blocks.
|
|
||||||
// If empty, runs in standalone mode with periodic polling.
|
|
||||||
func runI3Bar(sockPath, i3statusCmd string) {
|
|
||||||
if i3statusCmd != "" {
|
|
||||||
runI3BarWrapped(sockPath, i3statusCmd)
|
|
||||||
// If i3status exits, fall through to standalone
|
|
||||||
}
|
|
||||||
runI3BarStandalone(sockPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runI3BarStandalone(sockPath string) {
|
|
||||||
os.Stdout.Write([]byte("{\"version\":1}\n"))
|
|
||||||
os.Stdout.Write([]byte("[\n"))
|
|
||||||
os.Stdout.Sync()
|
|
||||||
|
|
||||||
first := true
|
|
||||||
for {
|
|
||||||
block := queryVmuxBlock(sockPath)
|
|
||||||
writeI3BarLine([]I3BarBlock{block}, first)
|
|
||||||
first = false
|
first = false
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runI3BarWrapped(sockPath, i3statusCmd string) {
|
|
||||||
cmd := exec.Command("sh", "-c", i3statusCmd)
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("i3bar: cannot pipe i3status stdout: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
log.Printf("i3bar: cannot start i3status: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(stdout)
|
|
||||||
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
|
||||||
|
|
||||||
// Forward header line (e.g. {"version":1})
|
|
||||||
if !scanner.Scan() {
|
|
||||||
log.Print("i3bar: i3status produced no output")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
os.Stdout.Write([]byte(scanner.Text() + "\n"))
|
|
||||||
os.Stdout.Sync()
|
|
||||||
|
|
||||||
// Forward opening bracket
|
|
||||||
if !scanner.Scan() {
|
|
||||||
log.Print("i3bar: i3status produced no opening bracket")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
os.Stdout.Write([]byte(scanner.Text() + "\n"))
|
|
||||||
os.Stdout.Sync()
|
|
||||||
|
|
||||||
first := true
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
trimmed := strings.TrimLeft(line, ",")
|
|
||||||
if trimmed == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var blocks []I3BarBlock
|
|
||||||
if err := json.Unmarshal([]byte(trimmed), &blocks); err != nil {
|
|
||||||
// Not a JSON array line, forward as-is
|
|
||||||
os.Stdout.Write([]byte(line + "\n"))
|
|
||||||
os.Stdout.Sync()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
vmuxBlock := queryVmuxBlock(sockPath)
|
|
||||||
blocks = append([]I3BarBlock{vmuxBlock}, blocks...)
|
|
||||||
|
|
||||||
writeI3BarLine(blocks, first)
|
|
||||||
first = false
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Wait()
|
|
||||||
}
|
|
||||||
|
|||||||
168
i3bar_test.go
168
i3bar_test.go
@@ -2,24 +2,51 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var i3barTestNow = time.Date(2026, 3, 24, 12, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
func TestFormatI3BarBlocks_MixedStates(t *testing.T) {
|
func TestFormatI3BarBlocks_MixedStates(t *testing.T) {
|
||||||
|
waiting := i3barTestNow.Add(-3 * time.Minute)
|
||||||
sessions := []SessionInfo{
|
sessions := []SessionInfo{
|
||||||
{Cwd: "/home/pierre/Code/auth", State: "Needs Input"},
|
{Cwd: "/home/pierre/Code/auth", Workspace: "3", State: "Needs Input", WaitType: "question", WaitingSince: &waiting},
|
||||||
{Cwd: "/home/pierre/Code/portal", State: "Working"},
|
{Cwd: "/home/pierre/Code/portal", Workspace: "2", State: "Working"},
|
||||||
{Cwd: "/home/pierre/Code/neia", State: "Idle"},
|
{Cwd: "/home/pierre/Code/neia", Workspace: "5", State: "Idle"},
|
||||||
}
|
}
|
||||||
blocks := formatI3BarBlocks(sessions)
|
blocks := formatI3BarBlocks(sessions, i3barTestNow)
|
||||||
if len(blocks) != 1 {
|
if len(blocks) != 3 {
|
||||||
t.Fatalf("expected 1 block, got %d", len(blocks))
|
t.Fatalf("expected 3 blocks, got %d", len(blocks))
|
||||||
}
|
}
|
||||||
want := "vmux: auth[!] portal[W] neia[I]"
|
if blocks[0].FullText != "3-auth[? 3m]" || blocks[0].Color != "#ff0000" {
|
||||||
if blocks[0].FullText != want {
|
t.Errorf("block 0: %q %q", blocks[0].FullText, blocks[0].Color)
|
||||||
t.Errorf("full_text = %q, want %q", blocks[0].FullText, want)
|
|
||||||
}
|
}
|
||||||
if blocks[0].Color != "#ff0000" {
|
if blocks[1].FullText != "2-portal[W]" || blocks[1].Color != "#00ff00" {
|
||||||
t.Errorf("color = %q, want #ff0000", blocks[0].Color)
|
t.Errorf("block 1: %q %q", blocks[1].FullText, blocks[1].Color)
|
||||||
|
}
|
||||||
|
if blocks[2].FullText != "5-neia[I]" || blocks[2].Color != "#888888" {
|
||||||
|
t.Errorf("block 2: %q %q", blocks[2].FullText, blocks[2].Color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatI3BarBlocks_PermissionMarker(t *testing.T) {
|
||||||
|
waiting := i3barTestNow.Add(-30 * time.Second)
|
||||||
|
sessions := []SessionInfo{
|
||||||
|
{Cwd: "/a/proj", Workspace: "1", State: "Needs Input", WaitType: "permission", WaitingSince: &waiting},
|
||||||
|
}
|
||||||
|
blocks := formatI3BarBlocks(sessions, i3barTestNow)
|
||||||
|
if blocks[0].FullText != "1-proj[⚡ 30s]" {
|
||||||
|
t.Errorf("full_text = %q, want %q", blocks[0].FullText, "1-proj[⚡ 30s]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatI3BarBlocks_NeedsInputNoWaitType(t *testing.T) {
|
||||||
|
sessions := []SessionInfo{
|
||||||
|
{Cwd: "/a/proj", State: "Needs Input"},
|
||||||
|
}
|
||||||
|
blocks := formatI3BarBlocks(sessions, i3barTestNow)
|
||||||
|
if blocks[0].FullText != "proj[!]" {
|
||||||
|
t.Errorf("full_text = %q, want %q", blocks[0].FullText, "proj[!]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,48 +54,38 @@ func TestFormatI3BarBlocks_AllWorking(t *testing.T) {
|
|||||||
sessions := []SessionInfo{
|
sessions := []SessionInfo{
|
||||||
{Cwd: "/a/b/one", State: "Working"},
|
{Cwd: "/a/b/one", State: "Working"},
|
||||||
{Cwd: "/a/b/two", State: "Working"},
|
{Cwd: "/a/b/two", State: "Working"},
|
||||||
{Cwd: "/a/b/three", State: "Working"},
|
|
||||||
}
|
}
|
||||||
blocks := formatI3BarBlocks(sessions)
|
blocks := formatI3BarBlocks(sessions, i3barTestNow)
|
||||||
want := "vmux: all working (3)"
|
if len(blocks) != 2 {
|
||||||
if blocks[0].FullText != want {
|
t.Fatalf("expected 2 blocks, got %d", len(blocks))
|
||||||
t.Errorf("full_text = %q, want %q", blocks[0].FullText, want)
|
|
||||||
}
|
}
|
||||||
if blocks[0].Color != "#00ff00" {
|
if blocks[0].FullText != "one[W]" || blocks[0].Color != "#00ff00" {
|
||||||
t.Errorf("color = %q, want #00ff00", blocks[0].Color)
|
t.Errorf("block 0: %q %q", blocks[0].FullText, blocks[0].Color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatI3BarBlocks_NoSessions(t *testing.T) {
|
func TestFormatI3BarBlocks_NoSessions(t *testing.T) {
|
||||||
blocks := formatI3BarBlocks(nil)
|
blocks := formatI3BarBlocks(nil, i3barTestNow)
|
||||||
want := "vmux: no sessions"
|
if len(blocks) != 1 || blocks[0].FullText != "vmux: no sessions" {
|
||||||
if blocks[0].FullText != want {
|
t.Errorf("full_text = %q", blocks[0].FullText)
|
||||||
t.Errorf("full_text = %q, want %q", blocks[0].FullText, want)
|
|
||||||
}
|
|
||||||
if blocks[0].Color != "#00ff00" {
|
|
||||||
t.Errorf("color = %q, want #00ff00", blocks[0].Color)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatI3BarBlocks_ColorRed(t *testing.T) {
|
func TestFormatI3BarBlocks_PerSessionColor(t *testing.T) {
|
||||||
sessions := []SessionInfo{
|
sessions := []SessionInfo{
|
||||||
{Cwd: "/a/b/x", State: "Working"},
|
{Cwd: "/a/x", State: "Working"},
|
||||||
{Cwd: "/a/b/y", State: "Needs Input"},
|
{Cwd: "/a/y", State: "Needs Input"},
|
||||||
|
{Cwd: "/a/z", State: "Idle"},
|
||||||
}
|
}
|
||||||
blocks := formatI3BarBlocks(sessions)
|
blocks := formatI3BarBlocks(sessions, i3barTestNow)
|
||||||
if blocks[0].Color != "#ff0000" {
|
|
||||||
t.Errorf("color = %q, want #ff0000", blocks[0].Color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatI3BarBlocks_ColorGreen(t *testing.T) {
|
|
||||||
sessions := []SessionInfo{
|
|
||||||
{Cwd: "/a/b/x", State: "Working"},
|
|
||||||
{Cwd: "/a/b/y", State: "Idle"},
|
|
||||||
}
|
|
||||||
blocks := formatI3BarBlocks(sessions)
|
|
||||||
if blocks[0].Color != "#00ff00" {
|
if blocks[0].Color != "#00ff00" {
|
||||||
t.Errorf("color = %q, want #00ff00", blocks[0].Color)
|
t.Errorf("Working color = %q, want #00ff00", blocks[0].Color)
|
||||||
|
}
|
||||||
|
if blocks[1].Color != "#ff0000" {
|
||||||
|
t.Errorf("Needs Input color = %q, want #ff0000", blocks[1].Color)
|
||||||
|
}
|
||||||
|
if blocks[2].Color != "#888888" {
|
||||||
|
t.Errorf("Idle color = %q, want #888888", blocks[2].Color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,54 +93,61 @@ func TestFormatI3BarBlocks_UsesLabel(t *testing.T) {
|
|||||||
sessions := []SessionInfo{
|
sessions := []SessionInfo{
|
||||||
{Cwd: "/home/pierre/Code/auth-service", Label: "auth", State: "Working"},
|
{Cwd: "/home/pierre/Code/auth-service", Label: "auth", State: "Working"},
|
||||||
}
|
}
|
||||||
blocks := formatI3BarBlocks(sessions)
|
blocks := formatI3BarBlocks(sessions, i3barTestNow)
|
||||||
want := "vmux: all working (1)"
|
if blocks[0].FullText != "auth[W]" {
|
||||||
if blocks[0].FullText != want {
|
t.Errorf("full_text = %q, want %q", blocks[0].FullText, "auth[W]")
|
||||||
t.Errorf("full_text = %q, want %q", blocks[0].FullText, want)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatI3BarBlocks_UsesLabelInMixed(t *testing.T) {
|
func TestFormatI3BarBlocks_WorkspacePrefix(t *testing.T) {
|
||||||
sessions := []SessionInfo{
|
sessions := []SessionInfo{
|
||||||
{Cwd: "/home/pierre/Code/auth-service", Label: "auth", State: "Needs Input"},
|
{Cwd: "/home/pierre/Code/vibe/vmux", Workspace: "10", State: "Working"},
|
||||||
}
|
}
|
||||||
blocks := formatI3BarBlocks(sessions)
|
blocks := formatI3BarBlocks(sessions, i3barTestNow)
|
||||||
want := "vmux: auth[!]"
|
if blocks[0].FullText != "10-vmux[W]" {
|
||||||
if blocks[0].FullText != want {
|
t.Errorf("full_text = %q, want %q", blocks[0].FullText, "10-vmux[W]")
|
||||||
t.Errorf("full_text = %q, want %q", blocks[0].FullText, want)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatI3BarBlocks_UsesCwdBase(t *testing.T) {
|
func TestFormatI3BarBlocks_LongWaitDuration(t *testing.T) {
|
||||||
|
waiting := i3barTestNow.Add(-75 * time.Minute)
|
||||||
sessions := []SessionInfo{
|
sessions := []SessionInfo{
|
||||||
{Cwd: "/home/pierre/Code/vibe/vmux", State: "Needs Input"},
|
{Cwd: "/a/proj", Workspace: "2", State: "Needs Input", WaitType: "question", WaitingSince: &waiting},
|
||||||
}
|
}
|
||||||
blocks := formatI3BarBlocks(sessions)
|
blocks := formatI3BarBlocks(sessions, i3barTestNow)
|
||||||
want := "vmux: vmux[!]"
|
if blocks[0].FullText != "2-proj[? 1h15m]" {
|
||||||
if blocks[0].FullText != want {
|
t.Errorf("full_text = %q, want %q", blocks[0].FullText, "2-proj[? 1h15m]")
|
||||||
t.Errorf("full_text = %q, want %q", blocks[0].FullText, want)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatI3BarBlocks_NeedsInputMarker(t *testing.T) {
|
func TestShortDuration(t *testing.T) {
|
||||||
sessions := []SessionInfo{
|
tests := []struct {
|
||||||
{Cwd: "/a/proj", State: "Needs Input"},
|
d time.Duration
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{10 * time.Second, "10s"},
|
||||||
|
{59 * time.Second, "59s"},
|
||||||
|
{1 * time.Minute, "1m"},
|
||||||
|
{5 * time.Minute, "5m"},
|
||||||
|
{65 * time.Minute, "1h5m"},
|
||||||
|
{120 * time.Minute, "2h0m"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := shortDuration(tt.d)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("shortDuration(%v) = %q, want %q", tt.d, got, tt.want)
|
||||||
}
|
}
|
||||||
blocks := formatI3BarBlocks(sessions)
|
|
||||||
want := "vmux: proj[!]"
|
|
||||||
if blocks[0].FullText != want {
|
|
||||||
t.Errorf("full_text = %q, want %q", blocks[0].FullText, want)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatI3BarBlocks_IdleMarker(t *testing.T) {
|
func TestWaitTypeMarker(t *testing.T) {
|
||||||
sessions := []SessionInfo{
|
if waitTypeMarker("permission") != "⚡" {
|
||||||
{Cwd: "/a/proj", State: "Idle"},
|
t.Error("permission should be ⚡")
|
||||||
{Cwd: "/a/other", State: "Needs Input"},
|
|
||||||
}
|
}
|
||||||
blocks := formatI3BarBlocks(sessions)
|
if waitTypeMarker("question") != "?" {
|
||||||
want := "vmux: proj[I] other[!]"
|
t.Error("question should be ?")
|
||||||
if blocks[0].FullText != want {
|
}
|
||||||
t.Errorf("full_text = %q, want %q", blocks[0].FullText, want)
|
if waitTypeMarker("") != "!" {
|
||||||
|
t.Error("empty should be !")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
main.go
8
main.go
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -147,11 +146,7 @@ func main() {
|
|||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
i3statusCmd := ""
|
runI3Bar(sockPath)
|
||||||
if _, err := exec.LookPath("i3status"); err == nil {
|
|
||||||
i3statusCmd = "i3status"
|
|
||||||
}
|
|
||||||
runI3Bar(sockPath, i3statusCmd)
|
|
||||||
|
|
||||||
case "daemon":
|
case "daemon":
|
||||||
runDaemon(sockPath)
|
runDaemon(sockPath)
|
||||||
@@ -203,6 +198,7 @@ func (p *realI3TreeProvider) GetTree() (i3.Tree, error) {
|
|||||||
return i3.GetTree()
|
return i3.GetTree()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func printUsage() {
|
func printUsage() {
|
||||||
fmt.Fprintf(os.Stderr, `Usage: vmux <command> [args...]
|
fmt.Fprintf(os.Stderr, `Usage: vmux <command> [args...]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user