extension_manifest.rs

  1use anyhow::{anyhow, Context, Result};
  2use collections::BTreeMap;
  3use fs::Fs;
  4use language::LanguageServerName;
  5use serde::{Deserialize, Serialize};
  6use std::{
  7    ffi::OsStr,
  8    path::{Path, PathBuf},
  9    sync::Arc,
 10};
 11use util::SemanticVersion;
 12
 13/// This is the old version of the extension manifest, from when it was `extension.json`.
 14#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
 15pub struct OldExtensionManifest {
 16    pub name: String,
 17    pub version: Arc<str>,
 18
 19    #[serde(default)]
 20    pub description: Option<String>,
 21    #[serde(default)]
 22    pub repository: Option<String>,
 23    #[serde(default)]
 24    pub authors: Vec<String>,
 25
 26    #[serde(default)]
 27    pub themes: BTreeMap<Arc<str>, PathBuf>,
 28    #[serde(default)]
 29    pub languages: BTreeMap<Arc<str>, PathBuf>,
 30    #[serde(default)]
 31    pub grammars: BTreeMap<Arc<str>, PathBuf>,
 32}
 33
 34#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
 35pub struct SchemaVersion(pub i32);
 36
 37impl SchemaVersion {
 38    pub const ZERO: Self = Self(0);
 39
 40    pub fn is_v0(&self) -> bool {
 41        self == &Self::ZERO
 42    }
 43}
 44
 45#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
 46pub struct ExtensionManifest {
 47    pub id: Arc<str>,
 48    pub name: String,
 49    pub version: Arc<str>,
 50    pub schema_version: SchemaVersion,
 51
 52    #[serde(default)]
 53    pub description: Option<String>,
 54    #[serde(default)]
 55    pub repository: Option<String>,
 56    #[serde(default)]
 57    pub authors: Vec<String>,
 58    #[serde(default)]
 59    pub lib: LibManifestEntry,
 60
 61    #[serde(default)]
 62    pub themes: Vec<PathBuf>,
 63    #[serde(default)]
 64    pub languages: Vec<PathBuf>,
 65    #[serde(default)]
 66    pub grammars: BTreeMap<Arc<str>, GrammarManifestEntry>,
 67    #[serde(default)]
 68    pub language_servers: BTreeMap<LanguageServerName, LanguageServerManifestEntry>,
 69}
 70
 71#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
 72pub struct LibManifestEntry {
 73    pub kind: Option<ExtensionLibraryKind>,
 74    pub version: Option<SemanticVersion>,
 75}
 76
 77#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
 78pub enum ExtensionLibraryKind {
 79    Rust,
 80}
 81
 82#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
 83pub struct GrammarManifestEntry {
 84    pub repository: String,
 85    #[serde(alias = "commit")]
 86    pub rev: String,
 87}
 88
 89#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
 90pub struct LanguageServerManifestEntry {
 91    pub language: Arc<str>,
 92}
 93
 94impl ExtensionManifest {
 95    pub async fn load(fs: Arc<dyn Fs>, extension_dir: &Path) -> Result<Self> {
 96        let extension_name = extension_dir
 97            .file_name()
 98            .and_then(OsStr::to_str)
 99            .ok_or_else(|| anyhow!("invalid extension name"))?;
100
101        let mut extension_manifest_path = extension_dir.join("extension.json");
102        if fs.is_file(&extension_manifest_path).await {
103            let manifest_content = fs
104                .load(&extension_manifest_path)
105                .await
106                .with_context(|| format!("failed to load {extension_name} extension.json"))?;
107            let manifest_json = serde_json::from_str::<OldExtensionManifest>(&manifest_content)
108                .with_context(|| {
109                    format!("invalid extension.json for extension {extension_name}")
110                })?;
111
112            Ok(manifest_from_old_manifest(manifest_json, extension_name))
113        } else {
114            extension_manifest_path.set_extension("toml");
115            let manifest_content = fs
116                .load(&extension_manifest_path)
117                .await
118                .with_context(|| format!("failed to load {extension_name} extension.toml"))?;
119            toml::from_str(&manifest_content)
120                .with_context(|| format!("invalid extension.json for extension {extension_name}"))
121        }
122    }
123}
124
125fn manifest_from_old_manifest(
126    manifest_json: OldExtensionManifest,
127    extension_id: &str,
128) -> ExtensionManifest {
129    ExtensionManifest {
130        id: extension_id.into(),
131        name: manifest_json.name,
132        version: manifest_json.version,
133        description: manifest_json.description,
134        repository: manifest_json.repository,
135        authors: manifest_json.authors,
136        schema_version: SchemaVersion::ZERO,
137        lib: Default::default(),
138        themes: {
139            let mut themes = manifest_json.themes.into_values().collect::<Vec<_>>();
140            themes.sort();
141            themes.dedup();
142            themes
143        },
144        languages: {
145            let mut languages = manifest_json.languages.into_values().collect::<Vec<_>>();
146            languages.sort();
147            languages.dedup();
148            languages
149        },
150        grammars: manifest_json
151            .grammars
152            .into_keys()
153            .map(|grammar_name| (grammar_name, Default::default()))
154            .collect(),
155        language_servers: Default::default(),
156    }
157}