VSCode Settings import (#29018)
Things this doesn't currently handle: - [x] ~testing~ - ~we really need an snapshot test that takes a vscode settings file with all options that we support, and verifies the zed settings file you get from importing it, both from an empty starting file or one with lots of conflicts. that way we can open said vscode settings file in vscode to ensure that those options all still exist in the future.~ - Discussed this, we don't think this will meaningfully protect us from future failures, and we will just do this as a manual validation step before merging this PR. Any imports that have meaningfully complex translation steps should still be tested. - [x] confirmation (right now it just clobbers your settings file silently) - it'd be really cool if we could show a diff multibuffer of your current settings with the result of the vscode import and let you pick "hunks" to keep, but that's probably too much effort for this feature, especially given that we expect most of the people using it to have an empty/barebones zed config when they run the import. - [x] ~UI in the "welcome" page~ - we're planning on redoing our welcome/walkthrough experience anyways, but in the meantime it'd be nice to conditionally show a button there if we see a user level vscode config - we'll add it to the UI when we land the new walkthrough experience, for now it'll be accessible through the action - [ ] project-specific settings - handling translation of `.vscode/settings.json` or `.code-workspace` settings to `.zed/settings.json` will come in a future PR, along with UI to prompt the user for those actions when opening a project with local vscode settings for the first time - [ ] extension settings - we probably want to do a best-effort pass of popular extensions like vim and git lens - it's also possible to look for installed/enabled extensions with `code --list-extensions`, but we'd have to maintain some sort of mapping of those to our settings and/or extensions - [ ] LSP settings - these are tricky without access to the json schemas for various language server extensions. we could probably manage to do translations for a couple popular languages and avoid solving it in the general case. - [ ] platform specific settings (`[macos].blah`) - this is blocked on #16392 which I'm hoping to address soon - [ ] language specific settings (`[rust].foo`) - totally doable, just haven't gotten to it yet ~We may want to put this behind some kind of flag and/or not land it until some of the above issues are addressed, given that we expect people to only run this importer once there's an incentive to get it right the first time. Maybe we land it alongside a keymap importer so you don't have to go through separate imports for those?~ We are gonna land this as-is, all these unchecked items at the bottom will be addressed in followup PRs, so maybe don't run the importer for now if you have a large and complex VsCode settings file you'd like to import. Release Notes: - Added a VSCode settings importer, available via a `zed::ImportVsCodeSettings` action --------- Co-authored-by: Mikayla Maki <mikayla@zed.dev> Co-authored-by: Kirill Bulatov <kirill@zed.dev> Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com> Co-authored-by: Marshall Bowers <git@maxdeviant.com>
This commit is contained in:
parent
40b5a1b028
commit
f11c749353
41 changed files with 1383 additions and 137 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -13167,7 +13167,12 @@ dependencies = [
|
|||
"command_palette_hooks",
|
||||
"editor",
|
||||
"feature_flags",
|
||||
"fs",
|
||||
"gpui",
|
||||
"log",
|
||||
"paths",
|
||||
"schemars",
|
||||
"serde",
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::sync::Arc;
|
|||
|
||||
use assistant_settings::{
|
||||
AgentProfile, AgentProfileContent, AgentProfileId, AssistantSettings, AssistantSettingsContent,
|
||||
ContextServerPresetContent, VersionedAssistantSettingsContent,
|
||||
ContextServerPresetContent,
|
||||
};
|
||||
use assistant_tool::{ToolSource, ToolWorkingSet};
|
||||
use fs::Fs;
|
||||
|
@ -201,10 +201,10 @@ impl PickerDelegate for ToolPickerDelegate {
|
|||
let profile_id = self.profile_id.clone();
|
||||
let default_profile = self.profile.clone();
|
||||
let tool = tool.clone();
|
||||
move |settings, _cx| match settings {
|
||||
AssistantSettingsContent::Versioned(boxed) => {
|
||||
if let VersionedAssistantSettingsContent::V2(ref mut settings) = **boxed {
|
||||
let profiles = settings.profiles.get_or_insert_default();
|
||||
move |settings: &mut AssistantSettingsContent, _cx| {
|
||||
settings
|
||||
.v2_setting(|v2_settings| {
|
||||
let profiles = v2_settings.profiles.get_or_insert_default();
|
||||
let profile =
|
||||
profiles
|
||||
.entry(profile_id)
|
||||
|
@ -240,9 +240,10 @@ impl PickerDelegate for ToolPickerDelegate {
|
|||
*preset.tools.entry(tool.name.clone()).or_default() = is_enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -44,4 +44,6 @@ impl Settings for SlashCommandSettings {
|
|||
.chain(sources.server),
|
||||
)
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
|
|
@ -112,13 +112,27 @@ impl AssistantSettings {
|
|||
}
|
||||
|
||||
/// Assistant panel settings
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
||||
pub struct AssistantSettingsContent {
|
||||
#[serde(flatten)]
|
||||
pub inner: Option<AssistantSettingsContentInner>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum AssistantSettingsContent {
|
||||
pub enum AssistantSettingsContentInner {
|
||||
Versioned(Box<VersionedAssistantSettingsContent>),
|
||||
Legacy(LegacyAssistantSettingsContent),
|
||||
}
|
||||
|
||||
impl AssistantSettingsContentInner {
|
||||
fn for_v2(content: AssistantSettingsContentV2) -> Self {
|
||||
AssistantSettingsContentInner::Versioned(Box::new(VersionedAssistantSettingsContent::V2(
|
||||
content,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for AssistantSettingsContent {
|
||||
fn schema_name() -> String {
|
||||
VersionedAssistantSettingsContent::schema_name()
|
||||
|
@ -133,26 +147,21 @@ impl JsonSchema for AssistantSettingsContent {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for AssistantSettingsContent {
|
||||
fn default() -> Self {
|
||||
Self::Versioned(Box::new(VersionedAssistantSettingsContent::default()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AssistantSettingsContent {
|
||||
pub fn is_version_outdated(&self) -> bool {
|
||||
match self {
|
||||
AssistantSettingsContent::Versioned(settings) => match **settings {
|
||||
match &self.inner {
|
||||
Some(AssistantSettingsContentInner::Versioned(settings)) => match **settings {
|
||||
VersionedAssistantSettingsContent::V1(_) => true,
|
||||
VersionedAssistantSettingsContent::V2(_) => false,
|
||||
},
|
||||
AssistantSettingsContent::Legacy(_) => true,
|
||||
Some(AssistantSettingsContentInner::Legacy(_)) => true,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn upgrade(&self) -> AssistantSettingsContentV2 {
|
||||
match self {
|
||||
AssistantSettingsContent::Versioned(settings) => match **settings {
|
||||
match &self.inner {
|
||||
Some(AssistantSettingsContentInner::Versioned(settings)) => match **settings {
|
||||
VersionedAssistantSettingsContent::V1(ref settings) => AssistantSettingsContentV2 {
|
||||
enabled: settings.enabled,
|
||||
button: settings.button,
|
||||
|
@ -212,7 +221,7 @@ impl AssistantSettingsContent {
|
|||
},
|
||||
VersionedAssistantSettingsContent::V2(ref settings) => settings.clone(),
|
||||
},
|
||||
AssistantSettingsContent::Legacy(settings) => AssistantSettingsContentV2 {
|
||||
Some(AssistantSettingsContentInner::Legacy(settings)) => AssistantSettingsContentV2 {
|
||||
enabled: None,
|
||||
button: settings.button,
|
||||
dock: settings.dock,
|
||||
|
@ -237,12 +246,13 @@ impl AssistantSettingsContent {
|
|||
always_allow_tool_actions: None,
|
||||
notify_when_agent_waiting: None,
|
||||
},
|
||||
None => AssistantSettingsContentV2::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_dock(&mut self, dock: AssistantDockPosition) {
|
||||
match self {
|
||||
AssistantSettingsContent::Versioned(settings) => match **settings {
|
||||
match &mut self.inner {
|
||||
Some(AssistantSettingsContentInner::Versioned(settings)) => match **settings {
|
||||
VersionedAssistantSettingsContent::V1(ref mut settings) => {
|
||||
settings.dock = Some(dock);
|
||||
}
|
||||
|
@ -250,9 +260,17 @@ impl AssistantSettingsContent {
|
|||
settings.dock = Some(dock);
|
||||
}
|
||||
},
|
||||
AssistantSettingsContent::Legacy(settings) => {
|
||||
Some(AssistantSettingsContentInner::Legacy(settings)) => {
|
||||
settings.dock = Some(dock);
|
||||
}
|
||||
None => {
|
||||
self.inner = Some(AssistantSettingsContentInner::for_v2(
|
||||
AssistantSettingsContentV2 {
|
||||
dock: Some(dock),
|
||||
..Default::default()
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,8 +278,8 @@ impl AssistantSettingsContent {
|
|||
let model = language_model.id().0.to_string();
|
||||
let provider = language_model.provider_id().0.to_string();
|
||||
|
||||
match self {
|
||||
AssistantSettingsContent::Versioned(settings) => match **settings {
|
||||
match &mut self.inner {
|
||||
Some(AssistantSettingsContentInner::Versioned(settings)) => match **settings {
|
||||
VersionedAssistantSettingsContent::V1(ref mut settings) => {
|
||||
match provider.as_ref() {
|
||||
"zed.dev" => {
|
||||
|
@ -337,56 +355,80 @@ impl AssistantSettingsContent {
|
|||
settings.default_model = Some(LanguageModelSelection { provider, model });
|
||||
}
|
||||
},
|
||||
AssistantSettingsContent::Legacy(settings) => {
|
||||
Some(AssistantSettingsContentInner::Legacy(settings)) => {
|
||||
if let Ok(model) = OpenAiModel::from_id(&language_model.id().0) {
|
||||
settings.default_open_ai_model = Some(model);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.inner = Some(AssistantSettingsContentInner::for_v2(
|
||||
AssistantSettingsContentV2 {
|
||||
default_model: Some(LanguageModelSelection { provider, model }),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_inline_assistant_model(&mut self, provider: String, model: String) {
|
||||
if let AssistantSettingsContent::Versioned(boxed) = self {
|
||||
if let VersionedAssistantSettingsContent::V2(ref mut settings) = **boxed {
|
||||
settings.inline_assistant_model = Some(LanguageModelSelection { provider, model });
|
||||
}
|
||||
}
|
||||
self.v2_setting(|setting| {
|
||||
setting.inline_assistant_model = Some(LanguageModelSelection { provider, model });
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn set_commit_message_model(&mut self, provider: String, model: String) {
|
||||
if let AssistantSettingsContent::Versioned(boxed) = self {
|
||||
if let VersionedAssistantSettingsContent::V2(ref mut settings) = **boxed {
|
||||
settings.commit_message_model = Some(LanguageModelSelection { provider, model });
|
||||
self.v2_setting(|setting| {
|
||||
setting.commit_message_model = Some(LanguageModelSelection { provider, model });
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn v2_setting(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut AssistantSettingsContentV2) -> anyhow::Result<()>,
|
||||
) -> anyhow::Result<()> {
|
||||
match self.inner.get_or_insert_with(|| {
|
||||
AssistantSettingsContentInner::for_v2(AssistantSettingsContentV2 {
|
||||
..Default::default()
|
||||
})
|
||||
}) {
|
||||
AssistantSettingsContentInner::Versioned(boxed) => {
|
||||
if let VersionedAssistantSettingsContent::V2(ref mut settings) = **boxed {
|
||||
f(settings)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_thread_summary_model(&mut self, provider: String, model: String) {
|
||||
if let AssistantSettingsContent::Versioned(boxed) = self {
|
||||
if let VersionedAssistantSettingsContent::V2(ref mut settings) = **boxed {
|
||||
settings.thread_summary_model = Some(LanguageModelSelection { provider, model });
|
||||
}
|
||||
}
|
||||
self.v2_setting(|setting| {
|
||||
setting.thread_summary_model = Some(LanguageModelSelection { provider, model });
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn set_always_allow_tool_actions(&mut self, allow: bool) {
|
||||
let AssistantSettingsContent::Versioned(boxed) = self else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let VersionedAssistantSettingsContent::V2(ref mut settings) = **boxed {
|
||||
settings.always_allow_tool_actions = Some(allow);
|
||||
}
|
||||
self.v2_setting(|setting| {
|
||||
setting.always_allow_tool_actions = Some(allow);
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn set_profile(&mut self, profile_id: AgentProfileId) {
|
||||
let AssistantSettingsContent::Versioned(boxed) = self else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let VersionedAssistantSettingsContent::V2(ref mut settings) = **boxed {
|
||||
settings.default_profile = Some(profile_id);
|
||||
}
|
||||
self.v2_setting(|setting| {
|
||||
setting.default_profile = Some(profile_id);
|
||||
Ok(())
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn create_profile(
|
||||
|
@ -394,11 +436,7 @@ impl AssistantSettingsContent {
|
|||
profile_id: AgentProfileId,
|
||||
profile: AgentProfile,
|
||||
) -> Result<()> {
|
||||
let AssistantSettingsContent::Versioned(boxed) = self else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if let VersionedAssistantSettingsContent::V2(ref mut settings) = **boxed {
|
||||
self.v2_setting(|settings| {
|
||||
let profiles = settings.profiles.get_or_insert_default();
|
||||
if profiles.contains_key(&profile_id) {
|
||||
bail!("profile with ID '{profile_id}' already exists");
|
||||
|
@ -424,9 +462,9 @@ impl AssistantSettingsContent {
|
|||
.collect(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -461,7 +499,7 @@ impl Default for VersionedAssistantSettingsContent {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)]
|
||||
pub struct AssistantSettingsContentV2 {
|
||||
/// Whether the Assistant is enabled.
|
||||
///
|
||||
|
@ -708,6 +746,39 @@ impl Settings for AssistantSettings {
|
|||
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
if let Some(b) = vscode
|
||||
.read_value("chat.agent.enabled")
|
||||
.and_then(|b| b.as_bool())
|
||||
{
|
||||
match &mut current.inner {
|
||||
Some(AssistantSettingsContentInner::Versioned(versioned)) => {
|
||||
match versioned.as_mut() {
|
||||
VersionedAssistantSettingsContent::V1(setting) => {
|
||||
setting.enabled = Some(b);
|
||||
setting.button = Some(b);
|
||||
}
|
||||
|
||||
VersionedAssistantSettingsContent::V2(setting) => {
|
||||
setting.enabled = Some(b);
|
||||
setting.button = Some(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(AssistantSettingsContentInner::Legacy(setting)) => setting.button = Some(b),
|
||||
None => {
|
||||
current.inner = Some(AssistantSettingsContentInner::for_v2(
|
||||
AssistantSettingsContentV2 {
|
||||
enabled: Some(b),
|
||||
button: Some(b),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn merge<T>(target: &mut T, value: Option<T>) {
|
||||
|
@ -751,28 +822,30 @@ mod tests {
|
|||
settings::SettingsStore::global(cx).update_settings_file::<AssistantSettings>(
|
||||
fs.clone(),
|
||||
|settings, _| {
|
||||
*settings = AssistantSettingsContent::Versioned(Box::new(
|
||||
VersionedAssistantSettingsContent::V2(AssistantSettingsContentV2 {
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: "test-provider".into(),
|
||||
model: "gpt-99".into(),
|
||||
}),
|
||||
inline_assistant_model: None,
|
||||
commit_message_model: None,
|
||||
thread_summary_model: None,
|
||||
inline_alternatives: None,
|
||||
enabled: None,
|
||||
button: None,
|
||||
dock: None,
|
||||
default_width: None,
|
||||
default_height: None,
|
||||
enable_experimental_live_diffs: None,
|
||||
default_profile: None,
|
||||
profiles: None,
|
||||
always_allow_tool_actions: None,
|
||||
notify_when_agent_waiting: None,
|
||||
}),
|
||||
))
|
||||
*settings = AssistantSettingsContent {
|
||||
inner: Some(AssistantSettingsContentInner::for_v2(
|
||||
AssistantSettingsContentV2 {
|
||||
default_model: Some(LanguageModelSelection {
|
||||
provider: "test-provider".into(),
|
||||
model: "gpt-99".into(),
|
||||
}),
|
||||
inline_assistant_model: None,
|
||||
commit_message_model: None,
|
||||
thread_summary_model: None,
|
||||
inline_alternatives: None,
|
||||
enabled: None,
|
||||
button: None,
|
||||
dock: None,
|
||||
default_width: None,
|
||||
default_height: None,
|
||||
enable_experimental_live_diffs: None,
|
||||
default_profile: None,
|
||||
profiles: None,
|
||||
always_allow_tool_actions: None,
|
||||
notify_when_agent_waiting: None,
|
||||
},
|
||||
)),
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
@ -118,6 +118,13 @@ impl Settings for AutoUpdateSetting {
|
|||
|
||||
Ok(Self(auto_update.0))
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
vscode.enum_setting("update.mode", current, |s| match s {
|
||||
"none" | "manual" => Some(AutoUpdateSettingContent(false)),
|
||||
_ => Some(AutoUpdateSettingContent(true)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
@ -32,4 +32,6 @@ impl Settings for CallSettings {
|
|||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
|
|
@ -104,6 +104,8 @@ impl Settings for ClientSettings {
|
|||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
|
@ -130,6 +132,10 @@ impl Settings for ProxySettings {
|
|||
.or(sources.default.proxy.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
vscode.string_setting("http.proxy", &mut current.proxy);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_settings(cx: &mut App) {
|
||||
|
@ -518,6 +524,18 @@ impl settings::Settings for TelemetrySettings {
|
|||
.unwrap_or(sources.default.metrics.ok_or_else(Self::missing_default)?),
|
||||
})
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
vscode.enum_setting("telemetry.telemetryLevel", &mut current.metrics, |s| {
|
||||
Some(s == "all")
|
||||
});
|
||||
vscode.enum_setting("telemetry.telemetryLevel", &mut current.diagnostics, |s| {
|
||||
Some(matches!(s, "all" | "error" | "crash"))
|
||||
});
|
||||
// we could translate telemetry.telemetryLevel, but just because users didn't want
|
||||
// to send microsoft telemetry doesn't mean they don't want to send it to zed. their
|
||||
// all/error/crash/off correspond to combinations of our "diagnostics" and "metrics".
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
|
|
|
@ -86,6 +86,8 @@ impl Settings for CollaborationPanelSettings {
|
|||
) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
impl Settings for ChatPanelSettings {
|
||||
|
@ -99,6 +101,8 @@ impl Settings for ChatPanelSettings {
|
|||
) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
impl Settings for NotificationPanelSettings {
|
||||
|
@ -112,6 +116,8 @@ impl Settings for NotificationPanelSettings {
|
|||
) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
impl Settings for MessageEditorSettings {
|
||||
|
@ -125,4 +131,6 @@ impl Settings for MessageEditorSettings {
|
|||
) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
|
|
@ -58,4 +58,42 @@ impl Settings for ContextServerSettings {
|
|||
) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
// we don't handle "inputs" replacement strings, see perplexity-key in this example:
|
||||
// https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_configuration-example
|
||||
#[derive(Deserialize)]
|
||||
struct VsCodeServerCommand {
|
||||
command: String,
|
||||
args: Option<Vec<String>>,
|
||||
env: Option<HashMap<String, String>>,
|
||||
// note: we don't support envFile and type
|
||||
}
|
||||
impl From<VsCodeServerCommand> for ServerCommand {
|
||||
fn from(cmd: VsCodeServerCommand) -> Self {
|
||||
Self {
|
||||
path: cmd.command,
|
||||
args: cmd.args.unwrap_or_default(),
|
||||
env: cmd.env,
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(mcp) = vscode.read_value("mcp").and_then(|v| v.as_object()) {
|
||||
current
|
||||
.context_servers
|
||||
.extend(mcp.iter().filter_map(|(k, v)| {
|
||||
Some((
|
||||
k.clone().into(),
|
||||
ServerConfig {
|
||||
command: Some(
|
||||
serde_json::from_value::<VsCodeServerCommand>(v.clone())
|
||||
.ok()?
|
||||
.into(),
|
||||
),
|
||||
settings: None,
|
||||
},
|
||||
))
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,8 @@ impl Settings for DebuggerSettings {
|
|||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
impl Global for DebuggerSettings {}
|
||||
|
|
|
@ -2,7 +2,7 @@ use gpui::App;
|
|||
use language::CursorShape;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use settings::{Settings, SettingsSources, VsCodeSettings};
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct EditorSettings {
|
||||
|
@ -388,7 +388,7 @@ pub struct ToolbarContent {
|
|||
}
|
||||
|
||||
/// Scrollbar related settings
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
|
||||
pub struct ScrollbarContent {
|
||||
/// When to show the scrollbar in the editor.
|
||||
///
|
||||
|
@ -423,7 +423,7 @@ pub struct ScrollbarContent {
|
|||
}
|
||||
|
||||
/// Forcefully enable or disable the scrollbar for each axis
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
|
||||
pub struct ScrollbarAxesContent {
|
||||
/// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
|
||||
///
|
||||
|
@ -475,4 +475,164 @@ impl Settings for EditorSettings {
|
|||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
|
||||
vscode.enum_setting(
|
||||
"editor.cursorBlinking",
|
||||
&mut current.cursor_blink,
|
||||
|s| match s {
|
||||
"blink" | "phase" | "expand" | "smooth" => Some(true),
|
||||
"solid" => Some(false),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
vscode.enum_setting(
|
||||
"editor.cursorStyle",
|
||||
&mut current.cursor_shape,
|
||||
|s| match s {
|
||||
"block" => Some(CursorShape::Block),
|
||||
"block-outline" => Some(CursorShape::Hollow),
|
||||
"line" | "line-thin" => Some(CursorShape::Bar),
|
||||
"underline" | "underline-thin" => Some(CursorShape::Underline),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
|
||||
vscode.enum_setting(
|
||||
"editor.renderLineHighlight",
|
||||
&mut current.current_line_highlight,
|
||||
|s| match s {
|
||||
"gutter" => Some(CurrentLineHighlight::Gutter),
|
||||
"line" => Some(CurrentLineHighlight::Line),
|
||||
"all" => Some(CurrentLineHighlight::All),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
|
||||
vscode.bool_setting(
|
||||
"editor.selectionHighlight",
|
||||
&mut current.selection_highlight,
|
||||
);
|
||||
vscode.bool_setting("editor.hover.enabled", &mut current.hover_popover_enabled);
|
||||
vscode.u64_setting("editor.hover.delay", &mut current.hover_popover_delay);
|
||||
|
||||
let mut gutter = GutterContent::default();
|
||||
vscode.enum_setting(
|
||||
"editor.showFoldingControls",
|
||||
&mut gutter.folds,
|
||||
|s| match s {
|
||||
"always" | "mouseover" => Some(true),
|
||||
"never" => Some(false),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
vscode.enum_setting(
|
||||
"editor.lineNumbers",
|
||||
&mut gutter.line_numbers,
|
||||
|s| match s {
|
||||
"on" | "relative" => Some(true),
|
||||
"off" => Some(false),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
if let Some(old_gutter) = current.gutter.as_mut() {
|
||||
if gutter.folds.is_some() {
|
||||
old_gutter.folds = gutter.folds
|
||||
}
|
||||
if gutter.line_numbers.is_some() {
|
||||
old_gutter.line_numbers = gutter.line_numbers
|
||||
}
|
||||
} else {
|
||||
if gutter != GutterContent::default() {
|
||||
current.gutter = Some(gutter)
|
||||
}
|
||||
}
|
||||
if let Some(b) = vscode.read_bool("editor.scrollBeyondLastLine") {
|
||||
current.scroll_beyond_last_line = Some(if b {
|
||||
ScrollBeyondLastLine::OnePage
|
||||
} else {
|
||||
ScrollBeyondLastLine::Off
|
||||
})
|
||||
}
|
||||
|
||||
let mut scrollbar_axes = ScrollbarAxesContent::default();
|
||||
vscode.enum_setting(
|
||||
"editor.scrollbar.horizontal",
|
||||
&mut scrollbar_axes.horizontal,
|
||||
|s| match s {
|
||||
"auto" | "visible" => Some(true),
|
||||
"hidden" => Some(false),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
vscode.enum_setting(
|
||||
"editor.scrollbar.vertical",
|
||||
&mut scrollbar_axes.horizontal,
|
||||
|s| match s {
|
||||
"auto" | "visible" => Some(true),
|
||||
"hidden" => Some(false),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
|
||||
if scrollbar_axes != ScrollbarAxesContent::default() {
|
||||
let scrollbar_settings = current.scrollbar.get_or_insert_default();
|
||||
let axes_settings = scrollbar_settings.axes.get_or_insert_default();
|
||||
|
||||
if let Some(vertical) = scrollbar_axes.vertical {
|
||||
axes_settings.vertical = Some(vertical);
|
||||
}
|
||||
if let Some(horizontal) = scrollbar_axes.horizontal {
|
||||
axes_settings.horizontal = Some(horizontal);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: check if this does the int->float conversion?
|
||||
vscode.f32_setting(
|
||||
"editor.cursorSurroundingLines",
|
||||
&mut current.vertical_scroll_margin,
|
||||
);
|
||||
vscode.f32_setting(
|
||||
"editor.mouseWheelScrollSensitivity",
|
||||
&mut current.scroll_sensitivity,
|
||||
);
|
||||
if Some("relative") == vscode.read_string("editor.lineNumbers") {
|
||||
current.relative_line_numbers = Some(true);
|
||||
}
|
||||
|
||||
vscode.enum_setting(
|
||||
"editor.find.seedSearchStringFromSelection",
|
||||
&mut current.seed_search_query_from_cursor,
|
||||
|s| match s {
|
||||
"always" => Some(SeedQuerySetting::Always),
|
||||
"selection" => Some(SeedQuerySetting::Selection),
|
||||
"never" => Some(SeedQuerySetting::Never),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
vscode.bool_setting("search.smartCase", &mut current.use_smartcase_search);
|
||||
vscode.enum_setting(
|
||||
"editor.multiCursorModifier",
|
||||
&mut current.multi_cursor_modifier,
|
||||
|s| match s {
|
||||
"ctrlCmd" => Some(MultiCursorModifier::CmdOrCtrl),
|
||||
"alt" => Some(MultiCursorModifier::Alt),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
|
||||
vscode.bool_setting(
|
||||
"editor.parameterHints.enabled",
|
||||
&mut current.auto_signature_help,
|
||||
);
|
||||
vscode.bool_setting(
|
||||
"editor.parameterHints.enabled",
|
||||
&mut current.show_signature_help_after_edits,
|
||||
);
|
||||
|
||||
if let Some(use_ignored) = vscode.read_bool("search.useIgnoreFiles") {
|
||||
let search = current.search.get_or_insert_default();
|
||||
search.include_ignored = use_ignored;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,4 +50,10 @@ impl Settings for ExtensionSettings {
|
|||
.chain(sources.server),
|
||||
)
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {
|
||||
// settingsSync.ignoredExtensions controls autoupdate for vscode extensions, but we
|
||||
// don't have a mapping to zed-extensions. there's also extensions.autoCheckUpdates
|
||||
// and extensions.autoUpdate which are global switches, we don't support those yet
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ impl Settings for FileFinderSettings {
|
|||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut gpui::App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)]
|
||||
|
|
|
@ -81,4 +81,6 @@ impl Settings for GitHostingProviderSettings {
|
|||
fn load(sources: settings::SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
|
|
@ -87,4 +87,9 @@ impl Settings for GitPanelSettings {
|
|||
) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
vscode.bool_setting("git.enabled", &mut current.button);
|
||||
vscode.string_setting("git.defaultBranchName", &mut current.fallback_branch_name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -280,10 +280,6 @@ pub(crate) enum LineIndicatorFormat {
|
|||
Long,
|
||||
}
|
||||
|
||||
/// Whether or not to automatically check for updates.
|
||||
///
|
||||
/// Values: short, long
|
||||
/// Default: short
|
||||
#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub(crate) struct LineIndicatorFormatContent(LineIndicatorFormat);
|
||||
|
@ -301,4 +297,6 @@ impl Settings for LineIndicatorFormat {
|
|||
|
||||
Ok(format.0)
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
|
|
@ -39,4 +39,6 @@ impl Settings for ImageViewerSettings {
|
|||
.chain(sources.server),
|
||||
)
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,8 @@ impl settings::Settings for JournalSettings {
|
|||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
pub fn init(_: Arc<AppState>, cx: &mut App) {
|
||||
|
|
|
@ -1219,11 +1219,11 @@ impl settings::Settings for AllLanguageSettings {
|
|||
|
||||
let mut file_types: FxHashMap<Arc<str>, GlobSet> = FxHashMap::default();
|
||||
|
||||
for (language, suffixes) in &default_value.file_types {
|
||||
for (language, patterns) in &default_value.file_types {
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
|
||||
for suffix in suffixes {
|
||||
builder.add(Glob::new(suffix)?);
|
||||
for pattern in patterns {
|
||||
builder.add(Glob::new(pattern)?);
|
||||
}
|
||||
|
||||
file_types.insert(language.clone(), builder.build()?);
|
||||
|
@ -1280,20 +1280,20 @@ impl settings::Settings for AllLanguageSettings {
|
|||
);
|
||||
}
|
||||
|
||||
for (language, suffixes) in &user_settings.file_types {
|
||||
for (language, patterns) in &user_settings.file_types {
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
|
||||
let default_value = default_value.file_types.get(&language.clone());
|
||||
|
||||
// Merge the default value with the user's value.
|
||||
if let Some(suffixes) = default_value {
|
||||
for suffix in suffixes {
|
||||
builder.add(Glob::new(suffix)?);
|
||||
if let Some(patterns) = default_value {
|
||||
for pattern in patterns {
|
||||
builder.add(Glob::new(pattern)?);
|
||||
}
|
||||
}
|
||||
|
||||
for suffix in suffixes {
|
||||
builder.add(Glob::new(suffix)?);
|
||||
for pattern in patterns {
|
||||
builder.add(Glob::new(pattern)?);
|
||||
}
|
||||
|
||||
file_types.insert(language.clone(), builder.build()?);
|
||||
|
@ -1370,6 +1370,120 @@ impl settings::Settings for AllLanguageSettings {
|
|||
|
||||
root_schema
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
let d = &mut current.defaults;
|
||||
if let Some(size) = vscode
|
||||
.read_value("editor.tabSize")
|
||||
.and_then(|v| v.as_u64())
|
||||
.and_then(|n| NonZeroU32::new(n as u32))
|
||||
{
|
||||
d.tab_size = Some(size);
|
||||
}
|
||||
if let Some(v) = vscode.read_bool("editor.insertSpaces") {
|
||||
d.hard_tabs = Some(!v);
|
||||
}
|
||||
|
||||
vscode.enum_setting("editor.wordWrap", &mut d.soft_wrap, |s| match s {
|
||||
"on" => Some(SoftWrap::EditorWidth),
|
||||
"wordWrapColumn" => Some(SoftWrap::PreferLine),
|
||||
"bounded" => Some(SoftWrap::Bounded),
|
||||
"off" => Some(SoftWrap::None),
|
||||
_ => None,
|
||||
});
|
||||
vscode.u32_setting("editor.wordWrapColumn", &mut d.preferred_line_length);
|
||||
|
||||
if let Some(arr) = vscode
|
||||
.read_value("editor.rulers")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|v| v.iter().map(|n| n.as_u64().map(|n| n as usize)).collect())
|
||||
{
|
||||
d.wrap_guides = arr;
|
||||
}
|
||||
if let Some(b) = vscode.read_bool("editor.guides.indentation") {
|
||||
if let Some(guide_settings) = d.indent_guides.as_mut() {
|
||||
guide_settings.enabled = b;
|
||||
} else {
|
||||
d.indent_guides = Some(IndentGuideSettings {
|
||||
enabled: b,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(b) = vscode.read_bool("editor.guides.formatOnSave") {
|
||||
d.format_on_save = Some(if b {
|
||||
FormatOnSave::On
|
||||
} else {
|
||||
FormatOnSave::Off
|
||||
});
|
||||
}
|
||||
vscode.bool_setting(
|
||||
"editor.trimAutoWhitespace",
|
||||
&mut d.remove_trailing_whitespace_on_save,
|
||||
);
|
||||
vscode.bool_setting(
|
||||
"files.insertFinalNewline",
|
||||
&mut d.ensure_final_newline_on_save,
|
||||
);
|
||||
vscode.bool_setting("editor.inlineSuggest.enabled", &mut d.show_edit_predictions);
|
||||
vscode.enum_setting("editor.renderWhitespace", &mut d.show_whitespaces, |s| {
|
||||
Some(match s {
|
||||
"boundary" | "trailing" => ShowWhitespaceSetting::Boundary,
|
||||
"selection" => ShowWhitespaceSetting::Selection,
|
||||
"all" => ShowWhitespaceSetting::All,
|
||||
_ => ShowWhitespaceSetting::None,
|
||||
})
|
||||
});
|
||||
vscode.enum_setting(
|
||||
"editor.autoSurround",
|
||||
&mut d.use_auto_surround,
|
||||
|s| match s {
|
||||
"languageDefined" | "quotes" | "brackets" => Some(true),
|
||||
"never" => Some(false),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
vscode.bool_setting("editor.formatOnType", &mut d.use_on_type_format);
|
||||
vscode.bool_setting("editor.linkedEditing", &mut d.linked_edits);
|
||||
vscode.bool_setting("editor.formatOnPaste", &mut d.auto_indent_on_paste);
|
||||
vscode.bool_setting(
|
||||
"editor.suggestOnTriggerCharacters",
|
||||
&mut d.show_completions_on_input,
|
||||
);
|
||||
if let Some(b) = vscode.read_bool("editor.suggest.showWords") {
|
||||
let mode = if b {
|
||||
WordsCompletionMode::Enabled
|
||||
} else {
|
||||
WordsCompletionMode::Disabled
|
||||
};
|
||||
if let Some(completion_settings) = d.completions.as_mut() {
|
||||
completion_settings.words = mode;
|
||||
} else {
|
||||
d.completions = Some(CompletionSettings {
|
||||
words: mode,
|
||||
lsp: true,
|
||||
lsp_fetch_timeout_ms: 0,
|
||||
lsp_insert_mode: LspInsertMode::ReplaceSuffix,
|
||||
});
|
||||
}
|
||||
}
|
||||
// TODO: pull ^ out into helper and reuse for per-language settings
|
||||
|
||||
// vscodes file association map is inverted from ours, so we flip the mapping before merging
|
||||
let mut associations: HashMap<Arc<str>, Vec<String>> = HashMap::default();
|
||||
if let Some(map) = vscode
|
||||
.read_value("files.associations")
|
||||
.and_then(|v| v.as_object())
|
||||
{
|
||||
for (k, v) in map {
|
||||
let Some(v) = v.as_str() else { continue };
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
|
||||
|
|
|
@ -413,4 +413,6 @@ impl settings::Settings for AllLanguageModelSettings {
|
|||
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
|
|
@ -118,4 +118,13 @@ impl Settings for OutlinePanelSettings {
|
|||
) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
if let Some(b) = vscode.read_bool("outline.icons") {
|
||||
current.file_icons = Some(b);
|
||||
current.folder_icons = Some(b);
|
||||
}
|
||||
|
||||
vscode.bool_setting("git.decorations.enabled", &mut current.git_status);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -410,3 +410,18 @@ pub fn local_debug_file_relative_path() -> &'static Path {
|
|||
pub fn local_vscode_launch_file_relative_path() -> &'static Path {
|
||||
Path::new(".vscode/launch.json")
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
config_dir().join(rel_path)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ pub struct NodeBinarySettings {
|
|||
pub path: Option<String>,
|
||||
/// The path to the npm binary Zed should use (defaults to `.path/../npm`).
|
||||
pub npm_path: Option<String>,
|
||||
/// If disabled, Zed will download its own copy of Node.
|
||||
/// If enabled, Zed will download its own copy of Node.
|
||||
#[serde(default)]
|
||||
pub ignore_system_version: Option<bool>,
|
||||
}
|
||||
|
@ -330,6 +330,32 @@ impl Settings for ProjectSettings {
|
|||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
// this just sets the binary name instead of a full path so it relies on path lookup
|
||||
// resolving to the one you want
|
||||
vscode.enum_setting(
|
||||
"npm.packageManager",
|
||||
&mut current.node.npm_path,
|
||||
|s| match s {
|
||||
v @ ("npm" | "yarn" | "bun" | "pnpm") => Some(v.to_owned()),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(b) = vscode.read_bool("git.blame.editorDecoration.enabled") {
|
||||
if let Some(blame) = current.git.inline_blame.as_mut() {
|
||||
blame.enabled = b
|
||||
} else {
|
||||
current.git.inline_blame = Some(InlineBlameSettings {
|
||||
enabled: b,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: translate lsp settings for rust-analyzer and other popular ones to old.lsp
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SettingsObserverMode {
|
||||
|
|
|
@ -158,4 +158,24 @@ impl Settings for ProjectPanelSettings {
|
|||
) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
vscode.bool_setting("explorer.excludeGitIgnore", &mut current.hide_gitignore);
|
||||
vscode.bool_setting("explorer.autoReveal", &mut current.auto_reveal_entries);
|
||||
vscode.bool_setting("explorer.compactFolders", &mut current.auto_fold_dirs);
|
||||
|
||||
if Some(false) == vscode.read_bool("git.decorations.enabled") {
|
||||
current.git_status = Some(false);
|
||||
}
|
||||
if Some(false) == vscode.read_bool("problems.decorations.enabled") {
|
||||
current.show_diagnostics = Some(ShowDiagnostics::Off);
|
||||
}
|
||||
if let (Some(false), Some(false)) = (
|
||||
vscode.read_bool("explorer.decorations.badges"),
|
||||
vscode.read_bool("explorer.decorations.colors"),
|
||||
) {
|
||||
current.git_status = Some(false);
|
||||
current.show_diagnostics = Some(ShowDiagnostics::Off);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,6 +125,8 @@ impl Settings for SshSettings {
|
|||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
pub struct SshPrompt {
|
||||
|
|
|
@ -60,4 +60,6 @@ impl Settings for JupyterSettings {
|
|||
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ mod key_equivalents;
|
|||
mod keymap_file;
|
||||
mod settings_file;
|
||||
mod settings_store;
|
||||
mod vscode_import;
|
||||
|
||||
use gpui::App;
|
||||
use rust_embed::RustEmbed;
|
||||
|
@ -21,6 +22,7 @@ pub use settings_store::{
|
|||
InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources,
|
||||
SettingsStore, TaskKind, parse_json_with_comments,
|
||||
};
|
||||
pub use vscode_import::VsCodeSettings;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
|
||||
pub struct WorktreeId(usize);
|
||||
|
|
|
@ -10,6 +10,7 @@ use paths::{
|
|||
};
|
||||
use schemars::{JsonSchema, r#gen::SchemaGenerator, schema::RootSchema};
|
||||
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
||||
use serde_json::Value;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
any::{Any, TypeId, type_name},
|
||||
|
@ -27,7 +28,7 @@ use util::{ResultExt as _, merge_non_null_json_value_into};
|
|||
|
||||
pub type EditorconfigProperties = ec4rs::Properties;
|
||||
|
||||
use crate::{SettingsJsonSchemaParams, WorktreeId};
|
||||
use crate::{SettingsJsonSchemaParams, VsCodeSettings, WorktreeId};
|
||||
|
||||
/// A value that can be defined as a user setting.
|
||||
///
|
||||
|
@ -68,6 +69,10 @@ pub trait Settings: 'static + Send + Sync {
|
|||
anyhow::anyhow!("missing default")
|
||||
}
|
||||
|
||||
/// Use [the helpers in the vscode_import module](crate::vscode_import) to apply known
|
||||
/// equivalent settings from a vscode config to our config
|
||||
fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent);
|
||||
|
||||
#[track_caller]
|
||||
fn register(cx: &mut App)
|
||||
where
|
||||
|
@ -149,7 +154,7 @@ impl<'a, T: Serialize> SettingsSources<'a, T> {
|
|||
pub fn json_merge_with<O: DeserializeOwned>(
|
||||
customizations: impl Iterator<Item = &'a T>,
|
||||
) -> Result<O> {
|
||||
let mut merged = serde_json::Value::Null;
|
||||
let mut merged = Value::Null;
|
||||
for value in customizations {
|
||||
merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged);
|
||||
}
|
||||
|
@ -174,11 +179,11 @@ pub struct SettingsLocation<'a> {
|
|||
/// A set of strongly-typed setting values defined via multiple config files.
|
||||
pub struct SettingsStore {
|
||||
setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
|
||||
raw_default_settings: serde_json::Value,
|
||||
raw_user_settings: serde_json::Value,
|
||||
raw_server_settings: Option<serde_json::Value>,
|
||||
raw_extension_settings: serde_json::Value,
|
||||
raw_local_settings: BTreeMap<(WorktreeId, Arc<Path>), serde_json::Value>,
|
||||
raw_default_settings: Value,
|
||||
raw_user_settings: Value,
|
||||
raw_server_settings: Option<Value>,
|
||||
raw_extension_settings: Value,
|
||||
raw_local_settings: BTreeMap<(WorktreeId, Arc<Path>), Value>,
|
||||
raw_editorconfig_settings: BTreeMap<(WorktreeId, Arc<Path>), (String, Option<Editorconfig>)>,
|
||||
tab_size_callback: Option<(
|
||||
TypeId,
|
||||
|
@ -233,7 +238,7 @@ struct SettingValue<T> {
|
|||
trait AnySettingValue: 'static + Send + Sync {
|
||||
fn key(&self) -> Option<&'static str>;
|
||||
fn setting_type_name(&self) -> &'static str;
|
||||
fn deserialize_setting(&self, json: &serde_json::Value) -> Result<DeserializedSetting>;
|
||||
fn deserialize_setting(&self, json: &Value) -> Result<DeserializedSetting>;
|
||||
fn load_setting(
|
||||
&self,
|
||||
sources: SettingsSources<DeserializedSetting>,
|
||||
|
@ -248,6 +253,14 @@ trait AnySettingValue: 'static + Send + Sync {
|
|||
_: &SettingsJsonSchemaParams,
|
||||
cx: &App,
|
||||
) -> RootSchema;
|
||||
fn edits_for_update(
|
||||
&self,
|
||||
raw_settings: &serde_json::Value,
|
||||
tab_size: usize,
|
||||
vscode_settings: &VsCodeSettings,
|
||||
text: &mut String,
|
||||
edits: &mut Vec<(Range<usize>, String)>,
|
||||
);
|
||||
}
|
||||
|
||||
struct DeserializedSetting(Box<dyn Any>);
|
||||
|
@ -380,7 +393,7 @@ impl SettingsStore {
|
|||
///
|
||||
/// For user-facing functionality use the typed setting interface.
|
||||
/// (e.g. ProjectSettings::get_global(cx))
|
||||
pub fn raw_user_settings(&self) -> &serde_json::Value {
|
||||
pub fn raw_user_settings(&self) -> &Value {
|
||||
&self.raw_user_settings
|
||||
}
|
||||
|
||||
|
@ -461,6 +474,41 @@ impl SettingsStore {
|
|||
.ok();
|
||||
}
|
||||
|
||||
pub fn import_vscode_settings(&self, fs: Arc<dyn Fs>, vscode_settings: VsCodeSettings) {
|
||||
self.setting_file_updates_tx
|
||||
.unbounded_send(Box::new(move |cx: AsyncApp| {
|
||||
async move {
|
||||
let old_text = Self::load_settings(&fs).await?;
|
||||
let new_text = cx.read_global(|store: &SettingsStore, _cx| {
|
||||
store.get_vscode_edits(old_text, &vscode_settings)
|
||||
})?;
|
||||
let settings_path = paths::settings_file().as_path();
|
||||
if fs.is_file(settings_path).await {
|
||||
let resolved_path =
|
||||
fs.canonicalize(settings_path).await.with_context(|| {
|
||||
format!("Failed to canonicalize settings path {:?}", settings_path)
|
||||
})?;
|
||||
|
||||
fs.atomic_write(resolved_path.clone(), new_text)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to write settings to file {:?}", resolved_path)
|
||||
})?;
|
||||
} else {
|
||||
fs.atomic_write(settings_path.to_path_buf(), new_text)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to write settings to file {:?}", settings_path)
|
||||
})?;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.boxed_local()
|
||||
}))
|
||||
.ok();
|
||||
}
|
||||
|
||||
/// Updates the value of a setting in a JSON file, returning the new text
|
||||
/// for that JSON file.
|
||||
pub fn new_text_for_update<T: Settings>(
|
||||
|
@ -476,6 +524,20 @@ impl SettingsStore {
|
|||
new_text
|
||||
}
|
||||
|
||||
pub fn get_vscode_edits(&self, mut old_text: String, vscode: &VsCodeSettings) -> String {
|
||||
let mut new_text = old_text.clone();
|
||||
let mut edits: Vec<(Range<usize>, String)> = Vec::new();
|
||||
let raw_settings = parse_json_with_comments::<Value>(&old_text).unwrap_or_default();
|
||||
let tab_size = self.json_tab_size();
|
||||
for v in self.setting_values.values() {
|
||||
v.edits_for_update(&raw_settings, tab_size, vscode, &mut old_text, &mut edits);
|
||||
}
|
||||
for (range, replacement) in edits.into_iter() {
|
||||
new_text.replace_range(range, &replacement);
|
||||
}
|
||||
new_text
|
||||
}
|
||||
|
||||
/// Updates the value of a setting in a JSON file, returning a list
|
||||
/// of edits to apply to the JSON file.
|
||||
pub fn edits_for_update<T: Settings>(
|
||||
|
@ -491,7 +553,7 @@ impl SettingsStore {
|
|||
.setting_values
|
||||
.get(&setting_type_id)
|
||||
.unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()));
|
||||
let raw_settings = parse_json_with_comments::<serde_json::Value>(text).unwrap_or_default();
|
||||
let raw_settings = parse_json_with_comments::<Value>(text).unwrap_or_default();
|
||||
let old_content = match setting.deserialize_setting(&raw_settings) {
|
||||
Ok(content) => content.0.downcast::<T::FileContent>().unwrap(),
|
||||
Err(_) => Box::<<T as Settings>::FileContent>::default(),
|
||||
|
@ -555,7 +617,7 @@ impl SettingsStore {
|
|||
default_settings_content: &str,
|
||||
cx: &mut App,
|
||||
) -> Result<()> {
|
||||
let settings: serde_json::Value = parse_json_with_comments(default_settings_content)?;
|
||||
let settings: Value = parse_json_with_comments(default_settings_content)?;
|
||||
if settings.is_object() {
|
||||
self.raw_default_settings = settings;
|
||||
self.recompute_values(None, cx)?;
|
||||
|
@ -570,8 +632,8 @@ impl SettingsStore {
|
|||
&mut self,
|
||||
user_settings_content: &str,
|
||||
cx: &mut App,
|
||||
) -> Result<serde_json::Value> {
|
||||
let settings: serde_json::Value = if user_settings_content.is_empty() {
|
||||
) -> Result<Value> {
|
||||
let settings: Value = if user_settings_content.is_empty() {
|
||||
parse_json_with_comments("{}")?
|
||||
} else {
|
||||
parse_json_with_comments(user_settings_content)?
|
||||
|
@ -588,7 +650,7 @@ impl SettingsStore {
|
|||
server_settings_content: &str,
|
||||
cx: &mut App,
|
||||
) -> Result<()> {
|
||||
let settings: Option<serde_json::Value> = if server_settings_content.is_empty() {
|
||||
let settings: Option<Value> = if server_settings_content.is_empty() {
|
||||
None
|
||||
} else {
|
||||
parse_json_with_comments(server_settings_content)?
|
||||
|
@ -639,10 +701,12 @@ impl SettingsStore {
|
|||
.remove(&(root_id, directory_path.clone()));
|
||||
}
|
||||
(LocalSettingsKind::Settings, Some(settings_contents)) => {
|
||||
let new_settings = parse_json_with_comments::<serde_json::Value>(settings_contents)
|
||||
.map_err(|e| InvalidSettingsError::LocalSettings {
|
||||
path: directory_path.join(local_settings_file_relative_path()),
|
||||
message: e.to_string(),
|
||||
let new_settings =
|
||||
parse_json_with_comments::<Value>(settings_contents).map_err(|e| {
|
||||
InvalidSettingsError::LocalSettings {
|
||||
path: directory_path.join(local_settings_file_relative_path()),
|
||||
message: e.to_string(),
|
||||
}
|
||||
})?;
|
||||
match self
|
||||
.raw_local_settings
|
||||
|
@ -707,7 +771,7 @@ impl SettingsStore {
|
|||
}
|
||||
|
||||
pub fn set_extension_settings<T: Serialize>(&mut self, content: T, cx: &mut App) -> Result<()> {
|
||||
let settings: serde_json::Value = serde_json::to_value(content)?;
|
||||
let settings: Value = serde_json::to_value(content)?;
|
||||
anyhow::ensure!(settings.is_object(), "settings must be an object");
|
||||
self.raw_extension_settings = settings;
|
||||
self.recompute_values(None, cx)?;
|
||||
|
@ -754,11 +818,7 @@ impl SettingsStore {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn json_schema(
|
||||
&self,
|
||||
schema_params: &SettingsJsonSchemaParams,
|
||||
cx: &App,
|
||||
) -> serde_json::Value {
|
||||
pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams, cx: &App) -> Value {
|
||||
use schemars::{
|
||||
r#gen::SchemaSettings,
|
||||
schema::{Schema, SchemaObject},
|
||||
|
@ -1101,7 +1161,7 @@ impl<T: Settings> AnySettingValue for SettingValue<T> {
|
|||
)?))
|
||||
}
|
||||
|
||||
fn deserialize_setting(&self, mut json: &serde_json::Value) -> Result<DeserializedSetting> {
|
||||
fn deserialize_setting(&self, mut json: &Value) -> Result<DeserializedSetting> {
|
||||
if let Some(key) = T::KEY {
|
||||
if let Some(value) = json.get(key) {
|
||||
json = value;
|
||||
|
@ -1150,26 +1210,58 @@ impl<T: Settings> AnySettingValue for SettingValue<T> {
|
|||
) -> RootSchema {
|
||||
T::json_schema(generator, params, cx)
|
||||
}
|
||||
|
||||
fn edits_for_update(
|
||||
&self,
|
||||
raw_settings: &serde_json::Value,
|
||||
tab_size: usize,
|
||||
vscode_settings: &VsCodeSettings,
|
||||
text: &mut String,
|
||||
edits: &mut Vec<(Range<usize>, String)>,
|
||||
) {
|
||||
let old_content = match self.deserialize_setting(raw_settings) {
|
||||
Ok(content) => content.0.downcast::<T::FileContent>().unwrap(),
|
||||
Err(_) => Box::<<T as Settings>::FileContent>::default(),
|
||||
};
|
||||
let mut new_content = old_content.clone();
|
||||
T::import_from_vscode(vscode_settings, &mut new_content);
|
||||
|
||||
let old_value = serde_json::to_value(&old_content).unwrap();
|
||||
let new_value = serde_json::to_value(new_content).unwrap();
|
||||
|
||||
let mut key_path = Vec::new();
|
||||
if let Some(key) = T::KEY {
|
||||
key_path.push(key);
|
||||
}
|
||||
|
||||
update_value_in_json_text(
|
||||
text,
|
||||
&mut key_path,
|
||||
tab_size,
|
||||
&old_value,
|
||||
&new_value,
|
||||
T::PRESERVED_KEYS.unwrap_or_default(),
|
||||
edits,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_value_in_json_text<'a>(
|
||||
text: &mut String,
|
||||
key_path: &mut Vec<&'a str>,
|
||||
tab_size: usize,
|
||||
old_value: &'a serde_json::Value,
|
||||
new_value: &'a serde_json::Value,
|
||||
old_value: &'a Value,
|
||||
new_value: &'a Value,
|
||||
preserved_keys: &[&str],
|
||||
edits: &mut Vec<(Range<usize>, String)>,
|
||||
) {
|
||||
// If the old and new values are both objects, then compare them key by key,
|
||||
// preserving the comments and formatting of the unchanged parts. Otherwise,
|
||||
// replace the old value with the new value.
|
||||
if let (serde_json::Value::Object(old_object), serde_json::Value::Object(new_object)) =
|
||||
(old_value, new_value)
|
||||
{
|
||||
if let (Value::Object(old_object), Value::Object(new_object)) = (old_value, new_value) {
|
||||
for (key, old_sub_value) in old_object.iter() {
|
||||
key_path.push(key);
|
||||
let new_sub_value = new_object.get(key).unwrap_or(&serde_json::Value::Null);
|
||||
let new_sub_value = new_object.get(key).unwrap_or(&Value::Null);
|
||||
update_value_in_json_text(
|
||||
text,
|
||||
key_path,
|
||||
|
@ -1188,7 +1280,7 @@ fn update_value_in_json_text<'a>(
|
|||
text,
|
||||
key_path,
|
||||
tab_size,
|
||||
&serde_json::Value::Null,
|
||||
&Value::Null,
|
||||
new_sub_value,
|
||||
preserved_keys,
|
||||
edits,
|
||||
|
@ -1215,7 +1307,7 @@ fn replace_value_in_json_text(
|
|||
text: &str,
|
||||
key_path: &[&str],
|
||||
tab_size: usize,
|
||||
new_value: &serde_json::Value,
|
||||
new_value: &Value,
|
||||
) -> (Range<usize>, String) {
|
||||
static PAIR_QUERY: LazyLock<Query> = LazyLock::new(|| {
|
||||
Query::new(
|
||||
|
@ -1667,6 +1759,165 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_vscode_import(cx: &mut App) {
|
||||
let mut store = SettingsStore::new(cx);
|
||||
store.register_setting::<UserSettings>(cx);
|
||||
store.register_setting::<JournalSettings>(cx);
|
||||
store.register_setting::<LanguageSettings>(cx);
|
||||
store.register_setting::<MultiKeySettings>(cx);
|
||||
|
||||
// create settings that werent present
|
||||
check_vscode_import(
|
||||
&mut store,
|
||||
r#"{
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
r#" { "user.age": 37 } "#.to_owned(),
|
||||
r#"{
|
||||
"user": {
|
||||
"age": 37
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
// persist settings that were present
|
||||
check_vscode_import(
|
||||
&mut store,
|
||||
r#"{
|
||||
"user": {
|
||||
"staff": true,
|
||||
"age": 37
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
r#"{ "user.age": 42 }"#.to_owned(),
|
||||
r#"{
|
||||
"user": {
|
||||
"staff": true,
|
||||
"age": 42
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
// don't clobber settings that aren't present in vscode
|
||||
check_vscode_import(
|
||||
&mut store,
|
||||
r#"{
|
||||
"user": {
|
||||
"staff": true,
|
||||
"age": 37
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
r#"{}"#.to_owned(),
|
||||
r#"{
|
||||
"user": {
|
||||
"staff": true,
|
||||
"age": 37
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
// custom enum
|
||||
check_vscode_import(
|
||||
&mut store,
|
||||
r#"{
|
||||
"journal": {
|
||||
"hour_format": "hour12"
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
r#"{ "time_format": "24" }"#.to_owned(),
|
||||
r#"{
|
||||
"journal": {
|
||||
"hour_format": "hour24"
|
||||
}
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
// Multiple keys for one setting
|
||||
check_vscode_import(
|
||||
&mut store,
|
||||
r#"{
|
||||
"key1": "value"
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
r#"{
|
||||
"key_1_first": "hello",
|
||||
"key_1_second": "world"
|
||||
}"#
|
||||
.to_owned(),
|
||||
r#"{
|
||||
"key1": "hello world"
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
// Merging lists together entries added and updated
|
||||
check_vscode_import(
|
||||
&mut store,
|
||||
r#"{
|
||||
"languages": {
|
||||
"JSON": {
|
||||
"language_setting_1": true
|
||||
},
|
||||
"Rust": {
|
||||
"language_setting_2": true
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
r#"{
|
||||
"vscode_languages": [
|
||||
{
|
||||
"name": "JavaScript",
|
||||
"language_setting_1": true
|
||||
},
|
||||
{
|
||||
"name": "Rust",
|
||||
"language_setting_2": false
|
||||
}
|
||||
]
|
||||
}"#
|
||||
.to_owned(),
|
||||
r#"{
|
||||
"languages": {
|
||||
"JavaScript": {
|
||||
"language_setting_1": true
|
||||
},
|
||||
"JSON": {
|
||||
"language_setting_1": true
|
||||
},
|
||||
"Rust": {
|
||||
"language_setting_2": false
|
||||
}
|
||||
}
|
||||
}"#
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
fn check_settings_update<T: Settings>(
|
||||
store: &mut SettingsStore,
|
||||
old_json: String,
|
||||
|
@ -1683,6 +1934,18 @@ mod tests {
|
|||
pretty_assertions::assert_eq!(new_json, expected_new_json);
|
||||
}
|
||||
|
||||
fn check_vscode_import(
|
||||
store: &mut SettingsStore,
|
||||
old: String,
|
||||
vscode: String,
|
||||
expected: String,
|
||||
cx: &mut App,
|
||||
) {
|
||||
store.set_user_settings(&old, cx).ok();
|
||||
let new = store.get_vscode_edits(old, &VsCodeSettings::from_str(&vscode).unwrap());
|
||||
pretty_assertions::assert_eq!(new, expected);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize)]
|
||||
struct UserSettings {
|
||||
name: String,
|
||||
|
@ -1704,6 +1967,10 @@ mod tests {
|
|||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
|
||||
vscode.u32_setting("user.age", &mut current.age);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
|
@ -1716,6 +1983,8 @@ mod tests {
|
|||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||
|
@ -1740,6 +2009,15 @@ mod tests {
|
|||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
|
||||
let first_value = vscode.read_string("key_1_first");
|
||||
let second_value = vscode.read_string("key_1_second");
|
||||
|
||||
if let Some((first, second)) = first_value.zip(second_value) {
|
||||
current.key1 = Some(format!("{} {}", first, second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -1769,6 +2047,14 @@ mod tests {
|
|||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
|
||||
vscode.enum_setting("time_format", &mut current.hour_format, |s| match s {
|
||||
"12" => Some(HourFormat::Hour12),
|
||||
"24" => Some(HourFormat::Hour24),
|
||||
_ => None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||
|
@ -1791,5 +2077,30 @@ mod tests {
|
|||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) {
|
||||
current.languages.extend(
|
||||
vscode
|
||||
.read_value("vscode_languages")
|
||||
.and_then(|value| value.as_array())
|
||||
.map(|languages| {
|
||||
languages
|
||||
.iter()
|
||||
.filter_map(|value| value.as_object())
|
||||
.filter_map(|item| {
|
||||
let mut rest = item.clone();
|
||||
let name = rest.remove("name")?.as_str()?.to_string();
|
||||
let entry = serde_json::from_value::<LanguageSettingEntry>(
|
||||
serde_json::Value::Object(rest),
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
Some((name, entry))
|
||||
})
|
||||
})
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
88
crates/settings/src/vscode_import.rs
Normal file
88
crates/settings/src/vscode_import.rs
Normal file
|
@ -0,0 +1,88 @@
|
|||
use anyhow::Result;
|
||||
use fs::Fs;
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct VsCodeSettings {
|
||||
content: Map<String, Value>,
|
||||
}
|
||||
|
||||
impl VsCodeSettings {
|
||||
pub fn from_str(content: &str) -> Result<Self> {
|
||||
Ok(Self {
|
||||
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?;
|
||||
Ok(Self {
|
||||
content: serde_json_lenient::from_str(&content)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_value(&self, setting: &str) -> Option<&Value> {
|
||||
if let Some(value) = self.content.get(setting) {
|
||||
return Some(value);
|
||||
}
|
||||
// TODO: maybe check if it's in [platform] settings for current platform as a fallback
|
||||
// TODO: deal with language specific settings
|
||||
None
|
||||
}
|
||||
|
||||
pub fn read_string(&self, setting: &str) -> Option<&str> {
|
||||
self.read_value(setting).and_then(|v| v.as_str())
|
||||
}
|
||||
|
||||
pub fn read_bool(&self, setting: &str) -> Option<bool> {
|
||||
self.read_value(setting).and_then(|v| v.as_bool())
|
||||
}
|
||||
|
||||
pub fn string_setting(&self, key: &str, setting: &mut Option<String>) {
|
||||
if let Some(s) = self.content.get(key).and_then(Value::as_str) {
|
||||
*setting = Some(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bool_setting(&self, key: &str, setting: &mut Option<bool>) {
|
||||
if let Some(s) = self.content.get(key).and_then(Value::as_bool) {
|
||||
*setting = Some(s)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn u32_setting(&self, key: &str, setting: &mut Option<u32>) {
|
||||
if let Some(s) = self.content.get(key).and_then(Value::as_u64) {
|
||||
*setting = Some(s as u32)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn u64_setting(&self, key: &str, setting: &mut Option<u64>) {
|
||||
if let Some(s) = self.content.get(key).and_then(Value::as_u64) {
|
||||
*setting = Some(s)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn usize_setting(&self, key: &str, setting: &mut Option<usize>) {
|
||||
if let Some(s) = self.content.get(key).and_then(Value::as_u64) {
|
||||
*setting = Some(s.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn f32_setting(&self, key: &str, setting: &mut Option<f32>) {
|
||||
if let Some(s) = self.content.get(key).and_then(Value::as_f64) {
|
||||
*setting = Some(s as f32)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enum_setting<T>(
|
||||
&self,
|
||||
key: &str,
|
||||
setting: &mut Option<T>,
|
||||
f: impl FnOnce(&str) -> Option<T>,
|
||||
) {
|
||||
if let Some(s) = self.content.get(key).and_then(Value::as_str).and_then(f) {
|
||||
*setting = Some(s)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,9 +15,14 @@ path = "src/settings_ui.rs"
|
|||
command_palette_hooks.workspace = true
|
||||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
gpui.workspace = true
|
||||
log.workspace = true
|
||||
paths.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
workspace.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
serde.workspace = true
|
||||
schemars.workspace = true
|
||||
|
|
|
@ -5,7 +5,14 @@ use std::any::TypeId;
|
|||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use editor::EditorSettingsControls;
|
||||
use feature_flags::{FeatureFlag, FeatureFlagViewExt};
|
||||
use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, actions};
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
App, AsyncWindowContext, Entity, EventEmitter, FocusHandle, Focusable, Task, actions,
|
||||
impl_actions,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use settings::SettingsStore;
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
use workspace::item::{Item, ItemEvent};
|
||||
|
@ -18,6 +25,13 @@ impl FeatureFlag for SettingsUiFeatureFlag {
|
|||
const NAME: &'static str = "settings-ui";
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema)]
|
||||
pub struct ImportVsCodeSettings {
|
||||
#[serde(default)]
|
||||
pub skip_prompt: bool,
|
||||
}
|
||||
|
||||
impl_actions!(zed, [ImportVsCodeSettings]);
|
||||
actions!(zed, [OpenSettingsEditor]);
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
|
@ -41,6 +55,59 @@ pub fn init(cx: &mut App) {
|
|||
}
|
||||
});
|
||||
|
||||
workspace.register_action(|_workspace, action: &ImportVsCodeSettings, window, cx| {
|
||||
let fs = <dyn Fs>::global(cx);
|
||||
let action = *action;
|
||||
|
||||
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()
|
||||
))
|
||||
);
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
let settings_ui_actions = [TypeId::of::<OpenSettingsEditor>()];
|
||||
|
||||
CommandPaletteFilter::update_global(cx, |filter, _cx| {
|
||||
|
|
|
@ -255,6 +255,70 @@ impl settings::Settings for TerminalSettings {
|
|||
|
||||
root_schema
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
let name = |s| format!("terminal.integrated.{s}");
|
||||
|
||||
vscode.f32_setting(&name("fontSize"), &mut current.font_size);
|
||||
vscode.string_setting(&name("fontFamily"), &mut current.font_family);
|
||||
vscode.bool_setting(&name("copyOnSelection"), &mut current.copy_on_select);
|
||||
vscode.bool_setting("macOptionIsMeta", &mut current.option_as_meta);
|
||||
vscode.usize_setting("scrollback", &mut current.max_scroll_history_lines);
|
||||
match vscode.read_bool(&name("cursorBlinking")) {
|
||||
Some(true) => current.blinking = Some(TerminalBlink::On),
|
||||
Some(false) => current.blinking = Some(TerminalBlink::Off),
|
||||
None => {}
|
||||
}
|
||||
vscode.enum_setting(
|
||||
&name("cursorStyle"),
|
||||
&mut current.cursor_shape,
|
||||
|s| match s {
|
||||
"block" => Some(CursorShape::Block),
|
||||
"line" => Some(CursorShape::Bar),
|
||||
"underline" => Some(CursorShape::Underline),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
// they also have "none" and "outline" as options but just for the "Inactive" variant
|
||||
if let Some(height) = vscode
|
||||
.read_value(&name("lineHeight"))
|
||||
.and_then(|v| v.as_f64())
|
||||
{
|
||||
current.line_height = Some(TerminalLineHeight::Custom(height as f32))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let platform = "windows";
|
||||
#[cfg(target_os = "linux")]
|
||||
let platform = "linux";
|
||||
#[cfg(target_os = "macos")]
|
||||
let platform = "osx";
|
||||
|
||||
// TODO: handle arguments
|
||||
let shell_name = format!("{platform}Exec");
|
||||
if let Some(s) = vscode.read_string(&name(&shell_name)) {
|
||||
current.shell = Some(Shell::Program(s.to_owned()))
|
||||
}
|
||||
|
||||
if let Some(env) = vscode
|
||||
.read_value(&name(&format!("env.{platform}")))
|
||||
.and_then(|v| v.as_object())
|
||||
{
|
||||
for (k, v) in env {
|
||||
if v.is_null() {
|
||||
if let Some(zed_env) = current.env.as_mut() {
|
||||
zed_env.remove(k);
|
||||
}
|
||||
}
|
||||
let Some(v) = v.as_str() else { continue };
|
||||
if let Some(zed_env) = current.env.as_mut() {
|
||||
zed_env.insert(k.clone(), v.to_owned());
|
||||
} else {
|
||||
current.env = Some([(k.clone(), v.to_owned())].into_iter().collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
|
||||
|
|
|
@ -950,6 +950,13 @@ impl settings::Settings for ThemeSettings {
|
|||
|
||||
root_schema
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
vscode.f32_setting("editor.fontWeight", &mut current.buffer_font_weight);
|
||||
vscode.f32_setting("editor.fontSize", &mut current.buffer_font_size);
|
||||
vscode.string_setting("editor.font", &mut current.buffer_font_family);
|
||||
// TODO: possibly map editor.fontLigatures to buffer_font_features?
|
||||
}
|
||||
}
|
||||
|
||||
fn merge<T: Copy>(target: &mut T, value: Option<T>) {
|
||||
|
|
|
@ -1798,4 +1798,8 @@ impl Settings for VimSettings {
|
|||
cursor_shape: settings.cursor_shape.ok_or_else(Self::missing_default)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {
|
||||
// TODO: translate vim extension settings
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,4 +33,8 @@ impl Settings for VimModeSetting {
|
|||
.unwrap_or(sources.default.ok_or_else(Self::missing_default)?),
|
||||
))
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {
|
||||
// TODO: could possibly check if any of the `vim.<foo>` keys are set?
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,4 +107,8 @@ impl Settings for BaseKeymap {
|
|||
}
|
||||
sources.default.ok_or_else(Self::missing_default)
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
*current = Some(BaseKeymap::VSCode);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -201,7 +201,8 @@ impl Render for WelcomePage {
|
|||
zed_actions::OpenSettings,
|
||||
), cx);
|
||||
})),
|
||||
),
|
||||
)
|
||||
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
|
|
|
@ -141,6 +141,35 @@ impl Settings for ItemSettings {
|
|||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
if let Some(b) = vscode.read_bool("workbench.editor.tabActionCloseVisibility") {
|
||||
current.show_close_button = Some(if b {
|
||||
ShowCloseButton::Always
|
||||
} else {
|
||||
ShowCloseButton::Hidden
|
||||
})
|
||||
}
|
||||
vscode.enum_setting(
|
||||
"workbench.editor.tabActionLocation",
|
||||
&mut current.close_position,
|
||||
|s| match s {
|
||||
"right" => Some(ClosePosition::Right),
|
||||
"left" => Some(ClosePosition::Left),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
if let Some(b) = vscode.read_bool("workbench.editor.focusRecentEditorAfterClose") {
|
||||
current.activate_on_close = Some(if b {
|
||||
ActivateOnClose::History
|
||||
} else {
|
||||
ActivateOnClose::LeftNeighbour
|
||||
})
|
||||
}
|
||||
|
||||
vscode.bool_setting("workbench.editor.showIcons", &mut current.file_icons);
|
||||
vscode.bool_setting("git.decorations.enabled", &mut current.git_status);
|
||||
}
|
||||
}
|
||||
|
||||
impl Settings for PreviewTabsSettings {
|
||||
|
@ -151,6 +180,18 @@ impl Settings for PreviewTabsSettings {
|
|||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
vscode.bool_setting("workbench.editor.enablePreview", &mut current.enabled);
|
||||
vscode.bool_setting(
|
||||
"workbench.editor.enablePreviewFromCodeNavigation",
|
||||
&mut current.enable_preview_from_code_navigation,
|
||||
);
|
||||
vscode.bool_setting(
|
||||
"workbench.editor.enablePreviewFromQuickOpen",
|
||||
&mut current.enable_preview_from_file_finder,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
|
||||
|
|
|
@ -48,7 +48,7 @@ impl OnLastWindowClosed {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct ActivePanelModifiers {
|
||||
/// Scale by which to zoom the active pane.
|
||||
|
@ -277,6 +277,89 @@ impl Settings for WorkspaceSettings {
|
|||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
if vscode
|
||||
.read_bool("accessibility.dimUnfocused.enabled")
|
||||
.unwrap_or_default()
|
||||
{
|
||||
if let Some(opacity) = vscode
|
||||
.read_value("accessibility.dimUnfocused.opacity")
|
||||
.and_then(|v| v.as_f64())
|
||||
{
|
||||
if let Some(settings) = current.active_pane_modifiers.as_mut() {
|
||||
settings.inactive_opacity = Some(opacity as f32)
|
||||
} else {
|
||||
current.active_pane_modifiers = Some(ActivePanelModifiers {
|
||||
inactive_opacity: Some(opacity as f32),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vscode.enum_setting(
|
||||
"window.confirmBeforeClose",
|
||||
&mut current.confirm_quit,
|
||||
|s| match s {
|
||||
"always" | "keyboardOnly" => Some(true),
|
||||
"never" => Some(false),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
|
||||
vscode.bool_setting(
|
||||
"workbench.editor.restoreViewState",
|
||||
&mut current.restore_on_file_reopen,
|
||||
);
|
||||
|
||||
if let Some(b) = vscode.read_bool("window.closeWhenEmpty") {
|
||||
current.when_closing_with_no_tabs = Some(if b {
|
||||
CloseWindowWhenNoItems::CloseWindow
|
||||
} else {
|
||||
CloseWindowWhenNoItems::KeepWindowOpen
|
||||
})
|
||||
}
|
||||
|
||||
if let Some(b) = vscode.read_bool("files.simpleDialog.enable") {
|
||||
current.use_system_path_prompts = Some(!b);
|
||||
}
|
||||
|
||||
vscode.enum_setting("files.autoSave", &mut current.autosave, |s| match s {
|
||||
"off" => Some(AutosaveSetting::Off),
|
||||
"afterDelay" => Some(AutosaveSetting::AfterDelay {
|
||||
milliseconds: vscode
|
||||
.read_value("files.autoSaveDelay")
|
||||
.and_then(|v| v.as_u64())
|
||||
.unwrap_or(1000),
|
||||
}),
|
||||
"onFocusChange" => Some(AutosaveSetting::OnFocusChange),
|
||||
"onWindowChange" => Some(AutosaveSetting::OnWindowChange),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
// workbench.editor.limit contains "enabled", "value", and "perEditorGroup"
|
||||
// our semantics match if those are set to true, some N, and true respectively.
|
||||
// we'll ignore "perEditorGroup" for now since we only support a global max
|
||||
if let Some(n) = vscode
|
||||
.read_value("workbench.editor.limit.value")
|
||||
.and_then(|v| v.as_u64())
|
||||
.and_then(|n| NonZeroUsize::new(n as usize))
|
||||
{
|
||||
if vscode
|
||||
.read_bool("workbench.editor.limit.enabled")
|
||||
.unwrap_or_default()
|
||||
{
|
||||
current.max_tabs = Some(n)
|
||||
}
|
||||
}
|
||||
|
||||
// some combination of "window.restoreWindows" and "workbench.startupEditor" might
|
||||
// map to our "restore_on_startup"
|
||||
|
||||
// there doesn't seem to be a way to read whether the bottom dock's "justified"
|
||||
// setting is enabled in vscode. that'd be our equivalent to "bottom_dock_layout"
|
||||
}
|
||||
}
|
||||
|
||||
impl Settings for TabBarSettings {
|
||||
|
@ -287,4 +370,19 @@ impl Settings for TabBarSettings {
|
|||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
vscode.enum_setting(
|
||||
"workbench.editor.showTabs",
|
||||
&mut current.show,
|
||||
|s| match s {
|
||||
"multiple" => Some(true),
|
||||
"single" | "none" => Some(false),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
if Some("hidden") == vscode.read_string("workbench.editor.editorActionsLocation") {
|
||||
current.show_tab_bar_buttons = Some(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,6 +96,31 @@ impl Settings for WorktreeSettings {
|
|||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
if let Some(inclusions) = vscode
|
||||
.read_value("files.watcherInclude")
|
||||
.and_then(|v| v.as_array())
|
||||
.and_then(|v| v.iter().map(|n| n.as_str().map(str::to_owned)).collect())
|
||||
{
|
||||
if let Some(old) = current.file_scan_inclusions.as_mut() {
|
||||
old.extend(inclusions)
|
||||
} else {
|
||||
current.file_scan_inclusions = Some(inclusions)
|
||||
}
|
||||
}
|
||||
if let Some(exclusions) = vscode
|
||||
.read_value("files.watcherExclude")
|
||||
.and_then(|v| v.as_array())
|
||||
.and_then(|v| v.iter().map(|n| n.as_str().map(str::to_owned)).collect())
|
||||
{
|
||||
if let Some(old) = current.file_scan_exclusions.as_mut() {
|
||||
old.extend(exclusions)
|
||||
} else {
|
||||
current.file_scan_exclusions = Some(exclusions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn path_matchers(values: &[String], context: &'static str) -> anyhow::Result<PathMatcher> {
|
||||
|
|
|
@ -32,4 +32,6 @@ impl Settings for ZlogSettings {
|
|||
{
|
||||
sources.json_merge()
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue