From 1fbe1e351246dd54577324e9632c6f9a48bc6ece Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 17 Oct 2025 11:47:05 -0600 Subject: [PATCH] VSCode settings import refactor (#40513) A small follow-up to the settings refactor of a few weeks ago to move all the VSCode settings imports to one place. This should make it easier to spot missing imports, and easier to test the importer. Release Notes: - N/A --- crates/agent_settings/src/agent_settings.rs | 12 +- crates/client/src/client.rs | 25 - crates/editor/src/editor_settings.rs | 208 +---- crates/git_ui/src/git_panel_settings.rs | 14 +- crates/language/src/language_settings.rs | 127 +-- .../src/outline_panel_settings.rs | 16 - crates/project/src/project.rs | 6 - crates/project/src/project_settings.rs | 59 -- .../src/project_panel_settings.rs | 40 +- crates/settings/src/base_keymap_setting.rs | 11 +- crates/settings/src/settings_store.rs | 71 +- crates/settings/src/vscode_import.rs | 812 ++++++++++++++++-- crates/terminal/src/terminal_settings.rs | 80 +- crates/theme/src/settings.rs | 11 - .../vim_mode_setting/src/vim_mode_setting.rs | 6 - crates/workspace/src/item.rs | 59 -- crates/workspace/src/workspace_settings.rs | 110 --- crates/worktree/src/worktree_settings.rs | 27 +- crates/zlog_settings/src/zlog_settings.rs | 2 - 19 files changed, 773 insertions(+), 923 deletions(-) diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs index adab899fadf3b36d199dd13ee19dc8421da9da8f..988340318c9f6a68d7b36010eecf0957df145236 100644 --- a/crates/agent_settings/src/agent_settings.rs +++ b/crates/agent_settings/src/agent_settings.rs @@ -10,7 +10,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{ DefaultAgentView, DockPosition, LanguageModelParameters, LanguageModelSelection, - NotifyWhenAgentWaiting, Settings, SettingsContent, + NotifyWhenAgentWaiting, Settings, }; pub use crate::agent_profile::*; @@ -185,14 +185,4 @@ impl Settings for AgentSettings { message_editor_min_lines: agent.message_editor_min_lines.unwrap(), } } - - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { - if let Some(b) = vscode - .read_value("chat.agent.enabled") - .and_then(|b| b.as_bool()) - { - current.agent.get_or_insert_default().enabled = Some(b); - current.agent.get_or_insert_default().button = Some(b); - } - } } diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 911cada78f14ee587a1b4570c9a35181a2e6fdec..5aff87155f3a0328aa017060604b5fc79604731e 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -138,10 +138,6 @@ impl Settings for ProxySettings { proxy: content.proxy.clone(), } } - - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { - vscode.string_setting("http.proxy", &mut current.proxy); - } } pub fn init_settings(cx: &mut App) { @@ -525,27 +521,6 @@ impl settings::Settings for TelemetrySettings { metrics: content.telemetry.as_ref().unwrap().metrics.unwrap(), } } - - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { - let mut telemetry = settings::TelemetrySettingsContent::default(); - vscode.enum_setting("telemetry.telemetryLevel", &mut telemetry.metrics, |s| { - Some(s == "all") - }); - vscode.enum_setting( - "telemetry.telemetryLevel", - &mut telemetry.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". - if let Some(diagnostics) = telemetry.diagnostics { - current.telemetry.get_or_insert_default().diagnostics = Some(diagnostics) - } - if let Some(metrics) = telemetry.metrics { - current.telemetry.get_or_insert_default().metrics = Some(metrics) - } - } } impl Client { diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 066d827bb90b96481823a92ea747d8123b95b47d..9ecbbff97612d391e56271f19331160ef08ba534 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -1,16 +1,14 @@ use core::num; -use std::num::NonZeroU32; use gpui::App; use language::CursorShape; use project::project_settings::DiagnosticSeverity; +use settings::Settings; pub use settings::{ CurrentLineHighlight, DisplayIn, DocumentColorsRenderMode, DoubleClickInMultibuffer, GoToDefinitionFallback, HideMouseMode, MinimapThumb, MinimapThumbBorder, MultiCursorModifier, ScrollBeyondLastLine, ScrollbarDiagnostics, SeedQuerySetting, ShowMinimap, SnippetSortOrder, - VsCodeSettings, }; -use settings::{Settings, SettingsContent}; use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar}; /// Imports from the VSCode settings at @@ -270,208 +268,4 @@ impl Settings for EditorSettings { minimum_contrast_for_highlights: editor.minimum_contrast_for_highlights.unwrap().0, } } - - fn import_from_vscode(vscode: &VsCodeSettings, current: &mut SettingsContent) { - vscode.enum_setting( - "editor.cursorBlinking", - &mut current.editor.cursor_blink, - |s| match s { - "blink" | "phase" | "expand" | "smooth" => Some(true), - "solid" => Some(false), - _ => None, - }, - ); - vscode.enum_setting( - "editor.cursorStyle", - &mut current.editor.cursor_shape, - |s| match s { - "block" => Some(settings::CursorShape::Block), - "block-outline" => Some(settings::CursorShape::Hollow), - "line" | "line-thin" => Some(settings::CursorShape::Bar), - "underline" | "underline-thin" => Some(settings::CursorShape::Underline), - _ => None, - }, - ); - - vscode.enum_setting( - "editor.renderLineHighlight", - &mut current.editor.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.editor.selection_highlight, - ); - vscode.bool_setting( - "editor.roundedSelection", - &mut current.editor.rounded_selection, - ); - vscode.bool_setting( - "editor.hover.enabled", - &mut current.editor.hover_popover_enabled, - ); - vscode.u64_setting( - "editor.hover.delay", - &mut current.editor.hover_popover_delay, - ); - - let mut gutter = settings::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.editor.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 != settings::GutterContent::default() { - current.editor.gutter = Some(gutter) - } - if let Some(b) = vscode.read_bool("editor.scrollBeyondLastLine") { - current.editor.scroll_beyond_last_line = Some(if b { - ScrollBeyondLastLine::OnePage - } else { - ScrollBeyondLastLine::Off - }) - } - - let mut scrollbar_axes = settings::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 != settings::ScrollbarAxesContent::default() { - let scrollbar_settings = current.editor.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.editor.vertical_scroll_margin, - ); - vscode.f32_setting( - "editor.mouseWheelScrollSensitivity", - &mut current.editor.scroll_sensitivity, - ); - vscode.f32_setting( - "editor.fastScrollSensitivity", - &mut current.editor.fast_scroll_sensitivity, - ); - if Some("relative") == vscode.read_string("editor.lineNumbers") { - current.editor.relative_line_numbers = Some(true); - } - - vscode.enum_setting( - "editor.find.seedSearchStringFromSelection", - &mut current.editor.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.editor.use_smartcase_search); - vscode.enum_setting( - "editor.multiCursorModifier", - &mut current.editor.multi_cursor_modifier, - |s| match s { - "ctrlCmd" => Some(MultiCursorModifier::CmdOrCtrl), - "alt" => Some(MultiCursorModifier::Alt), - _ => None, - }, - ); - - vscode.bool_setting( - "editor.parameterHints.enabled", - &mut current.editor.auto_signature_help, - ); - vscode.bool_setting( - "editor.parameterHints.enabled", - &mut current.editor.show_signature_help_after_edits, - ); - - if let Some(use_ignored) = vscode.read_bool("search.useIgnoreFiles") { - let search = current.editor.search.get_or_insert_default(); - search.include_ignored = Some(use_ignored); - } - - let mut minimap = settings::MinimapContent::default(); - let minimap_enabled = vscode.read_bool("editor.minimap.enabled").unwrap_or(true); - let autohide = vscode.read_bool("editor.minimap.autohide"); - let mut max_width_columns: Option = None; - vscode.u32_setting("editor.minimap.maxColumn", &mut max_width_columns); - if minimap_enabled { - if let Some(false) = autohide { - minimap.show = Some(ShowMinimap::Always); - } else { - minimap.show = Some(ShowMinimap::Auto); - } - } else { - minimap.show = Some(ShowMinimap::Never); - } - if let Some(max_width_columns) = max_width_columns { - minimap.max_width_columns = NonZeroU32::new(max_width_columns); - } - - vscode.enum_setting( - "editor.minimap.showSlider", - &mut minimap.thumb, - |s| match s { - "always" => Some(MinimapThumb::Always), - "mouseover" => Some(MinimapThumb::Hover), - _ => None, - }, - ); - - if minimap != settings::MinimapContent::default() { - current.editor.minimap = Some(minimap) - } - } } diff --git a/crates/git_ui/src/git_panel_settings.rs b/crates/git_ui/src/git_panel_settings.rs index f98493d1d9ef4bcf9b53393671091c8b72dcd998..83259b228b59c5bb063473cc4a04710a0520808c 100644 --- a/crates/git_ui/src/git_panel_settings.rs +++ b/crates/git_ui/src/git_panel_settings.rs @@ -2,7 +2,7 @@ use editor::EditorSettings; use gpui::Pixels; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsContent, StatusStyle}; +use settings::{Settings, StatusStyle}; use ui::{ px, scrollbars::{ScrollbarVisibility, ShowScrollbar}, @@ -58,16 +58,4 @@ impl Settings for GitPanelSettings { collapse_untracked_diff: git_panel.collapse_untracked_diff.unwrap(), } } - - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { - if let Some(git_enabled) = vscode.read_bool("git.enabled") { - current.git_panel.get_or_insert_default().button = Some(git_enabled); - } - if let Some(default_branch) = vscode.read_string("git.defaultBranchName") { - current - .git_panel - .get_or_insert_default() - .fallback_branch_name = Some(default_branch.to_string()); - } - } } diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 2f96e850f49dd5cda6c5d34b2b424812b23a524d..b6c65ede0596fe96ba1a750bcbcbcb971a3be617 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -15,7 +15,7 @@ pub use settings::{ Formatter, FormatterList, InlayHintKind, LanguageSettingsContent, LspInsertMode, RewrapBehavior, ShowWhitespaceSetting, SoftWrap, WordsCompletionMode, }; -use settings::{ExtendingVec, Settings, SettingsContent, SettingsLocation, SettingsStore}; +use settings::{Settings, SettingsLocation, SettingsStore}; use shellexpand; use std::{borrow::Cow, num::NonZeroU32, path::Path, sync::Arc}; @@ -679,131 +679,6 @@ impl settings::Settings for AllLanguageSettings { file_types, } } - - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { - let d = &mut current.project.all_languages.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") { - d.indent_guides.get_or_insert_default().enabled = Some(b); - } - - 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" => ShowWhitespaceSetting::Boundary, - "trailing" => ShowWhitespaceSetting::Trailing, - "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 - }; - d.completions.get_or_insert_default().words = Some(mode); - } - // 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, ExtendingVec> = 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().0.push(k.clone()); - } - } - - // TODO: do we want to merge imported globs per filetype? for now we'll just replace - current - .project - .all_languages - .file_types - .get_or_insert_default() - .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 - .project - .all_languages - .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()), - ); - } - } } #[derive(Default, Debug, Clone, PartialEq, Eq)] diff --git a/crates/outline_panel/src/outline_panel_settings.rs b/crates/outline_panel/src/outline_panel_settings.rs index 58598bdb4f9089e2c6284976869b82be600825ae..77fb15ddeb273b6fbe928e5f364f4a135321e7be 100644 --- a/crates/outline_panel/src/outline_panel_settings.rs +++ b/crates/outline_panel/src/outline_panel_settings.rs @@ -62,20 +62,4 @@ impl Settings for OutlinePanelSettings { expand_outlines_with_depth: panel.expand_outlines_with_depth.unwrap(), } } - - fn import_from_vscode( - vscode: &settings::VsCodeSettings, - current: &mut settings::SettingsContent, - ) { - if let Some(b) = vscode.read_bool("outline.icons") { - let outline_panel = current.outline_panel.get_or_insert_default(); - outline_panel.file_icons = Some(b); - outline_panel.folder_icons = Some(b); - } - - if let Some(b) = vscode.read_bool("git.decorations.enabled") { - let outline_panel = current.outline_panel.get_or_insert_default(); - outline_panel.git_status = Some(b); - } - } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 4fed6be0ef3eb8eeb587015df323f00864bd95ea..56a2811f07a4c3f37c610df48bb7c6db1904f9f2 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -985,12 +985,6 @@ impl settings::Settings for DisableAiSettings { disable_ai: content.disable_ai.unwrap().0, } } - - fn import_from_vscode( - _vscode: &settings::VsCodeSettings, - _current: &mut settings::SettingsContent, - ) { - } } impl Project { diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index 88fe7bb6d215540e68d7770c799bc3028cadc674..788cd0212a094154e6c4e3b3eb0d379ecadaf11c 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -522,65 +522,6 @@ impl Settings for ProjectSettings { }, } } - - fn import_from_vscode( - vscode: &settings::VsCodeSettings, - current: &mut settings::SettingsContent, - ) { - // this just sets the binary name instead of a full path so it relies on path lookup - // resolving to the one you want - let npm_path = vscode.read_enum("npm.packageManager", |s| match s { - v @ ("npm" | "yarn" | "bun" | "pnpm") => Some(v.to_owned()), - _ => None, - }); - if npm_path.is_some() { - current.node.get_or_insert_default().npm_path = npm_path; - } - - if let Some(b) = vscode.read_bool("git.blame.editorDecoration.enabled") { - current - .git - .get_or_insert_default() - .inline_blame - .get_or_insert_default() - .enabled = Some(b); - } - - #[derive(Deserialize)] - struct VsCodeContextServerCommand { - command: PathBuf, - args: Option>, - env: Option>, - // note: we don't support envFile and type - } - if let Some(mcp) = vscode.read_value("mcp").and_then(|v| v.as_object()) { - current - .project - .context_servers - .extend(mcp.iter().filter_map(|(k, v)| { - Some(( - k.clone().into(), - settings::ContextServerSettingsContent::Custom { - enabled: true, - command: serde_json::from_value::( - v.clone(), - ) - .ok() - .map(|cmd| { - settings::ContextServerCommand { - path: cmd.command, - args: cmd.args.unwrap_or_default(), - env: cmd.env, - timeout: None, - } - })?, - }, - )) - })); - } - - // TODO: translate lsp settings for rust-analyzer and other popular ones to old.lsp - } } pub enum SettingsObserverMode { diff --git a/crates/project_panel/src/project_panel_settings.rs b/crates/project_panel/src/project_panel_settings.rs index 3eca8a6e8685b787069bc14482bdffce551d87ac..632537fc0213f3702755144c045e58fcb737ed30 100644 --- a/crates/project_panel/src/project_panel_settings.rs +++ b/crates/project_panel/src/project_panel_settings.rs @@ -2,10 +2,7 @@ use editor::EditorSettings; use gpui::Pixels; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{ - DockSide, ProjectPanelEntrySpacing, Settings, SettingsContent, ShowDiagnostics, - ShowIndentGuides, -}; +use settings::{DockSide, ProjectPanelEntrySpacing, Settings, ShowDiagnostics, ShowIndentGuides}; use ui::{ px, scrollbars::{ScrollbarVisibility, ShowScrollbar}, @@ -86,39 +83,4 @@ impl Settings for ProjectPanelSettings { open_file_on_paste: project_panel.open_file_on_paste.unwrap(), } } - - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { - if let Some(hide_gitignore) = vscode.read_bool("explorer.excludeGitIgnore") { - current.project_panel.get_or_insert_default().hide_gitignore = Some(hide_gitignore); - } - if let Some(auto_reveal) = vscode.read_bool("explorer.autoReveal") { - current - .project_panel - .get_or_insert_default() - .auto_reveal_entries = Some(auto_reveal); - } - if let Some(compact_folders) = vscode.read_bool("explorer.compactFolders") { - current.project_panel.get_or_insert_default().auto_fold_dirs = Some(compact_folders); - } - - if Some(false) == vscode.read_bool("git.decorations.enabled") { - current.project_panel.get_or_insert_default().git_status = Some(false); - } - if Some(false) == vscode.read_bool("problems.decorations.enabled") { - current - .project_panel - .get_or_insert_default() - .show_diagnostics = Some(ShowDiagnostics::Off); - } - if let (Some(false), Some(false)) = ( - vscode.read_bool("explorer.decorations.badges"), - vscode.read_bool("explorer.decorations.colors"), - ) { - current.project_panel.get_or_insert_default().git_status = Some(false); - current - .project_panel - .get_or_insert_default() - .show_diagnostics = Some(ShowDiagnostics::Off); - } - } } diff --git a/crates/settings/src/base_keymap_setting.rs b/crates/settings/src/base_keymap_setting.rs index e955a539ac73d2c8c8ab6c6ca443962bf2959e75..4915bdd85319e4abaf3ea575d387a39cc14f302d 100644 --- a/crates/settings/src/base_keymap_setting.rs +++ b/crates/settings/src/base_keymap_setting.rs @@ -1,12 +1,9 @@ use std::fmt::{Display, Formatter}; -use crate::{ - self as settings, - settings_content::{BaseKeymapContent, SettingsContent}, -}; +use crate::{self as settings, settings_content::BaseKeymapContent}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, VsCodeSettings}; +use settings::Settings; /// Base key bindings scheme. Base keymaps can be overridden with user keymaps. /// @@ -133,8 +130,4 @@ impl Settings for BaseKeymap { fn from_settings(s: &crate::settings_content::SettingsContent) -> Self { s.base_keymap.unwrap().into() } - - fn import_from_vscode(_vscode: &VsCodeSettings, current: &mut SettingsContent) { - current.base_keymap = Some(BaseKeymapContent::VSCode); - } } diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 559ed777c7aa86047f6acb33c8b358e0bd7b6e58..709b4982706250f91c7aaefc365ddf7613cdf5f4 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -70,10 +70,6 @@ pub trait Settings: 'static + Send + Sync + Sized { /// and you should add a default to default.json for documentation. fn from_settings(content: &SettingsContent) -> Self; - /// 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 SettingsContent) {} - #[track_caller] fn register(cx: &mut App) where @@ -208,11 +204,6 @@ trait AnySettingValue: 'static + Send + Sync { fn all_local_values(&self) -> Vec<(WorktreeId, Arc, &dyn Any)>; fn set_global_value(&mut self, value: Box); fn set_local_value(&mut self, root_id: WorktreeId, path: Arc, value: Box); - fn import_from_vscode( - &self, - vscode_settings: &VsCodeSettings, - settings_content: &mut SettingsContent, - ); } impl SettingsStore { @@ -614,10 +605,8 @@ impl SettingsStore { } pub fn get_vscode_edits(&self, old_text: String, vscode: &VsCodeSettings) -> String { - self.new_text_for_update(old_text, |settings_content| { - for v in self.setting_values.values() { - v.import_from_vscode(vscode, settings_content) - } + self.new_text_for_update(old_text, |content| { + content.merge_from(&vscode.settings_content()) }) } @@ -1129,14 +1118,6 @@ impl AnySettingValue for SettingValue { Err(ix) => self.local_values.insert(ix, (root_id, path, value)), } } - - fn import_from_vscode( - &self, - vscode_settings: &VsCodeSettings, - settings_content: &mut SettingsContent, - ) { - T::import_from_vscode(vscode_settings, settings_content); - } } #[cfg(test)] @@ -1179,19 +1160,6 @@ mod tests { git_status: content.git_status.unwrap(), } } - - fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) { - let mut show = None; - - vscode.bool_setting("workbench.editor.decorations.colors", &mut show); - if let Some(show) = show { - content - .tabs - .get_or_insert_default() - .git_status - .replace(show); - } - } } #[derive(Debug, PartialEq)] @@ -1208,18 +1176,6 @@ mod tests { preferred_line_length: content.preferred_line_length.unwrap(), } } - - fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) { - let content = &mut content.project.all_languages.defaults; - - if let Some(size) = vscode - .read_value("editor.tabSize") - .and_then(|v| v.as_u64()) - .and_then(|n| NonZeroU32::new(n as u32)) - { - content.tab_size = Some(size); - } - } } #[derive(Debug, PartialEq)] @@ -1236,16 +1192,6 @@ mod tests { buffer_font_fallbacks: content.buffer_font_fallbacks.unwrap(), } } - - fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) { - let content = &mut content.theme; - - vscode.font_family_setting( - "editor.fontFamily", - &mut content.buffer_font_family, - &mut content.buffer_font_fallbacks, - ); - } } #[gpui::test] @@ -1581,6 +1527,7 @@ mod tests { .unindent(), r#" { "editor.tabSize": 37 } "#.to_owned(), r#"{ + "base_keymap": "VSCode", "tab_size": 37 } "# @@ -1598,6 +1545,7 @@ mod tests { .unindent(), r#"{ "editor.tabSize": 42 }"#.to_owned(), r#"{ + "base_keymap": "VSCode", "tab_size": 42, "preferred_line_length": 99, } @@ -1617,6 +1565,7 @@ mod tests { .unindent(), r#"{}"#.to_owned(), r#"{ + "base_keymap": "VSCode", "preferred_line_length": 99, "tab_size": 42 } @@ -1632,8 +1581,15 @@ mod tests { } "# .unindent(), - r#"{ "workbench.editor.decorations.colors": true }"#.to_owned(), + r#"{ "git.decorations.enabled": true }"#.to_owned(), r#"{ + "project_panel": { + "git_status": true + }, + "outline_panel": { + "git_status": true + }, + "base_keymap": "VSCode", "tabs": { "git_status": true } @@ -1652,6 +1608,7 @@ mod tests { .unindent(), r#"{ "editor.fontFamily": "Cascadia Code, 'Consolas', Courier New" }"#.to_owned(), r#"{ + "base_keymap": "VSCode", "buffer_font_fallbacks": [ "Consolas", "Courier New" diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index c0c1085684b448dbd3d4ef83faabf21ca1cfbf7f..cc55613c63ef7d21b5f4830b0f5c6496ac1930f2 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -1,10 +1,15 @@ +use crate::*; use anyhow::{Context as _, Result, anyhow}; +use collections::HashMap; use fs::Fs; use paths::{cursor_settings_file_paths, vscode_settings_file_paths}; +use serde::Deserialize; use serde_json::{Map, Value}; -use std::{path::Path, sync::Arc}; - -use crate::FontFamilyName; +use std::{ + num::{NonZeroU32, NonZeroUsize}, + path::{Path, PathBuf}, + sync::Arc, +}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum VsCodeSettingsSource { @@ -79,83 +84,53 @@ impl VsCodeSettings { }) } - pub fn read_value(&self, setting: &str) -> Option<&Value> { + fn read_value(&self, setting: &str) -> Option<&Value> { self.content.get(setting) } - pub fn read_string(&self, setting: &str) -> Option<&str> { + fn read_str(&self, setting: &str) -> Option<&str> { self.read_value(setting).and_then(|v| v.as_str()) } - pub fn read_bool(&self, setting: &str) -> Option { - self.read_value(setting).and_then(|v| v.as_bool()) - } - - pub fn string_setting(&self, key: &str, setting: &mut Option) { - 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) { - if let Some(s) = self.content.get(key).and_then(Value::as_bool) { - *setting = Some(s) - } + fn read_string(&self, setting: &str) -> Option { + self.read_value(setting) + .and_then(|v| v.as_str()) + .map(|s| s.to_owned()) } - pub fn u32_setting(&self, key: &str, setting: &mut Option) { - if let Some(s) = self.content.get(key).and_then(Value::as_u64) { - *setting = Some(s as u32) - } + fn read_bool(&self, setting: &str) -> Option { + self.read_value(setting).and_then(|v| v.as_bool()) } - pub fn u64_setting(&self, key: &str, setting: &mut Option) { - if let Some(s) = self.content.get(key).and_then(Value::as_u64) { - *setting = Some(s) - } + fn read_f32(&self, setting: &str) -> Option { + self.read_value(setting) + .and_then(|v| v.as_f64()) + .map(|v| v as f32) } - pub fn usize_setting(&self, key: &str, setting: &mut Option) { - if let Some(s) = self.content.get(key).and_then(Value::as_u64) { - *setting = Some(s.try_into().unwrap()) - } + fn read_u64(&self, setting: &str) -> Option { + self.read_value(setting).and_then(|v| v.as_u64()) } - pub fn f32_setting(&self, key: &str, setting: &mut Option) { - if let Some(s) = self.content.get(key).and_then(Value::as_f64) { - *setting = Some(s as f32) - } + fn read_usize(&self, setting: &str) -> Option { + self.read_value(setting) + .and_then(|v| v.as_u64()) + .and_then(|v| v.try_into().ok()) } - pub fn from_f32_setting>(&self, key: &str, setting: &mut Option) { - if let Some(s) = self.content.get(key).and_then(Value::as_f64) { - *setting = Some(T::from(s as f32)) - } + fn read_u32(&self, setting: &str) -> Option { + self.read_value(setting) + .and_then(|v| v.as_u64()) + .and_then(|v| v.try_into().ok()) } - pub fn enum_setting( - &self, - key: &str, - setting: &mut Option, - f: impl FnOnce(&str) -> Option, - ) { - if let Some(s) = self.content.get(key).and_then(Value::as_str).and_then(f) { - *setting = Some(s) - } - } - - pub fn read_enum(&self, key: &str, f: impl FnOnce(&str) -> Option) -> Option { + fn read_enum(&self, key: &str, f: impl FnOnce(&str) -> Option) -> Option { self.content.get(key).and_then(Value::as_str).and_then(f) } - pub fn font_family_setting( - &self, - key: &str, - font_family: &mut Option, - font_fallbacks: &mut Option>, - ) { + fn read_fonts(&self, key: &str) -> (Option, Option>) { let Some(css_name) = self.content.get(key).and_then(Value::as_str) else { - return; + return (None, None); }; let mut name_buffer = String::new(); @@ -188,12 +163,723 @@ impl VsCodeSettings { } add_font(&mut name_buffer); + if fonts.is_empty() { + return (None, None); + } + (Some(fonts.remove(0)), skip_default(fonts)) + } + + pub fn settings_content(&self) -> SettingsContent { + SettingsContent { + agent: self.agent_settings_content(), + agent_servers: None, + audio: None, + auto_update: None, + base_keymap: Some(BaseKeymapContent::VSCode), + calls: None, + collaboration_panel: None, + debugger: None, + diagnostics: None, + disable_ai: None, + editor: self.editor_settings_content(), + extension: ExtensionSettingsContent::default(), + file_finder: None, + git: self.git_settings_content(), + git_panel: self.git_panel_settings_content(), + global_lsp_settings: None, + helix_mode: None, + image_viewer: None, + journal: None, + language_models: None, + line_indicator_format: None, + log: None, + message_editor: None, + node: self.node_binary_settings(), + notification_panel: None, + outline_panel: self.outline_panel_settings_content(), + preview_tabs: self.preview_tabs_settings_content(), + project: self.project_settings_content(), + project_panel: self.project_panel_settings_content(), + proxy: self.read_string("http.proxy"), + remote: RemoteSettingsContent::default(), + repl: None, + server_url: None, + session: None, + status_bar: self.status_bar_settings_content(), + tab_bar: self.tab_bar_settings_content(), + tabs: self.item_settings_content(), + telemetry: self.telemetry_settings_content(), + terminal: self.terminal_settings_content(), + theme: Box::new(self.theme_settings_content()), + title_bar: None, + vim: None, + vim_mode: None, + workspace: self.workspace_settings_content(), + } + } + + fn agent_settings_content(&self) -> Option { + let enabled = self.read_bool("chat.agent.enabled"); + skip_default(AgentSettingsContent { + enabled: enabled, + button: enabled, + ..Default::default() + }) + } + + fn editor_settings_content(&self) -> EditorSettingsContent { + EditorSettingsContent { + auto_signature_help: self.read_bool("editor.parameterHints.enabled"), + autoscroll_on_clicks: None, + cursor_blink: self.read_enum("editor.cursorBlinking", |s| match s { + "blink" | "phase" | "expand" | "smooth" => Some(true), + "solid" => Some(false), + _ => None, + }), + cursor_shape: self.read_enum("editor.cursorStyle", |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, + }), + current_line_highlight: self.read_enum("editor.renderLineHighlight", |s| match s { + "gutter" => Some(CurrentLineHighlight::Gutter), + "line" => Some(CurrentLineHighlight::Line), + "all" => Some(CurrentLineHighlight::All), + _ => None, + }), + diagnostics_max_severity: None, + double_click_in_multibuffer: None, + drag_and_drop_selection: None, + excerpt_context_lines: None, + expand_excerpt_lines: None, + fast_scroll_sensitivity: self.read_f32("editor.fastScrollSensitivity"), + go_to_definition_fallback: None, + gutter: self.gutter_content(), + hide_mouse: None, + horizontal_scroll_margin: None, + hover_popover_delay: self.read_u64("editor.hover.delay"), + hover_popover_enabled: self.read_bool("editor.hover.enabled"), + inline_code_actions: None, + jupyter: None, + lsp_document_colors: None, + lsp_highlight_debounce: None, + middle_click_paste: None, + minimap: self.minimap_content(), + minimum_contrast_for_highlights: None, + multi_cursor_modifier: self.read_enum("editor.multiCursorModifier", |s| match s { + "ctrlCmd" => Some(MultiCursorModifier::CmdOrCtrl), + "alt" => Some(MultiCursorModifier::Alt), + _ => None, + }), + redact_private_values: None, + relative_line_numbers: self.read_enum("editor.lineNumbers", |s| match s { + "relative" => Some(true), + _ => None, + }), + rounded_selection: self.read_bool("editor.roundedSelection"), + scroll_beyond_last_line: None, + scroll_sensitivity: self.read_f32("editor.mouseWheelScrollSensitivity"), + scrollbar: self.scrollbar_content(), + search: self.search_content(), + search_wrap: None, + seed_search_query_from_cursor: self.read_enum( + "editor.find.seedSearchStringFromSelection", + |s| match s { + "always" => Some(SeedQuerySetting::Always), + "selection" => Some(SeedQuerySetting::Selection), + "never" => Some(SeedQuerySetting::Never), + _ => None, + }, + ), + selection_highlight: self.read_bool("editor.selectionHighlight"), + show_signature_help_after_edits: self.read_bool("editor.parameterHints.enabled"), + snippet_sort_order: None, + toolbar: None, + use_smartcase_search: self.read_bool("search.smartCase"), + vertical_scroll_margin: self.read_f32("editor.cursorSurroundingLines"), + } + } + + fn gutter_content(&self) -> Option { + skip_default(GutterContent { + line_numbers: self.read_enum("editor.lineNumbers", |s| match s { + "on" | "relative" => Some(true), + "off" => Some(false), + _ => None, + }), + min_line_number_digits: None, + runnables: None, + breakpoints: None, + folds: self.read_enum("editor.showFoldingControls", |s| match s { + "always" | "mouseover" => Some(true), + "never" => Some(false), + _ => None, + }), + }) + } + + fn scrollbar_content(&self) -> Option { + let scrollbar_axes = skip_default(ScrollbarAxesContent { + horizontal: self.read_enum("editor.scrollbar.horizontal", |s| match s { + "auto" | "visible" => Some(true), + "hidden" => Some(false), + _ => None, + }), + vertical: self.read_enum("editor.scrollbar.vertical", |s| match s { + "auto" | "visible" => Some(true), + "hidden" => Some(false), + _ => None, + }), + })?; + + Some(ScrollbarContent { + axes: Some(scrollbar_axes), + ..Default::default() + }) + } + + fn search_content(&self) -> Option { + skip_default(SearchSettingsContent { + include_ignored: self.read_bool("search.useIgnoreFiles"), + ..Default::default() + }) + } + + fn minimap_content(&self) -> Option { + let minimap_enabled = self.read_bool("editor.minimap.enabled"); + let autohide = self.read_bool("editor.minimap.autohide"); + let show = match (minimap_enabled, autohide) { + (Some(true), Some(false)) => Some(ShowMinimap::Always), + (Some(true), _) => Some(ShowMinimap::Auto), + (Some(false), _) => Some(ShowMinimap::Never), + _ => None, + }; + + skip_default(MinimapContent { + show, + thumb: self.read_enum("editor.minimap.showSlider", |s| match s { + "always" => Some(MinimapThumb::Always), + "mouseover" => Some(MinimapThumb::Hover), + _ => None, + }), + max_width_columns: self + .read_u32("editor.minimap.maxColumn") + .and_then(|v| NonZeroU32::new(v)), + ..Default::default() + }) + } - let mut iter = fonts.into_iter(); - *font_family = iter.next(); - let fallbacks: Vec<_> = iter.collect(); - if !fallbacks.is_empty() { - *font_fallbacks = Some(fallbacks); + fn git_panel_settings_content(&self) -> Option { + skip_default(GitPanelSettingsContent { + button: self.read_bool("git.enabled"), + fallback_branch_name: self.read_string("git.defaultBranchName"), + ..Default::default() + }) + } + + fn project_settings_content(&self) -> ProjectSettingsContent { + ProjectSettingsContent { + all_languages: AllLanguageSettingsContent { + features: None, + edit_predictions: self.edit_predictions_settings_content(), + defaults: self.default_language_settings_content(), + languages: Default::default(), + file_types: self.file_types(), + }, + worktree: self.worktree_settings_content(), + lsp: Default::default(), + terminal: None, + dap: Default::default(), + context_servers: self.context_servers(), + load_direnv: None, + slash_commands: None, + git_hosting_providers: None, + } + } + + fn default_language_settings_content(&self) -> LanguageSettingsContent { + LanguageSettingsContent { + allow_rewrap: None, + always_treat_brackets_as_autoclosed: None, + auto_indent: None, + auto_indent_on_paste: self.read_bool("editor.formatOnPaste"), + code_actions_on_format: None, + completions: skip_default(CompletionSettingsContent { + words: self.read_bool("editor.suggest.showWords").map(|b| { + if b { + WordsCompletionMode::Enabled + } else { + WordsCompletionMode::Disabled + } + }), + ..Default::default() + }), + debuggers: None, + edit_predictions_disabled_in: None, + enable_language_server: None, + ensure_final_newline_on_save: self.read_bool("files.insertFinalNewline"), + extend_comment_on_newline: None, + format_on_save: self.read_bool("editor.guides.formatOnSave").map(|b| { + if b { + FormatOnSave::On + } else { + FormatOnSave::Off + } + }), + formatter: None, + hard_tabs: self.read_bool("editor.insertSpaces").map(|v| !v), + indent_guides: skip_default(IndentGuideSettingsContent { + enabled: self.read_bool("editor.guides.indentation"), + ..Default::default() + }), + inlay_hints: None, + jsx_tag_auto_close: None, + language_servers: None, + linked_edits: self.read_bool("editor.linkedEditing"), + preferred_line_length: self.read_u32("editor.wordWrapColumn"), + prettier: None, + remove_trailing_whitespace_on_save: self.read_bool("editor.trimAutoWhitespace"), + show_completion_documentation: None, + show_completions_on_input: self.read_bool("editor.suggestOnTriggerCharacters"), + show_edit_predictions: self.read_bool("editor.inlineSuggest.enabled"), + show_whitespaces: self.read_enum("editor.renderWhitespace", |s| { + Some(match s { + "boundary" => ShowWhitespaceSetting::Boundary, + "trailing" => ShowWhitespaceSetting::Trailing, + "selection" => ShowWhitespaceSetting::Selection, + "all" => ShowWhitespaceSetting::All, + _ => ShowWhitespaceSetting::None, + }) + }), + show_wrap_guides: None, + soft_wrap: self.read_enum("editor.wordWrap", |s| match s { + "on" => Some(SoftWrap::EditorWidth), + "wordWrapColumn" => Some(SoftWrap::PreferLine), + "bounded" => Some(SoftWrap::Bounded), + "off" => Some(SoftWrap::None), + _ => None, + }), + tab_size: self + .read_u32("editor.tabSize") + .and_then(|n| NonZeroU32::new(n)), + tasks: None, + use_auto_surround: self.read_enum("editor.autoSurround", |s| match s { + "languageDefined" | "quotes" | "brackets" => Some(true), + "never" => Some(false), + _ => None, + }), + use_autoclose: None, + use_on_type_format: self.read_bool("editor.formatOnType"), + whitespace_map: None, + wrap_guides: self + .read_value("editor.rulers") + .and_then(|v| v.as_array()) + .map(|v| { + v.iter() + .flat_map(|n| n.as_u64().map(|n| n as usize)) + .collect() + }), } } + + fn file_types(&self) -> Option, ExtendingVec>> { + // vscodes file association map is inverted from ours, so we flip the mapping before merging + let mut associations: HashMap, ExtendingVec> = HashMap::default(); + let map = self.read_value("files.associations")?.as_object()?; + for (k, v) in map { + let Some(v) = v.as_str() else { continue }; + associations.entry(v.into()).or_default().0.push(k.clone()); + } + skip_default(associations) + } + + fn edit_predictions_settings_content(&self) -> Option { + let disabled_globs = self + .read_value("cursor.general.globalCursorIgnoreList")? + .as_array()?; + + skip_default(EditPredictionSettingsContent { + disabled_globs: skip_default( + disabled_globs + .iter() + .filter_map(|glob| glob.as_str()) + .map(|s| s.to_string()) + .collect(), + ), + ..Default::default() + }) + } + + fn outline_panel_settings_content(&self) -> Option { + skip_default(OutlinePanelSettingsContent { + file_icons: self.read_bool("outline.icons"), + folder_icons: self.read_bool("outline.icons"), + git_status: self.read_bool("git.decorations.enabled"), + ..Default::default() + }) + } + + fn node_binary_settings(&self) -> Option { + // this just sets the binary name instead of a full path so it relies on path lookup + // resolving to the one you want + skip_default(NodeBinarySettings { + npm_path: self.read_enum("npm.packageManager", |s| match s { + v @ ("npm" | "yarn" | "bun" | "pnpm") => Some(v.to_owned()), + _ => None, + }), + ..Default::default() + }) + } + + fn git_settings_content(&self) -> Option { + let inline_blame = self.read_bool("git.blame.editorDecoration.enabled")?; + skip_default(GitSettings { + inline_blame: Some(InlineBlameSettings { + enabled: Some(inline_blame), + ..Default::default() + }), + ..Default::default() + }) + } + + fn context_servers(&self) -> HashMap, ContextServerSettingsContent> { + #[derive(Deserialize)] + struct VsCodeContextServerCommand { + command: PathBuf, + args: Option>, + env: Option>, + // note: we don't support envFile and type + } + let Some(mcp) = self.read_value("mcp").and_then(|v| v.as_object()) else { + return Default::default(); + }; + mcp.iter() + .filter_map(|(k, v)| { + Some(( + k.clone().into(), + ContextServerSettingsContent::Custom { + enabled: true, + command: serde_json::from_value::(v.clone()) + .ok() + .map(|cmd| ContextServerCommand { + path: cmd.command, + args: cmd.args.unwrap_or_default(), + env: cmd.env, + timeout: None, + })?, + }, + )) + }) + .collect() + } + + fn item_settings_content(&self) -> Option { + skip_default(ItemSettingsContent { + git_status: self.read_bool("git.decorations.enabled"), + close_position: self.read_enum("workbench.editor.tabActionLocation", |s| match s { + "right" => Some(ClosePosition::Right), + "left" => Some(ClosePosition::Left), + _ => None, + }), + file_icons: self.read_bool("workbench.editor.showIcons"), + activate_on_close: self + .read_bool("workbench.editor.focusRecentEditorAfterClose") + .map(|b| { + if b { + ActivateOnClose::History + } else { + ActivateOnClose::LeftNeighbour + } + }), + show_diagnostics: None, + show_close_button: self + .read_bool("workbench.editor.tabActionCloseVisibility") + .map(|b| { + if b { + ShowCloseButton::Always + } else { + ShowCloseButton::Hidden + } + }), + }) + } + + fn preview_tabs_settings_content(&self) -> Option { + skip_default(PreviewTabsSettingsContent { + enabled: self.read_bool("workbench.editor.enablePreview"), + enable_preview_from_file_finder: self + .read_bool("workbench.editor.enablePreviewFromQuickOpen"), + enable_preview_from_code_navigation: self + .read_bool("workbench.editor.enablePreviewFromCodeNavigation"), + }) + } + + fn tab_bar_settings_content(&self) -> Option { + skip_default(TabBarSettingsContent { + show: self.read_enum("workbench.editor.showTabs", |s| match s { + "multiple" => Some(true), + "single" | "none" => Some(false), + _ => None, + }), + show_nav_history_buttons: None, + show_tab_bar_buttons: self + .read_str("workbench.editor.editorActionsLocation") + .and_then(|str| if str == "hidden" { Some(false) } else { None }), + }) + } + + fn status_bar_settings_content(&self) -> Option { + skip_default(StatusBarSettingsContent { + show: self.read_bool("workbench.statusBar.visible"), + active_language_button: None, + cursor_position_button: None, + }) + } + + fn project_panel_settings_content(&self) -> Option { + let mut project_panel_settings = ProjectPanelSettingsContent { + auto_fold_dirs: self.read_bool("explorer.compactFolders"), + auto_reveal_entries: self.read_bool("explorer.autoReveal"), + button: None, + default_width: None, + dock: None, + drag_and_drop: None, + entry_spacing: None, + file_icons: None, + folder_icons: None, + git_status: self.read_bool("git.decorations.enabled"), + hide_gitignore: self.read_bool("explorer.excludeGitIgnore"), + hide_hidden: None, + hide_root: None, + indent_guides: None, + indent_size: None, + open_file_on_paste: None, + scrollbar: None, + show_diagnostics: self + .read_bool("problems.decorations.enabled") + .and_then(|b| if b { Some(ShowDiagnostics::Off) } else { None }), + starts_open: None, + sticky_scroll: None, + }; + + if let (Some(false), Some(false)) = ( + self.read_bool("explorer.decorations.badges"), + self.read_bool("explorer.decorations.colors"), + ) { + project_panel_settings.git_status = Some(false); + project_panel_settings.show_diagnostics = Some(ShowDiagnostics::Off); + } + + skip_default(project_panel_settings) + } + + fn telemetry_settings_content(&self) -> Option { + self.read_enum("telemetry.telemetryLevel", |level| { + let (metrics, diagnostics) = match level { + "all" => (true, true), + "error" | "crash" => (false, true), + "off" => (false, false), + _ => return None, + }; + Some(TelemetrySettingsContent { + metrics: Some(metrics), + diagnostics: Some(diagnostics), + }) + }) + } + + fn terminal_settings_content(&self) -> Option { + let (font_family, font_fallbacks) = self.read_fonts("terminal.integrated.fontFamily"); + skip_default(TerminalSettingsContent { + alternate_scroll: None, + blinking: self + .read_bool("terminal.integrated.cursorBlinking") + .map(|b| { + if b { + TerminalBlink::On + } else { + TerminalBlink::Off + } + }), + button: None, + copy_on_select: self.read_bool("terminal.integrated.copyOnSelection"), + cursor_shape: self.read_enum("terminal.integrated.cursorStyle", |s| match s { + "block" => Some(CursorShapeContent::Block), + "line" => Some(CursorShapeContent::Bar), + "underline" => Some(CursorShapeContent::Underline), + _ => None, + }), + default_height: None, + default_width: None, + dock: None, + font_fallbacks, + font_family, + font_features: None, + font_size: self.read_f32("terminal.integrated.fontSize"), + font_weight: None, + keep_selection_on_copy: None, + line_height: self + .read_f32("terminal.integrated.lineHeight") + .map(|lh| TerminalLineHeight::Custom(lh)), + max_scroll_history_lines: self.read_usize("terminal.integrated.scrollback"), + minimum_contrast: None, + option_as_meta: self.read_bool("terminal.integrated.macOptionIsMeta"), + project: self.project_terminal_settings_content(), + scrollbar: None, + toolbar: None, + }) + } + + fn project_terminal_settings_content(&self) -> ProjectTerminalSettingsContent { + #[cfg(target_os = "windows")] + let platform = "windows"; + #[cfg(target_os = "linux")] + let platform = "linux"; + #[cfg(target_os = "macos")] + let platform = "osx"; + #[cfg(target_os = "freebsd")] + let platform = "freebsd"; + let env = self + .read_value(&format!("terminal.integrated.env.{platform}")) + .and_then(|v| v.as_object()) + .map(|v| v.iter().map(|(k, v)| (k.clone(), v.to_string())).collect()); + + ProjectTerminalSettingsContent { + // TODO: handle arguments + shell: self + .read_string(&format!("terminal.integrated.{platform}Exec")) + .map(|s| Shell::Program(s)), + working_directory: None, + env, + detect_venv: None, + } + } + + fn theme_settings_content(&self) -> ThemeSettingsContent { + let (buffer_font_family, buffer_font_fallbacks) = self.read_fonts("editor.fontFamily"); + ThemeSettingsContent { + ui_font_size: None, + ui_font_family: None, + ui_font_fallbacks: None, + ui_font_features: None, + ui_font_weight: None, + buffer_font_family, + buffer_font_fallbacks, + buffer_font_size: self.read_f32("editor.fontSize"), + buffer_font_weight: self.read_f32("editor.fontWeight").map(|w| w.into()), + buffer_line_height: None, + buffer_font_features: None, + agent_ui_font_size: None, + agent_buffer_font_size: None, + theme: None, + icon_theme: None, + ui_density: None, + unnecessary_code_fade: None, + experimental_theme_overrides: None, + theme_overrides: Default::default(), + } + } + + fn workspace_settings_content(&self) -> WorkspaceSettingsContent { + WorkspaceSettingsContent { + active_pane_modifiers: self.active_pane_modifiers(), + autosave: self.read_enum("files.autoSave", |s| match s { + "off" => Some(AutosaveSetting::Off), + "afterDelay" => Some(AutosaveSetting::AfterDelay { + milliseconds: self + .read_value("files.autoSaveDelay") + .and_then(|v| v.as_u64()) + .unwrap_or(1000), + }), + "onFocusChange" => Some(AutosaveSetting::OnFocusChange), + "onWindowChange" => Some(AutosaveSetting::OnWindowChange), + _ => None, + }), + bottom_dock_layout: None, + centered_layout: None, + close_on_file_delete: None, + command_aliases: Default::default(), + confirm_quit: self.read_enum("window.confirmBeforeClose", |s| match s { + "always" | "keyboardOnly" => Some(true), + "never" => Some(false), + _ => None, + }), + drop_target_size: 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 + max_tabs: if self.read_bool("workbench.editor.limit.enabled") == Some(true) { + self.read_usize("workbench.editor.limit.value") + .and_then(|n| NonZeroUsize::new(n)) + } else { + None + }, + on_last_window_closed: None, + pane_split_direction_horizontal: None, + pane_split_direction_vertical: None, + resize_all_panels_in_dock: None, + restore_on_file_reopen: self.read_bool("workbench.editor.restoreViewState"), + restore_on_startup: None, + show_call_status_icon: None, + use_system_path_prompts: self.read_bool("files.simpleDialog.enable"), + use_system_prompts: None, + use_system_window_tabs: self.read_bool("window.nativeTabs"), + when_closing_with_no_tabs: self.read_bool("window.closeWhenEmpty").map(|b| { + if b { + CloseWindowWhenNoItems::CloseWindow + } else { + CloseWindowWhenNoItems::KeepWindowOpen + } + }), + zoomed_padding: None, + } + } + + fn active_pane_modifiers(&self) -> Option { + if self.read_bool("accessibility.dimUnfocused.enabled") == Some(true) + && let Some(opacity) = self.read_f32("accessibility.dimUnfocused.opacity") + { + Some(ActivePaneModifiers { + border_size: None, + inactive_opacity: Some(opacity), + }) + } else { + None + } + } + + fn worktree_settings_content(&self) -> WorktreeSettingsContent { + WorktreeSettingsContent { + project_name: None, + file_scan_exclusions: self + .read_value("files.watcherExclude") + .and_then(|v| v.as_array()) + .map(|v| { + v.iter() + .filter_map(|n| n.as_str().map(str::to_owned)) + .collect::>() + }) + .filter(|r| !r.is_empty()), + file_scan_inclusions: self + .read_value("files.watcherInclude") + .and_then(|v| v.as_array()) + .map(|v| { + v.iter() + .filter_map(|n| n.as_str().map(str::to_owned)) + .collect::>() + }) + .filter(|r| !r.is_empty()), + private_files: None, + } + } +} + +fn skip_default(value: T) -> Option { + if value == T::default() { + None + } else { + Some(value) + } } diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index 27cccea126fecd7d015b21cec6d18809b756bdf8..9bb5ffb517b15225eed711a6d4e31e2977626d0a 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -8,9 +8,8 @@ use serde::{Deserialize, Serialize}; pub use settings::AlternateScroll; use settings::{ - CursorShapeContent, SettingsContent, ShowScrollbar, TerminalBlink, TerminalDockPosition, - TerminalLineHeight, TerminalSettingsContent, VenvSettings, WorkingDirectory, - merge_from::MergeFrom, + ShowScrollbar, TerminalBlink, TerminalDockPosition, TerminalLineHeight, VenvSettings, + WorkingDirectory, merge_from::MergeFrom, }; use task::Shell; use theme::FontFamilyName; @@ -116,81 +115,6 @@ impl settings::Settings for TerminalSettings { minimum_contrast: user_content.minimum_contrast.unwrap(), } } - - fn import_from_vscode(vscode: &settings::VsCodeSettings, content: &mut SettingsContent) { - let mut default = TerminalSettingsContent::default(); - let current = content.terminal.as_mut().unwrap_or(&mut default); - let name = |s| format!("terminal.integrated.{s}"); - - vscode.f32_setting(&name("fontSize"), &mut current.font_size); - vscode.font_family_setting( - &name("fontFamily"), - &mut current.font_family, - &mut current.font_fallbacks, - ); - 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(CursorShapeContent::Block), - "line" => Some(CursorShapeContent::Bar), - "underline" => Some(CursorShapeContent::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"; - #[cfg(target_os = "freebsd")] - let platform = "freebsd"; - - // TODO: handle arguments - let shell_name = format!("{platform}Exec"); - if let Some(s) = vscode.read_string(&name(&shell_name)) { - current.project.shell = Some(settings::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() - && let Some(zed_env) = current.project.env.as_mut() - { - zed_env.remove(k); - } - let Some(v) = v.as_str() else { continue }; - if let Some(zed_env) = current.project.env.as_mut() { - zed_env.insert(k.clone(), v.to_owned()); - } else { - current.project.env = Some([(k.clone(), v.to_owned())].into_iter().collect()) - } - } - } - if content.terminal.is_none() && default != TerminalSettingsContent::default() { - content.terminal = Some(default) - } - } } #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index 9f753d5a034466631d2324e52fbad7bd858e8c5c..3ac0f410efbdb4418236959e06d1b6772f7e3684 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -727,15 +727,4 @@ impl settings::Settings for ThemeSettings { unnecessary_code_fade: content.unnecessary_code_fade.unwrap().0.clamp(0.0, 0.9), } } - - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { - vscode.from_f32_setting("editor.fontWeight", &mut current.theme.buffer_font_weight); - vscode.from_f32_setting("editor.fontSize", &mut current.theme.buffer_font_size); - vscode.font_family_setting( - "editor.fontFamily", - &mut current.theme.buffer_font_family, - &mut current.theme.buffer_font_fallbacks, - ) - // TODO: possibly map editor.fontLigatures to buffer_font_features? - } } diff --git a/crates/vim_mode_setting/src/vim_mode_setting.rs b/crates/vim_mode_setting/src/vim_mode_setting.rs index d9495c556646f9b9f12dc0b52b9530796a5ad5e3..4caa95b2b412755bd4663a024197c074cb0f1b51 100644 --- a/crates/vim_mode_setting/src/vim_mode_setting.rs +++ b/crates/vim_mode_setting/src/vim_mode_setting.rs @@ -19,10 +19,6 @@ impl Settings for VimModeSetting { fn from_settings(content: &SettingsContent) -> Self { Self(content.vim_mode.unwrap()) } - - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _content: &mut SettingsContent) { - // TODO: could possibly check if any of the `vim.` keys are set? - } } pub struct HelixModeSetting(pub bool); @@ -31,6 +27,4 @@ impl Settings for HelixModeSetting { fn from_settings(content: &SettingsContent) -> Self { Self(content.helix_mode.unwrap()) } - - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut SettingsContent) {} } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index f868547dbf1da85bce8cf90c4bca266f941f78d9..1a6a09c38d0aea0c2df59947455628ec4a7ccd43 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -76,40 +76,6 @@ impl Settings for ItemSettings { show_close_button: tabs.show_close_button.unwrap(), } } - - fn import_from_vscode( - vscode: &settings::VsCodeSettings, - current: &mut settings::SettingsContent, - ) { - if let Some(b) = vscode.read_bool("workbench.editor.tabActionCloseVisibility") { - current.tabs.get_or_insert_default().show_close_button = Some(if b { - ShowCloseButton::Always - } else { - ShowCloseButton::Hidden - }) - } - if let Some(s) = vscode.read_enum("workbench.editor.tabActionLocation", |s| match s { - "right" => Some(ClosePosition::Right), - "left" => Some(ClosePosition::Left), - _ => None, - }) { - current.tabs.get_or_insert_default().close_position = Some(s) - } - if let Some(b) = vscode.read_bool("workbench.editor.focusRecentEditorAfterClose") { - current.tabs.get_or_insert_default().activate_on_close = Some(if b { - ActivateOnClose::History - } else { - ActivateOnClose::LeftNeighbour - }) - } - - if let Some(b) = vscode.read_bool("workbench.editor.showIcons") { - current.tabs.get_or_insert_default().file_icons = Some(b); - }; - if let Some(b) = vscode.read_bool("git.decorations.enabled") { - current.tabs.get_or_insert_default().git_status = Some(b); - } - } } impl Settings for PreviewTabsSettings { @@ -123,31 +89,6 @@ impl Settings for PreviewTabsSettings { .unwrap(), } } - - fn import_from_vscode( - vscode: &settings::VsCodeSettings, - current: &mut settings::SettingsContent, - ) { - if let Some(enabled) = vscode.read_bool("workbench.editor.enablePreview") { - current.preview_tabs.get_or_insert_default().enabled = Some(enabled); - } - if let Some(enable_preview_from_code_navigation) = - vscode.read_bool("workbench.editor.enablePreviewFromCodeNavigation") - { - current - .preview_tabs - .get_or_insert_default() - .enable_preview_from_code_navigation = Some(enable_preview_from_code_navigation) - } - if let Some(enable_preview_from_file_finder) = - vscode.read_bool("workbench.editor.enablePreviewFromQuickOpen") - { - current - .preview_tabs - .get_or_insert_default() - .enable_preview_from_file_finder = Some(enable_preview_from_file_finder) - } - } } #[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs index 541194b0044dd897723c89763abc7d3a2abc20f3..ffa6767427d150d59b7f9f66575e3e385cca9564 100644 --- a/crates/workspace/src/workspace_settings.rs +++ b/crates/workspace/src/workspace_settings.rs @@ -108,91 +108,6 @@ impl Settings for WorkspaceSettings { zoomed_padding: workspace.zoomed_padding.unwrap(), } } - - fn import_from_vscode( - vscode: &settings::VsCodeSettings, - current: &mut settings::SettingsContent, - ) { - if vscode - .read_bool("accessibility.dimUnfocused.enabled") - .unwrap_or_default() - && let Some(opacity) = vscode - .read_value("accessibility.dimUnfocused.opacity") - .and_then(|v| v.as_f64()) - { - current - .workspace - .active_pane_modifiers - .get_or_insert_default() - .inactive_opacity = Some(opacity as f32); - } - - vscode.enum_setting( - "window.confirmBeforeClose", - &mut current.workspace.confirm_quit, - |s| match s { - "always" | "keyboardOnly" => Some(true), - "never" => Some(false), - _ => None, - }, - ); - - vscode.bool_setting( - "workbench.editor.restoreViewState", - &mut current.workspace.restore_on_file_reopen, - ); - - if let Some(b) = vscode.read_bool("window.closeWhenEmpty") { - current.workspace.when_closing_with_no_tabs = Some(if b { - settings::CloseWindowWhenNoItems::CloseWindow - } else { - settings::CloseWindowWhenNoItems::KeepWindowOpen - }); - } - - if let Some(b) = vscode.read_bool("files.simpleDialog.enable") { - current.workspace.use_system_path_prompts = Some(!b); - } - - if let Some(v) = vscode.read_enum("files.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, - }) { - current.workspace.autosave = Some(v); - } - - // 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)) - && vscode - .read_bool("workbench.editor.limit.enabled") - .unwrap_or_default() - { - current.workspace.max_tabs = Some(n) - } - - if let Some(b) = vscode.read_bool("window.nativeTabs") { - current.workspace.use_system_window_tabs = Some(b); - } - - // 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 { @@ -204,22 +119,6 @@ impl Settings for TabBarSettings { show_tab_bar_buttons: tab_bar.show_tab_bar_buttons.unwrap(), } } - - fn import_from_vscode( - vscode: &settings::VsCodeSettings, - current: &mut settings::SettingsContent, - ) { - if let Some(b) = vscode.read_enum("workbench.editor.showTabs", |s| match s { - "multiple" => Some(true), - "single" | "none" => Some(false), - _ => None, - }) { - current.tab_bar.get_or_insert_default().show = Some(b); - } - if Some("hidden") == vscode.read_string("workbench.editor.editorActionsLocation") { - current.tab_bar.get_or_insert_default().show_tab_bar_buttons = Some(false) - } - } } #[derive(Deserialize)] @@ -238,13 +137,4 @@ impl Settings for StatusBarSettings { cursor_position_button: status_bar.cursor_position_button.unwrap(), } } - - fn import_from_vscode( - vscode: &settings::VsCodeSettings, - current: &mut settings::SettingsContent, - ) { - if let Some(show) = vscode.read_bool("workbench.statusBar.visible") { - current.status_bar.get_or_insert_default().show = Some(show); - } - } } diff --git a/crates/worktree/src/worktree_settings.rs b/crates/worktree/src/worktree_settings.rs index a9fcbf0909617986dd2d1d816ed513dd281f2940..8e432f8affbbfa9e7eb53ec474970c43ec0e8a94 100644 --- a/crates/worktree/src/worktree_settings.rs +++ b/crates/worktree/src/worktree_settings.rs @@ -1,7 +1,7 @@ use std::path::Path; use anyhow::Context as _; -use settings::{Settings, SettingsContent}; +use settings::Settings; use util::{ ResultExt, paths::{PathMatcher, PathStyle}, @@ -64,31 +64,6 @@ impl Settings for WorktreeSettings { .unwrap_or_default(), } } - - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { - 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.project.worktree.file_scan_inclusions.as_mut() { - old.extend(inclusions) - } else { - current.project.worktree.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.project.worktree.file_scan_exclusions.as_mut() { - old.extend(exclusions) - } else { - current.project.worktree.file_scan_exclusions = Some(exclusions) - } - } - } } fn path_matchers(mut values: Vec, context: &'static str) -> anyhow::Result { diff --git a/crates/zlog_settings/src/zlog_settings.rs b/crates/zlog_settings/src/zlog_settings.rs index 1f695aa8ff5f8eb09d4cc0c2ae04282c469fb29c..abbce9a98c3106de0093a8586313fbda9750b12b 100644 --- a/crates/zlog_settings/src/zlog_settings.rs +++ b/crates/zlog_settings/src/zlog_settings.rs @@ -29,6 +29,4 @@ impl Settings for ZlogSettings { scopes: content.log.clone().unwrap(), } } - - fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut settings::SettingsContent) {} }