Improve logic for finding VSCode / Cursor settings files (#32721)
* Fixes a bug where for Cursor, `config_dir()` (Zed's config dir) was being used instead of `dirs::config_dir` (`~/.config` / `$XDG_CONFIG_HOME`). * Adds support for windows, before it was using the user profile folder + `/.config` which is incorrect. * Now looks using a variety of product names - `["Code", "Code - OSS", "Code Dev", "Code - OSS Dev", "code-oss-dev", "VSCodium"]`. * Now shows settings path that was read before confirming import. Including this path in the confirmation modal is a bit ugly (making it link-styled and clickable would be nice), but I think it's better to include it now that it is selecting the first match of a list of candidate paths:  Release Notes: - Added more settings file locations to check for VS Code / Cursor settings import.
This commit is contained in:
parent
afa70034d5
commit
a5ceef35fa
5 changed files with 138 additions and 59 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -14605,12 +14605,12 @@ dependencies = [
|
|||
"fs",
|
||||
"gpui",
|
||||
"log",
|
||||
"paths",
|
||||
"schemars",
|
||||
"serde",
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
"workspace",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! Paths to locations used by Zed.
|
||||
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
|
@ -106,6 +107,7 @@ pub fn data_dir() -> &'static PathBuf {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the path to the temp directory used by Zed.
|
||||
pub fn temp_dir() -> &'static PathBuf {
|
||||
static TEMP_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||
|
@ -426,32 +428,74 @@ pub fn global_ssh_config_file() -> &'static Path {
|
|||
Path::new("/etc/ssh/ssh_config")
|
||||
}
|
||||
|
||||
/// Returns the path to the vscode user settings file
|
||||
pub fn vscode_settings_file() -> &'static PathBuf {
|
||||
static LOGS_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||
let rel_path = "Code/User/settings.json";
|
||||
LOGS_DIR.get_or_init(|| {
|
||||
if cfg!(target_os = "macos") {
|
||||
home_dir()
|
||||
.join("Library/Application Support")
|
||||
.join(rel_path)
|
||||
} else {
|
||||
home_dir().join(".config").join(rel_path)
|
||||
}
|
||||
})
|
||||
/// Returns candidate paths for the vscode user settings file
|
||||
pub fn vscode_settings_file_paths() -> Vec<PathBuf> {
|
||||
let mut paths = vscode_user_data_paths();
|
||||
for path in paths.iter_mut() {
|
||||
path.push("User/settings.json");
|
||||
}
|
||||
paths
|
||||
}
|
||||
|
||||
/// Returns the path to the cursor user settings file
|
||||
pub fn cursor_settings_file() -> &'static PathBuf {
|
||||
static LOGS_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||
let rel_path = "Cursor/User/settings.json";
|
||||
LOGS_DIR.get_or_init(|| {
|
||||
if cfg!(target_os = "macos") {
|
||||
/// Returns candidate paths for the cursor user settings file
|
||||
pub fn cursor_settings_file_paths() -> Vec<PathBuf> {
|
||||
let mut paths = cursor_user_data_paths();
|
||||
for path in paths.iter_mut() {
|
||||
path.push("User/settings.json");
|
||||
}
|
||||
paths
|
||||
}
|
||||
|
||||
fn vscode_user_data_paths() -> Vec<PathBuf> {
|
||||
// https://github.com/microsoft/vscode/blob/23e7148cdb6d8a27f0109ff77e5b1e019f8da051/src/vs/platform/environment/node/userDataPath.ts#L45
|
||||
const VSCODE_PRODUCT_NAMES: &[&str] = &[
|
||||
"Code",
|
||||
"Code - OSS",
|
||||
"VSCodium",
|
||||
"Code Dev",
|
||||
"Code - OSS Dev",
|
||||
"code-oss-dev",
|
||||
];
|
||||
let mut paths = Vec::new();
|
||||
if let Ok(portable_path) = env::var("VSCODE_PORTABLE") {
|
||||
paths.push(Path::new(&portable_path).join("user-data"));
|
||||
}
|
||||
if let Ok(vscode_appdata) = env::var("VSCODE_APPDATA") {
|
||||
for product_name in VSCODE_PRODUCT_NAMES {
|
||||
paths.push(Path::new(&vscode_appdata).join(product_name));
|
||||
}
|
||||
}
|
||||
for product_name in VSCODE_PRODUCT_NAMES {
|
||||
add_vscode_user_data_paths(&mut paths, product_name);
|
||||
}
|
||||
paths
|
||||
}
|
||||
|
||||
fn cursor_user_data_paths() -> Vec<PathBuf> {
|
||||
let mut paths = Vec::new();
|
||||
add_vscode_user_data_paths(&mut paths, "Cursor");
|
||||
paths
|
||||
}
|
||||
|
||||
fn add_vscode_user_data_paths(paths: &mut Vec<PathBuf>, product_name: &str) {
|
||||
if cfg!(target_os = "macos") {
|
||||
paths.push(
|
||||
home_dir()
|
||||
.join("Library/Application Support")
|
||||
.join(rel_path)
|
||||
} else {
|
||||
config_dir().join(rel_path)
|
||||
.join(product_name),
|
||||
);
|
||||
} else if cfg!(target_os = "windows") {
|
||||
if let Some(data_local_dir) = dirs::data_local_dir() {
|
||||
paths.push(data_local_dir.join(product_name));
|
||||
}
|
||||
})
|
||||
if let Some(data_dir) = dirs::data_dir() {
|
||||
paths.push(data_dir.join(product_name));
|
||||
}
|
||||
} else {
|
||||
paths.push(
|
||||
dirs::config_dir()
|
||||
.unwrap_or(home_dir().join(".config"))
|
||||
.join(product_name),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use anyhow::Result;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use fs::Fs;
|
||||
use paths::{cursor_settings_file_paths, vscode_settings_file_paths};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{path::Path, rc::Rc, sync::Arc};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum VsCodeSettingsSource {
|
||||
|
@ -21,26 +21,59 @@ impl std::fmt::Display for VsCodeSettingsSource {
|
|||
|
||||
pub struct VsCodeSettings {
|
||||
pub source: VsCodeSettingsSource,
|
||||
pub path: Rc<Path>,
|
||||
content: Map<String, Value>,
|
||||
}
|
||||
|
||||
impl VsCodeSettings {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn from_str(content: &str, source: VsCodeSettingsSource) -> Result<Self> {
|
||||
Ok(Self {
|
||||
source,
|
||||
path: Path::new("/example-path/Code/User/settings.json").into(),
|
||||
content: serde_json_lenient::from_str(content)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn load_user_settings(source: VsCodeSettingsSource, fs: Arc<dyn Fs>) -> Result<Self> {
|
||||
let path = match source {
|
||||
VsCodeSettingsSource::VsCode => paths::vscode_settings_file(),
|
||||
VsCodeSettingsSource::Cursor => paths::cursor_settings_file(),
|
||||
let candidate_paths = match source {
|
||||
VsCodeSettingsSource::VsCode => vscode_settings_file_paths(),
|
||||
VsCodeSettingsSource::Cursor => cursor_settings_file_paths(),
|
||||
};
|
||||
let content = fs.load(path).await?;
|
||||
let mut path = None;
|
||||
for candidate_path in candidate_paths.iter() {
|
||||
if fs.is_file(candidate_path).await {
|
||||
path = Some(candidate_path.clone());
|
||||
}
|
||||
}
|
||||
let Some(path) = path else {
|
||||
return Err(anyhow!(
|
||||
"No settings file found, expected to find it in one of the following paths:\n{}",
|
||||
candidate_paths
|
||||
.into_iter()
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
));
|
||||
};
|
||||
let content = fs.load(&path).await.with_context(|| {
|
||||
format!(
|
||||
"Error loading {} settings file from {}",
|
||||
source,
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
let content = serde_json_lenient::from_str(&content).with_context(|| {
|
||||
format!(
|
||||
"Error parsing {} settings file from {}",
|
||||
source,
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
Ok(Self {
|
||||
source,
|
||||
content: serde_json_lenient::from_str(&content)?,
|
||||
path: path.into(),
|
||||
content,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -18,11 +18,11 @@ feature_flags.workspace = true
|
|||
fs.workspace = true
|
||||
gpui.workspace = true
|
||||
log.workspace = true
|
||||
paths.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
workspace.workspace = true
|
||||
util.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
serde.workspace = true
|
||||
schemars.workspace = true
|
||||
workspace.workspace = true
|
||||
|
|
|
@ -15,6 +15,7 @@ use schemars::JsonSchema;
|
|||
use serde::Deserialize;
|
||||
use settings::{SettingsStore, VsCodeSettingsSource};
|
||||
use ui::prelude::*;
|
||||
use util::truncate_and_remove_front;
|
||||
use workspace::item::{Item, ItemEvent};
|
||||
use workspace::{Workspace, with_active_or_new_workspace};
|
||||
|
||||
|
@ -129,33 +130,32 @@ async fn handle_import_vscode_settings(
|
|||
fs: Arc<dyn Fs>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) {
|
||||
let vscode = match settings::VsCodeSettings::load_user_settings(source, fs.clone()).await {
|
||||
Ok(vscode) => vscode,
|
||||
Err(err) => {
|
||||
println!(
|
||||
"Failed to load {source} settings: {}",
|
||||
err.context(format!(
|
||||
"Loading {source} settings from path: {:?}",
|
||||
paths::vscode_settings_file()
|
||||
))
|
||||
);
|
||||
|
||||
let _ = cx.prompt(
|
||||
gpui::PromptLevel::Info,
|
||||
&format!("Could not find or load a {source} settings file"),
|
||||
None,
|
||||
&["Ok"],
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let vscode_settings =
|
||||
match settings::VsCodeSettings::load_user_settings(source, fs.clone()).await {
|
||||
Ok(vscode_settings) => vscode_settings,
|
||||
Err(err) => {
|
||||
log::error!("{err}");
|
||||
let _ = cx.prompt(
|
||||
gpui::PromptLevel::Info,
|
||||
&format!("Could not find or load a {source} settings file"),
|
||||
None,
|
||||
&["Ok"],
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let prompt = if skip_prompt {
|
||||
Task::ready(Some(0))
|
||||
} else {
|
||||
let prompt = cx.prompt(
|
||||
gpui::PromptLevel::Warning,
|
||||
"Importing settings may overwrite your existing settings",
|
||||
&format!(
|
||||
"Importing {} settings may overwrite your existing settings. \
|
||||
Will import settings from {}",
|
||||
vscode_settings.source,
|
||||
truncate_and_remove_front(&vscode_settings.path.to_string_lossy(), 128),
|
||||
),
|
||||
None,
|
||||
&["Ok", "Cancel"],
|
||||
);
|
||||
|
@ -166,9 +166,11 @@ async fn handle_import_vscode_settings(
|
|||
}
|
||||
|
||||
cx.update(|_, cx| {
|
||||
let source = vscode_settings.source;
|
||||
let path = vscode_settings.path.clone();
|
||||
cx.global::<SettingsStore>()
|
||||
.import_vscode_settings(fs, vscode);
|
||||
log::info!("Imported settings from {source}");
|
||||
.import_vscode_settings(fs, vscode_settings);
|
||||
log::info!("Imported {source} settings from {}", path.display());
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue