From 674a2d2f49283d87f73a330291383203a59455cc Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Wed, 16 Apr 2025 13:58:34 +0100 Subject: [PATCH 1/3] allow a custom message with Live overflow --- rich/live.py | 16 ++++++++++++---- rich/live_render.py | 4 +++- tests/test_live.py | 17 +++++++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/rich/live.py b/rich/live.py index 8738cf09f4..161522831e 100644 --- a/rich/live.py +++ b/rich/live.py @@ -1,7 +1,7 @@ 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 +44,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 +59,7 @@ 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 +82,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..cf6fe0356d 100644 --- a/tests/test_live.py +++ b/tests/test_live.py @@ -83,6 +83,23 @@ 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() From 735ed874f03043b5118fbbaf8a079050c9e37499 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Wed, 16 Apr 2025 14:02:31 +0100 Subject: [PATCH 2/3] formatting --- rich/live.py | 20 +++++++++++++++++--- tests/test_live.py | 4 +++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/rich/live.py b/rich/live.py index 161522831e..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, Literal, Optional, TextIO, Tuple, Type, Union, 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 @@ -59,7 +71,9 @@ def __init__( transient: bool = False, redirect_stdout: bool = True, redirect_stderr: bool = True, - vertical_overflow: Union[VerticalOverflowMethod, Tuple[Literal["ellipsis"], str]] = "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" @@ -93,7 +107,7 @@ def __init__( self._live_render = LiveRender( self.get_renderable(), vertical_overflow=vertical_overflow, - vertical_overflow_message=vertical_overflow_message + vertical_overflow_message=vertical_overflow_message, ) @property diff --git a/tests/test_live.py b/tests/test_live.py index cf6fe0356d..7b72eccd37 100644 --- a/tests/test_live.py +++ b/tests/test_live.py @@ -87,7 +87,9 @@ 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") + console=console, + auto_refresh=False, + vertical_overflow=("ellipsis", "custom msg"), ) as live: display = "" for step in range(5): From 01bc82ac7a3c82efa9c34ba7923ab493e882bcfb Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Wed, 16 Apr 2025 14:06:32 +0100 Subject: [PATCH 3/3] changelog and contributing --- CHANGELOG.md | 6 ++++++ CONTRIBUTORS.md | 1 + 2 files changed, 7 insertions(+) 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)