diff --git a/Cargo.lock b/Cargo.lock index 21285747..b6cd9ee8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -104,6 +110,21 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.0.92" @@ -187,6 +208,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -218,6 +252,47 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.5.0", + "crossterm_winapi", + "libc", + "mio 0.8.11", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags 2.5.0", + "crossterm_winapi", + "mio 1.0.3", + "parking_lot", + "rustix 0.38.40", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "csv" version = "1.3.0" @@ -299,6 +374,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.8" @@ -315,6 +396,12 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "getrandom" version = "0.2.14" @@ -332,6 +419,23 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -383,6 +487,24 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -432,18 +554,61 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + [[package]] name = "memchr" version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "nix" version = "0.29.0" @@ -510,6 +675,35 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "phf" version = "0.11.3" @@ -668,6 +862,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "ratatui" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" +dependencies = [ + "bitflags 2.5.0", + "cassowary", + "compact_str", + "crossterm 0.27.0", + "itertools 0.12.1", + "lru", + "paste", + "stability", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + [[package]] name = "rayon" version = "1.10.0" @@ -688,6 +902,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "redox_users" version = "0.4.5" @@ -791,6 +1014,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.197" @@ -811,12 +1040,49 @@ dependencies = [ "syn", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio 0.8.11", + "mio 1.0.3", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "smawk" version = "0.3.2" @@ -833,12 +1099,50 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "stability" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "syn" version = "2.0.87" @@ -1005,6 +1309,23 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "unicode-width" version = "0.1.11" @@ -1138,9 +1459,10 @@ version = "0.0.1" dependencies = [ "chrono", "clap", + "crossterm 0.28.1", "libc", "nix", - "prettytable-rs", + "ratatui", "sysinfo", "uucore", ] diff --git a/Cargo.toml b/Cargo.toml index 02975cd8..4d09acc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,8 @@ uucore = "0.0.29" walkdir = "2.5.0" windows = { version = "0.59.0" } xattr = "1.3.1" +ratatui = "0.26.1" +crossterm = "0.28.1" [dependencies] clap = { workspace = true } diff --git a/src/uu/top/Cargo.toml b/src/uu/top/Cargo.toml index 800a2fc3..dc93b28f 100644 --- a/src/uu/top/Cargo.toml +++ b/src/uu/top/Cargo.toml @@ -16,9 +16,10 @@ uucore = { workspace = true, features = ["utmpx"] } clap = { workspace = true } libc = { workspace = true } nix = { workspace = true } -prettytable-rs = { workspace = true } sysinfo = { workspace = true } chrono = { workspace = true } +ratatui = { workspace = true } +crossterm = { workspace = true } [lib] path = "src/top.rs" diff --git a/src/uu/top/src/top.rs b/src/uu/top/src/top.rs index 6c4a12cc..77b08f86 100644 --- a/src/uu/top/src/top.rs +++ b/src/uu/top/src/top.rs @@ -6,19 +6,21 @@ use clap::{arg, crate_version, value_parser, ArgAction, ArgGroup, ArgMatches, Command}; use picker::pickers; use picker::sysinfo; -use prettytable::{format::consts::FORMAT_CLEAN, Row, Table}; +// use prettytable::{format::consts::FORMAT_CLEAN, Row, Table}; use std::{thread::sleep, time::Duration}; use sysinfo::{Pid, Users}; use uucore::{ error::{UResult, USimpleError}, format_usage, help_about, help_usage, }; +use tui::start_tui; const ABOUT: &str = help_about!("top.md"); const USAGE: &str = help_usage!("top.md"); mod field; mod picker; +mod tui; #[allow(unused)] #[derive(Debug)] @@ -84,40 +86,46 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let fields = selected_fields(); - let collected = collect(&settings, &fields); + + start_tui(move || { + let collected = collect(&settings, &fields); + (fields.clone(), collected) + })?; - let table = { - let mut table = Table::new(); + // let collected = collect(&settings, &fields); - table.set_format(*FORMAT_CLEAN); + // let table = { + // let mut table = Table::new(); - table.add_row(Row::from_iter(fields)); - table.extend(collected.iter().map(Row::from_iter)); + // table.set_format(*FORMAT_CLEAN); - table - }; + // table.add_row(Row::from_iter(fields)); + // table.extend(collected.iter().map(Row::from_iter)); - println!("{}", header()); - println!("\n"); + // table + // }; - let cutter = { - #[inline] - fn f(f: impl Fn(&str) -> String + 'static) -> Box String> { - Box::new(f) - } + // println!("{}", header()); + // println!("\n"); - if let Some(width) = settings.width { - f(move |line: &str| apply_width(line, width)) - } else { - f(|line: &str| line.to_string()) - } - }; + // let cutter = { + // #[inline] + // fn f(f: impl Fn(&str) -> String + 'static) -> Box String> { + // Box::new(f) + // } + + // if let Some(width) = settings.width { + // f(move |line: &str| apply_width(line, width)) + // } else { + // f(|line: &str| line.to_string()) + // } + // }; - table - .to_string() - .lines() - .map(cutter) - .for_each(|it| println!("{}", it)); + // table + // .to_string() + // .lines() + // .map(cutter) + // .for_each(|it| println!("{}", it)); Ok(()) } @@ -142,25 +150,25 @@ where .ok_or(USimpleError::new(1, "Invalid user")) } -fn apply_width(input: T, width: usize) -> String -where - T: Into, -{ - let input: String = input.into(); - - if input.len() > width { - input.chars().take(width).collect() - } else { - let mut result = String::from(&input); - result.extend(std::iter::repeat(' ').take(width - input.len())); - result - } -} +// fn apply_width(input: T, width: usize) -> String +// where +// T: Into, +// { +// let input: String = input.into(); + +// if input.len() > width { +// input.chars().take(width).collect() +// } else { +// let mut result = String::from(&input); +// result.extend(std::iter::repeat(' ').take(width - input.len())); +// result +// } +// } // TODO: Implement information collecting. -fn header() -> String { - "TODO".into() -} +// fn header() -> String { +// "TODO".into() +// } // TODO: Implement fields selecting fn selected_fields() -> Vec { diff --git a/src/uu/top/src/tui.rs b/src/uu/top/src/tui.rs new file mode 100755 index 00000000..6e3abb65 --- /dev/null +++ b/src/uu/top/src/tui.rs @@ -0,0 +1,99 @@ +use crossterm::{ + event::{self, Event, KeyCode}, + terminal::{disable_raw_mode, enable_raw_mode}, +}; +use ratatui::{ + backend::{Backend, CrosstermBackend}, + layout::{Constraint, Direction, Layout}, + style::{Color, Modifier, Style}, + text::{Line, Text}, + widgets::{Paragraph, Row, Table}, + Terminal +}; +use std::io::{Result, stdout}; + +pub fn start_tui(data_provider: F) -> Result<()> +where + F: Fn() -> (Vec, Vec>), +{ + enable_raw_mode()?; + + let stdout = stdout(); + + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + + let result = run_app(&mut terminal, data_provider); + + disable_raw_mode()?; + + terminal.show_cursor()?; + + result +} + +fn render_top_info() -> Vec { + let info = "top - 04:30:13 up 6:02, 3 users, load average: 0.40, 0.89, 1.24 +Tasks: 260 total, 3 running, 257 sleeping, 0 stopped, 0 zombie +%Cpu(s): 1.9 us, 0.3 sy, 0.0 ni, 97.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +MiB Mem : 3911.6 total, 190.6 free, 2549.1 used, 1171.9 buff/cache +MiB Swap: 2048.0 total, 692.0 free, 1356.0 used. 1017.7 avail Mem"; + + info.lines().map(String::from).collect() +} + +fn run_app(terminal: &mut Terminal, data_provider: F) -> Result<()> +where + F: Fn() -> (Vec, Vec>), + B: Backend, +{ + loop { + let (fields, data) = data_provider(); + + terminal.clear().unwrap(); + terminal.draw(|f| { + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(5), Constraint::Min(1)].as_ref()) + .spacing(1) + .split(f.size()); + + let rows = data.into_iter().map(|row| Row::new(row)); + let widths = (0..fields.len()) + .map(|_| Constraint::Length(10)) + .collect::>(); + + let top_paragraph = Paragraph::new(Text::from( + render_top_info() + .into_iter() + .map(|line| Line::from(line)) + .collect::>() + )); + + let table = Table::new(rows, widths) + .header(Row::new(fields).style(Style::default().fg(Color::Black).bg(Color::White))) + .highlight_style( + Style::default() + .add_modifier(Modifier::BOLD), + ); + + f.render_widget(top_paragraph, layout[0]); + f.render_widget(table, layout[1]); + })?; + + // handle events + if let Event::Key(key) = event::read()? { + if key.kind == event::KeyEventKind::Release { + // Skip events that are not KeyEventKind::Press + continue; + } + match key.code { + KeyCode::Char('q') => { + println!(); + return Ok(()); + } + _ => {} + } + } + } +}