diff --git a/README.md b/README.md index 787ef84..7b3bc3c 100644 --- a/README.md +++ b/README.md @@ -98,8 +98,8 @@ underneath. `Tab`/`Shift-Tab` or `1`–`3` move the focus between panes, `z` zooms the focused pane to the whole window, and `w` hides it so the others widen (its number brings it back). Every list takes `↑`/`↓`, `Home`/`End`, `PgUp`/`PgDn` and the vim keys, and `h`/`l` or `←`/`→` scroll a long file name, -folder or query sideways, `0` and `$` to either end. `PgUp`/`PgDn` scroll a chat log back through its history, and `?` -shows every key for where you are. +folder or query sideways, `0` and `$` to either end. `PgUp`/`PgDn` scroll a +chat log back through its history, and `?` shows every key for where you are. ### Daemon mode diff --git a/soulseek-rs/src/ui/main_tui/mod.rs b/soulseek-rs/src/ui/main_tui/mod.rs index 127386c..2e58c53 100644 --- a/soulseek-rs/src/ui/main_tui/mod.rs +++ b/soulseek-rs/src/ui/main_tui/mod.rs @@ -1274,18 +1274,6 @@ mod tests { ); } - #[test] - fn scrolling_runs_to_the_end_of_the_folder_when_it_is_the_longer() { - let mut tui = results_tui(3); - tui.state.results_items[1].filename = - format!("{}\\short.mp3", "f".repeat(20)); - tui.state.results_table_state.select(Some(1)); - press(&mut tui, KeyCode::Char('$')); - assert_eq!(tui.state.results_name_offset, 14); - press(&mut tui, KeyCode::Char('l')); - assert_eq!(tui.state.results_name_offset, 14, "stops at the end"); - } - #[test] fn picking_another_search_starts_its_results_unscrolled() { let mut tui = results_tui(3); diff --git a/soulseek-rs/src/ui/panes/results_pane.rs b/soulseek-rs/src/ui/panes/results_pane.rs index af0f003..4ae8ef6 100644 --- a/soulseek-rs/src/ui/panes/results_pane.rs +++ b/soulseek-rs/src/ui/panes/results_pane.rs @@ -3,7 +3,7 @@ use crate::models::FileDisplayData; use crate::ui::{ BYTES_PER_MB, HIGHLIGHT_SYMBOL, body_style, dimmed_style, format_bytes, header_style, info_style, pane_block, pane_title, row_highlight_style, - split_path, success_style, warning_style, + success_style, warning_style, }; use ratatui::{ Frame, @@ -47,6 +47,12 @@ pub struct ResultsPaneParams<'a> { const NAME_COLUMN: usize = 1; const FOLDER_COLUMN: usize = 2; +/// A shared path as `(folder, name)`. Peers send backslashes, but a slash +/// counts too. +fn split_path(path: &str) -> (&str, &str) { + path.rsplit_once(['\\', '/']).unwrap_or(("", path)) +} + /// The offset that brings the end of a result's longer column into view. /// One offset scrolls the name and the folder together, so it runs until /// both tails are showing. @@ -285,55 +291,30 @@ mod tests { } #[test] - fn a_path_shows_its_name_and_folder_in_separate_columns() { + fn a_path_shows_its_name_before_its_folder() { let items = [file("@@abc\\Music\\Album\\01.flac"), file("cover.jpg")]; let screen = render_rows(&items, 0); - let header = screen.lines().nth(1).expect("header"); - let name = header.find("Filename").expect("name header"); - let folder = header.find("Folder").expect("folder header"); - assert!(name < folder, "{header}"); - - let row = screen.lines().nth(2).expect("first row"); - assert!(row.contains("01.flac "), "{row}"); - assert!(!row.contains("Album\\01.flac"), "the name alone: {row}"); - assert!(row.contains("@@abc\\M"), "{row}"); - assert!( - row.find("01.flac") < row.find("@@abc"), - "the name before the folder: {row}" - ); - - let row = screen.lines().nth(3).expect("second row"); - assert!(row.contains("cover.jpg"), "{row}"); + assert!(screen.contains("01.flac @@abc\\M"), "{screen}"); + assert!(screen.contains("cover.jpg "), "{screen}"); } #[test] fn one_offset_scrolls_the_name_and_the_folder_together() { let items = [file("@@abc\\Music\\Album\\abcdefghijklmnopqrstuvwxyz")]; let screen = render_rows(&items, 4); - assert!(screen.contains("…efghijklmn"), "{screen}"); - assert!(screen.contains("…c\\Musi"), "{screen}"); + assert!(screen.contains("…efghijklmn …c\\Musi"), "{screen}"); } #[test] fn the_end_offset_is_the_longer_columns() { - use super::name_end_offset; - use ratatui::layout::Rect; // 88 cells wide: 11 for the name, 7 for the folder. - let area = Rect::new(0, 0, 88, 6); - assert_eq!(name_end_offset("short.mp3", area), 0); - assert_eq!(name_end_offset(&"x".repeat(40), area), 30); + let area = ratatui::layout::Rect::new(0, 0, 88, 6); + let folder = "f".repeat(20); + assert_eq!(super::name_end_offset(&format!("{folder}\\a"), area), 14); + let name = "x".repeat(40); assert_eq!( - name_end_offset(&format!("{}\\short.mp3", "f".repeat(20)), area), - 14, - "the folder overflows its narrower column" - ); - assert_eq!( - name_end_offset( - &format!("{}\\{}", "f".repeat(20), "x".repeat(40)), - area - ), - 30, - "whichever runs further" + super::name_end_offset(&format!("{folder}\\{name}"), area), + 30 ); } diff --git a/website/src/components/tui/panes.tsx b/website/src/components/tui/panes.tsx index 5d897d9..a86b0e2 100644 --- a/website/src/components/tui/panes.tsx +++ b/website/src/components/tui/panes.tsx @@ -6,14 +6,6 @@ import { TuiPane } from './chrome' import type { ResultRow, SearchRow, TransferRow, TransferStatus } from './data' const RESULT_COLS = '30px 3fr 2fr 74px 92px 78px 74px 42px' - -/** A shared path split at its last separator, as the TUI's two columns show it. */ -const splitPath = (path: string): { folder: string; name: string } => { - const at = Math.max(path.lastIndexOf('\\'), path.lastIndexOf('/')) - return at < 0 - ? { folder: '', name: path } - : { folder: path.slice(0, at), name: path.slice(at + 1) } -} const TRANSFER_COLS = '48px 1fr 96px 168px 74px' interface SearchesPaneProps { @@ -152,7 +144,7 @@ export const ResultsPane = ({
{rows.map((row, i) => { const { glyph, tone } = resultGlyph(row) - const { folder, name } = splitPath(row.name) + const parts = row.name.split('\\') const isSelected = selected === i return (