Skip to content

Commit fe42a13

Browse files
committed
Update Jedi
1 parent 0f72554 commit fe42a13

20 files changed

Lines changed: 307 additions & 163 deletions

‎lib/jedi/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
good text editor, while still having very good IDE features for Python.
3737
"""
3838

39-
__version__ = '0.9.0'
39+
__version__ = '0.10.0'
4040

4141
from jedi.api import Script, Interpreter, NotFoundError, set_debug_function
4242
from jedi.api import preload_module, defined_names, names

‎lib/jedi/_compatibility.py‎

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import imp
77
import os
88
import re
9+
import pkgutil
910
try:
1011
import importlib
1112
except ImportError:
@@ -18,6 +19,18 @@
1819
is_py26 = not is_py3 and sys.version_info[1] < 7
1920

2021

22+
class DummyFile(object):
23+
def __init__(self, loader, string):
24+
self.loader = loader
25+
self.string = string
26+
27+
def read(self):
28+
return self.loader.get_source(self.string)
29+
30+
def close(self):
31+
del self.loader
32+
33+
2134
def find_module_py33(string, path=None):
2235
loader = importlib.machinery.PathFinder.find_module(string, path)
2336

@@ -35,30 +48,73 @@ def find_module_py33(string, path=None):
3548
try:
3649
is_package = loader.is_package(string)
3750
if is_package:
38-
module_path = os.path.dirname(loader.path)
39-
module_file = None
51+
if hasattr(loader, 'path'):
52+
module_path = os.path.dirname(loader.path)
53+
else:
54+
# At least zipimporter does not have path attribute
55+
module_path = os.path.dirname(loader.get_filename(string))
56+
if hasattr(loader, 'archive'):
57+
module_file = DummyFile(loader, string)
58+
else:
59+
module_file = None
4060
else:
4161
module_path = loader.get_filename(string)
42-
module_file = open(module_path, 'rb')
62+
module_file = DummyFile(loader, string)
4363
except AttributeError:
4464
# ExtensionLoader has not attribute get_filename, instead it has a
4565
# path attribute that we can use to retrieve the module path
4666
try:
4767
module_path = loader.path
48-
module_file = open(loader.path, 'rb')
68+
module_file = DummyFile(loader, string)
4969
except AttributeError:
5070
module_path = string
5171
module_file = None
5272
finally:
5373
is_package = False
5474

75+
if hasattr(loader, 'archive'):
76+
module_path = loader.archive
77+
5578
return module_file, module_path, is_package
5679

5780

5881
def find_module_pre_py33(string, path=None):
59-
module_file, module_path, description = imp.find_module(string, path)
60-
module_type = description[2]
61-
return module_file, module_path, module_type is imp.PKG_DIRECTORY
82+
try:
83+
module_file, module_path, description = imp.find_module(string, path)
84+
module_type = description[2]
85+
return module_file, module_path, module_type is imp.PKG_DIRECTORY
86+
except ImportError:
87+
pass
88+
89+
if path is None:
90+
path = sys.path
91+
for item in path:
92+
loader = pkgutil.get_importer(item)
93+
if loader:
94+
try:
95+
loader = loader.find_module(string)
96+
if loader:
97+
is_package = loader.is_package(string)
98+
is_archive = hasattr(loader, 'archive')
99+
try:
100+
module_path = loader.get_filename(string)
101+
except AttributeError:
102+
# fallback for py26
103+
try:
104+
module_path = loader._get_filename(string)
105+
except AttributeError:
106+
continue
107+
if is_package:
108+
module_path = os.path.dirname(module_path)
109+
if is_archive:
110+
module_path = loader.archive
111+
file = None
112+
if not is_package or is_archive:
113+
file = DummyFile(loader, string)
114+
return (file, module_path, is_package)
115+
except ImportError:
116+
pass
117+
raise ImportError("No module named {0}".format(string))
62118

63119

64120
find_module = find_module_py33 if is_py33 else find_module_pre_py33

‎lib/jedi/api/classes.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,10 +366,11 @@ class Completion(BaseDefinition):
366366
`Completion` objects are returned from :meth:`api.Script.completions`. They
367367
provide additional information about a completion.
368368
"""
369-
def __init__(self, evaluator, name, like_name_length):
369+
def __init__(self, evaluator, name, stack, like_name_length):
370370
super(Completion, self).__init__(evaluator, name)
371371

372372
self._like_name_length = like_name_length
373+
self._stack = stack
373374

374375
# Completion objects with the same Completion name (which means
375376
# duplicate items in the completion)
@@ -381,8 +382,10 @@ def _complete(self, like_name):
381382
and self.type == 'Function':
382383
append = '('
383384

384-
if isinstance(self._definition, tree.Param):
385-
append += '='
385+
if isinstance(self._definition, tree.Param) and self._stack is not None:
386+
node_names = list(self._stack.get_node_names(self._evaluator.grammar))
387+
if 'trailer' in node_names and 'argument' not in node_names:
388+
append += '='
386389

387390
name = str(self._name)
388391
if like_name:

‎lib/jedi/api/completion.py‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def get_call_signature_param_names(call_signatures):
2828
yield p._name
2929

3030

31-
def filter_names(evaluator, completion_names, like_name):
31+
def filter_names(evaluator, completion_names, stack, like_name):
3232
comp_dct = {}
3333
for name in set(completion_names):
3434
if settings.case_insensitive_completion \
@@ -42,6 +42,7 @@ def filter_names(evaluator, completion_names, like_name):
4242
new = classes.Completion(
4343
evaluator,
4444
name,
45+
stack,
4546
len(like_name)
4647
)
4748
k = (new.name, new.complete) # key
@@ -79,7 +80,7 @@ def __init__(self, evaluator, module, code_lines, position, call_signatures_meth
7980
self._code_lines = code_lines
8081

8182
# The first step of completions is to get the name
82-
self._like_name = helpers.get_on_completion_name(code_lines, position)
83+
self._like_name = helpers.get_on_completion_name(module, code_lines, position)
8384
# The actual cursor position is not what we need to calculate
8485
# everything. We want the start of the name we're on.
8586
self._position = position[0], position[1] - len(self._like_name)
@@ -89,7 +90,7 @@ def completions(self):
8990
completion_names = self._get_context_completions()
9091

9192
completions = filter_names(self._evaluator, completion_names,
92-
self._like_name)
93+
self.stack, self._like_name)
9394

9495
return sorted(completions, key=lambda x: (x.name.startswith('__'),
9596
x.name.startswith('_'),
@@ -113,28 +114,30 @@ def _get_context_completions(self):
113114
grammar = self._evaluator.grammar
114115

115116
try:
116-
stack = helpers.get_stack_at_position(
117+
self.stack = helpers.get_stack_at_position(
117118
grammar, self._code_lines, self._module, self._position
118119
)
119120
except helpers.OnErrorLeaf as e:
121+
self.stack = None
120122
if e.error_leaf.value == '.':
121123
# After ErrorLeaf's that are dots, we will not do any
122124
# completions since this probably just confuses the user.
123125
return []
124126
# If we don't have a context, just use global completion.
127+
125128
return self._global_completions()
126129

127130
allowed_keywords, allowed_tokens = \
128-
helpers.get_possible_completion_types(grammar, stack)
131+
helpers.get_possible_completion_types(grammar, self.stack)
129132

130133
completion_names = list(self._get_keyword_completion_names(allowed_keywords))
131134

132135
if token.NAME in allowed_tokens:
133136
# This means that we actually have to do type inference.
134137

135-
symbol_names = list(stack.get_node_names(grammar))
138+
symbol_names = list(self.stack.get_node_names(grammar))
136139

137-
nodes = list(stack.get_nodes())
140+
nodes = list(self.stack.get_nodes())
138141

139142
if "import_stmt" in symbol_names:
140143
level = 0

‎lib/jedi/api/helpers.py‎

Lines changed: 68 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,20 @@ def sorted_definitions(defs):
2020
return sorted(defs, key=lambda x: (x.module_path or '', x.line or 0, x.column or 0))
2121

2222

23-
def get_on_completion_name(lines, position):
24-
line = lines[position[0] - 1]
25-
# The first step of completions is to get the name
26-
return re.search(
27-
r'(?!\d)\w+$|$', line[:position[1]]
28-
).group(0)
23+
def get_on_completion_name(module, lines, position):
24+
leaf = module.get_leaf_for_position(position)
25+
if leaf is None:
26+
return ''
27+
elif leaf.type in ('string', 'error_leaf'):
28+
# Completions inside strings are a bit special, we need to parse the
29+
# string.
30+
line = lines[position[0] - 1]
31+
# The first step of completions is to get the name
32+
return re.search(r'(?!\d)\w+$|$', line[:position[1]]).group(0)
33+
elif leaf.type not in ('name', 'keyword'):
34+
return ''
35+
36+
return leaf.value[:position[1] - leaf.start_pos[1]]
2937

3038

3139
def _get_code(code_lines, start_pos, end_pos):
@@ -44,71 +52,83 @@ def error_leaf(self):
4452
return self.args[0]
4553

4654

55+
def _get_code_for_stack(code_lines, module, position):
56+
leaf = module.get_leaf_for_position(position, include_prefixes=True)
57+
# It might happen that we're on whitespace or on a comment. This means
58+
# that we would not get the right leaf.
59+
if leaf.start_pos >= position:
60+
try:
61+
leaf = leaf.get_previous_leaf()
62+
except IndexError:
63+
return u('') # At the beginning of the file.
64+
is_after_newline = leaf.type == 'newline'
65+
while leaf.type == 'newline':
66+
try:
67+
leaf = leaf.get_previous_leaf()
68+
except IndexError:
69+
return u('')
70+
71+
if leaf.type in ('indent', 'dedent'):
72+
return u('')
73+
elif leaf.type == 'error_leaf' or leaf.type == 'string':
74+
# Error leafs cannot be parsed, completion in strings is also
75+
# impossible.
76+
raise OnErrorLeaf(leaf)
77+
else:
78+
if leaf == ';':
79+
user_stmt = leaf.parent
80+
else:
81+
user_stmt = leaf.get_definition()
82+
if user_stmt.parent.type == 'simple_stmt':
83+
user_stmt = user_stmt.parent
84+
85+
if is_after_newline:
86+
if user_stmt.start_pos[1] > position[1]:
87+
# This means that it's actually a dedent and that means that we
88+
# start without context (part of a suite).
89+
return u('')
90+
91+
# This is basically getting the relevant lines.
92+
code = _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position)
93+
if code.startswith('pass'):
94+
import pdb; pdb.set_trace()
95+
96+
return code
97+
98+
4799
def get_stack_at_position(grammar, code_lines, module, pos):
48100
"""
49101
Returns the possible node names (e.g. import_from, xor_test or yield_stmt).
50102
"""
51-
user_stmt = module.get_statement_for_position(pos)
52-
53-
if user_stmt is not None and user_stmt.type in ('indent', 'dedent'):
54-
code = u('')
55-
else:
56-
if user_stmt is None:
57-
user_stmt = module.get_leaf_for_position(pos, include_prefixes=True)
58-
if pos <= user_stmt.start_pos:
59-
try:
60-
leaf = user_stmt.get_previous_leaf()
61-
except IndexError:
62-
pass
63-
else:
64-
user_stmt = module.get_statement_for_position(leaf.start_pos)
65-
66-
if user_stmt.type == 'error_leaf' or user_stmt.type == 'string':
67-
# Error leafs cannot be parsed, completion in strings is also
68-
# impossible.
69-
raise OnErrorLeaf(user_stmt)
70-
71-
start_pos = user_stmt.start_pos
72-
if user_stmt.first_leaf() == '@':
73-
# TODO this once again proves that just using user_stmt.get_code
74-
# would probably be nicer than _get_code.
75-
# Remove the indent to have a statement that is aligned (properties
76-
# on the same line as function)
77-
start_pos = start_pos[0], 0
78-
79-
code = _get_code(code_lines, start_pos, pos)
80-
if code == ';':
81-
# ; cannot be parsed.
82-
code = u('')
83-
84-
# Remove whitespace at the end. Necessary, because the tokenizer will parse
85-
# an error token (there's no new line at the end in our case). This doesn't
86-
# alter any truth about the valid tokens at that position.
87-
code = code.rstrip('\t ')
88-
# Remove as many indents from **all** code lines as possible.
89-
code = dedent(code)
90-
91103
class EndMarkerReached(Exception):
92104
pass
93105

94106
def tokenize_without_endmarker(code):
95107
tokens = tokenize.source_tokens(code, use_exact_op_types=True)
96108
for token_ in tokens:
97-
if token_[0] == token.ENDMARKER:
109+
if token_.string == safeword:
98110
raise EndMarkerReached()
99-
elif token_[0] == token.DEDENT:
111+
elif token_.type == token.DEDENT and False:
100112
# Ignore those. Error statements should not contain them, if
101113
# they do it's for cases where an indentation happens and
102114
# before the endmarker we still see them.
103115
pass
104116
else:
105117
yield token_
106118

119+
code = _get_code_for_stack(code_lines, module, pos)
120+
# We use a word to tell Jedi when we have reached the start of the
121+
# completion.
122+
safeword = 'XXX_USER_WANTS_TO_COMPLETE_HERE_WITH_JEDI'
123+
# Remove as many indents from **all** code lines as possible.
124+
code = dedent(code + safeword)
125+
107126
p = parser.Parser(grammar, code, start_parsing=False)
108127
try:
109128
p.parse(tokenizer=tokenize_without_endmarker(code))
110129
except EndMarkerReached:
111130
return Stack(p.stack)
131+
raise SystemError("This really shouldn't happen. There's a bug in Jedi.")
112132

113133

114134
class Stack(list):

0 commit comments

Comments
 (0)