diff --git a/jedi/api/completion.py b/jedi/api/completion.py index deef40b6..7fe6d665 100644 --- a/jedi/api/completion.py +++ b/jedi/api/completion.py @@ -96,20 +96,20 @@ class Completion: # The actual cursor position is not what we need to calculate # everything. We want the start of the name we're on. self._original_position = position - self._position = position[0], position[1] - len(self._like_name) self._signatures_callback = signatures_callback self._fuzzy = fuzzy def complete(self, fuzzy): - leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True) - string, start_leaf = _extract_string_while_in_string(leaf, self._position) + leaf = self._module_node.get_leaf_for_position(self._original_position, include_prefixes=True) + string, start_leaf, quote = _extract_string_while_in_string(leaf, self._original_position) prefixed_completions = complete_dict( self._module_context, - leaf, + self._code_lines, + start_leaf or leaf, self._original_position, - string, + None if string is None else quote + string, fuzzy=fuzzy, ) @@ -152,6 +152,10 @@ class Completion: grammar = self._inference_state.grammar self.stack = stack = None + self._position = ( + self._original_position[0], + self._original_position[1] - len(self._like_name) + ) try: self.stack = stack = helpers.get_stack_at_position( @@ -430,21 +434,33 @@ def _gather_nodes(stack): def _extract_string_while_in_string(leaf, position): if position < leaf.start_pos: - return None, None + return None, None, None if leaf.type == 'string': match = re.match(r'^\w*(\'{3}|"{3}|\'|")', leaf.value) - quote = match.group(1) + start = match.group(0) if leaf.line == position[0] and position[1] < leaf.column + match.end(): - return None, None - if leaf.end_pos[0] == position[0] and position[1] > leaf.end_pos[1] - len(quote): - return None, None - return cut_value_at_position(leaf, position)[match.end():], leaf + return None, None, None + if leaf.end_pos[0] == position[0] and position[1] > leaf.end_pos[1] - len(start): + return None, None, None + return cut_value_at_position(leaf, position)[match.end():], leaf, start leaves = [] while leaf is not None and leaf.line == position[0]: if leaf.type == 'error_leaf' and ('"' in leaf.value or "'" in leaf.value): - return ''.join(l.get_code() for l in leaves), leaf + prefix_leaf = None + if not leaf.prefix: + prefix_leaf = leaf.get_previous_leaf() + if prefix_leaf is None or prefix_leaf.type != 'name' \ + or not all(c in 'rubf' for c in prefix_leaf.value.lower()): + prefix_leaf = None + + return ( + ''.join(cut_value_at_position(l, position) for l in leaves), + prefix_leaf or leaf, + ('' if prefix_leaf is None else prefix_leaf.value) + + cut_value_at_position(leaf, position), + ) leaves.insert(0, leaf) leaf = leaf.get_previous_leaf() - return None, None + return None, None, None diff --git a/jedi/api/file_name.py b/jedi/api/file_name.py index 94c5bf62..f48c14a2 100644 --- a/jedi/api/file_name.py +++ b/jedi/api/file_name.py @@ -48,7 +48,7 @@ def complete_file_name(inference_state, module_context, start_leaf, string, match = start_match(name, must_start_with) if match: if is_in_os_path_join or not entry.is_dir(): - name += get_quote_ending(start_leaf, code_lines, position) + name += get_quote_ending(start_leaf.value, code_lines, position) else: name += os.path.sep diff --git a/jedi/api/strings.py b/jedi/api/strings.py index 3c9d4929..774dd444 100644 --- a/jedi/api/strings.py +++ b/jedi/api/strings.py @@ -23,26 +23,19 @@ class StringName(AbstractArbitraryName): is_value_name = False -def complete_dict(module_context, leaf, position, string, fuzzy): - if string is None: - string = '' +def complete_dict(module_context, code_lines, leaf, position, string, fuzzy): bracket_leaf = leaf - end_quote = '' - if bracket_leaf.type in ('number', 'error_leaf'): - string = cut_value_at_position(bracket_leaf, position) - if bracket_leaf.end_pos > position: - end_quote = _get_string_quote(string) or '' - if end_quote: - ending = cut_value_at_position( - bracket_leaf, - (position[0], position[1] + len(end_quote)) - ) - if not ending.endswith(end_quote): - end_quote = '' + if bracket_leaf != '[': + bracket_leaf = leaf.get_previous_leaf() - bracket_leaf = bracket_leaf.get_previous_leaf() + cut_end_quote = '' + if string: + cut_end_quote = get_quote_ending(string, code_lines, position, invert_result=True) if bracket_leaf == '[': + if string is None and leaf is not bracket_leaf: + string = cut_value_at_position(leaf, position) + context = module_context.create_context(bracket_leaf) before_bracket_leaf = bracket_leaf.get_previous_leaf() if before_bracket_leaf.type in ('atom', 'trailer', 'name'): @@ -51,17 +44,17 @@ def complete_dict(module_context, leaf, position, string, fuzzy): module_context.inference_state, values, '' if string is None else string, - end_quote, + cut_end_quote, fuzzy=fuzzy, )) return [] -def _completions_for_dicts(inference_state, dicts, literal_string, end_quote, fuzzy): +def _completions_for_dicts(inference_state, dicts, literal_string, cut_end_quote, fuzzy): for dict_key in sorted(_get_python_keys(dicts), key=lambda x: repr(x)): dict_key_str = _create_repr_string(literal_string, dict_key) if dict_key_str.startswith(literal_string): - n = dict_key_str[len(literal_string):-len(end_quote) or None] + n = dict_key_str[len(literal_string):-len(cut_end_quote) or None] name = StringName(inference_state, n) yield Completion( inference_state, @@ -78,6 +71,8 @@ def _create_repr_string(literal_string, dict_key): r = repr(dict_key) prefix, quote = _get_string_prefix_and_quote(literal_string) + if quote is None: + return r if quote == r[0]: return prefix + r return prefix + quote + r[1:-1] + quote @@ -103,15 +98,14 @@ def _get_string_quote(string): return _get_string_prefix_and_quote(string)[1] -def get_quote_ending(start_leaf, code_lines, position): - if start_leaf.type == 'string': - quote = _get_string_quote(start_leaf) - else: - assert start_leaf.type == 'error_leaf' - quote = start_leaf.value - potential_other_quote = \ - code_lines[position[0] - 1][position[1]:position[1] + len(quote)] +def _matches_quote_at_position(code_lines, quote, position): + string = code_lines[position[0] - 1][position[1]:position[1] + len(quote)] + return string == quote + + +def get_quote_ending(string, code_lines, position, invert_result=False): + quote = _get_string_quote(string) # Add a quote only if it's not already there. - if quote == potential_other_quote: + if _matches_quote_at_position(code_lines, quote, position) != invert_result: return '' return quote diff --git a/jedi/inference/compiled/access.py b/jedi/inference/compiled/access.py index de762c36..20a19698 100644 --- a/jedi/inference/compiled/access.py +++ b/jedi/inference/compiled/access.py @@ -410,7 +410,7 @@ class DirectObjectAccess(object): return [self._create_access(module), access] def get_safe_value(self): - if type(self._obj) in (bool, bytes, float, int, str, unicode, slice): + if type(self._obj) in (bool, bytes, float, int, str, unicode, slice) or self._obj is None: return self._obj raise ValueError("Object is type %s and not simple" % type(self._obj)) diff --git a/test/test_api/test_completion.py b/test/test_api/test_completion.py index 41da9c7d..2440a17b 100644 --- a/test/test_api/test_completion.py +++ b/test/test_api/test_completion.py @@ -298,15 +298,15 @@ _dict_keys_completion_tests = [ ('strs[b"]', 8, []), ('strs[r"asd', 10, ['f"']), ('strs[R"asd', 10, ['f"']), - ('strs[f"asd', 10, ['f"']), + ('strs[f"asd', 10, []), - ('strs["f', 7, ['oo"]']), - ('strs["f"', 7, ['oo']), - ('strs["f]', 7, ['oo"]']), - ('strs["f"]', 7, ['oo']), + ('strs["f', 7, ['bar"', 'oo"']), + ('strs["f"', 7, ['bar', 'oo']), + ('strs["f]', 7, ['bar"', 'oo"']), + ('strs["f"]', 7, ['bar', 'oo']), - ('mixed[', 6, ['1', '1.1', 'None', "'a\sdf'", "b'foo'"]), - ('mixed[1', 6, ['', '.1']), + ('mixed[', 6, [r"'a\\sdf'", '1', '1.1', 'None', "b'foo'", Ellipsis]), + ('mixed[1', 7, ['', '.1']), ('casted["f', 9, ['3"', 'bar"', 'oo"']), ('casted_mod["f', 13, ['3"', 'bar"', 'oo"', 'uuu"', 'ull"']),