Primera version de Autocompletado
This commit is contained in:
parent
261b20df5c
commit
e44cc3af8f
|
@ -360,7 +360,7 @@ def Helper(input_str):
|
||||||
### 3. **Manejo Centralizado de Helpers**
|
### 3. **Manejo Centralizado de Helpers**
|
||||||
|
|
||||||
En el motor principal de la aplicación, se debe mantener una lista de todas las funciones Helper disponibles (incluyendo la de SymPy).
|
En el motor principal de la aplicación, se debe mantener una lista de todas las funciones Helper disponibles (incluyendo la de SymPy).
|
||||||
Al evaluar la línea de entrada:
|
Al evaluar la línea de entrada y esta da error de evaluacion entonces:
|
||||||
|
|
||||||
- Se llama a cada Helper en orden.
|
- Se llama a cada Helper en orden.
|
||||||
- Si alguna Helper retorna una ayuda, se muestra esa ayuda al usuario (en la línea de resultado, tooltip, etc.).
|
- Si alguna Helper retorna una ayuda, se muestra esa ayuda al usuario (en la línea de resultado, tooltip, etc.).
|
||||||
|
@ -388,7 +388,7 @@ def obtener_ayuda(input_str):
|
||||||
### 4. **Autocompletado de Métodos y Funciones (Popup tras el punto)**
|
### 4. **Autocompletado de Métodos y Funciones (Popup tras el punto)**
|
||||||
|
|
||||||
- Cuando el usuario escribe un punto (`.`) después de un objeto válido, se evalúa el objeto y se obtiene la lista de métodos disponibles.
|
- Cuando el usuario escribe un punto (`.`) después de un objeto válido, se evalúa el objeto y se obtiene la lista de métodos disponibles.
|
||||||
- Se muestra un popup de autocompletado con los métodos relevantes (filtrando los no útiles).
|
- Se muestra un popup de autocompletado con los métodos relevantes (filtrando los no útiles). La lista de funciones se debe obtener de una funcione de cada objeto llamada PopupFunctionList() esta funcion en cada objeto mantendra la lista de las funciones disponibles y una explicacion corta tipo hint. Esta funcion retorna una lista de tuplas con el nombre de la funcion y el hint.
|
||||||
- El usuario puede seleccionar un método con el teclado o mouse, y se inserta automáticamente (con paréntesis si corresponde).
|
- El usuario puede seleccionar un método con el teclado o mouse, y se inserta automáticamente (con paréntesis si corresponde).
|
||||||
- El popup solo aparece tras el punto, no en cada pulsación de tecla, para no ser invasivo.
|
- El popup solo aparece tras el punto, no en cada pulsación de tecla, para no ser invasivo.
|
||||||
|
|
||||||
|
|
17
bin_type.py
17
bin_type.py
|
@ -2,6 +2,7 @@
|
||||||
Clase híbrida para números binarios
|
Clase híbrida para números binarios
|
||||||
"""
|
"""
|
||||||
from hybrid_base import HybridCalcType
|
from hybrid_base import HybridCalcType
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
class HybridBin(HybridCalcType):
|
class HybridBin(HybridCalcType):
|
||||||
|
@ -38,14 +39,16 @@ class HybridBin(HybridCalcType):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def Helper(input_str):
|
def Helper(input_str):
|
||||||
"""Ayuda contextual para Bin"""
|
"""Ayuda contextual para Bin"""
|
||||||
return """
|
if re.match(r"^\s*Bin\b", input_str, re.IGNORECASE):
|
||||||
Formato Bin:
|
return 'Ej: Bin[1010], Bin[10]\nFunciones: toDecimal()'
|
||||||
- Con prefijo: 0b1010
|
return None
|
||||||
- Sin prefijo: 1010
|
|
||||||
|
|
||||||
Conversiones:
|
@staticmethod
|
||||||
- toDecimal(): Convierte a decimal
|
def PopupFunctionList():
|
||||||
"""
|
"""Lista de métodos sugeridos para autocompletado de Bin"""
|
||||||
|
return [
|
||||||
|
("toDecimal", "Convierte a decimal"),
|
||||||
|
]
|
||||||
|
|
||||||
def toDecimal(self):
|
def toDecimal(self):
|
||||||
"""Convierte a decimal"""
|
"""Convierte a decimal"""
|
||||||
|
|
21
chr_type.py
21
chr_type.py
|
@ -2,6 +2,7 @@
|
||||||
Clase híbrida para caracteres
|
Clase híbrida para caracteres
|
||||||
"""
|
"""
|
||||||
from hybrid_base import HybridCalcType
|
from hybrid_base import HybridCalcType
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
class HybridChr(HybridCalcType):
|
class HybridChr(HybridCalcType):
|
||||||
|
@ -39,16 +40,18 @@ class HybridChr(HybridCalcType):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def Helper(input_str):
|
def Helper(input_str):
|
||||||
"""Ayuda contextual para Chr"""
|
"""Ayuda contextual para Chr"""
|
||||||
return """
|
if re.match(r"^\s*Chr\b", input_str, re.IGNORECASE):
|
||||||
Formato Chr:
|
return "Ej: Chr[A], Chr[65]\nFunciones: toDecimal(), toHex(), toBin()"
|
||||||
- Carácter: 'A'
|
return None
|
||||||
- Código ASCII: 65
|
|
||||||
|
|
||||||
Conversiones:
|
@staticmethod
|
||||||
- toDecimal(): Obtiene código ASCII
|
def PopupFunctionList():
|
||||||
- toHex(): Convierte a hexadecimal
|
"""Lista de métodos sugeridos para autocompletado de Chr"""
|
||||||
- toBin(): Convierte a binario
|
return [
|
||||||
"""
|
("toDecimal", "Obtiene código ASCII"),
|
||||||
|
("toHex", "Convierte a hexadecimal"),
|
||||||
|
("toBin", "Convierte a binario"),
|
||||||
|
]
|
||||||
|
|
||||||
def toDecimal(self):
|
def toDecimal(self):
|
||||||
"""Obtiene código ASCII"""
|
"""Obtiene código ASCII"""
|
||||||
|
|
18
dec_type.py
18
dec_type.py
|
@ -2,6 +2,7 @@
|
||||||
Clase híbrida para números decimales
|
Clase híbrida para números decimales
|
||||||
"""
|
"""
|
||||||
from hybrid_base import HybridCalcType
|
from hybrid_base import HybridCalcType
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
class HybridDec(HybridCalcType):
|
class HybridDec(HybridCalcType):
|
||||||
|
@ -34,14 +35,17 @@ class HybridDec(HybridCalcType):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def Helper(input_str):
|
def Helper(input_str):
|
||||||
"""Ayuda contextual para Dec"""
|
"""Ayuda contextual para Dec"""
|
||||||
return """
|
if re.match(r"^\s*Dec\b", input_str, re.IGNORECASE):
|
||||||
Formato Dec:
|
return 'Ej: Dec[42], Dec[100]\nFunciones: toHex(), toBin()'
|
||||||
- Número entero: 42
|
return None
|
||||||
|
|
||||||
Conversiones:
|
@staticmethod
|
||||||
- toHex(): Convierte a hexadecimal
|
def PopupFunctionList():
|
||||||
- toBin(): Convierte a binario
|
"""Lista de métodos sugeridos para autocompletado de Dec"""
|
||||||
"""
|
return [
|
||||||
|
("toHex", "Convierte a hexadecimal"),
|
||||||
|
("toBin", "Convierte a binario"),
|
||||||
|
]
|
||||||
|
|
||||||
def toHex(self):
|
def toHex(self):
|
||||||
"""Convierte a hexadecimal"""
|
"""Convierte a hexadecimal"""
|
||||||
|
|
17
hex_type.py
17
hex_type.py
|
@ -2,6 +2,7 @@
|
||||||
Clase híbrida para números hexadecimales
|
Clase híbrida para números hexadecimales
|
||||||
"""
|
"""
|
||||||
from hybrid_base import HybridCalcType
|
from hybrid_base import HybridCalcType
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
class HybridHex(HybridCalcType):
|
class HybridHex(HybridCalcType):
|
||||||
|
@ -38,14 +39,16 @@ class HybridHex(HybridCalcType):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def Helper(input_str):
|
def Helper(input_str):
|
||||||
"""Ayuda contextual para Hex"""
|
"""Ayuda contextual para Hex"""
|
||||||
return """
|
if re.match(r"^\s*Hex\b", input_str, re.IGNORECASE):
|
||||||
Formato Hex:
|
return 'Ej: Hex[FF], Hex[255]\nFunciones: toDecimal()'
|
||||||
- Con prefijo: 0x1A
|
return None
|
||||||
- Sin prefijo: 1A
|
|
||||||
|
|
||||||
Conversiones:
|
@staticmethod
|
||||||
- toDecimal(): Convierte a decimal
|
def PopupFunctionList():
|
||||||
"""
|
"""Lista de métodos sugeridos para autocompletado de Hex"""
|
||||||
|
return [
|
||||||
|
("toDecimal", "Convierte a decimal"),
|
||||||
|
]
|
||||||
|
|
||||||
def toDecimal(self):
|
def toDecimal(self):
|
||||||
"""Convierte a decimal"""
|
"""Convierte a decimal"""
|
||||||
|
|
|
@ -3,3 +3,7 @@ ip=IP4[192.168.1.100/24]
|
||||||
|
|
||||||
|
|
||||||
500/25
|
500/25
|
||||||
|
|
||||||
|
Dec[Hex[ff]]
|
||||||
|
|
||||||
|
Hex[ff]
|
21
ip4_type.py
21
ip4_type.py
|
@ -115,17 +115,18 @@ class HybridIP4(HybridCalcType):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def Helper(input_str):
|
def Helper(input_str):
|
||||||
"""Ayuda contextual para IP4"""
|
"""Ayuda contextual para IP4"""
|
||||||
return """
|
if re.match(r"^\s*IP4\b", input_str, re.IGNORECASE):
|
||||||
Formato IP4:
|
return 'Ej: IP4[192.168.1.1/24], IP4[10.0.0.1, 8], o IP4[172.16.0.5, 255.255.0.0]\nFunciones: NetworkAddress(), BroadcastAddress(), Nodes()'
|
||||||
- CIDR: 192.168.1.1/24
|
return None
|
||||||
- Con máscara: 192.168.1.1 255.255.255.0
|
|
||||||
- Solo IP: 192.168.1.1
|
|
||||||
|
|
||||||
Métodos disponibles:
|
@staticmethod
|
||||||
- NetworkAddress(): Obtiene dirección de red
|
def PopupFunctionList():
|
||||||
- BroadcastAddress(): Obtiene dirección de broadcast
|
"""Lista de métodos sugeridos para autocompletado de IP4"""
|
||||||
- Nodes(): Obtiene número de nodos disponibles
|
return [
|
||||||
"""
|
("NetworkAddress", "Obtiene la dirección de red"),
|
||||||
|
("BroadcastAddress", "Obtiene la dirección de broadcast"),
|
||||||
|
("Nodes", "Cantidad de nodos disponibles"),
|
||||||
|
]
|
||||||
|
|
||||||
def NetworkAddress(self):
|
def NetworkAddress(self):
|
||||||
"""Obtiene la dirección de red"""
|
"""Obtiene la dirección de red"""
|
||||||
|
|
136
main_calc_app.py
136
main_calc_app.py
|
@ -8,6 +8,7 @@ import json
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
|
import re
|
||||||
|
|
||||||
# Importar componentes del CAS híbrido
|
# Importar componentes del CAS híbrido
|
||||||
from main_evaluation import HybridEvaluationEngine, EvaluationResult
|
from main_evaluation import HybridEvaluationEngine, EvaluationResult
|
||||||
|
@ -18,6 +19,7 @@ from bin_type import HybridBin as Bin
|
||||||
from dec_type import HybridDec as Dec
|
from dec_type import HybridDec as Dec
|
||||||
from chr_type import HybridChr as Chr
|
from chr_type import HybridChr as Chr
|
||||||
import sympy
|
import sympy
|
||||||
|
from sympy_helper import Helper as SympyHelper
|
||||||
|
|
||||||
|
|
||||||
class HybridCalculatorApp:
|
class HybridCalculatorApp:
|
||||||
|
@ -26,6 +28,15 @@ class HybridCalculatorApp:
|
||||||
SETTINGS_FILE = "hybrid_calc_settings.json"
|
SETTINGS_FILE = "hybrid_calc_settings.json"
|
||||||
HISTORY_FILE = "hybrid_calc_history.txt"
|
HISTORY_FILE = "hybrid_calc_history.txt"
|
||||||
|
|
||||||
|
HELPERS = [
|
||||||
|
IP4.Helper,
|
||||||
|
Hex.Helper,
|
||||||
|
Bin.Helper,
|
||||||
|
Dec.Helper,
|
||||||
|
Chr.Helper,
|
||||||
|
SympyHelper,
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, root: tk.Tk):
|
def __init__(self, root: tk.Tk):
|
||||||
self.root = root
|
self.root = root
|
||||||
self.root.title("Calculadora MAV - CAS Híbrido")
|
self.root.title("Calculadora MAV - CAS Híbrido")
|
||||||
|
@ -264,9 +275,115 @@ class HybridCalculatorApp:
|
||||||
self._debounce_job = self.root.after(300, self._evaluate_and_update)
|
self._debounce_job = self.root.after(300, self._evaluate_and_update)
|
||||||
|
|
||||||
def _handle_dot_autocomplete(self):
|
def _handle_dot_autocomplete(self):
|
||||||
"""Maneja autocompletado con punto (simplificado por ahora)"""
|
self._close_autocomplete_popup()
|
||||||
# TODO: Implementar autocompletado para métodos de objetos
|
cursor_index_str = self.input_text.index(tk.INSERT)
|
||||||
pass
|
line_num_str, char_num_str = cursor_index_str.split('.')
|
||||||
|
current_line_num = int(line_num_str)
|
||||||
|
char_idx_of_dot = int(char_num_str)
|
||||||
|
obj_expr_str = self.input_text.get(f"{current_line_num}.0", f"{current_line_num}.{char_idx_of_dot -1}").strip()
|
||||||
|
if not obj_expr_str:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Preprocesar para convertir sintaxis de corchetes a llamada de clase
|
||||||
|
# Ejemplo: Hex[FF] -> Hex('FF')
|
||||||
|
bracket_match = re.match(r"([A-Za-z_][A-Za-z0-9_]*)\[(.*)\]$", obj_expr_str)
|
||||||
|
if bracket_match:
|
||||||
|
class_name, arg = bracket_match.groups()
|
||||||
|
# Si el argumento es un número, no poner comillas
|
||||||
|
if arg.isdigit():
|
||||||
|
obj_expr_str = f"{class_name}({arg})"
|
||||||
|
else:
|
||||||
|
obj_expr_str = f"{class_name}('{arg}')"
|
||||||
|
|
||||||
|
eval_context = self.engine._get_full_context() if hasattr(self.engine, '_get_full_context') else {}
|
||||||
|
obj = None
|
||||||
|
try:
|
||||||
|
obj = eval(obj_expr_str, eval_context)
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
if obj is not None and hasattr(obj, 'PopupFunctionList'):
|
||||||
|
methods = obj.PopupFunctionList()
|
||||||
|
if methods:
|
||||||
|
self._show_autocomplete_popup(methods)
|
||||||
|
|
||||||
|
def _show_autocomplete_popup(self, suggestions):
|
||||||
|
# suggestions: lista de tuplas (nombre, hint)
|
||||||
|
cursor_bbox = self.input_text.bbox(tk.INSERT)
|
||||||
|
if not cursor_bbox:
|
||||||
|
return
|
||||||
|
x, y, _, height = cursor_bbox
|
||||||
|
popup_x = self.input_text.winfo_rootx() + x
|
||||||
|
popup_y = self.input_text.winfo_rooty() + y + height + 2
|
||||||
|
self._autocomplete_popup = tk.Toplevel(self.root)
|
||||||
|
self._autocomplete_popup.wm_overrideredirect(True)
|
||||||
|
self._autocomplete_popup.wm_geometry(f"+{popup_x}+{popup_y}")
|
||||||
|
self._autocomplete_popup.attributes('-topmost', True)
|
||||||
|
self.root.after(100, lambda: self._autocomplete_popup.attributes('-topmost', False) if self._autocomplete_popup else None)
|
||||||
|
self._autocomplete_listbox = tk.Listbox(
|
||||||
|
self._autocomplete_popup, bg="#3c3f41", fg="#bbbbbb",
|
||||||
|
selectbackground="#007acc", selectforeground="white",
|
||||||
|
borderwidth=1, relief="solid", exportselection=False, activestyle="none"
|
||||||
|
)
|
||||||
|
for name, hint in suggestions:
|
||||||
|
self._autocomplete_listbox.insert(tk.END, f"{name} — {hint}")
|
||||||
|
if suggestions:
|
||||||
|
self._autocomplete_listbox.select_set(0)
|
||||||
|
self._autocomplete_listbox.pack(expand=True, fill=tk.BOTH)
|
||||||
|
self._autocomplete_listbox.bind("<Return>", self._on_autocomplete_select)
|
||||||
|
self._autocomplete_listbox.bind("<Escape>", lambda e: self._close_autocomplete_popup())
|
||||||
|
self._autocomplete_listbox.bind("<Double-Button-1>", self._on_autocomplete_select)
|
||||||
|
self._autocomplete_listbox.focus_set()
|
||||||
|
self._autocomplete_listbox.bind("<Up>", lambda e: self._navigate_autocomplete(e, -1))
|
||||||
|
self._autocomplete_listbox.bind("<Down>", lambda e: self._navigate_autocomplete(e, 1))
|
||||||
|
self.input_text.bind("<FocusOut>", lambda e: self._close_autocomplete_popup(), add=True)
|
||||||
|
self.input_text.bind("<Button-1>", lambda e: self._close_autocomplete_popup(), add=True)
|
||||||
|
self.root.bind("<Button-1>", lambda e: self._close_autocomplete_popup(), add=True)
|
||||||
|
self.input_text.bind("<Key>", lambda e: self._close_autocomplete_popup(), add=True)
|
||||||
|
max_len = max(len(name) for name, _ in suggestions) if suggestions else 10
|
||||||
|
width = max(15, min(max_len + 10, 50))
|
||||||
|
height = min(len(suggestions), 10)
|
||||||
|
self._autocomplete_listbox.config(width=width, height=height)
|
||||||
|
else:
|
||||||
|
self._close_autocomplete_popup()
|
||||||
|
|
||||||
|
def _navigate_autocomplete(self, event, direction):
|
||||||
|
if not hasattr(self, '_autocomplete_listbox') or not self._autocomplete_listbox:
|
||||||
|
return "break"
|
||||||
|
current_selection = self._autocomplete_listbox.curselection()
|
||||||
|
if not current_selection:
|
||||||
|
new_idx = 0 if direction == 1 else self._autocomplete_listbox.size() -1
|
||||||
|
else:
|
||||||
|
idx = current_selection[0]
|
||||||
|
new_idx = idx + direction
|
||||||
|
if 0 <= new_idx < self._autocomplete_listbox.size():
|
||||||
|
if current_selection:
|
||||||
|
self._autocomplete_listbox.select_clear(current_selection[0])
|
||||||
|
self._autocomplete_listbox.select_set(new_idx)
|
||||||
|
self._autocomplete_listbox.activate(new_idx)
|
||||||
|
self._autocomplete_listbox.see(new_idx)
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def _on_autocomplete_select(self, event):
|
||||||
|
if not hasattr(self, '_autocomplete_listbox') or not self._autocomplete_listbox:
|
||||||
|
return "break"
|
||||||
|
selection = self._autocomplete_listbox.curselection()
|
||||||
|
if not selection:
|
||||||
|
self._close_autocomplete_popup()
|
||||||
|
return "break"
|
||||||
|
selected = self._autocomplete_listbox.get(selection[0])
|
||||||
|
method_name = selected.split()[0]
|
||||||
|
self.input_text.insert(tk.INSERT, method_name + "()")
|
||||||
|
self.input_text.mark_set(tk.INSERT, f"{tk.INSERT}-1c") # Cursor dentro de los paréntesis
|
||||||
|
self._close_autocomplete_popup()
|
||||||
|
self.input_text.focus_set()
|
||||||
|
self.on_key_release() # Trigger re-evaluation
|
||||||
|
return "break"
|
||||||
|
|
||||||
|
def _close_autocomplete_popup(self):
|
||||||
|
if hasattr(self, '_autocomplete_popup') and self._autocomplete_popup:
|
||||||
|
self._autocomplete_popup.destroy()
|
||||||
|
self._autocomplete_popup = None
|
||||||
|
self._autocomplete_listbox = None
|
||||||
|
|
||||||
def _evaluate_and_update(self):
|
def _evaluate_and_update(self):
|
||||||
"""Evalúa todas las líneas y actualiza la salida"""
|
"""Evalúa todas las líneas y actualiza la salida"""
|
||||||
|
@ -309,7 +426,11 @@ class HybridCalculatorApp:
|
||||||
output_parts = []
|
output_parts = []
|
||||||
|
|
||||||
if result.is_error:
|
if result.is_error:
|
||||||
output_parts.append(("error", f"Error: {result.error}"))
|
ayuda = self.obtener_ayuda(result.original_line)
|
||||||
|
if ayuda:
|
||||||
|
output_parts.append(("helper", ayuda))
|
||||||
|
else:
|
||||||
|
output_parts.append(("error", f"Error: {result.error}"))
|
||||||
elif result.result_type == "comment":
|
elif result.result_type == "comment":
|
||||||
output_parts.append(("comment", result.original_line))
|
output_parts.append(("comment", result.original_line))
|
||||||
elif result.result_type == "equation_added":
|
elif result.result_type == "equation_added":
|
||||||
|
@ -819,6 +940,13 @@ programación y análisis numérico.
|
||||||
|
|
||||||
self.root.destroy()
|
self.root.destroy()
|
||||||
|
|
||||||
|
def obtener_ayuda(self, input_str):
|
||||||
|
for helper in self.HELPERS:
|
||||||
|
ayuda = helper(input_str)
|
||||||
|
if ayuda:
|
||||||
|
return ayuda
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Función principal"""
|
"""Función principal"""
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
def Helper(input_str):
|
||||||
|
"""Ayuda contextual para funciones SymPy comunes"""
|
||||||
|
sympy_funcs = {
|
||||||
|
"diff": "Derivada: diff(expr, var). Ej: diff(sin(x), x)",
|
||||||
|
"integrate": "Integral: integrate(expr, var). Ej: integrate(x**2, x)",
|
||||||
|
"solve": "Resolver ecuaciones: solve(expr, var). Ej: solve(x**2-1, x)",
|
||||||
|
"limit": "Límite: limit(expr, var, valor). Ej: limit(sin(x)/x, x, 0)",
|
||||||
|
"series": "Serie de Taylor: series(expr, var, punto, n). Ej: series(exp(x), x, 0, 5)",
|
||||||
|
"Matrix": "Matrices: Matrix([[a, b], [c, d]]). Ej: Matrix([[1,2],[3,4]])",
|
||||||
|
"plot": "Gráfica: plot(expr, (var, a, b)). Ej: plot(sin(x), (x, 0, 2*pi))",
|
||||||
|
"plot3d": "Gráfica 3D: plot3d(expr, (x, a, b), (y, c, d))",
|
||||||
|
"simplify": "Simplificar: simplify(expr). Ej: simplify((x**2 + 2*x + 1))",
|
||||||
|
"expand": "Expandir: expand(expr). Ej: expand((x+1)**2)",
|
||||||
|
"factor": "Factorizar: factor(expr). Ej: factor(x**2 + 2*x + 1)",
|
||||||
|
"collect": "Agrupar: collect(expr, var). Ej: collect(x*y + x, x)",
|
||||||
|
"cancel": "Cancelar: cancel(expr). Ej: cancel((x**2 + 2*x + 1)/(x+1))",
|
||||||
|
"apart": "Fracciones parciales: apart(expr, var). Ej: apart(1/(x*(x+1)), x)",
|
||||||
|
"together": "Unir fracciones: together(expr). Ej: together(1/x + 1/y)",
|
||||||
|
}
|
||||||
|
for func, ayuda in sympy_funcs.items():
|
||||||
|
if input_str.strip().startswith(func):
|
||||||
|
return ayuda
|
||||||
|
return None
|
Loading…
Reference in New Issue