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}