Fix less mouse pager fallback handling

This commit is contained in:
2026-04-01 22:53:44 -07:00
parent 32a8a1d4ad
commit 32a0471910

View File

@@ -1,53 +1,81 @@
"""Custom pager with mouse scroll support.""" """Less-based pager integration with mouse support and fallback."""
from __future__ import annotations from __future__ import annotations
import re
import signal
import shutil import shutil
import subprocess import subprocess
from rich.pager import Pager, SystemPager from rich.pager import Pager, SystemPager
def _is_mouse_capable_less() -> bool: def _mouse_capable_less() -> str | None:
"""Check whether the system has a usable ``less`` executable. """Return a ``less`` path that likely supports ``--mouse``.
Mouse support was added in ``less`` 543. This probe is best-effort: it
checks that ``less`` exists, asks for its version string, and extracts the
version number from the leading ``less <version>`` banner. Any unexpected
output is treated as unsupported so the caller can fall back safely.
Returns: Returns:
True if ``less`` is installed and responds to ``--version``. The resolved ``less`` executable path when it appears mouse-capable,
otherwise ``None``.
""" """
less = shutil.which("less") less = shutil.which("less")
if less is None: if less is None:
return False return None
try: try:
result = subprocess.run( result = subprocess.run(
[less, "--version"], capture_output=True, text=True, timeout=5 [less, "--version"], capture_output=True, text=True, timeout=5
) )
version_str = result.stdout or result.stderr or ""
return "GNU" in version_str or "less" in version_str.lower()
except (subprocess.TimeoutExpired, OSError): except (subprocess.TimeoutExpired, OSError):
return False return None
version_str = result.stdout or result.stderr or ""
match = re.search(r"\bless\s+(\d+)\b", version_str, re.IGNORECASE)
if match is None:
return None
if int(match.group(1)) < 543:
return None
return less
class MousePager(Pager): class MousePager(Pager):
"""Pager that prefers ``less --mouse`` and falls back to the system pager.""" """Pager that prefers ``less -R --mouse`` for mouse scrolling.
``less`` is the prerequisite for mouse-enabled paging in this project. If
``less`` is missing, too old, cannot be launched, or exits with a non-zero
status, we fall back to Rich's ``SystemPager``. A user interrupt does not
trigger a second pager session.
"""
def show(self, content: str) -> None: def show(self, content: str) -> None:
"""Display content with mouse-scroll support when available. """Display rendered content in ``less -R --mouse`` when possible.
Uses ``less --mouse`` when available; otherwise falls back to the system
pager.
Args: Args:
content: Content to display in pager. content: Fully rendered text from Rich.
""" """
if _is_mouse_capable_less(): less = _mouse_capable_less()
try: if less is None:
subprocess.run( SystemPager().show(content)
["less", "-R", "--mouse"], return
input=content,
text=True, try:
check=True, result = subprocess.run(
) [less, "-R", "--mouse"],
return input=content,
except (subprocess.CalledProcessError, FileNotFoundError): text=True,
pass )
SystemPager().show(content) except OSError:
SystemPager().show(content)
return
if result.returncode in (-signal.SIGINT, 128 + signal.SIGINT):
return
if result.returncode != 0:
SystemPager().show(content)