diff options
| author | pablu <me@pablu.de> | 2026-06-03 00:59:42 +0200 |
|---|---|---|
| committer | pablu <me@pablu.de> | 2026-06-03 00:59:42 +0200 |
| commit | bb7178262d721d61062b151df17bc2ba9f327366 (patch) | |
| tree | 25a6fbf900019f538dee372e5893a30ac7c72bae | |
| parent | 9a0d2b1c0e7b31ace9f3477622ef907f8d11ad7c (diff) | |
increment version in cargo.toml to v0.2.0, add filtering of worldset to installed_packages view, make use flags actually readable for packages with alot of useflags
| -rw-r--r-- | Cargo.lock | 98 | ||||
| -rw-r--r-- | Cargo.toml | 3 | ||||
| -rw-r--r-- | src/app.rs | 2 | ||||
| -rw-r--r-- | src/gentoo/portage.rs | 52 | ||||
| -rw-r--r-- | src/main.rs | 10 | ||||
| -rw-r--r-- | src/screens/dashboard.rs | 4 | ||||
| -rw-r--r-- | src/screens/installed_packages.rs | 82 | ||||
| -rw-r--r-- | src/widgets/use_flags.rs | 76 |
8 files changed, 257 insertions, 70 deletions
@@ -42,6 +42,56 @@ dependencies = [ ] [[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -176,6 +226,33 @@ dependencies = [ ] [[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] name = "color-eyre" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -203,6 +280,12 @@ dependencies = [ ] [[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] name = "compact_str" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -655,6 +738,12 @@ dependencies = [ ] [[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -893,6 +982,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] name = "ordered-float" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1082,9 +1177,10 @@ dependencies = [ [[package]] name = "qview" -version = "0.1.0" +version = "0.2.0" dependencies = [ "chrono", + "clap", "color-eyre", "quick-xml", "ratatui", @@ -1,6 +1,6 @@ [package] name = "qview" -version = "0.1.0" +version = "0.2.0" edition = "2024" authors = [ "me@pablu.de" ] readme = "README.md" @@ -9,6 +9,7 @@ license = "MIT" [dependencies] chrono = "0.4.44" +clap = {version= "4.6.1", features = ["cargo"]} color-eyre = "0.6.5" quick-xml = "0.39.3" ratatui = "0.30.0" @@ -43,7 +43,7 @@ impl App { dashboard_screen: DashboardScreen::default(), installed_package_screen: InstalledPackagesScreen::default(), - view: ViewState::InstalledPackages, + view: ViewState::Dashboard, } } diff --git a/src/gentoo/portage.rs b/src/gentoo/portage.rs index c837eb6..9b13867 100644 --- a/src/gentoo/portage.rs +++ b/src/gentoo/portage.rs @@ -7,24 +7,52 @@ use crate::gentoo::{ #[derive(Debug, Default)] pub struct Portage { - pub installed_packages: Vec<InstalledPackage>, - pub world_packages: HashSet<PackageKey>, - pub available_packages: Vec<Package>, + installed_packages: Vec<InstalledPackage>, + world_packages: HashSet<PackageKey>, + available_packages: Vec<Package>, } impl Portage { - pub fn new() -> Self { + // TODO: Do this differently to allow backgrounded loading + pub fn new( + installed_packages: Vec<InstalledPackage>, + world_packages: HashSet<PackageKey>, + available_packages: Vec<Package>, + ) -> Self { Self { - installed_packages: vec![], - world_packages: HashSet::new(), - available_packages: vec![], + installed_packages, + world_packages, + available_packages, } } - pub fn get_installed_package(&self, key: PackageKey) -> Option<InstalledPackage> { - let pkg = self.installed_packages.iter().find(|pkg| pkg.atom == key); + pub fn installed_packages(&self) -> Vec<&InstalledPackage> { + self.installed_packages.iter().collect() + } + + pub fn world_packages_len(&self) -> usize { + self.world_packages.len() + } + + pub fn world_packages(&self) -> Vec<&InstalledPackage> { + self.installed_packages + .iter() + .filter(|pkg| self.world_packages.contains(&pkg.atom)) + .collect() + } + + pub fn get_installed_package(&self, index: usize) -> Option<&InstalledPackage> { + if self.installed_packages.len() > index && index > 0 { + return Some(&self.installed_packages[index]); + } + + None + } + + pub fn get_installed_package_key(&self, key: &PackageKey) -> Option<&InstalledPackage> { + let pkg = self.installed_packages.iter().find(|pkg| pkg.atom == *key); if let Some(pkg) = pkg { - return Some(pkg.clone()); + return Some(pkg); } None @@ -33,4 +61,8 @@ impl Portage { pub fn total_installed_size(&self) -> usize { self.installed_packages.iter().map(|p| p.size).sum() } + + pub(crate) fn installed_packages_len(&self) -> usize { + self.installed_packages.len() + } } diff --git a/src/main.rs b/src/main.rs index 5a8940b..eecbffc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use std::{ panic::{set_hook, take_hook}, }; +use clap::command; use ratatui::{ Terminal, crossterm::{ @@ -34,14 +35,17 @@ impl Drop for TuiGuard { fn main() -> color_eyre::Result<()> { color_eyre::install()?; + + // Just for --help and --version right now, later --config and possably more + let _ = command!().get_matches(); let _guard = TuiGuard; let tui = init_tui()?; - let mut p = Portage::new(); - p.world_packages = portage_loader::load_world_packages()?; - p.installed_packages = portage_loader::load_installed_packages()?; + let world_packages = portage_loader::load_world_packages()?; + let installed_packages = portage_loader::load_installed_packages()?; // p.available_packages = portage_loader::load_available_packages()?; + let p = Portage::new(installed_packages, world_packages, vec![]); let mut app = App::new(p); diff --git a/src/screens/dashboard.rs b/src/screens/dashboard.rs index 9a6c277..87d2e81 100644 --- a/src/screens/dashboard.rs +++ b/src/screens/dashboard.rs @@ -87,7 +87,7 @@ impl Screen for DashboardScreen { ); frame.render_widget(keys, key_hints); - let installed_packages_len = repo.installed_packages.len().to_string(); + let installed_packages_len = repo.installed_packages_len().to_string(); let installed_packages = create_stats("Installed Packages", installed_packages_len.as_str()); let global_use_flags = create_stats("Global USE Flags", "COMING SOON"); @@ -104,7 +104,7 @@ impl Screen for DashboardScreen { .spacing(Spacing::Overlap(1)) .split(stats_middle); - let world_package_count = repo.world_packages.len().to_string(); + let world_package_count = repo.world_packages_len().to_string(); let world_packages = create_stats("World Packages", &world_package_count); let pending_updates = create_stats("Pending Updates", "COMING SOON"); let last_emerge = create_stats("Last Emerge", "COMING SOON"); diff --git a/src/screens/installed_packages.rs b/src/screens/installed_packages.rs index 6175e08..4526d38 100644 --- a/src/screens/installed_packages.rs +++ b/src/screens/installed_packages.rs @@ -9,7 +9,7 @@ use ratatui::{ use ratatui_textarea::TextArea; use crate::{ - gentoo::{InstalledPackage, Portage}, + gentoo::{InstalledPackage, Portage, package::PackageKey}, screens::screen::Screen, signal::Signal, theme::Theme, @@ -49,9 +49,8 @@ impl SearchPopup { self.textarea.clear(); } - pub fn search(&mut self, repo: &Portage) -> Option<usize> { - let indexes: Vec<usize> = repo - .installed_packages + pub fn search(&mut self, packages: Vec<&InstalledPackage>) -> Option<usize> { + let indexes: Vec<usize> = packages .iter() .enumerate() .filter(|(_, s)| { @@ -74,22 +73,27 @@ impl SearchPopup { } #[derive(Debug)] +enum FilterState { + Unfiltered, + WorldSet, +} + +#[derive(Debug)] pub struct InstalledPackagesScreen { list_state: ListState, + filter_state: FilterState, search_popup: SearchPopup, } impl InstalledPackagesScreen { - fn render_packages(&mut self, frame: &mut Frame, area: Rect, repo: &Portage) { - // TODO: Put this into portage method - let items: Vec<String> = repo - .installed_packages - .iter() - .map(|x| x.atom.qualified_name()) - .collect(); + fn render_packages(&mut self, frame: &mut Frame, area: Rect, package_keys: Vec<String>) { + let title = match self.filter_state { + FilterState::Unfiltered => "Installed packages", + FilterState::WorldSet => "World packages", + }; - let list = List::new(items) + let list = List::new(package_keys) .style(Color::White) .highlight_style(Theme::selected()) .highlight_symbol("> ") @@ -97,11 +101,20 @@ impl InstalledPackagesScreen { Block::default() .borders(Borders::ALL) .border_style(Theme::block()) - .title("Installed packages"), + .title(title), ); frame.render_stateful_widget(list, area, &mut self.list_state); } + + fn cycle_filter(&mut self) { + self.list_state.select(Some(0)); + + self.filter_state = match self.filter_state { + FilterState::Unfiltered => FilterState::WorldSet, + FilterState::WorldSet => FilterState::Unfiltered, + } + } } impl Default for InstalledPackagesScreen { @@ -110,6 +123,7 @@ impl Default for InstalledPackagesScreen { list_state.select(Some(0)); Self { list_state: list_state, + filter_state: FilterState::Unfiltered, search_popup: Default::default(), } } @@ -128,14 +142,28 @@ impl Screen for InstalledPackagesScreen { let split_vert = Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]) .split(split[1]); + // TODO: Dont get repo in here just a list of packages + // TODO: Put this into portage method + let items: Vec<&PackageKey> = match self.filter_state { + FilterState::Unfiltered => repo.installed_packages().iter().map(|x| &x.atom).collect(), + FilterState::WorldSet => repo.world_packages().iter().map(|x| &x.atom).collect(), + }; + // TODO: Atleast this doesnt crash but its still weird, it should show some Info that this - // package is completly unkown or placeholder data, or at best a warning - let mut pkg: Option<&InstalledPackage> = None; - if repo.installed_packages.len() > self.list_state.selected().unwrap_or(0) { - pkg = Some(&repo.installed_packages[self.list_state.selected().unwrap_or(0)]); - } + // package is completly unkown or placeholder data, or at best a warning, although this + // should never really happen on modern gentoo systems, as there will always be atleast one + // (this) package installed + let selected_package_key = items.get(self.list_state.selected().unwrap_or(0)); + let pkg: Option<&InstalledPackage> = match selected_package_key { + Some(selected_package_key) => repo.get_installed_package_key(selected_package_key), + None => None, + }; - self.render_packages(frame, split[0], repo); + self.render_packages( + frame, + split[0], + items.iter().map(|x| x.qualified_name()).collect(), + ); render_use_flags(frame, split_vert[0], pkg); render_package_metadata(frame, split_vert[1], pkg); @@ -195,10 +223,15 @@ impl Screen for InstalledPackagesScreen { fn update(&mut self, key: KeyEvent, repo: &Portage) -> Option<Signal> { if self.search_popup.visible { + let packages = match self.filter_state { + FilterState::Unfiltered => repo.installed_packages(), + FilterState::WorldSet => repo.world_packages(), + }; + match key.code { KeyCode::Esc => self.search_popup.toggle(), KeyCode::Enter => { - let result = self.search_popup.search(repo); + let result = self.search_popup.search(packages); self.search_popup.toggle(); if let Some(index) = result { @@ -207,7 +240,7 @@ impl Screen for InstalledPackagesScreen { } _ => { self.search_popup.textarea.input_without_shortcuts(key); - let result = self.search_popup.search(repo); + let result = self.search_popup.search(packages); if let Some(index) = result { self.list_state.select(Some(index)); @@ -239,8 +272,11 @@ impl Screen for InstalledPackagesScreen { KeyCode::Char('j') => self.list_state.select_next(), KeyCode::Char('k') => self.list_state.select_previous(), KeyCode::Char('/') => self.search_popup.toggle(), - KeyCode::Char('n') => todo!("Next search"), - KeyCode::Char('N') => todo!("prev search"), + + // TODO: Reimplement searchable "spaces" + // KeyCode::Char('n') => todo!("Next search"), + // KeyCode::Char('N') => todo!("prev search"), + KeyCode::Char('f') => self.cycle_filter(), _ => {} }, diff --git a/src/widgets/use_flags.rs b/src/widgets/use_flags.rs index 711a45a..59e5781 100644 --- a/src/widgets/use_flags.rs +++ b/src/widgets/use_flags.rs @@ -2,7 +2,8 @@ use ratatui::{ Frame, layout::Rect, style::{Color, Modifier}, - widgets::{Block, Borders, List, ListItem, ListState}, + text::{Line, Span}, + widgets::{Block, Borders, List, ListItem, ListState, Paragraph}, }; use crate::{gentoo::InstalledPackage, theme::Theme}; @@ -12,36 +13,53 @@ pub fn render_use_flags(frame: &mut Frame, area: Rect, package: Option<&Installe return; }; - let items = package.iuse.iter().map(|x| { - let text_style = if package.enabled_use_flags.contains(&x.name) { - Theme::success() - } else if x.default { - Theme::info() - } else { - Theme::error() - }; + let flags: Vec<_> = package.iuse.iter().collect(); - let text_style = if x.default { - text_style.add_modifier(Modifier::ITALIC) - } else { - text_style - }; + let max_len = flags.iter().map(|f| f.name.len()).max().unwrap_or(1); - ListItem::new(x.name.clone()).style(text_style) - }); - let list = List::new(items) - .style(Color::White) - .scroll_padding(1) - .direction(ratatui::widgets::ListDirection::TopToBottom) - .block( - Block::default() - .borders(Borders::ALL) - .title("USE Flags") - .border_style(Theme::block()), - ); + // width per column (+ padding between columns) + let col_width = max_len as u16 + 3; - let mut list_state = ListState::default(); - list_state.select_first(); + let columns = (area.width / col_width).max(1) as usize; + let rows = flags.len().div_ceil(columns); - frame.render_stateful_widget(list, area, &mut list_state); + let mut lines = Vec::new(); + + for row in 0..rows { + let mut spans = Vec::new(); + + for col in 0..columns { + let idx = row * columns + col; + + if let Some(flag) = flags.get(idx) { + let mut style = if package.enabled_use_flags.contains(&flag.name) { + Theme::success() + } else if flag.default { + Theme::info() + } else { + Theme::error() + }; + + if flag.default { + style = style.add_modifier(Modifier::ITALIC); + } + + spans.push(Span::styled( + format!("{:<width$}", flag.name, width = max_len + 2), + style, + )); + } + } + + lines.push(Line::from(spans)); + } + + let paragraph = Paragraph::new(lines).block( + Block::default() + .borders(Borders::ALL) + .title("USE Flags") + .border_style(Theme::block()), + ); + + frame.render_widget(paragraph, area); } |
