Skip to main content

vtcode_ui/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::tui::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 { session, area: None }
34    }
35
36    /// Set the area for rendering (used for cursor positioning calculations)
37    #[must_use]
38    pub fn area(mut self, area: Rect) -> Self {
39        self.area = Some(area);
40        self
41    }
42}
43
44impl<'a> Widget for InputWidget<'a> {
45    fn render(self, area: Rect, buf: &mut Buffer) {
46        Clear.render(area, buf);
47        if area.height == 0 {
48            return;
49        }
50
51        let mut input_area = area;
52        let mut status_area = None;
53        let mut status_line = None;
54
55        // Calculate areas for input and status line
56        if area.height >= 2
57            && let Some(spans) = self.session.build_input_status_widget_data(area.width)
58        {
59            let block_height = area.height.saturating_sub(1).max(1);
60            input_area.height = block_height;
61            status_area = Some(Rect::new(area.x, area.y + block_height, area.width, 1));
62            status_line = Some(Line::from(spans));
63        }
64
65        let temp_data = self.session.build_input_widget_data(1, 1);
66        let shell_mode_title = self.session.shell_mode_border_title();
67        let mut block = if shell_mode_title.is_some() {
68            ratatui::widgets::Block::bordered()
69        } else {
70            ratatui::widgets::Block::new()
71        };
72        block = block
73            .style(temp_data.background_style)
74            .padding(self.session.input_block_padding());
75        if let Some(title) = shell_mode_title {
76            block = block
77                .title(title)
78                .border_type(crate::tui::ui::tui::session::terminal_capabilities::get_border_type())
79                .border_style(self.session.styles.accent_style().add_modifier(Modifier::BOLD));
80        }
81        let inner = block.inner(input_area);
82        let input_data = self.session.build_input_widget_data(inner.width, inner.height);
83
84        let paragraph = Paragraph::new(input_data.text)
85            .style(input_data.background_style)
86            .wrap(Wrap { trim: false })
87            .block(block);
88        paragraph.render(input_area, buf);
89
90        // Handle cursor positioning using buffer API
91        if input_data.cursor_should_be_visible && inner.width > 0 && inner.height > 0 {
92            let cursor_x = input_data.cursor_x.min(inner.width.saturating_sub(1)).saturating_add(inner.x);
93            let cursor_y = input_data.cursor_y.min(inner.height.saturating_sub(1)).saturating_add(inner.y);
94
95            if let Some(cell) = buf.cell_mut((cursor_x, cursor_y)) {
96                if input_data.use_fake_cursor {
97                    let mut style = cell.style();
98                    style = style.add_modifier(Modifier::REVERSED);
99                    cell.set_style(style);
100                    if cell.symbol().is_empty() {
101                        cell.set_symbol(" ");
102                    }
103                } else {
104                    cell.set_symbol("");
105                    // The cursor position is managed by the terminal, we just need to ensure
106                    // the cell is accessible for cursor placement
107                }
108            }
109        }
110
111        // Render status line if present
112        if let (Some(status_rect), Some(line)) = (status_area, status_line) {
113            let paragraph = Paragraph::new(line).style(input_data.default_style).wrap(Wrap { trim: false });
114            paragraph.render(status_rect, buf);
115        }
116    }
117}