Cargo.lock 🔗
@@ -14605,12 +14605,12 @@ dependencies = [
"fs",
"gpui",
"log",
- "paths",
"schemars",
"serde",
"settings",
"theme",
"ui",
+ "util",
"workspace",
"workspace-hack",
]
Michael Sloan created
* 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.
Cargo.lock | 2
crates/paths/src/paths.rs | 88 +++++++++++++++++++++-------
crates/settings/src/vscode_import.rs | 49 +++++++++++++--
crates/settings_ui/Cargo.toml | 8 +-
crates/settings_ui/src/settings_ui.rs | 48 ++++++++-------
5 files changed, 137 insertions(+), 58 deletions(-)
@@ -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 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
}
-/// 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") {
+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 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?;
+ 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();
}