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