From 01647d7b6bb9a4911ee9ab0fd1762869eda17fd3 Mon Sep 17 00:00:00 2001 From: Ivin Joel Abraham Date: Fri, 25 Apr 2025 19:45:27 +0530 Subject: [PATCH 1/3] refactor: introduce infrastructure layer External utilities are moved to a separate directory called `infrastructure`. This includes logging and terminal functions. Some of which were contained in a `utils.rs` file. The contents of that file are now neatly sorted into either `terminal.rs`, `logging.rs` or `macros.rs` Signed-off-by: Ivin Joel Abraham --- src/app.rs | 16 +- src/app/cover_renderer.rs | 6 +- src/app/logging/log_on_error.rs | 27 --- src/app/patch_renderer.rs | 8 +- src/cli.rs | 8 +- src/handler.rs | 4 +- src/handler/details_actions.rs | 17 +- src/infrastructure/errors.rs | 74 +++++++ .../logging/garbage_collector.rs | 0 .../logging/mod.rs} | 9 +- src/infrastructure/mod.rs | 3 + src/infrastructure/terminal.rs | 42 ++++ src/macros.rs | 77 +++++++ src/main.rs | 26 ++- src/ui/edit_config.rs | 3 +- src/utils.rs | 189 ------------------ 16 files changed, 244 insertions(+), 265 deletions(-) delete mode 100644 src/app/logging/log_on_error.rs create mode 100644 src/infrastructure/errors.rs rename src/{app => infrastructure}/logging/garbage_collector.rs (100%) rename src/{app/logging.rs => infrastructure/logging/mod.rs} (99%) create mode 100644 src/infrastructure/mod.rs create mode 100644 src/infrastructure/terminal.rs create mode 100644 src/macros.rs delete mode 100644 src/utils.rs diff --git a/src/app.rs b/src/app.rs index a5faef22..76fed375 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,6 @@ +use crate::infrastructure::logging::Logger; use crate::{ + infrastructure::logging, log_on_error, ui::popup::{info_popup::InfoPopUp, PopUp}, }; @@ -6,7 +8,6 @@ use ansi_to_tui::IntoText; use color_eyre::eyre::bail; use config::Config; use cover_renderer::render_cover; -use logging::Logger; use patch_hub::lore::{ lore_api_client::BlockingLoreAPIClient, lore_session, @@ -24,11 +25,8 @@ use screens::{ }; use std::collections::{HashMap, HashSet}; -use crate::utils; - pub mod config; pub mod cover_renderer; -pub mod logging; pub mod patch_renderer; pub mod screens; @@ -391,30 +389,30 @@ impl App { pub fn check_external_deps(&self) -> bool { let mut app_can_run = true; - if !utils::binary_exists("b4") { + if which::which("b4").is_err() { Logger::error("b4 is not installed, patchsets cannot be downloaded"); app_can_run = false; } - if !utils::binary_exists("git") { + if which::which("git").is_err() { Logger::warn("git is not installed, send-email won't work"); } match self.config.patch_renderer() { PatchRenderer::Bat => { - if !utils::binary_exists("bat") { + if which::which("bat").is_err() { Logger::warn("bat is not installed, patch rendering will fallback to default"); } } PatchRenderer::Delta => { - if !utils::binary_exists("delta") { + if which::which("delta").is_err() { Logger::warn( "delta is not installed, patch rendering will fallback to default", ); } } PatchRenderer::DiffSoFancy => { - if !utils::binary_exists("diff-so-fancy") { + if which::which("diff-so-fancy").is_err() { Logger::warn( "diff-so-fancy is not installed, patch rendering will fallback to default", ); diff --git a/src/app/cover_renderer.rs b/src/app/cover_renderer.rs index fc30f266..9541b8ab 100644 --- a/src/app/cover_renderer.rs +++ b/src/app/cover_renderer.rs @@ -1,12 +1,12 @@ +use serde::{Deserialize, Serialize}; + use std::{ fmt::Display, io::Write, process::{Command, Stdio}, }; -use serde::{Deserialize, Serialize}; - -use super::logging::Logger; +use crate::infrastructure::logging::Logger; #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default)] pub enum CoverRenderer { diff --git a/src/app/logging/log_on_error.rs b/src/app/logging/log_on_error.rs deleted file mode 100644 index b156c33d..00000000 --- a/src/app/logging/log_on_error.rs +++ /dev/null @@ -1,27 +0,0 @@ -#[macro_export] -macro_rules! log_on_error { - ($result:expr) => { - log_on_error!($crate::app::logging::LogLevel::Error, $result) - }; - ($level:expr, $result:expr) => { - match $result { - Ok(_) => $result, - Err(ref error) => { - let error_message = - format!("Error executing {:?}: {}", stringify!($result), &error); - match $level { - $crate::app::logging::LogLevel::Info => { - Logger::info(error_message); - } - $crate::app::logging::LogLevel::Warning => { - Logger::warn(error_message); - } - $crate::app::logging::LogLevel::Error => { - Logger::error(error_message); - } - } - $result - } - } - }; -} diff --git a/src/app/patch_renderer.rs b/src/app/patch_renderer.rs index 4ff8bf92..a3b69205 100644 --- a/src/app/patch_renderer.rs +++ b/src/app/patch_renderer.rs @@ -1,13 +1,13 @@ +use color_eyre::eyre::eyre; +use serde::{Deserialize, Serialize}; + use std::{ fmt::Display, io::Write, process::{Command, Stdio}, }; -use color_eyre::eyre::eyre; -use serde::{Deserialize, Serialize}; - -use super::logging::Logger; +use crate::infrastructure::logging::Logger; #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default)] pub enum PatchRenderer { diff --git a/src/cli.rs b/src/cli.rs index 5ed52dad..621ff15f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,10 +1,10 @@ -use std::ops::ControlFlow; - use clap::Parser; use color_eyre::eyre::eyre; use ratatui::{prelude::Backend, Terminal}; -use crate::{app::config::Config, utils}; +use std::ops::ControlFlow; + +use crate::{app::config::Config, infrastructure::terminal::restore}; #[derive(Debug, Parser)] #[command(version, about)] @@ -25,7 +25,7 @@ impl Cli { ) -> ControlFlow, Terminal> { if self.show_configs { drop(terminal); - if let Err(err) = utils::restore() { + if let Err(err) = restore() { return ControlFlow::Break(Err(eyre!(err))); } match serde_json::to_string_pretty(&config) { diff --git a/src/handler.rs b/src/handler.rs index ef003201..b1e452d5 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -10,9 +10,7 @@ use std::{ }; use crate::{ - app::{logging::Logger, screens::CurrentScreen, App}, - loading_screen, - ui::draw_ui, + app::screens::CurrentScreen, infrastructure::logging::Logger, loading_screen, ui::draw_ui, App, }; use bookmarked::handle_bookmarked_patchsets; diff --git a/src/handler/details_actions.rs b/src/handler/details_actions.rs index b7ca73a5..26a072b2 100644 --- a/src/handler/details_actions.rs +++ b/src/handler/details_actions.rs @@ -1,14 +1,15 @@ +use ratatui::{ + backend::Backend, + crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}, + Terminal, +}; + use std::time::Duration; use crate::{ app::{screens::CurrentScreen, App}, + infrastructure::terminal::{setup_user_io, teardown_user_io}, ui::popup::{help::HelpPopUpBuilder, review_trailers::ReviewTrailersPopUp, PopUp}, - utils, -}; -use ratatui::{ - backend::Backend, - crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}, - Terminal, }; use super::wait_key_press; @@ -107,7 +108,7 @@ pub fn handle_patchset_details( } KeyCode::Enter => { if patchset_details_and_actions.actions_require_user_io() { - utils::setup_user_io(terminal)?; + setup_user_io(terminal)?; app.consolidate_patchset_actions()?; println!("\nPress ENTER continue..."); loop { @@ -117,7 +118,7 @@ pub fn handle_patchset_details( } } } - utils::teardown_user_io(terminal)?; + teardown_user_io(terminal)?; } else { app.consolidate_patchset_actions()?; } diff --git a/src/infrastructure/errors.rs b/src/infrastructure/errors.rs new file mode 100644 index 00000000..02c75795 --- /dev/null +++ b/src/infrastructure/errors.rs @@ -0,0 +1,74 @@ +use std::panic; + +use super::{logging::Logger, terminal::restore}; + +/// This replaces the standard color_eyre panic and error hooks with hooks that +/// restore the terminal before printing the panic or error. +pub fn install_hooks() -> color_eyre::Result<()> { + let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default().into_hooks(); + + // convert from a color_eyre PanicHook to a standard panic hook + let panic_hook = panic_hook.into_panic_hook(); + panic::set_hook(Box::new(move |panic_info| { + restore().unwrap(); + Logger::flush(); + panic_hook(panic_info); + })); + + // convert from a color_eyre EyreHook to a eyre ErrorHook + let eyre_hook = eyre_hook.into_eyre_hook(); + color_eyre::eyre::set_hook(Box::new( + move |error: &(dyn std::error::Error + 'static)| { + restore().unwrap(); + eyre_hook(error) + }, + ))?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::sync::Once; + + use super::*; + + static INIT: Once = Once::new(); + + // Tests can be run in parallel, we don't want to override previously installed hooks + fn setup() { + INIT.call_once(|| { + install_hooks().expect("Failed to install hooks"); + }) + } + + #[test] + fn test_install_hooks() { + setup(); + } + + #[test] + fn test_error_hook_works() { + setup(); + + let result: color_eyre::Result<()> = Err(color_eyre::eyre::eyre!("Test error")); + + // We can't directly test the hook's formatting, but we can verify + // that handling an error doesn't cause unexpected panics + match result { + Ok(_) => panic!("Expected an error"), + Err(e) => { + let _ = format!("{:?}", e); + } + } + } + + #[test] + fn test_panic_hook() { + setup(); + + let result = std::panic::catch_unwind(|| std::panic!("Test panic")); + + assert!(result.is_err()); + } +} diff --git a/src/app/logging/garbage_collector.rs b/src/infrastructure/logging/garbage_collector.rs similarity index 100% rename from src/app/logging/garbage_collector.rs rename to src/infrastructure/logging/garbage_collector.rs diff --git a/src/app/logging.rs b/src/infrastructure/logging/mod.rs similarity index 99% rename from src/app/logging.rs rename to src/infrastructure/logging/mod.rs index 1e6ec8fe..c4c60bd3 100644 --- a/src/app/logging.rs +++ b/src/infrastructure/logging/mod.rs @@ -1,16 +1,15 @@ +pub mod garbage_collector; + +use chrono::Local; + use std::{ fmt::Display, fs::{self, File, OpenOptions}, io::Write, }; -use chrono::Local; - use crate::app::config::Config; -pub mod garbage_collector; -pub mod log_on_error; - const LATEST_LOG_FILENAME: &str = "latest.log"; static mut LOG_BUFFER: Logger = Logger { diff --git a/src/infrastructure/mod.rs b/src/infrastructure/mod.rs new file mode 100644 index 00000000..a41d82b7 --- /dev/null +++ b/src/infrastructure/mod.rs @@ -0,0 +1,3 @@ +pub mod errors; +pub mod logging; +pub mod terminal; diff --git a/src/infrastructure/terminal.rs b/src/infrastructure/terminal.rs new file mode 100644 index 00000000..c8bf6db9 --- /dev/null +++ b/src/infrastructure/terminal.rs @@ -0,0 +1,42 @@ +use ratatui::{ + crossterm::{ + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, + }, + layout::Position, + prelude::{Backend, CrosstermBackend}, + Terminal, +}; + +use std::io::{self, stdout, Stdout}; + +/// A type alias for the terminal type used in this application +pub type Tui = Terminal>; + +/// Initialize the terminal +pub fn init() -> io::Result { + execute!(stdout(), EnterAlternateScreen)?; + enable_raw_mode()?; + Terminal::new(CrosstermBackend::new(stdout())) +} + +/// Restore the terminal to its original state +pub fn restore() -> io::Result<()> { + execute!(stdout(), LeaveAlternateScreen)?; + disable_raw_mode()?; + Ok(()) +} + +pub fn setup_user_io(terminal: &mut Terminal) -> color_eyre::Result<()> { + terminal.clear()?; + terminal.set_cursor_position(Position::new(0, 0))?; + terminal.show_cursor()?; + disable_raw_mode()?; + Ok(()) +} + +pub fn teardown_user_io(terminal: &mut Terminal) -> color_eyre::Result<()> { + enable_raw_mode()?; + terminal.clear()?; + Ok(()) +} diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 00000000..9664121a --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,77 @@ +#[macro_export] +/// Macro that encapsulates a piece of code that takes long to run and displays a loading screen while it runs. +/// +/// This macro takes two arguments: the terminal and the title of the loading screen (anything that implements `Display`). +/// After a `=>` token, you can pass the code that takes long to run. +/// +/// When the execution finishes, the macro will return the terminal. +/// +/// Important to notice that the code block will run in the same scope as the rest of the macro. +/// Be aware that in Rust, when using `?` or `return` inside a closure, they apply to the outer function, +/// not the closure itself. This can lead to unexpected behavior if you expect the closure to handle +/// errors or return values independently of the enclosing function. +/// +/// # Example +/// ```rust norun +/// terminal = loading_screen! { terminal, "Loading stuff" => { +/// // code that takes long to run +/// }}; +/// ``` +macro_rules! loading_screen { + { $terminal:expr, $title:expr => $inst:expr} => { + { + let loading = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true)); + let loading_clone = std::sync::Arc::clone(&loading); + let mut terminal = $terminal; + + let handle = std::thread::spawn(move || { + while loading_clone.load(std::sync::atomic::Ordering::Relaxed) { + terminal = $crate::ui::loading_screen::render(terminal, $title); + std::thread::sleep(std::time::Duration::from_millis(200)); + } + + terminal + }); + + // we have to sleep so the loading thread completes at least one render + std::thread::sleep(std::time::Duration::from_millis(200)); + let inst_result = $inst; + + loading.store(false, std::sync::atomic::Ordering::Relaxed); + + let terminal = handle.join().unwrap(); + + inst_result?; + + terminal + } + }; +} + +#[macro_export] +macro_rules! log_on_error { + ($result:expr) => { + log_on_error!($crate::infrastructure::logging::LogLevel::Error, $result) + }; + ($level:expr, $result:expr) => { + match $result { + Ok(_) => $result, + Err(ref error) => { + let error_message = + format!("Error executing {:?}: {}", stringify!($result), &error); + match $level { + $crate::infrastructure::logging::LogLevel::Info => { + Logger::info(error_message); + } + $crate::infrastructure::logging::LogLevel::Warning => { + Logger::warn(error_message); + } + $crate::infrastructure::logging::LogLevel::Error => { + Logger::error(error_message); + } + } + $result + } + } + }; +} diff --git a/src/main.rs b/src/main.rs index 34612f6a..99b5547f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,26 @@ +mod app; +mod cli; +mod handler; +mod infrastructure; +mod macros; +mod ui; + use std::ops::ControlFlow; -use crate::app::App; -use app::{config::Config, logging::Logger}; +use app::{config::Config, App}; use clap::Parser; use cli::Cli; use handler::run_app; - -mod app; -mod cli; -mod handler; -mod ui; -mod utils; +use infrastructure::{ + logging::Logger, + terminal::{init, restore}, +}; fn main() -> color_eyre::Result<()> { let args = Cli::parse(); - utils::install_hooks()?; - let mut terminal = utils::init()?; + infrastructure::errors::install_hooks()?; + let mut terminal = init()?; let config = Config::build(); config.create_dirs(); @@ -29,7 +33,7 @@ fn main() -> color_eyre::Result<()> { let app = App::new(config)?; run_app(terminal, app)?; - utils::restore()?; + restore()?; Logger::info("patch-hub finished"); Logger::flush(); diff --git a/src/ui/edit_config.rs b/src/ui/edit_config.rs index 88b5a57d..ef62a8e1 100644 --- a/src/ui/edit_config.rs +++ b/src/ui/edit_config.rs @@ -6,8 +6,7 @@ use ratatui::{ Frame, }; -use crate::app::logging::Logger; -use crate::app::App; +use crate::{app::App, infrastructure::logging::Logger}; pub fn render_main(f: &mut Frame, app: &App, chunk: Rect) { let edit_config = app.edit_config.as_ref().unwrap(); diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index 8dcb5a58..00000000 --- a/src/utils.rs +++ /dev/null @@ -1,189 +0,0 @@ -use color_eyre::{config::HookBuilder, eyre}; -use ratatui::layout::Position; -use std::io::{self, stdout, Stdout}; -use std::panic; - -use ratatui::{ - backend::CrosstermBackend, - crossterm::{ - execute, - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, - }, - prelude::Backend, - Terminal, -}; - -use crate::app::logging::Logger; - -/// A type alias for the terminal type used in this application -pub type Tui = Terminal>; - -/// Initialize the terminal -pub fn init() -> io::Result { - execute!(stdout(), EnterAlternateScreen)?; - enable_raw_mode()?; - Terminal::new(CrosstermBackend::new(stdout())) -} - -/// Restore the terminal to its original state -pub fn restore() -> io::Result<()> { - execute!(stdout(), LeaveAlternateScreen)?; - disable_raw_mode()?; - Ok(()) -} - -/// This replaces the standard color_eyre panic and error hooks with hooks that -/// restore the terminal before printing the panic or error. -/// -/// # Tests -/// -/// [tests::test_error_hook] -/// [tests::test_panic_hook] -pub fn install_hooks() -> color_eyre::Result<()> { - let (panic_hook, eyre_hook) = HookBuilder::default().into_hooks(); - - // convert from a color_eyre PanicHook to a standard panic hook - let panic_hook = panic_hook.into_panic_hook(); - panic::set_hook(Box::new(move |panic_info| { - restore().unwrap(); - Logger::flush(); - panic_hook(panic_info); - })); - - // convert from a color_eyre EyreHook to a eyre ErrorHook - let eyre_hook = eyre_hook.into_eyre_hook(); - eyre::set_hook(Box::new( - move |error: &(dyn std::error::Error + 'static)| { - restore().unwrap(); - eyre_hook(error) - }, - ))?; - - Ok(()) -} - -pub fn setup_user_io(terminal: &mut Terminal) -> color_eyre::Result<()> { - terminal.clear()?; - terminal.set_cursor_position(Position::new(0, 0))?; - terminal.show_cursor()?; - disable_raw_mode()?; - Ok(()) -} - -pub fn teardown_user_io(terminal: &mut Terminal) -> color_eyre::Result<()> { - enable_raw_mode()?; - terminal.clear()?; - Ok(()) -} - -#[inline] -/// Simply calls `which` to check if a binary exists -/// -/// # Tests -/// -/// [tests::test_binary_exists] -pub fn binary_exists(binary: &str) -> bool { - which::which(binary).is_ok() -} - -#[macro_export] -/// Macro that encapsulates a piece of code that takes long to run and displays a loading screen while it runs. -/// -/// This macro takes two arguments: the terminal and the title of the loading screen (anything that implements `Display`). -/// After a `=>` token, you can pass the code that takes long to run. -/// -/// When the execution finishes, the macro will return the terminal. -/// -/// Important to notice that the code block will run in the same scope as the rest of the macro. -/// Be aware that in Rust, when using `?` or `return` inside a closure, they apply to the outer function, -/// not the closure itself. This can lead to unexpected behavior if you expect the closure to handle -/// errors or return values independently of the enclosing function. -/// -/// # Example -/// ```rust norun -/// terminal = loading_screen! { terminal, "Loading stuff" => { -/// // code that takes long to run -/// }}; -/// ``` -macro_rules! loading_screen { - { $terminal:expr, $title:expr => $inst:expr} => { - { - let loading = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true)); - let loading_clone = std::sync::Arc::clone(&loading); - let mut terminal = $terminal; - - let handle = std::thread::spawn(move || { - while loading_clone.load(std::sync::atomic::Ordering::Relaxed) { - terminal = $crate::ui::loading_screen::render(terminal, $title); - std::thread::sleep(std::time::Duration::from_millis(200)); - } - - terminal - }); - - // we have to sleep so the loading thread completes at least one render - std::thread::sleep(std::time::Duration::from_millis(200)); - let inst_result = $inst; - - loading.store(false, std::sync::atomic::Ordering::Relaxed); - - let terminal = handle.join().unwrap(); - - inst_result?; - - terminal - } - }; -} - -#[cfg(test)] -mod tests { - use std::sync::Once; - - use super::*; - - static INIT: Once = Once::new(); - - // Tests can be run in parallel, we don't want to override previously installed hooks - fn setup() { - INIT.call_once(|| { - install_hooks().expect("Failed to install hooks"); - }) - } - - #[test] - /// Tests [binary_exists] - fn test_binary_exists() { - // cargo should always exist since we are running the tests with `cargo test` - assert!(super::binary_exists("cargo")); - // there is no way this binary exists - assert!(!super::binary_exists("there_is_no_way_this_binary_exists")); - } - - #[test] - /// Tests [install_hooks] - fn test_error_hook() { - setup(); - - let result: color_eyre::Result<()> = Err(eyre::eyre!("Test error")); - - // We can't directly test the hook's formatting, but we can verify - // that handling an error doesn't cause unexpected panics - match result { - Ok(_) => panic!("Expected an error"), - Err(e) => { - let _ = format!("{:?}", e); - } - } - } - - #[test] - /// Tests [install_hooks] - fn test_panic_hook() { - setup(); - - let result = std::panic::catch_unwind(|| std::panic!("Test panic")); - - assert!(result.is_err()); - } -} From f472cc9888b71e5800a89c9f896926f7c1f82438 Mon Sep 17 00:00:00 2001 From: Ivin Joel Abraham Date: Fri, 25 Apr 2025 20:01:43 +0530 Subject: [PATCH 2/3] fix!: remove `lore` from being a public module `lore` was originally intended to serve as a library. However, in #128, it was decided to package it as a separate project instead of sideloading alongside the `patch-hub` crate. Additionally, `Patch::new` was detected as unused code and was removed. Signed-off-by: Ivin Joel Abraham --- src/app.rs | 10 ++-- src/app/screens/bookmarked.rs | 2 +- src/app/screens/details_actions.rs | 17 ++++-- src/app/screens/latest.rs | 3 +- src/app/screens/mail_list.rs | 3 +- src/lib.rs | 1 - src/lore/lore_session.rs | 27 ++++----- src/{lore.rs => lore/mod.rs} | 0 src/lore/patch.rs | 19 ------- src/lore/patch/tests.rs | 88 ++++++++++++++++++++---------- src/main.rs | 1 + src/ui/latest.rs | 4 +- src/ui/popup/review_trailers.rs | 7 +-- 13 files changed, 101 insertions(+), 81 deletions(-) delete mode 100644 src/lib.rs rename src/{lore.rs => lore/mod.rs} (100%) diff --git a/src/app.rs b/src/app.rs index 76fed375..fe0d231d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,9 @@ use crate::infrastructure::logging::Logger; +use crate::lore::{ + lore_api_client::BlockingLoreAPIClient, + lore_session, + patch::{Author, Patch}, +}; use crate::{ infrastructure::logging, log_on_error, @@ -8,11 +13,6 @@ use ansi_to_tui::IntoText; use color_eyre::eyre::bail; use config::Config; use cover_renderer::render_cover; -use patch_hub::lore::{ - lore_api_client::BlockingLoreAPIClient, - lore_session, - patch::{Author, Patch}, -}; use patch_renderer::{render_patch_preview, PatchRenderer}; use ratatui::text::Text; use screens::{ diff --git a/src/app/screens/bookmarked.rs b/src/app/screens/bookmarked.rs index bdfdf0d2..ec075efa 100644 --- a/src/app/screens/bookmarked.rs +++ b/src/app/screens/bookmarked.rs @@ -1,4 +1,4 @@ -use patch_hub::lore::patch::Patch; +use crate::lore::patch::Patch; pub struct BookmarkedPatchsets { pub bookmarked_patchsets: Vec, diff --git a/src/app/screens/details_actions.rs b/src/app/screens/details_actions.rs index c5cd40ef..076f167d 100644 --- a/src/app/screens/details_actions.rs +++ b/src/app/screens/details_actions.rs @@ -1,16 +1,23 @@ -use crate::app::config::{Config, KernelTree}; - -use super::CurrentScreen; -use ::patch_hub::lore::{lore_api_client::BlockingLoreAPIClient, lore_session, patch::Patch}; use color_eyre::eyre::{bail, eyre}; -use patch_hub::lore::patch::Author; use ratatui::text::Text; + use std::{ collections::{HashMap, HashSet}, path::Path, process::Command, }; +use crate::{ + app::config::{Config, KernelTree}, + lore::{ + lore_api_client::BlockingLoreAPIClient, + lore_session, + patch::{Author, Patch}, + }, +}; + +use super::CurrentScreen; + pub struct DetailsActions { pub representative_patch: Patch, /// Raw patches as plain text files diff --git a/src/app/screens/latest.rs b/src/app/screens/latest.rs index 61d8c9b4..ce7d448c 100644 --- a/src/app/screens/latest.rs +++ b/src/app/screens/latest.rs @@ -1,6 +1,7 @@ use color_eyre::eyre::bail; use derive_getters::Getters; -use patch_hub::lore::{ + +use crate::lore::{ lore_api_client::{BlockingLoreAPIClient, ClientError}, lore_session::{LoreSession, LoreSessionError}, patch::Patch, diff --git a/src/app/screens/mail_list.rs b/src/app/screens/mail_list.rs index 52d49f7f..e4537b1b 100644 --- a/src/app/screens/mail_list.rs +++ b/src/app/screens/mail_list.rs @@ -1,5 +1,6 @@ use color_eyre::eyre::bail; -use patch_hub::lore::{ + +use crate::lore::{ lore_api_client::BlockingLoreAPIClient, lore_session, mailing_list::MailingList, }; diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index b48300f6..00000000 --- a/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod lore; diff --git a/src/lore/lore_session.rs b/src/lore/lore_session.rs index 57d68a6b..5dd2b0e6 100644 --- a/src/lore/lore_session.rs +++ b/src/lore/lore_session.rs @@ -1,22 +1,23 @@ -use crate::lore::lore_api_client::{ - AvailableListsRequest, ClientError, PatchFeedRequest, PatchHTMLRequest, -}; -use crate::lore::mailing_list::MailingList; -use crate::lore::patch::{Patch, PatchFeed, PatchRegex}; use derive_getters::Getters; use regex::Regex; use serde_xml_rs::from_str; -use std::collections::{HashMap, HashSet}; -use std::io::{BufRead, BufReader}; -use std::mem::swap; -use std::path::Path; -use std::process::{Command, Stdio}; -use std::sync::LazyLock; +use thiserror::Error; + use std::{ + collections::{HashMap, HashSet}, fs::{self, File}, - io, + io::{self, BufRead, BufReader}, + mem::swap, + path::Path, + process::{Command, Stdio}, + sync::LazyLock, +}; + +use crate::lore::{ + lore_api_client::{AvailableListsRequest, ClientError, PatchFeedRequest, PatchHTMLRequest}, + mailing_list::MailingList, + patch::{Patch, PatchFeed, PatchRegex}, }; -use thiserror::Error; #[cfg(test)] mod tests; diff --git a/src/lore.rs b/src/lore/mod.rs similarity index 100% rename from src/lore.rs rename to src/lore/mod.rs diff --git a/src/lore/patch.rs b/src/lore/patch.rs index cda53497..50975297 100644 --- a/src/lore/patch.rs +++ b/src/lore/patch.rs @@ -62,25 +62,6 @@ fn default_total_in_series() -> usize { } impl Patch { - pub fn new( - title: String, - author: Author, - message_id: MessageID, - in_reply_to: Option, - updated: String, - ) -> Patch { - Patch { - title, - author, - version: 1, - number_in_series: 1, - total_in_series: 1, - message_id, - in_reply_to, - updated, - } - } - pub fn version(&self) -> usize { self.version } diff --git a/src/lore/patch/tests.rs b/src/lore/patch/tests.rs index 7f2fed4b..f3ee2383 100644 --- a/src/lore/patch/tests.rs +++ b/src/lore/patch/tests.rs @@ -3,18 +3,27 @@ use serde_xml_rs::from_str; #[test] fn can_deserialize_patch_without_in_reply_to() { - let expected_patch: Patch = Patch::new( - "[PATCH 0/42] hitchhiker/guide: Complete Collection".to_string(), - Author { + let expected_patch: Patch = { + let title = "[PATCH 0/42] hitchhiker/guide: Complete Collection".to_string(); + let author = Author { name: "Foo Bar".to_string(), email: "foo@bar.foo.bar".to_string(), - }, - MessageID { + }; + let message_id = MessageID { href: "http://lore.kernel.org/some-list/1234-1-foo@bar.foo.bar".to_string(), - }, - None, - "2024-07-06T19:15:48Z".to_string(), - ); + }; + let updated = "2024-07-06T19:15:48Z".to_string(); + Patch { + title, + author, + version: 1, + number_in_series: 1, + total_in_series: 1, + message_id, + in_reply_to: None, + updated, + } + }; let serialized_patch: &str = r#" @@ -40,20 +49,30 @@ fn can_deserialize_patch_without_in_reply_to() { #[test] fn can_deserialize_patch_with_in_reply_to() { - let expected_patch: Patch = Patch::new( - "[PATCH 3/42] hitchhiker/guide: Life, the Universe and Everything".to_string(), - Author { + let expected_patch: Patch = { + let title = "[PATCH 3/42] hitchhiker/guide: Life, the Universe and Everything".to_string(); + let author = Author { name: "Foo Bar".to_string(), email: "foo@bar.foo.bar".to_string(), - }, - MessageID { + }; + let message_id = MessageID { href: "http://lore.kernel.org/some-list/1234-2-foo@bar.foo.bar".to_string(), - }, - Some(MessageID { + }; + let in_reply_to = Some(MessageID { href: "http://lore.kernel.org/some-list/1234-1-foo@bar.foo.bar".to_string(), - }), - "2024-07-06T19:16:53Z".to_string(), - ); + }); + let updated = "2024-07-06T19:16:53Z".to_string(); + Patch { + title, + author, + version: 1, + number_in_series: 1, + total_in_series: 1, + message_id, + in_reply_to, + updated, + } + }; let serialized_patch: &str = r#" @@ -83,20 +102,31 @@ fn can_deserialize_patch_with_in_reply_to() { #[test] fn test_update_patch_metadata() { let patch_regex: PatchRegex = PatchRegex::new(); - let mut patch: Patch = Patch::new( - "[RESEND][v7 PATCH 3/42] hitchhiker/guide: Life, the Universe and Everything".to_string(), - Author { + let mut patch: Patch = { + let title = "[RESEND][v7 PATCH 3/42] hitchhiker/guide: Life, the Universe and Everything" + .to_string(); + let author = Author { name: "Foo Bar".to_string(), email: "foo@bar.foo.bar".to_string(), - }, - MessageID { + }; + let message_id = MessageID { href: "http://lore.kernel.org/some-list/1234-2-foo@bar.foo.bar".to_string(), - }, - Some(MessageID { + }; + let in_reply_to = Some(MessageID { href: "http://lore.kernel.org/some-list/1234-1-foo@bar.foo.bar".to_string(), - }), - "2024-07-06T19:16:53Z".to_string(), - ); + }); + let updated = "2024-07-06T19:16:53Z".to_string(); + Patch { + title, + author, + version: 1, + number_in_series: 1, + total_in_series: 1, + message_id, + in_reply_to, + updated, + } + }; patch.update_patch_metadata(&patch_regex); diff --git a/src/main.rs b/src/main.rs index 99b5547f..5d9b0c8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod app; mod cli; mod handler; mod infrastructure; +mod lore; mod macros; mod ui; diff --git a/src/ui/latest.rs b/src/ui/latest.rs index 4288543c..e6776366 100644 --- a/src/ui/latest.rs +++ b/src/ui/latest.rs @@ -1,5 +1,3 @@ -use crate::app::App; -use patch_hub::lore::patch::Patch; use ratatui::{ layout::Rect, style::{Color, Modifier, Style}, @@ -8,6 +6,8 @@ use ratatui::{ Frame, }; +use crate::{app::App, lore::patch::Patch}; + pub fn render_main(f: &mut Frame, app: &App, chunk: Rect) { let page_number = app.latest_patchsets.as_ref().unwrap().page_number(); let patchset_index = app.latest_patchsets.as_ref().unwrap().patchset_index(); diff --git a/src/ui/popup/review_trailers.rs b/src/ui/popup/review_trailers.rs index 9fa48598..f626bcc4 100644 --- a/src/ui/popup/review_trailers.rs +++ b/src/ui/popup/review_trailers.rs @@ -1,6 +1,3 @@ -use std::collections::HashSet; - -use patch_hub::lore::patch::Author; use ratatui::{ crossterm::event::KeyCode, layout::Alignment, @@ -9,7 +6,9 @@ use ratatui::{ widgets::{Clear, Paragraph}, }; -use crate::app::screens::details_actions::DetailsActions; +use std::collections::HashSet; + +use crate::{app::screens::details_actions::DetailsActions, lore::patch::Author}; use super::PopUp; From 20922842c5e9db6ab54817c5142f9a49db8c19e8 Mon Sep 17 00:00:00 2001 From: Ivin Joel Abraham Date: Fri, 25 Apr 2025 20:06:50 +0530 Subject: [PATCH 3/3] refactor: use mod.rs to clean up directory structure Using `mod.rs` allows for a cleaner directory as we can avoid having a file for each module in the root directory. This makes it easier to identify the important files and represents the compartmentalization of the code better. Additionally, imports (of modules, external crates and std create) in files now follow the recommended order. Signed-off-by: Ivin Joel Abraham --- src/app/config.rs | 1 + src/{app.rs => app/mod.rs} | 36 ++++++++++++++++-------------- src/app/screens/edit_config.rs | 5 +++-- src/handler/bookmarked.rs | 11 ++++----- src/handler/edit_config.rs | 3 ++- src/handler/latest.rs | 11 ++++----- src/handler/mail_list.rs | 11 ++++----- src/{handler.rs => handler/mod.rs} | 26 ++++++++++++--------- src/infrastructure/mod.rs | 2 ++ src/lore/lore_api_client.rs | 4 ++-- src/lore/lore_session/tests.rs | 7 +++--- src/lore/patch.rs | 4 ++-- src/lore/patch/tests.rs | 3 ++- src/ui/bookmarked.rs | 4 ++-- src/ui/loading_screen.rs | 4 ++-- src/{ui.rs => ui/mod.rs} | 19 ++++++++-------- src/ui/navigation_bar.rs | 7 +++--- src/ui/popup.rs | 8 +++---- src/ui/popup/help.rs | 1 + 19 files changed, 93 insertions(+), 74 deletions(-) rename src/{app.rs => app/mod.rs} (98%) rename src/{handler.rs => handler/mod.rs} (95%) rename src/{ui.rs => ui/mod.rs} (99%) diff --git a/src/app/config.rs b/src/app/config.rs index 6b3dfc01..465cac1b 100644 --- a/src/app/config.rs +++ b/src/app/config.rs @@ -1,6 +1,7 @@ use derive_getters::Getters; use proc_macros::serde_individual_default; use serde::{Deserialize, Serialize}; + use std::{ collections::{HashMap, HashSet}, env, diff --git a/src/app.rs b/src/app/mod.rs similarity index 98% rename from src/app.rs rename to src/app/mod.rs index fe0d231d..9ea1783b 100644 --- a/src/app.rs +++ b/src/app/mod.rs @@ -1,20 +1,28 @@ -use crate::infrastructure::logging::Logger; -use crate::lore::{ - lore_api_client::BlockingLoreAPIClient, - lore_session, - patch::{Author, Patch}, -}; +pub mod config; +mod cover_renderer; +mod patch_renderer; +pub mod screens; + +use ansi_to_tui::IntoText; +use color_eyre::eyre::bail; +use ratatui::text::Text; + +use std::collections::{HashMap, HashSet}; + use crate::{ - infrastructure::logging, + infrastructure::{garbage_collector, logging::Logger}, log_on_error, + lore::{ + lore_api_client::BlockingLoreAPIClient, + lore_session, + patch::{Author, Patch}, + }, ui::popup::{info_popup::InfoPopUp, PopUp}, }; -use ansi_to_tui::IntoText; -use color_eyre::eyre::bail; + use config::Config; use cover_renderer::render_cover; use patch_renderer::{render_patch_preview, PatchRenderer}; -use ratatui::text::Text; use screens::{ bookmarked::BookmarkedPatchsets, details_actions::{DetailsActions, PatchsetAction}, @@ -23,12 +31,6 @@ use screens::{ mail_list::MailingListSelection, CurrentScreen, }; -use std::collections::{HashMap, HashSet}; - -pub mod config; -pub mod cover_renderer; -pub mod patch_renderer; -pub mod screens; /// Type that represents the overall state of the application. It can be viewed /// as the **Model** component of `patch-hub`. @@ -80,7 +82,7 @@ impl App { // Initialize the logger before the app starts Logger::init_log_file(&config)?; Logger::info("patch-hub started"); - logging::garbage_collector::collect_garbage(&config); + garbage_collector::collect_garbage(&config); Ok(App { current_screen: CurrentScreen::MailingListSelection, diff --git a/src/app/screens/edit_config.rs b/src/app/screens/edit_config.rs index e39e0fde..8714f1ca 100644 --- a/src/app/screens/edit_config.rs +++ b/src/app/screens/edit_config.rs @@ -1,8 +1,9 @@ +use color_eyre::eyre::bail; +use derive_getters::Getters; + use std::{collections::HashMap, fmt::Display, path::Path}; use crate::app::config::Config; -use color_eyre::eyre::bail; -use derive_getters::Getters; #[derive(Debug, Getters)] pub struct EditConfig { diff --git a/src/handler/bookmarked.rs b/src/handler/bookmarked.rs index ab3ec4b8..3bca0ccc 100644 --- a/src/handler/bookmarked.rs +++ b/src/handler/bookmarked.rs @@ -1,3 +1,9 @@ +use ratatui::{ + crossterm::event::{KeyCode, KeyEvent}, + prelude::Backend, + Terminal, +}; + use std::ops::ControlFlow; use crate::{ @@ -5,11 +11,6 @@ use crate::{ loading_screen, ui::popup::{help::HelpPopUpBuilder, PopUp}, }; -use ratatui::{ - crossterm::event::{KeyCode, KeyEvent}, - prelude::Backend, - Terminal, -}; pub fn handle_bookmarked_patchsets( app: &mut App, diff --git a/src/handler/edit_config.rs b/src/handler/edit_config.rs index 405f6d5d..f0f2f158 100644 --- a/src/handler/edit_config.rs +++ b/src/handler/edit_config.rs @@ -1,8 +1,9 @@ +use ratatui::crossterm::event::{KeyCode, KeyEvent}; + use crate::{ app::{screens::CurrentScreen, App}, ui::popup::{help::HelpPopUpBuilder, PopUp}, }; -use ratatui::crossterm::event::{KeyCode, KeyEvent}; pub fn handle_edit_config(app: &mut App, key: KeyEvent) -> color_eyre::Result<()> { if let Some(edit_config_state) = app.edit_config.as_mut() { diff --git a/src/handler/latest.rs b/src/handler/latest.rs index 6a3b1e66..1d0493e1 100644 --- a/src/handler/latest.rs +++ b/src/handler/latest.rs @@ -1,3 +1,9 @@ +use ratatui::{ + crossterm::event::{KeyCode, KeyEvent}, + prelude::Backend, + Terminal, +}; + use std::ops::ControlFlow; use crate::{ @@ -5,11 +11,6 @@ use crate::{ loading_screen, ui::popup::{help::HelpPopUpBuilder, PopUp}, }; -use ratatui::{ - crossterm::event::{KeyCode, KeyEvent}, - prelude::Backend, - Terminal, -}; pub fn handle_latest_patchsets( app: &mut App, diff --git a/src/handler/mail_list.rs b/src/handler/mail_list.rs index d8797a09..d5a0ffc4 100644 --- a/src/handler/mail_list.rs +++ b/src/handler/mail_list.rs @@ -1,3 +1,9 @@ +use ratatui::{ + crossterm::event::{KeyCode, KeyEvent}, + prelude::Backend, + Terminal, +}; + use std::ops::ControlFlow; use crate::{ @@ -5,11 +11,6 @@ use crate::{ loading_screen, ui::popup::{help::HelpPopUpBuilder, PopUp}, }; -use ratatui::{ - crossterm::event::{KeyCode, KeyEvent}, - prelude::Backend, - Terminal, -}; pub fn handle_mailing_list_selection( app: &mut App, diff --git a/src/handler.rs b/src/handler/mod.rs similarity index 95% rename from src/handler.rs rename to src/handler/mod.rs index b1e452d5..6dc2a59c 100644 --- a/src/handler.rs +++ b/src/handler/mod.rs @@ -1,8 +1,14 @@ -pub mod bookmarked; -pub mod details_actions; -pub mod edit_config; -pub mod latest; -pub mod mail_list; +mod bookmarked; +mod details_actions; +mod edit_config; +mod latest; +mod mail_list; + +use ratatui::{ + crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}, + prelude::Backend, + Terminal, +}; use std::{ ops::ControlFlow, @@ -10,7 +16,10 @@ use std::{ }; use crate::{ - app::screens::CurrentScreen, infrastructure::logging::Logger, loading_screen, ui::draw_ui, App, + app::{screens::CurrentScreen, App}, + infrastructure::logging::Logger, + loading_screen, + ui::draw_ui, }; use bookmarked::handle_bookmarked_patchsets; @@ -19,11 +28,6 @@ use details_actions::handle_patchset_details; use edit_config::handle_edit_config; use latest::handle_latest_patchsets; use mail_list::handle_mailing_list_selection; -use ratatui::{ - crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}, - prelude::Backend, - Terminal, -}; fn key_handling( mut terminal: Terminal, diff --git a/src/infrastructure/mod.rs b/src/infrastructure/mod.rs index a41d82b7..f05edfcb 100644 --- a/src/infrastructure/mod.rs +++ b/src/infrastructure/mod.rs @@ -1,3 +1,5 @@ pub mod errors; pub mod logging; pub mod terminal; + +pub use logging::garbage_collector; diff --git a/src/lore/lore_api_client.rs b/src/lore/lore_api_client.rs index 5b9336a3..6295bd47 100644 --- a/src/lore/lore_api_client.rs +++ b/src/lore/lore_api_client.rs @@ -1,10 +1,10 @@ -use std::time::Duration; - use mockall::automock; use thiserror::Error; use ureq::tls::TlsConfig; use ureq::Agent; +use std::time::Duration; + #[cfg(test)] mod tests; diff --git a/src/lore/lore_session/tests.rs b/src/lore/lore_session/tests.rs index 456392cb..de777f23 100644 --- a/src/lore/lore_session/tests.rs +++ b/src/lore/lore_session/tests.rs @@ -1,10 +1,11 @@ +use mockall::mock; + use io::Read; +use std::fs; -use super::*; use crate::lore::patch::Author; -use mockall::mock; -use std::fs; +use super::*; mock! { BlockingLoreAPIClient {} diff --git a/src/lore/patch.rs b/src/lore/patch.rs index 50975297..c8e1e925 100644 --- a/src/lore/patch.rs +++ b/src/lore/patch.rs @@ -1,9 +1,9 @@ -use std::fmt::Display; - use derive_getters::Getters; use regex::Regex; use serde::{Deserialize, Serialize}; +use std::fmt::Display; + #[cfg(test)] mod tests; diff --git a/src/lore/patch/tests.rs b/src/lore/patch/tests.rs index f3ee2383..ed28215f 100644 --- a/src/lore/patch/tests.rs +++ b/src/lore/patch/tests.rs @@ -1,6 +1,7 @@ -use super::*; use serde_xml_rs::from_str; +use super::*; + #[test] fn can_deserialize_patch_without_in_reply_to() { let expected_patch: Patch = { diff --git a/src/ui/bookmarked.rs b/src/ui/bookmarked.rs index 8a5193c2..a6a9ccfe 100644 --- a/src/ui/bookmarked.rs +++ b/src/ui/bookmarked.rs @@ -1,5 +1,3 @@ -use crate::app; -use app::screens::bookmarked::BookmarkedPatchsets; use ratatui::{ layout::Rect, style::{Color, Modifier, Style}, @@ -8,6 +6,8 @@ use ratatui::{ Frame, }; +use crate::app::screens::bookmarked::BookmarkedPatchsets; + pub fn render_main(f: &mut Frame, bookmarked_patchsets: &BookmarkedPatchsets, chunk: Rect) { let patchset_index = bookmarked_patchsets.patchset_index; let mut list_items = Vec::::new(); diff --git a/src/ui/loading_screen.rs b/src/ui/loading_screen.rs index 46e26cf8..99343b4c 100644 --- a/src/ui/loading_screen.rs +++ b/src/ui/loading_screen.rs @@ -1,5 +1,3 @@ -use std::fmt::Display; - use ratatui::{ prelude::Backend, style::{Color, Style}, @@ -8,6 +6,8 @@ use ratatui::{ Frame, Terminal, }; +use std::fmt::Display; + use super::centered_rect; const SPINNER: [char; 8] = [ diff --git a/src/ui.rs b/src/ui/mod.rs similarity index 99% rename from src/ui.rs rename to src/ui/mod.rs index a01d4c68..ed528754 100644 --- a/src/ui.rs +++ b/src/ui/mod.rs @@ -1,12 +1,3 @@ -use crate::app::{screens::CurrentScreen, App}; -use ratatui::{ - layout::{Alignment, Constraint, Direction, Layout, Rect}, - style::{Color, Style, Stylize}, - text::Text, - widgets::{Block, Borders, Clear, Paragraph}, - Frame, -}; - mod bookmarked; mod details_actions; mod edit_config; @@ -16,6 +7,16 @@ mod mail_list; mod navigation_bar; pub mod popup; +use ratatui::{ + layout::{Alignment, Constraint, Direction, Layout, Rect}, + style::{Color, Style, Stylize}, + text::Text, + widgets::{Block, Borders, Clear, Paragraph}, + Frame, +}; + +use crate::app::{screens::CurrentScreen, App}; + pub fn draw_ui(f: &mut Frame, app: &App) { // Clear the whole screen for sanitizing reasons f.render_widget(Clear, f.area()); diff --git a/src/ui/navigation_bar.rs b/src/ui/navigation_bar.rs index b601bf97..47dd6a10 100644 --- a/src/ui/navigation_bar.rs +++ b/src/ui/navigation_bar.rs @@ -1,6 +1,3 @@ -use super::{bookmarked, details_actions, edit_config, latest, mail_list}; -use crate::app::{self, App}; -use app::screens::CurrentScreen; use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, text::Line, @@ -8,6 +5,10 @@ use ratatui::{ Frame, }; +use crate::app::{screens::CurrentScreen, App}; + +use super::{bookmarked, details_actions, edit_config, latest, mail_list}; + pub fn render(f: &mut Frame, app: &App, chunk: Rect) { let mode_footer_text = match app.current_screen { CurrentScreen::MailingListSelection => mail_list::mode_footer_text(app), diff --git a/src/ui/popup.rs b/src/ui/popup.rs index 55928346..f2496123 100644 --- a/src/ui/popup.rs +++ b/src/ui/popup.rs @@ -1,11 +1,11 @@ -use std::fmt::Debug; - -use ratatui::{crossterm::event::KeyEvent, layout::Rect, Frame}; - pub mod help; pub mod info_popup; pub mod review_trailers; +use ratatui::{crossterm::event::KeyEvent, layout::Rect, Frame}; + +use std::fmt::Debug; + /// A trait that represents a popup that can be rendered on top of a screen pub trait PopUp: Debug { /// Returns the dimensions of the popup in percentage of the screen diff --git a/src/ui/popup/help.rs b/src/ui/popup/help.rs index fae6d037..5c70fcfa 100644 --- a/src/ui/popup/help.rs +++ b/src/ui/popup/help.rs @@ -5,6 +5,7 @@ use ratatui::{ text::Line, widgets::{Clear, Paragraph}, }; + use std::fmt::Display; use super::PopUp;