Skip to content

Commit 7f9a9b2

Browse files
committed
added import completion
1 parent eac9aa3 commit 7f9a9b2

File tree

2 files changed

+123
-9
lines changed

2 files changed

+123
-9
lines changed

bpython/cli.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@
5555
from pygments.lexers import PythonLexer
5656
from bpython.formatter import BPythonFormatter
5757

58+
# This for import completion
59+
from bpython import importcompletion
60+
5861
# And these are used for argspec.
5962
from pyparsing import Forward, Suppress, QuotedString, dblQuotedString, \
6063
Group, OneOrMore, ZeroOrMore, Literal, Optional, Word, \
@@ -535,26 +538,31 @@ def _complete(self, unused_tab=False):
535538
if not cw:
536539
self.matches = []
537540

538-
try:
539-
self.completer.complete(cw, 0)
540-
except Exception:
541+
# Check for import completion
542+
e = False
543+
matches = importcompletion.complete(self.s, cw)
544+
if not matches:
545+
# Nope, no import, continue with normal completion
546+
try:
547+
self.completer.complete(cw, 0)
548+
except Exception:
541549
# This sucks, but it's either that or list all the exceptions that could
542550
# possibly be raised here, so if anyone wants to do that, feel free to send me
543551
# a patch. XXX: Make sure you raise here if you're debugging the completion
544552
# stuff !
545-
e = True
546-
else:
547-
e = False
553+
e = True
554+
else:
555+
matches = self.completer.matches
548556

549-
if e or not self.completer.matches:
557+
if e or not matches:
550558
self.matches = []
551559
if not self.argspec:
552560
self.scr.redrawwin()
553561
return False
554562

555-
if not e and self.completer.matches:
563+
if not e and matches:
556564
# remove duplicates and restore order
557-
self.matches = sorted(set(self.completer.matches))
565+
self.matches = sorted(set(matches))
558566

559567
if len(self.matches) == 1 and not OPTS.auto_display_list:
560568
self.list_win_visible = True

bpython/importcompletion.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# The MIT License
2+
#
3+
# Copyright (c) 2009 Andreas Stuehrk
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 imp
25+
import os
26+
import sys
27+
28+
29+
# The cached list of all known modules
30+
modules = list()
31+
32+
33+
def complete(line, cw):
34+
"""Construct a full list of possibly completions for imports."""
35+
tokens = line.split()
36+
if tokens[0] in ['from', 'import']:
37+
if tokens[0] == 'from':
38+
completing_from = True
39+
if len(tokens) > 3:
40+
if '.' in cw:
41+
# This will result in a SyntaxError, so do not return
42+
# any matches
43+
return list()
44+
cw = '%s.%s' % (tokens[1], cw)
45+
elif len(tokens) == 3:
46+
return ['import']
47+
else:
48+
completing_from = False
49+
50+
matches = list()
51+
for name in modules:
52+
if not (name.startswith(cw) and name.find('.', len(cw)) == -1):
53+
continue
54+
if completing_from:
55+
name = name[len(tokens[1]) + 1:]
56+
matches.append(name)
57+
return matches
58+
else:
59+
return list()
60+
61+
62+
def find_modules(path):
63+
"""Find all modules (and packages) for a given directory."""
64+
if not os.path.isdir(path):
65+
# Perhaps a zip file
66+
return
67+
68+
for name in os.listdir(path):
69+
if not any(name.endswith(suffix[0]) for suffix in imp.get_suffixes()):
70+
# Possibly a package
71+
if '.' in name:
72+
continue
73+
name = os.path.splitext(name)[0]
74+
try:
75+
fo, pathname, _ = imp.find_module(name, [path])
76+
except ImportError:
77+
continue
78+
else:
79+
if fo is not None:
80+
fo.close()
81+
else:
82+
# Yay, package
83+
for subname in find_modules(pathname):
84+
if subname != '__init__':
85+
yield '%s.%s' % (name, subname)
86+
yield name
87+
88+
89+
def find_all_modules(path=None):
90+
"""Return a list with all modules in `path`, which should be a list of
91+
directory names. If path is not given, sys.path will be used."""
92+
modules = set()
93+
if path is None:
94+
modules.update(sys.builtin_module_names)
95+
path = sys.path
96+
97+
for p in path:
98+
modules.update(find_modules(p))
99+
100+
return modules
101+
102+
103+
def reload():
104+
"""Refresh the list of known modules."""
105+
global modules
106+
modules = find_all_modules()

0 commit comments

Comments
 (0)