style: apply rustfmt formatting to all files

Also moves rust toolchain to a nightly equivalent of stable 1.87, in
order to allow for existing rustfmt configs such as imports granularity
to be used.
This commit is contained in:
Erica Marigold 2025-08-19 07:01:31 +01:00
parent ee524dc160
commit 3c089bbdc3
Signed by: DevComp
SSH key fingerprint: SHA256:jD3oMT4WL3WHPJQbrjC3l5feNCnkv7ndW8nYaHX5wFw
21 changed files with 154 additions and 324 deletions

View file

@ -36,8 +36,8 @@ fn main() -> Result<()> {
continue; continue;
} }
let key = PrivateKey::random(&mut rng, algo.to_owned()) let key =
.map_err(anyhow::Error::from)?; PrivateKey::random(&mut rng, algo.to_owned()).map_err(anyhow::Error::from)?;
key.write_openssh_file(&path, LineEnding::default())?; key.write_openssh_file(&path, LineEnding::default())?;
} }

View file

@ -1,4 +1,3 @@
[toolchain] [toolchain]
channel = "stable" channel = "nightly-2025-03-28"
version = "1.87"
components = ["clippy", "rust-analyzer", "cargo", "rustc"] components = ["clippy", "rust-analyzer", "cargo", "rustc"]

View file

@ -1,6 +1,7 @@
use std::fmt; use std::fmt;
use serde::{de::{self, Visitor}, Deserialize, Deserializer, Serialize}; use serde::de::{self, Visitor};
use serde::{Deserialize, Deserializer, Serialize};
use strum::Display; use strum::Display;
#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize)] #[derive(Debug, Clone, PartialEq, Eq, Display, Serialize)]
@ -30,8 +31,8 @@ pub enum Action {
impl<'de> Deserialize<'de> for Action { impl<'de> Deserialize<'de> for Action {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de> where
{ D: Deserializer<'de>, {
struct ActionVisitor; struct ActionVisitor;
impl<'de> Visitor<'de> for ActionVisitor { impl<'de> Visitor<'de> for ActionVisitor {
@ -42,8 +43,8 @@ impl<'de> Deserialize<'de> for Action {
} }
fn visit_str<E>(self, v: &str) -> Result<Action, E> fn visit_str<E>(self, v: &str) -> Result<Action, E>
where E: de::Error where
{ E: de::Error, {
if v == "Continue" { if v == "Continue" {
Ok(Action::Continue(None)) Ok(Action::Continue(None))
} else { } else {
@ -62,7 +63,7 @@ impl<'de> Deserialize<'de> for Action {
SelectNext, SelectNext,
SelectPrev, SelectPrev,
} }
let helper: Helper = serde_json::from_str(&format!("\"{}\"", v)) let helper: Helper = serde_json::from_str(&format!("\"{}\"", v))
.map_err(|_| de::Error::unknown_variant(v, &["Continue"]))?; .map_err(|_| de::Error::unknown_variant(v, &["Continue"]))?;
Ok(match helper { Ok(match helper {

View file

@ -416,8 +416,8 @@ impl App {
// If blog feature is not enabled, render a placeholder // If blog feature is not enabled, render a placeholder
content_rect.height = 1; content_rect.height = 1;
let placeholder = Paragraph::new( let placeholder = Paragraph::new(
"Blog feature is disabled. Enable the `blog` feature \ "Blog feature is disabled. Enable the `blog` feature to view this \
to view this tab.", tab.",
) )
.style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)); .style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD));

View file

@ -28,30 +28,20 @@ pub mod blog {
lazy_static! { lazy_static! {
static ref POSTS_CACHE_STORE: MemoryStore<usize, (Instant, com::whtwnd::blog::entry::Record)> = static ref POSTS_CACHE_STORE: MemoryStore<usize, (Instant, com::whtwnd::blog::entry::Record)> =
MemoryStore::default(); MemoryStore::default();
static ref AGENT: Agent< static ref AGENT: Agent<CredentialSession<MemoryStore<(), Object<SessionOutputData>>, ReqwestClient>> =
CredentialSession< Agent::new(CredentialSession::new(
MemoryStore<(), Object<SessionOutputData>>, ReqwestClient::new("https://bsky.social"),
ReqwestClient, MemorySessionStore::default(),
>, ));
> = Agent::new(CredentialSession::new(
ReqwestClient::new("https://bsky.social"),
MemorySessionStore::default(),
));
} }
#[instrument(level = "debug")] #[instrument(level = "debug")]
pub async fn get_all_posts() -> Result<Vec<com::whtwnd::blog::entry::Record>> pub async fn get_all_posts() -> Result<Vec<com::whtwnd::blog::entry::Record>> {
{
let mut i = 0; let mut i = 0;
let mut posts = Vec::new(); let mut posts = Vec::new();
while let Some((cache_creation_time, post)) = while let Some((cache_creation_time, post)) = POSTS_CACHE_STORE.get(&i).await? {
POSTS_CACHE_STORE.get(&i).await?
{
if cache_creation_time.elapsed() > CACHE_INVALIDATION_PERIOD { if cache_creation_time.elapsed() > CACHE_INVALIDATION_PERIOD {
tracing::info!( tracing::info!("Cache for post #{} is stale, fetching new posts", i);
"Cache for post #{} is stale, fetching new posts",
i
);
POSTS_CACHE_STORE.clear().await?; POSTS_CACHE_STORE.clear().await?;
return fetch_posts_into_cache().await; return fetch_posts_into_cache().await;
} }
@ -62,9 +52,7 @@ pub mod blog {
} }
if posts.is_empty() { if posts.is_empty() {
tracing::info!( tracing::info!("No blog posts found in cache, fetching from ATProto");
"No blog posts found in cache, fetching from ATProto"
);
return fetch_posts_into_cache().await; return fetch_posts_into_cache().await;
} }
@ -72,8 +60,7 @@ pub mod blog {
} }
#[instrument(level = "trace")] #[instrument(level = "trace")]
async fn fetch_posts_into_cache( async fn fetch_posts_into_cache() -> Result<Vec<com::whtwnd::blog::entry::Record>> {
) -> Result<Vec<com::whtwnd::blog::entry::Record>> {
let records = &AGENT let records = &AGENT
.api .api
.com .com
@ -100,9 +87,7 @@ pub mod blog {
.map(|elem| { .map(|elem| {
if let Unknown::Object(btree_map) = &elem.data.value { if let Unknown::Object(btree_map) = &elem.data.value {
let ser = serde_json::to_string(&btree_map)?; let ser = serde_json::to_string(&btree_map)?;
let des = serde_json::from_str::< let des = serde_json::from_str::<com::whtwnd::blog::entry::Record>(&ser)?;
com::whtwnd::blog::entry::Record,
>(&ser)?;
return Ok(des); return Ok(des);
} }

View file

@ -45,10 +45,7 @@ pub trait Component: Send {
/// # Returns /// # Returns
/// ///
/// * `Result<()>` - An Ok result or an error. /// * `Result<()>` - An Ok result or an error.
fn register_action_handler( fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
&mut self,
tx: UnboundedSender<Action>,
) -> Result<()> {
let _ = tx; // to appease clippy let _ = tx; // to appease clippy
Ok(()) Ok(())
} }
@ -87,15 +84,10 @@ pub trait Component: Send {
/// # Returns /// # Returns
/// ///
/// * `Result<Option<Action>>` - An action to be processed or none. /// * `Result<Option<Action>>` - An action to be processed or none.
fn handle_events( fn handle_events(&mut self, event: Option<Event>) -> Result<Option<Action>> {
&mut self,
event: Option<Event>,
) -> Result<Option<Action>> {
let action = match event { let action = match event {
Some(Event::Key(key_event)) => self.handle_key_event(key_event)?, Some(Event::Key(key_event)) => self.handle_key_event(key_event)?,
Some(Event::Mouse(mouse_event)) => { Some(Event::Mouse(mouse_event)) => self.handle_mouse_event(mouse_event)?,
self.handle_mouse_event(mouse_event)?
}
_ => None, _ => None,
}; };
Ok(action) Ok(action)
@ -122,10 +114,7 @@ pub trait Component: Send {
/// # Returns /// # Returns
/// ///
/// * `Result<Option<Action>>` - An action to be processed or none. /// * `Result<Option<Action>>` - An action to be processed or none.
fn handle_mouse_event( fn handle_mouse_event(&mut self, mouse: MouseEvent) -> Result<Option<Action>> {
&mut self,
mouse: MouseEvent,
) -> Result<Option<Action>> {
let _ = mouse; // to appease clippy let _ = mouse; // to appease clippy
Ok(None) Ok(None)
} }

View file

@ -2,16 +2,14 @@ use std::io::{BufReader, Cursor};
use std::sync::Arc; use std::sync::Arc;
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use color_eyre::owo_colors::OwoColorize;
use color_eyre::Result; use color_eyre::Result;
use image::{ImageReader, Rgba}; use image::{ImageReader, Rgba};
use ratatui::layout::{Constraint, Flex, Layout, Rect, Size}; use ratatui::layout::{Constraint, Flex, Layout, Rect, Size};
use ratatui::style::{Color, Modifier, Style}; use ratatui::prelude::*;
use ratatui::text::{self, Line, Span, Text}; use ratatui::widgets::*;
use ratatui::widgets::{Block, Paragraph, Widget as _, Wrap};
use ratatui_image::picker::{Picker, ProtocolType}; use ratatui_image::picker::{Picker, ProtocolType};
use ratatui_image::protocol::StatefulProtocol; use ratatui_image::protocol::StatefulProtocol;
use ratatui_image::{FilterType, FontSize, Resize, StatefulImage}; use ratatui_image::{Resize, StatefulImage};
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use tokio::sync::RwLock; use tokio::sync::RwLock;

View file

@ -28,10 +28,7 @@ impl Cat {
} }
impl Component for Cat { impl Component for Cat {
fn register_action_handler( fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
&mut self,
tx: UnboundedSender<Action>,
) -> Result<()> {
self.command_tx = Some(tx); self.command_tx = Some(tx);
Ok(()) Ok(())
} }
@ -57,12 +54,7 @@ impl Component for Cat {
.fg(Color::Magenta) .fg(Color::Magenta)
.add_modifier(Modifier::SLOW_BLINK | Modifier::BOLD), .add_modifier(Modifier::SLOW_BLINK | Modifier::BOLD),
), ),
Rect { Rect { x: area.width - 17, y: area.height - 4, width: 16, height: 6 },
x: area.width - 17,
y: area.height - 4,
width: 16,
height: 6,
},
); );
Ok(()) Ok(())

View file

@ -41,11 +41,8 @@ impl Content {
.ok_or(eyre!("Failed to create figlet header for about page"))? .ok_or(eyre!("Failed to create figlet header for about page"))?
.to_string(); .to_string();
let lines: Vec<String> = greetings_header let lines: Vec<String> =
.trim_end_matches('\n') greetings_header.trim_end_matches('\n').split('\n').map(String::from).collect();
.split('\n')
.map(String::from)
.collect();
let mut content = lines let mut content = lines
.iter() .iter()
@ -60,10 +57,7 @@ impl Content {
"she/they", "she/they",
Style::default().add_modifier(Modifier::ITALIC), Style::default().add_modifier(Modifier::ITALIC),
), ),
Span::from( Span::from("), and I make scalable systems or something. IDFK."),
"), and I make scalable systems or something. \
IDFK.",
),
]); ]);
} }
Line::raw(format!(" {}", line)) Line::raw(format!(" {}", line))
@ -75,9 +69,7 @@ impl Content {
Line::from(""), Line::from(""),
Line::from(vec![ Line::from(vec![
Span::from(" "), Span::from(" "),
Span::from( Span::from("I specialize in systems programming, primarily in "),
"I specialize in systems programming, primarily in ",
),
Span::styled( Span::styled(
"Rust 🦀", "Rust 🦀",
Style::default() Style::default()
@ -95,98 +87,79 @@ impl Content {
]), ]),
Line::from(""), Line::from(""),
Line::from( Line::from(
" I am an avid believer of open-source software, and \ " I am an avid believer of open-source software, and contribute to a few \
contribute to a few projects such as:", projects such as:",
), ),
]); ]);
let projects = vec![ let projects = vec![
( (
Style::default() Style::default().fg(Color::LightMagenta).add_modifier(Modifier::BOLD),
.fg(Color::LightMagenta)
.add_modifier(Modifier::BOLD),
"lune-org/lune: A standalone Luau runtime", "lune-org/lune: A standalone Luau runtime",
), ),
( (
Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD), Style::default().fg(Color::Blue).add_modifier(Modifier::BOLD),
"DiscordLuau/discord-luau: A Luau library for creating \ "DiscordLuau/discord-luau: A Luau library for creating Discord bots, powered \
Discord bots, powered by Lune", by Lune",
), ),
( (
Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD), Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD),
"pesde-pkg/pesde: A package manager for the Luau programming \ "pesde-pkg/pesde: A package manager for the Luau programming language, \
language, supporting multiple runtimes including Roblox and \ supporting multiple runtimes including Roblox and Lune",
Lune",
), ),
]; ];
for (style, project) in projects { for (style, project) in projects {
let parts: Vec<&str> = project.splitn(2, ':').collect(); let parts: Vec<&str> = project.splitn(2, ':').collect();
let (left, right) = if parts.len() == 2 { let (left, right) =
(parts[0], parts[1]) if parts.len() == 2 { (parts[0], parts[1]) } else { (project, "") };
} else {
(project, "")
};
let formatted_left = Span::styled(left, style); let formatted_left = Span::styled(left, style);
let bullet = ""; let bullet = "";
let indent = " "; let indent = " ";
let first_line = let first_line = if project.len() > area.width as usize - bullet.len() {
if project.len() > area.width as usize - bullet.len() { let split_point = project
let split_point = project .char_indices()
.char_indices() .take_while(|(i, _)| *i < area.width as usize - bullet.len())
.take_while(|(i, _)| { .last()
*i < area.width as usize - bullet.len() .map(|(i, _)| i)
}) .unwrap_or(project.len());
.last() let (first, rest) = project.split_at(split_point);
.map(|(i, _)| i) content.push(Line::from(vec![
.unwrap_or(project.len()); Span::from(bullet),
let (first, rest) = project.split_at(split_point); formatted_left,
content.push(Line::from(vec![ Span::from(":"),
Span::from(bullet), Span::styled(
formatted_left, first.trim_start_matches(format!("{left}:").as_str()).to_string(),
Span::from(":"), Style::default().fg(Color::White),
Span::styled( ),
first ]));
.trim_start_matches(format!("{left}:").as_str()) rest.to_string()
.to_string(), } else {
Style::default().fg(Color::White), content.push(Line::from(vec![
), Span::from(bullet),
])); formatted_left,
rest.to_string() Span::from(":"),
} else { Span::styled(right.to_string(), Style::default().fg(Color::White)),
content.push(Line::from(vec![ ]));
Span::from(bullet), String::new()
formatted_left, };
Span::from(":"),
Span::styled(
right.to_string(),
Style::default().fg(Color::White),
),
]));
String::new()
};
let mut remaining_text = first_line; let mut remaining_text = first_line;
while !remaining_text.is_empty() { while !remaining_text.is_empty() {
if remaining_text.len() > area.width as usize - indent.len() { if remaining_text.len() > area.width as usize - indent.len() {
let split_point = remaining_text let split_point = remaining_text
.char_indices() .char_indices()
.take_while(|(i, _)| { .take_while(|(i, _)| *i < area.width as usize - indent.len())
*i < area.width as usize - indent.len()
})
.last() .last()
.map(|(i, _)| i) .map(|(i, _)| i)
.unwrap_or(remaining_text.len()); .unwrap_or(remaining_text.len());
let (first, rest) = remaining_text.split_at(split_point); let (first, rest) = remaining_text.split_at(split_point);
content.push(Line::from(vec![ content.push(Line::from(vec![
Span::from(indent), Span::from(indent),
Span::styled( Span::styled(first.to_string(), Style::default().fg(Color::White)),
first.to_string(),
Style::default().fg(Color::White),
),
])); ]));
remaining_text = rest.to_string(); remaining_text = rest.to_string();
} else { } else {
@ -205,8 +178,8 @@ impl Content {
content.extend(vec![ content.extend(vec![
Line::from(""), Line::from(""),
Line::from( Line::from(
" I am also a fan of the 8 bit aesthetic and think seals are \ " I am also a fan of the 8 bit aesthetic and think seals are super adorable \
super adorable :3", :3",
), ),
]); ]);
@ -230,10 +203,7 @@ impl Content {
} }
impl Component for Content { impl Component for Content {
fn register_action_handler( fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
&mut self,
tx: UnboundedSender<Action>,
) -> Result<()> {
self.command_tx = Some(tx); self.command_tx = Some(tx);
Ok(()) Ok(())
} }
@ -263,9 +233,7 @@ impl Component for Content {
// Create the border lines // Create the border lines
let mut border_top = Line::default(); let mut border_top = Line::default();
border_top border_top.spans.push(Span::styled("", Style::default().fg(Color::DarkGray)));
.spans
.push(Span::styled("", Style::default().fg(Color::DarkGray)));
let devcomp_width = 13; let devcomp_width = 13;
border_top.spans.push(Span::styled( border_top.spans.push(Span::styled(
@ -277,17 +245,11 @@ impl Component for Content {
let mut current_pos = 1 + devcomp_width; let mut current_pos = 1 + devcomp_width;
for (i, &tab) in tabs.iter().enumerate() { for (i, &tab) in tabs.iter().enumerate() {
let (char, style) = let (char, style) = if i == self.selected_tab.load(Ordering::Relaxed) {
if i == self.selected_tab.load(Ordering::Relaxed) { ("", Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD))
( } else {
"", ("", Style::default().fg(Color::DarkGray))
Style::default() };
.fg(Color::Magenta)
.add_modifier(Modifier::BOLD),
)
} else {
("", Style::default().fg(Color::DarkGray))
};
let default_style = Style::default().fg(Color::DarkGray); let default_style = Style::default().fg(Color::DarkGray);
@ -305,19 +267,15 @@ impl Component for Content {
Style::default().fg(Color::DarkGray), Style::default().fg(Color::DarkGray),
)); ));
border_top border_top.spans.push(Span::styled("", Style::default().fg(Color::DarkGray)));
.spans
.push(Span::styled("", Style::default().fg(Color::DarkGray)));
let border_bottom = Line::from(Span::styled( let border_bottom = Line::from(Span::styled(
"".to_owned() + &"".repeat(area.width as usize - 2) + "", "".to_owned() + &"".repeat(area.width as usize - 2) + "",
Style::default().fg(Color::DarkGray), Style::default().fg(Color::DarkGray),
)); ));
let border_left = let border_left = Span::styled("", Style::default().fg(Color::DarkGray));
Span::styled("", Style::default().fg(Color::DarkGray)); let border_right = Span::styled("", Style::default().fg(Color::DarkGray));
let border_right =
Span::styled("", Style::default().fg(Color::DarkGray));
// Render the content // Render the content
let content_widget = Paragraph::new(content) let content_widget = Paragraph::new(content)
@ -342,12 +300,7 @@ impl Component for Content {
frame.render_widget( frame.render_widget(
Paragraph::new(border_bottom), Paragraph::new(border_bottom),
Rect { Rect { x: area.x, y: area.y + area.height - 1, width: area.width, height: 1 },
x: area.x,
y: area.y + area.height - 1,
width: area.width,
height: 1,
},
); );
for i in 1..area.height - 1 { for i in 1..area.height - 1 {
@ -358,12 +311,7 @@ impl Component for Content {
frame.render_widget( frame.render_widget(
Paragraph::new(Line::from(border_right.clone())), Paragraph::new(Line::from(border_right.clone())),
Rect { Rect { x: area.x + area.width - 1, y: area.y + i, width: 1, height: 1 },
x: area.x + area.width - 1,
y: area.y + i,
width: 1,
height: 1,
},
); );
} }

View file

@ -1,8 +1,7 @@
use chrono::DateTime; use chrono::DateTime;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use ratatui::style::{Color, Modifier, Style}; use ratatui::prelude::*;
use ratatui::text::{Line, Span}; use ratatui::widgets::*;
use ratatui::widgets::{Block, Borders, List, ListItem, ListState};
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use crate::action::Action; use crate::action::Action;
@ -100,8 +99,13 @@ impl Component for SelectionList<Post> {
]; ];
let subtitle_span = Span::raw( let subtitle_span = Span::raw(
[" ", post.subtitle.as_ref().unwrap_or(&super::truncate(post.content.as_ref(), 40))] [
.concat(), " ",
post.subtitle
.as_ref()
.unwrap_or(&super::truncate(post.content.as_ref(), 40)),
]
.concat(),
); );
list_content.push(Line::from([line_format.as_slice(), &[subtitle_span]].concat())); list_content.push(Line::from([line_format.as_slice(), &[subtitle_span]].concat()));

View file

@ -19,10 +19,7 @@ pub struct Tabs {
} }
impl Tabs { impl Tabs {
pub fn new( pub fn new(tabs: Vec<&'static str>, selected_tab: Arc<AtomicUsize>) -> Self {
tabs: Vec<&'static str>,
selected_tab: Arc<AtomicUsize>,
) -> Self {
Self { tabs, selected_tab, ..Default::default() } Self { tabs, selected_tab, ..Default::default() }
} }
@ -44,10 +41,7 @@ impl Tabs {
} }
impl Component for Tabs { impl Component for Tabs {
fn register_action_handler( fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
&mut self,
tx: UnboundedSender<Action>,
) -> Result<()> {
self.command_tx = Some(tx); self.command_tx = Some(tx);
Ok(()) Ok(())
} }
@ -84,17 +78,13 @@ impl Component for Tabs {
Style::default().fg(Color::DarkGray), Style::default().fg(Color::DarkGray),
)); ));
tab_lines[1] tab_lines[1].spans.push(Span::styled("", Style::default().fg(Color::DarkGray)));
.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] tab_lines[1].spans.push(Span::styled("", Style::default().fg(Color::DarkGray)));
.spans
.push(Span::styled("", Style::default().fg(Color::DarkGray)));
} }
let tabs_widget = Paragraph::new(tab_lines) let tabs_widget =
.block(Block::default().borders(Borders::NONE)); Paragraph::new(tab_lines).block(Block::default().borders(Borders::NONE));
frame.render_widget( frame.render_widget(
tabs_widget, tabs_widget,

View file

@ -38,16 +38,11 @@ pub struct Config {
} }
lazy_static! { lazy_static! {
pub static ref PROJECT_NAME: String = pub static ref PROJECT_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase().to_string();
env!("CARGO_CRATE_NAME").to_uppercase().to_string();
pub static ref DATA_FOLDER: Option<PathBuf> = pub static ref DATA_FOLDER: Option<PathBuf> =
env::var(format!("{}_DATA", PROJECT_NAME.clone())) env::var(format!("{}_DATA", PROJECT_NAME.clone())).ok().map(PathBuf::from);
.ok()
.map(PathBuf::from);
pub static ref CONFIG_FOLDER: Option<PathBuf> = pub static ref CONFIG_FOLDER: Option<PathBuf> =
env::var(format!("{}_CONFIG", PROJECT_NAME.clone())) env::var(format!("{}_CONFIG", PROJECT_NAME.clone())).ok().map(PathBuf::from);
.ok()
.map(PathBuf::from);
} }
impl Config { impl Config {
@ -68,19 +63,15 @@ impl Config {
]; ];
let mut found_config = false; let mut found_config = false;
for (file, format) in &config_files { for (file, format) in &config_files {
let source = config::File::from(config_dir.join(file)) let source =
.format(*format) config::File::from(config_dir.join(file)).format(*format).required(false);
.required(false);
builder = builder.add_source(source); builder = builder.add_source(source);
if config_dir.join(file).exists() { if config_dir.join(file).exists() {
found_config = true found_config = true
} }
} }
if !found_config { if !found_config {
error!( error!("No configuration file found. Application may not behave as expected");
"No configuration file found. Application may not behave as \
expected"
);
} }
let mut cfg: Self = builder.build()?.try_deserialize()?; let mut cfg: Self = builder.build()?.try_deserialize()?;
@ -135,18 +126,14 @@ impl<'de> Deserialize<'de> for KeyBindings {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: Deserializer<'de>, { D: Deserializer<'de>, {
let parsed_map = HashMap::<Mode, HashMap<String, Action>>::deserialize( let parsed_map = HashMap::<Mode, HashMap<String, Action>>::deserialize(deserializer)?;
deserializer,
)?;
let keybindings = parsed_map let keybindings = parsed_map
.into_iter() .into_iter()
.map(|(mode, inner_map)| { .map(|(mode, inner_map)| {
let converted_inner_map = inner_map let converted_inner_map = inner_map
.into_iter() .into_iter()
.map(|(key_str, cmd)| { .map(|(key_str, cmd)| (parse_key_sequence(&key_str).unwrap(), cmd))
(parse_key_sequence(&key_str).unwrap(), cmd)
})
.collect(); .collect();
(mode, converted_inner_map) (mode, converted_inner_map)
}) })
@ -301,9 +288,7 @@ pub fn key_event_to_string(key_event: &KeyEvent) -> String {
} }
pub fn parse_key_sequence(raw: &str) -> Result<Vec<KeyEvent>, String> { pub fn parse_key_sequence(raw: &str) -> Result<Vec<KeyEvent>, String> {
if raw.chars().filter(|c| *c == '>').count() if raw.chars().filter(|c| *c == '>').count() != raw.chars().filter(|c| *c == '<').count() {
!= raw.chars().filter(|c| *c == '<').count()
{
return Err(format!("Unable to parse `{}`", raw)); return Err(format!("Unable to parse `{}`", raw));
} }
let raw = if !raw.contains("><") { let raw = if !raw.contains("><") {
@ -336,9 +321,7 @@ impl<'de> Deserialize<'de> for Styles {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: Deserializer<'de>, { D: Deserializer<'de>, {
let parsed_map = HashMap::<Mode, HashMap<String, String>>::deserialize( let parsed_map = HashMap::<Mode, HashMap<String, String>>::deserialize(deserializer)?;
deserializer,
)?;
let styles = parsed_map let styles = parsed_map
.into_iter() .into_iter()
@ -405,16 +388,12 @@ fn parse_color(s: &str) -> Option<Color> {
let c = s.trim_start_matches("color").parse::<u8>().unwrap_or_default(); let c = s.trim_start_matches("color").parse::<u8>().unwrap_or_default();
Some(Color::Indexed(c)) Some(Color::Indexed(c))
} else if s.contains("gray") { } else if s.contains("gray") {
let c = 232 let c = 232 + s.trim_start_matches("gray").parse::<u8>().unwrap_or_default();
+ s.trim_start_matches("gray").parse::<u8>().unwrap_or_default();
Some(Color::Indexed(c)) Some(Color::Indexed(c))
} else if s.contains("rgb") { } else if s.contains("rgb") {
let red = let red = (s.as_bytes()[3] as char).to_digit(10).unwrap_or_default() as u8;
(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 green = let blue = (s.as_bytes()[5] as char).to_digit(10).unwrap_or_default() as u8;
(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; let c = 16 + red * 36 + green * 6 + blue;
Some(Color::Indexed(c)) Some(Color::Indexed(c))
} else if s == "bold black" { } else if s == "bold black" {
@ -487,8 +466,7 @@ mod tests {
#[test] #[test]
fn test_process_color_string() { fn test_process_color_string() {
let (color, modifiers) = let (color, modifiers) = process_color_string("underline bold inverse gray");
process_color_string("underline bold inverse gray");
assert_eq!(color, "gray"); assert_eq!(color, "gray");
assert!(modifiers.contains(Modifier::UNDERLINED)); assert!(modifiers.contains(Modifier::UNDERLINED));
assert!(modifiers.contains(Modifier::BOLD)); assert!(modifiers.contains(Modifier::BOLD));
@ -562,18 +540,12 @@ mod tests {
fn test_multiple_modifiers() { fn test_multiple_modifiers() {
assert_eq!( assert_eq!(
parse_key_event("ctrl-alt-a").unwrap(), parse_key_event("ctrl-alt-a").unwrap(),
KeyEvent::new( KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL | KeyModifiers::ALT)
KeyCode::Char('a'),
KeyModifiers::CONTROL | KeyModifiers::ALT
)
); );
assert_eq!( assert_eq!(
parse_key_event("ctrl-shift-enter").unwrap(), parse_key_event("ctrl-shift-enter").unwrap(),
KeyEvent::new( KeyEvent::new(KeyCode::Enter, KeyModifiers::CONTROL | KeyModifiers::SHIFT)
KeyCode::Enter,
KeyModifiers::CONTROL | KeyModifiers::SHIFT
)
); );
} }

View file

@ -21,9 +21,8 @@ pub fn init() -> Result<()> {
let metadata = metadata!(); let metadata = metadata!();
let file_path = handle_dump(&metadata, panic_info); let file_path = handle_dump(&metadata, panic_info);
// prints human-panic message // prints human-panic message
print_msg(file_path, &metadata).expect( print_msg(file_path, &metadata)
"human-panic: printing error message to console failed", .expect("human-panic: printing error message to console failed");
);
eprintln!("{}", panic_hook.panic_report(panic_info)); // prints color-eyre stack trace to stderr eprintln!("{}", panic_hook.panic_report(panic_info)); // prints color-eyre stack trace to stderr
} }
let msg = format!("{}", panic_hook.panic_report(panic_info)); let msg = format!("{}", panic_hook.panic_report(panic_info));

View file

@ -99,15 +99,9 @@ impl KeyCodeExt for KeyCode {
fn into_key_event(self) -> KeyEvent { fn into_key_event(self) -> KeyEvent {
match self { match self {
Self::Char(CTRL_C) => { Self::Char(CTRL_C) => KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL),
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_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()), other => KeyEvent::new(other, KeyModifiers::empty()),
} }
} }

View file

@ -10,11 +10,7 @@ macro_rules! embedded_route {
($path:expr) => { ($path:expr) => {
match WebLandingServer::get($path) { match WebLandingServer::get($path) {
Some(content) => HttpResponse::Ok() Some(content) => HttpResponse::Ok()
.content_type( .content_type(mime_guess::from_path($path).first_or_octet_stream().as_ref())
mime_guess::from_path($path)
.first_or_octet_stream()
.as_ref(),
)
.body(content.data.into_owned()), .body(content.data.into_owned()),
None => HttpResponse::NotFound().body("404 Not Found"), None => HttpResponse::NotFound().body("404 Not Found"),
} }
@ -45,11 +41,7 @@ impl WebLandingServer {
tracing::info!("Web server listening on {}", addr); tracing::info!("Web server listening on {}", addr);
HttpServer::new(|| { HttpServer::new(|| {
// TODO: register a default service for a nicer 404 page // TODO: register a default service for a nicer 404 page
App::new() App::new().service(index).service(favicon).service(dist).wrap(Logger::default())
.service(index)
.service(favicon)
.service(dist)
.wrap(Logger::default())
}) })
.bind(addr)? .bind(addr)?
.run() .run()

View file

@ -30,20 +30,16 @@ pub fn init() -> Result<()> {
// //
// Stage 1: Construct base filter // Stage 1: Construct base filter
let env_filter = EnvFilter::builder().with_default_directive( let env_filter = EnvFilter::builder().with_default_directive(if cfg!(debug_assertions) {
if cfg!(debug_assertions) { tracing::Level::DEBUG.into()
tracing::Level::DEBUG.into() } else {
} else { tracing::Level::INFO.into()
tracing::Level::INFO.into() });
},
);
// Stage 2: Attempt to read from {RUST|CRATE_NAME}_LOG env var or ignore // Stage 2: Attempt to read from {RUST|CRATE_NAME}_LOG env var or ignore
let env_filter = env_filter let env_filter = env_filter
.try_from_env() .try_from_env()
.unwrap_or_else(|_| { .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()
})
.add_directive("russh::cipher=info".parse().unwrap()) .add_directive("russh::cipher=info".parse().unwrap())
.add_directive("tui_markdown=info".parse().unwrap()); .add_directive("tui_markdown=info".parse().unwrap());
@ -86,9 +82,7 @@ pub fn init() -> Result<()> {
let layer = layer let layer = layer
.compact() .compact()
.without_time() .without_time()
.with_span_events( .with_span_events(tracing_subscriber::fmt::format::FmtSpan::NONE)
tracing_subscriber::fmt::format::FmtSpan::NONE,
)
.with_target(false) .with_target(false)
.with_thread_ids(false); .with_thread_ids(false);
layer layer

View file

@ -50,10 +50,8 @@ async fn main() -> Result<()> {
crate::logging::init()?; crate::logging::init()?;
let _ = *OPTIONS; // force clap to run by evaluating it let _ = *OPTIONS; // force clap to run by evaluating it
let ssh_socket_addr = let ssh_socket_addr = SSH_SOCKET_ADDR.ok_or(eyre!("Invalid host IP provided"))?;
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 web_server_addr =
WEB_SERVER_ADDR.ok_or(eyre!("Invalid host IP provided"))?;
tokio::select! { tokio::select! {
ssh_res = SshServer::start(ssh_socket_addr, ssh_config()) => ssh_res, ssh_res = SshServer::start(ssh_socket_addr, ssh_config()) => ssh_res,
@ -68,9 +66,9 @@ pub fn host_ip() -> Result<[u8; 4]> {
.host .host
.splitn(4, ".") .splitn(4, ".")
.map(|octet_str| { .map(|octet_str| {
octet_str.parse::<u8>().map_err(|_| { octet_str
eyre!("Octet component out of range (expected u8)") .parse::<u8>()
}) .map_err(|_| eyre!("Octet component out of range (expected u8)"))
}) })
.collect::<Result<Vec<u8>>>()?, .collect::<Result<Vec<u8>>>()?,
) )

View file

@ -213,10 +213,7 @@ impl Handler for SshSession {
session: &mut Session, session: &mut Session,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
tracing::info!("PTY requested by terminal: {term}"); tracing::info!("PTY requested by terminal: {term}");
tracing::debug!( tracing::debug!("dims: {col_width} * {row_height}, pixel: {pix_width} * {pix_height}");
"dims: {col_width} * {row_height}, pixel: {pix_width} * \
{pix_height}"
);
if pix_width != 0 && pix_height != 0 { if pix_width != 0 && pix_height != 0 {
self.terminal_info.write().await.set_font_size(( self.terminal_info.write().await.set_font_size((

View file

@ -49,9 +49,7 @@ impl Backend for SshBackend {
self.inner.show_cursor() self.inner.show_cursor()
} }
fn get_cursor_position( fn get_cursor_position(&mut self) -> io::Result<ratatui::prelude::Position> {
&mut self,
) -> io::Result<ratatui::prelude::Position> {
self.inner.get_cursor_position() self.inner.get_cursor_position()
} }

View file

@ -7,8 +7,8 @@ use backend::SshBackend;
use color_eyre::Result; use color_eyre::Result;
use crossterm::cursor; use crossterm::cursor;
use crossterm::event::{ use crossterm::event::{
DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
EnableMouseCapture, KeyEvent, MouseEvent, KeyEvent, MouseEvent,
}; };
use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen}; use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -118,10 +118,8 @@ impl Tui {
tick_rate: f64, tick_rate: f64,
frame_rate: f64, frame_rate: f64,
) { ) {
let mut tick_interval = let mut tick_interval = interval(Duration::from_secs_f64(1.0 / tick_rate));
interval(Duration::from_secs_f64(1.0 / tick_rate)); let mut render_interval = interval(Duration::from_secs_f64(1.0 / frame_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 // 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");
@ -162,14 +160,9 @@ impl Tui {
}; };
if timeout(attempt_timeout, self.await_shutdown()).await.is_err() { if timeout(attempt_timeout, self.await_shutdown()).await.is_err() {
timeout(attempt_timeout, abort_shutdown).await.inspect_err( timeout(attempt_timeout, abort_shutdown).await.inspect_err(|_| {
|_| { error!("Failed to abort task in 100 milliseconds for unknown reason")
error!( })?;
"Failed to abort task in 100 milliseconds for unknown \
reason"
)
},
)?;
} }
Ok(()) Ok(())
@ -178,11 +171,7 @@ impl Tui {
pub fn enter(&mut self) -> Result<()> { pub fn enter(&mut self) -> Result<()> {
let mut term = self.terminal.try_lock()?; let mut term = self.terminal.try_lock()?;
// crossterm::terminal::enable_raw_mode()?; // TODO: Enable raw mode for pty // crossterm::terminal::enable_raw_mode()?; // TODO: Enable raw mode for pty
crossterm::execute!( crossterm::execute!(term.backend_mut(), EnterAlternateScreen, cursor::Hide)?;
term.backend_mut(),
EnterAlternateScreen,
cursor::Hide
)?;
if self.mouse { if self.mouse {
crossterm::execute!(term.backend_mut(), EnableMouseCapture)?; crossterm::execute!(term.backend_mut(), EnableMouseCapture)?;
@ -212,11 +201,7 @@ impl Tui {
crossterm::execute!(term.backend_mut(), DisableMouseCapture)?; crossterm::execute!(term.backend_mut(), DisableMouseCapture)?;
} }
crossterm::execute!( crossterm::execute!(term.backend_mut(), LeaveAlternateScreen, cursor::Show)?;
term.backend_mut(),
LeaveAlternateScreen,
cursor::Show
)?;
Ok(()) Ok(())
} }
@ -231,8 +216,7 @@ impl Tui {
// Update the status and initialize a cancellation token // Update the status and initialize a cancellation token
let token = Arc::new(CancellationToken::new()); let token = Arc::new(CancellationToken::new());
let suspension = Arc::new(Mutex::new(())); let suspension = Arc::new(Mutex::new(()));
*self.status.write().await = *self.status.write().await = TuiStatus::Suspended(Arc::clone(&suspension));
TuiStatus::Suspended(Arc::clone(&suspension));
// Spawn a task holding on the lock until a notification interrupts it // Spawn a task holding on the lock until a notification interrupts it
let status = Arc::clone(&self.status); let status = Arc::clone(&self.status);
@ -263,9 +247,7 @@ impl Drop for Tui {
block_in_place(|| { block_in_place(|| {
let handle = Handle::current(); let handle = Handle::current();
let _ = handle.block_on(async { let _ = handle.block_on(async {
self.exit() self.exit().await.inspect_err(|err| error!("Failed to exit Tui: {err}"))
.await
.inspect_err(|err| error!("Failed to exit Tui: {err}"))
}); });
}) })
} }

View file

@ -1,10 +1,8 @@
use std::default::Default; use std::default::Default;
use default_variant::default; use default_variant::default;
use ratatui_image::{ use ratatui_image::picker::{Capability, ProtocolType};
picker::{Capability, ProtocolType}, use ratatui_image::FontSize;
FontSize,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::Display; use strum::Display;