diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 42589f1..c7603b9 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -6,10 +6,12 @@ version = 4 name = "CodeForge" version = "25.0.4" dependencies = [ + "async-trait", "chrono", "dirs", "fern", "fix-path-env", + "flate2", "futures-util", "log", "regex", @@ -17,6 +19,7 @@ dependencies = [ "rfd", "serde", "serde_json", + "tar", "tauri", "tauri-build", "tauri-plugin-dialog", @@ -26,6 +29,7 @@ dependencies = [ "tempfile", "tokio", "uuid", + "zip", ] [[package]] @@ -43,6 +47,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -88,6 +103,15 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "ashpd" version = "0.11.0" @@ -429,6 +453,25 @@ dependencies = [ "serde", ] +[[package]] +name = "bzip2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" +dependencies = [ + "bzip2-sys", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "cairo-rs" version = "0.18.5" @@ -502,6 +545,8 @@ version = "1.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -559,6 +604,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "combine" version = "4.6.7" @@ -578,6 +633,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "convert_case" version = "0.4.0" @@ -653,6 +714,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.5.0" @@ -759,6 +835,12 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "deflate64" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" + [[package]] name = "deranged" version = "0.4.0" @@ -769,6 +851,17 @@ dependencies = [ "serde", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "derive_more" version = "0.99.20" @@ -790,6 +883,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -1053,6 +1147,18 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "filetime" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.60.2", +] + [[package]] name = "fix-path-env" version = "0.0.0" @@ -1372,9 +1478,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -1586,6 +1694,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.11" @@ -1933,6 +2050,15 @@ dependencies = [ "cfb", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "io-uring" version = "0.7.9" @@ -2030,6 +2156,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -2139,6 +2275,7 @@ checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ "bitflags 2.9.1", "libc", + "redox_syscall", ] [[package]] @@ -2165,9 +2302,30 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] [[package]] name = "mac" @@ -2772,6 +2930,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -3745,6 +3913,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.9" @@ -3953,6 +4132,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "swift-rs" version = "1.0.7" @@ -4048,9 +4233,9 @@ dependencies = [ [[package]] name = "tao" -version = "0.34.2" +version = "0.34.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4daa814018fecdfb977b59a094df4bd43b42e8e21f88fddfc05807e6f46efaaf" +checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" dependencies = [ "bitflags 2.9.1", "block2 0.6.1", @@ -4097,6 +4282,17 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.12.16" @@ -4105,9 +4301,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.8.4" +version = "2.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d545ccf7b60dcd44e07c6fb5aeb09140966f0aabd5d2aa14a6821df7bc99348" +checksum = "8a3868da5508446a7cd08956d523ac3edf0a8bc20bf7e4038f9a95c2800d2033" dependencies = [ "anyhow", "bytes", @@ -4148,7 +4344,6 @@ dependencies = [ "tokio", "tray-icon", "url", - "urlpattern", "webkit2gtk", "webview2-com", "window-vibrancy", @@ -4157,9 +4352,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.4.0" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67945dbaf8920dbe3a1e56721a419a0c3d085254ab24cff5b9ad55e2b0016e0b" +checksum = "17fcb8819fd16463512a12f531d44826ce566f486d7ccd211c9c8cebdaec4e08" dependencies = [ "anyhow", "cargo_toml", @@ -4179,9 +4374,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.4.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a" +checksum = "9fa9844cefcf99554a16e0a278156ae73b0d8680bbc0e2ad1e4287aadd8489cf" dependencies = [ "base64 0.22.1", "brotli", @@ -4206,9 +4401,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.4.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e" +checksum = "3764a12f886d8245e66b7ee9b43ccc47883399be2019a61d80cf0f4117446fde" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -4237,9 +4432,9 @@ dependencies = [ [[package]] name = "tauri-plugin-dialog" -version = "2.3.2" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e5858cc7b455a73ab4ea2ebc08b5be33682c00ff1bf4cad5537d4fb62499d9" +checksum = "313f8138692ddc4a2127c4c9607d616a46f5c042e77b3722450866da0aad2f19" dependencies = [ "log", "raw-window-handle", @@ -4255,9 +4450,9 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" -version = "2.4.2" +version = "2.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "315784ec4be45e90a987687bae7235e6be3d6e9e350d2b75c16b8a4bf22c1db7" +checksum = "47df422695255ecbe7bac7012440eddaeefd026656171eac9559f5243d3230d9" dependencies = [ "anyhow", "dunce", @@ -4277,9 +4472,9 @@ dependencies = [ [[package]] name = "tauri-plugin-opener" -version = "2.4.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecee219f11cdac713ab32959db5d0cceec4810ba4f4458da992292ecf9660321" +checksum = "c26b72571d25dee25667940027114e60f569fc3974f8cefbe50c2cbc5fd65e3b" dependencies = [ "dunce", "glob", @@ -4320,9 +4515,9 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.8.0" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846" +checksum = "87f766fe9f3d1efc4b59b17e7a891ad5ed195fa8d23582abb02e6c9a01137892" dependencies = [ "cookie", "dpi", @@ -4345,9 +4540,9 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.8.1" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807" +checksum = "187a3f26f681bdf028f796ccf57cf478c1ee422c50128e5a0a6ebeb3f5910065" dependencies = [ "gtk", "http 1.3.1", @@ -4372,9 +4567,9 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.7.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212" +checksum = "76a423c51176eb3616ee9b516a9fa67fed5f0e78baaba680e44eb5dd2cc37490" dependencies = [ "anyhow", "brotli", @@ -5724,9 +5919,9 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "wry" -version = "0.53.2" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b6763512fe4b51c80b3ce9b50939d682acb4de335dfabbdb20d7a2642199b7" +checksum = "728b7d4c8ec8d81cab295e0b5b8a4c263c0d41a785fb8f8c4df284e5411140a2" dependencies = [ "base64 0.22.1", "block2 0.6.1", @@ -5788,6 +5983,25 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + [[package]] name = "yoke" version = "0.8.0" @@ -5914,6 +6128,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "zerotrie" version = "0.2.2" @@ -5947,6 +6181,76 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "getrandom 0.3.3", + "hmac", + "indexmap 2.10.0", + "lzma-rs", + "memchr", + "pbkdf2", + "sha1", + "thiserror 2.0.12", + "time", + "xz2", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "zvariant" version = "5.6.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 6d65b0b..a4946c5 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -14,10 +14,10 @@ tauri-build = { version = "2", features = [] } chrono = { version = "0.4.41", features = ["serde"] } [dependencies] -tauri = { version = "2", features = ["devtools"] } -tauri-plugin-opener = "2" +tauri = { version = "2.9", features = ["devtools"] } +tauri-plugin-opener = "2.5" tauri-plugin-shell = "2.0" -tauri-plugin-dialog = "2.0" +tauri-plugin-dialog = "2.4" tauri-plugin-fs = "2.4.2" serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -33,3 +33,7 @@ reqwest = { version = "0.11", features = ["json", "stream"] } futures-util = "0.3" rfd = "0.15" fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs" } +async-trait = "0.1" +zip = "2.2.2" +flate2 = "1.0" +tar = "0.4" diff --git a/src-tauri/src/env_commands.rs b/src-tauri/src/env_commands.rs new file mode 100644 index 0000000..fda94f2 --- /dev/null +++ b/src-tauri/src/env_commands.rs @@ -0,0 +1,49 @@ +use crate::env_manager::{EnvironmentInfo, EnvironmentManager}; +use log::info; +use tauri::{AppHandle, State}; +use tokio::sync::Mutex; + +pub type EnvironmentManagerState = Mutex; + +#[tauri::command] +pub async fn get_environment_info( + language: String, + env_manager: State<'_, EnvironmentManagerState>, +) -> Result { + info!("获取 {} 环境信息", language); + let manager = env_manager.lock().await; + manager.get_environment_info(&language).await +} + +#[tauri::command] +pub async fn download_and_install_version( + language: String, + version: String, + app_handle: AppHandle, + env_manager: State<'_, EnvironmentManagerState>, +) -> Result { + info!("下载并安装 {} 版本 {}", language, version); + let manager = env_manager.lock().await; + manager + .download_and_install_version(&language, &version, app_handle) + .await +} + +#[tauri::command] +pub async fn switch_environment_version( + language: String, + version: String, + env_manager: State<'_, EnvironmentManagerState>, +) -> Result<(), String> { + info!("切换 {} 到版本 {}", language, version); + let manager = env_manager.lock().await; + manager.switch_version(&language, &version).await +} + +#[tauri::command] +pub async fn get_supported_environment_languages( + env_manager: State<'_, EnvironmentManagerState>, +) -> Result, String> { + let manager = env_manager.lock().await; + Ok(manager.get_supported_languages()) +} diff --git a/src-tauri/src/env_manager.rs b/src-tauri/src/env_manager.rs new file mode 100644 index 0000000..78f293a --- /dev/null +++ b/src-tauri/src/env_manager.rs @@ -0,0 +1,177 @@ +use async_trait::async_trait; +use log::{error, info}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::path::PathBuf; +use tauri::{AppHandle, Emitter}; + +// 环境版本信息 +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct EnvironmentVersion { + pub version: String, + pub download_url: String, + pub install_path: Option, + pub is_installed: bool, + pub size: Option, + pub release_date: Option, +} + +// 环境管理器响应 +#[derive(Debug, Serialize, Deserialize)] +pub struct EnvironmentInfo { + pub language: String, + pub current_version: Option, + pub installed_versions: Vec, + pub available_versions: Vec, +} + +// 下载进度事件 +#[derive(Debug, Serialize, Clone)] +pub struct DownloadProgress { + pub language: String, + pub version: String, + pub downloaded: u64, + pub total: u64, + pub percentage: f64, + pub status: DownloadStatus, +} + +#[derive(Debug, Serialize, Clone)] +#[serde(rename_all = "lowercase")] +pub enum DownloadStatus { + Downloading, + Extracting, + Installing, + Completed, + _Failed, +} + +// 语言环境提供者特征 +#[async_trait] +pub trait EnvironmentProvider: Send + Sync { + // 获取语言名称 + fn get_language(&self) -> &'static str; + + // 获取可用版本列表 + async fn fetch_available_versions(&self) -> Result, String>; + + // 获取已安装版本列表 + async fn get_installed_versions(&self) -> Result, String>; + + // 下载并安装指定版本 + async fn download_and_install( + &self, + version: &str, + app_handle: AppHandle, + ) -> Result; + + // 切换到指定版本 + async fn switch_version(&self, version: &str) -> Result<(), String>; + + // 获取当前激活的版本 + async fn get_current_version(&self) -> Result, String>; + + // 获取安装目录 + #[allow(dead_code)] + fn get_install_dir(&self) -> PathBuf; +} + +// 环境管理器 +pub struct EnvironmentManager { + providers: HashMap>, +} + +impl EnvironmentManager { + pub fn new() -> Self { + Self { + providers: HashMap::new(), + } + } + + pub fn register_provider(&mut self, provider: Box) { + let language = provider.get_language().to_string(); + info!("注册环境提供者: {}", language); + self.providers.insert(language, provider); + } + + pub async fn get_environment_info(&self, language: &str) -> Result { + let provider = self + .providers + .get(language) + .ok_or_else(|| format!("暂未支持 {} 语言,请前往 github 提供 issus", language))?; + + info!("获取 {} 环境信息", language); + + let current_version = provider.get_current_version().await.ok().flatten(); + let installed_versions = provider.get_installed_versions().await.unwrap_or_default(); + let available_versions = provider + .fetch_available_versions() + .await + .unwrap_or_default(); + + Ok(EnvironmentInfo { + language: language.to_string(), + current_version, + installed_versions, + available_versions, + }) + } + + pub async fn download_and_install_version( + &self, + language: &str, + version: &str, + app_handle: AppHandle, + ) -> Result { + let provider = self + .providers + .get(language) + .ok_or_else(|| format!("暂未支持 {} 语言,请前往 github 提供 issus", language))?; + + info!("开始下载并安装 {} 版本 {}", language, version); + provider.download_and_install(version, app_handle).await + } + + pub async fn switch_version(&self, language: &str, version: &str) -> Result<(), String> { + let provider = self + .providers + .get(language) + .ok_or_else(|| format!("暂未支持 {} 语言,请前往 github 提供 issus", language))?; + + info!("切换 {} 到版本 {}", language, version); + provider.switch_version(version).await + } + + pub fn get_supported_languages(&self) -> Vec { + self.providers.keys().cloned().collect() + } +} + +// 辅助函数:发送下载进度事件 +pub fn emit_download_progress( + app_handle: &AppHandle, + language: &str, + version: &str, + downloaded: u64, + total: u64, + status: DownloadStatus, +) { + let percentage = if total > 0 { + (downloaded as f64 / total as f64) * 100.0 + } else { + 0.0 + }; + + let progress = DownloadProgress { + language: language.to_string(), + version: version.to_string(), + downloaded, + total, + percentage, + status, + }; + + if let Err(e) = app_handle.emit("env-download-progress", progress) { + error!("发送下载进度事件失败: {}", e); + } +} diff --git a/src-tauri/src/env_providers/mod.rs b/src-tauri/src/env_providers/mod.rs new file mode 100644 index 0000000..3d87810 --- /dev/null +++ b/src-tauri/src/env_providers/mod.rs @@ -0,0 +1,3 @@ +pub mod scala; + +pub use scala::ScalaEnvironmentProvider; diff --git a/src-tauri/src/env_providers/scala.rs b/src-tauri/src/env_providers/scala.rs new file mode 100644 index 0000000..d2500ef --- /dev/null +++ b/src-tauri/src/env_providers/scala.rs @@ -0,0 +1,703 @@ +use crate::env_manager::{ + DownloadStatus, EnvironmentProvider, EnvironmentVersion, emit_download_progress, +}; +use log::{error, info, warn}; +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; +use std::time::{Duration, SystemTime}; +use tauri::AppHandle; + +// Scala 版本信息(从 GitHub API 获取) +#[derive(Debug, Deserialize, Serialize, Clone)] +struct GithubRelease { + tag_name: String, + name: String, + published_at: String, + assets: Vec, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +struct GithubAsset { + name: String, + browser_download_url: String, + size: u64, +} + +// 缓存数据结构 +#[derive(Debug, Deserialize, Serialize)] +struct CachedReleases { + releases: Vec, + cached_at: SystemTime, +} + +pub struct ScalaEnvironmentProvider { + install_dir: PathBuf, + cache_file: PathBuf, +} + +impl ScalaEnvironmentProvider { + pub fn new() -> Self { + let install_dir = Self::get_default_install_dir(); + let cache_file = install_dir.join("releases_cache.json"); + + // 确保安装目录存在 + if let Err(e) = std::fs::create_dir_all(&install_dir) { + error!("创建 Scala 安装目录失败: {}", e); + } + + Self { + install_dir, + cache_file, + } + } + + fn get_default_install_dir() -> PathBuf { + let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); + home_dir.join(".codeforge").join("scala") + } + + // 从缓存读取版本列表 + fn read_cache(&self) -> Option> { + if !self.cache_file.exists() { + return None; + } + + match std::fs::read_to_string(&self.cache_file) { + Ok(content) => { + match serde_json::from_str::(&content) { + Ok(cached) => { + // 检查缓存是否过期(1小时) + if let Ok(elapsed) = SystemTime::now().duration_since(cached.cached_at) { + if elapsed < Duration::from_secs(3600) { + info!("使用缓存的 Scala 版本列表(缓存时间: {:?})", elapsed); + return Some(cached.releases); + } else { + info!("缓存已过期({:?}),将重新获取", elapsed); + } + } + } + Err(e) => { + warn!("解析缓存文件失败: {}", e); + } + } + } + Err(e) => { + warn!("读取缓存文件失败: {}", e); + } + } + + None + } + + // 写入缓存 + fn write_cache(&self, releases: &[GithubRelease]) { + let cached = CachedReleases { + releases: releases.to_vec(), + cached_at: SystemTime::now(), + }; + + match serde_json::to_string_pretty(&cached) { + Ok(content) => { + if let Err(e) = std::fs::write(&self.cache_file, content) { + warn!("写入缓存文件失败: {}", e); + } else { + info!("已缓存 Scala 版本列表"); + } + } + Err(e) => { + warn!("序列化缓存数据失败: {}", e); + } + } + } + + // 检测操作系统和架构,返回合适的下载文件模式 + fn get_download_pattern() -> &'static str { + if cfg!(target_os = "windows") { + "x86_64-pc-win32.zip" + } else if cfg!(target_os = "macos") { + if cfg!(target_arch = "aarch64") { + "aarch64-apple-darwin.tar.gz" + } else { + "x86_64-apple-darwin.tar.gz" + } + } else { + "x86_64-pc-linux.tar.gz" + } + } + + // 从 GitHub Releases 获取版本列表(支持缓存和 Token) + async fn fetch_github_releases(&self) -> Result, String> { + // 先尝试从缓存读取 + if let Some(cached_releases) = self.read_cache() { + return Ok(cached_releases); + } + + let url = "https://api.github.com/repos/scala/scala3/releases?per_page=20"; + + info!("从 GitHub API 获取 Scala 版本列表: {}", url); + + let client = reqwest::Client::builder() + .user_agent("CodeForge") + .build() + .map_err(|e| format!("创建 HTTP 客户端失败: {}", e))?; + + // 构建请求,如果有 GitHub Token 则添加认证头 + let mut request = client.get(url); + + // 尝试从环境变量获取 GitHub Token + if let Ok(token) = std::env::var("GITHUB_TOKEN") { + info!("使用 GITHUB_TOKEN 进行认证"); + request = request.header("Authorization", format!("token {}", token)); + } + + let response = request.send().await.map_err(|e| { + // 如果请求失败,尝试使用缓存(即使过期) + if let Some(_cached_releases) = self.read_cache_ignore_expiry() { + warn!("GitHub API 请求失败,使用过期缓存: {}", e); + return format!("GitHub API 请求失败,已使用缓存数据: {}", e); + } + format!("请求 GitHub API 失败: {}", e) + })?; + + let status = response.status(); + + // 处理 API 限流 + if status.as_u16() == 403 || status.as_u16() == 429 { + let error_msg = if let Ok(body) = response.text().await { + if body.contains("rate limit") { + warn!("GitHub API 限流,尝试使用缓存"); + // 尝试使用缓存(即使过期) + if let Some(cached_releases) = self.read_cache_ignore_expiry() { + return Ok(cached_releases); + } + format!( + "GitHub API 限流已超出。请稍后再试,或设置 GITHUB_TOKEN 环境变量以增加限额。详情: {}", + body + ) + } else { + format!("GitHub API 返回错误 ({}): {}", status, body) + } + } else { + format!("GitHub API 返回错误: {}", status) + }; + return Err(error_msg); + } + + if !status.is_success() { + return Err(format!("GitHub API 返回错误: {}", status)); + } + + let releases: Vec = response + .json() + .await + .map_err(|e| format!("解析 GitHub API 响应失败: {}", e))?; + + info!("成功获取 {} 个 Scala 版本", releases.len()); + + // 缓存结果 + self.write_cache(&releases); + + Ok(releases) + } + + // 读取缓存(忽略过期时间)- 用于 API 失败时的降级方案 + fn read_cache_ignore_expiry(&self) -> Option> { + if !self.cache_file.exists() { + return None; + } + + match std::fs::read_to_string(&self.cache_file) { + Ok(content) => match serde_json::from_str::(&content) { + Ok(cached) => { + info!("使用缓存的 Scala 版本列表(忽略过期时间)"); + Some(cached.releases) + } + Err(e) => { + warn!("解析缓存文件失败: {}", e); + None + } + }, + Err(e) => { + warn!("读取缓存文件失败: {}", e); + None + } + } + } + + // 获取指定版本的安装路径 + fn get_version_install_path(&self, version: &str) -> PathBuf { + self.install_dir.join(version) + } + + // 检查版本是否已安装 + fn is_version_installed(&self, version: &str) -> bool { + let install_path = self.get_version_install_path(version); + if !install_path.exists() { + return false; + } + + // 检查是否有包含 bin 目录的子目录 + if let Ok(entries) = std::fs::read_dir(&install_path) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() && path.join("bin").exists() { + return true; + } + } + } + + false + } + + // 下载文件并显示进度 + async fn download_file( + &self, + url: &str, + dest: &PathBuf, + app_handle: AppHandle, + version: &str, + ) -> Result<(), String> { + info!("开始下载: {} -> {}", url, dest.display()); + + let client = reqwest::Client::builder() + .user_agent("CodeForge") + .build() + .map_err(|e| format!("创建 HTTP 客户端失败: {}", e))?; + + let response = client + .get(url) + .send() + .await + .map_err(|e| format!("下载失败: {}", e))?; + + if !response.status().is_success() { + return Err(format!("下载失败: HTTP {}", response.status())); + } + + let total_size = response.content_length().unwrap_or(0); + info!("文件大小: {} bytes", total_size); + + emit_download_progress( + &app_handle, + "scala", + version, + 0, + total_size, + DownloadStatus::Downloading, + ); + + // 创建目标文件 + let mut file = std::fs::File::create(dest).map_err(|e| format!("创建文件失败: {}", e))?; + + let mut downloaded: u64 = 0; + let mut stream = response.bytes_stream(); + + use futures_util::StreamExt; + use std::io::Write; + + while let Some(chunk) = stream.next().await { + let chunk = chunk.map_err(|e| format!("下载数据失败: {}", e))?; + file.write_all(&chunk) + .map_err(|e| format!("写入文件失败: {}", e))?; + + downloaded += chunk.len() as u64; + + // 每下载 1MB 发送一次进度更新 + if downloaded % (1024 * 1024) == 0 || downloaded == total_size { + emit_download_progress( + &app_handle, + "scala", + version, + downloaded, + total_size, + DownloadStatus::Downloading, + ); + } + } + + info!("下载完成: {}", dest.display()); + Ok(()) + } + + // 解压文件 + async fn extract_archive( + &self, + archive_path: &PathBuf, + dest_dir: &PathBuf, + app_handle: AppHandle, + version: &str, + ) -> Result<(), String> { + info!( + "开始解压: {} -> {}", + archive_path.display(), + dest_dir.display() + ); + + emit_download_progress( + &app_handle, + "scala", + version, + 0, + 0, + DownloadStatus::Extracting, + ); + + std::fs::create_dir_all(dest_dir).map_err(|e| format!("创建目录失败: {}", e))?; + + if archive_path.extension().and_then(|s| s.to_str()) == Some("zip") { + // 解压 ZIP 文件 + self.extract_zip(archive_path, dest_dir)?; + } else { + // 解压 tar.gz 文件 + self.extract_tar_gz(archive_path, dest_dir)?; + } + + info!("解压完成"); + Ok(()) + } + + fn extract_zip(&self, archive_path: &PathBuf, dest_dir: &Path) -> Result<(), String> { + use zip::ZipArchive; + + let file = + std::fs::File::open(archive_path).map_err(|e| format!("打开压缩文件失败: {}", e))?; + + let mut archive = ZipArchive::new(file).map_err(|e| format!("读取 ZIP 文件失败: {}", e))?; + + for i in 0..archive.len() { + let mut file = archive + .by_index(i) + .map_err(|e| format!("读取 ZIP 条目失败: {}", e))?; + + let outpath = match file.enclosed_name() { + Some(path) => dest_dir.join(path), + None => continue, + }; + + if file.name().ends_with('/') { + std::fs::create_dir_all(&outpath).map_err(|e| format!("创建目录失败: {}", e))?; + } else { + if let Some(p) = outpath.parent() { + std::fs::create_dir_all(p).map_err(|e| format!("创建目录失败: {}", e))?; + } + let mut outfile = + std::fs::File::create(&outpath).map_err(|e| format!("创建文件失败: {}", e))?; + std::io::copy(&mut file, &mut outfile) + .map_err(|e| format!("解压文件失败: {}", e))?; + } + + // Unix 权限 + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + if let Some(mode) = file.unix_mode() { + std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode)).ok(); + } + } + } + + Ok(()) + } + + fn extract_tar_gz(&self, archive_path: &PathBuf, dest_dir: &PathBuf) -> Result<(), String> { + use flate2::read::GzDecoder; + use tar::Archive; + + let file = + std::fs::File::open(archive_path).map_err(|e| format!("打开压缩文件失败: {}", e))?; + + let gz = GzDecoder::new(file); + let mut archive = Archive::new(gz); + + archive + .unpack(dest_dir) + .map_err(|e| format!("解压 tar.gz 失败: {}", e))?; + + Ok(()) + } + + // 更新配置以使用新版本 + async fn update_plugin_config(&self, version: &str, install_path: &str) -> Result<(), String> { + use crate::config::{get_app_config_internal, update_app_config}; + + info!( + "更新 Scala 插件配置: 版本={}, 路径={}", + version, install_path + ); + + let mut config = get_app_config_internal().map_err(|e| format!("获取配置失败: {}", e))?; + + if let Some(ref mut plugins) = config.plugins { + if let Some(scala_plugin) = plugins.iter_mut().find(|p| p.language == "scala") { + // execute_home 应该是包含 bin 目录的父目录 + scala_plugin.execute_home = Some(install_path.to_string()); + + // 根据操作系统设置 run_command + let run_cmd = if cfg!(target_os = "windows") { + "bin/scala.bat $filename" + } else { + "bin/scala $filename" + }; + scala_plugin.run_command = Some(String::from(run_cmd)); + + info!( + "已更新 Scala 插件配置: execute_home={}, run_command={}", + install_path, run_cmd + ); + } + } + + update_app_config(config) + .await + .map_err(|e| format!("保存配置失败: {}", e))?; + + Ok(()) + } +} + +#[async_trait::async_trait] +impl EnvironmentProvider for ScalaEnvironmentProvider { + fn get_language(&self) -> &'static str { + "scala" + } + + async fn fetch_available_versions(&self) -> Result, String> { + let releases = self.fetch_github_releases().await?; + let pattern = Self::get_download_pattern(); + + let mut versions = Vec::new(); + + for release in releases { + // 查找匹配当前平台的资源 + if let Some(asset) = release.assets.iter().find(|a| a.name.contains(pattern)) { + let version = release.tag_name.trim_start_matches('v').to_string(); + let is_installed = self.is_version_installed(&version); + + // 如果已安装,查找实际的包含 bin 目录的路径 + let install_path = if is_installed { + let version_dir = self.get_version_install_path(&version); + let mut actual_path = version_dir.clone(); + + if let Ok(entries) = std::fs::read_dir(&version_dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() && path.join("bin").exists() { + actual_path = path; + break; + } + } + } + + Some(actual_path.to_string_lossy().to_string()) + } else { + None + }; + + versions.push(EnvironmentVersion { + version: version.clone(), + download_url: asset.browser_download_url.clone(), + install_path, + is_installed, + size: Some(asset.size), + release_date: Some(release.published_at.clone()), + }); + } + } + + Ok(versions) + } + + async fn get_installed_versions(&self) -> Result, String> { + let mut installed = Vec::new(); + + if !self.install_dir.exists() { + return Ok(installed); + } + + let entries = + std::fs::read_dir(&self.install_dir).map_err(|e| format!("读取安装目录失败: {}", e))?; + + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + let version = path + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or("") + .to_string(); + + if self.is_version_installed(&version) { + // 查找实际的包含 bin 目录的路径 + let mut actual_install_path = path.clone(); + if let Ok(sub_entries) = std::fs::read_dir(&path) { + for sub_entry in sub_entries.flatten() { + let sub_path = sub_entry.path(); + if sub_path.is_dir() && sub_path.join("bin").exists() { + actual_install_path = sub_path; + break; + } + } + } + + installed.push(EnvironmentVersion { + version: version.clone(), + download_url: String::new(), + install_path: Some(actual_install_path.to_string_lossy().to_string()), + is_installed: true, + size: None, + release_date: None, + }); + } + } + } + + Ok(installed) + } + + async fn download_and_install( + &self, + version: &str, + app_handle: AppHandle, + ) -> Result { + info!("开始下载并安装 Scala {}", version); + + // 检查是否已安装 + if self.is_version_installed(version) { + return Err(format!("Scala {} 已经安装", version)); + } + + emit_download_progress( + &app_handle, + "scala", + version, + 0, + 0, + DownloadStatus::Downloading, + ); + + // 获取可用版本 + let available_versions = self.fetch_available_versions().await?; + let version_info = available_versions + .iter() + .find(|v| v.version == version) + .ok_or_else(|| format!("未找到版本: {}", version))?; + + // 下载文件 + let download_url = &version_info.download_url; + let file_name = download_url + .split('/') + .last() + .ok_or_else(|| "无效的下载 URL".to_string())?; + let temp_file = std::env::temp_dir().join(file_name); + + self.download_file(download_url, &temp_file, app_handle.clone(), version) + .await?; + + // 解压到安装目录 + let install_path = self.get_version_install_path(version); + self.extract_archive(&temp_file, &install_path, app_handle.clone(), version) + .await?; + + // 清理临时文件 + std::fs::remove_file(&temp_file).ok(); + + emit_download_progress( + &app_handle, + "scala", + version, + 0, + 0, + DownloadStatus::Installing, + ); + + // 查找解压后的实际目录(可能包含版本号前缀) + let mut actual_install_path = install_path.clone(); + if let Ok(entries) = std::fs::read_dir(&install_path) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() && path.join("bin").exists() { + actual_install_path = path; + break; + } + } + } + + // 更新插件配置 + self.update_plugin_config(version, &actual_install_path.to_string_lossy()) + .await?; + + emit_download_progress( + &app_handle, + "scala", + version, + 0, + 0, + DownloadStatus::Completed, + ); + + info!("Scala {} 安装成功", version); + Ok(actual_install_path.to_string_lossy().to_string()) + } + + async fn switch_version(&self, version: &str) -> Result<(), String> { + info!("切换 Scala 版本到 {}", version); + + if !self.is_version_installed(version) { + return Err(format!("版本 {} 未安装", version)); + } + + let install_path = self.get_version_install_path(version); + + // 查找实际的安装目录 + let mut actual_install_path = install_path.clone(); + if let Ok(entries) = std::fs::read_dir(&install_path) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() && path.join("bin").exists() { + actual_install_path = path; + break; + } + } + } + + self.update_plugin_config(version, &actual_install_path.to_string_lossy()) + .await?; + + info!("成功切换到 Scala {}", version); + Ok(()) + } + + async fn get_current_version(&self) -> Result, String> { + use crate::config::get_app_config_internal; + + let config = get_app_config_internal().map_err(|e| format!("获取配置失败: {}", e))?; + + if let Some(plugins) = config.plugins { + if let Some(scala_plugin) = plugins.iter().find(|p| p.language == "scala") { + if let Some(ref execute_home) = scala_plugin.execute_home { + // 从路径中提取版本号 + // execute_home 格式: ~/.codeforge/scala/3.8.0-RC4/scala3-3.8.0-RC4-aarch64-apple-darwin + // 我们需要提取 3.8.0-RC4 + let path = PathBuf::from(execute_home); + + // 检查路径是否在安装目录下 + if let Ok(relative) = path.strip_prefix(&self.install_dir) { + // 获取第一个路径组件(版本号) + if let Some(version_component) = relative.components().next() { + if let Some(version) = version_component.as_os_str().to_str() { + info!("当前 Scala 版本: {}", version); + return Ok(Some(version.to_string())); + } + } + } + } + } + } + + Ok(None) + } + + fn get_install_dir(&self) -> PathBuf { + self.install_dir.clone() + } +} diff --git a/src-tauri/src/example.rs b/src-tauri/src/example.rs index 57bfcf0..1ef5106 100644 --- a/src-tauri/src/example.rs +++ b/src-tauri/src/example.rs @@ -18,7 +18,7 @@ pub async fn load_example( let manager = plugin_manager.lock().await; let plugin = manager .get_plugin(&language) - .ok_or_else(|| format!("不支持的语言: {}", language))?; + .ok_or_else(|| format!("暂未支持 {} 语言,请前往 github 提供 issus", language))?; // 获取该语言的文件扩展名 let file_extension = plugin.get_file_extension(); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index bd08120..2dff372 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -4,6 +4,9 @@ )] mod config; +mod env_commands; +mod env_manager; +mod env_providers; mod example; mod execution; mod font; @@ -14,6 +17,12 @@ mod setup; mod update; mod utils; +use crate::env_commands::{ + EnvironmentManagerState, download_and_install_version, get_environment_info, + get_supported_environment_languages, switch_environment_version, +}; +use crate::env_manager::EnvironmentManager; +use crate::env_providers::ScalaEnvironmentProvider; use crate::execution::{ ExecutionHistory, PluginManagerState as ExecutionPluginManagerState, clear_execution_history, execute_code, get_execution_history, is_execution_running, stop_execution, @@ -34,6 +43,10 @@ fn main() { // 设置系统环境变量 let _ = fix_path_env::fix(); + // 初始化环境管理器 + let mut env_manager = EnvironmentManager::new(); + env_manager.register_provider(Box::new(ScalaEnvironmentProvider::new())); + tauri::Builder::default() .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_dialog::init()) @@ -41,6 +54,7 @@ fn main() { .plugin(tauri_plugin_fs::init()) .manage(ExecutionHistory::default()) .manage(ExecutionPluginManagerState::new(PluginManager::new())) + .manage(EnvironmentManagerState::new(env_manager)) .setup(|app| { // 第一步:初始化配置系统 if let Err(e) = init_config(Some(app.handle())) { @@ -74,6 +88,11 @@ fn main() { // 信息相关命令 get_info, get_supported_languages, + // 环境管理相关命令 + get_environment_info, + download_and_install_version, + switch_environment_version, + get_supported_environment_languages, // 应用信息命令 get_app_info, // 日志相关命令 diff --git a/src-tauri/src/plugins/scala.rs b/src-tauri/src/plugins/scala.rs index 7ed1b4d..1e5253d 100644 --- a/src-tauri/src/plugins/scala.rs +++ b/src-tauri/src/plugins/scala.rs @@ -31,14 +31,21 @@ impl LanguagePlugin for ScalaPlugin { } fn get_default_config(&self) -> PluginConfig { + // Windows 使用 .bat,Unix-like 系统使用无后缀的可执行文件 + let (run_cmd, after_cmd) = if cfg!(target_os = "windows") { + ("bin/scala.bat $filename", "del /f *.class") + } else { + ("bin/scala $filename", "rm -f *.class") + }; + PluginConfig { enabled: true, language: String::from("scala"), before_compile: None, extension: String::from("scala"), execute_home: None, - run_command: Some(String::from("scala $filename")), - after_compile: Some(String::from("rm -f *.class")), + run_command: Some(String::from(run_cmd)), + after_compile: Some(String::from(after_cmd)), template: Some(String::from("// 在这里输入 Scala 代码")), timeout: Some(45), console_type: Some(String::from("console")), diff --git a/src/App.vue b/src/App.vue index c59bd45..8a0b501 100644 --- a/src/App.vue +++ b/src/App.vue @@ -114,6 +114,7 @@ const { getLanguageDisplayName, getCurrentConsoleType, handleLanguageChange, + refreshLanguageList, initialize } = useLanguageManager(code, clearOutput, toast) @@ -136,13 +137,13 @@ const { const editorConfigKey = ref(0) const consoleType = ref('console') -// 处理设置变更 -const handleSettingsChanged = (config: any) => { +const handleSettingsChanged = async (config: any) => { console.log('主组件接收到设置变更:', config) - // 延迟一点点再刷新,减少闪烁 setTimeout(() => { editorConfigKey.value++ }, 50) + + await refreshLanguageList() } const loadExample = (content: string) => { diff --git a/src/components/Settings.vue b/src/components/Settings.vue index 52224fc..21a5e96 100644 --- a/src/components/Settings.vue +++ b/src/components/Settings.vue @@ -1,5 +1,5 @@