fix(gpui): sync settings resets and pane gutters

This commit is contained in:
kuddev committed 2026-08-26 16:51:37 +08:00
1 parent 4852049495
commit e41b3c8e7c
4 files changed
+110 -25

No files matched your search

+14 -2
View File
@@ -8,11 +8,23 @@ Every release entry is provided in English and Simplified Chinese.
### English
No unreleased changes yet.
#### Fixed
- **Common formulas such as `$E=mc^2$` and `$n!$` are recognized without reinterpreting shell variables, prompts, prices, or `sed`/`grep` BRE groups as mathematics.** Inline delimiters may cross terminal soft wraps but no longer join unrelated hard lines.
- **Resetting a settings drop-down immediately restores its displayed default value** instead of updating the saved setting while leaving the old selection visible.
- **Rounded terminal panes no longer expose an outline or a gap along their top edge** in either light or dark themes; the shell paint now follows the pane's actual left, right, bottom, and zero-width top gutters.
#### Improved
- **Terminal TeX rendering now detects formulas from terminal content instead of a recognized AI process name**, so block formulas remain available through WSL, SSH, and full-screen agent interfaces. Standard `$...$`, `$$...$$`, `\(...\)`, and `\[...\]` forms, Markdown-damaged bare delimiters, compact fraction arguments such as `\frac13`, dimmed reasoning output, cell backgrounds, and overlapping persisted formulas are handled more reliably.
### 简体中文
暂无未发布改动。
#### 修复
- **`$E=mc^2$`、`$n!$` 等常见公式可以被正确识别,同时不会把 shell 变量、提示符、价格或 `sed`/`grep` 的 BRE 分组误当成数学公式。** 行内定界符可以跨终端软换行,但不会再连接互不相关的真实换行。
- **还原设置下拉框后会立即显示默认选项**,不再出现设置值已经写回、界面却仍停留在旧选项的情况。
- **浅色与深色主题下,圆角终端 pane 四周不再露出线框或顶部缝隙**;壳层绘制现在严格采用 pane 真实的左、右、下卡缝以及零宽度上卡缝。
#### 改进
- **终端 TeX 渲染改为根据终端内容识别公式,不再依赖识别 AI 进程名称**,因此块级公式可继续用于 WSL、SSH 和全屏 Agent 界面。标准 `$...$`、`$$...$$`、`\(...\)`、`\[...\]`、Markdown 损坏后的裸定界符、`\frac13` 等紧凑分式参数、DIM 推理输出、单元格背景和持久化公式重叠都能得到更可靠的处理。
## 1.3.1 - 2026-08-25
+38 -5
View File
@@ -392,7 +392,10 @@ pub struct SettingsPane {
about_logo: Arc<Image>,
about_update: AboutUpdateState,
about_update_seq: u64,
selects: Vec<(&'static str, SharedSelect)>,
/// 每项还带着自己的 `values` 表:`SelectState` 只认索引,而从代码侧
/// 改设置(还原默认值、命令面板切换)时手里只有配置文件记号,没有
/// 这张表就没法把闭框的选中项拉回去。
selects: Vec<(&'static str, SharedSelect, &'static [&'static str])>,
shell_select: SharedShellSelect,
/// 外观页背景色:闭态 combobox + 开态 SV/色相/色板/hex 浮层(旧壳
/// `SettingsDropdown::BackgroundColor`,不是组件库 ColorPicker)。
@@ -489,7 +492,7 @@ pub struct SettingsPane {
impl SettingsPane {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let runtime = RuntimeSettings::load();
let mut selects: Vec<(&'static str, SharedSelect)> = Vec::new();
let mut selects: Vec<(&'static str, SharedSelect, &'static [&'static str])> = Vec::new();
let mut subscriptions = Vec::new();
let mut add_select = |key: &'static str,
@@ -523,7 +526,7 @@ impl SettingsPane {
}
},
));
selects.push((key, select));
selects.push((key, select, values));
};
let cursor_current =
@@ -1285,7 +1288,34 @@ impl SettingsPane {
}
pub(super) fn select_of(&self, key: &str) -> Option<SharedSelect> {
self.selects.iter().find(|(k, _)| *k == key).map(|(_, entity)| entity.clone())
self.selects.iter().find(|(k, _, _)| *k == key).map(|(_, entity, _)| entity.clone())
}
/// 从代码侧改掉某项设置后,把闭框的选中项拉回新值。
///
/// `SelectState` 自己持有选中索引,而 [`Self::persist`] 只写设置文件和
/// `runtime`——两者之间没有任何联系。少了这一步,"还原为默认值"会把值
/// 真的写回去、撤销图标也如期消失,但闭框仍显示旧标签,看上去就是
/// 撤销按钮没反应。
///
/// 值不在 `values` 里(光标形状的"未设置"写空串)时落到第 0 项,与
/// `add_select` 建初始索引时的同一条回落规则一致。
fn sync_select(
&mut self,
key: &str,
value: &str,
window: &mut Window,
cx: &mut Context<Self>,
) {
let Some((_, select, values)) = self.selects.iter().find(|(k, _, _)| *k == key) else {
return;
};
let row = values.iter().position(|candidate| *candidate == value).unwrap_or(0);
// `set_selected_index` 只改索引和 `final_selected_index`,不发
// `Confirm`,所以不会绕回订阅里再 persist 一次。
select.clone().update(cx, |state, cx| {
state.set_selected_index(Some(IndexPath::default().row(row)), window, cx);
});
}
fn select_row(
@@ -1551,8 +1581,11 @@ impl SettingsPane {
label,
desc,
dirty,
move |this, _, cx| {
move |this, window, cx| {
this.persist(&[(key, factory.clone())], cx);
// 开关行读 `runtime`,notify 就够;下拉框自己存索引,
// 必须显式拉回,否则撤销只改了值不改显示。
this.sync_select(key, &factory, window, cx);
},
control,
cx,
+31 -12
View File
@@ -211,30 +211,51 @@ pub fn card_content_bg(cx: &App) -> Hsla {
///
/// GPUI 没有凹圆角 primitive,因此四角按物理像素扫描圆外区域;卡本身的
/// 抗锯齿圆角仍是唯一可见边,壳色不会侵入卡内形成第二次 alpha 叠加。
pub fn paint_shell_around_card(bounds: Bounds<Pixels>, window: &mut Window, cx: &App) {
///
/// `insets` 必须是调用方**真实**的卡缝,四边分别给——不能在这里假设对称。
/// 终端卡的上边距是零(08-26 裁定:侧栏 / 终端卡 / 右侧抽屉三列顶边都贴
/// chrome 下沿),而左右和底部是 8px。这里若按四边 8px 推算,卡矩形整体
/// 下移 8px:左右两条壳色带从 `y+8` 起画,卡真正的上两个圆角却在 `y`,
/// 于是 `y..y+8` 里圆角外侧那两块三角没有任何人覆盖——深色主题下露的是
/// 深色不易察觉,浅色主题下直接露出一圈白边和一道顶部白缝。
pub fn paint_shell_around_card(
bounds: Bounds<Pixels>,
insets: gpui::Edges<f32>,
window: &mut Window,
cx: &App,
) {
let color = cx.theme().background;
let width = f32::from(bounds.size.width);
let height = f32::from(bounds.size.height);
let inset = 8.0_f32.min(width * 0.5).min(height * 0.5);
if width <= 0.0 || height <= 0.0 || inset <= 0.0 {
if width <= 0.0 || height <= 0.0 {
return;
}
// 逐边钳制,且后一边扣掉前一边已占的空间:窄窗口下两侧卡缝相加超过
// 容器宽度时,卡矩形不能退化成负尺寸。
let top = insets.top.clamp(0.0, height);
let bottom = insets.bottom.clamp(0.0, height - top);
let left = insets.left.clamp(0.0, width);
let right = insets.right.clamp(0.0, width - left);
let x = f32::from(bounds.origin.x);
let y = f32::from(bounds.origin.y);
let card_w = (width - inset * 2.0).max(0.0);
let card_h = (height - inset * 2.0).max(0.0);
let card_x = x + left;
let card_y = y + top;
let card_w = (width - left - right).max(0.0);
let card_h = (height - top - bottom).max(0.0);
let paint = |window: &mut Window, x: f32, y: f32, w: f32, h: f32| {
if w > 0.0 && h > 0.0 {
window.paint_quad(fill(Bounds::new(point(px(x), px(y)), size(px(w), px(h))), color));
}
};
// 四条带互不重叠,每个像素只承受一次壳色 alpha。
paint(window, x, y, width, inset);
paint(window, x, y + height - inset, width, inset);
paint(window, x, y + inset, inset, card_h);
paint(window, x + width - inset, y + inset, inset, card_h);
// 四条带互不重叠,每个像素只承受一次壳色 alpha。某一边卡缝为零时
// 对应的带宽度为零,`paint` 直接跳过。
paint(window, x, y, width, top);
paint(window, x, y + height - bottom, width, bottom);
paint(window, x, card_y, left, card_h);
paint(window, x + width - right, card_y, right, card_h);
let radius = crate::display::UI_SHELL_RADIUS_LOGICAL.min(card_w * 0.5).min(card_h * 0.5);
if radius <= 0.0 {
@@ -242,8 +263,6 @@ pub fn paint_shell_around_card(bounds: Bounds<Pixels>, window: &mut Window, cx:
}
let step = 1.0 / window.scale_factor().max(1.0);
let rows = (radius / step).ceil() as usize;
let card_x = x + inset;
let card_y = y + inset;
let card_right = card_x + card_w;
let card_bottom = card_y + card_h;
for row in 0..rows {
+27 -6
View File
@@ -110,6 +110,11 @@ const PALETTE_KEY_CONTEXT: &str = "NebulaCommandPalette";
const SIDEBAR_RESIZE_VISUAL_OFFSET: f32 = 8.0;
const SIDEBAR_RESIZE_HANDLE_WIDTH: f32 = 6.0;
/// 终端卡的左 / 右 / 下卡缝(上边为零,见卡容器处的裁定)。布局的 padding
/// 与 `paint_shell_around_card` 的壳色带必须读同一个数——两边各写一份
/// 字面量就是那圈白边的来源。
const CARD_GUTTER: f32 = 8.0;
/// 工作区静态默认绑定的 combo 集([`init`] 的镜像)。撤销已失效的自定义
/// 注入时要排除:gpui 的 NoAction 打在静态默认键上会误杀基础功能。
const STATIC_DEFAULT_COMBOS: &[&str] = &[
@@ -4530,9 +4535,13 @@ impl Render for NebulaWorkspace {
.child(
// 终端卡(一体化外壳):唯一的结构分界。圆角与旧壳卡
// 同源(UI_SHELL_RADIUS_LOGICAL=14),无描边——融合靠
// 壳色包围圆角卡本身,不靠线框。侧栏模式四边保留旧壳
// 8px 卡缝;顶部 tab 模式取消上边距,让 tab 底边与正文
// 直接相接,左右和底部卡缝不变。
// 壳色包围圆角卡本身,不靠线框。
//
// 上边距一律为零:侧栏 / 终端卡 / 右侧抽屉三列的顶边都贴
// chrome 下沿(用户 08-26 裁定「pane 和左侧 tab 抬到和文件树
// 顶部一致」)。左右和底部保留旧壳 8px 卡缝,抽屉在场时那条
// 右卡缝就是它和终端之间的呼吸位,不再归零——归零后的观感是
// 文件树压在终端上。
div()
.flex_1()
.min_w_0()
@@ -4541,16 +4550,28 @@ impl Render for NebulaWorkspace {
gpui::canvas(
|_, _, _| (),
|bounds, _, window, cx| {
// 卡缝必须和下面的 `px_2` / `pb_2` 逐边
// 对上:上边距为零是上面那条裁定,壳色
// 若按四边对称推算,卡的上两个圆角外侧
// 会漏覆盖,浅色主题下露出一圈白边。
crate::gpui_shell::theme::paint_shell_around_card(
bounds, window, cx,
bounds,
gpui::Edges {
top: 0.0,
right: CARD_GUTTER,
bottom: CARD_GUTTER,
left: CARD_GUTTER,
},
window,
cx,
);
},
)
.absolute()
.inset_0(),
)
.when(top_tabs, |card| card.px_2().pb(px(8.0)))
.when(!top_tabs, |card| card.p_2())
.px(px(CARD_GUTTER))
.pb(px(CARD_GUTTER))
.child(
div()
.size_full()