forked from VimPlug/jedi
144 lines
5.2 KiB
Python
144 lines
5.2 KiB
Python
import os
|
|
|
|
from jedi._compatibility import FileNotFoundError, force_unicode
|
|
from jedi.evaluate.names import AbstractArbitraryName
|
|
from jedi.api import classes
|
|
from jedi.evaluate.helpers import get_str_or_none
|
|
|
|
|
|
def file_name_completions(evaluator, module_context, start_leaf, string, like_name):
|
|
# First we want to find out what can actually be changed as a name.
|
|
like_name_length = len(os.path.basename(string) + like_name)
|
|
|
|
addition = _get_string_additions(module_context, start_leaf)
|
|
if addition is None:
|
|
return
|
|
string = addition + string
|
|
|
|
# Here we use basename again, because if strings are added like
|
|
# `'foo' + 'bar`, it should complete to `foobar/`.
|
|
must_start_with = os.path.basename(string) + like_name
|
|
string = os.path.dirname(string)
|
|
|
|
string, is_in_os_path_join = _maybe_add_os_path_join(module_context, start_leaf, string)
|
|
base_path = os.path.join(evaluator.project._path, string)
|
|
try:
|
|
listed = os.listdir(base_path)
|
|
except FileNotFoundError:
|
|
return
|
|
for name in listed:
|
|
if name.startswith(must_start_with):
|
|
path_for_name = os.path.join(base_path, name)
|
|
if os.path.isdir(path_for_name) and not is_in_os_path_join:
|
|
name += os.path.sep
|
|
|
|
yield classes.Completion(
|
|
evaluator,
|
|
FileName(evaluator, name[len(must_start_with) - like_name_length:]),
|
|
stack=None,
|
|
like_name_length=like_name_length
|
|
)
|
|
|
|
|
|
def _get_string_additions(module_context, start_leaf):
|
|
def iterate_nodes():
|
|
node = addition.parent
|
|
was_addition = True
|
|
for child_node in reversed(node.children[:node.children.index(addition)]):
|
|
if was_addition:
|
|
was_addition = False
|
|
yield child_node
|
|
continue
|
|
|
|
if child_node != '+':
|
|
break
|
|
was_addition = True
|
|
|
|
addition = start_leaf.get_previous_leaf()
|
|
if addition != '+':
|
|
return ''
|
|
context = module_context.create_context(start_leaf)
|
|
return _add_strings(context, reversed(list(iterate_nodes())))
|
|
|
|
|
|
def _add_strings(context, nodes, add_slash=False):
|
|
string = ''
|
|
first = True
|
|
for child_node in nodes:
|
|
contexts = context.eval_node(child_node)
|
|
if len(contexts) != 1:
|
|
return None
|
|
c, = contexts
|
|
s = get_str_or_none(c)
|
|
if s is None:
|
|
return None
|
|
if not first and add_slash:
|
|
string += os.path.sep
|
|
string += force_unicode(s)
|
|
first = False
|
|
return string
|
|
|
|
|
|
class FileName(AbstractArbitraryName):
|
|
api_type = u'path'
|
|
is_context_name = False
|
|
|
|
|
|
def _maybe_add_os_path_join(module_context, start_leaf, string):
|
|
def check_for_power(join_atom, nodes):
|
|
context = module_context.create_context(join_atom)
|
|
contexts = context.eval_node(join_atom)
|
|
if any([c.name.get_qualified_names(include_module_names=True)
|
|
!= ('os', 'path', 'join') for c in contexts]):
|
|
return string, False
|
|
return _add_strings(context, nodes, add_slash=True), True
|
|
|
|
def check_trailer(trailer, arglist_nodes):
|
|
atom = trailer.get_previous_sibling()
|
|
if atom.type != 'trailer':
|
|
return check_for_power(atom, arglist_nodes)
|
|
return string, False
|
|
|
|
# Maybe an arglist or some weird error case. Therefore checked below.
|
|
arglist = start_leaf.parent
|
|
index = arglist.children.index(start_leaf)
|
|
arglist_nodes = arglist.children[:index]
|
|
print(arglist, arglist_nodes)
|
|
if start_leaf.type == 'error_leaf':
|
|
# Unfinished string literal, like `join('`
|
|
if index > 0:
|
|
error_node = arglist_nodes[-1]
|
|
if error_node.type == 'error_node' and len(error_node.children) >= 2:
|
|
index = -2
|
|
if error_node.children[-1].type == 'arglist':
|
|
arglist_nodes = error_node.children[-1].children
|
|
index -= 1
|
|
else:
|
|
arglist_nodes = []
|
|
|
|
if error_node.children[index + 1] == '(':
|
|
atom = error_node.children[index]
|
|
if atom.type in ('atom_expr', 'power', 'name'):
|
|
return check_for_power(atom, arglist_nodes[::2])
|
|
elif arglist.type == 'arglist':
|
|
trailer = arglist.parent
|
|
if trailer.type == 'error_node':
|
|
trailer_index = trailer.children.index(arglist)
|
|
assert trailer_index >= 2
|
|
assert trailer.children[trailer_index - 1] == '('
|
|
name = trailer.children[trailer_index - 2]
|
|
if name.type in ('atom_expr', 'power', 'name'):
|
|
return check_for_power(name, arglist_nodes[::2])
|
|
elif trailer.type == 'trailer':
|
|
return check_trailer(trailer, arglist_nodes[::2])
|
|
elif arglist.type == 'trailer':
|
|
return check_trailer(arglist, [])
|
|
elif arglist.type == 'error_node':
|
|
# Stuff like `join(""`
|
|
if len(arglist_nodes) >= 2 and arglist_nodes[-1] == '(':
|
|
name = arglist_nodes[-2]
|
|
if name.type in ('atom_expr', 'power', 'name'):
|
|
return check_for_power(name, [])
|
|
|
|
return string, False
|