crashdump_viewer_lib/
handler.rs

1// Copyright (c) Meta Platforms, Inc. and affiliates.
2
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6
7//     http://www.apache.org/licenses/LICENSE-2.0
8
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::app::{App, AppResult, ProcessSortColumn, ProcessGroupSortColumn, ProcessViewState, SelectedTab, SortDirection};
16use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
17
18pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
19    match app.selected_tab {
20        SelectedTab::Inspect => match key_event.code {
21            KeyCode::Esc | KeyCode::Char('q') => {
22                app.quit();
23            }
24            KeyCode::Char('c') | KeyCode::Char('C')
25                if key_event.modifiers == KeyModifiers::CONTROL =>
26            {
27                app.quit();
28            }
29            KeyCode::Char('i') | KeyCode::Char('I') => {
30                app.selected_tab = SelectedTab::Process;
31            }
32            KeyCode::Right => app.next_tab(),
33            KeyCode::Left => app.prev_tab(),
34            KeyCode::Char('j') | KeyCode::Down => app.inspect_scroll_state.scroll_down(),
35            KeyCode::Char('k') | KeyCode::Up => app.inspect_scroll_state.scroll_up(),
36            KeyCode::Char('f') | KeyCode::PageDown => app.inspect_scroll_state.scroll_page_down(),
37            KeyCode::Char('b') | KeyCode::PageUp => app.inspect_scroll_state.scroll_page_up(),
38            KeyCode::Char('g') | KeyCode::Home => app.inspect_scroll_state.scroll_to_top(),
39            KeyCode::Char('G') | KeyCode::End => app.inspect_scroll_state.scroll_to_bottom(),
40            KeyCode::Char('?') => {
41                app.show_help = !app.show_help;
42            }
43            _ => {}
44        },
45        SelectedTab::ProcessGroup => {
46            match key_event.code {
47                KeyCode::Esc | KeyCode::Char('q') => app.quit(),
48                KeyCode::Char('c') | KeyCode::Char('C')
49                    if key_event.modifiers == KeyModifiers::CONTROL =>
50                {
51                    app.quit()
52                }
53                KeyCode::Right => app.next_tab(),
54                KeyCode::Left => app.prev_tab(),
55
56                KeyCode::Down => {
57                    // Get the list first
58                    if let Some(list) = app.tab_lists.get(&app.selected_tab) {
59                        // Then get the mutable state
60                        if let Some(table_state) = app.table_states.get_mut(&app.selected_tab) {
61                            let amount_items = list.len(); // Use the list directly
62                            if amount_items == 0 {
63                                // Handle empty list case
64                                table_state.select(None);
65                            } else if let Some(selected) = table_state.selected() {
66                                if selected < amount_items - 1 {
67                                    table_state.select(Some(selected + 1));
68                                } else {
69                                    table_state.select(Some(0)); // Wrap to top
70                                }
71                            } else {
72                                // Nothing selected, select the first item
73                                table_state.select(Some(0));
74                            }
75                        }
76                    }
77                }
78                KeyCode::Up => {
79                    if let Some(list) = app.tab_lists.get(&app.selected_tab) {
80                        if let Some(table_state) = app.table_states.get_mut(&app.selected_tab) {
81                            let amount_items = list.len();
82                            if amount_items == 0 {
83
84                            } else if let Some(selected) = table_state.selected() {
85                                if selected > 0 {
86                                    table_state.select(Some(selected - 1));
87                                } else {
88                                    table_state.select(Some(amount_items - 1));
89                                }
90                            } else {
91                                table_state.select(Some(amount_items - 1));
92                            }
93                        }
94                    }
95                }
96                KeyCode::Char('?') => {
97                    app.show_help = !app.show_help;
98                }
99                KeyCode::Char(c) if key_event.modifiers == KeyModifiers::SHIFT => {
100                    let mut new_sort_column: Option<ProcessGroupSortColumn> = None;
101                    match c {
102                        'T' => new_sort_column = Some(ProcessGroupSortColumn::TotalMemorySize),
103                        'P' => new_sort_column = Some(ProcessGroupSortColumn::Pid),
104                        'N' => new_sort_column = Some(ProcessGroupSortColumn::Name),
105                        'C' => new_sort_column = Some(ProcessGroupSortColumn::ChildrenCount),
106                        _ => {} // Ignore other Shift+key combinations
107                    }
108                    if let Some(sort_col) = new_sort_column {
109                        if app.process_group_sort_column == sort_col {
110                            app.process_group_sort_direction = match app.process_group_sort_direction {
111                                SortDirection::Ascending => SortDirection::Descending,
112                                SortDirection::Descending => SortDirection::Ascending,
113                            };
114                        } else {
115                            app.process_group_sort_column = sort_col;
116                            match sort_col {
117                                ProcessGroupSortColumn::Pid | ProcessGroupSortColumn::Name => {
118                                    app.process_group_sort_direction = SortDirection::Ascending;
119                                }
120                                _ => {
121                                    // Numerical columns
122                                    app.process_group_sort_direction = SortDirection::Descending;
123                                }
124                            }
125                        }
126                        app.sort_and_update_process_group_table();
127                    }
128                }
129
130
131                _ => {}
132            }
133        }
134        SelectedTab::Process => {
135            match key_event.code {
136                KeyCode::Esc | KeyCode::Char('q') => app.quit(),
137                KeyCode::Char('c') | KeyCode::Char('C')
138                    if key_event.modifiers == KeyModifiers::CONTROL =>
139                {
140                    app.quit()
141                }
142                KeyCode::Right => app.next_tab(),
143                KeyCode::Left => app.prev_tab(),
144                KeyCode::Down | KeyCode::Char('j') => {
145                    if let Some(table_state) = app.table_states.get_mut(&SelectedTab::Process) {
146                        if let Some(selected) = table_state.selected() {
147                            let amount_items = app.tab_lists[&SelectedTab::Process].len();
148                            if selected < amount_items.saturating_sub(1) {
149                                table_state.select(Some(selected + 1));
150                            } else if amount_items > 0 {
151                                table_state.select(Some(0));
152                            }
153                        } else if !app.tab_lists[&SelectedTab::Process].is_empty() {
154                            table_state.select(Some(0));
155                        }
156                    }
157                }
158                KeyCode::Up | KeyCode::Char('k') => {
159                    if let Some(table_state) = app.table_states.get_mut(&SelectedTab::Process) {
160                        if let Some(selected) = table_state.selected() {
161                            let amount_items = app.tab_lists[&SelectedTab::Process].len();
162                            if selected > 0 {
163                                table_state.select(Some(selected - 1));
164                            } else if amount_items > 0 {
165                                table_state.select(Some(amount_items - 1));
166                            }
167                        } else if !app.tab_lists[&SelectedTab::Process].is_empty() {
168                            let amount_items = app.tab_lists[&SelectedTab::Process].len();
169                            table_state.select(Some(amount_items - 1));
170                        }
171                    }
172                }
173                KeyCode::Char('s') => app.process_view_state = ProcessViewState::Stack,
174                KeyCode::Char('h') => app.process_view_state = ProcessViewState::Heap,
175                KeyCode::Char('m') => app.process_view_state = ProcessViewState::MessageQueue,
176                KeyCode::Char('i') | KeyCode::Char('I') => app.selected_tab = SelectedTab::Inspect,
177                KeyCode::Char('?') => {
178                    app.show_help = !app.show_help;
179                }
180                KeyCode::Char(c) if key_event.modifiers == KeyModifiers::SHIFT => {
181                    let mut new_sort_column: Option<ProcessSortColumn> = None;
182                    match c {
183                        'Q' => new_sort_column = Some(ProcessSortColumn::MessageQueueLength),
184                        'M' => new_sort_column = Some(ProcessSortColumn::Memory),
185                        'T' => new_sort_column = Some(ProcessSortColumn::TotalBinVHeap),
186                        'B' => new_sort_column = Some(ProcessSortColumn::BinVHeap),
187                        'P' => new_sort_column = Some(ProcessSortColumn::Pid),
188                        'N' => new_sort_column = Some(ProcessSortColumn::Name),
189                        'U' => new_sort_column = Some(ProcessSortColumn::BinVHeapUnused),
190                        'O' => new_sort_column = Some(ProcessSortColumn::OldBinVHeap),
191                        'V' => new_sort_column = Some(ProcessSortColumn::OldBinVHeapUnused),
192                        _ => {} // Ignore other Shift+key combinations
193                    }
194
195                    if let Some(sort_col) = new_sort_column {
196                        if app.process_sort_column == sort_col {
197                            app.process_sort_direction = match app.process_sort_direction {
198                                SortDirection::Ascending => SortDirection::Descending,
199                                SortDirection::Descending => SortDirection::Ascending,
200                            };
201                        } else {
202                            app.process_sort_column = sort_col;
203                            match sort_col {
204                                ProcessSortColumn::Pid | ProcessSortColumn::Name => {
205                                    app.process_sort_direction = SortDirection::Ascending;
206                                }
207                                _ => {
208                                    // Numerical columns
209                                    app.process_sort_direction = SortDirection::Descending;
210                                }
211                            }
212                        }
213                        app.sort_and_update_process_table();
214                    }
215                }
216
217                _ => {} // Ignore other keys
218            }
219        }
220        _ => {
221            // Handling for other tabs (General, ProcessGroup, etc.)
222            match key_event.code {
223                KeyCode::Esc | KeyCode::Char('q') => app.quit(),
224                KeyCode::Char('c') | KeyCode::Char('C')
225                    if key_event.modifiers == KeyModifiers::CONTROL =>
226                {
227                    app.quit()
228                }
229                KeyCode::Right => app.next_tab(),
230                KeyCode::Left => app.prev_tab(),
231
232                KeyCode::Down => {
233                    // Get the list first
234                    if let Some(list) = app.tab_lists.get(&app.selected_tab) {
235                        // Then get the mutable state
236                        if let Some(table_state) = app.table_states.get_mut(&app.selected_tab) {
237                            let amount_items = list.len(); // Use the list directly
238                            if amount_items == 0 {
239                                // Handle empty list case
240                                table_state.select(None);
241                            } else if let Some(selected) = table_state.selected() {
242                                if selected < amount_items - 1 {
243                                    table_state.select(Some(selected + 1));
244                                } else {
245                                    table_state.select(Some(0)); // Wrap to top
246                                }
247                            } else {
248                                // Nothing selected, select the first item
249                                table_state.select(Some(0));
250                            }
251                        }
252                    }
253                }
254                KeyCode::Up => {
255                    if let Some(list) = app.tab_lists.get(&app.selected_tab) {
256                        if let Some(table_state) = app.table_states.get_mut(&app.selected_tab) {
257                            let amount_items = list.len();
258                            if amount_items == 0 {
259
260                            } else if let Some(selected) = table_state.selected() {
261                                if selected > 0 {
262                                    table_state.select(Some(selected - 1));
263                                } else {
264                                    table_state.select(Some(amount_items - 1));
265                                }
266                            } else {
267                                table_state.select(Some(amount_items - 1));
268                            }
269                        }
270                    }
271                }
272                KeyCode::Char('?') => {
273                    app.show_help = !app.show_help;
274                }
275                _ => {}
276            }
277        }
278    }
279    Ok(())
280}