Skip to content

fix(macos): 日志列宽保持松手位置,LSP 日志按记录增量生成文本 - #1712

Merged
xiaoyumuxi merged 1 commit into
previewfrom
fix/1700-log-column-width-and-lsp-render
Nov 4, 2026
Merged

xiaoyumuxi merged 1 commit into
previewfrom
fix/1700-log-column-width-and-lsp-render

Conversation

@xiaoyumuxi

Copy link
Copy Markdown
Collaborator

概要

修复 #1700 合入后 lithe-code-review 报告的两个 P2 问题:

  1. 日志窗口表头拖动扩展列,松手后列宽会再次扩张。
  2. 编辑器区“LSP 日志”标签页每次状态发布都在主线程重建全文。

改动

1. 手动列宽松手后保持在松手位置(LogWindowTable.swift)

问题:表格把视口剩余宽度按基准宽度的比例分给事件、消息、字段三个扩展列。原来松手时,把扩展列的显示宽度直接存成基准宽度,下一次适配视口时又在上面加一份剩余宽度。例如约 1900pt 视口里事件列自动扩到约 483pt,拖到 493pt 松手后变成约 750pt,而且自动扩张的部分也被保存进偏好。

修复:

  • LogTableView 新增 adoptResizedWidths(previous:),把表头拖动后的宽度换算成基准宽度:
    • 固定列:基准就是松手宽度,先处理,因为它们决定扩展列能分多少空间。
    • 扩展列:设可分配宽度为 S,其他扩展列的基准之和为 B,列的显示宽度是 b·S/(b+B),反推得 b = r·B/(S−r)。下一次适配时正好显示松手宽度 r。
    • 反推结果低于最小列宽 40pt 时,基准取 40pt,其余扩展列的基准按同一比例放大,松手宽度照样保持。
    • 只剩一个扩展列时它总是填满整行;松手宽度超过 S−B 时已经没有剩余宽度。这两种情况基准就是松手宽度。
  • Coordinator.commitColumnWidths 改为只保存上面返回的基准宽度,然后重新适配。
  • fitColumnsToViewport 抽出 expandingColumns 和 base 两个辅助方法,与反推共用,适配算法本身不变。
  • LogWindowModel 新增 columnWidth(_:),把原来两处 min(2000, max(40, …)) 收成一个函数;表格的基准宽度使用同一个范围,保证布局用的值就是重开窗口恢复的值。

结果:松手后、窗口缩窄再放大后、重新打开窗口后,列都停在松手的位置;窗口缩窄到没有剩余宽度时,显示基准宽度并横向滚动;调整窗口大小不写偏好。

2. LSP 日志按记录增量生成文本(LanguageServerLogText.swift,新文件)

问题:LanguageServerLogOutput.output 在每次 LogWindowModel.state 发布时,对最多 20000 条记录都执行一次 map + joined(),状态、摘要、选中这类不改变记录的发布也一样,全部在主线程上。

修复:

  • 新增 @MainActor final class LanguageServerLogText,由视图通过 @StateObject 持有,按记录身份逐条缓存。记录身份与 LogWindowModel.recordID 相同(路径、seq、时间戳)。
    • 记录没有变化:直接返回缓存的文本,不调用格式化。
    • 实时批次(末尾追加;缓冲满 20000 条后从开头淘汰):只格式化新增记录,按缓存的每行 UTF-8 长度从文本开头截掉被淘汰的行。
    • 新查询、清空视图、中间缺行等其他变化:整份重建一次。
  • 缓存只保存每行的身份和字节长度,行文本只在合并后的文本里存一份。
  • 单行格式移到 LanguageServerLogText.line,输出与原来逐字节一致([LEVEL] 时间 消息,有字段时换行显示按键排序的 k=v);时间格式样式只创建一次。
  • LanguageServerLogsView 改为调用 text.update(logs.state.records),其他不变。原生输出控件 OutputTextView 本来就会对追加和截头做增量更新,这次只是去掉了它上游的全量字符串拼接。

结果:每批的格式化次数等于新增记录数,与缓冲大小无关;不改变记录的发布一次都不格式化。

全量准备没有移到后台线程:整份重建只在打开标签页、切换服务器或级别、新查询时发生,频率与应用日志窗口首次查询时在主线程逐条准备显示内容相同。后台准备需要额外处理取消和过期结果,这次没有引入。

3. 文档与功能矩阵

  • Agent Note 2026-11-03-structured-logging-log-window.md:
    • “窗口尺寸”一节说明基准宽度模型、松手反推公式、最小列宽的回退做法,并把“松手显示宽度直接存成基准宽度”列为反例。
    • “LSP 日志标签页”一节说明增量文本缓存,并把“每次发布全量重建”列为反例。
  • 功能矩阵 lsp-status-logs:补充新文件和测试作为证据,追加这次修复的验证说明。

测试

  • LogWindowTests.draggedColumnsKeepTheirReleasedWidth(新增,覆盖评审要求的“自动扩宽后拖动,再缩窄、重开窗口”):
    1. 在 1900pt 宽的窗口里确认事件列已自动扩宽;
    2. 模拟表头拖动,在 isResizingColumns 期间改宽度后提交,拖宽 10pt;
    3. 确认松手宽度保持、整行仍然填满视口、只保存了事件列、保存的基准小于显示宽度;
    4. 缩窄到 680pt 显示基准宽度,放大回 1900pt 恢复松手宽度,调整窗口大小不写偏好;
    5. 用保存的偏好重开窗口,宽度相同;
    6. 再拖到 60pt(低于最小基准能表示的宽度),确认松手宽度保持、事件列保存 40pt,再次重开后宽度相同。
  • LanguageServerLogTextTests(新增):
    • 用计数格式化器验证 20000 条满载时格式化 20000 次;同样的记录再次发布格式化 0 次;淘汰 10 条并追加 10 条只格式化 10 次;未满时追加 1 条只格式化 1 次。每一步的文本都等于全量重建的结果。
    • 同数量换一批记录、中间缺行、首条相同后续不同、清空后再追加,都正确重建。
    • 含中文、emoji、组合字符的行被淘汰后,剩余文本完整。
    • 默认单行格式与原实现一致。

验证

  • 通过:
    • .agents/skills/write-stable-tests/scripts/verify-test-stability.sh
    • node scripts/i18n/verify-app-language-localization.mjs
    • node scripts/i18n/generate-i18n.mjs --check
    • scripts/platform-feature-matrix/verify-platform-feature-matrix.sh
    • scripts/agent-notes/verify-agent-notes.sh
    • git diff --check
  • macOS 定向测试(test-stability-macos.sh -- --filter 'LanguageServerLogTextTests|LogWindowTests')正在本地编译运行(这个工作树需要从头编译 Rust Core)。结果出来后补充到这里,再标记 ready。

限制

  • 没有做整机拖动列宽和长时间 LSP 输出的人工验收,也没有截图;功能矩阵里这两项保持 pending。
  • 20000 条全量重建的耗时和内存基线还没测,测完补充到这里。

- 日志表拖动扩展列松手后,把松手宽度反推成基准宽度保存,适配视口、
  缩窄再放大和重开窗口后列仍停在松手位置,不再保存自动扩张的部分;
  反推低于最小列宽时按比例放大其余扩展列的基准
- LSP 日志标签页按记录身份缓存输出文本,实时批次只格式化新增记录并
  从开头截掉被淘汰的行,记录不变的发布不再重建全文
- 新增列宽回归测试与 LanguageServerLogTextTests;同步 Agent Note 与功能矩阵
@ghfind-review ghfind-review Bot added the review: high ghfind author score; see https://ghfind.com label Nov 4, 2026
@xiaoyumuxi
xiaoyumuxi marked this pull request as ready for review November 4, 2026 04:57
@xiaoyumuxi
xiaoyumuxi merged commit 5705732 into preview Nov 4, 2026
24 of 25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review: high ghfind author score; see https://ghfind.com

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant