diff --git a/install.py b/install.py index 5822a0ee4..fad91a913 100644 --- a/install.py +++ b/install.py @@ -10785,12 +10785,13 @@ def _system_rows(state: TuiState) -> list[TuiRow]: right = f"{_plural(need, 'file')}, {format_size(row['bytes_needed'])}" narrow = (format_size(row["bytes_needed"]),) elif not own: + # Two spellings of shared, never cut: "+ 28 sh." and ", shared" + # sat beside "+ 1689 shared" on one 40-column screen. right = f"{_plural(need, 'file')}, {format_size(size)}, all shared" - narrow = (f"{format_size(size)}, shared", format_size(size)) + narrow = (f"{format_size(size)}, all shared", format_size(size)) else: right = f"{_plural(own, 'file')}, {format_size(own_size)} + {count} shared" - narrow = (f"{format_size(own_size)} + {count} shared", - f"{format_size(own_size)} + {count} sh.", format_size(own_size)) + narrow = (f"{format_size(own_size)} + {count} shared", format_size(own_size)) rows.append(TuiRow("system", f"{box} {label}", right, row["system"], "" if need else "dim", narrow)) return rows @@ -11090,7 +11091,7 @@ _POSIX_ROOT_SYSTEM = frozenset(( _WINDOWS_HIDDEN = 0x2 | 0x4 -def _system_folder(path: Path, folder: Path) -> bool: +def _os_folder(path: Path, folder: Path) -> bool: """A folder of the system, not one a person keeps files in: the recycle bin and restore points of a Windows drive ($RECYCLE.BIN, System Volume Information, hidden by Explorer), and /bin, /etc, /usr and the @@ -11121,7 +11122,7 @@ def _browse_to(state: TuiState, folder: Path | None, came_from: Path | None = No focus = next((index for index, row in enumerate(state.browse_rows) if row.action == "dir" and isinstance(row.value, Path) and row.label == f"{row.value.name}/" - and not _system_folder(row.value, folder)), 0) + and not _os_folder(row.value, folder)), 0) if not focus and not state.status: state.status = ("This is your home folder: no setup reads its BIOS files " "here. Go back to type a path." if folder == state.home @@ -12256,6 +12257,7 @@ def _ticked_files(state: TuiState) -> list[dict]: def _view_systems(state: TuiState, width: int, height: int) -> tuple: rows = tui_rows(state) + whole_rows = rows if width < 60: # The name first: the size alone stays beside it, the count is in # the total below. @@ -12273,6 +12275,14 @@ def _view_systems(state: TuiState, width: int, height: int) -> tuple: f"{format_size(sum(e['size'] for e in chosen))}" + (" before your options" if _counts_predate_options(state) else "")] focused = rows[min(state.focus.get("systems", 0), len(rows) - 1)] + drawn = str(_row_line(focused, True, width)) + if focused.action == "system" and not ( + f"> {focused.label}" in drawn + and any(form and drawn.endswith(form) for form in (focused.right, *focused.narrow))): + # A long name kept whole leaves no room for its size, and a name + # longer than the screen is cut: both are spelled out below. + whole = whole_rows[min(state.focus.get("systems", 0), len(whole_rows) - 1)] + footer.insert(0, f"{whole.label[4:]}: {whole.right}") if focused.value == OTHER_SYSTEMS: footer.insert(0, OTHER_HELP) elif "shared" in focused.right: diff --git a/tests/test_install_tui.py b/tests/test_install_tui.py index 170e7008d..7b8021979 100644 --- a/tests/test_install_tui.py +++ b/tests/test_install_tui.py @@ -447,7 +447,7 @@ class Flow(unittest.TestCase): self.assertEqual(press(state, "enter"), []) self.assertIn("No system is ticked", state.status) focus = next(i for i, row in enumerate(install.tui_rows(state)) - if row.value == "nintendo-gba") + if row.value == "epoch-scv") state.focus["systems"] = focus press(state, "right") self.assertEqual(press(state, "enter"), [("run", "confirm")]) @@ -1513,7 +1513,7 @@ class AuditRoundTwo(unittest.TestCase): state = self.systems() press(state, "n") state.focus["systems"] = next(i for i, row in enumerate(install.tui_rows(state)) - if row.value == "nintendo-gba") + if row.value == "epoch-scv") press(state, "right") self.assertEqual(state.ticked, {"nintendo-gba"}) press(state, "/", "p", "l", "a", "y") @@ -2841,6 +2841,35 @@ class NarrowSystemNames(unittest.TestCase): self.assertIn("3 files, 30.0 MB + 27 shared", "\n".join(install.tui_view(state, 120, 40))) + def test_one_spelling_of_shared_and_the_focused_size_below(self): + """At 40 columns the shared part read "+ 1689 shared", "+ 28 sh.", + ", shared" and ", all shared" on one screen, and a long name kept + whole lost its size.""" + state = checked_state() + names = {"epoch-scv": "Epoch Super Cassette Vision", "sega-dc": "Sega Dreamcast", + "snk-ng": "SNK Neo Geo"} + state.manifest = {**state.manifest, "system_titles": names} + state.systems = [ + {"event": "system", "platform": "retroarch", "system": system, "name": system, + "files": count, "present": 0, "missing": count, "wrong_hash": 0, + "bytes_needed": count * 1024} + for system, count in (("epoch-scv", 30), ("sega-dc", 1690), ("snk-ng", 3))] + state.files = [{"event": "file", "platform": "retroarch", "dest": f"s{n}.bin", + "systems": ["sega-dc", "snk-ng"], "size": 1024, "state": "missing"} + for n in range(3)] + state.ticked = set(names) + state.stack.append("systems") + spellings = set() + for cols in (40, 46, 52, 80): + text = "\n".join(install.tui_view(state, cols, 15)) + spellings |= set(re.findall(r"\+ \d+ sh\w*\.?|, (?:all )?shared", text)) + self.assertLessEqual(spellings, {"+ 3 shared", ", all shared"}, spellings) + state.focus["systems"] = next( + index for index, row in enumerate(install.tui_rows(state)) + if row.value == "epoch-scv") + text = " ".join(" ".join(install.tui_view(state, 40, 15)).split()) + self.assertIn("Epoch Super Cassette Vision: 30 files, 30.0 KB", text) + class Crash(unittest.TestCase): """An unexpected error ends on one sentence and a log, never a traceback."""