Skip to main content

vtcode_tui/core_tui/widgets/
input.rs

1use ratatui::{
2    buffer::Buffer,
3    layout::Rect,
4    style::Modifier,
5    text::Line,
6    widgets::{Clear, Paragraph, Widget, Wrap},
7};
8
9use crate::ui::tui::session::Session;
10
11/// Widget for rendering the input area with user text entry
12///
13/// This widget handles:
14/// - Input area rendering with a subtle background shade and padding
15/// - Status line rendering below the input box
16/// - Cursor positioning using buffer capabilities
17/// - Text content from session's input widget data
18///
19/// # Example
20/// ```ignore
21/// InputWidget::new(session)
22///     .area(area)
23///     .render(area, buf);
24/// ```
25pub struct InputWidget<'a> {
26    session: &'a mut Session,
27    area: Option<Rect>,
28}
29
30impl<'a> InputWidget<'a> {
31    /// Create a new InputWidget with required parameters
32    pub fn new(session: &'a mut Session) -> Self {
33        Self {
34            session,
35            area: None,
36        }
37    }
38
39    /// Set the area for rendering (used for cursor positioning calculations)
40    #[must_use]
41    pub fn area(mut self, area: Rect) -> Self {
42        self.area = Some(area);
43        self
44    }
45}
46
47impl<'a> Widget for InputWidget<'a> {
48    fn render(self, area: Rect, buf: &mut Buffer) {
49        Clear.render(area, buf);
50        if area.height == 0 {
51            return;
52        }
53
54        let mut input_area = area;
55        let mut status_area = None;
56        let mut status_line = None;
57
58        // Calculate areas for input and status line
59        if area.height >= 2
60            && let Some(spans) = self.session.build_input_status_widget_data(area.width)
61        {
62            let block_height = area.height.saturating_sub(1).max(1);
63            input_area.height = block_height;
64            status_area = Some(Rect::new(area.x, area.y + block_height, area.width, 1));
65            status_line = Some(Line::from(spans));
66        }
67
68        let temp_data = self.session.build_input_widget_data(1, 1);
69        let shell_mode_title = self.session.shell_mode_border_title();
70        let mut block = if shell_mode_title.is_some() {
71            ratatui::widgets::Block::bordered()
72        } else {
73            ratatui::widgets::Block::new()
74        };
75        block = block
76            .style(temp_data.background_style)
77            .padding(self.session.input_block_padding());
78        if let Some(title) = shell_mode_title {
79            block = block
80                .title(title)
81                .border_type(crate::ui::tui::session::terminal_capabilities::get_border_type())
82                .border_style(
83                    self.session
84                        .styles
85                        .accent_style()
86                        .add_modifier(Modifier::BOLD),
87                );
88        }
89        let inner = block.inner(input_area);
90        let input_data = self
91            .session
92            .build_input_widget_data(inner.width, inner.height);
93
94        let paragraph = Paragraph::new(input_data.text)
95            .style(input_data.background_style)
96            .wrap(Wrap { trim: false })
97            .block(block);
98        paragraph.render(input_area, buf);
99
100        // Handle cursor positioning using buffer API
101        if input_data.cursor_should_be_visible && inner.width > 0 && inner.height > 0 {
102            let cursor_x = input_data
103                .cursor_x
104                .min(inner.width.saturating_sub(1))
105                .saturating_add(inner.x);
106            let cursor_y = input_data
107                .cursor_y
108                .min(inner.height.saturating_sub(1))
109                .saturating_add(inner.y);
110
111            if let Some(cell) = buf.cell_mut((cursor_x, cursor_y)) {
112                if input_data.use_fake_cursor {
113                    let mut style = cell.style();
114                    style = style.add_modifier(Modifier::REVERSED);
115                    cell.set_style(style);
116                    if cell.symbol().is_empty() {
117                        cell.set_symbol(" ");
118                    }
119                } else {
120                    cell.set_symbol("");
121                    // The cursor position is managed by the terminal, we just need to ensure
122                    // the cell is accessible for cursor placement
123                }
124            }
125        }
126
127        // Render status line if present
128        if let (Some(status_rect), Some(line)) = (status_area, status_line) {
129            let paragraph = Paragraph::new(line)
130                .style(input_data.default_style)
131                .wrap(Wrap { trim: false });
132            paragraph.render(status_rect, buf);
133        }
134    }
135}