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