tui

package
v0.1.8 Latest Latest
Warning

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

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

README

Genie gocui TUI

This is the gocui-based TUI implementation for Genie, providing better overlay support and a more organized component structure.

Architecture

The TUI is organized into focused, maintainable files:

Core Files
  • tui.go - Main TUI struct, initialization, and core setup
  • start.go - Entry point and Genie logging configuration
  • layout.go - UI layout management and view positioning
  • handlers.go - Key binding handlers and command processing
  • messages.go - Message rendering and display logic
  • debug.go - Debug panel component
  • history.go - Command history navigation
  • events.go - Event bus subscriptions

Features

✅ Implemented
  • Split-screen debug panel (Ctrl+D or :debug)
  • Command history navigation (Up/Down arrows)
  • File-based logging (logs to .genie/tui.log)
  • Modal dialog overlays (no viewport jumping)
  • Comprehensive help system (:help)
  • Focus management (Tab to cycle, visual indicators)
  • Advanced scrolling (Ctrl+U/D, PgUp/PgDn, Home/End)
  • Tool confirmation dialogs
  • Tool call messages (real-time tool progress with white ● indicators)
  • Tool execution results (green ● success, red ● failure)
  • Markdown rendering (rich AI response formatting)
  • Swappable renderers (Glamour, plain text, custom)
  • Request cancellation (ESC)
🚧 In Progress
  • Configuration system (:config)
  • Expandable tool results
  • Context viewer
  • Scrollable confirmations

Usage

# Or use the default TUI
./genie

Logging

The gocui TUI automatically configures file-based logging:

  • Log location: {project}/.genie/tui.log
  • Log level: Info (good balance of detail)
  • Format: Text with timestamps
  • Scope: All Genie components log to file

This keeps the terminal UI clean while providing full debugging information.

Component Pattern

Each component follows gocui's manager pattern:

// 1. State in TUI struct
type TUI struct {
    showDebug bool
    debugMessages []string
}

// 2. Layout logic in layout.go
if t.showDebug {
    // Create debug view
}

// 3. Event handlers in handlers.go
func (t *TUI) toggleDebugPanel(g *gocui.Gui, v *gocui.View) error {
    t.showDebug = !t.showDebug
    return nil
}

// 4. Rendering in dedicated component file
func (t *TUI) renderDebugMessages(v *gocui.View) {
    // Render logic
}

Key Bindings

Global Controls
  • Ctrl+C - Quit
  • Tab - Cycle focus between panels
  • ESC - Cancel current request
Scrolling (works on focused panel)
  • PgUp/PgDn - Page up/down
  • Ctrl+U/Ctrl+D - Half-page up/down
  • Ctrl+B/Ctrl+F - Page up/down (vi-style)
  • Home/End - Jump to top/bottom
Input Panel (when focused)
  • Enter - Send message/command
  • Up/Down - Navigate command history
  • Ctrl+D - Toggle debug panel
Dialog Controls
  • y/n - Confirm/cancel dialogs
  • ESC - Cancel dialog
Focus Indicators
  • Yellow border - Currently focused panel
  • Default border - Unfocused panels

Commands

  • :help - Show help
  • :debug - Toggle debug panel
  • :config - Configure TUI settings (cursor, theme, output mode, etc.)
  • :theme [name] - Change color theme
  • :renderer [type] - Show/switch markdown renderer
  • :clear - Clear messages
  • :exit - Quit
Configuration Options

Genie supports both global and local configuration scopes:

# Local config (project-specific, saves to .genie/settings.tui.json)
:config <setting> <value>

# Global config (system-wide, saves to ~/.genie/settings.tui.json)
:config --global <setting> <value>

Local configs override global configs, allowing you to set global defaults and project-specific customizations.

Available settings:

  • cursor - Show/hide cursor (true/false)
  • markdown - Enable/disable markdown rendering (true/false)
  • theme - Change color theme (default/dracula/monokai/solarized/nord)
  • wrap - Enable/disable message wrapping (true/false)
  • timestamps - Show/hide timestamps (true/false)
  • border - Show/hide border around messages panel (true/false)
  • output - Terminal output mode:
    • true - 24-bit color with enhanced Unicode support (recommended)
    • 256 - 256-color mode with standard Unicode
    • normal - 8-color mode with basic character support

Examples:

:config theme dark                    # Local theme
:config --global theme dark           # Global theme
:config tool TodoWrite hide true      # Local tool config
:config --global tool bash accept true # Global tool config
:config reset                         # Remove local config (reverts to global)
:config --global reset                # Reset global config to defaults

Note: Changes to output mode require restarting the application. Border settings take effect immediately.

Message Types

The TUI displays different types of messages with distinct styling:

  • User messages (> ) - Cyan color with prompt prefix
  • AI responses - Green color, plain text
  • System messages () - Yellow color with bullet prefix
  • Error messages () - Red color with error icon
  • Tool call messages () - Bright blue, real-time tool progress
  • Tool executions (🔧 ) - Magenta color, tool completion results

Markdown Rendering

The TUI supports multiple markdown renderers that can be switched on-the-fly:

Available Renderers
  • Glamour (default): Rich markdown with syntax highlighting, themes, and full CommonMark support
  • PlainText: No formatting, fastest performance, always available as fallback
  • Custom: Placeholder for future goldmark-based renderer (not yet implemented)
Renderer Commands
/renderer                    # Show current renderer info
/renderer glamour           # Switch to Glamour renderer  
/renderer plaintext         # Switch to plain text renderer
/renderer custom            # Switch to custom renderer (fallback to plaintext)
Automatic Fallback

The renderer system includes automatic fallback:

  1. Try preferred renderer (e.g., Glamour)
  2. If unavailable, fallback to Glamour
  3. If Glamour fails, fallback to plain text
  4. Plain text is always available
Architecture

The renderer is completely isolated and swappable:

// Interface for easy swapping
type MarkdownRenderer interface {
    Render(content string) (string, error)
    UpdateWidth(width int) error
    IsEnabled() bool
}

// Easy to add new renderers
func NewCustomRenderer(width int) MarkdownRenderer {
    // Implement your renderer here
}

Development

The organized structure makes it easy to:

  • Add new components - Create new files following the pattern
  • Extend functionality - Components are isolated and focused
  • Debug issues - Debug panel shows real-time system events
  • Maintain code - Each file has a single responsibility

Documentation

Index

Constants

This section is empty.

Variables

View Source
var AllComponentsSet = wire.NewSet(
	StateSet,
	ComponentSet,
	LayoutSet,
	GuiSet,
)

AllComponentsSet - All UI components and layout

View Source
var AllControllersSet = wire.NewSet(
	ControllerSet,
	CommandSet,
)

AllControllersSet - All controllers and commands

CommandSet - All commands and command handler

ComponentSet - UI components

ControllerSet - Controllers with interface bindings

CoreServicesSet - Core services and dependencies

GuiSet - GUI and interface types

LayoutSet - Layout management

ProdAppDepsSet - Production app dependencies (includes config-based GUI)

StateSet - All state management

TestAppDepsSet - Test app dependencies (uses custom output mode GUI)

Functions

func NewGocuiGui

func NewGocuiGui(configManager *helpers.ConfigManager) (*gocui.Gui, error)

NewGocuiGui - Production GUI provider (uses config-based output mode)

func NewGocuiGuiWithOutputMode

func NewGocuiGuiWithOutputMode(outputMode gocui.OutputMode) (*gocui.Gui, error)

NewGocuiGuiWithOutputMode - Test/Custom GUI provider (accepts custom output mode)

func ProvideChatController

func ProvideChatController(messagesComponent *component.MessagesComponent, gui types.Gui, genieService genie.Genie, stateAccessor *state.StateAccessor, configManager *helpers.ConfigManager, commandEventBus2 *events.CommandEventBus) (*controllers.ChatController, error)

func ProvideChatState

func ProvideChatState(configManager *helpers.ConfigManager) *state.ChatState

func ProvideClearCommand

func ProvideClearCommand(chatController *controllers.ChatController) *commands.ClearCommand

func ProvideClipboard

func ProvideClipboard() *helpers.Clipboard

func ProvideCommandEventBus

func ProvideCommandEventBus() *events.CommandEventBus

ProvideCommandEventBus provides a shared command event bus instance

func ProvideCommandHandler

func ProvideCommandHandler(commandEventBus2 *events.CommandEventBus, chatController *controllers.ChatController, registry *commands.CommandRegistry, contextCommand *commands.ContextCommand, clearCommand *commands.ClearCommand, debugCommand *commands.DebugCommand, exitCommand *commands.ExitCommand, yankCommand *commands.YankCommand, themeCommand *commands.ThemeCommand, configCommand *commands.ConfigCommand, statusCommand *commands.StatusCommand, writeCommand *commands.WriteCommand, updateCommand *commands.UpdateCommand) *commands.CommandHandler

func ProvideCommandRegistry added in v0.1.6

func ProvideCommandRegistry() *commands.CommandRegistry

func ProvideCommandSuggester added in v0.1.6

func ProvideCommandSuggester(registry *commands.CommandRegistry) *shell.CommandSuggester

func ProvideConfigCommand

func ProvideConfigCommand(configManager *helpers.ConfigManager, commandEventBus2 *events.CommandEventBus, gui types.Gui, chatController *controllers.ChatController) *commands.ConfigCommand

func ProvideConfigManager

func ProvideConfigManager() (*helpers.ConfigManager, error)

func ProvideContextCommand

func ProvideContextCommand(llmContextController *controllers.LLMContextController) *commands.ContextCommand

func ProvideDebugCommand

func ProvideDebugCommand(debugController *controllers.DebugController, chatController *controllers.ChatController) *commands.DebugCommand

func ProvideDebugComponent

func ProvideDebugComponent(gui types.Gui, debugState *state.DebugState, configManager *helpers.ConfigManager, commandEventBus2 *events.CommandEventBus) (*component.DebugComponent, error)

func ProvideDebugController

func ProvideDebugController(genieService genie.Genie, gui types.Gui, debugState *state.DebugState, debugComponent *component.DebugComponent, layoutManager *layout.LayoutManager, clipboard *helpers.Clipboard, configManager *helpers.ConfigManager, commandEventBus2 *events.CommandEventBus) (*controllers.DebugController, error)

func ProvideDebugState

func ProvideDebugState() *state.DebugState

func ProvideDiffViewerComponent

func ProvideDiffViewerComponent(gui types.Gui, configManager *helpers.ConfigManager, commandEventBus2 *events.CommandEventBus) (*component.DiffViewerComponent, error)

func ProvideEventBus

func ProvideEventBus(genieService genie.Genie) events2.EventBus

ProvideEventBus extracts the event bus from the Genie service

func ProvideExitCommand

func ProvideExitCommand(commandEventBus2 *events.CommandEventBus) *commands.ExitCommand

func ProvideGenie

func ProvideGenie() (genie.Genie, error)

ProvideGenie provides a shared Genie singleton instance

func ProvideGlobalLogger added in v0.1.6

func ProvideGlobalLogger() logging.Logger

ProvideGlobalLogger provides the global logger instance

func ProvideGui

func ProvideGui(gui *gocui.Gui) types.Gui

func ProvideHistoryPathString

func ProvideHistoryPathString(historyPath HistoryPath) string

func ProvideInputComponent

func ProvideInputComponent(gui types.Gui, configManager *helpers.ConfigManager, commandEventBus2 *events.CommandEventBus, clipboard *helpers.Clipboard, historyPath HistoryPath, commandSuggester *shell.CommandSuggester, slashCommandSuggester *shell.SlashCommandSuggester) (*component.InputComponent, error)

func ProvideLLMContextController

func ProvideLLMContextController(gui types.Gui, genieService genie.Genie, layoutManager *layout.LayoutManager, stateAccessor *state.StateAccessor, configManager *helpers.ConfigManager, commandEventBus2 *events.CommandEventBus) (*controllers.LLMContextController, error)

func ProvideLayoutManager

func ProvideLayoutManager(layoutBuilder *LayoutBuilder) *layout.LayoutManager

func ProvideMessagesComponent

func ProvideMessagesComponent(gui types.Gui, chatState *state.ChatState, configManager *helpers.ConfigManager, commandEventBus2 *events.CommandEventBus) (*component.MessagesComponent, error)

func ProvideSlashCommandController added in v0.1.6

func ProvideSlashCommandController(commandEventBus2 *events.CommandEventBus, slashCommandManager *slashcommands.Manager, notification types.Notification) *controllers.SlashCommandController

func ProvideSlashCommandManager added in v0.1.6

func ProvideSlashCommandManager() *slashcommands.Manager

ProvideSlashCommandManager provides a shared instance of SlashCommandManager

func ProvideSlashCommandSuggester added in v0.1.6

func ProvideSlashCommandSuggester(manager *slashcommands.Manager) *shell.SlashCommandSuggester

func ProvideStateAccessor

func ProvideStateAccessor(chatState *state.ChatState, uiState *state.UIState) *state.StateAccessor

func ProvideStatusCommand

func ProvideStatusCommand(chatController *controllers.ChatController, genieService genie.Genie) *commands.StatusCommand

func ProvideStatusComponent

func ProvideStatusComponent(gui types.Gui, stateAccessor *state.StateAccessor, configManager *helpers.ConfigManager, commandEventBus2 *events.CommandEventBus) (*component.StatusComponent, error)

func ProvideTextViewerComponent

func ProvideTextViewerComponent(gui types.Gui, configManager *helpers.ConfigManager, commandEventBus2 *events.CommandEventBus) (*component.TextViewerComponent, error)

func ProvideThemeCommand

func ProvideThemeCommand(configManager *helpers.ConfigManager, commandEventBus2 *events.CommandEventBus, chatController *controllers.ChatController) *commands.ThemeCommand

func ProvideToolConfirmationController

func ProvideToolConfirmationController(gui types.Gui, stateAccessor *state.StateAccessor, layoutManager *layout.LayoutManager, inputComponent *component.InputComponent, configManager *helpers.ConfigManager, eventBus events2.EventBus, commandEventBus2 *events.CommandEventBus) (*controllers.ToolConfirmationController, error)

func ProvideUIState

func ProvideUIState() *state.UIState

func ProvideUpdateCommand added in v0.1.6

func ProvideUpdateCommand(notification types.Notification) *commands.UpdateCommand

func ProvideUserConfirmationController

func ProvideUserConfirmationController(gui types.Gui, stateAccessor *state.StateAccessor, layoutManager *layout.LayoutManager, inputComponent *component.InputComponent, diffViewerComponent *component.DiffViewerComponent, configManager *helpers.ConfigManager, eventBus events2.EventBus, commandEventBus2 *events.CommandEventBus) (*controllers.UserConfirmationController, error)

func ProvideWriteCommand

func ProvideWriteCommand(writeController *controllers.WriteController) *commands.WriteCommand

func ProvideWriteController

func ProvideWriteController(gui types.Gui, configManager *helpers.ConfigManager, commandEventBus2 *events.CommandEventBus, layoutManager *layout.LayoutManager) (*controllers.WriteController, error)

func ProvideYankCommand

func ProvideYankCommand(chatState *state.ChatState, clipboard *helpers.Clipboard, chatController *controllers.ChatController) *commands.YankCommand

Types

type App

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

func InjectTestApp

func InjectTestApp(genieService genie.Genie, session *genie.Session, outputMode gocui.OutputMode) (*App, error)

InjectTestApp - Test App injector (custom output mode)

func NewApp

func NewApp(
	gui types.Gui,
	commandEventBus *events.CommandEventBus,
	configManager *helpers.ConfigManager,
	layoutManager *layout.LayoutManager,
	commandHandler *commands.CommandHandler,
	notification types.Notification,
	uiState *state.UIState,
	confirmationInit *ConfirmationInitializer,
	slashCommandManager *slashcommands.Manager,
) (*App, error)

func NewAppWithOutputMode

func NewAppWithOutputMode(
	gui types.Gui,
	commandEventBus *events.CommandEventBus,
	configManager *helpers.ConfigManager,
	layoutManager *layout.LayoutManager,
	commandHandler *commands.CommandHandler,
	notification types.Notification,
	uiState *state.UIState,
	confirmationInit *ConfirmationInitializer,
	slashCommandManager *slashcommands.Manager,
	outputMode *gocui.OutputMode,
) (*App, error)

func (*App) Close

func (app *App) Close()

func (*App) DisableGlobalKeybindings

func (app *App) DisableGlobalKeybindings() error

DisableGlobalKeybindings removes global keybindings (those with empty view name "")

func (*App) EnableGlobalKeybindings

func (app *App) EnableGlobalKeybindings() error

EnableGlobalKeybindings restores global keybindings

func (*App) GetGui

func (app *App) GetGui() *gocui.Gui

GetGui for testing.

func (*App) PageDown

func (app *App) PageDown() error

func (*App) PageUp

func (app *App) PageUp() error

func (*App) Run

func (app *App) Run() error

func (*App) RunWithMessage added in v0.1.4

func (app *App) RunWithMessage(initialMessage string) error

func (*App) ScrollDown

func (app *App) ScrollDown() error

func (*App) ScrollToBottom

func (app *App) ScrollToBottom() error

func (*App) ScrollToTop

func (app *App) ScrollToTop() error

func (*App) ScrollUp

func (app *App) ScrollUp() error

type ConfirmationInitializer

type ConfirmationInitializer struct{}

ConfirmationInitializer is a marker type to ensure confirmation controllers are initialized

func InitializeConfirmationControllers

func InitializeConfirmationControllers(
	toolController *controllers.ToolConfirmationController,
	userController *controllers.UserConfirmationController,
	slashCommandController *controllers.SlashCommandController,
) *ConfirmationInitializer

InitializeConfirmationControllers forces Wire to create confirmation controllers They will subscribe to events during construction but don't need to be held by anything

type Gui

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

func (*Gui) GetGui

func (g *Gui) GetGui() *gocui.Gui

func (*Gui) PostUIUpdate

func (g *Gui) PostUIUpdate(fn func())

type HelpRenderer

type HelpRenderer interface {
	// RenderHelp generates complete help text in man-page style
	RenderHelp() string
	// RenderCommands generates the COMMANDS section
	RenderCommands() string
	// RenderShortcuts generates the SHORTCUTS section
	RenderShortcuts() string
	// RenderCommandsByCategory generates commands grouped by category
	RenderCommandsByCategory() string
	// RenderSlashCommands generates slash commands help
	RenderSlashCommands() string
}

HelpRenderer generates man-style help documentation from CommandRegistry and Keymap

func NewManPageHelpRenderer

func NewManPageHelpRenderer(registry *commands.CommandRegistry, keymap *Keymap, slashCommandManager *slashcommands.Manager) HelpRenderer

NewManPageHelpRenderer creates a new help renderer

type HistoryPath

type HistoryPath string

HistoryPath represents the path to the chat history file

func ProvideHistoryPath

func ProvideHistoryPath(session *genie.Session) HistoryPath

ProvideHistoryPath provides the chat history file path based on session working directory

type KeyAction

type KeyAction struct {
	Type        string       // "command" or "function"
	CommandName string       // For "command" type - name of command to execute
	Function    func() error // For "function" type - direct function to call
}

KeyAction represents an action that can be triggered by a key

func CommandAction

func CommandAction(commandName string) KeyAction

CommandAction creates a KeyAction that executes a command

func FunctionAction

func FunctionAction(fn func() error) KeyAction

FunctionAction creates a KeyAction that calls a function directly

type Keymap

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

Keymap manages the application's keybindings

func NewKeymap

func NewKeymap() *Keymap

NewKeymap creates a new empty keymap

func (*Keymap) AddEntry

func (k *Keymap) AddEntry(entry KeymapEntry)

AddEntry adds a new keybinding entry to the keymap

func (*Keymap) GetEntries

func (k *Keymap) GetEntries() []KeymapEntry

GetEntries returns all keymap entries

type KeymapEntry

type KeymapEntry struct {
	Key         gocui.Key      // The key to bind
	Mod         gocui.Modifier // Key modifier (Ctrl, Alt, etc.)
	Action      KeyAction      // What action to perform
	Description string         // Human-readable description
}

KeymapEntry represents a single keybinding in the keymap

type LayoutBuilder

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

LayoutBuilder helps construct and configure the layout manager

func NewLayoutBuilder

func NewLayoutBuilder(
	gui *gocui.Gui,
	configManager *helpers.ConfigManager,
	messagesComponent *component.MessagesComponent,
	inputComponent *component.InputComponent,
	statusComponent *component.StatusComponent,
	textViewerComponent *component.TextViewerComponent,
	diffViewerComponent *component.DiffViewerComponent,
	debugComponent *component.DebugComponent,
) *LayoutBuilder

NewLayoutBuilder creates a new layout builder with all components

func ProvideLayoutBuilder

func ProvideLayoutBuilder(
	gui *gocui.Gui,
	configManager *helpers.ConfigManager,
	messagesComponent *component.MessagesComponent,
	inputComponent *component.InputComponent,
	statusComponent *component.StatusComponent,
	textViewerComponent *component.TextViewerComponent,
	diffViewerComponent *component.DiffViewerComponent,
	debugComponent *component.DebugComponent,
) *LayoutBuilder

func (*LayoutBuilder) GetLayoutManager

func (lb *LayoutBuilder) GetLayoutManager() *layout.LayoutManager

GetLayoutManager returns the configured layout manager

type ManPageHelpRenderer

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

ManPageHelpRenderer implements HelpRenderer with man-page style formatting

func (*ManPageHelpRenderer) RenderCommands

func (h *ManPageHelpRenderer) RenderCommands() string

RenderCommands generates the COMMANDS section (flat list)

func (*ManPageHelpRenderer) RenderCommandsByCategory

func (h *ManPageHelpRenderer) RenderCommandsByCategory() string

RenderCommandsByCategory generates commands grouped by category

func (*ManPageHelpRenderer) RenderHelp

func (h *ManPageHelpRenderer) RenderHelp() string

RenderHelp generates complete man-style help documentation

func (*ManPageHelpRenderer) RenderShortcuts

func (h *ManPageHelpRenderer) RenderShortcuts() string

RenderShortcuts generates the SHORTCUTS section by dynamically analyzing keymap

func (*ManPageHelpRenderer) RenderSlashCommands added in v0.1.6

func (h *ManPageHelpRenderer) RenderSlashCommands() string

RenderSlashCommands generates slash commands help

type TUI

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

func InjectTUI

func InjectTUI(session *genie.Session) (*TUI, error)

InjectTUI - Production TUI injector (default output mode from config)

func New

func New(app *App) *TUI

New creates a TUI with an injected App instance

func (*TUI) GetApp

func (t *TUI) GetApp() *App

GetApp returns the internal App instance for testing

func (*TUI) Start

func (t *TUI) Start() error

func (*TUI) StartWithMessage added in v0.1.4

func (t *TUI) StartWithMessage(initialMessage string) error

func (*TUI) Stop

func (t *TUI) Stop()

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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