dev-manager-tauri/src-tauri/src/commands/shell.rs
lanrtop 533c069e43 feat: add WSL editor launch modes and update checking functionality
- Introduced WSL editor launch modes in SettingsPage with options for internal, remote, and UNC path launches.
- Enhanced settings saving to include the new WSL launch mode.
- Added an update section to check for application updates, download, and install them with progress tracking.
- Updated Toast component to handle error messages with a copy button for convenience.
- Expanded commands to include project tags and templates management, along with WSL proxy synchronization features.
2026-04-01 23:12:23 +09:00

162 lines
6.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::db::pool;
use rusqlite::params;
use std::path::Path;
use std::process::Command;
/// 将 UNC WSL 路径转换为 Linux 路径
/// \\wsl.localhost\Ubuntu\home\user\proj → /home/user/proj
/// \\wsl$\Ubuntu\home\user\proj → /home/user/proj
/// /home/user/proj → /home/user/proj直接返回
fn unc_to_linux(path: &str) -> String {
for prefix in &["\\\\wsl.localhost\\", "\\\\wsl$\\"] {
if path.to_lowercase().starts_with(&prefix.to_lowercase()) {
let rest = &path[prefix.len()..];
// 去掉 distro 名(第一段),剩余部分转 /
let after_distro = rest.splitn(2, '\\').nth(1).unwrap_or("");
return format!("/{}", after_distro.replace('\\', "/"));
}
}
// 已经是 Linux 路径,直接返回
path.to_string()
}
fn get_setting(key: &str) -> Option<String> {
let conn = pool().get().ok()?;
// 先查全局 settings 表
if let Ok(val) = conn.query_row(
"SELECT value FROM settings WHERE key = ?1",
params![key],
|row| row.get::<_, String>(0),
) {
if !val.is_empty() {
return Some(val);
}
}
// 兼容旧数据:回退查 space_settings任意空间取第一条
conn.query_row(
"SELECT value FROM space_settings WHERE key = ?1 LIMIT 1",
params![key],
|row| row.get::<_, String>(0),
).ok()
}
#[tauri::command]
pub fn open_editor(project_id: String, env: String, editor_path: String) -> Result<String, String> {
let conn = pool().get().map_err(|e| e.to_string())?;
let (wsl_path, win_path): (Option<String>, Option<String>) = conn
.query_row(
"SELECT wsl_path, win_path FROM project_workspaces WHERE id = ?1",
params![project_id],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.map_err(|_| format!("Project not found: {project_id}"))?;
let wsl_distro = get_setting("wsl_distro").unwrap_or_else(|| "Ubuntu".into());
// WSL 环境:按 wsl_open_mode 配置自适应启动
// wsl_internal (默认): wsl -d <distro> -- <editor> <linux_path>
// remote_flag : <editor> --remote wsl+<distro> <linux_path>
// unc_path : <editor> <unc_path>(直接用 UNC 路径,不走 Remote
if env == "wsl" {
let path = wsl_path.ok_or("WSL path not configured")?;
let open_mode = get_setting("wsl_open_mode")
.unwrap_or_else(|| "wsl_internal".into());
if open_mode == "wsl_internal" {
// 直接调用 wsl 进程,用参数数组传路径,避免 cmd /c 的引号转义问题
let linux_path = unc_to_linux(&path);
let cmd_name = std::path::Path::new(&editor_path)
.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.to_lowercase())
.unwrap_or_else(|| editor_path.clone());
Command::new("wsl")
.args(["-d", &wsl_distro, "--", &cmd_name, &linux_path])
.spawn()
.map_err(|e| format!("无法启动编辑器: {e}"))?;
return Ok(format!("wsl -d {} -- {} {}", wsl_distro, cmd_name, linux_path));
}
let cmd_str = match open_mode.as_str() {
"remote_flag" => {
let linux_path = unc_to_linux(&path);
format!("\"{}\" --remote wsl+{} \"{}\"", editor_path, wsl_distro, linux_path)
}
_ => {
// unc_path直接用原始路径不走 Remote
format!("\"{}\" \"{}\"", editor_path, path)
}
};
Command::new("cmd")
.args(["/c", &cmd_str])
.spawn()
.map_err(|e| format!("无法启动编辑器: {e}"))?;
return Ok(cmd_str);
}
let args: Vec<String> = match env.as_str() {
"win" => {
let path = win_path.ok_or("Windows path not configured")?;
vec![path]
}
_ => return Err(format!("Unknown env: {env}")),
};
// Windows 上编辑器可能是 .cmd 脚本,通过 cmd /c 让 shell 解析
#[cfg(target_os = "windows")]
let spawn_result = Command::new("cmd")
.args(["/c", &editor_path])
.args(&args)
.spawn();
#[cfg(not(target_os = "windows"))]
let spawn_result = Command::new(&editor_path).args(&args).spawn();
spawn_result.map_err(|e| {
format!("无法启动编辑器 \"{editor_path}\": {e}\n请在设置中检查编辑器路径是否正确")
})?;
Ok(format!("{editor_path} {}", args.join(" ")))
}
#[tauri::command]
pub fn open_terminal(project_id: String, env: String) -> Result<String, String> {
let conn = pool().get().map_err(|e| e.to_string())?;
let (wsl_path, win_path): (Option<String>, Option<String>) = conn
.query_row(
"SELECT wsl_path, win_path FROM project_workspaces WHERE id = ?1",
params![project_id],
|row| Ok((row.get(0)?, row.get(1)?)),
)
.map_err(|_| format!("Project not found: {project_id}"))?;
let wsl_distro = get_setting("wsl_distro").unwrap_or_else(|| "Ubuntu".into());
let args: Vec<String> = match env.as_str() {
"wsl" => {
let path = wsl_path.ok_or("WSL path not configured")?;
vec!["-p".into(), wsl_distro, "--".into(), "wsl".into(), "--cd".into(), path]
}
"win" => {
let path = win_path.ok_or("Windows path not configured")?;
if Path::new(&path).exists() {
vec!["-p".into(), "PowerShell".into(), "-d".into(), path]
} else {
vec!["-p".into(), "PowerShell".into()]
}
}
_ => return Err(format!("Unknown env: {env}")),
};
#[cfg(target_os = "windows")]
let term_result = Command::new("cmd").args(["/c", "wt"]).args(&args).spawn();
#[cfg(not(target_os = "windows"))]
let term_result = Command::new("wt").args(&args).spawn();
term_result.map_err(|e| format!("无法启动终端: {e}"))?;
Ok(format!("wt {}", args.join(" ")))
}