1use crate::action::{Action, ResponseType};
2use crate::model::panel_state::{CommandPanelState, InteractionMode, ScriptCache, ScriptStatus};
3use crate::ui::emoji::{EmojiConfig, ScriptStatus as EmojiScriptStatus, TraceElement};
4use crate::ui::strings::UIStrings;
5
6#[derive(Debug, Clone)]
8pub struct TraceDetails {
9 pub trace_id: Option<u32>,
10 pub binary_path: Option<String>,
11 pub address: Option<u64>,
12 pub source_file: Option<String>,
13 pub line_number: Option<u32>,
14 pub function_name: Option<String>,
15}
16
17#[derive(Debug, Clone)]
19pub struct TraceErrorDetails {
20 pub compilation_errors: Option<Vec<(u32, String)>>, pub uprobe_error: Option<String>,
22 pub suggestion: Option<String>,
23}
24
25pub struct ScriptEditor;
27
28impl ScriptEditor {
29 pub fn enter_script_mode(state: &mut CommandPanelState, command: &str) -> Vec<Action> {
31 let rest = command.trim_start_matches("trace").trim();
32 let mut parts = rest.split_whitespace();
34 let base_target = parts.next().unwrap_or("");
35 let index_opt = parts.next().and_then(|s| s.parse::<usize>().ok());
36
37 if base_target.is_empty() {
38 let plain =
39 "Usage: trace <function_name|file:line|0xADDR|module_suffix:0xADDR>".to_string();
40 let styled = vec![
41 crate::components::command_panel::style_builder::StyledLineBuilder::new()
42 .styled(
43 plain.clone(),
44 crate::components::command_panel::style_builder::StylePresets::ERROR,
45 )
46 .build(),
47 ];
48 return vec![Action::AddResponseWithStyle {
49 content: plain,
50 styled_lines: Some(styled),
51 response_type: ResponseType::Error,
52 }];
53 }
54
55 let (lines, cursor_line, cursor_col, restored_from_cache) =
57 if let Some(ref cache) = state.script_cache {
58 if let Some(saved_script) = cache.saved_scripts.get(base_target) {
59 let mut lines: Vec<String> =
60 saved_script.content.lines().map(String::from).collect();
61 if lines.is_empty() {
63 lines.push(String::new());
64 }
65 (
66 lines,
67 saved_script.cursor_line,
68 saved_script.cursor_col,
69 true,
70 )
71 } else {
72 (vec![String::new()], 0, 0, false)
73 }
74 } else {
75 (vec![String::new()], 0, 0, false)
76 };
77
78 state.script_cache = Some(ScriptCache {
80 target: base_target.to_string(),
81 original_command: command.to_string(),
82 selected_index: index_opt,
83 lines,
84 cursor_line,
85 cursor_col,
86 status: ScriptStatus::Draft,
87 saved_scripts: state
88 .script_cache
89 .as_ref()
90 .map(|c| c.saved_scripts.clone())
91 .unwrap_or_default(),
92 });
93
94 state.mode = InteractionMode::ScriptEditor;
96
97 let message = if restored_from_cache {
98 if let Some(idx) = index_opt {
99 format!("📝 Script editor opened for '{base_target}' (index {idx}, restored)\nPress Ctrl+S to submit, ESC to cancel")
100 } else {
101 format!("📝 Script editor opened for '{base_target}' (restored from cache)\nPress Ctrl+S to submit, ESC to cancel")
102 }
103 } else if let Some(idx) = index_opt {
104 format!("📝 Script editor opened for '{base_target}' (index {idx})\nPress Ctrl+S to submit, ESC to cancel")
105 } else {
106 format!("📝 Script editor opened for '{base_target}'\nPress Ctrl+S to submit, ESC to cancel")
107 };
108
109 let styled = vec![
110 crate::components::command_panel::style_builder::StyledLineBuilder::new()
111 .styled(
112 message.clone(),
113 crate::components::command_panel::style_builder::StylePresets::TIP,
114 )
115 .build(),
116 ];
117
118 vec![Action::AddResponseWithStyle {
119 content: message,
120 styled_lines: Some(styled),
121 response_type: ResponseType::Info,
122 }]
123 }
124
125 pub fn exit_script_mode(state: &mut CommandPanelState) -> Vec<Action> {
127 if let Some(ref mut cache) = state.script_cache {
129 let script_content = cache.lines.join("\n");
130 cache.saved_scripts.insert(
133 cache.target.clone(),
134 crate::model::panel_state::SavedScript {
135 content: script_content,
136 cursor_line: cache.cursor_line,
137 cursor_col: cache.cursor_col,
138 },
139 );
140 }
141
142 state.mode = InteractionMode::Input;
143
144 let plain = "Script editing cancelled".to_string();
145 let styled = vec![
146 crate::components::command_panel::style_builder::StyledLineBuilder::new()
147 .styled(
148 plain.clone(),
149 crate::components::command_panel::style_builder::StylePresets::WARNING,
150 )
151 .build(),
152 ];
153
154 vec![Action::AddResponseWithStyle {
155 content: plain,
156 styled_lines: Some(styled),
157 response_type: ResponseType::Warning,
158 }]
159 }
160
161 pub fn submit_script(state: &mut CommandPanelState) -> Vec<Action> {
163 if let Some(ref mut cache) = state.script_cache {
164 while cache
166 .lines
167 .last()
168 .is_some_and(|line| line.trim().is_empty())
169 {
170 cache.lines.pop();
171 }
172
173 if cache.lines.is_empty() {
175 cache.lines.push(String::new());
176 }
177
178 let script_content = cache.lines.join("\n");
179 let wrapped_script = if script_content.trim().is_empty() {
181 "{}".to_string()
182 } else {
183 format!("{{{script_content}}}")
184 };
185 let full_script = format!("trace {target} {wrapped_script}", target = cache.target);
186
187 cache.saved_scripts.insert(
189 cache.target.clone(),
190 crate::model::panel_state::SavedScript {
191 content: script_content,
192 cursor_line: cache.cursor_line,
193 cursor_col: cache.cursor_col,
194 },
195 );
196 cache.status = ScriptStatus::Submitted;
197
198 state.input_state = crate::model::panel_state::InputState::WaitingResponse {
200 command: if let Some(idx) = cache.selected_index {
201 format!("trace {} {}", cache.target, idx)
202 } else {
203 format!("trace {}", cache.target)
204 },
205 sent_time: std::time::Instant::now(),
206 command_type: crate::model::panel_state::CommandType::Script,
207 };
208
209 state.mode = InteractionMode::Input;
211
212 return vec![Action::SendRuntimeCommand(
213 crate::action::RuntimeCommand::ExecuteScript {
214 command: full_script,
215 selected_index: cache.selected_index,
216 },
217 )];
218 }
219
220 let plain = "No script to submit".to_string();
221 let styled = vec![
222 crate::components::command_panel::style_builder::StyledLineBuilder::new()
223 .styled(
224 plain.clone(),
225 crate::components::command_panel::style_builder::StylePresets::ERROR,
226 )
227 .build(),
228 ];
229
230 vec![Action::AddResponseWithStyle {
231 content: plain,
232 styled_lines: Some(styled),
233 response_type: ResponseType::Error,
234 }]
235 }
236
237 pub fn clear_script(state: &mut CommandPanelState) -> Vec<Action> {
239 if let Some(ref mut cache) = state.script_cache {
240 cache.lines = vec![String::new()];
241 cache.cursor_line = 0;
242 cache.cursor_col = 0;
243 cache.status = ScriptStatus::Draft;
244 }
245
246 Vec::new()
247 }
248
249 pub fn insert_char(state: &mut CommandPanelState, c: char) -> Vec<Action> {
251 if let Some(ref mut cache) = state.script_cache {
252 if cache.cursor_line < cache.lines.len() {
253 let line = &mut cache.lines[cache.cursor_line];
254 let byte_pos = Self::char_pos_to_byte_pos(line, cache.cursor_col);
255 line.insert(byte_pos, c);
256 cache.cursor_col += 1;
257 }
258 }
259 Vec::new()
260 }
261
262 pub fn insert_text(state: &mut CommandPanelState, text: &str) -> Vec<Action> {
264 if let Some(ref mut cache) = state.script_cache {
265 let normalized = text.replace("\r\n", "\n").replace('\r', "\n");
267 let mut iter = normalized.split('\n');
268
269 if cache.cursor_line >= cache.lines.len() {
270 cache.lines.push(String::new());
271 cache.cursor_line = cache.lines.len() - 1;
272 cache.cursor_col = 0;
273 }
274
275 if let Some(first) = iter.next() {
277 let line = &mut cache.lines[cache.cursor_line];
278 let byte_pos = Self::char_pos_to_byte_pos(line, cache.cursor_col);
279 line.insert_str(byte_pos, first);
280 cache.cursor_col += first.chars().count();
281 }
282
283 let mut is_first_newline = true;
285 for seg in iter {
286 if is_first_newline {
288 let byte_pos = Self::char_pos_to_byte_pos(
289 &cache.lines[cache.cursor_line],
290 cache.cursor_col,
291 );
292 let right = cache.lines[cache.cursor_line].split_off(byte_pos);
293 let new_line = format!("{seg}{right}");
294 cache.cursor_line += 1;
295 cache.lines.insert(cache.cursor_line, new_line);
296 cache.cursor_col = seg.chars().count();
297 is_first_newline = false;
298 } else {
299 cache.cursor_line += 1;
300 cache.lines.insert(cache.cursor_line, seg.to_string());
301 cache.cursor_col = seg.chars().count();
302 }
303 }
304 }
305 Vec::new()
306 }
307
308 pub fn insert_newline(state: &mut CommandPanelState) -> Vec<Action> {
310 if let Some(ref mut cache) = state.script_cache {
311 if cache.cursor_line < cache.lines.len() {
312 let byte_pos =
313 Self::char_pos_to_byte_pos(&cache.lines[cache.cursor_line], cache.cursor_col);
314
315 let current_line = cache.lines[cache.cursor_line].clone();
317 let (left, right) = current_line.split_at(byte_pos);
318 cache.lines[cache.cursor_line] = left.to_string();
319 cache.lines.insert(cache.cursor_line + 1, right.to_string());
320
321 cache.cursor_line += 1;
323 cache.cursor_col = 0;
324 }
325 }
326 Vec::new()
327 }
328
329 pub fn insert_tab(state: &mut CommandPanelState) -> Vec<Action> {
331 if let Some(ref mut cache) = state.script_cache {
332 if cache.cursor_line < cache.lines.len() {
333 let line = &mut cache.lines[cache.cursor_line];
334 let byte_pos = Self::char_pos_to_byte_pos(line, cache.cursor_col);
335 line.insert_str(byte_pos, " ");
336 cache.cursor_col += 4;
337 }
338 }
339 Vec::new()
340 }
341
342 pub fn delete_char(state: &mut CommandPanelState) -> Vec<Action> {
344 if let Some(ref mut cache) = state.script_cache {
345 if cache.cursor_col > 0 {
346 if cache.cursor_line < cache.lines.len() {
348 let line = &mut cache.lines[cache.cursor_line];
349 cache.cursor_col -= 1;
350 let byte_pos = Self::char_pos_to_byte_pos(line, cache.cursor_col);
351 if byte_pos < line.len() {
352 let mut end_pos = byte_pos + 1;
353 while end_pos < line.len() && !line.is_char_boundary(end_pos) {
354 end_pos += 1;
355 }
356 line.drain(byte_pos..end_pos);
357 }
358 }
359 } else if cache.cursor_line > 0 {
360 let current_line = cache.lines.remove(cache.cursor_line);
362 cache.cursor_line -= 1;
363 cache.cursor_col = cache.lines[cache.cursor_line].chars().count();
364 cache.lines[cache.cursor_line].push_str(¤t_line);
365 }
366 }
367 Vec::new()
368 }
369
370 pub fn move_cursor_up(state: &mut CommandPanelState) -> Vec<Action> {
372 if let Some(ref mut cache) = state.script_cache {
373 if cache.cursor_line > 0 {
374 cache.cursor_line -= 1;
375 let line_len = cache.lines[cache.cursor_line].chars().count();
376 cache.cursor_col = cache.cursor_col.min(line_len);
377 }
378 }
379 Vec::new()
380 }
381
382 pub fn move_cursor_down(state: &mut CommandPanelState) -> Vec<Action> {
384 if let Some(ref mut cache) = state.script_cache {
385 if cache.cursor_line + 1 < cache.lines.len() {
386 cache.cursor_line += 1;
387 let line_len = cache.lines[cache.cursor_line].chars().count();
388 cache.cursor_col = cache.cursor_col.min(line_len);
389 }
390 }
391 Vec::new()
392 }
393
394 pub fn move_cursor_left(state: &mut CommandPanelState) -> Vec<Action> {
396 if let Some(ref mut cache) = state.script_cache {
397 if cache.cursor_col > 0 {
398 cache.cursor_col -= 1;
399 } else if cache.cursor_line > 0 {
400 cache.cursor_line -= 1;
402 cache.cursor_col = cache.lines[cache.cursor_line].chars().count();
403 }
404 }
405 Vec::new()
406 }
407
408 pub fn move_cursor_right(state: &mut CommandPanelState) -> Vec<Action> {
410 if let Some(ref mut cache) = state.script_cache {
411 if cache.cursor_line < cache.lines.len() {
412 let line_len = cache.lines[cache.cursor_line].chars().count();
413 if cache.cursor_col < line_len {
414 cache.cursor_col += 1;
415 } else if cache.cursor_line + 1 < cache.lines.len() {
416 cache.cursor_line += 1;
418 cache.cursor_col = 0;
419 }
420 }
421 }
422 Vec::new()
423 }
424
425 pub fn move_to_beginning(state: &mut CommandPanelState) -> Vec<Action> {
427 if let Some(ref mut cache) = state.script_cache {
428 cache.cursor_col = 0;
429 }
430 Vec::new()
431 }
432
433 pub fn move_to_end(state: &mut CommandPanelState) -> Vec<Action> {
435 if let Some(ref mut cache) = state.script_cache {
436 if cache.cursor_line < cache.lines.len() {
437 cache.cursor_col = cache.lines[cache.cursor_line].chars().count();
438 }
439 }
440 Vec::new()
441 }
442
443 pub fn move_to_next_word(state: &mut CommandPanelState) -> Vec<Action> {
445 if let Some(ref mut cache) = state.script_cache {
446 if cache.cursor_line < cache.lines.len() {
447 let line = &cache.lines[cache.cursor_line];
448 let chars: Vec<char> = line.chars().collect();
449 let mut pos = cache.cursor_col;
450
451 while pos < chars.len() && !chars[pos].is_whitespace() {
453 pos += 1;
454 }
455 while pos < chars.len() && chars[pos].is_whitespace() {
457 pos += 1;
458 }
459
460 cache.cursor_col = pos;
461 }
462 }
463 Vec::new()
464 }
465
466 pub fn move_to_previous_word(state: &mut CommandPanelState) -> Vec<Action> {
468 if let Some(ref mut cache) = state.script_cache {
469 if cache.cursor_line < cache.lines.len() {
470 let line = &cache.lines[cache.cursor_line];
471 let chars: Vec<char> = line.chars().collect();
472 let mut pos = cache.cursor_col;
473
474 while pos > 0 && chars[pos - 1].is_whitespace() {
476 pos -= 1;
477 }
478 while pos > 0 && !chars[pos - 1].is_whitespace() {
480 pos -= 1;
481 }
482
483 cache.cursor_col = pos;
484 }
485 }
486 Vec::new()
487 }
488
489 pub fn delete_previous_word(state: &mut CommandPanelState) -> Vec<Action> {
491 if let Some(ref mut cache) = state.script_cache {
492 if cache.cursor_line < cache.lines.len() {
493 let line = &mut cache.lines[cache.cursor_line];
494 let start_pos = cache.cursor_col;
495 let mut end_pos = start_pos;
496
497 let chars: Vec<char> = line.chars().collect();
499 while end_pos > 0 && chars[end_pos - 1].is_whitespace() {
500 end_pos -= 1;
501 }
502 while end_pos > 0 && !chars[end_pos - 1].is_whitespace() {
503 end_pos -= 1;
504 }
505
506 let start_byte = Self::char_pos_to_byte_pos(line, end_pos);
508 let end_byte = Self::char_pos_to_byte_pos(line, start_pos);
509
510 line.drain(start_byte..end_byte);
512 cache.cursor_col = end_pos;
513 }
514 }
515 Vec::new()
516 }
517
518 pub fn delete_to_end(state: &mut CommandPanelState) -> Vec<Action> {
520 if let Some(ref mut cache) = state.script_cache {
521 if cache.cursor_line < cache.lines.len() {
522 let line = &mut cache.lines[cache.cursor_line];
523 let byte_pos = Self::char_pos_to_byte_pos(line, cache.cursor_col);
524 line.truncate(byte_pos);
525 }
526 }
527 Vec::new()
528 }
529
530 pub fn delete_to_line_start(state: &mut CommandPanelState) -> Vec<Action> {
532 if let Some(ref mut cache) = state.script_cache {
533 if cache.cursor_line < cache.lines.len() {
534 let line = &mut cache.lines[cache.cursor_line];
535 let byte_pos = Self::char_pos_to_byte_pos(line, cache.cursor_col);
536 let remaining = line[byte_pos..].to_string();
537 cache.lines[cache.cursor_line] = remaining;
538 cache.cursor_col = 0;
539 }
540 }
541 Vec::new()
542 }
543
544 pub fn delete_to_beginning(state: &mut CommandPanelState) -> Vec<Action> {
546 Self::delete_to_line_start(state)
547 }
548
549 pub fn delete_char_at_cursor(state: &mut CommandPanelState) -> Vec<Action> {
551 if let Some(ref mut cache) = state.script_cache {
552 if cache.cursor_line < cache.lines.len() && cache.cursor_col > 0 {
553 let line = &mut cache.lines[cache.cursor_line];
554 let char_pos = cache.cursor_col - 1;
555 let byte_pos = Self::char_pos_to_byte_pos(line, char_pos);
556 let next_byte_pos = Self::char_pos_to_byte_pos(line, cache.cursor_col);
557
558 line.drain(byte_pos..next_byte_pos);
560 cache.cursor_col = char_pos;
561 }
562 }
563 Vec::new()
564 }
565
566 pub fn can_edit_script(state: &CommandPanelState) -> bool {
568 state
569 .script_cache
570 .as_ref()
571 .is_some_and(|cache| cache.status == ScriptStatus::Submitted)
572 }
573
574 pub fn edit_script_again(state: &mut CommandPanelState) -> Vec<Action> {
576 if let Some(ref mut cache) = state.script_cache {
577 if cache.status == ScriptStatus::Submitted {
578 cache.status = ScriptStatus::Draft;
579 state.mode = InteractionMode::ScriptEditor;
580
581 let plain = format!("📝 Re-editing script for '{}'", cache.target);
582 let styled = vec![
583 crate::components::command_panel::style_builder::StyledLineBuilder::new()
584 .styled(
585 plain.clone(),
586 crate::components::command_panel::style_builder::StylePresets::TIP,
587 )
588 .build(),
589 ];
590
591 return vec![Action::AddResponseWithStyle {
592 content: plain,
593 styled_lines: Some(styled),
594 response_type: ResponseType::Info,
595 }];
596 }
597 }
598 Vec::new()
599 }
600
601 pub fn format_script_display_with_config(
603 target: &str,
604 lines: &[String],
605 emoji_config: &EmojiConfig,
606 ) -> String {
607 let mut result = Vec::new();
608
609 let target_emoji = emoji_config.get_trace_element(TraceElement::Target);
611
612 result.push(format!(
613 "{} {} {}",
614 target_emoji,
615 UIStrings::SCRIPT_TARGET_PREFIX,
616 target
617 ));
618 result.push(UIStrings::SCRIPT_SEPARATOR.repeat(50));
619
620 if lines.is_empty() || (lines.len() == 1 && lines[0].trim().is_empty()) {
622 result.push(format!(
623 " {} No script content",
624 emoji_config.get_script_status(EmojiScriptStatus::Error)
625 ));
626 } else {
627 for (line_idx, line) in lines.iter().enumerate() {
628 if line.trim().is_empty() {
629 result.push(format!("{:3} │", line_idx + 1));
630 } else {
631 result.push(format!("{:3} │ {line}", line_idx + 1));
632 }
633 }
634 }
635
636 result.push(UIStrings::SCRIPT_SEPARATOR.repeat(50));
637
638 let compile_emoji = emoji_config.get_script_status(EmojiScriptStatus::Compiling);
640 result.push(format!("{compile_emoji} Compiling and loading script..."));
641
642 result.join("\n")
643 }
644
645 pub fn format_trace_success_response(
647 target: &str,
648 details: Option<&TraceDetails>,
649 emoji_config: &EmojiConfig,
650 ) -> String {
651 Self::format_trace_success_response_with_script(target, details, None, emoji_config)
652 }
653
654 pub fn format_trace_success_response_with_script(
655 target: &str,
656 details: Option<&TraceDetails>,
657 script_content: Option<&str>,
658 emoji_config: &EmojiConfig,
659 ) -> String {
660 let mut result = Vec::new();
661
662 if let Some(script) = script_content {
664 let script_lines = Self::format_script_display_section(script, emoji_config);
665 result.extend(script_lines);
666 }
667
668 let target_emoji = emoji_config.get_trace_element(crate::ui::emoji::TraceElement::Target);
670 result.push(format!("{target_emoji} Target: {target}"));
671
672 result.push("".to_string());
674
675 let success_emoji = emoji_config.get_script_status(crate::ui::emoji::ScriptStatus::Success);
677
678 if let Some(details) = details {
679 let address_display = if let Some(address) = details.address {
680 format!("(0x{address:x})")
681 } else {
682 "".to_string()
683 };
684
685 result.push(format!(
686 "{success_emoji} Trace Results: \x1b[32m1 successful\x1b[0m, 0 failed"
687 ));
688
689 if let Some(trace_id) = details.trace_id {
690 result.push(format!(
691 " • {target} {address_display} → trace_id: {trace_id}"
692 ));
693 } else {
694 result.push(format!(" • {target} {address_display} → trace attached"));
695 }
696 } else {
697 result.push(format!(
698 "{success_emoji} Trace Results: \x1b[32m1 successful\x1b[0m, 0 failed"
699 ));
700 result.push(format!(" • {target} → trace attached"));
701 }
702
703 result.join("\n")
704 }
705
706 pub fn format_compilation_results(
708 compilation_details: &crate::events::ScriptCompilationDetails,
709 script_content: Option<&str>,
710 emoji_config: &EmojiConfig,
711 ) -> String {
712 let mut result = Vec::new();
713
714 if let Some(script) = script_content {
716 let script_lines = Self::format_script_display_section(script, emoji_config);
717 result.extend(script_lines);
718 }
719
720 let target_emoji = emoji_config.get_trace_element(crate::ui::emoji::TraceElement::Target);
722 let target = compilation_details
723 .results
724 .first()
725 .map(|r| r.target_name.clone())
726 .unwrap_or_else(|| "unknown".to_string());
727 result.push(format!("{target_emoji} Target: {target}"));
728
729 result.push("".to_string());
731
732 let success_emoji = emoji_config.get_script_status(crate::ui::emoji::ScriptStatus::Success);
734 let error_emoji = emoji_config.get_script_status(crate::ui::emoji::ScriptStatus::Error);
735
736 let summary_emoji = if compilation_details.failed_count > 0 {
737 error_emoji
738 } else {
739 success_emoji
740 };
741
742 let success_part = if compilation_details.success_count > 0 {
744 format!(
745 "\x1b[32m{} successful\x1b[0m",
746 compilation_details.success_count
747 )
748 } else {
749 format!("{} successful", compilation_details.success_count)
750 };
751
752 let failed_part = if compilation_details.failed_count > 0 {
753 format!("\x1b[31m{} failed\x1b[0m", compilation_details.failed_count)
754 } else {
755 format!("{} failed", compilation_details.failed_count)
756 };
757
758 result.push(format!(
759 "{summary_emoji} Trace Results: {success_part}, {failed_part}"
760 ));
761
762 let mut trace_idx = 0;
764 for exec_result in &compilation_details.results {
765 match &exec_result.status {
766 crate::events::ExecutionStatus::Success => {
767 let trace_id = compilation_details.trace_ids.get(trace_idx).copied();
768 let class_part = match exec_result.is_inline {
770 Some(true) => "inline",
771 Some(false) => "call",
772 None => "",
773 };
774 let src_part = match (&exec_result.source_file, exec_result.source_line) {
775 (Some(f), Some(l)) => format!(" @ {f}:{l}"),
776 _ => String::new(),
777 };
778
779 if let Some(tid) = trace_id {
780 if class_part.is_empty() && src_part.is_empty() {
781 result.push(format!(
782 " • {} (0x{:x}) → trace_id: {}",
783 exec_result.target_name, exec_result.pc_address, tid
784 ));
785 } else {
786 result.push(format!(
787 " • {} (0x{:x}) — {}{} → trace_id: {}",
788 exec_result.target_name,
789 exec_result.pc_address,
790 class_part,
791 src_part,
792 tid
793 ));
794 }
795 trace_idx += 1;
796 } else if class_part.is_empty() && src_part.is_empty() {
797 result.push(format!(
798 " • {} (0x{:x}) → trace attached",
799 exec_result.target_name, exec_result.pc_address
800 ));
801 } else {
802 result.push(format!(
803 " • {} (0x{:x}) — {}{} → trace attached",
804 exec_result.target_name, exec_result.pc_address, class_part, src_part
805 ));
806 }
807 }
808 crate::events::ExecutionStatus::Failed(error) => {
809 result.push(format!(
810 " ✗ {} (0x{:x}): {}",
811 exec_result.target_name, exec_result.pc_address, error
812 ));
813 }
814 crate::events::ExecutionStatus::Skipped(reason) => {
815 result.push(format!(
816 " ⊘ {} (0x{:x}): {}",
817 exec_result.target_name, exec_result.pc_address, reason
818 ));
819 }
820 }
821 }
822
823 result.join("\n")
824 }
825
826 pub fn format_trace_error_response(
828 target: &str,
829 error: &str,
830 details: Option<&TraceErrorDetails>,
831 emoji_config: &EmojiConfig,
832 ) -> String {
833 Self::format_trace_error_response_with_script(target, error, details, None, emoji_config)
834 }
835
836 pub fn format_trace_error_response_with_script(
837 target: &str,
838 error: &str,
839 _details: Option<&TraceErrorDetails>,
840 script_content: Option<&str>,
841 emoji_config: &EmojiConfig,
842 ) -> String {
843 let mut result = Vec::new();
844
845 if let Some(script) = script_content {
847 let script_lines = Self::format_script_display_section(script, emoji_config);
848 result.extend(script_lines);
849 }
850
851 let target_emoji = emoji_config.get_trace_element(crate::ui::emoji::TraceElement::Target);
853 result.push(format!("{target_emoji} Target: {target}"));
854
855 result.push("".to_string());
857
858 let error_emoji = emoji_config.get_script_status(crate::ui::emoji::ScriptStatus::Error);
860 result.push(format!(
861 "{error_emoji} Trace Results: 0 successful, \x1b[31m1 failed\x1b[0m"
862 ));
863
864 let clean_error = if error.contains("eBPF Loading Error:") {
866 error.replace("eBPF Loading Error:", "").trim().to_string()
867 } else if error.contains("Uprobe Attachment Error:") {
868 error
869 .replace("Uprobe Attachment Error:", "")
870 .trim()
871 .to_string()
872 } else if error.contains("Code Generation Error:") {
873 error
874 .replace("Code Generation Error:", "")
875 .trim()
876 .to_string()
877 } else if error.contains("Script compilation failed") {
878 error
879 .replace("Script compilation failed", "")
880 .trim()
881 .to_string()
882 } else {
883 error.to_string()
884 };
885
886 result.push(format!(" • {target} → {clean_error}"));
887
888 result.join("\n")
889 }
890
891 fn format_script_display_section(script: &str, emoji_config: &EmojiConfig) -> Vec<String> {
893 let mut result = Vec::new();
894 let script_emoji = emoji_config.get_trace_element(crate::ui::emoji::TraceElement::Line);
895
896 if script.trim().is_empty() {
897 result.push(format!("{script_emoji} Script: {{}}"));
898 } else {
899 let script_lines: Vec<&str> = script.lines().collect();
900 if script_lines.len() == 1 {
901 result.push(format!("{script_emoji} Script: {}", script.trim()));
903 } else {
904 result.push(format!("{script_emoji} Script:"));
906 for (idx, line) in script_lines.iter().enumerate() {
907 result.push(format!(" {:2} │ {line}", idx + 1));
908 }
909 }
910 }
911
912 result
913 }
914
915 fn char_pos_to_byte_pos(text: &str, char_pos: usize) -> usize {
917 text.char_indices()
918 .nth(char_pos)
919 .map_or(text.len(), |(pos, _)| pos)
920 }
921}