почему-то внуки, правнуки и так далее в поиске на находятся...
This commit is contained in:
parent
4f949e9854
commit
69c0bf1574
@ -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]
|
||||||
|
|
||||||
# Ищем, какой символ идёт после текущего текста
|
# Ищем, какой символ идёт после текущего текста
|
||||||
|
Loading…
Reference in New Issue
Block a user