From 71760bd6eded50f3ec7089fabcfcfd77a0a59e0f Mon Sep 17 00:00:00 2001 From: Lutsai Aleksandr Date: Tue, 31 Dec 2024 22:04:22 +0300 Subject: [PATCH] Improve info field output --- Cargo.toml | 11 +++- src/main.rs | 130 ++++++++--------------------------------- src/mountpoints.rs | 2 +- src/tui.rs | 141 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 175 insertions(+), 109 deletions(-) create mode 100644 src/tui.rs diff --git a/Cargo.toml b/Cargo.toml index ab8c86c..aca86cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,10 @@ name = "mmt" version = "0.1.0" edition = "2021" +authors = ["Lutsai Aleksandr "] +description = "Terminal User Interface disk mount manager for TUI file managers" +repository = "https://github.com/SL-RU/mmt" +license = "MIT" [dependencies] crossterm = { version = "0.28.1", features = ["event-stream"]} @@ -15,9 +19,12 @@ udisks2 = "0.2.0" pedantic = { level = "warn", priority = -1 } must_use_candidate = { level = "allow" } expect_used = { level = "allow" } -unwrap_used = { level = "warn" } +unwrap_used = { level = "allow" } panic = { level = "allow" } doc_markdown = { level = "allow" } [profile.release] -strip = true \ No newline at end of file +strip = true + +[package.metadata.aur] +depends = ["udisks2"] \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 870f805..fa16a26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,32 +1,23 @@ mod drives; mod mountpoints; - -use std::{io::stderr, sync::Arc, time::Duration}; +mod tui; use crossterm::{ - event::{Event, EventStream, KeyCode, KeyEventKind}, + event::{Event, EventStream, KeyEventKind}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; -use ratatui::{ - layout::{Alignment, Constraint, Layout}, - prelude::CrosstermBackend, - style::Color, - text::{Line, Text}, - widgets::{ - Block, BorderType, Padding, Paragraph, Row, StatefulWidget, Table, TableState, Widget, Wrap, - }, - Frame, Terminal, -}; +use ratatui::{prelude::CrosstermBackend, Terminal}; +use std::{io::stderr, sync::Arc, time::Duration}; use tokio::sync::Mutex; use tokio_stream::StreamExt; +use tui::{InputResult, Tui}; #[tokio::main] async fn main() -> udisks2::Result<()> { enable_raw_mode().unwrap(); execute!(stderr(), EnterAlternateScreen).unwrap(); let mut terminal = Terminal::new(CrosstermBackend::new(stderr())).unwrap(); - let mut ts = TableState::new(); let period = Duration::from_secs_f32(1.0 / 10.0); let mut interval = tokio::time::interval(period); @@ -42,107 +33,34 @@ async fn main() -> udisks2::Result<()> { } }); - let mut output = String::new(); - let mut last_status = String::new(); - + let mut output_change_path = String::new(); + let mut tui = Tui::default(); loop { - let drv = state.lock().await.clone(); - let mut selected: Option = None; + tui.drv = state.lock().await.to_vec(); + tui.selected = None; terminal - .draw(|f| draw(f, &mut ts, &drv, &mut selected, &last_status)) + .draw(|f| tui.draw(f)) .expect("failed to draw frame"); - tokio::select! { - _ = interval.tick() => { }, - Some(Ok(event)) = events.next() => { - if let Event::Key(key) = event { - if key.kind == KeyEventKind::Press { - match key.code { - KeyCode::Up | KeyCode::Char('k') => ts.select_previous(), - KeyCode::Down | KeyCode::Char('j') => ts.select_next(), - KeyCode::Char('m') => if let Some(b) = &selected { - last_status = format!("{:?}", drives::mount(b).await); - } - KeyCode::Char('u') => if let Some(b) = &selected { - last_status = format!("{:?}", drives::unmount(b).await); - } - KeyCode::Esc | KeyCode::Char('q') => break, - KeyCode::Enter => { - output = selected.unwrap().mount.unwrap(); - break; - } - _ => {} - } - } + let key = tokio::select! { + _ = interval.tick() => continue, + Some(Ok(Event::Key(key))) = events.next() => key, + }; + + if key.kind == KeyEventKind::Press { + match tui.input(key).await { + InputResult::None => continue, + InputResult::Quit => break, + InputResult::QuitChangeDirectory(p) => { + output_change_path = p; + break; } - }, + } } } disable_raw_mode().unwrap(); execute!(stderr(), LeaveAlternateScreen).unwrap(); - println!("{output}"); + println!("{output_change_path}"); Ok(()) } - -fn draw( - frame: &mut Frame, - state: &mut TableState, - drv: &[drives::Drive], - selected: &mut Option, - last_status: &str, -) { - let text = Text::raw("Hello World!"); - frame.render_widget(text, frame.area()); - - let layout = Layout::default() - .direction(ratatui::layout::Direction::Vertical) - .constraints(vec![Constraint::Fill(1), Constraint::Length(5)]) - .split(frame.area()); - - let block = Block::bordered() - .title(Line::styled("Mount", Color::White)) - .title_alignment(Alignment::Center) - .padding(Padding::symmetric(1, 1)) - .border_type(BorderType::Rounded) - .border_style(Color::Yellow); - block.clone().render(layout[0], frame.buffer_mut()); - - let rows: Vec = drv.iter().flat_map(|d| d.blocks.clone()).collect(); - - state - .selected() - .and_then(|n| rows.get(n).cloned()) - .clone_into(selected); - - let rows = rows.iter().map(|i| { - Row::new(vec![ - i.dev.clone(), - i.label.clone(), - i.mount.clone().unwrap_or_default(), - if i.mounted { - "M".to_owned() - } else { - "O".to_owned() - }, - ]) - }); - let widths = [ - Constraint::Ratio(1, 3), - Constraint::Ratio(1, 3), - Constraint::Ratio(1, 3), - Constraint::Length(3), - ]; - let table = Table::new(rows, widths) - .row_highlight_style(Color::Green) - .highlight_symbol(">"); - - StatefulWidget::render(table, block.inner(frame.area()), frame.buffer_mut(), state); - frame.render_widget( - Paragraph::new(format!( - "j - UP, k - DOWN, l - Goto mountpoint, m - Mount, u - Unmount, e - Eject\n{selected:?} {last_status:?}" - )) - .wrap(Wrap { trim: true }), - layout[1], - ); -} diff --git a/src/mountpoints.rs b/src/mountpoints.rs index aa53595..c71bbaa 100644 --- a/src/mountpoints.rs +++ b/src/mountpoints.rs @@ -29,7 +29,7 @@ impl MountPoint { Some(MountPoint { dev: parts .next() - .and_then(|d| if !d.starts_with('#') { Some(d) } else { None })? + .and_then(|d| if d.starts_with('#') { None } else { Some(d) })? .into(), path: Some(parts.next()?.to_string()), fs: parts.next()?.into(), diff --git a/src/tui.rs b/src/tui.rs new file mode 100644 index 0000000..e7d5676 --- /dev/null +++ b/src/tui.rs @@ -0,0 +1,141 @@ +use crate::drives; +use crossterm::event::{KeyCode, KeyEvent}; +use ratatui::{ + layout::{Alignment, Constraint, Layout}, + style::Color, + text::Line, + widgets::{ + Block, BorderType, Padding, Paragraph, Row, StatefulWidget, Table, TableState, Widget, Wrap, + }, + Frame, +}; + +pub enum InputResult { + None, + Quit, + QuitChangeDirectory(String), +} + +#[derive(Default)] +pub struct Tui { + ts: TableState, + pub drv: Vec, + pub selected: Option, + pub last_status: String, +} + +impl Tui { + pub async fn input(&mut self, key: KeyEvent) -> InputResult { + match key.code { + KeyCode::Up | KeyCode::Char('k') => { + self.ts.select_previous(); + InputResult::None + } + KeyCode::Down | KeyCode::Char('j') => { + self.ts.select_next(); + InputResult::None + } + KeyCode::Char('m') => { + if let Some(b) = &self.selected { + self.last_status = format!("{:?}", drives::mount(b).await); + } + InputResult::None + } + KeyCode::Char('u') => { + if let Some(b) = &self.selected { + self.last_status = format!("{:?}", drives::unmount(b).await); + } + InputResult::None + } + KeyCode::Esc | KeyCode::Char('q') => InputResult::Quit, + KeyCode::Enter => { + let output = self.selected.clone().unwrap().mount.unwrap(); + InputResult::QuitChangeDirectory(output) + } + _ => InputResult::None, + } + } + + pub fn draw(&mut self, frame: &mut Frame) { + let layout = Layout::default() + .direction(ratatui::layout::Direction::Vertical) + .constraints(vec![Constraint::Fill(1), Constraint::Length(5)]) + .split(frame.area()); + + let block = Block::bordered() + .title(Line::styled("Mount", Color::White)) + .title_alignment(Alignment::Center) + .padding(Padding::symmetric(1, 1)) + .border_type(BorderType::Rounded) + .border_style(Color::Yellow); + block.clone().render(layout[0], frame.buffer_mut()); + + let rows: Vec = self.drv.iter().flat_map(|d| d.blocks.clone()).collect(); + + if self.ts.selected().is_none() && !self.drv.is_empty() { + self.ts.select(Some(0)); + } + + self.ts + .selected() + .and_then(|n| rows.get(n).cloned()) + .clone_into(&mut self.selected); + + let rows = rows.iter().map(|i| { + Row::new(vec![ + i.dev.clone(), + i.label.clone(), + i.mount.clone().unwrap_or_default(), + if i.mounted { + "M".to_owned() + } else { + "O".to_owned() + }, + ]) + }); + let widths = [ + Constraint::Ratio(1, 3), + Constraint::Ratio(1, 3), + Constraint::Ratio(1, 3), + Constraint::Length(3), + ]; + let table = Table::new(rows, widths) + .row_highlight_style(Color::Green) + .highlight_symbol(">"); + + StatefulWidget::render( + table, + block.inner(frame.area()), + frame.buffer_mut(), + &mut self.ts, + ); + + let descr = match &self.selected { + Some(s) => { + let mnt_point = match &s.mount { + None => "", + Some(m) => m, + }; + let mounted = if s.mounted { + format!("mounted to {mnt_point:?}") + } else { + format!("not mounted {mnt_point:?}") + }; + + format!( + "dev: {:?} label: {:?} type: {:?} {mounted} ", + s.dev, s.label, s.fstype + ) + } + None => String::new(), + }; + + let info = format!( + "j - UP, k - DOWN, l - Goto mountpoint, m - Mount, u - Unmount, e - Eject\n{descr} {:?}", + self.last_status + ); + + let info = Paragraph::new(info).wrap(Wrap { trim: true }); + frame.render_widget(info, layout[1]); + } +}