From d769b45f548f0230f9f7f9ddc6ed4880b98ed332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 8 May 2026 21:20:29 -0400 Subject: [PATCH] Wire plugin file install command --- crates/ely_app/src/shell/command_actions.rs | 49 +++++++++++++++++++ crates/ely_app/src/shell/mod.rs | 31 ++++++------ crates/ely_app/src/shell/plugins.rs | 19 +++++++ crates/ely_browser_core/src/navigation.rs | 4 ++ crates/ely_browser_core/src/state/commands.rs | 15 ++++-- crates/ely_browser_core/tests/plugins.rs | 15 ++++++ 6 files changed, 113 insertions(+), 20 deletions(-) create mode 100644 crates/ely_app/src/shell/command_actions.rs diff --git a/crates/ely_app/src/shell/command_actions.rs b/crates/ely_app/src/shell/command_actions.rs new file mode 100644 index 0000000..50028b1 --- /dev/null +++ b/crates/ely_app/src/shell/command_actions.rs @@ -0,0 +1,49 @@ +use ely_domain::CommandIntent; +use gpui::{Context, Window}; + +use super::ElyShell; + +impl ElyShell { + pub(super) fn handle_shell_command_intent( + &mut self, + intent: Option<&CommandIntent>, + window: &mut Window, + cx: &mut Context, + ) { + let Some(CommandIntent::Command(command)) = intent else { + return; + }; + + if install_plugin_from_file_command(command) { + self.choose_plugin_package(window, cx); + } + } +} + +fn install_plugin_from_file_command(command: &str) -> bool { + matches!( + command.trim().to_ascii_lowercase().as_str(), + "install-plugin-from-file" + | "install plugin from file" + | "install-plugin" + | "install plugin" + ) +} + +#[cfg(test)] +mod tests { + use super::install_plugin_from_file_command; + + #[test] + fn install_plugin_from_file_command_matches_prd_aliases() { + assert!(install_plugin_from_file_command("install-plugin-from-file")); + assert!(install_plugin_from_file_command("Install Plugin from File")); + assert!(install_plugin_from_file_command("install plugin")); + } + + #[test] + fn install_plugin_from_file_command_rejects_other_plugin_commands() { + assert!(!install_plugin_from_file_command("plugins")); + assert!(!install_plugin_from_file_command("open plugins")); + } +} diff --git a/crates/ely_app/src/shell/mod.rs b/crates/ely_app/src/shell/mod.rs index ac8ab7c..34ef4aa 100644 --- a/crates/ely_app/src/shell/mod.rs +++ b/crates/ely_app/src/shell/mod.rs @@ -1,5 +1,6 @@ mod archive_labels; mod bookmarks; +mod command_actions; mod downloads; mod focus; mod history; @@ -25,9 +26,9 @@ mod web_surface_view; use ely_browser_core::{BrowserCore, InitialBrowserConfig}; use ely_domain::{ - ArchivePolicy, CommandIntent, DownloadPolicy, FavoriteLimit, HistoryRecordingPolicy, - NewTabDestination, ProfileId, ProfileSyncPolicy, SearchEngine, SpaceId, SyncObjectKind, - SyncObjectPolicy, TabId, UrlText, + ArchivePolicy, DownloadPolicy, FavoriteLimit, HistoryRecordingPolicy, NewTabDestination, + ProfileId, ProfileSyncPolicy, SearchEngine, SpaceId, SyncObjectKind, SyncObjectPolicy, TabId, + UrlText, }; use gpui::{AppContext, Context, Entity, FocusHandle, Subscription, Window}; use gpui_component::input::{InputEvent, InputState, SelectAll}; @@ -53,7 +54,6 @@ pub struct ElyShell { state: ShellState, focus_handle: FocusHandle, command_input: Entity, - last_intent: Option, download_action_error: Option, download_clear_confirmation: bool, download_security_confirmation: Option, @@ -84,21 +84,21 @@ impl ElyShell { let mut sync_address = false; let submitted = matches!(event, InputEvent::PressEnter { .. }); - let ShellState::Ready(core) = &mut shell.state else { - return; - }; + { + let ShellState::Ready(core) = &mut shell.state else { + return; + }; - let value = input.read(cx).value().to_string(); - core.set_command_query(value); + let value = input.read(cx).value().to_string(); + core.set_command_query(value); - if submitted { - submitted_intent = core.submit_command().ok().flatten(); - sync_address = core.command_query().is_empty(); + if submitted { + submitted_intent = core.submit_command().ok().flatten(); + sync_address = core.command_query().is_empty(); + } } - if submitted { - shell.last_intent = submitted_intent; - } + shell.handle_shell_command_intent(submitted_intent.as_ref(), window, cx); if sync_address { shell.sync_address_input(window, cx); @@ -122,7 +122,6 @@ impl ElyShell { state, focus_handle: cx.focus_handle(), command_input, - last_intent: None, download_action_error: None, download_clear_confirmation: false, download_security_confirmation: None, diff --git a/crates/ely_app/src/shell/plugins.rs b/crates/ely_app/src/shell/plugins.rs index 8008204..e41b606 100644 --- a/crates/ely_app/src/shell/plugins.rs +++ b/crates/ely_app/src/shell/plugins.rs @@ -10,6 +10,8 @@ use crate::services::{ use super::{ElyShell, ShellState}; +const PLUGIN_SETTINGS_URL: &str = "ely://settings/plugins"; + #[derive(Clone, Debug)] pub(super) struct PendingPluginInstall { package: VerifiedPluginPackage, @@ -56,6 +58,8 @@ impl PendingPluginUninstall { 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, @@ -96,6 +100,21 @@ impl ElyShell { .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); + } + + fn active_tab_matches_url(&self, url: &str) -> bool { + match &self.state { + ShellState::Ready(core) => core.active_tab().is_ok_and(|tab| tab.url().as_str() == url), + ShellState::StartupError(_) => false, + } + } + pub(super) fn confirm_plugin_install(&mut self, cx: &mut Context) { let Some(pending) = self.pending_plugin_install.take() else { cx.notify(); diff --git a/crates/ely_browser_core/src/navigation.rs b/crates/ely_browser_core/src/navigation.rs index 579a769..93dbe81 100644 --- a/crates/ely_browser_core/src/navigation.rs +++ b/crates/ely_browser_core/src/navigation.rs @@ -202,6 +202,10 @@ pub(crate) fn plugins_url() -> Result { internal_page_url("ely://plugins") } +pub(crate) fn plugin_settings_url() -> Result { + internal_page_url("ely://settings/plugins") +} + pub(crate) fn plugin_detail_url(plugin_id: &PluginId) -> Result { let route = format!("ely://plugin/{}", plugin_id.as_str()); internal_page_url(&route) diff --git a/crates/ely_browser_core/src/state/commands.rs b/crates/ely_browser_core/src/state/commands.rs index e6ce5d8..df0d386 100644 --- a/crates/ely_browser_core/src/state/commands.rs +++ b/crates/ely_browser_core/src/state/commands.rs @@ -9,10 +9,10 @@ use crate::{ navigation::{ about_url, archive_idle_days, archive_url, bookmarks_url, downloads_url, history_url, move_tab_space_name, new_private_profile_name, new_profile_name, new_space_name, note_body, - notes_url, plugin_detail_url, plugins_url, reading_list_url, reading_progress_percent, - rename_tab_group_name, search_url, settings_page_url, settings_url, shortcut_settings_url, - space_icon, split_group_name, switch_profile_name, sync_status_url, tab_group_color_hex, - tab_group_name, tab_note_body, task_manager_url, + notes_url, plugin_detail_url, plugin_settings_url, plugins_url, reading_list_url, + reading_progress_percent, rename_tab_group_name, search_url, settings_page_url, + settings_url, shortcut_settings_url, space_icon, split_group_name, switch_profile_name, + sync_status_url, tab_group_color_hex, tab_group_name, tab_note_body, task_manager_url, }, }; @@ -271,6 +271,13 @@ impl BrowserCore { self.open_tab(plugins_url()?); Ok(true) } + "install-plugin-from-file" + | "install plugin from file" + | "install-plugin" + | "install plugin" => { + self.open_tab(plugin_settings_url()?); + Ok(true) + } "site-settings" | "open-site-settings" | "open site settings" => { let Some(url) = self.active_tab_site_settings_url()? else { return Ok(false); diff --git a/crates/ely_browser_core/tests/plugins.rs b/crates/ely_browser_core/tests/plugins.rs index d59cf41..2bfba83 100644 --- a/crates/ely_browser_core/tests/plugins.rs +++ b/crates/ely_browser_core/tests/plugins.rs @@ -3,6 +3,21 @@ use std::{error::Error, io}; use ely_browser_core::{BrowserCore, CoreError, InitialBrowserConfig, PluginAuditAction}; use ely_domain::{CommandIntent, CommandScope, PluginId, PluginManifest}; +#[test] +fn install_plugin_from_file_command_opens_plugin_settings_page() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + + core.set_command_query(">install-plugin-from-file"); + let intent = core.submit_command()?; + let active_tab = core.active_tab()?; + + assert_eq!(intent, Some(CommandIntent::Command("install-plugin-from-file".to_string()))); + assert_eq!(active_tab.title(), "Plugin Settings"); + assert_eq!(active_tab.url().as_str(), "ely://settings/plugins"); + assert_eq!(core.snapshot()?.command_query, ""); + Ok(()) +} + #[test] fn installs_standard_plugin_and_records_audit_event() -> Result<(), Box> { let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;