почему-то внуки, правнуки и так далее в поиске на находятся...
This commit is contained in:
parent
4f949e9854
commit
69c0bf1574
@ -1,5 +1,3 @@
|
||||
# Поместите этот код перед классом VariableSelectorDialog
|
||||
|
||||
import re
|
||||
from PySide2.QtWidgets import (
|
||||
QWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QLineEdit,
|
||||
@ -54,38 +52,98 @@ def split_path(path):
|
||||
tokens.append(token)
|
||||
return tokens
|
||||
|
||||
def filter_vars(vars_list, path_parts):
|
||||
"""Рекурсивно фильтруем vars_list по path_parts по вхождению на любом уровне."""
|
||||
filtered = []
|
||||
def hide_all(item):
|
||||
item.setHidden(True)
|
||||
for i in range(item.childCount()):
|
||||
hide_all(item.child(i))
|
||||
|
||||
def matches_path(name, search_parts):
|
||||
name_parts = split_path(name)
|
||||
if len(name_parts) < len(search_parts):
|
||||
return False
|
||||
# Функция парсит имя с индексами в (базовое_имя, список_индексов)
|
||||
def parse_name_with_indices(name):
|
||||
# Регулярка для имени и индексов:
|
||||
# имя - подряд букв/цифр/подчёркиваний,
|
||||
# индексы в квадратных скобках
|
||||
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):
|
||||
if sp not in np:
|
||||
return False
|
||||
def match_path_part(search_part, node_part):
|
||||
# Если часть — индекс (вида [N]), требуем точное совпадение
|
||||
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
|
||||
|
||||
for var in vars_list:
|
||||
fullname = var.get('fullname', var['name']) # желательно иметь полное имя
|
||||
if not path_parts or matches_path(fullname, path_parts):
|
||||
new_var = var.copy()
|
||||
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)
|
||||
if level >= len(node_parts):
|
||||
# Уровень поиска больше длины пути узла — скрываем
|
||||
item.setHidden(True)
|
||||
return False
|
||||
|
||||
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):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
@ -177,7 +235,11 @@ class VariableSelectWidget(QWidget):
|
||||
while item:
|
||||
names.append(item.text(0))
|
||||
item = item.parent()
|
||||
return '.'.join(reversed(names))
|
||||
fullname = '.'.join(reversed(names))
|
||||
# Заменяем '->' на '.'
|
||||
fullname = fullname.replace('->', '.')
|
||||
return fullname
|
||||
|
||||
|
||||
def add_tree_item_recursively(self, parent, var):
|
||||
"""
|
||||
@ -213,31 +275,30 @@ class VariableSelectWidget(QWidget):
|
||||
|
||||
for child in var.get('children', []):
|
||||
self.add_tree_item_recursively(item, child)
|
||||
|
||||
|
||||
|
||||
|
||||
def filter_tree(self):
|
||||
text = self.search_input.text().strip().lower()
|
||||
path_parts = split_path(text) if text else []
|
||||
filtered_vars = filter_vars(self.expanded_vars, path_parts)
|
||||
|
||||
# Сначала перерисовываем дерево
|
||||
self.populate_tree(filtered_vars)
|
||||
if '.' not in text and '->' not in text and '[' not in text and text != '':
|
||||
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', [])
|
||||
return node
|
||||
|
||||
|
||||
def update_completions(self, text=None):
|
||||
if text is None:
|
||||
text = self.search_input.text().strip()
|
||||
else:
|
||||
text = text.strip()
|
||||
|
||||
normalized_text = text.replace('->', '.')
|
||||
parts = split_path(text)
|
||||
path_parts = parts[:-1] if parts else []
|
||||
prefix = parts[-1].lower() if parts else ''
|
||||
@ -287,6 +350,8 @@ class VariableSelectWidget(QWidget):
|
||||
seen = set()
|
||||
for i in range(parent_node.childCount()):
|
||||
child = parent_node.child(i)
|
||||
if child.isHidden():
|
||||
continue
|
||||
cname = child.text(0)
|
||||
m = re.match(rf'^{re.escape(base_text)}\[(\d+)\]$', cname)
|
||||
if m and cname not in seen:
|
||||
@ -298,11 +363,17 @@ class VariableSelectWidget(QWidget):
|
||||
if ends_with_sep:
|
||||
node = self.find_node_by_fullname(text[:-1])
|
||||
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:
|
||||
# Первый уровень — только если имя начинается с prefix
|
||||
for i in range(self.tree.topLevelItemCount()):
|
||||
item = self.tree.topLevelItem(i)
|
||||
if item.isHidden():
|
||||
continue
|
||||
name = item.text(0)
|
||||
if name.lower().startswith(prefix):
|
||||
completions.append(name)
|
||||
@ -311,6 +382,8 @@ class VariableSelectWidget(QWidget):
|
||||
if node:
|
||||
for i in range(node.childCount()):
|
||||
child = node.child(i)
|
||||
if child.isHidden():
|
||||
continue
|
||||
name = child.text(0)
|
||||
name_parts = child.data(0, Qt.UserRole + 10)
|
||||
if name_parts is None:
|
||||
@ -327,13 +400,13 @@ class VariableSelectWidget(QWidget):
|
||||
return completions
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Функция для поиска узла с полным именем
|
||||
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):
|
||||
node = self.find_node_by_fullname(text)
|
||||
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])
|
||||
if node and node.childCount() > 0:
|
||||
# Используем первую подсказку, чтобы определить нужный разделитель
|
||||
completions = self.update_completions(text + '.')
|
||||
completions = self.update_completions(text + '.')
|
||||
if not completions:
|
||||
return
|
||||
suggestion = completions[0]
|
||||
|
||||
# Ищем, какой символ идёт после текущего текста
|
||||
|
Loading…
Reference in New Issue
Block a user