gostui-setup #1
159
src/app.rs
159
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 crossterm::event::{self, KeyCode, KeyEvent, KeyEventKind};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
DefaultTerminal, Frame,
|
DefaultTerminal, Frame,
|
||||||
@@ -6,15 +8,22 @@ use ratatui::{
|
|||||||
layout::{Constraint, Layout, Rect},
|
layout::{Constraint, Layout, Rect},
|
||||||
style::Stylize,
|
style::Stylize,
|
||||||
text::{Line, Span},
|
text::{Line, Span},
|
||||||
widgets::Widget,
|
widgets::{Paragraph, Widget},
|
||||||
};
|
};
|
||||||
|
use rnix::{Parse, Root, SyntaxNode};
|
||||||
use std::{
|
use std::{
|
||||||
|
fs,
|
||||||
io::Result,
|
io::Result,
|
||||||
|
path::PathBuf,
|
||||||
sync::mpsc::{self, Receiver},
|
sync::mpsc::{self, Receiver},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub exit: bool,
|
pub exit: bool,
|
||||||
|
pub system_path: PathBuf,
|
||||||
|
pub home_path: PathBuf,
|
||||||
|
pub system_ast: Parse<Root>,
|
||||||
|
pub home_ast: Parse<Root>,
|
||||||
pub system_modules: Vec<ConfigOption>,
|
pub system_modules: Vec<ConfigOption>,
|
||||||
pub home_modules: Vec<ConfigOption>,
|
pub home_modules: Vec<ConfigOption>,
|
||||||
pub current_file: ConfigSource,
|
pub current_file: ConfigSource,
|
||||||
@@ -24,7 +33,7 @@ pub struct App {
|
|||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Input(KeyEvent),
|
Input(KeyEvent),
|
||||||
EditFile,
|
// EditFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
@@ -37,7 +46,7 @@ impl App {
|
|||||||
match event {
|
match event {
|
||||||
Event::Input(key_event) => self.handle_key_event(key_event)?,
|
Event::Input(key_event) => self.handle_key_event(key_event)?,
|
||||||
// Event::EditFile => self.handle_file_edit_events()?,
|
// Event::EditFile => self.handle_file_edit_events()?,
|
||||||
_ => {}
|
// _ => {}
|
||||||
}
|
}
|
||||||
terminal.draw(|frame: &mut Frame<'_>| self.draw(frame))?;
|
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') {
|
if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Char('q') {
|
||||||
self.exit = true;
|
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ę
|
// ↑ - scroll w górę
|
||||||
else if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Up {
|
else if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Up {
|
||||||
self.selected_index = self.selected_index.saturating_sub(1);
|
self.selected_index = self.selected_index.saturating_sub(1);
|
||||||
@@ -64,21 +81,53 @@ impl App {
|
|||||||
}
|
}
|
||||||
// ENTER - zmień wartość boolen danej opcji
|
// ENTER - zmień wartość boolen danej opcji
|
||||||
else if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Enter {
|
else if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Enter {
|
||||||
}
|
let modules: &Vec<ConfigOption> = match self.current_file {
|
||||||
// 1 - zmień plik na ConfigSource::System (pomiń wykonywaniej, jeżeli już jest wybrany)
|
ConfigSource::System => &self.system_modules.clone(),
|
||||||
else if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Char('1') {
|
ConfigSource::Home => &self.home_modules.clone(),
|
||||||
if self.current_file != ConfigSource::System {
|
};
|
||||||
self.current_file = ConfigSource::System;
|
let node: &SyntaxNode = match self.current_file {
|
||||||
} else {
|
ConfigSource::System => &self.system_ast.syntax(),
|
||||||
self.current_file = ConfigSource::Home;
|
ConfigSource::Home => &self.home_ast.syntax(),
|
||||||
}
|
};
|
||||||
}
|
if let Some(module) = modules.get(self.selected_index) {
|
||||||
// 2 - zmień plik na ConfigSource::Home (pomiń wykonywaniej, jeżeli już jest wybrany)
|
let new_node: SyntaxNode = toggle_bool_at_path(node, module.path.as_str());
|
||||||
else if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Char('2') {
|
|
||||||
if self.current_file != ConfigSource::Home {
|
let new_ast: Parse<Root> = Root::parse(&new_node.to_string().as_str());
|
||||||
self.current_file = ConfigSource::Home;
|
|
||||||
} else {
|
let new_modules: Vec<ConfigOption> =
|
||||||
self.current_file = ConfigSource::System;
|
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,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
let vertical_layout: Layout = Layout::vertical([
|
let vertical_layout: Layout = Layout::vertical([
|
||||||
Constraint::Percentage(10),
|
Constraint::Percentage(5),
|
||||||
Constraint::Percentage(80),
|
Constraint::Percentage(90),
|
||||||
Constraint::Percentage(10),
|
Constraint::Percentage(5),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let [title_area, content_area, footer_area] = vertical_layout.areas(area);
|
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()),
|
let (system_file, home_file) = match self.current_file {
|
||||||
ConfigSource::Home => ("System".reset(), "(Home)".blue().bold()),
|
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()),
|
let title: Line<'_> = Line::from(vec![
|
||||||
Span::from(" * ".reset()),
|
Span::from("GarandOS TUI".bold().italic().yellow()),
|
||||||
system_file,
|
Span::from(" * ".reset()),
|
||||||
Span::from(" [1]".reset()),
|
system_file,
|
||||||
Span::from(" * ".reset()),
|
Span::from(" [1]".reset()),
|
||||||
home_file,
|
Span::from(" * ".reset()),
|
||||||
Span::from(" [2]".reset()),
|
home_file,
|
||||||
Span::from(" *".reset()),
|
Span::from(" [2]".reset()),
|
||||||
Span::from(" Quit".reset()),
|
Span::from(" *".reset()),
|
||||||
Span::from(" [q]".reset()),
|
Span::from(" Quit".reset()),
|
||||||
])
|
Span::from(" [q]".reset()),
|
||||||
.left_aligned();
|
])
|
||||||
|
.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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
41
src/cli.rs
41
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 clap::Parser;
|
||||||
use rnix::{Parse, Root};
|
use std::path::PathBuf;
|
||||||
use std::{fs, path::PathBuf};
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(
|
#[command(
|
||||||
@@ -24,41 +23,7 @@ pub struct Cli {
|
|||||||
pub hf: PathBuf,
|
pub hf: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NixModules {
|
|
||||||
pub system_modules: Vec<ConfigOption>,
|
|
||||||
pub home_modules: Vec<ConfigOption>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_modules() -> NixModules {
|
pub fn get_modules() -> NixModules {
|
||||||
let args: Cli = Cli::parse();
|
let args: Cli = Cli::parse();
|
||||||
|
build_nix_modules(args.sf, args.hf)
|
||||||
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<Root> = get_ast(&system_file);
|
|
||||||
let home_ast: Parse<Root> = get_ast(&home_file);
|
|
||||||
|
|
||||||
let system_modules: Vec<ConfigOption> =
|
|
||||||
collect_nix_options(&system_ast.syntax(), "", ConfigSource::System);
|
|
||||||
let home_modules: Vec<ConfigOption> =
|
|
||||||
collect_nix_options(&home_ast.syntax(), "", ConfigSource::Home);
|
|
||||||
|
|
||||||
NixModules {
|
|
||||||
system_modules,
|
|
||||||
home_modules,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_ast(file: &str) -> Parse<Root> {
|
|
||||||
let ast: Parse<Root> = 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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use garandos_tui::{
|
use garandos_tui::{
|
||||||
app::{App, Event, handle_input_events},
|
app::{App, Event, handle_input_events},
|
||||||
cli::{NixModules, get_modules},
|
cli::get_modules,
|
||||||
nix::ConfigSource,
|
nix::{ConfigSource, NixModules},
|
||||||
};
|
};
|
||||||
use ratatui::{Terminal, prelude::CrosstermBackend};
|
use ratatui::{Terminal, prelude::CrosstermBackend};
|
||||||
use std::{
|
use std::{
|
||||||
@@ -15,6 +15,10 @@ fn main() -> Result<()> {
|
|||||||
let mut terminal: Terminal<CrosstermBackend<Stdout>> = ratatui::init();
|
let mut terminal: Terminal<CrosstermBackend<Stdout>> = ratatui::init();
|
||||||
let mut app: App = App {
|
let mut app: App = App {
|
||||||
exit: false,
|
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,
|
system_modules: nix_modules.system_modules,
|
||||||
home_modules: nix_modules.home_modules,
|
home_modules: nix_modules.home_modules,
|
||||||
current_file: ConfigSource::System,
|
current_file: ConfigSource::System,
|
||||||
|
|||||||
51
src/nix.rs
51
src/nix.rs
@@ -1,6 +1,16 @@
|
|||||||
use rnix::{NodeOrToken, Root, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize};
|
use rnix::{NodeOrToken, Parse, Root, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize};
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, fs, path::PathBuf};
|
||||||
|
|
||||||
|
pub struct NixModules {
|
||||||
|
pub system_path: PathBuf,
|
||||||
|
pub system_ast: Parse<Root>,
|
||||||
|
pub system_modules: Vec<ConfigOption>,
|
||||||
|
pub home_path: PathBuf,
|
||||||
|
pub home_ast: Parse<Root>,
|
||||||
|
pub home_modules: Vec<ConfigOption>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct ConfigOption {
|
pub struct ConfigOption {
|
||||||
pub category: String,
|
pub category: String,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
@@ -8,18 +18,43 @@ pub struct ConfigOption {
|
|||||||
pub source: ConfigSource,
|
pub source: ConfigSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Clone)]
|
||||||
pub enum ConfigSource {
|
pub enum ConfigSource {
|
||||||
System,
|
System,
|
||||||
Home,
|
Home,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for ConfigSource {
|
pub fn parse_nix(src: &str) -> Parse<Root> {
|
||||||
fn clone(&self) -> Self {
|
let ast: Parse<Root> = Root::parse(src);
|
||||||
match self {
|
if !ast.errors().is_empty() {
|
||||||
ConfigSource::System => ConfigSource::System,
|
eprintln!("Błędy parsowania:");
|
||||||
ConfigSource::Home => ConfigSource::Home,
|
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<Root> = parse_nix(&system_src);
|
||||||
|
let home_ast: Parse<Root> = parse_nix(&home_src);
|
||||||
|
|
||||||
|
let system_modules: Vec<ConfigOption> =
|
||||||
|
collect_nix_options(&system_ast.syntax(), "", ConfigSource::System);
|
||||||
|
let home_modules: Vec<ConfigOption> =
|
||||||
|
collect_nix_options(&home_ast.syntax(), "", ConfigSource::Home);
|
||||||
|
|
||||||
|
NixModules {
|
||||||
|
system_path,
|
||||||
|
system_ast,
|
||||||
|
system_modules,
|
||||||
|
home_path,
|
||||||
|
home_ast,
|
||||||
|
home_modules,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user