Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
30 changes: 26 additions & 4 deletions rich/live.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
"""

Expand All @@ -59,7 +71,9 @@ def __init__(
transient: bool = False,
redirect_stdout: bool = True,
redirect_stderr: bool = True,
vertical_overflow: VerticalOverflowMethod = "ellipsis",
vertical_overflow: Union[
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not keen on overloading a parameter like this. If it becomes a general purpose extension mechanism, it could quickly become unwieldy. I'd prefer to keep the message a separate value.

VerticalOverflowMethod, Tuple[Literal["ellipsis"], str]
] = "ellipsis",
get_renderable: Optional[Callable[[], RenderableType]] = None,
) -> None:
assert refresh_per_second > 0, "refresh_per_second must be > 0"
Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion rich/live_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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="",
Expand Down
19 changes: 19 additions & 0 deletions tests/test_live.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading