Cursor settings import (#31424)
Closes #ISSUE Release Notes: - Added support for importing settings from cursor. Cursor settings can be imported using the `zed: import cursor settings` command from the command palette
This commit is contained in:
parent
21bd91a773
commit
b9a5d437db
6 changed files with 147 additions and 48 deletions
|
@ -1493,8 +1493,27 @@ impl settings::Settings for AllLanguageSettings {
|
||||||
associations.entry(v.into()).or_default().push(k.clone());
|
associations.entry(v.into()).or_default().push(k.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: do we want to merge imported globs per filetype? for now we'll just replace
|
// TODO: do we want to merge imported globs per filetype? for now we'll just replace
|
||||||
current.file_types.extend(associations);
|
current.file_types.extend(associations);
|
||||||
|
|
||||||
|
// cursor global ignore list applies to cursor-tab, so transfer it to edit_predictions.disabled_globs
|
||||||
|
if let Some(disabled_globs) = vscode
|
||||||
|
.read_value("cursor.general.globalCursorIgnoreList")
|
||||||
|
.and_then(|v| v.as_array())
|
||||||
|
{
|
||||||
|
current
|
||||||
|
.edit_predictions
|
||||||
|
.get_or_insert_default()
|
||||||
|
.disabled_globs
|
||||||
|
.get_or_insert_default()
|
||||||
|
.extend(
|
||||||
|
disabled_globs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|glob| glob.as_str())
|
||||||
|
.map(|s| s.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -439,3 +439,18 @@ pub fn vscode_settings_file() -> &'static PathBuf {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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") {
|
||||||
|
home_dir()
|
||||||
|
.join("Library/Application Support")
|
||||||
|
.join(rel_path)
|
||||||
|
} else {
|
||||||
|
config_dir().join(rel_path)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub use settings_store::{
|
||||||
InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources,
|
InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources,
|
||||||
SettingsStore, parse_json_with_comments,
|
SettingsStore, parse_json_with_comments,
|
||||||
};
|
};
|
||||||
pub use vscode_import::VsCodeSettings;
|
pub use vscode_import::{VsCodeSettings, VsCodeSettingsSource};
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
|
||||||
pub struct WorktreeId(usize);
|
pub struct WorktreeId(usize);
|
||||||
|
|
|
@ -1522,6 +1522,8 @@ pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T>
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::VsCodeSettingsSource;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
use unindent::Unindent;
|
use unindent::Unindent;
|
||||||
|
@ -2004,7 +2006,10 @@ mod tests {
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
store.set_user_settings(&old, cx).ok();
|
store.set_user_settings(&old, cx).ok();
|
||||||
let new = store.get_vscode_edits(old, &VsCodeSettings::from_str(&vscode).unwrap());
|
let new = store.get_vscode_edits(
|
||||||
|
old,
|
||||||
|
&VsCodeSettings::from_str(&vscode, VsCodeSettingsSource::VsCode).unwrap(),
|
||||||
|
);
|
||||||
pretty_assertions::assert_eq!(new, expected);
|
pretty_assertions::assert_eq!(new, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,20 +4,42 @@ use serde_json::{Map, Value};
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum VsCodeSettingsSource {
|
||||||
|
VsCode,
|
||||||
|
Cursor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for VsCodeSettingsSource {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
VsCodeSettingsSource::VsCode => write!(f, "VS Code"),
|
||||||
|
VsCodeSettingsSource::Cursor => write!(f, "Cursor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct VsCodeSettings {
|
pub struct VsCodeSettings {
|
||||||
|
pub source: VsCodeSettingsSource,
|
||||||
content: Map<String, Value>,
|
content: Map<String, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VsCodeSettings {
|
impl VsCodeSettings {
|
||||||
pub fn from_str(content: &str) -> Result<Self> {
|
pub fn from_str(content: &str, source: VsCodeSettingsSource) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
source,
|
||||||
content: serde_json_lenient::from_str(content)?,
|
content: serde_json_lenient::from_str(content)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_user_settings(fs: Arc<dyn Fs>) -> Result<Self> {
|
pub async fn load_user_settings(source: VsCodeSettingsSource, fs: Arc<dyn Fs>) -> Result<Self> {
|
||||||
let content = fs.load(paths::vscode_settings_file()).await?;
|
let path = match source {
|
||||||
|
VsCodeSettingsSource::VsCode => paths::vscode_settings_file(),
|
||||||
|
VsCodeSettingsSource::Cursor => paths::cursor_settings_file(),
|
||||||
|
};
|
||||||
|
let content = fs.load(path).await?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
source,
|
||||||
content: serde_json_lenient::from_str(&content)?,
|
content: serde_json_lenient::from_str(&content)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
mod appearance_settings_controls;
|
mod appearance_settings_controls;
|
||||||
|
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
use editor::EditorSettingsControls;
|
use editor::EditorSettingsControls;
|
||||||
|
@ -12,7 +13,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use settings::SettingsStore;
|
use settings::{SettingsStore, VsCodeSettingsSource};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
use workspace::item::{Item, ItemEvent};
|
use workspace::item::{Item, ItemEvent};
|
||||||
|
@ -31,7 +32,13 @@ pub struct ImportVsCodeSettings {
|
||||||
pub skip_prompt: bool,
|
pub skip_prompt: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_actions!(zed, [ImportVsCodeSettings]);
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema)]
|
||||||
|
pub struct ImportCursorSettings {
|
||||||
|
#[serde(default)]
|
||||||
|
pub skip_prompt: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_actions!(zed, [ImportVsCodeSettings, ImportCursorSettings]);
|
||||||
actions!(zed, [OpenSettingsEditor]);
|
actions!(zed, [OpenSettingsEditor]);
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
|
@ -61,49 +68,30 @@ pub fn init(cx: &mut App) {
|
||||||
|
|
||||||
window
|
window
|
||||||
.spawn(cx, async move |cx: &mut AsyncWindowContext| {
|
.spawn(cx, async move |cx: &mut AsyncWindowContext| {
|
||||||
let vscode =
|
handle_import_vscode_settings(
|
||||||
match settings::VsCodeSettings::load_user_settings(fs.clone()).await {
|
VsCodeSettingsSource::VsCode,
|
||||||
Ok(vscode) => vscode,
|
action.skip_prompt,
|
||||||
Err(err) => {
|
fs,
|
||||||
println!(
|
cx,
|
||||||
"Failed to load VsCode settings: {}",
|
)
|
||||||
err.context(format!(
|
.await
|
||||||
"Loading VsCode settings from path: {:?}",
|
|
||||||
paths::vscode_settings_file()
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = cx.prompt(
|
|
||||||
gpui::PromptLevel::Info,
|
|
||||||
"Could not find or load a VsCode settings file",
|
|
||||||
None,
|
|
||||||
&["Ok"],
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let prompt = if action.skip_prompt {
|
|
||||||
Task::ready(Some(0))
|
|
||||||
} else {
|
|
||||||
let prompt = cx.prompt(
|
|
||||||
gpui::PromptLevel::Warning,
|
|
||||||
"Importing settings may overwrite your existing settings",
|
|
||||||
None,
|
|
||||||
&["Ok", "Cancel"],
|
|
||||||
);
|
|
||||||
cx.spawn(async move |_| prompt.await.ok())
|
|
||||||
};
|
|
||||||
if prompt.await != Some(0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.update(|_, cx| {
|
|
||||||
cx.global::<SettingsStore>()
|
|
||||||
.import_vscode_settings(fs, vscode);
|
|
||||||
log::info!("Imported settings from VsCode");
|
|
||||||
})
|
})
|
||||||
.ok();
|
.detach();
|
||||||
|
});
|
||||||
|
|
||||||
|
workspace.register_action(|_workspace, action: &ImportCursorSettings, window, cx| {
|
||||||
|
let fs = <dyn Fs>::global(cx);
|
||||||
|
let action = *action;
|
||||||
|
|
||||||
|
window
|
||||||
|
.spawn(cx, async move |cx: &mut AsyncWindowContext| {
|
||||||
|
handle_import_vscode_settings(
|
||||||
|
VsCodeSettingsSource::Cursor,
|
||||||
|
action.skip_prompt,
|
||||||
|
fs,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
});
|
});
|
||||||
|
@ -133,6 +121,56 @@ pub fn init(cx: &mut App) {
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_import_vscode_settings(
|
||||||
|
source: VsCodeSettingsSource,
|
||||||
|
skip_prompt: bool,
|
||||||
|
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 prompt = if skip_prompt {
|
||||||
|
Task::ready(Some(0))
|
||||||
|
} else {
|
||||||
|
let prompt = cx.prompt(
|
||||||
|
gpui::PromptLevel::Warning,
|
||||||
|
"Importing settings may overwrite your existing settings",
|
||||||
|
None,
|
||||||
|
&["Ok", "Cancel"],
|
||||||
|
);
|
||||||
|
cx.spawn(async move |_| prompt.await.ok())
|
||||||
|
};
|
||||||
|
if prompt.await != Some(0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.update(|_, cx| {
|
||||||
|
cx.global::<SettingsStore>()
|
||||||
|
.import_vscode_settings(fs, vscode);
|
||||||
|
log::info!("Imported settings from {source}");
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SettingsPage {
|
pub struct SettingsPage {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue