mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 14:04:26 +08:00
On Python 3.5+, we can make use of scandir that not only list the content of the directory as an iterator but caches some infomations (for example, `is_dir()`; this avoid extra stats call to the underlying filesytem and can be – according to pep 471 – 2x to 20 time faster especially on NFS filesystem where stats call is expensive. From a quick this is the only place where scandir would make sens, as most other places only require the name. Fixes 1381
186 lines
6.4 KiB
Python
186 lines
6.4 KiB
Python
import os
|
|
import sys
|
|
|
|
from jedi._compatibility import FileNotFoundError, force_unicode, scandir
|
|
from jedi.evaluate.names import AbstractArbitraryName
|
|
from jedi.api import classes
|
|
from jedi.evaluate.helpers import get_str_or_none
|
|
from jedi.parser_utils import get_string_quote
|
|
|
|
|
|
if sys.version_info < (3,6) or True:
|
|
"""
|
|
A super-minimal shim around listdir that behave like
|
|
scandir for the information we need.
|
|
"""
|
|
class DirEntry:
|
|
|
|
def __init__(self, name, basepath):
|
|
self.name = name
|
|
self.basepath = basepath
|
|
|
|
def is_dir(self):
|
|
path_for_name = os.path.join(self.basepath, self.name)
|
|
return os.path.isdir(path_for_name)
|
|
|
|
def scandir(dir):
|
|
return [DirEntry(name, dir) for name in os.listdir(dir)]
|
|
else:
|
|
from os import scandir
|
|
|
|
|
|
|
|
|
|
def file_name_completions(evaluator, module_context, start_leaf, string,
|
|
like_name, call_signatures_callback, code_lines, position):
|
|
# 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)
|
|
|
|
sigs = call_signatures_callback()
|
|
is_in_os_path_join = sigs and all(s.full_name == 'os.path.join' for s in sigs)
|
|
if is_in_os_path_join:
|
|
to_be_added = _add_os_path_join(module_context, start_leaf, sigs[0].bracket_start)
|
|
if to_be_added is None:
|
|
is_in_os_path_join = False
|
|
else:
|
|
string = to_be_added + string
|
|
base_path = os.path.join(evaluator.project._path, string)
|
|
try:
|
|
listed = scandir(base_path)
|
|
except FileNotFoundError:
|
|
return
|
|
for entry in listed:
|
|
name = entry.name
|
|
if name.startswith(must_start_with):
|
|
if is_in_os_path_join or not entry.is_dir():
|
|
if start_leaf.type == 'string':
|
|
quote = get_string_quote(start_leaf)
|
|
else:
|
|
assert start_leaf.type == 'error_leaf'
|
|
quote = start_leaf.value
|
|
potential_other_quote = \
|
|
code_lines[position[0] - 1][position[1]:position[1] + len(quote)]
|
|
# Add a quote if it's not already there.
|
|
if quote != potential_other_quote:
|
|
name += quote
|
|
else:
|
|
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 _add_os_path_join(module_context, start_leaf, bracket_start):
|
|
def check(maybe_bracket, nodes):
|
|
if maybe_bracket.start_pos != bracket_start:
|
|
return None
|
|
|
|
if not nodes:
|
|
return ''
|
|
context = module_context.create_context(nodes[0])
|
|
return _add_strings(context, nodes, add_slash=True) or ''
|
|
|
|
if start_leaf.type == 'error_leaf':
|
|
# Unfinished string literal, like `join('`
|
|
context_node = start_leaf.parent
|
|
index = context_node.children.index(start_leaf)
|
|
if index > 0:
|
|
error_node = context_node.children[index - 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 = []
|
|
|
|
return check(error_node.children[index + 1], arglist_nodes[::2])
|
|
return None
|
|
|
|
# Maybe an arglist or some weird error case. Therefore checked below.
|
|
searched_node_child = start_leaf
|
|
while searched_node_child.parent is not None \
|
|
and searched_node_child.parent.type not in ('arglist', 'trailer', 'error_node'):
|
|
searched_node_child = searched_node_child.parent
|
|
|
|
if searched_node_child.get_first_leaf() is not start_leaf:
|
|
return None
|
|
searched_node = searched_node_child.parent
|
|
if searched_node is None:
|
|
return None
|
|
|
|
index = searched_node.children.index(searched_node_child)
|
|
arglist_nodes = searched_node.children[:index]
|
|
if searched_node.type == 'arglist':
|
|
trailer = searched_node.parent
|
|
if trailer.type == 'error_node':
|
|
trailer_index = trailer.children.index(searched_node)
|
|
assert trailer_index >= 2
|
|
assert trailer.children[trailer_index - 1] == '('
|
|
return check(trailer.children[trailer_index - 1], arglist_nodes[::2])
|
|
elif trailer.type == 'trailer':
|
|
return check(trailer.children[0], arglist_nodes[::2])
|
|
elif searched_node.type == 'trailer':
|
|
return check(searched_node.children[0], [])
|
|
elif searched_node.type == 'error_node':
|
|
# Stuff like `join(""`
|
|
return check(arglist_nodes[-1], [])
|