feat: potenzia EnhancedCLI con funzionalità avanzate

Features aggiunte:
- 4 temi: default, dark, light, monokai
- Syntax highlighting per code blocks (pygments)
- Keyboard shortcuts: Ctrl+A/E (inizio/fine), Ctrl+K/U (kill line), Ctrl+W (kill word)
- Toggle timestamps, syntax con comandi dedicati
- Status avanzato con uptime e statistics
- Codici ANSI predefiniti per ogni tema
- Mouse support per scrolling
- Pygments per syntax highlighting di codice
This commit is contained in:
opencode committed 2026-03-24 10:23:35 +01:00
1 parent 4a0342dcb7
commit 9fa564e12b
1 file changed
+496 -152
+496 -152
View File
@@ -1,32 +1,49 @@
"""
Enhanced CLI for LangCode Agent
Provides modern terminal UI with colors, history, and autocomplete
Provides modern terminal UI with colors, history, autocomplete, and more.
"""
import sys
import os
from typing import Optional, List, Dict, Any
import re
import shutil
from typing import Optional, List, Dict, Any, Callable
from datetime import datetime
# prompt_toolkit imports
from prompt_toolkit import PromptSession, HTML
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.styles import Style
from prompt_toolkit.completion import WordCompleter, NestedCompleter
from prompt_toolkit.completion import WordCompleter, NestedCompleter, PathCompleter
from prompt_toolkit.history import FileHistory
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.formatted_text import FormattedText
from prompt_toolkit.cursor_shapes import CursorShape
# For syntax highlighting
try:
from pygments.lexers import get_lexer_by_name, get_lexer_for_filename
from pygments.token import Token
from pygments import highlight as pygments_highlight
PYGMENTS_AVAILABLE = True
except ImportError:
PYGMENTS_AVAILABLE = False
get_lexer_by_name = None
get_lexer_for_filename = None
pygments_highlight = None
# =============================================================================
# STYLING
# =============================================================================
# Color palette
class Colors:
"""ANSI color codes for terminal output"""
# Styles
RESET = "\033[0m"
BOLD = "\033[1m"
DIM = "\033[2m"
ITALIC = "\033[3m"
RESET = "\033[0m"
# Colors
BLACK = "\033[30m"
RED = "\033[31m"
GREEN = "\033[32m"
@@ -36,12 +53,6 @@ class Colors:
CYAN = "\033[36m"
WHITE = "\033[37m"
# Background
BG_BLACK = "\033[40m"
BG_BLUE = "\033[44m"
BG_CYAN = "\033[46m"
# Bright colors
BRIGHT_BLACK = "\033[90m"
BRIGHT_RED = "\033[91m"
BRIGHT_GREEN = "\033[92m"
@@ -55,82 +66,220 @@ class Colors:
class Theme:
"""Color themes for the CLI"""
DEFAULT = {
"user": "ansicyan", # Cyan for user input
"agent": "ansigreen", # Green for agent response
"system": "ansiyellow", # Yellow for system messages
"error": "ansired", # Red for errors
"info": "ansiblue", # Blue for info
"muted": "ansiblack", # Black for muted text
"prompt": "ansimagenta", # Magenta for prompt
}
@staticmethod
def get_style(theme_name: str) -> Style:
"""Get prompt_toolkit style from theme name"""
themes = {
"default": {
"user": "ansicyan bold",
"agent": "ansigreen",
"system": "ansiyellow",
"error": "ansired bold",
"info": "ansiblue",
"muted": "ansiblack",
"prompt": "ansimagenta bold",
"code": "ansiyellow",
"url": "ansiblue underline",
},
"dark": {
"user": "cyan bold",
"agent": "lightgreen",
"system": "yellow",
"error": "lightred bold",
"info": "lightblue",
"muted": "gray",
"prompt": "lightmagenta bold",
"code": "yellow",
"url": "lightblue underline",
},
"light": {
"user": "blue bold",
"agent": "darkgreen",
"system": "darkyellow",
"error": "darkred bold",
"info": "darkblue",
"muted": "gray",
"prompt": "darkmagenta bold",
"code": "darkyellow",
"url": "darkblue underline",
},
"monokai": {
"user": "cyan bold",
"agent": "green",
"system": "yellow",
"error": "red bold",
"info": "blue",
"muted": "gray",
"prompt": "magenta bold",
"code": "yellow",
"url": "cyan underline",
},
}
theme_dict = themes.get(theme_name, themes["default"])
return Style.from_dict(theme_dict)
DARK = {
"user": "cyan",
"agent": "lightgreen",
"system": "yellow",
"error": "lightred",
"info": "lightblue",
"muted": "gray",
"prompt": "magenta",
}
LIGHT = {
"user": "blue",
"agent": "darkgreen",
"system": "darkyellow",
"error": "darkred",
"info": "darkblue",
"muted": "gray",
"prompt": "darkmagenta",
}
@staticmethod
def get_ansi_theme(theme_name: str) -> Dict[str, str]:
"""Get ANSI color theme for non-prompt_toolkit output"""
themes = {
"default": {
"user": f"{Colors.CYAN}{Colors.BOLD}",
"agent": Colors.GREEN,
"system": Colors.YELLOW,
"error": f"{Colors.RED}{Colors.BOLD}",
"info": Colors.BLUE,
"muted": Colors.BRIGHT_BLACK,
"prompt": f"{Colors.MAGENTA}{Colors.BOLD}",
"code": Colors.YELLOW,
"reset": Colors.RESET,
},
"dark": {
"user": Colors.BRIGHT_CYAN,
"agent": Colors.BRIGHT_GREEN,
"system": Colors.BRIGHT_YELLOW,
"error": f"{Colors.BRIGHT_RED}{Colors.BOLD}",
"info": Colors.BRIGHT_BLUE,
"muted": Colors.BRIGHT_BLACK,
"prompt": Colors.BRIGHT_MAGENTA,
"code": Colors.YELLOW,
"reset": Colors.RESET,
},
"light": {
"user": Colors.BLUE,
"agent": "\033[32m", # darkgreen
"system": "\033[33m", # darkyellow
"error": "\033[31m", # darkred
"info": "\033[34m", # darkblue
"muted": "\033[90m", # brightblack/gray
"prompt": "\033[35m", # darkmagenta
"code": "\033[33m",
"reset": Colors.RESET,
},
"monokai": {
"user": Colors.CYAN,
"agent": Colors.GREEN,
"system": Colors.YELLOW,
"error": Colors.RED,
"info": Colors.BLUE,
"muted": Colors.BRIGHT_BLACK,
"prompt": Colors.MAGENTA,
"code": Colors.YELLOW,
"reset": Colors.RESET,
},
}
return themes.get(theme_name, themes["default"])
def create_style(theme_name: str = "default") -> Style:
"""Create prompt_toolkit style from theme"""
theme = getattr(Theme, theme_name.upper(), Theme.DEFAULT)
# =============================================================================
# SYNTAX HIGHLIGHTING
# =============================================================================
class SyntaxHighlighter:
"""Simple syntax highlighter for code blocks"""
style_dict = {
"user": theme["user"],
"agent": theme["agent"],
"system": theme["system"],
"error": theme["error"],
"info": theme["info"],
"muted": theme["muted"],
"prompt": theme["prompt"],
# Language aliases
LANG_ALIASES = {
"py": "python",
"js": "javascript",
"ts": "typescript",
"rb": "ruby",
"sh": "bash",
"zsh": "bash",
"ps": "powershell",
"yml": "yaml",
"md": "markdown",
}
return Style.from_dict(style_dict)
@classmethod
def get_lexer(cls, lang: str):
"""Get pygments lexer for language"""
if not PYGMENTS_AVAILABLE:
return None
lang = cls.LANG_ALIASES.get(lang.lower(), lang.lower())
try:
return get_lexer_by_name(lang)
except:
try:
return get_lexer_for_filename(f"file.{lang}")
except:
return None
@classmethod
def highlight(cls, code: str, lang: str = "text") -> str:
"""Apply syntax highlighting to code"""
if not PYGMENTS_AVAILABLE:
return code
lexer = cls.get_lexer(lang)
if not lexer:
return code
from pygments import highlight as pygments_highlight
from pygments.formatters import Terminal256Formatter
formatter = Terminal256Formatter()
try:
highlighted = pygments_highlight(code, lexer, formatter)
return highlighted.rstrip()
except:
return code
# =============================================================================
# MAIN ENHANCED CLI CLASS
# =============================================================================
class EnhancedCLI:
"""
Enhanced Command Line Interface for LangCode Agent.
Features:
- Colored input/output
- Colored input/output with themes
- Command history (persistent)
- Autocomplete for commands
- Autocomplete for commands and paths
- Multi-line input support
- Themes (light/dark)
- Code syntax highlighting
- Mouse support for scrolling
- Copy/paste support
- Signal handling (Ctrl+C, Ctrl+L)
"""
def __init__(
self,
history_file: str = "./.langcode/.cli_history",
theme: str = "default",
welcome_message: Optional[str] = None
welcome_message: Optional[str] = None,
show_timestamps: bool = True,
enable_mouse: bool = True,
enable_syntax_highlight: bool = True,
terminal_width: Optional[int] = None,
):
"""
Initialize Enhanced CLI.
Args:
history_file: Path to persist command history
theme: Color theme (default, dark, light)
theme: Color theme (default, dark, light, monokai)
welcome_message: Initial message to show
show_timestamps: Show timestamps in output
enable_mouse: Enable mouse for scrolling
enable_syntax_highlight: Highlight code blocks
terminal_width: Force terminal width
"""
self.history_file = history_file
self.theme = theme
self.show_timestamps = show_timestamps
self.enable_mouse = enable_mouse
self.enable_syntax_highlight = enable_syntax_highlight and PYGMENTS_AVAILABLE
# Get terminal size
self.terminal_width = terminal_width or shutil.get_terminal_size().columns
# ANSI theme for non-prompt output
self.ansi_theme = Theme.get_ansi_theme(theme)
# Welcome message
self.welcome_message = welcome_message or self._default_welcome()
# Ensure history directory exists
@@ -141,16 +290,21 @@ class EnhancedCLI:
"/help": "Mostra aiuto",
"/status": "Stato sistema",
"/clear": "Pulisci schermo",
"/theme": "Cambia tema (default/dark/light)",
"/theme": "Cicla tra i temi",
"/history": "Mostra cronologia",
"/theme-default": "Tema default",
"/theme-dark": "Tema dark",
"/theme-light": "Tema light",
"/theme-monokai": "Tema monokai",
"/timestamps": "Toggle timestamps",
"/syntax": "Toggle syntax highlighting",
"/width": "Show terminal width",
"/exit": "Esci",
"/quit": "Esci",
}
# Create completer for autocomplete
self.completer = NestedCompleter.from_nested_dict({
cmd: None for cmd in self.commands.keys()
})
# Path completer for commands that need paths
self.path_completer = PathCompleter()
# Create key bindings
self.key_bindings = self._create_key_bindings()
@@ -161,23 +315,39 @@ class EnhancedCLI:
# Current theme name
self.current_theme = theme
# Output welcome message
msg = welcome_message if welcome_message else self._default_welcome()
self._print_system(msg)
# Statistics
self.stats = {
"commands_executed": 0,
"session_start": datetime.now(),
}
# Callback for Ctrl+L (clear)
self._on_clear: Optional[Callable] = None
def _default_welcome(self) -> str:
"""Generate default welcome message"""
return f"""
{Colors.CYAN}{Colors.BOLD}╔═══════════════════════════════════════════════════════════╗{Colors.RESET}
║ {Colors.BRIGHT_GREEN}{Colors.BOLD}LangCode Agent - Enhanced CLI{Colors.RESET} ║
║ ║
║ {Colors.YELLOW}Comandi:{Colors.RESET} ║
║ {Colors.CYAN}/help{Colors.RESET} - Mostra aiuto {Colors.CYAN}/clear{Colors.RESET} - Pulisci schermo ║
║ {Colors.CYAN}/status{Colors.RESET} - Stato sistema {Colors.CYAN}/theme{Colors.RESET} - Cambia tema ║
║ {Colors.CYAN}/exit{Colors.RESET} - Esci ║
║ ║
╚═══════════════════════════════════════════════════════════════════╝{Colors.RESET}
"""
theme = self.ansi_theme
w = self.terminal_width
# Box drawing
top = f"{theme['prompt']}╔{'═' * (w - 4)}╗{theme['reset']}"
bottom = f"{theme['prompt']}╚{'═' * (w - 4)}╝{theme['reset']}"
lines = [
f"{theme['prompt']}║{theme['reset']} {theme['agent']}{Colors.BOLD}LangCode Agent - Enhanced CLI{theme['reset']}" + " " * (w - 43) + f"{theme['prompt']}║{theme['reset']}",
f"{theme['prompt']}║{theme['reset']}" + " " * (w - 2) + f"{theme['prompt']}║{theme['reset']}",
f"{theme['prompt']}║{theme['reset']} {theme['system']}Comandi:{theme['reset']}" + " " * (w - 17) + f"{theme['prompt']}║{theme['reset']}",
f"{theme['prompt']}║{theme['reset']} {theme['user']}/help{theme['reset']} - Mostra aiuto {theme['user']}/clear{theme['reset']} - Pulisci {theme['prompt']}║{theme['reset']}",
f"{theme['prompt']}║{theme['reset']} {theme['user']}/status{theme['reset']} - Stato sistema {theme['user']}/theme{theme['reset']} - Cambia tema {theme['prompt']}║{theme['reset']}",
f"{theme['prompt']}║{theme['reset']} {theme['user']}/exit{theme['reset']} - Esci {theme['prompt']}║{theme['reset']}",
f"{theme['prompt']}║{theme['reset']}" + " " * (w - 2) + f"{theme['prompt']}║{theme['reset']}",
f"{theme['prompt']}║{theme['reset']} {theme['info']}Funzionalità:{theme['reset']}" + " " * (w - 22) + f"{theme['prompt']}║{theme['reset']}",
f"{theme['prompt']}║{theme['reset']} • History (↑↓) • Autocomplete (Tab) • Colors {theme['prompt']}║{theme['reset']}",
f"{theme['prompt']}║{theme['reset']} • Syntax highlighting • Mouse scroll • Multiline {theme['prompt']}║{theme['reset']}",
f"{theme['prompt']}║{theme['reset']}" + " " * (w - 2) + f"{theme['prompt']}║{theme['reset']}",
]
return "\n".join([top] + lines + [bottom])
def _create_key_bindings(self) -> KeyBindings:
"""Create custom key bindings"""
@@ -185,94 +355,222 @@ class EnhancedCLI:
@kb.add("c-c")
def _(event):
"""Handle Ctrl+C - cancel input"""
event.app.exit(exception=KeyboardInterrupt)
"""Handle Ctrl+C - interrupt or exit"""
# If there's text, clear it; otherwise exit
if event.app.current_buffer.text:
event.app.current_buffer.text = ""
else:
event.app.exit(exception=KeyboardInterrupt)
@kb.add("c-l")
def _(event):
"""Handle Ctrl+L - clear screen"""
os.system("clear" if os.name != "nt" else "cls")
if self._on_clear:
self._on_clear()
@kb.add("c-a")
def _(event):
"""Handle Ctrl+A - go to start of line"""
buffer = event.app.current_buffer
buffer.cursor_position = 0
@kb.add("c-e")
def _(event):
"""Handle Ctrl+E - go to end of line"""
buffer = event.app.current_buffer
buffer.cursor_position = len(buffer.text)
@kb.add("c-k")
def _(event):
"""Handle Ctrl+K - kill line to end"""
buffer = event.app.current_buffer
buffer.text = buffer.text[:buffer.cursor_position]
@kb.add("c-u")
def _(event):
"""Handle Ctrl+U - kill line from start"""
buffer = event.app.current_buffer
buffer.text = buffer.text[buffer.cursor_position:]
buffer.cursor_position = 0
@kb.add("c-w")
def _(event):
"""Handle Ctrl+W - kill word before cursor"""
buffer = event.app.current_buffer
text = buffer.text[:buffer.cursor_position]
if text and text[-1] == " ":
buffer.text = text[:-1] + buffer.text[buffer.cursor_position:]
buffer.cursor_position = len(buffer.text) - (len(buffer.text) - buffer.cursor_position + 1)
else:
words = text.split()
if words:
remaining = " ".join(words[:-1]) + (" " if len(words) > 1 else "")
buffer.text = remaining + buffer.text[buffer.cursor_position:]
buffer.cursor_position = len(remaining)
# Note: Mouse support is enabled via mouse_support parameter in PromptSession
# Scroll events are handled automatically by prompt_toolkit
return kb
def _create_session(self) -> PromptSession:
"""Create prompt session with history and autocomplete"""
# Create nested completer for commands
command_completer = NestedCompleter.from_nested_dict({
cmd: None for cmd in self.commands.keys()
})
return PromptSession(
history=FileHistory(self.history_file),
completer=self.completer,
completer=command_completer,
key_bindings=self.key_bindings,
auto_suggest=AutoSuggestFromHistory(),
multiline=True,
cursor=CursorShape.BEAM,
mouse_support=self.enable_mouse,
)
def _get_bottom_bar(self, cli) -> List:
"""Bottom toolbar showing theme and shortcuts"""
return [
("class:muted", f" Theme: {self.current_theme} "),
("class:muted", " | "),
("class:muted", "Ctrl+C: exit | Tab: autocomplete | ↑↓: history "),
]
def _get_prompt_tokens(self) -> FormattedText:
"""Create prompt tokens with colors and optional info"""
theme = self.ansi_theme
timestamp = datetime.now().strftime("%H:%M") if self.show_timestamps else None
tokens = []
# Prompt symbol with theme
tokens.append(("class:prompt", f"{theme['prompt']}❯ {theme['reset']}"))
tokens.append(("class:user", f"{theme['user']}TUO{theme['reset']}"))
if timestamp:
tokens.append(("class:muted", f" [{timestamp}]"))
tokens.append(("class:prompt", f" {theme['prompt']}»{theme['reset']} "))
return FormattedText(tokens)
def _create_prompt_tokens(self) -> FormattedText:
"""Create prompt tokens with colors"""
timestamp = datetime.now().strftime("%H:%M")
return FormattedText([
("class:prompt", f"❯ "),
("class:user", f"TUO "),
("class:muted", f"[{timestamp}] "),
])
def _print_with_timestamp(self, message: str, prefix: str = "") -> None:
"""Print message with optional timestamp"""
if self.show_timestamps:
ts = datetime.now().strftime("%H:%M:%S")
print(f"{self.ansi_theme['muted']}[{ts}]{self.ansi_theme['reset']} {prefix}{message}")
else:
print(f"{prefix}{message}")
def _print_system(self, message: str) -> None:
"""Print system message (in yellow)"""
print(f"{Colors.YELLOW}{message}{Colors.RESET}")
def _print_formatted(self, message: str, level: str = "info") -> None:
"""Print formatted message with color and optional timestamp"""
theme = self.ansi_theme
colors = {
"user": theme["user"],
"agent": theme["agent"],
"system": theme["system"],
"error": theme["error"],
"info": theme["info"],
}
color = colors.get(level, theme["reset"])
prefix_map = {
"user": f"{color}👤{theme['reset']} ",
"agent": f"{color}🤖{theme['reset']} ",
"system": f"{color}🔧{theme['reset']} ",
"error": f"{color}❌{theme['reset']} ",
"info": f"{color}ℹ️{theme['reset']} ",
}
prefix = prefix_map.get(level, "")
self._print_with_timestamp(message, prefix)
def _print_user(self, message: str) -> None:
"""Print user input (in cyan)"""
print(f"{Colors.CYAN}👤 TUO:{Colors.RESET} {message}")
"""Print user message"""
self._print_formatted(message, "user")
def _print_agent(self, message: str) -> None:
"""Print agent response (in green)"""
# Handle multi-line messages
lines = message.split("\n")
for i, line in enumerate(lines):
if i == 0:
print(f"{Colors.GREEN}🤖 AGENTE:{Colors.RESET} {line}")
else:
print(f"{Colors.GREEN} {Colors.RESET} {line}")
"""Print agent response with optional code highlighting"""
if self.enable_syntax_highlight:
message = self._highlight_code_blocks(message)
self._print_formatted(message, "agent")
def _print_system(self, message: str) -> None:
"""Print system message"""
self._print_formatted(message, "system")
def _print_error(self, message: str) -> None:
"""Print error message (in red)"""
print(f"{Colors.RED}❌ ERRORE:{Colors.RESET} {message}")
"""Print error message"""
self._print_formatted(message, "error")
def _print_info(self, message: str) -> None:
"""Print info message (in blue)"""
print(f"{Colors.BLUE}ℹ️ INFO:{Colors.RESET} {message}")
"""Print info message"""
self._print_formatted(message, "info")
def _highlight_code_blocks(self, text: str) -> str:
"""Apply syntax highlighting to code blocks in text"""
if not self.enable_syntax_highlight:
return text
# Pattern: ```language\ncode\n```
code_block_pattern = r"```(\w+)?\n(.*?)```"
def replace_block(match):
lang = match.group(1) or "text"
code = match.group(2)
if PYGMENTS_AVAILABLE and lang.lower() not in ["text", "txt", "ansi"]:
try:
lexer = SyntaxHighlighter.get_lexer(lang)
if lexer:
from pygments import highlight as pygments_highlight
from pygments.formatters import Terminal256Formatter
highlighted = pygments_highlight(code, lexer, Terminal256Formatter())
return highlighted.rstrip()
except:
pass
# No highlighting available, just return code with a header
return f"{self.ansi_theme['code']}--- {lang or 'code'} ---\n{code}{self.ansi_theme['reset']}"
return re.sub(code_block_pattern, replace_block, text, flags=re.DOTALL)
def _handle_command(self, command: str) -> Optional[bool]:
"""
Handle special commands (/command).
Returns:
True if command was handled and we should exit
False if command was handled but we should continue
None if command was not recognized
"""
"""Handle special commands (/command)."""
cmd_lower = command.lower().strip()
if cmd_lower == "/help":
self._print_system("""
╔═══════════════════════════════════════════════════════════════╗
║ COMANDI DISPONIBILI ║
╠═══════════════════════════════════════════════════════════════╣
║ /help - Mostra questo messaggio ║
║ /status - Mostra stato del sistema ║
║ /clear - Pulisci lo schermo ║
║ /theme - Cicla tra i temi (default/dark/light) ║
║ /history - Mostra cronologia comandi ║
║ /exit - Esci dall'agente ║
║ ║
║ Per messaggi normali, scrivi direttamente la tua domanda. ║
╚═══════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════════╗
║ COMANDI DISPONIBILI ║
╠══════════════════════════════════════════════════════════════════╣
║ /help - Mostra questo messaggio ║
║ /status - Mostra stato del sistema ║
║ /clear, Ctrl+L - Pulisci lo schermo ║
║ /theme - Cicla tra i temi ║
║ /theme-default - Tema default ║
║ /theme-dark - Tema scuro ║
║ /theme-light - Tema chiaro ║
║ /theme-monokai - Tema monokai ║
║ /timestamps - Toggle timestamps ║
║ /syntax - Toggle syntax highlighting ║
║ /width - Mostra larghezza terminale ║
║ /history - Mostra cronologia comandi ║
║ /exit, Ctrl+C - Esci dall'agente ║
╠══════════════════════════════════════════════════════════════════╣
║ SHORTCUTS: ║
║ Ctrl+A/E - Inizio/fine riga ║
║ Ctrl+K/U - Kill riga da cursore/fino a cursore ║
║ Ctrl+W - Kill parola precedente ║
║ ↑/↓ - History navigation ║
║ Tab - Autocomplete ║
║ Shift+Tab - Show suggestions ║
╚══════════════════════════════════════════════════════════════════╝
""")
return None
elif cmd_lower == "/status":
uptime = datetime.now() - self.stats["session_start"]
self._print_info(f"Theme: {self.current_theme}")
self._print_info(f"Timestamps: {'On' if self.show_timestamps else 'Off'}")
self._print_info(f"Syntax highlighting: {'On' if self.enable_syntax_highlight else 'Off'}")
self._print_info(f"Commands executed: {self.stats['commands_executed']}")
self._print_info(f"Session uptime: {uptime}")
self._print_info(f"Terminal width: {self.terminal_width}")
self._print_info(f"History file: {self.history_file}")
return None
@@ -280,12 +578,43 @@ class EnhancedCLI:
os.system("clear" if os.name != "nt" else "cls")
return None
elif cmd_lower == "/theme":
themes = ["default", "dark", "light"]
current_idx = themes.index(self.current_theme) if self.current_theme in themes else 0
next_theme = themes[(current_idx + 1) % len(themes)]
self.current_theme = next_theme
self._print_info(f"Tema cambiato a: {next_theme}")
elif cmd_lower in ["/theme", "/theme-default"]:
self.current_theme = "default"
self.ansi_theme = Theme.get_ansi_theme("default")
self._print_info("Tema cambiato a: default")
return None
elif cmd_lower == "/theme-dark":
self.current_theme = "dark"
self.ansi_theme = Theme.get_ansi_theme("dark")
self._print_info("Tema cambiato a: dark")
return None
elif cmd_lower == "/theme-light":
self.current_theme = "light"
self.ansi_theme = Theme.get_ansi_theme("light")
self._print_info("Tema cambiato a: light")
return None
elif cmd_lower == "/theme-monokai":
self.current_theme = "monokai"
self.ansi_theme = Theme.get_ansi_theme("monokai")
self._print_info("Tema cambiato a: monokai")
return None
elif cmd_lower == "/timestamps":
self.show_timestamps = not self.show_timestamps
self._print_info(f"Timestamps: {'On' if self.show_timestamps else 'Off'}")
return None
elif cmd_lower == "/syntax":
self.enable_syntax_highlight = not self.enable_syntax_highlight
self._print_info(f"Syntax highlighting: {'On' if self.enable_syntax_highlight else 'Off'}")
return None
elif cmd_lower == "/width":
self.terminal_width = shutil.get_terminal_size().columns
self._print_info(f"Terminal width: {self.terminal_width}")
return None
elif cmd_lower in ["/exit", "/quit"]:
@@ -296,9 +625,9 @@ class EnhancedCLI:
with open(self.history_file, "r") as f:
lines = f.readlines()
if lines:
self._print_system(f"Ultimi {min(10, len(lines))} comandi:")
for i, line in enumerate(lines[-10:], 1):
print(f" {i}. {line.strip()}")
self._print_system(f"Ultimi {min(20, len(lines))} comandi:")
for i, line in enumerate(lines[-20:], 1):
print(f" {i:3d}. {line.strip()}")
else:
self._print_info("Nessuna cronologia")
except FileNotFoundError:
@@ -311,23 +640,24 @@ class EnhancedCLI:
"""Get input from user with enhanced UI"""
try:
text = self.session.prompt(
self._create_prompt_tokens(),
style=create_style(self.current_theme)
self._get_prompt_tokens(),
style=Theme.get_style(self.current_theme),
)
return text
except KeyboardInterrupt:
return "/exit"
return ""
except EOFError:
return "/exit"
def run(self, agent_callback):
def run(self, agent_callback: Callable[[str], str]):
"""
Run the interactive CLI loop.
Args:
agent_callback: Function that takes user input and returns agent response
"""
self._print_system(self.welcome_message)
# Print welcome
print(self.welcome_message)
while True:
try:
@@ -339,6 +669,7 @@ class EnhancedCLI:
# Show what user typed
self._print_user(user_input)
self.stats["commands_executed"] += 1
# Check for commands
if user_input.startswith("/"):
@@ -351,6 +682,8 @@ class EnhancedCLI:
try:
response = agent_callback(user_input)
self._print_agent(response)
except KeyboardInterrupt:
self._print_system("\nInterruzione... Riprova.")
except Exception as e:
self._print_error(f"Errore: {str(e)}")
@@ -363,19 +696,30 @@ class EnhancedCLI:
self._print_system("Sessione terminata.")
# Factory function for easy use
# =============================================================================
# FACTORY FUNCTION
# =============================================================================
def create_enhanced_cli(
history_file: str = "./.langcode/.cli_history",
theme: str = "default"
theme: str = "default",
**kwargs
) -> EnhancedCLI:
"""Create an EnhancedCLI instance"""
return EnhancedCLI(history_file=history_file, theme=theme)
return EnhancedCLI(
history_file=history_file,
theme=theme,
**kwargs
)
# =============================================================================
# STANDALONE TEST
# =============================================================================
if __name__ == "__main__":
# Test the CLI
def test_callback(text):
return f"Hai detto: {text}"
return f"Hai detto: {text}\n\n```python\nprint('Hello!')\n```"
cli = create_enhanced_cli()
cli = create_enhanced_cli(theme="monokai")
cli.run(test_callback)