ghostscope_ui/components/command_panel/
command_parser.rs

1use crate::action::{Action, ResponseType, RuntimeCommand};
2use crate::model::panel_state::{CommandPanelState, CommandType, InputState};
3use crate::ui::strings::UIStrings;
4use ratatui::style::Modifier;
5use std::time::Instant;
6
7/// Handles command parsing and built-in command execution
8pub struct CommandParser;
9
10impl CommandParser {
11    /// Parse and handle a command, returning appropriate actions
12    pub fn parse_command(state: &mut CommandPanelState, command: &str) -> Vec<Action> {
13        let cmd = command.trim();
14
15        // Handle built-in help commands with styled responses
16        if cmd == "help" {
17            let plain = Self::format_help_message();
18            let styled = Self::format_help_message_styled();
19            return vec![Action::AddResponseWithStyle {
20                content: plain,
21                styled_lines: Some(styled),
22                response_type: ResponseType::Info,
23            }];
24        }
25        if cmd == "help srcpath" {
26            let plain = Self::format_srcpath_help();
27            let styled = Self::format_srcpath_help_styled();
28            return vec![Action::AddResponseWithStyle {
29                content: plain,
30                styled_lines: Some(styled),
31                response_type: ResponseType::Info,
32            }];
33        }
34
35        // Handle UI commands
36        if let Some(actions) = Self::parse_ui_command(cmd) {
37            return actions;
38        }
39
40        // Handle sync commands (enable/disable/delete)
41        if let Some(actions) = Self::parse_sync_command(state, cmd) {
42            return actions;
43        }
44
45        // Handle trace command (support both full command and abbreviation)
46        if cmd.starts_with("trace ") {
47            return vec![Action::EnterScriptMode(cmd.to_string())];
48        }
49        if cmd.starts_with("t ") {
50            // Convert "t target" to "trace target" for consistent handling
51            let target = cmd.strip_prefix("t ").unwrap();
52            let full_command = format!("trace {target}");
53            return vec![Action::EnterScriptMode(full_command)];
54        }
55
56        // Handle shortcut commands
57        if let Some(actions) = Self::parse_shortcut_command(state, cmd) {
58            return actions;
59        }
60
61        // Handle info commands
62        if let Some(actions) = Self::parse_info_command(state, cmd) {
63            return actions;
64        }
65
66        // Handle save commands (traces, output, session)
67        if let Some(actions) = Self::parse_save_command(state, cmd) {
68            return actions;
69        }
70
71        // Handle stop command (stop realtime logging)
72        if let Some(actions) = Self::parse_stop_command(cmd) {
73            return actions;
74        }
75
76        // Handle source command
77        if let Some(actions) = Self::parse_source_command(state, cmd) {
78            return actions;
79        }
80
81        // Handle srcpath command
82        if let Some(actions) = Self::parse_srcpath_command(state, cmd) {
83            return actions;
84        }
85
86        // Handle quit/exit commands
87        if cmd == "quit" || cmd == "exit" {
88            return vec![Action::Quit];
89        }
90
91        // Handle clear command
92        if cmd == "clear" {
93            // Clear command history
94            state.command_history.clear();
95            let plain = "✅ Command history cleared.".to_string();
96            let styled = vec![
97                crate::components::command_panel::style_builder::StyledLineBuilder::new()
98                    .styled(
99                        plain.clone(),
100                        crate::components::command_panel::style_builder::StylePresets::SUCCESS,
101                    )
102                    .build(),
103            ];
104            return vec![Action::AddResponseWithStyle {
105                content: plain,
106                styled_lines: Some(styled),
107                response_type: ResponseType::Info,
108            }];
109        }
110
111        // Unknown command
112        {
113            let plain = format!("{} {}", UIStrings::ERROR_PREFIX, UIStrings::UNKNOWN_COMMAND);
114            let styled =
115                crate::components::command_panel::ResponseFormatter::style_generic_message_lines(
116                    &plain,
117                );
118            vec![Action::AddResponseWithStyle {
119                content: plain,
120                styled_lines: Some(styled),
121                response_type: ResponseType::Error,
122            }]
123        }
124    }
125
126    /// Format detailed srcpath help message
127    fn format_srcpath_help() -> String {
128        [
129            "📘 Source Path Command - Detailed Help",
130            "",
131            "The 'srcpath' command helps resolve source files when DWARF debug info contains",
132            "compilation-time paths that differ from runtime paths (e.g., compiled on CI server).",
133            "",
134            "Commands:",
135            "  srcpath                      - Show current path mappings and search directories",
136            "  srcpath map <from> <to>      - Map DWARF compilation directory to local path (⭐ Recommended)",
137            "  srcpath add <dir>            - Add search directory (fallback, non-recursive)",
138            "  srcpath remove <path>        - Remove a mapping or search directory",
139            "  srcpath clear                - Clear all runtime rules (keep config file rules)",
140            "  srcpath reset                - Reset to config file rules only",
141            "",
142            "Resolution Strategy:",
143            "  1. Try exact path from DWARF",
144            "  2. Apply path substitutions (runtime rules first, then config file)",
145            "  3. Search by filename in additional directories (root only, non-recursive)",
146            "",
147            "⭐ Recommended Usage:",
148            "  Use 'srcpath map' to map DWARF compilation directory:",
149            "    srcpath map /home/build/nginx-1.27.1 /home/user/nginx-1.27.1",
150            "  This maps ALL relative paths automatically.",
151            "",
152            "Examples:",
153            "  srcpath map /build/project /home/user/project    # Map compilation directory",
154            "  srcpath add /usr/local/include                    # Add search directory (fallback)",
155            "  srcpath remove /build/project                     # Remove a rule",
156            "",
157            "Configuration:",
158            "  Rules can be persisted in config.toml under [source] section.",
159            "  Runtime rules (via commands) take priority over config file rules.",
160            "",
161            "💡 Tip: Check file loading errors for 'DWARF Directory', then map it directly.",
162            "   Type 'help srcpath' for more details.",
163        ]
164        .join("\n")
165    }
166
167    /// Format comprehensive help message
168    fn format_help_message() -> String {
169        format!(
170            "📘 Ghostscope Commands:\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}",
171            Self::format_tracing_commands(),
172            Self::format_info_commands(),
173            Self::format_srcpath_commands(),
174            Self::format_ui_commands(),
175            Self::format_control_commands(),
176            Self::format_navigation_commands(),
177            Self::format_general_commands()
178        )
179    }
180
181    /// Styled comprehensive help message
182    fn format_help_message_styled() -> Vec<ratatui::text::Line<'static>> {
183        use crate::components::command_panel::style_builder::StyledLineBuilder;
184        use ratatui::text::Line;
185
186        let mut lines = Vec::new();
187        // Title
188        lines.push(
189            StyledLineBuilder::new()
190                .title("📘 Ghostscope Commands:")
191                .build(),
192        );
193        lines.push(Line::from(""));
194
195        // Sections
196        lines.extend(Self::format_section_styled(&Self::format_tracing_commands()));
197        lines.push(Line::from(""));
198        lines.extend(Self::format_section_styled(&Self::format_info_commands()));
199        lines.push(Line::from(""));
200        lines.extend(Self::format_section_styled(&Self::format_srcpath_commands()));
201        lines.push(Line::from(""));
202        lines.extend(Self::format_section_styled(&Self::format_ui_commands()));
203        lines.push(Line::from(""));
204        lines.extend(Self::format_section_styled(&Self::format_control_commands()));
205        lines.push(Line::from(""));
206        lines.extend(Self::format_section_styled(
207            &Self::format_navigation_commands(),
208        ));
209        lines.push(Line::from(""));
210        lines.extend(Self::format_section_styled(&Self::format_general_commands()));
211        lines
212    }
213
214    /// Helper: format a single help section's text into styled lines
215    fn format_section_styled(section_text: &str) -> Vec<ratatui::text::Line<'static>> {
216        use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
217        let mut out = Vec::new();
218        for raw in section_text.lines() {
219            if raw.is_empty() {
220                out.push(ratatui::text::Line::from(""));
221                continue;
222            }
223            if matches!(
224                raw.chars().next(),
225                Some('📊' | '🔍' | '🗂' | '⚙' | '🧭' | '🔧' | '🖥')
226            ) {
227                out.push(
228                    StyledLineBuilder::new()
229                        .styled(raw, StylePresets::SECTION)
230                        .build(),
231                );
232                continue;
233            }
234            if raw.starts_with("  ") {
235                out.push(Self::build_help_command_line(raw));
236                continue;
237            }
238            if raw.contains("💡") {
239                out.push(
240                    StyledLineBuilder::new()
241                        .styled(raw, StylePresets::TIP)
242                        .build(),
243                );
244                continue;
245            }
246            out.push(StyledLineBuilder::new().value(raw).build());
247        }
248        out
249    }
250
251    /// Styled srcpath detailed help
252    fn format_srcpath_help_styled() -> Vec<ratatui::text::Line<'static>> {
253        use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
254        use ratatui::text::Line;
255
256        let mut lines = Vec::new();
257        lines.push(
258            StyledLineBuilder::new()
259                .title("📘 Source Path Command - Detailed Help")
260                .build(),
261        );
262        lines.push(Line::from(""));
263
264        for raw in Self::format_srcpath_help().lines() {
265            if raw.is_empty() {
266                lines.push(Line::from(""));
267                continue;
268            }
269            if raw.starts_with("📘") {
270                continue; // already handled as title
271            }
272            if matches!(
273                raw,
274                "Commands:"
275                    | "Resolution Strategy:"
276                    | "⭐ Recommended Usage:"
277                    | "Examples:"
278                    | "Configuration:"
279            ) {
280                lines.push(
281                    StyledLineBuilder::new()
282                        .styled(raw, StylePresets::SECTION)
283                        .build(),
284                );
285                continue;
286            }
287            if raw.starts_with("  ") {
288                lines.push(Self::build_help_command_line(raw));
289                continue;
290            }
291            if raw.contains("💡") {
292                lines.push(
293                    StyledLineBuilder::new()
294                        .styled(raw, StylePresets::TIP)
295                        .build(),
296                );
297                continue;
298            }
299            lines.push(StyledLineBuilder::new().value(raw).build());
300        }
301
302        lines
303    }
304
305    /// Styled info help (info commands usage)
306    fn format_info_help_styled() -> Vec<ratatui::text::Line<'static>> {
307        use crate::components::command_panel::style_builder::{StylePresets, StyledLineBuilder};
308        let mut lines = Vec::new();
309        for raw in Self::format_info_help().lines() {
310            if raw.is_empty() {
311                lines.push(ratatui::text::Line::from(""));
312                continue;
313            }
314            if raw.starts_with("🔍 Info") {
315                lines.push(StyledLineBuilder::new().title(raw).build());
316                continue;
317            }
318            if raw.starts_with("  ") {
319                lines.push(Self::build_help_command_line(raw));
320                continue;
321            }
322            if raw.contains("💡") {
323                lines.push(
324                    StyledLineBuilder::new()
325                        .styled(raw, StylePresets::TIP)
326                        .build(),
327                );
328                continue;
329            }
330            lines.push(StyledLineBuilder::new().value(raw).build());
331        }
332        lines
333    }
334
335    /// Helper: convert a single help command line into styled spans
336    fn build_help_command_line(line: &str) -> ratatui::text::Line<'static> {
337        use ratatui::{
338            style::{Color, Style},
339            text::{Line, Span},
340        };
341
342        // Preserve leading spaces
343        let mut spans: Vec<Span<'static>> = Vec::new();
344        let trimmed = line.trim_start();
345        let leading = line.len() - trimmed.len();
346        if leading > 0 {
347            spans.push(Span::raw(" ".repeat(leading)));
348        }
349
350        // Split into command part and description
351        let (cmd_part, desc_part) = match trimmed.find(" - ") {
352            Some(pos) => (&trimmed[..pos], Some(&trimmed[pos..])),
353            None => (trimmed, None),
354        };
355
356        // Walk cmd_part and style parameters in <...> as Yellow, others as White+Bold
357        let mut current = String::new();
358        let mut in_param = false;
359        for ch in cmd_part.chars() {
360            match ch {
361                '<' => {
362                    if !current.is_empty() {
363                        spans.push(Span::styled(
364                            current.clone(),
365                            Style::default()
366                                .fg(Color::White)
367                                .add_modifier(Modifier::BOLD),
368                        ));
369                        current.clear();
370                    }
371                    in_param = true;
372                    current.push('<');
373                }
374                '>' => {
375                    current.push('>');
376                    spans.push(Span::styled(
377                        current.clone(),
378                        Style::default().fg(Color::Yellow),
379                    ));
380                    current.clear();
381                    in_param = false;
382                }
383                _ => current.push(ch),
384            }
385        }
386        if !current.is_empty() {
387            if in_param {
388                spans.push(Span::styled(current, Style::default().fg(Color::Yellow)));
389            } else {
390                spans.push(Span::styled(
391                    current,
392                    Style::default()
393                        .fg(Color::White)
394                        .add_modifier(Modifier::BOLD),
395                ));
396            }
397        }
398
399        // Description part in dark gray
400        if let Some(desc) = desc_part {
401            spans.push(Span::styled(
402                desc.to_string(),
403                Style::default().fg(Color::DarkGray),
404            ));
405        }
406
407        Line::from(spans)
408    }
409
410    /// Styled 'Usage: ...' helper: highlights 'Usage:' and parameters in <...>
411    fn styled_usage(usage: &str) -> Vec<ratatui::text::Line<'static>> {
412        use ratatui::{
413            style::{Color, Style},
414            text::{Line, Span},
415        };
416
417        let mut spans: Vec<Span<'static>> = Vec::new();
418        let trimmed = usage.trim_start();
419        // Preserve indent if any
420        let leading = usage.len() - trimmed.len();
421        if leading > 0 {
422            spans.push(Span::raw(" ".repeat(leading)));
423        }
424
425        let prefix = "Usage:";
426        if let Some(rest) = trimmed.strip_prefix(prefix) {
427            spans.push(Span::styled(
428                prefix.to_string(),
429                Style::default().fg(Color::Cyan),
430            ));
431            spans.push(Span::raw(" "));
432            // Highlight parameters between <...>
433            let mut current = String::new();
434            let mut in_param = false;
435            for ch in rest.chars() {
436                match ch {
437                    '<' => {
438                        if !current.is_empty() {
439                            spans.push(Span::styled(
440                                current.clone(),
441                                Style::default().fg(Color::White),
442                            ));
443                            current.clear();
444                        }
445                        in_param = true;
446                        current.push('<');
447                    }
448                    '>' => {
449                        current.push('>');
450                        spans.push(Span::styled(
451                            current.clone(),
452                            Style::default().fg(Color::Yellow),
453                        ));
454                        current.clear();
455                        in_param = false;
456                    }
457                    _ => current.push(ch),
458                }
459            }
460            if !current.is_empty() {
461                let style = if in_param {
462                    Style::default().fg(Color::Yellow)
463                } else {
464                    Style::default().fg(Color::White)
465                };
466                spans.push(Span::styled(current, style));
467            }
468        } else {
469            // No prefix, color whole line white
470            spans.push(Span::styled(
471                trimmed.to_string(),
472                Style::default().fg(Color::White),
473            ));
474        }
475
476        vec![Line::from(spans)]
477    }
478
479    /// Format tracing command section
480    fn format_tracing_commands() -> String {
481        [
482            "📊 Tracing Commands:",
483            "  trace <target>             - Start tracing a function/line/address (t)",
484            "    - target: function_name | file:line | 0xADDR | module_suffix:0xADDR",
485            "  enable <id|all>            - Enable specific trace or all traces (en)",
486            "  disable <id|all>           - Disable specific trace or all traces (dis)",
487            "  delete <id|all>            - Delete specific trace or all traces (del)",
488            "  save traces [file]         - Save all traces to file (s t)",
489            "  save traces enabled [file] - Save only enabled traces",
490            "  save traces disabled [file]- Save only disabled traces",
491            "  save output [file]         - Start realtime eBPF output logging (s o)",
492            "  save session [file]        - Start realtime session logging (s s)",
493            "  stop output                - Stop realtime eBPF output logging",
494            "  stop session               - Stop realtime session logging",
495            "  source <file>              - Load traces from file (s)",
496        ]
497        .join("\n")
498    }
499
500    /// Format info command section
501    fn format_info_commands() -> String {
502        [
503            "🔍 Information Commands:",
504            "  info                 - Show available info commands",
505            "  info file            - Show executable file info and sections (i f, i file)",
506            "  info trace [id]      - Show trace status (i t [id])",
507            "  info source          - Show all source files (i s)",
508            "  info share           - Show shared libraries WITH debug info (i sh)",
509            "  info share all       - Show ALL loaded shared libraries (i sh all)",
510            "  info function <name> [verbose|v] - Show debug info for function (i f <name> [v])",
511            "  info line <file:line> [verbose|v] - Show debug info for line (i l <file:line> [v])",
512            "  info address <addr> [verbose|v]   - Show debug info for address (i a <addr> [v]) [TODO]",
513        ]
514        .join("\n")
515    }
516
517    /// Format source path command section
518    fn format_srcpath_commands() -> String {
519        [
520            "🗂️  Source Path Commands:",
521            "  srcpath                      - Show current path mappings and search directories",
522            "  srcpath map <from> <to>      - Map DWARF compilation directory (⭐ Recommended)",
523            "  srcpath add <dir>            - Add search directory (fallback, non-recursive)",
524            "  srcpath remove <path>        - Remove a mapping or search directory",
525            "  srcpath clear                - Clear all runtime rules",
526            "  srcpath reset                - Reset to config file rules",
527            "",
528            "  💡 Tip: Use 'help srcpath' for detailed usage and best practices",
529        ]
530        .join("\n")
531    }
532
533    /// Format control command section
534    fn format_control_commands() -> String {
535        [
536            "⚙️  Control Commands:",
537            "  clear                - Clear command history",
538            "  quit, exit           - Exit ghostscope",
539        ]
540        .join("\n")
541    }
542
543    /// Format UI control commands
544    fn format_ui_commands() -> String {
545        [
546            "🖥 UI Controls:",
547            "  ui source on         - Enable source panel",
548            "  ui source off        - Disable source panel (keep eBPF & command panels)",
549            "",
550            "  💡 No source available? Use 'ui source off', or start with --no-source-panel,",
551            "     or set [ui].show_source_panel=false in config.",
552        ]
553        .join("\n")
554    }
555
556    /// Format navigation command section
557    fn format_navigation_commands() -> String {
558        [
559            "🧭 Navigation & Input:",
560            "Input Mode:",
561            "  Tab                  - Command completion",
562            "  Right/Ctrl+E         - Accept auto-suggestion",
563            "  Ctrl+P/N             - Navigate command history",
564            "  Ctrl+A/E             - Move to beginning/end of line (emacs)",
565            "  Ctrl+B/F             - Move cursor left/right (emacs)",
566            "  Ctrl+H               - Delete previous character (emacs)",
567            "  Ctrl+W               - Delete previous word (emacs)",
568            "",
569            "Command Mode (vim-style):",
570            "  jk/Escape            - Enter command mode",
571            "  hjkl                 - Navigate (left/down/up/right)",
572            "  i                    - Return to input mode",
573        ]
574        .join("\n")
575    }
576
577    /// Format general command section
578    fn format_general_commands() -> String {
579        [
580            "🔧 General:",
581            "  help                 - Show this help message",
582            "",
583            "💡 Input: Tab=completion, Right/Ctrl+E=auto-suggest, emacs keys | Command: jk/Esc enter, i exit, hjkl move",
584        ]
585        .join("\n")
586    }
587
588    /// Get command completion for the given input
589    pub fn get_command_completion(input: &str) -> Option<String> {
590        let input = input.trim();
591
592        // Check if we're completing the verbose parameter for info commands
593        // e.g., "info function main v<Tab>" -> "info function main verbose"
594        if let Some(verbose_completion) = Self::complete_verbose_parameter(input) {
595            return Some(verbose_completion);
596        }
597
598        // All available commands (full commands + abbreviations)
599        let commands = [
600            // Primary commands
601            "trace",
602            "enable",
603            "disable",
604            "delete",
605            "save",
606            "source",
607            "ui",
608            "info",
609            "help",
610            "clear",
611            "quit",
612            "exit",
613            "srcpath",
614            // Abbreviations
615            "t",
616            "en",
617            "dis",
618            "del",
619            // Save subcommands
620            "save traces",
621            "save traces enabled",
622            "save traces disabled",
623            "save output",
624            "save session",
625            // Stop subcommands
626            "stop output",
627            "stop session",
628            // UI
629            "ui source on",
630            "ui source off",
631            // Info subcommands
632            "info file",
633            "info trace",
634            "info source",
635            "info share",
636            "info share all",
637            "info function",
638            "info line",
639            "info address",
640            // Source path subcommands
641            "srcpath",
642            "srcpath add",
643            "srcpath map",
644            "srcpath remove",
645            "srcpath clear",
646            "srcpath reset",
647            // Shortcut commands
648            "i",
649            "i file",
650            "i s",
651            "i sh",
652            "i sh all",
653            "i t",
654            "i f",
655            "i l",
656            "i a",
657            "s t", // "s" alone is ambiguous (save/source), so we only support specific "s" shortcuts
658            "s o", // save output
659            "s s", // save session
660        ];
661
662        // Find commands that start with the input
663        let matches: Vec<&str> = commands
664            .iter()
665            .filter(|cmd| cmd.starts_with(input) && cmd.len() > input.len())
666            .cloned()
667            .collect();
668
669        match matches.len() {
670            0 => None, // No matches
671            1 => {
672                // Single match - return the completion part
673                let full_command = matches[0];
674                Some(full_command[input.len()..].to_string())
675            }
676            _ => {
677                // Multiple matches - find common prefix
678                Self::find_common_prefix(&matches, input.len())
679            }
680        }
681    }
682
683    /// Complete the verbose parameter for info commands
684    /// Returns the completion suffix if input matches an info command pattern with partial verbose
685    fn complete_verbose_parameter(input: &str) -> Option<String> {
686        // Split input into words
687        let parts: Vec<&str> = input.split_whitespace().collect();
688
689        // Need at least 3 parts: command + subcommand + target [+ partial_verbose]
690        if parts.len() < 3 {
691            return None;
692        }
693
694        // Check if this is an info command
695        let is_info_cmd = matches!(
696            (parts[0], parts.get(1)),
697            ("info", Some(&"function"))
698                | ("info", Some(&"line"))
699                | ("info", Some(&"address"))
700                | ("i", Some(&"f"))
701                | ("i", Some(&"l"))
702                | ("i", Some(&"a"))
703        );
704
705        if !is_info_cmd {
706            return None;
707        }
708
709        // Check if last part could be start of "verbose"
710        let last_part = parts.last()?;
711
712        // If it already says "verbose" or "v", no completion needed
713        if *last_part == "verbose" || *last_part == "v" {
714            return None;
715        }
716
717        // Check if last part is a prefix of "verbose" or is "v"
718        if "verbose".starts_with(last_part) && last_part.len() < "verbose".len() {
719            // Calculate how much of "verbose" remains to be completed
720            let remaining = &"verbose"[last_part.len()..];
721            return Some(remaining.to_string());
722        }
723
724        None
725    }
726
727    /// Find the longest common prefix among multiple command matches
728    fn find_common_prefix(matches: &[&str], input_len: usize) -> Option<String> {
729        if matches.is_empty() {
730            return None;
731        }
732
733        let first = &matches[0][input_len..];
734        let mut common_len = first.len();
735
736        for &cmd in &matches[1..] {
737            let suffix = &cmd[input_len..];
738            common_len = first
739                .chars()
740                .zip(suffix.chars())
741                .take_while(|(a, b)| a == b)
742                .count()
743                .min(common_len);
744        }
745
746        if common_len > 0 {
747            let common_prefix = &first[..common_len];
748            // Don't complete with just whitespace
749            if common_prefix.trim().is_empty() {
750                None
751            } else {
752                Some(common_prefix.to_string())
753            }
754        } else {
755            None
756        }
757    }
758
759    /// Parse synchronous commands (enable/disable/delete)
760    fn parse_sync_command(state: &mut CommandPanelState, command: &str) -> Option<Vec<Action>> {
761        // Support both full command and abbreviation for disable
762        if command.starts_with("disable ") {
763            let target = command.strip_prefix("disable ").unwrap().trim();
764            return Some(Self::parse_disable_command(state, target));
765        }
766        if command.starts_with("dis ") {
767            let target = command.strip_prefix("dis ").unwrap().trim();
768            return Some(Self::parse_disable_command(state, target));
769        }
770
771        // Support both full command and abbreviation for enable
772        if command.starts_with("enable ") {
773            let target = command.strip_prefix("enable ").unwrap().trim();
774            return Some(Self::parse_enable_command(state, target));
775        }
776        if command.starts_with("en ") {
777            let target = command.strip_prefix("en ").unwrap().trim();
778            return Some(Self::parse_enable_command(state, target));
779        }
780
781        // Support both full command and abbreviation for delete
782        if command.starts_with("delete ") {
783            let target = command.strip_prefix("delete ").unwrap().trim();
784            return Some(Self::parse_delete_command(state, target));
785        }
786        if command.starts_with("del ") {
787            let target = command.strip_prefix("del ").unwrap().trim();
788            return Some(Self::parse_delete_command(state, target));
789        }
790
791        None
792    }
793
794    /// Parse disable command
795    fn parse_disable_command(state: &mut CommandPanelState, target: &str) -> Vec<Action> {
796        if target == "all" {
797            state.input_state = InputState::WaitingResponse {
798                command: format!("disable {target}"),
799                sent_time: Instant::now(),
800                command_type: CommandType::DisableAll,
801            };
802            vec![Action::SendRuntimeCommand(RuntimeCommand::DisableAllTraces)]
803        } else if let Ok(trace_id) = target.parse::<u32>() {
804            state.input_state = InputState::WaitingResponse {
805                command: format!("disable {target}"),
806                sent_time: Instant::now(),
807                command_type: CommandType::Disable { trace_id },
808            };
809            vec![Action::SendRuntimeCommand(RuntimeCommand::DisableTrace(
810                trace_id,
811            ))]
812        } else {
813            let plain = "Usage: disable <trace_id|all>".to_string();
814            let styled = Self::styled_usage(&plain);
815            vec![Action::AddResponseWithStyle {
816                content: plain,
817                styled_lines: Some(styled),
818                response_type: ResponseType::Error,
819            }]
820        }
821    }
822
823    /// Parse enable command
824    fn parse_enable_command(state: &mut CommandPanelState, target: &str) -> Vec<Action> {
825        if target == "all" {
826            state.input_state = InputState::WaitingResponse {
827                command: format!("enable {target}"),
828                sent_time: Instant::now(),
829                command_type: CommandType::EnableAll,
830            };
831            vec![Action::SendRuntimeCommand(RuntimeCommand::EnableAllTraces)]
832        } else if let Ok(trace_id) = target.parse::<u32>() {
833            state.input_state = InputState::WaitingResponse {
834                command: format!("enable {target}"),
835                sent_time: Instant::now(),
836                command_type: CommandType::Enable { trace_id },
837            };
838            vec![Action::SendRuntimeCommand(RuntimeCommand::EnableTrace(
839                trace_id,
840            ))]
841        } else {
842            let plain = "Usage: enable <trace_id|all>".to_string();
843            let styled = Self::styled_usage(&plain);
844            vec![Action::AddResponseWithStyle {
845                content: plain,
846                styled_lines: Some(styled),
847                response_type: ResponseType::Error,
848            }]
849        }
850    }
851
852    /// Parse delete command
853    fn parse_delete_command(state: &mut CommandPanelState, target: &str) -> Vec<Action> {
854        if target == "all" {
855            state.input_state = InputState::WaitingResponse {
856                command: format!("delete {target}"),
857                sent_time: Instant::now(),
858                command_type: CommandType::DeleteAll,
859            };
860            vec![Action::SendRuntimeCommand(RuntimeCommand::DeleteAllTraces)]
861        } else if let Ok(trace_id) = target.parse::<u32>() {
862            state.input_state = InputState::WaitingResponse {
863                command: format!("delete {target}"),
864                sent_time: Instant::now(),
865                command_type: CommandType::Delete { trace_id },
866            };
867            vec![Action::SendRuntimeCommand(RuntimeCommand::DeleteTrace(
868                trace_id,
869            ))]
870        } else {
871            let plain = "Usage: delete <trace_id|all>".to_string();
872            let styled = Self::styled_usage(&plain);
873            vec![Action::AddResponseWithStyle {
874                content: plain,
875                styled_lines: Some(styled),
876                response_type: ResponseType::Error,
877            }]
878        }
879    }
880
881    /// Parse info commands
882    fn parse_info_command(state: &mut CommandPanelState, command: &str) -> Option<Vec<Action>> {
883        if command == "info" {
884            return Some(vec![Action::AddResponseWithStyle {
885                content: Self::format_info_help(),
886                styled_lines: Some(Self::format_info_help_styled()),
887                response_type: ResponseType::Info,
888            }]);
889        }
890
891        if command == "info file" {
892            state.input_state = InputState::WaitingResponse {
893                command: command.to_string(),
894                sent_time: Instant::now(),
895                command_type: CommandType::InfoFile,
896            };
897            return Some(vec![Action::SendRuntimeCommand(RuntimeCommand::InfoFile)]);
898        }
899
900        if command == "info source" {
901            state.input_state = InputState::WaitingResponse {
902                command: command.to_string(),
903                sent_time: Instant::now(),
904                command_type: CommandType::InfoSource,
905            };
906            return Some(vec![Action::SendRuntimeCommand(RuntimeCommand::InfoSource)]);
907        }
908
909        if command == "info share" {
910            state.input_state = InputState::WaitingResponse {
911                command: command.to_string(),
912                sent_time: Instant::now(),
913                command_type: CommandType::InfoShare,
914            };
915            return Some(vec![Action::SendRuntimeCommand(RuntimeCommand::InfoShare)]);
916        }
917
918        if command == "info share all" {
919            state.input_state = InputState::WaitingResponse {
920                command: command.to_string(),
921                sent_time: Instant::now(),
922                command_type: CommandType::InfoShareAll,
923            };
924            // Runtime returns full list; UI decides to show all vs filter
925            return Some(vec![Action::SendRuntimeCommand(RuntimeCommand::InfoShare)]);
926        }
927
928        if command == "info trace" {
929            return Some(Self::parse_info_trace_command(state, None));
930        }
931
932        if command.starts_with("info trace ") {
933            let id_str = command.strip_prefix("info trace ").unwrap().trim();
934            if let Ok(trace_id) = id_str.parse::<u32>() {
935                return Some(Self::parse_info_trace_command(state, Some(trace_id)));
936            } else {
937                let plain = "Usage: info trace [trace_id]".to_string();
938                let styled = Self::styled_usage(&plain);
939                return Some(vec![Action::AddResponseWithStyle {
940                    content: plain,
941                    styled_lines: Some(styled),
942                    response_type: ResponseType::Error,
943                }]);
944            }
945        }
946
947        // Handle info function command
948        if command.starts_with("info function ") || command.starts_with("i f ") {
949            let args = if command.starts_with("info function ") {
950                command.strip_prefix("info function ").unwrap()
951            } else {
952                command.strip_prefix("i f ").unwrap()
953            };
954
955            let parts: Vec<&str> = args.split_whitespace().collect();
956            if parts.is_empty() {
957                let plain = "Usage: info function <function_name> [verbose|v]".to_string();
958                let styled = Self::styled_usage(&plain);
959                return Some(vec![Action::AddResponseWithStyle {
960                    content: plain,
961                    styled_lines: Some(styled),
962                    response_type: ResponseType::Error,
963                }]);
964            }
965
966            let target = parts[0].to_string();
967            let verbose = parts.len() > 1 && (parts[1] == "verbose" || parts[1] == "v");
968
969            state.input_state = InputState::WaitingResponse {
970                command: command.to_string(),
971                sent_time: Instant::now(),
972                command_type: CommandType::InfoFunction {
973                    target: target.clone(),
974                    verbose,
975                },
976            };
977            return Some(vec![Action::SendRuntimeCommand(
978                RuntimeCommand::InfoFunction { target, verbose },
979            )]);
980        }
981
982        // Handle info line command
983        if command.starts_with("info line ") || command.starts_with("i l ") {
984            let args = if command.starts_with("info line ") {
985                command.strip_prefix("info line ").unwrap()
986            } else {
987                command.strip_prefix("i l ").unwrap()
988            };
989
990            let parts: Vec<&str> = args.split_whitespace().collect();
991            if parts.is_empty() {
992                let plain = "Usage: info line <file:line> [verbose|v]".to_string();
993                let styled = Self::styled_usage(&plain);
994                return Some(vec![Action::AddResponseWithStyle {
995                    content: plain,
996                    styled_lines: Some(styled),
997                    response_type: ResponseType::Error,
998                }]);
999            }
1000
1001            let target = parts[0].to_string();
1002            let verbose = parts.len() > 1 && (parts[1] == "verbose" || parts[1] == "v");
1003
1004            state.input_state = InputState::WaitingResponse {
1005                command: command.to_string(),
1006                sent_time: Instant::now(),
1007                command_type: CommandType::InfoLine {
1008                    target: target.clone(),
1009                    verbose,
1010                },
1011            };
1012            return Some(vec![Action::SendRuntimeCommand(RuntimeCommand::InfoLine {
1013                target,
1014                verbose,
1015            })]);
1016        }
1017
1018        // Handle info address command
1019        if command.starts_with("info address ") || command.starts_with("i a ") {
1020            let args = if command.starts_with("info address ") {
1021                command.strip_prefix("info address ").unwrap()
1022            } else {
1023                command.strip_prefix("i a ").unwrap()
1024            };
1025
1026            let parts: Vec<&str> = args.split_whitespace().collect();
1027            if parts.is_empty() {
1028                let plain =
1029                    "Usage: info address <0xADDR | module_suffix:0xADDR> [verbose|v]".to_string();
1030                let styled = Self::styled_usage(&plain);
1031                return Some(vec![Action::AddResponseWithStyle {
1032                    content: plain,
1033                    styled_lines: Some(styled),
1034                    response_type: ResponseType::Error,
1035                }]);
1036            }
1037
1038            let target = parts[0].to_string();
1039            let verbose = parts.len() > 1 && (parts[1] == "verbose" || parts[1] == "v");
1040
1041            state.input_state = InputState::WaitingResponse {
1042                command: command.to_string(),
1043                sent_time: Instant::now(),
1044                command_type: CommandType::InfoAddress {
1045                    target: target.clone(),
1046                    verbose,
1047                },
1048            };
1049
1050            return Some(vec![Action::SendRuntimeCommand(
1051                RuntimeCommand::InfoAddress { target, verbose },
1052            )]);
1053        }
1054
1055        None
1056    }
1057
1058    /// Parse all save commands (traces, output, session)
1059    fn parse_save_command(state: &mut CommandPanelState, command: &str) -> Option<Vec<Action>> {
1060        let parts: Vec<&str> = command.split_whitespace().collect();
1061
1062        if parts.is_empty() || parts[0] != "save" {
1063            return None;
1064        }
1065
1066        if parts.len() < 2 {
1067            let plain = "Usage: save <traces|output|session> [filename]".to_string();
1068            let styled = Self::styled_usage(&plain);
1069            return Some(vec![Action::AddResponseWithStyle {
1070                content: plain,
1071                styled_lines: Some(styled),
1072                response_type: ResponseType::Error,
1073            }]);
1074        }
1075
1076        match parts[1] {
1077            "traces" => Self::parse_save_traces_command(state, command),
1078            "output" => Self::parse_save_output_command(state, command),
1079            "session" => Self::parse_save_session_command(state, command),
1080            _ => {
1081                Some(vec![{
1082                    let plain = format!(
1083                    "Unknown save target: '{}'. Use 'save traces', 'save output', or 'save session'",
1084                    parts[1]
1085                );
1086                    let styled = vec![
1087                    crate::components::command_panel::style_builder::StyledLineBuilder::new()
1088                        .styled(plain.clone(), crate::components::command_panel::style_builder::StylePresets::ERROR)
1089                        .build(),
1090                ];
1091                    Action::AddResponseWithStyle {
1092                        content: plain,
1093                        styled_lines: Some(styled),
1094                        response_type: ResponseType::Error,
1095                    }
1096                }])
1097            }
1098        }
1099    }
1100
1101    /// Parse save traces command
1102    fn parse_save_traces_command(
1103        state: &mut CommandPanelState,
1104        command: &str,
1105    ) -> Option<Vec<Action>> {
1106        use crate::components::command_panel::trace_persistence::CommandParser as TraceCmdParser;
1107
1108        // Use the CommandParser trait to parse the command
1109        if let Some((filename, filter)) = command.parse_save_traces_command() {
1110            state.input_state = InputState::WaitingResponse {
1111                command: command.to_string(),
1112                sent_time: Instant::now(),
1113                command_type: CommandType::SaveTraces,
1114            };
1115
1116            return Some(vec![Action::SendRuntimeCommand(
1117                RuntimeCommand::SaveTraces { filename, filter },
1118            )]);
1119        }
1120
1121        None
1122    }
1123
1124    /// Parse save output command (handled directly in UI)
1125    fn parse_save_output_command(
1126        _state: &mut CommandPanelState,
1127        command: &str,
1128    ) -> Option<Vec<Action>> {
1129        let parts: Vec<&str> = command.split_whitespace().collect();
1130
1131        // save output [filename]
1132        let filename = if parts.len() > 2 {
1133            Some(parts[2..].join(" "))
1134        } else {
1135            None
1136        };
1137
1138        Some(vec![Action::SaveEbpfOutput { filename }])
1139    }
1140
1141    /// Parse save session command (handled directly in UI)
1142    fn parse_save_session_command(
1143        _state: &mut CommandPanelState,
1144        command: &str,
1145    ) -> Option<Vec<Action>> {
1146        let parts: Vec<&str> = command.split_whitespace().collect();
1147
1148        // save session [filename]
1149        let filename = if parts.len() > 2 {
1150            Some(parts[2..].join(" "))
1151        } else {
1152            None
1153        };
1154
1155        Some(vec![Action::SaveCommandSession { filename }])
1156    }
1157
1158    /// Parse stop command to stop realtime logging
1159    fn parse_stop_command(command: &str) -> Option<Vec<Action>> {
1160        let parts: Vec<&str> = command.split_whitespace().collect();
1161
1162        if parts.is_empty() || parts[0] != "stop" {
1163            return None;
1164        }
1165
1166        if parts.len() < 2 {
1167            let plain = "Usage: stop <output|session>".to_string();
1168            let styled = Self::styled_usage(&plain);
1169            return Some(vec![Action::AddResponseWithStyle {
1170                content: plain,
1171                styled_lines: Some(styled),
1172                response_type: ResponseType::Error,
1173            }]);
1174        }
1175
1176        match parts[1] {
1177            "output" => Some(vec![Action::StopSaveOutput]),
1178            "session" => Some(vec![Action::StopSaveSession]),
1179            _ => {
1180                let plain = format!(
1181                    "Unknown stop target: '{}'. Use 'stop output' or 'stop session'",
1182                    parts[1]
1183                );
1184                let styled = vec![
1185                    crate::components::command_panel::style_builder::StyledLineBuilder::new()
1186                        .styled(
1187                            plain.clone(),
1188                            crate::components::command_panel::style_builder::StylePresets::ERROR,
1189                        )
1190                        .build(),
1191                ];
1192                Some(vec![Action::AddResponseWithStyle {
1193                    content: plain,
1194                    styled_lines: Some(styled),
1195                    response_type: ResponseType::Error,
1196                }])
1197            }
1198        }
1199    }
1200
1201    /// Parse UI control commands
1202    fn parse_ui_command(command: &str) -> Option<Vec<Action>> {
1203        if !command.starts_with("ui ") {
1204            return None;
1205        }
1206
1207        let parts: Vec<&str> = command.split_whitespace().collect();
1208        if parts.len() < 3 {
1209            let plain = "Usage: ui source <on|off>".to_string();
1210            let styled = Self::styled_usage(&plain);
1211            return Some(vec![Action::AddResponseWithStyle {
1212                content: plain,
1213                styled_lines: Some(styled),
1214                response_type: ResponseType::Error,
1215            }]);
1216        }
1217
1218        match (parts[1], parts[2]) {
1219            ("source", "on") => Some(vec![Action::SetSourcePanelVisibility(true)]),
1220            ("source", "off") => Some(vec![Action::SetSourcePanelVisibility(false)]),
1221            _ => {
1222                let plain = "Usage: ui source <on|off>".to_string();
1223                let styled = Self::styled_usage(&plain);
1224                Some(vec![Action::AddResponseWithStyle {
1225                    content: plain,
1226                    styled_lines: Some(styled),
1227                    response_type: ResponseType::Error,
1228                }])
1229            }
1230        }
1231    }
1232
1233    /// Parse source command to load traces from file
1234    fn parse_source_command(state: &mut CommandPanelState, command: &str) -> Option<Vec<Action>> {
1235        use crate::components::command_panel::trace_persistence::TracePersistence;
1236
1237        // Parse "source <filename>" or "s <filename>"
1238        let filename = if command.starts_with("source ") {
1239            command.strip_prefix("source ").unwrap().trim()
1240        } else if command.starts_with("s ")
1241            && !command.starts_with("s t")
1242            && !command.starts_with("save")
1243        {
1244            // Handle "s <filename>" but not "s t" (save traces) or "save"
1245            command.strip_prefix("s ").unwrap().trim()
1246        } else {
1247            return None;
1248        };
1249
1250        if filename.is_empty() {
1251            let plain = "Usage: source <filename>".to_string();
1252            let styled = Self::styled_usage(&plain);
1253            return Some(vec![Action::AddResponseWithStyle {
1254                content: plain,
1255                styled_lines: Some(styled),
1256                response_type: ResponseType::Error,
1257            }]);
1258        }
1259
1260        // Try to load and parse the file
1261        match TracePersistence::load_traces_from_file(filename) {
1262            Ok(traces) => {
1263                if traces.is_empty() {
1264                    let plain = format!("No traces found in {filename}");
1265                    let styled = vec![
1266                        crate::components::command_panel::style_builder::StyledLineBuilder::new()
1267                            .styled(plain.clone(), crate::components::command_panel::style_builder::StylePresets::WARNING)
1268                            .build(),
1269                    ];
1270                    return Some(vec![Action::AddResponseWithStyle {
1271                        content: plain,
1272                        styled_lines: Some(styled),
1273                        response_type: ResponseType::Warning,
1274                    }]);
1275                }
1276
1277                // Initialize batch loading state
1278                state.batch_loading = Some(crate::model::panel_state::BatchLoadingState {
1279                    filename: filename.to_string(),
1280                    total_count: traces.len(),
1281                    completed_count: 0,
1282                    success_count: 0,
1283                    failed_count: 0,
1284                    disabled_count: 0,
1285                    details: Vec::new(),
1286                });
1287
1288                state.input_state = InputState::WaitingResponse {
1289                    command: command.to_string(),
1290                    sent_time: Instant::now(),
1291                    command_type: CommandType::LoadTraces,
1292                };
1293
1294                Some(vec![Action::SendRuntimeCommand(
1295                    RuntimeCommand::LoadTraces {
1296                        filename: filename.to_string(),
1297                        traces,
1298                    },
1299                )])
1300            }
1301            Err(e) => {
1302                Some(vec![{
1303                    let plain = format!("✗ Failed to load {filename}: {e}");
1304                    let styled = vec![
1305                    crate::components::command_panel::style_builder::StyledLineBuilder::new()
1306                        .styled(plain.clone(), crate::components::command_panel::style_builder::StylePresets::ERROR)
1307                        .build(),
1308                ];
1309                    Action::AddResponseWithStyle {
1310                        content: plain,
1311                        styled_lines: Some(styled),
1312                        response_type: ResponseType::Error,
1313                    }
1314                }])
1315            }
1316        }
1317    }
1318
1319    /// Parse srcpath command for source path configuration
1320    fn parse_srcpath_command(state: &mut CommandPanelState, command: &str) -> Option<Vec<Action>> {
1321        if !command.starts_with("srcpath") {
1322            return None;
1323        }
1324
1325        let parts: Vec<&str> = command.split_whitespace().collect();
1326
1327        if parts.len() == 1 {
1328            // srcpath - show current configuration
1329            state.input_state = InputState::WaitingResponse {
1330                command: command.to_string(),
1331                sent_time: Instant::now(),
1332                command_type: CommandType::SrcPath,
1333            };
1334            return Some(vec![Action::SendRuntimeCommand(
1335                RuntimeCommand::SrcPathList,
1336            )]);
1337        }
1338
1339        match parts.get(1) {
1340            Some(&"add") if parts.len() == 3 => {
1341                // srcpath add /path
1342                let dir = parts[2].to_string();
1343                state.input_state = InputState::WaitingResponse {
1344                    command: command.to_string(),
1345                    sent_time: Instant::now(),
1346                    command_type: CommandType::SrcPathAdd,
1347                };
1348                Some(vec![Action::SendRuntimeCommand(
1349                    RuntimeCommand::SrcPathAddDir { dir },
1350                )])
1351            }
1352            Some(&"map") if parts.len() == 4 => {
1353                // srcpath map /old /new
1354                let from = parts[2].to_string();
1355                let to = parts[3].to_string();
1356                state.input_state = InputState::WaitingResponse {
1357                    command: command.to_string(),
1358                    sent_time: Instant::now(),
1359                    command_type: CommandType::SrcPathMap,
1360                };
1361                Some(vec![Action::SendRuntimeCommand(
1362                    RuntimeCommand::SrcPathAddMap { from, to },
1363                )])
1364            }
1365            Some(&"remove") if parts.len() == 3 => {
1366                // srcpath remove /path
1367                let pattern = parts[2].to_string();
1368                state.input_state = InputState::WaitingResponse {
1369                    command: command.to_string(),
1370                    sent_time: Instant::now(),
1371                    command_type: CommandType::SrcPathRemove,
1372                };
1373                Some(vec![Action::SendRuntimeCommand(
1374                    RuntimeCommand::SrcPathRemove { pattern },
1375                )])
1376            }
1377            Some(&"clear") if parts.len() == 2 => {
1378                // srcpath clear
1379                state.input_state = InputState::WaitingResponse {
1380                    command: command.to_string(),
1381                    sent_time: Instant::now(),
1382                    command_type: CommandType::SrcPathClear,
1383                };
1384                Some(vec![Action::SendRuntimeCommand(
1385                    RuntimeCommand::SrcPathClear,
1386                )])
1387            }
1388            Some(&"reset") if parts.len() == 2 => {
1389                // srcpath reset
1390                state.input_state = InputState::WaitingResponse {
1391                    command: command.to_string(),
1392                    sent_time: Instant::now(),
1393                    command_type: CommandType::SrcPathReset,
1394                };
1395                Some(vec![Action::SendRuntimeCommand(
1396                    RuntimeCommand::SrcPathReset,
1397                )])
1398            }
1399            _ => {
1400                Some(vec![{
1401                    let plain = "Usage: srcpath [add <dir> | map <from> <to> | remove <path> | clear | reset]".to_string();
1402                    let styled = Self::styled_usage(&plain);
1403                    Action::AddResponseWithStyle {
1404                        content: plain,
1405                        styled_lines: Some(styled),
1406                        response_type: ResponseType::Error,
1407                    }
1408                }])
1409            }
1410        }
1411    }
1412
1413    /// Parse shortcut commands (i s, i f, i l, i t, s t, s o, s s, etc.)
1414    fn parse_shortcut_command(state: &mut CommandPanelState, command: &str) -> Option<Vec<Action>> {
1415        // Handle "s t" -> "save traces"
1416        if command == "s t" {
1417            return Self::parse_save_traces_command(state, "save traces");
1418        }
1419
1420        // Handle "s t <filename>" -> "save traces <filename>"
1421        if command.starts_with("s t ") {
1422            let rest = command.strip_prefix("s t ").unwrap();
1423            let full_command = format!("save traces {rest}");
1424            return Self::parse_save_traces_command(state, &full_command);
1425        }
1426
1427        // Handle "s o" -> "save output"
1428        if command == "s o" {
1429            return Self::parse_save_output_command(state, "save output");
1430        }
1431
1432        // Handle "s o <filename>" -> "save output <filename>"
1433        if command.starts_with("s o ") {
1434            let rest = command.strip_prefix("s o ").unwrap();
1435            let full_command = format!("save output {rest}");
1436            return Self::parse_save_output_command(state, &full_command);
1437        }
1438
1439        // Handle "s s" -> "save session"
1440        if command == "s s" {
1441            return Self::parse_save_session_command(state, "save session");
1442        }
1443
1444        // Handle "s s <filename>" -> "save session <filename>"
1445        if command.starts_with("s s ") {
1446            let rest = command.strip_prefix("s s ").unwrap();
1447            let full_command = format!("save session {rest}");
1448            return Self::parse_save_session_command(state, &full_command);
1449        }
1450
1451        // Handle "s <filename>" -> "source <filename>" (but not "s t", "s o", "s s")
1452        if command.starts_with("s ")
1453            && !command.starts_with("s t")
1454            && !command.starts_with("s o")
1455            && !command.starts_with("s s")
1456        {
1457            return Self::parse_source_command(state, command);
1458        }
1459
1460        // Handle "i s" -> "info source"
1461        if command == "i s" {
1462            state.input_state = InputState::WaitingResponse {
1463                command: "info source".to_string(),
1464                sent_time: Instant::now(),
1465                command_type: CommandType::InfoSource,
1466            };
1467            return Some(vec![Action::SendRuntimeCommand(RuntimeCommand::InfoSource)]);
1468        }
1469
1470        // Handle "i sh" -> "info share"
1471        if command == "i sh" {
1472            state.input_state = InputState::WaitingResponse {
1473                command: "info share".to_string(),
1474                sent_time: Instant::now(),
1475                command_type: CommandType::InfoShare,
1476            };
1477            return Some(vec![Action::SendRuntimeCommand(RuntimeCommand::InfoShare)]);
1478        }
1479
1480        // Handle "i sh all" -> "info share all"
1481        if command == "i sh all" {
1482            state.input_state = InputState::WaitingResponse {
1483                command: "info share all".to_string(),
1484                sent_time: Instant::now(),
1485                command_type: CommandType::InfoShareAll,
1486            };
1487            return Some(vec![Action::SendRuntimeCommand(RuntimeCommand::InfoShare)]);
1488        }
1489
1490        // Handle "i file" or "i f" (no args) -> "info file"
1491        if command == "i file" || command == "i f" {
1492            state.input_state = InputState::WaitingResponse {
1493                command: "info file".to_string(),
1494                sent_time: Instant::now(),
1495                command_type: CommandType::InfoFile,
1496            };
1497            return Some(vec![Action::SendRuntimeCommand(RuntimeCommand::InfoFile)]);
1498        }
1499
1500        // Handle "i t" -> "info trace"
1501        if command == "i t" {
1502            return Some(Self::parse_info_trace_command(state, None));
1503        }
1504
1505        // Handle "i t <id>" -> "info trace <id>"
1506        if command.starts_with("i t ") {
1507            let id_str = command.strip_prefix("i t ").unwrap().trim();
1508            if let Ok(trace_id) = id_str.parse::<u32>() {
1509                return Some(Self::parse_info_trace_command(state, Some(trace_id)));
1510            } else {
1511                let plain = "Usage: i t [trace_id]".to_string();
1512                let styled = Self::styled_usage(&plain);
1513                return Some(vec![Action::AddResponseWithStyle {
1514                    content: plain,
1515                    styled_lines: Some(styled),
1516                    response_type: ResponseType::Error,
1517                }]);
1518            }
1519        }
1520
1521        None
1522    }
1523
1524    /// Parse info trace command
1525    fn parse_info_trace_command(
1526        state: &mut CommandPanelState,
1527        trace_id: Option<u32>,
1528    ) -> Vec<Action> {
1529        state.input_state = InputState::WaitingResponse {
1530            command: if let Some(id) = trace_id {
1531                format!("info trace {id}")
1532            } else {
1533                "info trace".to_string()
1534            },
1535            sent_time: Instant::now(),
1536            command_type: CommandType::InfoTrace { trace_id },
1537        };
1538
1539        if trace_id.is_some() {
1540            vec![Action::SendRuntimeCommand(RuntimeCommand::InfoTrace {
1541                trace_id,
1542            })]
1543        } else {
1544            vec![Action::SendRuntimeCommand(RuntimeCommand::InfoTraceAll)]
1545        }
1546    }
1547
1548    /// Format general info help message
1549    fn format_info_help() -> String {
1550        [
1551            "🔍 Info Commands Usage:",
1552            "",
1553            "  info                  - Show this help message",
1554            "  info file             - Show executable file info and sections (i f, i file)",
1555            "  info trace [id]       - Show trace status (i t [id])",
1556            "  info source           - Show all source files by module (i s)",
1557            "  info share            - Show shared libraries WITH debug info (i sh)",
1558            "  info share all        - Show ALL loaded shared libraries (i sh all)",
1559            "  info function <name>  - Show debug info for function (i f <name>)",
1560            "  info line <file:line> - Show debug info for source line (i l <file:line>)",
1561            "  info address <addr>   - Show debug info for address (i a <addr>) [TODO]",
1562            "",
1563            "💡 Shortcuts:",
1564            "  i f / i file          - Same as 'info file'",
1565            "  i s                   - Same as 'info source'",
1566            "  i sh                  - Same as 'info share'",
1567            "  i sh all              - Same as 'info share all'",
1568            "  i t [id]              - Same as 'info trace [id]'",
1569            "  i f <name>            - Same as 'info function <name>'",
1570            "  i l <file:line>       - Same as 'info line <file:line>'",
1571            "  i a <addr>            - Same as 'info address <addr>' [TODO]",
1572            "",
1573            "Examples:",
1574            "  info file             - Show executable file information",
1575            "  info trace            - Show all traces",
1576            "  i t 1                 - Show specific trace info",
1577            "  i f main              - Show debug info for 'main' function",
1578            "  i l file.c:42         - Show debug info for source line",
1579            "",
1580            "💡 Use 'help' for complete command reference.",
1581        ]
1582        .join("\n")
1583    }
1584
1585    /// Check if input should show prompt
1586    pub fn should_show_input_prompt(state: &CommandPanelState) -> bool {
1587        matches!(state.input_state, InputState::Ready)
1588    }
1589
1590    /// Get current prompt string
1591    pub fn get_prompt(state: &CommandPanelState) -> String {
1592        if !Self::should_show_input_prompt(state) {
1593            return String::new();
1594        }
1595
1596        match state.mode {
1597            crate::model::panel_state::InteractionMode::Input => {
1598                UIStrings::GHOSTSCOPE_PROMPT.to_string()
1599            }
1600            crate::model::panel_state::InteractionMode::Command => {
1601                UIStrings::GHOSTSCOPE_PROMPT.to_string()
1602            }
1603            crate::model::panel_state::InteractionMode::ScriptEditor => {
1604                UIStrings::GHOSTSCOPE_PROMPT.to_string()
1605            }
1606        }
1607    }
1608}
1609
1610#[cfg(test)]
1611mod tests {
1612    use super::*;
1613
1614    #[test]
1615    fn test_command_completion_exact_match() {
1616        // Test exact single match
1617        assert_eq!(
1618            CommandParser::get_command_completion("tr"),
1619            Some("ace".to_string())
1620        );
1621        assert_eq!(
1622            CommandParser::get_command_completion("hel"),
1623            Some("p".to_string())
1624        );
1625        assert_eq!(
1626            CommandParser::get_command_completion("clea"),
1627            Some("r".to_string())
1628        );
1629    }
1630
1631    #[test]
1632    fn test_command_completion_multiple_matches() {
1633        // Test multiple matches - should return None when no common prefix exists
1634        assert_eq!(CommandParser::get_command_completion("d"), None); // "delete", "disable", "dis", "del" have no common prefix
1635        assert_eq!(CommandParser::get_command_completion("e"), None); // "enable", "exit" have no common prefix beyond "e"
1636
1637        // Test with actual matches
1638        assert_eq!(
1639            CommandParser::get_command_completion("de"),
1640            Some("l".to_string())
1641        ); // "del" and "delete" -> common prefix "del"
1642        assert_eq!(CommandParser::get_command_completion("info "), None); // Multiple info subcommands, whitespace prefix filtered out
1643    }
1644
1645    #[test]
1646    fn test_command_completion_no_match() {
1647        // Test no matches
1648        assert_eq!(CommandParser::get_command_completion("xyz"), None);
1649        assert_eq!(CommandParser::get_command_completion("unknown"), None);
1650    }
1651
1652    #[test]
1653    fn test_command_completion_exact_command() {
1654        // Test already complete commands
1655        assert_eq!(CommandParser::get_command_completion("trace"), None);
1656        assert_eq!(CommandParser::get_command_completion("help"), None);
1657    }
1658
1659    #[test]
1660    fn test_command_completion_abbreviations() {
1661        // Test abbreviations work
1662        assert_eq!(
1663            CommandParser::get_command_completion("en"),
1664            Some("able".to_string())
1665        ); // "en" -> "enable"
1666        assert_eq!(
1667            CommandParser::get_command_completion("di"),
1668            Some("s".to_string())
1669        ); // "di" -> "dis" (common prefix of "disable")
1670    }
1671}