How the transition() Function Maps Keyboard Events to Widget State in tui-logger

The transition() function in gin66/tui-logger serves as a state dispatcher that converts TuiWidgetEvent keyboard inputs into direct mutations of the widget's internal model, toggling visibility flags, adjusting selection indexes, and updating log level configurations.

The gin66/tui-logger crate provides a terminal user interface for filtering and viewing logs in Rust applications. At the heart of its interactive capabilities lies the transition() method, which bridges user input with the widget's rendering state by translating high-level keyboard events into concrete data mutations.

Architecture of the transition() Method

The transition() implementation follows a two-layer architecture that separates the public API from internal state management. The public entry point resides in src/widget/inner.rs at lines 37-38, where TuiWidgetState::transition acquires a lock on the inner mutable state and forwards the event:

pub fn transition(&self, event: TuiWidgetEvent) {
    self.inner.lock().transition(event);
}

The heavy lifting occurs within TuiWidgetInnerState::transition, which matches against the TuiWidgetEvent enum defined in the same file. This method directly mutates fields such as hide_off, selected, and config based on the specific key pressed.

Keyboard Event to State Mappings

The transition() method handles twelve distinct keyboard events, each mapped to specific state mutations that control the logger's display behavior.

Toggle Controls (Space, Hide, Focus)

Three keys control boolean flags that determine what content appears in the widget:

  • SpaceKey: Toggles the global hide off flag using XOR logic (self.hide_off ^= true) at lines 86-88. When enabled, the widget hides log entries filtered out by the current configuration.
  • HideKey (h): Flips the hide_target boolean at lines 89-91, controlling whether the currently selected log target appears in the target list.
  • FocusKey (f): Toggles focus_selected at lines 92-94, enabling a mode where only the selected line receives visual emphasis.

Arrow keys manipulate selection indexes and visibility levels, but only when the target list is not hidden:

  • UpKey: Decrements self.selected by one if !self.hide_target && self.selected > 0, preventing navigation beyond the top of the list (lines 95-99).
  • DownKey: Increments self.selected if within bounds (self.selected + 1 < self.nr_items) and targets are visible (lines 100-104).
  • LeftKey: Decreases the visibility level for the selected target by consuming opt_selected_visibility_less and updating the LevelConfig via self.config.set() (lines 106-110).
  • RightKey: Increases the visibility level using opt_selected_visibility_more with similar config updates (lines 113-119).

Recording Level Adjustments (+ and -)

Unlike visibility levels (which affect display), recording levels control what log data is captured globally:

  • PlusKey: Raises the minimum log level for the selected target by calling set_level_for_target(&selected_target, selected_recording_more) from src/logger/api.rs (lines 121-126).
  • MinusKey: Lowers the recording level using opt_selected_recording_less with the same API (lines 128-133).

Paging and View Controls

Page navigation manipulates center line pointers that control which portion of the log buffer remains visible:

  • PrevPageKey: Sets self.opt_line_pointer_center = self.opt_line_pointer_prev_page to jump to the previous page's center line (line 135).
  • NextPageKey: Advances to the next stored center pointer via self.opt_line_pointer_center = self.opt_line_pointer_next_page (line 136).
  • EscapeKey: Clears the explicit center pointer with self.opt_line_pointer_center = None, returning the view to default scrolling behavior (line 137).

Implementation Details in src/widget/inner.rs

The core logic resides in a single match statement within TuiWidgetInnerState::transition that exhaustively handles the TuiWidgetEvent enum variants. Each arm performs direct field mutations on the TuiWidgetInnerState struct, which holds all UI-related data including selection indexes (selected), visibility flags (hide_off, hide_target, focus_selected), and the LevelConfig instance that persists target-specific settings.

When processing LeftKey, RightKey, PlusKey, or MinusKey, the method first validates that a target is currently selected using helper options like opt_selected_target before attempting configuration updates. This prevents panics when no target is active.

Practical Usage Example

To integrate keyboard handling in your application, create a TuiWidgetState instance and feed it TuiWidgetEvent values from your event loop:

use tui_logger::widget::{TuiWidgetState, TuiWidgetEvent};
use log::LevelFilter;

// Initialize state with default display level
let widget_state = TuiWidgetState::new()
    .set_default_display_level(LevelFilter::Info);

// Simulate keyboard interactions
widget_state.transition(TuiWidgetEvent::UpKey);      // Move selection up
widget_state.transition(TuiWidgetEvent::SpaceKey);   // Toggle hide-off flag
widget_state.transition(TuiWidgetEvent::RightKey);   // Increase visibility level
widget_state.transition(TuiWidgetEvent::PlusKey);    // Raise recording level

The state changes immediately affect how the widget renders during the next draw call, as implemented in the various widget rendering modules that read these internal flags.

Summary

  • The transition() function in src/widget/inner.rs (lines 37-38) acts as the public API entry point that delegates to the inner locked state.
  • Space, h, and f keys toggle boolean flags controlling global hiding, target hiding, and focus modes via XOR operations at lines 86-94.
  • Arrow keys navigate the selection index (lines 95-104) and adjust target-specific visibility levels in the LevelConfig (lines 106-119).
  • Plus and minus keys modify global recording levels by invoking set_level_for_target from src/logger/api.rs (lines 121-133).
  • Page Up, Page Down, and Escape manipulate paging pointers to control which log segment remains centered in the view (lines 135-137).

Frequently Asked Questions

Where is the transition() function implemented in tui-logger?

The primary implementation resides in src/widget/inner.rs within the TuiWidgetInnerState struct. The public TuiWidgetState::transition method at lines 37-38 serves as a thin wrapper that acquires a mutex lock on the inner state before calling the actual logic.

How does the Space key specifically affect widget rendering?

When transition() receives TuiWidgetEvent::SpaceKey, it executes self.hide_off ^= true at lines 86-88. This XOR operation flips the hide_off boolean, which the rendering code checks to determine whether to suppress display of filtered-out log entries, effectively toggling their visibility without changing the underlying filter configuration.

What is the difference between visibility levels and recording levels in tui-logger?

Visibility levels (controlled by Left/Right arrow keys) determine which log levels appear in the TUI display for a specific target, stored in the LevelConfig at lines 106-119. Recording levels (controlled by +/- keys) determine the minimum log level captured by the global logger for that target, persisted via set_level_for_target calls at lines 121-133. You can record DEBUG level logs while only displaying ERROR levels in the widget.

How does transition() handle concurrent keyboard events?

The TuiWidgetState::transition method wraps the inner state in a std::sync::Mutex lock (line 38), ensuring that state mutations occur atomically even when called from multiple threads. This thread-safe design allows the UI event loop to safely mutate state while the rendering thread reads the same data structure.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →