- feat(codex): Add “Apply to VS Code/Remove from VS Code” button on current Codex provider card
- feat(tauri): Add commands to read/write VS Code settings.json with cross-variant detection (Code/Insiders/VSCodium/OSS) - fix(vscode): Use top-level keys “chatgpt.apiBase” and “chatgpt.config.preferred_auth_method” - fix(vscode): Handle empty settings.json (skip deletes, direct write) to avoid “Can not delete in empty document” - fix(windows): Make atomic writes robust by removing target before rename - ui(provider-list): Improve error surfacing when applying/removing - chore(types): Extend window.api typings and tauri-api wrappers for VS Code commands - deps: Add jsonc-parser
This commit is contained in:
@@ -7,6 +7,8 @@ use tauri_plugin_opener::OpenerExt;
|
||||
use crate::app_config::AppType;
|
||||
use crate::codex_config;
|
||||
use crate::config::{get_claude_settings_path, ConfigStatus};
|
||||
use crate::vscode;
|
||||
use crate::config;
|
||||
use crate::provider::Provider;
|
||||
use crate::store::AppState;
|
||||
|
||||
@@ -633,3 +635,36 @@ pub async fn check_for_updates(handle: tauri::AppHandle) -> Result<bool, String>
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// VS Code: 获取用户 settings.json 状态
|
||||
#[tauri::command]
|
||||
pub async fn get_vscode_settings_status() -> Result<ConfigStatus, String> {
|
||||
if let Some(p) = vscode::find_existing_settings() {
|
||||
Ok(ConfigStatus { exists: true, path: p.to_string_lossy().to_string() })
|
||||
} else {
|
||||
// 默认返回 macOS 稳定版路径(或其他平台首选项的第一个候选),但标记不存在
|
||||
let preferred = vscode::candidate_settings_paths().into_iter().next();
|
||||
Ok(ConfigStatus { exists: false, path: preferred.unwrap_or_default().to_string_lossy().to_string() })
|
||||
}
|
||||
}
|
||||
|
||||
/// VS Code: 读取 settings.json 文本(仅当文件存在)
|
||||
#[tauri::command]
|
||||
pub async fn read_vscode_settings() -> Result<String, String> {
|
||||
if let Some(p) = vscode::find_existing_settings() {
|
||||
std::fs::read_to_string(&p).map_err(|e| format!("读取 VS Code 设置失败: {}", e))
|
||||
} else {
|
||||
Err("未找到 VS Code 用户设置文件".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// VS Code: 写入 settings.json 文本(仅当文件存在;不自动创建)
|
||||
#[tauri::command]
|
||||
pub async fn write_vscode_settings(content: String) -> Result<bool, String> {
|
||||
if let Some(p) = vscode::find_existing_settings() {
|
||||
config::write_text_file(&p, &content)?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Err("未找到 VS Code 用户设置文件".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +175,19 @@ pub fn atomic_write(path: &Path, data: &[u8]) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
fs::rename(&tmp, path).map_err(|e| format!("原子替换失败: {}", e))?;
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// Windows 上 rename 目标存在会失败,先移除再重命名(尽量接近原子性)
|
||||
if path.exists() {
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
fs::rename(&tmp, path).map_err(|e| format!("原子替换失败: {}", e))?;
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
fs::rename(&tmp, path).map_err(|e| format!("原子替换失败: {}", e))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ mod app_config;
|
||||
mod codex_config;
|
||||
mod commands;
|
||||
mod config;
|
||||
mod vscode;
|
||||
mod migration;
|
||||
mod provider;
|
||||
mod store;
|
||||
@@ -357,6 +358,9 @@ pub fn run() {
|
||||
commands::get_settings,
|
||||
commands::save_settings,
|
||||
commands::check_for_updates,
|
||||
commands::get_vscode_settings_status,
|
||||
commands::read_vscode_settings,
|
||||
commands::write_vscode_settings,
|
||||
update_tray_menu,
|
||||
]);
|
||||
|
||||
|
||||
61
src-tauri/src/vscode.rs
Normal file
61
src-tauri/src/vscode.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use std::path::{PathBuf};
|
||||
|
||||
/// 枚举可能的 VS Code 发行版配置目录名称
|
||||
fn vscode_product_dirs() -> Vec<&'static str> {
|
||||
vec![
|
||||
"Code", // VS Code Stable
|
||||
"Code - Insiders", // VS Code Insiders
|
||||
"VSCodium", // VSCodium
|
||||
"Code - OSS", // OSS 发行版
|
||||
]
|
||||
}
|
||||
|
||||
/// 获取 VS Code 用户 settings.json 的候选路径列表(按优先级排序)
|
||||
pub fn candidate_settings_paths() -> Vec<PathBuf> {
|
||||
let mut paths = Vec::new();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
for prod in vscode_product_dirs() {
|
||||
paths.push(
|
||||
home.join("Library").join("Application Support").join(prod).join("User").join("settings.json")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// Windows: %APPDATA%\Code\User\settings.json
|
||||
if let Some(roaming) = dirs::config_dir() {
|
||||
for prod in vscode_product_dirs() {
|
||||
paths.push(roaming.join(prod).join("User").join("settings.json"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
{
|
||||
// Linux: ~/.config/Code/User/settings.json
|
||||
if let Some(config) = dirs::config_dir() {
|
||||
for prod in vscode_product_dirs() {
|
||||
paths.push(config.join(prod).join("User").join("settings.json"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paths
|
||||
}
|
||||
|
||||
/// 返回第一个存在的 settings.json 路径
|
||||
pub fn find_existing_settings() -> Option<PathBuf> {
|
||||
for p in candidate_settings_paths() {
|
||||
if let Ok(meta) = std::fs::metadata(&p) {
|
||||
if meta.is_file() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
Reference in New Issue
Block a user