""" Sistema de Evaluación y Procesamiento para la Calculadora MAV CAS Híbrida """ import logging from typing import List, Dict, Any, Optional, Tuple from PySide6.QtGui import QTextCursor, QTextCharFormat, QColor, QFont from .evaluation import EvaluationResult from .gui_latex import LatexProcessor import sympy class EvaluationManager: """Gestor del sistema de evaluación y procesamiento de resultados""" def __init__(self, main_window): self.main_window = main_window self.logger = logging.getLogger(__name__) self._latex_equations = [] # Timer para debounce de evaluación from PySide6.QtCore import QTimer self._debounce_timer = QTimer() self._debounce_timer.setSingleShot(True) self._debounce_timer.timeout.connect(self.evaluate_and_update) # Configurar formatos de salida self._setup_output_formats() def _setup_output_formats(self): """Configura los formatos de texto para la salida""" self.output_formats = { 'error': self._create_format("#f44747", bold=True), 'comment': self._create_format("#6a9955", italic=True), 'assignment': self._create_format("#dcdcaa"), 'equation': self._create_format("#c586c0"), 'symbolic': self._create_format("#9cdcfe"), 'numeric': self._create_format("#b5cea8"), 'boolean': self._create_format("#569cd6"), 'string': self._create_format("#ce9178"), 'custom_type': self._create_format("#4ec9b0"), 'plot': self._create_format("#569cd6", underline=True), 'type_indicator': self._create_format("#808080"), 'clickable': self._create_format("#4fc3f7", underline=True), 'helper': self._create_format("#ffd700", italic=True) } def _create_format(self, color: str, bold: bool = False, italic: bool = False, underline: bool = False) -> QTextCharFormat: """Crea un formato de texto""" fmt = QTextCharFormat() fmt.setForeground(QColor(color)) if bold: fmt.setFontWeight(QFont.Bold) if italic: fmt.setFontItalic(True) if underline: fmt.setFontUnderline(True) return fmt def evaluate_and_update(self): """Evalúa todas las líneas y actualiza la salida""" try: input_content = self.main_window.input_text.toPlainText() if not input_content.strip(): self._clear_output() return # Limpiar contexto del motor self.main_window.engine.equations.clear() self.main_window.engine.symbol_table.clear() self.main_window.engine.variables.clear() self.logger.debug("Contexto del motor limpiado") # Limpiar ecuaciones LaTeX en memoria self._latex_equations.clear() # Solo limpiar panel visual si está visible if self.main_window.latex_panel_visible: self.main_window.latex_panel.clear_equations() lines = input_content.splitlines() self._evaluate_lines(lines) except Exception as e: self._show_error(f"Error durante evaluación: {e}") def _evaluate_lines(self, lines: List[str]): """Evalúa múltiples líneas de código""" output_data = [] for line_num, line in enumerate(lines, 1): line_stripped = line.strip() # Líneas vacías o comentarios if not line_stripped or line_stripped.startswith('#'): if line_stripped: output_data.append([("comment", line_stripped)]) # Añadir comentario al panel LaTeX if line_stripped.startswith('#'): comment_text = line_stripped[1:].strip() self._add_to_latex_panel("comment", comment_text) else: output_data.append([("", "")]) continue # Evaluar línea result = self.main_window.engine.evaluate_line(line_stripped) line_output = self._process_evaluation_result(result) output_data.append(line_output) self._display_output(output_data) def _process_evaluation_result(self, result: EvaluationResult) -> List[tuple]: """Procesa el resultado de evaluación para display""" output_parts = [] indicator_text: Optional[str] = None # Añadir al panel LaTeX si es aplicable self._add_to_latex_panel_if_applicable(result) if result.result_type == "comment": output_parts.append(("comment", result.output if result.output is not None else "")) return output_parts if not result.success: # Manejo de errores con ayuda contextual error_msg = f"Error: {result.error_message}" # Intentar obtener ayuda ayuda_text = self._obtener_ayuda(result.input_line) if ayuda_text: ayuda_linea = ayuda_text.replace("\n", " ").strip() if len(ayuda_linea) > 120: ayuda_linea = ayuda_linea[:117] + "..." output_parts.append(("error", error_msg)) output_parts.append(("\n", "\n")) output_parts.append(("helper", f"Sugerencia: {ayuda_linea}")) else: output_parts.append(("error", error_msg)) else: # Intentar crear tag interactivo if self.main_window.interactive_manager: interactive_info = self.main_window.interactive_manager.create_interactive_link( result.actual_result_object ) if interactive_info: link_id, display_text, result_object = interactive_info output_parts.append(("clickable", display_text, link_id, result_object)) # Añadir indicador de tipo algebraico if result.algebraic_type: indicator_text = f"[{result.algebraic_type}]" output_parts.append((" ", " ")) output_parts.append(("type_indicator", indicator_text)) return output_parts # Si no es interactivo, usar formato normal main_output_tag = "base" if result.is_assignment: main_output_tag = "assignment" indicator_text = "[=]" elif result.is_equation: main_output_tag = "equation" indicator_text = "[eq]" elif result.result_type == "plot": main_output_tag = "plot" else: # Determinar tag según tipo algebraico if result.algebraic_type: type_lower = result.algebraic_type.lower() if isinstance(result.actual_result_object, sympy.Basic): main_output_tag = "symbolic" elif type_lower in ["int", "float", "complex"]: main_output_tag = "numeric" elif type_lower == "bool": main_output_tag = "boolean" elif type_lower == "str": main_output_tag = "string" else: main_output_tag = "custom_type" if result.algebraic_type: is_collection = any(kw in result.algebraic_type.lower() for kw in ["matrix", "list", "dict", "tuple", "vector", "array"]) if is_collection or isinstance(result.actual_result_object, sympy.Basic): indicator_text = f"[{result.algebraic_type}]" output_parts.append((main_output_tag, result.output if result.output is not None else "")) if indicator_text: output_parts.append((" ", " ")) output_parts.append(("type_indicator", indicator_text)) return output_parts def _add_to_latex_panel_if_applicable(self, result: EvaluationResult): """Agrega resultado al panel LaTeX si es aplicable""" try: should_add, equation_type = LatexProcessor.should_add_to_latex(result) if should_add: # Generar contenido LaTeX que incluya toda la información del output latex_content = LatexProcessor.generate_complete_latex_content(result) self._add_to_latex_panel(equation_type, latex_content) except Exception as e: self.logger.error(f"Error procesando para panel LaTeX: {e}") def _add_to_latex_panel(self, equation_type: str, latex_content: str): """Añade una ecuación al panel LaTeX""" self._latex_equations.append({ 'type': equation_type, 'content': latex_content }) # Solo actualizar el panel si está visible/activo if self.main_window.latex_panel_visible: self.main_window.latex_panel.add_equation(equation_type, latex_content) # Actualizar indicador visual self._update_latex_indicator() def _update_latex_indicator(self): """Actualiza el indicador visual de contenido LaTeX""" equation_count = len(self._latex_equations) if equation_count > 0: self.main_window.latex_button.setToolTip(f"📐 Panel LaTeX ({equation_count} ecuaciones)") else: self.main_window.latex_button.setToolTip("📐 Panel LaTeX (sin ecuaciones)") def _display_output(self, output_data: List[List[tuple]]): """Muestra los datos de salida en el widget""" self.main_window.output_text.clear() self.main_window.output_text.clickable_links.clear() cursor = self.main_window.output_text.textCursor() for line_idx, line_parts in enumerate(output_data): if line_idx > 0: cursor.insertText("\n") if not line_parts or (len(line_parts) == 1 and line_parts[0][0] == "" and line_parts[0][1] == ""): continue for part_idx, part_data in enumerate(line_parts): if len(part_data) >= 4 and part_data[0] == "clickable": # Link clickeable _, display_text, link_id, result_object = part_data start_pos = cursor.position() cursor.insertText(display_text, self.output_formats.get('clickable')) end_pos = cursor.position() self.main_window.output_text.clickable_links[(start_pos, end_pos)] = (link_id, result_object) elif len(part_data) >= 2: tag, content = part_data[0], part_data[1] if not content: continue if part_idx > 0: prev_tag = line_parts[part_idx-1][0] if part_idx > 0 else None if tag not in ["type_indicator"] and prev_tag: cursor.insertText(" ; ") elif tag == "type_indicator" and prev_tag: cursor.insertText(" ") format_obj = self.output_formats.get(tag, None) if format_obj: cursor.insertText(str(content), format_obj) else: cursor.insertText(str(content)) def _clear_output(self): """Limpia el panel de salida""" self.main_window.output_text.clear() def _show_error(self, error_msg: str): """Muestra un error en el panel de salida""" self.main_window.output_text.clear() cursor = self.main_window.output_text.textCursor() cursor.insertText(error_msg, self.output_formats['error']) # Intentar obtener ayuda para el error try: input_content = self.main_window.input_text.toPlainText() last_line = input_content.strip().split('\n')[-1] if input_content.strip() else "" if last_line: ayuda = self._obtener_ayuda(last_line) if ayuda: cursor.insertText("\n\n💡 Ayuda:\n", self.output_formats['helper']) cursor.insertText(ayuda, self.output_formats['helper']) except Exception as e: self.logger.debug(f"Error obteniendo ayuda: {e}") def _obtener_ayuda(self, input_str: str) -> Optional[str]: """Obtiene ayuda usando helpers dinámicos""" for helper in self.main_window.HELPERS: try: ayuda = helper(input_str) if ayuda: return ayuda except Exception as e: self.logger.debug(f"Error en helper: {e}") return None def trigger_initial_latex_render(self): """Activa el renderizado inicial del panel LaTeX cuando MathJax está listo""" try: # Solo hacer evaluación inicial si hay contenido en el input input_content = self.main_window.input_text.toPlainText() if input_content.strip(): logging.debug("🎯 Activando renderizado inicial de LaTeX") self.evaluate_and_update() except Exception as e: logging.error(f"Error en renderizado inicial de LaTeX: {e}") def clear_latex_equations(self): """Limpia las ecuaciones LaTeX""" self._latex_equations.clear() self._update_latex_indicator() def get_latex_equations(self): """Obtiene las ecuaciones LaTeX actuales""" return self._latex_equations.copy() if self._latex_equations else [] def schedule_evaluation(self): """Programa una evaluación con debounce""" self._debounce_timer.stop() self._debounce_timer.start(300) # 300ms de debounce