aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock98
-rw-r--r--Cargo.toml3
-rw-r--r--src/app.rs2
-rw-r--r--src/gentoo/portage.rs52
-rw-r--r--src/main.rs10
-rw-r--r--src/screens/dashboard.rs4
-rw-r--r--src/screens/installed_packages.rs82
-rw-r--r--src/widgets/use_flags.rs76
8 files changed, 257 insertions, 70 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 7b27357..7060e83 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index 872e879..d6379e9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/src/app.rs b/src/app.rs
index ef0e92b..aa6091c 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -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);
}