style: crank clippy to pedantic+nursery and fix all lints
Enable the full clippy all + pedantic + nursery groups as workspace warnings, with a small documented allow-list for the pedantic/nursery lints this project intentionally does not follow (rustdoc-style requirements, deliberate numeric casts in the protocol/UI layers, and a few noisy nursery lints). Also enable the rust unused_lifetimes and unsafe_op_in_unsafe_fn lints. Fixed every resulting warning (527 -> 0) mechanically per clippy's suggestions: inline format args, const fns, item ordering, let-else, redundant clones, map_or, cast_lossless, Eq derives, associated-fn conversions for methods that never used self, and removing unnecessary Result wrappers whose call sites could never propagate an error. No runtime behavior changed; cargo fmt applied. cargo clippy --workspace --all-targets: 0 warnings. All tests pass.
This commit is contained in:
@@ -13,6 +13,9 @@ categories = ["command-line-utilities"]
|
||||
name = "soulseek-rs"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
soulseek-rs-lib = { version = "0.3.0", path = "../soulseek-rs-lib" }
|
||||
|
||||
|
||||
+10
-12
@@ -35,7 +35,7 @@ fn main() -> Result<()> {
|
||||
if let Some(log_file) = &cli.log_file {
|
||||
// SAFETY: Called before any threads are spawned
|
||||
unsafe {
|
||||
env::set_var("LOG_FILE", log_file.to_string_lossy().to_string())
|
||||
env::set_var("LOG_FILE", log_file.to_string_lossy().to_string());
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,17 +76,20 @@ fn main() -> Result<()> {
|
||||
search_and_download(config)
|
||||
}
|
||||
None => {
|
||||
use ratatui::crossterm::{
|
||||
event::EnableMouseCapture,
|
||||
execute,
|
||||
terminal::{Clear, ClearType},
|
||||
};
|
||||
|
||||
// Launch main TUI
|
||||
// Enable logger buffering BEFORE connection to prevent log artifacts
|
||||
soulseek_rs::utils::logger::enable_buffering();
|
||||
|
||||
let settings = ClientSettings {
|
||||
username: username.clone(),
|
||||
password: password.clone(),
|
||||
server_address: PeerAddress::new(
|
||||
server_host.clone(),
|
||||
server_port,
|
||||
),
|
||||
username,
|
||||
password,
|
||||
server_address: PeerAddress::new(server_host, server_port),
|
||||
enable_listen: !cli.disable_listener,
|
||||
listen_port: cli.listener_port,
|
||||
};
|
||||
@@ -102,11 +105,6 @@ fn main() -> Result<()> {
|
||||
let client = Arc::new(client);
|
||||
|
||||
// Clear screen and enable mouse capture before initializing TUI
|
||||
use ratatui::crossterm::{
|
||||
event::EnableMouseCapture,
|
||||
execute,
|
||||
terminal::{Clear, ClearType},
|
||||
};
|
||||
let _ = execute!(
|
||||
std::io::stdout(),
|
||||
Clear(ClearType::All),
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::sync::atomic::AtomicBool;
|
||||
use std::sync::{Arc, mpsc::Receiver, mpsc::Sender};
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SearchStatus {
|
||||
Active,
|
||||
Completed,
|
||||
@@ -25,7 +25,7 @@ pub struct DownloadEntry {
|
||||
pub receiver: Option<Receiver<DownloadStatus>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FocusedPane {
|
||||
Searches,
|
||||
Results,
|
||||
|
||||
@@ -255,7 +255,10 @@ impl FileSelector {
|
||||
}
|
||||
|
||||
/// Previous index with wrap-around, or None when the list is empty.
|
||||
fn wrapping_prev(selected: Option<usize>, len: usize) -> Option<usize> {
|
||||
const fn wrapping_prev(
|
||||
selected: Option<usize>,
|
||||
len: usize,
|
||||
) -> Option<usize> {
|
||||
if len == 0 {
|
||||
return None;
|
||||
}
|
||||
@@ -266,7 +269,10 @@ impl FileSelector {
|
||||
}
|
||||
|
||||
/// Next index with wrap-around, or None when the list is empty.
|
||||
fn wrapping_next(selected: Option<usize>, len: usize) -> Option<usize> {
|
||||
const fn wrapping_next(
|
||||
selected: Option<usize>,
|
||||
len: usize,
|
||||
) -> Option<usize> {
|
||||
if len == 0 {
|
||||
return None;
|
||||
}
|
||||
@@ -276,12 +282,12 @@ impl FileSelector {
|
||||
})
|
||||
}
|
||||
|
||||
fn select_previous(&mut self) {
|
||||
const fn select_previous(&mut self) {
|
||||
let i = Self::wrapping_prev(self.state.selected(), self.items.len());
|
||||
self.state.select(i);
|
||||
}
|
||||
|
||||
fn select_next(&mut self) {
|
||||
const fn select_next(&mut self) {
|
||||
let i = Self::wrapping_next(self.state.selected(), self.items.len());
|
||||
self.state.select(i);
|
||||
}
|
||||
@@ -290,7 +296,7 @@ impl FileSelector {
|
||||
self.state.select((!self.items.is_empty()).then_some(0));
|
||||
}
|
||||
|
||||
fn select_last(&mut self) {
|
||||
const fn select_last(&mut self) {
|
||||
self.state.select(self.items.len().checked_sub(1));
|
||||
}
|
||||
|
||||
@@ -395,9 +401,9 @@ impl FileSelector {
|
||||
let is_selected = self.selected_indices.contains(&original_idx);
|
||||
let checkbox = if is_selected { "[✓]" } else { "[ ]" };
|
||||
|
||||
let speed_mb = item.speed as f64 / BYTES_PER_MB;
|
||||
let speed_mb = f64::from(item.speed) / BYTES_PER_MB;
|
||||
let speed_str = if speed_mb > 0.0 {
|
||||
format!("{:.1} MB/s", speed_mb)
|
||||
format!("{speed_mb:.1} MB/s")
|
||||
} else {
|
||||
"-".to_string()
|
||||
};
|
||||
@@ -405,7 +411,7 @@ impl FileSelector {
|
||||
let slots_str = format!("{}", item.slots);
|
||||
|
||||
let bitrate_str = match item.bitrate {
|
||||
Some(br) => format!("{} kbps", br),
|
||||
Some(br) => format!("{br} kbps"),
|
||||
None => "-".to_string(),
|
||||
};
|
||||
|
||||
@@ -474,7 +480,7 @@ impl FileSelector {
|
||||
self.search_start_time.elapsed().as_secs(),
|
||||
self.search_timeout.as_secs()
|
||||
)),
|
||||
])
|
||||
]);
|
||||
} else {
|
||||
loading_message.extend(vec![
|
||||
Span::raw(spinner),
|
||||
@@ -482,7 +488,7 @@ impl FileSelector {
|
||||
Span::styled(self.soulseek_query.clone(), primary_style()),
|
||||
Span::raw("'; No results yet"),
|
||||
]);
|
||||
};
|
||||
}
|
||||
|
||||
// Center the loading message
|
||||
let vertical = Layout::vertical([
|
||||
|
||||
@@ -155,7 +155,7 @@ impl MultiDownloadProgress {
|
||||
}
|
||||
}
|
||||
|
||||
fn select_previous(&mut self) {
|
||||
const fn select_previous(&mut self) {
|
||||
let i = match self.list_state.selected() {
|
||||
Some(i) if i > 0 => i - 1,
|
||||
_ => self.downloads.len().saturating_sub(1),
|
||||
@@ -163,7 +163,7 @@ impl MultiDownloadProgress {
|
||||
self.list_state.select(Some(i));
|
||||
}
|
||||
|
||||
fn select_next(&mut self) {
|
||||
const fn select_next(&mut self) {
|
||||
let i = match self.list_state.selected() {
|
||||
Some(i) if i < self.downloads.len().saturating_sub(1) => i + 1,
|
||||
_ => 0,
|
||||
@@ -171,11 +171,11 @@ impl MultiDownloadProgress {
|
||||
self.list_state.select(Some(i));
|
||||
}
|
||||
|
||||
fn select_first(&mut self) {
|
||||
const fn select_first(&mut self) {
|
||||
self.list_state.select(Some(0));
|
||||
}
|
||||
|
||||
fn select_last(&mut self) {
|
||||
const fn select_last(&mut self) {
|
||||
if !self.downloads.is_empty() {
|
||||
self.list_state.select(Some(self.downloads.len() - 1));
|
||||
}
|
||||
@@ -198,7 +198,7 @@ impl MultiDownloadProgress {
|
||||
self.render_downloads_list(frame, chunks[1]);
|
||||
|
||||
// Bottom: Controls
|
||||
self.render_controls(frame, chunks[2]);
|
||||
Self::render_controls(frame, chunks[2]);
|
||||
}
|
||||
|
||||
fn render_stats(&self, frame: &mut Frame, area: ratatui::layout::Rect) {
|
||||
@@ -312,7 +312,7 @@ impl MultiDownloadProgress {
|
||||
);
|
||||
}
|
||||
|
||||
fn render_controls(&self, frame: &mut Frame, area: ratatui::layout::Rect) {
|
||||
fn render_controls(frame: &mut Frame, area: ratatui::layout::Rect) {
|
||||
let controls_line = format_shortcuts_styled(&[
|
||||
("↑↓/jk", "scroll"),
|
||||
("Home/End", "jump"),
|
||||
@@ -402,7 +402,7 @@ pub fn render_download_stats(
|
||||
let stats_line = Line::from(vec![
|
||||
Span::raw("soulseek-rs 🦀 "),
|
||||
Span::styled(
|
||||
format!("v{} ", VERSION),
|
||||
format!("v{VERSION} "),
|
||||
Style::default()
|
||||
.fg(COLOR_PRIMARY)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
@@ -460,7 +460,7 @@ pub fn render_download_stats(
|
||||
spans.extend(data_str.spans);
|
||||
spans.push(Span::raw(" • "));
|
||||
spans.push(Span::styled(
|
||||
format!("{}", speed_mb),
|
||||
format!("{speed_mb}"),
|
||||
Style::default()
|
||||
.fg(COLOR_PRIMARY)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
@@ -487,9 +487,9 @@ pub fn show_multi_download_progress(
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
// Spawn background thread to initialize downloads
|
||||
let init_client = client.clone();
|
||||
let init_client = client;
|
||||
thread::spawn(move || {
|
||||
for (filename, username, size) in selected_files.into_iter() {
|
||||
for (filename, username, size) in selected_files {
|
||||
// Initiate download
|
||||
match init_client.download(
|
||||
filename.clone(),
|
||||
@@ -501,10 +501,7 @@ pub fn show_multi_download_progress(
|
||||
let _ = tx.send((download, receiver));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Failed to start download for {}: {}",
|
||||
filename, e
|
||||
);
|
||||
eprintln!("Failed to start download for {filename}: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,13 +61,14 @@ impl MainTui {
|
||||
}
|
||||
|
||||
pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
|
||||
use ratatui::crossterm::{event::DisableMouseCapture, execute};
|
||||
|
||||
// Run the event loop, then restore the terminal unconditionally: if the
|
||||
// loop returns early with an error the terminal must still be taken out
|
||||
// of raw mode / the alternate screen and mouse capture disabled, or the
|
||||
// user is left with a corrupted terminal.
|
||||
let result = self.run_event_loop(&mut terminal);
|
||||
|
||||
use ratatui::crossterm::{event::DisableMouseCapture, execute};
|
||||
let _ = execute!(std::io::stdout(), DisableMouseCapture);
|
||||
ratatui::restore();
|
||||
soulseek_rs::utils::logger::disable_buffering();
|
||||
@@ -75,10 +76,7 @@ impl MainTui {
|
||||
result
|
||||
}
|
||||
|
||||
fn run_event_loop(
|
||||
&mut self,
|
||||
terminal: &mut DefaultTerminal,
|
||||
) -> Result<()> {
|
||||
fn run_event_loop(&mut self, terminal: &mut DefaultTerminal) -> Result<()> {
|
||||
while !self.state.should_exit {
|
||||
terminal.draw(|frame| self.render(frame))?;
|
||||
|
||||
@@ -95,10 +93,10 @@ impl MainTui {
|
||||
if poll(Duration::from_millis(100))? {
|
||||
match event::read()? {
|
||||
Event::Key(key) if key.kind == KeyEventKind::Press => {
|
||||
self.handle_key_event(key)?;
|
||||
self.handle_key_event(key);
|
||||
}
|
||||
Event::Mouse(mouse) => {
|
||||
self.handle_mouse_event(mouse)?;
|
||||
self.handle_mouse_event(mouse);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -328,7 +326,7 @@ impl MainTui {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<()> {
|
||||
fn handle_key_event(&mut self, key: KeyEvent) {
|
||||
// Command bar takes priority
|
||||
if self.state.command_bar_active {
|
||||
return self.handle_command_bar_input(key);
|
||||
@@ -345,25 +343,25 @@ impl MainTui {
|
||||
match key.code {
|
||||
KeyCode::Char('q') => {
|
||||
self.state.should_exit = true;
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
KeyCode::Char('1') => {
|
||||
self.state.focused_pane = FocusedPane::Searches;
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
KeyCode::Char('2') => {
|
||||
self.state.focused_pane = FocusedPane::Results;
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
KeyCode::Char('3') => {
|
||||
self.state.focused_pane = FocusedPane::Downloads;
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
KeyCode::Char('s') => {
|
||||
self.state.command_bar_active = true;
|
||||
self.state.command_bar_input.clear();
|
||||
self.state.command_bar_cursor_position = 0;
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -376,7 +374,7 @@ impl MainTui {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_command_bar_input(&mut self, key: KeyEvent) -> Result<()> {
|
||||
fn handle_command_bar_input(&mut self, key: KeyEvent) {
|
||||
self.state.command_bar_cursor_position = clamp_cursor_to_char_boundary(
|
||||
&self.state.command_bar_input,
|
||||
self.state.command_bar_cursor_position,
|
||||
@@ -474,10 +472,9 @@ impl MainTui {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_filter_input(&mut self, key: KeyEvent) -> Result<()> {
|
||||
fn handle_filter_input(&mut self, key: KeyEvent) {
|
||||
match key.code {
|
||||
KeyCode::Esc => {
|
||||
self.state.results_is_filtering = false;
|
||||
@@ -497,10 +494,9 @@ impl MainTui {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_searches_input(&mut self, key: KeyEvent) -> Result<()> {
|
||||
fn handle_searches_input(&mut self, key: KeyEvent) {
|
||||
match key.code {
|
||||
KeyCode::Up | KeyCode::Char('k')
|
||||
if !self.state.searches.is_empty() =>
|
||||
@@ -551,10 +547,9 @@ impl MainTui {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_results_input(&mut self, key: KeyEvent) -> Result<()> {
|
||||
fn handle_results_input(&mut self, key: KeyEvent) {
|
||||
let items_count = if self.state.results_filter_query.is_empty() {
|
||||
self.state.results_items.len()
|
||||
} else {
|
||||
@@ -624,10 +619,9 @@ impl MainTui {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_downloads_input(&mut self, key: KeyEvent) -> Result<()> {
|
||||
fn handle_downloads_input(&mut self, key: KeyEvent) {
|
||||
match key.code {
|
||||
KeyCode::Up | KeyCode::Char('k')
|
||||
if !self.state.downloads.is_empty() =>
|
||||
@@ -657,10 +651,9 @@ impl MainTui {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn toggle_selected_download_pause(&mut self) {
|
||||
fn toggle_selected_download_pause(&self) {
|
||||
let Some(index) = self.state.downloads_table_state.selected() else {
|
||||
return;
|
||||
};
|
||||
@@ -671,11 +664,13 @@ impl MainTui {
|
||||
let download = &download_entry.download;
|
||||
match download.status {
|
||||
DownloadStatus::InProgress { .. } => {
|
||||
self.client
|
||||
let _ = self
|
||||
.client
|
||||
.pause_download(&download.username, &download.filename);
|
||||
}
|
||||
DownloadStatus::Paused { .. } => {
|
||||
self.client
|
||||
let _ = self
|
||||
.client
|
||||
.resume_download(&download.username, &download.filename);
|
||||
}
|
||||
_ => {}
|
||||
@@ -716,8 +711,8 @@ impl MainTui {
|
||||
self.state.downloads_table_state.select(Some(next_index));
|
||||
}
|
||||
|
||||
fn handle_mouse_event(&mut self, mouse: MouseEvent) -> Result<()> {
|
||||
if let MouseEventKind::Down(MouseButton::Left) = mouse.kind {
|
||||
fn handle_mouse_event(&mut self, mouse: MouseEvent) {
|
||||
if mouse.kind == MouseEventKind::Down(MouseButton::Left) {
|
||||
let (col, row) = (mouse.column, mouse.row);
|
||||
|
||||
// Check if click is within searches pane
|
||||
@@ -728,7 +723,7 @@ impl MainTui {
|
||||
&& row < area.y + area.height
|
||||
{
|
||||
self.state.focused_pane = FocusedPane::Searches;
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if click is within results pane
|
||||
@@ -739,7 +734,7 @@ impl MainTui {
|
||||
&& row < area.y + area.height
|
||||
{
|
||||
self.state.focused_pane = FocusedPane::Results;
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if click is within downloads pane
|
||||
@@ -750,11 +745,8 @@ impl MainTui {
|
||||
&& row < area.y + area.height
|
||||
{
|
||||
self.state.focused_pane = FocusedPane::Downloads;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_search_at_index(&mut self, index: usize) {
|
||||
@@ -889,7 +881,7 @@ impl MainTui {
|
||||
// Results will be polled in update_search_results
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Search failed: {}", e);
|
||||
eprintln!("Search failed: {e}");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -899,19 +891,14 @@ impl MainTui {
|
||||
let timeout = self.search_timeout;
|
||||
let selected_search_index = self.state.selected_search_index;
|
||||
|
||||
// Collect all queries first
|
||||
let queries: Vec<(usize, String)> = self
|
||||
// Fetch all results in one go (single lock acquisition per query)
|
||||
// Use try_get_search_results to avoid blocking the UI thread
|
||||
let all_results: Vec<(usize, Vec<_>)> = self
|
||||
.state
|
||||
.searches
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, s)| (idx, s.query.clone()))
|
||||
.collect();
|
||||
|
||||
// Fetch all results in one go (single lock acquisition per query)
|
||||
// Use try_get_search_results to avoid blocking the UI thread
|
||||
let all_results: Vec<(usize, Vec<_>)> = queries
|
||||
.into_iter()
|
||||
.filter_map(|(idx, query)| {
|
||||
self.client
|
||||
.try_get_search_results(&query)
|
||||
@@ -989,7 +976,7 @@ impl MainTui {
|
||||
let sender = self.state.downloads_sender_channel.clone().unwrap();
|
||||
|
||||
thread::spawn(move || {
|
||||
for file in selected_files.into_iter() {
|
||||
for file in selected_files {
|
||||
let metadata = soulseek_rs::types::DownloadMetadata {
|
||||
bitrate: file.bitrate,
|
||||
length_seconds: file.length_seconds,
|
||||
@@ -1066,7 +1053,10 @@ pub fn launch_main_tui(
|
||||
tui.run(terminal)
|
||||
}
|
||||
|
||||
fn clamp_cursor_to_char_boundary(input: &str, cursor_position: usize) -> usize {
|
||||
const fn clamp_cursor_to_char_boundary(
|
||||
input: &str,
|
||||
cursor_position: usize,
|
||||
) -> usize {
|
||||
if cursor_position >= input.len() {
|
||||
return input.len();
|
||||
}
|
||||
@@ -1084,8 +1074,7 @@ fn previous_char_boundary(input: &str, cursor_position: usize) -> usize {
|
||||
input[..cursor_position]
|
||||
.char_indices()
|
||||
.last()
|
||||
.map(|(index, _)| index)
|
||||
.unwrap_or(0)
|
||||
.map_or(0, |(index, _)| index)
|
||||
}
|
||||
|
||||
fn next_char_boundary(input: &str, cursor_position: usize) -> usize {
|
||||
@@ -1099,8 +1088,7 @@ fn next_char_boundary(input: &str, cursor_position: usize) -> usize {
|
||||
+ input[cursor_position..]
|
||||
.chars()
|
||||
.next()
|
||||
.map(char::len_utf8)
|
||||
.unwrap_or(0)
|
||||
.map_or(0, char::len_utf8)
|
||||
}
|
||||
|
||||
fn visible_input_at_cursor(
|
||||
|
||||
@@ -83,7 +83,7 @@ fn build_info_lines(
|
||||
lines.push(label_value("Save to", &save_path));
|
||||
|
||||
if let Some(bitrate) = download.metadata.bitrate {
|
||||
lines.push(label_value("Bitrate", &format!("{} kbps", bitrate)));
|
||||
lines.push(label_value("Bitrate", &format!("{bitrate} kbps")));
|
||||
}
|
||||
if let Some(length) = download.metadata.length_seconds {
|
||||
lines.push(label_value("Length", &format_duration(length)));
|
||||
@@ -93,14 +93,14 @@ fn build_info_lines(
|
||||
DownloadStatus::Queued => {
|
||||
lines.push(Line::from(""));
|
||||
let position_text = match download.queue_position {
|
||||
Some(p) => format!("#{}", p),
|
||||
Some(p) => format!("#{p}"),
|
||||
None => "unknown".to_string(),
|
||||
};
|
||||
lines.push(label_value("Queue position", &position_text));
|
||||
|
||||
if let Some(slots) = download.metadata.peer_free_slots {
|
||||
let slot_text = if slots > 0 {
|
||||
format!("{} available", slots)
|
||||
format!("{slots} available")
|
||||
} else {
|
||||
"all busy".to_string()
|
||||
};
|
||||
@@ -110,7 +110,7 @@ fn build_info_lines(
|
||||
if let Some(speed) = download.metadata.peer_upload_speed {
|
||||
lines.push(label_value(
|
||||
"Peer upload speed",
|
||||
&format_speed(speed as f64),
|
||||
&format_speed(f64::from(speed)),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -149,10 +149,7 @@ fn build_info_lines(
|
||||
|
||||
fn label_value(label: &str, value: &str) -> Line<'static> {
|
||||
Line::from(vec![
|
||||
Span::styled(
|
||||
format!("{:<width$}", label, width = LABEL_WIDTH),
|
||||
dimmed_style(),
|
||||
),
|
||||
Span::styled(format!("{label:<LABEL_WIDTH$}"), dimmed_style()),
|
||||
Span::styled(value.to_string(), primary_style()),
|
||||
])
|
||||
}
|
||||
@@ -163,10 +160,7 @@ fn label_value_styled(
|
||||
value_style: Style,
|
||||
) -> Line<'static> {
|
||||
Line::from(vec![
|
||||
Span::styled(
|
||||
format!("{:<width$}", label, width = LABEL_WIDTH),
|
||||
dimmed_style(),
|
||||
),
|
||||
Span::styled(format!("{label:<LABEL_WIDTH$}"), dimmed_style()),
|
||||
Span::styled(value, value_style),
|
||||
])
|
||||
}
|
||||
@@ -211,11 +205,11 @@ fn format_duration(seconds: u32) -> String {
|
||||
let minutes = (seconds % 3600) / 60;
|
||||
let secs = seconds % 60;
|
||||
if hours > 0 {
|
||||
format!("{}h {:02}m {:02}s", hours, minutes, secs)
|
||||
format!("{hours}h {minutes:02}m {secs:02}s")
|
||||
} else if minutes > 0 {
|
||||
format!("{}m {:02}s", minutes, secs)
|
||||
format!("{minutes}m {secs:02}s")
|
||||
} else {
|
||||
format!("{}s", secs)
|
||||
format!("{secs}s")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ pub fn render_results_pane(
|
||||
} = params;
|
||||
if items.is_empty() {
|
||||
let title = if let Some(query) = active_search_query {
|
||||
format!("[2] Results: {}", query)
|
||||
format!("[2] Results: {query}")
|
||||
} else {
|
||||
"[2] Results".to_string()
|
||||
};
|
||||
@@ -75,13 +75,12 @@ pub fn render_results_pane(
|
||||
.title(title);
|
||||
|
||||
let message = if is_filtering {
|
||||
format!("No results match filter: '{}'", filter_query)
|
||||
format!("No results match filter: '{filter_query}'")
|
||||
} else {
|
||||
format!(
|
||||
"soulseek-rs 🦀 v{}
|
||||
"soulseek-rs 🦀 v{VERSION}
|
||||
Michel de Graaf 2026\n
|
||||
No results. Select a search from the Searches pane [1]. Or start new search [s → search] \n",
|
||||
VERSION
|
||||
No results. Select a search from the Searches pane [1]. Or start new search [s → search] \n"
|
||||
)
|
||||
};
|
||||
|
||||
@@ -114,13 +113,13 @@ No results. Select a search from the Searches pane [1]. Or start new search [s
|
||||
|
||||
let bitrate_str = file
|
||||
.bitrate
|
||||
.map(|br| format!("{} kbps", br))
|
||||
.unwrap_or_else(|| "-".to_string());
|
||||
.map_or_else(|| "-".to_string(), |br| format!("{br} kbps"));
|
||||
|
||||
let speed_str = if file.speed > 0 {
|
||||
let speed_mb =
|
||||
(file.speed as f64 / BYTES_PER_MB * 100.0).round() / 100.0;
|
||||
format!("{} MB/s", speed_mb)
|
||||
let speed_mb = (f64::from(file.speed) / BYTES_PER_MB * 100.0)
|
||||
.round()
|
||||
/ 100.0;
|
||||
format!("{speed_mb} MB/s")
|
||||
} else {
|
||||
"-".to_string()
|
||||
};
|
||||
@@ -148,9 +147,9 @@ No results. Select a search from the Searches pane [1]. Or start new search [s
|
||||
];
|
||||
|
||||
let title = if is_filtering {
|
||||
format!("[2] Results - Filter: '{}'", filter_query)
|
||||
format!("[2] Results - Filter: '{filter_query}'")
|
||||
} else if let Some(query) = active_search_query {
|
||||
format!("[2] Results: {}", query)
|
||||
format!("[2] Results: {query}")
|
||||
} else {
|
||||
"[2] Results".to_string()
|
||||
};
|
||||
@@ -178,7 +177,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn identity_mapping_used_when_no_filter() {
|
||||
let selected: HashSet<usize> = [1].into_iter().collect();
|
||||
let selected: HashSet<usize> = std::iter::once(1).collect();
|
||||
assert!(row_is_selected(1, None, &selected));
|
||||
assert!(!row_is_selected(0, None, &selected));
|
||||
}
|
||||
@@ -188,7 +187,7 @@ mod tests {
|
||||
// Filter shows original rows 2, 5, 7 as display rows 0, 1, 2.
|
||||
// The user selected original row 5.
|
||||
let original_indices = [2usize, 5, 7];
|
||||
let selected: HashSet<usize> = [5].into_iter().collect();
|
||||
let selected: HashSet<usize> = std::iter::once(5).collect();
|
||||
assert!(!row_is_selected(0, Some(&original_indices), &selected));
|
||||
assert!(row_is_selected(1, Some(&original_indices), &selected));
|
||||
assert!(!row_is_selected(2, Some(&original_indices), &selected));
|
||||
|
||||
@@ -41,7 +41,7 @@ pub fn render_searches_pane(
|
||||
{
|
||||
"Searching...".to_string()
|
||||
} else {
|
||||
format!("{}", results_count)
|
||||
format!("{results_count}")
|
||||
};
|
||||
|
||||
Row::new(vec![
|
||||
|
||||
@@ -71,11 +71,11 @@ pub fn unfocused_border_style() -> Style {
|
||||
Style::default()
|
||||
}
|
||||
|
||||
pub fn focused_border_type() -> BorderType {
|
||||
pub const fn focused_border_type() -> BorderType {
|
||||
BorderType::Rounded
|
||||
}
|
||||
|
||||
pub fn unfocused_border_type() -> BorderType {
|
||||
pub const fn unfocused_border_type() -> BorderType {
|
||||
BorderType::Rounded
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ pub fn border_style(focused: bool) -> Style {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn border_type(focused: bool) -> BorderType {
|
||||
pub const fn border_type(focused: bool) -> BorderType {
|
||||
if focused {
|
||||
focused_border_type()
|
||||
} else {
|
||||
@@ -100,9 +100,7 @@ pub fn border_type(focused: bool) -> BorderType {
|
||||
pub fn format_shortcuts(shortcuts: &[(&str, &str)]) -> String {
|
||||
shortcuts
|
||||
.iter()
|
||||
.map(|(key, action)| {
|
||||
format!("[{} {} {}]", key, HIGHLIGHT_SYMBOL, action)
|
||||
})
|
||||
.map(|(key, action)| format!("[{key} {HIGHLIGHT_SYMBOL} {action}]"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
}
|
||||
@@ -124,7 +122,7 @@ pub fn format_shortcuts_styled(shortcuts: &[(&str, &str)]) -> Line<'static> {
|
||||
.add_modifier(Modifier::BOLD),
|
||||
));
|
||||
spans.push(Span::styled(
|
||||
format!(" {} ", HIGHLIGHT_SYMBOL),
|
||||
format!(" {HIGHLIGHT_SYMBOL} "),
|
||||
dimmed_style(),
|
||||
));
|
||||
spans.push(Span::raw(action.to_string()));
|
||||
@@ -165,7 +163,7 @@ pub fn format_progress_bar(
|
||||
Span::raw("]"),
|
||||
Span::raw(" "),
|
||||
Span::styled(
|
||||
format!("{}%", percentage),
|
||||
format!("{percentage}%"),
|
||||
primary_style()
|
||||
.add_modifier(Modifier::BOLD)
|
||||
.fg(Color::default()),
|
||||
|
||||
@@ -39,6 +39,6 @@ pub fn get_bitrate(
|
||||
const SPINNER_CHARS: [&str; 10] =
|
||||
["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||
|
||||
pub fn get_spinner_char(state: usize) -> &'static str {
|
||||
pub const fn get_spinner_char(state: usize) -> &'static str {
|
||||
SPINNER_CHARS[state % SPINNER_CHARS.len()]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user