forked from xonsh/xonsh
-
Notifications
You must be signed in to change notification settings - Fork 0
/
jedi.py
167 lines (134 loc) · 4.46 KB
/
jedi.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
"""Use Jedi as xonsh's python completer."""
import os
import xonsh
from xonsh.lazyasd import lazyobject, lazybool
from xonsh.built_ins import XSH
from xonsh.completers.tools import (
get_filter_function,
RichCompletion,
contextual_completer,
)
from xonsh.completers import _aliases
from xonsh.parsers.completion_context import CompletionContext
__all__ = ()
# an error will be printed in xontribs
# if jedi isn't installed
import jedi
@lazybool
def JEDI_NEW_API():
if hasattr(jedi, "__version__"):
return tuple(map(int, jedi.__version__.split("."))) >= (0, 16, 0)
else:
return False
@lazyobject
def XONSH_SPECIAL_TOKENS():
return {
"?",
"??",
"$(",
"${",
"$[",
"![",
"!(",
"@(",
"@$(",
"@",
}
@lazyobject
def XONSH_SPECIAL_TOKENS_FIRST():
return {tok[0] for tok in XONSH_SPECIAL_TOKENS}
@contextual_completer
def complete_jedi(context: CompletionContext):
"""Completes python code using Jedi and xonsh operators"""
if context.python is None:
return None
ctx = context.python.ctx or {}
# if the first word is a known command (and we're not completing it), don't complete.
# taken from xonsh/completers/python.py
if context.command and context.command.arg_index != 0:
first = context.command.args[0].value
if first in XSH.commands_cache and first not in ctx: # type: ignore
return None
# if we're completing a possible command and the prefix contains a valid path, don't complete.
if context.command:
path_parts = os.path.split(context.command.prefix)
if len(path_parts) > 1 and os.path.isdir(os.path.join(*path_parts[:-1])):
return None
filter_func = get_filter_function()
jedi.settings.case_insensitive_completion = not XSH.env.get(
"CASE_SENSITIVE_COMPLETIONS"
)
source = context.python.multiline_code
index = context.python.cursor_index
row = source.count("\n", 0, index) + 1
column = (
index - source.rfind("\n", 0, index) - 1
) # will be `index - (-1) - 1` if there's no newline
extra_ctx = {"__xonsh__": XSH}
try:
extra_ctx["_"] = _
except NameError:
pass
if JEDI_NEW_API:
script = jedi.Interpreter(source, [ctx, extra_ctx])
else:
script = jedi.Interpreter(source, [ctx, extra_ctx], line=row, column=column)
script_comp = set()
try:
if JEDI_NEW_API:
script_comp = script.complete(row, column)
else:
script_comp = script.completions()
except Exception:
pass
res = set(create_completion(comp) for comp in script_comp if should_complete(comp))
if index > 0:
last_char = source[index - 1]
res.update(
RichCompletion(t, prefix_len=1)
for t in XONSH_SPECIAL_TOKENS
if filter_func(t, last_char)
)
else:
res.update(RichCompletion(t, prefix_len=0) for t in XONSH_SPECIAL_TOKENS)
return res
def should_complete(comp: jedi.api.classes.Completion):
"""
make sure _* names are completed only when
the user writes the first underscore
"""
name = comp.name
if not name.startswith("_"):
return True
completion = comp.complete
# only if we're not completing the first underscore:
return completion and len(completion) <= len(name) - 1
def create_completion(comp: jedi.api.classes.Completion):
"""Create a RichCompletion from a Jedi Completion object"""
comp_type = None
description = None
if comp.type != "instance":
sigs = comp.get_signatures()
if sigs:
comp_type = comp.type
description = sigs[0].to_string()
if comp_type is None:
# jedi doesn't know exactly what this is
inf = comp.infer()
if inf:
comp_type = inf[0].type
description = inf[0].description
display = comp.name + ("()" if comp_type == "function" else "")
description = description or comp.type
prefix_len = len(comp.name) - len(comp.complete)
return RichCompletion(
comp.name,
display=display,
description=description,
prefix_len=prefix_len,
)
# monkey-patch the original python completer in 'base'.
xonsh.completers.base.complete_python = complete_jedi
# Jedi ignores leading '@(' and friends
_aliases._add_one_completer("jedi_python", complete_jedi, "<python")
_aliases._remove_completer(["python"])