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::UnboundedReceiver<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        binary: String,
819        script_preview: Option<String>,
820        pc: u64,
821    },
822    /// All trace info with structured data for UI rendering
823    TraceInfoAll {
824        summary: TraceSummaryInfo,
825        traces: Vec<TraceDetailInfo>,
826    },
827    /// Failed to get info for a specific trace
828    TraceInfoFailed {
829        trace_id: u32,
830        error: String,
831    },
832    /// Source file information response (grouped by module)
833    FileInfo {
834        groups: Vec<SourceFileGroup>,
835    },
836    /// Failed to get file information
837    FileInfoFailed {
838        error: String,
839    },
840    /// Traces saved to file successfully
841    TracesSaved {
842        filename: String,
843        saved_count: usize,
844        total_count: usize,
845    },
846    /// Failed to save traces
847    TracesSaveFailed {
848        error: String,
849    },
850    /// Traces loaded from file successfully
851    TracesLoaded {
852        filename: String,
853        total_count: usize,
854        success_count: usize,
855        failed_count: usize,
856        disabled_count: usize,
857        details: Vec<TraceLoadDetail>,
858    },
859    /// Failed to load traces
860    TracesLoadFailed {
861        filename: String,
862        error: String,
863    },
864    /// Shared library information response
865    ShareInfo {
866        libraries: Vec<SharedLibraryInfo>,
867    },
868    /// Failed to get shared library information
869    ShareInfoFailed {
870        error: String,
871    },
872    /// Executable file information response
873    ExecutableFileInfo {
874        file_path: String,
875        file_type: String,
876        entry_point: Option<u64>,
877        has_symbols: bool,
878        has_debug_info: bool,
879        debug_file_path: Option<String>,
880        text_section: Option<SectionInfo>,
881        data_section: Option<SectionInfo>,
882        mode_description: String,
883    },
884    /// Failed to get executable file information
885    ExecutableFileInfoFailed {
886        error: String,
887    },
888    // Module-level loading progress (new)
889    DwarfModuleDiscovered {
890        module_path: String,
891        total_modules: usize,
892    },
893    DwarfModuleLoadingStarted {
894        module_path: String,
895        current: usize,
896        total: usize,
897    },
898    DwarfModuleLoadingCompleted {
899        module_path: String,
900        stats: ModuleLoadingStats,
901        current: usize,
902        total: usize,
903    },
904    DwarfModuleLoadingFailed {
905        module_path: String,
906        error: String,
907        current: usize,
908        total: usize,
909    },
910    SrcPathInfo {
911        info: SourcePathInfo,
912    },
913    SrcPathUpdated {
914        message: String,
915    },
916    SrcPathFailed {
917        error: String,
918    },
919}
920
921/// Statistics for a loaded module
922#[derive(Debug, Clone)]
923pub struct ModuleLoadingStats {
924    pub functions: usize,
925    pub variables: usize,
926    pub types: usize,
927    pub load_time_ms: u64,
928}
929
930/// Summary information for all traces
931#[derive(Debug, Clone)]
932pub struct TraceSummaryInfo {
933    pub total: usize,
934    pub active: usize,
935    pub disabled: usize,
936}
937
938/// Detailed information for a specific trace
939#[derive(Debug, Clone)]
940pub struct TraceDetailInfo {
941    pub trace_id: u32,
942    pub target_display: String,
943    pub binary_path: String,
944    pub pc: u64,
945    pub status: TraceStatus,
946    pub duration: String, // "5m32s", "1h5m", etc.
947}
948
949impl TraceDetailInfo {
950    /// Format trace info line with binary path and PC information
951    pub fn format_line(&self) -> String {
952        // Extract binary name from path for cleaner display
953        let binary_name = std::path::Path::new(&self.binary_path)
954            .file_name()
955            .and_then(|name| name.to_str())
956            .unwrap_or(&self.binary_path);
957
958        format!(
959            "#{} | {}+0x{:x} | {} ({}) ",
960            self.trace_id, binary_name, self.pc, self.target_display, self.status
961        )
962    }
963}
964
965/// Source file information
966#[derive(Debug, Clone)]
967pub struct SourceFileInfo {
968    pub path: String,
969    pub directory: String,
970}
971
972/// Group of source files for a specific module
973#[derive(Debug, Clone)]
974pub struct SourceFileGroup {
975    pub module_path: String,
976    pub files: Vec<SourceFileInfo>,
977}
978
979/// Shared library information (similar to GDB's "info share" output)
980#[derive(Debug, Clone)]
981pub struct SharedLibraryInfo {
982    pub from_address: u64,               // Starting address in memory
983    pub to_address: u64,                 // Ending address in memory
984    pub symbols_read: bool,              // Whether symbols were successfully read
985    pub debug_info_available: bool,      // Whether debug information is available
986    pub library_path: String,            // Full path to the library file
987    pub size: u64,                       // Size of the library in memory
988    pub debug_file_path: Option<String>, // Path to separate debug file (if via .gnu_debuglink)
989}
990
991/// Section information for executable files
992#[derive(Debug, Clone)]
993pub struct SectionInfo {
994    pub start_address: u64, // Starting address of the section
995    pub end_address: u64,   // Ending address of the section
996    pub size: u64,          // Size of the section in bytes
997}
998
999impl EventRegistry {
1000    pub fn new() -> (Self, RuntimeChannels) {
1001        let (command_tx, command_rx) = mpsc::unbounded_channel();
1002        let (trace_tx, trace_rx) = mpsc::unbounded_channel::<ParsedTraceEvent>();
1003        let (status_tx, status_rx) = mpsc::unbounded_channel();
1004
1005        let registry = EventRegistry {
1006            command_sender: command_tx,
1007            trace_receiver: trace_rx,
1008            status_receiver: status_rx,
1009        };
1010
1011        let channels = RuntimeChannels {
1012            command_receiver: command_rx,
1013            trace_sender: trace_tx.clone(),
1014            status_sender: status_tx.clone(),
1015        };
1016
1017        (registry, channels)
1018    }
1019}
1020
1021/// Channels used by the runtime to receive commands and send events
1022#[derive(Debug)]
1023pub struct RuntimeChannels {
1024    pub command_receiver: mpsc::UnboundedReceiver<RuntimeCommand>,
1025    pub trace_sender: mpsc::UnboundedSender<ParsedTraceEvent>,
1026    pub status_sender: mpsc::UnboundedSender<RuntimeStatus>,
1027}
1028
1029impl RuntimeChannels {
1030    /// Create a status sender that can be shared with other tasks
1031    pub fn create_status_sender(&self) -> mpsc::UnboundedSender<RuntimeStatus> {
1032        self.status_sender.clone()
1033    }
1034
1035    /// Create a trace sender that can be shared with other tasks
1036    pub fn create_trace_sender(&self) -> mpsc::UnboundedSender<ParsedTraceEvent> {
1037        self.trace_sender.clone()
1038    }
1039}
1040
1041impl RuntimeStatus {
1042    /// Format TraceInfo for enhanced display
1043    pub fn format_trace_info(&self) -> Option<String> {
1044        match self {
1045            RuntimeStatus::TraceInfo {
1046                trace_id,
1047                target,
1048                status,
1049                pid,
1050                binary,
1051                script_preview,
1052                pc,
1053            } => {
1054                // Header line
1055                let mut result =
1056                    format!("πŸ”Ž Trace [{}] {} {}\n", trace_id, status.to_emoji(), status);
1057
1058                // Collect fields for aligned key-value formatting
1059                let binary_name = std::path::Path::new(binary)
1060                    .file_name()
1061                    .and_then(|name| name.to_str())
1062                    .unwrap_or(binary);
1063
1064                let mut fields: Vec<(&str, String)> = Vec::new();
1065                fields.push(("🎯 Target", target.clone()));
1066                fields.push(("πŸ“¦ Binary", binary.clone()));
1067                fields.push(("πŸ“ Address", format!("{binary_name}+0x{pc:x}")));
1068                if let Some(pid_val) = pid {
1069                    fields.push(("🏷️ PID", pid_val.to_string()));
1070                }
1071                if let Some(ref script) = script_preview {
1072                    fields.push(("πŸ“ Script", script.clone()));
1073                }
1074
1075                // Compute max key width (accounting for emoji display width)
1076                let max_key_width = fields.iter().map(|(k, _)| k.width()).max().unwrap_or(0);
1077
1078                for (key, value) in fields {
1079                    let key_width = key.width();
1080                    let pad = max_key_width.saturating_sub(key_width);
1081                    let spaces = " ".repeat(pad);
1082                    result.push_str(&format!("  {key}{spaces}: {value}\n"));
1083                }
1084
1085                Some(result)
1086            }
1087            _ => None,
1088        }
1089    }
1090
1091    /// Styled version of TraceInfo for display
1092    pub fn format_trace_info_styled(&self) -> Option<Vec<ratatui::text::Line<'static>>> {
1093        use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
1094        use ratatui::text::Line;
1095
1096        match self {
1097            RuntimeStatus::TraceInfo {
1098                trace_id,
1099                target,
1100                status,
1101                pid,
1102                binary,
1103                script_preview: _,
1104                pc,
1105            } => {
1106                let mut lines = Vec::new();
1107
1108                // Title
1109                lines.push(
1110                    StyledLineBuilder::new()
1111                        .title(format!(
1112                            "πŸ”Ž Trace [{}] {} {}",
1113                            trace_id,
1114                            status.to_emoji(),
1115                            status
1116                        ))
1117                        .build(),
1118                );
1119
1120                let binary_name = std::path::Path::new(binary)
1121                    .file_name()
1122                    .and_then(|name| name.to_str())
1123                    .unwrap_or(binary)
1124                    .to_string();
1125
1126                lines.push(
1127                    StyledLineBuilder::new()
1128                        .text("  ")
1129                        .key("🎯 Target:")
1130                        .text(" ")
1131                        .value(target)
1132                        .build(),
1133                );
1134                lines.push(
1135                    StyledLineBuilder::new()
1136                        .text("  ")
1137                        .key("πŸ“¦ Binary:")
1138                        .text(" ")
1139                        .value(binary)
1140                        .build(),
1141                );
1142                lines.push(
1143                    StyledLineBuilder::new()
1144                        .text("  ")
1145                        .key("πŸ“ Address:")
1146                        .text(" ")
1147                        .value(format!("{binary_name}+0x{pc:x}"))
1148                        .build(),
1149                );
1150
1151                if let Some(p) = pid {
1152                    lines.push(
1153                        StyledLineBuilder::new()
1154                            .text("  ")
1155                            .key("🏷️ PID:")
1156                            .text(" ")
1157                            .value(p.to_string())
1158                            .build(),
1159                    );
1160                }
1161
1162                Some(lines)
1163            }
1164            RuntimeStatus::TraceInfoAll { summary, traces } => {
1165                let mut lines = Vec::new();
1166                // Title
1167                lines.push(
1168                    StyledLineBuilder::new()
1169                        .title(format!(
1170                            "πŸ” All Traces ({} total, {} active):",
1171                            summary.total, summary.active
1172                        ))
1173                        .build(),
1174                );
1175                lines.push(Line::from(""));
1176
1177                for t in traces {
1178                    let binary_name = std::path::Path::new(&t.binary_path)
1179                        .file_name()
1180                        .and_then(|name| name.to_str())
1181                        .unwrap_or(&t.binary_path)
1182                        .to_string();
1183                    let status_style = match t.status {
1184                        TraceStatus::Active => StylePresets::SUCCESS,
1185                        TraceStatus::Disabled => StylePresets::LOCATION,
1186                        TraceStatus::Failed => StylePresets::ERROR,
1187                    };
1188                    let line = StyledLineBuilder::new()
1189                        .text("  ")
1190                        .styled(format!("#{}", t.trace_id), StylePresets::ADDRESS)
1191                        .text("  | ")
1192                        .styled(format!("{}+0x{:x}", binary_name, t.pc), StylePresets::KEY)
1193                        .text("  | ")
1194                        .value(&t.target_display)
1195                        .text("  (")
1196                        .styled(t.status.to_string(), status_style)
1197                        .text(")")
1198                        .build();
1199                    lines.push(line);
1200                }
1201
1202                Some(lines)
1203            }
1204            _ => None,
1205        }
1206    }
1207}
1208
1209/// Source path information for display (shared between UI and runtime)
1210#[derive(Debug, Clone)]
1211pub struct SourcePathInfo {
1212    pub substitutions: Vec<PathSubstitution>,
1213    pub search_dirs: Vec<String>,
1214    pub runtime_substitution_count: usize,
1215    pub runtime_search_dir_count: usize,
1216    pub config_substitution_count: usize,
1217    pub config_search_dir_count: usize,
1218}
1219
1220impl SourcePathInfo {
1221    /// Format for display in command panel
1222    pub fn format_for_display(&self) -> String {
1223        let mut output = String::new();
1224
1225        output.push_str("πŸ—‚οΈ  Source Path Configuration:\n\n");
1226
1227        // Path substitutions
1228        if self.substitutions.is_empty() {
1229            output.push_str("Path Substitutions: (none)\n");
1230        } else {
1231            output.push_str(&format!(
1232                "Path Substitutions ({}):\n",
1233                self.substitutions.len()
1234            ));
1235            for (i, sub) in self.substitutions.iter().enumerate() {
1236                let marker = if i < self.runtime_substitution_count {
1237                    "[runtime]"
1238                } else {
1239                    "[config] "
1240                };
1241                output.push_str(&format!("  {} {} -> {}\n", marker, sub.from, sub.to));
1242            }
1243        }
1244
1245        output.push('\n');
1246
1247        // Search directories
1248        if self.search_dirs.is_empty() {
1249            output.push_str("Search Directories: (none)\n");
1250        } else {
1251            output.push_str(&format!(
1252                "Search Directories ({}):\n",
1253                self.search_dirs.len()
1254            ));
1255            for (i, dir) in self.search_dirs.iter().enumerate() {
1256                let marker = if i < self.runtime_search_dir_count {
1257                    "[runtime]"
1258                } else {
1259                    "[config] "
1260                };
1261                output.push_str(&format!("  {marker} {dir}\n"));
1262            }
1263        }
1264
1265        output.push_str("\nπŸ’‘ Runtime rules take precedence over config file rules.\n");
1266        output.push_str(
1267            "πŸ’‘ Use 'srcpath clear' to remove runtime rules, 'srcpath reset' to reset to config.\n",
1268        );
1269
1270        output
1271    }
1272
1273    /// Styled version for display
1274    pub fn format_for_display_styled(&self) -> Vec<ratatui::text::Line<'static>> {
1275        use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
1276        use ratatui::text::Line;
1277
1278        let mut lines = Vec::new();
1279
1280        // Title
1281        lines.push(
1282            StyledLineBuilder::new()
1283                .title("πŸ—‚οΈ  Source Path Configuration:")
1284                .build(),
1285        );
1286        lines.push(Line::from(""));
1287
1288        // Path substitutions
1289        if self.substitutions.is_empty() {
1290            lines.push(
1291                StyledLineBuilder::new()
1292                    .key("Path Substitutions:")
1293                    .text(" (none)")
1294                    .build(),
1295            );
1296        } else {
1297            lines.push(
1298                StyledLineBuilder::new()
1299                    .key(format!(
1300                        "Path Substitutions ({}):",
1301                        self.substitutions.len()
1302                    ))
1303                    .build(),
1304            );
1305            for (i, sub) in self.substitutions.iter().enumerate() {
1306                let marker = if i < self.runtime_substitution_count {
1307                    "[runtime]"
1308                } else {
1309                    "[config] "
1310                };
1311                lines.push(
1312                    StyledLineBuilder::new()
1313                        .text("  ")
1314                        .styled(marker, StylePresets::MARKER)
1315                        .text(" ")
1316                        .value(&sub.from)
1317                        .styled(" -> ", StylePresets::TREE)
1318                        .styled(&sub.to, StylePresets::KEY)
1319                        .build(),
1320                );
1321            }
1322        }
1323
1324        lines.push(Line::from(""));
1325
1326        // Search directories
1327        if self.search_dirs.is_empty() {
1328            lines.push(
1329                StyledLineBuilder::new()
1330                    .key("Search Directories:")
1331                    .text(" (none)")
1332                    .build(),
1333            );
1334        } else {
1335            lines.push(
1336                StyledLineBuilder::new()
1337                    .key(format!("Search Directories ({}):", self.search_dirs.len()))
1338                    .build(),
1339            );
1340            for (i, dir) in self.search_dirs.iter().enumerate() {
1341                let marker = if i < self.runtime_search_dir_count {
1342                    "[runtime]"
1343                } else {
1344                    "[config] "
1345                };
1346                lines.push(
1347                    StyledLineBuilder::new()
1348                        .text("  ")
1349                        .styled(marker, StylePresets::MARKER)
1350                        .text(" ")
1351                        .value(dir)
1352                        .build(),
1353                );
1354            }
1355        }
1356
1357        lines.push(Line::from(""));
1358        lines.push(
1359            StyledLineBuilder::new()
1360                .styled(
1361                    "πŸ’‘ Runtime rules take precedence over config file rules.",
1362                    StylePresets::TIP,
1363                )
1364                .build(),
1365        );
1366        lines.push(
1367            StyledLineBuilder::new()
1368                .styled(
1369                    "πŸ’‘ Use 'srcpath clear' to remove runtime rules, 'srcpath reset' to reset to config.",
1370                    StylePresets::TIP,
1371                )
1372                .build(),
1373        );
1374
1375        lines
1376    }
1377}
1378
1379/// Path substitution rule (shared definition)
1380#[derive(Debug, Clone, PartialEq, Eq)]
1381pub struct PathSubstitution {
1382    pub from: String,
1383    pub to: String,
1384}