Scripts: every script label in a Scripts folder level stays distinct

A script renamed to its whole file name could take the label another
script kept, as with tidy.js, tidy.jsx and tidy.js.js in one folder.
The rename now repeats until no label is shared. A folder entry whose
type cannot be read now marks the listing as incomplete.
This commit is contained in:
dotCooCoo
2026-11-10 18:48:59 -08:00
parent ea9b93d3ce
commit a06ab45911
2 changed files with 29 additions and 13 deletions
+26 -11
View File
@@ -10,8 +10,8 @@
//!
//! The values here are VectorCraft's own limits: [`MAX_ENTRIES`], [`MAX_SCRIPTS`], [`MAX_FOLDERS`],
//! [`MAX_DEPTH`], [`MAX_NAME_BYTES`], [`MAX_LABEL_CHARS`] and [`MAX_SCAN`]. A listing that reaches
//! one of them, or that cannot read a subfolder, keeps what it found and says so
//! ([`Listing::truncated`]).
//! one of them, or that cannot read a subfolder or an entry of a folder, keeps what it found and
//! says so ([`Listing::truncated`]).
use std::io::Read;
use std::path::{Component, Path, PathBuf};
@@ -157,7 +157,10 @@ impl Scan<'_> {
continue;
};
let Ok(name) = entry.file_name().into_string() else { continue };
let Ok(kind) = entry.file_type() else { continue };
let Ok(kind) = entry.file_type() else {
self.truncated = true;
continue;
};
if name.len() > MAX_NAME_BYTES || name.starts_with('.') || kind.is_symlink() || hidden(&entry) {
continue;
}
@@ -173,14 +176,26 @@ impl Scan<'_> {
found.push(Found { label: shown(stem), name, folder: false });
}
}
// A script whose label another row of this folder has shows its whole file name.
let mut count = std::collections::HashMap::<String, usize>::new();
for f in &found {
*count.entry(f.label.clone()).or_default() += 1;
}
for f in found.iter_mut().filter(|f| !f.folder) {
if count.get(&f.label).is_some_and(|n| *n > 1) {
f.label = shown(&f.name);
// A script whose label another row of this folder has shows its whole file name, until no
// label is shared. Each pass gives at least one more script its whole file name, and names
// are unique within a folder, so this ends within `found.len()` passes.
loop {
let mut count = std::collections::HashMap::<String, usize>::new();
for f in &found {
*count.entry(f.label.clone()).or_default() += 1;
}
let mut renamed = false;
for f in found.iter_mut().filter(|f| !f.folder) {
if count.get(&f.label).is_some_and(|n| *n > 1) {
let whole = shown(&f.name);
if f.label != whole {
f.label = whole;
renamed = true;
}
}
}
if !renamed {
break;
}
}
found.sort_by(|a, b| (a.label.to_lowercase(), &a.label, &a.name).cmp(&(b.label.to_lowercase(), &b.label, &b.name)));
+3 -2
View File
@@ -77,10 +77,11 @@ fn scripts_and_subfolders_are_listed_by_name_without_the_extension() {
fn a_script_whose_label_another_row_has_shows_its_whole_file_name() {
let dir = folder("same");
let root = dir.join("Scripts");
for name in ["tidy.js", "tidy.jsx", "tidy/x.js", "alone.jsx"] {
for name in ["tidy.js", "tidy.jsx", "tidy.js.js", "tidy/x.js", "alone.jsx"] {
touch(&root, name);
}
assert_eq!(rows(&folder::list(&root).entries), "alone, tidy/[x], tidy.js, tidy.jsx");
// `tidy.js.js` is labeled `tidy.js` until `tidy.js` takes that label, then shows its whole name.
assert_eq!(rows(&folder::list(&root).entries), "alone, tidy/[x], tidy.js, tidy.js.js, tidy.jsx");
let _ = std::fs::remove_dir_all(dir);
}