Skip to content

Commit ed88780

Browse files
authored
Added rich_utils.indent() to ensure indented text wraps instead of truncating when soft_wrap is enabled. (#1487)
1 parent 2f55890 commit ed88780

File tree

4 files changed

+90
-29
lines changed

4 files changed

+90
-29
lines changed

cmd2/argparse_custom.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ def get_items(self) -> list[CompletionItems]:
284284
RenderableType,
285285
)
286286
from rich.protocol import is_renderable
287-
from rich.table import Column, Table
287+
from rich.table import Column
288288
from rich.text import Text
289289
from rich_argparse import (
290290
ArgumentDefaultsRichHelpFormatter,
@@ -295,6 +295,7 @@ def get_items(self) -> list[CompletionItems]:
295295
)
296296

297297
from . import constants
298+
from . import rich_utils as ru
298299
from .rich_utils import Cmd2RichArgparseConsole
299300
from .styles import Cmd2Style
300301

@@ -1377,17 +1378,10 @@ def __rich__(self) -> Group:
13771378
style=formatter.styles["argparse.groups"],
13781379
)
13791380

1380-
# Left pad the text like an argparse argument group does
1381-
left_padding = formatter._indent_increment
1382-
text_table = Table(
1383-
Column(overflow="fold"),
1384-
box=None,
1385-
show_header=False,
1386-
padding=(0, 0, 0, left_padding),
1387-
)
1388-
text_table.add_row(self.text)
1381+
# Indent text like an argparse argument group does
1382+
indented_text = ru.indent(self.text, formatter._indent_increment)
13891383

1390-
return Group(styled_title, text_table)
1384+
return Group(styled_title, indented_text)
13911385

13921386

13931387
class Cmd2ArgumentParser(argparse.ArgumentParser):

cmd2/cmd2.py

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@
6565

6666
import rich.box
6767
from rich.console import Group
68-
from rich.padding import Padding
6968
from rich.rule import Rule
7069
from rich.style import Style, StyleType
7170
from rich.table import (
@@ -4076,9 +4075,8 @@ def do_help(self, args: argparse.Namespace) -> None:
40764075
# Indent doc_leader to align with the help tables.
40774076
self.poutput()
40784077
self.poutput(
4079-
Padding.indent(self.doc_leader, 1),
4078+
ru.indent(self.doc_leader, 1),
40804079
style=Cmd2Style.HELP_LEADER,
4081-
soft_wrap=False,
40824080
)
40834081
self.poutput()
40844082

@@ -4135,17 +4133,20 @@ def print_topics(self, header: str, cmds: list[str] | None, cmdlen: int, maxcol:
41354133
if not cmds:
41364134
return
41374135

4138-
# Add a row that looks like a table header.
4139-
header_grid = Table.grid()
4140-
header_grid.add_row(header, style=Cmd2Style.HELP_HEADER)
4141-
header_grid.add_row(Rule(characters=self.ruler, style=Cmd2Style.TABLE_BORDER))
4142-
self.poutput(Padding.indent(header_grid, 1))
4136+
# Print a row that looks like a table header.
4137+
if header:
4138+
header_grid = Table.grid()
4139+
header_grid.add_row(header, style=Cmd2Style.HELP_HEADER)
4140+
header_grid.add_row(Rule(characters=self.ruler, style=Cmd2Style.TABLE_BORDER))
4141+
self.poutput(ru.indent(header_grid, 1))
4142+
4143+
# Subtract 2 from the max column width to account for the
4144+
# one-space indentation and a one-space right margin.
4145+
maxcol = min(maxcol, ru.console_width()) - 2
41434146

41444147
# Print the topics in columns.
4145-
# Subtract 1 from maxcol to account for indentation.
4146-
maxcol = min(maxcol, ru.console_width()) - 1
41474148
columnized_cmds = self.render_columns(cmds, maxcol)
4148-
self.poutput(Padding.indent(columnized_cmds, 1), soft_wrap=False)
4149+
self.poutput(ru.indent(columnized_cmds, 1))
41494150
self.poutput()
41504151

41514152
def _print_documented_command_topics(self, header: str, cmds: list[str], verbose: bool) -> None:
@@ -4160,11 +4161,7 @@ def _print_documented_command_topics(self, header: str, cmds: list[str], verbose
41604161
return
41614162

41624163
# Indent header to align with the help tables.
4163-
self.poutput(
4164-
Padding.indent(header, 1),
4165-
style=Cmd2Style.HELP_HEADER,
4166-
soft_wrap=False,
4167-
)
4164+
self.poutput(ru.indent(header, 1), style=Cmd2Style.HELP_HEADER)
41684165
topics_table = Table(
41694166
Column("Name", no_wrap=True),
41704167
Column("Description", overflow="fold"),
@@ -5529,7 +5526,7 @@ class TestMyAppCase(Cmd2TestCase):
55295526
num_transcripts = len(transcripts_expanded)
55305527
plural = '' if len(transcripts_expanded) == 1 else 's'
55315528
self.poutput(
5532-
Rule("cmd2 transcript test", style=Style.null()),
5529+
Rule("cmd2 transcript test", characters=self.ruler, style=Style.null()),
55335530
style=Style(bold=True),
55345531
)
55355532
self.poutput(f'platform {sys.platform} -- Python {verinfo}, cmd2-{cmd2.__version__}, readline-{rl_type}')
@@ -5548,7 +5545,7 @@ class TestMyAppCase(Cmd2TestCase):
55485545
if test_results.wasSuccessful():
55495546
self.perror(stream.read(), end="", style=None)
55505547
finish_msg = f'{num_transcripts} transcript{plural} passed in {execution_time:.3f} seconds'
5551-
self.psuccess(Rule(finish_msg, style=Style.null()))
5548+
self.psuccess(Rule(finish_msg, characters=self.ruler, style=Style.null()))
55525549
else:
55535550
# Strip off the initial traceback which isn't particularly useful for end users
55545551
error_str = stream.read()

cmd2/rich_utils.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717
RenderableType,
1818
RichCast,
1919
)
20+
from rich.padding import Padding
2021
from rich.style import StyleType
22+
from rich.table import (
23+
Column,
24+
Table,
25+
)
2126
from rich.text import Text
2227
from rich.theme import Theme
2328
from rich_argparse import RichHelpFormatter
@@ -288,6 +293,30 @@ def string_to_rich_text(text: str) -> Text:
288293
return result
289294

290295

296+
def indent(renderable: RenderableType, level: int) -> Padding:
297+
"""Indent a Rich renderable.
298+
299+
When soft-wrapping is enabled, a Rich console is unable to properly print a
300+
Padding object of indented text, as it truncates long strings instead of wrapping
301+
them. This function provides a workaround for this issue, ensuring that indented
302+
text is printed correctly regardless of the soft-wrap setting.
303+
304+
For non-text objects, this function merely serves as a convenience
305+
wrapper around Padding.indent().
306+
307+
:param renderable: a Rich renderable to indent.
308+
:param level: number of characters to indent.
309+
:return: a Padding object containing the indented content.
310+
"""
311+
if isinstance(renderable, (str, Text)):
312+
# Wrap text in a grid to handle the wrapping.
313+
text_grid = Table.grid(Column(overflow="fold"))
314+
text_grid.add_row(renderable)
315+
renderable = text_grid
316+
317+
return Padding.indent(renderable, level)
318+
319+
291320
def prepare_objects_for_rich_print(*objects: Any) -> tuple[RenderableType, ...]:
292321
"""Prepare a tuple of objects for printing by Rich's Console.print().
293322

tests/test_rich_utils.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
"""Unit testing for cmd2/rich_utils.py module"""
22

33
import pytest
4+
import rich.box
45
from rich.style import Style
6+
from rich.table import Table
57
from rich.text import Text
68

79
from cmd2 import (
@@ -59,6 +61,45 @@ def test_string_to_rich_text() -> None:
5961
assert ru.string_to_rich_text(input_string).plain == input_string
6062

6163

64+
def test_indented_text() -> None:
65+
console = ru.Cmd2GeneralConsole()
66+
67+
# With an indention of 10, text will be evenly split across two lines.
68+
console.width = 20
69+
text = "A" * 20
70+
level = 10
71+
indented_text = ru.indent(text, level)
72+
73+
with console.capture() as capture:
74+
console.print(indented_text)
75+
result = capture.get().splitlines()
76+
77+
padding = " " * level
78+
expected_line = padding + ("A" * 10)
79+
assert result[0] == expected_line
80+
assert result[1] == expected_line
81+
82+
83+
def test_indented_table() -> None:
84+
console = ru.Cmd2GeneralConsole()
85+
86+
level = 2
87+
table = Table("Column", box=rich.box.ASCII)
88+
table.add_row("Some Data")
89+
indented_table = ru.indent(table, level)
90+
91+
with console.capture() as capture:
92+
console.print(indented_table)
93+
result = capture.get().splitlines()
94+
95+
padding = " " * level
96+
assert result[0].startswith(padding + "+-----------+")
97+
assert result[1].startswith(padding + "| Column |")
98+
assert result[2].startswith(padding + "|-----------|")
99+
assert result[3].startswith(padding + "| Some Data |")
100+
assert result[4].startswith(padding + "+-----------+")
101+
102+
62103
@pytest.mark.parametrize(
63104
('rich_text', 'string'),
64105
[

0 commit comments

Comments
 (0)