1693 lines · 60.9 KB · Rust
RawDownload
pub mod extension_settings;pub mod headless_host;pub mod wasm_host; #[cfg(test)]mod extension_store_test; use anyhow::{anyhow, bail, Context as _, Result};use async_compression::futures::bufread::GzipDecoder;use async_tar::Archive;use client::ExtensionProvides;use client::{proto, telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse};use collections::{btree_map, BTreeMap, BTreeSet, HashMap, HashSet};use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder};pub use extension::ExtensionManifest;use extension::{    ExtensionContextServerProxy, ExtensionGrammarProxy, ExtensionHostProxy,    ExtensionIndexedDocsProviderProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy,    ExtensionSlashCommandProxy, ExtensionSnippetProxy, ExtensionThemeProxy,};use fs::{Fs, RemoveOptions};use futures::{    channel::{        mpsc::{unbounded, UnboundedSender},        oneshot,    },    io::BufReader,    select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,};use gpui::{    actions, App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Global, Task,    WeakEntity,};use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};use language::{    LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LoadedLanguage, Rope,    QUERY_FILENAME_PREFIXES,};use node_runtime::NodeRuntime;use project::ContextProviderWithTasks;use release_channel::ReleaseChannel;use remote::SshRemoteClient;use semantic_version::SemanticVersion;use serde::{Deserialize, Serialize};use settings::Settings;use std::ops::RangeInclusive;use std::str::FromStr;use std::{    cmp::Ordering,    path::{self, Path, PathBuf},    sync::Arc,    time::{Duration, Instant},};use url::Url;use util::ResultExt;use wasm_host::{    wit::{is_supported_wasm_api_version, wasm_api_version_range},    WasmExtension, WasmHost,}; pub use extension::{    ExtensionLibraryKind, GrammarManifestEntry, OldExtensionManifest, SchemaVersion,};pub use extension_settings::ExtensionSettings; pub const RELOAD_DEBOUNCE_DURATION: Duration = Duration::from_millis(200);const FS_WATCH_LATENCY: Duration = Duration::from_millis(100); /// The current extension [`SchemaVersion`] supported by Zed.const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(1); /// Returns the [`SchemaVersion`] range that is compatible with this version of Zed.pub fn schema_version_range() -> RangeInclusive<SchemaVersion> {    SchemaVersion::ZERO..=CURRENT_SCHEMA_VERSION} /// Returns whether the given extension version is compatible with this version of Zed.pub fn is_version_compatible(    release_channel: ReleaseChannel,    extension_version: &ExtensionMetadata,) -> bool {    let schema_version = extension_version.manifest.schema_version.unwrap_or(0);    if CURRENT_SCHEMA_VERSION.0 < schema_version {        return false;    }     if let Some(wasm_api_version) = extension_version        .manifest        .wasm_api_version        .as_ref()        .and_then(|wasm_api_version| SemanticVersion::from_str(wasm_api_version).ok())    {        if !is_supported_wasm_api_version(release_channel, wasm_api_version) {            return false;        }    }     true} pub struct ExtensionStore {    pub proxy: Arc<ExtensionHostProxy>,    pub builder: Arc<ExtensionBuilder>,    pub extension_index: ExtensionIndex,    pub fs: Arc<dyn Fs>,    pub http_client: Arc<HttpClientWithUrl>,    pub telemetry: Option<Arc<Telemetry>>,    pub reload_tx: UnboundedSender<Option<Arc<str>>>,    pub reload_complete_senders: Vec<oneshot::Sender<()>>,    pub installed_dir: PathBuf,    pub outstanding_operations: BTreeMap<Arc<str>, ExtensionOperation>,    pub index_path: PathBuf,    pub modified_extensions: HashSet<Arc<str>>,    pub wasm_host: Arc<WasmHost>,    pub wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,    pub tasks: Vec<Task<()>>,    pub ssh_clients: HashMap<String, WeakEntity<SshRemoteClient>>,    pub ssh_registered_tx: UnboundedSender<()>,} #[derive(Clone, Copy)]pub enum ExtensionOperation {    Upgrade,    Install,    Remove,} #[derive(Clone)]pub enum Event {    ExtensionsUpdated,    StartedReloading,    ExtensionInstalled(Arc<str>),    ExtensionFailedToLoad(Arc<str>),} impl EventEmitter<Event> for ExtensionStore {} struct GlobalExtensionStore(Entity<ExtensionStore>); impl Global for GlobalExtensionStore {} #[derive(Debug, Deserialize, Serialize, Default, PartialEq, Eq)]pub struct ExtensionIndex {    pub extensions: BTreeMap<Arc<str>, ExtensionIndexEntry>,    pub themes: BTreeMap<Arc<str>, ExtensionIndexThemeEntry>,    #[serde(default)]    pub icon_themes: BTreeMap<Arc<str>, ExtensionIndexIconThemeEntry>,    pub languages: BTreeMap<LanguageName, ExtensionIndexLanguageEntry>,} #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]pub struct ExtensionIndexEntry {    pub manifest: Arc<ExtensionManifest>,    pub dev: bool,} #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]pub struct ExtensionIndexThemeEntry {    pub extension: Arc<str>,    pub path: PathBuf,} #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]pub struct ExtensionIndexIconThemeEntry {    pub extension: Arc<str>,    pub path: PathBuf,} #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]pub struct ExtensionIndexLanguageEntry {    pub extension: Arc<str>,    pub path: PathBuf,    pub matcher: LanguageMatcher,    pub hidden: bool,    pub grammar: Option<Arc<str>>,} actions!(zed, [ReloadExtensions]); pub fn init(    extension_host_proxy: Arc<ExtensionHostProxy>,    fs: Arc<dyn Fs>,    client: Arc<Client>,    node_runtime: NodeRuntime,    cx: &mut App,) {    ExtensionSettings::register(cx);     let store = cx.new(move |cx| {        ExtensionStore::new(            paths::extensions_dir().clone(),            None,            extension_host_proxy,            fs,            client.http_client().clone(),            client.http_client().clone(),            Some(client.telemetry().clone()),            node_runtime,            cx,        )    });     cx.on_action(|_: &ReloadExtensions, cx| {        let store = cx.global::<GlobalExtensionStore>().0.clone();        store.update(cx, |store, cx| drop(store.reload(None, cx)));    });     cx.set_global(GlobalExtensionStore(store));} impl ExtensionStore {    pub fn try_global(cx: &App) -> Option<Entity<Self>> {        cx.try_global::<GlobalExtensionStore>()            .map(|store| store.0.clone())    }     pub fn global(cx: &App) -> Entity<Self> {        cx.global::<GlobalExtensionStore>().0.clone()    }     #[allow(clippy::too_many_arguments)]    pub fn new(        extensions_dir: PathBuf,        build_dir: Option<PathBuf>,        extension_host_proxy: Arc<ExtensionHostProxy>,        fs: Arc<dyn Fs>,        http_client: Arc<HttpClientWithUrl>,        builder_client: Arc<dyn HttpClient>,        telemetry: Option<Arc<Telemetry>>,        node_runtime: NodeRuntime,        cx: &mut Context<Self>,    ) -> Self {        let work_dir = extensions_dir.join("work");        let build_dir = build_dir.unwrap_or_else(|| extensions_dir.join("build"));        let installed_dir = extensions_dir.join("installed");        let index_path = extensions_dir.join("index.json");         let (reload_tx, mut reload_rx) = unbounded();        let (connection_registered_tx, mut connection_registered_rx) = unbounded();        let mut this = Self {            proxy: extension_host_proxy.clone(),            extension_index: Default::default(),            installed_dir,            index_path,            builder: Arc::new(ExtensionBuilder::new(builder_client, build_dir)),            outstanding_operations: Default::default(),            modified_extensions: Default::default(),            reload_complete_senders: Vec::new(),            wasm_host: WasmHost::new(                fs.clone(),                http_client.clone(),                node_runtime,                extension_host_proxy,                work_dir,                cx,            ),            wasm_extensions: Vec::new(),            fs,            http_client,            telemetry,            reload_tx,            tasks: Vec::new(),             ssh_clients: HashMap::default(),            ssh_registered_tx: connection_registered_tx,        };         // The extensions store maintains an index file, which contains a complete        // list of the installed extensions and the resources that they provide.        // This index is loaded synchronously on startup.        let (index_content, index_metadata, extensions_metadata) =            cx.background_executor().block(async {                futures::join!(                    this.fs.load(&this.index_path),                    this.fs.metadata(&this.index_path),                    this.fs.metadata(&this.installed_dir),                )            });         // Normally, there is no need to rebuild the index. But if the index file        // is invalid or is out-of-date according to the filesystem mtimes, then        // it must be asynchronously rebuilt.        let mut extension_index = ExtensionIndex::default();        let mut extension_index_needs_rebuild = true;        if let Ok(index_content) = index_content {            if let Some(index) = serde_json::from_str(&index_content).log_err() {                extension_index = index;                if let (Ok(Some(index_metadata)), Ok(Some(extensions_metadata))) =                    (index_metadata, extensions_metadata)                {                    if index_metadata                        .mtime                        .bad_is_greater_than(extensions_metadata.mtime)                    {                        extension_index_needs_rebuild = false;                    }                }            }        }         // Immediately load all of the extensions in the initial manifest. If the        // index needs to be rebuild, then enqueue        let load_initial_extensions = this.extensions_updated(extension_index, cx);        let mut reload_future = None;        if extension_index_needs_rebuild {            reload_future = Some(this.reload(None, cx));        }         cx.spawn(|this, mut cx| async move {            if let Some(future) = reload_future {                future.await;            }            this.update(&mut cx, |this, cx| this.auto_install_extensions(cx))                .ok();            this.update(&mut cx, |this, cx| this.check_for_updates(cx))                .ok();        })        .detach();         // Perform all extension loading in a single task to ensure that we        // never attempt to simultaneously load/unload extensions from multiple        // parallel tasks.        this.tasks.push(cx.spawn(|this, mut cx| {            async move {                load_initial_extensions.await;                 let mut index_changed = false;                let mut debounce_timer = cx.background_spawn(futures::future::pending()).fuse();                loop {                    select_biased! {                        _ = debounce_timer => {                            if index_changed {                                let index = this                                    .update(&mut cx, |this, cx| this.rebuild_extension_index(cx))?                                    .await;                                this.update(&mut cx, |this, cx| this.extensions_updated(index, cx))?                                    .await;                                index_changed = false;                            }                             Self::update_ssh_clients(&this, &mut cx).await?;                        }                        _ = connection_registered_rx.next() => {                            debounce_timer = cx                                .background_executor()                                .timer(RELOAD_DEBOUNCE_DURATION)                                .fuse();                        }                        extension_id = reload_rx.next() => {                            let Some(extension_id) = extension_id else { break; };                            this.update(&mut cx, |this, _| {                                this.modified_extensions.extend(extension_id);                            })?;                            index_changed = true;                            debounce_timer = cx                                .background_executor()                                .timer(RELOAD_DEBOUNCE_DURATION)                                .fuse();                        }                    }                }                 anyhow::Ok(())            }            .map(drop)        }));         // Watch the installed extensions directory for changes. Whenever changes are        // detected, rebuild the extension index, and load/unload any extensions that        // have been added, removed, or modified.        this.tasks.push(cx.background_spawn({            let fs = this.fs.clone();            let reload_tx = this.reload_tx.clone();            let installed_dir = this.installed_dir.clone();            async move {                let (mut paths, _) = fs.watch(&installed_dir, FS_WATCH_LATENCY).await;                while let Some(events) = paths.next().await {                    for event in events {                        let Ok(event_path) = event.path.strip_prefix(&installed_dir) else {                            continue;                        };                         if let Some(path::Component::Normal(extension_dir_name)) =                            event_path.components().next()                        {                            if let Some(extension_id) = extension_dir_name.to_str() {                                reload_tx.unbounded_send(Some(extension_id.into())).ok();                            }                        }                    }                }            }        }));         this    }     pub fn reload(        &mut self,        modified_extension: Option<Arc<str>>,        cx: &mut Context<Self>,    ) -> impl Future<Output = ()> {        let (tx, rx) = oneshot::channel();        self.reload_complete_senders.push(tx);        self.reload_tx            .unbounded_send(modified_extension)            .expect("reload task exited");        cx.emit(Event::StartedReloading);         async move {            rx.await.ok();        }    }     fn extensions_dir(&self) -> PathBuf {        self.installed_dir.clone()    }     pub fn outstanding_operations(&self) -> &BTreeMap<Arc<str>, ExtensionOperation> {        &self.outstanding_operations    }     pub fn installed_extensions(&self) -> &BTreeMap<Arc<str>, ExtensionIndexEntry> {        &self.extension_index.extensions    }     pub fn dev_extensions(&self) -> impl Iterator<Item = &Arc<ExtensionManifest>> {        self.extension_index            .extensions            .values()            .filter_map(|extension| extension.dev.then_some(&extension.manifest))    }     /// Returns the names of themes provided by extensions.    pub fn extension_themes<'a>(        &'a self,        extension_id: &'a str,    ) -> impl Iterator<Item = &'a Arc<str>> {        self.extension_index            .themes            .iter()            .filter_map(|(name, theme)| theme.extension.as_ref().eq(extension_id).then_some(name))    }     /// Returns the path to the theme file within an extension, if there is an    /// extension that provides the theme.    pub fn path_to_extension_theme(&self, theme_name: &str) -> Option<PathBuf> {        let entry = self.extension_index.themes.get(theme_name)?;         Some(            self.extensions_dir()                .join(entry.extension.as_ref())                .join(&entry.path),        )    }     /// Returns the names of icon themes provided by extensions.    pub fn extension_icon_themes<'a>(        &'a self,        extension_id: &'a str,    ) -> impl Iterator<Item = &'a Arc<str>> {        self.extension_index            .icon_themes            .iter()            .filter_map(|(name, icon_theme)| {                icon_theme                    .extension                    .as_ref()                    .eq(extension_id)                    .then_some(name)            })    }     /// Returns the path to the icon theme file within an extension, if there is    /// an extension that provides the icon theme.    pub fn path_to_extension_icon_theme(        &self,        icon_theme_name: &str,    ) -> Option<(PathBuf, PathBuf)> {        let entry = self.extension_index.icon_themes.get(icon_theme_name)?;         let icon_theme_path = self            .extensions_dir()            .join(entry.extension.as_ref())            .join(&entry.path);        let icons_root_path = self.extensions_dir().join(entry.extension.as_ref());         Some((icon_theme_path, icons_root_path))    }     pub fn fetch_extensions(        &self,        search: Option<&str>,        provides_filter: Option<&BTreeSet<ExtensionProvides>>,        cx: &mut Context<Self>,    ) -> Task<Result<Vec<ExtensionMetadata>>> {        let version = CURRENT_SCHEMA_VERSION.to_string();        let mut query = vec![("max_schema_version", version.as_str())];        if let Some(search) = search {            query.push(("filter", search));

Lines 1–500 of 1693.