1
0
mirror of https://github.com/danog/ytop.git synced 2024-11-30 04:29:10 +01:00

Implement basic scrolling, pausing, and tabbing

This commit is contained in:
Caleb Bassi 2020-01-15 09:03:51 -08:00
parent 0e4e9ff00e
commit d88e202098
4 changed files with 169 additions and 10 deletions

View File

@ -23,6 +23,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add sensor label to temperature identifier
- Process cpu percents are now working
- Draw the proc cursor
- Add basic scrolling for `g`, `G`, `j`, and `k`
- Add process grouping toggling with `Tab`
- Add pausing with `Space`
### Changed

View File

@ -108,3 +108,44 @@ pub fn draw_help_menu<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) ->
app.help_menu.render(&mut frame, rect);
})
}
// TODO: figure out how to draw the proc widget without clearing rest of the screen
pub fn draw_proc<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> io::Result<()> {
draw(terminal, app)
// terminal.draw(|mut frame| {
// let chunks = if app.statusbar.is_some() {
// Layout::default()
// .constraints([Constraint::Min(0), Constraint::Length(1)].as_ref())
// .split(frame.size())
// } else {
// Layout::default()
// .constraints(vec![Constraint::Percentage(100)])
// .split(frame.size())
// };
// let vertical_chunks = if app.widgets.temp.is_some() {
// Layout::default()
// .direction(Direction::Vertical)
// .constraints(
// [
// Constraint::Ratio(1, 3),
// Constraint::Ratio(1, 3),
// Constraint::Ratio(1, 3),
// ]
// .as_ref(),
// )
// .split(chunks[0])
// } else {
// Layout::default()
// .direction(Direction::Vertical)
// .constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)].as_ref())
// .split(chunks[0])
// };
// let horizontal_chunks = Layout::default()
// .direction(Direction::Horizontal)
// .constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)].as_ref())
// .split(*vertical_chunks.last().unwrap());
// app.widgets.proc.render(&mut frame, horizontal_chunks[1]);
// })
}

View File

@ -15,7 +15,7 @@ use std::time::{Duration, Instant};
use anyhow::Result;
use backtrace::Backtrace;
use crossbeam_channel::{select, tick, unbounded, Receiver};
use crossterm::event::{self, Event, KeyCode, KeyModifiers};
use crossterm::event::{self, Event, KeyCode, KeyModifiers, MouseEvent};
use crossterm::execute;
use crossterm::terminal;
use num_rational::Ratio;
@ -108,6 +108,7 @@ fn main() {
let args = Args::from_args();
let update_ratio = Ratio::new(1, args.rate);
let mut show_help_menu = false;
let mut paused = false;
let program_name = env!("CARGO_PKG_NAME");
let app_dirs = AppDirs::new(Some(program_name), AppUI::CommandLine).unwrap();
@ -135,10 +136,12 @@ fn main() {
break;
}
recv(ticker) -> _ => {
update_seconds = (update_seconds + update_ratio) % Ratio::from_integer(60);
update_widgets(&mut app.widgets, update_seconds);
if !show_help_menu {
draw(&mut terminal, &mut app).unwrap();
if !paused {
update_seconds = (update_seconds + update_ratio) % Ratio::from_integer(60);
update_widgets(&mut app.widgets, update_seconds);
if !show_help_menu {
draw(&mut terminal, &mut app).unwrap();
}
}
}
recv(ui_events_receiver) -> message => {
@ -160,6 +163,33 @@ fn main() {
draw(&mut terminal, &mut app).unwrap();
}
},
' ' => {
paused = !paused;
},
'j' => {
app.widgets.proc.scroll_down();
if !show_help_menu {
draw_proc(&mut terminal, &mut app).unwrap();
}
},
'k' => {
app.widgets.proc.scroll_up();
if !show_help_menu {
draw_proc(&mut terminal, &mut app).unwrap();
}
},
'g' => {
app.widgets.proc.scroll_top();
if !show_help_menu {
draw_proc(&mut terminal, &mut app).unwrap();
}
},
'G' => {
app.widgets.proc.scroll_bottom();
if !show_help_menu {
draw_proc(&mut terminal, &mut app).unwrap();
}
},
_ => {}
}
},
@ -169,6 +199,12 @@ fn main() {
draw(&mut terminal, &mut app).unwrap();
}
}
KeyCode::Tab => {
app.widgets.proc.toggle_grouping();
if !show_help_menu {
draw_proc(&mut terminal, &mut app).unwrap();
}
},
_ => {}
}
} else if key_event.modifiers == KeyModifiers::CONTROL {
@ -186,9 +222,21 @@ fn main() {
}
}
}
// TODO: figure out why these aren't working
Event::Mouse(mouse_event) => match mouse_event {
_ => {
}
MouseEvent::ScrollUp(_, _, _) => {
app.widgets.proc.scroll_up();
if !show_help_menu {
draw_proc(&mut terminal, &mut app).unwrap();
}
},
MouseEvent::ScrollDown(_, _, _) => {
app.widgets.proc.scroll_down();
if !show_help_menu {
draw_proc(&mut terminal, &mut app).unwrap();
}
},
_ => {}
}
Event::Resize(_width, _height) => {
if show_help_menu {

View File

@ -1,4 +1,5 @@
use std::collections::HashMap;
use std::process::Command;
use num_rational::Ratio;
use psutil::cpu;
@ -51,6 +52,8 @@ pub struct ProcWidget<'a> {
selected_proc: Option<SelectedProc>,
sorting: ProcSorting,
sort_direction: SortDirection,
view_offset: usize,
scrolled: bool,
cpu_count: u64,
@ -71,6 +74,8 @@ impl ProcWidget<'_> {
selected_proc: None,
sorting: ProcSorting::Cpu,
sort_direction: SortDirection::Down,
view_offset: 0,
scrolled: false,
cpu_count: cpu::cpu_count(),
@ -79,6 +84,58 @@ impl ProcWidget<'_> {
processes: HashMap::new(),
}
}
fn scroll_count(&mut self, count: isize) {
self.selected_row = isize::max(0, self.selected_row as isize + count) as usize;
self.selected_proc = None;
self.scrolled = true;
}
fn scroll_to(&mut self, count: usize) {
self.selected_row = usize::min(
count,
if self.grouping {
self.grouped_procs.len()
} else {
self.procs.len()
} - 1,
);
self.selected_proc = None;
self.scrolled = true;
}
pub fn scroll_up(&mut self) {
self.scroll_count(-1);
}
pub fn scroll_down(&mut self) {
self.scroll_count(1);
}
pub fn scroll_top(&mut self) {
self.scroll_to(0);
}
pub fn scroll_bottom(&mut self) {
self.scroll_to(if self.grouping {
self.grouped_procs.len()
} else {
self.procs.len()
});
}
pub fn toggle_grouping(&mut self) {
self.grouping = !self.grouping;
self.selected_proc = None;
}
fn kill_process(&self) {
let (command, arg) = match self.selected_proc.as_ref().unwrap() {
SelectedProc::Pid(pid) => ("kill", pid.to_string()),
SelectedProc::Name(name) => ("pkill", name.clone()),
};
Command::new(command).arg(arg).spawn().unwrap();
}
}
impl UpdatableWidget for ProcWidget<'_> {
@ -191,6 +248,7 @@ impl Widget for ProcWidget<'_> {
.unwrap_or(self.selected_row),
None => self.selected_row,
};
self.scroll_to(self.selected_row);
self.selected_proc = if self.grouping {
Some(SelectedProc::Name(
procs[self.selected_row].name.to_string(),
@ -199,9 +257,18 @@ impl Widget for ProcWidget<'_> {
Some(SelectedProc::Pid(procs[self.selected_row].num))
};
if self.scrolled {
self.scrolled = false;
if self.selected_row > area.height as usize + self.view_offset - 4 {
self.view_offset = self.selected_row + 4 - area.height as usize;
} else if self.selected_row < self.view_offset {
self.view_offset = self.selected_row;
}
}
Table::new(
header.iter(),
procs.into_iter().map(|proc| {
procs.into_iter().skip(self.view_offset).map(|proc| {
Row::StyledData(
vec![
proc.num.to_string(),
@ -221,7 +288,7 @@ impl Widget for ProcWidget<'_> {
.block(block::new(self.colorscheme, &self.title))
.header_style(self.colorscheme.text.modifier(Modifier::BOLD))
.widths(&[
Constraint::Length(5),
Constraint::Length(6),
// Constraint::Min(5),
Constraint::Length(u16::max((area.width as i16 - 2 - 18) as u16, 5)),
Constraint::Length(5),
@ -231,7 +298,7 @@ impl Widget for ProcWidget<'_> {
.header_gap(0)
.draw(area, buf);
let cursor_y = area.y + 2 + self.selected_row as u16;
let cursor_y = area.y + 2 + self.selected_row as u16 - self.view_offset as u16;
if cursor_y < area.y + area.height - 1 {
for i in (area.x + 1)..(area.x + area.width - 1) {
let cell = buf.get_mut(i, cursor_y);