Skip to content

Commit 7d9ebc7

Browse files
committed
Fixed not being able to clear CompletionItem.text.
1 parent 143a99e commit 7d9ebc7

2 files changed

Lines changed: 35 additions & 11 deletions

File tree

cmd2/completion.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@
3131
from . import rich_utils as ru
3232

3333

34+
class _UnsetText(str):
35+
"""Internal sentinel to distinguish between an unset and an explicit empty string."""
36+
37+
__slots__ = ()
38+
39+
40+
_UNSET_TEXT = _UnsetText("")
41+
42+
3443
@dataclass(frozen=True, slots=True, kw_only=True)
3544
class CompletionItem:
3645
"""A single completion result."""
@@ -39,13 +48,16 @@ class CompletionItem:
3948
# control sequences (like ^J or ^I) in the completion menu.
4049
_CONTROL_WHITESPACE_RE = re.compile(r"\r\n|[\n\r\t\f\v]")
4150

42-
# The underlying object this completion represents (e.g., str, int, Path).
43-
# This is used to support argparse choices validation.
51+
# The core object this completion represents (e.g., str, int, Path).
52+
# This serves as the default source for the completion string and is used
53+
# to support object-based validation when used in argparse choices.
4454
value: Any = field(kw_only=False)
4555

46-
# The actual string that will be inserted into the command line.
47-
# If not provided, it defaults to str(value).
48-
text: str = ""
56+
# The actual completion string. If not provided, it defaults to str(value)
57+
# during initialization. This can be used to provide a human-friendly alias
58+
# for complex objects in an argparse choices list (requires a matching
59+
# 'type' converter for validation).
60+
text: str = _UNSET_TEXT
4961

5062
# Optional string for displaying the completion differently in the completion menu.
5163
# This can contain ANSI style sequences. A plain version is stored in display_plain.
@@ -60,9 +72,8 @@ class CompletionItem:
6072
table_data: Sequence[Any] = field(default_factory=tuple)
6173

6274
# Plain text versions of display fields (stripped of ANSI) for sorting/filtering.
63-
# These are set in __post_init__().
64-
display_plain: str = field(init=False)
65-
display_meta_plain: str = field(init=False)
75+
display_plain: str = field(default="", init=False)
76+
display_meta_plain: str = field(default="", init=False)
6677

6778
@classmethod
6879
def _clean_display(cls, val: str) -> str:
@@ -78,8 +89,8 @@ def _clean_display(cls, val: str) -> str:
7889

7990
def __post_init__(self) -> None:
8091
"""Finalize the object after initialization."""
81-
# Derive text from value if it wasn't explicitly provided
82-
if not self.text:
92+
# If the completion string was not provided, derive it from value
93+
if isinstance(self.text, _UnsetText):
8394
object.__setattr__(self, "text", str(self.value))
8495

8596
# Ensure display is never blank.
@@ -163,7 +174,7 @@ class CompletionResultsBase:
163174

164175
# True if every item in this collection has a numeric display string.
165176
# Used for sorting and alignment.
166-
numeric_display: bool = field(init=False)
177+
numeric_display: bool = field(default=False, init=False)
167178

168179
def __post_init__(self) -> None:
169180
"""Finalize the object after initialization."""

tests/test_completion.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,3 +1300,16 @@ def test_subcommand_tab_completion_with_no_completer_scu(scu_app) -> None:
13001300

13011301
completions = scu_app.complete(text, line, begidx, endidx)
13021302
assert not completions
1303+
1304+
1305+
def test_completion_item_blank_text() -> None:
1306+
# Verify that CompletionItem correctly handles blank text when set via dataclasses.replace
1307+
import dataclasses
1308+
1309+
value = "something"
1310+
item = CompletionItem(value=value)
1311+
assert item.text == value
1312+
1313+
item2 = dataclasses.replace(item, text="")
1314+
assert item2.text == ""
1315+
assert item2.display == value

0 commit comments

Comments
 (0)