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