Coverage Report

Created: 2026-04-05 07:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
crates/flotilla-tui/src/widgets/mod.rs
Line
Count
Source
1
pub mod action_menu;
2
pub mod branch_input;
3
pub mod close_confirm;
4
pub mod columns;
5
pub mod command_palette;
6
pub mod delete_confirm;
7
pub mod event_log;
8
pub mod file_picker;
9
pub mod help;
10
pub mod issue_search;
11
pub mod overview_page;
12
pub mod preview_panel;
13
pub mod repo_page;
14
pub mod screen;
15
pub mod section_table;
16
pub mod split_table;
17
pub mod status_bar_widget;
18
pub mod tabs;
19
20
use std::{any::Any, collections::HashMap};
21
22
use crossterm::event::{KeyEvent, MouseEvent};
23
use flotilla_core::config::ConfigStore;
24
use flotilla_protocol::{HostName, ProvisioningTarget, RepoIdentity};
25
use ratatui::{layout::Rect, Frame};
26
27
use crate::{
28
    app::{CommandQueue, InFlightCommand, TuiModel, UiState},
29
    binding_table::{KeyBindingMode, StatusFragment},
30
    keymap::{Action, Keymap},
31
    theme::Theme,
32
};
33
34
/// Human-readable label and protocol key for each provider category.
35
///
36
/// Shared by widgets that render provider status tables (event log, work-item table).
37
pub(crate) const PROVIDER_CATEGORIES: [(&str, &str); 9] = [
38
    ("VCS", "vcs"),
39
    ("Checkout mgr", "checkout_manager"),
40
    ("Change request", "change_request"),
41
    ("Issue tracker", "issue_tracker"),
42
    ("Cloud agents", "cloud_agent"),
43
    ("AI utility", "ai_utility"),
44
    ("Workspace mgr", "workspace_manager"),
45
    ("Terminal pool", "terminal_pool"),
46
    ("Environment", "environment_provider"),
47
];
48
49
/// App-level effects that widgets can request. Processed by the event
50
/// loop after widget dispatch — widgets declare intent, the app executes.
51
#[derive(Debug, Clone)]
52
pub enum AppAction {
53
    Quit,
54
    CancelCommand(u64),
55
    CycleTheme,
56
    SetTheme(String),
57
    CycleLayout,
58
    SetLayout(String),
59
    CycleHost,
60
    SetTarget(String),
61
    ToggleDebug,
62
    ToggleStatusBarKeys,
63
    ToggleProviders,
64
    ToggleMultiSelect,
65
    OpenActionMenu,
66
    ActionEnter,
67
    StatusBarKeyPress { code: crossterm::event::KeyCode, modifiers: crossterm::event::KeyModifiers },
68
    ClearError(usize),
69
    SwitchToConfig,
70
    SwitchToRepo(usize),
71
    SaveTabOrder,
72
    OpenFilePicker,
73
    PrevTab,
74
    NextTab,
75
    MoveTabLeft,
76
    MoveTabRight,
77
    Refresh,
78
    ShowStatus(String),
79
    SetSearchQuery { repo: RepoIdentity, query: String },
80
    ClearSearchQuery { repo: RepoIdentity },
81
}
82
83
/// Result of handling an event in a widget.
84
pub enum Outcome {
85
    /// Event was handled; no further dispatch needed.
86
    Consumed,
87
    /// Event was not handled; try the next widget in the stack.
88
    Ignored,
89
    /// Widget is done; pop it from the stack.
90
    Finished,
91
    /// Push a new widget on top of the current one.
92
    Push(Box<dyn InteractiveWidget>),
93
    /// Pop the current widget and push a replacement.
94
    Swap(Box<dyn InteractiveWidget>),
95
}
96
97
/// Mutable context provided to widgets during event handling.
98
pub struct WidgetContext<'a> {
99
    pub model: &'a TuiModel,
100
    pub keymap: &'a Keymap,
101
    pub config: &'a ConfigStore,
102
    pub in_flight: &'a HashMap<u64, InFlightCommand>,
103
    pub provisioning_target: &'a ProvisioningTarget,
104
    pub my_host: Option<HostName>,
105
    pub active_repo: usize,
106
    pub repo_order: &'a [RepoIdentity],
107
    pub commands: &'a mut CommandQueue,
108
    pub is_config: &'a mut bool,
109
    pub active_repo_is_remote_only: bool,
110
    pub app_actions: Vec<AppAction>,
111
}
112
113
/// Context provided to widgets during rendering.
114
///
115
/// Mutable fields (`ui`) are needed because the base layer rendering updates
116
/// table state and layout areas.
117
pub struct RenderContext<'a> {
118
    pub model: &'a TuiModel,
119
    pub ui: &'a mut UiState,
120
    pub theme: &'a Theme,
121
    pub keymap: &'a Keymap,
122
    pub in_flight: &'a HashMap<u64, InFlightCommand>,
123
}
124
125
/// A self-contained interactive widget that handles events and renders itself.
126
pub trait InteractiveWidget {
127
    /// Handle a resolved keymap action.
128
    fn handle_action(&mut self, action: Action, ctx: &mut WidgetContext) -> Outcome;
129
130
    /// Handle a raw key event (for text input widgets that need every keystroke).
131
2
    fn handle_raw_key(&mut self, _key: KeyEvent, _ctx: &mut WidgetContext) -> Outcome {
132
2
        Outcome::Ignored
133
2
    }
134
135
    /// Handle a mouse event.
136
1
    fn handle_mouse(&mut self, _mouse: MouseEvent, _ctx: &mut WidgetContext) -> Outcome {
137
1
        Outcome::Ignored
138
1
    }
139
140
    /// Render the widget into the given area.
141
    fn render(&mut self, frame: &mut Frame, area: Rect, ctx: &mut RenderContext);
142
143
    /// The binding mode for keymap resolution.
144
    fn binding_mode(&self) -> KeyBindingMode;
145
146
    /// Whether this widget needs raw key events instead of resolved actions.
147
27
    fn captures_raw_keys(&self) -> bool {
148
27
        false
149
27
    }
150
151
    /// Widget-provided status content for the status bar.
152
    ///
153
    /// The default returns an empty `StatusFragment`. Modal widgets should
154
    /// override this to provide mode-specific labels or input text.
155
0
    fn status_fragment(&self) -> StatusFragment {
156
0
        StatusFragment::default()
157
0
    }
158
159
    /// Downcast support for reading widget state from outside the trait.
160
    fn as_any(&self) -> &dyn Any;
161
162
    /// Downcast support for updating widget state from outside the trait.
163
    fn as_any_mut(&mut self) -> &mut dyn Any;
164
}