fix(settings): show mcp client config

This commit is contained in:
Fini 2026-05-30 19:12:27 +08:00
parent 920af1b8f5
commit db0e33fc55
3 changed files with 98 additions and 8 deletions

View file

@ -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

View file

@ -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(),
}

View file

@ -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();