No description
- Remove duplicate focus event handling (bare ESC I/O) - Add robust mouse event parsing with bounds validation - Remove input buffer blocking for mouse selection - Replace flaky ESC deselection with navigation-based clearing - Preserve colors in selection using reverse video with ANSI reset handling - Ignore mouse press when unfocused to prevent accidental selection on focus - Clear selection on focus loss for visual consistency - Add focus reporting infrastructure (enable/disable methods) - Centralize log formatting with tag registry system - Update mouse tracking to button-event mode (ESC[?1002h) |
||
|---|---|---|
| examples/mockserver | ||
| buffer.go | ||
| colors.go | ||
| go.mod | ||
| go.sum | ||
| input.go | ||
| README.md | ||
| renderer.go | ||
| terminal.go | ||
| tui.go | ||
TUI
Super-lightweight TUI library for use in the server binaries. Lets us send commands to the server easily even while its running, scroll through output cleanly, et cetera. Just looks good! Built to be non-blocking and has a huge default buffer.
Installation
go get git.sharkk.net/eq2go/tui
Quick Start
package main
import (
"log"
"git.sharkk.net/eq2go/tui"
)
func main() {
// Create TUI
ui, err := tui.New(tui.Config{
ServerName: "MyServer",
ServerVersion: "1.0.0",
})
if err != nil {
log.Fatal(err)
}
// Register commands
ui.RegisterCommand("status", "Show server status", func(t *tui.TUI) {
t.WriteLine("Status: Running")
})
// Start TUI
if err := ui.Start(); err != nil {
log.Fatal(err)
}
defer ui.Stop()
// Process commands (blocks until quit)
ui.ProcessCommands()
}
API Reference
Creating a TUI
ui, err := tui.New(tui.Config{
ServerName: "MyServer", // Required
ServerVersion: "1.0.0", // Optional (default: "0.0.0")
PromptSymbol: "➜", // Optional (default: "➜")
})
Starting and Stopping
// Start the TUI (takes over terminal)
err := ui.Start()
// Stop the TUI (restore terminal)
err := ui.Stop()
Writing Output
// Write a line (thread-safe, non-blocking)
ui.WriteLine("Hello, world!")
// Write formatted line
ui.WriteLinef("Processed %d requests", count)
Command Registration
// Register a command
ui.RegisterCommand("name", "Description", func(t *tui.TUI) {
t.WriteLine("Command executed!")
})
// Process commands (blocking, runs until quit)
ui.ProcessCommands()
Built-in commands (automatically registered):
help- Show all commands (auto-generated)quit/exit- Exit the applicationclear- Clear the screen
Colors and Formatting
// Available colors
tui.Black, tui.Red, tui.Green, tui.Yellow
tui.Blue, tui.Magenta, tui.Cyan, tui.White
// Background colors
tui.BgBlack, tui.BgRed, tui.BgGreen, tui.BgYellow
tui.BgBlue, tui.BgMagenta, tui.BgCyan, tui.BgWhite
// Styles
tui.Bold, tui.Reset
// Colorize text
colored := tui.Colorize("Error!", tui.Red, tui.Bold)
ui.WriteLine(colored)
Utility Functions
// Clear the output buffer
ui.ClearBuffer()
// Update header at runtime
ui.SetHeader("NewName", "2.0.0")
Complete Example
See examples/mockserver/main.go for a full working example.
package main
import (
"fmt"
"log"
"time"
"git.sharkk.net/eq2go/tui"
)
func main() {
ui, _ := tui.New(tui.Config{
ServerName: "ExampleServer",
ServerVersion: "1.0.0",
})
// Register custom commands
ui.RegisterCommand("stats", "Show statistics", func(t *tui.TUI) {
t.WriteLine(tui.Colorize("Statistics:", tui.Cyan, tui.Bold))
t.WriteLinef("Uptime: %s", time.Since(startTime))
t.WriteLinef("Requests: %d", requestCount)
})
ui.RegisterCommand("debug", "Toggle debug mode", func(t *tui.TUI) {
debugMode = !debugMode
t.WriteLinef("Debug mode: %v", debugMode)
})
ui.Start()
defer ui.Stop()
// Background logging
go func() {
ticker := time.NewTicker(5 * time.Second)
for range ticker.C {
ui.WriteLine(formatLog("INFO", tui.Green, "Heartbeat"))
}
}()
ui.ProcessCommands()
}
func formatLog(level string, color tui.Color, msg string) string {
ts := time.Now().Format("15:04:05")
tag := tui.Colorize(fmt.Sprintf("[%-7s]", level), color)
return fmt.Sprintf("%s %s %s", ts, tag, msg)
}
User Interface
ServerName vVersion ================================
14:32:45 [INFO ] Server started successfully
14:32:46 [INFO ] Listening on 0.0.0.0:8080
14:32:47 [WARNING] Cache miss detected
14:32:48 [INFO ] Request completed in 42ms
====================================================
➜ status
====================================================
Features Detail
Mouse Scrolling
- Scroll up: Mouse wheel up
- Scroll down: Mouse wheel down
- Auto-scroll: Automatically scrolls to bottom on new output
- Manual scroll: Disables auto-scroll until you scroll to bottom
Command History
- Up arrow: Previous command
- Down arrow: Next command (or empty if at end)
- History size: Last 100 commands
Buffer Management
- Size: 10,000 lines (≈1-2 MB)
- Type: Circular buffer (auto-evicts old lines)
- Thread-safe: Safe concurrent access
- Scrollable: Full history accessible via mouse wheel
Performance
- Non-blocking writes:
WriteLine()never blocks (O(1)) - Double-buffering: Flicker-free rendering
- Rate limiting: Max 20 FPS rendering
- Hot-path safe: 10,000 message buffer for burst handling
Architecture
┌─────────────────────────────────────┐
│ User Application │
│ (Registers commands, writes logs) │
└────────────┬────────────────────────┘
│
v
┌─────────────────────────────────────┐
│ TUI Library (tui.go) │
│ - Command routing │
│ - Output buffering (10K channel) │
│ - Event loop orchestration │
└────┬────────────────┬───────────────┘
│ │
v v
┌─────────┐ ┌──────────────┐
│ Renderer│ │ InputHandler │
│ (render)│ │ (keyboard) │
└─────────┘ └──────────────┘
│ │
v v
┌─────────────────────────────────────┐
│ Terminal (terminal.go) │
│ - Raw mode, ANSI codes, mouse │
└─────────────────────────────────────┘
Technical Details
Terminal Control
- Raw mode: Using
golang.org/x/term - Alternate screen: Preserves shell history
- ANSI escape codes: Direct control for speed
- Mouse tracking: SGR extended mode
Rendering
- Strategy: Double-buffered in-memory rendering
- Line clearing:
\033[2Kbefore each line write - Cursor positioning: Manual control for prompt placement
- No flicker: Overwrite instead of clear+redraw
Thread Safety
- All public methods are thread-safe
- Uses channels for async communication
- Read/write mutexes for shared state
- Safe to call from goroutines
License
Copyright © 2025 EQ2Go, Sharkk Sharkk Minimal License
Examples
Run the mock server:
cd examples/mockserver
go build
./mockserver
Try these commands:
help- See all available commandsstatus- Show server statusinfo- Show an info messagewarn- Show a warning messageerror- Show an error messageclear- Clear the screenquit- Exit
Use mouse wheel to scroll through output history!