use anyhow::{anyhow, Context, Result}; use collections::{BTreeMap, HashMap}; use fs::Fs; use language::LanguageServerName; use semantic_version::SemanticVersion; use serde::{Deserialize, Serialize}; use std::{ ffi::OsStr, fmt, path::{Path, PathBuf}, sync::Arc, }; /// This is the old version of the extension manifest, from when it was `extension.json`. #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct OldExtensionManifest { pub name: String, pub version: Arc, #[serde(default)] pub description: Option, #[serde(default)] pub repository: Option, #[serde(default)] pub authors: Vec, #[serde(default)] pub themes: BTreeMap, PathBuf>, #[serde(default)] pub languages: BTreeMap, PathBuf>, #[serde(default)] pub grammars: BTreeMap, PathBuf>, } /// The schema version of the [`ExtensionManifest`]. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)] pub struct SchemaVersion(pub i32); impl fmt::Display for SchemaVersion { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl SchemaVersion { pub const ZERO: Self = Self(0); pub fn is_v0(&self) -> bool { self == &Self::ZERO } } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct ExtensionManifest { pub id: Arc, pub name: String, pub version: Arc, pub schema_version: SchemaVersion, #[serde(default)] pub description: Option, #[serde(default)] pub repository: Option, #[serde(default)] pub authors: Vec, #[serde(default)] pub lib: LibManifestEntry, #[serde(default)] pub themes: Vec, #[serde(default)] pub languages: Vec, #[serde(default)] pub grammars: BTreeMap, GrammarManifestEntry>, #[serde(default)] pub language_servers: BTreeMap, } #[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)] pub struct LibManifestEntry { pub kind: Option, pub version: Option, } #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] pub enum ExtensionLibraryKind { Rust, } #[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)] pub struct GrammarManifestEntry { pub repository: String, #[serde(alias = "commit")] pub rev: String, #[serde(default)] pub path: Option, } #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] pub struct LanguageServerManifestEntry { pub language: Arc, #[serde(default)] pub language_ids: HashMap, } impl ExtensionManifest { pub async fn load(fs: Arc, extension_dir: &Path) -> Result { let extension_name = extension_dir .file_name() .and_then(OsStr::to_str) .ok_or_else(|| anyhow!("invalid extension name"))?; let mut extension_manifest_path = extension_dir.join("extension.json"); if fs.is_file(&extension_manifest_path).await { let manifest_content = fs .load(&extension_manifest_path) .await .with_context(|| format!("failed to load {extension_name} extension.json"))?; let manifest_json = serde_json::from_str::(&manifest_content) .with_context(|| { format!("invalid extension.json for extension {extension_name}") })?; Ok(manifest_from_old_manifest(manifest_json, extension_name)) } else { extension_manifest_path.set_extension("toml"); let manifest_content = fs .load(&extension_manifest_path) .await .with_context(|| format!("failed to load {extension_name} extension.toml"))?; toml::from_str(&manifest_content) .with_context(|| format!("invalid extension.json for extension {extension_name}")) } } } fn manifest_from_old_manifest( manifest_json: OldExtensionManifest, extension_id: &str, ) -> ExtensionManifest { ExtensionManifest { id: extension_id.into(), name: manifest_json.name, version: manifest_json.version, description: manifest_json.description, repository: manifest_json.repository, authors: manifest_json.authors, schema_version: SchemaVersion::ZERO, lib: Default::default(), themes: { let mut themes = manifest_json.themes.into_values().collect::>(); themes.sort(); themes.dedup(); themes }, languages: { let mut languages = manifest_json.languages.into_values().collect::>(); languages.sort(); languages.dedup(); languages }, grammars: manifest_json .grammars .into_keys() .map(|grammar_name| (grammar_name, Default::default())) .collect(), language_servers: Default::default(), } }