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::Components => self.tabs.components.select_prev(),
85 TabKind::Vulnerabilities => self.tabs.vulnerabilities.select_prev(),
86 TabKind::Licenses => self.tabs.licenses.select_prev(),
87 TabKind::Source => self.tabs.source.select_prev(),
88 _ => {}
89 }
90 }
91
92 pub fn select_down(&mut self) {
94 match self.active_tab {
95 TabKind::Components => self.tabs.components.select_next(),
96 TabKind::Vulnerabilities => self.tabs.vulnerabilities.select_next(),
97 TabKind::Licenses => self.tabs.licenses.select_next(),
98 TabKind::Source => self.tabs.source.select_next(),
99 _ => {}
100 }
101 }
102
103 pub fn select_first(&mut self) {
105 match self.active_tab {
106 TabKind::Components => self.tabs.components.go_first(),
107 TabKind::Vulnerabilities => self.tabs.vulnerabilities.go_first(),
108 TabKind::Licenses => self.tabs.licenses.go_first(),
109 TabKind::Source => self.tabs.source.select_first(),
110 _ => {}
111 }
112 }
113
114 pub fn select_last(&mut self) {
116 match self.active_tab {
117 TabKind::Components => self.tabs.components.go_last(),
118 TabKind::Vulnerabilities => self.tabs.vulnerabilities.go_last(),
119 TabKind::Source => self.tabs.source.select_last(),
120 _ => {}
121 }
122 }
123
124 pub fn page_up(&mut self) {
126 match self.active_tab {
127 TabKind::Components => self.tabs.components.page_up(),
128 TabKind::Vulnerabilities => self.tabs.vulnerabilities.page_up(),
129 TabKind::Source => self.tabs.source.page_up(),
130 _ => {}
131 }
132 }
133
134 pub fn page_down(&mut self) {
136 match self.active_tab {
137 TabKind::Components => self.tabs.components.page_down(),
138 TabKind::Vulnerabilities => self.tabs.vulnerabilities.page_down(),
139 TabKind::Source => self.tabs.source.page_down(),
140 _ => {}
141 }
142 }
143
144 pub fn navigate_vuln_to_component(&mut self, vuln_id: &str, component_name: &str) {
150 self.navigation_ctx.push_breadcrumb(
152 TabKind::Vulnerabilities,
153 vuln_id.to_string(),
154 self.tabs.vulnerabilities.selected,
155 );
156
157 self.navigation_ctx.target_component = Some(component_name.to_string());
159 self.active_tab = TabKind::Components;
160
161 self.find_and_select_component(component_name);
163 }
164
165 pub fn navigate_dep_to_component(&mut self, dep_name: &str) {
167 let dep_name = dep_name
168 .split_once(":+:")
169 .map(|(_, dep)| dep)
170 .or_else(|| dep_name.split_once(":-:").map(|(_, dep)| dep))
171 .unwrap_or(dep_name);
172
173 if dep_name.starts_with("__") {
174 return;
175 }
176
177 self.navigation_ctx.push_breadcrumb(
179 TabKind::Dependencies,
180 dep_name.to_string(),
181 self.tabs.dependencies.selected,
182 );
183
184 self.navigation_ctx.target_component = Some(dep_name.to_string());
186 self.active_tab = TabKind::Components;
187
188 self.find_and_select_component(dep_name);
190 }
191
192 pub fn navigate_back(&mut self) -> bool {
194 if let Some(breadcrumb) = self.navigation_ctx.pop_breadcrumb() {
195 self.active_tab = breadcrumb.tab;
196
197 match breadcrumb.tab {
199 TabKind::Vulnerabilities => {
200 self.tabs.vulnerabilities.selected = breadcrumb.selection_index;
201 }
202 TabKind::Components => {
203 self.tabs.components.selected = breadcrumb.selection_index;
204 }
205 TabKind::Dependencies => {
206 self.tabs.dependencies.selected = breadcrumb.selection_index;
207 }
208 TabKind::Licenses => {
209 self.tabs.licenses.selected = breadcrumb.selection_index;
210 }
211 TabKind::Source => {
212 self.tabs.source.active_panel_mut().selected = breadcrumb.selection_index;
213 }
214 _ => {}
215 }
216
217 self.navigation_ctx.clear_targets();
218 true
219 } else {
220 false
221 }
222 }
223
224 pub(super) fn find_and_select_component(&mut self, name: &str) {
226 if self.data.diff_result.is_some() {
227 self.tabs.components.filter = ComponentFilter::All;
229
230 let name_lower = name.to_lowercase();
231 let index = {
232 let items = self.diff_component_items(ComponentFilter::All);
233 items
234 .iter()
235 .position(|comp| comp.name.to_lowercase() == name_lower)
236 };
237
238 if let Some(index) = index {
239 self.tabs.components.selected = index;
240 }
241 }
242 }
243
244 #[must_use]
246 pub fn has_navigation_history(&self) -> bool {
247 self.navigation_ctx.has_history()
248 }
249
250 #[must_use]
252 pub fn breadcrumb_trail(&self) -> String {
253 self.navigation_ctx.breadcrumb_trail()
254 }
255
256 pub(super) fn navigate_to_target(&mut self, target: super::traits::TabTarget) {
258 use super::traits::TabTarget;
259
260 match target {
261 TabTarget::Summary => self.active_tab = TabKind::Summary,
262 TabTarget::Components => self.active_tab = TabKind::Components,
263 TabTarget::Dependencies => self.active_tab = TabKind::Dependencies,
264 TabTarget::Licenses => self.active_tab = TabKind::Licenses,
265 TabTarget::Vulnerabilities => self.active_tab = TabKind::Vulnerabilities,
266 TabTarget::Quality => self.active_tab = TabKind::Quality,
267 TabTarget::Compliance => self.active_tab = TabKind::Compliance,
268 TabTarget::SideBySide => self.active_tab = TabKind::SideBySide,
269 TabTarget::GraphChanges => self.active_tab = TabKind::GraphChanges,
270 TabTarget::Source => self.active_tab = TabKind::Source,
271 TabTarget::ComponentByName(name) => {
272 self.active_tab = TabKind::Components;
273 self.find_and_select_component(&name);
274 }
275 TabTarget::VulnerabilityById(id) => {
276 self.active_tab = TabKind::Vulnerabilities;
277 if let Some(idx) = self.find_vulnerability_index(&id) {
278 self.tabs.vulnerabilities.selected = idx;
279 }
280 }
281 }
282 }
283}