devtui

package module
v0.0.103 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 22, 2025 License: MIT Imports: 18 Imported by: 0

README

DevTUI

Project Badges

Interactive Terminal User Interface library for Go applications development (principal tui in GoDEV App)

Features

  • Tab-based interface organization
  • Editable and non-editable fields with real-time validation
  • Keyboard navigation (Tab, Shift+Tab, Left/Right arrows, Enter, Esc)
  • Field validation and change callbacks
  • Message system with unique timestamps and types (Success, Error, Warning, Info)
  • Real-time content updates through channel-based messaging
  • Customizable styles and colors
  • Auto-detection of message types
  • Viewport-based scrolling content area

devtui

Core Architecture

DevTUI Structure
  • DevTUI: Main TUI instance that manages tabs, keyboard input, and viewport
  • tabSection: Individual tabs containing fields and message content
  • field: Input fields with validation, editing capabilities, and change handlers
  • tabContent: Messages with unique IDs, timestamps, and type classification
Message System

Every action generates a tabContent message with:

  • Unique ID: Unix timestamp-based identifier for message tracking
  • Content: The actual message text
  • Type: Auto-detected or explicit (Success, Error, Warning, Info)
  • tabSection: Reference to the tab that generated the message

Basic Usage

package main

import (
    "fmt"
    "strings"
    "strconv"
    "sync"
    "time"
    "github.com/cdvelop/devtui"
)

// Example handlers implementing the FieldHandler interface

// Editable field handler
type HostHandler struct {
    currentHost string
}

func (h *HostHandler) Label() string { return "Host" }
func (h *HostHandler) Value() string { return h.currentHost }
func (h *HostHandler) Editable() bool { return true }
func (h *HostHandler) Timeout() time.Duration { return 5 * time.Second }
func (h *HostHandler) Change(newValue any) (string, error) {
    host := strings.TrimSpace(newValue.(string))
    if host == "" {
        return "", fmt.Errorf("host cannot be empty")
    }
    // Simulate async network validation - DevTUI handles this transparently
    time.Sleep(1 * time.Second)
    h.currentHost = host
    return fmt.Sprintf("Host configured: %s", host), nil
}

// Port validation handler
type PortHandler struct {
    currentPort string
}

func (h *PortHandler) Label() string { return "Port" }
func (h *PortHandler) Value() string { return h.currentPort }
func (h *PortHandler) Editable() bool { return true }
func (h *PortHandler) Timeout() time.Duration { return 0 } // No timeout needed
func (h *PortHandler) Change(newValue any) (string, error) {
    portStr := newValue.(string)
    port, err := strconv.Atoi(portStr)
    if err != nil {
        return "", fmt.Errorf("port must be a number")
    }
    if port < 1 || port > 65535 {
        return "", fmt.Errorf("port must be between 1 and 65535")
    }
    h.currentPort = portStr
    return fmt.Sprintf("Port set to: %d", port), nil
}

// Non-editable action handler
type DeployHandler struct {
    environment string
}

func (h *DeployHandler) Label() string { return "Deploy" }
func (h *DeployHandler) Value() string { return "Press Enter to deploy" }
func (h *DeployHandler) Editable() bool { return false }
func (h *DeployHandler) Timeout() time.Duration { return 30 * time.Second }
func (h *DeployHandler) Change(newValue any) (string, error) {
    // Simulate long deployment operation - DevTUI shows spinner automatically
    time.Sleep(3 * time.Second)
    return fmt.Sprintf("Deployed to %s successfully", h.environment), nil
}

func main() {
    config := &devtui.TuiConfig{
        AppName:       "MyApp",
        TabIndexStart: 0,
        ExitChan:      make(chan bool),
        Color: &devtui.ColorStyle{
            Foreground: "#F4F4F4",
            Background: "#000000",
            Highlight:  "#FF6600",
            Lowlight:   "#666666",
        },
        LogToFile: func(messages ...any) {
            fmt.Println(append([]any{"DevTUI Log:"}, messages...)...)
        },
    }

    tui := devtui.NewTUI(config)

    // Create handlers
    hostHandler := &HostHandler{currentHost: "localhost"}
    portHandler := &PortHandler{currentPort: "8080"}
    deployHandler := &DeployHandler{environment: "production"}

    // Use the handler-based API with async operations
    tui.NewTabSection("Server", "Server configuration").
        NewField(hostHandler).
        NewField(portHandler)
        
    tui.NewTabSection("Actions", "Deployment operations").
        NewField(deployHandler)

    var wg sync.WaitGroup
    wg.Add(1)
    go tui.Start(&wg)
    wg.Wait()
}

API Reference

DevTUI Creation
  • NewTUI(config *TuiConfig) *DevTUI: Create new TUI instance
  • DefaultTUIForTest() *DevTUI: Create TUI with default testing configuration
Tab Management
  • NewTabSection(name, description string) *tabSection: Create new tab with chained API
  • AddTabSections(tabs ...*tabSection): Add multiple tabs to TUI
  • GetActiveTab() *tabSection: Get currently active tab
  • NextTab() / PreviousTab(): Navigate between tabs
Field Management (Handler-based API)

DevTUI uses a handler-based approach where each field implements the FieldHandler interface. This provides transparent asynchronous execution with visual feedback (spinners, progress indicators) and robust error handling.

// FieldHandler interface - implement this for each field
type FieldHandler interface {
    Label() string                    // Field display name/label
    Value() string                    // Current field VALUE (not description)
    Editable() bool                   // true for input fields, false for actions
    Change(newValue any) (string, error) // Handle field changes/actions
    Timeout() time.Duration           // Operation timeout (0 = no timeout)
}
Important: Value() Method

The Value() method should return the actual field value, not a description:

  • Correct: "8080" (for a port field), "c" (for a mode selector)
  • Incorrect: "Current port is 8080", "Coding mode" (descriptions)
  • Rationale: DevTUI displays this value in the field, and users expect to see the actual value they can edit
Handler Examples:

Editable Field with Value:

type DatabaseURLHandler struct {
    currentURL string
}

func (h *DatabaseURLHandler) Label() string { return "Database URL" }
func (h *DatabaseURLHandler) Value() string { return h.currentURL } // Actual URL value
func (h *DatabaseURLHandler) Editable() bool { return true }
func (h *DatabaseURLHandler) Timeout() time.Duration { return 10 * time.Second }
func (h *DatabaseURLHandler) Change(newValue any) (string, error) {
    url := strings.TrimSpace(newValue.(string))
    if url == "" {
        return "", fmt.Errorf("database URL cannot be empty")
    }
    
    // Simulate database connection test - runs async automatically
    time.Sleep(2 * time.Second)
    
    h.currentURL = url
    return "Database connection verified successfully", nil
}

Action Button Handler:

type BuildProjectHandler struct {
    projectPath string
}

func (h *BuildProjectHandler) Label() string { return "Build Project" }
func (h *BuildProjectHandler) Value() string { return "Press Enter to build" } // Action instruction
func (h *BuildProjectHandler) Editable() bool { return false }
func (h *BuildProjectHandler) Timeout() time.Duration { return 60 * time.Second }
func (h *BuildProjectHandler) Change(newValue any) (string, error) {
    // Long-running build operation - spinner shows automatically
    cmd := exec.Command("go", "build", h.projectPath)
    if err := cmd.Run(); err != nil {
        return "", fmt.Errorf("build failed: %v", err)
    }
    return "Build completed successfully", nil
}
Value() Method Guidelines:
  • Editable fields: Return the actual editable value ("localhost", "8080", "production")
  • Action buttons: Return instruction text ("Press Enter to build", "Click to deploy")
  • Mode selectors: Return current mode value ("c", "debug", "enabled")
  • Status fields: Return current status ("connected", "running", "stopped")

// Add fields using handler instances tui.NewTabSection("TabName", "Description"). NewField(handler1). NewField(handler2)


#### Key Features:
- **Transparent Async**: Write simple synchronous `Change` methods; DevTUI handles goroutines automatically
- **Visual Feedback**: Automatic spinners during operations, no additional code needed  
- **Timeout Support**: Configurable timeouts per handler with graceful error handling
- **Type Safety**: Strong typing with interfaces, easy to test and maintain

### Message System
Messages are automatically generated from field operations and displayed in the viewport:

#### Message Types (Auto-detected):
- **Error**: Contains error keywords ("error", "failed", "fail", etc.) - Red color
- **Success**: Contains success keywords ("ok", "success", "complete", etc.) - Green color  
- **Warning**: Contains warning keywords ("warn", "alert", "caution", etc.) - Yellow color
- **Info**: Default type for other messages - Default color

#### Message Structure:
```go
type tabContent struct {
	id         string      // Unix timestamp-based unique identifier
	content    string      // The actual message text
	msgType    MessageType // Auto-detected message type
	tabSection *tabSection // Reference to originating tab
}
Keyboard Navigation
  • Tab: Move to next field
  • Shift+Tab: Move to previous field
  • Left/Right Arrows: Navigate between tabs
  • Enter: Edit field (editable) or trigger action (non-editable)
  • Escape: Cancel editing, return to navigation mode
  • Ctrl+C: Exit application
  • Space: Input space character in edit mode
Configuration
type TuiConfig struct {
	AppName       string                    // Application title
	TabIndexStart int                       // Initial tab index (0-based)
	ExitChan      chan bool                 // Channel for shutdown coordination
	Color         *ColorStyle               // Optional color customization
	LogToFile     func(messages ...any)     // Optional logging function
}

type ColorStyle struct {
	Foreground string // Text color (hex format)
	Background string // Background color (hex format) 
	Highlight  string // Highlight color (hex format)
	Lowlight   string // Lowlight color (hex format)
}

Advanced Examples

Multiple Field Types with Different Behaviors
// Network configuration handlers
type HostHandler struct {
    currentHost string
}

func (h *HostHandler) Label() string { return "Host" }
func (h *HostHandler) Value() string { return h.currentHost }
func (h *HostHandler) Editable() bool { return true }
func (h *HostHandler) Timeout() time.Duration { return 5 * time.Second }
func (h *HostHandler) Change(newValue any) (string, error) {
    host := strings.TrimSpace(newValue.(string))
    if host == "" {
        return "", fmt.Errorf("host cannot be empty")
    }
    
    // Network validation - async with timeout
    time.Sleep(1 * time.Second)
    h.currentHost = host
    return fmt.Sprintf("Host configured: %s", host), nil
}

type PortHandler struct {
    currentPort string
}

func (h *PortHandler) Label() string { return "Port" }
func (h *PortHandler) Value() string { return h.currentPort }
func (h *PortHandler) Editable() bool { return true }
func (h *PortHandler) Timeout() time.Duration { return 0 } // No timeout
func (h *PortHandler) Change(newValue any) (string, error) {
    portStr := newValue.(string)
    port, err := strconv.Atoi(portStr)
    if err != nil {
        return "", fmt.Errorf("port must be a number")
    }
    if port < 1 || port > 65535 {
        return "", fmt.Errorf("port must be between 1 and 65535")
    }
    h.currentPort = portStr
    return fmt.Sprintf("Port set to: %d", port), nil
}

// Usage
hostHandler := &HostHandler{currentHost: "localhost"}
portHandler := &PortHandler{currentPort: "8080"}

tui.NewTabSection("Network", "Server configuration").
    NewField(hostHandler).
    NewField(portHandler)
Long-Running Operations with Progress Feedback
// CI/CD Pipeline handler with timeout
type DeploymentHandler struct {
    environment string
    version     string
}

func (h *DeploymentHandler) Label() string { 
    return fmt.Sprintf("Deploy v%s", h.version) 
}
func (h *DeploymentHandler) Value() string { 
    return fmt.Sprintf("Deploy to %s", h.environment) 
}
func (h *DeploymentHandler) Editable() bool { return false }
func (h *DeploymentHandler) Timeout() time.Duration { return 2 * time.Minute }
func (h *DeploymentHandler) Change(newValue any) (string, error) {
    // Long deployment process - DevTUI shows spinner automatically
    time.Sleep(5 * time.Second) // Simulate deployment
    return fmt.Sprintf("Successfully deployed v%s to %s", h.version, h.environment), nil
}

// Health check handler with shorter timeout
type HealthCheckHandler struct {
    serviceName string
}

func (h *HealthCheckHandler) Label() string { return "Health Check" }
func (h *HealthCheckHandler) Value() string { 
    return fmt.Sprintf("Check %s status", h.serviceName) 
}
func (h *HealthCheckHandler) Editable() bool { return false }
func (h *HealthCheckHandler) Timeout() time.Duration { return 10 * time.Second }
func (h *HealthCheckHandler) Change(newValue any) (string, error) {
    // Simulate health check API call
    time.Sleep(2 * time.Second)
    return fmt.Sprintf("%s is healthy and responding", h.serviceName), nil
}

// Usage
deployHandler := &DeploymentHandler{environment: "production", version: "1.2.3"}
healthHandler := &HealthCheckHandler{serviceName: "API Service"}

tui.NewTabSection("Operations", "Deployment and monitoring").
    NewField(deployHandler).
    NewField(healthHandler)
Multiple Tabs with Different Purposes
// Create handlers for different functional areas
databaseHandler := &DatabaseURLHandler{currentURL: "postgresql://localhost:5432/mydb"}
apiKeyHandler := &APIKeyHandler{currentKey: ""}

buildHandler := &BuildProjectHandler{projectPath: "./"}
testHandler := &TestSuiteHandler{testPath: "./tests"}

healthHandler := &HealthCheckHandler{serviceName: "API Service"}
logHandler := &ViewLogsHandler{logLevel: "INFO"}

// Organize into logical tabs
tui.NewTabSection("Config", "Application settings").
    NewField(databaseHandler).
    NewField(apiKeyHandler)
    
tui.NewTabSection("Build", "Development operations").  
    NewField(buildHandler).
    NewField(testHandler)
    
tui.NewTabSection("Monitor", "System monitoring").
    NewField(healthHandler).
    NewField(logHandler)

Key Features

Asynchronous Operations

All field operations execute asynchronously with transparent internal handling:

  • Non-blocking UI: Interface remains responsive during long operations
  • Visual Progress: Automatic spinners and status indicators during execution
  • Timeout Support: Configurable timeouts per operation with graceful error handling
  • Error Management: Clear error messages and safe operation cancellation
  • Context Management: Automatic cleanup of resources and goroutines
Handler Benefits
  • Simple API: Write synchronous code, get async behavior automatically
  • Type Safety: Strong typing with interfaces for better maintainability
  • Easy Testing: Mock handlers for comprehensive unit testing
  • Clean Separation: UI logic separated from business logic
  • Extensible Design: Easy to add new field types and behaviors
Visual Feedback
  • Real-time Spinners: Animated progress indicators during operations
  • Status Messages: Success, error, and progress messages in viewport
  • Message Correlation: Each operation tracked with unique IDs
  • Auto-detection: Message types automatically detected and colored

Dependencies

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ColorStyle

type ColorStyle struct {
	Foreground string // eg: #F4F4F4
	Background string // eg: #000000
	Highlight  string // eg: #FF6600
	Lowlight   string // eg: #666666
}

type DevTUI

type DevTUI struct {
	*TuiConfig
	// contains filtered or unexported fields
}

DevTUI mantiene el estado de la aplicación

func NewTUI

func NewTUI(c *TuiConfig) *DevTUI

NewTUI creates a new DevTUI instance and initializes it.

Usage Example:

config := &TuiConfig{
    AppName: "MyApp",
    TabIndexStart: 0,
    ExitChan: make(chan bool),
    Color: nil, // or your *ColorStyle
    LogToFile: func(err any) { fmt.Println(err) },
}
tui := NewTUI(config)

// Configure your sections and fields:
tui.NewTabSection("My Section", "Description").
	NewField("Field1", "value", true, nil)

// Start the TUI:
var wg sync.WaitGroup
wg.Add(1)
go tui.Run(&wg)
wg.Wait()

func (*DevTUI) AddTabSections added in v0.0.9

func (t *DevTUI) AddTabSections(sections ...*tabSection) *DevTUI

AddTabSections adds one or more tabSections to the DevTUI If a tab with title "DEFAULT" exists, it will be replaced by the first tab section Deprecated: Use NewTabSection and append to tabSections directly

func (*DevTUI) ContentView

func (h *DevTUI) ContentView() string

ContentView renderiza los mensajes para una sección de contenido

func (*DevTUI) GetTotalTabSections added in v0.0.9

func (t *DevTUI) GetTotalTabSections() int

GetTotalTabSections returns the total number of tab sections

func (*DevTUI) HandleKeyboard added in v0.0.10

func (h *DevTUI) HandleKeyboard(msg tea.KeyMsg) (bool, tea.Cmd)

HandleKeyboard processes keyboard input and updates the model state returns whether the update function should continue processing or return early

func (*DevTUI) Init

func (h *DevTUI) Init() tea.Cmd

Init initializes the terminal UI application.

func (*DevTUI) NewTabSection added in v0.0.43

func (t *DevTUI) NewTabSection(title, footer string) *tabSection

NewTabSection creates and initializes a new tabSection with the given title and footer NewTabSection creates a new tab section and automatically adds it to the TUI

Example:

tab := tui.NewTabSection("BUILD", "Press enter to compile")

func (*DevTUI) Print

func (h *DevTUI) Print(messages ...any)

Print sends a normal Label or error to the tui in current tab

func (*DevTUI) ReturnFocus

func (t *DevTUI) ReturnFocus() error

func (*DevTUI) Start added in v0.0.93

func (h *DevTUI) Start(args ...any)

Start initializes and runs the terminal UI application.

It accepts optional variadic arguments of any type. If a *sync.WaitGroup is provided among these arguments, Start will call its Done() method before returning.

The method runs the UI using the internal tea engine, and handles any errors that may occur during execution. If an error occurs, it will be displayed on the console and the application will wait for user input before exiting.

Parameters:

  • args ...any: Optional arguments. Can include a *sync.WaitGroup for synchronization.

func (*DevTUI) Update

func (h *DevTUI) Update(msg tea.Msg) (tea.Model, tea.Cmd)

Update maneja las actualizaciones del estado

func (*DevTUI) View

func (h *DevTUI) View() string

type FieldHandler added in v0.0.7

type FieldHandler interface {
	Label() string                       // Field label (e.g., "Server Port")
	Value() string                       // Current field value (e.g., "8080")
	Editable() bool                      // Whether field is editable or action button
	Change(newValue any) (string, error) // SAME signature as current changeFunc
	Timeout() time.Duration              // Return 0 for no timeout, or specific duration

	// NEW: WritingHandler methods (REQUIRED for all handlers)
	WritingHandler
}

FieldHandler interface defines the contract for field handlers This replaces the individual parameters approach with a unified interface

type HandlerWriter added in v0.0.103

type HandlerWriter struct {
	// contains filtered or unexported fields
}

NEW: HandlerWriter wraps tabSection with handler identification

func (*HandlerWriter) Write added in v0.0.103

func (hw *HandlerWriter) Write(p []byte) (n int, err error)

type ShortcutsHandler added in v0.0.96

type ShortcutsHandler struct {
	// contains filtered or unexported fields
}

ShortcutsHandler - Shows keyboard navigation instructions

func NewShortcutsHandler added in v0.0.96

func NewShortcutsHandler() *ShortcutsHandler

func (*ShortcutsHandler) Change added in v0.0.96

func (h *ShortcutsHandler) Change(newValue any) (string, error)

func (*ShortcutsHandler) Editable added in v0.0.96

func (h *ShortcutsHandler) Editable() bool

func (*ShortcutsHandler) GetLastOperationID added in v0.0.103

func (h *ShortcutsHandler) GetLastOperationID() string

func (*ShortcutsHandler) Label added in v0.0.96

func (h *ShortcutsHandler) Label() string

func (*ShortcutsHandler) Name added in v0.0.103

func (h *ShortcutsHandler) Name() string

WritingHandler methods

func (*ShortcutsHandler) SetLastOperationID added in v0.0.103

func (h *ShortcutsHandler) SetLastOperationID(lastOpID string)

func (*ShortcutsHandler) Timeout added in v0.0.96

func (h *ShortcutsHandler) Timeout() time.Duration

func (*ShortcutsHandler) Value added in v0.0.96

func (h *ShortcutsHandler) Value() string

type TuiConfig

type TuiConfig struct {
	AppName       string    // app name eg: "MyApp"
	TabIndexStart int       // is the index of the tab section to start default 0
	ExitChan      chan bool //  global chan to close app eg: make(chan bool)
	/* *ColorStyle style for the TUI
	 eg:
	type ColorStyle struct {
	 Foreground string // eg: #F4F4F4
	 Background string // eg: #000000
	 Highlight  string // eg: #FF6600
	 Lowlight   string // eg: #666666
	}*/
	Color *ColorStyle

	LogToFile func(messages ...any) // function to write log error
	TestMode  bool                  // only used in tests to enable synchronous behavior
}

type WritingHandler added in v0.0.103

type WritingHandler interface {
	Name() string                       // Handler identifier (e.g., "TinyWasm", "MainServer")
	SetLastOperationID(lastOpID string) // DevTUI calls this after processing each message
	GetLastOperationID() string         // Handler returns ID for message updates, "" for new messages
}

WritingHandler interface provides message source identification and operation ID management ALL handlers must implement this interface for message source control

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL