1use anyhow::Result;
2use crossterm::{
3 event::{self, Event, KeyCode, KeyEventKind, KeyModifiers},
4 terminal::{disable_raw_mode, enable_raw_mode},
5};
6use ratatui::{prelude::*, TerminalOptions};
7use std::io;
8
9mod rendering;
10
11use crate::{
12 processes::{FilterOptions, ProcessManager, ProcessSearchResults},
13 settings::AppSettings,
14};
15
16use self::rendering::Tui;
17
18struct App {
19 process_manager: ProcessManager,
20 search_results: ProcessSearchResults,
21 filter_options: FilterOptions,
22 tui: Tui,
23}
24
25impl App {
26 fn new(search_criteria: String, app_settings: AppSettings) -> Result<App> {
27 let mut app = App {
28 process_manager: ProcessManager::new()?,
29 search_results: ProcessSearchResults::empty(),
30 filter_options: app_settings.filter_opions,
31 tui: Tui::new(search_criteria),
32 };
33 app.search_for_processess();
34 Ok(app)
35 }
36
37 fn enter_char(&mut self, new_char: char) {
38 self.tui.enter_char(new_char);
39 self.search_for_processess();
40 }
41
42 fn search_for_processess(&mut self) {
43 self.tui.reset_error_message();
44 self.process_manager.refresh();
45 self.search_results = self
46 .process_manager
47 .find_processes(self.tui.search_input_text(), self.filter_options);
48 self.tui
49 .update_process_table_number_of_items(self.search_results.len());
50 }
51
52 fn delete_char(&mut self) {
53 self.tui.delete_char();
54 self.search_for_processess();
55 }
56
57 fn kill_selected_process(&mut self) {
58 self.tui.reset_error_message();
59 let prc_index = self.tui.get_selected_row_index();
60 if let Some(prc) = self.search_results.nth(prc_index) {
61 let pid = prc.pid;
62 if self.process_manager.kill_process(pid) {
63 self.search_for_processess();
64 self.search_results.remove(pid);
66 self.tui
69 .update_process_table_number_of_items(self.search_results.len());
70 } else {
71 self.tui
72 .set_error_message("Failed to kill process, check permissions");
73 }
74 }
75 }
76}
77
78pub fn start_app(search_criteria: String, app_settings: AppSettings) -> Result<()> {
79 enable_raw_mode()?;
81 let backend = CrosstermBackend::new(io::stdout());
82 let viewport = app_settings.viewport.clone();
83 let mut terminal = Terminal::with_options(backend, TerminalOptions { viewport })?;
84
85 let app = App::new(search_criteria, app_settings)?;
87 let res = run_app(&mut terminal, app);
88
89 disable_raw_mode()?;
91 terminal.clear()?;
92
93 if let Err(err) = res {
95 println!("{err:?}");
96 }
97
98 Ok(())
99}
100
101fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<()> {
102 loop {
103 terminal.draw(|f| app.tui.render_ui(&app.search_results, f))?;
104
105 if let Event::Key(key) = event::read()? {
106 if key.kind == KeyEventKind::Press {
107 use KeyCode::*;
108 match key.code {
109 Esc => return Ok(()),
110 Up if key.modifiers.contains(KeyModifiers::CONTROL) => {
111 app.tui.select_first_row()
112 }
113 Down if key.modifiers.contains(KeyModifiers::CONTROL) => {
114 app.tui.select_last_row()
115 }
116 Up | BackTab => app.tui.select_previous_row(1),
117 Tab | Down => app.tui.select_next_row(1),
118 Char('j') if key.modifiers.contains(KeyModifiers::CONTROL) => {
119 app.tui.select_next_row(1);
120 }
121 Char('k') if key.modifiers.contains(KeyModifiers::CONTROL) => {
122 app.tui.select_previous_row(1);
123 }
124 PageUp => app.tui.select_previous_row(10),
125 PageDown => app.tui.select_next_row(10),
126 Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
127 return Ok(());
128 }
129 Char('x') if key.modifiers.contains(KeyModifiers::CONTROL) => {
130 app.kill_selected_process()
131 }
132 Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => {
133 app.search_for_processess()
134 }
135 Char('f') if key.modifiers.contains(KeyModifiers::CONTROL) => {
136 app.tui.process_details_down(&mut terminal.get_frame())
137 }
138 Char('b') if key.modifiers.contains(KeyModifiers::CONTROL) => {
139 app.tui.process_details_up()
140 }
141 Char(to_insert) => app.enter_char(to_insert),
142 Backspace => app.delete_char(),
143 _ => app.tui.handle_input(key),
144 }
145 }
146 }
147 }
148}