sbom_tools/tui/
app_impl_search.rs1use super::app::{App, TabKind};
4use super::app_states::{
5 ChangeType, ComponentFilter, DiffSearchResult, VulnChangeType, VulnFilter, VulnSort,
6};
7
8impl App {
9 pub fn start_search(&mut self) {
11 self.overlays.search.active = true;
12 self.overlays.search.clear();
13 self.overlays.show_export = false;
14 self.overlays.show_legend = false;
15 }
16
17 pub const fn stop_search(&mut self) {
19 self.overlays.search.active = false;
20 }
21
22 pub fn search_push(&mut self, c: char) {
24 self.overlays.search.push_char(c);
25 }
26
27 pub fn search_pop(&mut self) {
29 self.overlays.search.pop_char();
30 }
31
32 pub fn execute_search(&mut self) {
34 if self.overlays.search.query.len() < 2 {
35 self.overlays.search.results.clear();
36 return;
37 }
38
39 let matcher = match crate::tui::app_states::SearchMatcher::build(
41 &self.overlays.search.query,
42 self.overlays.search.mode,
43 ) {
44 Ok(m) => {
45 self.overlays.search.search_error = None;
46 m
47 }
48 Err(e) => {
49 self.overlays.search.search_error = Some(e);
50 self.overlays.search.results.clear();
51 return;
52 }
53 };
54 let matches_query = |text: &str| -> bool { matcher.is_match(text) };
55
56 let mut results = Vec::new();
57
58 if let Some(ref diff) = self.data.diff_result {
60 for comp in &diff.components.added {
62 if matches_query(&comp.name) {
63 results.push(DiffSearchResult::Component {
64 name: comp.name.clone(),
65 version: comp.new_version.clone(),
66 change_type: ChangeType::Added,
67 });
68 }
69 }
70
71 for comp in &diff.components.removed {
73 if matches_query(&comp.name) {
74 results.push(DiffSearchResult::Component {
75 name: comp.name.clone(),
76 version: comp.old_version.clone(),
77 change_type: ChangeType::Removed,
78 });
79 }
80 }
81
82 for change in &diff.components.modified {
84 if matches_query(&change.name) {
85 let change_type = if change.change_type == crate::diff::ChangeType::Unchanged {
86 ChangeType::Unchanged
87 } else {
88 ChangeType::Modified
89 };
90 results.push(DiffSearchResult::Component {
91 name: change.name.clone(),
92 version: change.new_version.clone(),
93 change_type,
94 });
95 }
96 }
97
98 for vuln in &diff.vulnerabilities.introduced {
100 if matches_query(&vuln.id) {
101 results.push(DiffSearchResult::Vulnerability {
102 id: vuln.id.clone(),
103 component_name: vuln.component_name.clone(),
104 severity: Some(vuln.severity.clone()),
105 change_type: VulnChangeType::Introduced,
106 });
107 }
108 }
109
110 for vuln in &diff.vulnerabilities.resolved {
112 if matches_query(&vuln.id) {
113 results.push(DiffSearchResult::Vulnerability {
114 id: vuln.id.clone(),
115 component_name: vuln.component_name.clone(),
116 severity: Some(vuln.severity.clone()),
117 change_type: VulnChangeType::Resolved,
118 });
119 }
120 }
121
122 for lic_change in &diff.licenses.new_licenses {
124 if matches_query(&lic_change.license) {
125 let component_name = lic_change
126 .components
127 .first()
128 .cloned()
129 .unwrap_or_else(|| "multiple".to_string());
130 results.push(DiffSearchResult::License {
131 license: lic_change.license.clone(),
132 component_name,
133 change_type: ChangeType::Added,
134 });
135 }
136 }
137
138 for lic_change in &diff.licenses.removed_licenses {
140 if matches_query(&lic_change.license) {
141 let component_name = lic_change
142 .components
143 .first()
144 .cloned()
145 .unwrap_or_else(|| "multiple".to_string());
146 results.push(DiffSearchResult::License {
147 license: lic_change.license.clone(),
148 component_name,
149 change_type: ChangeType::Removed,
150 });
151 }
152 }
153 }
154
155 if self.data.diff_result.is_none()
157 && let Some(ref sbom) = self.data.sbom
158 {
159 for comp in sbom.components.values() {
161 if matches_query(&comp.name) {
162 results.push(DiffSearchResult::Component {
163 name: comp.name.clone(),
164 version: comp.version.clone(),
165 change_type: ChangeType::Added, });
167 }
168 }
169
170 for comp in sbom.components.values() {
172 for vuln in &comp.vulnerabilities {
173 if matches_query(&vuln.id) {
174 results.push(DiffSearchResult::Vulnerability {
175 id: vuln.id.clone(),
176 component_name: comp.name.clone(),
177 severity: vuln.severity.as_ref().map(|s| format!("{s:?}")),
178 change_type: VulnChangeType::Introduced, });
180 }
181 }
182 }
183
184 for comp in sbom.components.values() {
186 for lic in &comp.licenses.declared {
187 if matches_query(&lic.expression) {
188 results.push(DiffSearchResult::License {
189 license: lic.expression.clone(),
190 component_name: comp.name.clone(),
191 change_type: ChangeType::Added, });
193 }
194 }
195 }
196 }
197
198 let comp_filter = self.components_state().filter;
201 if comp_filter != ComponentFilter::All {
202 results.retain(|r| match r {
203 DiffSearchResult::Component { change_type, .. } => match comp_filter {
204 ComponentFilter::Added => *change_type == ChangeType::Added,
205 ComponentFilter::Removed => *change_type == ChangeType::Removed,
206 ComponentFilter::Modified => *change_type == ChangeType::Modified,
207 _ => true,
209 },
210 DiffSearchResult::Vulnerability { .. } | DiffSearchResult::License { .. } => true,
212 });
213 }
214
215 results.truncate(50);
217 self.overlays.search.results = results;
218 self.overlays.search.selected = 0;
219 }
220
221 pub fn jump_to_search_result(&mut self) {
223 if let Some(result) = self
224 .overlays
225 .search
226 .results
227 .get(self.overlays.search.selected)
228 .cloned()
229 {
230 match result {
231 DiffSearchResult::Component {
232 name,
233 version,
234 change_type,
235 ..
236 } => {
237 if let Some(index) =
239 self.find_component_index_all(&name, Some(change_type), version.as_deref())
240 {
241 self.components_state_mut().filter = ComponentFilter::All;
242 self.components_state_mut().selected = index;
243 self.select_tab(TabKind::Components);
244 self.stop_search();
245 return;
246 }
247
248 if let Some(index) = self.find_component_index_all(&name, None, None) {
250 self.components_state_mut().filter = ComponentFilter::All;
251 self.components_state_mut().selected = index;
252 self.select_tab(TabKind::Components);
253 self.stop_search();
254 return;
255 }
256
257 self.components_state_mut().filter = ComponentFilter::All;
258 self.select_tab(TabKind::Components);
259 }
260 DiffSearchResult::Vulnerability {
261 id, change_type, ..
262 } => {
263 self.vulnerabilities_state_mut().sort_by = VulnSort::Id;
265 self.vulnerabilities_state_mut().filter = match change_type {
266 VulnChangeType::Introduced => VulnFilter::Introduced,
267 VulnChangeType::Resolved => VulnFilter::Resolved,
268 };
269
270 if let Some(index) = self.find_vulnerability_index(&id) {
271 self.vulnerabilities_state_mut().selected = index;
272 }
273
274 self.select_tab(TabKind::Vulnerabilities);
275 }
276 DiffSearchResult::License { license, .. } => {
277 if let Some(ref diff) = self.data.diff_result {
279 let mut index = 0;
280
281 for lic in &diff.licenses.new_licenses {
283 if lic.license == license {
284 self.licenses_state_mut().selected = index;
285 self.select_tab(TabKind::Licenses);
286 self.stop_search();
287 return;
288 }
289 index += 1;
290 }
291
292 for lic in &diff.licenses.removed_licenses {
294 if lic.license == license {
295 self.licenses_state_mut().selected = index;
296 self.select_tab(TabKind::Licenses);
297 self.stop_search();
298 return;
299 }
300 index += 1;
301 }
302 }
303 self.select_tab(TabKind::Licenses);
304 }
305 }
306 self.stop_search();
307 }
308 }
309}