222 lines · 7.0 KB · Rust
RawDownload
use anyhow::{anyhow, Context as _, Result};use collections::{BTreeMap, HashMap};use fs::Fs;use language::LanguageName;use lsp::LanguageServerName;use semantic_version::SemanticVersion;use serde::{Deserialize, Serialize};use std::{    ffi::OsStr,    fmt,    path::{Path, PathBuf},    sync::Arc,}; /// This is the old version of the extension manifest, from when it was `extension.json`.#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]pub struct OldExtensionManifest {    pub name: String,    pub version: Arc<str>,     #[serde(default)]    pub description: Option<String>,    #[serde(default)]    pub repository: Option<String>,    #[serde(default)]    pub authors: Vec<String>,     #[serde(default)]    pub themes: BTreeMap<Arc<str>, PathBuf>,    #[serde(default)]    pub languages: BTreeMap<Arc<str>, PathBuf>,    #[serde(default)]    pub grammars: BTreeMap<Arc<str>, PathBuf>,} /// The schema version of the [`ExtensionManifest`].#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]pub struct SchemaVersion(pub i32); impl fmt::Display for SchemaVersion {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        write!(f, "{}", self.0)    }} impl SchemaVersion {    pub const ZERO: Self = Self(0);     pub fn is_v0(&self) -> bool {        self == &Self::ZERO    }} #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]pub struct ExtensionManifest {    pub id: Arc<str>,    pub name: String,    pub version: Arc<str>,    pub schema_version: SchemaVersion,     #[serde(default)]    pub description: Option<String>,    #[serde(default)]    pub repository: Option<String>,    #[serde(default)]    pub authors: Vec<String>,    #[serde(default)]    pub lib: LibManifestEntry,     #[serde(default)]    pub themes: Vec<PathBuf>,    #[serde(default)]    pub icon_themes: Vec<PathBuf>,    #[serde(default)]    pub languages: Vec<PathBuf>,    #[serde(default)]    pub grammars: BTreeMap<Arc<str>, GrammarManifestEntry>,    #[serde(default)]    pub language_servers: BTreeMap<LanguageServerName, LanguageServerManifestEntry>,    #[serde(default)]    pub context_servers: BTreeMap<Arc<str>, ContextServerManifestEntry>,    #[serde(default)]    pub slash_commands: BTreeMap<Arc<str>, SlashCommandManifestEntry>,    #[serde(default)]    pub indexed_docs_providers: BTreeMap<Arc<str>, IndexedDocsProviderEntry>,    #[serde(default)]    pub snippets: Option<PathBuf>,} #[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]pub struct LibManifestEntry {    pub kind: Option<ExtensionLibraryKind>,    pub version: Option<SemanticVersion>,} #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]pub enum ExtensionLibraryKind {    Rust,} #[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]pub struct GrammarManifestEntry {    pub repository: String,    #[serde(alias = "commit")]    pub rev: String,    #[serde(default)]    pub path: Option<String>,} #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]pub struct LanguageServerManifestEntry {    /// Deprecated in favor of `languages`.    #[serde(default)]    language: Option<LanguageName>,    /// The list of languages this language server should work with.    #[serde(default)]    languages: Vec<LanguageName>,    #[serde(default)]    pub language_ids: HashMap<String, String>,    #[serde(default)]    pub code_action_kinds: Option<Vec<lsp::CodeActionKind>>,} impl LanguageServerManifestEntry {    /// Returns the list of languages for the language server.    ///    /// Prefer this over accessing the `language` or `languages` fields directly,    /// as we currently support both.    ///    /// We can replace this with just field access for the `languages` field once    /// we have removed `language`.    pub fn languages(&self) -> impl IntoIterator<Item = LanguageName> + '_ {        let language = if self.languages.is_empty() {            self.language.clone()        } else {            None        };        self.languages.iter().cloned().chain(language)    }} #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]pub struct ContextServerManifestEntry {} #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]pub struct SlashCommandManifestEntry {    pub description: String,    pub requires_argument: bool,} #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]pub struct IndexedDocsProviderEntry {} impl ExtensionManifest {    pub async fn load(fs: Arc<dyn Fs>, extension_dir: &Path) -> Result<Self> {        let extension_name = extension_dir            .file_name()            .and_then(OsStr::to_str)            .ok_or_else(|| anyhow!("invalid extension name"))?;         let mut extension_manifest_path = extension_dir.join("extension.json");        if fs.is_file(&extension_manifest_path).await {            let manifest_content = fs                .load(&extension_manifest_path)                .await                .with_context(|| format!("failed to load {extension_name} extension.json"))?;            let manifest_json = serde_json::from_str::<OldExtensionManifest>(&manifest_content)                .with_context(|| {                    format!("invalid extension.json for extension {extension_name}")                })?;             Ok(manifest_from_old_manifest(manifest_json, extension_name))        } else {            extension_manifest_path.set_extension("toml");            let manifest_content = fs                .load(&extension_manifest_path)                .await                .with_context(|| format!("failed to load {extension_name} extension.toml"))?;            toml::from_str(&manifest_content)                .with_context(|| format!("invalid extension.toml for extension {extension_name}"))        }    }} fn manifest_from_old_manifest(    manifest_json: OldExtensionManifest,    extension_id: &str,) -> ExtensionManifest {    ExtensionManifest {        id: extension_id.into(),        name: manifest_json.name,        version: manifest_json.version,        description: manifest_json.description,        repository: manifest_json.repository,        authors: manifest_json.authors,        schema_version: SchemaVersion::ZERO,        lib: Default::default(),        themes: {            let mut themes = manifest_json.themes.into_values().collect::<Vec<_>>();            themes.sort();            themes.dedup();            themes        },        icon_themes: Vec::new(),        languages: {            let mut languages = manifest_json.languages.into_values().collect::<Vec<_>>();            languages.sort();            languages.dedup();            languages        },        grammars: manifest_json            .grammars            .into_keys()            .map(|grammar_name| (grammar_name, Default::default()))            .collect(),        language_servers: Default::default(),        context_servers: BTreeMap::default(),        slash_commands: BTreeMap::default(),        indexed_docs_providers: BTreeMap::default(),        snippets: None,    }}