diff --git a/CHANGELOG.md b/CHANGELOG.md index 10933867af..bf85a404c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +- Allow a custom message with a `Live` overflows, instead of an ellipsis. https://github.com/Textualize/rich/pull/3702 + ## [14.0.0] - 2025-03-30 ### Added diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 9893915a4a..d73f6918db 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -92,3 +92,4 @@ The following people have contributed to the development of Rich: - [chthollyphile](https://github.com/chthollyphile) - [Jonathan Helmus](https://github.com/jjhelmus) - [Brandon Capener](https://github.com/bcapener) +- [Samuel Colvin](https://github.com/samuelcolvin) diff --git a/rich/live.py b/rich/live.py index 8738cf09f4..aa21314006 100644 --- a/rich/live.py +++ b/rich/live.py @@ -1,7 +1,19 @@ import sys from threading import Event, RLock, Thread from types import TracebackType -from typing import IO, Any, Callable, List, Optional, TextIO, Type, cast +from typing import ( + IO, + Any, + Callable, + List, + Literal, + Optional, + TextIO, + Tuple, + Type, + Union, + cast, +) from . import get_console from .console import Console, ConsoleRenderable, RenderableType, RenderHook @@ -44,7 +56,7 @@ class Live(JupyterMixin, RenderHook): transient (bool, optional): Clear the renderable on exit (has no effect when screen=True). Defaults to False. redirect_stdout (bool, optional): Enable redirection of stdout, so ``print`` may be used. Defaults to True. redirect_stderr (bool, optional): Enable redirection of stderr. Defaults to True. - vertical_overflow (VerticalOverflowMethod, optional): How to handle renderable when it is too tall for the console. Defaults to "ellipsis". + vertical_overflow (VerticalOverflowMethod, optional): How to handle renderable when it is too tall for the console, pass `("ellipsis", "message ...")` for a custom message on overflow. Defaults to "ellipsis". get_renderable (Callable[[], RenderableType], optional): Optional callable to get renderable. Defaults to None. """ @@ -59,7 +71,9 @@ def __init__( transient: bool = False, redirect_stdout: bool = True, redirect_stderr: bool = True, - vertical_overflow: VerticalOverflowMethod = "ellipsis", + vertical_overflow: Union[ + VerticalOverflowMethod, Tuple[Literal["ellipsis"], str] + ] = "ellipsis", get_renderable: Optional[Callable[[], RenderableType]] = None, ) -> None: assert refresh_per_second > 0, "refresh_per_second must be > 0" @@ -82,10 +96,18 @@ def __init__( self._refresh_thread: Optional[_RefreshThread] = None self.refresh_per_second = refresh_per_second + if isinstance(vertical_overflow, tuple): + vertical_overflow, vertical_overflow_message = vertical_overflow + else: + vertical_overflow_message = "..." + self.vertical_overflow = vertical_overflow + self.vertical_overflow_message = vertical_overflow_message self._get_renderable = get_renderable self._live_render = LiveRender( - self.get_renderable(), vertical_overflow=vertical_overflow + self.get_renderable(), + vertical_overflow=vertical_overflow, + vertical_overflow_message=vertical_overflow_message, ) @property diff --git a/rich/live_render.py b/rich/live_render.py index 4284cccc4b..286485fa33 100644 --- a/rich/live_render.py +++ b/rich/live_render.py @@ -30,10 +30,12 @@ def __init__( renderable: RenderableType, style: StyleType = "", vertical_overflow: VerticalOverflowMethod = "ellipsis", + vertical_overflow_message: str = "...", ) -> None: self.renderable = renderable self.style = style self.vertical_overflow = vertical_overflow + self.vertical_overflow_message = vertical_overflow_message self._shape: Optional[Tuple[int, int]] = None def set_renderable(self, renderable: RenderableType) -> None: @@ -95,7 +97,7 @@ def __rich_console__( elif self.vertical_overflow == "ellipsis": lines = lines[: (options.size.height - 1)] overflow_text = Text( - "...", + self.vertical_overflow_message, overflow="crop", justify="center", end="", diff --git a/tests/test_live.py b/tests/test_live.py index f037e4b8b4..7b72eccd37 100644 --- a/tests/test_live.py +++ b/tests/test_live.py @@ -83,6 +83,25 @@ def test_growing_display_overflow_ellipsis() -> None: ) +def test_growing_display_overflow_ellipsis_message() -> None: + console = create_capture_console(height=3) + console.begin_capture() + with Live( + console=console, + auto_refresh=False, + vertical_overflow=("ellipsis", "custom msg"), + ) as live: + display = "" + for step in range(5): + display += f"Step {step}\n" + live.update(display, refresh=True) + output = console.end_capture() + assert ( + output + == "\x1b[?25lStep 0\n\r\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\n custom msg \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\n custom msg \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\n custom msg \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\n\n\x1b[?25h" + ) + + def test_growing_display_overflow_crop() -> None: console = create_capture_console(height=5) console.begin_capture()