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    /// Page up in action log
34    ActionLogPageUp,
35    /// Page down in action log
36    ActionLogPageDown,
37    /// Show detail for selected action
38    ActionLogShowDetail,
39    /// Go back from detail view to action log
40    ActionLogBackToList,
41}
42
43impl DebugAction {
44    /// Standard command names for keybinding lookup
45    pub const CMD_TOGGLE: &'static str = "debug.toggle";
46    pub const CMD_COPY_FRAME: &'static str = "debug.copy";
47    pub const CMD_TOGGLE_STATE: &'static str = "debug.state";
48    pub const CMD_TOGGLE_ACTION_LOG: &'static str = "debug.action_log";
49    pub const CMD_TOGGLE_MOUSE: &'static str = "debug.mouse";
50    pub const CMD_CLOSE_OVERLAY: &'static str = "debug.close";
51
52    /// Try to parse a command string into a debug action
53    pub fn from_command(cmd: &str) -> Option<Self> {
54        match cmd {
55            Self::CMD_TOGGLE => Some(Self::Toggle),
56            Self::CMD_COPY_FRAME => Some(Self::CopyFrame),
57            Self::CMD_TOGGLE_STATE => Some(Self::ToggleState),
58            Self::CMD_TOGGLE_ACTION_LOG => Some(Self::ToggleActionLog),
59            Self::CMD_TOGGLE_MOUSE => Some(Self::ToggleMouseCapture),
60            Self::CMD_CLOSE_OVERLAY => Some(Self::CloseOverlay),
61            _ => None,
62        }
63    }
64
65    /// Get the command string for this action
66    pub fn command(&self) -> Option<&'static str> {
67        match self {
68            Self::Toggle => Some(Self::CMD_TOGGLE),
69            Self::CopyFrame => Some(Self::CMD_COPY_FRAME),
70            Self::ToggleState => Some(Self::CMD_TOGGLE_STATE),
71            Self::ToggleActionLog => Some(Self::CMD_TOGGLE_ACTION_LOG),
72            Self::ToggleMouseCapture => Some(Self::CMD_TOGGLE_MOUSE),
73            Self::CloseOverlay => Some(Self::CMD_CLOSE_OVERLAY),
74            // These don't have command strings (triggered programmatically)
75            Self::InspectCell { .. }
76            | Self::RequestCapture
77            | Self::ActionLogScrollUp
78            | Self::ActionLogScrollDown
79            | Self::ActionLogScrollTop
80            | Self::ActionLogScrollBottom
81            | Self::ActionLogPageUp
82            | Self::ActionLogPageDown
83            | Self::ActionLogShowDetail
84            | Self::ActionLogBackToList => None,
85        }
86    }
87}
88
89/// Side effects that the app needs to handle after debug actions
90///
91/// The `DebugLayer` returns these when processing actions that require
92/// app-level handling (clipboard access, queued action processing).
93#[derive(Debug)]
94pub enum DebugSideEffect<A> {
95    /// Process queued actions (when exiting debug mode)
96    ///
97    /// These actions were queued while the UI was frozen and should
98    /// now be dispatched through the normal action pipeline.
99    ProcessQueuedActions(Vec<A>),
100
101    /// Copy text to clipboard
102    ///
103    /// The app should use its preferred clipboard mechanism (OSC52, etc).
104    CopyToClipboard(String),
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_from_command() {
113        assert_eq!(
114            DebugAction::from_command("debug.toggle"),
115            Some(DebugAction::Toggle)
116        );
117        assert_eq!(
118            DebugAction::from_command("debug.copy"),
119            Some(DebugAction::CopyFrame)
120        );
121        assert_eq!(
122            DebugAction::from_command("debug.state"),
123            Some(DebugAction::ToggleState)
124        );
125        assert_eq!(
126            DebugAction::from_command("debug.action_log"),
127            Some(DebugAction::ToggleActionLog)
128        );
129        assert_eq!(DebugAction::from_command("unknown"), None);
130    }
131
132    #[test]
133    fn test_command_roundtrip() {
134        let actions = [
135            DebugAction::Toggle,
136            DebugAction::CopyFrame,
137            DebugAction::ToggleState,
138            DebugAction::ToggleActionLog,
139            DebugAction::ToggleMouseCapture,
140            DebugAction::CloseOverlay,
141        ];
142
143        for action in actions {
144            let cmd = action.command().expect("should have command");
145            let parsed = DebugAction::from_command(cmd).expect("should parse");
146            assert_eq!(parsed, action);
147        }
148    }
149
150    #[test]
151    fn test_scroll_actions_no_command() {
152        // Scroll actions are triggered programmatically, not via commands
153        assert!(DebugAction::ActionLogScrollUp.command().is_none());
154        assert!(DebugAction::ActionLogScrollDown.command().is_none());
155        assert!(DebugAction::ActionLogScrollTop.command().is_none());
156        assert!(DebugAction::ActionLogScrollBottom.command().is_none());
157    }
158}