extension_manifest.rs

  1use anyhow::{anyhow, Context, Result};
  2use collections::{BTreeMap, HashMap};
  3use fs::Fs;
  4use language::LanguageServerName;
  5use semantic_version::SemanticVersion;
  6use serde::{Deserialize, Serialize};
  7use std::{
  8    ffi::OsStr,
  9    fmt,
 10    path::{Path, PathBuf},
 11    sync::Arc,
 12};
 13
 14/// This is the old version of the extension manifest, from when it was `extension.json`.
 15#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
 16pub struct OldExtensionManifest {
 17    pub name: String,
 18    pub version: Arc<str>,
 19
 20    #[serde(default)]
 21    pub description: Option<String>,
 22    #[serde(default)]
 23    pub repository: Option<String>,
 24    #[serde(default)]
 25    pub authors: Vec<String>,
 26
 27    #[serde(default)]
 28    pub themes: BTreeMap<Arc<str>, PathBuf>,
 29    #[serde(default)]
 30    pub languages: BTreeMap<Arc<str>, PathBuf>,
 31    #[serde(default)]
 32    pub grammars: BTreeMap<Arc<str>, PathBuf>,
 33}
 34
 35/// The schema version of the [`ExtensionManifest`].
 36#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
 37pub struct SchemaVersion(pub i32);
 38
 39impl fmt::Display for SchemaVersion {
 40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 41        write!(f, "{}", self.0)
 42    }
 43}
 44
 45impl SchemaVersion {
 46    pub const ZERO: Self = Self(0);
 47
 48    pub fn is_v0(&self) -> bool {
 49        self == &Self::ZERO
 50    }
 51}
 52
 53#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
 54pub struct ExtensionManifest {
 55    pub id: Arc<str>,
 56    pub name: String,
 57    pub version: Arc<str>,
 58    pub schema_version: SchemaVersion,
 59
 60    #[serde(default)]
 61    pub description: Option<String>,
 62    #[serde(default)]
 63    pub repository: Option<String>,
 64    #[serde(default)]
 65    pub authors: Vec<String>,
 66    #[serde(default)]
 67    pub lib: LibManifestEntry,
 68
 69    #[serde(default)]
 70    pub themes: Vec<PathBuf>,
 71    #[serde(default)]
 72    pub languages: Vec<PathBuf>,
 73    #[serde(default)]
 74    pub grammars: BTreeMap<Arc<str>, GrammarManifestEntry>,
 75    #[serde(default)]
 76    pub language_servers: BTreeMap<LanguageServerName, LanguageServerManifestEntry>,
 77    #[serde(default)]
 78    pub slash_commands: BTreeMap<Arc<str>, SlashCommandManifestEntry>,
 79    #[serde(default)]
 80    pub indexed_docs_providers: BTreeMap<Arc<str>, IndexedDocsProviderEntry>,
 81    #[serde(default)]
 82    pub snippets: Option<PathBuf>,
 83}
 84
 85#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
 86pub struct LibManifestEntry {
 87    pub kind: Option<ExtensionLibraryKind>,
 88    pub version: Option<SemanticVersion>,
 89}
 90
 91#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
 92pub enum ExtensionLibraryKind {
 93    Rust,
 94}
 95
 96#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
 97pub struct GrammarManifestEntry {
 98    pub repository: String,
 99    #[serde(alias = "commit")]
100    pub rev: String,
101    #[serde(default)]
102    pub path: Option<String>,
103}
104
105#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
106pub struct LanguageServerManifestEntry {
107    /// Deprecated in favor of `languages`.
108    #[serde(default)]
109    language: Option<Arc<str>>,
110    /// The list of languages this language server should work with.
111    #[serde(default)]
112    languages: Vec<Arc<str>>,
113    #[serde(default)]
114    pub language_ids: HashMap<String, String>,
115    #[serde(default)]
116    pub code_action_kinds: Option<Vec<lsp::CodeActionKind>>,
117}
118
119impl LanguageServerManifestEntry {
120    /// Returns the list of languages for the language server.
121    ///
122    /// Prefer this over accessing the `language` or `languages` fields directly,
123    /// as we currently support both.
124    ///
125    /// We can replace this with just field access for the `languages` field once
126    /// we have removed `language`.
127    pub fn languages(&self) -> impl IntoIterator<Item = Arc<str>> + '_ {
128        let language = if self.languages.is_empty() {
129            self.language.clone()
130        } else {
131            None
132        };
133        self.languages.iter().cloned().chain(language)
134    }
135}
136
137#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
138pub struct SlashCommandManifestEntry {
139    pub description: String,
140    pub tooltip_text: String,
141    pub requires_argument: bool,
142}
143
144#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
145pub struct IndexedDocsProviderEntry {}
146
147impl ExtensionManifest {
148    pub async fn load(fs: Arc<dyn Fs>, extension_dir: &Path) -> Result<Self> {
149        let extension_name = extension_dir
150            .file_name()
151            .and_then(OsStr::to_str)
152            .ok_or_else(|| anyhow!("invalid extension name"))?;
153
154        let mut extension_manifest_path = extension_dir.join("extension.json");
155        if fs.is_file(&extension_manifest_path).await {
156            let manifest_content = fs
157                .load(&extension_manifest_path)
158                .await
159                .with_context(|| format!("failed to load {extension_name} extension.json"))?;
160            let manifest_json = serde_json::from_str::<OldExtensionManifest>(&manifest_content)
161                .with_context(|| {
162                    format!("invalid extension.json for extension {extension_name}")
163                })?;
164
165            Ok(manifest_from_old_manifest(manifest_json, extension_name))
166        } else {
167            extension_manifest_path.set_extension("toml");
168            let manifest_content = fs
169                .load(&extension_manifest_path)
170                .await
171                .with_context(|| format!("failed to load {extension_name} extension.toml"))?;
172            toml::from_str(&manifest_content)
173                .with_context(|| format!("invalid extension.json for extension {extension_name}"))
174        }
175    }
176}
177
178fn manifest_from_old_manifest(
179    manifest_json: OldExtensionManifest,
180    extension_id: &str,
181) -> ExtensionManifest {
182    ExtensionManifest {
183        id: extension_id.into(),
184        name: manifest_json.name,
185        version: manifest_json.version,
186        description: manifest_json.description,
187        repository: manifest_json.repository,
188        authors: manifest_json.authors,
189        schema_version: SchemaVersion::ZERO,
190        lib: Default::default(),
191        themes: {
192            let mut themes = manifest_json.themes.into_values().collect::<Vec<_>>();
193            themes.sort();
194            themes.dedup();
195            themes
196        },
197        languages: {
198            let mut languages = manifest_json.languages.into_values().collect::<Vec<_>>();
199            languages.sort();
200            languages.dedup();
201            languages
202        },
203        grammars: manifest_json
204            .grammars
205            .into_keys()
206            .map(|grammar_name| (grammar_name, Default::default()))
207            .collect(),
208        language_servers: Default::default(),
209        slash_commands: BTreeMap::default(),
210        indexed_docs_providers: BTreeMap::default(),
211        snippets: None,
212    }
213}