Skip to main content

ghostscope_ui/
events.rs

1use crossterm::event::{KeyEvent, MouseEvent};
2use ghostscope_protocol::ParsedTraceEvent;
3use tokio::sync::mpsc;
4use unicode_width::UnicodeWidthStr;
5
6/// Trace status enumeration for shared use between UI and runtime
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum TraceStatus {
9    Active,
10    Disabled,
11    Failed,
12}
13
14impl std::fmt::Display for TraceStatus {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        match self {
17            TraceStatus::Active => write!(f, "Active"),
18            TraceStatus::Disabled => write!(f, "Disabled"),
19            TraceStatus::Failed => write!(f, "Failed"),
20        }
21    }
22}
23
24impl TraceStatus {
25    /// Convert to emoji representation
26    pub fn to_emoji(&self) -> String {
27        match self {
28            TraceStatus::Active => "βœ…".to_string(),
29            TraceStatus::Disabled => "⏸️".to_string(),
30            TraceStatus::Failed => "❌".to_string(),
31        }
32    }
33
34    /// Parse from string (for backward compatibility)
35    pub fn from_string(s: &str) -> Self {
36        match s {
37            "Active" => TraceStatus::Active,
38            "Disabled" => TraceStatus::Disabled,
39            "Failed" => TraceStatus::Failed,
40            _ => TraceStatus::Failed, // Default to Failed for unknown status
41        }
42    }
43}
44
45/// TUI events that can be handled by the application
46#[derive(Debug, Clone)]
47pub enum TuiEvent {
48    Key(KeyEvent),
49    Mouse(MouseEvent),
50    Resize(u16, u16),
51    Quit,
52}
53
54/// Registry for event communication between TUI and runtime
55#[derive(Debug)]
56pub struct EventRegistry {
57    // TUI -> Runtime communication
58    pub command_sender: mpsc::UnboundedSender<RuntimeCommand>,
59
60    // Runtime -> TUI communication
61    pub trace_receiver: mpsc::Receiver<ParsedTraceEvent>,
62    pub status_receiver: mpsc::UnboundedReceiver<RuntimeStatus>,
63}
64
65/// Source code information for display in TUI
66#[derive(Debug, Clone)]
67pub struct SourceCodeInfo {
68    pub file_path: String,
69    pub current_line: Option<usize>,
70}
71
72/// Debug information for a target (function or source location)
73#[derive(Debug, Clone)]
74pub struct TargetDebugInfo {
75    pub target: String,
76    pub target_type: TargetType,
77    pub file_path: Option<String>,
78    pub line_number: Option<u32>,
79    pub function_name: Option<String>,
80    pub modules: Vec<ModuleDebugInfo>, // Grouped by module/binary
81}
82
83impl TargetDebugInfo {
84    /// Format target debug info with tree-style layout for display
85    pub fn format_for_display(&self, verbose: bool) -> String {
86        let mut result = String::new();
87
88        // Calculate statistics
89        let module_count = self.modules.len();
90        let total_addresses: usize = self
91            .modules
92            .iter()
93            .map(|module| module.address_mappings.len())
94            .sum();
95
96        // Header by target type
97        let header_prefix = match self.target_type {
98            TargetType::Function => "πŸ”§ Function Debug Info",
99            TargetType::SourceLocation => "πŸ“„ Line Debug Info",
100            TargetType::Address => "πŸ“ Address Debug Info",
101        };
102        result.push_str(&format!(
103            "{header_prefix}: {} ({} modules, {} traceable addresses)\n\n",
104            self.target, module_count, total_addresses
105        ));
106
107        // Format modules with tree structure - modules will show their own paths and source info
108        for (module_idx, module) in self.modules.iter().enumerate() {
109            let is_last_module = module_idx == self.modules.len() - 1;
110            result.push_str(&module.format_for_display(
111                is_last_module,
112                &self.file_path,
113                self.line_number,
114                verbose,
115            ));
116        }
117
118        // Suggestions for address targets
119        if let TargetType::Address = self.target_type {
120            // Try to pick the first address for an example
121            let example_addr = self
122                .modules
123                .iter()
124                .flat_map(|m| m.address_mappings.iter())
125                .map(|m| m.address)
126                .next();
127            if let Some(addr) = example_addr {
128                result.push_str("\nπŸ’‘ Tips:\n");
129                result.push_str(&format!(
130                    "  - In '-t <module>' mode: use `trace 0x{addr:x} {{ ... }}` (defaults to that module)\n"
131                ));
132                result.push_str(&format!(
133                    "  - In '-p <pid>' mode: default module is the main executable; for library addresses, start GhostScope with '-t <that .so>' then use `trace 0x{addr:x} {{ ... }}`\n"
134                ));
135            }
136        }
137
138        result
139    }
140
141    /// Styled version for display (pre-styled lines for UI rendering)
142    pub fn format_for_display_styled(&self, verbose: bool) -> Vec<ratatui::text::Line<'static>> {
143        use crate::components::command_panel::style_builder::StyledLineBuilder;
144        use ratatui::text::Line;
145
146        let mut lines = Vec::new();
147
148        // Title line by type
149        let total_addresses: usize = self.modules.iter().map(|m| m.address_mappings.len()).sum();
150        let header_prefix = match self.target_type {
151            TargetType::Function => "πŸ”§ Function Debug Info",
152            TargetType::SourceLocation => "πŸ“„ Line Debug Info",
153            TargetType::Address => "πŸ“ Address Debug Info",
154        };
155        lines.push(
156            StyledLineBuilder::new()
157                .title(format!(
158                    "{header_prefix}: {} ({} modules, {} addresses)",
159                    self.target,
160                    self.modules.len(),
161                    total_addresses
162                ))
163                .build(),
164        );
165        lines.push(Line::from(""));
166
167        for (idx, module) in self.modules.iter().enumerate() {
168            let is_last = idx + 1 == self.modules.len();
169            lines.extend(module.format_for_display_styled(
170                is_last,
171                &self.file_path,
172                self.line_number,
173                verbose,
174            ));
175        }
176
177        // Suggestions for address targets
178        if let TargetType::Address = self.target_type {
179            // Example address from first mapping
180            if let Some(addr) = self
181                .modules
182                .iter()
183                .flat_map(|m| m.address_mappings.iter())
184                .map(|m| m.address)
185                .next()
186            {
187                lines.push(Line::from(""));
188                lines.push(
189                    StyledLineBuilder::new()
190                        .styled(
191                            "πŸ’‘ Tips:",
192                            crate::components::command_panel::style_builder::StylePresets::SECTION,
193                        )
194                        .build(),
195                );
196                lines.push(
197                    StyledLineBuilder::new()
198                        .text("  - In '-t <module>' mode: use ")
199                        .value(format!("trace 0x{addr:x} {{ ... }}"))
200                        .text(" (defaults to that module)")
201                        .build(),
202                );
203                lines.push(
204                    StyledLineBuilder::new()
205                        .text("  - In '-p <pid>' mode: default module is main executable; for library addresses, start with '-t <that .so>' then use ")
206                        .value(format!("trace 0x{addr:x} {{ ... }}"))
207                        .build(),
208                );
209            }
210        }
211
212        lines
213    }
214}
215
216/// Debug information for a module (binary) containing one or more addresses
217#[derive(Debug, Clone)]
218pub struct ModuleDebugInfo {
219    pub binary_path: String,
220    pub address_mappings: Vec<AddressMapping>,
221}
222
223impl ModuleDebugInfo {
224    /// Format module info with tree-style layout for display
225    pub fn format_for_display(
226        &self,
227        is_last_module: bool,
228        source_file: &Option<String>,
229        source_line: Option<u32>,
230        verbose: bool,
231    ) -> String {
232        let mut result = String::new();
233
234        // Module header with full path and source info
235        result.push_str(&format!("πŸ“¦ {}", &self.binary_path));
236
237        // Add source information if available
238        if let Some(ref file) = source_file {
239            if let Some(line) = source_line {
240                result.push_str(&format!(" @ {file}:{line}\n"));
241            } else {
242                result.push_str(&format!(" @ {file}\n"));
243            }
244        } else {
245            result.push('\n');
246        }
247
248        for (addr_idx, mapping) in self.address_mappings.iter().enumerate() {
249            let is_last_addr = addr_idx == self.address_mappings.len() - 1;
250            let addr_prefix = match (is_last_module, is_last_addr) {
251                (true, true) => "   └─",
252                (true, false) => "   β”œβ”€",
253                (false, true) => "β”‚  └─",
254                (false, false) => "β”‚  β”œβ”€",
255            };
256
257            // Enhanced PC address display with optional index + classification/source
258            let mut pc_description = if let Some(i) = mapping.index {
259                format!("[{}] 🎯 0x{:x}", i, mapping.address)
260            } else {
261                format!("🎯 0x{:x}", mapping.address)
262            };
263            if let Some(is_inline) = mapping.is_inline {
264                pc_description
265                    .push_str(&format!(" β€” {}", if is_inline { "inline" } else { "call" }));
266            }
267            if let (Some(ref file), Some(line)) = (&mapping.source_file, mapping.source_line) {
268                pc_description.push_str(&format!(" @ {file}:{line}"));
269            }
270
271            result.push_str(&format!("{addr_prefix} {pc_description}\n"));
272
273            // Format parameters
274            if !mapping.parameters.is_empty() {
275                let param_prefix = match (is_last_module, is_last_addr) {
276                    (true, true) => "      β”œβ”€",
277                    (true, false) => "   β”‚  β”œβ”€",
278                    (false, true) => "β”‚     β”œβ”€",
279                    (false, false) => "β”‚  β”‚  β”œβ”€",
280                };
281
282                result.push_str(&format!("{param_prefix} πŸ“₯ Parameters\n"));
283
284                for (param_idx, param) in mapping.parameters.iter().enumerate() {
285                    let is_last_param =
286                        param_idx == mapping.parameters.len() - 1 && mapping.variables.is_empty();
287                    let item_prefix = match (is_last_module, is_last_addr, is_last_param) {
288                        (true, true, true) => "      β”‚  └─",
289                        (true, true, false) => "      β”‚  β”œβ”€",
290                        (true, false, true) => "   β”‚  β”‚  └─",
291                        (true, false, false) => "   β”‚  β”‚  β”œβ”€",
292                        (false, true, true) => "β”‚     β”‚  └─",
293                        (false, true, false) => "β”‚     β”‚  β”œβ”€",
294                        (false, false, true) => "β”‚  β”‚  β”‚  └─",
295                        (false, false, false) => "β”‚  β”‚  β”‚  β”œβ”€",
296                    };
297
298                    let param_line = Self::format_variable_line(param, verbose);
299
300                    result.push_str(&Self::wrap_long_line(
301                        &format!("{item_prefix} {param_line}"),
302                        80,
303                        item_prefix,
304                    ));
305                }
306            }
307
308            // Format variables
309            if !mapping.variables.is_empty() {
310                let var_prefix = match (is_last_module, is_last_addr) {
311                    (true, true) => "      └─",
312                    (true, false) => "   β”‚  └─",
313                    (false, true) => "β”‚     └─",
314                    (false, false) => "β”‚  β”‚  └─",
315                };
316
317                result.push_str(&format!("{var_prefix} πŸ“¦ Variables\n"));
318
319                for (var_idx, var) in mapping.variables.iter().enumerate() {
320                    let is_last_var = var_idx == mapping.variables.len() - 1;
321                    let item_prefix = match (is_last_module, is_last_addr, is_last_var) {
322                        (true, true, true) => "         └─",
323                        (true, true, false) => "         β”œβ”€",
324                        (true, false, true) => "   β”‚     └─",
325                        (true, false, false) => "   β”‚     β”œβ”€",
326                        (false, true, true) => "β”‚        └─",
327                        (false, true, false) => "β”‚        β”œβ”€",
328                        (false, false, true) => "β”‚  β”‚     └─",
329                        (false, false, false) => "β”‚  β”‚     β”œβ”€",
330                    };
331
332                    let var_line = Self::format_variable_line(var, verbose);
333
334                    result.push_str(&Self::wrap_long_line(
335                        &format!("{item_prefix} {var_line}"),
336                        80,
337                        item_prefix,
338                    ));
339                }
340            }
341        }
342
343        result
344    }
345
346    /// Overload helper: build from VariableDebugInfo
347    pub fn format_variable_line(var: &VariableDebugInfo, verbose: bool) -> String {
348        // Use enhanced DWARF type display (includes type name and size)
349        let type_display = var
350            .type_pretty
351            .as_ref()
352            .filter(|pretty| !pretty.is_empty())
353            .cloned()
354            .unwrap_or_else(|| "unknown".to_string());
355
356        let name = &var.name;
357        if !verbose || var.location_description.is_empty() || var.location_description == "None" {
358            format!("{name} ({type_display})")
359        } else {
360            let location = &var.location_description;
361            format!("{name} ({type_display}) = {location}")
362        }
363    }
364
365    /// Wrap long lines with proper indentation
366    fn wrap_long_line(text: &str, max_width: usize, indent: &str) -> String {
367        if text.len() <= max_width {
368            format!("{text}\n")
369        } else {
370            let mut result = String::new();
371            let mut current_line = text.to_string();
372
373            while current_line.len() > max_width {
374                let break_point = current_line
375                    .rfind(' ')
376                    .unwrap_or(max_width.saturating_sub(10));
377                let (first_part, rest) = current_line.split_at(break_point);
378                result.push_str(&format!("{first_part}\n"));
379
380                // Create continuation line with proper indentation
381                let continuation_indent =
382                    format!("{}   ", indent.replace("β”œβ”€", "β”‚ ").replace("└─", "  "));
383                let trimmed_rest = rest.trim();
384                current_line = format!("{continuation_indent}{trimmed_rest}");
385            }
386
387            if !current_line.trim().is_empty() {
388                result.push_str(&format!("{current_line}\n"));
389            }
390
391            result
392        }
393    }
394}
395
396impl ModuleDebugInfo {
397    /// Styled module info lines
398    pub fn format_for_display_styled(
399        &self,
400        is_last_module: bool,
401        source_file: &Option<String>,
402        source_line: Option<u32>,
403        verbose: bool,
404    ) -> Vec<ratatui::text::Line<'static>> {
405        use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
406
407        let mut lines = Vec::new();
408
409        let mut builder = StyledLineBuilder::new()
410            .styled("πŸ“¦ ", StylePresets::SECTION)
411            .styled(&self.binary_path, StylePresets::SECTION);
412
413        if let Some(ref file) = source_file {
414            builder = builder.text(" @ ").styled(
415                if let Some(line) = source_line {
416                    format!("{file}:{line}")
417                } else {
418                    file.clone()
419                },
420                StylePresets::LOCATION,
421            );
422        }
423
424        lines.push(builder.build());
425
426        for (addr_idx, mapping) in self.address_mappings.iter().enumerate() {
427            let is_last_addr = addr_idx + 1 == self.address_mappings.len();
428            lines.extend(mapping.format_for_display_styled(is_last_module, is_last_addr, verbose));
429        }
430
431        lines
432    }
433}
434
435/// Debug information for a specific address within a module
436#[derive(Debug, Clone)]
437pub struct AddressMapping {
438    pub address: u64,
439    pub binary_path: String, // Full binary path for this address
440    pub function_name: Option<String>,
441    pub variables: Vec<VariableDebugInfo>,
442    pub parameters: Vec<VariableDebugInfo>,
443    pub source_file: Option<String>,
444    pub source_line: Option<u32>,
445    pub is_inline: Option<bool>,
446    pub index: Option<usize>, // 1-based global index for selection
447}
448
449impl AddressMapping {
450    /// Styled address mapping lines with tree prefixes
451    pub fn format_for_display_styled(
452        &self,
453        is_last_module: bool,
454        is_last_addr: bool,
455        verbose: bool,
456    ) -> Vec<ratatui::text::Line<'static>> {
457        use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
458
459        let mut lines = Vec::new();
460
461        let prefix = match (is_last_module, is_last_addr) {
462            (true, true) => "   └─",
463            (true, false) => "   β”œβ”€",
464            (false, true) => "β”‚  └─",
465            (false, false) => "β”‚  β”œβ”€",
466        };
467
468        // Header line with index + address + optional classification and source location
469        let mut header = StyledLineBuilder::new().styled(prefix, StylePresets::TREE);
470        if let Some(i) = self.index {
471            header = header
472                .text(" ")
473                .styled(format!("[{i}]"), StylePresets::ADDRESS);
474        }
475        header = header.text(" 🎯 ").address(self.address);
476
477        if let Some(is_inline) = self.is_inline {
478            header = header
479                .text(" ")
480                .key("β€”")
481                .text(" ")
482                .styled(if is_inline { "inline" } else { "call" }, StylePresets::KEY);
483        }
484        if let (Some(ref file), Some(line)) = (&self.source_file, self.source_line) {
485            header = header
486                .text(" ")
487                .key("@")
488                .text(" ")
489                .value(format!("{file}:{line}"));
490        }
491
492        lines.push(header.build());
493
494        if !self.parameters.is_empty() {
495            let param_prefix = match (is_last_module, is_last_addr) {
496                (true, true) => "      β”œβ”€",
497                (true, false) => "   β”‚  β”œβ”€",
498                (false, true) => "β”‚     β”œβ”€",
499                (false, false) => "β”‚  β”‚  β”œβ”€",
500            };
501
502            lines.push(
503                StyledLineBuilder::new()
504                    .styled(param_prefix, StylePresets::TREE)
505                    .styled(" πŸ“₯ Parameters", StylePresets::SECTION)
506                    .build(),
507            );
508
509            for (param_idx, param) in self.parameters.iter().enumerate() {
510                let is_last_param =
511                    param_idx + 1 == self.parameters.len() && self.variables.is_empty();
512                let item_prefix = match (is_last_module, is_last_addr, is_last_param) {
513                    (true, true, true) => "      β”‚  └─",
514                    (true, true, false) => "      β”‚  β”œβ”€",
515                    (true, false, true) => "   β”‚  β”‚  └─",
516                    (true, false, false) => "   β”‚  β”‚  β”œβ”€",
517                    (false, true, true) => "β”‚     β”‚  └─",
518                    (false, true, false) => "β”‚     β”‚  β”œβ”€",
519                    (false, false, true) => "β”‚  β”‚  β”‚  └─",
520                    (false, false, false) => "β”‚  β”‚  β”‚  β”œβ”€",
521                };
522
523                lines.push(Self::format_variable_styled(item_prefix, param, verbose));
524            }
525        }
526
527        if !self.variables.is_empty() {
528            let var_prefix = match (is_last_module, is_last_addr) {
529                (true, true) => "      └─",
530                (true, false) => "   β”‚  └─",
531                (false, true) => "β”‚     └─",
532                (false, false) => "β”‚  β”‚  └─",
533            };
534
535            lines.push(
536                StyledLineBuilder::new()
537                    .styled(var_prefix, StylePresets::TREE)
538                    .styled(" πŸ“¦ Variables", StylePresets::SECTION)
539                    .build(),
540            );
541
542            for (var_idx, var) in self.variables.iter().enumerate() {
543                let is_last_var = var_idx + 1 == self.variables.len();
544                let item_prefix = match (is_last_module, is_last_addr, is_last_var) {
545                    (true, true, true) => "         └─",
546                    (true, true, false) => "         β”œβ”€",
547                    (true, false, true) => "   β”‚     └─",
548                    (true, false, false) => "   β”‚     β”œβ”€",
549                    (false, true, true) => "β”‚        └─",
550                    (false, true, false) => "β”‚        β”œβ”€",
551                    (false, false, true) => "β”‚  β”‚     └─",
552                    (false, false, false) => "β”‚  β”‚     β”œβ”€",
553                };
554
555                lines.push(Self::format_variable_styled(item_prefix, var, verbose));
556            }
557        }
558
559        lines
560    }
561
562    fn format_variable_styled(
563        indent_prefix: &str,
564        var: &VariableDebugInfo,
565        verbose: bool,
566    ) -> ratatui::text::Line<'static> {
567        use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
568
569        let type_display = var
570            .type_pretty
571            .as_ref()
572            .filter(|s| !s.is_empty())
573            .map(|s| s.as_str())
574            .unwrap_or("unknown");
575
576        let mut builder = StyledLineBuilder::new()
577            .styled(indent_prefix, StylePresets::TREE)
578            .text(" ")
579            .value(&var.name)
580            .key(": ")
581            .styled(type_display, StylePresets::TYPE);
582
583        if let Some(size) = var.size {
584            builder = builder.text(" ").text(format!("({size} bytes)"));
585        }
586
587        if verbose && !var.location_description.is_empty() && var.location_description != "None" {
588            builder = builder
589                .text(" ")
590                .key("@")
591                .text(" ")
592                .styled(&var.location_description, StylePresets::LOCATION);
593        }
594
595        builder.build()
596    }
597}
598
599/// Type of target being inspected
600#[derive(Debug, Clone)]
601pub enum TargetType {
602    Function,
603    SourceLocation,
604    Address,
605}
606
607/// Variable debug information
608#[derive(Debug, Clone)]
609pub struct VariableDebugInfo {
610    pub name: String,
611    pub type_name: String,
612    pub type_pretty: Option<String>,
613    pub location_description: String,
614    pub size: Option<u64>,
615    pub scope_start: Option<u64>,
616    pub scope_end: Option<u64>,
617}
618
619/// Commands that TUI can send to runtime
620#[derive(Debug, Clone)]
621pub enum RuntimeCommand {
622    ExecuteScript {
623        command: String,
624        selected_index: Option<usize>,
625    },
626    RequestSourceCode, // Request source code for current function/address
627    DisableTrace(u32), // Disable specific trace by ID
628    EnableTrace(u32),  // Enable specific trace by ID
629    DisableAllTraces,  // Disable all traces
630    EnableAllTraces,   // Enable all traces
631    DeleteTrace(u32),  // Completely delete specific trace and all resources
632    DeleteAllTraces,   // Delete all traces and resources
633    InfoFunction {
634        target: String,
635        verbose: bool,
636    }, // Get debug info for a function by name
637    InfoLine {
638        target: String,
639        verbose: bool,
640    }, // Get debug info for a source line (file:line)
641    InfoAddress {
642        target: String,
643        verbose: bool,
644    }, // Get debug info for a memory address (TODO: not implemented yet)
645    InfoTrace {
646        trace_id: Option<u32>,
647    }, // Get info for one/all traces (individual messages)
648    InfoTraceAll,
649    InfoSource, // Get all source files information
650    InfoShare,  // Get shared library information (like GDB's "info share")
651    InfoFile,   // Get executable file information and sections (like GDB's "info file")
652    SaveTraces {
653        filename: Option<String>,
654        filter: crate::components::command_panel::trace_persistence::SaveFilter,
655    }, // Save traces to a file
656    LoadTraces {
657        filename: String,
658        traces: Vec<TraceDefinition>,
659    }, // Load traces from a file
660    SrcPathList,
661    SrcPathAddDir {
662        dir: String,
663    },
664    SrcPathAddMap {
665        from: String,
666        to: String,
667    },
668    SrcPathRemove {
669        pattern: String,
670    },
671    SrcPathClear,
672    SrcPathReset,
673    Shutdown,
674}
675
676/// Definition of a trace to be loaded
677#[derive(Debug, Clone)]
678pub struct TraceDefinition {
679    pub target: String,
680    pub script: String,
681    pub enabled: bool,
682    pub selected_index: Option<usize>,
683}
684
685/// Result of loading a single trace
686#[derive(Debug, Clone)]
687pub struct TraceLoadDetail {
688    pub target: String,
689    pub trace_id: Option<u32>,
690    pub status: LoadStatus,
691    pub error: Option<String>,
692}
693
694/// Status of loading a trace
695#[derive(Debug, Clone)]
696pub enum LoadStatus {
697    Created,         // Successfully created and enabled
698    CreatedDisabled, // Created but disabled
699    Failed,          // Failed to create
700    Skipped,         // Skipped (e.g., duplicate)
701}
702
703/// Execution status for individual script targets
704#[derive(Debug, Clone)]
705pub enum ExecutionStatus {
706    Success,
707    Failed(String),  // Contains error message
708    Skipped(String), // Contains reason for skipping
709}
710
711/// Result of executing a single script target (PC/function)
712#[derive(Debug, Clone)]
713pub struct ScriptExecutionResult {
714    pub pc_address: u64,
715    pub target_name: String,
716    pub binary_path: String, // Full path to the binary
717    pub status: ExecutionStatus,
718    pub source_file: Option<String>,
719    pub source_line: Option<u32>,
720    pub is_inline: Option<bool>,
721}
722
723/// Detailed compilation result for a script with multiple targets
724#[derive(Debug, Clone)]
725pub struct ScriptCompilationDetails {
726    pub trace_ids: Vec<u32>, // List of generated trace IDs (one per successful compilation)
727    pub results: Vec<ScriptExecutionResult>,
728    pub total_count: usize,
729    pub success_count: usize,
730    pub failed_count: usize,
731}
732
733#[derive(Debug, Clone)]
734pub enum RuntimeStatus {
735    DwarfLoadingStarted,
736    DwarfLoadingCompleted {
737        symbols_count: usize,
738    },
739    DwarfLoadingFailed(String),
740    ScriptCompilationCompleted {
741        details: ScriptCompilationDetails, // Contains trace_ids, success/failed counts and results
742    },
743    UprobeAttached {
744        function: String,
745        address: u64,
746    },
747    UprobeDetached {
748        function: String,
749    },
750    SourceCodeLoaded(SourceCodeInfo),
751    SourceCodeLoadFailed(String),
752    TraceEnabled {
753        trace_id: u32,
754    },
755    TraceDisabled {
756        trace_id: u32,
757    },
758    AllTracesEnabled {
759        count: usize,
760        error: Option<String>, // Error message if operation completely failed
761    },
762    AllTracesDisabled {
763        count: usize,
764        error: Option<String>, // Error message if operation completely failed
765    },
766    TraceEnableFailed {
767        trace_id: u32,
768        error: String,
769    },
770    TraceDisableFailed {
771        trace_id: u32,
772        error: String,
773    },
774    TraceDeleted {
775        trace_id: u32,
776    },
777    AllTracesDeleted {
778        count: usize,
779        error: Option<String>, // Error message if operation completely failed
780    },
781    TraceDeleteFailed {
782        trace_id: u32,
783        error: String,
784    },
785    InfoFunctionResult {
786        target: String,
787        info: TargetDebugInfo,
788        verbose: bool,
789    },
790    InfoFunctionFailed {
791        target: String,
792        error: String,
793    },
794    InfoLineResult {
795        target: String,
796        info: TargetDebugInfo,
797        verbose: bool,
798    },
799    InfoLineFailed {
800        target: String,
801        error: String,
802    },
803    InfoAddressResult {
804        target: String,
805        info: TargetDebugInfo,
806        verbose: bool,
807    },
808    InfoAddressFailed {
809        target: String,
810        error: String,
811    },
812    /// Detailed info for a trace (summary + PC)
813    TraceInfo {
814        trace_id: u32,
815        target: String,
816        status: TraceStatus,
817        pid: Option<u32>,
818        host_pid: Option<u32>,
819        binary: String,
820        script_preview: Option<String>,
821        pc: u64,
822    },
823    /// All trace info with structured data for UI rendering
824    TraceInfoAll {
825        summary: TraceSummaryInfo,
826        traces: Vec<TraceDetailInfo>,
827    },
828    /// Failed to get info for a specific trace
829    TraceInfoFailed {
830        trace_id: u32,
831        error: String,
832    },
833    /// Source file information response (grouped by module)
834    FileInfo {
835        groups: Vec<SourceFileGroup>,
836    },
837    /// Failed to get file information
838    FileInfoFailed {
839        error: String,
840    },
841    /// Traces saved to file successfully
842    TracesSaved {
843        filename: String,
844        saved_count: usize,
845        total_count: usize,
846    },
847    /// Failed to save traces
848    TracesSaveFailed {
849        error: String,
850    },
851    /// Traces loaded from file successfully
852    TracesLoaded {
853        filename: String,
854        total_count: usize,
855        success_count: usize,
856        failed_count: usize,
857        disabled_count: usize,
858        details: Vec<TraceLoadDetail>,
859    },
860    /// Failed to load traces
861    TracesLoadFailed {
862        filename: String,
863        error: String,
864    },
865    /// Shared library information response
866    ShareInfo {
867        libraries: Vec<SharedLibraryInfo>,
868    },
869    /// Failed to get shared library information
870    ShareInfoFailed {
871        error: String,
872    },
873    /// Executable file information response
874    ExecutableFileInfo {
875        file_path: String,
876        file_type: String,
877        entry_point: Option<u64>,
878        has_symbols: bool,
879        has_debug_info: bool,
880        debug_file_path: Option<String>,
881        text_section: Option<SectionInfo>,
882        data_section: Option<SectionInfo>,
883        mode_description: String,
884    },
885    /// Failed to get executable file information
886    ExecutableFileInfoFailed {
887        error: String,
888    },
889    // Module-level loading progress (new)
890    DwarfModuleDiscovered {
891        module_path: String,
892        total_modules: usize,
893    },
894    DwarfModuleLoadingStarted {
895        module_path: String,
896        current: usize,
897        total: usize,
898    },
899    DwarfModuleLoadingCompleted {
900        module_path: String,
901        stats: ModuleLoadingStats,
902        current: usize,
903        total: usize,
904    },
905    DwarfModuleLoadingFailed {
906        module_path: String,
907        error: String,
908        current: usize,
909        total: usize,
910    },
911    SrcPathInfo {
912        info: SourcePathInfo,
913    },
914    SrcPathUpdated {
915        message: String,
916    },
917    SrcPathFailed {
918        error: String,
919    },
920    /// Runtime->UI trace channel backpressure warning (events dropped)
921    TraceBackpressure {
922        dropped_since_last: u64,
923        dropped_total: u64,
924        queue_capacity: usize,
925    },
926}
927
928/// Statistics for a loaded module
929#[derive(Debug, Clone)]
930pub struct ModuleLoadingStats {
931    pub functions: usize,
932    pub variables: usize,
933    pub types: usize,
934    pub load_time_ms: u64,
935}
936
937/// Summary information for all traces
938#[derive(Debug, Clone)]
939pub struct TraceSummaryInfo {
940    pub total: usize,
941    pub active: usize,
942    pub disabled: usize,
943}
944
945/// Detailed information for a specific trace
946#[derive(Debug, Clone)]
947pub struct TraceDetailInfo {
948    pub trace_id: u32,
949    pub target_display: String,
950    pub binary_path: String,
951    pub pc: u64,
952    pub status: TraceStatus,
953    pub duration: String, // "5m32s", "1h5m", etc.
954}
955
956impl TraceDetailInfo {
957    /// Format trace info line with binary path and PC information
958    pub fn format_line(&self) -> String {
959        // Extract binary name from path for cleaner display
960        let binary_name = std::path::Path::new(&self.binary_path)
961            .file_name()
962            .and_then(|name| name.to_str())
963            .unwrap_or(&self.binary_path);
964
965        format!(
966            "#{} | {}+0x{:x} | {} ({}) ",
967            self.trace_id, binary_name, self.pc, self.target_display, self.status
968        )
969    }
970}
971
972/// Source file information
973#[derive(Debug, Clone)]
974pub struct SourceFileInfo {
975    pub path: String,
976    pub directory: String,
977}
978
979/// Group of source files for a specific module
980#[derive(Debug, Clone)]
981pub struct SourceFileGroup {
982    pub module_path: String,
983    pub files: Vec<SourceFileInfo>,
984}
985
986/// Shared library information (similar to GDB's "info share" output)
987#[derive(Debug, Clone)]
988pub struct SharedLibraryInfo {
989    pub from_address: u64,               // Starting address in memory
990    pub to_address: u64,                 // Ending address in memory
991    pub symbols_read: bool,              // Whether symbols were successfully read
992    pub debug_info_available: bool,      // Whether debug information is available
993    pub library_path: String,            // Full path to the library file
994    pub size: u64,                       // Size of the library in memory
995    pub debug_file_path: Option<String>, // Path to separate debug file (if via .gnu_debuglink)
996}
997
998/// Section information for executable files
999#[derive(Debug, Clone)]
1000pub struct SectionInfo {
1001    pub start_address: u64, // Starting address of the section
1002    pub end_address: u64,   // Ending address of the section
1003    pub size: u64,          // Size of the section in bytes
1004}
1005
1006impl EventRegistry {
1007    pub fn new() -> (Self, RuntimeChannels) {
1008        Self::new_with_trace_capacity(DEFAULT_TRACE_CHANNEL_CAPACITY)
1009    }
1010
1011    pub fn new_with_trace_capacity(trace_capacity: usize) -> (Self, RuntimeChannels) {
1012        let trace_capacity = trace_capacity.max(1);
1013        let (command_tx, command_rx) = mpsc::unbounded_channel();
1014        let (trace_tx, trace_rx) = mpsc::channel::<ParsedTraceEvent>(trace_capacity);
1015        let (status_tx, status_rx) = mpsc::unbounded_channel();
1016
1017        let registry = EventRegistry {
1018            command_sender: command_tx,
1019            trace_receiver: trace_rx,
1020            status_receiver: status_rx,
1021        };
1022
1023        let channels = RuntimeChannels {
1024            command_receiver: command_rx,
1025            trace_sender: trace_tx.clone(),
1026            status_sender: status_tx.clone(),
1027            trace_channel_capacity: trace_capacity,
1028        };
1029
1030        (registry, channels)
1031    }
1032}
1033
1034/// Default queue size for runtime->UI trace events.
1035pub const DEFAULT_TRACE_CHANNEL_CAPACITY: usize = 4096;
1036
1037/// Channels used by the runtime to receive commands and send events
1038#[derive(Debug)]
1039pub struct RuntimeChannels {
1040    pub command_receiver: mpsc::UnboundedReceiver<RuntimeCommand>,
1041    pub trace_sender: mpsc::Sender<ParsedTraceEvent>,
1042    pub status_sender: mpsc::UnboundedSender<RuntimeStatus>,
1043    pub trace_channel_capacity: usize,
1044}
1045
1046impl RuntimeChannels {
1047    /// Create a status sender that can be shared with other tasks
1048    pub fn create_status_sender(&self) -> mpsc::UnboundedSender<RuntimeStatus> {
1049        self.status_sender.clone()
1050    }
1051
1052    /// Create a trace sender that can be shared with other tasks
1053    pub fn create_trace_sender(&self) -> mpsc::Sender<ParsedTraceEvent> {
1054        self.trace_sender.clone()
1055    }
1056}
1057
1058impl RuntimeStatus {
1059    /// Format TraceInfo for enhanced display
1060    pub fn format_trace_info(&self) -> Option<String> {
1061        match self {
1062            RuntimeStatus::TraceInfo {
1063                trace_id,
1064                target,
1065                status,
1066                pid,
1067                host_pid,
1068                binary,
1069                script_preview,
1070                pc,
1071            } => {
1072                // Header line
1073                let mut result =
1074                    format!("πŸ”Ž Trace [{}] {} {}\n", trace_id, status.to_emoji(), status);
1075
1076                // Collect fields for aligned key-value formatting
1077                let binary_name = std::path::Path::new(binary)
1078                    .file_name()
1079                    .and_then(|name| name.to_str())
1080                    .unwrap_or(binary);
1081
1082                let mut fields: Vec<(&str, String)> = Vec::new();
1083                fields.push(("🎯 Target", target.clone()));
1084                fields.push(("πŸ“¦ Binary", binary.clone()));
1085                fields.push(("πŸ“ Address", format!("{binary_name}+0x{pc:x}")));
1086                match (pid, host_pid) {
1087                    (Some(proc_pid), Some(host_pid_val)) if proc_pid != host_pid_val => {
1088                        fields.push(("🏷️ PID(proc)", proc_pid.to_string()));
1089                        fields.push(("🏷️ PID(host)", host_pid_val.to_string()));
1090                    }
1091                    (Some(proc_pid), _) => {
1092                        fields.push(("🏷️ PID", proc_pid.to_string()));
1093                    }
1094                    (None, Some(host_pid_val)) => {
1095                        fields.push(("🏷️ PID(host)", host_pid_val.to_string()));
1096                    }
1097                    (None, None) => {}
1098                }
1099                if let Some(ref script) = script_preview {
1100                    fields.push(("πŸ“ Script", script.clone()));
1101                }
1102
1103                // Compute max key width (accounting for emoji display width)
1104                let max_key_width = fields.iter().map(|(k, _)| k.width()).max().unwrap_or(0);
1105
1106                for (key, value) in fields {
1107                    let key_width = key.width();
1108                    let pad = max_key_width.saturating_sub(key_width);
1109                    let spaces = " ".repeat(pad);
1110                    result.push_str(&format!("  {key}{spaces}: {value}\n"));
1111                }
1112
1113                Some(result)
1114            }
1115            _ => None,
1116        }
1117    }
1118
1119    /// Styled version of TraceInfo for display
1120    pub fn format_trace_info_styled(&self) -> Option<Vec<ratatui::text::Line<'static>>> {
1121        use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
1122        use ratatui::text::Line;
1123
1124        match self {
1125            RuntimeStatus::TraceInfo {
1126                trace_id,
1127                target,
1128                status,
1129                pid,
1130                host_pid,
1131                binary,
1132                script_preview: _,
1133                pc,
1134            } => {
1135                let mut lines = Vec::new();
1136
1137                // Title
1138                lines.push(
1139                    StyledLineBuilder::new()
1140                        .title(format!(
1141                            "πŸ”Ž Trace [{}] {} {}",
1142                            trace_id,
1143                            status.to_emoji(),
1144                            status
1145                        ))
1146                        .build(),
1147                );
1148
1149                let binary_name = std::path::Path::new(binary)
1150                    .file_name()
1151                    .and_then(|name| name.to_str())
1152                    .unwrap_or(binary)
1153                    .to_string();
1154
1155                lines.push(
1156                    StyledLineBuilder::new()
1157                        .text("  ")
1158                        .key("🎯 Target:")
1159                        .text(" ")
1160                        .value(target)
1161                        .build(),
1162                );
1163                lines.push(
1164                    StyledLineBuilder::new()
1165                        .text("  ")
1166                        .key("πŸ“¦ Binary:")
1167                        .text(" ")
1168                        .value(binary)
1169                        .build(),
1170                );
1171                lines.push(
1172                    StyledLineBuilder::new()
1173                        .text("  ")
1174                        .key("πŸ“ Address:")
1175                        .text(" ")
1176                        .value(format!("{binary_name}+0x{pc:x}"))
1177                        .build(),
1178                );
1179
1180                match (pid, host_pid) {
1181                    (Some(proc_pid), Some(host_pid_val)) if proc_pid != host_pid_val => {
1182                        lines.push(
1183                            StyledLineBuilder::new()
1184                                .text("  ")
1185                                .key("🏷️ PID(proc):")
1186                                .text(" ")
1187                                .value(proc_pid.to_string())
1188                                .build(),
1189                        );
1190                        lines.push(
1191                            StyledLineBuilder::new()
1192                                .text("  ")
1193                                .key("🏷️ PID(host):")
1194                                .text(" ")
1195                                .value(host_pid_val.to_string())
1196                                .build(),
1197                        );
1198                    }
1199                    (Some(proc_pid), _) => {
1200                        lines.push(
1201                            StyledLineBuilder::new()
1202                                .text("  ")
1203                                .key("🏷️ PID:")
1204                                .text(" ")
1205                                .value(proc_pid.to_string())
1206                                .build(),
1207                        );
1208                    }
1209                    (None, Some(host_pid_val)) => {
1210                        lines.push(
1211                            StyledLineBuilder::new()
1212                                .text("  ")
1213                                .key("🏷️ PID(host):")
1214                                .text(" ")
1215                                .value(host_pid_val.to_string())
1216                                .build(),
1217                        );
1218                    }
1219                    (None, None) => {}
1220                }
1221
1222                Some(lines)
1223            }
1224            RuntimeStatus::TraceInfoAll { summary, traces } => {
1225                let mut lines = Vec::new();
1226                // Title
1227                lines.push(
1228                    StyledLineBuilder::new()
1229                        .title(format!(
1230                            "πŸ” All Traces ({} total, {} active):",
1231                            summary.total, summary.active
1232                        ))
1233                        .build(),
1234                );
1235                lines.push(Line::from(""));
1236
1237                for t in traces {
1238                    let binary_name = std::path::Path::new(&t.binary_path)
1239                        .file_name()
1240                        .and_then(|name| name.to_str())
1241                        .unwrap_or(&t.binary_path)
1242                        .to_string();
1243                    let status_style = match t.status {
1244                        TraceStatus::Active => StylePresets::SUCCESS,
1245                        TraceStatus::Disabled => StylePresets::LOCATION,
1246                        TraceStatus::Failed => StylePresets::ERROR,
1247                    };
1248                    let line = StyledLineBuilder::new()
1249                        .text("  ")
1250                        .styled(format!("#{}", t.trace_id), StylePresets::ADDRESS)
1251                        .text("  | ")
1252                        .styled(format!("{}+0x{:x}", binary_name, t.pc), StylePresets::KEY)
1253                        .text("  | ")
1254                        .value(&t.target_display)
1255                        .text("  (")
1256                        .styled(t.status.to_string(), status_style)
1257                        .text(")")
1258                        .build();
1259                    lines.push(line);
1260                }
1261
1262                Some(lines)
1263            }
1264            _ => None,
1265        }
1266    }
1267}
1268
1269#[cfg(test)]
1270mod tests {
1271    use super::*;
1272
1273    fn sample_event(trace_id: u64) -> ParsedTraceEvent {
1274        ParsedTraceEvent {
1275            timestamp: 0,
1276            trace_id,
1277            pid: 42,
1278            tid: 42,
1279            instructions: vec![],
1280        }
1281    }
1282
1283    #[test]
1284    fn event_registry_uses_bounded_trace_channel_capacity() {
1285        let (_registry, channels) = EventRegistry::new_with_trace_capacity(1);
1286        assert_eq!(channels.trace_channel_capacity, 1);
1287
1288        channels.trace_sender.try_send(sample_event(1)).unwrap();
1289        assert!(channels.trace_sender.try_send(sample_event(2)).is_err());
1290    }
1291}
1292
1293/// Source path information for display (shared between UI and runtime)
1294#[derive(Debug, Clone)]
1295pub struct SourcePathInfo {
1296    pub substitutions: Vec<PathSubstitution>,
1297    pub search_dirs: Vec<String>,
1298    pub runtime_substitution_count: usize,
1299    pub runtime_search_dir_count: usize,
1300    pub config_substitution_count: usize,
1301    pub config_search_dir_count: usize,
1302}
1303
1304impl SourcePathInfo {
1305    /// Format for display in command panel
1306    pub fn format_for_display(&self) -> String {
1307        let mut output = String::new();
1308
1309        output.push_str("πŸ—‚οΈ  Source Path Configuration:\n\n");
1310
1311        // Path substitutions
1312        if self.substitutions.is_empty() {
1313            output.push_str("Path Substitutions: (none)\n");
1314        } else {
1315            output.push_str(&format!(
1316                "Path Substitutions ({}):\n",
1317                self.substitutions.len()
1318            ));
1319            for (i, sub) in self.substitutions.iter().enumerate() {
1320                let marker = if i < self.runtime_substitution_count {
1321                    "[runtime]"
1322                } else {
1323                    "[config] "
1324                };
1325                output.push_str(&format!("  {} {} -> {}\n", marker, sub.from, sub.to));
1326            }
1327        }
1328
1329        output.push('\n');
1330
1331        // Search directories
1332        if self.search_dirs.is_empty() {
1333            output.push_str("Search Directories: (none)\n");
1334        } else {
1335            output.push_str(&format!(
1336                "Search Directories ({}):\n",
1337                self.search_dirs.len()
1338            ));
1339            for (i, dir) in self.search_dirs.iter().enumerate() {
1340                let marker = if i < self.runtime_search_dir_count {
1341                    "[runtime]"
1342                } else {
1343                    "[config] "
1344                };
1345                output.push_str(&format!("  {marker} {dir}\n"));
1346            }
1347        }
1348
1349        output.push_str("\nπŸ’‘ Runtime rules take precedence over config file rules.\n");
1350        output.push_str(
1351            "πŸ’‘ Use 'srcpath clear' to remove runtime rules, 'srcpath reset' to reset to config.\n",
1352        );
1353
1354        output
1355    }
1356
1357    /// Styled version for display
1358    pub fn format_for_display_styled(&self) -> Vec<ratatui::text::Line<'static>> {
1359        use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
1360        use ratatui::text::Line;
1361
1362        let mut lines = Vec::new();
1363
1364        // Title
1365        lines.push(
1366            StyledLineBuilder::new()
1367                .title("πŸ—‚οΈ  Source Path Configuration:")
1368                .build(),
1369        );
1370        lines.push(Line::from(""));
1371
1372        // Path substitutions
1373        if self.substitutions.is_empty() {
1374            lines.push(
1375                StyledLineBuilder::new()
1376                    .key("Path Substitutions:")
1377                    .text(" (none)")
1378                    .build(),
1379            );
1380        } else {
1381            lines.push(
1382                StyledLineBuilder::new()
1383                    .key(format!(
1384                        "Path Substitutions ({}):",
1385                        self.substitutions.len()
1386                    ))
1387                    .build(),
1388            );
1389            for (i, sub) in self.substitutions.iter().enumerate() {
1390                let marker = if i < self.runtime_substitution_count {
1391                    "[runtime]"
1392                } else {
1393                    "[config] "
1394                };
1395                lines.push(
1396                    StyledLineBuilder::new()
1397                        .text("  ")
1398                        .styled(marker, StylePresets::MARKER)
1399                        .text(" ")
1400                        .value(&sub.from)
1401                        .styled(" -> ", StylePresets::TREE)
1402                        .styled(&sub.to, StylePresets::KEY)
1403                        .build(),
1404                );
1405            }
1406        }
1407
1408        lines.push(Line::from(""));
1409
1410        // Search directories
1411        if self.search_dirs.is_empty() {
1412            lines.push(
1413                StyledLineBuilder::new()
1414                    .key("Search Directories:")
1415                    .text(" (none)")
1416                    .build(),
1417            );
1418        } else {
1419            lines.push(
1420                StyledLineBuilder::new()
1421                    .key(format!("Search Directories ({}):", self.search_dirs.len()))
1422                    .build(),
1423            );
1424            for (i, dir) in self.search_dirs.iter().enumerate() {
1425                let marker = if i < self.runtime_search_dir_count {
1426                    "[runtime]"
1427                } else {
1428                    "[config] "
1429                };
1430                lines.push(
1431                    StyledLineBuilder::new()
1432                        .text("  ")
1433                        .styled(marker, StylePresets::MARKER)
1434                        .text(" ")
1435                        .value(dir)
1436                        .build(),
1437                );
1438            }
1439        }
1440
1441        lines.push(Line::from(""));
1442        lines.push(
1443            StyledLineBuilder::new()
1444                .styled(
1445                    "πŸ’‘ Runtime rules take precedence over config file rules.",
1446                    StylePresets::TIP,
1447                )
1448                .build(),
1449        );
1450        lines.push(
1451            StyledLineBuilder::new()
1452                .styled(
1453                    "πŸ’‘ Use 'srcpath clear' to remove runtime rules, 'srcpath reset' to reset to config.",
1454                    StylePresets::TIP,
1455                )
1456                .build(),
1457        );
1458
1459        lines
1460    }
1461}
1462
1463/// Path substitution rule (shared definition)
1464#[derive(Debug, Clone, PartialEq, Eq)]
1465pub struct PathSubstitution {
1466    pub from: String,
1467    pub to: String,
1468}