strings.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. """
  2. This module is here for string completions. This means mostly stuff where
  3. strings are returned, like `foo = dict(bar=3); foo["ba` would complete to
  4. `"bar"]`.
  5. It however does the same for numbers. The difference between string completions
  6. and other completions is mostly that this module doesn't return defined
  7. names in a module, but pretty much an arbitrary string.
  8. """
  9. import re
  10. from jedi.inference.names import AbstractArbitraryName
  11. from jedi.inference.helpers import infer_call_of_leaf
  12. from jedi.api.classes import Completion
  13. from jedi.parser_utils import cut_value_at_position
  14. _sentinel = object()
  15. class StringName(AbstractArbitraryName):
  16. api_type = 'string'
  17. is_value_name = False
  18. def complete_dict(module_context, code_lines, leaf, position, string, fuzzy):
  19. bracket_leaf = leaf
  20. if bracket_leaf != '[':
  21. bracket_leaf = leaf.get_previous_leaf()
  22. cut_end_quote = ''
  23. if string:
  24. cut_end_quote = get_quote_ending(string, code_lines, position, invert_result=True)
  25. if bracket_leaf == '[':
  26. if string is None and leaf is not bracket_leaf:
  27. string = cut_value_at_position(leaf, position)
  28. context = module_context.create_context(bracket_leaf)
  29. before_node = before_bracket_leaf = bracket_leaf.get_previous_leaf()
  30. if before_node in (')', ']', '}'):
  31. before_node = before_node.parent
  32. if before_node.type in ('atom', 'trailer', 'name'):
  33. values = infer_call_of_leaf(context, before_bracket_leaf)
  34. return list(_completions_for_dicts(
  35. module_context.inference_state,
  36. values,
  37. '' if string is None else string,
  38. cut_end_quote,
  39. fuzzy=fuzzy,
  40. ))
  41. return []
  42. def _completions_for_dicts(inference_state, dicts, literal_string, cut_end_quote, fuzzy):
  43. for dict_key in sorted(_get_python_keys(dicts), key=lambda x: repr(x)):
  44. dict_key_str = _create_repr_string(literal_string, dict_key)
  45. if dict_key_str.startswith(literal_string):
  46. name = StringName(inference_state, dict_key_str[:-len(cut_end_quote) or None])
  47. yield Completion(
  48. inference_state,
  49. name,
  50. stack=None,
  51. like_name_length=len(literal_string),
  52. is_fuzzy=fuzzy
  53. )
  54. def _create_repr_string(literal_string, dict_key):
  55. if not isinstance(dict_key, (str, bytes)) or not literal_string:
  56. return repr(dict_key)
  57. r = repr(dict_key)
  58. prefix, quote = _get_string_prefix_and_quote(literal_string)
  59. if quote is None:
  60. return r
  61. if quote == r[0]:
  62. return prefix + r
  63. return prefix + quote + r[1:-1] + quote
  64. def _get_python_keys(dicts):
  65. for dct in dicts:
  66. if dct.array_type == 'dict':
  67. for key in dct.get_key_values():
  68. dict_key = key.get_safe_value(default=_sentinel)
  69. if dict_key is not _sentinel:
  70. yield dict_key
  71. def _get_string_prefix_and_quote(string):
  72. match = re.match(r'(\w*)("""|\'{3}|"|\')', string)
  73. if match is None:
  74. return None, None
  75. return match.group(1), match.group(2)
  76. def _matches_quote_at_position(code_lines, quote, position):
  77. string = code_lines[position[0] - 1][position[1]:position[1] + len(quote)]
  78. return string == quote
  79. def get_quote_ending(string, code_lines, position, invert_result=False):
  80. _, quote = _get_string_prefix_and_quote(string)
  81. if quote is None:
  82. return ''
  83. # Add a quote only if it's not already there.
  84. if _matches_quote_at_position(code_lines, quote, position) != invert_result:
  85. return ''
  86. return quote