Skip to main content

ghostscope_ui/components/app/
event_loop.rs

1use super::App;
2use crate::action::{Action, PanelType};
3use crate::components::loading::LoadingState;
4use anyhow::Result;
5use crossterm::{
6    event::{DisableBracketedPaste, Event, EventStream, KeyCode, KeyEventKind},
7    execute,
8    terminal::{disable_raw_mode, LeaveAlternateScreen},
9};
10use futures_util::StreamExt;
11use tracing::debug;
12
13impl App {
14    pub async fn run(&mut self) -> Result<()> {
15        debug!("Starting new TEA-based TUI application");
16
17        // Create async event stream (proper crossterm async support)
18        let mut event_stream = EventStream::new();
19        let mut needs_render = true;
20
21        // Create a timeout for loading - if no runtime response, go to ready
22        const LOADING_TIMEOUT_SECS: u64 = 30;
23        let loading_timeout =
24            tokio::time::sleep(tokio::time::Duration::from_secs(LOADING_TIMEOUT_SECS));
25        tokio::pin!(loading_timeout);
26
27        // Create a 1-second interval for loading UI updates (elapsed time, spinner, etc.)
28        let mut loading_ui_ticker = tokio::time::interval(tokio::time::Duration::from_secs(1));
29        loading_ui_ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
30
31        // Periodic housekeeping ticker for lightweight timeout/cleanup checks.
32        // Use an interval instead of recreating sleep futures in each select iteration.
33        let mut housekeeping_ticker = tokio::time::interval(tokio::time::Duration::from_millis(50));
34        housekeeping_ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
35
36        // Initial render
37        self.terminal.draw(|f| Self::draw_ui(f, &mut self.state))?;
38
39        loop {
40            // Handle events using select! to monitor multiple sources
41            tokio::select! {
42                // Handle crossterm events (keyboard, mouse, resize) - proper async
43                Some(event_result) = event_stream.next() => {
44                    match event_result {
45                        Ok(event) => {
46                            if let Event::Key(key) = &event {
47                                tracing::debug!("Raw crossterm event: {:?}", key);
48                            }
49                            if let Err(e) = self.handle_event(event).await {
50                                tracing::error!("Error handling terminal event: {}", e);
51                            }
52                            needs_render = true;
53                        }
54                        Err(e) => {
55                            tracing::error!("Error reading terminal events: {}", e);
56                            break;
57                        }
58                    }
59                }
60
61                // Handle runtime status messages
62                Some(status) = self.state.event_registry.status_receiver.recv() => {
63                    self.handle_runtime_status(status).await;
64                    needs_render = true;
65                }
66
67                // Handle trace events
68                Some(trace_event) = self.state.event_registry.trace_receiver.recv() => {
69                    self.handle_trace_event(trace_event).await;
70                    needs_render = true;
71                }
72
73                // Loading timeout - show error in loading UI
74                () = &mut loading_timeout, if !self.state.loading_state.is_ready() && !self.state.loading_state.is_failed() => {
75                    tracing::info!("No runtime response after {} seconds, connection timeout", LOADING_TIMEOUT_SECS);
76                    self.state.set_loading_state(LoadingState::Failed("Connection timeout - no runtime response".to_string()));
77                    needs_render = true;
78                }
79
80                // Update loading UI periodically (elapsed time, spinner animation)
81                _ = loading_ui_ticker.tick(), if self.state.is_loading() => {
82                    // Just trigger a redraw to update elapsed time and spinner
83                    // No state changes needed - the UI will read fresh elapsed time on render
84                    needs_render = true;
85                }
86
87                // Check for jk escape sequence timeout and periodic cleanup
88                _ = housekeeping_ticker.tick() => {
89                    // Check jk timeout
90                    if crate::components::command_panel::input_handler::InputHandler::check_jk_timeout(&mut self.state.command_panel) {
91                        needs_render = true;
92                    }
93
94                    // Check for command response timeout
95                    if let crate::model::panel_state::InputState::WaitingResponse { sent_time, command, .. } = &self.state.command_panel.input_state {
96                        const COMMAND_TIMEOUT_SECS: u64 = 5;
97                        if sent_time.elapsed().as_secs() >= COMMAND_TIMEOUT_SECS {
98                            let timeout_msg = format!("Command timeout: '{command}' - no response after {COMMAND_TIMEOUT_SECS} seconds");
99                            self.clear_waiting_state();
100                            crate::components::command_panel::ResponseFormatter::add_simple_styled_response(
101                                &mut self.state.command_panel,
102                                timeout_msg,
103                                crate::components::command_panel::style_builder::StylePresets::ERROR,
104                                crate::action::ResponseType::Error,
105                            );
106                            needs_render = true;
107                        }
108                    }
109
110                    // Periodic cleanup of file completion cache
111                    self.state.command_panel.cleanup_file_completion_cache();
112                }
113            }
114
115            // Render only when needed (event-driven)
116            if needs_render {
117                self.terminal.draw(|f| Self::draw_ui(f, &mut self.state))?;
118                needs_render = false;
119            }
120
121            // Check for quit condition
122            if self.should_quit || self.state.should_quit {
123                break;
124            }
125        }
126
127        // Send shutdown command to runtime before cleanup
128        if let Err(e) = self
129            .state
130            .event_registry
131            .command_sender
132            .send(crate::events::RuntimeCommand::Shutdown)
133        {
134            tracing::warn!("Failed to send shutdown command to runtime: {}", e);
135        }
136
137        self.cleanup().await
138    }
139
140    /// Handle terminal events and convert to actions
141    async fn handle_event(&mut self, event: Event) -> Result<bool> {
142        let mut actions_to_process = Vec::new();
143
144        match event {
145            Event::Key(key) => {
146                tracing::debug!(
147                    "Event received: key={:?}, is_loading={}",
148                    key,
149                    self.state.is_loading()
150                );
151                if key.kind == KeyEventKind::Press {
152                    // Always handle input - loading state should not block user interaction
153                    // Loading is purely a visual indication
154
155                    // Handle window navigation mode first
156                    if self.state.ui.focus.expecting_window_nav {
157                        match key.code {
158                            KeyCode::Char('h') => {
159                                actions_to_process.push(Action::WindowNavMove(
160                                    crate::action::WindowDirection::Left,
161                                ));
162                                actions_to_process.push(Action::ExitWindowNavMode);
163                            }
164                            KeyCode::Char('j') => {
165                                actions_to_process.push(Action::WindowNavMove(
166                                    crate::action::WindowDirection::Down,
167                                ));
168                                actions_to_process.push(Action::ExitWindowNavMode);
169                            }
170                            KeyCode::Char('k') => {
171                                actions_to_process.push(Action::WindowNavMove(
172                                    crate::action::WindowDirection::Up,
173                                ));
174                                actions_to_process.push(Action::ExitWindowNavMode);
175                            }
176                            KeyCode::Char('l') => {
177                                actions_to_process.push(Action::WindowNavMove(
178                                    crate::action::WindowDirection::Right,
179                                ));
180                                actions_to_process.push(Action::ExitWindowNavMode);
181                            }
182                            KeyCode::Char('v') => {
183                                actions_to_process.push(Action::SwitchLayout);
184                                actions_to_process.push(Action::ExitWindowNavMode);
185                            }
186                            KeyCode::Char('z') => {
187                                actions_to_process.push(Action::ToggleFullscreen);
188                                actions_to_process.push(Action::ExitWindowNavMode);
189                            }
190                            _ => {
191                                // Any other key cancels window navigation
192                                actions_to_process.push(Action::ExitWindowNavMode);
193                            }
194                        }
195                    }
196
197                    // Normal key handling
198
199                    // Clear Ctrl+C flag for any key that's not Ctrl+C
200                    let is_ctrl_c = matches!(key.code, KeyCode::Char('c'))
201                        && key
202                            .modifiers
203                            .contains(crossterm::event::KeyModifiers::CONTROL);
204                    if !is_ctrl_c {
205                        self.state.expecting_second_ctrl_c = false;
206                    }
207
208                    match key.code {
209                        KeyCode::Char('c')
210                            if key
211                                .modifiers
212                                .contains(crossterm::event::KeyModifiers::CONTROL) =>
213                        {
214                            // Use the new centralized Ctrl+C handler
215                            let ctrl_c_actions = self.handle_ctrl_c();
216                            actions_to_process.extend(ctrl_c_actions);
217                        }
218                        KeyCode::Char('w')
219                            if key
220                                .modifiers
221                                .contains(crossterm::event::KeyModifiers::CONTROL) =>
222                        {
223                            // Handle Ctrl+W based on current focus and mode - priority order matters!
224                            if self.state.ui.focus.current_panel == crate::action::PanelType::Source
225                                && self.state.source_panel.mode
226                                    == crate::model::panel_state::SourcePanelMode::FileSearch
227                            {
228                                // HIGHEST PRIORITY: File search delete word
229                                if let Some(ref cache) =
230                                    self.state.command_panel.file_completion_cache
231                                {
232                                    let delete_actions = crate::components::source_panel::SourceSearch::delete_word_file_search(
233                                        &mut self.state.source_panel,
234                                        cache,
235                                    );
236                                    actions_to_process.extend(delete_actions);
237                                }
238                            } else if self.state.ui.focus.current_panel
239                                == crate::action::PanelType::InteractiveCommand
240                            {
241                                match self.state.command_panel.mode {
242                                    crate::model::panel_state::InteractionMode::Input => {
243                                        actions_to_process.push(Action::DeletePreviousWord);
244                                    }
245                                    crate::model::panel_state::InteractionMode::ScriptEditor => {
246                                        actions_to_process.push(Action::DeletePreviousWord);
247                                    }
248                                    _ => {
249                                        // In command mode, use for window navigation
250                                        actions_to_process.push(Action::EnterWindowNavMode);
251                                    }
252                                }
253                            } else {
254                                // In other panels, use for window navigation
255                                actions_to_process.push(Action::EnterWindowNavMode);
256                            }
257                        }
258                        KeyCode::Tab => {
259                            // Handle Tab based on current panel and mode - priority order matters!
260                            if self.state.ui.focus.current_panel == crate::action::PanelType::Source
261                                && self.state.source_panel.mode
262                                    == crate::model::panel_state::SourcePanelMode::FileSearch
263                            {
264                                // HIGHEST PRIORITY: File search navigation
265                                let move_actions = crate::components::source_panel::SourceSearch::move_file_search_down(
266                                        &mut self.state.source_panel,
267                                    );
268                                actions_to_process.extend(move_actions);
269                            } else if self.state.ui.focus.current_panel
270                                == crate::action::PanelType::InteractiveCommand
271                                && self.state.command_panel.mode
272                                    == crate::model::panel_state::InteractionMode::ScriptEditor
273                            {
274                                // Script editor Tab inserts spaces
275                                actions_to_process.push(Action::InsertTab);
276                            } else if self.state.ui.focus.current_panel
277                                == crate::action::PanelType::InteractiveCommand
278                                && self.state.command_panel.mode
279                                    == crate::model::panel_state::InteractionMode::Input
280                            {
281                                // COMMAND INPUT MODE: Let Tab go to focused panel handler for auto-suggestion
282                                let panel_actions = self.handle_focused_panel_input(key)?;
283                                actions_to_process.extend(panel_actions);
284                            } else {
285                                // Normal Tab behavior: cycle focus
286                                actions_to_process.push(Action::FocusNext);
287                            }
288                        }
289                        KeyCode::BackTab => {
290                            // Handle Shift+Tab based on current panel and mode
291                            if self.state.ui.focus.current_panel == crate::action::PanelType::Source
292                                && self.state.source_panel.mode
293                                    == crate::model::panel_state::SourcePanelMode::FileSearch
294                            {
295                                // HIGHEST PRIORITY: File search navigation (up)
296                                let move_actions = crate::components::source_panel::SourceSearch::move_file_search_up(
297                                        &mut self.state.source_panel,
298                                    );
299                                actions_to_process.extend(move_actions);
300                            } else {
301                                // Normal Shift+Tab behavior: cycle focus backward
302                                actions_to_process.push(Action::FocusPrevious);
303                            }
304                        }
305                        KeyCode::F(1) => {
306                            actions_to_process.push(Action::ToggleFullscreen);
307                        }
308                        KeyCode::F(2) => {
309                            actions_to_process.push(Action::SwitchLayout);
310                        }
311                        _ => {
312                            // Forward to focused panel handler
313                            let panel_actions = self.handle_focused_panel_input(key)?;
314                            actions_to_process.extend(panel_actions);
315                        }
316                    }
317                }
318            }
319            Event::Resize(width, height) => {
320                actions_to_process.push(Action::Resize(width, height));
321            }
322            Event::Paste(pasted) => {
323                tracing::debug!("Event received: paste_len={}", pasted.len());
324                // Batch insert pasted text depending on focused panel and mode
325                match self.state.ui.focus.current_panel {
326                    PanelType::InteractiveCommand => {
327                        match self.state.command_panel.mode {
328                            crate::model::panel_state::InteractionMode::Input => {
329                                let actions = self
330                                    .state
331                                    .command_input_handler
332                                    .insert_str(&mut self.state.command_panel, &pasted);
333                                actions_to_process.extend(actions);
334                                self.state.command_renderer.mark_pending_updates();
335                            }
336                            crate::model::panel_state::InteractionMode::ScriptEditor => {
337                                let actions =
338                                    crate::components::command_panel::ScriptEditor::insert_text(
339                                        &mut self.state.command_panel,
340                                        &pasted,
341                                    );
342                                actions_to_process.extend(actions);
343                                self.state.command_renderer.mark_pending_updates();
344                            }
345                            crate::model::panel_state::InteractionMode::Command => {
346                                // Ignore paste in command mode
347                            }
348                        }
349                    }
350                    _ => {
351                        // Ignore paste in other panels
352                    }
353                }
354            }
355            _ => {}
356        }
357
358        // Process all actions
359        for action in actions_to_process {
360            let is_quit = matches!(action, Action::Quit);
361            let additional_actions = self.handle_action(action)?;
362
363            // Process any additional actions returned
364            for additional_action in additional_actions {
365                self.handle_action(additional_action)?;
366            }
367
368            if is_quit || self.state.should_quit {
369                return Ok(true);
370            }
371        }
372
373        Ok(false)
374    }
375
376    /// Handle input for the currently focused panel
377    fn handle_focused_panel_input(
378        &mut self,
379        key: crossterm::event::KeyEvent,
380    ) -> Result<Vec<Action>> {
381        let mut actions = Vec::new();
382
383        match self.state.ui.focus.current_panel {
384            PanelType::InteractiveCommand => {
385                // First, try the new unified key event handler for history and suggestions
386                let unified_actions = self
387                    .state
388                    .command_input_handler
389                    .handle_key_event(&mut self.state.command_panel, key);
390
391                if !unified_actions.is_empty() {
392                    // The unified handler handled the key, mark for updates and return
393                    self.state.command_renderer.mark_pending_updates();
394                    return Ok(unified_actions);
395                }
396
397                // Fall back to existing character-based handling
398                match key.code {
399                    KeyCode::Char(c) => {
400                        tracing::debug!(
401                            "App received char='{}' (code={}), modifiers={:?}, current_panel={:?}",
402                            c,
403                            c as u32,
404                            key.modifiers,
405                            self.state.ui.focus.current_panel
406                        );
407                        // Handle Ctrl+key combinations first
408                        if key
409                            .modifiers
410                            .contains(crossterm::event::KeyModifiers::CONTROL)
411                        {
412                            match c {
413                                's' => {
414                                    // Ctrl+S - only submit script in script mode
415                                    if matches!(
416                                        self.state.command_panel.mode,
417                                        crate::model::panel_state::InteractionMode::ScriptEditor
418                                    ) {
419                                        actions.push(Action::SubmitScript);
420                                    }
421                                }
422                                'a' => {
423                                    match self.state.command_panel.mode {
424                                        crate::model::panel_state::InteractionMode::ScriptEditor => {
425                                            // Ctrl+A - move to beginning of current line in script mode
426                                            let script_actions = crate::components::command_panel::ScriptEditor::move_to_beginning(
427                                                &mut self.state.command_panel,
428                                            );
429                                            actions.extend(script_actions);
430                                        }
431                                        _ => {
432                                            // Ctrl+A - move to beginning of line in input/command mode
433                                            actions.push(Action::MoveCursor(crate::action::CursorDirection::Home));
434                                        }
435                                    }
436                                }
437                                'e' => {
438                                    match self.state.command_panel.mode {
439                                        crate::model::panel_state::InteractionMode::ScriptEditor => {
440                                            // Ctrl+E - move to end of current line in script mode
441                                            let script_actions = crate::components::command_panel::ScriptEditor::move_to_end(
442                                                &mut self.state.command_panel,
443                                            );
444                                            actions.extend(script_actions);
445                                        }
446                                        _ => {
447                                            // Ctrl+E - move to end of line in input/command mode
448                                            actions.push(Action::MoveCursor(crate::action::CursorDirection::End));
449                                        }
450                                    }
451                                }
452                                'f' => {
453                                    match self.state.command_panel.mode {
454                                        crate::model::panel_state::InteractionMode::ScriptEditor => {
455                                            // Ctrl+F - move cursor right (forward one character) in script mode
456                                            let script_actions = crate::components::command_panel::ScriptEditor::move_cursor_right(
457                                                &mut self.state.command_panel,
458                                            );
459                                            actions.extend(script_actions);
460                                        }
461                                        _ => {
462                                            // Ctrl+F - move cursor right in input/command mode
463                                            actions.push(Action::MoveCursor(crate::action::CursorDirection::Right));
464                                        }
465                                    }
466                                }
467                                'b' => {
468                                    match self.state.command_panel.mode {
469                                        crate::model::panel_state::InteractionMode::ScriptEditor => {
470                                            // Ctrl+B - move cursor left (back one character) in script mode
471                                            let script_actions = crate::components::command_panel::ScriptEditor::move_cursor_left(
472                                                &mut self.state.command_panel,
473                                            );
474                                            actions.extend(script_actions);
475                                        }
476                                        _ => {
477                                            // Ctrl+B - move cursor left in input/command mode
478                                            actions.push(Action::MoveCursor(crate::action::CursorDirection::Left));
479                                        }
480                                    }
481                                }
482                                'u' => {
483                                    match self.state.command_panel.mode {
484                                        crate::model::panel_state::InteractionMode::ScriptEditor => {
485                                            // Ctrl+U - delete from cursor to line start in script mode
486                                            let script_actions = crate::components::command_panel::ScriptEditor::delete_to_line_start(
487                                                &mut self.state.command_panel,
488                                            );
489                                            actions.extend(script_actions);
490                                        }
491                                        crate::model::panel_state::InteractionMode::Command => {
492                                            // Ctrl+U - half page up in command mode (fast scroll)
493                                            actions.push(Action::CommandHalfPageUp);
494                                        }
495                                        _ => {
496                                            // Ctrl+U - delete to beginning in input mode
497                                            actions.push(Action::DeleteToBeginning);
498                                        }
499                                    }
500                                }
501                                'd' => {
502                                    match self.state.command_panel.mode {
503                                        crate::model::panel_state::InteractionMode::Command => {
504                                            // Ctrl+D - half page down in command mode (fast scroll)
505                                            actions.push(Action::CommandHalfPageDown);
506                                        }
507                                        _ => {
508                                            // Ctrl+D might be used for other purposes in other modes
509                                        }
510                                    }
511                                }
512                                'k' => {
513                                    match self.state.command_panel.mode {
514                                        crate::model::panel_state::InteractionMode::ScriptEditor => {
515                                            // Ctrl+K - delete from cursor to line end in script mode
516                                            let script_actions = crate::components::command_panel::ScriptEditor::delete_to_end(
517                                                &mut self.state.command_panel,
518                                            );
519                                            actions.extend(script_actions);
520                                        }
521                                        _ => {
522                                            // Ctrl+K - delete to end in input/command mode
523                                            actions.push(Action::DeleteToEnd);
524                                        }
525                                    }
526                                }
527                                'w' => {
528                                    match self.state.command_panel.mode {
529                                        crate::model::panel_state::InteractionMode::ScriptEditor => {
530                                            // Ctrl+W - delete previous word in script mode
531                                            let script_actions = crate::components::command_panel::ScriptEditor::delete_previous_word(
532                                                &mut self.state.command_panel,
533                                            );
534                                            actions.extend(script_actions);
535                                        }
536                                        _ => {
537                                            // Ctrl+W - delete previous word in input/command mode
538                                            actions.push(Action::DeletePreviousWord);
539                                        }
540                                    }
541                                }
542                                'p' => {
543                                    match self.state.command_panel.mode {
544                                        crate::model::panel_state::InteractionMode::Input => {
545                                            // Ctrl+P - go to previous command in input mode
546                                            actions.push(Action::HistoryPrevious);
547                                        }
548                                        crate::model::panel_state::InteractionMode::ScriptEditor => {
549                                            // Ctrl+P - move cursor up (previous line) in script mode
550                                            let script_actions = crate::components::command_panel::ScriptEditor::move_cursor_up(
551                                                &mut self.state.command_panel,
552                                            );
553                                            actions.extend(script_actions);
554                                        }
555                                        _ => {
556                                            // Other modes: use original behavior
557                                            actions.push(Action::HistoryUp);
558                                        }
559                                    }
560                                }
561                                'n' => {
562                                    match self.state.command_panel.mode {
563                                        crate::model::panel_state::InteractionMode::Input => {
564                                            // Ctrl+N - go to next command in input mode
565                                            actions.push(Action::HistoryNext);
566                                        }
567                                        crate::model::panel_state::InteractionMode::ScriptEditor => {
568                                            // Ctrl+N - move cursor down (next line) in script mode
569                                            let script_actions = crate::components::command_panel::ScriptEditor::move_cursor_down(
570                                                &mut self.state.command_panel,
571                                            );
572                                            actions.extend(script_actions);
573                                        }
574                                        _ => {
575                                            // Other modes: use original behavior
576                                            actions.push(Action::HistoryDown);
577                                        }
578                                    }
579                                }
580                                'i' => actions.push(Action::InsertTab),
581                                'h' => {
582                                    match self.state.command_panel.mode {
583                                        crate::model::panel_state::InteractionMode::ScriptEditor => {
584                                            // Ctrl+H - delete character (backspace) in script mode
585                                            let script_actions = crate::components::command_panel::ScriptEditor::delete_char_at_cursor(
586                                                &mut self.state.command_panel,
587                                            );
588                                            actions.extend(script_actions);
589                                        }
590                                        _ => {
591                                            // Ctrl+H - Backspace in input/command mode
592                                            let handler_actions = self
593                                                .state
594                                                .command_input_handler
595                                                .handle_backspace(&mut self.state.command_panel);
596                                            actions.extend(handler_actions);
597                                            self.state.command_renderer.mark_pending_updates();
598                                        }
599                                    }
600                                }
601                                _ => {
602                                    // Use optimized input handler for regular character input
603                                    let handler_actions = self
604                                        .state
605                                        .command_input_handler
606                                        .handle_char_input(&mut self.state.command_panel, c);
607                                    actions.extend(handler_actions);
608                                    self.state.command_renderer.mark_pending_updates();
609                                }
610                            }
611                        } else {
612                            // Handle non-Ctrl character input based on mode
613                            match self.state.command_panel.mode {
614                                crate::model::panel_state::InteractionMode::Command => {
615                                    // In command mode, handle vim-style navigation
616                                    match c {
617                                        'j' => {
618                                            // Move cursor down in unified line view
619                                            actions.push(Action::CommandCursorDown);
620                                        }
621                                        'k' => {
622                                            // Move cursor up in unified line view
623                                            actions.push(Action::CommandCursorUp);
624                                        }
625                                        'h' => {
626                                            // Move cursor left in current line
627                                            actions.push(Action::CommandCursorLeft);
628                                        }
629                                        'l' => {
630                                            // Move cursor right in current line
631                                            actions.push(Action::CommandCursorRight);
632                                        }
633                                        'i' => {
634                                            // Exit command mode and return to previous mode
635                                            actions.push(Action::ExitCommandMode);
636                                        }
637                                        'g' => {
638                                            // Go to top of history (vim style)
639                                            self.state.command_panel.command_cursor_line = 0;
640                                            self.state.command_panel.command_cursor_column = 0;
641                                            self.state.command_renderer.mark_pending_updates();
642                                        }
643                                        'G' => {
644                                            // Go to the last line of the entire content, including current input
645                                            // Use wrapped lines to handle text that exceeds panel width
646                                            let wrapped_lines = self
647                                                .state
648                                                .command_panel
649                                                .get_command_mode_wrapped_lines(
650                                                    self.state.command_panel_width,
651                                                );
652
653                                            if !wrapped_lines.is_empty() {
654                                                let last_line =
655                                                    wrapped_lines.len().saturating_sub(1);
656                                                self.state.command_panel.command_cursor_line =
657                                                    last_line;
658                                                // Set column to end of the last line
659                                                self.state.command_panel.command_cursor_column =
660                                                    wrapped_lines[last_line].chars().count();
661                                            }
662                                            self.state.command_renderer.mark_pending_updates();
663                                        }
664                                        '$' => {
665                                            // Go to end of current line
666                                            if self.state.command_panel.command_cursor_line
667                                                < self.state.command_panel.command_history.len()
668                                            {
669                                                self.state.command_panel.command_cursor_column =
670                                                    self.state.command_panel.command_history[self
671                                                        .state
672                                                        .command_panel
673                                                        .command_cursor_line]
674                                                        .command
675                                                        .chars()
676                                                        .count();
677                                            }
678                                            self.state.command_renderer.mark_pending_updates();
679                                        }
680                                        '0' => {
681                                            // Go to beginning of current line
682                                            self.state.command_panel.command_cursor_column = 0;
683                                            self.state.command_renderer.mark_pending_updates();
684                                        }
685                                        _ => {
686                                            // For other characters in command mode, do nothing or handle as needed
687                                        }
688                                    }
689                                }
690                                _ => {
691                                    // For input and script modes, use normal input handler
692                                    let handler_actions = self
693                                        .state
694                                        .command_input_handler
695                                        .handle_char_input(&mut self.state.command_panel, c);
696                                    actions.extend(handler_actions);
697                                    self.state.command_renderer.mark_pending_updates();
698                                }
699                            }
700                        }
701                    }
702                    KeyCode::Backspace => {
703                        let handler_actions = self
704                            .state
705                            .command_input_handler
706                            .handle_backspace(&mut self.state.command_panel);
707                        actions.extend(handler_actions);
708                        self.state.command_renderer.mark_pending_updates();
709                    }
710                    KeyCode::Enter => {
711                        actions.push(Action::SubmitCommand);
712                    }
713                    KeyCode::Up
714                    | KeyCode::Down
715                    | KeyCode::Left
716                    | KeyCode::Right
717                    | KeyCode::Home
718                    | KeyCode::End => {
719                        let direction = match key.code {
720                            KeyCode::Up => crate::action::CursorDirection::Up,
721                            KeyCode::Down => crate::action::CursorDirection::Down,
722                            KeyCode::Left => crate::action::CursorDirection::Left,
723                            KeyCode::Right => crate::action::CursorDirection::Right,
724                            KeyCode::Home => crate::action::CursorDirection::Home,
725                            KeyCode::End => crate::action::CursorDirection::End,
726                            _ => unreachable!(),
727                        };
728                        let handler_actions = self
729                            .state
730                            .command_input_handler
731                            .handle_movement(&mut self.state.command_panel, direction);
732                        actions.extend(handler_actions);
733                        self.state.command_renderer.mark_pending_updates();
734                    }
735                    KeyCode::Esc => {
736                        // Handle Esc based on current mode
737                        match self.state.command_panel.mode {
738                            crate::model::panel_state::InteractionMode::ScriptEditor => {
739                                // Script mode: Esc exits to input mode (traditional behavior)
740                                actions.push(Action::ExitScriptMode);
741                            }
742                            crate::model::panel_state::InteractionMode::Input => {
743                                // Input mode: Esc enters command mode
744                                actions.push(Action::EnterCommandMode);
745                            }
746                            crate::model::panel_state::InteractionMode::Command => {
747                                // Already in command mode, do nothing
748                            }
749                        }
750                    }
751                    _ => {}
752                }
753            }
754            PanelType::Source => {
755                // Handle source panel input based on current mode
756                match self.state.source_panel.mode {
757                    crate::model::panel_state::SourcePanelMode::Normal => match key.code {
758                        KeyCode::Up => {
759                            actions
760                                .push(Action::NavigateSource(crate::action::SourceNavigation::Up));
761                        }
762                        KeyCode::Down => {
763                            actions.push(Action::NavigateSource(
764                                crate::action::SourceNavigation::Down,
765                            ));
766                        }
767                        KeyCode::Left => {
768                            actions.push(Action::NavigateSource(
769                                crate::action::SourceNavigation::Left,
770                            ));
771                        }
772                        KeyCode::Right => {
773                            actions.push(Action::NavigateSource(
774                                crate::action::SourceNavigation::Right,
775                            ));
776                        }
777                        KeyCode::PageUp => {
778                            actions.push(Action::NavigateSource(
779                                crate::action::SourceNavigation::PageUp,
780                            ));
781                        }
782                        KeyCode::PageDown => {
783                            actions.push(Action::NavigateSource(
784                                crate::action::SourceNavigation::PageDown,
785                            ));
786                        }
787                        KeyCode::Char('/') => {
788                            actions.push(Action::EnterTextSearch);
789                        }
790                        KeyCode::Char('o') => {
791                            actions.push(Action::EnterFileSearch);
792                        }
793                        KeyCode::Char('g') => {
794                            actions.push(Action::SourceGoToLine);
795                        }
796                        KeyCode::Char('G') => {
797                            actions.push(Action::SourceGoToBottom);
798                        }
799                        KeyCode::Char('h') => {
800                            actions.push(Action::NavigateSource(
801                                crate::action::SourceNavigation::Left,
802                            ));
803                        }
804                        KeyCode::Char('j') => {
805                            actions.push(Action::NavigateSource(
806                                crate::action::SourceNavigation::Down,
807                            ));
808                        }
809                        KeyCode::Char('k') => {
810                            actions
811                                .push(Action::NavigateSource(crate::action::SourceNavigation::Up));
812                        }
813                        KeyCode::Char('l') => {
814                            actions.push(Action::NavigateSource(
815                                crate::action::SourceNavigation::Right,
816                            ));
817                        }
818                        KeyCode::Char('n') => {
819                            actions.push(Action::NavigateSource(
820                                crate::action::SourceNavigation::NextMatch,
821                            ));
822                        }
823                        KeyCode::Char('N') => {
824                            actions.push(Action::NavigateSource(
825                                crate::action::SourceNavigation::PrevMatch,
826                            ));
827                        }
828                        KeyCode::Char('w') => {
829                            actions.push(Action::NavigateSource(
830                                crate::action::SourceNavigation::WordForward,
831                            ));
832                        }
833                        KeyCode::Char('b') => {
834                            actions.push(Action::NavigateSource(
835                                crate::action::SourceNavigation::WordBackward,
836                            ));
837                        }
838                        KeyCode::Char('^') => {
839                            actions.push(Action::NavigateSource(
840                                crate::action::SourceNavigation::LineStart,
841                            ));
842                        }
843                        KeyCode::Char('$') => {
844                            actions.push(Action::NavigateSource(
845                                crate::action::SourceNavigation::LineEnd,
846                            ));
847                        }
848                        KeyCode::Char(' ') => {
849                            // Space key - set trace at current line
850                            actions.push(Action::SetTraceFromSourceLine);
851                        }
852                        KeyCode::Char(c) => {
853                            // Handle Ctrl+key combinations in source panel
854                            if key
855                                .modifiers
856                                .contains(crossterm::event::KeyModifiers::CONTROL)
857                            {
858                                match c {
859                                    'd' => {
860                                        // Ctrl+D - half page down (10 lines)
861                                        actions.push(Action::NavigateSource(
862                                            crate::action::SourceNavigation::HalfPageDown,
863                                        ));
864                                    }
865                                    'u' => {
866                                        // Ctrl+U - half page up (10 lines)
867                                        actions.push(Action::NavigateSource(
868                                            crate::action::SourceNavigation::HalfPageUp,
869                                        ));
870                                    }
871                                    _ => {}
872                                }
873                            } else if c.is_ascii_digit() {
874                                actions.push(Action::SourceNumberInput(c));
875                            }
876                        }
877                        KeyCode::Esc => {
878                            // Clear all search highlights and navigation state (like vim)
879                            let clear_actions =
880                                crate::components::source_panel::SourceNavigation::clear_all_state(
881                                    &mut self.state.source_panel,
882                                );
883                            actions.extend(clear_actions);
884                        }
885                        _ => {}
886                    },
887                    crate::model::panel_state::SourcePanelMode::TextSearch => match key.code {
888                        KeyCode::Char(c) => {
889                            actions.push(Action::SourceSearchInput(c));
890                        }
891                        KeyCode::Backspace => {
892                            actions.push(Action::SourceSearchBackspace);
893                        }
894                        KeyCode::Enter => {
895                            actions.push(Action::SourceSearchConfirm);
896                        }
897                        KeyCode::Esc => {
898                            actions.push(Action::ExitTextSearch);
899                        }
900                        _ => {}
901                    },
902                    crate::model::panel_state::SourcePanelMode::FileSearch => match key.code {
903                        KeyCode::Char(c) => {
904                            // Handle Ctrl+key combinations in file search
905                            if key
906                                .modifiers
907                                .contains(crossterm::event::KeyModifiers::CONTROL)
908                            {
909                                match c {
910                                    'n' => {
911                                        // Ctrl+N - move down in file search
912                                        let move_actions = crate::components::source_panel::SourceSearch::move_file_search_down(
913                                            &mut self.state.source_panel,
914                                        );
915                                        actions.extend(move_actions);
916                                    }
917                                    'p' => {
918                                        // Ctrl+P - move up in file search
919                                        let move_actions = crate::components::source_panel::SourceSearch::move_file_search_up(
920                                            &mut self.state.source_panel,
921                                        );
922                                        actions.extend(move_actions);
923                                    }
924                                    'd' => {
925                                        // Ctrl+D - page down in file search (move down multiple items)
926                                        for _ in 0..5 {
927                                            let move_actions = crate::components::source_panel::SourceSearch::move_file_search_down(
928                                                &mut self.state.source_panel,
929                                            );
930                                            actions.extend(move_actions);
931                                        }
932                                    }
933                                    'u' => {
934                                        // Ctrl+U - clear entire query
935                                        if let Some(ref cache) =
936                                            self.state.command_panel.file_completion_cache
937                                        {
938                                            let clear_actions = crate::components::source_panel::SourceSearch::clear_file_search_query(
939                                                &mut self.state.source_panel,
940                                                cache,
941                                            );
942                                            actions.extend(clear_actions);
943                                        }
944                                    }
945                                    'a' => {
946                                        // Ctrl+A - move cursor to beginning
947                                        let move_actions = crate::components::source_panel::SourceSearch::move_cursor_to_start(
948                                            &mut self.state.source_panel,
949                                        );
950                                        actions.extend(move_actions);
951                                    }
952                                    'e' => {
953                                        // Ctrl+E - move cursor to end
954                                        let move_actions = crate::components::source_panel::SourceSearch::move_cursor_to_end(
955                                            &mut self.state.source_panel,
956                                        );
957                                        actions.extend(move_actions);
958                                    }
959                                    'w' => {
960                                        // Ctrl+W - delete previous word
961                                        if let Some(ref cache) =
962                                            self.state.command_panel.file_completion_cache
963                                        {
964                                            let delete_actions = crate::components::source_panel::SourceSearch::delete_word_file_search(
965                                                &mut self.state.source_panel,
966                                                cache,
967                                            );
968                                            actions.extend(delete_actions);
969                                        }
970                                    }
971                                    'b' => {
972                                        // Ctrl+B - move cursor left
973                                        let move_actions = crate::components::source_panel::SourceSearch::move_cursor_left(
974                                            &mut self.state.source_panel,
975                                        );
976                                        actions.extend(move_actions);
977                                    }
978                                    'f' => {
979                                        // Ctrl+F - move cursor right
980                                        let move_actions = crate::components::source_panel::SourceSearch::move_cursor_right(
981                                            &mut self.state.source_panel,
982                                        );
983                                        actions.extend(move_actions);
984                                    }
985                                    'h' => {
986                                        // Ctrl+H - delete previous character (same as backspace)
987                                        actions.push(Action::SourceFileSearchBackspace);
988                                    }
989                                    _ => {
990                                        // Regular character input
991                                        actions.push(Action::SourceFileSearchInput(c));
992                                    }
993                                }
994                            } else {
995                                // Regular character input
996                                actions.push(Action::SourceFileSearchInput(c));
997                            }
998                        }
999                        KeyCode::Backspace => {
1000                            actions.push(Action::SourceFileSearchBackspace);
1001                        }
1002                        KeyCode::Enter => {
1003                            actions.push(Action::SourceFileSearchConfirm);
1004                        }
1005                        KeyCode::Up => {
1006                            // Arrow Up - move up in file search
1007                            let move_actions =
1008                                crate::components::source_panel::SourceSearch::move_file_search_up(
1009                                    &mut self.state.source_panel,
1010                                );
1011                            actions.extend(move_actions);
1012                        }
1013                        KeyCode::Down => {
1014                            // Arrow Down - move down in file search
1015                            let move_actions = crate::components::source_panel::SourceSearch::move_file_search_down(
1016                                &mut self.state.source_panel,
1017                            );
1018                            actions.extend(move_actions);
1019                        }
1020                        KeyCode::Esc => {
1021                            actions.push(Action::ExitFileSearch);
1022                        }
1023                        _ => {}
1024                    },
1025                }
1026            }
1027            PanelType::EbpfInfo => {
1028                // Handle eBPF panel input using the dedicated handler
1029                let panel_actions = self
1030                    .state
1031                    .ebpf_panel_handler
1032                    .handle_key_event(&mut self.state.ebpf_panel, key);
1033                actions.extend(panel_actions);
1034            }
1035        }
1036        Ok(actions)
1037    }
1038
1039    fn handle_ctrl_c(&mut self) -> Vec<Action> {
1040        // If eBPF panel is in expanded view, close it on single Ctrl+C
1041        if self.state.ui.focus.current_panel == crate::action::PanelType::EbpfInfo
1042            && self.state.ebpf_panel.is_expanded()
1043        {
1044            self.state.ebpf_panel.close_expanded();
1045            // Do not treat as first press for quitting
1046            self.state.expecting_second_ctrl_c = false;
1047            return vec![];
1048        }
1049        // Check if this is a double Ctrl+C press (consecutive, no timeout)
1050        let is_double_press = self.state.expecting_second_ctrl_c;
1051
1052        // Set flag for next Ctrl+C press
1053        self.state.expecting_second_ctrl_c = true;
1054
1055        // Handle double press - always quit
1056        if is_double_press {
1057            tracing::info!("Double Ctrl+C detected, quitting application");
1058            return vec![Action::Quit];
1059        }
1060
1061        // Single Ctrl+C - handle based on current context
1062        match self.state.ui.focus.current_panel {
1063            crate::action::PanelType::InteractiveCommand => {
1064                // Command panel specific handling
1065                if self.state.command_panel.is_in_history_search() {
1066                    // In history search mode - exit search directly
1067                    self.state.command_panel.exit_history_search();
1068                    self.state.command_panel.input_text.clear();
1069                    self.state.command_panel.cursor_position = 0;
1070                    // Don't add empty response - would overwrite previous command's response
1071                    vec![]
1072                } else {
1073                    match self.state.command_panel.mode {
1074                        crate::model::panel_state::InteractionMode::ScriptEditor => {
1075                            // In script mode - exit to input mode
1076                            vec![Action::ExitScriptMode]
1077                        }
1078                        crate::model::panel_state::InteractionMode::Input => {
1079                            // In input mode - clear input and add "quit" command
1080                            self.state.command_panel.input_text.clear();
1081                            self.state.command_panel.cursor_position = 0;
1082                            self.state.command_panel.input_text = "quit".to_string();
1083                            self.state.command_panel.cursor_position = 4;
1084                            // Clear auto-suggestion to prevent suggestions after "quit"
1085                            self.state.command_panel.auto_suggestion.clear();
1086                            // Don't add response here - it would attach to previous command in history
1087                            // User will see "quit" in input box, which is clear enough
1088                            vec![]
1089                        }
1090                        _ => {
1091                            // Other modes - no action needed
1092                            vec![]
1093                        }
1094                    }
1095                }
1096            }
1097            crate::action::PanelType::Source => {
1098                if self.state.source_panel.mode
1099                    == crate::model::panel_state::SourcePanelMode::FileSearch
1100                {
1101                    // In file search mode - exit file search
1102                    vec![Action::ExitFileSearch]
1103                } else {
1104                    // Normal source panel - no action needed
1105                    vec![]
1106                }
1107            }
1108            _ => {
1109                // Other panels - no action needed
1110                vec![]
1111            }
1112        }
1113    }
1114
1115    /// Cleanup terminal
1116    async fn cleanup(&mut self) -> Result<()> {
1117        disable_raw_mode()?;
1118        // Disable bracketed paste before leaving alternate screen
1119        execute!(self.terminal.backend_mut(), DisableBracketedPaste)?;
1120        execute!(self.terminal.backend_mut(), LeaveAlternateScreen)?;
1121        // Mouse capture was not enabled, so no need to disable it
1122        self.terminal.show_cursor()?;
1123        Ok(())
1124    }
1125}