From ad51f266d09bbd2153d7871b738545e66bc24d0a Mon Sep 17 00:00:00 2001 From: Fini Date: Sat, 30 May 2026 19:12:27 +0800 Subject: [PATCH] fix(settings): show mcp client config --- .../src/widgets/agent_settings_mcp.rs | 87 +++++++++++++++++-- .../src/widgets/agent_settings_panel.rs | 2 +- .../src/widgets/agent_settings_panel_tests.rs | 17 ++++ 3 files changed, 98 insertions(+), 8 deletions(-) diff --git a/crates/op-editor-ui/src/widgets/agent_settings_mcp.rs b/crates/op-editor-ui/src/widgets/agent_settings_mcp.rs index f5595dcfa..c20b02d9a 100644 --- a/crates/op-editor-ui/src/widgets/agent_settings_mcp.rs +++ b/crates/op-editor-ui/src/widgets/agent_settings_mcp.rs @@ -9,6 +9,8 @@ use op_editor_core::editor_ui_state::EditorUiState; const TITLE_H: f32 = 36.0; const SERVER_CARD_H: f32 = 52.0; +const CLIENT_CONFIG_H: f32 = 58.0; +const CLIENT_CONFIG_GAP: f32 = 8.0; const SECTION_GAP: f32 = 28.0; const SECTION_TITLE_H: f32 = 28.0; const SUBTITLE_H: f32 = 20.0; @@ -28,18 +30,28 @@ fn server_card_top(content: Rect) -> f32 { content.origin.y + TITLE_H } -fn grid_top(content: Rect) -> f32 { +fn client_config_block_h(settings: &AgentSettings) -> f32 { + if settings.mcp_server.running { + CLIENT_CONFIG_GAP + CLIENT_CONFIG_H + } else { + 0.0 + } +} + +fn grid_top(content: Rect, settings: &AgentSettings) -> f32 { server_card_top(content) + SERVER_CARD_H + + client_config_block_h(settings) + SECTION_GAP + SECTION_TITLE_H + SUBTITLE_H * 2.0 + ROW_GAP_BEFORE_GRID } -pub(super) fn content_height() -> f32 { +pub(super) fn content_height(settings: &AgentSettings) -> f32 { TITLE_H + SERVER_CARD_H + + client_config_block_h(settings) + SECTION_GAP + SECTION_TITLE_H + SUBTITLE_H * 2.0 @@ -86,14 +98,14 @@ fn port_field_rect(content: Rect) -> Rect { } } -fn cli_cell_rect(content: Rect, idx: usize) -> Rect { +fn cli_cell_rect(content: Rect, settings: &AgentSettings, idx: usize) -> Rect { let col = (idx % 2) as f32; let row = (idx / 2) as f32; let cell_w = (content.size.x - CELL_HGAP) / 2.0; Rect { origin: Point2D::new( content.origin.x + col * (cell_w + CELL_HGAP), - grid_top(content) + row * (CELL_H + CELL_VGAP), + grid_top(content, settings) + row * (CELL_H + CELL_VGAP), ), size: Point2D::new(cell_w, CELL_H), } @@ -107,7 +119,7 @@ pub fn hit_test(content: Rect, settings: &AgentSettings, scrolled: Point2D) -> M return McpHit::FocusPort; } for (i, cli) in McpCli::ALL.iter().enumerate() { - if rect_contains(cli_cell_rect(content, i), scrolled) { + if rect_contains(cli_cell_rect(content, settings, i), scrolled) { return McpHit::ToggleCli(*cli); } } @@ -133,8 +145,10 @@ pub(super) fn paint_mcp_tab( Point2D::new(content.origin.x, content.origin.y + 20.0), ); paint_server_card(cx, theme, settings, ui, content); + paint_client_config(cx, theme, settings, ui, content); - let mut y = server_card_top(content) + SERVER_CARD_H + SECTION_GAP; + let mut y = + server_card_top(content) + SERVER_CARD_H + client_config_block_h(settings) + SECTION_GAP; let section_title = TextLayout::single_run( t_settings(ui, "settings.mcp.terminalIntegrations"), "system-ui", @@ -166,7 +180,7 @@ pub(super) fn paint_mcp_tab( .draw_text(&s2, Point2D::new(content.origin.x, y + 13.0)); for (i, cli) in McpCli::ALL.iter().enumerate() { - let cell = cli_cell_rect(content, i); + let cell = cli_cell_rect(content, settings, i); paint_cli_cell(cx, theme, *cli, settings.mcp_cli_enabled[i], cell); } } @@ -302,6 +316,54 @@ fn paint_server_card( ); } +fn paint_client_config( + cx: &mut PaintCx<'_>, + theme: &Theme, + settings: &AgentSettings, + ui: &EditorUiState, + content: Rect, +) { + if !settings.mcp_server.running { + return; + } + let rect = Rect { + origin: Point2D::new( + content.origin.x, + server_card_top(content) + SERVER_CARD_H + 8.0, + ), + size: Point2D::new(content.size.x, CLIENT_CONFIG_H), + }; + cx.backend.fill_round_rect(rect, 8.0, theme.card); + cx.backend.stroke_round_rect(rect, 8.0, theme.border, 1.0); + let title = TextLayout::single_run( + t_settings(ui, "agents.mcpClientConfig"), + "system-ui", + 11.0, + to_jian(theme.muted_foreground), + Point2D::new(0.0, 0.0), + ); + cx.backend.draw_text( + &title, + Point2D::new(rect.origin.x + 12.0, rect.origin.y + 18.0), + ); + let config = format!( + r#"{{ "type": "http", "url": "http://127.0.0.1:{}/mcp" }}"#, + settings.mcp_server.port + ); + let config = ellipsize(cx, &config, rect.size.x - 24.0, 10.0); + let config_lay = TextLayout::single_run( + &config, + "monospace", + 10.0, + to_jian(theme.muted_foreground), + Point2D::new(0.0, 0.0), + ); + cx.backend.draw_text( + &config_lay, + Point2D::new(rect.origin.x + 12.0, rect.origin.y + 40.0), + ); +} + fn paint_cli_cell(cx: &mut PaintCx<'_>, theme: &Theme, cli: McpCli, enabled: bool, cell: Rect) { let bg = if enabled { theme.muted } else { theme.card }; cx.backend.fill_round_rect(cell, 10.0, bg); @@ -364,6 +426,17 @@ fn rect_contains(r: Rect, p: Point2D) -> bool { && p.y <= r.origin.y + r.size.y } +fn ellipsize(cx: &mut PaintCx<'_>, value: &str, max_w: f32, size: f32) -> String { + if cx.backend.measure_text(value, size) <= max_w { + return value.to_string(); + } + let mut out = value.to_string(); + while !out.is_empty() && cx.backend.measure_text(&format!("{out}..."), size) > max_w { + out.pop(); + } + format!("{out}...") +} + fn to_jian(c: Color) -> jian_core::scene::Color { fn ch(v: f32) -> u8 { (v.clamp(0.0, 1.0) * 255.0).round() as u8 diff --git a/crates/op-editor-ui/src/widgets/agent_settings_panel.rs b/crates/op-editor-ui/src/widgets/agent_settings_panel.rs index 17bb2a5b6..c8ce1d3b5 100644 --- a/crates/op-editor-ui/src/widgets/agent_settings_panel.rs +++ b/crates/op-editor-ui/src/widgets/agent_settings_panel.rs @@ -240,7 +240,7 @@ impl<'a> AgentSettingsPanel<'a> { pub fn content_total_height(&self) -> f32 { match self.settings.tab { AgentSettingsTab::Agents => agents_content_height(&self.settings), - AgentSettingsTab::Mcp => agent_settings_mcp::content_height(), + AgentSettingsTab::Mcp => agent_settings_mcp::content_height(&self.settings), AgentSettingsTab::Images => agent_settings_images::content_height(&self.settings), AgentSettingsTab::System => agent_settings_system::content_height(), } diff --git a/crates/op-editor-ui/src/widgets/agent_settings_panel_tests.rs b/crates/op-editor-ui/src/widgets/agent_settings_panel_tests.rs index 5c0ff45a6..7f545c15f 100644 --- a/crates/op-editor-ui/src/widgets/agent_settings_panel_tests.rs +++ b/crates/op-editor-ui/src/widgets/agent_settings_panel_tests.rs @@ -166,6 +166,23 @@ fn mcp_port_field_is_not_focusable_while_server_is_running() { assert_eq!(panel.hit_test(rect, point), AgentSettingsHit::Inside); } +#[test] +fn mcp_running_server_exposes_client_config_height() { + let mut stopped_state = EditorState::default(); + stopped_state.editor_ui.agent_settings.tab = AgentSettingsTab::Mcp; + let stopped = AgentSettingsPanel::for_editor(&stopped_state).content_total_height(); + + let mut running_state = EditorState::default(); + running_state.editor_ui.agent_settings.tab = AgentSettingsTab::Mcp; + running_state.editor_ui.agent_settings.mcp_server.running = true; + let running = AgentSettingsPanel::for_editor(&running_state).content_total_height(); + + assert!( + running > stopped, + "running MCP server should reserve space for the HTTP client config row" + ); +} + #[test] fn system_auto_update_switch_has_click_target() { let mut state = EditorState::default();