Textualize / rich

Rich is a Python library for rich text and beautiful formatting in the terminal.
https://rich.readthedocs.io/en/latest/
MIT License
49.48k stars 1.72k forks source link

[BUG] table column no_wrap can squash columns below their min_width #2890

Open rjbs opened 1 year ago

rjbs commented 1 year ago

Describe the bug

I'll start with: the bug may be with my understanding of options, so I'm prepared to accept that I'm thinking about things wrong. That said, I feel pretty confident that my thinking is not incoherent. ๐Ÿ˜‰

In a tableย column, the no_wrap option does not function as I would expect. My expectation is that the content of a column would not be allowed to wrap, and that the overflow property would apply. Further, that with no specification of the column's width, its width would be determined by finding the total available table width, meeting the width requirements of other columns (and borders) and then using the remaining space.

Instead, a no_wrap column is forcingย other table column contents to be squashed below their minimum width. Consider this program:

from rich import print
from rich.table import Table
from rich.text import Text

table = Table(show_header=True, header_style="bold magenta")
table.add_column("Issue", min_width=7, width=7)
table.add_column("S", min_width=1, width=1)
table.add_column("Pri", min_width=3, width=3)
table.add_column("Title", overflow="ellipsis")

too_long = """\
This is a full line sentence and it fills a whole line so it overflows a cell!\
"""

for i in range(2):
    table.add_row("ABC-123", "X", "123", " ".join([too_long, too_long]))

print(table)

This prints the following table on my 80 column terminal (iTerm2):

Screenshot 2023-03-21 at 20 05 58

If I update the program to use no_wrap:

from rich import print
from rich.table import Table
from rich.text import Text

table = Table(show_header=True, header_style="bold magenta")
table.add_column("Issue", min_width=7, width=7)
table.add_column("S", min_width=1, width=1)
table.add_column("Pri", min_width=3, width=3)
table.add_column("Title", overflow="ellipsis", no_wrap=True)

too_long = """\
This is a full line sentence and it fills a whole line so it overflows a cell!\
"""

for i in range(2):
    table.add_row("ABC-123", "X", "123", " ".join([too_long, too_long]))

print(table)

...then the table produced squashes the first three columns below their minimum before applying the ellipsis overflow behavior.

Screenshot 2023-03-21 at 20 07 17

I think instead, the left columns should be displayed at their specified widths, and the rightmost column truncated earlier.

Platform

Click to expand What platform (Win/Linux/Mac) are you running on? What terminal software are you using? I'm running on macOS Venture: ``` Darwin snowdrop 22.3.0 Darwin Kernel Version 22.3.0: Mon Jan 30 20:38:43 PST 2023; root:xnu-8792.81.3~2/RELEASE_ARM64_T8112 arm64 ``` I'm using iTerm2 v3.4.19.
rich.diagnose โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ A high level console interface. โ”‚ โ”‚ โ”‚ โ”‚ โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚ โ”‚ โ”‚ โ”‚ color_system = 'truecolor' โ”‚ โ”‚ encoding = 'utf-8' โ”‚ โ”‚ file = <_io.TextIOWrapper name='' mode='w' โ”‚ โ”‚ encoding='utf-8'> โ”‚ โ”‚ height = 33 โ”‚ โ”‚ is_alt_screen = False โ”‚ โ”‚ is_dumb_terminal = False โ”‚ โ”‚ is_interactive = True โ”‚ โ”‚ is_jupyter = False โ”‚ โ”‚ is_terminal = True โ”‚ โ”‚ legacy_windows = False โ”‚ โ”‚ no_color = False โ”‚ โ”‚ options = ConsoleOptions( โ”‚ โ”‚ size=ConsoleDimensions(width=80, height=33), โ”‚ โ”‚ legacy_windows=False, โ”‚ โ”‚ min_width=1, โ”‚ โ”‚ max_width=80, โ”‚ โ”‚ is_terminal=True, โ”‚ โ”‚ encoding='utf-8', โ”‚ โ”‚ max_height=33, โ”‚ โ”‚ justify=None, โ”‚ โ”‚ overflow=None, โ”‚ โ”‚ no_wrap=False, โ”‚ โ”‚ highlight=None, โ”‚ โ”‚ markup=None, โ”‚ โ”‚ height=None โ”‚ โ”‚ ) โ”‚ โ”‚ quiet = False โ”‚ โ”‚ record = False โ”‚ โ”‚ safe_box = True โ”‚ โ”‚ size = ConsoleDimensions(width=80, height=33) โ”‚ โ”‚ soft_wrap = False โ”‚ โ”‚ stderr = False โ”‚ โ”‚ style = None โ”‚ โ”‚ tab_size = 8 โ”‚ โ”‚ width = 80 โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€โ”€โ”€ โ”€โ”€โ”€โ”€โ•ฎ โ”‚ Windows features available. โ”‚ โ”‚ โ”‚ โ”‚ โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ โ”‚ โ”‚ WindowsConsoleFeatures(vt=False, truecolor=False) โ”‚ โ”‚ โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚ โ”‚ โ”‚ โ”‚ truecolor = False โ”‚ โ”‚ vt = False โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ โ•ญโ”€โ”€โ”€โ”€โ”€โ”€ Environment Variables โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ { โ”‚ โ”‚ 'TERM': 'xterm-256color', โ”‚ โ”‚ 'COLORTERM': 'truecolor', โ”‚ โ”‚ 'CLICOLOR': None, โ”‚ โ”‚ 'NO_COLOR': None, โ”‚ โ”‚ 'TERM_PROGRAM': 'iTerm.app', โ”‚ โ”‚ 'COLUMNS': None, โ”‚ โ”‚ 'LINES': None, โ”‚ โ”‚ 'JUPYTER_COLUMNS': None, โ”‚ โ”‚ 'JUPYTER_LINES': None, โ”‚ โ”‚ 'JPY_PARENT_PID': None, โ”‚ โ”‚ 'VSCODE_VERBOSE_LOGGING': None โ”‚ โ”‚ } โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ platform="Darwin" ```
pip freeze | grep rich ``` snowdrop:~/fm/code/glinpy$ pip freeze | grep rich rich==13.3.2 ```
github-actions[bot] commented 1 year ago

Thank you for your issue. Give us a little time to review it.

PS. You might want to check the FAQ if you haven't done so already.

This is an automated reply, generated by FAQtory

willmcgugan commented 1 year ago

You have given Rich an impossible to satisfy set of restraints. The first three columns are fixed, and the last isn't permitted to wrap. There is no way to shrink that without breaking at least one constraint, so it defaults to a "last resort" algorithm to compress the table.

A workaround would be to remove the constraints from the columns and prevent the cell from wrapping.

from rich import print
from rich.table import Table
from rich.text import Text

table = Table(show_header=True, header_style="bold magenta", expand=True)
table.add_column("Issue")
table.add_column("S")
table.add_column("Pri")
table.add_column("Title")

too_long = """\
This is a full line sentence and it fills a whole line so it overflows a cell!\
"""

for i in range(2):
    table.add_row(
        "ABC-123",
        "X",
        "123",
        Text(" ".join([too_long, too_long]), no_wrap=True),
    )

print(table)
rjbs commented 1 year ago

Thanks, @willmcgugan, that does get me the behavior that I wanted. I really appreciate it.

I'd like to ask a follow-up question, if I may. You said, "You have given Rich an impossible to satisfy set of restraints." and I reckon you're right! But I would've thought the model worked differently, and I think I'll do better moving forward if I understand it more. Maybe you can speak the three words that will enlighten me, or just point me at something for me to read over and over until it clicks. :)

You wrote:

The first three columns are fixed, and the last isn't permitted to wrap. There is no way to shrink that without breaking at least one constraint, so it defaults to a "last resort" algorithm to compress the table.

I would have thought that it would be able to be satisfied like so:

I suspect that my misunderstanding is related to the way in which the no_wrap/overflow of the column are communicated to, or share dependency ordering with, the renderable in each row of the column, but I'm afraid I don't understand my own understanding.

If you can clear it up, I would be grateful. If not, well, I'm already grateful for the above. Thanks!

FrancoisBeaune commented 12 months ago

I struggled with this exact problem for a long time, and @willmcgugan solution also worked perfectly for me.

To be honest, I'm not sure I understand the difference between a column not being allowed to wrap vs a cell not being able to wrap...

peterjc commented 2 weeks ago

For the reference of anyone reading, the workaround gives the following (desired) output:

>>> print("x"*80)
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
>>> print(table)
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“
โ”ƒ Issue   โ”ƒ S โ”ƒ Pri โ”ƒ Title                                                    โ”ƒ
โ”กโ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฉ
โ”‚ ABC-123 โ”‚ X โ”‚ 123 โ”‚ This is a full line sentence and it fills a whole line โ€ฆ โ”‚
โ”‚ ABC-123 โ”‚ X โ”‚ 123 โ”‚ This is a full line sentence and it fills a whole line โ€ฆ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜