1use std::any::Any;
4use std::sync::Mutex;
5
6use crate::component::Component;
7use crate::theme::theme;
8
9pub struct SearchBar {
11 query: Mutex<String>,
12 match_count: Mutex<usize>,
13 current_match: Mutex<usize>,
14 visible: Mutex<bool>,
15}
16
17impl SearchBar {
18 pub fn new() -> Self {
19 Self {
20 query: Mutex::new(String::new()),
21 match_count: Mutex::new(0),
22 current_match: Mutex::new(0),
23 visible: Mutex::new(false),
24 }
25 }
26
27 pub fn set_query(&self, query: &str) {
28 *self.query.lock().unwrap() = query.to_string();
29 }
30
31 pub fn set_match_info(&self, current: usize, total: usize) {
32 *self.current_match.lock().unwrap() = current;
33 *self.match_count.lock().unwrap() = total;
34 }
35
36 pub fn set_visible(&self, visible: bool) {
37 *self.visible.lock().unwrap() = visible;
38 }
39
40 pub fn is_visible(&self) -> bool {
41 *self.visible.lock().unwrap()
42 }
43}
44
45impl Default for SearchBar {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51impl Component for SearchBar {
52 fn render(&self, width: usize) -> Vec<String> {
53 if !self.is_visible() {
54 return vec![];
55 }
56
57 let query = self.query.lock().unwrap().clone();
58 let match_count = *self.match_count.lock().unwrap();
59 let current_match = *self.current_match.lock().unwrap();
60
61 let theme = theme();
62 let colors = &theme.colors;
63
64 let search_icon = "🔍";
66 let match_info = if match_count == 0 {
67 if query.is_empty() {
68 String::new()
69 } else {
70 format!("No matches")
71 }
72 } else {
73 format!("{}/{}", current_match + 1, match_count)
74 };
75
76 let left = format!("{} Search: {}", search_icon, query);
77 let right = match_info;
78
79 let left_len = left.chars().count();
81 let right_len = right.chars().count();
82 let padding = if width > left_len + right_len + 2 {
83 width - left_len - right_len - 2
84 } else {
85 1
86 };
87
88 let line = format!(
89 "{}{}{}{}",
90 colors.background.fg(&left),
91 " ".repeat(padding),
92 colors.dim.fg(&right),
93 " ".repeat(2)
94 );
95
96 vec![line]
97 }
98
99 fn invalidate(&self) {
100 }
102
103 fn as_any(&self) -> &dyn Any {
104 self
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn test_search_bar_hidden() {
114 let bar = SearchBar::new();
115 assert_eq!(bar.render(80).len(), 0);
116 }
117
118 #[test]
119 fn test_search_bar_visible() {
120 let bar = SearchBar::new();
121 bar.set_visible(true);
122 bar.set_query("test");
123 bar.set_match_info(0, 5);
124 let lines = bar.render(80);
125 assert_eq!(lines.len(), 1);
126 assert!(lines[0].contains("test"));
127 assert!(lines[0].contains("1/5"));
128 }
129}