refactor(tui): trim the folder column change to what it needs
The path splitter is a one-line stdlib call with a single caller, so it lives beside that caller as a private fn, without its own tests. The pane tests assert the two columns in one screen fragment each, the end-offset test keeps only the two cases the existing name tests do not cover, and the key-handling test that repeated one of them goes. The website mock splits on the backslash the same way its Info pane already does.
This commit is contained in:
@@ -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
|
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`,
|
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,
|
`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 `?`
|
folder or query sideways, `0` and `$` to either end. `PgUp`/`PgDn` scroll a
|
||||||
shows every key for where you are.
|
chat log back through its history, and `?` shows every key for where you are.
|
||||||
|
|
||||||
### Daemon mode
|
### Daemon mode
|
||||||
|
|
||||||
|
|||||||
@@ -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]
|
#[test]
|
||||||
fn picking_another_search_starts_its_results_unscrolled() {
|
fn picking_another_search_starts_its_results_unscrolled() {
|
||||||
let mut tui = results_tui(3);
|
let mut tui = results_tui(3);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::models::FileDisplayData;
|
|||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
BYTES_PER_MB, HIGHLIGHT_SYMBOL, body_style, dimmed_style, format_bytes,
|
BYTES_PER_MB, HIGHLIGHT_SYMBOL, body_style, dimmed_style, format_bytes,
|
||||||
header_style, info_style, pane_block, pane_title, row_highlight_style,
|
header_style, info_style, pane_block, pane_title, row_highlight_style,
|
||||||
split_path, success_style, warning_style,
|
success_style, warning_style,
|
||||||
};
|
};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
Frame,
|
Frame,
|
||||||
@@ -47,6 +47,12 @@ pub struct ResultsPaneParams<'a> {
|
|||||||
const NAME_COLUMN: usize = 1;
|
const NAME_COLUMN: usize = 1;
|
||||||
const FOLDER_COLUMN: usize = 2;
|
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.
|
/// 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
|
/// One offset scrolls the name and the folder together, so it runs until
|
||||||
/// both tails are showing.
|
/// both tails are showing.
|
||||||
@@ -285,55 +291,30 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 items = [file("@@abc\\Music\\Album\\01.flac"), file("cover.jpg")];
|
||||||
let screen = render_rows(&items, 0);
|
let screen = render_rows(&items, 0);
|
||||||
let header = screen.lines().nth(1).expect("header");
|
assert!(screen.contains("01.flac @@abc\\M"), "{screen}");
|
||||||
let name = header.find("Filename").expect("name header");
|
assert!(screen.contains("cover.jpg "), "{screen}");
|
||||||
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}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_offset_scrolls_the_name_and_the_folder_together() {
|
fn one_offset_scrolls_the_name_and_the_folder_together() {
|
||||||
let items = [file("@@abc\\Music\\Album\\abcdefghijklmnopqrstuvwxyz")];
|
let items = [file("@@abc\\Music\\Album\\abcdefghijklmnopqrstuvwxyz")];
|
||||||
let screen = render_rows(&items, 4);
|
let screen = render_rows(&items, 4);
|
||||||
assert!(screen.contains("…efghijklmn"), "{screen}");
|
assert!(screen.contains("…efghijklmn …c\\Musi"), "{screen}");
|
||||||
assert!(screen.contains("…c\\Musi"), "{screen}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_end_offset_is_the_longer_columns() {
|
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.
|
// 88 cells wide: 11 for the name, 7 for the folder.
|
||||||
let area = Rect::new(0, 0, 88, 6);
|
let area = ratatui::layout::Rect::new(0, 0, 88, 6);
|
||||||
assert_eq!(name_end_offset("short.mp3", area), 0);
|
let folder = "f".repeat(20);
|
||||||
assert_eq!(name_end_offset(&"x".repeat(40), area), 30);
|
assert_eq!(super::name_end_offset(&format!("{folder}\\a"), area), 14);
|
||||||
|
let name = "x".repeat(40);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
name_end_offset(&format!("{}\\short.mp3", "f".repeat(20)), area),
|
super::name_end_offset(&format!("{folder}\\{name}"), area),
|
||||||
14,
|
30
|
||||||
"the folder overflows its narrower column"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
name_end_offset(
|
|
||||||
&format!("{}\\{}", "f".repeat(20), "x".repeat(40)),
|
|
||||||
area
|
|
||||||
),
|
|
||||||
30,
|
|
||||||
"whichever runs further"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,6 @@ import { TuiPane } from './chrome'
|
|||||||
import type { ResultRow, SearchRow, TransferRow, TransferStatus } from './data'
|
import type { ResultRow, SearchRow, TransferRow, TransferStatus } from './data'
|
||||||
|
|
||||||
const RESULT_COLS = '30px 3fr 2fr 74px 92px 78px 74px 42px'
|
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'
|
const TRANSFER_COLS = '48px 1fr 96px 168px 74px'
|
||||||
|
|
||||||
interface SearchesPaneProps {
|
interface SearchesPaneProps {
|
||||||
@@ -152,7 +144,7 @@ export const ResultsPane = ({
|
|||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
{rows.map((row, i) => {
|
{rows.map((row, i) => {
|
||||||
const { glyph, tone } = resultGlyph(row)
|
const { glyph, tone } = resultGlyph(row)
|
||||||
const { folder, name } = splitPath(row.name)
|
const parts = row.name.split('\\')
|
||||||
const isSelected = selected === i
|
const isSelected = selected === i
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@@ -177,10 +169,10 @@ export const ResultsPane = ({
|
|||||||
isSelected ? 'text-[var(--color-paper)]' : 'text-[var(--color-paper-dim)]',
|
isSelected ? 'text-[var(--color-paper)]' : 'text-[var(--color-paper-dim)]',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{name}
|
{parts.at(-1)}
|
||||||
</span>
|
</span>
|
||||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap text-[var(--color-dust)]">
|
<span className="overflow-hidden text-ellipsis whitespace-nowrap text-[var(--color-dust)]">
|
||||||
{folder}
|
{parts.slice(0, -1).join('\\')}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-right text-[var(--color-tape)]">{row.size}</span>
|
<span className="text-right text-[var(--color-tape)]">{row.size}</span>
|
||||||
<span className="text-[var(--color-signal)]">{row.user}</span>
|
<span className="text-[var(--color-signal)]">{row.user}</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user