Add a search function for the text field. I should be shown as a small symbol in the upper right corner of the text field. When the user clicks on it a small bar should open that aligns with the text field and only covers 25% of its width and has a height that fits the font size. When the user types something in this field it should search for what the user typed in in the text field and only show the lines of results that include what ever the user searched for. Python 3.12 Code: import math
import re
from typing import Any, Union, Callable
from math import ceil

from PySide6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QLineEdit, QTextEdit, QScrollBar, QPushButton, QLabel, QFrame,
    QGridLayout
)
from PySide6.QtGui import QFont
from PySide6.QtCore import Qt

# -- Utility Functions --

def convert_angle(func: Callable[[float], float], x: float, use_degrees: bool) -> float:
    """Convert angle based on degree mode."""
    return func(math.radians(x) if use_degrees else x)

# -- Conversions Category --

def kilograms_to_pounds(kg: float) -> float:
    """Convert kilograms to pounds."""
    return kg / 0.45359237

def pounds_to_kilograms(lb: float) -> float:
    """Convert pounds to kilograms."""
    return lb * 0.45359237

def celsius_to_kelvin(c: float) -> float:
    """Convert Celsius to Kelvin."""
    return c + 273.15

def kelvin_to_celsius(k: float) -> float:
    """Convert Kelvin to Celsius."""
    return k - 273.15

def celsius_to_fahrenheit(c: float) -> float:
    """Convert Celsius to Fahrenheit."""
    return c * 9/5 + 32

def fahrenheit_to_celsius(f: float) -> float:
    """Convert Fahrenheit to Celsius."""
    return (f - 32) * 5/9

# -- Text Category --

def capitalize_sentence(sentence: str) -> str:
    """Capitalize the first letter of each word in the sentence."""
    return ' '.join(word.capitalize() for word in sentence.split())

def remove_duplicates(text: str) -> str:
    """Remove duplicate words and empty lines from a string."""
    words = ' '.join(line.strip() for line in text.splitlines() if line.strip()).split()
    return ' '.join(dict.fromkeys(words))

# -- Processing --

def get_safe_globals(use_degrees: bool, variables: dict) -> dict:
    """Return a dictionary of safe globals for expression evaluation, including user-defined variables."""
    trig_funcs = {'sin', 'cos', 'tan', 'asin', 'acos', 'atan'}
    safe_globals = {
        "sqrt": math.sqrt,
        "log": math.log10,
        "ln": math.log,
        "pi": math.pi,
        "e": math.e,
        "lb": kilograms_to_pounds,
        "kg": pounds_to_kilograms,
        "C": kelvin_to_celsius,
        "K": celsius_to_kelvin,
        "F": celsius_to_fahrenheit,
        "CF": fahrenheit_to_celsius,
        "capitalize": capitalize_sentence,
        "duplicates": remove_duplicates,
    }
    # Add trigonometric functions with angle conversion
    safe_globals.update({
        func: (lambda f: (lambda x: convert_angle(getattr(math, f), x, use_degrees)))(func)
        for func in trig_funcs
    })
    # Include user-defined variables
    safe_globals.update(variables)
    return safe_globals

def preprocess_text_functions(expression: str) -> str:
    """Preprocess text functions to ensure proper quoting of arguments."""
    def add_quotes(match):
        func, args = match.groups()
        return f'{func}("{args}")' if func in ['capitalize', 'duplicates'] else match.group(0)
    # Corrected regex pattern with only two capturing groups
    return re.sub(r'\b(capitalize|duplicates)\(([^)]+)\)', add_quotes, expression)

def validate_inverse_trig(expression: str, safe_globals: dict) -> Union[str, None]:
    """Validate inputs for inverse trigonometric functions."""
    for func, arg in re.findall(r"(\b\w+\b)\(([^)]+)\)", expression):
        if func in ["asin", "acos"]:
            try:
                value = eval(arg, {"__builtins__": None}, safe_globals)
                if abs(value) > 1:
                    return f"Error: {func}(x) is undefined for |x| > 1"
            except Exception as e:
                return f"Error: {e}"
    return None

def safe_evaluate(expression: str, safe_globals: dict, use_e_notation: bool) -> Union[str, Any]:
    """Safely evaluate the expression and format the result."""
    try:
        result = eval(expression, {"__builtins__": None}, safe_globals)
        if isinstance(result, float) and abs(result) < 1e-10:
            result = 0.0
        if isinstance(result, (int, float)):
            if use_e_notation:
                # Format using scientific notation with up to 2 decimal places
                result = f"{result:.2e}"
            else:
                # Standard formatting with rounding
                result = round(result, 2)
        return result
    except Exception as e:
        return f"Error: {e}"

def evaluate_expression(expression: str, variables: dict, use_degrees: bool = False, use_e_notation: bool = False) -> Union[float, str]:
    """Safely evaluate a mathematical expression considering degree mode and handle variable assignments."""
    # Check for variable assignment
    assignment_match = re.match(r'^\s*([a-zA-Z_]\w*)\s*=\s*(.+)$', expression)
    if assignment_match:
        var_name, var_expr = assignment_match.groups()
        # Evaluate the expression for the variable
        safe_globals = get_safe_globals(use_degrees, variables)
        var_value = safe_evaluate(var_expr, safe_globals, use_e_notation)
        if isinstance(var_value, (int, float)):
            variables[var_name] = var_value
            return f"{var_name} = {var_value}"
        elif isinstance(var_value, str) and var_value.startswith("Error"):
            return var_value
        else:
            return f"{var_name} = {var_value}"
    else:
        # Regular expression evaluation
        safe_globals = get_safe_globals(use_degrees, variables)
        expression = expression.replace("^", "**")
        expression = preprocess_text_functions(expression)
        error = validate_inverse_trig(expression, safe_globals)
        if error:
            return error
        return safe_evaluate(expression, safe_globals, use_e_notation)

# -- GUI --

class CalculatorGUI(QMainWindow):
    """PySide6-based Calculator with Degrees/Radians toggle, Variables, and history."""

    def __init__(self):
        super().__init__()
        self.use_degrees = False
        self.use_e_notation = False
        self.variables = {}  # Dictionary to store variables
        self.setup_window()
        self.create_widgets()
        self.layout_widgets()

    def setup_window(self) -> None:
        """Configure the main application window."""
        self.setWindowTitle("Calculator")
        self.setGeometry(100, 100, 1280, 720)

    def create_widgets(self) -> None:
        """Initialize all GUI components."""
        font = QFont()
        font.setPointSize(12)
        font.setFamily('Literata')

        self.entry = QLineEdit()
        self.entry.setFont(font)
        self.entry.returnPressed.connect(self.calculate)
        self.entry.setPlaceholderText('Enter expression')
        self.entry.setClearButtonEnabled(True)

        self.history_text = QTextEdit()
        self.history_text.setFont(font)
        self.history_text.setReadOnly(True)

        self.scrollbar = QScrollBar(Qt.Vertical)
        self.history_text.setVerticalScrollBar(self.scrollbar)

        self.function_buttons = {
            "Functions": ["sqrt", "sin", "cos", "tan", "asin", "acos", "atan", "log", "ln"],
            "Conversions": ["lb", "kg", "K", "C", "F", "CF"],
            "Text": ["capitalize", "duplicates"],
            "Constants": ["pi", "e"],
            "Settings": []
        }
        self.function_frame = QFrame()
        self.create_function_buttons(font)

    def create_function_buttons(self, font: QFont) -> None:
        """Create buttons for inserting functions into the entry."""
        layout = QGridLayout()

        # Updated col_span for "Settings" from 2 to 1
        categories = [("Functions", 3), ("Conversions", 2), ("Text", 2), ("Constants", 2), ("Settings", 1)]
        starting_col = 0

        for category, col_span in categories:
            label = QLabel(category)
            label.setFont(font)
            layout.addWidget(label, 0, starting_col, 1, col_span, Qt.AlignCenter)

            buttons = self.function_buttons.get(category, [])
            if category == "Functions":
                for i, func in enumerate(buttons):
                    button = QPushButton(func)
                    button.setFont(font)
                    button.clicked.connect(lambda _, f=func: self.insert_function(f, False))
                    layout.addWidget(button, (i // 3) + 1, starting_col + (i % 3))
            
            elif category == "Settings":
                # Arrange Settings buttons vertically within a single column
                self.mode_button = QPushButton("Radians")
                self.mode_button.setFont(font)
                self.mode_button.clicked.connect(self.toggle_mode)
                layout.addWidget(self.mode_button, 1, starting_col, 1, 1)

                self.e_notation_button = QPushButton("E Notation: Off")
                self.e_notation_button.setFont(font)
                self.e_notation_button.clicked.connect(self.toggle_e_notation)
                layout.addWidget(self.e_notation_button, 2, starting_col, 1, 1)
            
            elif category == "Conversions":
                for idx, func in enumerate(buttons):
                    if func == "lb":
                        display_text = "lb(kg)"
                    elif func == "kg":
                        display_text = "kg(lb)"
                    elif func == "K":
                        display_text = "K(°C)"
                    elif func == "C":
                        display_text = "°C(K)"
                    elif func == "F":
                        display_text = "F(°C)"
                    elif func == "CF":
                        display_text = "°C(F)"
                    else:
                        display_text = func
                    button = QPushButton(display_text)
                    button.setFont(font)
                    button.clicked.connect(lambda _, f=func: self.insert_function(f, False))
                    row = 1 + (idx // col_span)
                    col = starting_col + (idx % col_span)
                    layout.addWidget(button, row, col)
            
            else:
                num_buttons = len(buttons)
                num_rows = ceil(num_buttons / col_span)

                for idx, func in enumerate(buttons):
                    button_text = f"{func}"
                    button = QPushButton(button_text)
                    button.setFont(font)
                    button.clicked.connect(lambda _, f=func, c=(category == "Constants"): self.insert_function(f, c))
                    row = 1 + (idx // col_span)
                    col = starting_col + (idx % col_span)
                    layout.addWidget(button, row, col)

                for extra in range(col_span * num_rows - num_buttons):
                    layout.addWidget(QLabel(""), 1 + (extra // col_span), starting_col + (extra % col_span))

            starting_col += col_span

        self.function_frame.setLayout(layout)

    def layout_widgets(self) -> None:
        """Arrange widgets within the window."""
        central_widget = QWidget()
        main_layout = QVBoxLayout()

        main_layout.addWidget(self.entry)

        history_layout = QHBoxLayout()
        history_layout.addWidget(self.history_text)
        history_layout.addWidget(self.scrollbar)
        main_layout.addLayout(history_layout)

        main_layout.addWidget(self.function_frame)

        central_widget.setLayout(main_layout)
        self.setCentralWidget(central_widget)

    def toggle_mode(self) -> None:
        """Switch between Degrees and Radians modes."""
        self.use_degrees = not self.use_degrees
        self.mode_button.setText("Degrees" if self.use_degrees else "Radians")

    def toggle_e_notation(self) -> None:
        """Toggle the E notation setting."""
        self.use_e_notation = not self.use_e_notation
        state = "On" if self.use_e_notation else "Off"
        self.e_notation_button.setText(f"E Notation: {state}")

    def insert_function(self, function: str, is_constant: bool = False) -> None:
        """Insert function or constant text into the expression entry."""
        current_text = self.entry.text()
        cursor_position = self.entry.cursorPosition()
        if is_constant:
            new_text = f"{current_text[:cursor_position]}{function}"
            new_cursor_position = cursor_position + len(function)
        else:
            new_text = f"{current_text[:cursor_position]}{function}() {current_text[cursor_position:]}"
            new_cursor_position = cursor_position + len(function) + 1
        self.entry.setText(new_text)
        self.entry.setCursorPosition(new_cursor_position)
        self.entry.setFocus()

    def calculate(self) -> None:
        """Evaluate the expression and update history."""
        expression = self.entry.text().strip()
        if expression:
            result = evaluate_expression(expression, self.variables, use_degrees=self.use_degrees, use_e_notation=self.use_e_notation)
            self.display_result(expression, result)
            self.entry.clear()

    def display_result(self, expression: str, result: Union[float, str]) -> None:
        """Append the calculation to the history log."""
        mode = "D" if self.use_degrees else "R"
        # Check if the expression is a variable assignment
        if re.match(r'^\s*[a-zA-Z_]\w*\s*=\s*.+$', expression):
            # For variable assignments, display only the expression
            self.history_text.append(f"[{mode}] {result}")
        else:
            # For regular expressions, display expression and result
            self.history_text.append(f"[{mode}] {expression} = {result}")
        self.history_text.verticalScrollBar().setValue(
            self.history_text.verticalScrollBar().maximum()
        )

def main() -> None:
    """Run the Calculator application."""
    app = QApplication([])
    calculator = CalculatorGUI()
    calculator.show()
    app.exec()

if __name__ == "__main__":
    main()