Skip to content

Commit 2d61b19

Browse files
committed
Refactor Completer code into Autocomplete class
1 parent ffc15c8 commit 2d61b19

File tree

3 files changed

+130
-91
lines changed

3 files changed

+130
-91
lines changed

bpython/autocomplete.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# The MIT License
2+
#
3+
# Copyright (c) 2009-2011 the bpython authors.
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
# THE SOFTWARE.
22+
#
23+
24+
import __builtin__
25+
import __main__
26+
import rlcompleter
27+
import re
28+
from bpython import inspection
29+
30+
31+
32+
class Autocomplete(rlcompleter.Completer):
33+
"""
34+
"""
35+
36+
def __init__(self, namespace = None, config = None):
37+
rlcompleter.Completer.__init__(self, namespace)
38+
self.locals = namespace
39+
if hasattr(config, 'autocomplete_mode'):
40+
self.autocomplete_mode = config.autocomplete_mode
41+
else:
42+
self.autocomplete_mode = 1
43+
44+
def attr_matches(self, text):
45+
"""Taken from rlcompleter.py and bent to my will.
46+
"""
47+
48+
# Gna, Py 2.6's rlcompleter searches for __call__ inside the
49+
# instance instead of the type, so we monkeypatch to prevent
50+
# side-effects (__getattr__/__getattribute__)
51+
m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text)
52+
if not m:
53+
return []
54+
55+
expr, attr = m.group(1, 3)
56+
if expr.isdigit():
57+
# Special case: float literal, using attrs here will result in
58+
# a SyntaxError
59+
return []
60+
obj = eval(expr, self.locals)
61+
with inspection.AttrCleaner(obj):
62+
matches = self.attr_lookup(obj, expr, attr)
63+
return matches
64+
65+
def attr_lookup(self, obj, expr, attr):
66+
"""Second half of original attr_matches method factored out so it can
67+
be wrapped in a safe try/finally block in case anything bad happens to
68+
restore the original __getattribute__ method."""
69+
words = dir(obj)
70+
if hasattr(obj, '__class__'):
71+
words.append('__class__')
72+
words = words + rlcompleter.get_class_members(obj.__class__)
73+
if has_abc and not isinstance(obj.__class__, abc.ABCMeta):
74+
try:
75+
words.remove('__abstractmethods__')
76+
except ValueError:
77+
pass
78+
79+
matches = []
80+
n = len(attr)
81+
for word in words:
82+
if self.method_match(word, n, attr) and word != "__builtins__":
83+
matches.append("%s.%s" % (expr, word))
84+
return matches
85+
86+
def _callable_postfix(self, value, word):
87+
"""rlcompleter's _callable_postfix done right."""
88+
with inspection.AttrCleaner(value):
89+
if inspection.is_callable(value):
90+
word += '('
91+
return word
92+
93+
def global_matches(self, text):
94+
"""Compute matches when text is a simple name.
95+
Return a list of all keywords, built-in functions and names currently
96+
defined in self.namespace that match.
97+
"""
98+
99+
hash = {}
100+
n = len(text)
101+
import keyword
102+
for word in keyword.kwlist:
103+
if self.method_match(word, n, text):
104+
hash[word] = 1
105+
for nspace in [__builtin__.__dict__, __main__.__dict__]:
106+
for word, val in nspace.items():
107+
if self.method_match(word, len(text), text) and word != "__builtins__":
108+
hash[self._callable_postfix(val, word)] = 1
109+
matches = hash.keys()
110+
matches.sort()
111+
return matches
112+
113+
def method_match(self, word, size, text):
114+
if self.autocomplete_mode == "1":
115+
return word[:size] == text
116+
else:
117+
s = r'.*%s.*' % '.*'.join(list(text))
118+
return re.search(s, word)
119+

bpython/repl.py

Lines changed: 2 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,12 @@
2222
#
2323

2424
from __future__ import with_statement
25-
import __builtin__
26-
import __main__
27-
2825
import code
2926
import codecs
3027
import errno
3128
import inspect
3229
import os
3330
import pydoc
34-
import re
35-
import rlcompleter
3631
import subprocess
3732
import sys
3833
import textwrap
@@ -52,6 +47,7 @@
5247

5348
from bpython import importcompletion, inspection
5449
from bpython.formatter import Parenthesis
50+
from bpython.autocomplete import Autocomplete
5551

5652
# Needed for special handling of __abstractmethods__
5753
# abc only exists since 2.6, so check both that it exists and that it's
@@ -380,14 +376,7 @@ def __init__(self, interp, config):
380376
self.s_hist = []
381377
self.history = []
382378
self.evaluating = False
383-
# Use the interpreter's namespace only for the readline stuff:
384-
self.completer = rlcompleter.Completer(self.interp.locals)
385-
self.completer.attr_matches = self.attr_matches
386-
# Gna, Py 2.6's rlcompleter searches for __call__ inside the
387-
# instance instead of the type, so we monkeypatch to prevent
388-
# side-effects (__getattr__/__getattribute__)
389-
self.completer._callable_postfix = self._callable_postfix
390-
self.completer.global_matches = self.global_matches
379+
self.completer = Autocomplete(self.interp.locals, config)
391380
self.matches = []
392381
self.matches_iter = MatchesIterator()
393382
self.argspec = None
@@ -425,51 +414,6 @@ def startup(self):
425414
else:
426415
self.interp.runsource(f.read(), filename, 'exec', encode=False)
427416

428-
def attr_matches(self, text):
429-
"""Taken from rlcompleter.py and bent to my will."""
430-
431-
m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text)
432-
if not m:
433-
return []
434-
435-
expr, attr = m.group(1, 3)
436-
if expr.isdigit():
437-
# Special case: float literal, using attrs here will result in
438-
# a SyntaxError
439-
return []
440-
obj = eval(expr, self.interp.locals)
441-
with inspection.AttrCleaner(obj):
442-
matches = self.attr_lookup(obj, expr, attr)
443-
return matches
444-
445-
def attr_lookup(self, obj, expr, attr):
446-
"""Second half of original attr_matches method factored out so it can
447-
be wrapped in a safe try/finally block in case anything bad happens to
448-
restore the original __getattribute__ method."""
449-
words = dir(obj)
450-
if hasattr(obj, '__class__'):
451-
words.append('__class__')
452-
words = words + rlcompleter.get_class_members(obj.__class__)
453-
if has_abc and not isinstance(obj.__class__, abc.ABCMeta):
454-
try:
455-
words.remove('__abstractmethods__')
456-
except ValueError:
457-
pass
458-
459-
matches = []
460-
n = len(attr)
461-
for word in words:
462-
if self.method_match(word, n, attr) and word != "__builtins__":
463-
matches.append("%s.%s" % (expr, word))
464-
return matches
465-
466-
def _callable_postfix(self, value, word):
467-
"""rlcompleter's _callable_postfix done right."""
468-
with inspection.AttrCleaner(value):
469-
if inspection.is_callable(value):
470-
word += '('
471-
return word
472-
473417
def current_string(self, concatenate=False):
474418
"""Return the current string."""
475419
tokens = self.tokenize(self.current_line())
@@ -499,33 +443,6 @@ def current_string(self, concatenate=False):
499443
return ''
500444
return ''.join(string)
501445

502-
def global_matches(self, text):
503-
"""Compute matches when text is a simple name.
504-
Return a list of all keywords, built-in functions and names currently
505-
defined in self.namespace that match.
506-
"""
507-
508-
hash = {}
509-
n = len(text)
510-
import keyword
511-
for word in keyword.kwlist:
512-
if self.method_match(word, n, text):
513-
hash[word] = 1
514-
for nspace in [__builtin__.__dict__, __main__.__dict__]:
515-
for word, val in nspace.items():
516-
if self.method_match(word, len(text), text) and word != "__builtins__":
517-
hash[self._callable_postfix(val, word)] = 1
518-
matches = hash.keys()
519-
matches.sort()
520-
return matches
521-
522-
def method_match(self, word, size, text):
523-
if self.config.autocomplete_mode == "1":
524-
return word[:size] == text
525-
else:
526-
s = r'.*%s.*' % '.*'.join(list(text))
527-
return re.search(s, word)
528-
529446
def get_object(self, name):
530447
attributes = name.split('.')
531448
obj = eval(attributes.pop(0), self.interp.locals)

bpython/test/test_repl.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,13 @@ def test_update(self):
132132

133133

134134
class FakeRepl(repl.Repl):
135-
def __init__(self):
135+
def __init__(self, conf={}):
136+
136137
config_struct = config.Struct()
137138
config.loadini(config_struct, os.devnull)
139+
if 'autocomplete_mode' in conf:
140+
config_struct.autocomplete_mode = conf['autocomplete_mode']
141+
138142
repl.Repl.__init__(self, repl.Interpreter(), config_struct)
139143
self.input_line = ""
140144
self.current_word = ""
@@ -203,12 +207,9 @@ def test_nonexistent_name(self):
203207
self.assertFalse(self.repl.get_args())
204208

205209
class TestRepl(unittest.TestCase):
206-
def setUp(self):
207-
self.repl = FakeRepl()
208-
self.repl.config.autocomplete_mode = "1"
209-
210210

211211
def test_default_complete(self):
212+
self.repl = FakeRepl({'autocomplete_mode':"1"})
212213
self.repl.input_line = "d"
213214
self.repl.current_word = "d"
214215

@@ -217,10 +218,11 @@ def test_default_complete(self):
217218
self.assertEqual(self.repl.completer.matches,
218219
['def', 'del', 'delattr(', 'dict(', 'dir(', 'divmod('])
219220

221+
220222
def test_alternate_complete(self):
223+
self.repl = FakeRepl({'autocomplete_mode':"2"})
221224
self.repl.input_line = "doc"
222225
self.repl.current_word = "doc"
223-
self.repl.config.autocomplete_mode = "2"
224226

225227
self.assertTrue(self.repl.complete())
226228
self.assertTrue(hasattr(self.repl.completer,'matches'))
@@ -229,5 +231,6 @@ def test_alternate_complete(self):
229231

230232

231233

234+
232235
if __name__ == '__main__':
233236
unittest.main()

0 commit comments

Comments
 (0)