pub mod edit_plugin;
pub mod events;
pub mod edit_host;
pub mod plugin_manager;
pub mod event_handler;
pub mod context_gatherer;
pub mod ai_assistant;
pub mod streaming_handler;
pub mod config_integration;
use async_trait::async_trait;
use std::path::{Path, PathBuf};
use crate::lsp::{Position, Range};
use crate::core::error::IntegrationError;
use crate::tools::Permission;
#[async_trait]
pub trait HostIntegration: Send + Sync {
async fn on_event(&self, event: CoderEvent) -> Result<(), IntegrationError>;
async fn request_permission(&self, permission: Permission) -> Result<bool, IntegrationError>;
async fn get_file_content(&self, path: &Path) -> Result<String, IntegrationError>;
async fn update_file_content(&self, path: &Path, content: &str) -> Result<(), IntegrationError>;
async fn get_cursor_position(&self) -> Result<Position, IntegrationError>;
async fn set_cursor_position(&self, position: Position) -> Result<(), IntegrationError>;
async fn get_selection(&self) -> Result<Option<Range>, IntegrationError>;
async fn set_selection(&self, range: Range) -> Result<(), IntegrationError>;
async fn insert_text(&self, text: &str) -> Result<(), IntegrationError>;
async fn replace_text(&self, range: Range, text: &str) -> Result<(), IntegrationError>;
async fn get_active_file(&self) -> Result<Option<PathBuf>, IntegrationError>;
async fn get_open_files(&self) -> Result<Vec<PathBuf>, IntegrationError>;
async fn get_project_root(&self) -> Result<Option<PathBuf>, IntegrationError>;
async fn show_message(&self, message: &str, level: MessageLevel) -> Result<(), IntegrationError>;
async fn show_notification(&self, title: &str, message: &str) -> Result<(), IntegrationError>;
async fn request_input(&self, prompt: &str, default: Option<&str>) -> Result<Option<String>, IntegrationError>;
async fn confirm(&self, message: &str) -> Result<bool, IntegrationError>;
async fn execute_command(&self, command: &str, args: &[&str]) -> Result<String, IntegrationError>;
fn get_host_info(&self) -> HostInfo;
}
#[derive(Debug, Clone)]
pub enum CoderEvent {
ProcessingStarted {
session_id: String,
},
ResponseChunk {
session_id: String,
content: String,
},
ProcessingCompleted {
session_id: String,
response: String,
},
ProcessingFailed {
session_id: String,
error: String,
},
ToolExecutionStarted {
session_id: String,
tool_name: String,
parameters: serde_json::Value,
},
ToolExecutionCompleted {
session_id: String,
tool_name: String,
result: String,
success: bool,
},
FileModified {
path: PathBuf,
changes: Vec<TextEdit>,
},
StatusUpdate {
message: String,
},
ProgressUpdate {
current: u32,
total: u32,
message: String,
},
SessionCreated {
session_id: String,
title: String,
},
SessionUpdated {
session_id: String,
title: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum MessageLevel {
Info,
Warning,
Error,
Success,
}
#[derive(Debug, Clone)]
pub struct HostInfo {
pub name: String,
pub version: String,
pub capabilities: HostCapabilities,
pub metadata: serde_json::Value,
}
#[derive(Debug, Clone)]
pub struct HostCapabilities {
pub file_modification: bool,
pub ui_dialogs: bool,
pub command_execution: bool,
pub notifications: bool,
pub project_access: bool,
pub cursor_control: bool,
pub syntax_highlighting: bool,
pub multi_file: bool,
}
#[derive(Debug, Clone)]
pub struct TextEdit {
pub range: Option<Range>,
pub new_text: String,
pub description: Option<String>,
}
pub trait Plugin: Send + Sync {
fn name(&self) -> &str;
fn version(&self) -> &str;
fn initialize(&mut self, context: PluginContext) -> Result<(), IntegrationError>;
fn handle_event(&mut self, event: HostEvent) -> Result<Option<HostCommand>, IntegrationError>;
fn shutdown(&mut self) -> Result<(), IntegrationError>;
}
#[derive(Debug, Clone)]
pub struct PluginContext {
pub host_info: HostInfo,
pub config: serde_json::Value,
pub data_dir: PathBuf,
}
#[derive(Debug, Clone)]
pub enum HostEvent {
ApplicationStarted,
ApplicationShutdown,
FileOpened(PathBuf),
FileClosed(PathBuf),
FileSaved(PathBuf),
FileModified(PathBuf),
CursorMoved(Position),
SelectionChanged(Option<Range>),
KeyPressed(String),
CommandExecuted(String),
ProjectOpened(PathBuf),
ProjectClosed,
}
#[derive(Debug, Clone)]
pub enum HostCommand {
ShowMessage {
message: String,
level: MessageLevel,
},
ExecuteCommand {
command: String,
args: Vec<String>,
},
ModifyFile {
path: PathBuf,
edits: Vec<TextEdit>,
},
SetCursor(Position),
SetSelection(Range),
InsertText(String),
OpenFile(PathBuf),
SaveFile(PathBuf),
ShowDialog {
title: String,
message: String,
buttons: Vec<String>,
},
}
impl Default for HostCapabilities {
fn default() -> Self {
Self {
file_modification: true,
ui_dialogs: true,
command_execution: false,
notifications: true,
project_access: true,
cursor_control: true,
syntax_highlighting: false,
multi_file: true,
}
}
}
impl TextEdit {
pub fn insert(text: String) -> Self {
Self {
range: None,
new_text: text,
description: None,
}
}
pub fn replace(range: Range, text: String) -> Self {
Self {
range: Some(range),
new_text: text,
description: None,
}
}
pub fn with_description(mut self, description: String) -> Self {
self.description = Some(description);
self
}
}
impl std::fmt::Display for MessageLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MessageLevel::Info => write!(f, "info"),
MessageLevel::Warning => write!(f, "warning"),
MessageLevel::Error => write!(f, "error"),
MessageLevel::Success => write!(f, "success"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_text_edit_creation() {
let insert_edit = TextEdit::insert("Hello, world!".to_string());
assert!(insert_edit.range.is_none());
assert_eq!(insert_edit.new_text, "Hello, world!");
let range = Range {
start: Position { line: 0, character: 0 },
end: Position { line: 0, character: 5 },
};
let replace_edit = TextEdit::replace(range, "Hi".to_string());
assert!(replace_edit.range.is_some());
assert_eq!(replace_edit.new_text, "Hi");
}
#[test]
fn test_message_level_display() {
assert_eq!(MessageLevel::Info.to_string(), "info");
assert_eq!(MessageLevel::Warning.to_string(), "warning");
assert_eq!(MessageLevel::Error.to_string(), "error");
assert_eq!(MessageLevel::Success.to_string(), "success");
}
#[test]
fn test_host_capabilities_default() {
let caps = HostCapabilities::default();
assert!(caps.file_modification);
assert!(caps.ui_dialogs);
assert!(!caps.command_execution); assert!(caps.notifications);
}
#[test]
fn test_coder_event_creation() {
let event = CoderEvent::ProcessingStarted {
session_id: "test-session".to_string(),
};
match event {
CoderEvent::ProcessingStarted { session_id } => {
assert_eq!(session_id, "test-session");
}
_ => panic!("Expected ProcessingStarted event"),
}
}
}
pub use edit_host::*;
pub use plugin_manager::*;
pub use event_handler::*;
pub use context_gatherer::*;
pub use ai_assistant::*;
pub use streaming_handler::*;
pub use config_integration::*;
pub struct MockHost;
#[async_trait]
impl HostIntegration for MockHost {
async fn on_event(&self, _event: CoderEvent) -> Result<(), IntegrationError> {
Ok(())
}
async fn request_permission(&self, _permission: Permission) -> Result<bool, IntegrationError> {
Ok(true)
}
async fn get_file_content(&self, path: &Path) -> Result<String, IntegrationError> {
tokio::fs::read_to_string(path).await
.map_err(|e| IntegrationError::FileNotFound(path.to_path_buf()))
}
async fn update_file_content(&self, path: &Path, content: &str) -> Result<(), IntegrationError> {
tokio::fs::write(path, content).await
.map_err(|e| IntegrationError::OperationFailed(e.to_string()))
}
async fn get_cursor_position(&self) -> Result<Position, IntegrationError> {
Ok(Position { line: 0, character: 0 })
}
async fn get_selection(&self) -> Result<Option<Range>, IntegrationError> {
Ok(None)
}
async fn set_cursor_position(&self, _position: Position) -> Result<(), IntegrationError> {
Ok(())
}
async fn set_selection(&self, _range: Range) -> Result<(), IntegrationError> {
Ok(())
}
async fn insert_text(&self, _text: &str) -> Result<(), IntegrationError> {
Ok(())
}
async fn replace_text(&self, _range: Range, _text: &str) -> Result<(), IntegrationError> {
Ok(())
}
async fn get_active_file(&self) -> Result<Option<PathBuf>, IntegrationError> {
Ok(None)
}
async fn get_open_files(&self) -> Result<Vec<PathBuf>, IntegrationError> {
Ok(Vec::new())
}
async fn get_project_root(&self) -> Result<Option<PathBuf>, IntegrationError> {
Ok(Some(std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))))
}
async fn show_message(&self, _message: &str, _level: MessageLevel) -> Result<(), IntegrationError> {
Ok(())
}
async fn show_notification(&self, _title: &str, _message: &str) -> Result<(), IntegrationError> {
Ok(())
}
async fn request_input(&self, _prompt: &str, _default: Option<&str>) -> Result<Option<String>, IntegrationError> {
Ok(Some("mock input".to_string()))
}
async fn confirm(&self, _message: &str) -> Result<bool, IntegrationError> {
Ok(true)
}
async fn execute_command(&self, command: &str, args: &[&str]) -> Result<String, IntegrationError> {
Ok(format!("Mock execution: {} {:?}", command, args))
}
fn get_host_info(&self) -> HostInfo {
HostInfo {
name: "Mock Host".to_string(),
version: "1.0.0".to_string(),
capabilities: HostCapabilities::default(),
metadata: serde_json::json!({}),
}
}
}