From ec619077e65074ee891da791f4996a16d4135a13 Mon Sep 17 00:00:00 2001 From: MohammadYusif Date: Sun, 23 Aug 2026 12:39:05 +0300 Subject: [PATCH] fix: apply column max widths when rendering tables (#88) `Table.set_column_max_width()` stored the width in `_column_widths` instead of `_column_max_widths`. Since `_column_max_widths` was read in `_build_table_rows()` and `_get_cell_width()` but never written, the max-width and cell-wrapping machinery was unreachable: a wide column was never capped, and because `_get_cell_width()` folds `_column_widths` in with `max()`, the value acted as a *minimum* width and padded narrow columns out instead. Routing the setter to the right attribute exposes a second defect in the now-reachable wrapping branch: it asserted the table's io was an `Output`, but `Table` accepts `IO | Output` and `Command.table()` passes an `IO`, so wrapping raised `AssertionError` for the common case. The formatter is now resolved from either, mirroring `IO.remove_format()`. This restores the documented way to fit a table that is wider than the terminal. --- news/88.bugfix.md | 1 + src/cleo/ui/table.py | 8 +++++--- tests/ui/test_table.py | 43 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 news/88.bugfix.md diff --git a/news/88.bugfix.md b/news/88.bugfix.md new file mode 100644 index 00000000..69de7fa6 --- /dev/null +++ b/news/88.bugfix.md @@ -0,0 +1 @@ +Fix `Table.set_column_max_width()` having no effect on rendered tables diff --git a/src/cleo/ui/table.py b/src/cleo/ui/table.py index 2651bb51..da2a3410 100644 --- a/src/cleo/ui/table.py +++ b/src/cleo/ui/table.py @@ -104,7 +104,7 @@ def set_column_widths(self, widths: list[int]) -> Table: return self def set_column_max_width(self, column_index: int, width: int) -> Table: - self._column_widths[column_index] = width + self._column_max_widths[column_index] = width return self @@ -426,8 +426,10 @@ def _build_table_rows(self, rows: Rows) -> Iterator[Row | TableSeparator]: if column in self._column_max_widths and self._column_max_widths[ column ] < len(self._io.remove_format(cell)): - assert isinstance(self._io, Output) - cell = self._io.formatter.format_and_wrap( + output = ( + self._io if isinstance(self._io, Output) else self._io.output + ) + cell = output.formatter.format_and_wrap( cell, self._column_max_widths[column] * colspan ) diff --git a/tests/ui/test_table.py b/tests/ui/test_table.py index d10b60c2..3901b114 100644 --- a/tests/ui/test_table.py +++ b/tests/ui/test_table.py @@ -481,3 +481,46 @@ def test_style_for_side_effects(io: BufferedIO) -> None: output2 = io.fetch_output() assert output1 != output2 + + +def test_column_max_width_wraps_long_cells(io: BufferedIO) -> None: + table = Table(io) + table.set_headers(["ISBN", "Description"]) + table.set_rows( + [["99921-58-10-7", "A long description that has to be wrapped to fit"]] + ) + table.set_column_max_width(1, 12) + + table.render() + + expected = """\ ++---------------+--------------+ +| ISBN | Description | ++---------------+--------------+ +| 99921-58-10-7 | A long descr | +| | iption that | +| | has to be wr | +| | apped to fit | ++---------------+--------------+ +""" + + assert io.fetch_output() == expected + + +def test_column_max_width_does_not_widen_short_cells(io: BufferedIO) -> None: + table = Table(io) + table.set_headers(["ISBN", "Title"]) + table.set_rows([["99921-58-10-7", "Divine Comedy"]]) + table.set_column_max_width(1, 40) + + table.render() + + expected = """\ ++---------------+---------------+ +| ISBN | Title | ++---------------+---------------+ +| 99921-58-10-7 | Divine Comedy | ++---------------+---------------+ +""" + + assert io.fetch_output() == expected