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}