use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use tokio::fs;
use tracing::{debug, info};
use crate::core::{CoderLibConfig, CoderLibError};
use crate::core::error::ConfigError;
use crate::integration::EditHost;
pub struct EditConfigIntegration {
edit_config_dir: PathBuf,
coderlib_config_path: PathBuf,
cached_config: std::sync::RwLock<Option<EditIntegratedConfig>>,
watchers: std::sync::RwLock<Vec<ConfigWatcher>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EditIntegratedConfig {
pub coderlib: CoderLibConfig,
pub edit: EditSettings,
pub ui: UIIntegrationSettings,
pub hotkeys: HotkeyBindings,
pub plugins: PluginSettings,
pub advanced: AdvancedSettings,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EditSettings {
pub auto_save_interval: u64,
pub show_line_numbers: bool,
pub tab_size: usize,
pub use_spaces: bool,
pub word_wrap: bool,
pub theme: String,
pub font_family: String,
pub font_size: u32,
pub show_whitespace: bool,
pub highlight_current_line: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UIIntegrationSettings {
pub ai_panel_position: PanelPosition,
pub ai_panel_width: f32,
pub ai_panel_height: f32,
pub show_inline_suggestions: bool,
pub auto_show_on_errors: bool,
pub show_token_usage: bool,
pub animation_duration: u32,
pub transparency: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum PanelPosition {
Right,
Left,
Bottom,
Top,
Floating { x: i32, y: i32 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HotkeyBindings {
pub ai_assistant: String,
pub explain_code: String,
pub refactor_selection: String,
pub generate_tests: String,
pub fix_errors: String,
pub format_code: String,
pub toggle_ai_panel: String,
pub accept_suggestion: String,
pub reject_suggestion: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginSettings {
pub enabled: bool,
pub auto_start: bool,
pub priority: i32,
pub allowed_permissions: Vec<String>,
pub resource_limits: ResourceLimits,
pub updates: UpdateSettings,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceLimits {
pub max_memory_mb: u64,
pub max_cpu_percent: f32,
pub max_network_requests: u32,
pub max_file_operations: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateSettings {
pub auto_check: bool,
pub channel: String,
pub check_interval_hours: u32,
pub auto_install: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdvancedSettings {
pub debug_mode: bool,
pub log_level: String,
pub performance_monitoring: bool,
pub telemetry_enabled: bool,
pub environment_variables: HashMap<String, String>,
pub feature_flags: HashMap<String, bool>,
pub experimental_features: Vec<String>,
}
pub struct ConfigWatcher {
pub path: PathBuf,
pub last_modified: std::time::SystemTime,
pub callback: Box<dyn Fn(&Path) + Send + Sync>,
}
impl std::fmt::Debug for ConfigWatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ConfigWatcher")
.field("path", &self.path)
.field("last_modified", &self.last_modified)
.field("callback", &"<callback function>")
.finish()
}
}
impl Default for EditSettings {
fn default() -> Self {
Self {
auto_save_interval: 30,
show_line_numbers: true,
tab_size: 4,
use_spaces: true,
word_wrap: false,
theme: "dark".to_string(),
font_family: "Consolas".to_string(),
font_size: 14,
show_whitespace: false,
highlight_current_line: true,
}
}
}
impl Default for UIIntegrationSettings {
fn default() -> Self {
Self {
ai_panel_position: PanelPosition::Right,
ai_panel_width: 40.0,
ai_panel_height: 60.0,
show_inline_suggestions: true,
auto_show_on_errors: false,
show_token_usage: true,
animation_duration: 300,
transparency: 0.95,
}
}
}
impl Default for HotkeyBindings {
fn default() -> Self {
Self {
ai_assistant: "Ctrl+I".to_string(),
explain_code: "Ctrl+Shift+E".to_string(),
refactor_selection: "Ctrl+Shift+R".to_string(),
generate_tests: "Ctrl+Shift+T".to_string(),
fix_errors: "Ctrl+Shift+F".to_string(),
format_code: "Ctrl+Shift+P".to_string(),
toggle_ai_panel: "Ctrl+Alt+A".to_string(),
accept_suggestion: "Tab".to_string(),
reject_suggestion: "Escape".to_string(),
}
}
}
impl Default for PluginSettings {
fn default() -> Self {
Self {
enabled: true,
auto_start: true,
priority: 100,
allowed_permissions: vec![
"file_read".to_string(),
"file_write".to_string(),
"network_access".to_string(),
],
resource_limits: ResourceLimits::default(),
updates: UpdateSettings::default(),
}
}
}
impl Default for ResourceLimits {
fn default() -> Self {
Self {
max_memory_mb: 512,
max_cpu_percent: 25.0,
max_network_requests: 100,
max_file_operations: 50,
}
}
}
impl Default for UpdateSettings {
fn default() -> Self {
Self {
auto_check: true,
channel: "stable".to_string(),
check_interval_hours: 24,
auto_install: false,
}
}
}
impl Default for AdvancedSettings {
fn default() -> Self {
Self {
debug_mode: false,
log_level: "info".to_string(),
performance_monitoring: false,
telemetry_enabled: true,
environment_variables: HashMap::new(),
feature_flags: HashMap::new(),
experimental_features: Vec::new(),
}
}
}
impl EditConfigIntegration {
pub fn new(edit_config_dir: PathBuf) -> Self {
let coderlib_config_path = edit_config_dir.join("coderlib.toml");
Self {
edit_config_dir,
coderlib_config_path,
cached_config: std::sync::RwLock::new(None),
watchers: std::sync::RwLock::new(Vec::new()),
}
}
pub async fn load_config(&self) -> Result<EditIntegratedConfig, CoderLibError> {
info!("Loading integrated configuration");
if let Ok(cache) = self.cached_config.read() {
if let Some(config) = cache.as_ref() {
debug!("Using cached configuration");
return Ok(config.clone());
}
}
let coderlib_config = if self.coderlib_config_path.exists() {
self.load_coderlib_config().await?
} else {
info!("CoderLib config not found, using defaults");
CoderLibConfig::default()
};
let edit_settings = self.load_edit_settings().await?;
let ui_settings = self.load_ui_settings().await?;
let hotkeys = self.load_hotkey_bindings().await?;
let plugins = self.load_plugin_settings().await?;
let advanced = self.load_advanced_settings().await?;
let integrated_config = EditIntegratedConfig {
coderlib: coderlib_config,
edit: edit_settings,
ui: ui_settings,
hotkeys,
plugins,
advanced,
};
if let Ok(mut cache) = self.cached_config.write() {
*cache = Some(integrated_config.clone());
}
Ok(integrated_config)
}
pub async fn save_config(&self, config: &EditIntegratedConfig) -> Result<(), CoderLibError> {
info!("Saving integrated configuration");
self.save_coderlib_config(&config.coderlib).await?;
self.save_edit_settings(&config.edit).await?;
self.save_ui_settings(&config.ui).await?;
self.save_hotkey_bindings(&config.hotkeys).await?;
self.save_plugin_settings(&config.plugins).await?;
self.save_advanced_settings(&config.advanced).await?;
if let Ok(mut cache) = self.cached_config.write() {
*cache = Some(config.clone());
}
info!("Configuration saved successfully");
Ok(())
}
async fn load_coderlib_config(&self) -> Result<CoderLibConfig, CoderLibError> {
let content = fs::read_to_string(&self.coderlib_config_path).await
.map_err(|e| CoderLibError::Config(ConfigError::LoadFailed(format!("Failed to read config: {}", e))))?;
toml::from_str(&content)
.map_err(|e| CoderLibError::Config(ConfigError::ParseFailed(format!("Failed to parse config: {}", e))))
}
async fn save_coderlib_config(&self, config: &CoderLibConfig) -> Result<(), CoderLibError> {
let content = toml::to_string_pretty(config)
.map_err(|e| CoderLibError::Config(ConfigError::ParseFailed(format!("Failed to serialize config: {}", e))))?;
if let Some(parent) = self.coderlib_config_path.parent() {
fs::create_dir_all(parent).await
.map_err(|e| CoderLibError::Io(e))?;
}
fs::write(&self.coderlib_config_path, content).await
.map_err(|e| CoderLibError::Io(e))?;
Ok(())
}
async fn load_edit_settings(&self) -> Result<EditSettings, CoderLibError> {
let settings_path = self.edit_config_dir.join("edit_settings.toml");
if settings_path.exists() {
let content = fs::read_to_string(&settings_path).await
.map_err(|e| CoderLibError::Config(ConfigError::LoadFailed(format!("Failed to read Edit settings: {}", e))))?;
toml::from_str(&content)
.map_err(|e| CoderLibError::Config(ConfigError::ParseFailed(format!("Failed to parse Edit settings: {}", e))))
} else {
Ok(EditSettings::default())
}
}
async fn save_edit_settings(&self, settings: &EditSettings) -> Result<(), CoderLibError> {
let settings_path = self.edit_config_dir.join("edit_settings.toml");
let content = toml::to_string_pretty(settings)
.map_err(|e| CoderLibError::Config(ConfigError::ParseFailed(format!("Failed to serialize Edit settings: {}", e))))?;
fs::write(&settings_path, content).await
.map_err(|e| CoderLibError::Io(e))?;
Ok(())
}
async fn load_ui_settings(&self) -> Result<UIIntegrationSettings, CoderLibError> {
let settings_path = self.edit_config_dir.join("ui_integration.toml");
if settings_path.exists() {
let content = fs::read_to_string(&settings_path).await
.map_err(|e| CoderLibError::Config(ConfigError::LoadFailed(format!("Failed to read UI settings: {}", e))))?;
toml::from_str(&content)
.map_err(|e| CoderLibError::Config(ConfigError::ParseFailed(format!("Failed to parse UI settings: {}", e))))
} else {
Ok(UIIntegrationSettings::default())
}
}
async fn save_ui_settings(&self, settings: &UIIntegrationSettings) -> Result<(), CoderLibError> {
let settings_path = self.edit_config_dir.join("ui_integration.toml");
let content = toml::to_string_pretty(settings)
.map_err(|e| CoderLibError::Config(ConfigError::ParseFailed(format!("Failed to serialize UI settings: {}", e))))?;
fs::write(&settings_path, content).await
.map_err(|e| CoderLibError::Io(e))?;
Ok(())
}
async fn load_hotkey_bindings(&self) -> Result<HotkeyBindings, CoderLibError> {
let bindings_path = self.edit_config_dir.join("hotkeys.toml");
if bindings_path.exists() {
let content = fs::read_to_string(&bindings_path).await
.map_err(|e| CoderLibError::Config(ConfigError::LoadFailed(format!("Failed to read hotkey bindings: {}", e))))?;
toml::from_str(&content)
.map_err(|e| CoderLibError::Config(ConfigError::ParseFailed(format!("Failed to parse hotkey bindings: {}", e))))
} else {
Ok(HotkeyBindings::default())
}
}
async fn save_hotkey_bindings(&self, bindings: &HotkeyBindings) -> Result<(), CoderLibError> {
let bindings_path = self.edit_config_dir.join("hotkeys.toml");
let content = toml::to_string_pretty(bindings)
.map_err(|e| CoderLibError::Config(ConfigError::ParseFailed(format!("Failed to serialize hotkey bindings: {}", e))))?;
fs::write(&bindings_path, content).await
.map_err(|e| CoderLibError::Io(e))?;
Ok(())
}
async fn load_plugin_settings(&self) -> Result<PluginSettings, CoderLibError> {
let settings_path = self.edit_config_dir.join("plugin_settings.toml");
if settings_path.exists() {
let content = fs::read_to_string(&settings_path).await
.map_err(|e| CoderLibError::Config(ConfigError::LoadFailed(format!("Failed to read plugin settings: {}", e))))?;
toml::from_str(&content)
.map_err(|e| CoderLibError::Config(ConfigError::ParseFailed(format!("Failed to parse plugin settings: {}", e))))
} else {
Ok(PluginSettings::default())
}
}
async fn save_plugin_settings(&self, settings: &PluginSettings) -> Result<(), CoderLibError> {
let settings_path = self.edit_config_dir.join("plugin_settings.toml");
let content = toml::to_string_pretty(settings)
.map_err(|e| CoderLibError::Config(ConfigError::ParseFailed(format!("Failed to serialize plugin settings: {}", e))))?;
fs::write(&settings_path, content).await
.map_err(|e| CoderLibError::Io(e))?;
Ok(())
}
async fn load_advanced_settings(&self) -> Result<AdvancedSettings, CoderLibError> {
let settings_path = self.edit_config_dir.join("advanced_settings.toml");
if settings_path.exists() {
let content = fs::read_to_string(&settings_path).await
.map_err(|e| CoderLibError::Config(ConfigError::LoadFailed(format!("Failed to read advanced settings: {}", e))))?;
toml::from_str(&content)
.map_err(|e| CoderLibError::Config(ConfigError::ParseFailed(format!("Failed to parse advanced settings: {}", e))))
} else {
Ok(AdvancedSettings::default())
}
}
async fn save_advanced_settings(&self, settings: &AdvancedSettings) -> Result<(), CoderLibError> {
let settings_path = self.edit_config_dir.join("advanced_settings.toml");
let content = toml::to_string_pretty(settings)
.map_err(|e| CoderLibError::Config(ConfigError::ParseFailed(format!("Failed to serialize advanced settings: {}", e))))?;
fs::write(&settings_path, content).await
.map_err(|e| CoderLibError::Io(e))?;
Ok(())
}
pub async fn start_watching(&self) -> Result<(), CoderLibError> {
info!("Starting configuration file watching");
let config_files = vec![
self.coderlib_config_path.clone(),
self.edit_config_dir.join("edit_settings.toml"),
self.edit_config_dir.join("ui_integration.toml"),
self.edit_config_dir.join("hotkeys.toml"),
self.edit_config_dir.join("plugin_settings.toml"),
self.edit_config_dir.join("advanced_settings.toml"),
];
let mut watchers = self.watchers.write().unwrap();
for config_file in config_files {
if config_file.exists() {
let metadata = fs::metadata(&config_file).await
.map_err(|e| CoderLibError::Io(e))?;
let last_modified = metadata.modified()
.map_err(|e| CoderLibError::Io(e))?;
let watcher = ConfigWatcher {
path: config_file.clone(),
last_modified,
callback: Box::new(move |path| {
info!("Configuration file changed: {}", path.display());
}),
};
watchers.push(watcher);
}
}
info!("Configuration watching started for {} files", watchers.len());
Ok(())
}
pub fn stop_watching(&self) {
let mut watchers = self.watchers.write().unwrap();
watchers.clear();
info!("Configuration watching stopped");
}
pub async fn check_for_changes(&self) -> Result<Vec<PathBuf>, CoderLibError> {
let mut changed_files = Vec::new();
let mut watchers = self.watchers.write().unwrap();
for watcher in watchers.iter_mut() {
if watcher.path.exists() {
let metadata = fs::metadata(&watcher.path).await
.map_err(|e| CoderLibError::Io(e))?;
let current_modified = metadata.modified()
.map_err(|e| CoderLibError::Io(e))?;
if current_modified > watcher.last_modified {
changed_files.push(watcher.path.clone());
watcher.last_modified = current_modified;
(watcher.callback)(&watcher.path);
}
}
}
if !changed_files.is_empty() {
if let Ok(mut cache) = self.cached_config.write() {
*cache = None;
}
}
Ok(changed_files)
}
pub async fn apply_to_edit_host(&self, edit_host: &mut EditHost, config: &EditIntegratedConfig) -> Result<(), CoderLibError> {
info!("Applying configuration to Edit host");
debug!("Applied Edit settings: theme={}, font_size={}", config.edit.theme, config.edit.font_size);
debug!("Applied UI settings: panel_position={:?}, panel_width={}", config.ui.ai_panel_position, config.ui.ai_panel_width);
debug!("Applied hotkey bindings: ai_assistant={}", config.hotkeys.ai_assistant);
debug!("Applied plugin settings: enabled={}, auto_start={}", config.plugins.enabled, config.plugins.auto_start);
info!("Configuration applied successfully to Edit host");
Ok(())
}
pub fn validate_config(&self, config: &EditIntegratedConfig) -> Result<(), CoderLibError> {
if config.edit.tab_size == 0 {
return Err(CoderLibError::Config(ConfigError::InvalidValue("Tab size must be greater than 0".to_string())));
}
if config.edit.font_size < 8 || config.edit.font_size > 72 {
return Err(CoderLibError::Config(ConfigError::InvalidValue("Font size must be between 8 and 72".to_string())));
}
if config.ui.ai_panel_width <= 0.0 || config.ui.ai_panel_width > 100.0 {
return Err(CoderLibError::Config(ConfigError::InvalidValue("AI panel width must be between 0 and 100 percent".to_string())));
}
if config.ui.ai_panel_height <= 0.0 || config.ui.ai_panel_height > 100.0 {
return Err(CoderLibError::Config(ConfigError::InvalidValue("AI panel height must be between 0 and 100 percent".to_string())));
}
if config.ui.transparency < 0.0 || config.ui.transparency > 1.0 {
return Err(CoderLibError::Config(ConfigError::InvalidValue("Transparency must be between 0.0 and 1.0".to_string())));
}
if config.plugins.resource_limits.max_memory_mb == 0 {
return Err(CoderLibError::Config(ConfigError::InvalidValue("Maximum memory must be greater than 0".to_string())));
}
if config.plugins.resource_limits.max_cpu_percent <= 0.0 || config.plugins.resource_limits.max_cpu_percent > 100.0 {
return Err(CoderLibError::Config(ConfigError::InvalidValue("Maximum CPU percentage must be between 0 and 100".to_string())));
}
if !["stable", "beta", "nightly"].contains(&config.plugins.updates.channel.as_str()) {
return Err(CoderLibError::Config(ConfigError::InvalidValue("Update channel must be 'stable', 'beta', or 'nightly'".to_string())));
}
if !["error", "warn", "info", "debug", "trace"].contains(&config.advanced.log_level.as_str()) {
return Err(CoderLibError::Config(ConfigError::InvalidValue("Log level must be one of: error, warn, info, debug, trace".to_string())));
}
Ok(())
}
pub fn get_config_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"edit": {
"type": "object",
"title": "Edit Settings",
"properties": {
"auto_save_interval": {
"type": "integer",
"title": "Auto-save interval (seconds)",
"minimum": 1,
"maximum": 3600,
"default": 30
},
"show_line_numbers": {
"type": "boolean",
"title": "Show line numbers",
"default": true
},
"tab_size": {
"type": "integer",
"title": "Tab size",
"minimum": 1,
"maximum": 16,
"default": 4
},
"theme": {
"type": "string",
"title": "Theme",
"enum": ["light", "dark", "auto"],
"default": "dark"
},
"font_family": {
"type": "string",
"title": "Font family",
"default": "Consolas"
},
"font_size": {
"type": "integer",
"title": "Font size",
"minimum": 8,
"maximum": 72,
"default": 14
}
}
},
"ui": {
"type": "object",
"title": "UI Integration",
"properties": {
"ai_panel_position": {
"type": "string",
"title": "AI panel position",
"enum": ["Right", "Left", "Bottom", "Top"],
"default": "Right"
},
"ai_panel_width": {
"type": "number",
"title": "AI panel width (%)",
"minimum": 10.0,
"maximum": 80.0,
"default": 40.0
},
"show_inline_suggestions": {
"type": "boolean",
"title": "Show inline suggestions",
"default": true
},
"show_token_usage": {
"type": "boolean",
"title": "Show token usage",
"default": true
}
}
},
"hotkeys": {
"type": "object",
"title": "Hotkey Bindings",
"properties": {
"ai_assistant": {
"type": "string",
"title": "Activate AI assistant",
"default": "Ctrl+I"
},
"explain_code": {
"type": "string",
"title": "Explain code",
"default": "Ctrl+Shift+E"
},
"refactor_selection": {
"type": "string",
"title": "Refactor selection",
"default": "Ctrl+Shift+R"
}
}
}
}
})
}
pub async fn export_config(&self) -> Result<String, CoderLibError> {
let config = self.load_config().await?;
serde_json::to_string_pretty(&config)
.map_err(|e| CoderLibError::Config(ConfigError::ParseFailed(format!("Failed to export config: {}", e))))
}
pub async fn import_config(&self, config_json: &str) -> Result<(), CoderLibError> {
let config: EditIntegratedConfig = serde_json::from_str(config_json)
.map_err(|e| CoderLibError::Config(ConfigError::ParseFailed(format!("Failed to parse imported config: {}", e))))?;
self.validate_config(&config)?;
self.save_config(&config).await?;
info!("Configuration imported successfully");
Ok(())
}
}