From 6c50da8c1876dfee2074df34369ef7226fb51384 Mon Sep 17 00:00:00 2001 From: GarandPLG Date: Sat, 6 Dec 2025 17:21:44 +0100 Subject: [PATCH] Refactor Nix handling and add toggle UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Introduce `NixModules` struct containing file paths, parsed ASTs, and option lists. - Add `parse_nix` and `build_nix_modules` helpers for centralized parsing. - Update CLI to use `build_nix_modules` instead of manual parsing. - Extend `App` with system/home paths and ASTs, enabling in‑place editing. - Implement boolean toggle on Enter: update AST, rewrite file, refresh modules, and report action. - Simplify event handling, adding shortcuts `1`/`2` to switch files. - Revise UI layout, introduce footer with usage hints and last‑action status. - Adjust imports and remove obsolete code across modules. --- src/app.rs | 159 +++++++++++++++++++++++++++++++++++++++------------- src/cli.rs | 41 +------------- src/main.rs | 8 ++- src/nix.rs | 51 ++++++++++++++--- 4 files changed, 171 insertions(+), 88 deletions(-) diff --git a/src/app.rs b/src/app.rs index 5f2e749..2313521 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,6 @@ -use crate::nix::{ConfigOption, ConfigSource}; +use crate::nix::{ + ConfigOption, ConfigSource, collect_nix_options, get_nix_value_by_path, toggle_bool_at_path, +}; use crossterm::event::{self, KeyCode, KeyEvent, KeyEventKind}; use ratatui::{ DefaultTerminal, Frame, @@ -6,15 +8,22 @@ use ratatui::{ layout::{Constraint, Layout, Rect}, style::Stylize, text::{Line, Span}, - widgets::Widget, + widgets::{Paragraph, Widget}, }; +use rnix::{Parse, Root, SyntaxNode}; use std::{ + fs, io::Result, + path::PathBuf, sync::mpsc::{self, Receiver}, }; pub struct App { pub exit: bool, + pub system_path: PathBuf, + pub home_path: PathBuf, + pub system_ast: Parse, + pub home_ast: Parse, pub system_modules: Vec, pub home_modules: Vec, pub current_file: ConfigSource, @@ -24,7 +33,7 @@ pub struct App { pub enum Event { Input(KeyEvent), - EditFile, + // EditFile, } impl App { @@ -37,7 +46,7 @@ impl App { match event { Event::Input(key_event) => self.handle_key_event(key_event)?, // Event::EditFile => self.handle_file_edit_events()?, - _ => {} + // _ => {} } terminal.draw(|frame: &mut Frame<'_>| self.draw(frame))?; } @@ -54,6 +63,14 @@ impl App { if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Char('q') { self.exit = true; } + // 1 - zmień plik na ConfigSource::System (pomiń wykonywaniej, jeżeli już jest wybrany) + else if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Char('1') { + self.current_file = ConfigSource::System; + } + // 2 - zmień plik na ConfigSource::Home (pomiń wykonywaniej, jeżeli już jest wybrany) + else if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Char('2') { + self.current_file = ConfigSource::Home; + } // ↑ - scroll w górę else if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Up { self.selected_index = self.selected_index.saturating_sub(1); @@ -64,21 +81,53 @@ impl App { } // ENTER - zmień wartość boolen danej opcji else if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Enter { - } - // 1 - zmień plik na ConfigSource::System (pomiń wykonywaniej, jeżeli już jest wybrany) - else if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Char('1') { - if self.current_file != ConfigSource::System { - self.current_file = ConfigSource::System; - } else { - self.current_file = ConfigSource::Home; - } - } - // 2 - zmień plik na ConfigSource::Home (pomiń wykonywaniej, jeżeli już jest wybrany) - else if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Char('2') { - if self.current_file != ConfigSource::Home { - self.current_file = ConfigSource::Home; - } else { - self.current_file = ConfigSource::System; + let modules: &Vec = match self.current_file { + ConfigSource::System => &self.system_modules.clone(), + ConfigSource::Home => &self.home_modules.clone(), + }; + let node: &SyntaxNode = match self.current_file { + ConfigSource::System => &self.system_ast.syntax(), + ConfigSource::Home => &self.home_ast.syntax(), + }; + if let Some(module) = modules.get(self.selected_index) { + let new_node: SyntaxNode = toggle_bool_at_path(node, module.path.as_str()); + + let new_ast: Parse = Root::parse(&new_node.to_string().as_str()); + + let new_modules: Vec = + collect_nix_options(&new_node, "", self.current_file.clone()); + + let new_module_value: bool = + if let Some(value) = get_nix_value_by_path(&new_node, module.path.as_str()) { + value + } else { + module.value.clone() + }; + + self.last_action_status = if let Some(module) = modules.get(self.selected_index) { + match self.current_file { + ConfigSource::System => { + self.system_modules = new_modules; + self.system_ast = new_ast; + fs::write(&self.system_path, new_node.to_string()) + .expect("Nie można zapisać pliku modułów systemowych"); + } + ConfigSource::Home => { + self.home_modules = new_modules; + self.home_ast = new_ast; + fs::write(&self.home_path, new_node.to_string()) + .expect("Nie można zapisać pliku modułów domowych"); + } + }; + format!( + "{} = {} -> {}", + module.path.clone(), + module.value.clone(), + new_module_value + ) + } else { + "Nothing changed".to_string() + }; } } @@ -92,32 +141,62 @@ impl Widget for &App { Self: Sized, { let vertical_layout: Layout = Layout::vertical([ - Constraint::Percentage(10), - Constraint::Percentage(80), - Constraint::Percentage(10), + Constraint::Percentage(5), + Constraint::Percentage(90), + Constraint::Percentage(5), ]); let [title_area, content_area, footer_area] = vertical_layout.areas(area); - let (system_file, home_file) = match self.current_file { - ConfigSource::System => ("(System)".blue().bold(), "Home".reset()), - ConfigSource::Home => ("System".reset(), "(Home)".blue().bold()), - }; - let navbar: Line<'_> = Line::from(vec![ - Span::from("GarandOS TUI".bold().italic().yellow()), - Span::from(" * ".reset()), - system_file, - Span::from(" [1]".reset()), - Span::from(" * ".reset()), - home_file, - Span::from(" [2]".reset()), - Span::from(" *".reset()), - Span::from(" Quit".reset()), - Span::from(" [q]".reset()), - ]) - .left_aligned(); + { + let (system_file, home_file) = match self.current_file { + ConfigSource::System => ("(System)".blue().bold(), "Home".reset()), + ConfigSource::Home => ("System".reset(), "(Home)".blue().bold()), + }; + let title: Line<'_> = Line::from(vec![ + Span::from("GarandOS TUI".bold().italic().yellow()), + Span::from(" * ".reset()), + system_file, + Span::from(" [1]".reset()), + Span::from(" * ".reset()), + home_file, + Span::from(" [2]".reset()), + Span::from(" *".reset()), + Span::from(" Quit".reset()), + Span::from(" [q]".reset()), + ]) + .left_aligned(); - navbar.render(title_area, buf); + title.render(title_area, buf); + } + + { + let footer_line1: Line<'_> = Line::from(vec![ + Span::from("Use "), + Span::from("↑/↓".italic().green()), + Span::from(" to navigate, "), + Span::from("Enter".bold().white()), + Span::from(" to toggle, "), + Span::from("1/2".bold().blue()), + Span::from(" to switch file, "), + Span::from("q".bold().red()), + Span::from(" to quit."), + ]); + let last_action: Span<'_> = if self.last_action_status.is_empty() { + Span::from("Nothing changed") + } else { + self.last_action_status.clone().into() + }; + let footer_line2: Line<'_> = Line::from(vec![ + Span::from("Last action: "), + last_action.italic().green(), + Span::from("."), + ]); + let footer: Paragraph<'_> = + Paragraph::new(vec![footer_line1, footer_line2]).left_aligned(); + + footer.render(footer_area, buf); + } } } diff --git a/src/cli.rs b/src/cli.rs index fe15034..7e1e48c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,6 @@ -use crate::nix::{ConfigOption, ConfigSource, collect_nix_options}; +use crate::nix::{NixModules, build_nix_modules}; use clap::Parser; -use rnix::{Parse, Root}; -use std::{fs, path::PathBuf}; +use std::path::PathBuf; #[derive(Parser, Debug)] #[command( @@ -24,41 +23,7 @@ pub struct Cli { pub hf: PathBuf, } -pub struct NixModules { - pub system_modules: Vec, - pub home_modules: Vec, -} - pub fn get_modules() -> NixModules { let args: Cli = Cli::parse(); - - let system_file: String = fs::read_to_string(&args.sf).expect("Failed to read system file"); - let home_file: String = fs::read_to_string(&args.hf).expect("Failed to read home file"); - - let system_ast: Parse = get_ast(&system_file); - let home_ast: Parse = get_ast(&home_file); - - let system_modules: Vec = - collect_nix_options(&system_ast.syntax(), "", ConfigSource::System); - let home_modules: Vec = - collect_nix_options(&home_ast.syntax(), "", ConfigSource::Home); - - NixModules { - system_modules, - home_modules, - } -} - -fn get_ast(file: &str) -> Parse { - let ast: Parse = Root::parse(&file); - - if !ast.errors().is_empty() { - eprintln!("Błędy parsowania:"); - for error in ast.errors() { - eprintln!(" - {}", error); - } - panic!("Błąd z parsowaniem pliku .nix") - } - - ast + build_nix_modules(args.sf, args.hf) } diff --git a/src/main.rs b/src/main.rs index 7fb5ead..e43dd73 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use garandos_tui::{ app::{App, Event, handle_input_events}, - cli::{NixModules, get_modules}, - nix::ConfigSource, + cli::get_modules, + nix::{ConfigSource, NixModules}, }; use ratatui::{Terminal, prelude::CrosstermBackend}; use std::{ @@ -15,6 +15,10 @@ fn main() -> Result<()> { let mut terminal: Terminal> = ratatui::init(); let mut app: App = App { exit: false, + system_path: nix_modules.system_path, + home_path: nix_modules.home_path, + system_ast: nix_modules.system_ast, + home_ast: nix_modules.home_ast, system_modules: nix_modules.system_modules, home_modules: nix_modules.home_modules, current_file: ConfigSource::System, diff --git a/src/nix.rs b/src/nix.rs index f15d361..4c5ccb2 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -1,6 +1,16 @@ -use rnix::{NodeOrToken, Root, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize}; -use std::collections::HashMap; +use rnix::{NodeOrToken, Parse, Root, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize}; +use std::{collections::HashMap, fs, path::PathBuf}; +pub struct NixModules { + pub system_path: PathBuf, + pub system_ast: Parse, + pub system_modules: Vec, + pub home_path: PathBuf, + pub home_ast: Parse, + pub home_modules: Vec, +} + +#[derive(Clone)] pub struct ConfigOption { pub category: String, pub path: String, @@ -8,18 +18,43 @@ pub struct ConfigOption { pub source: ConfigSource, } -#[derive(PartialEq)] +#[derive(PartialEq, Clone)] pub enum ConfigSource { System, Home, } -impl Clone for ConfigSource { - fn clone(&self) -> Self { - match self { - ConfigSource::System => ConfigSource::System, - ConfigSource::Home => ConfigSource::Home, +pub fn parse_nix(src: &str) -> Parse { + let ast: Parse = Root::parse(src); + if !ast.errors().is_empty() { + eprintln!("Błędy parsowania:"); + for err in ast.errors() { + eprintln!(" - {}", err); } + panic!("Błąd parsowania pliku .nix"); + } + ast +} + +pub fn build_nix_modules(system_path: PathBuf, home_path: PathBuf) -> NixModules { + let system_src: String = fs::read_to_string(&system_path).expect("Failed to read system file"); + let home_src: String = fs::read_to_string(&home_path).expect("Failed to read home file"); + + let system_ast: Parse = parse_nix(&system_src); + let home_ast: Parse = parse_nix(&home_src); + + let system_modules: Vec = + collect_nix_options(&system_ast.syntax(), "", ConfigSource::System); + let home_modules: Vec = + collect_nix_options(&home_ast.syntax(), "", ConfigSource::Home); + + NixModules { + system_path, + system_ast, + system_modules, + home_path, + home_ast, + home_modules, } }