From 62951fe3b06a3066708b98025d7b86cc58de50b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 7 May 2026 19:26:02 -0400 Subject: [PATCH] Split browser shell rendering --- crates/ely_app/src/shell/mod.rs | 191 +++++++++++++++++ .../ely_app/src/{shell.rs => shell/render.rs} | 196 +----------------- 2 files changed, 197 insertions(+), 190 deletions(-) create mode 100644 crates/ely_app/src/shell/mod.rs rename crates/ely_app/src/{shell.rs => shell/render.rs} (56%) diff --git a/crates/ely_app/src/shell/mod.rs b/crates/ely_app/src/shell/mod.rs new file mode 100644 index 0000000..c5dfdb1 --- /dev/null +++ b/crates/ely_app/src/shell/mod.rs @@ -0,0 +1,191 @@ +mod render; + +use ely_browser_core::{BrowserCore, InitialBrowserConfig}; +use ely_domain::{CommandIntent, TabId, UrlText}; +use gpui::{App, AppContext, Context, Entity, FocusHandle, Focusable, Subscription, Window}; +use gpui_component::input::{InputEvent, InputState, SelectAll}; + +use crate::{CloseCurrentTab, FocusAddressBar, OpenNewTab, SelectNextTab, SelectPreviousTab}; + +enum ShellState { + Ready(BrowserCore), + StartupError(String), +} + +pub struct ElyShell { + state: ShellState, + focus_handle: FocusHandle, + command_input: Entity, + last_intent: Option, + _command_subscription: Subscription, +} + +impl ElyShell { + pub fn new(window: &mut Window, cx: &mut Context) -> Self { + let command_input = + cx.new(|cx| InputState::new(window, cx).placeholder("Search or enter address")); + + let command_subscription = cx.subscribe_in( + &command_input, + window, + |shell: &mut Self, input, event: &InputEvent, window, cx| { + let mut submitted_intent = None; + let mut sync_address = false; + let submitted = matches!(event, InputEvent::PressEnter { .. }); + + let ShellState::Ready(core) = &mut shell.state else { + return; + }; + + 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 { + shell.last_intent = submitted_intent; + } + + if sync_address { + shell.sync_address_input(window, cx); + } + + cx.notify(); + }, + ); + + let state = match InitialBrowserConfig::ely_defaults().and_then(|config| { + BrowserCore::new(config).map_err(|error| match error { + ely_browser_core::CoreError::Domain(source) => source, + _ => ely_domain::DomainError::InvalidCommand, + }) + }) { + Ok(core) => ShellState::Ready(core), + Err(error) => ShellState::StartupError(error.to_string()), + }; + + Self { + state, + focus_handle: cx.focus_handle(), + command_input, + last_intent: None, + _command_subscription: command_subscription, + } + } + + fn open_new_tab(&mut self, window: &mut Window, cx: &mut Context) { + if let ShellState::Ready(core) = &mut self.state + && let Ok(url) = UrlText::parse("ely://new-tab") + { + core.open_tab(url); + self.sync_address_input(window, cx); + self.focus_address_bar(window, cx); + cx.notify(); + } + } + + fn focus_address_bar(&mut self, window: &mut Window, cx: &mut Context) { + self.command_input.update(cx, |input, cx| { + input.focus(window, cx); + }); + window.dispatch_action(Box::new(SelectAll), cx); + } + + fn select_tab(&mut self, tab_id: &TabId, window: &mut Window, cx: &mut Context) { + if let ShellState::Ready(core) = &mut self.state + && core.select_tab(tab_id).is_ok() + { + self.sync_address_input(window, cx); + cx.notify(); + } + } + + fn select_next_tab(&mut self, window: &mut Window, cx: &mut Context) { + if let ShellState::Ready(core) = &mut self.state + && core.select_next_tab().is_ok() + { + self.sync_address_input(window, cx); + cx.notify(); + } + } + + fn select_previous_tab(&mut self, window: &mut Window, cx: &mut Context) { + if let ShellState::Ready(core) = &mut self.state + && core.select_previous_tab().is_ok() + { + self.sync_address_input(window, cx); + cx.notify(); + } + } + + fn close_active_tab(&mut self, window: &mut Window, cx: &mut Context) { + if let ShellState::Ready(core) = &mut self.state + && core.close_active_tab().is_ok() + { + self.sync_address_input(window, cx); + cx.notify(); + } + } + + fn on_close_current_tab( + &mut self, + _: &CloseCurrentTab, + window: &mut Window, + cx: &mut Context, + ) { + self.close_active_tab(window, cx); + } + + fn on_focus_address_bar( + &mut self, + _: &FocusAddressBar, + window: &mut Window, + cx: &mut Context, + ) { + self.focus_address_bar(window, cx); + } + + fn on_open_new_tab(&mut self, _: &OpenNewTab, window: &mut Window, cx: &mut Context) { + self.open_new_tab(window, cx); + } + + fn on_select_next_tab( + &mut self, + _: &SelectNextTab, + window: &mut Window, + cx: &mut Context, + ) { + self.select_next_tab(window, cx); + } + + fn on_select_previous_tab( + &mut self, + _: &SelectPreviousTab, + window: &mut Window, + cx: &mut Context, + ) { + self.select_previous_tab(window, cx); + } + + fn sync_address_input(&mut self, window: &mut Window, cx: &mut Context) { + let ShellState::Ready(core) = &mut self.state else { + return; + }; + + if let Ok(active_tab) = core.active_tab() { + let value = active_tab.url().as_str().to_string(); + self.command_input.update(cx, |input, cx| { + input.set_value(value, window, cx); + }); + } + } +} + +impl Focusable for ElyShell { + fn focus_handle(&self, _: &App) -> FocusHandle { + self.focus_handle.clone() + } +} diff --git a/crates/ely_app/src/shell.rs b/crates/ely_app/src/shell/render.rs similarity index 56% rename from crates/ely_app/src/shell.rs rename to crates/ely_app/src/shell/render.rs index 9d5d154..4835708 100644 --- a/crates/ely_app/src/shell.rs +++ b/crates/ely_app/src/shell/render.rs @@ -1,201 +1,17 @@ -use ely_browser_core::{BrowserCore, BrowserSnapshot, InitialBrowserConfig}; +use ely_browser_core::BrowserSnapshot; use ely_design_system::{ELY_THEME, colors, spacing}; -use ely_domain::{BrowserTab, CommandIntent, TabId, UrlText}; +use ely_domain::BrowserTab; use gpui::{ - AnyElement, App, AppContext, Context, Entity, FocusHandle, Focusable, InteractiveElement, - IntoElement, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, - Subscription, Window, div, px, rgb, + AnyElement, Context, InteractiveElement, IntoElement, ParentElement, Render, SharedString, + StatefulInteractiveElement, Styled, Window, div, px, rgb, }; use gpui_component::{ Sizable, StyledExt, button::{Button, ButtonVariants}, - input::{Input, InputEvent, InputState, SelectAll}, + input::Input, }; -use crate::{CloseCurrentTab, FocusAddressBar, OpenNewTab, SelectNextTab, SelectPreviousTab}; - -enum ShellState { - Ready(BrowserCore), - StartupError(String), -} - -pub struct ElyShell { - state: ShellState, - focus_handle: FocusHandle, - command_input: Entity, - last_intent: Option, - _command_subscription: Subscription, -} - -impl ElyShell { - pub fn new(window: &mut Window, cx: &mut Context) -> Self { - let command_input = - cx.new(|cx| InputState::new(window, cx).placeholder("Search or enter address")); - - let command_subscription = cx.subscribe_in( - &command_input, - window, - |shell: &mut Self, input, event: &InputEvent, window, cx| { - let mut submitted_intent = None; - let mut sync_address = false; - let submitted = matches!(event, InputEvent::PressEnter { .. }); - - let ShellState::Ready(core) = &mut shell.state else { - return; - }; - - 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 { - shell.last_intent = submitted_intent; - } - - if sync_address { - shell.sync_address_input(window, cx); - } - - cx.notify(); - }, - ); - - let state = match InitialBrowserConfig::ely_defaults().and_then(|config| { - BrowserCore::new(config).map_err(|error| match error { - ely_browser_core::CoreError::Domain(source) => source, - _ => ely_domain::DomainError::InvalidCommand, - }) - }) { - Ok(core) => ShellState::Ready(core), - Err(error) => ShellState::StartupError(error.to_string()), - }; - - Self { - state, - focus_handle: cx.focus_handle(), - command_input, - last_intent: None, - _command_subscription: command_subscription, - } - } - - fn open_new_tab(&mut self, window: &mut Window, cx: &mut Context) { - if let ShellState::Ready(core) = &mut self.state - && let Ok(url) = UrlText::parse("ely://new-tab") - { - core.open_tab(url); - self.sync_address_input(window, cx); - self.focus_address_bar(window, cx); - cx.notify(); - } - } - - fn focus_address_bar(&mut self, window: &mut Window, cx: &mut Context) { - self.command_input.update(cx, |input, cx| { - input.focus(window, cx); - }); - window.dispatch_action(Box::new(SelectAll), cx); - } - - fn select_tab(&mut self, tab_id: &TabId, window: &mut Window, cx: &mut Context) { - if let ShellState::Ready(core) = &mut self.state - && core.select_tab(tab_id).is_ok() - { - self.sync_address_input(window, cx); - cx.notify(); - } - } - - fn select_next_tab(&mut self, window: &mut Window, cx: &mut Context) { - if let ShellState::Ready(core) = &mut self.state - && core.select_next_tab().is_ok() - { - self.sync_address_input(window, cx); - cx.notify(); - } - } - - fn select_previous_tab(&mut self, window: &mut Window, cx: &mut Context) { - if let ShellState::Ready(core) = &mut self.state - && core.select_previous_tab().is_ok() - { - self.sync_address_input(window, cx); - cx.notify(); - } - } - - fn close_active_tab(&mut self, window: &mut Window, cx: &mut Context) { - if let ShellState::Ready(core) = &mut self.state - && core.close_active_tab().is_ok() - { - self.sync_address_input(window, cx); - cx.notify(); - } - } - - fn on_close_current_tab( - &mut self, - _: &CloseCurrentTab, - window: &mut Window, - cx: &mut Context, - ) { - self.close_active_tab(window, cx); - } - - fn on_focus_address_bar( - &mut self, - _: &FocusAddressBar, - window: &mut Window, - cx: &mut Context, - ) { - self.focus_address_bar(window, cx); - } - - fn on_open_new_tab(&mut self, _: &OpenNewTab, window: &mut Window, cx: &mut Context) { - self.open_new_tab(window, cx); - } - - fn on_select_next_tab( - &mut self, - _: &SelectNextTab, - window: &mut Window, - cx: &mut Context, - ) { - self.select_next_tab(window, cx); - } - - fn on_select_previous_tab( - &mut self, - _: &SelectPreviousTab, - window: &mut Window, - cx: &mut Context, - ) { - self.select_previous_tab(window, cx); - } - - fn sync_address_input(&mut self, window: &mut Window, cx: &mut Context) { - let ShellState::Ready(core) = &mut self.state else { - return; - }; - - if let Ok(active_tab) = core.active_tab() { - let value = active_tab.url().as_str().to_string(); - self.command_input.update(cx, |input, cx| { - input.set_value(value, window, cx); - }); - } - } -} - -impl Focusable for ElyShell { - fn focus_handle(&self, _: &App) -> FocusHandle { - self.focus_handle.clone() - } -} +use super::{ElyShell, ShellState}; impl Render for ElyShell { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement {