use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::{mpsc, RwLock};
use tracing::info;
use crate::core::{CoderLib, CoderLibConfig, CoderLibError};
use crate::integration::{EditHost, EditConfig, EditCommand, EditResponse, EditState};
use crate::llm::ProviderFactory;
#[derive(Debug, Clone, PartialEq)]
pub enum PluginState {
Uninitialized,
Initializing,
Active,
Paused,
Error(String),
ShuttingDown,
}
pub struct EditPluginManager {
state: Arc<RwLock<PluginState>>,
coderlib: Option<Arc<CoderLib>>,
edit_host: Option<Arc<EditHost>>,
config: Option<CoderLibConfig>,
edit_config: EditConfig,
command_receiver: Option<mpsc::UnboundedReceiver<EditCommand>>,
response_sender: Option<mpsc::UnboundedSender<EditResponse>>,
plugin_dir: PathBuf,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginInitParams {
pub config_path: Option<PathBuf>,
pub edit_config: EditConfig,
pub plugin_dir: PathBuf,
pub initial_state: EditState,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginCapabilities {
pub name: String,
pub version: String,
pub edit_version_range: String,
pub providers: Vec<String>,
pub tools: Vec<String>,
pub supported_file_types: Vec<String>,
pub required_permissions: Vec<String>,
}
impl EditPluginManager {
pub fn new() -> Self {
Self {
state: Arc::new(RwLock::new(PluginState::Uninitialized)),
coderlib: None,
edit_host: None,
config: None,
edit_config: EditConfig::default(),
command_receiver: None,
response_sender: None,
plugin_dir: PathBuf::from("."),
}
}
pub fn get_capabilities() -> PluginCapabilities {
PluginCapabilities {
name: "CoderLib".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
edit_version_range: ">=1.0.0".to_string(),
providers: ProviderFactory::available_providers().into_iter().map(|s| s.to_string()).collect(),
tools: vec![
"file_read".to_string(),
"file_write".to_string(),
"file_list".to_string(),
"shell_command".to_string(),
"file_search".to_string(),
"content_search".to_string(),
"code_analysis".to_string(),
"git".to_string(),
],
supported_file_types: vec![
"rust".to_string(),
"python".to_string(),
"javascript".to_string(),
"typescript".to_string(),
"go".to_string(),
"java".to_string(),
"c".to_string(),
"cpp".to_string(),
"csharp".to_string(),
"html".to_string(),
"css".to_string(),
"json".to_string(),
"yaml".to_string(),
"toml".to_string(),
"markdown".to_string(),
],
required_permissions: vec![
"file_read".to_string(),
"file_write".to_string(),
"command_execute".to_string(),
"network_access".to_string(),
],
}
}
pub async fn initialize(&mut self, params: PluginInitParams) -> Result<(), CoderLibError> {
info!("Initializing CoderLib plugin for Edit");
{
let mut state = self.state.write().await;
*state = PluginState::Initializing;
}
self.edit_config = params.edit_config.clone();
self.plugin_dir = params.plugin_dir;
let config = if let Some(config_path) = params.config_path {
CoderLibConfig::load_from_file(&config_path)?
} else {
let mut config = CoderLibConfig::default();
config.debug = false;
config.log_level = "info".to_string();
config
};
self.config = Some(config.clone());
let mut edit_host = EditHost::new(params.edit_config);
edit_host.update_state(params.initial_state);
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
let (resp_tx, resp_rx) = mpsc::unbounded_channel();
edit_host.set_event_sender(cmd_tx);
self.command_receiver = Some(cmd_rx);
self.response_sender = Some(resp_tx);
let edit_host = Arc::new(edit_host);
self.edit_host = Some(edit_host.clone());
let coderlib = CoderLib::new(config).await?;
self.coderlib = Some(Arc::new(coderlib));
{
let mut state = self.state.write().await;
*state = PluginState::Active;
}
info!("CoderLib plugin initialized successfully");
Ok(())
}
pub async fn get_state(&self) -> PluginState {
self.state.read().await.clone()
}
pub async fn pause(&mut self) -> Result<(), CoderLibError> {
let mut state = self.state.write().await;
if *state == PluginState::Active {
*state = PluginState::Paused;
info!("CoderLib plugin paused");
}
Ok(())
}
pub async fn resume(&mut self) -> Result<(), CoderLibError> {
let mut state = self.state.write().await;
if *state == PluginState::Paused {
*state = PluginState::Active;
info!("CoderLib plugin resumed");
}
Ok(())
}
pub async fn shutdown(&mut self) -> Result<(), CoderLibError> {
info!("Shutting down CoderLib plugin");
{
let mut state = self.state.write().await;
*state = PluginState::ShuttingDown;
}
self.coderlib = None;
self.edit_host = None;
self.command_receiver = None;
self.response_sender = None;
info!("CoderLib plugin shutdown complete");
Ok(())
}
pub async fn handle_command(&self, command: EditCommand) -> Result<EditResponse, CoderLibError> {
let state = self.get_state().await;
if state != PluginState::Active {
return Ok(EditResponse::Error {
message: format!("Plugin not active (state: {:?})", state),
});
}
match command {
EditCommand::ShowAIAssistant { initial_prompt } => {
self.handle_ai_assistant_request(initial_prompt).await
}
EditCommand::OpenFile { path } => {
if let Some(edit_host) = &self.edit_host {
let mut state = edit_host.get_state();
state.current_file = Some(path.clone());
if !state.open_files.contains(&path) {
state.open_files.push(path);
}
edit_host.update_state(state);
}
Ok(EditResponse::Success)
}
EditCommand::CloseFile { path } => {
if let Some(edit_host) = &self.edit_host {
let mut state = edit_host.get_state();
state.open_files.retain(|f| f != &path);
if state.current_file.as_ref() == Some(&path) {
state.current_file = state.open_files.first().cloned();
}
edit_host.update_state(state);
}
Ok(EditResponse::Success)
}
EditCommand::MoveCursor { position } => {
if let Some(edit_host) = &self.edit_host {
let mut state = edit_host.get_state();
state.cursor_position = position;
edit_host.update_state(state);
}
Ok(EditResponse::Success)
}
EditCommand::SetSelection { range } => {
if let Some(edit_host) = &self.edit_host {
let mut state = edit_host.get_state();
state.selection = Some(range);
edit_host.update_state(state);
}
Ok(EditResponse::Success)
}
_ => {
Ok(EditResponse::Success)
}
}
}
async fn handle_ai_assistant_request(&self, initial_prompt: Option<String>) -> Result<EditResponse, CoderLibError> {
if let (Some(_coderlib), Some(edit_host)) = (&self.coderlib, &self.edit_host) {
let context = edit_host.gather_intelligent_context().await?;
let _prompt = if let Some(initial) = initial_prompt {
format!("{}\n\nContext:\n{}", initial, context)
} else {
format!("How can I help you with your code?\n\nContext:\n{}", context)
};
Ok(EditResponse::Success)
} else {
Ok(EditResponse::Error {
message: "CoderLib not properly initialized".to_string(),
})
}
}
pub fn get_coderlib(&self) -> Option<Arc<CoderLib>> {
self.coderlib.clone()
}
pub fn get_edit_host(&self) -> Option<Arc<EditHost>> {
self.edit_host.clone()
}
pub async fn update_editor_state(&self, state: EditState) -> Result<(), CoderLibError> {
if let Some(edit_host) = &self.edit_host {
edit_host.update_state(state);
}
Ok(())
}
}
impl Default for EditPluginManager {
fn default() -> Self {
Self::new()
}
}