ghostscope_ui/model/
app_state.rs

1use crate::components::command_panel::{OptimizedInputHandler, OptimizedRenderer};
2use crate::components::ebpf_panel::{EbpfPanelHandler, EbpfPanelRenderer};
3use crate::components::loading::{LoadingState, LoadingUI};
4use crate::events::EventRegistry;
5use crate::model::ui_state::LayoutMode;
6use crate::model::{CommandPanelState, EbpfPanelState, SourcePanelState, UIState};
7use crate::ui::EmojiConfig;
8use std::fs::File;
9use std::io::{BufWriter, Write};
10use std::path::PathBuf;
11
12/// Realtime logging state for command session
13#[derive(Debug)]
14pub struct RealtimeSessionLogger {
15    pub enabled: bool,
16    pub file_path: Option<PathBuf>,
17    pub writer: Option<BufWriter<File>>,
18}
19
20impl Default for RealtimeSessionLogger {
21    fn default() -> Self {
22        Self::new()
23    }
24}
25
26impl RealtimeSessionLogger {
27    pub fn new() -> Self {
28        Self {
29            enabled: false,
30            file_path: None,
31            writer: None,
32        }
33    }
34
35    /// Start realtime logging to a file
36    pub fn start(&mut self, file_path: PathBuf) -> anyhow::Result<()> {
37        let file = std::fs::OpenOptions::new()
38            .create(true)
39            .append(true)
40            .open(&file_path)?;
41
42        self.file_path = Some(file_path);
43        self.writer = Some(BufWriter::new(file));
44        self.enabled = true;
45        Ok(())
46    }
47
48    /// Stop realtime logging and flush
49    pub fn stop(&mut self) -> anyhow::Result<()> {
50        if let Some(mut writer) = self.writer.take() {
51            writer.flush()?;
52        }
53        self.enabled = false;
54        self.file_path = None;
55        Ok(())
56    }
57
58    /// Write a line to the log file
59    pub fn write_line(&mut self, line: &str) -> anyhow::Result<()> {
60        if let Some(writer) = &mut self.writer {
61            writeln!(writer, "{line}")?;
62            writer.flush()?; // Flush immediately for realtime logging
63        }
64        Ok(())
65    }
66}
67
68/// Realtime logging state for eBPF output
69#[derive(Debug)]
70pub struct RealtimeOutputLogger {
71    pub enabled: bool,
72    pub file_path: Option<PathBuf>,
73    pub writer: Option<BufWriter<File>>,
74}
75
76impl Default for RealtimeOutputLogger {
77    fn default() -> Self {
78        Self::new()
79    }
80}
81
82impl RealtimeOutputLogger {
83    pub fn new() -> Self {
84        Self {
85            enabled: false,
86            file_path: None,
87            writer: None,
88        }
89    }
90
91    /// Start realtime logging to a file
92    pub fn start(&mut self, file_path: PathBuf) -> anyhow::Result<()> {
93        let file = std::fs::OpenOptions::new()
94            .create(true)
95            .append(true)
96            .open(&file_path)?;
97
98        self.file_path = Some(file_path);
99        self.writer = Some(BufWriter::new(file));
100        self.enabled = true;
101        Ok(())
102    }
103
104    /// Stop realtime logging and flush
105    pub fn stop(&mut self) -> anyhow::Result<()> {
106        if let Some(mut writer) = self.writer.take() {
107            writer.flush()?;
108        }
109        self.enabled = false;
110        self.file_path = None;
111        Ok(())
112    }
113
114    /// Write a line to the log file
115    pub fn write_line(&mut self, line: &str) -> anyhow::Result<()> {
116        if let Some(writer) = &mut self.writer {
117            writeln!(writer, "{line}")?;
118            writer.flush()?; // Flush immediately for realtime logging
119        }
120        Ok(())
121    }
122}
123
124/// Root application state following TEA Model pattern
125#[derive(Debug)]
126pub struct AppState {
127    pub should_quit: bool,
128
129    // Loading state
130    pub loading_state: LoadingState,
131    pub loading_ui: LoadingUI,
132
133    // Panel states
134    pub source_panel: SourcePanelState,
135    pub ebpf_panel: EbpfPanelState,
136    pub command_panel: CommandPanelState,
137
138    // Optimized command panel components
139    pub command_renderer: OptimizedRenderer,
140    pub command_input_handler: OptimizedInputHandler,
141
142    // eBPF panel components
143    pub ebpf_panel_handler: EbpfPanelHandler,
144    pub ebpf_panel_renderer: EbpfPanelRenderer,
145
146    // UI state
147    pub ui: UIState,
148
149    // Panel dimensions cache (updated during render)
150    pub command_panel_width: u16,
151
152    // Event communication
153    pub event_registry: EventRegistry,
154
155    // Special routing flags
156    pub route_file_info_to_file_search: bool,
157
158    // Process information
159    pub target_pid: Option<u32>,
160
161    // UI configuration
162    pub emoji_config: EmojiConfig,
163
164    // Ctrl+C tracking for double-press quit (true if last key was Ctrl+C)
165    pub expecting_second_ctrl_c: bool,
166
167    // Realtime logging for save commands
168    pub realtime_session_logger: RealtimeSessionLogger,
169    pub realtime_output_logger: RealtimeOutputLogger,
170}
171
172impl AppState {
173    pub fn new(event_registry: EventRegistry, layout_mode: LayoutMode) -> Self {
174        Self {
175            should_quit: false,
176            loading_state: LoadingState::Initializing, // Start with loading, wait for runtime response
177            loading_ui: LoadingUI::new(),
178            source_panel: SourcePanelState::new(),
179            ebpf_panel: EbpfPanelState::new(),
180            command_panel: CommandPanelState::new(),
181            command_renderer: OptimizedRenderer::new(),
182            command_input_handler: OptimizedInputHandler::new(),
183            ebpf_panel_handler: EbpfPanelHandler::new(),
184            ebpf_panel_renderer: EbpfPanelRenderer::new(),
185            ui: UIState::new_with_layout_mode(layout_mode),
186            command_panel_width: 80, // Default width, will be updated during render
187            event_registry,
188            route_file_info_to_file_search: false,
189            target_pid: None,
190            emoji_config: EmojiConfig::default(),
191            expecting_second_ctrl_c: false,
192            realtime_session_logger: RealtimeSessionLogger::new(),
193            realtime_output_logger: RealtimeOutputLogger::new(),
194        }
195    }
196
197    pub fn new_with_config(
198        event_registry: EventRegistry,
199        ui_config: crate::model::ui_state::UiConfig,
200    ) -> Self {
201        Self {
202            should_quit: false,
203            loading_state: LoadingState::Initializing, // Start with loading, wait for runtime response
204            loading_ui: LoadingUI::new(),
205            source_panel: SourcePanelState::new(),
206            ebpf_panel: EbpfPanelState::new_with_max_messages(ui_config.ebpf_max_messages),
207            command_panel: CommandPanelState::new_with_config(&ui_config.history),
208            command_renderer: OptimizedRenderer::new(),
209            command_input_handler: OptimizedInputHandler::new(),
210            ebpf_panel_handler: EbpfPanelHandler::new(),
211            ebpf_panel_renderer: EbpfPanelRenderer::new(),
212            ui: UIState::new(ui_config),
213            command_panel_width: 80, // Default width, will be updated during render
214            event_registry,
215            route_file_info_to_file_search: false,
216            target_pid: None,
217            emoji_config: EmojiConfig::default(),
218            expecting_second_ctrl_c: false,
219            realtime_session_logger: RealtimeSessionLogger::new(),
220            realtime_output_logger: RealtimeOutputLogger::new(),
221        }
222    }
223
224    /// Check if application is still in loading phase
225    pub fn is_loading(&self) -> bool {
226        !self.loading_state.is_ready() && !self.loading_state.is_failed()
227    }
228
229    /// Update loading state
230    pub fn set_loading_state(&mut self, state: LoadingState) {
231        tracing::debug!(
232            "Loading state change: {:?} -> {:?}",
233            self.loading_state,
234            state
235        );
236        self.loading_state = state;
237    }
238}