2929import re
3030import os
3131from glob import glob
32+ from functools import partial
3233from bpython import inspection
3334from bpython import importcompletion
3435from bpython ._py3compat import py3
5051
5152MAGIC_METHODS = ["__%s__" % s for s in [
5253 "init" , "repr" , "str" , "lt" , "le" , "eq" , "ne" , "gt" , "ge" , "cmp" , "hash" ,
53- "nonzero" , "unicode" , "getattr" , "setattr" , "get" , "set" ,"call" , "len" ,
54+ "nonzero" , "unicode" , "getattr" , "setattr" , "get" , "set" , "call" , "len" ,
5455 "getitem" , "setitem" , "iter" , "reversed" , "contains" , "add" , "sub" , "mul" ,
5556 "floordiv" , "mod" , "divmod" , "pow" , "lshift" , "rshift" , "and" , "xor" , "or" ,
5657 "div" , "truediv" , "neg" , "pos" , "abs" , "invert" , "complex" , "int" , "float" ,
6061def after_last_dot (name ):
6162 return name .rstrip ('.' ).rsplit ('.' )[- 1 ]
6263
63- def get_completer (cursor_offset , current_line , locals_ , argspec , current_block ,
64- mode , complete_magic_methods ):
65- """Returns a list of matches and a class for what kind of completion is happening
64+ def get_completer (completers , cursor_offset , line , ** kwargs ):
65+ """Returns a list of matches and an applicable completer
6666
67- If no completion type is relevant , returns None, None
67+ If no matches available , returns a tuple of an empty list and None
6868
69- Params :
69+ kwargs (all required) :
7070 cursor_offset is the current cursor column
71- current_line is a string of the current line
71+ line is a string of the current line
7272 locals_ is a dictionary of the environment
7373 argspec is an inspect.ArgSpec instance for the current function where
7474 the cursor is
@@ -79,53 +79,42 @@ def get_completer(cursor_offset, current_line, locals_, argspec, current_block,
7979 double underscore methods like __len__ in method signatures
8080 """
8181
82- kwargs = {'locals_' :locals_ , 'argspec' :argspec , 'current_block' :current_block ,
83- 'mode' :mode , 'complete_magic_methods' :complete_magic_methods }
84-
85- # mutually exclusive if matches: If one of these returns [], try the next one
86- for completer in [DictKeyCompletion ]:
87- matches = completer .matches (cursor_offset , current_line , ** kwargs )
88- if matches :
89- return sorted (set (matches )), completer
90-
91- # mutually exclusive matchers: if one returns [], don't go on
92- for completer in [StringLiteralAttrCompletion , ImportCompletion ,
93- FilenameCompletion , MagicMethodCompletion , GlobalCompletion ]:
94- matches = completer .matches (cursor_offset , current_line , ** kwargs )
82+ for completer in completers :
83+ matches = completer .matches (cursor_offset , line , ** kwargs )
9584 if matches is not None :
96- return sorted (set (matches )), completer
97-
98- matches = AttrCompletion .matches (cursor_offset , current_line , ** kwargs )
99-
100- # cumulative completions - try them all
101- # They all use current_word replacement and formatting
102- current_word_matches = []
103- for completer in [AttrCompletion , ParameterNameCompletion ]:
104- matches = completer .matches (cursor_offset , current_line , ** kwargs )
105- if matches is not None :
106- current_word_matches .extend (matches )
107-
108- if len (current_word_matches ) == 0 :
109- return None , None
110- return sorted (set (current_word_matches )), AttrCompletion
85+ return matches , (completer if matches else None )
86+ return [], None
87+
88+ def get_completer_bpython (** kwargs ):
89+ """"""
90+ return get_completer ([DictKeyCompletion ,
91+ StringLiteralAttrCompletion ,
92+ ImportCompletion ,
93+ FilenameCompletion ,
94+ MagicMethodCompletion ,
95+ GlobalCompletion ,
96+ CumulativeCompleter ([AttrCompletion , ParameterNameCompletion ])],
97+ ** kwargs )
11198
11299class BaseCompletionType (object ):
113100 """Describes different completion types"""
101+ @classmethod
114102 def matches (cls , cursor_offset , line , ** kwargs ):
115103 """Returns a list of possible matches given a line and cursor, or None
116104 if this completion type isn't applicable.
117105
118106 ie, import completion doesn't make sense if there cursor isn't after
119- an import or from statement
107+ an import or from statement, so it ought to return None.
120108
121109 Completion types are used to:
122- * `locate(cur, line)` their target word to replace given a line and cursor
110+ * `locate(cur, line)` their initial target word to replace given a line and cursor
123111 * find `matches(cur, line)` that might replace that word
124112 * `format(match)` matches to be displayed to the user
125113 * determine whether suggestions should be `shown_before_tab`
126114 * `substitute(cur, line, match)` in a match for what's found with `target`
127115 """
128116 raise NotImplementedError
117+ @classmethod
129118 def locate (cls , cursor_offset , line ):
130119 """Returns a start, stop, and word given a line and cursor, or None
131120 if no target for this type of completion is found under the cursor"""
@@ -134,25 +123,58 @@ def locate(cls, cursor_offset, line):
134123 def format (cls , word ):
135124 return word
136125 shown_before_tab = True # whether suggestions should be shown before the
137- # user hits tab, or only once that has happened
126+ # user hits tab, or only once that has happened
138127 def substitute (cls , cursor_offset , line , match ):
139128 """Returns a cursor offset and line with match swapped in"""
140129 start , end , word = cls .locate (cursor_offset , line )
141130 result = start + len (match ), line [:start ] + match + line [end :]
142131 return result
143132
133+ class CumulativeCompleter (object ):
134+ """Returns combined matches from several completers"""
135+ def __init__ (self , completers ):
136+ if not completers :
137+ raise ValueError ("CumulativeCompleter requires at least one completer" )
138+ self ._completers = completers
139+ self .shown_before_tab = True
140+
141+ @property
142+ def locate (self ):
143+ return self ._completers [0 ].locate if self ._completers else lambda * args : None
144+
145+ @property
146+ def format (self ):
147+ return self ._completers [0 ].format if self ._completers else lambda s : s
148+
149+ def matches (self , cursor_offset , line , locals_ , argspec , current_block , complete_magic_methods ):
150+ all_matches = []
151+ for completer in self ._completers :
152+ # these have to be explicitely listed to deal with the different
153+ # signatures of various matches() methods of completers
154+ matches = completer .matches (cursor_offset = cursor_offset ,
155+ line = line ,
156+ locals_ = locals_ ,
157+ argspec = argspec ,
158+ current_block = current_block ,
159+ complete_magic_methods = complete_magic_methods )
160+ if matches is not None :
161+ all_matches .extend (matches )
162+
163+ return sorted (set (all_matches ))
164+
165+
144166class ImportCompletion (BaseCompletionType ):
145167 @classmethod
146- def matches (cls , cursor_offset , current_line , ** kwargs ):
147- return importcompletion .complete (cursor_offset , current_line )
168+ def matches (cls , cursor_offset , line , ** kwargs ):
169+ return importcompletion .complete (cursor_offset , line )
148170 locate = staticmethod (lineparts .current_word )
149171 format = staticmethod (after_last_dot )
150172
151173class FilenameCompletion (BaseCompletionType ):
152174 shown_before_tab = False
153175 @classmethod
154- def matches (cls , cursor_offset , current_line , ** kwargs ):
155- cs = lineparts .current_string (cursor_offset , current_line )
176+ def matches (cls , cursor_offset , line , ** kwargs ):
177+ cs = lineparts .current_string (cursor_offset , line )
156178 if cs is None :
157179 return None
158180 start , end , text = cs
@@ -178,7 +200,7 @@ def format(cls, filename):
178200
179201class AttrCompletion (BaseCompletionType ):
180202 @classmethod
181- def matches (cls , cursor_offset , line , locals_ , mode , ** kwargs ):
203+ def matches (cls , cursor_offset , line , locals_ , ** kwargs ):
182204 r = cls .locate (cursor_offset , line )
183205 if r is None :
184206 return None
@@ -195,7 +217,7 @@ def matches(cls, cursor_offset, line, locals_, mode, **kwargs):
195217 break
196218 methodtext = text [- i :]
197219 matches = ['' .join ([text [:- i ], m ]) for m in
198- attr_matches (methodtext , locals_ , mode )]
220+ attr_matches (methodtext , locals_ )]
199221
200222 #TODO add open paren for methods via _callable_prefix (or decide not to)
201223 # unless the first character is a _ filter out all attributes starting with a _
@@ -242,7 +264,7 @@ def matches(cls, cursor_offset, line, current_block, **kwargs):
242264
243265class GlobalCompletion (BaseCompletionType ):
244266 @classmethod
245- def matches (cls , cursor_offset , line , locals_ , mode , ** kwargs ):
267+ def matches (cls , cursor_offset , line , locals_ , ** kwargs ):
246268 """Compute matches when text is a simple name.
247269 Return a list of all keywords, built-in functions and names currently
248270 defined in self.namespace that match.
@@ -256,11 +278,11 @@ def matches(cls, cursor_offset, line, locals_, mode, **kwargs):
256278 n = len (text )
257279 import keyword
258280 for word in keyword .kwlist :
259- if method_match (word , n , text , mode ):
281+ if method_match (word , n , text ):
260282 hash [word ] = 1
261283 for nspace in [__builtin__ .__dict__ , locals_ ]:
262284 for word , val in nspace .items ():
263- if method_match (word , len (text ), text , mode ) and word != "__builtins__" :
285+ if method_match (word , len (text ), text ) and word != "__builtins__" :
264286 hash [_callable_postfix (val , word )] = 1
265287 matches = hash .keys ()
266288 matches .sort ()
@@ -315,7 +337,7 @@ def safe_eval(expr, namespace):
315337 raise EvaluationError
316338
317339
318- def attr_matches (text , namespace , autocomplete_mode ):
340+ def attr_matches (text , namespace ):
319341 """Taken from rlcompleter.py and bent to my will.
320342 """
321343
@@ -336,10 +358,10 @@ def attr_matches(text, namespace, autocomplete_mode):
336358 except EvaluationError :
337359 return []
338360 with inspection .AttrCleaner (obj ):
339- matches = attr_lookup (obj , expr , attr , autocomplete_mode )
361+ matches = attr_lookup (obj , expr , attr )
340362 return matches
341363
342- def attr_lookup (obj , expr , attr , autocomplete_mode ):
364+ def attr_lookup (obj , expr , attr ):
343365 """Second half of original attr_matches method factored out so it can
344366 be wrapped in a safe try/finally block in case anything bad happens to
345367 restore the original __getattribute__ method."""
@@ -356,7 +378,7 @@ def attr_lookup(obj, expr, attr, autocomplete_mode):
356378 matches = []
357379 n = len (attr )
358380 for word in words :
359- if method_match (word , n , attr , autocomplete_mode ) and word != "__builtins__" :
381+ if method_match (word , n , attr ) and word != "__builtins__" :
360382 matches .append ("%s.%s" % (expr , word ))
361383 return matches
362384
@@ -367,14 +389,5 @@ def _callable_postfix(value, word):
367389 word += '('
368390 return word
369391
370- #TODO use method_match everywhere instead of startswith to implement other completion modes
371- # will also need to rewrite checking mode so cseq replace doesn't happen in frontends
372- def method_match (word , size , text , autocomplete_mode ):
373- if autocomplete_mode == SIMPLE :
374- return word [:size ] == text
375- elif autocomplete_mode == SUBSTRING :
376- s = r'.*%s.*' % text
377- return re .search (s , word )
378- else :
379- s = r'.*%s.*' % '.*' .join (list (text ))
380- return re .search (s , word )
392+ def method_match (word , size , text ):
393+ return word [:size ] == text
0 commit comments