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 which 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-
4933
5034@dataclass (frozen = True , slots = True , kw_only = True )
5135class CompletionItem :
5236 """A single completion result."""
5337
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+
5442 # The underlying object this completion represents (e.g., str, int, Path).
5543 # This is used to support argparse choices validation.
5644 value : Any = field (kw_only = False )
@@ -76,6 +64,18 @@ class CompletionItem:
7664 display_plain : str = field (init = False )
7765 display_meta_plain : str = field (init = False )
7866
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+
7979 def __post_init__ (self ) -> None :
8080 """Finalize the object after initialization."""
8181 # Derive text from value if it wasn't explicitly provided
@@ -86,7 +86,11 @@ def __post_init__(self) -> None:
8686 if not self .display :
8787 object .__setattr__ (self , "display" , self .text )
8888
89- # Pre-calculate plain text versions by stripping ANSI sequences.
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 ))
92+
93+ # Create plain text versions by stripping ANSI sequences.
9094 # These are stored as attributes for fast access during sorting/filtering.
9195 object .__setattr__ (self , "display_plain" , su .strip_style (self .display ))
9296 object .__setattr__ (self , "display_meta_plain" , su .strip_style (self .display_meta ))
@@ -135,20 +139,44 @@ def __hash__(self) -> int:
135139class CompletionResultsBase :
136140 """Base class for results containing a collection of CompletionItems."""
137141
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+
138157 # The collection of CompletionItems. This is stored internally as a tuple.
139158 items : Sequence [CompletionItem ] = field (default_factory = tuple , kw_only = False )
140159
141160 # If True, indicates the items are already provided in the desired display order.
142161 # If False, items will be sorted by their display value during initialization.
143162 is_sorted : bool = False
144163
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+
145168 def __post_init__ (self ) -> None :
146169 """Finalize the object after initialization."""
147170 from . import utils
148171
149172 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+
150178 if not self .is_sorted :
151- if all_display_numeric ( unique_items ) :
179+ if self . numeric_display :
152180 # Sort numerically
153181 unique_items .sort (key = lambda item : float (item .display_plain ))
154182 else :
@@ -249,8 +277,3 @@ class Completions(CompletionResultsBase):
249277
250278 # The quote character to use if adding an opening or closing quote to the matches.
251279 _quote_char : str = ""
252-
253-
254- def all_display_numeric (items : Collection [CompletionItem ]) -> bool :
255- """Return True if items is non-empty and every item.display_plain value is a numeric string."""
256- return bool (items ) and all (NUMERIC_RE .match (item .display_plain ) for item in items )
0 commit comments