diff --git a/jedi/api/completion.py b/jedi/api/completion.py index ee91025c..4c281cc1 100644 --- a/jedi/api/completion.py +++ b/jedi/api/completion.py @@ -99,7 +99,14 @@ class Completion: self._call_signatures_method = call_signatures_method def completions(self): - completion_names = self._get_context_completions() + leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True) + string = _extract_string_while_in_string(leaf, self._position) + if string is not None: + completions = list(file_name_completions(self._evaluator, string, self._like_name)) + if completions: + return completions + + completion_names = self._get_context_completions(leaf) completions = filter_names(self._evaluator, completion_names, self.stack, self._like_name) @@ -108,7 +115,7 @@ class Completion: x.name.startswith('_'), x.name.lower())) - def _get_context_completions(self): + def _get_context_completions(self, leaf): """ Analyzes the context that a completion is made in and decides what to return. @@ -126,13 +133,6 @@ class Completion: grammar = self._evaluator.grammar self.stack = stack = None - leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True) - string = _extract_string_while_in_string(leaf, self._position) - if string is not None: - completions = list(file_name_completions(self._evaluator, string, self._like_name)) - if completions: - return completions - try: self.stack = stack = helpers.get_stack_at_position( grammar, self._code_lines, leaf, self._position diff --git a/jedi/api/file_name.py b/jedi/api/file_name.py index c740baa3..1c375842 100644 --- a/jedi/api/file_name.py +++ b/jedi/api/file_name.py @@ -1,17 +1,32 @@ import os +from jedi._compatibility import FileNotFoundError from jedi.evaluate.names import AbstractArbitraryName +from jedi.api import classes def file_name_completions(evaluator, string, like_name): + base_name = os.path.basename(string) + like_name = base_name + like_name + string = os.path.dirname(string) + base_path = os.path.join(evaluator.project._path, string) - print(string, base_path) - for name in os.listdir(base_path): + try: + listed = os.listdir(base_path) + except FileNotFoundError: + return + for name in listed: if name.startswith(like_name): path_for_name = os.path.join(base_path, name) if os.path.isdir(path_for_name): name += os.path.sep - yield FileName(evaluator, name) + + yield classes.Completion( + evaluator, + FileName(evaluator, name), + stack=None, + like_name_length=len(like_name), + ) class FileName(AbstractArbitraryName): diff --git a/jedi/api/keywords.py b/jedi/api/keywords.py index 10cf28bf..cc301b8d 100644 --- a/jedi/api/keywords.py +++ b/jedi/api/keywords.py @@ -1,7 +1,7 @@ import pydoc from jedi.evaluate.utils import ignored -from jedi.evaluate.names import AbstractNameDefinition +from jedi.evaluate.names import AbstractArbitraryName try: from pydoc_data import topics as pydoc_topics @@ -19,7 +19,7 @@ def get_operator(evaluator, string, pos): return Keyword(evaluator, string, pos) -class KeywordName(AbstractNameDefinition): +class KeywordName(AbstractArbitraryName): api_type = u'keyword' def infer(self): diff --git a/jedi/evaluate/names.py b/jedi/evaluate/names.py index 81be899a..b1c2d403 100644 --- a/jedi/evaluate/names.py +++ b/jedi/evaluate/names.py @@ -61,8 +61,8 @@ class AbstractNameDefinition(object): class AbstractArbitraryName(AbstractNameDefinition): """ When you e.g. want to complete dicts keys, you probably want to complete - string literals, which is not really a name, but for Jedi it works the same - way + string literals, which is not really a name, but for Jedi we use this + concept of Name for completions as well. """ is_context_name = False diff --git a/test/test_api/test_completion.py b/test/test_api/test_completion.py index 293c8577..f907b8c6 100644 --- a/test/test_api/test_completion.py +++ b/test/test_api/test_completion.py @@ -169,19 +169,29 @@ def test_keyword_completion(Script, code, has_keywords): ('example.py', 'r"comp"', None, ...), ('example.py', 'r"tes"', None, ...), ('example.py', 'r"tes"', 5, ['t' + s]), + ('example.py', 'r" tes"', 6, []), ('test%sexample.py' % s, 'r"tes"', 5, ['t' + s]), ('test%sexample.py' % s, 'r"test%scomp"' % s, 5, ['t' + s]), ('test%sexample.py' % s, 'r"test%scomp"' % s, 11, ['letion' + s]), - ('test%sexample.py' % s, 'r"%s"' % join('test', 'completion', 'basi'), 22, ['c.py']), + ('test%sexample.py' % s, '"%s"' % join('test', 'completion', 'basi'), 21, ['c.py']), ('example.py', 'rb"' + join('..', 'jedi', 'tes'), None, ['t' + s]), # Absolute paths (None, '"' + join(root_dir, 'test', 'test_ca'), None, ['che.py']), (None, '"%s"' % join(root_dir, 'test', 'test_ca'), len(root_dir) + 14, ['che.py']), + + # Longer quotes + ('example.py', 'r"""test', None, [s]), + ('example.py', 'r"""\ntest', None, []), + ('example.py', 'u"""tes\n', (1, 7), ['t' + s]), + ('example.py', '"""test%stest_cache.p"""' % s, 20, ['y']), ] ) def test_file_path_completions(Script, file, code, column, expected): - comps = Script(code, path=file, column=column).completions() + line = None + if isinstance(column, tuple): + line, column = column + comps = Script(code, path=file, line=line, column=column).completions() if expected == ...: assert len(comps) > 100 # This is basically global completions. else: