33import re
44import sys
55from collections .abc import (
6- Collection ,
76 Iterable ,
87 Iterator ,
98 Sequence ,
3130
3231from . import rich_utils as ru
3332
34- # Regular expression to identify strings that we should sort numerically
35- _NUMERIC_RE = re .compile (
36- r"""
37- ^ # Start of string
38- [-+]? # Optional sign
39- (?: # Start of non-capturing group
40- \d+\.?\d* # Matches 123 or 123. or 123.45
41- | # OR
42- \.\d+ # Matches .45
43- ) # End of group
44- $ # End of string
45- """ ,
46- re .VERBOSE ,
47- )
48-
49- # Regular expression to identify whitespace characters that are rendered as
50- # control sequences (like ^J or ^I) in the completion menu.
51- _CONTROL_WHITESPACE_RE = re .compile (r'\r\n|[\n\r\t\f\v]' )
52-
5333
5434@dataclass (frozen = True , slots = True , kw_only = True )
5535class CompletionItem :
5636 """A single completion result."""
5737
38+ # Regular expression to identify whitespace characters that are rendered as
39+ # control sequences (like ^J or ^I) in the completion menu.
40+ _CONTROL_WHITESPACE_RE = re .compile (r'\r\n|[\n\r\t\f\v]' )
41+
5842 # The underlying object this completion represents (e.g., str, int, Path).
5943 # This is used to support argparse choices validation.
6044 value : Any = field (kw_only = False )
@@ -80,6 +64,18 @@ class CompletionItem:
8064 display_plain : str = field (init = False )
8165 display_meta_plain : str = field (init = False )
8266
67+ @classmethod
68+ def _clean_display (cls , val : str ) -> str :
69+ """Clean a string for display in the completion menu.
70+
71+ This replaces whitespace characters that are rendered as
72+ control sequences (like ^J or ^I) with spaces.
73+
74+ :param val: string to be cleaned
75+ :return: the cleaned string
76+ """
77+ return cls ._CONTROL_WHITESPACE_RE .sub (' ' , val )
78+
8379 def __post_init__ (self ) -> None :
8480 """Finalize the object after initialization."""
8581 # Derive text from value if it wasn't explicitly provided
@@ -90,9 +86,9 @@ def __post_init__(self) -> None:
9086 if not self .display :
9187 object .__setattr__ (self , "display" , self .text )
9288
93- # Sanitize display and display_meta
94- object .__setattr__ (self , "display" , sanitize_display_string (self .display ))
95- object .__setattr__ (self , "display_meta" , sanitize_display_string (self .display_meta ))
89+ # Clean display and display_meta
90+ object .__setattr__ (self , "display" , self . _clean_display (self .display ))
91+ object .__setattr__ (self , "display_meta" , self . _clean_display (self .display_meta ))
9692
9793 # Create plain text versions by stripping ANSI sequences.
9894 # These are stored as attributes for fast access during sorting/filtering.
@@ -143,20 +139,44 @@ def __hash__(self) -> int:
143139class CompletionResultsBase :
144140 """Base class for results containing a collection of CompletionItems."""
145141
142+ # Regular expression to identify strings that we should sort numerically
143+ _NUMERIC_RE = re .compile (
144+ r"""
145+ ^ # Start of string
146+ [-+]? # Optional sign
147+ (?: # Start of non-capturing group
148+ \d+\.?\d* # Matches 123 or 123. or 123.45
149+ | # OR
150+ \.\d+ # Matches .45
151+ ) # End of group
152+ $ # End of string
153+ """ ,
154+ re .VERBOSE ,
155+ )
156+
146157 # The collection of CompletionItems. This is stored internally as a tuple.
147158 items : Sequence [CompletionItem ] = field (default_factory = tuple , kw_only = False )
148159
149160 # If True, indicates the items are already provided in the desired display order.
150161 # If False, items will be sorted by their display value during initialization.
151162 is_sorted : bool = False
152163
164+ # True if every item in this collection has a numeric display string.
165+ # Used for sorting and alignment.
166+ numeric_display : bool = field (init = False )
167+
153168 def __post_init__ (self ) -> None :
154169 """Finalize the object after initialization."""
155170 from . import utils
156171
157172 unique_items = utils .remove_duplicates (self .items )
173+
174+ # Determine if all items have numeric display strings
175+ numeric_display = bool (unique_items ) and all (self ._NUMERIC_RE .match (i .display_plain ) for i in unique_items )
176+ object .__setattr__ (self , "numeric_display" , numeric_display )
177+
158178 if not self .is_sorted :
159- if all_display_numeric ( unique_items ) :
179+ if self . numeric_display :
160180 # Sort numerically
161181 unique_items .sort (key = lambda item : float (item .display_plain ))
162182 else :
@@ -257,20 +277,3 @@ class Completions(CompletionResultsBase):
257277
258278 # The quote character to use if adding an opening or closing quote to the matches.
259279 _quote_char : str = ""
260-
261-
262- def all_display_numeric (items : Collection [CompletionItem ]) -> bool :
263- """Return True if items is non-empty and every item.display_plain value is a numeric string."""
264- return bool (items ) and all (_NUMERIC_RE .match (item .display_plain ) for item in items )
265-
266-
267- def sanitize_display_string (val : str ) -> str :
268- """Sanitize a string for display in the completion menu.
269-
270- This replaces whitespace characters that are rendered as
271- control sequences (like ^J or ^I) with spaces.
272-
273- :param val: string to be sanitized
274- :return: the sanitized string
275- """
276- return _CONTROL_WHITESPACE_RE .sub (' ' , val )
0 commit comments