use crate::error::CliError;
use crate::session::SessionManager;
use crate::tui::TuiState;
use limit_tui::components::{ChatView, Message};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, PartialEq)]
pub enum CommandResult {
Continue,
Exit,
ClearChat,
Message(String),
NewSession,
LoadSession(String),
Share(String),
}
pub struct CommandContext {
pub chat_view: Arc<Mutex<ChatView>>,
pub session_manager: Arc<Mutex<SessionManager>>,
pub session_id: String,
pub state: Arc<Mutex<TuiState>>,
pub messages: Arc<Mutex<Vec<limit_llm::Message>>>,
pub total_input_tokens: Arc<Mutex<u64>>,
pub total_output_tokens: Arc<Mutex<u64>>,
pub clipboard: Option<Arc<Mutex<crate::clipboard::ClipboardManager>>>,
pub project_path: std::path::PathBuf,
}
impl CommandContext {
#[allow(clippy::too_many_arguments)]
pub fn new(
chat_view: Arc<Mutex<ChatView>>,
session_manager: Arc<Mutex<SessionManager>>,
session_id: String,
state: Arc<Mutex<TuiState>>,
messages: Arc<Mutex<Vec<limit_llm::Message>>>,
total_input_tokens: Arc<Mutex<u64>>,
total_output_tokens: Arc<Mutex<u64>>,
clipboard: Option<Arc<Mutex<crate::clipboard::ClipboardManager>>>,
project_path: std::path::PathBuf,
) -> Self {
Self {
chat_view,
session_manager,
session_id,
state,
messages,
total_input_tokens,
total_output_tokens,
clipboard,
project_path,
}
}
pub fn add_system_message(&self, text: String) {
let msg = Message::system(text);
self.chat_view.lock().unwrap().add_message(msg);
}
pub fn add_user_message(&self, text: String) {
let msg = Message::user(text);
self.chat_view.lock().unwrap().add_message(msg);
}
pub fn clear_chat(&self) {
self.chat_view.lock().unwrap().clear();
}
}
pub trait Command: Send + Sync {
fn name(&self) -> &str;
fn aliases(&self) -> Vec<&str> {
vec![]
}
fn description(&self) -> &str;
fn usage(&self) -> Vec<&str> {
vec![]
}
fn execute(&self, args: &str, ctx: &mut CommandContext) -> Result<CommandResult, CliError>;
}
pub struct CommandRegistry {
commands: HashMap<String, Box<dyn Command>>,
}
impl CommandRegistry {
pub fn new() -> Self {
Self {
commands: HashMap::new(),
}
}
pub fn register(&mut self, command: Box<dyn Command>) {
let name = command.name().to_string();
self.commands.insert(name, command);
}
#[inline]
pub fn list_commands(&self) -> Vec<&dyn Command> {
self.commands.values().map(|c| c.as_ref()).collect()
}
pub fn parse_and_execute(
&self,
input: &str,
ctx: &mut CommandContext,
) -> Result<Option<CommandResult>, CliError> {
let input = input.trim();
if !input.starts_with('/') {
return Ok(None);
}
let input = &input[1..]; let parts: Vec<&str> = input.splitn(2, ' ').collect();
let cmd_name = parts[0].to_lowercase();
let args = parts.get(1).copied().unwrap_or("");
for command in self.commands.values() {
if command.name() == cmd_name || command.aliases().contains(&cmd_name.as_str()) {
return Ok(Some(command.execute(args, ctx)?));
}
}
ctx.add_system_message(format!("Unknown command: /{}", cmd_name));
ctx.add_system_message("Type /help for available commands".to_string());
Ok(Some(CommandResult::Continue))
}
}
impl Default for CommandRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_command_registry_creation() {
let registry = CommandRegistry::new();
assert_eq!(registry.list_commands().len(), 0);
}
#[test]
fn test_command_registry_default() {
let registry = CommandRegistry::default();
assert_eq!(registry.list_commands().len(), 0);
}
}