use crate::*;use anyhow::{Context as _, Result, anyhow};use collections::HashMap;use fs::Fs;use gpui::Rgba;use paths::{cursor_settings_file_paths, vscode_settings_file_paths};use serde::Deserialize;use serde_json::{Map, Value};use std::{ num::{NonZeroU32, NonZeroUsize}, path::{Path, PathBuf}, sync::Arc,}; #[derive(Clone, Copy, PartialEq, Eq, Debug)]pub enum VsCodeSettingsSource { VsCode, Cursor,} impl std::fmt::Display for VsCodeSettingsSource { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { VsCodeSettingsSource::VsCode => write!(f, "VS Code"), VsCodeSettingsSource::Cursor => write!(f, "Cursor"), } }} pub struct VsCodeSettings { pub source: VsCodeSettingsSource, pub path: Arc<Path>, content: Map<String, Value>,} impl VsCodeSettings { #[cfg(any(test, feature = "test-support"))] pub fn from_str(content: &str, source: VsCodeSettingsSource) -> Result<Self> { Ok(Self { source, path: Path::new("/example-path/Code/User/settings.json").into(), content: serde_json_lenient::from_str(content)?, }) } pub async fn load_user_settings(source: VsCodeSettingsSource, fs: Arc<dyn Fs>) -> Result<Self> { let candidate_paths = match source { VsCodeSettingsSource::VsCode => vscode_settings_file_paths(), VsCodeSettingsSource::Cursor => cursor_settings_file_paths(), }; let mut path = None; for candidate_path in candidate_paths.iter() { if fs.is_file(candidate_path).await { path = Some(candidate_path.clone()); } } let Some(path) = path else { return Err(anyhow!( "No settings file found, expected to find it in one of the following paths:\n{}", candidate_paths .into_iter() .map(|path| path.to_string_lossy().into_owned()) .collect::<Vec<_>>() .join("\n") )); }; let content = fs.load(&path).await.with_context(|| { format!( "Error loading {} settings file from {}", source, path.display() ) })?; let content = serde_json_lenient::from_str(&content).with_context(|| { format!( "Error parsing {} settings file from {}", source, path.display() ) })?; Ok(Self { source, path: path.into(), content, }) } fn read_value(&self, setting: &str) -> Option<&Value> { self.content.get(setting) } fn read_str(&self, setting: &str) -> Option<&str> { self.read_value(setting).and_then(|v| v.as_str()) } fn read_string(&self, setting: &str) -> Option<String> { self.read_value(setting) .and_then(|v| v.as_str()) .map(|s| s.to_owned()) } fn read_bool(&self, setting: &str) -> Option<bool> { self.read_value(setting).and_then(|v| v.as_bool()) } fn read_f32(&self, setting: &str) -> Option<f32> { self.read_value(setting) .and_then(|v| v.as_f64()) .map(|v| v as f32) } fn read_u64(&self, setting: &str) -> Option<u64> { self.read_value(setting).and_then(|v| v.as_u64()) } fn read_usize(&self, setting: &str) -> Option<usize> { self.read_value(setting) .and_then(|v| v.as_u64()) .and_then(|v| v.try_into().ok()) } fn read_u32(&self, setting: &str) -> Option<u32> { self.read_value(setting) .and_then(|v| v.as_u64()) .and_then(|v| v.try_into().ok()) } fn read_enum<T>(&self, key: &str, f: impl FnOnce(&str) -> Option<T>) -> Option<T> { self.content.get(key).and_then(Value::as_str).and_then(f) } fn read_fonts(&self, key: &str) -> (Option<FontFamilyName>, Option<Vec<FontFamilyName>>) { let Some(css_name) = self.content.get(key).and_then(Value::as_str) else { return (None, None); }; let mut name_buffer = String::new(); let mut quote_char: Option<char> = None; let mut fonts = Vec::new(); let mut add_font = |buffer: &mut String| { let trimmed = buffer.trim(); if !trimmed.is_empty() { fonts.push(trimmed.to_string().into()); } buffer.clear(); }; for ch in css_name.chars() { match (ch, quote_char) { ('"' | '\'', None) => { quote_char = Some(ch); } (_, Some(q)) if ch == q => { quote_char = None; } (',', None) => { add_font(&mut name_buffer); } _ => { name_buffer.push(ch); } } } 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, 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: skip_default(GlobalLspSettingsContent { semantic_token_rules: self.semantic_token_rules(), ..GlobalLspSettingsContent::default() }), 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(), which_key: None, } } fn agent_settings_content(&self) -> Option<AgentSettingsContent> { 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"), sticky_scroll: self.sticky_scroll_content(), 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").map(Into::into), 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(RelativeLineNumbers::Enabled), _ => 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"), completion_menu_scrollbar: None, completion_detail_alignment: None, diff_view_style: None, } } fn sticky_scroll_content(&self) -> Option<StickyScrollContent> { skip_default(StickyScrollContent { enabled: self.read_bool("editor.stickyScroll.enabled"), }) } fn gutter_content(&self) -> Option<GutterContent> { 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<ScrollbarContent> { 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<SearchSettingsContent> { skip_default(SearchSettingsContent { include_ignored: self.read_bool("search.useIgnoreFiles"), ..Default::default() }) } fn semantic_token_rules(&self) -> Option<SemanticTokenRules> { let customizations = self .read_value("editor.semanticTokenColorCustomizations")? .as_object()?; skip_default(SemanticTokenRules { rules: customizations .get("rules") .and_then(|v| { Some( v.as_object()? .iter() .filter_map(|(k, v)| { let v = v.as_object()?; let mut underline = v .get("underline") .and_then(|b| b.as_bool()) .unwrap_or(false); let strikethrough = v .get("strikethrough") .and_then(|b| b.as_bool()) .unwrap_or(false); let mut font_weight = v.get("bold").and_then(|b| b.as_bool()).map(|b| { if b { SemanticTokenFontWeight::Bold } else { SemanticTokenFontWeight::Normal } }); let mut font_style = v.get("italic").and_then(|b| b.as_bool()).map(|b| { if b { SemanticTokenFontStyle::Italic } else { SemanticTokenFontStyle::Normal } }); match v.get("fontStyle").and_then(|s| s.as_str()).unwrap_or("") { "bold" => { font_style = Some(SemanticTokenFontStyle::Normal); font_weight = Some(SemanticTokenFontWeight::Bold); } "italic" => { font_style = Some(SemanticTokenFontStyle::Italic); font_weight = Some(SemanticTokenFontWeight::Normal); } "underline" => { underline = true; } "bold italic" | "italic bold" => { font_style = Some(SemanticTokenFontStyle::Italic); font_weight = Some(SemanticTokenFontWeight::Bold); } "normal" => { font_style = Some(SemanticTokenFontStyle::Normal); font_weight = Some(SemanticTokenFontWeight::Normal); } _ => {} } let foreground = v .get("foreground") .and_then(|v| Rgba::try_from(v.as_str()?).ok()) .map(|s| s.to_owned()); let background = v .get("background") .and_then(|v| Rgba::try_from(v.as_str()?).ok()) .map(|s| s.to_owned()); Some(SemanticTokenRule { token_type: Some(k.clone()), token_modifiers: vec![], style: vec![], underline: if underline { Some(SemanticTokenColorOverride::InheritForeground(true)) } else { None }, strikethrough: if strikethrough { Some(SemanticTokenColorOverride::InheritForeground(true)) } else { None }, foreground_color: foreground, background_color: background, font_weight, font_style, }) }) .collect(), ) }) .unwrap_or_default(), }) } fn minimap_content(&self) -> Option<MinimapContent> { 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() }) } fn git_panel_settings_content(&self) -> Option<GitPanelSettingsContent> { 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 { edit_predictions: self.edit_predictions_settings_content(), defaults: self.default_language_settings_content(), languages: Default::default(),Lines 1–500 of 1063. Load more
use crate::*; use anyhow::{Context as _, Result, anyhow}; use collections::HashMap; use fs::Fs; use gpui::Rgba; use paths::{cursor_settings_file_paths, vscode_settings_file_paths}; use serde::Deserialize; use serde_json::{Map, Value}; use std::{ num::{NonZeroU32, NonZeroUsize}, path::{Path, PathBuf}, sync::Arc, }; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum VsCodeSettingsSource { VsCode, Cursor, } impl std::fmt::Display for VsCodeSettingsSource { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { VsCodeSettingsSource::VsCode => write!(f, "VS Code"), VsCodeSettingsSource::Cursor => write!(f, "Cursor"), } } } pub struct VsCodeSettings { pub source: VsCodeSettingsSource, pub path: Arc<Path>, content: Map<String, Value>, } impl VsCodeSettings { #[cfg(any(test, feature = "test-support"))] pub fn from_str(content: &str, source: VsCodeSettingsSource) -> Result<Self> { Ok(Self { source, path: Path::new("/example-path/Code/User/settings.json").into(), content: serde_json_lenient::from_str(content)?, }) } pub async fn load_user_settings(source: VsCodeSettingsSource, fs: Arc<dyn Fs>) -> Result<Self> { let candidate_paths = match source { VsCodeSettingsSource::VsCode => vscode_settings_file_paths(), VsCodeSettingsSource::Cursor => cursor_settings_file_paths(), }; let mut path = None; for candidate_path in candidate_paths.iter() { if fs.is_file(candidate_path).await { path = Some(candidate_path.clone()); } } let Some(path) = path else { return Err(anyhow!( "No settings file found, expected to find it in one of the following paths:\n{}", candidate_paths .into_iter() .map(|path| path.to_string_lossy().into_owned()) .collect::<Vec<_>>() .join("\n") )); }; let content = fs.load(&path).await.with_context(|| { format!( "Error loading {} settings file from {}", source, path.display() ) })?; let content = serde_json_lenient::from_str(&content).with_context(|| { format!( "Error parsing {} settings file from {}", source, path.display() ) })?; Ok(Self { source, path: path.into(), content, }) } fn read_value(&self, setting: &str) -> Option<&Value> { self.content.get(setting) } fn read_str(&self, setting: &str) -> Option<&str> { self.read_value(setting).and_then(|v| v.as_str()) } fn read_string(&self, setting: &str) -> Option<String> { self.read_value(setting) .and_then(|v| v.as_str()) .map(|s| s.to_owned()) } fn read_bool(&self, setting: &str) -> Option<bool> { self.read_value(setting).and_then(|v| v.as_bool()) } fn read_f32(&self, setting: &str) -> Option<f32> { self.read_value(setting) .and_then(|v| v.as_f64()) .map(|v| v as f32) } fn read_u64(&self, setting: &str) -> Option<u64> { self.read_value(setting).and_then(|v| v.as_u64()) } fn read_usize(&self, setting: &str) -> Option<usize> { self.read_value(setting) .and_then(|v| v.as_u64()) .and_then(|v| v.try_into().ok()) } fn read_u32(&self, setting: &str) -> Option<u32> { self.read_value(setting) .and_then(|v| v.as_u64()) .and_then(|v| v.try_into().ok()) } fn read_enum<T>(&self, key: &str, f: impl FnOnce(&str) -> Option<T>) -> Option<T> { self.content.get(key).and_then(Value::as_str).and_then(f) } fn read_fonts(&self, key: &str) -> (Option<FontFamilyName>, Option<Vec<FontFamilyName>>) { let Some(css_name) = self.content.get(key).and_then(Value::as_str) else { return (None, None); }; let mut name_buffer = String::new(); let mut quote_char: Option<char> = None; let mut fonts = Vec::new(); let mut add_font = |buffer: &mut String| { let trimmed = buffer.trim(); if !trimmed.is_empty() { fonts.push(trimmed.to_string().into()); } buffer.clear(); }; for ch in css_name.chars() { match (ch, quote_char) { ('"' | '\'', None) => { quote_char = Some(ch); } (_, Some(q)) if ch == q => { quote_char = None; } (',', None) => { add_font(&mut name_buffer); } _ => { name_buffer.push(ch); } } } 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, 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: skip_default(GlobalLspSettingsContent { semantic_token_rules: self.semantic_token_rules(), ..GlobalLspSettingsContent::default() }), 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(), which_key: None, } } fn agent_settings_content(&self) -> Option<AgentSettingsContent> { 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"), sticky_scroll: self.sticky_scroll_content(), 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").map(Into::into), 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(RelativeLineNumbers::Enabled), _ => 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"), completion_menu_scrollbar: None, completion_detail_alignment: None, diff_view_style: None, } } fn sticky_scroll_content(&self) -> Option<StickyScrollContent> { skip_default(StickyScrollContent { enabled: self.read_bool("editor.stickyScroll.enabled"), }) } fn gutter_content(&self) -> Option<GutterContent> { 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<ScrollbarContent> { 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<SearchSettingsContent> { skip_default(SearchSettingsContent { include_ignored: self.read_bool("search.useIgnoreFiles"), ..Default::default() }) } fn semantic_token_rules(&self) -> Option<SemanticTokenRules> { let customizations = self .read_value("editor.semanticTokenColorCustomizations")? .as_object()?; skip_default(SemanticTokenRules { rules: customizations .get("rules") .and_then(|v| { Some( v.as_object()? .iter() .filter_map(|(k, v)| { let v = v.as_object()?; let mut underline = v .get("underline") .and_then(|b| b.as_bool()) .unwrap_or(false); let strikethrough = v .get("strikethrough") .and_then(|b| b.as_bool()) .unwrap_or(false); let mut font_weight = v.get("bold").and_then(|b| b.as_bool()).map(|b| { if b { SemanticTokenFontWeight::Bold } else { SemanticTokenFontWeight::Normal } }); let mut font_style = v.get("italic").and_then(|b| b.as_bool()).map(|b| { if b { SemanticTokenFontStyle::Italic } else { SemanticTokenFontStyle::Normal } }); match v.get("fontStyle").and_then(|s| s.as_str()).unwrap_or("") { "bold" => { font_style = Some(SemanticTokenFontStyle::Normal); font_weight = Some(SemanticTokenFontWeight::Bold); } "italic" => { font_style = Some(SemanticTokenFontStyle::Italic); font_weight = Some(SemanticTokenFontWeight::Normal); } "underline" => { underline = true; } "bold italic" | "italic bold" => { font_style = Some(SemanticTokenFontStyle::Italic); font_weight = Some(SemanticTokenFontWeight::Bold); } "normal" => { font_style = Some(SemanticTokenFontStyle::Normal); font_weight = Some(SemanticTokenFontWeight::Normal); } _ => {} } let foreground = v .get("foreground") .and_then(|v| Rgba::try_from(v.as_str()?).ok()) .map(|s| s.to_owned()); let background = v .get("background") .and_then(|v| Rgba::try_from(v.as_str()?).ok()) .map(|s| s.to_owned()); Some(SemanticTokenRule { token_type: Some(k.clone()), token_modifiers: vec![], style: vec![], underline: if underline { Some(SemanticTokenColorOverride::InheritForeground(true)) } else { None }, strikethrough: if strikethrough { Some(SemanticTokenColorOverride::InheritForeground(true)) } else { None }, foreground_color: foreground, background_color: background, font_weight, font_style, }) }) .collect(), ) }) .unwrap_or_default(), }) } fn minimap_content(&self) -> Option<MinimapContent> { 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() }) } fn git_panel_settings_content(&self) -> Option<GitPanelSettingsContent> { 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 { edit_predictions: self.edit_predictions_settings_content(), defaults: self.default_language_settings_content(), languages: Default::default(),