Skip to main content

vtcode_tui/core_tui/session/
impl_events.rs

1use super::*;
2
3impl Session {
4    pub fn handle_event(
5        &mut self,
6        event: CrosstermEvent,
7        events: &UnboundedSender<InlineEvent>,
8        callback: Option<&(dyn Fn(&InlineEvent) + Send + Sync + 'static)>,
9    ) {
10        match event {
11            CrosstermEvent::Key(key) => {
12                // Only process Press events to avoid duplicate character insertion
13                // Repeat events can cause characters to be inserted multiple times
14                if matches!(key.kind, KeyEventKind::Press)
15                    && let Some(outbound) = events::process_key(self, key)
16                {
17                    self.emit_inline_event(&outbound, events, callback);
18                }
19            }
20            CrosstermEvent::Mouse(mouse_event) => match mouse_event.kind {
21                MouseEventKind::ScrollDown => {
22                    // Check if history picker is active - delegate scrolling to picker
23                    if self.history_picker_state.active {
24                        self.history_picker_state.move_down();
25                        self.mark_dirty();
26                    } else {
27                        self.scroll_line_down();
28                        self.mark_dirty();
29                    }
30                }
31                MouseEventKind::ScrollUp => {
32                    // Check if history picker is active - delegate scrolling to picker
33                    if self.history_picker_state.active {
34                        self.history_picker_state.move_up();
35                        self.mark_dirty();
36                    } else {
37                        self.scroll_line_up();
38                        self.mark_dirty();
39                    }
40                }
41                MouseEventKind::Down(ratatui::crossterm::event::MouseButton::Left) => {
42                    // Start mouse text selection
43                    self.mouse_selection
44                        .start_selection(mouse_event.column, mouse_event.row);
45                    self.mark_dirty();
46                    if !self.handle_input_click(mouse_event) {
47                        self.handle_transcript_click(mouse_event);
48                    }
49                }
50                MouseEventKind::Drag(ratatui::crossterm::event::MouseButton::Left) => {
51                    self.mouse_selection
52                        .update_selection(mouse_event.column, mouse_event.row);
53                    self.mark_dirty();
54                }
55                MouseEventKind::Up(ratatui::crossterm::event::MouseButton::Left) => {
56                    self.mouse_selection
57                        .finish_selection(mouse_event.column, mouse_event.row);
58                    self.mark_dirty();
59                }
60                _ => {}
61            },
62            CrosstermEvent::Paste(content) => {
63                if self.input_enabled {
64                    self.insert_paste_text(&content);
65                    self.check_file_reference_trigger();
66                    self.mark_dirty();
67                } else if let Some(modal) = self.modal.as_mut()
68                    && let (Some(list), Some(search)) = (modal.list.as_mut(), modal.search.as_mut())
69                {
70                    search.insert(&content);
71                    list.apply_search(&search.query);
72                    self.mark_dirty();
73                }
74            }
75            CrosstermEvent::Resize(_, rows) => {
76                self.apply_view_rows(rows);
77                self.mark_dirty();
78            }
79            CrosstermEvent::FocusGained => {
80                // No-op: focus tracking is host/application concern.
81            }
82            CrosstermEvent::FocusLost => {
83                // No-op: focus tracking is host/application concern.
84            }
85        }
86    }
87
88    pub(crate) fn handle_transcript_click(&mut self, mouse_event: MouseEvent) -> bool {
89        if !matches!(
90            mouse_event.kind,
91            MouseEventKind::Down(ratatui::crossterm::event::MouseButton::Left)
92        ) {
93            return false;
94        }
95
96        let Some(area) = self.transcript_area else {
97            return false;
98        };
99
100        if mouse_event.row < area.y
101            || mouse_event.row >= area.y.saturating_add(area.height)
102            || mouse_event.column < area.x
103            || mouse_event.column >= area.x.saturating_add(area.width)
104        {
105            return false;
106        }
107
108        if self.transcript_width == 0 || self.transcript_rows == 0 {
109            return false;
110        }
111
112        let row_in_view = (mouse_event.row - area.y) as usize;
113        if row_in_view >= self.transcript_rows as usize {
114            return false;
115        }
116
117        let viewport_rows = self.transcript_rows.max(1) as usize;
118        let padding = usize::from(ui::INLINE_TRANSCRIPT_BOTTOM_PADDING);
119        let effective_padding = padding.min(viewport_rows.saturating_sub(1));
120        let total_rows = self.total_transcript_rows(self.transcript_width) + effective_padding;
121        let (top_offset, _clamped_total_rows) =
122            self.prepare_transcript_scroll(total_rows, viewport_rows);
123        let view_top = top_offset.min(self.scroll_manager.max_offset());
124        self.transcript_view_top = view_top;
125
126        let clicked_row = view_top.saturating_add(row_in_view);
127        let expanded = self.expand_collapsed_paste_at_row(self.transcript_width, clicked_row);
128        if expanded {
129            self.mark_dirty();
130        }
131        expanded
132    }
133
134    pub(crate) fn handle_input_click(&mut self, mouse_event: MouseEvent) -> bool {
135        if !matches!(
136            mouse_event.kind,
137            MouseEventKind::Down(ratatui::crossterm::event::MouseButton::Left)
138        ) {
139            return false;
140        }
141
142        let Some(area) = self.input_area else {
143            return false;
144        };
145
146        if mouse_event.row < area.y
147            || mouse_event.row >= area.y.saturating_add(area.height)
148            || mouse_event.column < area.x
149            || mouse_event.column >= area.x.saturating_add(area.width)
150        {
151            return false;
152        }
153
154        let cursor_at_end = self.input_manager.cursor() == self.input_manager.content().len();
155        if !self.input_compact_mode || !cursor_at_end {
156            return false;
157        }
158
159        if self.input_compact_placeholder().is_none() {
160            return false;
161        }
162
163        self.input_compact_mode = false;
164        self.mark_dirty();
165        true
166    }
167}