1use anyhow::{anyhow, Context as _, 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 icon_themes: Vec<PathBuf>,
74 #[serde(default)]
75 pub languages: Vec<PathBuf>,
76 #[serde(default)]
77 pub grammars: BTreeMap<Arc<str>, GrammarManifestEntry>,
78 #[serde(default)]
79 pub language_servers: BTreeMap<LanguageServerName, LanguageServerManifestEntry>,
80 #[serde(default)]
81 pub context_servers: BTreeMap<Arc<str>, ContextServerManifestEntry>,
82 #[serde(default)]
83 pub slash_commands: BTreeMap<Arc<str>, SlashCommandManifestEntry>,
84 #[serde(default)]
85 pub indexed_docs_providers: BTreeMap<Arc<str>, IndexedDocsProviderEntry>,
86 #[serde(default)]
87 pub snippets: Option<PathBuf>,
88}
89
90#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
91pub struct LibManifestEntry {
92 pub kind: Option<ExtensionLibraryKind>,
93 pub version: Option<SemanticVersion>,
94}
95
96#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
97pub enum ExtensionLibraryKind {
98 Rust,
99}
100
101#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
102pub struct GrammarManifestEntry {
103 pub repository: String,
104 #[serde(alias = "commit")]
105 pub rev: String,
106 #[serde(default)]
107 pub path: Option<String>,
108}
109
110#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
111pub struct LanguageServerManifestEntry {
112 /// Deprecated in favor of `languages`.
113 #[serde(default)]
114 language: Option<LanguageName>,
115 /// The list of languages this language server should work with.
116 #[serde(default)]
117 languages: Vec<LanguageName>,
118 #[serde(default)]
119 pub language_ids: HashMap<String, String>,
120 #[serde(default)]
121 pub code_action_kinds: Option<Vec<lsp::CodeActionKind>>,
122}
123
124impl LanguageServerManifestEntry {
125 /// Returns the list of languages for the language server.
126 ///
127 /// Prefer this over accessing the `language` or `languages` fields directly,
128 /// as we currently support both.
129 ///
130 /// We can replace this with just field access for the `languages` field once
131 /// we have removed `language`.
132 pub fn languages(&self) -> impl IntoIterator<Item = LanguageName> + '_ {
133 let language = if self.languages.is_empty() {
134 self.language.clone()
135 } else {
136 None
137 };
138 self.languages.iter().cloned().chain(language)
139 }
140}
141
142#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
143pub struct ContextServerManifestEntry {}
144
145#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
146pub struct SlashCommandManifestEntry {
147 pub description: String,
148 pub requires_argument: bool,
149}
150
151#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
152pub struct IndexedDocsProviderEntry {}
153
154impl ExtensionManifest {
155 pub async fn load(fs: Arc<dyn Fs>, extension_dir: &Path) -> Result<Self> {
156 let extension_name = extension_dir
157 .file_name()
158 .and_then(OsStr::to_str)
159 .ok_or_else(|| anyhow!("invalid extension name"))?;
160
161 let mut extension_manifest_path = extension_dir.join("extension.json");
162 if fs.is_file(&extension_manifest_path).await {
163 let manifest_content = fs
164 .load(&extension_manifest_path)
165 .await
166 .with_context(|| format!("failed to load {extension_name} extension.json"))?;
167 let manifest_json = serde_json::from_str::<OldExtensionManifest>(&manifest_content)
168 .with_context(|| {
169 format!("invalid extension.json for extension {extension_name}")
170 })?;
171
172 Ok(manifest_from_old_manifest(manifest_json, extension_name))
173 } else {
174 extension_manifest_path.set_extension("toml");
175 let manifest_content = fs
176 .load(&extension_manifest_path)
177 .await
178 .with_context(|| format!("failed to load {extension_name} extension.toml"))?;
179 toml::from_str(&manifest_content)
180 .with_context(|| format!("invalid extension.toml for extension {extension_name}"))
181 }
182 }
183}
184
185fn manifest_from_old_manifest(
186 manifest_json: OldExtensionManifest,
187 extension_id: &str,
188) -> ExtensionManifest {
189 ExtensionManifest {
190 id: extension_id.into(),
191 name: manifest_json.name,
192 version: manifest_json.version,
193 description: manifest_json.description,
194 repository: manifest_json.repository,
195 authors: manifest_json.authors,
196 schema_version: SchemaVersion::ZERO,
197 lib: Default::default(),
198 themes: {
199 let mut themes = manifest_json.themes.into_values().collect::<Vec<_>>();
200 themes.sort();
201 themes.dedup();
202 themes
203 },
204 icon_themes: Vec::new(),
205 languages: {
206 let mut languages = manifest_json.languages.into_values().collect::<Vec<_>>();
207 languages.sort();
208 languages.dedup();
209 languages
210 },
211 grammars: manifest_json
212 .grammars
213 .into_keys()
214 .map(|grammar_name| (grammar_name, Default::default()))
215 .collect(),
216 language_servers: Default::default(),
217 context_servers: BTreeMap::default(),
218 slash_commands: BTreeMap::default(),
219 indexed_docs_providers: BTreeMap::default(),
220 snippets: None,
221 }
222}