@@ -2213,6 +2213,17 @@ def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
22132213 # starts with `/home/`, `C:\`, etc)
22142214
22152215 text = context .token
2216+ code_until_cursor = self ._extract_code (context .text_until_cursor )
2217+ completion_type = self ._determine_completion_context (code_until_cursor )
2218+ in_cli_context = self ._is_completing_in_cli_context (code_until_cursor )
2219+ if (
2220+ completion_type == self ._CompletionContextType .ATTRIBUTE
2221+ and not in_cli_context
2222+ ):
2223+ return {
2224+ "completions" : [],
2225+ "suppress" : False ,
2226+ }
22162227
22172228 # chars that require escaping with backslash - i.e. chars
22182229 # that readline treats incorrectly as delimiters, but we
@@ -2482,8 +2493,9 @@ def _jedi_matcher(self, context: CompletionContext) -> _JediMatcherResult:
24822493 )
24832494 return {
24842495 "completions" : matches ,
2485- # static analysis should not suppress other matchers
2486- "suppress" : {_get_matcher_id (self .file_matcher )} if matches else False ,
2496+ # static analysis should not suppress other matcher
2497+ # NOTE: file_matcher is automatically suppressed on attribute completions
2498+ "suppress" : False ,
24872499 }
24882500
24892501 def _jedi_matches (
@@ -2599,12 +2611,47 @@ def _determine_completion_context(self, line):
25992611 return self ._CompletionContextType .GLOBAL
26002612
26012613 # Handle all other attribute matches np.ran, d[0].k, (a,b).count
2602- chain_match = re .search (r".*(.+\.(?:[a-zA-Z]\w*)?)$" , line )
2614+ chain_match = re .search (r".*(.+(?<!\s) \.(?:[a-zA-Z]\w*)?)$" , line )
26032615 if chain_match :
26042616 return self ._CompletionContextType .ATTRIBUTE
26052617
26062618 return self ._CompletionContextType .GLOBAL
26072619
2620+ def _is_completing_in_cli_context (self , text : str ) -> bool :
2621+ """
2622+ Determine if we are completing in a CLI alias, line magic, or bang expression context.
2623+ """
2624+ stripped = text .lstrip ()
2625+ if stripped .startswith ("!" ) or stripped .startswith ("%" ):
2626+ return True
2627+ # Check for CLI aliases
2628+ try :
2629+ tokens = stripped .split (None , 1 )
2630+ if not tokens :
2631+ return False
2632+ first_token = tokens [0 ]
2633+
2634+ # Must have arguments after the command for this to apply
2635+ if len (tokens ) < 2 :
2636+ return False
2637+
2638+ # Check if first token is a known alias
2639+ if not any (
2640+ alias [0 ] == first_token for alias in self .shell .alias_manager .aliases
2641+ ):
2642+ return False
2643+
2644+ try :
2645+ if first_token in self .shell .user_ns :
2646+ # There's a variable defined, so the alias is overshadowed
2647+ return False
2648+ except (AttributeError , KeyError ):
2649+ pass
2650+
2651+ return True
2652+ except Exception :
2653+ return False
2654+
26082655 def _is_in_string_or_comment (self , text ):
26092656 """
26102657 Determine if the cursor is inside a string or comment.
@@ -2726,7 +2773,11 @@ def python_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
27262773 """Match attributes or global python names"""
27272774 text = context .text_until_cursor
27282775 text = self ._extract_code (text )
2729- completion_type = self ._determine_completion_context (text )
2776+ in_cli_context = self ._is_completing_in_cli_context (text )
2777+ if in_cli_context :
2778+ completion_type = self ._CompletionContextType .GLOBAL
2779+ else :
2780+ completion_type = self ._determine_completion_context (text )
27302781 if completion_type == self ._CompletionContextType .ATTRIBUTE :
27312782 try :
27322783 matches , fragment = self ._attr_matches (
@@ -2746,8 +2797,6 @@ def python_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
27462797 matches = _convert_matcher_v1_result_to_v2 (
27472798 matches , type = "attribute" , fragment = fragment
27482799 )
2749- if matches ["completions" ]:
2750- matches ["suppress" ] = {_get_matcher_id (self .file_matcher )}
27512800 return matches
27522801 except NameError :
27532802 # catches <undefined attributes>.<tab>
0 commit comments