tui_dispatch_core/debug/
actions.rs

1//! Debug actions and side effects
2
3/// Debug actions provided by tui-dispatch
4///
5/// These are framework-level debug actions that apps can map from their own
6/// action types via keybindings.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum DebugAction {
9    /// Toggle debug freeze mode on/off
10    Toggle,
11    /// Copy frozen frame to clipboard
12    CopyFrame,
13    /// Toggle state overlay
14    ToggleState,
15    /// Toggle action log overlay
16    ToggleActionLog,
17    /// Toggle mouse capture mode for cell inspection
18    ToggleMouseCapture,
19    /// Inspect cell at position (from mouse click)
20    InspectCell { column: u16, row: u16 },
21    /// Close current overlay
22    CloseOverlay,
23    /// Request a new frame capture
24    RequestCapture,
25    /// Scroll action log up
26    ActionLogScrollUp,
27    /// Scroll action log down
28    ActionLogScrollDown,
29    /// Scroll action log to top
30    ActionLogScrollTop,
31    /// Scroll action log to bottom
32    ActionLogScrollBottom,
33}
34
35impl DebugAction {
36    /// Standard command names for keybinding lookup
37    pub const CMD_TOGGLE: &'static str = "debug.toggle";
38    pub const CMD_COPY_FRAME: &'static str = "debug.copy";
39    pub const CMD_TOGGLE_STATE: &'static str = "debug.state";
40    pub const CMD_TOGGLE_ACTION_LOG: &'static str = "debug.action_log";
41    pub const CMD_TOGGLE_MOUSE: &'static str = "debug.mouse";
42    pub const CMD_CLOSE_OVERLAY: &'static str = "debug.close";
43
44    /// Try to parse a command string into a debug action
45    pub fn from_command(cmd: &str) -> Option<Self> {
46        match cmd {
47            Self::CMD_TOGGLE => Some(Self::Toggle),
48            Self::CMD_COPY_FRAME => Some(Self::CopyFrame),
49            Self::CMD_TOGGLE_STATE => Some(Self::ToggleState),
50            Self::CMD_TOGGLE_ACTION_LOG => Some(Self::ToggleActionLog),
51            Self::CMD_TOGGLE_MOUSE => Some(Self::ToggleMouseCapture),
52            Self::CMD_CLOSE_OVERLAY => Some(Self::CloseOverlay),
53            _ => None,
54        }
55    }
56
57    /// Get the command string for this action
58    pub fn command(&self) -> Option<&'static str> {
59        match self {
60            Self::Toggle => Some(Self::CMD_TOGGLE),
61            Self::CopyFrame => Some(Self::CMD_COPY_FRAME),
62            Self::ToggleState => Some(Self::CMD_TOGGLE_STATE),
63            Self::ToggleActionLog => Some(Self::CMD_TOGGLE_ACTION_LOG),
64            Self::ToggleMouseCapture => Some(Self::CMD_TOGGLE_MOUSE),
65            Self::CloseOverlay => Some(Self::CMD_CLOSE_OVERLAY),
66            // These don't have command strings (triggered programmatically)
67            Self::InspectCell { .. }
68            | Self::RequestCapture
69            | Self::ActionLogScrollUp
70            | Self::ActionLogScrollDown
71            | Self::ActionLogScrollTop
72            | Self::ActionLogScrollBottom => None,
73        }
74    }
75}
76
77/// Side effects that the app needs to handle after debug actions
78///
79/// The `DebugLayer` returns these when processing actions that require
80/// app-level handling (clipboard access, mouse capture mode, etc).
81#[derive(Debug)]
82pub enum DebugSideEffect<A> {
83    /// Process queued actions (when exiting debug mode)
84    ///
85    /// These actions were queued while the UI was frozen and should
86    /// now be dispatched through the normal action pipeline.
87    ProcessQueuedActions(Vec<A>),
88
89    /// Copy text to clipboard
90    ///
91    /// The app should use its preferred clipboard mechanism (OSC52, etc).
92    CopyToClipboard(String),
93
94    /// Enable terminal mouse capture
95    ///
96    /// The app should enable mouse event capture for cell inspection.
97    EnableMouseCapture,
98
99    /// Disable terminal mouse capture
100    ///
101    /// The app should disable mouse capture and return to normal mode.
102    DisableMouseCapture,
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_from_command() {
111        assert_eq!(
112            DebugAction::from_command("debug.toggle"),
113            Some(DebugAction::Toggle)
114        );
115        assert_eq!(
116            DebugAction::from_command("debug.copy"),
117            Some(DebugAction::CopyFrame)
118        );
119        assert_eq!(
120            DebugAction::from_command("debug.state"),
121            Some(DebugAction::ToggleState)
122        );
123        assert_eq!(
124            DebugAction::from_command("debug.action_log"),
125            Some(DebugAction::ToggleActionLog)
126        );
127        assert_eq!(DebugAction::from_command("unknown"), None);
128    }
129
130    #[test]
131    fn test_command_roundtrip() {
132        let actions = [
133            DebugAction::Toggle,
134            DebugAction::CopyFrame,
135            DebugAction::ToggleState,
136            DebugAction::ToggleActionLog,
137            DebugAction::ToggleMouseCapture,
138            DebugAction::CloseOverlay,
139        ];
140
141        for action in actions {
142            let cmd = action.command().expect("should have command");
143            let parsed = DebugAction::from_command(cmd).expect("should parse");
144            assert_eq!(parsed, action);
145        }
146    }
147
148    #[test]
149    fn test_scroll_actions_no_command() {
150        // Scroll actions are triggered programmatically, not via commands
151        assert!(DebugAction::ActionLogScrollUp.command().is_none());
152        assert!(DebugAction::ActionLogScrollDown.command().is_none());
153        assert!(DebugAction::ActionLogScrollTop.command().is_none());
154        assert!(DebugAction::ActionLogScrollBottom.command().is_none());
155    }
156}