Skip to main content

rgx/ui/
test_input.rs

1use ratatui::{
2    buffer::Buffer,
3    layout::Rect,
4    style::{Modifier, Style},
5    text::{Line, Span},
6    widgets::{Block, Borders, Paragraph, Widget},
7};
8
9use crate::engine;
10use crate::input::editor::Editor;
11use crate::ui::theme;
12
13pub struct TestInput<'a> {
14    pub editor: &'a Editor,
15    pub focused: bool,
16    pub matches: &'a [engine::Match],
17}
18
19impl<'a> Widget for TestInput<'a> {
20    fn render(self, area: Rect, buf: &mut Buffer) {
21        let border_style = if self.focused {
22            Style::default().fg(theme::BLUE)
23        } else {
24            Style::default().fg(theme::OVERLAY)
25        };
26
27        let block = Block::default()
28            .borders(Borders::ALL)
29            .border_style(border_style)
30            .title(Span::styled(
31                " Test String ",
32                Style::default().fg(theme::TEXT),
33            ));
34
35        let content = self.editor.content();
36        let spans = build_highlighted_spans(content, self.matches);
37        let line = Line::from(spans);
38
39        let paragraph = Paragraph::new(line)
40            .block(block)
41            .style(Style::default().bg(theme::BASE));
42
43        paragraph.render(area, buf);
44
45        // Render cursor
46        if self.focused {
47            let cursor_x = area.x + 1 + self.editor.visual_cursor() as u16;
48            let cursor_y = area.y + 1;
49            if cursor_x < area.x + area.width - 1 && cursor_y < area.y + area.height - 1 {
50                if let Some(cell) = buf.cell_mut((cursor_x, cursor_y)) {
51                    cell.set_style(
52                        Style::default()
53                            .fg(theme::BASE)
54                            .bg(theme::TEXT)
55                            .add_modifier(Modifier::BOLD),
56                    );
57                }
58            }
59        }
60    }
61}
62
63fn build_highlighted_spans<'a>(text: &'a str, matches: &[engine::Match]) -> Vec<Span<'a>> {
64    if matches.is_empty() || text.is_empty() {
65        return vec![Span::styled(text, Style::default().fg(theme::TEXT))];
66    }
67
68    let mut spans = Vec::new();
69    let mut pos = 0;
70
71    for m in matches {
72        if m.start > pos {
73            spans.push(Span::styled(
74                &text[pos..m.start],
75                Style::default().fg(theme::TEXT),
76            ));
77        }
78
79        if m.captures.is_empty() {
80            spans.push(Span::styled(
81                &text[m.start..m.end],
82                Style::default()
83                    .fg(theme::TEXT)
84                    .bg(theme::MATCH_BG)
85                    .add_modifier(Modifier::BOLD),
86            ));
87        } else {
88            // Render with capture group colors
89            let mut inner_pos = m.start;
90            for cap in &m.captures {
91                if cap.start > inner_pos {
92                    spans.push(Span::styled(
93                        &text[inner_pos..cap.start],
94                        Style::default()
95                            .fg(theme::TEXT)
96                            .bg(theme::MATCH_BG)
97                            .add_modifier(Modifier::BOLD),
98                    ));
99                }
100                let color = theme::capture_color(cap.index.saturating_sub(1));
101                spans.push(Span::styled(
102                    &text[cap.start..cap.end],
103                    Style::default()
104                        .fg(theme::BASE)
105                        .bg(color)
106                        .add_modifier(Modifier::BOLD),
107                ));
108                inner_pos = cap.end;
109            }
110            if inner_pos < m.end {
111                spans.push(Span::styled(
112                    &text[inner_pos..m.end],
113                    Style::default()
114                        .fg(theme::TEXT)
115                        .bg(theme::MATCH_BG)
116                        .add_modifier(Modifier::BOLD),
117                ));
118            }
119        }
120
121        pos = m.end;
122    }
123
124    if pos < text.len() {
125        spans.push(Span::styled(&text[pos..], Style::default().fg(theme::TEXT)));
126    }
127
128    spans
129}