11"""Provides classes and functions related to command-line completion."""
22
3+ import copy
34import re
45import sys
56from collections .abc import (
@@ -39,12 +40,15 @@ class CompletionItem:
3940 # control sequences (like ^J or ^I) in the completion menu.
4041 _CONTROL_WHITESPACE_RE = re .compile (r"\r\n|[\n\r\t\f\v]" )
4142
42- # The underlying object this completion represents (e.g., str, int, Path).
43- # This is used to support argparse choices validation.
43+ # The core object this completion represents (e.g., str, int, Path).
44+ # This serves as the default source for the completion string and is used
45+ # to support object-based validation when used in argparse choices.
4446 value : Any = field (kw_only = False )
4547
46- # The actual string that will be inserted into the command line.
47- # If not provided, it defaults to str(value).
48+ # The actual completion string. If this is empty, it defaults to str(value)
49+ # during initialization. This can be used to provide a human-friendly alias
50+ # for complex objects in an argparse choices list (requires a matching
51+ # 'type' converter for validation).
4852 text : str = ""
4953
5054 # Optional string for displaying the completion differently in the completion menu.
@@ -59,10 +63,16 @@ class CompletionItem:
5963 # argument's table_columns. This is stored internally as a tuple.
6064 table_data : Sequence [Any ] = field (default_factory = tuple )
6165
66+ def _set_text (self , text : str ) -> None :
67+ """Update the completion string.
68+
69+ Used internally by cmd2 to prepare the value for the command line.
70+ """
71+ object .__setattr__ (self , "text" , text )
72+
6273 # 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 )
74+ display_plain : str = field (default = "" , init = False )
75+ display_meta_plain : str = field (default = "" , init = False )
6676
6777 @classmethod
6878 def _clean_display (cls , val : str ) -> str :
@@ -78,7 +88,7 @@ def _clean_display(cls, val: str) -> str:
7888
7989 def __post_init__ (self ) -> None :
8090 """Finalize the object after initialization."""
81- # Derive text from value if it wasn't explicitly provided
91+ # If the completion string is empty, derive it from value
8292 if not self .text :
8393 object .__setattr__ (self , "text" , str (self .value ))
8494
@@ -163,13 +173,15 @@ class CompletionResultsBase:
163173
164174 # True if every item in this collection has a numeric display string.
165175 # Used for sorting and alignment.
166- numeric_display : bool = field (init = False )
176+ numeric_display : bool = field (default = False , init = False )
167177
168178 def __post_init__ (self ) -> None :
169179 """Finalize the object after initialization."""
170180 from . import utils
171181
172- unique_items = utils .remove_duplicates (self .items )
182+ # Remove duplicates and then copy the items so any changes made during completion
183+ # don't affect the originals (e.g., persistent CompletionItems in a choices list)
184+ unique_items = [copy .copy (item ) for item in utils .remove_duplicates (self .items )]
173185
174186 # Determine if all items have numeric display strings
175187 numeric_display = bool (unique_items ) and all (self ._NUMERIC_RE .match (i .display_plain ) for i in unique_items )
@@ -262,18 +274,34 @@ class Completions(CompletionResultsBase):
262274 # This flag is ignored if there are multiple matches.
263275 allow_finalization : bool = True
264276
265- #####################################################################
266- # The following fields are used internally by cmd2 to handle
267- # automatic quoting and are not intended for user modification.
268- #####################################################################
269-
270277 # Whether to add an opening quote to the matches.
271- _add_opening_quote : bool = False
278+ _add_opening_quote : bool = field ( default = False , init = False )
272279
273280 # The starting index of the user-provided search text within a full match.
274281 # This accounts for leading shortcuts (e.g., in '?cmd', the offset is 1).
275282 # Used to ensure opening quotes are inserted after the shortcut rather than before it.
276- _search_text_offset : int = 0
283+ _search_text_offset : int = field ( default = 0 , init = False )
277284
278285 # The quote character to use if adding an opening or closing quote to the matches.
279- _quote_char : str = ""
286+ _quote_char : str = field (default = "" , init = False )
287+
288+ def _set_add_opening_quote (self , value : bool ) -> None :
289+ """Set whether to add an opening quote.
290+
291+ Used internally by cmd2 for automatic quoting.
292+ """
293+ object .__setattr__ (self , "_add_opening_quote" , value )
294+
295+ def _set_search_text_offset (self , value : int ) -> None :
296+ """Set the search text offset.
297+
298+ Used internally by cmd2 for automatic quoting.
299+ """
300+ object .__setattr__ (self , "_search_text_offset" , value )
301+
302+ def _set_quote_char (self , value : str ) -> None :
303+ """Set the quote character.
304+
305+ Used internally by cmd2 for automatic quoting.
306+ """
307+ object .__setattr__ (self , "_quote_char" , value )
0 commit comments