1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//! Sync widget state back to MarkdownState.
use ratatui::layout::Rect;
use crate::widgets::markdown_widget::state::MarkdownState;
use crate::widgets::markdown_widget::widget::MarkdownWidget;
/// State captured from MarkdownWidget that needs to be synced back to MarkdownState.
///
/// This struct holds the values that the widget may modify during event handling
/// that need to persist back to the application state.
#[derive(Debug, Clone)]
pub struct WidgetStateSync {
/// The inner area calculated during rendering (inside borders).
pub inner_area: Rect,
/// Whether the TOC is currently hovered.
pub toc_hovered: bool,
/// Index of the hovered TOC entry.
pub toc_hovered_entry: Option<usize>,
/// Scroll offset for the TOC list.
pub toc_scroll_offset: usize,
/// Whether selection mode is active.
pub selection_active: bool,
/// Last double-click info (line number, kind, content).
pub last_double_click: Option<(usize, String, String)>,
/// Current filter text (when in filter mode).
pub filter: Option<String>,
/// Whether filter mode is currently active.
pub filter_mode: bool,
}
impl WidgetStateSync {
/// Create a new sync state with the given inner area.
pub fn new(inner_area: Rect) -> Self {
Self {
inner_area,
toc_hovered: false,
toc_hovered_entry: None,
toc_scroll_offset: 0,
selection_active: false,
last_double_click: None,
filter: None,
filter_mode: false,
}
}
/// Apply this sync state to a MarkdownState.
///
/// # Arguments
///
/// * `state` - The MarkdownState to sync state to
pub fn apply_to(&self, state: &mut MarkdownState) {
state.set_inner_area(self.inner_area);
state.toc_hovered = self.toc_hovered;
state.toc_hovered_entry = self.toc_hovered_entry;
state.toc_scroll_offset = self.toc_scroll_offset;
state.selection_active = self.selection_active;
state.filter = self.filter.clone();
state.filter_mode = self.filter_mode;
}
/// Check if there was a double-click and consume it.
pub fn take_double_click(&mut self) -> Option<(usize, String, String)> {
self.last_double_click.take()
}
}
impl<'a> MarkdownWidget<'a> {
/// Get the state that needs to be synced back to MarkdownState.
///
/// This method captures the TOC and selection state from the widget
/// so it can be synced back after the widget is dropped.
///
/// # Returns
///
/// A `WidgetStateSync` struct containing the state values to sync.
///
/// # Example
///
/// ```rust,ignore
/// let sync_state = {
/// let mut widget = MarkdownWidget::from_state(&content, &mut state).show_toc(true);
/// widget.handle_toc_hover(&mouse, render_area);
/// widget.handle_toc_click(&mouse, render_area);
/// widget.handle_mouse_event(&mouse, render_area);
/// widget.get_state_sync()
/// };
/// sync_state.apply_to(&mut state);
/// ```
pub fn get_state_sync(&mut self) -> WidgetStateSync {
WidgetStateSync {
inner_area: self.inner_area.unwrap_or_default(),
toc_hovered: self.toc_hovered,
toc_hovered_entry: self.toc_hovered_entry,
toc_scroll_offset: self.toc_scroll_offset,
selection_active: self.selection.is_active(),
last_double_click: self.last_double_click.take(),
filter: self.filter.clone(),
filter_mode: self.filter_mode,
}
}
/// Sync widget state back to MarkdownState by consuming self.
///
/// This method consumes the widget and syncs TOC and selection state back to
/// the MarkdownState, ensuring state persistence between frames.
///
/// Call this after handling mouse events to preserve hover and selection state.
///
/// # Arguments
///
/// * `state` - The MarkdownState to sync state back to
///
/// # Example
///
/// ```rust,ignore
/// let mut widget = MarkdownWidget::from_state(&content, &mut state).show_toc(true);
/// widget.handle_toc_hover(&mouse, render_area);
/// widget.handle_toc_click(&mouse, render_area);
/// widget.handle_mouse_event(&mouse, render_area);
/// widget.sync_state_back(&mut state);
/// ```
pub fn sync_state_back(self, state: &mut MarkdownState) {
state.set_inner_area(self.inner_area.unwrap_or_default());
state.toc_hovered = self.toc_hovered;
state.toc_hovered_entry = self.toc_hovered_entry;
state.toc_scroll_offset = self.toc_scroll_offset;
state.selection_active = self.selection.is_active();
state.filter = self.filter;
state.filter_mode = self.filter_mode;
}
}