Skip to content
Merged
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
16 changes: 5 additions & 11 deletions cmd2/argparse_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ def get_items(self) -> list[CompletionItems]:
RenderableType,
)
from rich.protocol import is_renderable
from rich.table import Column, Table
from rich.table import Column
from rich.text import Text
from rich_argparse import (
ArgumentDefaultsRichHelpFormatter,
Expand All @@ -295,6 +295,7 @@ def get_items(self) -> list[CompletionItems]:
)

from . import constants
from . import rich_utils as ru
from .rich_utils import Cmd2RichArgparseConsole
from .styles import Cmd2Style

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

# Left pad the text like an argparse argument group does
left_padding = formatter._indent_increment
text_table = Table(
Column(overflow="fold"),
box=None,
show_header=False,
padding=(0, 0, 0, left_padding),
)
text_table.add_row(self.text)
# Indent text like an argparse argument group does
indented_text = ru.indent(self.text, formatter._indent_increment)

return Group(styled_title, text_table)
return Group(styled_title, indented_text)


class Cmd2ArgumentParser(argparse.ArgumentParser):
Expand Down
33 changes: 15 additions & 18 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@

import rich.box
from rich.console import Group
from rich.padding import Padding
from rich.rule import Rule
from rich.style import Style, StyleType
from rich.table import (
Expand Down Expand Up @@ -4076,9 +4075,8 @@ def do_help(self, args: argparse.Namespace) -> None:
# Indent doc_leader to align with the help tables.
self.poutput()
self.poutput(
Padding.indent(self.doc_leader, 1),
ru.indent(self.doc_leader, 1),
style=Cmd2Style.HELP_LEADER,
soft_wrap=False,
)
self.poutput()

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

# Add a row that looks like a table header.
header_grid = Table.grid()
header_grid.add_row(header, style=Cmd2Style.HELP_HEADER)
header_grid.add_row(Rule(characters=self.ruler, style=Cmd2Style.TABLE_BORDER))
self.poutput(Padding.indent(header_grid, 1))
# Print a row that looks like a table header.
if header:
header_grid = Table.grid()
header_grid.add_row(header, style=Cmd2Style.HELP_HEADER)
header_grid.add_row(Rule(characters=self.ruler, style=Cmd2Style.TABLE_BORDER))
self.poutput(ru.indent(header_grid, 1))

# Subtract 2 from the max column width to account for the
# one-space indentation and a one-space right margin.
maxcol = min(maxcol, ru.console_width()) - 2

# Print the topics in columns.
# Subtract 1 from maxcol to account for indentation.
maxcol = min(maxcol, ru.console_width()) - 1
columnized_cmds = self.render_columns(cmds, maxcol)
self.poutput(Padding.indent(columnized_cmds, 1), soft_wrap=False)
self.poutput(ru.indent(columnized_cmds, 1))
self.poutput()

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

# Indent header to align with the help tables.
self.poutput(
Padding.indent(header, 1),
style=Cmd2Style.HELP_HEADER,
soft_wrap=False,
)
self.poutput(ru.indent(header, 1), style=Cmd2Style.HELP_HEADER)
topics_table = Table(
Column("Name", no_wrap=True),
Column("Description", overflow="fold"),
Expand Down Expand Up @@ -5529,7 +5526,7 @@ class TestMyAppCase(Cmd2TestCase):
num_transcripts = len(transcripts_expanded)
plural = '' if len(transcripts_expanded) == 1 else 's'
self.poutput(
Rule("cmd2 transcript test", style=Style.null()),
Rule("cmd2 transcript test", characters=self.ruler, style=Style.null()),
style=Style(bold=True),
)
self.poutput(f'platform {sys.platform} -- Python {verinfo}, cmd2-{cmd2.__version__}, readline-{rl_type}')
Expand All @@ -5548,7 +5545,7 @@ class TestMyAppCase(Cmd2TestCase):
if test_results.wasSuccessful():
self.perror(stream.read(), end="", style=None)
finish_msg = f'{num_transcripts} transcript{plural} passed in {execution_time:.3f} seconds'
self.psuccess(Rule(finish_msg, style=Style.null()))
self.psuccess(Rule(finish_msg, characters=self.ruler, style=Style.null()))
else:
# Strip off the initial traceback which isn't particularly useful for end users
error_str = stream.read()
Expand Down
29 changes: 29 additions & 0 deletions cmd2/rich_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
RenderableType,
RichCast,
)
from rich.padding import Padding
from rich.style import StyleType
from rich.table import (
Column,
Table,
)
from rich.text import Text
from rich.theme import Theme
from rich_argparse import RichHelpFormatter
Expand Down Expand Up @@ -288,6 +293,30 @@ def string_to_rich_text(text: str) -> Text:
return result


def indent(renderable: RenderableType, level: int) -> Padding:
"""Indent a Rich renderable.

When soft-wrapping is enabled, a Rich console is unable to properly print a
Padding object of indented text, as it truncates long strings instead of wrapping
them. This function provides a workaround for this issue, ensuring that indented
text is printed correctly regardless of the soft-wrap setting.

For non-text objects, this function merely serves as a convenience
wrapper around Padding.indent().

:param renderable: a Rich renderable to indent.
:param level: number of characters to indent.
:return: a Padding object containing the indented content.
"""
if isinstance(renderable, (str, Text)):
# Wrap text in a grid to handle the wrapping.
text_grid = Table.grid(Column(overflow="fold"))
text_grid.add_row(renderable)
renderable = text_grid

return Padding.indent(renderable, level)


def prepare_objects_for_rich_print(*objects: Any) -> tuple[RenderableType, ...]:
"""Prepare a tuple of objects for printing by Rich's Console.print().

Expand Down
41 changes: 41 additions & 0 deletions tests/test_rich_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""Unit testing for cmd2/rich_utils.py module"""

import pytest
import rich.box
from rich.style import Style
from rich.table import Table
from rich.text import Text

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


def test_indented_text() -> None:
console = ru.Cmd2GeneralConsole()

# With an indention of 10, text will be evenly split across two lines.
console.width = 20
text = "A" * 20
level = 10
indented_text = ru.indent(text, level)

with console.capture() as capture:
console.print(indented_text)
result = capture.get().splitlines()

padding = " " * level
expected_line = padding + ("A" * 10)
assert result[0] == expected_line
assert result[1] == expected_line


def test_indented_table() -> None:
console = ru.Cmd2GeneralConsole()

level = 2
table = Table("Column", box=rich.box.ASCII)
table.add_row("Some Data")
indented_table = ru.indent(table, level)

with console.capture() as capture:
console.print(indented_table)
result = capture.get().splitlines()

padding = " " * level
assert result[0].startswith(padding + "+-----------+")
assert result[1].startswith(padding + "| Column |")
assert result[2].startswith(padding + "|-----------|")
assert result[3].startswith(padding + "| Some Data |")
assert result[4].startswith(padding + "+-----------+")


@pytest.mark.parametrize(
('rich_text', 'string'),
[
Expand Down
Loading