实时显示中 table 的垂直溢出应滚动内容

Vertical overflow of table in live display should scroll the content

我正在使用实时显示的Live display to show the content of a Table which grows over time. Eventually there is a vertical overflow and in that case I'd like the oldest (i.e. topmost) rows to vanish while the most recent rows should be shown together with the header, i.e. the content should be scrolled. The vertical_overflow参数提供了一个"visible"选项,但这使得table的header消失了。显然这是一个 Table 特定问题,因为 header 应该保留但内容应该滚动。

import time
from rich.live import Live
from rich.table import Table

table = Table()
table.add_column('Time')
table.add_column('Message')

with Live(table, refresh_per_second=5, vertical_overflow='visible'):
    for i in range(100):
        time.sleep(0.2)
        table.add_row(time.asctime(), f'Event {i:03d}')

左侧部分显示 vertical_overflow='visible' 的行为,右侧部分显示所需的行为:

到目前为止,我使用的变通方法是使用单独的数据结构来保存行,然后每次添加新行时都从头开始创建 table。这似乎不是很有效,所以我想知道是否有更好的解决方案。此变通方法对于 multi-line 行也失败,因为它将它们计为单行(因此会发生溢出)。

from collections import deque
import os
import time
from rich.live import Live
from rich.table import Table


def generate_table(rows):
    table = Table()
    table.add_column('Time')
    table.add_column('Message')
    for row in rows:
        table.add_row(*row)
    return table


width, height = os.get_terminal_size()
messages = deque(maxlen=height-4)  # save space for header and footer

with Live(generate_table(messages), refresh_per_second=5) as live:
    for i in range(100):
        time.sleep(0.2)
        messages.append((time.asctime(), f'Event {i:03d}'))
        live.update(generate_table(messages))

我最近在做同样的事情,但也找不到 built-in 解决方案。由于您正在渲染实时显示,table 不会超过 ~100 行,因此效率不应该是一个问题。

这是我的解决方案。它反复从顶部删除行,直到 table 适合。这是通过将 table 放入 Layout 来衡量的,如果 table 不合适,它会截断底部的 table。

from collections import deque
import os
import time
from rich.live import Live
from rich.table import Table
from rich.layout import Layout
from rich.console import Console


def generate_table(rows):
    layout = Layout()
    console = Console()

    table = Table()
    table.add_column('Time')
    table.add_column('Message')

    rows = list(rows)

    # This would also get the height:
    # render_map = layout.render(console, console.options)
    # render_map[layout].region.height
    n_rows = os.get_terminal_size()[1]

    while n_rows >= 0:
        table = Table()
        table.add_column('Time')
        table.add_column('Message')

        for row in rows[-n_rows:]:
            table.add_row(*row)

        layout.update(table)

        render_map = layout.render(console, console.options)

        if len(render_map[layout].render[-1]) > 2:
            # The table is overflowing
            n_rows -= 1
        else:
            break

    return table


width, height = os.get_terminal_size()
messages = deque(maxlen=height-4)  # save space for header and footer

with Live(generate_table(messages), refresh_per_second=5) as live:
    for i in range(100):
        time.sleep(0.2)
        messages.append((time.asctime(), f'Event {i:03d}'))
        live.update(generate_table(messages))

这里的魔法线是if len(render_map[layout].render[-1]) > 2:。 这是判断 table 是否完整打印的一种 hacky 方法。 如果是,render_map[layout].render 的最后一个元素将看起来像

[
    Segment('└──────────────────────────┘', Style()),
    Segment('                                                                         ',)
]

或喜欢

[
    Segment(
        '
',
    )
]

但如果它被截断,它将看起来像

[
    Segment('│', Style()),
    Segment(' ', Style()),
    Segment(
        '37',
        Style(color=Color('cyan', ColorType.STANDARD, number=6), bold=True, italic=False)
    ),
    Segment('                      ', Style()),
    Segment(' ', Style()),
    Segment('│', Style()),
    Segment('                                                                         ',)
]