1162 lines · 46.2 KB · Rust
RawDownload
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,            credentials_url: 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,            hide_mouse: None,            image_viewer: None,            markdown_preview: None,            journal: None,            language_models: None,            line_indicator_format: None,            log: None,            node: self.node_binary_settings(),             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"),            reduce_motion: self.read_enum("workbench.reduceMotion", |s| match s {                "on" => Some(ReduceMotionMode::On),                "off" => Some(ReduceMotionMode::Off),                _ => None,            }),            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,            modeline_lines: None,            feature_flags: None,            instrumentation: 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,            go_to_definition_scroll_strategy: None,            lsp_results_location: None,            gutter: self.gutter_content(),            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"),            hover_popover_sticky: self.read_bool("editor.hover.sticky"),            hover_popover_hiding_delay: self.read_u64("editor.hover.hidingDelay").map(Into::into),            inline_code_actions: None,            code_lens: None,            jupyter: None,            lsp_document_colors: None,            lsp_document_links: self.read_bool("editor.links"),            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,            mouse_wheel_zoom: self.read_bool("editor.mouseWheelZoom"),            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,            completion_menu_item_kind: None,            diff_view_style: None,            minimum_split_diff_width: 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,            bookmarks: 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").unwrap_or(true);        let autohide = self.read_bool("editor.minimap.autohide").unwrap_or(false);        let show = match (minimap_enabled, autohide) {            (true, false) => Some(ShowMinimap::Always),            (true, true) => Some(ShowMinimap::Auto),            (false, _) => Some(ShowMinimap::Never),        };         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)),

Lines 1–500 of 1162.