extension_manifest.rs

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