From 5726c293856a3348e8dff8c53d6c498c4e408c03 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Wed, 7 Aug 2019 01:34:46 +0200 Subject: [PATCH] Make some file path completions in os.path.join work --- jedi/api/file_name.py | 77 +++++++++++++++++++++++--------- test/test_api/test_completion.py | 7 ++- 2 files changed, 63 insertions(+), 21 deletions(-) diff --git a/jedi/api/file_name.py b/jedi/api/file_name.py index 37a8888b..5d680efb 100644 --- a/jedi/api/file_name.py +++ b/jedi/api/file_name.py @@ -10,12 +10,17 @@ def file_name_completions(evaluator, module_context, start_leaf, string, like_na # First we want to find out what can actually be changed as a name. like_name_length = len(os.path.basename(string) + like_name) - string = _get_string_additions(module_context, start_leaf) + string + 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 = _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) @@ -36,32 +41,64 @@ def file_name_completions(evaluator, module_context, start_leaf, string, like_na 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 '' - node = addition.parent - string = '' - was_addition = True - for child_node in reversed(node.children[:node.children.index(addition)]): - if was_addition: - was_addition = False - context = module_context.create_context(node) - contexts = context.eval_node(child_node) - if len(contexts) != 1: - return string - c, = contexts - s = get_str_or_none(c) - if s is None: - return string - string = force_unicode(s) + string - continue + return _add_strings(module_context, reversed(list(iterate_nodes()))) - if child_node != '+': - break - was_addition = True + +def _add_strings(module_context, nodes, add_slash=False): + string = '' + context = None + first = True + for child_node in nodes: + if context is None: + context = module_context.create_context(child_node) + 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): + arglist = start_leaf.parent + if arglist.type == 'arglist': + trailer = arglist.parent + if trailer.type == 'trailer': + atom = trailer.get_previous_sibling() + if atom.type != 'trailer': + context = module_context.create_context(atom) + contexts = context.eval_node(atom) + if any([c.name.get_qualified_names(include_module_names=True) + != ('os', 'path', 'join') for c in contexts]): + return string + nodes = arglist.children[:arglist.children.index(start_leaf):2] + return _add_strings(module_context, nodes, add_slash=True) + + return string diff --git a/test/test_api/test_completion.py b/test/test_api/test_completion.py index 6b656e9c..8fb9538c 100644 --- a/test/test_api/test_completion.py +++ b/test/test_api/test_completion.py @@ -194,7 +194,7 @@ os_path = 'from os.path import *\n' # Adding ('example.py', '"test" + "%stest_cac' % s, None, ['he.py']), ('example.py', '"test" + "%s" + "test_cac' % s, None, ['he.py']), - ('example.py', 'x = 1 + "test', None, [s]), + ('example.py', 'x = 1 + "test', None, []), ('example.py', 'x = f("te" + "st)', 16, [s]), ('example.py', 'x = f("te" + "st', 16, [s]), ('example.py', 'x = f("te" + "st"', 16, [s]), @@ -210,6 +210,11 @@ os_path = 'from os.path import *\n' (f2, os_path + 'dirname(__file__) + "%stest_ca' % s, None, ['che.py']), (f2, os_path + 'dirname(abspath(__file__)) + sep + "test_ca', None, ['che.py']), (f2, os_path + 'join(dirname(__file__), "completion") + sep + "basi', None, ['c.py']), + (f2, os_path + 'join(dirname(__file__), "completion", "basi', None, ['c.py']), + (f2, os_path + 'join(dirname(__file__), "completion", "basi)', 43, ['c.py']), + (f2, os_path + 'join(dirname(__file__), "completion", "basi")', 43, ['c.py']), + (f2, os_path + 'join(dirname(__file__), "completion", "basi)', 35, []), + (f2, os_path + 'join(dirname(__file__), "completion", "basi)', 33, ['on']), ] ) def test_file_path_completions(Script, file, code, column, expected):