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());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: do we want to merge imported globs per filetype? for now we'll just replace
|
||||
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,
|
||||
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)]
|
||||
pub struct WorktreeId(usize);
|
||||
|
|
|
@ -1522,6 +1522,8 @@ pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T>
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::VsCodeSettingsSource;
|
||||
|
||||
use super::*;
|
||||
use serde_derive::Deserialize;
|
||||
use unindent::Unindent;
|
||||
|
@ -2004,7 +2006,10 @@ mod tests {
|
|||
cx: &mut App,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,20 +4,42 @@ use serde_json::{Map, Value};
|
|||
|
||||
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 source: VsCodeSettingsSource,
|
||||
content: Map<String, Value>,
|
||||
}
|
||||
|
||||
impl VsCodeSettings {
|
||||
pub fn from_str(content: &str) -> Result<Self> {
|
||||
pub fn from_str(content: &str, source: VsCodeSettingsSource) -> Result<Self> {
|
||||
Ok(Self {
|
||||
source,
|
||||
content: serde_json_lenient::from_str(content)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn load_user_settings(fs: Arc<dyn Fs>) -> Result<Self> {
|
||||
let content = fs.load(paths::vscode_settings_file()).await?;
|
||||
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 content = fs.load(path).await?;
|
||||
Ok(Self {
|
||||
source,
|
||||
content: serde_json_lenient::from_str(&content)?,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
mod appearance_settings_controls;
|
||||
|
||||
use std::any::TypeId;
|
||||
use std::sync::Arc;
|
||||
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use editor::EditorSettingsControls;
|
||||
|
@ -12,7 +13,7 @@ use gpui::{
|
|||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use settings::SettingsStore;
|
||||
use settings::{SettingsStore, VsCodeSettingsSource};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
use workspace::item::{Item, ItemEvent};
|
||||
|
@ -31,7 +32,13 @@ pub struct ImportVsCodeSettings {
|
|||
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]);
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
|
@ -61,49 +68,30 @@ pub fn init(cx: &mut App) {
|
|||
|
||||
window
|
||||
.spawn(cx, async move |cx: &mut AsyncWindowContext| {
|
||||
let vscode =
|
||||
match settings::VsCodeSettings::load_user_settings(fs.clone()).await {
|
||||
Ok(vscode) => vscode,
|
||||
Err(err) => {
|
||||
println!(
|
||||
"Failed to load VsCode settings: {}",
|
||||
err.context(format!(
|
||||
"Loading VsCode settings from path: {:?}",
|
||||
paths::vscode_settings_file()
|
||||
))
|
||||
);
|
||||
handle_import_vscode_settings(
|
||||
VsCodeSettingsSource::VsCode,
|
||||
action.skip_prompt,
|
||||
fs,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
|
||||
let _ = cx.prompt(
|
||||
gpui::PromptLevel::Info,
|
||||
"Could not find or load a VsCode settings file",
|
||||
None,
|
||||
&["Ok"],
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
workspace.register_action(|_workspace, action: &ImportCursorSettings, window, cx| {
|
||||
let fs = <dyn Fs>::global(cx);
|
||||
let action = *action;
|
||||
|
||||
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();
|
||||
window
|
||||
.spawn(cx, async move |cx: &mut AsyncWindowContext| {
|
||||
handle_import_vscode_settings(
|
||||
VsCodeSettingsSource::Cursor,
|
||||
action.skip_prompt,
|
||||
fs,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
|
@ -133,6 +121,56 @@ pub fn init(cx: &mut App) {
|
|||
.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 {
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue