Coverage for src/secchi/ui/widgets/status_bar.py: 40%
50 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-14 23:28 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-14 23:28 +0000
1"""Custom bottom status bar — terminal-editor style with shortcut keys."""
3from __future__ import annotations
5import logging
6from datetime import UTC, datetime
7from pathlib import Path
9from textual.app import ComposeResult
10from textual.containers import Horizontal
11from textual.widgets import Static
13from secchi import __version__
14from secchi.ui import palette
16logger = logging.getLogger(__name__)
19def _format_key(key: str) -> str:
20 return f"[black on {palette.SECCHI}] {key} [/]"
23SHORTCUTS = (
24 f"{_format_key('r')} Refresh "
25 f"{_format_key('e')} Export "
26 f"{_format_key('/')} Search "
27 f"{_format_key('f')} Filter "
28 f"{_format_key('l')} Logs "
29 f"{_format_key('?')} Help "
30 f"{_format_key('q')} Quit"
31)
34def format_path(path: Path | None) -> str:
35 if path is None:
36 return "—"
37 try:
38 return "~/" + str(path.relative_to(Path.home()))
39 except ValueError:
40 return str(path)
43def _age_text(refreshed_at: datetime | None) -> str:
44 if refreshed_at is None:
45 return "refreshing…"
46 now = datetime.now(UTC)
47 mins = int((now - refreshed_at).total_seconds() / 60)
48 if mins < 1:
49 return "just now"
50 if mins == 1:
51 return "1m ago"
52 if mins < 60:
53 return f"{mins}m ago"
54 return f"{mins // 60}h ago"
57class SecchiFooter(Horizontal):
58 """Docked bottom bar: info left, shortcuts center, config right."""
60 def __init__(self, config_path: Path | None) -> None:
61 super().__init__()
62 self._config_path = config_path
64 def compose(self) -> ComposeResult:
65 yield Static("", id="footer-left")
66 yield Static(SHORTCUTS, id="footer-center")
67 yield Static(f"Config: {format_path(self._config_path)}", id="footer-right")
69 def on_mount(self) -> None:
70 self._tick()
71 self.set_interval(30, self._tick)
73 def _tick(self) -> None:
74 refreshed_at = getattr(self.app, "refreshed_at", None)
75 age = _age_text(refreshed_at)
76 try:
77 self.query_one("#footer-left", Static).update(
78 f"[{palette.GREEN}]secchi[/] {__version__} [dim]│[/] Data: {age}"
79 )
80 except Exception:
81 # The interval may tick while the footer is being unmounted.
82 logger.debug("Unable to update status bar", exc_info=True)