diff --git a/Src/VariableSelector.py b/Src/VariableSelector.py index 3fe18b5..b7a4431 100644 --- a/Src/VariableSelector.py +++ b/Src/VariableSelector.py @@ -24,7 +24,7 @@ class VariableSelectorDialog(QDialog): self.typedefs = typedefs self.expanded_vars = [] self.var_map = {v['name']: v for v in all_vars} - + self.node_index = {} self.xml_path = xml_path # сохраняем путь к xml # --- Добавляем чекбокс для автодополнения --- @@ -91,9 +91,32 @@ class VariableSelectorDialog(QDialog): self.setLayout(layout) self.expanded_vars = setupVars.expand_vars(self.all_vars, self.structs, self.typedefs) - + self.build_completion_list() self.populate_tree() + + def get_full_item_name(self, item): + names = [] + while item: + names.append(item.text(0)) + item = item.parent() + return '.'.join(reversed(names)) + + def build_completion_list(self): + # Собираем список полных имён всех переменных и вложенных полей + completions = [] + + def recurse(var, prefix=''): + fullname = f"{prefix}.{var['name']}" if prefix else var['name'] + completions.append(fullname) + for child in var.get('children', []): + recurse(child, fullname) + + for v in self.expanded_vars: + recurse(v) + + self.all_completions = completions + def add_tree_item_recursively(self, parent, var): """ Рекурсивно добавляет переменную и её дочерние поля в дерево. @@ -105,6 +128,8 @@ class VariableSelectorDialog(QDialog): item = QTreeWidgetItem([name, type_str]) item.setData(0, Qt.UserRole, name) + full_name = self.get_full_item_name(item) + self.node_index[full_name.lower()] = item # Делаем bitfield-поля неактивными if "(bitfield:" in type_str: @@ -130,6 +155,7 @@ class VariableSelectorDialog(QDialog): def populate_tree(self): self.tree.clear() + self.node_index.clear() for var in self.expanded_vars: self.add_tree_item_recursively(None, var) @@ -209,74 +235,130 @@ class VariableSelectorDialog(QDialog): item = self.tree.topLevelItem(i) hide_all(item) show_matching_path(item, 0) - - def update_completions(self, text = None): + + def find_node_by_path(self, root_vars, path_list): + current_level = root_vars + node = None + for part in path_list: + node = None + for var in current_level: + if var['name'] == part: + node = var + break + if node is None: + return None + current_level = node.get('children', []) + return node + + def update_completions(self, text=None): if text is None: text = self.search_input.text().strip() else: text = text.strip() + parts = self.split_path(text) - path_parts = parts[:-1] - prefix = parts[-1].lower() if not text.endswith(('.', '>')) else '' + path_parts = parts[:-1] if parts else [] + prefix = parts[-1].lower() if parts else '' + ends_with_sep = text.endswith('.') or text.endswith('->') or text.endswith('[') + is_index_suggestion = text.endswith('[') - # Если путь есть (например: project.adc или project.adc.), ищем внутри него - search_deep = len(path_parts) > 0 + completions = [] - def find_path_items(path_parts): + def find_path_items(parts): items = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())] - - for part in path_parts: + for part in parts: part_lower = part.lower() matched = [] - for item in items: - # Берём последний фрагмент имени item, разделённого точками - item_name_part = self.split_path(item.text(0))[-1].lower() - - if item_name_part == part_lower: + name_parts = self.split_path(item.text(0).lower()) + if name_parts and name_parts[-1] == part_lower: matched.append(item) - if not matched: return [] items = [] - # Собираем детей для следующего уровня поиска for node in matched: for i in range(node.childCount()): items.append(node.child(i)) - return matched - if not search_deep: - # Без точки — ищем только в топ-уровне, фильтруя по prefix - items = [] + if is_index_suggestion: + # Предлагаем индексы (ищем всех детей с форматом foo[0], foo[1], ...) + base_text = text[:-1] # убираем '[', получаем, например, 'foo' + parent_node = self.find_node_by_fullname(base_text) + if not parent_node: + parent_node = self.find_node_by_fullname(base_text.rstrip('0123456789[]')) + if parent_node: + seen = set() + for i in range(parent_node.childCount()): + child = parent_node.child(i) + cname = child.text(0) + m = re.match(rf'^{re.escape(base_text)}\[(\d+)\]$', cname) + if m and cname not in seen: + completions.append(cname) + seen.add(cname) + self.completer.setModel(QStringListModel(completions)) + return completions + + if ends_with_sep: + # Завершён путь (например: project.adc[0].), показываем детей + node = self.find_node_by_fullname(text[:-1]) + if node: + for i in range(node.childCount()): + completions.append(node.child(i).text(0)) + elif not path_parts: + # Первый уровень — по вхождению for i in range(self.tree.topLevelItemCount()): item = self.tree.topLevelItem(i) - name_part = self.split_path(item.text(0))[-1].lower() - if name_part.startswith(prefix): - items.append(item) - completions = [item.text(0) for item in items] + name = item.text(0).lower() + if prefix in name: + completions.append(item.text(0)) else: - # С точкой — углубляемся по пути и показываем имена детей - if len(path_parts) == 0: - items = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())] - else: - items = find_path_items(path_parts) - - completions = [] - for item in items: + # Углубляемся: на последнем уровне используем startswith(prefix) + matched_items = find_path_items(path_parts) + for item in matched_items: for i in range(item.childCount()): child = item.child(i) - name_part = self.split_path(child.text(0))[-1].lower() - if prefix == '' or name_part.startswith(prefix): - completions.append(child.text(0)) - - + name = child.text(0) + name_parts = self.split_path(name) + if not name_parts: + continue + last_part = name_parts[-1].lower() + if prefix == '' or last_part.startswith(prefix): + completions.append(name) self.completer.setModel(QStringListModel(completions)) return completions + # Функция для поиска узла с полным именем + def find_node_by_fullname(self, name): + return self.node_index.get(name.lower()) + + def insert_completion(self, text): + node = self.find_node_by_fullname(text) + if node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->') or text.endswith('[')): + # Определяем разделитель по имени первого ребёнка + child_name = node.child(0).text(0) + if child_name.startswith(text + '->'): + text += '->' + elif child_name.startswith(text + '.'): + text += '.' + elif '[' in child_name: + text += '[' # для массивов + else: + text += '.' # fallback + + self.search_input.setText(text) + self.search_input.setCursorPosition(len(text)) + self.update_completions() + self.completer.complete() + else: + self.search_input.setText(text) + self.search_input.setCursorPosition(len(text)) + + + def eventFilter(self, obj, event): if obj == self.search_input and isinstance(event, QKeyEvent): if event.key() == Qt.Key_Space and event.modifiers() & Qt.ControlModifier: @@ -325,36 +407,6 @@ class VariableSelectorDialog(QDialog): return super().eventFilter(obj, event) - # Функция для поиска узла с полным именем - def find_node_by_fullname(self, name): - stack = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())] - while stack: - node = stack.pop() - if node.text(0).lower() == name.lower(): - return node - for i in range(node.childCount()): - stack.append(node.child(i)) - return None - - def insert_completion(self, text): - - node = self.find_node_by_fullname(text) - if node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->')): - # Определяем разделитель по имени первого ребёнка - child_name = node.child(0).text(0) - if child_name.startswith(text + '->'): - text += '->' - else: - text += '.' - - self.search_input.setText(text) - self.search_input.setCursorPosition(len(text)) - self.update_completions() - self.completer.complete() - else: - self.search_input.setText(text) - self.search_input.setCursorPosition(len(text)) - def on_search_text_changed(self, text): if self.autocomplete_checkbox.isChecked(): completions = self.update_completions(text) @@ -532,6 +584,49 @@ class VariableSelectorDialog(QDialog): self.settings.setValue("autocomplete_enabled", self.autocomplete_checkbox.isChecked()) + def split_path(self, path): - # Разбиваем по точке или по -> (учитываем, что -> длиной 2 символа) - return re.split(r'\.|->', path) \ No newline at end of file + """ + Разбивает путь на компоненты: + - 'foo[2].bar[1]->baz' → ['foo', [2]', 'bar', '[1]' 'baz'] + """ + tokens = [] + token = '' + i = 0 + while i < len(path): + c = path[i] + # Разделители: '->' и '.' + if c == '-' and path[i:i+2] == '->': + if token: + tokens.append(token) + token = '' + i += 2 + continue + elif c == '.': + if token: + tokens.append(token) + token = '' + i += 1 + continue + elif c == '[': + # Заканчиваем текущий токен, если есть + if token: + tokens.append(token) + token = '' + # Собираем индекс [N] + idx = '' + while i < len(path) and path[i] != ']': + idx += path[i] + i += 1 + if i < len(path) and path[i] == ']': + idx += ']' + i += 1 + tokens.append(idx) + continue + else: + token += c + i += 1 + if token: + tokens.append(token) + return tokens + diff --git a/Src/setupVars.py b/Src/setupVars.py index 4de57a1..542f934 100644 --- a/Src/setupVars.py +++ b/Src/setupVars.py @@ -153,37 +153,158 @@ def parse_structs(filename): +def parse_array_dims(type_str): + """Возвращает базовый тип и список размеров массива""" + dims = list(map(int, re.findall(r'\[(\d+)\]', type_str))) + base_type = re.sub(r'\[\d+\]', '', type_str).strip() + return base_type, dims + +def generate_array_names(prefix, dims, depth=0): + """Рекурсивно генерирует имена для всех элементов многомерного массива""" + if not dims: + return [prefix] + + result = [] + for i in range(dims[0]): + new_prefix = f"{prefix}[{i}]" + children = generate_array_names(new_prefix, dims[1:], depth + 1) + result.append({ + 'name': new_prefix, + 'children': children if len(dims) > 1 else None + }) + return result + +def flatten_array_tree(array_tree): + """Разворачивает дерево массивов в линейный список с вложенными children""" + result = [] + for node in array_tree: + entry = {'name': node['name']} + if node['children']: + entry['children'] = flatten_array_tree(node['children']) + result.append(entry) + return result + + def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, depth=0): if depth > 10: return [] - # Если type_str — словарь структуры + # Вспомогательная функция для обработки массивов + def process_array(prefix, base_type, dims): + array_tree = generate_array_names(prefix, dims) + array_flat = flatten_array_tree(array_tree) + for node in array_flat: + sub_items = expand_struct_recursively(node['name'], base_type, structs, typedefs, var_attrs, depth + 1) + if sub_items: + node['children'] = sub_items + else: + node.update({ + 'type': base_type, + 'pt_type': '', + 'iq_type': '', + 'return_type': '', + 'file': var_attrs.get('file'), + 'extern': var_attrs.get('extern'), + 'static': var_attrs.get('static'), + }) + return array_flat + + # Если type_str — уже распарсенная структура (dict) if isinstance(type_str, dict): fields = type_str else: + # Проверяем, массив ли это + base_type, array_dims = parse_array_dims(type_str) + if array_dims: + return process_array(prefix, base_type, array_dims) + + # Ищем структуру по имени типа base_type = scanVars.strip_ptr_and_array(type_str) fields = structs.get(base_type) if not isinstance(fields, dict): + # Не структура и не массив — просто возвращаем пустой список return [] children = [] for field_name, field_value in fields.items(): - # Пропускаем поле 'type', оно служит для хранения имени типа if field_name == 'type': continue - # Определяем разделитель между prefix и полем + # Формируем полное имя поля if prefix.endswith('*'): separator = '->' - # Для красоты можно убрать пробелы у указателя - # например, если prefix="ptr*" -> "ptr->field" full_name = f"{prefix[:-1]}{separator}{field_name}" else: separator = '.' full_name = f"{prefix}{separator}{field_name}" + # Определяем тип поля + if isinstance(field_value, dict) and isinstance(field_value.get('type'), str): + field_type_str = field_value['type'] + elif isinstance(field_value, str): + field_type_str = field_value + else: + field_type_str = None + + # Обработка, если поле — строка (тип или массив) + if field_type_str: + base_subtype, sub_dims = parse_array_dims(field_type_str) + if sub_dims: + # Массив — раскрываем элементы + array_parent = { + 'name': full_name, + 'type': field_type_str, + 'pt_type': '', + 'iq_type': '', + 'return_type': '', + 'file': var_attrs.get('file'), + 'extern': var_attrs.get('extern'), + 'static': var_attrs.get('static'), + } + + array_children = [] + flat_names = generate_array_names(full_name, sub_dims) + for node in flat_names: + # node — dict с ключом 'name' и (возможно) 'children' + sub_items = expand_struct_recursively(node['name'], base_subtype, structs, typedefs, var_attrs, depth + 1) + child_node = { + 'name': node['name'], + 'type': base_subtype, + 'pt_type': '', + 'iq_type': '', + 'return_type': '', + 'file': var_attrs.get('file'), + 'extern': var_attrs.get('extern'), + 'static': var_attrs.get('static'), + } + if sub_items: + child_node['children'] = sub_items + array_children.append(child_node) + + array_parent['children'] = array_children + children.append(array_parent) + continue + + # Игнорируем указатели на функции + if "(" in field_type_str and "*" in field_type_str and ")" in field_type_str: + continue + + # Обычное поле (не массив, не функция) + child = { + 'name': full_name, + 'type': field_type_str, + 'pt_type': '', + 'iq_type': '', + 'return_type': '', + 'file': var_attrs.get('file'), + 'extern': var_attrs.get('extern'), + 'static': var_attrs.get('static'), + } + children.append(child) + continue + + # Если поле — dict без 'type' или со сложной структурой, обрабатываем как вложенную структуру if isinstance(field_value, dict): - # Если вложенная структура — берем её имя типа из поля 'type' или пустую строку type_name = field_value.get('type', '') child = { 'name': full_name, @@ -195,35 +316,14 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de 'extern': var_attrs.get('extern'), 'static': var_attrs.get('static'), } - if '*' in type_name and not full_name.endswith('*'): - full_name += '*' - # Рекурсивно раскрываем вложенные поля subchildren = expand_struct_recursively(full_name, field_value, structs, typedefs, var_attrs, depth + 1) if subchildren: child['children'] = subchildren - else: - # Простое поле — строка типа - # Пропускаем указатели на функции - if isinstance(field_value, str) and "(" in field_value and "*" in field_value and ")" in field_value: - continue - - child = { - 'name': full_name, - 'type': field_value, - 'pt_type': '', - 'iq_type': '', - 'return_type': '', - 'file': var_attrs.get('file'), - 'extern': var_attrs.get('extern'), - 'static': var_attrs.get('static'), - } - - children.append(child) + children.append(child) return children - def expand_vars(vars_list, structs, typedefs): """ Раскрывает структуры и массивы структур в деревья. @@ -233,26 +333,26 @@ def expand_vars(vars_list, structs, typedefs): for var in vars_list: pt_type = var.get('pt_type', '') raw_type = var.get('type', '') - base_type = scanVars.strip_ptr_and_array(raw_type) - fields = structs.get(base_type) - - if pt_type.startswith('pt_ptr_') and isinstance(fields, dict): + if var['name'] == 'project': + a = 1 + + if pt_type.startswith('pt_ptr_'): new_var = var.copy() new_var['children'] = expand_struct_recursively(var['name']+'*', raw_type, structs, typedefs, var) expanded.append(new_var) - if pt_type.startswith('pt_arr_') and isinstance(fields, dict): + if pt_type.startswith('pt_arr_'): new_var = var.copy() new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var) expanded.append(new_var) - elif pt_type == 'pt_struct' and isinstance(fields, dict): + elif pt_type == 'pt_struct': new_var = var.copy() new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var) expanded.append(new_var) - elif pt_type == 'pt_union' and isinstance(fields, dict): + elif pt_type == 'pt_union': new_var = var.copy() new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var) expanded.append(new_var) diff --git a/vars.xml b/vars.xml index 0dbb0e8..82ba255 100644 --- a/vars.xml +++ b/vars.xml @@ -1,5 +1,5 @@ - + true @@ -3592,7 +3592,7 @@ true true - project.cds_tk.count_elements=_pbus + project.cds_tk.count_elements_pbus pt_uint64 t_iq_none t_iq_none