From 256aa5b8bd395441a43ca30a33d6a4421cf78f50 Mon Sep 17 00:00:00 2001 From: Erica Marigold Date: Thu, 14 Aug 2025 10:24:50 +0100 Subject: [PATCH] style: introduce new clippy config and apply Also moves to nightly toolchain, mainly for specific clippy features. --- build.rs | 13 +- rust-toolchain | 1 + rustfmt.toml | 12 ++ src/app.rs | 76 ++++++----- src/atproto/mod.rs | 64 ++++++---- src/cli.rs | 2 +- src/components.rs | 37 ++++-- src/components/cat.rs | 11 +- src/components/content.rs | 211 +++++++++++++++++-------------- src/components/selection_list.rs | 30 +++-- src/components/tabs.rs | 48 ++++--- src/config.rs | 75 ++++++----- src/errors.rs | 5 +- src/keycode.rs | 21 ++- src/landing.rs | 6 +- src/logging.rs | 18 ++- src/main.rs | 19 ++- src/ssh.rs | 54 ++++---- src/tui/backend.rs | 33 ++--- src/tui/mod.rs | 67 +++++----- src/tui/status.rs | 6 +- 21 files changed, 465 insertions(+), 344 deletions(-) create mode 100644 rust-toolchain create mode 100644 rustfmt.toml diff --git a/build.rs b/build.rs index 0325129..a2320fb 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,5 @@ -use std::{env, path::PathBuf}; +use std::env; +use std::path::PathBuf; use anyhow::Result; use ssh_key::{rand_core, Algorithm, EcdsaCurve, LineEnding, PrivateKey}; @@ -11,12 +12,7 @@ const ATPROTO_CLIENT_DIR: &str = "src/atproto"; const SSH_KEY_ALGOS: &[(&str, Algorithm)] = &[ ("rsa.pem", Algorithm::Rsa { hash: None }), ("ed25519.pem", Algorithm::Ed25519), - ( - "ecdsa.pem", - Algorithm::Ecdsa { - curve: EcdsaCurve::NistP256, - }, - ), + ("ecdsa.pem", Algorithm::Ecdsa { curve: EcdsaCurve::NistP256 }), ]; fn main() -> Result<()> { @@ -36,7 +32,8 @@ fn main() -> Result<()> { continue; } - let key = PrivateKey::random(&mut rng, algo.to_owned()).map_err(anyhow::Error::from)?; + let key = PrivateKey::random(&mut rng, algo.to_owned()) + .map_err(anyhow::Error::from)?; key.write_openssh_file(&path, LineEnding::default())?; } diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..1cf9323 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly-2025-07-30 \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..6deb119 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,12 @@ +edition = "2021" +use_small_heuristics = "Max" +max_width = 80 +newline_style = "Unix" + +indent_style = "Block" +hard_tabs = false + +format_strings = true +brace_style = "PreferSameLine" + +imports_granularity = "Module" diff --git a/src/app.rs b/src/app.rs index 183ecc7..c3cd669 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,28 +1,23 @@ -use std::sync::{atomic::AtomicUsize, Arc}; +use std::sync::atomic::AtomicUsize; +use std::sync::Arc; use color_eyre::{eyre, Result}; use crossterm::event::{KeyCode, KeyEvent}; -use ratatui::{ - layout::{Alignment, Constraint, Direction, Layout, Rect}, - style::{Color, Modifier, Style}, - text::{Line, Span}, - widgets::{Block, Borders, Clear, Paragraph, Wrap}, -}; +use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; +use ratatui::style::{Color, Modifier, Style}; +use ratatui::text::{Line, Span}; +use ratatui::widgets::{Block, Borders, Clear, Paragraph, Wrap}; use serde::{Deserialize, Serialize}; -use tokio::{ - sync::{mpsc, Mutex, RwLock}, - task::block_in_place, -}; +use tokio::sync::{mpsc, Mutex, RwLock}; +use tokio::task::block_in_place; use tokio_util::sync::CancellationToken; use tracing::{debug, info}; -use crate::{ - action::Action, - components::*, - config::Config, - keycode::KeyCodeExt, - tui::{Event, Terminal, Tui}, -}; +use crate::action::Action; +use crate::components::*; +use crate::config::Config; +use crate::keycode::KeyCodeExt; +use crate::tui::{Event, Terminal, Tui}; pub struct App { config: Config, @@ -49,7 +44,9 @@ pub struct App { selection_list: Arc>, } -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive( + Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, +)] pub enum Mode { #[default] Home, @@ -121,8 +118,7 @@ impl App { // Force the dimensions to be validated before rendering anything by sending a `Resize` event let term_size = tui.terminal.try_lock()?.size()?; - tui.event_tx - .send(Event::Resize(term_size.width, term_size.height))?; + tui.event_tx.send(Event::Resize(term_size.width, term_size.height))?; // Blocking initialization logic for tui and components block_in_place(|| { @@ -256,7 +252,8 @@ impl App { Action::Resume => self.should_suspend = false, Action::ClearScreen => tui.terminal.try_lock()?.clear()?, Action::Resize(w, h) => { - self.needs_resize = w < Self::MIN_TUI_DIMS.0 || h < Self::MIN_TUI_DIMS.1; + self.needs_resize = + w < Self::MIN_TUI_DIMS.0 || h < Self::MIN_TUI_DIMS.1; self.resize(tui, w, h)?; } Action::Render => self.render(tui)?, @@ -264,10 +261,14 @@ impl App { } // Update each component - if let Some(action) = self.tabs.try_lock()?.update(action.clone())? { + if let Some(action) = + self.tabs.try_lock()?.update(action.clone())? + { self.action_tx.send(action)?; } - if let Some(action) = self.content.try_lock()?.update(action.clone())? { + if let Some(action) = + self.content.try_lock()?.update(action.clone())? + { self.action_tx.send(action)?; } if let Some(action) = self.cat.try_lock()?.update(action.clone())? { @@ -275,7 +276,9 @@ impl App { } #[cfg(feature = "blog")] - if let Some(action) = self.selection_list.try_lock()?.update(action.clone())? { + if let Some(action) = + self.selection_list.try_lock()?.update(action.clone())? + { self.action_tx.send(action)?; } } @@ -342,15 +345,15 @@ impl App { term.try_draw(|frame| { let chunks = Layout::default() .direction(Direction::Vertical) - .constraints([Constraint::Length(3), Constraint::Min(0)].as_ref()) + .constraints( + [Constraint::Length(3), Constraint::Min(0)].as_ref(), + ) .split(frame.area()); // Render the domain name text let title = Paragraph::new(Line::from(Span::styled( "devcomp.xyz ", - Style::default() - .fg(Color::White) - .add_modifier(Modifier::BOLD), + Style::default().fg(Color::White).add_modifier(Modifier::BOLD), ))); frame.render_widget( @@ -364,10 +367,8 @@ impl App { ); // Render the tabs - let mut tabs = self - .tabs - .try_lock() - .map_err(std::io::Error::other)?; + let mut tabs = + self.tabs.try_lock().map_err(std::io::Error::other)?; tabs.draw( frame, @@ -423,9 +424,14 @@ impl App { // If blog feature is not enabled, render a placeholder content_rect.height = 1; let placeholder = Paragraph::new( - "Blog feature is disabled. Enable the `blog` feature to view this tab.", + "Blog feature is disabled. Enable the `blog` feature \ + to view this tab.", ) - .style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)); + .style( + Style::default() + .fg(Color::Red) + .add_modifier(Modifier::BOLD), + ); frame.render_widget(placeholder, content_rect); } diff --git a/src/atproto/mod.rs b/src/atproto/mod.rs index 5c72fc2..457efc4 100644 --- a/src/atproto/mod.rs +++ b/src/atproto/mod.rs @@ -5,21 +5,18 @@ pub mod com; pub mod blog { use std::str::FromStr as _; + use atrium_api::agent::atp_agent::store::MemorySessionStore; + use atrium_api::agent::atp_agent::CredentialSession; + use atrium_api::agent::Agent; + use atrium_api::com::atproto::repo::list_records; use atrium_api::com::atproto::server::create_session::OutputData as SessionOutputData; - use atrium_api::{ - agent::{ - atp_agent::{store::MemorySessionStore, CredentialSession}, - Agent, - }, - com::atproto::repo::list_records, - types::{ - string::{AtIdentifier, Handle}, - Collection as _, Object, Unknown, - }, - }; - use atrium_common::store::{memory::MemoryStore, Store}; + use atrium_api::types::string::{AtIdentifier, Handle}; + use atrium_api::types::{Collection as _, Object, Unknown}; + use atrium_common::store::memory::MemoryStore; + use atrium_common::store::Store; use atrium_xrpc_client::reqwest::ReqwestClient; - use color_eyre::{eyre::eyre, Result}; + use color_eyre::eyre::eyre; + use color_eyre::Result; use ipld_core::ipld::Ipld; use lazy_static::lazy_static; use tokio::time::{Duration, Instant}; @@ -31,20 +28,30 @@ pub mod blog { lazy_static! { static ref POSTS_CACHE_STORE: MemoryStore = MemoryStore::default(); - static ref AGENT: Agent>, ReqwestClient>> = - Agent::new(CredentialSession::new( - ReqwestClient::new("https://bsky.social"), - MemorySessionStore::default(), - )); + static ref AGENT: Agent< + CredentialSession< + MemoryStore<(), Object>, + ReqwestClient, + >, + > = Agent::new(CredentialSession::new( + ReqwestClient::new("https://bsky.social"), + MemorySessionStore::default(), + )); } #[instrument(level = "debug")] - pub async fn get_all_posts() -> Result> { + pub async fn get_all_posts() -> Result> + { let mut i = 0; let mut posts = Vec::new(); - while let Some((cache_creation_time, post)) = POSTS_CACHE_STORE.get(&i).await? { + while let Some((cache_creation_time, post)) = + POSTS_CACHE_STORE.get(&i).await? + { if cache_creation_time.elapsed() > CACHE_INVALIDATION_PERIOD { - tracing::info!("Cache for post #{} is stale, fetching new posts", i); + tracing::info!( + "Cache for post #{} is stale, fetching new posts", + i + ); POSTS_CACHE_STORE.clear().await?; return fetch_posts_into_cache().await; } @@ -55,7 +62,9 @@ pub mod blog { } if posts.is_empty() { - tracing::info!("No blog posts found in cache, fetching from ATProto"); + tracing::info!( + "No blog posts found in cache, fetching from ATProto" + ); return fetch_posts_into_cache().await; } @@ -63,7 +72,8 @@ pub mod blog { } #[instrument(level = "trace")] - async fn fetch_posts_into_cache() -> Result> { + async fn fetch_posts_into_cache( + ) -> Result> { let records = &AGENT .api .com @@ -90,7 +100,9 @@ pub mod blog { .map(|elem| { if let Unknown::Object(btree_map) = &elem.data.value { let ser = serde_json::to_string(&btree_map)?; - let des = serde_json::from_str::(&ser)?; + let des = serde_json::from_str::< + com::whtwnd::blog::entry::Record, + >(&ser)?; return Ok(des); } @@ -100,9 +112,7 @@ pub mod blog { .collect::>>()?; for (i, post) in posts.iter().enumerate() { - POSTS_CACHE_STORE - .set(i, (Instant::now(), post.clone())) - .await?; + POSTS_CACHE_STORE.set(i, (Instant::now(), post.clone())).await?; } Ok(posts) diff --git a/src/cli.rs b/src/cli.rs index 2e96de4..86e8e3e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -22,7 +22,7 @@ pub struct Cli { pub ssh_port: u16, /// The port to start the web server on #[arg(short = 'p', long, value_name = "PORT", default_value_t = 80)] - pub web_port: u16 + pub web_port: u16, } const VERSION_MESSAGE: &str = concat!( diff --git a/src/components.rs b/src/components.rs index 46b19dd..4a7739e 100644 --- a/src/components.rs +++ b/src/components.rs @@ -1,28 +1,28 @@ use color_eyre::Result; use crossterm::event::{KeyEvent, MouseEvent}; -use ratatui::{ - layout::{Rect, Size}, - Frame, -}; +use ratatui::layout::{Rect, Size}; +use ratatui::Frame; use tokio::sync::mpsc::UnboundedSender; -use crate::{action::Action, config::Config, tui::Event}; +use crate::action::Action; +use crate::config::Config; +use crate::tui::Event; // // Component re-exports // -mod tabs; -mod content; mod cat; +mod content; #[cfg(feature = "blog")] mod selection_list; +mod tabs; -pub use tabs::*; -pub use content::*; pub use cat::*; +pub use content::*; #[cfg(feature = "blog")] pub use selection_list::*; +pub use tabs::*; /// `Component` is a trait that represents a visual and interactive element of the user interface. /// @@ -38,7 +38,10 @@ pub trait Component: Send { /// # Returns /// /// * `Result<()>` - An Ok result or an error. - fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { + fn register_action_handler( + &mut self, + tx: UnboundedSender, + ) -> Result<()> { let _ = tx; // to appease clippy Ok(()) } @@ -77,10 +80,15 @@ pub trait Component: Send { /// # Returns /// /// * `Result>` - An action to be processed or none. - fn handle_events(&mut self, event: Option) -> Result> { + fn handle_events( + &mut self, + event: Option, + ) -> Result> { let action = match event { Some(Event::Key(key_event)) => self.handle_key_event(key_event)?, - Some(Event::Mouse(mouse_event)) => self.handle_mouse_event(mouse_event)?, + Some(Event::Mouse(mouse_event)) => { + self.handle_mouse_event(mouse_event)? + } _ => None, }; Ok(action) @@ -107,7 +115,10 @@ pub trait Component: Send { /// # Returns /// /// * `Result>` - An action to be processed or none. - fn handle_mouse_event(&mut self, mouse: MouseEvent) -> Result> { + fn handle_mouse_event( + &mut self, + mouse: MouseEvent, + ) -> Result> { let _ = mouse; // to appease clippy Ok(None) } diff --git a/src/components/cat.rs b/src/components/cat.rs index 73959e5..7f728b1 100644 --- a/src/components/cat.rs +++ b/src/components/cat.rs @@ -1,10 +1,12 @@ use color_eyre::Result; use indoc::indoc; -use ratatui::{prelude::*, widgets::*}; +use ratatui::prelude::*; +use ratatui::widgets::*; use tokio::sync::mpsc::UnboundedSender; use super::Component; -use crate::{action::Action, config::Config}; +use crate::action::Action; +use crate::config::Config; const CAT_ASCII_ART: &str = indoc! {r#" |\__/,| (`\ @@ -26,7 +28,10 @@ impl Cat { } impl Component for Cat { - fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { + fn register_action_handler( + &mut self, + tx: UnboundedSender, + ) -> Result<()> { self.command_tx = Some(tx); Ok(()) } diff --git a/src/components/content.rs b/src/components/content.rs index bfa0165..983f080 100644 --- a/src/components/content.rs +++ b/src/components/content.rs @@ -1,15 +1,16 @@ -use std::sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, -}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; -use color_eyre::{eyre::eyre, Result}; +use color_eyre::eyre::eyre; +use color_eyre::Result; use figlet_rs::FIGfont; -use ratatui::{prelude::*, widgets::*}; +use ratatui::prelude::*; +use ratatui::widgets::*; use tokio::sync::mpsc::UnboundedSender; use super::Component; -use crate::{action::Action, config::Config}; +use crate::action::Action; +use crate::config::Config; #[derive(Default)] pub struct Content { @@ -20,19 +21,17 @@ pub struct Content { impl Content { pub fn new(selected_tab: Arc) -> Self { - Self { - selected_tab, - ..Default::default() - } + Self { selected_tab, ..Default::default() } } /// Generate the content for the "About" tab fn about_content(&self, area: Rect) -> Result>> { - let greetings_header = FIGfont::from_content(include_str!("../../assets/drpepper.flf")) - .map_err(|err| eyre!(err))? - .convert("hiya!") - .ok_or(eyre!("Failed to create figlet header for about page"))? - .to_string(); + let greetings_header = + FIGfont::from_content(include_str!("../../assets/drpepper.flf")) + .map_err(|err| eyre!(err))? + .convert("hiya!") + .ok_or(eyre!("Failed to create figlet header for about page"))? + .to_string(); let lines: Vec = greetings_header .trim_end_matches('\n') @@ -49,11 +48,18 @@ impl Content { Span::from(" "), Span::from(line.clone()), Span::from(" I'm Erica ("), - Span::styled("she/they", Style::default().add_modifier(Modifier::ITALIC)), - Span::from("), and I make scalable systems or something. IDFK."), + Span::styled( + "she/they", + Style::default().add_modifier(Modifier::ITALIC), + ), + Span::from( + "), and I make scalable systems or something. \ + IDFK.", + ), ]); } - Line::raw(format!(" {}", line)).style(Style::default().add_modifier(Modifier::BOLD)) + Line::raw(format!(" {}", line)) + .style(Style::default().add_modifier(Modifier::BOLD)) }) .collect::>>(); @@ -61,7 +67,9 @@ impl Content { Line::from(""), Line::from(vec![ Span::from(" "), - Span::from("I specialize in systems programming, primarily in "), + Span::from( + "I specialize in systems programming, primarily in ", + ), Span::styled( "Rust 🦀", Style::default() @@ -78,19 +86,30 @@ impl Content { Span::from("."), ]), Line::from(""), - Line::from(" I am an avid believer of open-source software, and contribute to a few projects such as:"), + Line::from( + " I am an avid believer of open-source software, and \ + contribute to a few projects such as:", + ), ]); let projects = vec![ - (Style::default() - .fg(Color::LightMagenta) - .add_modifier(Modifier::BOLD), "lune-org/lune: A standalone Luau runtime"), - (Style::default() - .fg(Color::Blue) - .add_modifier(Modifier::BOLD), "DiscordLuau/discord-luau: A Luau library for creating Discord bots, powered by Lune"), - (Style::default() - .fg(Color::Yellow) - .add_modifier(Modifier::BOLD), "pesde-pkg/pesde: A package manager for the Luau programming language, supporting multiple runtimes including Roblox and Lune"), + ( + Style::default() + .fg(Color::LightMagenta) + .add_modifier(Modifier::BOLD), + "lune-org/lune: A standalone Luau runtime", + ), + ( + Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD), + "DiscordLuau/discord-luau: A Luau library for creating \ + Discord bots, powered by Lune", + ), + ( + Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD), + "pesde-pkg/pesde: A package manager for the Luau programming \ + language, supporting multiple runtimes including Roblox and \ + Lune", + ), ]; for (style, project) in projects { @@ -106,55 +125,69 @@ impl Content { let bullet = " • "; let indent = " "; - let first_line = if project.len() > area.width as usize - bullet.len() { - let split_point = project - .char_indices() - .take_while(|(i, _)| *i < area.width as usize - bullet.len()) - .last() - .map(|(i, _)| i) - .unwrap_or(project.len()); - let (first, rest) = project.split_at(split_point); - content.push(Line::from(vec![ - Span::from(bullet), - formatted_left, - Span::from(":"), - Span::styled( - first - .trim_start_matches(format!("{left}:").as_str()) - .to_string(), - Style::default().fg(Color::White), - ), - ])); - rest.to_string() - } else { - content.push(Line::from(vec![ - Span::from(bullet), - formatted_left, - Span::from(":"), - Span::styled(right.to_string(), Style::default().fg(Color::White)), - ])); - String::new() - }; + let first_line = + if project.len() > area.width as usize - bullet.len() { + let split_point = project + .char_indices() + .take_while(|(i, _)| { + *i < area.width as usize - bullet.len() + }) + .last() + .map(|(i, _)| i) + .unwrap_or(project.len()); + let (first, rest) = project.split_at(split_point); + content.push(Line::from(vec![ + Span::from(bullet), + formatted_left, + Span::from(":"), + Span::styled( + first + .trim_start_matches(format!("{left}:").as_str()) + .to_string(), + Style::default().fg(Color::White), + ), + ])); + rest.to_string() + } else { + content.push(Line::from(vec![ + Span::from(bullet), + formatted_left, + Span::from(":"), + Span::styled( + right.to_string(), + Style::default().fg(Color::White), + ), + ])); + String::new() + }; let mut remaining_text = first_line; while !remaining_text.is_empty() { if remaining_text.len() > area.width as usize - indent.len() { let split_point = remaining_text .char_indices() - .take_while(|(i, _)| *i < area.width as usize - indent.len()) + .take_while(|(i, _)| { + *i < area.width as usize - indent.len() + }) .last() .map(|(i, _)| i) .unwrap_or(remaining_text.len()); let (first, rest) = remaining_text.split_at(split_point); content.push(Line::from(vec![ Span::from(indent), - Span::styled(first.to_string(), Style::default().fg(Color::White)), + Span::styled( + first.to_string(), + Style::default().fg(Color::White), + ), ])); remaining_text = rest.to_string(); } else { content.push(Line::from(vec![ Span::from(indent), - Span::styled(remaining_text.clone(), Style::default().fg(Color::White)), + Span::styled( + remaining_text.clone(), + Style::default().fg(Color::White), + ), ])); remaining_text.clear(); } @@ -164,7 +197,8 @@ impl Content { content.extend(vec![ Line::from(""), Line::from( - " I am also a fan of the 8 bit aesthetic and think seals are super adorable :3", + " I am also a fan of the 8 bit aesthetic and think seals are \ + super adorable :3", ), ]); @@ -187,7 +221,10 @@ impl Content { } impl Component for Content { - fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { + fn register_action_handler( + &mut self, + tx: UnboundedSender, + ) -> Result<()> { self.command_tx = Some(tx); Ok(()) } @@ -212,7 +249,6 @@ impl Component for Content { 0 => self.about_content(area)?, 1 => self.projects_content(), /* Blog tab handled in `App::render` */ - _ => vec![], }; @@ -232,24 +268,23 @@ impl Component for Content { let mut current_pos = 1 + devcomp_width; for (i, &tab) in tabs.iter().enumerate() { - let (char, style) = if i == self.selected_tab.load(Ordering::Relaxed) { - ( - "━", - Style::default() - .fg(Color::Magenta) - .add_modifier(Modifier::BOLD), - ) - } else { - ("─", Style::default().fg(Color::DarkGray)) - }; + let (char, style) = + if i == self.selected_tab.load(Ordering::Relaxed) { + ( + "━", + Style::default() + .fg(Color::Magenta) + .add_modifier(Modifier::BOLD), + ) + } else { + ("─", Style::default().fg(Color::DarkGray)) + }; let default_style = Style::default().fg(Color::DarkGray); border_top.spans.push(Span::styled("┴", default_style)); border_top.spans.push(Span::styled("─", default_style)); - border_top - .spans - .push(Span::styled(char.repeat(tab.len()), style)); + border_top.spans.push(Span::styled(char.repeat(tab.len()), style)); border_top.spans.push(Span::styled("─", default_style)); border_top.spans.push(Span::styled("┴", default_style)); @@ -270,8 +305,10 @@ impl Component for Content { Style::default().fg(Color::DarkGray), )); - let border_left = Span::styled("│", Style::default().fg(Color::DarkGray)); - let border_right = Span::styled("│", Style::default().fg(Color::DarkGray)); + let border_left = + Span::styled("│", Style::default().fg(Color::DarkGray)); + let border_right = + Span::styled("│", Style::default().fg(Color::DarkGray)); // Render the content let content_widget = Paragraph::new(content) @@ -291,12 +328,7 @@ impl Component for Content { // Render the borders frame.render_widget( Paragraph::new(border_top), - Rect { - x: area.x, - y: area.y, - width: area.width, - height: 1, - }, + Rect { x: area.x, y: area.y, width: area.width, height: 1 }, ); frame.render_widget( @@ -312,12 +344,7 @@ impl Component for Content { for i in 1..area.height - 1 { frame.render_widget( Paragraph::new(Line::from(border_left.clone())), - Rect { - x: area.x, - y: area.y + i, - width: 1, - height: 1, - }, + Rect { x: area.x, y: area.y + i, width: 1, height: 1 }, ); frame.render_widget( diff --git a/src/components/selection_list.rs b/src/components/selection_list.rs index fd5c7a2..7e6694b 100644 --- a/src/components/selection_list.rs +++ b/src/components/selection_list.rs @@ -1,11 +1,11 @@ use color_eyre::eyre::Result; -use ratatui::{ - style::{Color, Style}, - widgets::{List, ListState}, -}; +use ratatui::style::{Color, Style}; +use ratatui::widgets::{List, ListState}; use tokio::sync::mpsc::UnboundedSender; -use crate::{action::Action, components::Component, config::Config}; +use crate::action::Action; +use crate::components::Component; +use crate::config::Config; #[derive(Default)] pub struct SelectionList { @@ -21,7 +21,8 @@ impl SelectionList { list_state.select_first(); Self { - options: List::new(options).highlight_style(Style::default().fg(Color::Yellow)), + options: List::new(options) + .highlight_style(Style::default().fg(Color::Yellow)), list_state, ..Default::default() } @@ -29,7 +30,10 @@ impl SelectionList { } impl Component for SelectionList { - fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { + fn register_action_handler( + &mut self, + tx: UnboundedSender, + ) -> Result<()> { self.command_tx = Some(tx); Ok(()) } @@ -51,8 +55,16 @@ impl Component for SelectionList { Ok(None) } - fn draw(&mut self, frame: &mut ratatui::Frame, area: ratatui::prelude::Rect) -> Result<()> { - frame.render_stateful_widget(self.options.clone(), area, &mut self.list_state); + fn draw( + &mut self, + frame: &mut ratatui::Frame, + area: ratatui::prelude::Rect, + ) -> Result<()> { + frame.render_stateful_widget( + self.options.clone(), + area, + &mut self.list_state, + ); Ok(()) } } diff --git a/src/components/tabs.rs b/src/components/tabs.rs index cfe4e98..da41e28 100644 --- a/src/components/tabs.rs +++ b/src/components/tabs.rs @@ -1,11 +1,14 @@ -use std::sync::{atomic::{AtomicUsize, Ordering}, Arc}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; use color_eyre::Result; -use ratatui::{prelude::*, widgets::*}; +use ratatui::prelude::*; +use ratatui::widgets::*; use tokio::sync::mpsc::UnboundedSender; use super::Component; -use crate::{action::Action, config::Config}; +use crate::action::Action; +use crate::config::Config; #[derive(Default)] pub struct Tabs { @@ -16,12 +19,11 @@ pub struct Tabs { } impl Tabs { - pub fn new(tabs: Vec<&'static str>, selected_tab: Arc) -> Self { - Self { - tabs, - selected_tab, - ..Default::default() - } + pub fn new( + tabs: Vec<&'static str>, + selected_tab: Arc, + ) -> Self { + Self { tabs, selected_tab, ..Default::default() } } pub fn next(&mut self) { @@ -42,7 +44,10 @@ impl Tabs { } impl Component for Tabs { - fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { + fn register_action_handler( + &mut self, + tx: UnboundedSender, + ) -> Result<()> { self.command_tx = Some(tx); Ok(()) } @@ -60,7 +65,7 @@ impl Component for Tabs { Action::PrevTab => self.previous(), _ => {} }; - + Ok(None) } @@ -69,9 +74,7 @@ impl Component for Tabs { for (i, &tab) in self.tabs.iter().enumerate() { let style = if self.selected_tab.load(Ordering::Relaxed) == i { - Style::default() - .fg(Color::Magenta) - .add_modifier(Modifier::BOLD) + Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::White) }; @@ -84,25 +87,20 @@ impl Component for Tabs { tab_lines[1] .spans .push(Span::styled("│", Style::default().fg(Color::DarkGray))); - tab_lines[1] - .spans - .push(Span::styled(format!(" {} ", tab), style)); + tab_lines[1].spans.push(Span::styled(format!(" {} ", tab), style)); tab_lines[1] .spans .push(Span::styled("│", Style::default().fg(Color::DarkGray))); } - let tabs_widget = Paragraph::new(tab_lines).block(Block::default().borders(Borders::NONE)); + let tabs_widget = Paragraph::new(tab_lines) + .block(Block::default().borders(Borders::NONE)); frame.render_widget( tabs_widget, - Rect { - x: area.x, - y: area.y, - width: area.width, - height: 2, - }, + Rect { x: area.x, y: area.y, width: area.width, height: 2 }, ); Ok(()) - }} + } +} diff --git a/src/config.rs b/src/config.rs index 09b3d2c..0a71cdd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,8 @@ #![allow(dead_code)] // Remove this once you start using the code -use std::{collections::HashMap, env, path::PathBuf}; +use std::collections::HashMap; +use std::env; +use std::path::PathBuf; use color_eyre::Result; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; @@ -8,10 +10,12 @@ use derive_deref::{Deref, DerefMut}; use directories::ProjectDirs; use lazy_static::lazy_static; use ratatui::style::{Color, Modifier, Style}; -use serde::{de::Deserializer, Deserialize}; +use serde::de::Deserializer; +use serde::Deserialize; use tracing::error; -use crate::{action::Action, app::Mode}; +use crate::action::Action; +use crate::app::Mode; const CONFIG: &str = include_str!("../.config/config.json5"); @@ -34,7 +38,8 @@ pub struct Config { } lazy_static! { - pub static ref PROJECT_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase().to_string(); + pub static ref PROJECT_NAME: String = + env!("CARGO_CRATE_NAME").to_uppercase().to_string(); pub static ref DATA_FOLDER: Option = env::var(format!("{}_DATA", PROJECT_NAME.clone())) .ok() @@ -72,7 +77,10 @@ impl Config { } } if !found_config { - error!("No configuration file found. Application may not behave as expected"); + error!( + "No configuration file found. Application may not behave as \ + expected" + ); } let mut cfg: Self = builder.build()?.try_deserialize()?; @@ -80,9 +88,7 @@ impl Config { for (mode, default_bindings) in default_config.keybindings.iter() { let user_bindings = cfg.keybindings.entry(*mode).or_default(); for (key, cmd) in default_bindings.iter() { - user_bindings - .entry(key.clone()) - .or_insert_with(|| cmd.clone()); + user_bindings.entry(key.clone()).or_insert_with(|| cmd.clone()); } } for (mode, default_styles) in default_config.styles.iter() { @@ -128,16 +134,19 @@ pub struct KeyBindings(pub HashMap, Action>>); impl<'de> Deserialize<'de> for KeyBindings { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'de>, - { - let parsed_map = HashMap::>::deserialize(deserializer)?; + D: Deserializer<'de>, { + let parsed_map = HashMap::>::deserialize( + deserializer, + )?; let keybindings = parsed_map .into_iter() .map(|(mode, inner_map)| { let converted_inner_map = inner_map .into_iter() - .map(|(key_str, cmd)| (parse_key_sequence(&key_str).unwrap(), cmd)) + .map(|(key_str, cmd)| { + (parse_key_sequence(&key_str).unwrap(), cmd) + }) .collect(); (mode, converted_inner_map) }) @@ -292,7 +301,9 @@ pub fn key_event_to_string(key_event: &KeyEvent) -> String { } pub fn parse_key_sequence(raw: &str) -> Result, String> { - if raw.chars().filter(|c| *c == '>').count() != raw.chars().filter(|c| *c == '<').count() { + if raw.chars().filter(|c| *c == '>').count() + != raw.chars().filter(|c| *c == '<').count() + { return Err(format!("Unable to parse `{}`", raw)); } let raw = if !raw.contains("><") { @@ -324,9 +335,10 @@ pub struct Styles(pub HashMap>); impl<'de> Deserialize<'de> for Styles { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'de>, - { - let parsed_map = HashMap::>::deserialize(deserializer)?; + D: Deserializer<'de>, { + let parsed_map = HashMap::>::deserialize( + deserializer, + )?; let styles = parsed_map .into_iter() @@ -387,27 +399,22 @@ fn parse_color(s: &str) -> Option { let s = s.trim_end(); if s.contains("bright color") { let s = s.trim_start_matches("bright "); - let c = s - .trim_start_matches("color") - .parse::() - .unwrap_or_default(); + let c = s.trim_start_matches("color").parse::().unwrap_or_default(); Some(Color::Indexed(c.wrapping_shl(8))) } else if s.contains("color") { - let c = s - .trim_start_matches("color") - .parse::() - .unwrap_or_default(); + let c = s.trim_start_matches("color").parse::().unwrap_or_default(); Some(Color::Indexed(c)) } else if s.contains("gray") { let c = 232 - + s.trim_start_matches("gray") - .parse::() - .unwrap_or_default(); + + s.trim_start_matches("gray").parse::().unwrap_or_default(); Some(Color::Indexed(c)) } else if s.contains("rgb") { - let red = (s.as_bytes()[3] as char).to_digit(10).unwrap_or_default() as u8; - let green = (s.as_bytes()[4] as char).to_digit(10).unwrap_or_default() as u8; - let blue = (s.as_bytes()[5] as char).to_digit(10).unwrap_or_default() as u8; + let red = + (s.as_bytes()[3] as char).to_digit(10).unwrap_or_default() as u8; + let green = + (s.as_bytes()[4] as char).to_digit(10).unwrap_or_default() as u8; + let blue = + (s.as_bytes()[5] as char).to_digit(10).unwrap_or_default() as u8; let c = 16 + red * 36 + green * 6 + blue; Some(Color::Indexed(c)) } else if s == "bold black" { @@ -480,7 +487,8 @@ mod tests { #[test] fn test_process_color_string() { - let (color, modifiers) = process_color_string("underline bold inverse gray"); + let (color, modifiers) = + process_color_string("underline bold inverse gray"); assert_eq!(color, "gray"); assert!(modifiers.contains(Modifier::UNDERLINED)); assert!(modifiers.contains(Modifier::BOLD)); @@ -562,7 +570,10 @@ mod tests { assert_eq!( parse_key_event("ctrl-shift-enter").unwrap(), - KeyEvent::new(KeyCode::Enter, KeyModifiers::CONTROL | KeyModifiers::SHIFT) + KeyEvent::new( + KeyCode::Enter, + KeyModifiers::CONTROL | KeyModifiers::SHIFT + ) ); } diff --git a/src/errors.rs b/src/errors.rs index cf8a58e..74ebc74 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -27,8 +27,9 @@ pub fn init() -> Result<()> { let metadata = metadata!(); let file_path = handle_dump(&metadata, panic_info); // prints human-panic message - print_msg(file_path, &metadata) - .expect("human-panic: printing error message to console failed"); + print_msg(file_path, &metadata).expect( + "human-panic: printing error message to console failed", + ); eprintln!("{}", panic_hook.panic_report(panic_info)); // prints color-eyre stack trace to stderr } let msg = format!("{}", panic_hook.panic_report(panic_info)); diff --git a/src/keycode.rs b/src/keycode.rs index 5fc7e2d..ba19873 100644 --- a/src/keycode.rs +++ b/src/keycode.rs @@ -53,14 +53,14 @@ impl KeyCodeExt for KeyCode { // Caps/Num/Scroll Lock 20 => KeyCode::CapsLock, 144 => KeyCode::NumLock, - 145 => KeyCode::ScrollLock, + 145 => KeyCode::ScrollLock, // Pause/Break 19 => KeyCode::Pause, // Anything else _ => KeyCode::Null, - } + } } #[rustfmt::skip] @@ -95,18 +95,25 @@ impl KeyCodeExt for KeyCode { [single] => *single, _ => KeyCode::Null, } - } + } fn into_key_event(self) -> KeyEvent { match self { - Self::Char(CTRL_C) => KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL), - Self::Char(CTRL_D) => KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL), - Self::Char(CTRL_Z) => KeyEvent::new(KeyCode::Char('z'), KeyModifiers::CONTROL), + Self::Char(CTRL_C) => { + KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL) + } + Self::Char(CTRL_D) => { + KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL) + } + Self::Char(CTRL_Z) => { + KeyEvent::new(KeyCode::Char('z'), KeyModifiers::CONTROL) + } other => KeyEvent::new(other, KeyModifiers::empty()), } } } +#[rustfmt::skip] #[cfg(test)] mod tests { use super::*; @@ -169,4 +176,4 @@ mod tests { let key_event = ::into_key_event(key_code); assert_eq!(key_event, KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty())); } -} \ No newline at end of file +} diff --git a/src/landing.rs b/src/landing.rs index 117876f..c65a0e4 100644 --- a/src/landing.rs +++ b/src/landing.rs @@ -1,6 +1,8 @@ -use std::{io, net::SocketAddr}; +use std::io; +use std::net::SocketAddr; -use actix_web::{middleware::Logger, web, App, HttpResponse, HttpServer, Responder}; +use actix_web::middleware::Logger; +use actix_web::{web, App, HttpResponse, HttpServer, Responder}; use rust_embed::Embed; use tracing::instrument; diff --git a/src/logging.rs b/src/logging.rs index 34e74d2..5a07217 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -1,8 +1,11 @@ use std::io::stderr; -use color_eyre::{eyre::eyre, Result}; +use color_eyre::eyre::eyre; +use color_eyre::Result; use tracing_error::ErrorLayer; -use tracing_subscriber::{fmt, prelude::*, util::TryInitError, EnvFilter}; +use tracing_subscriber::prelude::*; +use tracing_subscriber::util::TryInitError; +use tracing_subscriber::{fmt, EnvFilter}; use crate::config; @@ -27,13 +30,12 @@ pub fn init() -> Result<()> { // // Stage 1: Construct base filter - let env_filter = EnvFilter::builder().with_default_directive(tracing::Level::INFO.into()); + let env_filter = EnvFilter::builder() + .with_default_directive(tracing::Level::INFO.into()); // Stage 2: Attempt to read from {RUST|CRATE_NAME}_LOG env var or ignore let env_filter = env_filter.try_from_env().unwrap_or_else(|_| { - env_filter - .with_env_var(LOG_ENV.to_string()) - .from_env_lossy() + env_filter.with_env_var(LOG_ENV.to_string()).from_env_lossy() }); // Stage 3: Enable directives to reduce verbosity for release mode builds @@ -75,7 +77,9 @@ pub fn init() -> Result<()> { let layer = layer .compact() .without_time() - .with_span_events(tracing_subscriber::fmt::format::FmtSpan::NONE) + .with_span_events( + tracing_subscriber::fmt::format::FmtSpan::NONE, + ) .with_target(false) .with_thread_ids(false); layer diff --git a/src/main.rs b/src/main.rs index 99d3e59..75b28b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,9 +2,12 @@ use std::net::SocketAddr; use clap::Parser as _; use cli::Cli; -use color_eyre::{eyre::eyre, Result}; +use color_eyre::eyre::eyre; +use color_eyre::Result; use lazy_static::lazy_static; -use russh::{keys::PrivateKey, server::Config, MethodSet}; +use russh::keys::PrivateKey; +use russh::server::Config; +use russh::MethodSet; use ssh::SshServer; #[cfg(feature = "blog")] @@ -47,8 +50,10 @@ async fn main() -> Result<()> { crate::logging::init()?; let _ = *OPTIONS; // force clap to run by evaluating it - let ssh_socket_addr = SSH_SOCKET_ADDR.ok_or(eyre!("Invalid host IP provided"))?; - let web_server_addr = WEB_SERVER_ADDR.ok_or(eyre!("Invalid host IP provided"))?; + let ssh_socket_addr = + SSH_SOCKET_ADDR.ok_or(eyre!("Invalid host IP provided"))?; + let web_server_addr = + WEB_SERVER_ADDR.ok_or(eyre!("Invalid host IP provided"))?; tokio::select! { ssh_res = SshServer::start(ssh_socket_addr, ssh_config()) => ssh_res, @@ -63,9 +68,9 @@ pub fn host_ip() -> Result<[u8; 4]> { .host .splitn(4, ".") .map(|octet_str| { - octet_str - .parse::() - .map_err(|_| eyre!("Octet component out of range (expected u8)")) + octet_str.parse::().map_err(|_| { + eyre!("Octet component out of range (expected u8)") + }) }) .collect::>>()?, ) diff --git a/src/ssh.rs b/src/ssh.rs index 0529af1..6221f1b 100644 --- a/src/ssh.rs +++ b/src/ssh.rs @@ -1,23 +1,20 @@ -use std::{io::Write, net::SocketAddr, sync::Arc}; +use std::io::Write; +use std::net::SocketAddr; +use std::sync::Arc; use async_trait::async_trait; use color_eyre::eyre::{self, eyre}; -use russh::{ - server::{Auth, Config, Handle, Handler, Msg, Server, Session}, - Channel, ChannelId, CryptoVec, Pty, -}; -use tokio::{ - net::TcpListener, - runtime::Handle as TokioHandle, - sync::{mpsc, oneshot, Mutex, RwLock}, -}; +use russh::server::{Auth, Config, Handle, Handler, Msg, Server, Session}; +use russh::{Channel, ChannelId, CryptoVec, Pty}; +use tokio::net::TcpListener; +use tokio::runtime::Handle as TokioHandle; +use tokio::sync::{mpsc, oneshot, Mutex, RwLock}; use tracing::instrument; -use crate::{ - app::App, - tui::{backend::SshBackend, Terminal, Tui}, - OPTIONS, -}; +use crate::app::App; +use crate::tui::backend::SshBackend; +use crate::tui::{Terminal, Tui}; +use crate::OPTIONS; #[derive(Debug)] pub struct TermWriter { @@ -31,11 +28,7 @@ impl TermWriter { #[instrument(skip(session, channel), level = "trace", fields(channel_id = %channel.id()))] fn new(session: Handle, channel: Channel) -> Self { tracing::trace!("Acquiring new SSH writer"); - Self { - session, - channel, - inner: CryptoVec::new(), - } + Self { session, channel, inner: CryptoVec::new() } } fn flush_inner(&mut self) -> std::io::Result<()> { @@ -45,7 +38,9 @@ impl TermWriter { .data(self.channel.id(), self.inner.clone()) .await .map_err(|err| { - std::io::Error::other(String::from_iter(err.iter().map(|item| *item as char))) + std::io::Error::other(String::from_iter( + err.iter().map(|item| *item as char), + )) }) .map(|_| self.inner.clear()) }) @@ -165,7 +160,8 @@ impl Handler for SshSession { Ok(()) => tracing::info!("Session exited successfully"), Err(err) => { tracing::error!("Session errored: {err}"); - let _ = session_handle.channel_failure(channel_id).await; + let _ = + session_handle.channel_failure(channel_id).await; } } }); @@ -189,7 +185,10 @@ impl Handler for SshSession { session: &mut Session, ) -> Result<(), Self::Error> { tracing::info!("PTY requested by terminal: {term}"); - tracing::debug!("dims: {col_width} * {row_height}, pixel: {pix_width} * {pix_height}"); + tracing::debug!( + "dims: {col_width} * {row_height}, pixel: {pix_width} * \ + {pix_height}" + ); if !term.contains("xterm") { session.channel_failure(channel_id)?; @@ -218,7 +217,10 @@ impl Handler for SshSession { data: &[u8], _session: &mut Session, ) -> Result<(), Self::Error> { - tracing::debug!("Received keystroke data from SSH: {:?}, sending", data); + tracing::debug!( + "Received keystroke data from SSH: {:?}, sending", + data + ); self.keystroke_tx .send(data.to_vec()) .map_err(|_| eyre!("Failed to send event keystroke data")) @@ -251,8 +253,7 @@ impl SshServer { pub async fn start(addr: SocketAddr, config: Config) -> eyre::Result<()> { let listener = TcpListener::bind(addr).await?; - Self - .run_on_socket(Arc::new(config), &listener) + Self.run_on_socket(Arc::new(config), &listener) .await .map_err(|err| eyre!(err)) } @@ -264,7 +265,6 @@ impl Server for SshServer { #[instrument(skip(self))] fn new_client(&mut self, peer_addr: Option) -> Self::Handler { - tokio::task::block_in_place(SshSession::new) } } diff --git a/src/tui/backend.rs b/src/tui/backend.rs index fd5f72a..e287cd2 100644 --- a/src/tui/backend.rs +++ b/src/tui/backend.rs @@ -1,9 +1,8 @@ -use std::{io, ops::{Deref, DerefMut}}; +use std::io; +use std::ops::{Deref, DerefMut}; -use ratatui::{ - backend::{Backend, CrosstermBackend, WindowSize}, - layout::Size, -}; +use ratatui::backend::{Backend, CrosstermBackend, WindowSize}; +use ratatui::layout::Size; use crate::ssh::TermWriter; @@ -15,28 +14,30 @@ pub struct SshBackend { } impl SshBackend { - pub fn new(writer: TermWriter, init_width: u16, init_height: u16, init_pixel_width: u16, init_pixdel_height: u16) -> Self { + pub fn new( + writer: TermWriter, + init_width: u16, + init_height: u16, + init_pixel_width: u16, + init_pixel_height: u16, + ) -> Self { let inner = CrosstermBackend::new(writer); SshBackend { inner, dims: (init_width, init_height), - pixel: (init_pixel_width, init_pixdel_height), + pixel: (init_pixel_width, init_pixel_height), } } } impl Backend for SshBackend { fn size(&self) -> io::Result { - Ok(Size { - width: self.dims.0, - height: self.dims.1, - }) + Ok(Size { width: self.dims.0, height: self.dims.1 }) } fn draw<'a, I>(&mut self, content: I) -> io::Result<()> where - I: Iterator, - { + I: Iterator, { self.inner.draw(content) } @@ -48,7 +49,9 @@ impl Backend for SshBackend { self.inner.show_cursor() } - fn get_cursor_position(&mut self) -> io::Result { + fn get_cursor_position( + &mut self, + ) -> io::Result { self.inner.get_cursor_position() } @@ -87,4 +90,4 @@ impl DerefMut for SshBackend { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } -} \ No newline at end of file +} diff --git a/src/tui/mod.rs b/src/tui/mod.rs index 7243508..cdbc9d6 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -1,28 +1,23 @@ #![allow(dead_code)] // TODO: Remove this once you start using the code -use std::{sync::Arc, time::Duration}; +use std::sync::Arc; +use std::time::Duration; use backend::SshBackend; use color_eyre::Result; -use crossterm::{ - cursor, - event::{ - DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture, - KeyEvent, MouseEvent, - }, - terminal::{EnterAlternateScreen, LeaveAlternateScreen}, +use crossterm::cursor; +use crossterm::event::{ + DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, + EnableMouseCapture, KeyEvent, MouseEvent, }; +use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen}; use serde::{Deserialize, Serialize}; use status::TuiStatus; -use tokio::{ - runtime::Handle, - sync::{ - mpsc::{self, UnboundedReceiver, UnboundedSender}, - Mutex, RwLock, - }, - task::{block_in_place, JoinHandle}, - time::{interval, sleep, timeout}, -}; +use tokio::runtime::Handle; +use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; +use tokio::sync::{Mutex, RwLock}; +use tokio::task::{block_in_place, JoinHandle}; +use tokio::time::{interval, sleep, timeout}; use tokio_util::sync::CancellationToken; use tracing::error; @@ -122,13 +117,13 @@ impl Tui { tick_rate: f64, frame_rate: f64, ) { - let mut tick_interval = interval(Duration::from_secs_f64(1.0 / tick_rate)); - let mut render_interval = interval(Duration::from_secs_f64(1.0 / frame_rate)); + let mut tick_interval = + interval(Duration::from_secs_f64(1.0 / tick_rate)); + let mut render_interval = + interval(Duration::from_secs_f64(1.0 / frame_rate)); // if this fails, then it's likely a bug in the calling code - event_tx - .send(Event::Init) - .expect("failed to send init event"); + event_tx.send(Event::Init).expect("failed to send init event"); let suspension_status = Arc::clone(&status); loop { @@ -166,11 +161,14 @@ impl Tui { }; if timeout(attempt_timeout, self.await_shutdown()).await.is_err() { - timeout(attempt_timeout, abort_shutdown) - .await - .inspect_err(|_| { - error!("Failed to abort task in 100 milliseconds for unknown reason") - })?; + timeout(attempt_timeout, abort_shutdown).await.inspect_err( + |_| { + error!( + "Failed to abort task in 100 milliseconds for unknown \ + reason" + ) + }, + )?; } Ok(()) @@ -179,7 +177,11 @@ impl Tui { pub fn enter(&mut self) -> Result<()> { let mut term = self.terminal.try_lock()?; // crossterm::terminal::enable_raw_mode()?; // TODO: Enable raw mode for pty - crossterm::execute!(term.backend_mut(), EnterAlternateScreen, cursor::Hide)?; + crossterm::execute!( + term.backend_mut(), + EnterAlternateScreen, + cursor::Hide + )?; if self.mouse { crossterm::execute!(term.backend_mut(), EnableMouseCapture)?; @@ -209,7 +211,11 @@ impl Tui { crossterm::execute!(term.backend_mut(), DisableMouseCapture)?; } - crossterm::execute!(term.backend_mut(), LeaveAlternateScreen, cursor::Show)?; + crossterm::execute!( + term.backend_mut(), + LeaveAlternateScreen, + cursor::Show + )?; Ok(()) } @@ -224,7 +230,8 @@ impl Tui { // Update the status and initialize a cancellation token let token = Arc::new(CancellationToken::new()); let suspension = Arc::new(Mutex::new(())); - *self.status.write().await = TuiStatus::Suspended(Arc::clone(&suspension)); + *self.status.write().await = + TuiStatus::Suspended(Arc::clone(&suspension)); // Spawn a task holding on the lock until a notification interrupts it let status = Arc::clone(&self.status); diff --git a/src/tui/status.rs b/src/tui/status.rs index c84bd8c..7cd96c0 100644 --- a/src/tui/status.rs +++ b/src/tui/status.rs @@ -1,4 +1,6 @@ -use std::{future::{Future, IntoFuture}, pin::Pin, sync::Arc}; +use std::future::{Future, IntoFuture}; +use std::pin::Pin; +use std::sync::Arc; use futures::future; use tokio::sync::Mutex; @@ -22,4 +24,4 @@ impl IntoFuture for TuiStatus { Box::pin(future::ready(())) } -} \ No newline at end of file +}