sbom_tools/tui/
app_impl_nav.rs1use super::app::{App, AppMode, TabKind};
4use super::app_states::ComponentFilter;
5use super::state::ListNavigation;
6
7impl App {
8 pub fn next_tab(&mut self) {
10 let has_graph_changes = self
11 .data
12 .diff_result
13 .as_ref()
14 .is_some_and(|r| !r.graph_changes.is_empty());
15
16 self.active_tab = match self.active_tab {
17 TabKind::Summary => TabKind::Components,
18 TabKind::Components => TabKind::Dependencies,
19 TabKind::Dependencies => TabKind::Licenses,
20 TabKind::Licenses => TabKind::Vulnerabilities,
21 TabKind::Vulnerabilities => TabKind::Quality,
22 TabKind::Quality => {
23 if self.mode == AppMode::Diff {
24 TabKind::Compliance
25 } else {
26 TabKind::Summary
27 }
28 }
29 TabKind::Compliance => TabKind::SideBySide,
30 TabKind::SideBySide => {
31 if has_graph_changes {
32 TabKind::GraphChanges
33 } else {
34 TabKind::Source
35 }
36 }
37 TabKind::GraphChanges => TabKind::Source,
38 TabKind::Source => TabKind::Summary,
39 };
40 }
41
42 pub fn prev_tab(&mut self) {
44 let has_graph_changes = self
45 .data
46 .diff_result
47 .as_ref()
48 .is_some_and(|r| !r.graph_changes.is_empty());
49
50 self.active_tab = match self.active_tab {
51 TabKind::Summary => {
52 if self.mode == AppMode::Diff {
53 TabKind::Source
54 } else {
55 TabKind::Quality
56 }
57 }
58 TabKind::Components => TabKind::Summary,
59 TabKind::Dependencies => TabKind::Components,
60 TabKind::Licenses => TabKind::Dependencies,
61 TabKind::Vulnerabilities => TabKind::Licenses,
62 TabKind::Quality => TabKind::Vulnerabilities,
63 TabKind::Compliance => TabKind::Quality,
64 TabKind::SideBySide => TabKind::Compliance,
65 TabKind::GraphChanges => TabKind::SideBySide,
66 TabKind::Source => {
67 if has_graph_changes {
68 TabKind::GraphChanges
69 } else {
70 TabKind::SideBySide
71 }
72 }
73 };
74 }
75
76 pub const fn select_tab(&mut self, tab: TabKind) {
78 self.active_tab = tab;
79 }
80
81 pub fn select_up(&mut self) {
83 match self.active_tab {
84 TabKind::Summary => self.summary_state_mut().select_prev(),
85 TabKind::Components => self.components_state_mut().select_prev(),
86 TabKind::Vulnerabilities => self.vulnerabilities_state_mut().select_prev(),
87 TabKind::Licenses => self.licenses_state_mut().select_prev(),
88 TabKind::Source => self.source_state_mut().select_prev(),
89 _ => {}
90 }
91 }
92
93 pub fn select_down(&mut self) {
95 match self.active_tab {
96 TabKind::Summary => self.summary_state_mut().select_next(),
97 TabKind::Components => self.components_state_mut().select_next(),
98 TabKind::Vulnerabilities => self.vulnerabilities_state_mut().select_next(),
99 TabKind::Licenses => self.licenses_state_mut().select_next(),
100 TabKind::Source => self.source_state_mut().select_next(),
101 _ => {}
102 }
103 }
104
105 pub fn select_first(&mut self) {
107 match self.active_tab {
108 TabKind::Summary => self.summary_state_mut().go_first(),
109 TabKind::Components => self.components_state_mut().go_first(),
110 TabKind::Vulnerabilities => self.vulnerabilities_state_mut().go_first(),
111 TabKind::Licenses => self.licenses_state_mut().go_first(),
112 TabKind::Source => self.source_state_mut().select_first(),
113 _ => {}
114 }
115 }
116
117 pub fn select_last(&mut self) {
119 match self.active_tab {
120 TabKind::Summary => self.summary_state_mut().go_last(),
121 TabKind::Components => self.components_state_mut().go_last(),
122 TabKind::Vulnerabilities => self.vulnerabilities_state_mut().go_last(),
123 TabKind::Licenses => self.licenses_state_mut().go_last(),
124 TabKind::Source => self.source_state_mut().select_last(),
125 _ => {}
126 }
127 }
128
129 pub fn page_up(&mut self) {
131 match self.active_tab {
132 TabKind::Summary => self.summary_state_mut().page_up(),
133 TabKind::Components => self.components_state_mut().page_up(),
134 TabKind::Vulnerabilities => self.vulnerabilities_state_mut().page_up(),
135 TabKind::Licenses => self.licenses_state_mut().page_up(),
136 TabKind::Source => self.source_state_mut().page_up(),
137 _ => {}
138 }
139 }
140
141 pub fn page_down(&mut self) {
143 match self.active_tab {
144 TabKind::Summary => self.summary_state_mut().page_down(),
145 TabKind::Components => self.components_state_mut().page_down(),
146 TabKind::Vulnerabilities => self.vulnerabilities_state_mut().page_down(),
147 TabKind::Licenses => self.licenses_state_mut().page_down(),
148 TabKind::Source => self.source_state_mut().page_down(),
149 _ => {}
150 }
151 }
152
153 pub fn navigate_vuln_to_component(&mut self, vuln_id: &str, component_name: &str) {
159 let selected = self.vulnerabilities_state().selected;
161 self.navigation_ctx.push_breadcrumb(
162 TabKind::Vulnerabilities,
163 vuln_id.to_string(),
164 selected,
165 );
166
167 self.navigation_ctx.target_component = Some(component_name.to_string());
169 self.active_tab = TabKind::Components;
170
171 self.find_and_select_component(component_name);
173 }
174
175 pub fn navigate_dep_to_component(&mut self, dep_name: &str) {
177 let dep_name = dep_name
178 .split_once(":+:")
179 .map(|(_, dep)| dep)
180 .or_else(|| dep_name.split_once(":-:").map(|(_, dep)| dep))
181 .unwrap_or(dep_name);
182
183 if dep_name.starts_with("__") {
184 return;
185 }
186
187 self.navigation_ctx.push_breadcrumb(
189 TabKind::Dependencies,
190 dep_name.to_string(),
191 self.dependencies_state().selected,
192 );
193
194 self.navigation_ctx.target_component = Some(dep_name.to_string());
196 self.active_tab = TabKind::Components;
197
198 self.find_and_select_component(dep_name);
200 }
201
202 pub fn navigate_back(&mut self) -> bool {
204 if let Some(breadcrumb) = self.navigation_ctx.pop_breadcrumb() {
205 self.active_tab = breadcrumb.tab;
206
207 match breadcrumb.tab {
209 TabKind::Vulnerabilities => {
210 self.vulnerabilities_state_mut().selected = breadcrumb.selection_index;
211 }
212 TabKind::Components => {
213 self.components_state_mut().selected = breadcrumb.selection_index;
214 }
215 TabKind::Dependencies => {
216 self.dependencies_state_mut().selected = breadcrumb.selection_index;
217 }
218 TabKind::Licenses => {
219 self.licenses_state_mut().selected = breadcrumb.selection_index;
220 }
221 TabKind::Source => {
222 self.source_state_mut().active_panel_mut().selected =
223 breadcrumb.selection_index;
224 }
225 _ => {}
226 }
227
228 self.navigation_ctx.clear_targets();
229 true
230 } else {
231 false
232 }
233 }
234
235 pub(super) fn find_and_select_component(&mut self, name: &str) {
237 if self.data.diff_result.is_some() {
238 self.components_state_mut().filter = ComponentFilter::All;
240
241 let name_lower = name.to_lowercase();
242 let index = {
243 let items = self.diff_component_items(ComponentFilter::All);
244 items
245 .iter()
246 .position(|comp| comp.name.to_lowercase() == name_lower)
247 };
248
249 if let Some(index) = index {
250 self.components_state_mut().selected = index;
251 }
252 }
253 }
254
255 #[must_use]
257 pub fn has_navigation_history(&self) -> bool {
258 self.navigation_ctx.has_history()
259 }
260
261 #[must_use]
263 pub fn breadcrumb_trail(&self) -> String {
264 self.navigation_ctx.breadcrumb_trail()
265 }
266
267 pub(super) fn navigate_to_target(&mut self, target: super::traits::TabTarget) {
269 use super::traits::TabTarget;
270
271 if matches!(
273 target,
274 TabTarget::ComponentByName(_)
275 | TabTarget::ComponentByLicense(_)
276 | TabTarget::VulnerabilityById(_)
277 ) {
278 let selection_index = self.current_tab_selection_index();
279 self.navigation_ctx
280 .push_breadcrumb(self.active_tab, String::new(), selection_index);
281 }
282
283 match target {
284 TabTarget::Summary => self.active_tab = TabKind::Summary,
285 TabTarget::Components => self.active_tab = TabKind::Components,
286 TabTarget::Dependencies => self.active_tab = TabKind::Dependencies,
287 TabTarget::Licenses => self.active_tab = TabKind::Licenses,
288 TabTarget::Vulnerabilities => self.active_tab = TabKind::Vulnerabilities,
289 TabTarget::Quality => self.active_tab = TabKind::Quality,
290 TabTarget::Compliance => self.active_tab = TabKind::Compliance,
291 TabTarget::SideBySide => self.active_tab = TabKind::SideBySide,
292 TabTarget::GraphChanges => self.active_tab = TabKind::GraphChanges,
293 TabTarget::Source => self.active_tab = TabKind::Source,
294 TabTarget::ComponentByName(name) => {
295 self.active_tab = TabKind::Components;
296 self.find_and_select_component(&name);
297 }
298 TabTarget::VulnerabilityById(id) => {
299 self.active_tab = TabKind::Vulnerabilities;
300 if let Some(idx) = self.find_vulnerability_index(&id) {
301 self.vulnerabilities_state_mut().selected = idx;
302 }
303 }
304 TabTarget::ComponentByLicense(license) => {
305 self.active_tab = TabKind::Components;
306 self.set_status_message(format!("Showing components with license: {license}"));
307 }
308 }
309 }
310
311 fn current_tab_selection_index(&self) -> usize {
313 match self.active_tab {
314 TabKind::Components => self.components_state().selected,
315 TabKind::Vulnerabilities => self.vulnerabilities_state().selected,
316 TabKind::Licenses => self.licenses_state().selected,
317 TabKind::Dependencies => self.dependencies_state().selected,
318 TabKind::Compliance => self.compliance_view.inner().selected_violation,
319 TabKind::Source => {
320 let state = self.source_state();
321 match state.active_side {
322 crate::tui::app_states::source::SourceSide::Old => state.old_panel.selected,
323 crate::tui::app_states::source::SourceSide::New => state.new_panel.selected,
324 }
325 }
326 _ => 0,
327 }
328 }
329}