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