Skip to content
Closed
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
60 changes: 21 additions & 39 deletions rich/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
Sequence,
Tuple,
Union,
Any,
)

from . import box, errors
Expand All @@ -22,7 +23,7 @@
from .protocol import is_renderable
from .segment import Segment
from .style import Style, StyleType
from .text import Text, TextType
from .text import Text, TextType, get_unicode_width

if TYPE_CHECKING:
from .console import (
Expand Down Expand Up @@ -422,49 +423,30 @@ def add_column(

def add_row(
self,
*renderables: Optional["RenderableType"],
*cells: Any,
style: Optional[StyleType] = None,
end_section: bool = False,
) -> None:
"""Add a row of renderables.

"""Add a row to the table.
Args:
*renderables (None or renderable): Each cell in a row must be a renderable object (including str),
or ``None`` for a blank cell.
style (StyleType, optional): An optional style to apply to the entire row. Defaults to None.
end_section (bool, optional): End a section and draw a line. Defaults to False.

Raises:
errors.NotRenderableError: If you add something that can't be rendered.
*cells: Cell contents.
style: Optional style to apply to the row.
end_section: End a section and draw a line. Defaults to False.
"""

def add_cell(column: Column, renderable: "RenderableType") -> None:
column._cells.append(renderable)

cell_renderables: List[Optional["RenderableType"]] = list(renderables)

columns = self.columns
if len(cell_renderables) < len(columns):
cell_renderables = [
*cell_renderables,
*[None] * (len(columns) - len(cell_renderables)),
]
for index, renderable in enumerate(cell_renderables):
if index == len(columns):
column = Column(_index=index, highlight=self.highlight)
for _ in self.rows:
add_cell(column, Text(""))
self.columns.append(column)
else:
column = columns[index]
if renderable is None:
add_cell(column, "")
elif is_renderable(renderable):
add_cell(column, renderable)
else:
raise errors.NotRenderableError(
f"unable to render {type(renderable).__name__}; a string or other renderable object is required"
)
if len(cells) != len(self.columns):
raise ValueError(
f"Expected {len(self.columns)} cells, got {len(cells)}"
)

# Calculate padding for each cell based on Unicode width
padded_cells = []
for cell, column in zip(cells, self.columns):
cell_str = str(cell)
width = get_unicode_width(cell_str)
padding = " " * (column.width - width) if hasattr(column, 'width') else ""
padded_cells.append(cell_str + padding)

self.rows.append(Row(style=style, end_section=end_section))

def add_section(self) -> None:
Expand Down
30 changes: 30 additions & 0 deletions rich/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from .measure import Measurement
from .segment import Segment
from .style import Style, StyleType
import unicodedata

if TYPE_CHECKING: # pragma: no cover
from .console import Console, ConsoleOptions, JustifyMethod, OverflowMethod
Expand Down Expand Up @@ -1333,6 +1334,35 @@ def with_indent_guides(
return new_text


def get_unicode_width(text: str) -> int:
"""Calculate the visual width of a string containing Unicode characters.

Args:
text (str): The text to measure.

Returns:
int: The visual width of the text.

Example:
>>> get_unicode_width("Hello")
5
>>> get_unicode_width("こんにちは")
10
>>> get_unicode_width("👋")
2
"""
width = 0
for char in text:
char_width = unicodedata.east_asian_width(char)
if char_width in ('F', 'W'): # Full-width or Wide characters
width += 2
elif char_width == 'A': # Ambiguous characters
width += 2 # Treat as full-width
else: # Narrow, Half-width, or Neutral characters
width += 1
return width


if __name__ == "__main__": # pragma: no cover
from rich.console import Console

Expand Down
Loading