Operating system
macOS
Netcatty version
1.1.82
How did you install Netcatty?
GitHub Release (.dmg / .exe / .AppImage / .deb / .rpm / .pacman)
Affected area
SSH connection / terminal
Can you reproduce it?
Always (100%)
Steps to reproduce
- 设置 → 终端 → 开启关键词高亮,加一条规则,例如正则:
.*WARN.*,黄色。
- 连上主机,跑一个每秒 10 行以上的持续输出,例如
tail -f /var/log/syslog 或 docker logs -f <container>。
- 盯着新进来的行看。
- 或者直接 tail -200f 查看日志(必现)
Expected behavior
关键词的颜色应当和文字在同一帧里一起出现,中间不该存在"无色"的可见状态。
Actual behavior
每批输出先以无色渲染出来,大约半秒后(scrollback 打满时接近 1 秒)整个视口重绘一次,颜色才一起出现。观感上就是高亮永远慢终端一步。
以下内容是我问了下 AI,仅供您参考:
不是"写入后着色"这个设计的问题——那条路径其实已经等价于"上屏前着色"了。xterm 在宏任务里解析(WriteBuffer.write() → _scheduleInnerWrite() → setTimeout(..., 0)),在 rAF 里渲染(RenderService.refreshRows() → RenderDebouncer),所以 write 回调里的改色会落在同一帧内。滞后完全来自延迟补偿路径:
components/terminal/keywordHighlight.ts:688 的 shouldBypassWrite(),当 countNewlines(data) >= BULK_WRITE_LINE_BREAKS(8,第 71 行)或 shouldDegradeTerminalKeywordHighlight() 为真时,整批跳过着色。
components/terminal/runtime/terminalOutputPressure.ts:52-54:100ms 窗口内累计达到 LARGE_OUTPUT_RATE_BYTES = 16 * 1024 即降级;TERMINAL_LONG_LINE_PRESSURE_BYTES 为 64KB(infrastructure/config/terminalFlowConstants.json)。
- 跳过的活由
scheduleCatchUp()(keywordHighlight.ts:764)重新排队,静默窗口是 XTERM_PERFORMANCE_CONFIG.highlighting.largeOutputQuietMs = 480(infrastructure/config/xtermPerformance.ts:106),scrollback 打满时翻倍到 960ms(terminalOutputPressure.ts:184-190)。
components/terminal/runtime/writeCoalescer.ts 把 PTY chunk 合并成每个 tick 一次 term.write()(16ms burst 窗口,第 36 行),所以流式日志几乎总会超过 8 个换行。对 tail 日志来说,bypass 是常态而不是例外。
- catch-up 真正跑起来时会重新着色并调用
recolorVisible() + refresh(),这就是第二次绘制,也就是肉眼看到的"闪一下"。
按性价比排序:
- bypass 时仍在 write 回调里同步给当前视口着色。成本固定为
O(视口行数 × 规则数),与输出量无关;只把视口外的历史行丢给 catch-up。这样用户永远看不到无色的帧,后台补色也不可见。
- 把
largeOutputQuietMs 从 480 降到帧级(0–32ms),并/或暴露成设置项。runCatchUp() 已有 4ms 切片预算和 yieldToRenderer() 让出,不会卡住主线程。
- 提高
BULK_WRITE_LINE_BREAKS(或改成按视口行数算),并放宽 16KB/100ms 的压力阈值——这个阈值只有约 160KB/s,普通日志很容易撞上。
- 每次写都用
recolorRange(..., force=true),会绕过 logicalLineIsCurrent() 的指纹缓存、每行每次都重新匹配。把这块开销降下来,才是减少 bypass 必要性的根源。
如果 480ms 是有意的吞吐取舍,那至少请把它做成可配置项,让日志重度用户可以自己选择"同帧着色"。
Logs / screenshots
No response
Before submitting
Operating system
macOS
Netcatty version
1.1.82
How did you install Netcatty?
GitHub Release (.dmg / .exe / .AppImage / .deb / .rpm / .pacman)
Affected area
SSH connection / terminal
Can you reproduce it?
Always (100%)
Steps to reproduce
.*WARN.*,黄色。tail -f /var/log/syslog或docker logs -f <container>。Expected behavior
关键词的颜色应当和文字在同一帧里一起出现,中间不该存在"无色"的可见状态。
Actual behavior
每批输出先以无色渲染出来,大约半秒后(scrollback 打满时接近 1 秒)整个视口重绘一次,颜色才一起出现。观感上就是高亮永远慢终端一步。
以下内容是我问了下 AI,仅供您参考:
不是"写入后着色"这个设计的问题——那条路径其实已经等价于"上屏前着色"了。xterm 在宏任务里解析(
WriteBuffer.write()→_scheduleInnerWrite()→setTimeout(..., 0)),在 rAF 里渲染(RenderService.refreshRows()→RenderDebouncer),所以 write 回调里的改色会落在同一帧内。滞后完全来自延迟补偿路径:components/terminal/keywordHighlight.ts:688的shouldBypassWrite(),当countNewlines(data) >= BULK_WRITE_LINE_BREAKS(8,第 71 行)或shouldDegradeTerminalKeywordHighlight()为真时,整批跳过着色。components/terminal/runtime/terminalOutputPressure.ts:52-54:100ms 窗口内累计达到LARGE_OUTPUT_RATE_BYTES = 16 * 1024即降级;TERMINAL_LONG_LINE_PRESSURE_BYTES为 64KB(infrastructure/config/terminalFlowConstants.json)。scheduleCatchUp()(keywordHighlight.ts:764)重新排队,静默窗口是XTERM_PERFORMANCE_CONFIG.highlighting.largeOutputQuietMs = 480(infrastructure/config/xtermPerformance.ts:106),scrollback 打满时翻倍到 960ms(terminalOutputPressure.ts:184-190)。components/terminal/runtime/writeCoalescer.ts把 PTY chunk 合并成每个 tick 一次term.write()(16ms burst 窗口,第 36 行),所以流式日志几乎总会超过 8 个换行。对 tail 日志来说,bypass 是常态而不是例外。recolorVisible()+refresh(),这就是第二次绘制,也就是肉眼看到的"闪一下"。按性价比排序:
O(视口行数 × 规则数),与输出量无关;只把视口外的历史行丢给 catch-up。这样用户永远看不到无色的帧,后台补色也不可见。largeOutputQuietMs从 480 降到帧级(0–32ms),并/或暴露成设置项。runCatchUp()已有 4ms 切片预算和yieldToRenderer()让出,不会卡住主线程。BULK_WRITE_LINE_BREAKS(或改成按视口行数算),并放宽 16KB/100ms 的压力阈值——这个阈值只有约 160KB/s,普通日志很容易撞上。recolorRange(..., force=true),会绕过logicalLineIsCurrent()的指纹缓存、每行每次都重新匹配。把这块开销降下来,才是减少 bypass 必要性的根源。如果 480ms 是有意的吞吐取舍,那至少请把它做成可配置项,让日志重度用户可以自己选择"同帧着色"。
Logs / screenshots
No response
Before submitting