pub mod action_logger;
pub mod actions;
pub mod cell;
pub mod config;
pub mod format;
pub mod layer;
pub mod scroll_state;
pub mod state;
pub mod table;
pub mod widgets;
pub use actions::{DebugAction, DebugSideEffect};
pub use config::{
default_debug_keybindings, default_debug_keybindings_with_toggle, DebugConfig, DebugStyle,
KeyStyles, ScrollbarStyle, StatusItem,
};
pub use layer::{BannerPosition, DebugLayer, DebugOutcome};
pub use state::{DebugEntry, DebugSection, DebugState, DebugWrapper};
pub use action_logger::{
glob_match, ActionLog, ActionLogConfig, ActionLogEntry, ActionLoggerConfig,
ActionLoggerMiddleware,
};
pub use scroll_state::ScrollState;
pub use cell::{
format_color_compact, format_modifier_compact, inspect_cell, point_in_rect, CellPreview,
};
pub use format::{debug_string, debug_string_compact, debug_string_pretty, pretty_reformat};
pub use table::{
ActionLogDisplayEntry, ActionLogOverlay, ComponentDetailOverlay, ComponentSnapshot,
ComponentsOverlay, DebugOverlay, DebugTableBuilder, DebugTableOverlay, DebugTableRow,
StateEntryDetail,
};
pub use widgets::{
buffer_to_text, dim_buffer, paint_snapshot, ActionLogStyle, ActionLogWidget, BannerItem,
CellPreviewWidget, DebugBanner, DebugTableStyle, DebugTableWidget,
};
use ratatui::buffer::Buffer;
use tui_dispatch_core::keybindings::BindingContext;
use tui_dispatch_core::runtime::{DebugAdapter, RenderContext};
use tui_dispatch_core::{Action, ActionParams};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
pub enum SimpleDebugContext {
#[default]
Normal,
Debug,
}
impl BindingContext for SimpleDebugContext {
fn name(&self) -> &'static str {
match self {
Self::Normal => "normal",
Self::Debug => "debug",
}
}
fn from_name(name: &str) -> Option<Self> {
match name {
"normal" => Some(Self::Normal),
"debug" => Some(Self::Debug),
_ => None,
}
}
fn all() -> &'static [Self] {
&[Self::Normal, Self::Debug]
}
}
#[derive(Debug)]
pub struct DebugFreeze<A> {
pub enabled: bool,
pub pending_capture: bool,
pub snapshot: Option<Buffer>,
pub snapshot_text: String,
pub queued_actions: Vec<A>,
pub message: Option<String>,
pub overlay: Option<DebugOverlay>,
pub mouse_capture_enabled: bool,
}
impl<A> Default for DebugFreeze<A> {
fn default() -> Self {
Self {
enabled: false,
pending_capture: false,
snapshot: None,
snapshot_text: String::new(),
queued_actions: Vec::new(),
message: None,
overlay: None,
mouse_capture_enabled: false,
}
}
}
impl<A> DebugFreeze<A> {
pub fn new() -> Self {
Self::default()
}
pub fn toggle(&mut self) {
if self.enabled {
self.enabled = false;
self.snapshot = None;
self.snapshot_text.clear();
self.overlay = None;
self.message = None;
} else {
self.enabled = true;
self.pending_capture = true;
self.queued_actions.clear();
self.message = None;
}
}
pub fn enable(&mut self) {
if !self.enabled {
self.toggle();
}
}
pub fn disable(&mut self) {
if self.enabled {
self.toggle();
}
}
pub fn capture(&mut self, buffer: &Buffer) {
self.snapshot = Some(buffer.clone());
self.snapshot_text = buffer_to_text(buffer);
self.pending_capture = false;
}
pub fn request_capture(&mut self) {
self.pending_capture = true;
}
pub fn queue(&mut self, action: A) {
self.queued_actions.push(action);
}
pub fn take_queued(&mut self) -> Vec<A> {
std::mem::take(&mut self.queued_actions)
}
pub fn set_message(&mut self, msg: impl Into<String>) {
self.message = Some(msg.into());
}
pub fn clear_message(&mut self) {
self.message = None;
}
pub fn set_overlay(&mut self, overlay: DebugOverlay) {
self.overlay = Some(overlay);
}
pub fn clear_overlay(&mut self) {
self.overlay = None;
}
pub fn toggle_mouse_capture(&mut self) {
self.mouse_capture_enabled = !self.mouse_capture_enabled;
}
}
impl<S, A> DebugAdapter<S, A> for DebugLayer<A>
where
S: DebugState + 'static,
A: Action + ActionParams,
{
fn render(
&mut self,
frame: &mut ratatui::Frame,
state: &S,
render_ctx: RenderContext,
render_fn: &mut dyn FnMut(&mut ratatui::Frame, ratatui::layout::Rect, &S, RenderContext),
) {
self.render_state(frame, state, |f, area| {
render_fn(f, area, state, render_ctx);
});
}
fn handle_event(
&mut self,
event: &tui_dispatch_core::EventKind,
state: &S,
action_tx: &tokio::sync::mpsc::UnboundedSender<A>,
) -> Option<bool> {
self.handle_event_with_state(event, state)
.dispatch_queued(|action| {
let _ = action_tx.send(action);
})
}
fn log_action(&mut self, action: &A) {
DebugLayer::log_action(self, action);
}
fn is_enabled(&self) -> bool {
DebugLayer::is_enabled(self)
}
#[cfg(feature = "tasks")]
fn with_task_manager(self, tasks: &tui_dispatch_core::tasks::TaskManager<A>) -> Self {
DebugLayer::with_task_manager(self, tasks)
}
#[cfg(feature = "subscriptions")]
fn with_subscriptions(self, subs: &tui_dispatch_core::subscriptions::Subscriptions<A>) -> Self {
DebugLayer::with_subscriptions(self, subs)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone)]
enum TestAction {
Foo,
Bar,
}
#[test]
fn test_debug_freeze_toggle() {
let mut freeze: DebugFreeze<TestAction> = DebugFreeze::new();
assert!(!freeze.enabled);
freeze.toggle();
assert!(freeze.enabled);
assert!(freeze.pending_capture);
freeze.toggle();
assert!(!freeze.enabled);
assert!(freeze.snapshot.is_none());
}
#[test]
fn test_debug_freeze_queue() {
let mut freeze: DebugFreeze<TestAction> = DebugFreeze::new();
freeze.enable();
freeze.queue(TestAction::Foo);
freeze.queue(TestAction::Bar);
assert_eq!(freeze.queued_actions.len(), 2);
let queued = freeze.take_queued();
assert_eq!(queued.len(), 2);
assert!(freeze.queued_actions.is_empty());
}
#[test]
fn test_debug_freeze_message() {
let mut freeze: DebugFreeze<TestAction> = DebugFreeze::new();
freeze.set_message("Test message");
assert_eq!(freeze.message, Some("Test message".to_string()));
freeze.clear_message();
assert!(freeze.message.is_none());
}
#[test]
fn test_simple_debug_context_binding_context() {
use tui_dispatch_core::keybindings::BindingContext;
assert_eq!(SimpleDebugContext::Normal.name(), "normal");
assert_eq!(SimpleDebugContext::Debug.name(), "debug");
assert_eq!(
SimpleDebugContext::from_name("normal"),
Some(SimpleDebugContext::Normal)
);
assert_eq!(
SimpleDebugContext::from_name("debug"),
Some(SimpleDebugContext::Debug)
);
assert_eq!(SimpleDebugContext::from_name("invalid"), None);
let all = SimpleDebugContext::all();
assert_eq!(all.len(), 2);
assert!(all.contains(&SimpleDebugContext::Normal));
assert!(all.contains(&SimpleDebugContext::Debug));
}
#[test]
fn test_simple_debug_context_default() {
let ctx: SimpleDebugContext = Default::default();
assert_eq!(ctx, SimpleDebugContext::Normal);
}
}