Skip to main content

rgx/ui/
regex_input.rs

1use ratatui::{
2    buffer::Buffer,
3    layout::Rect,
4    style::{Modifier, Style},
5    text::{Line, Span},
6    widgets::{Block, BorderType, Borders, Paragraph, Widget},
7};
8
9use crate::input::editor::Editor;
10use crate::ui::{syntax_highlight, theme};
11
12pub struct RegexInput<'a> {
13    pub editor: &'a Editor,
14    pub focused: bool,
15    pub error: Option<&'a str>,
16    pub error_offset: Option<usize>,
17    pub border_type: BorderType,
18    pub syntax_tokens: &'a [syntax_highlight::SyntaxToken],
19}
20
21impl<'a> Widget for RegexInput<'a> {
22    fn render(self, area: Rect, buf: &mut Buffer) {
23        let border_style = if self.focused {
24            Style::default().fg(theme::BLUE)
25        } else {
26            Style::default().fg(theme::OVERLAY)
27        };
28
29        let title = if let Some(err) = self.error {
30            let truncated: String = err
31                .chars()
32                .take((area.width as usize).saturating_sub(10))
33                .collect();
34            format!(" Pattern (Error: {truncated}) ")
35        } else {
36            " Pattern ".to_string()
37        };
38
39        let title_style = if self.error.is_some() {
40            Style::default().fg(theme::RED)
41        } else {
42            Style::default().fg(theme::TEXT)
43        };
44
45        let block = Block::default()
46            .borders(Borders::ALL)
47            .border_type(self.border_type)
48            .border_style(border_style)
49            .title(Span::styled(title, title_style));
50
51        let content = self.editor.content();
52        let tokens = self.syntax_tokens;
53        let spans = if tokens.is_empty() {
54            vec![Span::styled(
55                content.to_string(),
56                Style::default().fg(theme::TEXT),
57            )]
58        } else {
59            syntax_highlight::build_highlighted_spans(content, tokens)
60        };
61        let line = Line::from(spans);
62
63        let paragraph = Paragraph::new(line)
64            .block(block)
65            .style(Style::default().bg(theme::BASE));
66
67        paragraph.render(area, buf);
68
69        // Highlight error character
70        if let Some(offset) = self.error_offset {
71            let scroll = self.editor.scroll_offset();
72            if offset >= scroll {
73                let err_x = area.x + 1 + (offset - scroll) as u16;
74                let err_y = area.y + 1;
75                if err_x < area.x + area.width.saturating_sub(1)
76                    && err_y < area.y + area.height.saturating_sub(1)
77                {
78                    if let Some(cell) = buf.cell_mut((err_x, err_y)) {
79                        cell.set_style(Style::default().fg(theme::TEXT).bg(theme::RED));
80                    }
81                }
82            }
83        }
84
85        // Render cursor
86        if self.focused {
87            let cursor_x = area.x + 1 + self.editor.visual_cursor() as u16;
88            let cursor_y = area.y + 1;
89            if cursor_x < area.x + area.width.saturating_sub(1)
90                && cursor_y < area.y + area.height.saturating_sub(1)
91            {
92                if let Some(cell) = buf.cell_mut((cursor_x, cursor_y)) {
93                    cell.set_style(
94                        Style::default()
95                            .fg(theme::BASE)
96                            .bg(theme::TEXT)
97                            .add_modifier(Modifier::BOLD),
98                    );
99                }
100            }
101        }
102    }
103}