Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions USAGE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ cat test.log | ./build/gonzo
-b, --log-buffer=1000 # Maximum log buffer size
-m, --memory-size=10000 # Maximum entries in memory
--stop-words strings # Additional stop words to filter from analysis
--reverse-scroll-wheel # Reverse scroll wheel direction (natural scrolling)
--config string # Config file (default: ~/.gonzo.yaml)

# Version and help
Expand Down Expand Up @@ -224,6 +225,54 @@ export GONZO_STOP_WORDS="debug info warning error"
- Stop words only affect word frequency analysis, not log display or filtering
- Changes take effect immediately when logs are processed

## Reverse Scroll Wheel Configuration

### Overview
By default, Gonzo uses traditional scroll wheel behavior (scroll up = content moves up). If you prefer natural/trackpad-style scrolling (scroll up = content moves down), you can enable reverse scroll wheel mode.

### Enabling Reverse Scroll

#### Via Command Line
```bash
# Enable reverse scroll wheel
./build/gonzo -f app.log --reverse-scroll-wheel

# Works with all input modes
cat logs.json | ./build/gonzo --reverse-scroll-wheel
./build/gonzo --otlp-enabled --reverse-scroll-wheel
```

#### Via Configuration File
```yaml
# ~/.config/gonzo/config.yml
reverse-scroll-wheel: true
```

#### Via Environment Variable
```bash
# Set the environment variable
export GONZO_REVERSE_SCROLL_WHEEL=true
./build/gonzo -f app.log
```

### Behavior

When reverse scroll wheel is enabled:
- **Scroll wheel up** → Content moves down (viewport scrolls down)
- **Scroll wheel down** → Content moves up (viewport scrolls up)

This affects all scrollable areas:
- Main dashboard log navigation
- All modal windows (log details, patterns, statistics, etc.)
- Chat viewport scrolling
- Model selection
- Help screen

### Use Cases
- **MacOS users**: Match trackpad natural scrolling behavior
- **Consistency**: Keep same scroll direction across all applications
- **Personal preference**: Use whichever feels more intuitive to you

### Supported Integrations

- [Victoria Logs Integration](guides/VICTORIA_LOGS_USAGE.md) - Using Gonzo with Victoria Logs API
Expand Down
2 changes: 1 addition & 1 deletion cmd/gonzo/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func runApp(cmd *cobra.Command, args []string) error {
freqMemory := memory.NewFrequencyMemory(cfg.MemorySize)

// Initialize TUI model with components
dashboard := tui.NewDashboardModel(cfg.LogBuffer, cfg.UpdateInterval, cfg.AIModel, textAnalyzer.GetStopWords())
dashboard := tui.NewDashboardModel(cfg.LogBuffer, cfg.UpdateInterval, cfg.AIModel, textAnalyzer.GetStopWords(), cfg.ReverseScrollWheel)
if versionChecker != nil {
dashboard.SetVersionChecker(versionChecker)
}
Expand Down
3 changes: 3 additions & 0 deletions cmd/gonzo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type Config struct {
StopWords []string `mapstructure:"stop-words"`
Format string `mapstructure:"format"`
DisableVersionCheck bool `mapstructure:"disable-version-check"`
ReverseScrollWheel bool `mapstructure:"reverse-scroll-wheel"`
}

var (
Expand Down Expand Up @@ -156,6 +157,7 @@ func init() {
rootCmd.Flags().StringSlice("stop-words", []string{}, "Additional stop words to filter out from analysis (adds to built-in list)")
rootCmd.Flags().String("format", "", "Log format to use (auto-detect if not specified). Can be: otlp, json, text, or a custom format name from ~/.config/gonzo/formats/")
rootCmd.Flags().Bool("disable-version-check", false, "Disable automatic version checking on startup")
rootCmd.Flags().Bool("reverse-scroll-wheel", false, "Reverse scroll wheel direction (natural scrolling)")

// Bind flags to viper
viper.BindPFlag("memory-size", rootCmd.Flags().Lookup("memory-size"))
Expand All @@ -176,6 +178,7 @@ func init() {
viper.BindPFlag("stop-words", rootCmd.Flags().Lookup("stop-words"))
viper.BindPFlag("format", rootCmd.Flags().Lookup("format"))
viper.BindPFlag("disable-version-check", rootCmd.Flags().Lookup("disable-version-check"))
viper.BindPFlag("reverse-scroll-wheel", rootCmd.Flags().Lookup("reverse-scroll-wheel"))

// Add version command
rootCmd.AddCommand(versionCmd)
Expand Down
8 changes: 5 additions & 3 deletions internal/tui/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ type DashboardModel struct {
servicesBySeverity map[string][]ServiceCount // Top services by severity level

// Configuration
maxLogBuffer int
updateInterval time.Duration
maxLogBuffer int
updateInterval time.Duration
reverseScrollWheel bool

// Filter
filterInput textinput.Model
Expand Down Expand Up @@ -223,7 +224,7 @@ func initializeDrain3BySeverity() map[string]*Drain3Manager {
}

// NewDashboardModel creates a new dashboard model with stop words
func NewDashboardModel(maxLogBuffer int, updateInterval time.Duration, aiModel string, stopWords map[string]bool) *DashboardModel {
func NewDashboardModel(maxLogBuffer int, updateInterval time.Duration, aiModel string, stopWords map[string]bool, reverseScrollWheel bool) *DashboardModel {
filterInput := textinput.New()
filterInput.Placeholder = "Filter logs (regex supported)..."
filterInput.CharLimit = 200
Expand Down Expand Up @@ -263,6 +264,7 @@ func NewDashboardModel(maxLogBuffer int, updateInterval time.Duration, aiModel s
m := &DashboardModel{
maxLogBuffer: maxLogBuffer,
updateInterval: updateInterval,
reverseScrollWheel: reverseScrollWheel,
filterInput: filterInput,
searchInput: searchInput,
chatInput: chatInput,
Expand Down
178 changes: 137 additions & 41 deletions internal/tui/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,21 @@ func (m *DashboardModel) handleMouseEvent(msg tea.MouseMsg) (tea.Model, tea.Cmd)
return m.handleMouseClick(msg.X, msg.Y)

case tea.MouseButtonWheelUp:
// Scroll wheel up = move selection up (like up arrow)
m.moveSelection(1)
// Scroll wheel up = move selection up (like up arrow), or down if reversed
if m.reverseScrollWheel {
m.moveSelection(-1)
} else {
m.moveSelection(1)
}
return m, nil

case tea.MouseButtonWheelDown:
// Scroll wheel down = move selection down (like down arrow)
m.moveSelection(-1)
// Scroll wheel down = move selection down (like down arrow), or up if reversed
if m.reverseScrollWheel {
m.moveSelection(1)
} else {
m.moveSelection(-1)
}
return m, nil
}
}
Expand All @@ -187,13 +195,21 @@ func (m *DashboardModel) handleModalMouseEvent(msg tea.MouseMsg) (tea.Model, tea
case tea.MouseActionPress:
switch msg.Button {
case tea.MouseButtonWheelUp:
// Scroll up in single modal
m.infoViewport.ScrollUp(1)
// Scroll up in single modal, or down if reversed
if m.reverseScrollWheel {
m.infoViewport.ScrollDown(1)
} else {
m.infoViewport.ScrollUp(1)
}
return m, nil

case tea.MouseButtonWheelDown:
// Scroll down in single modal
m.infoViewport.ScrollDown(1)
// Scroll down in single modal, or up if reversed
if m.reverseScrollWheel {
m.infoViewport.ScrollUp(1)
} else {
m.infoViewport.ScrollDown(1)
}
return m, nil
}
}
Expand All @@ -210,22 +226,46 @@ func (m *DashboardModel) handleModalMouseEvent(msg tea.MouseMsg) (tea.Model, tea
case tea.MouseButtonWheelUp:
// Priority: if chat is active, always scroll chat viewport
if m.chatActive {
m.chatViewport.ScrollUp(1)
if m.reverseScrollWheel {
m.chatViewport.ScrollDown(1)
} else {
m.chatViewport.ScrollUp(1)
}
} else if m.modalActiveSection == "info" {
m.infoViewport.ScrollUp(1)
if m.reverseScrollWheel {
m.infoViewport.ScrollDown(1)
} else {
m.infoViewport.ScrollUp(1)
}
} else if m.modalActiveSection == "chat" {
m.chatViewport.ScrollUp(1)
if m.reverseScrollWheel {
m.chatViewport.ScrollDown(1)
} else {
m.chatViewport.ScrollUp(1)
}
}
return m, nil

case tea.MouseButtonWheelDown:
// Priority: if chat is active, always scroll chat viewport
if m.chatActive {
m.chatViewport.ScrollDown(1)
if m.reverseScrollWheel {
m.chatViewport.ScrollUp(1)
} else {
m.chatViewport.ScrollDown(1)
}
} else if m.modalActiveSection == "info" {
m.infoViewport.ScrollDown(1)
if m.reverseScrollWheel {
m.infoViewport.ScrollUp(1)
} else {
m.infoViewport.ScrollDown(1)
}
} else if m.modalActiveSection == "chat" {
m.chatViewport.ScrollDown(1)
if m.reverseScrollWheel {
m.chatViewport.ScrollUp(1)
} else {
m.chatViewport.ScrollDown(1)
}
}
return m, nil
}
Expand Down Expand Up @@ -292,13 +332,21 @@ func (m *DashboardModel) handleModelSelectionMouseEvent(msg tea.MouseMsg) (tea.M
case tea.MouseActionPress:
switch msg.Button {
case tea.MouseButtonWheelUp:
// Scroll up - move selection up by 1
m.selectedModelIndex = max(0, m.selectedModelIndex-1)
// Scroll up - move selection up by 1, or down if reversed
if m.reverseScrollWheel {
m.selectedModelIndex = min(len(m.availableModelsList)-1, m.selectedModelIndex+1)
} else {
m.selectedModelIndex = max(0, m.selectedModelIndex-1)
}
return m, nil

case tea.MouseButtonWheelDown:
// Scroll down - move selection down by 1
m.selectedModelIndex = min(len(m.availableModelsList)-1, m.selectedModelIndex+1)
// Scroll down - move selection down by 1, or up if reversed
if m.reverseScrollWheel {
m.selectedModelIndex = max(0, m.selectedModelIndex-1)
} else {
m.selectedModelIndex = min(len(m.availableModelsList)-1, m.selectedModelIndex+1)
}
return m, nil
}
}
Expand All @@ -312,13 +360,21 @@ func (m *DashboardModel) handleHelpModalMouseEvent(msg tea.MouseMsg) (tea.Model,
case tea.MouseActionPress:
switch msg.Button {
case tea.MouseButtonWheelUp:
// Scroll up in help modal
m.infoViewport.ScrollUp(1)
// Scroll up in help modal, or down if reversed
if m.reverseScrollWheel {
m.infoViewport.ScrollDown(1)
} else {
m.infoViewport.ScrollUp(1)
}
return m, nil

case tea.MouseButtonWheelDown:
// Scroll down in help modal
m.infoViewport.ScrollDown(1)
// Scroll down in help modal, or up if reversed
if m.reverseScrollWheel {
m.infoViewport.ScrollUp(1)
} else {
m.infoViewport.ScrollDown(1)
}
return m, nil
}
}
Expand All @@ -332,13 +388,21 @@ func (m *DashboardModel) handlePatternsModalMouseEvent(msg tea.MouseMsg) (tea.Mo
case tea.MouseActionPress:
switch msg.Button {
case tea.MouseButtonWheelUp:
// Scroll up in patterns modal
m.infoViewport.ScrollUp(1)
// Scroll up in patterns modal, or down if reversed
if m.reverseScrollWheel {
m.infoViewport.ScrollDown(1)
} else {
m.infoViewport.ScrollUp(1)
}
return m, nil

case tea.MouseButtonWheelDown:
// Scroll down in patterns modal
m.infoViewport.ScrollDown(1)
// Scroll down in patterns modal, or up if reversed
if m.reverseScrollWheel {
m.infoViewport.ScrollUp(1)
} else {
m.infoViewport.ScrollDown(1)
}
return m, nil
}
}
Expand All @@ -352,13 +416,21 @@ func (m *DashboardModel) handleStatsModalMouseEvent(msg tea.MouseMsg) (tea.Model
case tea.MouseActionPress:
switch msg.Button {
case tea.MouseButtonWheelUp:
// Scroll up in statistics modal
m.infoViewport.ScrollUp(1)
// Scroll up in statistics modal, or down if reversed
if m.reverseScrollWheel {
m.infoViewport.ScrollDown(1)
} else {
m.infoViewport.ScrollUp(1)
}
return m, nil

case tea.MouseButtonWheelDown:
// Scroll down in statistics modal
m.infoViewport.ScrollDown(1)
// Scroll down in statistics modal, or up if reversed
if m.reverseScrollWheel {
m.infoViewport.ScrollUp(1)
} else {
m.infoViewport.ScrollDown(1)
}
return m, nil
}
}
Expand All @@ -372,13 +444,21 @@ func (m *DashboardModel) handleCountsModalMouseEvent(msg tea.MouseMsg) (tea.Mode
case tea.MouseActionPress:
switch msg.Button {
case tea.MouseButtonWheelUp:
// Scroll up in counts modal
m.infoViewport.ScrollUp(1)
// Scroll up in counts modal, or down if reversed
if m.reverseScrollWheel {
m.infoViewport.ScrollDown(1)
} else {
m.infoViewport.ScrollUp(1)
}
return m, nil

case tea.MouseButtonWheelDown:
// Scroll down in counts modal
m.infoViewport.ScrollDown(1)
// Scroll down in counts modal, or up if reversed
if m.reverseScrollWheel {
m.infoViewport.ScrollUp(1)
} else {
m.infoViewport.ScrollDown(1)
}
return m, nil
}
}
Expand All @@ -392,16 +472,32 @@ func (m *DashboardModel) handleLogViewerModalMouseEvent(msg tea.MouseMsg) (tea.M
case tea.MouseActionPress:
switch msg.Button {
case tea.MouseButtonWheelUp:
// Navigate up in log list
if m.selectedLogIndex > 0 {
m.selectedLogIndex--
// Scroll up in log viewer - should behave like scrolling content up (move to later/newer logs)
if m.reverseScrollWheel {
// Reversed: wheel up goes to earlier logs
if m.selectedLogIndex > 0 {
m.selectedLogIndex--
}
} else {
// Normal: wheel up goes to later logs (like scrolling list up)
if m.selectedLogIndex < len(m.logEntries)-1 {
m.selectedLogIndex++
}
}
return m, nil

case tea.MouseButtonWheelDown:
// Navigate down in log list
if m.selectedLogIndex < len(m.logEntries)-1 {
m.selectedLogIndex++
// Scroll down in log viewer - should behave like scrolling content down (move to earlier/older logs)
if m.reverseScrollWheel {
// Reversed: wheel down goes to later logs
if m.selectedLogIndex < len(m.logEntries)-1 {
m.selectedLogIndex++
}
} else {
// Normal: wheel down goes to earlier logs (like scrolling list down)
if m.selectedLogIndex > 0 {
m.selectedLogIndex--
}
}
return m, nil
}
Expand Down
Loading