Skip to content

Commit 8afc374

Browse files
committed
Fixed issue where ANSI strings were being placed in tables without being converted to a Text object.
Fixed issue where ALLOW_STYLE policy was not being recognized when displaying completion results. Added plain text versions of display and display_meta fields for sorting.
1 parent 323e5e9 commit 8afc374

File tree

7 files changed

+38
-18
lines changed

7 files changed

+38
-18
lines changed

cmd2/argparse_completer.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
cast,
1919
)
2020

21+
from rich.text import Text
22+
2123
from .constants import INFINITY
2224
from .rich_utils import Cmd2GeneralConsole
2325

@@ -587,7 +589,7 @@ def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, used_f
587589
return Completions(items)
588590

589591
def _format_completions(self, arg_state: _ArgumentState, completions: Completions) -> Completions:
590-
"""Format CompletionItems into hint table."""
592+
"""Format CompletionItems into completion table."""
591593
# Skip table generation for single results or if the list exceeds the
592594
# user-defined threshold for table display.
593595
if len(completions) < 2 or len(completions) > self._cmd2_app.max_completion_table_items:
@@ -611,7 +613,7 @@ def _format_completions(self, arg_state: _ArgumentState, completions: Completion
611613
# Determine if all display values are numeric so we can right-align them
612614
all_nums = all_display_numeric(completions.items)
613615

614-
# Build header row for the hint table
616+
# Build header row
615617
rich_columns: list[Column] = []
616618
rich_columns.append(Column(destination.upper(), justify="right" if all_nums else "left", no_wrap=True))
617619
table_header = cast(Sequence[str | Column] | None, arg_state.action.get_table_header()) # type: ignore[attr-defined]
@@ -621,12 +623,12 @@ def _format_completions(self, arg_state: _ArgumentState, completions: Completion
621623
column if isinstance(column, Column) else Column(column, overflow="fold") for column in table_header
622624
)
623625

624-
# Build the hint table
626+
# Add the data rows
625627
hint_table = Table(*rich_columns, box=SIMPLE_HEAD, show_edge=False, border_style=Cmd2Style.TABLE_BORDER)
626628
for item in completions:
627-
hint_table.add_row(item.display, *item.table_row)
629+
hint_table.add_row(Text.from_ansi(item.display), *item.table_row)
628630

629-
# Generate the hint table string
631+
# Generate the table string
630632
console = Cmd2GeneralConsole()
631633
with console.capture() as capture:
632634
console.print(hint_table, end="", soft_wrap=False)

cmd2/cmd2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4424,7 +4424,7 @@ def do_set(self, args: argparse.Namespace) -> None:
44244424
settable_table.add_row(
44254425
param,
44264426
str(settable.value),
4427-
settable.description,
4427+
Text.from_ansi(settable.description),
44284428
)
44294429
self.last_result[param] = settable.value
44304430

cmd2/completion.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
overload,
2222
)
2323

24+
from . import string_utils as su
25+
2426
if TYPE_CHECKING: # pragma: no cover
2527
from .cmd2 import Cmd
2628
from .command_definition import CommandSet
@@ -64,15 +66,22 @@ class CompletionItem:
6466
text: str = ""
6567

6668
# Optional string for displaying the completion differently in the completion menu.
69+
# This can contain ANSI style sequences. A plain version is stored in display_plain.
6770
display: str = ""
6871

6972
# Optional meta information about completion which displays in the completion menu.
73+
# This can contain ANSI style sequences. A plain version is stored in display_meta_plain.
7074
display_meta: str = ""
7175

7276
# Optional row data for completion tables. Length must match the associated argparse
7377
# argument's table_header. This is stored internally as a tuple.
7478
table_row: Sequence[Any] = field(default_factory=tuple)
7579

80+
# Plain text versions of display fields (stripped of ANSI) for sorting/filtering.
81+
# These are set in __post_init__().
82+
display_plain: str = field(init=False)
83+
display_meta_plain: str = field(init=False)
84+
7685
def __post_init__(self) -> None:
7786
"""Finalize the object after initialization."""
7887
# Derive text from value if it wasn't explicitly provided
@@ -83,6 +92,11 @@ def __post_init__(self) -> None:
8392
if not self.display:
8493
object.__setattr__(self, "display", self.text)
8594

95+
# Pre-calculate plain text versions by stripping ANSI sequences.
96+
# These are stored as attributes for fast access during sorting/filtering.
97+
object.__setattr__(self, "display_plain", su.strip_style(self.display))
98+
object.__setattr__(self, "display_meta_plain", su.strip_style(self.display_meta))
99+
86100
# Make sure all table row objects are renderable by a Rich table.
87101
renderable_data = [obj if is_renderable(obj) else str(obj) for obj in self.table_row]
88102

@@ -140,10 +154,10 @@ def __post_init__(self) -> None:
140154
if not self.is_sorted:
141155
if all_display_numeric(unique_items):
142156
# Sort numerically
143-
unique_items.sort(key=lambda item: float(item.display))
157+
unique_items.sort(key=lambda item: float(item.display_plain))
144158
else:
145159
# Standard string sort
146-
unique_items.sort(key=lambda item: utils.DEFAULT_STR_SORT_KEY(item.display))
160+
unique_items.sort(key=lambda item: utils.DEFAULT_STR_SORT_KEY(item.display_plain))
147161

148162
object.__setattr__(self, "is_sorted", True)
149163

@@ -247,8 +261,8 @@ class Completions(CompletionResultsBase):
247261

248262

249263
def all_display_numeric(items: Collection[CompletionItem]) -> bool:
250-
"""Return True if items is non-empty and every item.display is a numeric string."""
251-
return bool(items) and all(NUMERIC_RE.match(item.display) for item in items)
264+
"""Return True if items is non-empty and every item.display_plain value is a numeric string."""
265+
return bool(items) and all(NUMERIC_RE.match(item.display_plain) for item in items)
252266

253267

254268
#############################################

cmd2/pt_utils.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
constants,
2525
utils,
2626
)
27+
from . import rich_utils as ru
2728

2829
if TYPE_CHECKING: # pragma: no cover
2930
from .cmd2 import Cmd
@@ -101,6 +102,9 @@ def get_completions(self, document: Document, _complete_event: object) -> Iterab
101102
buffer.cursor_right(search_text_length)
102103
return
103104

105+
# Determine if we should remove style from completion text
106+
remove_style = ru.ALLOW_STYLE == ru.AllowStyle.NEVER
107+
104108
# Return the completions
105109
for item in completions:
106110
# Set offset to the start of the current word to overwrite it with the completion
@@ -129,8 +133,8 @@ def get_completions(self, document: Document, _complete_event: object) -> Iterab
129133
yield Completion(
130134
match_text,
131135
start_position=start_position,
132-
display=ANSI(item.display),
133-
display_meta=ANSI(item.display_meta),
136+
display=item.display_plain if remove_style else ANSI(item.display),
137+
display_meta=item.display_meta_plain if remove_style else ANSI(item.display_meta),
134138
)
135139

136140

tests/scripts/postcmds.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
set allow_style Never
1+
set always_show_hint False

tests/scripts/precmds.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
set allow_style Always
1+
set always_show_hint True

tests/test_cmd2.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -465,11 +465,11 @@ def test_run_script_nested_run_scripts(base_app, request) -> None:
465465
expected = f"""
466466
{initial_run}
467467
_relative_run_script precmds.txt
468-
set allow_style Always
468+
set always_show_hint True
469469
help
470470
shortcuts
471471
_relative_run_script postcmds.txt
472-
set allow_style Never"""
472+
set always_show_hint False"""
473473
out, _err = run_cmd(base_app, 'history -s')
474474
assert out == normalize(expected)
475475

@@ -482,11 +482,11 @@ def test_runcmds_plus_hooks(base_app, request) -> None:
482482
base_app.runcmds_plus_hooks(['run_script ' + prefilepath, 'help', 'shortcuts', 'run_script ' + postfilepath])
483483
expected = f"""
484484
run_script {prefilepath}
485-
set allow_style Always
485+
set always_show_hint True
486486
help
487487
shortcuts
488488
run_script {postfilepath}
489-
set allow_style Never"""
489+
set always_show_hint False"""
490490

491491
out, _err = run_cmd(base_app, 'history -s')
492492
assert out == normalize(expected)

0 commit comments

Comments
 (0)