Skip to content

优化编辑器 Code Vision、Git Blame、Gutter 对齐与设置页背景 - #189

Merged
1lck merged 4 commits into
preview/0.3.0from
fix/app-ui-details
Aug 21, 2026
Merged

优化编辑器 Code Vision、Git Blame、Gutter 对齐与设置页背景#189
1lck merged 4 commits into
preview/0.3.0from
fix/app-ui-details

Conversation

@arkleselect

Copy link
Copy Markdown
Collaborator

变更说明

Code Vision 交互优化

  • 修复 NSTextView 默认 I-beam 光标与 Code Vision 链接光标之间的竞争,避免鼠标移动过程中光标在文本选择和链接状态之间闪烁。
  • 统一 Code Vision 元素的字体、字重、颜色、垂直基线和元素间距。
  • 调整作者图标的 attachment bounds 和文本基线,使用户图标与用户名处于同一水平线并在所在行内垂直居中。
  • 根据实际 attributed title 宽度计算 Code Vision 元素布局,减少不必要的固定宽度和空白间距。

Git Blame 信息优化

  • lick · 2026/8/2 等 Blame metadata 使用与代码正文一致的编辑器字体和字号。
  • 根据 NSLayoutManager 提供的实际行片段高度定位 Blame 信息,使其与对应代码行保持一致的垂直对齐。
  • 调整 Blame metadata gutter 宽度,避免作者和日期信息被截断。
  • 将作者和日期合并为紧凑的单行展示,降低 gutter 区域的视觉复杂度。
  • 保留连续相同提交信息的折叠展示逻辑,仅在首个可见行或提交边界显示 metadata,减少重复的视觉噪声。

编辑器字体与基线对齐

  • 统一代码正文与 Git Blame metadata 的编辑器字体和字号。
  • 行号使用比代码正文小一级的编辑器字体,保持辅助信息的视觉层级。
  • 修复直接使用 NSAttributedString.baselineOffset 导致行高被意外撑大的问题。
  • 将代码字形的垂直校正移动到 NSLayoutManager 的行片段基线阶段,避免基线调整参与行高计算。
  • 代码正文与 Blame metadata 使用一致的视觉基线修正;行号和折叠控件则在各自对应的行片段中独立垂直居中。
  • 保证 Blame metadata、行号、折叠控件和代码正文与同一代码行保持稳定的水平对齐关系。

折叠控件优化

  • 将原有实心折叠标记改为轻量的线性 chevron
  • 将折叠控件移动至行号右侧、编辑区分隔线左侧。
  • 折叠控件尺寸依据行号字体的实际字形高度动态计算,使其随编辑器字号变化,并与行号保持一致的视觉大小。
  • 移除折叠控件悬停和点击状态下的额外背景,避免与当前行高亮产生视觉叠加。
  • 统一折叠控件的绘制区域、命中区域和点击处理范围,避免视觉位置与交互位置不一致。

当前行高亮修复

  • 修复编辑器代码区域重复绘制当前行背景的问题。
  • 此前左侧行号栏仅绘制一层当前行背景,而右侧代码区域同时叠加整行背景和文本临时背景,导致两侧颜色深浅不一致,并产生颜色断层和重叠感。
  • 移除代码文本区域上的第二层当前行临时背景,只保留覆盖完整行片段的统一背景绘制。
  • 保留括号匹配、查找结果和符号高亮等独立 decoration,不影响现有编辑器反馈。
  • 修复后,行号栏与代码区域的当前行高亮保持连续一致,不再出现背景叠加变深的问题。

设置页面主题与窗口外观优化

  • 设置页面主体背景统一使用设置专用背景层级:
    • 深色模式:#28292C
    • 浅色模式:#ECEEF1
  • 设置页面控件背景使用独立的设置控件层级色,避免控件区域与主体背景形成过强对比。
  • Appearance mode 分段控件和设置页下拉选择控件统一使用设置页控件背景。
  • 设置页搜索框保持内部透明,仅保留边框和搜索交互元素。
  • 设置页面主按钮统一使用 #3871E1
  • 左侧设置分类选中态和 Appearance mode 选中态使用 #2B4271
  • AppKit 原生窗口标题栏与设置页面主体使用同一套背景颜色,避免标题栏出现独立的系统深色背景。
  • 处理 NSTitlebarContainerViewNSTitlebarViewNSThemeFrame 的背景层级,使标题栏与设置页面内容区域保持连续。
  • 修复 Light → System 后设置窗口仍残留浅色外观的问题。
  • 移除设置窗口中 SwiftUI .preferredColorScheme(...) 与 AppKit NSWindow.appearance 的冲突,由原生窗口外观统一管理 Light、Dark 和 System 状态。
  • System 模式下清除窗口级外观覆盖,使设置窗口重新继承 macOS 当前系统外观。
  • 标题栏背景覆盖层支持鼠标事件穿透,不影响窗口拖动、关闭和缩放按钮交互。

技术实现

  • 基于 AppKit NSButtonNSTrackingArea、cursor rect 和 cursorUpdate(with:) 管理 Code Vision 与折叠控件的交互状态。
  • 使用 NSAttributedString 对 Code Vision 中的数量、链接文本和行内图标分别应用字体、下划线与基线属性。
  • 使用 NSLayoutManager 的 line fragment 信息定位 gutter 元素、Git Blame metadata 和当前行背景。
  • NSLayoutManagerDelegate 的行片段布局阶段调整代码基线,使垂直校正不再改变段落行高。
  • 根据编辑器字体的 pointSize 和行号字体的 capHeight 动态计算折叠 chevron 的视觉尺寸。
  • 使用透明 overlay scroller、mini control size 和自定义 NSScroller 绘制实现接近 IntelliJ IDEA 的滚动条样式。
  • 将编辑器布局常量集中在 EditorLayoutMetrics 中,统一管理标准 gutter、Blame metadata 区域和组合宽度。
  • 使用动态 NSColor 根据 NSAppearance 解析设置页面背景色,确保 Light、Dark 和 System 模式切换时颜色及时刷新。
  • 使用 NSViewRepresentable 访问设置窗口并同步原生 NSWindow.appearance、标题栏透明度和标题栏背景容器。

验证

  • macOS arm64 Debug 构建通过。
  • macOS App 构建安全检查通过。
  • git diff --check 通过。
  • 编辑器布局相关定向测试已通过。
  • Light、Dark、System 设置窗口外观切换逻辑已完成构建验证。
  • 设置页面、Keymap、LSP 和项目树滚动条配置已完成构建验证。
  • 完整 Swift 测试在当前命令行工具链下因无法加载 Swift Testing 的 Testing 模块而未能执行;该问题属于本机工具链配置,与本次 UI 修改无关。

@arkleselect
arkleselect requested a review from 1lck as a code owner August 19, 2026 16:11
@xiaoyumuxi
xiaoyumuxi requested review from xiaoyumuxi and removed request for xiaoyumuxi August 19, 2026 16:26
@xiaoyumuxi

Copy link
Copy Markdown
Collaborator

CI 挂了啊

@xiaoyumuxi

Copy link
Copy Markdown
Collaborator

1. 建议修改:Fold 控件和 breakpoint 的区域发生了重叠

现在:

standardGutterWidth = 45
foldIndicatorX = editorGutterOriginX + standardGutterWidth - 13

所以 fold 控件的命中范围实际上是:

32 ... 45

但 breakpoint 被画在:

x: editorGutterOriginX + 29
width: 8

也就是:

29 ... 37

两者在 32...37 有直接重叠。

更关键的是 mouseDown 的优先级:

if git change ...
else if fold ...
else if implementation ...
else if breakpoint ...

所以一行如果既是可折叠行又有 breakpoint,用户点 breakpoint 右半部分时会触发 fold,而不是 breakpoint。视觉上 breakpoint 圆点和 fold chevron 也可能叠在一起。相关绘制和点击逻辑就在同一个 gutter 实现里。

另外,行号本身还是:

x: editorGutterOriginX
   + (standardGutterWidth - size.width) / 2

也就是继续在完整 45pt 内居中,但 fold 又占了最后 13pt。四位、五位行号越来越容易侵入 fold 区域。

建议

不要继续在 45pt 里面塞所有东西。明确拆列:

| breakpoint / impl | line number | fold | divider |

比如把 gutter 稍微扩宽,或者至少定义:

lineNumberWidth
foldIndicatorWidth

然后:

standardGutterWidth = lineNumberWidth + foldIndicatorWidth

绘制、hover、hitTest 都用相同的 rect。


2. 建议修改:System 模式下系统自动切换 Light/Dark,标题栏可能不刷新

这里 Light → System 的修复思路是对的:

window.appearance = nil

AppKit 在 appearance 为 nil 时确实会继承系统 appearance。([Apple Developer][1])

问题出在后面:

let settingsSurface = LitheTheme.settingsSurfaceNSColor(
    for: window.effectiveAppearance
)

window.backgroundColor = settingsSurface
...
current.layer?.backgroundColor = color.cgColor
...
backgroundView.layer?.backgroundColor = color.cgColor

这里已经把动态 appearance 解析成了当前时刻的静态颜色 / CGColor

场景:

Settings = System
        ↓
晚上 macOS 自动从 Light 切 Dark
        ↓
window.effectiveAppearance 已经变 Dark
        ↓
SwiftUI 动态 settingsSurface 可以重新绘制
        ↓
但之前塞进 titlebar layer 的 CGColor 还是 Light 色

themePreference 始终还是 .system,所以不能指望它发生一次新的 state change 来调用这段代码。

AppKit 本身专门提供了:

viewDidChangeEffectiveAppearance()

就是让 NSView 响应这种 appearance 变化的。([Apple Developer][2])

我会这么改

SettingsTitlebarBackgroundView 自己负责刷新颜色:

override func viewDidChangeEffectiveAppearance() {
    super.viewDidChangeEffectiveAppearance()
    updateBackground()
}

或者由 accessor/probe 监听 effective appearance 后重新执行 applySettingsSurface(...)

特别是这些:

layer?.backgroundColor = color.cgColor

必须在 effective appearance 改变之后重新 resolve。

这个也是我建议合并前修的。


3. 小问题:Code Vision 作者名 hover 下划线逻辑有 bug

这里很容易漏掉。

现在为了实现:

1 usage

hover 时只给 usage 下划线,你写的是:

if let separator = linkTitle.firstIndex(of: " ") {
    prefix // 不划线
    suffix // 划线
}

问题是 author 也传了:

underlinesOnHover: true

所以:

John Doe

hover 后会变成:

John Doe
     ^^^
     只有 Doe 下划线

Git 作者名带空格非常正常。代码目前确实对 usage 和 author 共用了同一个「第一个空格之后下划线」规则。

建议不要用 Bool:

enum HoverUnderlineStyle {
    case none
    case all
    case afterFirstSpace
}

然后:

usage          → afterFirstSpace
author         → all
implementation → all / none,按设计统一

顺便现在 implementationButton 是可点击元素,却没传 underlinesOnHover: true,跟 usage/author 的 hover 反馈也不完全一致。

这个属于 P3,非 blocker


4. Scrollbar 这里不是明显 bug,但代码语义最好再确认

这个实现叫:

CompactOverlayScroller

注释也一直强调 overlay,但:

let scrollerStyle: NSScroller.Style =
    alwaysShowVertical ? .legacy : .overlay

而设置页现在全部是:

alwaysShowVertical: true,
usesCompactScrollers: true

所以实际上这些页面运行的是:

CompactOverlayScroller
+
NSScroller.Style.legacy

并不是 overlay。

这倒不一定是你们写错了,因为 Apple 明确说明:overlay-style scroller 的自动隐藏行为不受 autohidesScrollers 控制。也就是说如果目标是「强制一直显示」,使用 legacy 确实有现实原因。([Apple Developer][3])

但代码注释:

enforce overlay mode afterwards

和实际:

alwaysShowVertical ? .legacy : .overlay

是矛盾的。

所以这里我建议至少明确产品需求:

  • 真正 overlay → 接受系统自动 fade;
  • 始终显示 → legacy 是合理方案,但别叫/注释成 overlay,并重点测一下内容区宽度、右侧空隙。

@1lck

1lck commented Aug 20, 2026

Copy link
Copy Markdown
Owner

@arkleselect 有空麻烦修一下上述几个阻塞点 以及ci

@arkleselect

Copy link
Copy Markdown
Collaborator Author

正在改

@arkleselect arkleselect changed the title 优化编辑器 Code Vision、Git Blame、Gutter 对齐、滚动条与设置页主题 优化编辑器 Code Vision、Git Blame、Gutter 对齐与设置页背景 Aug 20, 2026

@1lck 1lck left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查发现 3 个需要处理的交互回归,具体影响和建议修复方式见 inline comments。

Comment thread Sources/Lithe/Views/Editor/CodeEditorView.swift
Comment thread Sources/Lithe/Views/App/SettingsView.swift
Comment thread Sources/Lithe/LitheApp.swift Outdated
@1lck
1lck merged commit cf45acf into preview/0.3.0 Aug 21, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants