Get dict completions mostly working

This commit is contained in:
Dave Halter
2019-12-30 03:25:17 +01:00
parent 7e769b87f3
commit 9fa4811425
5 changed files with 60 additions and 50 deletions

View File

@@ -96,20 +96,20 @@ class Completion:
# The actual cursor position is not what we need to calculate # The actual cursor position is not what we need to calculate
# everything. We want the start of the name we're on. # everything. We want the start of the name we're on.
self._original_position = position self._original_position = position
self._position = position[0], position[1] - len(self._like_name)
self._signatures_callback = signatures_callback self._signatures_callback = signatures_callback
self._fuzzy = fuzzy self._fuzzy = fuzzy
def complete(self, fuzzy): def complete(self, fuzzy):
leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True) leaf = self._module_node.get_leaf_for_position(self._original_position, include_prefixes=True)
string, start_leaf = _extract_string_while_in_string(leaf, self._position) string, start_leaf, quote = _extract_string_while_in_string(leaf, self._original_position)
prefixed_completions = complete_dict( prefixed_completions = complete_dict(
self._module_context, self._module_context,
leaf, self._code_lines,
start_leaf or leaf,
self._original_position, self._original_position,
string, None if string is None else quote + string,
fuzzy=fuzzy, fuzzy=fuzzy,
) )
@@ -152,6 +152,10 @@ class Completion:
grammar = self._inference_state.grammar grammar = self._inference_state.grammar
self.stack = stack = None self.stack = stack = None
self._position = (
self._original_position[0],
self._original_position[1] - len(self._like_name)
)
try: try:
self.stack = stack = helpers.get_stack_at_position( self.stack = stack = helpers.get_stack_at_position(
@@ -430,21 +434,33 @@ def _gather_nodes(stack):
def _extract_string_while_in_string(leaf, position): def _extract_string_while_in_string(leaf, position):
if position < leaf.start_pos: if position < leaf.start_pos:
return None, None return None, None, None
if leaf.type == 'string': if leaf.type == 'string':
match = re.match(r'^\w*(\'{3}|"{3}|\'|")', leaf.value) 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(): if leaf.line == position[0] and position[1] < leaf.column + match.end():
return None, None return None, None, None
if leaf.end_pos[0] == position[0] and position[1] > leaf.end_pos[1] - len(quote): if leaf.end_pos[0] == position[0] and position[1] > leaf.end_pos[1] - len(start):
return None, None return None, None, None
return cut_value_at_position(leaf, position)[match.end():], leaf return cut_value_at_position(leaf, position)[match.end():], leaf, start
leaves = [] leaves = []
while leaf is not None and leaf.line == position[0]: while leaf is not None and leaf.line == position[0]:
if leaf.type == 'error_leaf' and ('"' in leaf.value or "'" in leaf.value): 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) leaves.insert(0, leaf)
leaf = leaf.get_previous_leaf() leaf = leaf.get_previous_leaf()
return None, None return None, None, None

View File

@@ -48,7 +48,7 @@ def complete_file_name(inference_state, module_context, start_leaf, string,
match = start_match(name, must_start_with) match = start_match(name, must_start_with)
if match: if match:
if is_in_os_path_join or not entry.is_dir(): 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: else:
name += os.path.sep name += os.path.sep

View File

@@ -23,26 +23,19 @@ class StringName(AbstractArbitraryName):
is_value_name = False is_value_name = False
def complete_dict(module_context, leaf, position, string, fuzzy): def complete_dict(module_context, code_lines, leaf, position, string, fuzzy):
if string is None:
string = ''
bracket_leaf = leaf bracket_leaf = leaf
end_quote = '' if bracket_leaf != '[':
if bracket_leaf.type in ('number', 'error_leaf'): bracket_leaf = leaf.get_previous_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 = ''
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 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) context = module_context.create_context(bracket_leaf)
before_bracket_leaf = bracket_leaf.get_previous_leaf() before_bracket_leaf = bracket_leaf.get_previous_leaf()
if before_bracket_leaf.type in ('atom', 'trailer', 'name'): 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, module_context.inference_state,
values, values,
'' if string is None else string, '' if string is None else string,
end_quote, cut_end_quote,
fuzzy=fuzzy, fuzzy=fuzzy,
)) ))
return [] 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)): for dict_key in sorted(_get_python_keys(dicts), key=lambda x: repr(x)):
dict_key_str = _create_repr_string(literal_string, dict_key) dict_key_str = _create_repr_string(literal_string, dict_key)
if dict_key_str.startswith(literal_string): 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) name = StringName(inference_state, n)
yield Completion( yield Completion(
inference_state, inference_state,
@@ -78,6 +71,8 @@ def _create_repr_string(literal_string, dict_key):
r = repr(dict_key) r = repr(dict_key)
prefix, quote = _get_string_prefix_and_quote(literal_string) prefix, quote = _get_string_prefix_and_quote(literal_string)
if quote is None:
return r
if quote == r[0]: if quote == r[0]:
return prefix + r return prefix + r
return prefix + quote + r[1:-1] + quote return prefix + quote + r[1:-1] + quote
@@ -103,15 +98,14 @@ def _get_string_quote(string):
return _get_string_prefix_and_quote(string)[1] return _get_string_prefix_and_quote(string)[1]
def get_quote_ending(start_leaf, code_lines, position): def _matches_quote_at_position(code_lines, quote, position):
if start_leaf.type == 'string': string = code_lines[position[0] - 1][position[1]:position[1] + len(quote)]
quote = _get_string_quote(start_leaf) return string == quote
else:
assert start_leaf.type == 'error_leaf'
quote = start_leaf.value def get_quote_ending(string, code_lines, position, invert_result=False):
potential_other_quote = \ quote = _get_string_quote(string)
code_lines[position[0] - 1][position[1]:position[1] + len(quote)]
# Add a quote only if it's not already there. # 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 ''
return quote return quote

View File

@@ -410,7 +410,7 @@ class DirectObjectAccess(object):
return [self._create_access(module), access] return [self._create_access(module), access]
def get_safe_value(self): 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 return self._obj
raise ValueError("Object is type %s and not simple" % type(self._obj)) raise ValueError("Object is type %s and not simple" % type(self._obj))

View File

@@ -298,15 +298,15 @@ _dict_keys_completion_tests = [
('strs[b"]', 8, []), ('strs[b"]', 8, []),
('strs[r"asd', 10, ['f"']), ('strs[r"asd', 10, ['f"']),
('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, ['bar"', 'oo"']),
('strs["f"', 7, ['oo']), ('strs["f"', 7, ['bar', 'oo']),
('strs["f]', 7, ['oo"]']), ('strs["f]', 7, ['bar"', 'oo"']),
('strs["f"]', 7, ['oo']), ('strs["f"]', 7, ['bar', 'oo']),
('mixed[', 6, ['1', '1.1', 'None', "'a\sdf'", "b'foo'"]), ('mixed[', 6, [r"'a\\sdf'", '1', '1.1', 'None', "b'foo'", Ellipsis]),
('mixed[1', 6, ['', '.1']), ('mixed[1', 7, ['', '.1']),
('casted["f', 9, ['3"', 'bar"', 'oo"']), ('casted["f', 9, ['3"', 'bar"', 'oo"']),
('casted_mod["f', 13, ['3"', 'bar"', 'oo"', 'uuu"', 'ull"']), ('casted_mod["f', 13, ['3"', 'bar"', 'oo"', 'uuu"', 'ull"']),