почему-то внуки, правнуки и так далее в поиске на находятся...

This commit is contained in:
Razvalyaev 2025-07-12 18:13:51 +03:00
parent 4f949e9854
commit 69c0bf1574

View File

@ -1,5 +1,3 @@
# Поместите этот код перед классом VariableSelectorDialog
import re import re
from PySide2.QtWidgets import ( from PySide2.QtWidgets import (
QWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QLineEdit, QWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QLineEdit,
@ -54,38 +52,98 @@ def split_path(path):
tokens.append(token) tokens.append(token)
return tokens return tokens
def filter_vars(vars_list, path_parts): def hide_all(item):
"""Рекурсивно фильтруем vars_list по path_parts по вхождению на любом уровне.""" item.setHidden(True)
filtered = [] for i in range(item.childCount()):
hide_all(item.child(i))
def matches_path(name, search_parts): # Функция парсит имя с индексами в (базовое_имя, список_индексов)
name_parts = split_path(name) def parse_name_with_indices(name):
if len(name_parts) < len(search_parts): # Регулярка для имени и индексов:
return False # имя - подряд букв/цифр/подчёркиваний,
# индексы в квадратных скобках
base_match = re.match(r'^([a-zA-Z_]\w*)', name)
if not base_match:
return name, [] # если не совпало, возвращаем как есть
base_name = base_match.group(1)
indices = re.findall(r'\[(\d+)\]', name)
indices = list(map(int, indices))
return base_name, indices
for sp, np in zip(search_parts, name_parts): def match_path_part(search_part, node_part):
if sp not in np: # Если часть — индекс (вида [N]), требуем точное совпадение
return False if search_part.startswith('[') and search_part.endswith(']'):
return search_part == node_part
# Если search_part содержит '[', значит поиск неполный индекс
if '[' in search_part:
return node_part.startswith(search_part)
# Иначе — обычное имя: допускаем совпадение по префиксу
return node_part.startswith(search_part)
def show_matching_path(item, path_parts, level=0):
node_name = item.text(0).lower()
node_parts = split_path(node_name)
if level >= len(path_parts):
# Путь полностью пройден — показываем только этот узел (без раскрытия всех детей)
item.setHidden(False)
# Показываем детей, которые тоже соответствуют, но на этом уровне их нет,
# поэтому детей не раскрываем
for i in range(item.childCount()):
hide_all(item.child(i))
item.setExpanded(False)
return True return True
for var in vars_list: if level >= len(node_parts):
fullname = var.get('fullname', var['name']) # желательно иметь полное имя # Уровень поиска больше длины пути узла — скрываем
if not path_parts or matches_path(fullname, path_parts): item.setHidden(True)
new_var = var.copy() return False
if 'children' in var:
new_var['children'] = filter_vars(var['children'], path_parts)
filtered.append(new_var)
else:
if 'children' in var:
child_filtered = filter_vars(var['children'], path_parts)
if child_filtered:
new_var = var.copy()
new_var['children'] = child_filtered
filtered.append(new_var)
return filtered search_part = path_parts[level]
node_part = node_parts[level]
if search_part == node_part:
# Точное совпадение — показываем узел, идём вглубь только по совпадениям
item.setHidden(False)
matched_any = False
for i in range(item.childCount()):
child = item.child(i)
if show_matching_path(child, path_parts, level + 1):
matched_any = True
else:
hide_all(child)
item.setExpanded(matched_any)
return matched_any or item.childCount() == 0
elif node_part.startswith(search_part):
# Неполное совпадение — показываем только этот узел, детей скрываем, не раскрываем
item.setHidden(False)
for i in range(item.childCount()):
hide_all(item.child(i))
item.setExpanded(False)
return True
else:
# Несовпадение — скрываем
item.setHidden(True)
return False
class VariableSelectWidget(QWidget): class VariableSelectWidget(QWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
@ -177,7 +235,11 @@ class VariableSelectWidget(QWidget):
while item: while item:
names.append(item.text(0)) names.append(item.text(0))
item = item.parent() item = item.parent()
return '.'.join(reversed(names)) fullname = '.'.join(reversed(names))
# Заменяем '->' на '.'
fullname = fullname.replace('->', '.')
return fullname
def add_tree_item_recursively(self, parent, var): def add_tree_item_recursively(self, parent, var):
""" """
@ -213,31 +275,30 @@ class VariableSelectWidget(QWidget):
for child in var.get('children', []): for child in var.get('children', []):
self.add_tree_item_recursively(item, child) self.add_tree_item_recursively(item, child)
def filter_tree(self): def filter_tree(self):
text = self.search_input.text().strip().lower() text = self.search_input.text().strip().lower()
path_parts = split_path(text) if text else [] path_parts = split_path(text) if text else []
filtered_vars = filter_vars(self.expanded_vars, path_parts)
# Сначала перерисовываем дерево if '.' not in text and '->' not in text and '[' not in text and text != '':
self.populate_tree(filtered_vars) for i in range(self.tree.topLevelItemCount()):
item = self.tree.topLevelItem(i)
name = item.text(0).lower()
if text in name:
item.setHidden(False)
# Не сбрасываем expanded, чтобы можно было раскрывать вручную
else:
hide_all(item)
item.setHidden(True)
else:
for i in range(self.tree.topLevelItemCount()):
item = self.tree.topLevelItem(i)
hide_all(item)
item = self.tree.topLevelItem(i)
show_matching_path(item, path_parts, 0)
# Теперь node_index уже пересоздан — можно работать
expand_level = len(path_parts) - 1 if path_parts else 0
for i in range(self.tree.topLevelItemCount()):
item = self.tree.topLevelItem(i)
self.expand_to_level(item, expand_level)
# Раскрываем путь до точного совпадения
if path_parts:
fullname = '.'.join(path_parts)
node = self.node_index.get(fullname.lower())
if node:
parent = node.parent()
while parent:
parent.setExpanded(True)
parent = parent.parent()
@ -255,12 +316,14 @@ class VariableSelectWidget(QWidget):
current_level = node.get('children', []) current_level = node.get('children', [])
return node return node
def update_completions(self, text=None): def update_completions(self, text=None):
if text is None: if text is None:
text = self.search_input.text().strip() text = self.search_input.text().strip()
else: else:
text = text.strip() text = text.strip()
normalized_text = text.replace('->', '.')
parts = split_path(text) parts = split_path(text)
path_parts = parts[:-1] if parts else [] path_parts = parts[:-1] if parts else []
prefix = parts[-1].lower() if parts else '' prefix = parts[-1].lower() if parts else ''
@ -287,6 +350,8 @@ class VariableSelectWidget(QWidget):
seen = set() seen = set()
for i in range(parent_node.childCount()): for i in range(parent_node.childCount()):
child = parent_node.child(i) child = parent_node.child(i)
if child.isHidden():
continue
cname = child.text(0) cname = child.text(0)
m = re.match(rf'^{re.escape(base_text)}\[(\d+)\]$', cname) m = re.match(rf'^{re.escape(base_text)}\[(\d+)\]$', cname)
if m and cname not in seen: if m and cname not in seen:
@ -298,11 +363,17 @@ class VariableSelectWidget(QWidget):
if ends_with_sep: if ends_with_sep:
node = self.find_node_by_fullname(text[:-1]) node = self.find_node_by_fullname(text[:-1])
if node: if node:
completions.extend(node.child(i).text(0) for i in range(node.childCount())) for i in range(node.childCount()):
child = node.child(i)
if child.isHidden():
continue
completions.append(child.text(0))
elif not path_parts: elif not path_parts:
# Первый уровень — только если имя начинается с prefix # Первый уровень — только если имя начинается с prefix
for i in range(self.tree.topLevelItemCount()): for i in range(self.tree.topLevelItemCount()):
item = self.tree.topLevelItem(i) item = self.tree.topLevelItem(i)
if item.isHidden():
continue
name = item.text(0) name = item.text(0)
if name.lower().startswith(prefix): if name.lower().startswith(prefix):
completions.append(name) completions.append(name)
@ -311,6 +382,8 @@ class VariableSelectWidget(QWidget):
if node: if node:
for i in range(node.childCount()): for i in range(node.childCount()):
child = node.child(i) child = node.child(i)
if child.isHidden():
continue
name = child.text(0) name = child.text(0)
name_parts = child.data(0, Qt.UserRole + 10) name_parts = child.data(0, Qt.UserRole + 10)
if name_parts is None: if name_parts is None:
@ -327,13 +400,13 @@ class VariableSelectWidget(QWidget):
return completions return completions
# Функция для поиска узла с полным именем # Функция для поиска узла с полным именем
def find_node_by_fullname(self, name): def find_node_by_fullname(self, name):
return self.node_index.get(name.lower()) if name is None:
return None
normalized_name = name.replace('->', '.').lower()
return self.node_index.get(normalized_name)
def insert_completion(self, text): def insert_completion(self, text):
node = self.find_node_by_fullname(text) node = self.find_node_by_fullname(text)
if node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->') or text.endswith('[')): if node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->') or text.endswith('[')):
@ -398,7 +471,9 @@ class VariableSelectWidget(QWidget):
node = find_exact_item(completions[0]) node = find_exact_item(completions[0])
if node and node.childCount() > 0: if node and node.childCount() > 0:
# Используем первую подсказку, чтобы определить нужный разделитель # Используем первую подсказку, чтобы определить нужный разделитель
completions = self.update_completions(text + '.') completions = self.update_completions(text + '.')
if not completions:
return
suggestion = completions[0] suggestion = completions[0]
# Ищем, какой символ идёт после текущего текста # Ищем, какой символ идёт после текущего текста