1use crossterm::event::{KeyEvent, MouseEvent};
2use ghostscope_protocol::ParsedTraceEvent;
3use tokio::sync::mpsc;
4use unicode_width::UnicodeWidthStr;
5
6#[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 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 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, }
42 }
43}
44
45#[derive(Debug, Clone)]
47pub enum TuiEvent {
48 Key(KeyEvent),
49 Mouse(MouseEvent),
50 Resize(u16, u16),
51 Quit,
52}
53
54#[derive(Debug)]
56pub struct EventRegistry {
57 pub command_sender: mpsc::UnboundedSender<RuntimeCommand>,
59
60 pub trace_receiver: mpsc::UnboundedReceiver<ParsedTraceEvent>,
62 pub status_receiver: mpsc::UnboundedReceiver<RuntimeStatus>,
63}
64
65#[derive(Debug, Clone)]
67pub struct SourceCodeInfo {
68 pub file_path: String,
69 pub current_line: Option<usize>,
70}
71
72#[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>, }
82
83impl TargetDebugInfo {
84 pub fn format_for_display(&self, verbose: bool) -> String {
86 let mut result = String::new();
87
88 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 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 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 if let TargetType::Address = self.target_type {
120 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 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 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 if let TargetType::Address = self.target_type {
179 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#[derive(Debug, Clone)]
218pub struct ModuleDebugInfo {
219 pub binary_path: String,
220 pub address_mappings: Vec<AddressMapping>,
221}
222
223impl ModuleDebugInfo {
224 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 result.push_str(&format!("π¦ {}", &self.binary_path));
236
237 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 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 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 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 pub fn format_variable_line(var: &VariableDebugInfo, verbose: bool) -> String {
348 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 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 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 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#[derive(Debug, Clone)]
437pub struct AddressMapping {
438 pub address: u64,
439 pub binary_path: String, 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>, }
448
449impl AddressMapping {
450 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 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#[derive(Debug, Clone)]
601pub enum TargetType {
602 Function,
603 SourceLocation,
604 Address,
605}
606
607#[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#[derive(Debug, Clone)]
621pub enum RuntimeCommand {
622 ExecuteScript {
623 command: String,
624 selected_index: Option<usize>,
625 },
626 RequestSourceCode, DisableTrace(u32), EnableTrace(u32), DisableAllTraces, EnableAllTraces, DeleteTrace(u32), DeleteAllTraces, InfoFunction {
634 target: String,
635 verbose: bool,
636 }, InfoLine {
638 target: String,
639 verbose: bool,
640 }, InfoAddress {
642 target: String,
643 verbose: bool,
644 }, InfoTrace {
646 trace_id: Option<u32>,
647 }, InfoTraceAll,
649 InfoSource, InfoShare, InfoFile, SaveTraces {
653 filename: Option<String>,
654 filter: crate::components::command_panel::trace_persistence::SaveFilter,
655 }, LoadTraces {
657 filename: String,
658 traces: Vec<TraceDefinition>,
659 }, 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#[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#[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#[derive(Debug, Clone)]
696pub enum LoadStatus {
697 Created, CreatedDisabled, Failed, Skipped, }
702
703#[derive(Debug, Clone)]
705pub enum ExecutionStatus {
706 Success,
707 Failed(String), Skipped(String), }
710
711#[derive(Debug, Clone)]
713pub struct ScriptExecutionResult {
714 pub pc_address: u64,
715 pub target_name: String,
716 pub binary_path: String, pub status: ExecutionStatus,
718 pub source_file: Option<String>,
719 pub source_line: Option<u32>,
720 pub is_inline: Option<bool>,
721}
722
723#[derive(Debug, Clone)]
725pub struct ScriptCompilationDetails {
726 pub trace_ids: Vec<u32>, 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, },
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>, },
762 AllTracesDisabled {
763 count: usize,
764 error: Option<String>, },
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>, },
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 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 TraceInfoAll {
824 summary: TraceSummaryInfo,
825 traces: Vec<TraceDetailInfo>,
826 },
827 TraceInfoFailed {
829 trace_id: u32,
830 error: String,
831 },
832 FileInfo {
834 groups: Vec<SourceFileGroup>,
835 },
836 FileInfoFailed {
838 error: String,
839 },
840 TracesSaved {
842 filename: String,
843 saved_count: usize,
844 total_count: usize,
845 },
846 TracesSaveFailed {
848 error: String,
849 },
850 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 TracesLoadFailed {
861 filename: String,
862 error: String,
863 },
864 ShareInfo {
866 libraries: Vec<SharedLibraryInfo>,
867 },
868 ShareInfoFailed {
870 error: String,
871 },
872 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 ExecutableFileInfoFailed {
886 error: String,
887 },
888 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#[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#[derive(Debug, Clone)]
932pub struct TraceSummaryInfo {
933 pub total: usize,
934 pub active: usize,
935 pub disabled: usize,
936}
937
938#[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, }
948
949impl TraceDetailInfo {
950 pub fn format_line(&self) -> String {
952 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#[derive(Debug, Clone)]
967pub struct SourceFileInfo {
968 pub path: String,
969 pub directory: String,
970}
971
972#[derive(Debug, Clone)]
974pub struct SourceFileGroup {
975 pub module_path: String,
976 pub files: Vec<SourceFileInfo>,
977}
978
979#[derive(Debug, Clone)]
981pub struct SharedLibraryInfo {
982 pub from_address: u64, pub to_address: u64, pub symbols_read: bool, pub debug_info_available: bool, pub library_path: String, pub size: u64, pub debug_file_path: Option<String>, }
990
991#[derive(Debug, Clone)]
993pub struct SectionInfo {
994 pub start_address: u64, pub end_address: u64, pub size: u64, }
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#[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 pub fn create_status_sender(&self) -> mpsc::UnboundedSender<RuntimeStatus> {
1032 self.status_sender.clone()
1033 }
1034
1035 pub fn create_trace_sender(&self) -> mpsc::UnboundedSender<ParsedTraceEvent> {
1037 self.trace_sender.clone()
1038 }
1039}
1040
1041impl RuntimeStatus {
1042 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 let mut result =
1056 format!("π Trace [{}] {} {}\n", trace_id, status.to_emoji(), status);
1057
1058 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 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 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 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 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#[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 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 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 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 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 lines.push(
1282 StyledLineBuilder::new()
1283 .title("ποΈ Source Path Configuration:")
1284 .build(),
1285 );
1286 lines.push(Line::from(""));
1287
1288 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 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#[derive(Debug, Clone, PartialEq, Eq)]
1381pub struct PathSubstitution {
1382 pub from: String,
1383 pub to: String,
1384}