Implementación de un sistema de logging para mejorar la gestión de advertencias y errores en la aplicación. Se establece un límite para la cantidad de archivos de log generados y se optimiza la configuración de la ventana. Se ajusta el manejo de la evaluación y el autocompletado, mejorando la experiencia del usuario.

This commit is contained in:
Miguel 2025-06-04 21:38:40 +02:00
parent 589bab03b2
commit 0c7ed33d0d
6 changed files with 1448 additions and 54 deletions

1262
.doc/OLD_calc.py Normal file

File diff suppressed because it is too large Load Diff

15
calc.py
View File

@ -19,6 +19,7 @@ import platform
def setup_logging(): def setup_logging():
"""Configura el sistema de logging completo""" """Configura el sistema de logging completo"""
MAX_LOG_FILES = 10 # Límite de archivos de log
log_dir = Path("logs") log_dir = Path("logs")
log_dir.mkdir(exist_ok=True) log_dir.mkdir(exist_ok=True)
@ -26,6 +27,20 @@ def setup_logging():
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
log_file = log_dir / f"mav_calc_{timestamp}.log" log_file = log_dir / f"mav_calc_{timestamp}.log"
# Eliminar logs antiguos si se supera el límite
try:
existing_logs = sorted(
[f for f in log_dir.glob("mav_calc_*.log") if f.is_file()],
key=os.path.getmtime
)
if len(existing_logs) >= MAX_LOG_FILES:
logs_to_delete = existing_logs[:len(existing_logs) - MAX_LOG_FILES + 1]
for old_log in logs_to_delete:
old_log.unlink()
logging.info(f"Eliminado log antiguo: {old_log}")
except Exception as e:
logging.warning(f"No se pudieron eliminar logs antiguos: {e}")
# Configurar logging # Configurar logging
logging.basicConfig( logging.basicConfig(
level=logging.DEBUG, level=logging.DEBUG,

View File

@ -1,10 +1,5 @@
a = 10 + b
a=?
x=12
a=x
x=2
b=x
a
b

View File

@ -1,6 +1,6 @@
{ {
"window_geometry": "1020x700+144+161", "window_geometry": "1020x700+467+199",
"sash_pos_x": 355, "sash_pos_x": 363,
"symbolic_mode": true, "symbolic_mode": true,
"show_numeric_approximation": true, "show_numeric_approximation": true,
"keep_symbolic_fractions": true, "keep_symbolic_fractions": true,

View File

@ -6,6 +6,7 @@ import tkinter as tk
from tkinter import scrolledtext, messagebox, Menu, filedialog from tkinter import scrolledtext, messagebox, Menu, filedialog
import tkinter.font as tkFont import tkinter.font as tkFont
import json import json
import logging # <--- AÑADIDO
import os import os
from pathlib import Path from pathlib import Path
import threading import threading
@ -35,10 +36,12 @@ except ImportError:
except ImportError: except ImportError:
HTML_VIEWER_TYPE = None HTML_VIEWER_TYPE = None
# Usar logging para estas advertencias iniciales
module_logger = logging.getLogger(__name__)
if not MARKDOWN_AVAILABLE: if not MARKDOWN_AVAILABLE:
print("Advertencia: La librería 'markdown' no está instalada. La ayuda se mostrará en texto plano.") module_logger.warning("La librería 'markdown' no está instalada. La ayuda se mostrará en texto plano.")
if MARKDOWN_AVAILABLE and HTML_VIEWER_TYPE is None: if MARKDOWN_AVAILABLE and HTML_VIEWER_TYPE is None:
print("Advertencia: 'markdown' está disponible, pero no se encontró un visor HTML (tkinterweb/tkhtmlview). La ayuda se mostrará en texto plano.") module_logger.warning("'markdown' está disponible, pero no se encontró un visor HTML (tkinterweb/tkhtmlview). La ayuda se mostrará en texto plano.")
# ========== IMPORTS ADAPTADOS AL NUEVO SISTEMA ========== # ========== IMPORTS ADAPTADOS AL NUEVO SISTEMA ==========
# Importar componentes del CAS híbrido con nuevo sistema de tipos # Importar componentes del CAS híbrido con nuevo sistema de tipos
@ -58,6 +61,7 @@ class HybridCalculatorApp:
def __init__(self, root: tk.Tk): def __init__(self, root: tk.Tk):
self.root = root self.root = root
self.logger = logging.getLogger(__name__) # <--- AÑADIDO: Logger para la instancia
self.root.title("Calculadora MAV - CAS Híbrido") self.root.title("Calculadora MAV - CAS Híbrido")
# Configuración y estado # Configuración y estado
@ -121,34 +125,35 @@ class HybridCalculatorApp:
# Obtener helpers registrados dinámicamente # Obtener helpers registrados dinámicamente
self.HELPERS = get_registered_helper_functions() self.HELPERS = get_registered_helper_functions()
# Añadir SympyHelper al final # Añadir SympyHelper.Helper al final
self.HELPERS.append(SympyHelper.Helper) self.HELPERS.append(SympyHelper.Helper)
print(f"🆘 Helpers dinámicos cargados: {len(self.HELPERS)}") # Usar logger en lugar de print, y sin emoji para la consola
self.logger.info(f"Helpers dinámicos cargados: {len(self.HELPERS)}") # Original: 🆘
except Exception as e: except Exception as e:
print(f"⚠️ Error cargando helpers dinámicos: {e}") # Usar logger en lugar de print, y sin emoji para la consola
self.logger.error(f"Error cargando helpers dinámicos: {e}", exc_info=True) # Original: ⚠️
# Fallback a helpers básicos # Fallback a helpers básicos
self.HELPERS = [SympyHelper.Helper] self.HELPERS = [SympyHelper.Helper]
def reload_types(self): def reload_types(self):
"""Recarga el sistema de tipos (útil para desarrollo)""" """Recarga el sistema de tipos (útil para desarrollo)"""
try: try:
print("🔄 Recargando sistema de tipos...") self.logger.info("Recargando sistema de tipos...") # Original: 🔄
# Recargar engine # Recargar engine
self.engine.reload_types() self.engine.reload_types()
# Recargar helpers # Recargar helpers
self._setup_dynamic_helpers() self._setup_dynamic_helpers()
# Re-evaluar contenido actual # Re-evaluar contenido actual
self._evaluate_and_update() self._evaluate_and_update()
print("✅ Sistema de tipos recargado") self.logger.info("Sistema de tipos recargado.") # Original: ✅
except Exception as e: except Exception as e:
print(f"Error recargando tipos: {e}") self.logger.error(f"Error recargando tipos: {e}", exc_info=True) # Original: ❌
messagebox.showerror("Error", f"Error recargando tipos:\n{e}") messagebox.showerror("Error", f"Error recargando tipos:\n{e}")
def show_types_info(self): def show_types_info(self):
@ -186,15 +191,15 @@ CLASES DISPONIBLES:
icon_path = script_dir / "icon.png" icon_path = script_dir / "icon.png"
if not icon_path.is_file(): if not icon_path.is_file():
print(f"Advertencia: Archivo de ícono no encontrado en '{icon_path}'.") self.logger.warning(f"Archivo de ícono no encontrado en '{icon_path}'.")
return return
self.app_icon = tk.PhotoImage(file=str(icon_path)) self.app_icon = tk.PhotoImage(file=str(icon_path))
self.root.iconphoto(True, self.app_icon) self.root.iconphoto(True, self.app_icon)
except tk.TclError as e: except tk.TclError as e:
print(f"Advertencia: No se pudo cargar el ícono desde '{icon_path}'. Error de Tkinter: {e}") self.logger.warning(f"No se pudo cargar el ícono desde '{icon_path}'. Error de Tkinter: {e}")
except Exception as e: except Exception as e:
print(f"Advertencia: Ocurrió un error inesperado al cargar el ícono desde '{icon_path}': {e}") self.logger.warning(f"Ocurrió un error inesperado al cargar el ícono desde '{icon_path}': {e}", exc_info=True)
def _load_settings(self) -> Dict[str, Any]: def _load_settings(self) -> Dict[str, Any]:
"""Carga configuración de la aplicación""" """Carga configuración de la aplicación"""
@ -221,7 +226,7 @@ CLASES DISPONIBLES:
json.dump(self.settings, f, indent=4, ensure_ascii=False) json.dump(self.settings, f, indent=4, ensure_ascii=False)
except Exception as e: except Exception as e:
if self.debug: if self.debug:
print(f"Error guardando configuración: {e}") self.logger.error(f"Error guardando configuración: {e}", exc_info=True)
def update_symbolic_settings(self, symbolic_mode=None, show_numeric=None, def update_symbolic_settings(self, symbolic_mode=None, show_numeric=None,
keep_fractions=None, auto_simplify=None): keep_fractions=None, auto_simplify=None):
@ -444,8 +449,8 @@ CLASES DISPONIBLES:
if event and event.char == '.' and self.input_text.focus_get() == self.input_text: if event and event.char == '.' and self.input_text.focus_get() == self.input_text:
self._handle_dot_autocomplete() self._handle_dot_autocomplete()
# Evaluación con debounce # Evaluación con debounce y auto-dimensionado
self._debounce_job = self.root.after(300, self._evaluate_and_update) self._debounce_job = self.root.after(300, self._process_input_and_adjust_layout)
def _handle_dot_autocomplete(self): def _handle_dot_autocomplete(self):
"""Maneja el autocompletado cuando se escribe un punto - VERSIÓN DINÁMICA""" """Maneja el autocompletado cuando se escribe un punto - VERSIÓN DINÁMICA"""
@ -456,7 +461,7 @@ CLASES DISPONIBLES:
char_idx_after_dot = int(char_num_str) char_idx_after_dot = int(char_num_str)
if char_idx_after_dot == 0: if char_idx_after_dot == 0:
print("DEBUG: Autocomplete: Cursor at beginning of line after dot. No action.") self.logger.debug("Autocomplete: Cursor at beginning of line after dot. No action.")
return return
dot_char_index_in_line = char_idx_after_dot - 1 dot_char_index_in_line = char_idx_after_dot - 1
@ -466,7 +471,7 @@ CLASES DISPONIBLES:
# 1. Determinar si es un popup GLOBAL (usando contexto dinámico) # 1. Determinar si es un popup GLOBAL (usando contexto dinámico)
if not stripped_text_before_dot: if not stripped_text_before_dot:
print("DEBUG: Dot on empty line or after spaces. Offering global suggestions.") self.logger.debug("Dot on empty line or after spaces. Offering global suggestions.")
suggestions = [] suggestions = []
# ========== USAR CONTEXTO DINÁMICO DEL REGISTRO ========== # ========== USAR CONTEXTO DINÁMICO DEL REGISTRO ==========
@ -485,12 +490,12 @@ CLASES DISPONIBLES:
if helper_text: if helper_text:
hint = helper_text.split('\n')[0] hint = helper_text.split('\n')[0]
except Exception as e_helper: except Exception as e_helper:
print(f"DEBUG: Error calling Helper for {name}: {e_helper}") self.logger.debug(f"Error calling Helper for {name}: {e_helper}")
pass pass
suggestions.append((name, hint)) suggestions.append((name, hint))
except Exception as e: except Exception as e:
print(f"DEBUG: Error obteniendo contexto dinámico: {e}") self.logger.debug(f"Error obteniendo contexto dinámico: {e}")
# Fallback básico # Fallback básico
suggestions = [("sin", "Función seno"), ("cos", "Función coseno")] suggestions = [("sin", "Función seno"), ("cos", "Función coseno")]
@ -503,7 +508,7 @@ CLASES DISPONIBLES:
if fname not in current_suggestion_names: if fname not in current_suggestion_names:
suggestions.append((fname, fhint)) suggestions.append((fname, fhint))
except Exception as e: except Exception as e:
print(f"DEBUG: Error calling SympyHelper.PopupFunctionList() for global: {e}") self.logger.debug(f"Error calling SympyHelper.PopupFunctionList() for global: {e}")
if suggestions: if suggestions:
suggestions.sort(key=lambda x: x[0]) suggestions.sort(key=lambda x: x[0])
@ -522,48 +527,48 @@ CLASES DISPONIBLES:
if not obj_expr_str_candidate or \ if not obj_expr_str_candidate or \
not re.match(r"^[a-zA-Z_0-9\(\)\[\]\.\"\'\+\-\*\/ ]*$", obj_expr_str_candidate) or \ not re.match(r"^[a-zA-Z_0-9\(\)\[\]\.\"\'\+\-\*\/ ]*$", obj_expr_str_candidate) or \
obj_expr_str_candidate.endswith(("+", "-", "*", "/", "(", ",")): obj_expr_str_candidate.endswith(("+", "-", "*", "/", "(", ",")):
print(f"DEBUG: Extracted expr '{obj_expr_str_candidate}' from '{stripped_text_before_dot}' not a valid object for dot autocomplete.") self.logger.debug(f"Extracted expr '{obj_expr_str_candidate}' from '{stripped_text_before_dot}' not a valid object for dot autocomplete.")
return return
obj_expr_str = obj_expr_str_candidate obj_expr_str = obj_expr_str_candidate
print(f"DEBUG: Autocomplete for object. Extracted: '{obj_expr_str}' from: '{text_on_line_up_to_dot}'") self.logger.debug(f"Autocomplete for object. Extracted: '{obj_expr_str}' from: '{text_on_line_up_to_dot}'")
if not obj_expr_str: if not obj_expr_str:
print("DEBUG: Object expression is empty after extraction. No autocomplete.") self.logger.debug("Object expression is empty after extraction. No autocomplete.")
return return
# 3. Caso especial para el módulo sympy # 3. Caso especial para el módulo sympy
if obj_expr_str == "sympy": if obj_expr_str == "sympy":
print(f"DEBUG: Detected 'sympy.', using SympyHelper for suggestions.") self.logger.debug(f"Detected 'sympy.', using SympyHelper for suggestions.")
try: try:
methods = SympyHelper.PopupFunctionList() methods = SympyHelper.PopupFunctionList()
if methods: if methods:
self._show_autocomplete_popup(methods, is_global_popup=False) self._show_autocomplete_popup(methods, is_global_popup=False)
else: else:
print(f"DEBUG: SympyHelper.PopupFunctionList returned no methods.") self.logger.debug(f"SympyHelper.PopupFunctionList returned no methods.")
except Exception as e: except Exception as e:
print(f"DEBUG: Error calling SympyHelper.PopupFunctionList(): {e}") self.logger.debug(f"Error calling SympyHelper.PopupFunctionList(): {e}")
return return
# 4. Preprocesar con BracketParser # 4. Preprocesar con BracketParser
if '[' in obj_expr_str: if '[' in obj_expr_str:
original_for_debug = obj_expr_str original_for_debug = obj_expr_str
obj_expr_str = self.engine.parser._transform_brackets(obj_expr_str) obj_expr_str = self.engine.parser._transform_brackets(obj_expr_str)
if obj_expr_str != original_for_debug: if obj_expr_str != original_for_debug and self.debug: # Solo loguear si self.debug es True
print(f"DEBUG: Preprocessed by BracketParser: '{original_for_debug}' -> '{obj_expr_str}'") self.logger.debug(f"Preprocessed by BracketParser: '{original_for_debug}' -> '{obj_expr_str}'")
# 5. Evaluar la expresión del objeto (usando contexto dinámico) # 5. Evaluar la expresión del objeto (usando contexto dinámico)
eval_context = self.engine._get_full_context() eval_context = self.engine._get_full_context()
obj = None obj = None
try: try:
if not obj_expr_str.strip(): if not obj_expr_str.strip():
print("DEBUG: Object expression became empty before eval. No action.") self.logger.debug("Object expression became empty before eval. No action.")
return return
print(f"DEBUG: Attempting to eval: '{obj_expr_str}'") self.logger.debug(f"Attempting to eval: '{obj_expr_str}'")
obj = eval(obj_expr_str, eval_context) obj = eval(obj_expr_str, eval_context)
print(f"DEBUG: Eval successful. Object: {type(obj)}, Value: {obj}") self.logger.debug(f"Eval successful. Object: {type(obj)}, Value: {obj}")
except Exception as e: except Exception as e:
print(f"DEBUG: Error evaluating object expression '{obj_expr_str}': {e}") self.logger.debug(f"Error evaluating object expression '{obj_expr_str}': {e}")
return return
# 6. Mostrar popup de autocompletado para el objeto # 6. Mostrar popup de autocompletado para el objeto
@ -793,7 +798,7 @@ CLASES DISPONIBLES:
except Exception as e: except Exception as e:
if self.debug: if self.debug:
print(f"DEBUG: Error en get_result_tag_dynamic: {e}") self.logger.debug(f"Error en get_result_tag_dynamic: {e}")
# Fallback a tags existentes para tipos no registrados # Fallback a tags existentes para tipos no registrados
if isinstance(result, sympy.Basic): if isinstance(result, sympy.Basic):
@ -813,7 +818,7 @@ CLASES DISPONIBLES:
except Exception as e: except Exception as e:
if self.debug: if self.debug:
print(f"DEBUG: Error en get_class_display_name_dynamic: {e}") self.logger.debug(f"Error en get_class_display_name_dynamic: {e}")
# Fallback a lógica existente para tipos nativos # Fallback a lógica existente para tipos nativos
if isinstance(obj, sympy.logic.boolalg.BooleanAtom): if isinstance(obj, sympy.logic.boolalg.BooleanAtom):
@ -933,7 +938,7 @@ CLASES DISPONIBLES:
self.input_text.delete("1.0", tk.END) self.input_text.delete("1.0", tk.END)
self.input_text.insert("1.0", content) self.input_text.insert("1.0", content)
self._evaluate_and_update() self._process_input_and_adjust_layout()
except Exception as e: except Exception as e:
messagebox.showerror("Error", f"No se pudo cargar el archivo:\n{e}") messagebox.showerror("Error", f"No se pudo cargar el archivo:\n{e}")
@ -1203,9 +1208,9 @@ programación y análisis numérico.
self.input_text.insert("1.0", content) self.input_text.insert("1.0", content)
# Hacer evaluación inicial para mostrar resultados del historial # Hacer evaluación inicial para mostrar resultados del historial
# Esto mantiene el comportamiento de contexto limpio pero muestra resultados # Esto mantiene el comportamiento de contexto limpio pero muestra resultados
self.root.after_idle(self._evaluate_and_update) self.root.after_idle(self._process_input_and_adjust_layout)
except Exception as e: except Exception as e:
print(f"Error cargando historial: {e}") self.logger.error(f"Error cargando historial: {e}", exc_info=True)
def save_history(self): def save_history(self):
"""Guarda historial de entrada""" """Guarda historial de entrada"""
@ -1217,7 +1222,7 @@ programación y análisis numérico.
elif os.path.exists(self.HISTORY_FILE): elif os.path.exists(self.HISTORY_FILE):
os.remove(self.HISTORY_FILE) os.remove(self.HISTORY_FILE)
except Exception as e: except Exception as e:
print(f"Error guardando historial: {e}") self.logger.error(f"Error guardando historial: {e}", exc_info=True)
def on_close(self): def on_close(self):
"""Maneja cierre de la aplicación""" """Maneja cierre de la aplicación"""
@ -1336,7 +1341,7 @@ programación y análisis numérico.
html_viewer.pack(padx=0, pady=0, fill=tk.BOTH, expand=True) html_viewer.pack(padx=0, pady=0, fill=tk.BOTH, expand=True)
except Exception as e: except Exception as e:
print(f"Error al renderizar Markdown a HTML: {e}") self.logger.error(f"Error al renderizar Markdown a HTML: {e}", exc_info=True)
# Fallback to text if HTML fails # Fallback to text if HTML fails
self._show_text_help(help_win, readme_content) self._show_text_help(help_win, readme_content)
else: else:
@ -1419,7 +1424,7 @@ Para crear un archivo de ayuda personalizado, cree un archivo `readme.md` en el
if ayuda: if ayuda:
return ayuda return ayuda
except Exception as e: except Exception as e:
print(f"DEBUG: Error en helper: {e}") self.logger.debug(f"Error en helper: {e}")
continue continue
return None return None
@ -1460,6 +1465,123 @@ Para crear un archivo de ayuda personalizado, cree un archivo `readme.md` en el
return f"{mode}{numeric_indicator}{fractions_indicator}" return f"{mode}{numeric_indicator}{fractions_indicator}"
def _get_input_font(self):
"""Obtiene o crea y cachea el objeto tk.Font para el panel de entrada."""
if not self._cached_input_font:
# Asume la fuente configurada en create_widgets: ("Consolas", 11)
self._cached_input_font = tkFont.Font(family="Consolas", size=11)
return self._cached_input_font
def _adjust_input_pane_width(self):
"""Ajusta el ancho del panel de entrada según su contenido."""
if not hasattr(self, 'paned_window') or not self.paned_window.winfo_exists():
return
# Esperar a que la ventana tenga un tamaño válido
if self.paned_window.winfo_width() <= 1:
return # Se reintentará en la siguiente llamada (ej. por KeyRelease)
# Obtener contenido excluyendo el último newline automático del widget Text
input_content = self.input_text.get("1.0", f"{tk.END}-1c")
lines = input_content.splitlines()
input_font = self._get_input_font()
max_pixel_width = 0
if not input_content.strip(): # Si está vacío o solo espacios en blanco
max_pixel_width = 5 # Ancho mínimo para el cursor o como placeholder
else:
for line in lines:
measured_width = input_font.measure(line) if line.strip() else input_font.measure(" ")
if measured_width > max_pixel_width:
max_pixel_width = measured_width
padding = 40 # Relleno para barra de desplazamiento, márgenes, etc.
width_needed_by_text = max_pixel_width + padding
# Debugging opcional (descomenta si necesitas depurar)
if self.debug:
self.logger.debug(f"--- Adjusting Input Pane ---")
self.logger.debug(f"Input content: '{input_content[:50]}...'")
self.logger.debug(f"Max pixel width of text: {max_pixel_width}")
self.logger.debug(f"Width needed by text (max_pixel_width + padding): {width_needed_by_text}")
min_input_pane_width = 200 # Definido en create_widgets
min_output_pane_width = 200 # Definido en create_widgets
total_width = self.paned_window.winfo_width()
current_sash_pos = 0
try:
sash_coords = self.paned_window.sash_coord(0)
if sash_coords:
current_sash_pos = sash_coords[0]
else:
if self.debug:
self.logger.debug("Could not get sash_coord.")
return
except tk.TclError:
if self.debug:
self.logger.debug("TclError getting sash_coord.")
return
if self.debug:
self.logger.debug(f"Current sash position (input pane width): {current_sash_pos}")
if width_needed_by_text > current_sash_pos:
if self.debug:
self.logger.debug(f"Condition MET: Text needs more space ({width_needed_by_text} > {current_sash_pos})")
new_input_width = width_needed_by_text # Punto de partida
# Asegurar que el nuevo ancho no sea menor que el mínimo del panel de entrada
new_input_width = max(new_input_width, min_input_pane_width)
# Asegurar que el panel de salida conserve su espacio mínimo
if total_width - new_input_width < min_output_pane_width:
new_input_width = total_width - min_output_pane_width
new_input_width = max(new_input_width, min_input_pane_width) # Re-verificar mínimo del input
# Aplicar un ratio máximo para el panel de entrada
max_input_ratio = 0.75 # Podría ser una constante de clase
max_width_by_ratio = int(total_width * max_input_ratio)
if new_input_width > max_width_by_ratio:
if max_width_by_ratio >= min_input_pane_width and \
(total_width - max_width_by_ratio) >= min_output_pane_width:
new_input_width = max_width_by_ratio
final_new_input_width = max(0, int(new_input_width)) # No debe ser negativo
if self.debug:
self.logger.debug(f"Calculated final new input width: {final_new_input_width}")
# Mover el sash solo si el nuevo ancho es significativamente mayor que el actual (umbral ajustado)
sash_adjustment_threshold = 3 # Píxeles
if final_new_input_width > current_sash_pos and \
(final_new_input_width - current_sash_pos) >= sash_adjustment_threshold:
if self.debug:
self.logger.debug(f"Condition MET for sash_place: New width {final_new_input_width} is significantly larger (diff >= {sash_adjustment_threshold}).")
try:
if self.paned_window.winfo_exists() and total_width >= (min_input_pane_width + min_output_pane_width):
self.paned_window.sash_place(0, final_new_input_width, 0) # Añadido el argumento y=0
if self.debug:
self.logger.debug(f"Sash placed at: {final_new_input_width}")
elif self.debug:
self.logger.debug("Paned window not ready or total width too small for sash_place.")
except tk.TclError as e_sash:
if self.debug:
self.logger.debug(f"TclError during sash_place: {e_sash}")
pass
elif self.debug:
self.logger.debug(f"Condition NOT MET for sash_place: final_new_input_width ({final_new_input_width}) vs current_sash_pos ({current_sash_pos}) or threshold ({sash_adjustment_threshold}).")
elif self.debug:
self.logger.debug(f"Condition NOT MET: Text does not need more space ({width_needed_by_text} <= {current_sash_pos})")
if self.debug:
self.logger.debug(f"--- End Adjusting Input Pane ---")
def _process_input_and_adjust_layout(self):
"""Evalúa todas las líneas y luego ajusta el ancho del panel de entrada."""
self._evaluate_and_update()
self._adjust_input_pane_width()
def main(): def main():
"""Función principal""" """Función principal"""

View File

@ -343,10 +343,10 @@ class HybridEvaluationEngine:
try: try:
numeric_eval = result.evalf() numeric_eval = result.evalf()
# Solo mostrar evaluación numérica si es diferente del resultado simbólico # Solo mostrar evaluación numérica si es diferente del resultado simbólico
if numeric_eval != result and not ( if (str(numeric_eval) != str(result) and numeric_eval != result and
hasattr(result, 'is_number') and result.is_number and not (isinstance(result, (int, float)) or
abs(float(numeric_eval) - float(result)) < 1e-15 (hasattr(result, 'is_number') and result.is_number and
): hasattr(result, 'is_Integer') and result.is_Integer))):
numeric_result = numeric_eval numeric_result = numeric_eval
except: except:
pass pass