оптимизировано. на первом запуске Выбора переменных конечно подвисает, но поиск работает относительно шустро
This commit is contained in:
parent
ad7b9126b7
commit
d3f1e824fa
@ -16,6 +16,7 @@ from VariableTable import VariableTableWidget, rows
|
||||
from scanVarGUI import ProcessOutputWindowDummy
|
||||
import scanVars
|
||||
import myXML
|
||||
import time
|
||||
|
||||
from PySide2.QtWidgets import (
|
||||
QApplication, QWidget, QTableWidget, QTableWidgetItem,
|
||||
|
@ -7,6 +7,7 @@ from PySide2.QtGui import QKeySequence, QKeyEvent
|
||||
from PySide2.QtCore import Qt, QStringListModel, QSettings
|
||||
import setupVars
|
||||
import myXML
|
||||
import time
|
||||
|
||||
|
||||
array_re = re.compile(r'^(\w+)\[(\d+)\]$')
|
||||
@ -18,6 +19,7 @@ class VariableSelectorDialog(QDialog):
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.resize(600, 500)
|
||||
self.selected_names = []
|
||||
self._bckspc_pressed = False # флаг подавления добавления разделителя
|
||||
|
||||
self.all_vars = all_vars
|
||||
self.structs = structs
|
||||
@ -153,88 +155,56 @@ class VariableSelectorDialog(QDialog):
|
||||
self.add_tree_item_recursively(item, child)
|
||||
|
||||
|
||||
def populate_tree(self):
|
||||
def populate_tree(self, vars_list=None):
|
||||
if vars_list is None:
|
||||
vars_list = self.expanded_vars
|
||||
self.tree.clear()
|
||||
self.node_index.clear()
|
||||
|
||||
for var in self.expanded_vars:
|
||||
for var in vars_list:
|
||||
self.add_tree_item_recursively(None, var)
|
||||
|
||||
header = self.tree.header()
|
||||
header.setSectionResizeMode(QHeaderView.Interactive) # вручную можно менять
|
||||
self.tree.setColumnWidth(0, 400)
|
||||
self.tree.resizeColumnToContents(1)
|
||||
""" header.setSectionResizeMode(0, QHeaderView.Stretch)
|
||||
header.setSectionResizeMode(1, QHeaderView.ResizeToContents) """
|
||||
|
||||
def expand_to_level(self, item, level, current_level=0):
|
||||
"""
|
||||
Рекурсивно раскрывает узлы до заданного уровня.
|
||||
"""
|
||||
if current_level < level:
|
||||
item.setExpanded(True)
|
||||
else:
|
||||
item.setExpanded(False)
|
||||
|
||||
for i in range(item.childCount()):
|
||||
self.expand_to_level(item.child(i), level, current_level + 1)
|
||||
|
||||
def filter_tree(self):
|
||||
text = self.search_input.text().strip().lower()
|
||||
path_parts = self.split_path(text) if text else []
|
||||
path_parts = text.split('.') if text else []
|
||||
filtered_vars = filter_vars(self.expanded_vars, path_parts)
|
||||
|
||||
def hide_all(item):
|
||||
item.setHidden(True)
|
||||
for i in range(item.childCount()):
|
||||
hide_all(item.child(i))
|
||||
# Сначала перерисовываем дерево
|
||||
self.populate_tree(filtered_vars)
|
||||
|
||||
def path_matches_search(name, search_parts):
|
||||
name_parts = self.split_path(name.lower())
|
||||
if len(name_parts) < len(search_parts):
|
||||
return False
|
||||
for sp, np in zip(search_parts, name_parts):
|
||||
if not np.startswith(sp):
|
||||
return False
|
||||
return True
|
||||
# Теперь 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)
|
||||
|
||||
def show_matching_path(item, level=0):
|
||||
name = item.text(0).lower()
|
||||
# Раскрываем путь до точного совпадения
|
||||
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()
|
||||
|
||||
# По умолчанию не совпадает
|
||||
matched = False
|
||||
|
||||
# Проверяем путь
|
||||
if not path_parts:
|
||||
matched = True
|
||||
elif path_matches_search(name, path_parts[:level+1]):
|
||||
matched = True
|
||||
|
||||
# Исключаем "плоские" элементы на верхнем уровне при глубоком поиске
|
||||
if level == 0 and item.childCount() == 0 and len(path_parts) > 1:
|
||||
matched = False
|
||||
|
||||
item.setHidden(not matched)
|
||||
|
||||
# Раскрываем узел, если он соответствует и ещё есть путь вниз
|
||||
if matched and level < len(path_parts) - 1:
|
||||
item.setExpanded(True)
|
||||
else:
|
||||
item.setExpanded(False)
|
||||
|
||||
matched_any_child = False
|
||||
for i in range(item.childCount()):
|
||||
child = item.child(i)
|
||||
if show_matching_path(child, level + 1):
|
||||
matched_any_child = True
|
||||
|
||||
return matched or matched_any_child
|
||||
|
||||
|
||||
# Если в поиске нет точки — особая логика для первого уровня
|
||||
if '.' 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)
|
||||
item.setExpanded(False) # НЕ раскрываем потомков
|
||||
else:
|
||||
hide_all(item)
|
||||
item.setHidden(True)
|
||||
else:
|
||||
# Обычная логика с поиском по пути
|
||||
for i in range(self.tree.topLevelItemCount()):
|
||||
item = self.tree.topLevelItem(i)
|
||||
hide_all(item)
|
||||
show_matching_path(item, 0)
|
||||
|
||||
|
||||
def find_node_by_path(self, root_vars, path_list):
|
||||
current_level = root_vars
|
||||
@ -249,13 +219,13 @@ class VariableSelectorDialog(QDialog):
|
||||
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] if parts else []
|
||||
prefix = parts[-1].lower() if parts else ''
|
||||
@ -264,29 +234,23 @@ class VariableSelectorDialog(QDialog):
|
||||
|
||||
completions = []
|
||||
|
||||
def find_path_items(parts):
|
||||
items = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
|
||||
for part in parts:
|
||||
part_lower = part.lower()
|
||||
matched = []
|
||||
for item in items:
|
||||
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
|
||||
def find_exact_node(parts):
|
||||
# Ищем точный узел по полному пути, используя node_index
|
||||
# Постепенно собираем fullname из parts
|
||||
if not parts:
|
||||
return None
|
||||
fullname = parts[0]
|
||||
for p in parts[1:]:
|
||||
fullname += '.' + p
|
||||
return self.node_index.get(fullname.lower())
|
||||
|
||||
if is_index_suggestion:
|
||||
# Предлагаем индексы (ищем всех детей с форматом foo[0], foo[1], ...)
|
||||
base_text = text[:-1] # убираем '[', получаем, например, 'foo'
|
||||
base_text = text[:-1] # убираем '['
|
||||
parent_node = self.find_node_by_fullname(base_text)
|
||||
if not parent_node:
|
||||
parent_node = self.find_node_by_fullname(base_text.rstrip('0123456789[]'))
|
||||
# если base_text может содержать индекс типа foo[12], попробуем очистить
|
||||
base_text_clean = re.sub(r'\[\d+\]$', '', base_text)
|
||||
parent_node = self.find_node_by_fullname(base_text_clean)
|
||||
if parent_node:
|
||||
seen = set()
|
||||
for i in range(parent_node.childCount()):
|
||||
@ -300,11 +264,10 @@ class VariableSelectorDialog(QDialog):
|
||||
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))
|
||||
completions.extend(node.child(i).text(0) for i in range(node.childCount()))
|
||||
elif not path_parts:
|
||||
# Первый уровень — по вхождению
|
||||
for i in range(self.tree.topLevelItemCount()):
|
||||
@ -313,24 +276,28 @@ class VariableSelectorDialog(QDialog):
|
||||
if prefix in name:
|
||||
completions.append(item.text(0))
|
||||
else:
|
||||
# Углубляемся: на последнем уровне используем startswith(prefix)
|
||||
matched_items = find_path_items(path_parts)
|
||||
for item in matched_items:
|
||||
for i in range(item.childCount()):
|
||||
child = item.child(i)
|
||||
node = find_exact_node(path_parts)
|
||||
if node:
|
||||
for i in range(node.childCount()):
|
||||
child = node.child(i)
|
||||
name = child.text(0)
|
||||
name_parts = self.split_path(name)
|
||||
# Оптимизируем split_path - кэширование
|
||||
name_parts = child.data(0, Qt.UserRole + 10)
|
||||
if name_parts is None:
|
||||
name_parts = self.split_path(name)
|
||||
child.setData(0, Qt.UserRole + 10, name_parts)
|
||||
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))
|
||||
|
||||
self.completer.complete()
|
||||
return completions
|
||||
|
||||
|
||||
|
||||
|
||||
# Функция для поиска узла с полным именем
|
||||
def find_node_by_fullname(self, name):
|
||||
return self.node_index.get(name.lower())
|
||||
@ -348,86 +315,82 @@ class VariableSelectorDialog(QDialog):
|
||||
text += '[' # для массивов
|
||||
else:
|
||||
text += '.' # fallback
|
||||
|
||||
if not self._bckspc_pressed:
|
||||
self.search_input.setText(text)
|
||||
self.search_input.setCursorPosition(len(text))
|
||||
|
||||
self.search_input.setText(text)
|
||||
self.search_input.setCursorPosition(len(text))
|
||||
self.update_completions()
|
||||
self.completer.complete()
|
||||
self.run_completions(text)
|
||||
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:
|
||||
completions = self.update_completions()
|
||||
self.completer.complete()
|
||||
text = self.search_input.text().strip()
|
||||
|
||||
if len(completions) == 1 and completions[0].lower() == text.lower():
|
||||
# Найдем узел с таким именем
|
||||
def find_exact_item(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
|
||||
|
||||
node = find_exact_item(completions[0])
|
||||
if node and node.childCount() > 0:
|
||||
# Используем первую подсказку, чтобы определить нужный разделитель
|
||||
completions = self.update_completions(text + '.')
|
||||
suggestion = completions[0]
|
||||
|
||||
# Ищем, какой символ идёт после текущего текста
|
||||
separator = '.'
|
||||
if suggestion.startswith(text):
|
||||
rest = suggestion[len(text):]
|
||||
if rest.startswith('->'):
|
||||
separator = '->'
|
||||
elif rest.startswith('.'):
|
||||
separator = '.'
|
||||
|
||||
self.search_input.setText(text + separator)
|
||||
completions = self.update_completions()
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
self.completer.complete()
|
||||
return True
|
||||
|
||||
# Иначе просто показываем подсказки
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
if completions:
|
||||
self.completer.complete()
|
||||
self.run_completions(text)
|
||||
return True
|
||||
if event.key() == Qt.Key_Backspace:
|
||||
self._bckspc_pressed = True
|
||||
else:
|
||||
self._bckspc_pressed = False
|
||||
|
||||
return super().eventFilter(obj, event)
|
||||
|
||||
def on_search_text_changed(self, text):
|
||||
if self.autocomplete_checkbox.isChecked():
|
||||
completions = self.update_completions(text)
|
||||
node = self.find_node_by_fullname(text)
|
||||
def run_completions(self, text):
|
||||
completions = self.update_completions(text)
|
||||
|
||||
should_show = False
|
||||
if len(completions) == 1 and completions[0].lower() == text.lower():
|
||||
# Найдем узел с таким именем
|
||||
def find_exact_item(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
|
||||
|
||||
if completions:
|
||||
if len(completions) > 1:
|
||||
should_show = True
|
||||
elif len(completions) == 1:
|
||||
single_node = self.find_node_by_fullname(completions[0])
|
||||
if single_node and single_node.childCount() > 0:
|
||||
should_show = True
|
||||
elif node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->')):
|
||||
should_show = True
|
||||
node = find_exact_item(completions[0])
|
||||
if node and node.childCount() > 0:
|
||||
# Используем первую подсказку, чтобы определить нужный разделитель
|
||||
completions = self.update_completions(text + '.')
|
||||
suggestion = completions[0]
|
||||
|
||||
if should_show:
|
||||
# Ищем, какой символ идёт после текущего текста
|
||||
separator = '.'
|
||||
if suggestion.startswith(text):
|
||||
rest = suggestion[len(text):]
|
||||
if rest.startswith(text + '->'):
|
||||
separator += '->'
|
||||
elif rest.startswith(text + '.'):
|
||||
separator += '.'
|
||||
elif '[' in rest:
|
||||
separator += '[' # для массивов
|
||||
else:
|
||||
separator += '.' # fallback
|
||||
|
||||
if not self._bckspc_pressed:
|
||||
self.search_input.setText(text + separator)
|
||||
completions = self.update_completions(text)
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
self.completer.complete()
|
||||
return True
|
||||
|
||||
# Иначе просто показываем подсказки
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
if completions:
|
||||
self.completer.complete()
|
||||
return True
|
||||
|
||||
def on_search_text_changed(self, text):
|
||||
self.filter_tree()
|
||||
if text == None:
|
||||
text = self.search_input.text().strip()
|
||||
if self.autocomplete_checkbox.isChecked():
|
||||
self.run_completions(text)
|
||||
|
||||
def on_add_clicked(self):
|
||||
self.selected_names = []
|
||||
@ -630,3 +593,36 @@ class VariableSelectorDialog(QDialog):
|
||||
tokens.append(token)
|
||||
return tokens
|
||||
|
||||
|
||||
def filter_vars(vars_list, path_parts):
|
||||
"""Рекурсивно фильтруем vars_list по path_parts и возвращаем только подходящие."""
|
||||
filtered = []
|
||||
|
||||
def matches_path(name, search_parts):
|
||||
name_parts = name.lower().split('.')
|
||||
if len(name_parts) < len(search_parts):
|
||||
return False
|
||||
for sp, np in zip(search_parts, name_parts):
|
||||
if not np.startswith(sp):
|
||||
return 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)
|
||||
|
||||
return filtered
|
||||
|
@ -289,18 +289,39 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
|
||||
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)
|
||||
# Проверим, является ли тип структурой (по имени)
|
||||
clean_type = scanVars.strip_ptr_and_array(field_type_str)
|
||||
struct_fields = structs.get(clean_type)
|
||||
|
||||
if isinstance(struct_fields, dict):
|
||||
# Это одиночная структура — раскрываем рекурсивно
|
||||
sub_items = expand_struct_recursively(full_name, field_type_str, structs, typedefs, var_attrs, depth + 1)
|
||||
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'),
|
||||
}
|
||||
if sub_items:
|
||||
child['children'] = sub_items
|
||||
children.append(child)
|
||||
else:
|
||||
# Обычное поле (int, float, etc.)
|
||||
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' или со сложной структурой, обрабатываем как вложенную структуру
|
||||
|
Loading…
Reference in New Issue
Block a user