From 43b4f886cf9c849288c7da1dd84b4f05b2fb1872 Mon Sep 17 00:00:00 2001 From: qianmoQ Date: Sat, 3 Jan 2026 15:18:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E8=AF=AD=E8=A8=80=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/Cargo.lock | 7 + src-tauri/Cargo.toml | 2 +- src-tauri/src/config.rs | 8 + src-tauri/src/custom_plugin_commands.rs | 142 ++++++++++++++ src-tauri/src/main.rs | 26 ++- src-tauri/src/plugins/applescript.rs | 1 + src-tauri/src/plugins/c.rs | 1 + src-tauri/src/plugins/cangjie.rs | 1 + src-tauri/src/plugins/clojure.rs | 1 + src-tauri/src/plugins/cpp.rs | 1 + src-tauri/src/plugins/css.rs | 1 + src-tauri/src/plugins/custom.rs | 77 ++++++++ src-tauri/src/plugins/go.rs | 1 + src-tauri/src/plugins/groovy.rs | 1 + src-tauri/src/plugins/haskell.rs | 1 + src-tauri/src/plugins/html.rs | 1 + src-tauri/src/plugins/java.rs | 1 + src-tauri/src/plugins/javascript_browser.rs | 1 + src-tauri/src/plugins/javascript_jquery.rs | 1 + src-tauri/src/plugins/javascript_nodejs.rs | 1 + src-tauri/src/plugins/kotlin.rs | 1 + src-tauri/src/plugins/lua.rs | 1 + src-tauri/src/plugins/manager.rs | 121 +++++++----- src-tauri/src/plugins/mod.rs | 24 ++- src-tauri/src/plugins/nodejs.rs | 1 + src-tauri/src/plugins/objective_c.rs | 1 + src-tauri/src/plugins/objective_cpp.rs | 1 + src-tauri/src/plugins/php.rs | 1 + src-tauri/src/plugins/python2.rs | 1 + src-tauri/src/plugins/python3.rs | 1 + src-tauri/src/plugins/r.rs | 1 + src-tauri/src/plugins/ruby.rs | 1 + src-tauri/src/plugins/rust.rs | 1 + src-tauri/src/plugins/scala.rs | 1 + src-tauri/src/plugins/shell.rs | 1 + src-tauri/src/plugins/svg.rs | 1 + src-tauri/src/plugins/swift.rs | 1 + src-tauri/src/plugins/typescript.rs | 1 + src-tauri/src/plugins/typescript_browser.rs | 1 + src-tauri/src/plugins/typescript_nodejs.rs | 1 + src-tauri/tauri.conf.json | 8 +- src/components/setting/Language.vue | 195 +++++++++++++++++++- src/composables/useLanguageManager.ts | 41 ++-- src/composables/useLanguageSettings.ts | 30 ++- src/composables/usePluginConfig.ts | 73 ++++++-- src/types/plugin.ts | 1 + 46 files changed, 688 insertions(+), 99 deletions(-) create mode 100644 src-tauri/src/custom_plugin_commands.rs create mode 100644 src-tauri/src/plugins/custom.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 01c527f..e793a3a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1782,6 +1782,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + [[package]] name = "httparse" version = "1.10.1" @@ -4318,6 +4324,7 @@ dependencies = [ "gtk", "heck 0.5.0", "http 1.3.1", + "http-range", "jni", "libc", "log", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index e3fda54..7311a47 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -14,7 +14,7 @@ tauri-build = { version = "2", features = [] } chrono = { version = "0.4.41", features = ["serde"] } [dependencies] -tauri = { version = "2.9", features = ["devtools"] } +tauri = { version = "2.9", features = ["devtools", "protocol-asset"] } tauri-plugin-opener = "2.5" tauri-plugin-shell = "2.0" tauri-plugin-dialog = "2.4" diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index 9734beb..8ce5336 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -41,6 +41,7 @@ pub struct AppConfig { pub keep_log_days: Option, pub theme: Option, pub plugins: Option>, + pub custom_plugins: Option>, pub editor: Option, pub environment_mirror: Option, pub github: Option, @@ -54,6 +55,7 @@ impl Default for AppConfig { keep_log_days: Some(30), theme: Some("system".to_string()), plugins: Some(vec![]), + custom_plugins: Some(vec![]), editor: Some(EditorConfig { indent_with_tab: Some(true), tab_size: Some(2), @@ -238,6 +240,7 @@ impl ConfigManager { keep_log_days: Some(30), theme: Some("system".to_string()), plugins: Self::get_default_plugins_config(app_handle), + custom_plugins: Some(vec![]), editor: Some(EditorConfig { indent_with_tab: Some(true), tab_size: Some(2), @@ -279,6 +282,11 @@ impl ConfigManager { self.config.log_directory = path; self.save_config() } + + pub fn update_config(&mut self, new_config: AppConfig) -> Result<(), String> { + self.config = new_config; + self.save_config() + } } // 初始化配置 diff --git a/src-tauri/src/custom_plugin_commands.rs b/src-tauri/src/custom_plugin_commands.rs new file mode 100644 index 0000000..233c61a --- /dev/null +++ b/src-tauri/src/custom_plugin_commands.rs @@ -0,0 +1,142 @@ +use crate::config::{get_app_config_internal, get_config_manager}; +use crate::execution::PluginManagerState; +use crate::plugins::PluginConfig; +use crate::plugins::custom::CustomPlugin; +use log::info; +use std::fs; +use tauri::{AppHandle, Emitter, State, command}; + +#[command] +pub async fn add_custom_plugin( + config: PluginConfig, + app_handle: AppHandle, + plugin_manager: State<'_, PluginManagerState>, +) -> Result<(), String> { + { + let mut guard = get_config_manager()?; + if let Some(config_manager) = guard.as_mut() { + let mut app_config = config_manager.get_config().clone(); + + let mut custom_plugins = app_config.custom_plugins.unwrap_or_default(); + + if custom_plugins.iter().any(|p| p.language == config.language) { + return Err(format!("自定义插件 {} 已存在", config.language)); + } + + custom_plugins.push(config.clone()); + app_config.custom_plugins = Some(custom_plugins); + + config_manager.update_config(app_config)?; + } else { + return Err("配置管理器未初始化".to_string()); + } + } + + let mut manager = plugin_manager.lock().await; + let custom_plugin = CustomPlugin::new(config.clone()); + manager.register_plugin(config.language.clone(), Box::new(custom_plugin)); + + app_handle.emit("config-updated", ()).ok(); + info!("自定义插件 {} 已添加", config.language); + + Ok(()) +} + +#[command] +pub async fn update_custom_plugin( + config: PluginConfig, + app_handle: AppHandle, + plugin_manager: State<'_, PluginManagerState>, +) -> Result<(), String> { + { + let mut guard = get_config_manager()?; + if let Some(config_manager) = guard.as_mut() { + let mut app_config = config_manager.get_config().clone(); + + let mut custom_plugins = app_config.custom_plugins.unwrap_or_default(); + + if let Some(index) = custom_plugins + .iter() + .position(|p| p.language == config.language) + { + custom_plugins[index] = config.clone(); + } else { + return Err(format!("自定义插件 {} 不存在", config.language)); + } + + app_config.custom_plugins = Some(custom_plugins); + + config_manager.update_config(app_config)?; + } else { + return Err("配置管理器未初始化".to_string()); + } + } + + let mut manager = plugin_manager.lock().await; + manager.unregister_plugin(&config.language); + let custom_plugin = CustomPlugin::new(config.clone()); + manager.register_plugin(config.language.clone(), Box::new(custom_plugin)); + + app_handle.emit("config-updated", ()).ok(); + info!("自定义插件 {} 已更新", config.language); + + Ok(()) +} + +#[command] +pub async fn remove_custom_plugin( + language: String, + app_handle: AppHandle, + plugin_manager: State<'_, PluginManagerState>, +) -> Result<(), String> { + { + let mut guard = get_config_manager()?; + if let Some(config_manager) = guard.as_mut() { + let mut app_config = config_manager.get_config().clone(); + + let mut custom_plugins = app_config.custom_plugins.unwrap_or_default(); + + custom_plugins.retain(|p| p.language != language); + + app_config.custom_plugins = Some(custom_plugins); + + config_manager.update_config(app_config)?; + } else { + return Err("配置管理器未初始化".to_string()); + } + } + + let mut manager = plugin_manager.lock().await; + manager.unregister_plugin(&language); + + app_handle.emit("config-updated", ()).ok(); + info!("自定义插件 {} 已移除", language); + + Ok(()) +} + +#[command] +pub async fn get_custom_plugins() -> Result, String> { + let app_config = get_app_config_internal()?; + Ok(app_config.custom_plugins.unwrap_or_default()) +} + +#[command] +pub async fn save_custom_icon( + language: String, + icon_data: Vec, + file_extension: String, +) -> Result { + let home_dir = dirs::home_dir().ok_or("无法获取用户主目录")?; + let icons_dir = home_dir.join(".codeforge").join("custom-icons"); + + fs::create_dir_all(&icons_dir).map_err(|e| format!("创建图标目录失败: {}", e))?; + + let icon_path = icons_dir.join(format!("{}.{}", language, file_extension)); + + fs::write(&icon_path, icon_data).map_err(|e| format!("保存图标文件失败: {}", e))?; + + info!("自定义图标已保存: {:?}", icon_path); + + Ok(icon_path.to_string_lossy().to_string()) +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index a92f1e7..c3eb931 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -5,6 +5,7 @@ mod cache; mod config; +mod custom_plugin_commands; mod env_commands; mod env_manager; mod env_providers; @@ -19,6 +20,10 @@ mod update; mod utils; use crate::cache::{clear_all_cache, clear_plugins_cache, get_cache_info}; +use crate::custom_plugin_commands::{ + add_custom_plugin, get_custom_plugins, remove_custom_plugin, save_custom_icon, + update_custom_plugin, +}; use crate::env_commands::{ EnvironmentManagerState, download_and_install_version, get_environment_info, get_supported_environment_languages, switch_environment_version, uninstall_environment_version, @@ -70,7 +75,20 @@ fn main() { eprintln!("Failed to initialize config: {}", e); } - // 第二步:初始化日志系统 + // 第二步:加载自定义插件 + use tauri::Manager; + if let Some(plugin_manager_state) = app.try_state::() { + if let Ok(mut manager) = plugin_manager_state.try_lock() { + if let Ok(app_config) = config::get_app_config_internal() { + if let Some(custom_plugins) = app_config.custom_plugins { + manager.load_custom_plugins(custom_plugins); + info!("初始化 -> 已加载自定义插件"); + } + } + } + } + + // 第三步:初始化日志系统 if let Err(e) = logger::setup_logger(app.handle()) { eprintln!("Failed to setup logger: {}", e); } @@ -115,6 +133,12 @@ fn main() { get_app_config, update_app_config, get_config_path, + // 自定义插件相关命令 + add_custom_plugin, + update_custom_plugin, + remove_custom_plugin, + get_custom_plugins, + save_custom_icon, // 缓存相关命令 get_cache_info, clear_plugins_cache, diff --git a/src-tauri/src/plugins/applescript.rs b/src-tauri/src/plugins/applescript.rs index 4c4cf68..d8596da 100644 --- a/src-tauri/src/plugins/applescript.rs +++ b/src-tauri/src/plugins/applescript.rs @@ -42,6 +42,7 @@ impl LanguagePlugin for AppleScriptPlugin { template: Some(String::from("-- 在这里输入 AppleScript 代码")), timeout: Some(45), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/c.rs b/src-tauri/src/plugins/c.rs index 515a567..3eb58f4 100644 --- a/src-tauri/src/plugins/c.rs +++ b/src-tauri/src/plugins/c.rs @@ -67,6 +67,7 @@ impl LanguagePlugin for CPlugin { template: Some(String::from("// 在这里输入 C 代码")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/cangjie.rs b/src-tauri/src/plugins/cangjie.rs index aea8844..dd86432 100644 --- a/src-tauri/src/plugins/cangjie.rs +++ b/src-tauri/src/plugins/cangjie.rs @@ -86,6 +86,7 @@ impl LanguagePlugin for CangjiePlugin { )), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/clojure.rs b/src-tauri/src/plugins/clojure.rs index 402b89f..f62ab19 100644 --- a/src-tauri/src/plugins/clojure.rs +++ b/src-tauri/src/plugins/clojure.rs @@ -42,6 +42,7 @@ impl LanguagePlugin for ClojurePlugin { template: Some(String::from(";; 在这里输入 Clojure 代码")), timeout: Some(45), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/cpp.rs b/src-tauri/src/plugins/cpp.rs index e102f11..76aa7d5 100644 --- a/src-tauri/src/plugins/cpp.rs +++ b/src-tauri/src/plugins/cpp.rs @@ -67,6 +67,7 @@ impl LanguagePlugin for CppPlugin { template: Some(String::from("// 在这里输入 C++ 代码")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/css.rs b/src-tauri/src/plugins/css.rs index 6a889b0..7df5608 100644 --- a/src-tauri/src/plugins/css.rs +++ b/src-tauri/src/plugins/css.rs @@ -44,6 +44,7 @@ impl LanguagePlugin for CssPlugin { template: Some(String::from("// 在这里输入 CSS 代码")), timeout: Some(30), console_type: Some(String::from("web")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/custom.rs b/src-tauri/src/plugins/custom.rs new file mode 100644 index 0000000..09dc42a --- /dev/null +++ b/src-tauri/src/plugins/custom.rs @@ -0,0 +1,77 @@ +use super::{LanguagePlugin, PluginConfig}; + +pub struct CustomPlugin { + config: PluginConfig, +} + +impl CustomPlugin { + pub fn new(config: PluginConfig) -> Self { + Self { config } + } +} + +impl LanguagePlugin for CustomPlugin { + fn get_language_name(&self) -> &'static str { + Box::leak(self.config.language.clone().into_boxed_str()) + } + + fn get_language_key(&self) -> &'static str { + Box::leak(self.config.language.clone().into_boxed_str()) + } + + fn get_file_extension(&self) -> String { + self.config.extension.clone() + } + + fn get_version_args(&self) -> Vec<&'static str> { + vec!["--version"] + } + + fn get_path_command(&self) -> String { + // 从 run_command 中提取第一个命令(如 "bash $filename" -> "bash") + self.config + .run_command + .as_ref() + .and_then(|cmd| cmd.split_whitespace().next()) + .map(|s| format!("which {}", s)) + .unwrap_or_else(|| "echo unknown".to_string()) + } + + fn get_command( + &self, + file_path: Option<&str>, + is_version: bool, + _file_name: Option, + ) -> String { + // 当获取版本信息时或没有file_path时,返回 run_command 的第一个命令 + if is_version || file_path.is_none() { + return self + .config + .run_command + .as_ref() + .and_then(|cmd| cmd.split_whitespace().next()) + .map(|s| s.to_string()) + .unwrap_or_else(|| self.config.language.clone()); + } + + // 执行代码时,返回完整的命令 + if let Some(run_cmd) = &self.config.run_command { + if let Some(path) = file_path { + return run_cmd.replace("$filename", path); + } + } + + self.get_default_command() + } + + fn get_default_config(&self) -> PluginConfig { + self.config.clone() + } + + fn get_default_command(&self) -> String { + self.config + .run_command + .clone() + .unwrap_or_else(|| self.config.language.clone()) + } +} diff --git a/src-tauri/src/plugins/go.rs b/src-tauri/src/plugins/go.rs index 1f9b935..b854fa6 100644 --- a/src-tauri/src/plugins/go.rs +++ b/src-tauri/src/plugins/go.rs @@ -36,6 +36,7 @@ impl LanguagePlugin for GoPlugin { template: Some(String::from("// 在这里输入 Go 代码")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/groovy.rs b/src-tauri/src/plugins/groovy.rs index 6715e56..70701c9 100644 --- a/src-tauri/src/plugins/groovy.rs +++ b/src-tauri/src/plugins/groovy.rs @@ -77,6 +77,7 @@ impl LanguagePlugin for GroovyPlugin { template: Some(String::from("// 在这里输入 Groovy 代码")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/haskell.rs b/src-tauri/src/plugins/haskell.rs index 561a5b5..e7fb06d 100644 --- a/src-tauri/src/plugins/haskell.rs +++ b/src-tauri/src/plugins/haskell.rs @@ -88,6 +88,7 @@ impl LanguagePlugin for HaskellPlugin { )), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/html.rs b/src-tauri/src/plugins/html.rs index aaae68e..118516e 100644 --- a/src-tauri/src/plugins/html.rs +++ b/src-tauri/src/plugins/html.rs @@ -42,6 +42,7 @@ impl LanguagePlugin for HtmlPlugin { template: Some(String::from("// 在这里输入 HTML 代码")), timeout: Some(30), console_type: Some(String::from("web")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/java.rs b/src-tauri/src/plugins/java.rs index 2291652..8df5a44 100644 --- a/src-tauri/src/plugins/java.rs +++ b/src-tauri/src/plugins/java.rs @@ -42,6 +42,7 @@ impl LanguagePlugin for JavaPlugin { template: Some(String::from("// 在这里输入 Java 代码")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/javascript_browser.rs b/src-tauri/src/plugins/javascript_browser.rs index 79c0eb3..6a9721f 100644 --- a/src-tauri/src/plugins/javascript_browser.rs +++ b/src-tauri/src/plugins/javascript_browser.rs @@ -44,6 +44,7 @@ impl LanguagePlugin for JavaScriptBrowserPlugin { template: Some(String::from("// 在这里输入 JavaScript (Browser) 代码")), timeout: Some(30), console_type: Some(String::from("web")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/javascript_jquery.rs b/src-tauri/src/plugins/javascript_jquery.rs index 952f690..f5add81 100644 --- a/src-tauri/src/plugins/javascript_jquery.rs +++ b/src-tauri/src/plugins/javascript_jquery.rs @@ -44,6 +44,7 @@ impl LanguagePlugin for JavaScriptJQueryPlugin { template: Some(String::from("// 在这里输入 JavaScript (jQuery) 代码")), timeout: Some(30), console_type: Some(String::from("web")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/javascript_nodejs.rs b/src-tauri/src/plugins/javascript_nodejs.rs index 8218f91..d69d1da 100644 --- a/src-tauri/src/plugins/javascript_nodejs.rs +++ b/src-tauri/src/plugins/javascript_nodejs.rs @@ -42,6 +42,7 @@ impl LanguagePlugin for JavaScriptNodeJsPlugin { template: Some(String::from("// 在这里输入 JavaScript (Node.js) 代码")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/kotlin.rs b/src-tauri/src/plugins/kotlin.rs index be69898..88dd556 100644 --- a/src-tauri/src/plugins/kotlin.rs +++ b/src-tauri/src/plugins/kotlin.rs @@ -69,6 +69,7 @@ impl LanguagePlugin for KotlinPlugin { template: Some(String::from("// 在这里输入 Kotlin 代码")), timeout: Some(60), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/lua.rs b/src-tauri/src/plugins/lua.rs index 234b200..9f72b40 100644 --- a/src-tauri/src/plugins/lua.rs +++ b/src-tauri/src/plugins/lua.rs @@ -71,6 +71,7 @@ impl LanguagePlugin for LuaPlugin { template: Some(String::from("-- Lua 示例代码 - CodeForge 代码执行环境\n\n")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/manager.rs b/src-tauri/src/plugins/manager.rs index 933e565..9511366 100644 --- a/src-tauri/src/plugins/manager.rs +++ b/src-tauri/src/plugins/manager.rs @@ -5,6 +5,7 @@ use crate::plugins::cangjie::CangjiePlugin; use crate::plugins::clojure::ClojurePlugin; use crate::plugins::cpp::CppPlugin; use crate::plugins::css::CssPlugin; +use crate::plugins::custom::CustomPlugin; use crate::plugins::go::GoPlugin; use crate::plugins::groovy::GroovyPlugin; use crate::plugins::haskell::HaskellPlugin; @@ -35,61 +36,83 @@ use std::collections::HashMap; pub struct PluginManager { plugins: HashMap>, + builtin_languages: Vec, } impl PluginManager { pub fn new() -> Self { let mut plugins: HashMap> = HashMap::new(); + let mut builtin_languages = Vec::new(); - plugins.insert("python2".to_string(), Box::new(Python2Plugin)); - plugins.insert("python3".to_string(), Box::new(Python3Plugin)); - plugins.insert("nodejs".to_string(), Box::new(NodeJSPlugin)); - plugins.insert("go".to_string(), Box::new(GoPlugin)); - plugins.insert("java".to_string(), Box::new(JavaPlugin)); - plugins.insert("shell".to_string(), Box::new(ShellPlugin)); - plugins.insert("rust".to_string(), Box::new(RustPlugin)); - plugins.insert("swift".to_string(), Box::new(SwiftPlugin)); - plugins.insert("scala".to_string(), Box::new(ScalaPlugin)); - plugins.insert("kotlin".to_string(), Box::new(KotlinPlugin)); - plugins.insert("clojure".to_string(), Box::new(ClojurePlugin)); - plugins.insert("c".to_string(), Box::new(CPlugin)); - plugins.insert("ruby".to_string(), Box::new(RubyPlugin)); - plugins.insert("applescript".to_string(), Box::new(AppleScriptPlugin)); - plugins.insert("typescript".to_string(), Box::new(TypeScriptPlugin)); - plugins.insert("cpp".to_string(), Box::new(CppPlugin)); - plugins.insert("groovy".to_string(), Box::new(GroovyPlugin)); - plugins.insert("html".to_string(), Box::new(HtmlPlugin)); - plugins.insert("css".to_string(), Box::new(CssPlugin)); - plugins.insert("svg".to_string(), Box::new(SvgPlugin)); - plugins.insert("php".to_string(), Box::new(PHPPlugin)); - plugins.insert("r".to_string(), Box::new(RPlugin)); - plugins.insert("cangjie".to_string(), Box::new(CangjiePlugin)); - plugins.insert("haskell".to_string(), Box::new(HaskellPlugin)); - plugins.insert("lua".to_string(), Box::new(LuaPlugin)); - plugins.insert("objective-c".to_string(), Box::new(ObjectiveCPlugin)); - plugins.insert("objective-cpp".to_string(), Box::new(ObjectiveCppPlugin)); - plugins.insert( - "javascript-nodejs".to_string(), - Box::new(JavaScriptNodeJsPlugin), - ); - plugins.insert( - "typescript-nodejs".to_string(), - Box::new(TypeScriptNodeJsPlugin), - ); - plugins.insert( - "typescript-browser".to_string(), - Box::new(TypeScriptBrowserPlugin), - ); - plugins.insert( - "javascript-browser".to_string(), - Box::new(JavaScriptBrowserPlugin), - ); - plugins.insert( - "javascript-jquery".to_string(), - Box::new(JavaScriptJQueryPlugin), - ); - - Self { plugins } + let builtin_plugins: Vec<(String, Box)> = vec![ + ("python2".to_string(), Box::new(Python2Plugin)), + ("python3".to_string(), Box::new(Python3Plugin)), + ("nodejs".to_string(), Box::new(NodeJSPlugin)), + ("go".to_string(), Box::new(GoPlugin)), + ("java".to_string(), Box::new(JavaPlugin)), + ("shell".to_string(), Box::new(ShellPlugin)), + ("rust".to_string(), Box::new(RustPlugin)), + ("swift".to_string(), Box::new(SwiftPlugin)), + ("scala".to_string(), Box::new(ScalaPlugin)), + ("kotlin".to_string(), Box::new(KotlinPlugin)), + ("clojure".to_string(), Box::new(ClojurePlugin)), + ("c".to_string(), Box::new(CPlugin)), + ("ruby".to_string(), Box::new(RubyPlugin)), + ("applescript".to_string(), Box::new(AppleScriptPlugin)), + ("typescript".to_string(), Box::new(TypeScriptPlugin)), + ("cpp".to_string(), Box::new(CppPlugin)), + ("groovy".to_string(), Box::new(GroovyPlugin)), + ("html".to_string(), Box::new(HtmlPlugin)), + ("css".to_string(), Box::new(CssPlugin)), + ("svg".to_string(), Box::new(SvgPlugin)), + ("php".to_string(), Box::new(PHPPlugin)), + ("r".to_string(), Box::new(RPlugin)), + ("cangjie".to_string(), Box::new(CangjiePlugin)), + ("haskell".to_string(), Box::new(HaskellPlugin)), + ("lua".to_string(), Box::new(LuaPlugin)), + ("objective-c".to_string(), Box::new(ObjectiveCPlugin)), + ("objective-cpp".to_string(), Box::new(ObjectiveCppPlugin)), + ( + "javascript-nodejs".to_string(), + Box::new(JavaScriptNodeJsPlugin), + ), + ( + "typescript-nodejs".to_string(), + Box::new(TypeScriptNodeJsPlugin), + ), + ( + "typescript-browser".to_string(), + Box::new(TypeScriptBrowserPlugin), + ), + ( + "javascript-browser".to_string(), + Box::new(JavaScriptBrowserPlugin), + ), + ( + "javascript-jquery".to_string(), + Box::new(JavaScriptJQueryPlugin), + ), + ]; + + for (key, plugin) in builtin_plugins { + builtin_languages.push(key.clone()); + plugins.insert(key, plugin); + } + + Self { + plugins, + builtin_languages, + } + } + + pub fn load_custom_plugins(&mut self, custom_configs: Vec) { + for config in custom_configs { + if !self.builtin_languages.contains(&config.language) { + let custom_plugin = CustomPlugin::new(config.clone()); + self.plugins + .insert(config.language.clone(), Box::new(custom_plugin)); + } + } } pub fn get_plugin(&self, language: &str) -> Option<&dyn LanguagePlugin> { diff --git a/src-tauri/src/plugins/mod.rs b/src-tauri/src/plugins/mod.rs index 61159c8..deff308 100644 --- a/src-tauri/src/plugins/mod.rs +++ b/src-tauri/src/plugins/mod.rs @@ -42,6 +42,7 @@ pub struct PluginConfig { pub template: Option, // 插件的模板 pub timeout: Option, // 插件的超时时间 pub console_type: Option, // 插件的输出类型 + pub icon_path: Option, // 自定义图标路径 } // 语言插件接口 @@ -119,11 +120,10 @@ pub trait LanguagePlugin: Send + Sync { fn get_config(&self) -> Option { // 获取全局应用配置 if let Ok(app_config) = get_app_config_internal() { - // 检查是否有插件配置 - if let Some(ref plugins) = app_config.plugins { - // 根据当前插件的语言名称过滤配置 - let language_name = self.get_language_key(); + let language_name = self.get_language_key(); + // 首先检查是否有插件配置 + if let Some(ref plugins) = app_config.plugins { // 查找匹配的插件配置 if let Some(found_config) = plugins .iter() @@ -137,6 +137,21 @@ pub trait LanguagePlugin: Send + Sync { return Some(found_config); } } + + // 如果在plugins中没找到,检查custom_plugins + if let Some(ref custom_plugins) = app_config.custom_plugins { + if let Some(found_config) = custom_plugins + .iter() + .find(|config| config.language == language_name) + .cloned() + { + debug!( + "执行代码 -> 获取自定义插件 [ {} ] 配置 {:?}", + language_name, found_config + ); + return Some(found_config); + } + } } // 如果没有找到配置,返回默认配置 @@ -375,6 +390,7 @@ pub mod cangjie; pub mod clojure; pub mod cpp; pub mod css; +pub mod custom; pub mod go; pub mod groovy; pub mod haskell; diff --git a/src-tauri/src/plugins/nodejs.rs b/src-tauri/src/plugins/nodejs.rs index 58d8cb4..8d3fa52 100644 --- a/src-tauri/src/plugins/nodejs.rs +++ b/src-tauri/src/plugins/nodejs.rs @@ -41,6 +41,7 @@ impl LanguagePlugin for NodeJSPlugin { template: Some(String::from("// 在这里输入 Node.js 代码")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/objective_c.rs b/src-tauri/src/plugins/objective_c.rs index d37a17b..63080b9 100644 --- a/src-tauri/src/plugins/objective_c.rs +++ b/src-tauri/src/plugins/objective_c.rs @@ -74,6 +74,7 @@ impl LanguagePlugin for ObjectiveCPlugin { )), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/objective_cpp.rs b/src-tauri/src/plugins/objective_cpp.rs index 231af12..e5e975c 100644 --- a/src-tauri/src/plugins/objective_cpp.rs +++ b/src-tauri/src/plugins/objective_cpp.rs @@ -74,6 +74,7 @@ impl LanguagePlugin for ObjectiveCppPlugin { )), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/php.rs b/src-tauri/src/plugins/php.rs index 8bfc29a..1333501 100644 --- a/src-tauri/src/plugins/php.rs +++ b/src-tauri/src/plugins/php.rs @@ -42,6 +42,7 @@ impl LanguagePlugin for PHPPlugin { template: Some(String::from("")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/python2.rs b/src-tauri/src/plugins/python2.rs index 95a5957..a7503f7 100644 --- a/src-tauri/src/plugins/python2.rs +++ b/src-tauri/src/plugins/python2.rs @@ -42,6 +42,7 @@ impl LanguagePlugin for Python2Plugin { template: Some(String::from("# 在这里输入 Python 2 代码")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/python3.rs b/src-tauri/src/plugins/python3.rs index 5ee737a..6608e2a 100644 --- a/src-tauri/src/plugins/python3.rs +++ b/src-tauri/src/plugins/python3.rs @@ -41,6 +41,7 @@ impl LanguagePlugin for Python3Plugin { template: Some(String::from("# 在这里输入 Python 3 代码")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/r.rs b/src-tauri/src/plugins/r.rs index fc158b3..fc875ec 100644 --- a/src-tauri/src/plugins/r.rs +++ b/src-tauri/src/plugins/r.rs @@ -50,6 +50,7 @@ impl LanguagePlugin for RPlugin { )), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/ruby.rs b/src-tauri/src/plugins/ruby.rs index 34a30ab..47a02fe 100644 --- a/src-tauri/src/plugins/ruby.rs +++ b/src-tauri/src/plugins/ruby.rs @@ -42,6 +42,7 @@ impl LanguagePlugin for RubyPlugin { template: Some(String::from("# 在这里输入 Ruby 代码")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/rust.rs b/src-tauri/src/plugins/rust.rs index 2a689d4..c196437 100644 --- a/src-tauri/src/plugins/rust.rs +++ b/src-tauri/src/plugins/rust.rs @@ -97,6 +97,7 @@ impl LanguagePlugin for RustPlugin { template: Some(String::from("# 在这里输入 Rust 代码")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/scala.rs b/src-tauri/src/plugins/scala.rs index 1e5253d..ef157a6 100644 --- a/src-tauri/src/plugins/scala.rs +++ b/src-tauri/src/plugins/scala.rs @@ -49,6 +49,7 @@ impl LanguagePlugin for ScalaPlugin { template: Some(String::from("// 在这里输入 Scala 代码")), timeout: Some(45), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/shell.rs b/src-tauri/src/plugins/shell.rs index 00587e6..7d8b89d 100644 --- a/src-tauri/src/plugins/shell.rs +++ b/src-tauri/src/plugins/shell.rs @@ -42,6 +42,7 @@ impl LanguagePlugin for ShellPlugin { template: Some(String::from("# 在这里输入 Shell 代码")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/svg.rs b/src-tauri/src/plugins/svg.rs index 17f84e5..f59822c 100644 --- a/src-tauri/src/plugins/svg.rs +++ b/src-tauri/src/plugins/svg.rs @@ -44,6 +44,7 @@ impl LanguagePlugin for SvgPlugin { )), timeout: Some(30), console_type: Some(String::from("web")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/swift.rs b/src-tauri/src/plugins/swift.rs index 82e0993..633c872 100644 --- a/src-tauri/src/plugins/swift.rs +++ b/src-tauri/src/plugins/swift.rs @@ -42,6 +42,7 @@ impl LanguagePlugin for SwiftPlugin { template: Some(String::from("// 在这里输入 Swift 代码")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/typescript.rs b/src-tauri/src/plugins/typescript.rs index afb33e1..0398d52 100644 --- a/src-tauri/src/plugins/typescript.rs +++ b/src-tauri/src/plugins/typescript.rs @@ -69,6 +69,7 @@ impl LanguagePlugin for TypeScriptPlugin { template: Some(String::from("// 在这里输入 TypeScript 代码")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/typescript_browser.rs b/src-tauri/src/plugins/typescript_browser.rs index 43ea4d9..6117d70 100644 --- a/src-tauri/src/plugins/typescript_browser.rs +++ b/src-tauri/src/plugins/typescript_browser.rs @@ -71,6 +71,7 @@ impl LanguagePlugin for TypeScriptBrowserPlugin { template: Some(String::from("// 在这里输入 TypeScript (Browser) 代码")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/src/plugins/typescript_nodejs.rs b/src-tauri/src/plugins/typescript_nodejs.rs index 1dc773a..8616780 100644 --- a/src-tauri/src/plugins/typescript_nodejs.rs +++ b/src-tauri/src/plugins/typescript_nodejs.rs @@ -42,6 +42,7 @@ impl LanguagePlugin for TypeScriptNodeJsPlugin { template: Some(String::from("// 在这里输入 TypeScript (Node.js) 代码")), timeout: Some(30), console_type: Some(String::from("console")), + icon_path: None, } } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index dc56685..4936414 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -23,7 +23,13 @@ } ], "security": { - "csp": null + "csp": null, + "assetProtocol": { + "enable": true, + "scope": [ + "$HOME/.codeforge/custom-icons/**" + ] + } } }, "bundle": { diff --git a/src/components/setting/Language.vue b/src/components/setting/Language.vue index 3aa54d8..3297401 100644 --- a/src/components/setting/Language.vue +++ b/src/components/setting/Language.vue @@ -1,11 +1,14 @@ diff --git a/src/composables/useLanguageManager.ts b/src/composables/useLanguageManager.ts index 18aff8c..bdfd42b 100644 --- a/src/composables/useLanguageManager.ts +++ b/src/composables/useLanguageManager.ts @@ -1,5 +1,5 @@ import {ref, type Ref, onMounted, onUnmounted} from 'vue' -import {invoke} from '@tauri-apps/api/core' +import {invoke, convertFileSrc} from '@tauri-apps/api/core' import {listen} from '@tauri-apps/api/event' import {EnvInfo, Language, LanguageInfo} from '../types/app.ts' @@ -97,15 +97,26 @@ export function useLanguageManager( const getSupportedLanguages = async () => { try { const languages = await invoke('get_supported_languages') - const allLanguages = languages.map((language) => ({ - name: language.name, - value: language.value, - svgUrl: `/icons/${language.value.replace(/\d+$/, '')}.svg` - })) + const allLanguages = languages.map((language) => { + let customPlugin = globalConfig.value?.custom_plugins?.find((p: any) => p.language === language.value) + + return { + name: language.name, + value: language.value, + svgUrl: customPlugin?.icon_path + ? convertFileSrc(customPlugin.icon_path) + : `/icons/${language.value.replace(/\d+$/, '')}.svg` + } + }) - if (globalConfig.value && globalConfig.value.plugins) { + if (globalConfig.value) { const filtered = allLanguages.filter((language) => { - const plugin = globalConfig.value.plugins.find((p: any) => p.language === language.value) + let plugin = globalConfig.value.plugins?.find((p: any) => p.language === language.value) + + if (!plugin && globalConfig.value.custom_plugins) { + plugin = globalConfig.value.custom_plugins.find((p: any) => p.language === language.value) + } + const enabled = !plugin || plugin.enabled !== false console.log(`语言 ${language.name} (${language.value}): enabled=${enabled}`) return enabled @@ -133,8 +144,14 @@ export function useLanguageManager( } const filterPluginTemplate = (plugin: any) => { - if (globalConfig.value && globalConfig.value.plugins) { - return globalConfig.value.plugins.find((p: any) => p.language === plugin)?.template || '' + if (globalConfig.value) { + let foundPlugin = globalConfig.value.plugins?.find((p: any) => p.language === plugin) + + if (!foundPlugin && globalConfig.value.custom_plugins) { + foundPlugin = globalConfig.value.custom_plugins.find((p: any) => p.language === plugin) + } + + return foundPlugin?.template || '' } return '' } @@ -200,8 +217,8 @@ export function useLanguageManager( onMounted(async () => { unlistenConfigUpdate = await listen('config-updated', async () => { - console.log('收到配置更新事件,重新加载环境信息') - await refreshEnvInfo() + console.log('收到配置更新事件,重新加载语言列表和环境信息') + await refreshLanguageList() }) }) diff --git a/src/composables/useLanguageSettings.ts b/src/composables/useLanguageSettings.ts index 5e9239b..0992419 100644 --- a/src/composables/useLanguageSettings.ts +++ b/src/composables/useLanguageSettings.ts @@ -42,7 +42,8 @@ export function useLanguageSettings(emit: any) handleTabChange, selectExecuteHome, updateGlobalConfig, - initializePlugin + initializePlugin, + getSupportedLanguages } = usePluginConfig(emit) // 编辑器状态 @@ -113,9 +114,14 @@ export function useLanguageSettings(emit: any) }, {immediate: false}) const handlePluginToggle = async (language: string, enabled: boolean, _event: Event) => { - if (!globalConfig.value || !globalConfig.value.plugins) return + if (!globalConfig.value) return + + let plugin = globalConfig.value.plugins?.find((p: any) => p.language === language) + + if (!plugin && globalConfig.value.custom_plugins) { + plugin = globalConfig.value.custom_plugins.find((p: any) => p.language === language) + } - const plugin = globalConfig.value.plugins.find((p: any) => p.language === language) if (plugin) { plugin.enabled = enabled await updateGlobalConfig(plugin) @@ -123,10 +129,15 @@ export function useLanguageSettings(emit: any) } const syncPluginStates = () => { - if (globalConfig.value && globalConfig.value.plugins) { + if (globalConfig.value) { const states: Record = {} tabsPluginData.value.forEach((tab) => { - const plugin = globalConfig.value.plugins.find((p: any) => p.language === tab.key) + let plugin = globalConfig.value.plugins?.find((p: any) => p.language === tab.key) + + if (!plugin && globalConfig.value.custom_plugins) { + plugin = globalConfig.value.custom_plugins.find((p: any) => p.language === tab.key) + } + states[tab.key] = plugin?.enabled !== false }) pluginEnabledStates.value = states @@ -161,6 +172,12 @@ export function useLanguageSettings(emit: any) console.log('Extensions updated:', currentExtensions.value) } + const reloadLanguages = async () => { + await getSupportedLanguages() + await initializePlugin() + syncPluginStates() + } + return { activeTab, tabsData, @@ -178,6 +195,7 @@ export function useLanguageSettings(emit: any) currentLanguage, templateContent, updateExtensions, - initialize + initialize, + reloadLanguages } } \ No newline at end of file diff --git a/src/composables/usePluginConfig.ts b/src/composables/usePluginConfig.ts index d5b29ff..47565db 100644 --- a/src/composables/usePluginConfig.ts +++ b/src/composables/usePluginConfig.ts @@ -1,5 +1,5 @@ import { ref, watch, onMounted, onUnmounted } from 'vue' -import { invoke } from '@tauri-apps/api/core' +import { invoke, convertFileSrc } from '@tauri-apps/api/core' import { listen } from '@tauri-apps/api/event' import { debounce } from 'lodash-es' import { open as openDialog } from '@tauri-apps/plugin-dialog' @@ -49,11 +49,20 @@ export function usePluginConfig(emit?: any) const getSupportedLanguages = async () => { try { const languages = await invoke('get_supported_languages') - tabsPluginData.value = languages.map((language) => ({ - key: language.value, - label: language.name, - svgUrl: `/icons/${ language.value.replace(/\d+$/, '') }.svg` - })) + + await getGlobalConfig() + + tabsPluginData.value = languages.map((language) => { + let customPlugin = globalConfig.value?.custom_plugins?.find((p: any) => p.language === language.value) + + return { + key: language.value, + label: language.name, + svgUrl: customPlugin?.icon_path + ? convertFileSrc(customPlugin.icon_path) + : `/icons/${ language.value.replace(/\d+$/, '') }.svg` + } + }) // 设置默认选中的插件 if (tabsPluginData.value.length > 0 && !activePlugin.value) { @@ -86,13 +95,19 @@ export function usePluginConfig(emit?: any) // 处理标签页切换 const handleTabChange = () => { - if (globalConfig.value && globalConfig.value.plugins && activePlugin.value) { + if (globalConfig.value && activePlugin.value) { isInitialLoad.value = true - const foundPlugin = globalConfig.value.plugins.find( + let foundPlugin = globalConfig.value.plugins?.find( (plugin: any) => plugin.language === activePlugin.value ) + if (!foundPlugin && globalConfig.value.custom_plugins) { + foundPlugin = globalConfig.value.custom_plugins.find( + (plugin: any) => plugin.language === activePlugin.value + ) + } + if (foundPlugin) { pluginConfig.value = { ...foundPlugin } console.log('切换到插件配置:', activePlugin.value, foundPlugin) @@ -142,25 +157,34 @@ export function usePluginConfig(emit?: any) // 更新全局配置 const updateGlobalConfig = async (updatedPlugin: PluginConfig) => { - if (!globalConfig.value || !globalConfig.value.plugins) { - console.error('全局配置未加载或插件列表为空') + if (!globalConfig.value) { + console.error('全局配置未加载') return } try { isSaving.value = true - const pluginIndex = globalConfig.value.plugins.findIndex( + let pluginIndex = globalConfig.value.plugins?.findIndex( (plugin: any) => plugin.language === updatedPlugin.language - ) + ) ?? -1 + + let isCustomPlugin = false + if (pluginIndex === -1 && globalConfig.value.custom_plugins) { + pluginIndex = globalConfig.value.custom_plugins.findIndex( + (plugin: any) => plugin.language === updatedPlugin.language + ) + isCustomPlugin = pluginIndex !== -1 + } - if (pluginIndex !== -1) { - // 更新现有插件配置 + if (isCustomPlugin && globalConfig.value.custom_plugins) { + globalConfig.value.custom_plugins[pluginIndex] = { ...updatedPlugin } + } else if (pluginIndex !== -1 && globalConfig.value.plugins) { globalConfig.value.plugins[pluginIndex] = { ...updatedPlugin } - } - else { - // 添加新的插件配置 + } else if (globalConfig.value.plugins) { globalConfig.value.plugins.push({ ...updatedPlugin }) + } else { + globalConfig.value.plugins = [{ ...updatedPlugin }] } await invoke('update_app_config', { config: globalConfig.value }) @@ -218,10 +242,18 @@ export function usePluginConfig(emit?: any) // 获取插件配置 const getPluginConfig = (language: string) => { - if (globalConfig.value && globalConfig.value.plugins) { - return globalConfig.value.plugins.find( + if (globalConfig.value) { + let plugin = globalConfig.value.plugins?.find( (plugin: any) => plugin.language === language ) + + if (!plugin && globalConfig.value.custom_plugins) { + plugin = globalConfig.value.custom_plugins.find( + (plugin: any) => plugin.language === language + ) + } + + return plugin || null } return null } @@ -274,7 +306,8 @@ export function usePluginConfig(emit?: any) // 监听配置更新事件 onMounted(async () => { unlistenConfigUpdate = await listen('config-updated', async () => { - console.log('收到配置更新事件,重新加载配置') + console.log('收到配置更新事件,重新加载配置和语言列表') + await getSupportedLanguages() await getGlobalConfig() }) }) diff --git a/src/types/plugin.ts b/src/types/plugin.ts index 5d2b1c0..1063f0c 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -10,4 +10,5 @@ export default interface PluginConfig template?: string // 插件的模板 timeout?: number // 插件的超时时间 console_type?: string // 插件的输出类型 + icon_path?: string // 自定义图标路径 }