file_name.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import os
  2. from jedi.api import classes
  3. from jedi.api.strings import StringName, get_quote_ending
  4. from jedi.api.helpers import match
  5. from jedi.inference.helpers import get_str_or_none
  6. class PathName(StringName):
  7. api_type = 'path'
  8. def complete_file_name(inference_state, module_context, start_leaf, quote, string,
  9. like_name, signatures_callback, code_lines, position, fuzzy):
  10. # First we want to find out what can actually be changed as a name.
  11. like_name_length = len(os.path.basename(string))
  12. addition = _get_string_additions(module_context, start_leaf)
  13. if string.startswith('~'):
  14. string = os.path.expanduser(string)
  15. if addition is None:
  16. return
  17. string = addition + string
  18. # Here we use basename again, because if strings are added like
  19. # `'foo' + 'bar`, it should complete to `foobar/`.
  20. must_start_with = os.path.basename(string)
  21. string = os.path.dirname(string)
  22. sigs = signatures_callback(*position)
  23. is_in_os_path_join = sigs and all(s.full_name == 'os.path.join' for s in sigs)
  24. if is_in_os_path_join:
  25. to_be_added = _add_os_path_join(module_context, start_leaf, sigs[0].bracket_start)
  26. if to_be_added is None:
  27. is_in_os_path_join = False
  28. else:
  29. string = to_be_added + string
  30. base_path = os.path.join(inference_state.project.path, string)
  31. try:
  32. listed = sorted(os.scandir(base_path), key=lambda e: e.name)
  33. # OSError: [Errno 36] File name too long: '...'
  34. except (FileNotFoundError, OSError):
  35. return
  36. quote_ending = get_quote_ending(quote, code_lines, position)
  37. for entry in listed:
  38. name = entry.name
  39. if match(name, must_start_with, fuzzy=fuzzy):
  40. if is_in_os_path_join or not entry.is_dir():
  41. name += quote_ending
  42. else:
  43. name += os.path.sep
  44. yield classes.Completion(
  45. inference_state,
  46. PathName(inference_state, name[len(must_start_with) - like_name_length:]),
  47. stack=None,
  48. like_name_length=like_name_length,
  49. is_fuzzy=fuzzy,
  50. )
  51. def _get_string_additions(module_context, start_leaf):
  52. def iterate_nodes():
  53. node = addition.parent
  54. was_addition = True
  55. for child_node in reversed(node.children[:node.children.index(addition)]):
  56. if was_addition:
  57. was_addition = False
  58. yield child_node
  59. continue
  60. if child_node != '+':
  61. break
  62. was_addition = True
  63. addition = start_leaf.get_previous_leaf()
  64. if addition != '+':
  65. return ''
  66. context = module_context.create_context(start_leaf)
  67. return _add_strings(context, reversed(list(iterate_nodes())))
  68. def _add_strings(context, nodes, add_slash=False):
  69. string = ''
  70. first = True
  71. for child_node in nodes:
  72. values = context.infer_node(child_node)
  73. if len(values) != 1:
  74. return None
  75. c, = values
  76. s = get_str_or_none(c)
  77. if s is None:
  78. return None
  79. if not first and add_slash:
  80. string += os.path.sep
  81. string += s
  82. first = False
  83. return string
  84. def _add_os_path_join(module_context, start_leaf, bracket_start):
  85. def check(maybe_bracket, nodes):
  86. if maybe_bracket.start_pos != bracket_start:
  87. return None
  88. if not nodes:
  89. return ''
  90. context = module_context.create_context(nodes[0])
  91. return _add_strings(context, nodes, add_slash=True) or ''
  92. if start_leaf.type == 'error_leaf':
  93. # Unfinished string literal, like `join('`
  94. value_node = start_leaf.parent
  95. index = value_node.children.index(start_leaf)
  96. if index > 0:
  97. error_node = value_node.children[index - 1]
  98. if error_node.type == 'error_node' and len(error_node.children) >= 2:
  99. index = -2
  100. if error_node.children[-1].type == 'arglist':
  101. arglist_nodes = error_node.children[-1].children
  102. index -= 1
  103. else:
  104. arglist_nodes = []
  105. return check(error_node.children[index + 1], arglist_nodes[::2])
  106. return None
  107. # Maybe an arglist or some weird error case. Therefore checked below.
  108. searched_node_child = start_leaf
  109. while searched_node_child.parent is not None \
  110. and searched_node_child.parent.type not in ('arglist', 'trailer', 'error_node'):
  111. searched_node_child = searched_node_child.parent
  112. if searched_node_child.get_first_leaf() is not start_leaf:
  113. return None
  114. searched_node = searched_node_child.parent
  115. if searched_node is None:
  116. return None
  117. index = searched_node.children.index(searched_node_child)
  118. arglist_nodes = searched_node.children[:index]
  119. if searched_node.type == 'arglist':
  120. trailer = searched_node.parent
  121. if trailer.type == 'error_node':
  122. trailer_index = trailer.children.index(searched_node)
  123. assert trailer_index >= 2
  124. assert trailer.children[trailer_index - 1] == '('
  125. return check(trailer.children[trailer_index - 1], arglist_nodes[::2])
  126. elif trailer.type == 'trailer':
  127. return check(trailer.children[0], arglist_nodes[::2])
  128. elif searched_node.type == 'trailer':
  129. return check(searched_node.children[0], [])
  130. elif searched_node.type == 'error_node':
  131. # Stuff like `join(""`
  132. return check(arglist_nodes[-1], [])