From f4c339ed0a3ce697a04248b28648cb6c04cd8086 Mon Sep 17 00:00:00 2001 From: Kayshen-X Date: Sun, 14 Jun 2026 00:56:30 +0800 Subject: [PATCH] refactor(editor): centralize agent draft readiness --- crates/op-editor-core/src/editor_ui_state.rs | 99 +++++++++++++++++++ .../src/widgets/agent_settings_acp.rs | 3 +- .../src/widgets/agent_settings_acp_draft.rs | 43 -------- .../src/widgets/agent_settings_builtin.rs | 10 +- .../widgets/agent_settings_builtin_draft.rs | 32 ------ crates/op-editor-ui/src/widgets/mod.rs | 2 - 6 files changed, 101 insertions(+), 88 deletions(-) delete mode 100644 crates/op-editor-ui/src/widgets/agent_settings_acp_draft.rs delete mode 100644 crates/op-editor-ui/src/widgets/agent_settings_builtin_draft.rs diff --git a/crates/op-editor-core/src/editor_ui_state.rs b/crates/op-editor-core/src/editor_ui_state.rs index b725f2bad..71062ede1 100644 --- a/crates/op-editor-core/src/editor_ui_state.rs +++ b/crates/op-editor-core/src/editor_ui_state.rs @@ -1346,6 +1346,77 @@ impl EditorUiState { self.variables_preset_menu_open && self.variables_preset_name_focus } + pub fn builtin_agent_draft_ready(&self) -> bool { + use crate::agent_settings::BuiltinAgentField; + + let Some(name) = self.builtin_agent_draft_field_text(BuiltinAgentField::DisplayName) else { + return false; + }; + let Some(api_key) = self.builtin_agent_draft_field_text(BuiltinAgentField::ApiKey) else { + return false; + }; + let Some(model) = self.builtin_agent_draft_field_text(BuiltinAgentField::Model) else { + return false; + }; + !name.trim().is_empty() && !api_key.trim().is_empty() && !model.trim().is_empty() + } + + pub fn acp_agent_draft_ready(&self) -> bool { + use crate::agent_settings::{AcpAgentField, AcpConnectionType}; + + let Some(draft) = self.agent_settings.acp_agent_draft.as_ref() else { + return false; + }; + let Some(name) = self.acp_agent_draft_field_text(AcpAgentField::DisplayName) else { + return false; + }; + let endpoint_field = match draft.connection_type { + AcpConnectionType::Local => AcpAgentField::Command, + AcpConnectionType::Remote => AcpAgentField::Url, + }; + let Some(endpoint) = self.acp_agent_draft_field_text(endpoint_field) else { + return false; + }; + !name.trim().is_empty() && !endpoint.trim().is_empty() + } + + pub fn builtin_agent_draft_field_text( + &self, + field: crate::agent_settings::BuiltinAgentField, + ) -> Option<&str> { + use crate::agent_settings::{BuiltinAgentField, SettingsFocus}; + + let draft = self.agent_settings.builtin_agent_draft.as_ref()?; + if self.agent_settings.focus == Some(SettingsFocus::BuiltinAgentDraft(field)) { + return Some(self.settings_input.text()); + } + Some(match field { + BuiltinAgentField::DisplayName => draft.display_name.as_str(), + BuiltinAgentField::ApiKey => draft.api_key.as_str(), + BuiltinAgentField::Model => draft.model.as_str(), + BuiltinAgentField::BaseUrl => draft.base_url.as_str(), + }) + } + + pub fn acp_agent_draft_field_text( + &self, + field: crate::agent_settings::AcpAgentField, + ) -> Option> { + use crate::agent_settings::{AcpAgentField, SettingsFocus}; + + let draft = self.agent_settings.acp_agent_draft.as_ref()?; + if self.agent_settings.focus == Some(SettingsFocus::AcpAgentDraft(field)) { + return Some(std::borrow::Cow::Borrowed(self.settings_input.text())); + } + Some(match field { + AcpAgentField::DisplayName => std::borrow::Cow::Borrowed(draft.display_name.as_str()), + AcpAgentField::Command => std::borrow::Cow::Borrowed(draft.command.as_str()), + AcpAgentField::Args => std::borrow::Cow::Owned(draft.args_text()), + AcpAgentField::Env => std::borrow::Cow::Owned(draft.env_text()), + AcpAgentField::Url => std::borrow::Cow::Borrowed(draft.url.as_deref().unwrap_or("")), + }) + } + /// Clear transient UI state that references specific document nodes/pages /// or the (now-cleared) selection, so a wholesale document replacement /// ([`crate::EditorState::replace_document`]) can't leave hover highlights, @@ -1427,6 +1498,34 @@ mod tests { assert_eq!(ExportFormat::Jpeg.extension(), "jpg"); } + #[test] + fn builtin_agent_draft_ready_reads_focused_settings_input() { + use crate::agent_settings::{BuiltinAgentField, SettingsFocus}; + + let mut ui = EditorUiState::new(); + ui.agent_settings.begin_builtin_agent_draft(); + assert!(!ui.builtin_agent_draft_ready()); + + ui.agent_settings.focus = Some(SettingsFocus::BuiltinAgentDraft(BuiltinAgentField::ApiKey)); + ui.settings_input.set_text("sk-test"); + + assert!(ui.builtin_agent_draft_ready()); + } + + #[test] + fn acp_agent_draft_ready_reads_focused_settings_input() { + use crate::agent_settings::{AcpAgentField, SettingsFocus}; + + let mut ui = EditorUiState::new(); + ui.agent_settings.begin_acp_agent_draft(); + assert!(!ui.acp_agent_draft_ready()); + + ui.agent_settings.focus = Some(SettingsFocus::AcpAgentDraft(AcpAgentField::Command)); + ui.settings_input.set_text("op-agent"); + + assert!(ui.acp_agent_draft_ready()); + } + #[test] fn dirty_ready_repo_keeps_header_popovers_allowed() { // TS parity (the ready view now shows for dirty trees too): a diff --git a/crates/op-editor-ui/src/widgets/agent_settings_acp.rs b/crates/op-editor-ui/src/widgets/agent_settings_acp.rs index d5d3bc5a4..8caa5ba8d 100644 --- a/crates/op-editor-ui/src/widgets/agent_settings_acp.rs +++ b/crates/op-editor-ui/src/widgets/agent_settings_acp.rs @@ -1,7 +1,6 @@ //! ACP Agent section for the Agent settings panel. use crate::theme::Theme; -use crate::widgets::agent_settings_acp_draft; use crate::widgets::agent_settings_caret::{paint_settings_input_view, settings_input_text}; use crate::widgets::agent_settings_form_actions::{ cancel_button_rect, paint_form_actions, save_button_rect, @@ -195,7 +194,7 @@ pub fn paint_acp_section( ui, card, EXPANDED_CARD_H, - agent_settings_acp_draft::ready(settings, ui), + ui.acp_agent_draft_ready(), ); y += card.size.y + CARD_GAP; } diff --git a/crates/op-editor-ui/src/widgets/agent_settings_acp_draft.rs b/crates/op-editor-ui/src/widgets/agent_settings_acp_draft.rs deleted file mode 100644 index 9052acb46..000000000 --- a/crates/op-editor-ui/src/widgets/agent_settings_acp_draft.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! ACP agent draft helpers. - -use op_editor_core::agent_settings::{ - AcpAgentField, AcpConnectionType, AgentSettings, SettingsFocus, -}; -use op_editor_core::editor_ui_state::EditorUiState; - -pub fn ready(settings: &AgentSettings, ui: &EditorUiState) -> bool { - let Some(draft) = settings.acp_agent_draft.as_ref() else { - return false; - }; - let name = field_value( - settings, - ui, - AcpAgentField::DisplayName, - &draft.display_name, - ); - let endpoint = match draft.connection_type { - AcpConnectionType::Local => { - field_value(settings, ui, AcpAgentField::Command, &draft.command) - } - AcpConnectionType::Remote => field_value( - settings, - ui, - AcpAgentField::Url, - draft.url.as_deref().unwrap_or(""), - ), - }; - !name.trim().is_empty() && !endpoint.trim().is_empty() -} - -fn field_value<'a>( - settings: &AgentSettings, - ui: &'a EditorUiState, - field: AcpAgentField, - fallback: &'a str, -) -> &'a str { - if settings.focus == Some(SettingsFocus::AcpAgentDraft(field)) { - ui.settings_input.text() - } else { - fallback - } -} diff --git a/crates/op-editor-ui/src/widgets/agent_settings_builtin.rs b/crates/op-editor-ui/src/widgets/agent_settings_builtin.rs index 84919edcb..5deb45fec 100644 --- a/crates/op-editor-ui/src/widgets/agent_settings_builtin.rs +++ b/crates/op-editor-ui/src/widgets/agent_settings_builtin.rs @@ -1,7 +1,6 @@ //! Built-in provider section for the Agent settings panel. use crate::theme::Theme; -use crate::widgets::agent_settings_builtin_draft; use crate::widgets::agent_settings_builtin_layout::{ add_provider_rect, card_height, card_rect, compact_edit_rect, compact_remove_rect, compact_switch_rect, draft_card_height, expanded_card_height, field_input_rect, is_editing, @@ -309,14 +308,7 @@ pub fn paint_builtin_section( ); paint_builtin_agent_form(cx, theme, settings, ui, draft, None, card, now_ms); let form_h = expanded_card_height(settings, None); - paint_form_actions( - cx, - theme, - ui, - card, - form_h, - agent_settings_builtin_draft::ready(settings, ui), - ); + paint_form_actions(cx, theme, ui, card, form_h, ui.builtin_agent_draft_ready()); y += card.size.y + CARD_GAP; } y diff --git a/crates/op-editor-ui/src/widgets/agent_settings_builtin_draft.rs b/crates/op-editor-ui/src/widgets/agent_settings_builtin_draft.rs deleted file mode 100644 index ce48e2fd5..000000000 --- a/crates/op-editor-ui/src/widgets/agent_settings_builtin_draft.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Built-in provider draft helpers. - -use op_editor_core::agent_settings::{AgentSettings, BuiltinAgentField, SettingsFocus}; -use op_editor_core::editor_ui_state::EditorUiState; - -pub fn ready(settings: &AgentSettings, ui: &EditorUiState) -> bool { - let Some(draft) = settings.builtin_agent_draft.as_ref() else { - return false; - }; - let api_key = field_value(settings, ui, BuiltinAgentField::ApiKey, &draft.api_key); - let model = field_value(settings, ui, BuiltinAgentField::Model, &draft.model); - let name = field_value( - settings, - ui, - BuiltinAgentField::DisplayName, - &draft.display_name, - ); - !api_key.trim().is_empty() && !model.trim().is_empty() && !name.trim().is_empty() -} - -fn field_value<'a>( - settings: &AgentSettings, - ui: &'a EditorUiState, - field: BuiltinAgentField, - fallback: &'a str, -) -> &'a str { - if settings.focus == Some(SettingsFocus::BuiltinAgentDraft(field)) { - ui.settings_input.text() - } else { - fallback - } -} diff --git a/crates/op-editor-ui/src/widgets/mod.rs b/crates/op-editor-ui/src/widgets/mod.rs index 5800a20df..c031ad846 100644 --- a/crates/op-editor-ui/src/widgets/mod.rs +++ b/crates/op-editor-ui/src/widgets/mod.rs @@ -117,9 +117,7 @@ pub mod brand_icons; // Step 4 — extra editor-chrome widgets (TS app parity). pub mod agent_settings_acp; -mod agent_settings_acp_draft; pub mod agent_settings_builtin; -mod agent_settings_builtin_draft; mod agent_settings_builtin_layout; mod agent_settings_builtin_parts; #[cfg(test)]