677 lines
28 KiB
Python
677 lines
28 KiB
Python
"""
|
|
Sistema de Autocompletado para la Calculadora MAV CAS Híbrida
|
|
"""
|
|
import time
|
|
import re
|
|
from typing import List, Tuple, Optional
|
|
from PySide6.QtCore import Qt, QTimer
|
|
from PySide6.QtGui import QTextCursor
|
|
import logging
|
|
from .gui_widgets import AutocompletePopup
|
|
from .sympy_Helper import SympyTools as SympyHelper
|
|
from .type_registry import get_registered_base_context
|
|
|
|
|
|
class AutocompleteManager:
|
|
"""Gestor del sistema de autocompletado"""
|
|
|
|
def __init__(self, parent_app):
|
|
self.parent_app = parent_app
|
|
self.logger = logging.getLogger(__name__)
|
|
|
|
# Variables de autocompletado
|
|
self._autocomplete_popup = None
|
|
self._autocomplete_active = False
|
|
self._autocomplete_suggestions = []
|
|
self._autocomplete_filter_text = ""
|
|
self._autocomplete_trigger_pos = None
|
|
self._popup_disabled_until_next_dot = False
|
|
self._variable_popup_active = False
|
|
self._last_navigation_time = 0
|
|
self._last_input_change = 0
|
|
self._current_suggestions = []
|
|
self._is_global_popup = False
|
|
|
|
# Timers
|
|
self._variable_popup_timer = QTimer()
|
|
self._variable_popup_timer.setSingleShot(True)
|
|
self._variable_popup_timer.timeout.connect(self._show_variable_autocomplete_improved)
|
|
|
|
def handle_key_press(self, event) -> bool:
|
|
"""Maneja eventos de teclado para autocompletado - retorna True si manejó el evento"""
|
|
# Navegación en popup
|
|
if self._autocomplete_active or self._variable_popup_active:
|
|
if event.key() == Qt.Key_Up:
|
|
self._handle_arrow_key(-1)
|
|
return True
|
|
elif event.key() == Qt.Key_Down:
|
|
self._handle_arrow_key(1)
|
|
return True
|
|
elif event.key() == Qt.Key_Tab:
|
|
self._handle_tab_key()
|
|
return True
|
|
elif event.key() == Qt.Key_Escape:
|
|
self._handle_escape_key()
|
|
return True
|
|
elif event.key() in [Qt.Key_Return, Qt.Key_Enter]:
|
|
self._select_autocomplete()
|
|
return True
|
|
|
|
# Detectar backspace para filtrar autocompletado o cerrar popup
|
|
if event.key() == Qt.Key_Backspace:
|
|
if self._autocomplete_active:
|
|
# Filtrar dinámicamente al eliminar caracteres
|
|
QTimer.singleShot(1, self._filter_autocomplete)
|
|
QTimer.singleShot(1, self._check_dot_removal)
|
|
else:
|
|
# Activar autocompletado de variables si no está activo
|
|
QTimer.singleShot(50, self._schedule_variable_autocomplete_improved)
|
|
|
|
# Procesar autocompletado después de insertar carácter
|
|
if event.text() and not event.modifiers() & Qt.ControlModifier:
|
|
# Guardar datos del evento para evitar que se elimine el objeto
|
|
event_text = event.text()
|
|
event_key = event.key()
|
|
QTimer.singleShot(10, lambda: self._on_key_release_deferred(event_text, event_key))
|
|
|
|
return False
|
|
|
|
def _on_key_release_deferred(self, event_text: str, event_key: int):
|
|
"""Maneja eventos después de insertar carácter usando datos guardados"""
|
|
# Cancelar timer de variables
|
|
self._variable_popup_timer.stop()
|
|
|
|
# Verificar si acabamos de navegar
|
|
just_navigated = (time.time() - self._last_navigation_time) < 0.1
|
|
|
|
# Caracteres que cierran el autocompletado
|
|
closing_chars = [' ', '+', '-', '*', '/', '(', ')', '=', ',', ';', '>', '<', '!']
|
|
|
|
# Cerrar autocompletado con símbolos y espacio
|
|
if self._autocomplete_active and event_text in closing_chars:
|
|
self._close_autocomplete_popup()
|
|
return
|
|
|
|
# Manejar autocompletado con punto
|
|
if event_text == '.' and not self._popup_disabled_until_next_dot:
|
|
if self._variable_popup_active:
|
|
self._close_autocomplete_popup()
|
|
self._handle_dot_autocomplete()
|
|
|
|
# Filtrar autocompletado si está activo (incluye caracteres alfanuméricos y backspace procesado)
|
|
elif self._autocomplete_active and not just_navigated:
|
|
self._filter_autocomplete()
|
|
|
|
# Marcar tiempo del último cambio
|
|
if event_text:
|
|
self._last_input_change = time.time()
|
|
|
|
# Programar autocompletado de variables - también cuando se eliminen caracteres
|
|
if not self._autocomplete_active and not self._popup_disabled_until_next_dot:
|
|
self._schedule_variable_autocomplete_improved()
|
|
elif self._variable_popup_active and not just_navigated:
|
|
# Si el popup de variables está activo, regenerar dinámicamente
|
|
QTimer.singleShot(50, self._regenerate_variable_autocomplete)
|
|
|
|
def _handle_dot_autocomplete(self):
|
|
"""Maneja el autocompletado cuando se escribe un punto"""
|
|
self._close_autocomplete_popup()
|
|
|
|
cursor = self.parent_app.input_text.textCursor()
|
|
cursor_pos = cursor.position()
|
|
|
|
# Obtener línea actual
|
|
cursor.select(QTextCursor.LineUnderCursor)
|
|
line_text = cursor.selectedText()
|
|
|
|
# Calcular posición del punto en la línea
|
|
line_start = cursor.selectionStart()
|
|
dot_pos_in_line = cursor_pos - line_start - 1
|
|
|
|
if dot_pos_in_line < 0:
|
|
return
|
|
|
|
# Guardar posición del trigger
|
|
self._autocomplete_trigger_pos = cursor_pos
|
|
self._autocomplete_filter_text = ""
|
|
|
|
# Texto antes del punto
|
|
text_before_dot = line_text[:dot_pos_in_line].strip()
|
|
|
|
# Determinar si es popup GLOBAL
|
|
if not text_before_dot:
|
|
self.logger.debug("Dot en línea vacía. Ofreciendo sugerencias globales.")
|
|
suggestions = self._get_global_suggestions()
|
|
if suggestions:
|
|
self._show_autocomplete_popup(suggestions, is_global_popup=True)
|
|
return
|
|
|
|
# Es popup de OBJETO
|
|
obj_expr_str = self._extract_object_expression(text_before_dot)
|
|
if not obj_expr_str:
|
|
return
|
|
|
|
self.logger.debug(f"Autocompletado para objeto: '{obj_expr_str}'")
|
|
|
|
# Caso especial para sympy
|
|
if obj_expr_str == "sympy":
|
|
methods = SympyHelper.PopupFunctionList()
|
|
if methods:
|
|
self._show_autocomplete_popup(methods, is_global_popup=False)
|
|
return
|
|
|
|
# Preprocesar con bracket parser si es necesario
|
|
if '[' in obj_expr_str:
|
|
obj_expr_str = self.parent_app.engine.parser._transform_brackets(obj_expr_str)
|
|
|
|
# Evaluar la expresión del objeto
|
|
eval_context = self.parent_app.engine._get_full_context()
|
|
try:
|
|
obj = eval(obj_expr_str, eval_context)
|
|
self.logger.debug(f"Objeto evaluado: {type(obj)}")
|
|
|
|
# Mostrar métodos del objeto
|
|
if hasattr(obj, 'PopupFunctionList'):
|
|
methods = obj.PopupFunctionList()
|
|
if methods:
|
|
self._show_autocomplete_popup(methods, is_global_popup=False)
|
|
|
|
except Exception as e:
|
|
self.logger.debug(f"Error evaluando objeto '{obj_expr_str}': {e}")
|
|
|
|
def _extract_object_expression(self, text: str) -> str:
|
|
"""Extrae la expresión del objeto del texto antes del punto"""
|
|
obj_expr_regex = r"([a-zA-Z_][a-zA-Z0-9_]*(?:\[[^\]]*\])?(?:(?:\s*\.\s*[a-zA-Z_][a-zA-Z0-9_]*)(?:\[[^\]]*\])?)*)$"
|
|
match = re.search(obj_expr_regex, text)
|
|
|
|
if match:
|
|
return match.group(1).replace(" ", "")
|
|
|
|
# Fallback
|
|
if re.match(r"^[a-zA-Z_0-9\(\)\[\]\.\"\'\+\-\*\/ ]*$", text) and \
|
|
not text.endswith(("+", "-", "*", "/", "(", ",")):
|
|
return text
|
|
|
|
return ""
|
|
|
|
def _get_global_suggestions(self) -> List[Tuple[str, str]]:
|
|
"""Obtiene sugerencias globales para autocompletado"""
|
|
suggestions = []
|
|
|
|
try:
|
|
# Obtener contexto dinámico
|
|
dynamic_context = get_registered_base_context()
|
|
|
|
for name, class_or_func in dynamic_context.items():
|
|
if name[0].isupper(): # Prioritizar capitalizados
|
|
hint = f"Tipo o función: {name}"
|
|
if hasattr(class_or_func, '__doc__') and class_or_func.__doc__:
|
|
first_line = class_or_func.__doc__.strip().split('\n')[0]
|
|
hint = f"{name} - {first_line}"
|
|
suggestions.append((name, hint))
|
|
|
|
# Añadir funciones SymPy
|
|
sympy_functions = SympyHelper.PopupFunctionList()
|
|
if sympy_functions:
|
|
current_names = {s[0] for s in suggestions}
|
|
for fname, fhint in sympy_functions:
|
|
if fname not in current_names:
|
|
suggestions.append((fname, fhint))
|
|
|
|
# Añadir variables del contexto actual
|
|
try:
|
|
context = self.parent_app.engine._get_full_context()
|
|
current_names = {s[0] for s in suggestions}
|
|
|
|
for name, value in context.items():
|
|
if (not name.startswith('_') and
|
|
not callable(value) and
|
|
name not in ['sympy', 'math', 'numpy', 'plt', 'builtins'] and
|
|
name not in current_names):
|
|
|
|
# Descripción del valor
|
|
value_str = str(value)
|
|
if len(value_str) > 20:
|
|
value_str = value_str[:17] + "..."
|
|
|
|
suggestions.append((name, f"Variable = {value_str}"))
|
|
except Exception:
|
|
pass
|
|
|
|
except Exception as e:
|
|
self.logger.debug(f"Error obteniendo sugerencias globales: {e}")
|
|
|
|
suggestions.sort(key=lambda x: x[0])
|
|
return suggestions
|
|
|
|
def _schedule_variable_autocomplete_improved(self):
|
|
"""Programa el autocompletado de variables"""
|
|
if self._autocomplete_active or self._popup_disabled_until_next_dot:
|
|
return
|
|
|
|
# Verificar que estemos escribiendo
|
|
cursor = self.parent_app.input_text.textCursor()
|
|
cursor.select(QTextCursor.LineUnderCursor)
|
|
current_line = cursor.selectedText().strip()
|
|
|
|
if not current_line or current_line.endswith('.'):
|
|
return
|
|
|
|
# Programar para 800ms después
|
|
self._variable_popup_timer.start(800)
|
|
|
|
def _regenerate_variable_autocomplete(self):
|
|
"""Regenera dinámicamente el autocompletado de variables"""
|
|
if not self._variable_popup_active:
|
|
return
|
|
|
|
# Obtener la palabra actual bajo el cursor
|
|
cursor = self.parent_app.input_text.textCursor()
|
|
cursor.select(QTextCursor.WordUnderCursor)
|
|
filter_text = cursor.selectedText().lower()
|
|
|
|
# Obtener variables del contexto actual
|
|
try:
|
|
context = self.parent_app.engine._get_full_context()
|
|
variables = []
|
|
|
|
# Filtrar variables
|
|
for name, value in context.items():
|
|
if (not name.startswith('_') and
|
|
not callable(value) and
|
|
name not in ['sympy', 'math', 'numpy', 'plt', 'builtins']):
|
|
|
|
# Descripción del valor
|
|
value_str = str(value)
|
|
if len(value_str) > 20:
|
|
value_str = value_str[:17] + "..."
|
|
|
|
variables.append((name, f"= {value_str}"))
|
|
|
|
if variables:
|
|
variables.sort(key=lambda x: x[0])
|
|
|
|
# Filtrar por texto actual si existe
|
|
if filter_text:
|
|
filtered_vars = [
|
|
(name, value) for name, value in variables
|
|
if name.lower().startswith(filter_text) and name.lower() != filter_text
|
|
]
|
|
else:
|
|
filtered_vars = variables
|
|
|
|
if filtered_vars:
|
|
# Actualizar sugerencias y popup
|
|
self._current_suggestions = variables
|
|
if self._autocomplete_popup:
|
|
self._autocomplete_popup.set_suggestions(filtered_vars)
|
|
self._autocomplete_popup.adjust_size()
|
|
else:
|
|
self._close_autocomplete_popup()
|
|
else:
|
|
self._close_autocomplete_popup()
|
|
|
|
except Exception as e:
|
|
self.logger.debug(f"Error regenerando variables: {e}")
|
|
|
|
def _show_variable_autocomplete_improved(self):
|
|
"""Muestra autocompletado de variables disponibles"""
|
|
if self._autocomplete_active or self._variable_popup_active:
|
|
return
|
|
|
|
# Verificar línea actual
|
|
cursor = self.parent_app.input_text.textCursor()
|
|
cursor.select(QTextCursor.LineUnderCursor)
|
|
current_line = cursor.selectedText().strip()
|
|
|
|
if not current_line or current_line.endswith('.'):
|
|
return
|
|
|
|
# Obtener variables del contexto
|
|
try:
|
|
context = self.parent_app.engine._get_full_context()
|
|
variables = []
|
|
|
|
# Filtrar variables
|
|
for name, value in context.items():
|
|
if (not name.startswith('_') and
|
|
not callable(value) and
|
|
name not in ['sympy', 'math', 'numpy', 'plt', 'builtins']):
|
|
|
|
# Descripción del valor
|
|
value_str = str(value)
|
|
if len(value_str) > 20:
|
|
value_str = value_str[:17] + "..."
|
|
|
|
variables.append((name, value_str))
|
|
|
|
if variables:
|
|
variables.sort(key=lambda x: x[0])
|
|
|
|
# Filtrar por palabra actual
|
|
words = current_line.split()
|
|
if words:
|
|
last_word = words[-1]
|
|
filtered_vars = [
|
|
(name, value) for name, value in variables
|
|
if name.lower().startswith(last_word.lower()) and name != last_word
|
|
]
|
|
|
|
if filtered_vars:
|
|
self._show_variable_popup(filtered_vars)
|
|
|
|
except Exception as e:
|
|
self.logger.debug(f"Error obteniendo variables: {e}")
|
|
|
|
def _show_autocomplete_popup(self, suggestions: List[Tuple[str, str]], is_global_popup: bool = False):
|
|
"""Muestra popup de autocompletado"""
|
|
if not suggestions:
|
|
return
|
|
|
|
self._close_autocomplete_popup()
|
|
|
|
# Guardar estado
|
|
self._current_suggestions = suggestions.copy()
|
|
self._is_global_popup = is_global_popup
|
|
self._autocomplete_active = True
|
|
|
|
# Crear popup
|
|
self._autocomplete_popup = AutocompletePopup(self.parent_app)
|
|
self._autocomplete_popup.set_suggestions(suggestions)
|
|
self._autocomplete_popup.item_selected.connect(self._on_autocomplete_selected)
|
|
|
|
# Posicionar
|
|
cursor_rect = self.parent_app.input_text.cursorRect()
|
|
global_pos = self.parent_app.input_text.mapToGlobal(cursor_rect.bottomLeft())
|
|
self._autocomplete_popup.move(global_pos)
|
|
self._autocomplete_popup.adjust_size()
|
|
self._autocomplete_popup.show()
|
|
|
|
def _show_variable_popup(self, variables: List[Tuple[str, str]]):
|
|
"""Muestra popup de variables"""
|
|
self._variable_popup_active = True
|
|
self._autocomplete_active = True # También debe estar activo para el filtrado
|
|
|
|
# Las variables ya vienen en el formato correcto (nombre, descripción)
|
|
self._show_autocomplete_popup(variables, is_global_popup=False)
|
|
|
|
def _filter_autocomplete(self):
|
|
"""Filtra las sugerencias del autocompletado dinámicamente"""
|
|
if not self._autocomplete_active:
|
|
return
|
|
|
|
cursor = self.parent_app.input_text.textCursor()
|
|
current_pos = cursor.position()
|
|
|
|
# Para popup de variables (filtrar por texto parcial)
|
|
if self._variable_popup_active:
|
|
# Obtener la palabra actual bajo el cursor
|
|
cursor.select(QTextCursor.WordUnderCursor)
|
|
filter_text = cursor.selectedText().lower()
|
|
self._autocomplete_filter_text = filter_text
|
|
|
|
# Si no hay texto que filtrar, regenerar popup con todas las variables
|
|
if not filter_text:
|
|
# Regenerar popup dinámicamente con variables actuales
|
|
try:
|
|
context = self.parent_app.engine._get_full_context()
|
|
variables = []
|
|
|
|
# Filtrar variables
|
|
for name, value in context.items():
|
|
if (not name.startswith('_') and
|
|
not callable(value) and
|
|
name not in ['sympy', 'math', 'numpy', 'plt', 'builtins']):
|
|
|
|
# Descripción del valor
|
|
value_str = str(value)
|
|
if len(value_str) > 20:
|
|
value_str = value_str[:17] + "..."
|
|
|
|
variables.append((name, f"= {value_str}"))
|
|
|
|
if variables:
|
|
variables.sort(key=lambda x: x[0])
|
|
self._current_suggestions = variables
|
|
if self._autocomplete_popup:
|
|
self._autocomplete_popup.set_suggestions(variables)
|
|
self._autocomplete_popup.adjust_size()
|
|
else:
|
|
self._close_autocomplete_popup()
|
|
except Exception:
|
|
self._close_autocomplete_popup()
|
|
return
|
|
|
|
# Filtrar sugerencias de variables existentes + regenerar con contexto actual
|
|
try:
|
|
context = self.parent_app.engine._get_full_context()
|
|
all_variables = []
|
|
|
|
# Obtener todas las variables actuales
|
|
for name, value in context.items():
|
|
if (not name.startswith('_') and
|
|
not callable(value) and
|
|
name not in ['sympy', 'math', 'numpy', 'plt', 'builtins']):
|
|
|
|
# Descripción del valor
|
|
value_str = str(value)
|
|
if len(value_str) > 20:
|
|
value_str = value_str[:17] + "..."
|
|
|
|
all_variables.append((name, f"= {value_str}"))
|
|
|
|
# Filtrar por el texto parcial
|
|
filtered = []
|
|
for name, hint in all_variables:
|
|
if name.lower().startswith(filter_text):
|
|
filtered.append((name, hint))
|
|
|
|
# Actualizar sugerencias actuales
|
|
self._current_suggestions = all_variables
|
|
|
|
if filtered and self._autocomplete_popup:
|
|
self._autocomplete_popup.set_suggestions(filtered)
|
|
self._autocomplete_popup.adjust_size()
|
|
else:
|
|
self._close_autocomplete_popup()
|
|
|
|
except Exception:
|
|
# Fallback al método anterior
|
|
filtered = []
|
|
for name, hint in self._current_suggestions:
|
|
if name.lower().startswith(filter_text):
|
|
filtered.append((name, hint))
|
|
|
|
if filtered and self._autocomplete_popup:
|
|
self._autocomplete_popup.set_suggestions(filtered)
|
|
self._autocomplete_popup.adjust_size()
|
|
else:
|
|
self._close_autocomplete_popup()
|
|
|
|
# Para popup de métodos (filtrar después del punto)
|
|
elif self._autocomplete_trigger_pos:
|
|
if current_pos >= self._autocomplete_trigger_pos:
|
|
cursor.setPosition(self._autocomplete_trigger_pos)
|
|
cursor.setPosition(current_pos, QTextCursor.KeepAnchor)
|
|
filter_text = cursor.selectedText().lower()
|
|
self._autocomplete_filter_text = filter_text
|
|
else:
|
|
# Si el cursor está antes del punto trigger, cerrar popup
|
|
self._close_autocomplete_popup()
|
|
return
|
|
|
|
# Filtrar sugerencias de métodos
|
|
filtered = []
|
|
for name, hint in self._current_suggestions:
|
|
if name.lower().startswith(self._autocomplete_filter_text):
|
|
filtered.append((name, hint))
|
|
|
|
if filtered and self._autocomplete_popup:
|
|
self._autocomplete_popup.set_suggestions(filtered)
|
|
self._autocomplete_popup.adjust_size()
|
|
else:
|
|
self._close_autocomplete_popup()
|
|
|
|
def _handle_arrow_key(self, direction: int):
|
|
"""Maneja navegación con flechas en el popup"""
|
|
if self._autocomplete_popup:
|
|
self._autocomplete_popup.navigate(direction)
|
|
self._last_navigation_time = time.time()
|
|
|
|
def _handle_tab_key(self):
|
|
"""Maneja tecla TAB para seleccionar"""
|
|
self._select_autocomplete()
|
|
|
|
def _handle_escape_key(self):
|
|
"""Maneja tecla ESC para cerrar popup"""
|
|
self._close_autocomplete_popup()
|
|
if self._autocomplete_active:
|
|
self._popup_disabled_until_next_dot = True
|
|
|
|
def _select_autocomplete(self):
|
|
"""Selecciona el item actual del autocompletado"""
|
|
if not self._autocomplete_popup:
|
|
return
|
|
|
|
selected_text = self._autocomplete_popup.get_selected_text()
|
|
if selected_text:
|
|
self._insert_autocomplete_text(selected_text)
|
|
|
|
self._close_autocomplete_popup()
|
|
|
|
def _on_autocomplete_selected(self, text: str):
|
|
"""Callback cuando se selecciona un item del popup"""
|
|
self._insert_autocomplete_text(text)
|
|
self._close_autocomplete_popup()
|
|
|
|
def _insert_autocomplete_text(self, text: str):
|
|
"""Inserta el texto seleccionado del autocompletado"""
|
|
cursor = self.parent_app.input_text.textCursor()
|
|
|
|
# Para popup de variables
|
|
if self._variable_popup_active:
|
|
# Obtener el texto parcial ya escrito
|
|
current_pos = cursor.position()
|
|
cursor.select(QTextCursor.WordUnderCursor)
|
|
partial_text = cursor.selectedText()
|
|
|
|
# Verificar si hay texto parcial que ya coincide con el inicio de la variable
|
|
if partial_text and text.lower().startswith(partial_text.lower()):
|
|
# Reemplazar completamente la palabra seleccionada con la variable completa
|
|
cursor.insertText(text)
|
|
elif partial_text:
|
|
# Si hay texto parcial pero no coincide, reemplazar completamente
|
|
cursor.insertText(text)
|
|
else:
|
|
# Si no hay texto parcial, insertar directamente
|
|
cursor.insertText(text)
|
|
return
|
|
|
|
# Para popup global (después de punto solo)
|
|
if self._is_global_popup:
|
|
# Eliminar el punto y añadir función/variable
|
|
if self._autocomplete_trigger_pos is not None:
|
|
cursor.setPosition(self._autocomplete_trigger_pos - 1)
|
|
else:
|
|
cursor.setPosition(0)
|
|
if self._autocomplete_trigger_pos is not None:
|
|
cursor.setPosition(self._autocomplete_trigger_pos, QTextCursor.KeepAnchor)
|
|
|
|
# Verificar si es una función o variable para decidir si agregar paréntesis
|
|
try:
|
|
context = self.parent_app.engine._get_full_context()
|
|
if text in context:
|
|
obj = context[text]
|
|
if callable(obj):
|
|
# Es una función, agregar paréntesis
|
|
cursor.insertText(text + "()")
|
|
cursor.setPosition((self._autocomplete_trigger_pos or 0) - 1 + len(text) + 1)
|
|
else:
|
|
# Es una variable, no agregar paréntesis
|
|
cursor.insertText(text)
|
|
cursor.setPosition((self._autocomplete_trigger_pos or 0) - 1 + len(text))
|
|
else:
|
|
# Por defecto, asumir que es función si no está en contexto
|
|
cursor.insertText(text + "()")
|
|
cursor.setPosition((self._autocomplete_trigger_pos or 0) - 1 + len(text) + 1)
|
|
except Exception:
|
|
# Fallback: agregar paréntesis por defecto
|
|
cursor.insertText(text + "()")
|
|
cursor.setPosition((self._autocomplete_trigger_pos or 0) - 1 + len(text) + 1)
|
|
|
|
self.parent_app.input_text.setTextCursor(cursor)
|
|
else:
|
|
# Para métodos de objeto - considerar texto ya escrito después del punto
|
|
if self._autocomplete_filter_text:
|
|
# Calcular la posición correcta del texto ya escrito
|
|
current_pos = cursor.position()
|
|
filter_len = len(self._autocomplete_filter_text)
|
|
|
|
# Seleccionar el texto filtrado para reemplazarlo
|
|
cursor.setPosition(current_pos - filter_len)
|
|
cursor.setPosition(current_pos, QTextCursor.KeepAnchor)
|
|
cursor.removeSelectedText()
|
|
|
|
# Insertar el texto completo del método
|
|
cursor.insertText(text + "()")
|
|
cursor.setPosition(cursor.position() - 1)
|
|
else:
|
|
# Si no hay texto filtrado, insertar normalmente
|
|
cursor.insertText(text + "()")
|
|
cursor.setPosition(cursor.position() - 1)
|
|
|
|
self.parent_app.input_text.setTextCursor(cursor)
|
|
|
|
def _check_dot_removal(self):
|
|
"""Verifica si se borró el punto que activó el autocompletado"""
|
|
if not self._autocomplete_active or not self._autocomplete_trigger_pos:
|
|
return
|
|
|
|
cursor = self.parent_app.input_text.textCursor()
|
|
current_pos = cursor.position()
|
|
|
|
# Si el cursor está antes de la posición del trigger, el punto fue eliminado
|
|
if current_pos < self._autocomplete_trigger_pos:
|
|
self._close_autocomplete_popup()
|
|
return
|
|
|
|
# Verificar si todavía existe el punto en la posición trigger
|
|
if self._autocomplete_trigger_pos > 0:
|
|
cursor.setPosition(self._autocomplete_trigger_pos - 1)
|
|
cursor.setPosition(self._autocomplete_trigger_pos, QTextCursor.KeepAnchor)
|
|
if cursor.selectedText() != '.':
|
|
self._close_autocomplete_popup()
|
|
|
|
def _close_autocomplete_popup(self):
|
|
"""Cierra el popup de autocompletado"""
|
|
if self._autocomplete_popup:
|
|
self._autocomplete_popup.close()
|
|
self._autocomplete_popup = None
|
|
|
|
# Resetear estado
|
|
self._autocomplete_active = False
|
|
self._variable_popup_active = False
|
|
self._autocomplete_trigger_pos = None
|
|
self._autocomplete_filter_text = ""
|
|
self._current_suggestions = []
|
|
|
|
def stop_timers(self):
|
|
"""Detiene todos los timers del autocompletado"""
|
|
self._variable_popup_timer.stop()
|
|
|
|
def reset_state(self):
|
|
"""Resetea el estado del autocompletado"""
|
|
self._close_autocomplete_popup()
|
|
self._popup_disabled_until_next_dot = False
|
|
|
|
def on_input_changed(self):
|
|
"""Método llamado cuando cambia el texto de entrada"""
|
|
# Marcar tiempo del cambio
|
|
self._last_input_change = time.time()
|
|
|
|
# Si hay autocompletado activo, filtrar dinámicamente
|
|
if self._autocomplete_active:
|
|
QTimer.singleShot(10, self._filter_autocomplete)
|
|
elif not self._popup_disabled_until_next_dot:
|
|
# Programar autocompletado de variables
|
|
self._schedule_variable_autocomplete_improved() |