use std::path::PathBuf; use ely_domain::{PluginId, PluginManifest, PluginPermission}; use gpui::{Context, PathPromptOptions, Window}; use crate::services::{ plugin_package_store::PluginPackageStore, plugin_packages::{PluginPackageError, PluginPackageReader, VerifiedPluginPackage}, }; use super::{ElyShell, ShellState}; const PLUGIN_SETTINGS_URL: &str = "ely://settings/plugins"; #[derive(Clone, Debug)] pub(super) struct PendingPluginInstall { package: VerifiedPluginPackage, high_risk_permissions: Vec, } #[derive(Clone, Debug)] pub(super) struct PendingPluginUninstall { plugin_id: PluginId, plugin_name: String, } impl PendingPluginInstall { fn new(package: VerifiedPluginPackage, high_risk_permissions: Vec) -> Self { Self { package, high_risk_permissions } } pub(super) fn manifest(&self) -> &PluginManifest { self.package.manifest() } pub(super) fn high_risk_permissions(&self) -> &[PluginPermission] { &self.high_risk_permissions } pub(super) fn requires_high_risk_confirmation(&self) -> bool { !self.high_risk_permissions.is_empty() } } impl PendingPluginUninstall { fn new(plugin_id: PluginId, plugin_name: String) -> Self { Self { plugin_id, plugin_name } } pub(super) fn plugin_id(&self) -> &PluginId { &self.plugin_id } pub(super) fn plugin_name(&self) -> &str { &self.plugin_name } } impl ElyShell { pub(super) fn choose_plugin_package(&mut self, window: &mut Window, cx: &mut Context) { self.ensure_plugin_install_surface(window, cx); let prompt = cx.prompt_for_paths(PathPromptOptions { files: false, directories: true, multiple: false, prompt: Some("Select .rplug package".into()), }); cx.spawn_in(window, async move |shell, window| { let selected_path = match prompt.await { Ok(Ok(Some(paths))) => paths.into_iter().next(), Ok(Ok(None)) => None, Ok(Err(error)) => { _ = shell.update_in(window, |shell, _, cx| { shell.plugin_install_error = Some(error.to_string()); cx.notify(); }); return; } Err(error) => { _ = shell.update_in(window, |shell, _, cx| { shell.plugin_install_error = Some(error.to_string()); cx.notify(); }); return; } }; let Some(path) = selected_path else { return; }; let result = window.background_executor().spawn(async move { load_plugin_package(path) }).await; _ = shell.update_in(window, |shell, _, cx| { shell.handle_plugin_package_result(result, cx); }); }) .detach(); } fn ensure_plugin_install_surface(&mut self, window: &mut Window, cx: &mut Context) { if self.active_tab_matches_url(PLUGIN_SETTINGS_URL) { return; } self.open_internal_tab(PLUGIN_SETTINGS_URL, window, cx); } pub(super) fn confirm_plugin_install(&mut self, cx: &mut Context) { let Some(pending) = self.pending_plugin_install.take() else { cx.notify(); return; }; let high_risk_confirmed = pending.requires_high_risk_confirmation(); self.install_plugin_package(pending.package, true, high_risk_confirmed, cx); } pub(super) fn cancel_plugin_install(&mut self, cx: &mut Context) { self.pending_plugin_install = None; cx.notify(); } pub(super) fn request_plugin_uninstall( &mut self, plugin_id: PluginId, plugin_name: String, cx: &mut Context, ) { self.pending_plugin_uninstall = Some(PendingPluginUninstall::new(plugin_id, plugin_name)); self.plugin_install_error = None; cx.notify(); } pub(super) fn cancel_plugin_uninstall(&mut self, cx: &mut Context) { self.pending_plugin_uninstall = None; cx.notify(); } pub(super) fn confirm_plugin_uninstall(&mut self, cx: &mut Context) { let Some(pending) = self.pending_plugin_uninstall.take() else { cx.notify(); return; }; self.uninstall_plugin(pending.plugin_id, cx); } pub(super) fn set_plugin_enabled( &mut self, plugin_id: PluginId, enabled: bool, cx: &mut Context, ) { let result = match &mut self.state { ShellState::Ready(core) => { let action = if enabled { core.enable_plugin(&plugin_id) } else { core.disable_plugin(&plugin_id) }; action.map_err(|error| error.to_string()) } ShellState::StartupError(message) => Err(message.clone()), }; self.plugin_install_error = result.err(); cx.notify(); } pub(super) fn set_plugin_private_window_allowed( &mut self, plugin_id: PluginId, allowed: bool, cx: &mut Context, ) { let result = match &mut self.state { ShellState::Ready(core) => core .set_plugin_private_window_allowed(&plugin_id, allowed) .map_err(|error| error.to_string()), ShellState::StartupError(message) => Err(message.clone()), }; self.plugin_install_error = result.err(); cx.notify(); } fn uninstall_plugin(&mut self, plugin_id: PluginId, cx: &mut Context) { let result = match &mut self.state { ShellState::Ready(core) => PluginPackageStore::application() .and_then(|store| store.remove_plugin(&plugin_id)) .map_err(|error| error.to_string()) .and_then(|_| core.uninstall_plugin(&plugin_id).map_err(|error| error.to_string())), ShellState::StartupError(message) => Err(message.clone()), }; self.plugin_install_error = result.err(); cx.notify(); } fn handle_plugin_package_result( &mut self, result: Result, cx: &mut Context, ) { match result { Ok(package) => self.install_plugin_package(package, false, false, cx), Err(error) => { self.plugin_install_error = Some(error.to_string()); self.pending_plugin_install = None; cx.notify(); } } } fn install_plugin_package( &mut self, package: VerifiedPluginPackage, local_package_confirmed: bool, high_risk_confirmed: bool, cx: &mut Context, ) { let high_risk_permissions = package.manifest().high_risk_permissions().cloned().collect::>(); if !local_package_confirmed || (!high_risk_confirmed && !high_risk_permissions.is_empty()) { self.pending_plugin_install = Some(PendingPluginInstall::new(package, high_risk_permissions)); self.plugin_install_error = None; cx.notify(); return; } let result = match &mut self.state { ShellState::Ready(core) => PluginPackageStore::application() .and_then(|store| store.store(&package)) .map_err(|error| error.to_string()) .and_then(|stored_package| { core.install_plugin(stored_package.manifest().clone(), high_risk_confirmed) .map(|_| ()) .map_err(|error| error.to_string()) }), ShellState::StartupError(message) => Err(message.clone()), }; self.plugin_install_error = result.err(); if self.plugin_install_error.is_none() { self.pending_plugin_install = None; } cx.notify(); } } fn load_plugin_package(path: PathBuf) -> Result { PluginPackageReader::read_directory_package(&path) }