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 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 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}