vscode_import.rs

  1use anyhow::{Context as _, Result, anyhow};
  2use fs::Fs;
  3use paths::{cursor_settings_file_paths, vscode_settings_file_paths};
  4use serde_json::{Map, Value};
  5use std::{path::Path, sync::Arc};
  6
  7#[derive(Clone, Copy, PartialEq, Eq, Debug)]
  8pub enum VsCodeSettingsSource {
  9    VsCode,
 10    Cursor,
 11}
 12
 13impl std::fmt::Display for VsCodeSettingsSource {
 14    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 15        match self {
 16            VsCodeSettingsSource::VsCode => write!(f, "VS Code"),
 17            VsCodeSettingsSource::Cursor => write!(f, "Cursor"),
 18        }
 19    }
 20}
 21
 22pub struct VsCodeSettings {
 23    pub source: VsCodeSettingsSource,
 24    pub path: Arc<Path>,
 25    content: Map<String, Value>,
 26}
 27
 28impl VsCodeSettings {
 29    #[cfg(any(test, feature = "test-support"))]
 30    pub fn from_str(content: &str, source: VsCodeSettingsSource) -> Result<Self> {
 31        Ok(Self {
 32            source,
 33            path: Path::new("/example-path/Code/User/settings.json").into(),
 34            content: serde_json_lenient::from_str(content)?,
 35        })
 36    }
 37
 38    pub async fn load_user_settings(source: VsCodeSettingsSource, fs: Arc<dyn Fs>) -> Result<Self> {
 39        let candidate_paths = match source {
 40            VsCodeSettingsSource::VsCode => vscode_settings_file_paths(),
 41            VsCodeSettingsSource::Cursor => cursor_settings_file_paths(),
 42        };
 43        let mut path = None;
 44        for candidate_path in candidate_paths.iter() {
 45            if fs.is_file(candidate_path).await {
 46                path = Some(candidate_path.clone());
 47            }
 48        }
 49        let Some(path) = path else {
 50            return Err(anyhow!(
 51                "No settings file found, expected to find it in one of the following paths:\n{}",
 52                candidate_paths
 53                    .into_iter()
 54                    .map(|path| path.to_string_lossy().into_owned())
 55                    .collect::<Vec<_>>()
 56                    .join("\n")
 57            ));
 58        };
 59        let content = fs.load(&path).await.with_context(|| {
 60            format!(
 61                "Error loading {} settings file from {}",
 62                source,
 63                path.display()
 64            )
 65        })?;
 66        let content = serde_json_lenient::from_str(&content).with_context(|| {
 67            format!(
 68                "Error parsing {} settings file from {}",
 69                source,
 70                path.display()
 71            )
 72        })?;
 73        Ok(Self {
 74            source,
 75            path: path.into(),
 76            content,
 77        })
 78    }
 79
 80    pub fn read_value(&self, setting: &str) -> Option<&Value> {
 81        self.content.get(setting)
 82    }
 83
 84    pub fn read_string(&self, setting: &str) -> Option<&str> {
 85        self.read_value(setting).and_then(|v| v.as_str())
 86    }
 87
 88    pub fn read_bool(&self, setting: &str) -> Option<bool> {
 89        self.read_value(setting).and_then(|v| v.as_bool())
 90    }
 91
 92    pub fn string_setting(&self, key: &str, setting: &mut Option<String>) {
 93        if let Some(s) = self.content.get(key).and_then(Value::as_str) {
 94            *setting = Some(s.to_owned())
 95        }
 96    }
 97
 98    pub fn bool_setting(&self, key: &str, setting: &mut Option<bool>) {
 99        if let Some(s) = self.content.get(key).and_then(Value::as_bool) {
100            *setting = Some(s)
101        }
102    }
103
104    pub fn u32_setting(&self, key: &str, setting: &mut Option<u32>) {
105        if let Some(s) = self.content.get(key).and_then(Value::as_u64) {
106            *setting = Some(s as u32)
107        }
108    }
109
110    pub fn u64_setting(&self, key: &str, setting: &mut Option<u64>) {
111        if let Some(s) = self.content.get(key).and_then(Value::as_u64) {
112            *setting = Some(s)
113        }
114    }
115
116    pub fn usize_setting(&self, key: &str, setting: &mut Option<usize>) {
117        if let Some(s) = self.content.get(key).and_then(Value::as_u64) {
118            *setting = Some(s.try_into().unwrap())
119        }
120    }
121
122    pub fn f32_setting(&self, key: &str, setting: &mut Option<f32>) {
123        if let Some(s) = self.content.get(key).and_then(Value::as_f64) {
124            *setting = Some(s as f32)
125        }
126    }
127
128    pub fn from_f32_setting<T: From<f32>>(&self, key: &str, setting: &mut Option<T>) {
129        if let Some(s) = self.content.get(key).and_then(Value::as_f64) {
130            *setting = Some(T::from(s as f32))
131        }
132    }
133
134    pub fn enum_setting<T>(
135        &self,
136        key: &str,
137        setting: &mut Option<T>,
138        f: impl FnOnce(&str) -> Option<T>,
139    ) {
140        if let Some(s) = self.content.get(key).and_then(Value::as_str).and_then(f) {
141            *setting = Some(s)
142        }
143    }
144
145    pub fn read_enum<T>(&self, key: &str, f: impl FnOnce(&str) -> Option<T>) -> Option<T> {
146        self.content.get(key).and_then(Value::as_str).and_then(f)
147    }
148}