diff --git a/jedi/api/file_name.py b/jedi/api/file_name.py index 2ff766d6..37a8888b 100644 --- a/jedi/api/file_name.py +++ b/jedi/api/file_name.py @@ -8,8 +8,7 @@ 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. - base_name = os.path.basename(string) - like_name_length = len(base_name + like_name) + like_name_length = len(os.path.basename(string) + like_name) string = _get_string_additions(module_context, start_leaf) + string # Here we use basename again, because if strings are added like diff --git a/jedi/evaluate/filters.py b/jedi/evaluate/filters.py index b59501f6..0b758ea7 100644 --- a/jedi/evaluate/filters.py +++ b/jedi/evaluate/filters.py @@ -386,7 +386,7 @@ def get_global_filters(evaluator, context, until_position, origin_scope): >>> list(filters[1].values()) # package modules -> Also empty. [] >>> sorted(name.string_name for name in filters[2].values()) # Module attributes - ['__doc__', '__file__', '__name__', '__package__'] + ['__doc__', '__name__', '__package__'] Finally, it yields the builtin filter, if `include_builtin` is true (default). diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index f53e3110..ebb8f1ab 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -10,6 +10,7 @@ the standard library. The usual way to understand the standard library is the compiled module that returns the types for C-builtins. """ import parso +import os from jedi._compatibility import force_unicode, Parameter from jedi import debug @@ -678,6 +679,22 @@ def _operator_itemgetter(args_context_set, obj, arguments): ]) +def _create_string_input_function(func): + @argument_clinic('string, /', want_obj=True, want_arguments=True) + def wrapper(strings, obj, arguments): + def iterate(): + for context in strings: + s = get_str_or_none(context) + if s is not None: + s = func(s) + yield compiled.create_simple_object(context.evaluator, s) + contexts = ContextSet(iterate()) + if contexts: + return contexts + return obj.py__call__(arguments) + return wrapper + + _implemented = { 'builtins': { 'getattr': builtins_getattr, @@ -729,6 +746,11 @@ _implemented = { # For now this works at least better than Jedi trying to understand it. 'dataclass': _dataclass }, + 'os.path': { + 'dirname': _create_string_input_function(os.path.dirname), + 'abspath': _create_string_input_function(os.path.abspath), + 'relpath': _create_string_input_function(os.path.relpath), + } } diff --git a/test/test_api/test_completion.py b/test/test_api/test_completion.py index 68d5a45e..03695bee 100644 --- a/test/test_api/test_completion.py +++ b/test/test_api/test_completion.py @@ -159,6 +159,10 @@ def test_keyword_completion(Script, code, has_keywords): assert has_keywords == any(x.is_keyword for x in Script(code).completions()) +f1 = join(root_dir, 'example.py') +f2 = join(root_dir, 'test', 'example.py') + + @pytest.mark.parametrize( 'file, code, column, expected', [ # General tests / relative paths @@ -199,6 +203,12 @@ def test_keyword_completion(Script, code, has_keywords): # Python 2. ('example.py', 'x = f(b"t" + "est")', 17, [s]), ('example.py', '"test" + "', None, [s]), + + # __file__ + (f1, 'from os.path import *\ndirname(__file__) + "%stest' % s, None, [s]), + (f2, 'from os.path import *\ndirname(__file__) + "%stest_ca' % s, None, ['che.py']), + (f2, 'from os.path import *\ndirname(abspath(__file__)) + "%stest_ca' % s, + None, ['che.py']), ] ) def test_file_path_completions(Script, file, code, column, expected):