Skip to content

Commit b01bf81

Browse files
Kasper JungeRalphify
authored andcommitted
refactor: extract _scrollbar_metrics from _FullscreenPeek render method
The scrollbar geometry calculation (thumb position, size, visibility) was inlined in the 75-line __rich_console__ method. Extract it into a pure function so the render method is easier to follow and the math gains dedicated test coverage. Co-authored-by: Ralphify <noreply@ralphify.co>
1 parent 5476414 commit b01bf81

File tree

2 files changed

+98
-14
lines changed

2 files changed

+98
-14
lines changed

src/ralphify/_console_emitter.py

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,43 @@ def _build_footer(self) -> Table:
575575
_FULLSCREEN_MIN_VISIBLE = 5
576576

577577

578+
class _ScrollbarMetrics:
579+
"""Pure-data result of scrollbar geometry calculation.
580+
581+
``show`` is ``False`` when the buffer fits in the viewport and no
582+
scrollbar should be rendered. ``thumb_start`` and ``thumb_size``
583+
are row indices within the viewport, valid only when ``show`` is
584+
``True``.
585+
"""
586+
587+
__slots__ = ("show", "thumb_start", "thumb_size")
588+
589+
def __init__(self, show: bool, thumb_start: int, thumb_size: int) -> None:
590+
self.show = show
591+
self.thumb_start = thumb_start
592+
self.thumb_size = thumb_size
593+
594+
595+
def _scrollbar_metrics(total: int, visible: int, offset: int) -> _ScrollbarMetrics:
596+
"""Compute scrollbar position and size for the fullscreen peek view.
597+
598+
*total* is the number of buffered lines, *visible* the viewport
599+
height, and *offset* how many lines are hidden below the viewport
600+
(0 = following the tail).
601+
602+
Returns a :class:`_ScrollbarMetrics` with ``show=False`` when the
603+
buffer fits within the viewport.
604+
"""
605+
if total <= visible:
606+
return _ScrollbarMetrics(show=False, thumb_start=0, thumb_size=0)
607+
thumb_size = max(1, visible * visible // total)
608+
max_off = max(total - visible, 1)
609+
frac = 1.0 - (offset / max_off)
610+
track_space = visible - thumb_size
611+
thumb_start = int(frac * track_space)
612+
return _ScrollbarMetrics(show=True, thumb_start=thumb_start, thumb_size=thumb_size)
613+
614+
578615
class _FullscreenPeek:
579616
"""Scrollable alt-screen view of the activity buffer.
580617
@@ -674,16 +711,7 @@ def __rich_console__(
674711
start = max(0, end - visible)
675712
window = lines[start:end]
676713

677-
# Scrollbar metrics
678-
show_scrollbar = total > visible
679-
thumb_start = 0
680-
thumb_size = visible
681-
if show_scrollbar:
682-
thumb_size = max(1, visible * visible // total)
683-
max_off_val = max(total - visible, 1)
684-
frac = 1.0 - (self._offset / max_off_val)
685-
track_space = visible - thumb_size
686-
thumb_start = int(frac * track_space)
714+
sb = _scrollbar_metrics(total, visible, self._offset)
687715

688716
rows: list[Any] = []
689717
rows.append(self._build_header(total, visible))
@@ -692,7 +720,7 @@ def __rich_console__(
692720
# Content area with optional scrollbar column
693721
content = Table.grid(expand=True)
694722
content.add_column(ratio=1, no_wrap=True, overflow="ellipsis")
695-
if show_scrollbar:
723+
if sb.show:
696724
content.add_column(width=1, no_wrap=True)
697725

698726
for i in range(visible):
@@ -702,8 +730,8 @@ def __rich_console__(
702730
line.overflow = "ellipsis"
703731
else:
704732
line = Text("")
705-
if show_scrollbar:
706-
in_thumb = thumb_start <= i < thumb_start + thumb_size
733+
if sb.show:
734+
in_thumb = sb.thumb_start <= i < sb.thumb_start + sb.thumb_size
707735
bar = Text(
708736
"█" if in_thumb else "│",
709737
style=_brand.PURPLE if in_thumb else "dim",
@@ -712,7 +740,7 @@ def __rich_console__(
712740
else:
713741
content.add_row(line)
714742

715-
if not window and not show_scrollbar:
743+
if not window and not sb.show:
716744
# Replace the empty grid with a waiting message
717745
rows.append(Text(" (waiting for activity…)", style="dim italic"))
718746
for _ in range(max(0, visible - 1)):

tests/test_console_emitter.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
_format_run_info,
1616
_format_summary,
1717
_is_claude_command,
18+
_scrollbar_metrics,
1819
_shorten_path,
1920
)
2021
from ralphify._events import Event, EventType
@@ -1618,6 +1619,61 @@ def test_empty_buffer_shows_waiting_message(self):
16181619
assert "waiting for activity" in console.export_text()
16191620

16201621

1622+
class TestScrollbarMetrics:
1623+
"""Unit tests for _scrollbar_metrics pure calculation."""
1624+
1625+
def test_no_scrollbar_when_buffer_fits(self):
1626+
sb = _scrollbar_metrics(total=10, visible=20, offset=0)
1627+
assert sb.show is False
1628+
1629+
def test_no_scrollbar_when_exact_fit(self):
1630+
sb = _scrollbar_metrics(total=20, visible=20, offset=0)
1631+
assert sb.show is False
1632+
1633+
def test_scrollbar_shown_when_buffer_exceeds_viewport(self):
1634+
sb = _scrollbar_metrics(total=100, visible=20, offset=0)
1635+
assert sb.show is True
1636+
1637+
def test_thumb_at_bottom_when_offset_zero(self):
1638+
sb = _scrollbar_metrics(total=100, visible=20, offset=0)
1639+
assert sb.show is True
1640+
# offset=0 means following tail; thumb should be at the bottom
1641+
# of the track (frac=1.0 → thumb_start = track_space).
1642+
track_space = 20 - sb.thumb_size
1643+
assert sb.thumb_start == track_space
1644+
1645+
def test_thumb_at_top_when_offset_at_max(self):
1646+
total, visible = 100, 20
1647+
max_offset = total - visible # 80
1648+
sb = _scrollbar_metrics(total=total, visible=visible, offset=max_offset)
1649+
assert sb.show is True
1650+
assert sb.thumb_start == 0
1651+
1652+
def test_thumb_size_proportional_to_viewport(self):
1653+
sb = _scrollbar_metrics(total=100, visible=20, offset=0)
1654+
# thumb_size = max(1, 20*20 // 100) = 4
1655+
assert sb.thumb_size == 4
1656+
1657+
def test_thumb_size_minimum_one(self):
1658+
sb = _scrollbar_metrics(total=10000, visible=10, offset=0)
1659+
# 10*10 // 10000 = 0 → clamped to 1
1660+
assert sb.thumb_size == 1
1661+
1662+
def test_midpoint_offset(self):
1663+
total, visible = 200, 40
1664+
max_offset = total - visible # 160
1665+
mid_offset = max_offset // 2 # 80
1666+
sb = _scrollbar_metrics(total=total, visible=visible, offset=mid_offset)
1667+
assert sb.show is True
1668+
# Thumb should be roughly in the middle of the track
1669+
track_space = visible - sb.thumb_size
1670+
assert 0 < sb.thumb_start < track_space
1671+
1672+
def test_empty_buffer(self):
1673+
sb = _scrollbar_metrics(total=0, visible=20, offset=0)
1674+
assert sb.show is False
1675+
1676+
16211677
class TestFullscreenPeekEmitter:
16221678
"""Integration tests for fullscreen peek on ConsoleEmitter."""
16231679

0 commit comments

Comments
 (0)