Skip to content

feat(desktop): #1794 基于 Viva 的跨平台工作台迁移(进行中) - #1799

Draft
xiaoyumuxi wants to merge 82 commits into
previewfrom
feature/1794-viva-workbench-shell
Draft

xiaoyumuxi wants to merge 82 commits into
previewfrom
feature/1794-viva-workbench-shell

Conversation

@xiaoyumuxi

@xiaoyumuxi xiaoyumuxi commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

最新进度与接手入口(2026-11-09,真实 Debug 与 Windows 原生边界节点)

持续开发,保持 Draft,不 ready、不自动合并。本节取代旧接手快照;下方历史说明仍保留。

当前提交与明确依赖

  • 客户端:7354cb7c4,现有分支 feature/1794-viva-workbench-shell。已保留并集成远端 e92acfa84、d12cca7c9、7aea0ff17 的编辑器/终端标签、Shell 设置、Git Log 等改动,没有覆盖其他工作。
  • Core 增量依赖:Lithe-IDEA/Lithe-core-rust#3,同为 Draft、未合并;客户端精确固定 b22c2ef80421a555a08d0a87eb9f39693c08a2b1。继续复用原 Rust Core;工具链 pin 编辑与 DAP 状态转换仍在 Core,不重写内核。
  • 前节点 60aadcaf3 已接回工具链发现、Core candidates/resolve、Build 设置草稿及显式批量保存,Build/Run/命令型 Tests 同时使用工具链程序与导出环境。toolchains.editPins 与共享 fixture 取代 Go 的 pin schema 合并;取消、外部修改冲突和原子文件发布仍由原生层负责。

本节点的具体行为

  • Debug 增加显式适配器选择;后台用原始插件 manifest 调 Core 进程计划、debug.adapters.executablePlan 和 startArguments,传真实 program/cwd、插件默认参数及目标绑定,修复空 launch arguments。自动查找跳过目录/非程序候选;明确配置失效时拒绝,不静默回退。
  • 工具运行时入口先验证包路径,工具链解析后调用 Core 模板生成 argv;macOS PATH 缺失时由通用 xcrun --find 查询选中开发工具,不硬编码工具或 Xcode 路径。serverProcess 4 仅有内部路径回归,未扩大宿主能力声明。
  • 原生进程创建移出 UI 线程;会话的 Core 调用、发送帧、发布更新串行完成,进程句柄原子发布。停止等待启动收尾,只销毁真正创建的 Core 会话,再回收进程/观察器与原生分配的独占状态目录。运行时写入插件可写 .state,安装包只读。
  • 真实 LLDB 可先暂停/结束再确认 launch;Core 只把仍处于 launching 的状态改为 running,避免覆盖暂停或复活结束的会话,新增共享时序 fixture。
  • Windows 插件可执行文件改由 GetBinaryTypeW 判断 Win32/64 程序,拒绝伪 .exe;不再用 Unix 执行位误拒真实程序。退出测试证明实际创建过进程,防止启动失败冒充清理成功。
  • Windows 工具链保存先给未发布临时文件复制原所有者/组/DACL及继承保护状态,再核对取消和字节基线、原子改名;权限准备失败保留源文件。新增真实受保护 ACL 回归。
  • 修正 Linux Expand All 测试的观察时机:直接断言该动作的上限提示,避免异步 watcher 的独立上限提示覆盖单一横幅。扩展512目录断言与另一个按钮派发回归保留,没有放宽限额。
  • 补齐共享中英文文案、矩阵、Notes、独占状态目录的资源复用排除规则及构建文档。

验证与限制

  • macOS:集成远端后的全量 Go 测试通过;普通/dev vet、桌面构建通过。Debug/工具链定向 race 有此前通过证据;工作台全包竞态二进制在恢复正常启动后于正确包目录复验,301项通过、8项条件跳过(54.57秒)。此复验用同一编译结果,临时移除Go DWARF并重新ad hoc签名,未改断言和期限;标准启动仍有下条限制。真实官方 LLDB manifest + clang -g + lldb-dap 验证 program/cwd/defaults、源码断点、main 栈帧、继续/输出/退出与清理,重复三轮通过;14条计时记录13项通过、1项仅子进程夹具跳过。
  • Windows Lithe-dev:与集成源码的520个 Go 文件逐一哈希一致;相关41条计时记录38项通过、3项条件跳过,build/vet 通过。原生程序误拒与 DACL 丢失均有先失败再通过的回归。没有启动持续运行的 Lithe 窗口;完整原生 GUI、真实 Windows LLDB 与发行流程不在这些证据内。
  • Core 工具链100项、pin14项计时、Debug引擎59项计时通过;Go/Rust共用 fixture。i18n三项检查(10,716中英词条)、225篇Notes、254项矩阵与变更门禁、36项资源复用测试、bundle只读静态检查、测试稳定性和diff检查通过。
  • macOS扩大验证时三个 Tests Provider 初始化超时;普通与 race 的单独复跑通过,集成后普通全量通过,但集成后全包 race 仍失败。随后新 race 可执行文件在 _dyld_start 停留超过100秒、尚未进入测试,签名验证有效;去掉Go DWARF、重新临时签名都未解决,不能把标准冷启动记作通过或断言就是同一根因。后续复用已正常启动的同一竞态二进制、在正确包目录运行,工作台全包301通过/8条件跳过;首次直接运行因cwd错误导致的fixture缺失也保留为失败证据。诊断源码已恢复;失败日志、原生采样与有界终止证据保留,未放宽期限、未跳过断言。旧 Core unused/dead-code 与最低 macOS 链接警告保留。日志等价门禁仍由 #1802 跟踪;缺失的 Core 注释自动门禁和旧共享契约全量脚本不记作通过,新改动已有定向检查与真实消费者测试。
  • 真实 LLDB 测试把组织原始 manifest 放入隔离夹具,不能当作市场安装/准入验收。settings 2、完整用户配置编辑、Java/TCP/attach/步进/变量、工具链 provider 查询及其他迁移能力仍未完成,不提升为 verified,不结束完整目标。

本节点 GitHub Draft Linux CI、格式与脚本、Notes 和矩阵检查已全部通过:Desktop CI run 42655477550。macOS/Windows runner 按 Draft 策略跳过,不能当作通过。

继续工作与恢复

  1. 先解决/隔离本机 _dyld_start 启动停滞并补齐最新全包 race 的验收;继续真实插件设置来源与确认写回、正式插件准入、完整 Run/Debug 配置与工具链 provider,核对设置快照切换/关闭项目的作用域保护。
  2. 复核完整迁移清单及真实用户流程。previous-batches/delivery-audit/ 是旧盘点;146 must-replace、48 pending-owner-decision 不是删功能许可。LS3、SDK、编辑器/Git/生成器等剩余能力与跨平台原生/打包/升级还须逐项验收。
  3. 保留 Viva 上游 [Bug] Windows preview 0.3.0 编辑器持续卡顿:每键全文同步、Git blame 与 LSP 轮询 #187/[Mac] 使用 Release 静态更新清单替代匿名 GitHub API 检查更新 #188 升级待办,未合并前不静默换 pin。
  4. 工作树:~/.codex/worktrees/viva-1799-takeover/Lithe-IDEA,本地分支 codex/1799-viva-completion;按大节点继续提交、同步本 PR,保持 Draft。
  5. 仓库根 .artifacts/1799-handoff/ 的旧恢复包保留;本次资料在 takeover-2026-11-09/,含最新源码、客户端增量 bundle(基于旧 final-snapshots.bundle 的 f105d32c)、独立 Core bundle、失败/通过日志及计时 HTML/JUnit。当前工作树继续使用,未归档;Windows 专属验证目录已在证据收集后删除,清理报告确认目录不存在、所属进程0;未动其他服务器工作区。本机诊断进程也已终止。

本批已验收集成(2026-11-09)

保留远端 db2024230 及此前编辑器性能优化,集成以下子任务;工具链和测试/Debug 工作树仍独立,未混入本次提交。

  • 官方插件包:同时接受根目录 plugin.json 与唯一 <plugin-id>/plugin.json 布局;内容根名称必须匹配 Core 校验后的插件 ID。拒绝歧义 manifest、根外条目、路径穿越、符号链接及配额违规。归档摘要和内容摘要的审批边界保持不变,无签名磁盘包仍需要明确审批。官方 Cargo 0.1.0 原始 ZIP 已通过安装、启用、模型加载与真实构建,flat 包兼容回归保留;不代表发布者签名验证已实现。
  • 语言操作:F12 / Cmd或Ctrl点击定义跳转、多目标选择,F1 / 指针停留 Hover,Ctrl+Space 显式补全及接受。消费 Core 规范化结果,支持取消与过期版本隔离,列表有界;snippet 转纯文本由 Core 完成。移除 lsp.request 不接受的 documentVersion/Extra,接现有 completionContext,并等待真实 ready。自动补全、只读/jar目标、重命名/格式化仍未实现。
  • 编辑器集成:适配最新 rope/string 编辑接口;接受补全时只在 UI 线程取得不可变快照,后台计算变更区间,回到 UI 后核对版本并应用局部编辑。回归证明旧整篇替换会发送 120009 字节,新路径只发送新增的 3 字节,撤销/重做保持正确。Core lsp.applyTextEdits 仍是后台全量文本接口,未宣称它已增量化。
  • Agent:会话历史读取、选择恢复、新建会话,以及 adapter 声明的模型/思考程度/模式选项;配置收到确认才生效。恢复回放使用独立有界缓存,恢复失败保留原会话文本和已确认配置,新建失败亦保留配置。历史缓存容量受限,补充认证要求/等待登录/连接丢失状态;不读取真实账户、不发起付费请求。归档历史、完整订阅认证与 boolean 配置仍未接入。
  • CI 架构:沿用远端已有 core.PluginArchitecture,不加入第二套换算;补充真实 Core 对 arm64/x86_64 接受及 amd64/x64/sparc 拒绝的契约回归。

本批验证:全量 Go 测试、workbench/plugininstall/pluginpkg/editorview 全包竞态、普通/dev vet、桌面构建、i18n 三项检查、Notes、功能矩阵、资源只读与测试稳定性门禁。真实 clangd 的定义/Hover/补全及官方 Cargo 原包 E2E 单独执行。所有证据为本机 macOS;Windows/Linux 原生端到端仍待验证。

编辑器性能:同机 Apple M4,100 KiB/10 MiB,相关 Paint/Type/UndoRedo/CaretMove/Scroll 基准各 6 次;28/28 在预算内,绘制/滚动 0 B/op、0 allocs/op。对比文件 .artifacts/editor-perf/batch2-before.txt、batch2-after.txt、batch2-after.md,不提交产物;本批没有新超预算项。

关联 Issue / Linked issue

PR 类型 / Type

  • feat:新功能或用户可见的改进
  • fix:缺陷修复(含审查发现的替换窗口、并发丢更新与 CI 失败项)
  • docs:文档、Agent Note、Skill 等规则文件
  • release:稳定版发布准备

改动说明 / What changes

背景 / Why

将原 macOS SwiftUI 和 Windows Tauri 客户端迁移到共用的 Viva(Go)桌面界面;共享规则继续由独立的 Lithe-core-rust 实现,语言、构建和其他工具能力由插件提供。这个 Draft 已删除旧客户端与仓库内旧 Core,但新客户端尚未实现或验收旧产品的全部功能,不能作为可发布版本。

改动 / Changes

界面与工作台(此前已完成)

  • 增加 Viva 欢迎页、工作台、可拖放工具窗口、终端、设置和日志窗口;项目文件树、编辑器标签、文件打开和保存、TextMate 高亮、文件图标与自动换行已有接入。
  • 欢迎页支持最近项目、搜索、Core 解析 Git 分支、打开项目与通过 Core 克隆仓库;新建项目窗口支持空项目、名称校验、选择位置、可选 Git 初始化、取消和失败后保留已创建目录。侧栏搜索通过 Core 匹配,插件入口打开插件市场页面。
  • 接入工作区文件和文本搜索、Search Everywhere、编辑器行列跳转、外部文件变更与保存冲突保护;Git 状态、分支、日志、暂存与提交使用已有 Core 命令。
  • 同步 Core 已合入的 languages: 3 TextMate 契约,撤回原 languages: 2 Monarch 语法;更新共享 fixture、i18n、功能矩阵、资源与测试脚本,并记录旧客户端相关决策的迁移状态。
  • 修复 macOS 上 Go 与 Rust Core 使用子进程时的 SIGCHLD 信号栈问题;增加有界 Go 测试计时、Windows Git Bash 路径识别及资源只读检查。
  • 修复 Windows 与 Linux 自定义标题栏的最小化、最大化/还原、关闭按钮在浅色外观下看不见:工作台、设置窗口和日志窗口通过 desktop/internal/windowcontrols 按各自标题栏的文字色着色,按钮名称使用共享 i18n 的 window.* 词条。三个窗口各有 TestWindowControlsFollowTheTitleBar 回归测试。

插件安装链路(此前已接入)

  • pluginpkg:ZIP 安全解压。拒绝契约禁止的绝对路径、.. 段、反斜杠、NUL 与符号链接,并补上跨平台安装所需的 Windows 设备名、尾随点/空格、大小写重复、文件与目录冲突;宿主自定 20000 条目 / 512 MiB 单文件 / 2 GiB 解压总量 / 200 倍膨胀上限(契约未给数值)。解压与摘要校验共用同一已打开句柄,关闭「校验完再按路径打开」的替换窗口;失败清理暂存目录;启动时清扫 .staging / .trash 残留。
  • plugintrust:批准记录。只保存来源、归档摘要、包内容摘要、权限与已批准程序;市场来源在摘要与程序集都没变时复用批准,磁盘来源每次都问。记录加互斥,避免并发读改写丢更新。
  • plugininstall:两阶段安装。Prepare 解压、校验清单、算内容摘要、取 Core 的安装审查并判定能否复用批准;Commit 才真正写入。解压与校验都响应取消。
  • pluginstate / pluginmanager:启用状态与生命周期。卸载删除该插件的全部已安装版本并清掉对应批准,最后才清启用选择。
  • pluginhost:能力声明、清单校验、包路径逐段解析(拒绝任一级符号链接)、宿主服务注册与派发(机器请求 30 秒期限,用户选择类请求豁免)、启动计划路径校验。
  • pluginbackend:Core 绑定的后端启动、停止与事件泵。宿主请求交给 pluginhost.Services 应答,其余事件(诊断等)交给调用方;systemCommand 启动用宿主工具发现解析裸命令。
  • pluginservice:装配上述各层,并把 Core 的安装审查、语法解码、文档同步适配成内部类型。

语言服务与诊断

  • 语言贡献:读取已启用插件包内的 plugin.json,语法文件经 pluginhost.ResolvePackagePath 逐段校验后交 Core 解码;行注释、块注释、括号与自动闭合都映射进编辑器语言表。单个语法被拒只让该语言降级为纯文本,不丢整个插件的语言。
  • 文档同步(pluginlsp):文档在版本 1 打开,之后只发增量变更;未打开的文档拒绝变更/保存;Core 解析出的 URI 作为唯一键。
  • 语言服务器生命周期:打开文件时按语言惰性启动(一个 Go 工作区不该为一个 Java 服务器付费),同一语言并发打开只起一个;关闭项目、禁用、卸载都会停止并销毁会话。
  • 诊断:Core 侧新增诊断事件解码(同时接受「事件带 uri/version + 裸数组」与「载荷自带身份」两种形状),有界存入 plugindiag,在编辑器里按单元格网格画 2px 下划线(滚动与换行时仍贴着字符),状态栏显示问题总数(单数另有措辞)。
  • 能力声明:Declared() 从空集改为 languages: [1,3]、languageServers: [1]、serverProcess: [1]。市场索引请求改为传真实能力集,条目可用性按实际支持过滤。

本轮集成:Problems / Build & Run / Agent(三个工作树压缩为一个提交)

  • Problems:接入 Core lsp.problems.query,虚拟化显示诊断并按 UTF-16 位置打开文件;诊断按插件隔离,关闭、禁用、崩溃、重启和项目切换时清理。
  • 文档同步:编辑器 open/change/save/close 接到现有 Core 会话,覆盖服务器延迟启动、增量编辑和迟到结果隔离。修正语言服务器启动计划、language_server 角色、Core root URI 和 utf16Column 字段;释放插件时使进行中的 Open 失效。
  • Build/Run:声明 buildSystems: [1]、buildProviders: [1,2];Core 检测实例、生成模型/任务/运行计划,插件提供工具知识,宿主执行原生进程并显示有界输出。支持 command/provider 任务、真实 ready、进度、取消及项目关闭清理;没有在 Go 内重写 Maven/Gradle/Cargo 规则。
  • 共享语言服务器:构建与编辑器复用会话,注册表统一同步并保留项目归属;构建先启动时同步已打开文档,关闭或停用后拒绝迟到注册。修复重复 ready/终态关闭、进度发送到已关闭通道、后台子进程持有输出管道造成退出卡死等生命周期问题。
  • Agent:通过 Core 发现和解析实际 provider 配置、显示来源及可用状态;显式安装适配器,支持进度、取消、重试;接入文本会话、流式输出、停止、权限回答、断开与项目关闭清理。对话显示有上限,截断保持 UTF-8 完整。凭据只在内存中传递,不写日志。
  • 同步中英文案、功能矩阵与决策笔记。集成检查修复 Unix 进程适配器的互斥锁值复制(go vet 失败项)。

本轮验收边界

  • 真实 clangd 通过隔离测试插件启动,验证编辑→Core→服务器→Problems、关闭/项目切换,以及构建先启动共享会话的路径;这不等于从市场安装正式语言插件已验收。
  • 官方 Cargo 0.1.0 的同一份内容重排为根目录布局后,安装、启用、模型加载和真实 cargo build 通过;此前原始 Release ZIP 布局曾被拒绝;本批已修复并以原包 E2E 替换拒绝回归。Maven 0.1.1 的 languageImport 与固定 Core 不兼容,未宣称 Maven 全流程通过。
  • Agent 使用受控适配器安装及 Core fixture 会话验证,未发起真实付费 provider 对话;Windows Job Object 仅交叉编译,尚无 Windows 原生运行证据。

编辑器性能基线与优化(本轮新增,11 个提交:5b24494b4..3031c2ff5)

  • 基线分三层:
    • rope、语法、视图三层 Go 基准,场景包括打开、输入、回车、撤销重做、粘贴、光标和翻页移动、视口染色、整篇染色、绘制、滚动、小地图、超长行。
    • 每个场景跑 100 KiB 和大文档两种规模。大文档默认 10 MiB,可用 LITHE_EDITOR_PERF_LARGE_BYTES 或 --large-bytes 修改。
    • 预算写在 desktop/benchmarks/editor-budgets.json,限定每次耗时、内存、分配次数、吞吐,以及大文档相对 100 KiB 的倍数。
    • scripts/desktop/perf/editor-bench.sh 负责运行、生成报告和 record。record 在机器不空闲时拒绝记录。超预算只报告,不阻塞,也不是 CI 必跑项。
  • 规则:AGENTS.md / CLAUDE.md 新增“编辑器性能”入口;develop-lithe 新增参考页 references/editor-performance.md;决策原因写进 Agent Note testing/2026-11-09-viva-editor-performance-baseline.md。
    • 改动前后在同一台机器上对比,写进 PR。
    • 不得上调预算、删除场景或缩小文档。
    • 绘制路径零分配。
    • 视图基准必须按窗口的方式配置视图。
  • 编辑器优化:
    • 编辑、撤销和打开不再随文档大小变慢。
    • 绘制一帧不再分配内存。
    • 超长行只读要画的那一段。
    • 撤销和重做只处理改动的那一段。
  • 染色引擎优化:正则引擎少试无望的起点,整篇染色快 7%–21%。
  • 变基到上游语言服务器同步(79bce218c)后发现一处回退:撤销、重做和重新加载把整篇文档当作一次替换报告给 OnEdit,每次复制整篇文本;超长行上每次按键还要从行首数 UTF-16 列。
    • 已修复:只报告 rope.Diff 找到的那一段,列号从较近的已知位置开始数。
    • 基准当初没有设置 OnEdit,所以没有测出这处回退;现在已补上。
  • 两处基准测错了对象,已修正(预算、场景、文档规模和生成方式都没改):
    • 绘制和滚动的计时循环改用预热过的画布。原来首帧画布扩容的成本被平摊进每一帧,显示为每帧 1–40 B;修正后 22 项结果都是 0 B/op、0 allocs/op。
    • TypeInLine 在两种规模下改为编辑同一行。原来比较的是两行不同的内容,修正后规模比为 1.02x–1.03x。

同一台 M4 MacBook Pro,大文档 10 MiB:

场景 改前 改后
按键输入(Type/go) 678 µs 46 µs
撤销/重做(UndoRedo/go) 225 ms 46 µs(1.4 KiB)
窗口线程上打开 Go 文件 224 ms 1.04 ms
粘贴 1 MiB 250 ms 3.5 ms(1.18 MiB)
稳定帧的分配 113–152 次 0

修复 OnEdit 回退前后,两份二进制交替各跑 6 轮,取中位数(两边都设置了 OnEdit):

基准 改前 改后 改前内存 改后内存
UndoRedo/go/100KiB 56.0 µs 48.9 µs 208.1 KiB 55 B
UndoRedo/go/10MiB 784 µs 46.4 µs 20.02 MiB 1.4 KiB
Paste/go/100KiB 3.49 ms 3.44 ms 3.39 MiB 1.17 MiB
Paste/go/10MiB 4.41 ms 3.49 ms 23.21 MiB 1.18 MiB
Type/go/10MiB 46.1 µs 46.1 µs 4.5 KiB 4.5 KiB
Enter/go/10MiB 34.6 µs 34.7 µs 3.1 KiB 3.1 KiB

超预算项:优化前 110 项中 52 项超出,优化后剩 20 项;两处基准修正去掉了其中 17 项不是编辑器造成的结果,剩下 3 项,都不是本 PR 新引入的:

结果 实测 预算 说明
SyntaxFullPass/go/100KiB 约 1.9 MB/s(54 ms) 20 MB/s(5.12 ms) 从头染整篇文档。TextMate 正则逐个起点回溯执行,每个起点都要试一遍 Go 语法的全部规则,开销集中在正则引擎;已快 14%。窗口里只染视口,视口冷启动约 1 ms,在预算内。
SyntaxFullPass/java/100KiB 约 2.5 MB/s(40 ms) 20 MB/s 原因同上;已快 7%。
SyntaxFullPass/markdown/100KiB 约 6.9 MB/s(15 ms) 20 MB/s 原因同上,另有 `(?:\n

desktop/benchmarks/history/m4-mbp.jsonl 里只有优化前的第一次 record(a7e52e32e,记录时工作区有未提交改动)。优化后的那次 record 按维护者要求停掉了,没有写入历史。

发现的既有性能债务。按维护者决定合并在一个 Issue 里跟踪:#1816,每项可以单独修复、单独提 PR:

  1. textmate/tokenizer.go:162:StateStack.push 每次压栈都在堆上新分配一个节点。
  2. editorview/view.go:widestLine 每次编辑都要重新找最宽的行。
  3. replaceFromDisk 在窗口线程上做 O(n) 的工作。
  4. workbench/document_observation.go:187:每次观察文档都用 bytes.Equal 比较整篇内容。
  5. syntax/semantic.go 逐行复制文本。
  6. 正则引擎是回溯执行,整篇染色吞吐受它限制。
  7. (?:\n|$) 一类模式算不出首字符,无法剪枝。
  8. 自动换行时,超长行每次编辑都整行重新换行。
  9. 小地图每次重新换行。
  10. 没有在超长行上输入的基准。

Git Log 提交详情作者行不截断(7aea0ff17)

  • 提交详情标题下的「哈希 / 作者 / 邮箱 / 日期」原来是一行,放不下时以省略号截断(如 Yao…、<23722032@bjt…)。现在四段逐段量宽、用 wrapTabs 分行,每段完整显示;某一段单独就比一行宽(如很长的邮箱)时独占一行,用 ui.Wrap 在段内折行。
  • 测试:TestLogCommitDetailsShowWholeAuthorLine 在最窄的详情面板中检查四段都完整画在面板内、没有省略号,超长邮箱独占一行且各行拼起来是完整邮箱;把 Wrap 换回 Ellipsis 时该测试失败。
  • 功能矩阵:git-history 补充证据与验证步骤。

终端标签栏横向滚动、按 Shell 新建终端、细滚动条可拖动(d12cca7c9)

项 之前 现在
终端工具窗口标签栏 放不下时裁掉右侧,无法左右移动 与编辑器组共用 drawGroupStrip:单行横向滚动、顶部细滚动条、滚轮/触控板左右滚动、⋯ 列出看不见的标签;加号和下拉箭头始终可见
新建终端 只能用登录 Shell 加号旁 ⌄ 菜单列出已安装的 Shell,选一项即以它新建终端;分隔线下「设置」打开 设置 > 工具 > 终端
新终端默认 Shell 不可设置 设置 > 工具 > 终端 选择;自动时显示实际生效的登录 Shell,已保存但不存在的路径显示「未找到」
横向细滚动条 只显示位置,拖不动 可拖动:编辑器标签栏、终端标签栏、项目树、Git Log 改动文件
底部区域默认高度 固定 260 主列高度的 42%,未拖动前随窗口变化;拖动后记住拖动值
Git Log 提交详情的分支/标签引用 单行,放不下时裁掉 换行显示
  • 终端 ⌄ 菜单在 workbench/terminal_shells.go:每次打开时在后台线程查找 Shell,查找期间显示上次的结果(第一次打开时显示查找中的提示);Esc、点击外部、打开隐藏标签列表或按钮不再绘制(工具窗口隐藏、项目关闭)时关闭。
  • Shell 查找的取舍(localshell):
    • macOS/Linux 新增 InstalledShells,列出 /etc/shells、常用前缀(/bin、/usr/bin、/usr/local/bin、/opt/homebrew/bin、/opt/local/bin)和 PATH 中每个不同的可执行文件。同一文件经符号链接或硬链接到达(如合并后的 /usr)只保留第一个位置;csh/tcsh 仍合并。
    • Windows 维持每个家族一项:PowerShell 7、Windows PowerShell、CMD、Git Bash(--login -i 启动)、Cygwin、Nushell。
    • 菜单标签用 Labels:名称唯一时只显示名称,同名多份时显示 名称 (路径),例如 bash (/bin/bash)、bash (/opt/homebrew/bin/bash)。
    • WSL 发行版(InstalledWSL)暂不列入菜单。
  • 设置窗口新增 pageTerminal(父级「工具」)与 settingswindow/terminal_view.go;Controller.OpenTerminal 打开该页并展开「工具」分组,已打开的窗口通过 requestPage 切页,插件页原来的单独切页逻辑并入其中。新建终端时未指定 Shell 就用 terminalShell 设置。
  • 细滚动条拖动:hscroll 新增 Grab / DragTo,workbench/sideways.go 统一登记滑块并处理指针。
  • 底部默认高度:固定 440 会在较矮窗口里挤掉左右工具窗口的内容(7 项 Agent 历史与 Commit 测试失败),因此改为按主列高度的比例(layoutState.bottomFor);停靠拖拽预览同用 bottomFor。
  • 测试:hscroll 拖动、TestEditorTabStripThumbDrags、gitlog_details_test、localshell 多安装与合并、Labels、settingswindow 终端页与切页。按维护者要求,终端 ⌄ 菜单本次没有新增 workbench 测试。
  • 功能矩阵:terminal-sessions 改为 Viva 证据、partial / pending,补充验证步骤与 Shell 查找取舍。

编辑器标签栏横向滚动与标签页位置设置(e92acfa84)

  • 编辑器组的标签放不下时,标签栏原来直接裁掉右侧、无法左右移动;现在横向滚动,参照旧版 macOS 标签栏:
    • 顶部细滚动条显示当前位置;滚轮和触控板在标签栏上左右滚动,纵向滚轮也转为横向。
    • 所选标签自动滚入视野(新打开、切换、从列表选择时)。
    • 右端 ⌄ 按钮列出部分或完全看不见的标签,每项带文件图标、名称、未保存标记和关闭按钮;选择后切换并滚入视野,关闭后列表保持打开;Esc 或点击外部关闭。
    • 实现在新增的 workbench/tabstrip.go:布局后读取每个标签的位置,算出滚动范围和看不见的标签,变化时再画一帧。
  • 设置 > 编辑器新增「标签页」,参照 IDEA「在以下位置显示标签页」:
    • 一行,标签页放不下时:滚动标签页面板(默认)/ 挤压标签页(标签最窄 72,名称截断)。
    • 多行:按宽度换行,软换行开关等按钮在第一行右端。
    • 写入设置文件的 editorTabRows / editorTabOverflow,修改立即生效;未知值回到默认,「恢复默认」同样复位。
  • 终端标签拖到编辑器组时,插入位置和插入竖线按行计算,多行标签栏可以放到任意一行(insertionIndex / insertionMark 增加纵坐标)。
  • 新增 desktop/internal/hscroll:横向滚动状态(范围修正、滚入视野、滑块、滚轮换算),项目树、Git Log 改动文件和编辑器标签栏共用;workbench/sideways.go 统一绘制细滚动条。项目树原来的手写横向滚动改用它,行为不变。
  • Git Log 提交详情的改动文件列表可以横向滚动,长路径不再被截断,底部显示细滚动条。
  • 删除的文件改用红色(新增调色板 Deleted,亮色 #DB3B4B、暗色 #DB5C5C),原来用次要文字色,暗色主题下显得发灰;Commit 工具窗口同样适用。
  • 测试:tabstrip_test.go 3 项(滚动与隐藏标签列表的选择、关闭、Esc、外部点击;挤压、多行与恢复默认;换行算法),editorgroups_test.go 新增跨行插入位置并扩展插入竖线,settingswindow/editor_view_test.go 覆盖设置页,hscroll_test.go 7 项,gitlog_files_test.go 新增横向滚动。
  • 功能矩阵:editor-multiline-tabs 改为 Viva 证据、partial / pending;git-history、file-explorer 补充证据与说明。

插件后端架构名修复(db2024230)

  • 插件契约和 Core 只接受 arm64 / x86_64,宿主却把 Go 的 amd64 换成插件市场用的 x64。Core 因此在 x64 的 Windows 和 Linux 上拒绝规划任何插件后端,语言服务器和构建提供者都启动不了。从 79bce218c 起 Linux CI 的 TestProviderModelTaskAndCancellation 一直因此失败。
  • 换算收成一处 core.PluginArchitecture(原来宿主和测试各写一份,其中两份是错的);新增 TestPluginArchitectureUsesContractNames,在任何架构的机器上都核对换算。插件市场的平台名(windows-x64 等)是另一套约定,保持不变。

项目树标题栏按钮与公共折叠树列表(18748cc8f)

  • 新增 desktop/internal/treelist:公共折叠树列表,负责行几何、缩进、展开箭头、悬停、有键盘焦点和失焦两种选择色、方向键/Home/End/翻页导航、双击展开和 Enter 激活,只声明可见行。行的无障碍属性报告 Selected、Expandable、Expanded、Level。项目树改用它绘制。
  • Git Log 提交详情的改动文件原来是平铺列表,现在按文件夹分组并可折叠,复用 treelist:文件夹先于文件,文件保持 Git 返回的顺序;单击箭头、双击文件夹或方向键展开收起,Enter 打开文件差异;切换提交后文件夹重新展开。
  • Project 标题栏在隐藏按钮前新增四个按钮,使用 Lucide 图标:
    • 定位当前文件:逐级列出并展开祖先目录,选中并居中目标行,下一帧把键盘焦点交给该行(同一帧的焦点请求会被按钮点击的焦点覆盖);当前文件不在项目内时按钮禁用;途中某级目录或文件已不存在时放弃,并记录 operation_rejected。
    • 刷新:重读已展开的目录;根目录读取失败时重新监听并读取。
    • 全部展开:广度优先,最多打开 512 个目录,超出时提示(新词条 desktop.project.expandAllIncomplete)。
    • 全部折叠:保留根目录,被隐藏的选择移到它所在的顶层目录,焦点留在按钮上。
  • 抽出 toolIconButton:禁用状态同时写入命中与无障碍树(ui.Box.Disabled 只调暗绘制)。项目搜索的刷新按钮改用它;Search Everywhere 改用与定位按钮共用的 projectRelativePath 计算相对路径。
  • 文件树监听未改动,说明如下:macOS 和 Windows 用 Viva 的 app.WatchTree 对项目根递归监听(FSEvents / ReadDirectoryChangesW),与项目搜索共用一条流;Linux 按已展开目录用 app.WatchDirectory(inotify),上限 128 个。事件经 Core FilterWorkspaceChanges 过滤,再由 Core ListDirectories 分批重读受影响的已展开目录。
  • 测试:treelist_test.go 8 项(几何、索引、最小滚动、居中、首行往返、键盘移动与展开、键盘滚动、双击),filetree_actions_test.go 6 项(定位、定位放弃、全部展开与折叠、512 上限、刷新、根目录恢复),gitlog_files_test.go 2 项(折叠、分组顺序)。
  • 功能矩阵更新 file-explorer、project-sidebar-header-icons、git-history、macos-shared-tree-list-control:后三项改为 Viva 证据,状态 partial / pending;尚未实现 IDEA SVG 图标、悬停渐显、标题下拉箭头、范围多选、拖放、行内编辑和插件树。

此前的审查与 CI 修复

  • 按只读代码审查报告修复 17 项缺陷,另有 2 项只做到部分:Unicode 规范化折叠(未引入 golang.org/x/text,改为把文件系统折叠导致的 EEXIST 翻译成重复路径错误)、Serve 返回后的会话自动处置(改为文档化调用方责任,避免与 Stop 双重销毁)。
  • 修复项目树同一段连续列目录失败记两条重复错误通知(Linux CI 稳定失败的真实产品缺陷)。
  • 修复 Verify Agent Notes:155 篇笔记里约 735 条指向已删除路径的引用,改成删除线加注「已随 [Feature] Windows/macOS 统一 Viva 工作台与原生编辑器,保留 Rust Core 和插件体系(分阶段重构) #1794 移除」并保留原文,校验器不再把它们当作当前入口。
  • 修复两个脚本测试:移除随旧客户端退役的 PlantUML 资源与远程构建脚本断言及对应注册表条目;测试稳定性脚本测试不再读已删除的 ci-windows.yml。

不在本次进度中 / Remaining work

  • 语言智能剩余接线:Problems、文档同步、定义跳转、Hover、显式补全已接入;自动补全、引用、只读目标、重命名及格式化仍待实现。
  • languageServers: 3 未声明。requestMethods、commands、serializedCommands、forwardNotifications、state、remote 还没有转发实现,插件后端无法驱动自己的服务器。
  • 工具发现只查 PATH。项目工具链与用户覆盖未实现,依赖它们的启动计划会被拒绝而不是凑合启动。
  • 新建项目目前只有空项目;插件提供的 Java、Maven Archetype、Spring Boot 等生成器、工具发现和完整生成事务未完成。
  • Agent 最小会话及基础 Build/Run 已接入;完整认证、历史、工具链、测试、调试和更多插件协议仍待迁移。功能矩阵区分实现状态与原生验证状态,未验收平台保持 pending。
  • macOS 原版与 Viva 的截图对照已开始,但整个界面尚未达到像素级一致;Windows 和 Linux 的原生窗口、打包与完整产品流程尚未完成验收。
  • 编辑器性能基线已建立(见上文);启动、打开项目、文件树和内存的跨平台基线,以及与 VS Code / Zed 和真实窗口的对照,尚未完成。
  • 数据迁移、签名与升级回退演练、插件 SDK 端到端均未开始。
  • PR 保持 Draft,不应合并或标记 ready。

Viva 升级

上游修复 vivaui/viva#187(Issue vivaui/viva#186)。它让 Mono 文本的布局、换行和截断都按等宽格子计算,中文等宽字符占 2 格,并新增 gfx.Canvas.WrapLinesStyle。

另一个上游修复 vivaui/viva#188:Windows 上等宽文字走 GDI,GDI 为没有 semibold 字面的 Consolas 合成 600 字重,字比格子宽,日志窗口“级别”列和工具栏的 INFO 显示成 INFC;修复后中等和半粗的等宽文字交给 Direct2D。Lithe 侧不加绕过代码。

本 PR 仍固定在 v0.5.2-0.20261109020342-0c11a3d0950d,等上游合并后再完成以下改动:

  • 升级 Viva:desktop/go.mod,以及 scripts/desktop/ 下 package-desktop.sh、generate-desktop-icons.sh、generate-desktop-file-icons.sh 里的 VIVA_VERSION,三个脚本与 go.mod 用同一版本。
  • 删除 desktop/internal/monogrid 及其测试。Git 日志详情里的提交标题(internal/workbench/gitlog_view.go 的 subjectLines 和 logView.subject* 缓存字段)改为 f.Wrap(subject).Mono().Bold().FontSize(logFontSize),并用 LineHeight 设为等宽格子高度,保持现在的行距。
  • internal/logwindow/window.go 的 mono():去掉非 ASCII 文本手动补宽度的分支,删除 printableASCII。
  • internal/logwindow/details.go 操作区下方的脱敏说明改用 f.Wrap(...).Mono(),删除 wrapDetailWords 及其两个测试,并核对行距与现在一致。
  • 保留 wrapDetailRows(按字符折行带颜色的 JSON 片段)和工具栏的 monoWidth(计算按钮宽度)。它们是正常功能,不是绕路代码。
  • 升级后运行 make -C desktop check,再用无头截图确认三处中文等宽文字不再超出边界:提交面板的分支名(不改代码,升级后自动修复)、Git 日志的提交标题、日志窗口。
  • 升级版本包含 windows: leave medium and semibold fixed-pitch runs to Direct2D vivaui/viva#188 时,在 Windows 确认日志窗口“级别”列和工具栏的 INFO 显示完整。

类型专属:feat

多平台对齐

  • 两端完整具备并验收:同一 Viva 界面面向 macOS 和 Windows;本次仅完成部分能力及针对性测试,完整对齐尚未验证。
  • [N/A] 只实现 macOS / 只实现 Windows:此 PR 目标为共用客户端,平台专属适配仍分别验证。

范围检查 / Scope checks

功能矩阵 / Feature matrix

  • 更新受影响的功能矩阵,未完成或未验证的能力保留相应状态。
  • 运行 ./scripts/platform-feature-matrix/verify-platform-feature-matrix.sh;生成视图未提交。
  • e92acfa84:更新 editor-multiline-tabs、git-history、file-explorer;结构检查与 60aadcaf3..HEAD 变更门禁通过(254 项能力)。
  • d12cca7c9:更新 terminal-sessions;结构检查与 e92acfa84..HEAD 变更门禁通过(254 项能力)。
  • 7aea0ff17:更新 git-history;结构检查通过(254 项能力)。
  • 完整变更范围核验和所有能力逐项验收仍待完成。新增的 10 个插件包与语言服务能力尚未写入矩阵。

统一日志系统 / Structured logging

  • 已接入 Core 日志目录及部分工作台失败事件;插件安装、语言服务器起停与诊断的失败事件也已接线,但全量日志门禁和所有用户流程的接线仍需验证。

界面文案 / i18n

  • 用户界面使用共享 i18n;更新中英文词条和生成结果(新增 17 条 desktop 词条,含插件安装、审查面板与问题计数)。
  • verify-app-language-localization.mjs、generate-i18n.mjs --check 和 i18n 测试通过。
  • 18748cc8f:files.collapseAll、outline.expandAll、native.revealActiveFileInProjectTree 加入 desktop 平台,新增 desktop.project.expandAllIncomplete;变基后中英词条 10,647 条完整。
  • e92acfa84:新增 desktop.editor.hiddenTabs 及 desktop.settings.editorTabs 等 7 条设置页词条;变基后中英词条 10,711 条完整。
  • d12cca7c9:新增终端 Shell 菜单与终端设置页词条,desktop 平台复用 native.newTerminalWithShell 等已有词条;中英词条 10,714 条完整。
  • 新增词条的两个语言包都已接上访问器;只接英文会让中文界面在调用时 panic,已由测试覆盖。

逻辑归属 / Logic placement

性能与内存 / Performance and memory

  • 编辑器的输入、撤销、粘贴、打开、染色、绘制、滚动和超长行已有 100 KiB / 10 MiB 两种规模的本地基线,改前改后对比见上文;剩下 3 项超预算结果已逐项说明。
  • 部分搜索、文件树、子进程生命周期与插件解压有有界和取消测试;启动、打开项目和内存的跨平台前后基线尚未完成。

设置 / Settings

  • 终端默认 Shell 只写 Lithe 自己的设置文件 terminalShell(不对应外部工具配置),回显当前值;自动时显示实际生效的登录 Shell,已保存但不存在的路径显示「未找到」。
  • 编辑器「标签页」设置只写 Lithe 自己的设置文件(不对应外部工具配置),回显当前值,「恢复默认」复位;未知值回到默认。
  • 插件页可显示安装状态、安装、启用/禁用与卸载。
  • 设置界面的真实工具配置来源、分类、说明和全功能对齐仍需逐项验收。

插件与共享契约 / Plugin and shared contracts

  • languages: 3 与 languageServers: 1、serverProcess: 1 已声明;声明依据写在 pluginhost.Declared 的注释里,未实现的能力不声明。
  • 合并前在本地补齐了 lsp.syncDocument / saveDocument / closeDocument / request 的 Go 入口(契约已有,客户端此前未包)。
  • 真实插件消费者(io.github.lithe-idea.java-support)仍未装上并跑通;languageServers: 3 的插件驱动能力未实现。
  • 合并前的 lithe-plugin-docs 三个 SDK 端到端测试尚未执行。
  • 合并后需按仓库规则在 lithe-plugin-docs 同步合并后的契约并登记协议变更。
  • [N/A] 本次未新增或修改插件宿主方法、通知;不新增 API 编号。

发行包与资源 / Bundle and resources

  • 运行静态 bundle 只读检查;新增计时报告的 worktree 复用规则和文档已更新。
  • 资源复用测试 36/36 通过;已移除随旧客户端退役的 Markdown/PlantUML 资源与远程构建脚本条目。
  • 插件运行时状态写入平台数据目录(插件根下的 .state/<id>/<version>),不写 bundle 或安装目录。
  • 完整生产打包、签名、更新与资源生命周期待验收。

无用代码与重构 / Dead code and refactoring

验证 / Verification

本批集成在 macOS 上验证,旧分支的结果不冒充本批结果:

命令或操作 结果
make -C desktop fmt-check;普通及 -tags dev 的 vet ./... 通过
desktop-go.sh test ./... -count=1 -timeout=600s 37 个有测试的包通过
pluginbackend / pluginservice / buildprovider / buildprocess 全包竞态;workbench 全包及最终 core / pluginlsp / workbench 受影响测试竞态 通过
test -tags dev ./internal/buildinfo;build ... ./cmd/lithe 通过
真实 clangd、官方 Cargo 内容及关键回归的逐项计时 12 条结果全部通过;报告 .artifacts/test-stability/integration.json / index.html(本地,不提交产物)
i18n 生成检查、用法检查及 40 项脚本测试 通过;10,617 条中英词条完整
功能矩阵结构及精确 PR base/head 变更门禁 通过;253 项能力
Agent Notes 校验 221 篇 active 笔记通过
runtime bundle immutability macOS/Windows 资源路径静态检查通过,不等于发行包运行时验收
测试稳定性静态门禁、git diff --check 通过;静态扫描不覆盖 Go,Go 另跑竞态与计时
scripts/desktop/perf/editor-bench.sh(110 项,macOS M4) 改前 52 项超预算,改后 3 项(均为 FullPass,已说明);只报告不阻塞
go -C desktop test ./internal/editor/... ./internal/editorview/...;desktop-go.sh vet ./... 通过
node --test scripts/desktop/perf/editor-perf-report.test.mjs 通过
架构名修复后 core / buildprovider / workbench / pluginbackend / pluginservice 测试与 vet 通过(本机 arm64;x64 路径由 Linux CI 验证)
工作树资源复用测试、Agent Notes 校验(含新增性能基线笔记) 通过
18748cc8f 变基到 72c2a92da 后:desktop-go.sh test ./... -count=1、vet ./...、gofmt -l;treelist、项目树按钮、Git Log 改动文件共 16 项新测试 通过
18748cc8f:i18n 生成与用法检查;功能矩阵结构及 72c2a92da..HEAD 变更门禁 通过;10,647 条词条,253 项能力
e92acfa84 变基到 60aadcaf3 后:desktop-go.sh test ./... -count=1(41 个包)、vet ./...、gofmt -l;标签栏、设置页、hscroll、跨行插入、Git Log 横向滚动共 14 项新增或扩展测试 通过
e92acfa84:i18n 生成与用法检查、测试稳定性门禁、功能矩阵结构及 60aadcaf3..HEAD 变更门禁 通过;10,711 条词条,254 项能力
d12cca7c9:desktop-go.sh test ./... -count=1(41 个包)、vet ./...、gofmt -l;localshell、termview 的 GOOS=windows 与 GOOS=linux 交叉 vet 通过
d12cca7c9:i18n 生成与用法检查、测试稳定性门禁、功能矩阵结构及 e92acfa84..HEAD 变更门禁 通过;10,714 条词条,254 项能力
7aea0ff17:desktop-go.sh test ./... -count=1(41 个包)、vet ./...、gofmt -l、测试稳定性门禁;新增测试的变异检查(换回 Ellipsis 时失败) 通过

未执行或仍有缺口:终端 ⌄ 菜单、终端标签栏横向滚动、细滚动条拖动、底部 42% 默认高度、Git Log 引用与作者行换行尚未在真实窗口验收;Windows 的 Shell 查找(Git Bash、PowerShell 7 等)与 Linux 的 Shell 列表只做了交叉 vet,未在真机运行,workbench 依赖 cgo 的 Core,无法交叉 vet;编辑器标签栏的滚动、隐藏标签列表、挤压与多行,以及 Git Log 改动文件横向滚动只有无头测试,尚未在真实窗口中用触控板验收;项目树按钮与 Git Log 改动文件折叠只有单元测试,尚未在真实窗口中点按验收;Windows/Linux 原生窗口与完整工作流、真实商业 Agent provider、市场正式语言插件端到端、完整打包发布与升级、插件 SDK 端到端;编辑器基线以外的跨平台性能基线、与 VS Code / Zed 的对照、真实窗口中的帧时间测量,以及优化后的 record。迁移分支中已移除的旧日志/Core 契约检查未当作通过;原生平台矩阵保持 pending。固定 Core 构建仍有既有 unused/dead-code 警告及本机 Rust 静态库 macOS 最低版本链接警告,本次未更改 Core 修订或发布兼容性声明。

最新 CI 以本 PR 推送后的运行结果为准,PR 保持 Draft。

提交前确认 / Final checks

  • 目标分支为 preview,PR 保持 Draft。
  • 全部本地门禁通过后再考虑 ready;本次按维护者要求先同步进行中的进度。
  • 本次新增提交未包含 .artifacts/、编译产物、日志、截图或经定向扫描发现的凭据及机器专属用户目录。
  • 本轮启动的本地 Lithe 对照实例和测试进程已关闭。

新目录 desktop/ 是 Viva v0.4.0 的 Go 客户端,业务规则全部调用 Lithe-core-rust 的 C ABI,Go 侧只做 I/O 和绘制。

- internal/core:内核桥接,类型化的最近项目、Git、外框布局(workbench.chromeLayout.reduce)和日志命令
- internal/logstore:统一日志 v2 写入端,编码、去重、查询、保留、收集都由内核计算
- internal/logwindow:一比一复刻 macOS 日志与诊断窗口(工具栏、通道、直方图、记录表、详情与调用链)
- internal/workbench:欢迎页、工作台外框、六个工具窗口锚点、按钮拖放插入线、齿轮菜单、设置对话框、只读 Git 工具窗口
- internal/monogrid:把等宽文字放上 Viva 的格子,并按格子宽度断行
- internal/i18n:类型化的 Go 中英文案
- internal/term、localshell、shellkind:从 motty 搬入的终端,尚未接线
- scripts/desktop/:固定内核提交的构建入口和 go 包装脚本
- CI 分类、资源复用清单、docs/ci-builds.md 和 develop-lithe 目录表同步登记 desktop/

Refs #1794
@ghfind-review ghfind-review Bot added the review: high ghfind author score; see https://ghfind.com label Nov 7, 2026
- logwindow:export.go 实现导出状态机(范围、通道、环境信息选项;打开或改选项即暂存,确认后只打包暂存目录),exportsheet.go 按 DiagnosticsExportSheet 绘制面板;工具栏“导出”和详情“导出诊断包…”启用
- 暂存目录在用户缓存目录 Lithe Desktop/diagnostics-export/<id>,重新规划、取消、完成和关窗时删除;失败记录 app.diagnostics.export_failed 并带 stage
- core:diagnostics.redactText 与 diagnostics.buildManifest 的类型化调用
- logstore:Collect 按内核解释的导出条件收集记录并按文件分组
- sysinfo:系统名称与版本、CPU 核数、常驻内存、磁盘剩余空间(macOS/Linux/Windows)
- i18n:导出面板中英词条
- 尚未收集系统崩溃报告(macOS 在非当前会话范围会附带)
- 终端:每个项目可开多个终端,标签可拖进编辑区加入分组或向边缘分屏、在分组间移动、拖回终端工具窗口;分屏可嵌套,分隔条支持鼠标与键盘调整。分组状态由内核 editor.workbenchState.reduce 计算,Go 侧只保存分屏布局
- termview:从 motty 搬入终端视图(行缓存、按帧预算解析输出、选择、输入、滚动条、光标闪烁),出处见 NOTICE
- 标题栏:项目头像、分支按钮、项目菜单(新建/打开/克隆/打开的项目/最近项目)
- 设置:独立设置窗口(搜索侧栏、分类树、面包屑、卡片、恢复默认/取消/好),目前只有“通用”页有内容
- 日志:新增 terminal.session.start_failed 事件(固定的内核目录尚未收录)
- localshell 测试改为带期限等待 Done(),不再轮询
- 提交工具窗口:变更列表(逐文件勾选暂存/取消暂存、全选、按状态着色、重命名显示 旧 → 新)、可拖动的分隔条、提交信息多行输入(⌘Enter 提交)、修改上一次提交(amend)、提交与提交并推送按钮
- 执行流程由内核 git.workspaceCommitPrepare/Step 计算:需要确认的计划、提交并推送和重试都先弹出审查框,逐仓库列出步骤、分支与暂存文件;推送目标有分歧时让用户选择发布到新分支还是推到上游
- 中途失败保留执行结果,可查看每个仓库的状态并重试剩余步骤;失败按 app.notification.operation_failed 记录 operation 与 error
- core:WorkspaceCommitPrepare/Step、GitStage/GitUnstage/GitStageAll 的类型化调用
- i18n:提交窗口与审查框中英词条
- 暂未提供:嵌套仓库发现、变更列表分组、回滚与右键菜单、差异预览、AI 生成提交信息、半选复选框样式
- 分支弹窗:搜索框、新建分支、检出标签或修订;最近、本地、远程、标签四组可折叠,搜索时不显示“最近”;每行显示上游与 ↑/↓ 计数,右侧子菜单提供检出、从此处新建、重命名和删除
- 分支名由内核 git.branchNamePreview 清理与校验,输入时实时替换空格和非法字符,重名、与远程冲突、目录冲突等给出提示
- 检出前由 git.checkoutPreflight 找出会被覆盖的本地改动,弹窗列出文件并提供智能检出(暂存后恢复)与强制检出
- 删除前确认,删除后通知显示原提交;失败按 app.notification.operation_failed 记录 operation 与 error
- core:GitCheckoutPreflight/GitCheckout/GitCheckoutRevision/GitCreateBranch/GitRenameBranch/GitDeleteBranch/GitBranchNamePreview 的类型化调用
- i18n:分支弹窗与对话框中英词条
- 删除 rust/(lithe-core 等 crate)、macos/、windows/、frontend/、Plugins/、shared/、third_party/,以及 Package.swift、bun 配置和只服务这些目录的脚本与工作流(ci-macos/windows/plugins/remote、release-*、verify-i18n/logging/platform-feature-matrix/host-language-specifics)和 .github/actions
- 内核改由 Lithe-IDEA/Lithe-core-rust 提供:scripts/desktop/build-desktop-core.sh 克隆固定提交并构建静态库;界面库来自 github.com/vivaui/viva v0.4.0
- .gitignore 忽略已删除的顶层目录,避免旧工作树里的构建产物(windows/、frontend/ 的 node_modules 等)变成未跟踪文件
- 应用图标从 macos/Resources/AppIcon.png 移到 desktop/assets/icon.png;Agent Notes 看板改用这个图标,并去掉生成功能矩阵的步骤
- core/errors.go 的注释改指 Lithe-core-rust 中的错误码定义
- 没有改动 .agents/(Skill、Agent Note)、docs/、README、AGENTS.md、CLAUDE.md 和 PR/Issue 模板;其中仍指向已删除路径的内容另行处理
- desktop/Makefile:make run/dev、build、core、test、vet、fmt、fmt-check、check、package、release、clean;所有 Go 命令都经过 scripts/desktop/desktop-go.sh 链接内核。make dev 等同 make run:viva dev 的热重载进程不转发原生文本输入事件
- desktop/viva.toml:应用名 Lithe Desktop、包标识 app.lithe.viva(不与 SwiftUI 客户端的 app.lithe.desktop 冲突)、版本 0.7.0、最低 macOS 13.0
- scripts/desktop/package-desktop.sh:构建 release 内核,用 -X main.version 写入 viva.toml 的版本,再用 viva package 生成 .app、zip 和 dmg;--arch amd64 在 Apple 芯片上交叉构建 x86_64
- build-desktop-core.sh:支持 LITHE_CORE_TARGET 交叉编译,暂存目录按 Rust target 分开;release 构建关闭 build-override 的 strip,否则 macOS 上 proc-macro 动态库被剥离后报 can't find crate;标准输出只输出暂存目录
- desktop-go.sh:按 GOARCH 选择内核 target
- 版本只保留 main.version 一个来源:欢迎页和设置窗口改用 Options.Version,删除 workbench 中写死的 0.1.0-dev
- Desktop CI:Linux 检查 gofmt、脚本语法和脚本测试;macOS 运行 make check,Draft PR 跳过
- Desktop Release:desktop-v*.*.* 标签或手动触发;校验版本与 viva.toml 一致、发布说明 docs/releases/desktop-v<版本>.md;arm64、amd64 各自打包并检查版本、架构、签名和 dmg;以 prerelease 发布,避免旧客户端的更新器读到它。签名沿用 MACOS_SIGNING_CERTIFICATE / MACOS_SIGNING_PASSWORD / MACOS_SIGNING_IDENTITY,未配置时不签名,配置不完整时失败;暂不公证
@xiaoyumuxi xiaoyumuxi changed the title feat(desktop): #1794 Viva 桌面客户端外壳:欢迎页、工作台外框、日志与诊断窗口 feat(desktop): #1794 Viva 桌面客户端:工作台、Git 提交与分支、打包发布,并删除旧内核与旧客户端 Nov 8, 2026
- desktop-go.sh、build-desktop-core.sh、package-desktop.sh 改为 bash,按宿主 GOOS/GOARCH
  选择内核 target(Windows 用 x86_64-pc-windows-gnu),Windows 路径经 cygpath 转换,
  CGO_LDFLAGS 把 liblithe_core.a 放在 rustc 报告的系统库之前
- 打包交给 viva package 构建程序(--exec 传入 cgo 环境),viva 工具固定 v0.4.0,
  安装到 .artifacts/desktop/tools/;macOS app/zip/dmg、Windows nsis/zip、Linux tar.gz/deb
- 版本只有一个来源:desktop/version.go 嵌入 viva.toml,去掉 -X main.version
- viva.toml 增加 [windows]、[linux] 元数据
- Desktop CI:shellcheck;Linux vet/test;Windows vet/test(Draft 不跑)
- Desktop Release:darwin arm64/amd64、windows amd64、linux amd64/arm64 五个任务,
  package-release.sh 按平台检查版本、架构、签名和校验和;Windows 签名沿用原有 secrets
- internal/editor/rope:带快照和撤销的 rope 文本缓冲
- internal/editor/textmate:vscode-textmate 9.3.2 算法的 Go 移植,Java、Makefile、CMake、
  Groovy 四个样例与 vscode-textmate + vscode-oniguruma 逐 token 一致;每行约 16 µs
  (同一样例上 vscode-textmate 约 29 µs)
- internal/editor/textmate/internal/regexp2:regexp2 v1.12.0 的修改副本,字符集 ASCII 位图、
  必含字面量预筛、确定性回溯上限代替超时、无分配的 FindRunesSpans;改动见 NOTICE.md
- internal/editor/syntax/grammars:随客户端提供的四个语法及其许可证
- scripts/desktop/generate-textmate-goldens.mjs:用参考引擎重新生成 token 金样
@xiaoyumuxi xiaoyumuxi changed the title feat(desktop): #1794 Viva 桌面客户端:工作台、Git 提交与分支、打包发布,并删除旧内核与旧客户端 feat(desktop): #1794 Viva 桌面客户端:工作台、Git 提交与分支、编辑器引擎、三平台打包发布,并删除旧内核与旧客户端 Nov 8, 2026
xiaoyumuxi and others added 17 commits November 8, 2026 19:55
Lithe-core-rust#1(workbench.chromeLayout.reduce)已合入 main,固定版本从 PR 头部提交改为合并提交。
- 新增 C/C++、CSS、dotenv、Go、HTML、INI、JavaScript/JSX、JSON/JSONC、Markdown、Rust、TOML、TypeScript/TSX、XML、YAML 的语法文件,每个上游各附许可证,NOTICE.txt 列出来源与版本。
- CMake 与 Groovy 语法只用作引擎测试样例,移到 textmate/testdata/grammars,并附带许可证与 NOTICE。
- ParseGrammar 按内核校验插件语法的规则读取(plugins/grammar.rs):键名精确匹配,重复键取最后一个,未读的键当作注释;捕获组编号上限与内核一致,flag 接受 true、1 和 "1"。
- 正则翻译支持 Oniguruma 的字符类交集(&&)、子表达式调用和码点写法,无法翻译的写法明确报错。
- 黄金样例覆盖全部内置语言(samples.json 列出样例与 scope),生成脚本同时读取内置语法与测试专用语法。
- 语言注册表按文件名和扩展名找语言;语法在打开对应文件时才解析,外部 include 按需加载。
- 增量高亮在后台分词,按检查点只重算改动之后的行,结果按行缓存并限定内存。
- 主题把 TextMate scope 和语义 token 映射到 Lithe、IDEA、VS Code、GitHub、One 配色(附许可证),支持亮色/暗色、注释斜体和自定义规则。
- 侧栏按钮拖离侧栏后进入脱离状态,幽灵按钮跟随指针并保留按下位置;回到任一侧栏时重新吸附,脱离与吸附边界之间留有缓冲带。
- 脱离状态下按左、底、右三个停靠区(打开的窗格或记住的位置)判定落点:来源停靠区优先,可见的侧栏按钮优先于停靠区,并以高亮框和“移到左侧上方”等标题预览落点。
- 侧栏的分组区改为紧贴底部分组之上,三组的视觉顺序与落点一致。
- 落点判定只依赖几何和指针位置,新增不依赖时钟和动画的单元测试,以及中英文的拖放流程测试。
- 用等宽网格绘制行号、当前行、选区、按语法着色的文本和闪烁光标;制表符按 4 列展开,中日韩等宽字符各占两列,横向和纵向滚动条按内容显示。
- 编辑基于 rope:方向键、Home/End、PageUp/PageDown 移动,⌥ 按词移动,⌘ 到行首/行尾或文档首尾,Shift 扩展选区;单击、双击、三击按字符、词、行选择并支持拖动。
- 回车沿用缩进,在括号之间把右括号放到新行;Tab/Shift-Tab 缩进或反缩进选中的行,退格按缩进单位删除。
- 撤销按词合并连续输入、合并连续删除;⌘Z/⇧⌘Z、⌘A/⌘C/⌘X/⌘V、⌘S 保存;输入法组字通过 Viva 的文本输入接口显示。
- 修改状态与保存时的文本比较,撤销回保存时的内容即为未修改。
- 新增 documents.go:后台读取文件(16 MiB 上限、二进制嗅探),只为打开的文件按语言注册表取语法
- 文件作为 "file:N" 成员进入内核的编辑器分组,与终端标签共用分组、关闭和激活逻辑
- 文档标签页显示完整文件名、未保存圆点与关闭按钮;⌘S 后台写回并保留文件权限
- 关闭标签或项目时保存未保存的文件,保存失败时保留标签并写日志、提示用户
- 键盘、快捷键、滚轮、输入法和指针事件路由到获得焦点的编辑器视图
- 新增中英文案:关闭标签、打开失败、保存失败、非文本文件、文件过大
- 新增 core.ListDirectories,通过 Core 的 workspace.listDirectories 按需列出展开的目录
- Project 工具窗口显示项目文件树:单击打开文件、展开/折叠目录、方向键导航
- 文件名不再用省略号截断:内容宽度按最宽的行测量,横向滚轮或 Shift+滚轮横向滚动,底部显示滚动条
一行文字原来画成一个标签,对应一张纹理。压缩成一行的 JSON 有几十万点宽,
超过 macOS 渲染器 16384 像素的纹理上限,渲染器报
"failed to rebuild text label textures" 后程序退出。

现在每行只画可见列,并按固定网格切成不超过 2048 点宽的块;块的起点对齐网格,
滚动时未变的块文本相同,纹理可以复用。语法着色的列号改为从块起点计。
项目树、编辑器标签页、提交视图和 Git 日志的文件与文件夹行,按名称显示
IntelliJ 风格的彩色文件类型图标,浅色和深色外观各用对应版本。匹配规则逐条
移植自旧 macOS 客户端的 LitheIcons.swift;构建输出目录(target、build、
node_modules 等)显示排除目录图标。

- internal/fileicons:svg/ 是旧客户端 IDEAIcons 的 76 个 SVG 原文件,
  NOTICE 和 Apache 2.0 许可证注明来源;icons.go 由脚本生成。
- scripts/desktop/generate-desktop-file-icons.sh:先用固定版本的 picosvg
  把彩色描边转成同色填充(Viva 图标编译器只保留填充颜色),再用 viva icons
  编译;CI 增加该脚本的 zsh -n 语法检查。
编辑器
- 行号裁剪到编辑区内,滚到一半的行号不再画到标签栏上;行号放到等宽格子上
  右对齐,9、10、100 的数位在同一列。
- 自动换行:编辑器分组标签栏最右侧加开关(RoleSwitch,读屏可读开/关),
  开启后长行按分组宽度折行,所有打开的文件一起切换,只在本次运行内有效。
  - 在空格、制表符之后和中文等宽字符前后断行,超过一行宽度的单词在词内断开;
    制表符保持整行的制表位。
  - 折行的行只在第一行显示行号;上下键、点击和选区高亮都按显示行计算。
  - 窗口变宽变窄时只重排比可见列更宽的行;只有可见行会拆成具体的显示行,
    缓存最多 512 行。300 KB 的单行折行约 6 ms。
  - 开关自动换行时,原来在顶部的行(包括滚出视口的部分)保持在顶部。
- 新增 Lucide 的 wrap-text 图标,中英词条“自动换行 / Soft Wrap”。

Viva
- 升级到 v0.5.1-0.20261108113640-16ca40bd07ab(上游 b357903,含
  roundPosition 修复),图标生成脚本和打包脚本同步改成这个版本。
- monogrid 的注释改成新版的等宽排版方式。

应用图标
- 窗口图标(主窗口、设置、日志与诊断)改用 Lithe 图标,取自
  desktop/assets/icon.png(assets.Icon,通过 go:embed 嵌入)。
- make build / run / dev 带 dev 构建标签(internal/buildinfo),Dock 图标上显示
  “Dev”角标,做法同 motty(SetDockBadge)。make package 和发布构建不带这个标签。

文件图标
- 补齐旧客户端图标目录里还没映射的图标:类型和符号(class、variable)、目录
  标记(源码根、资源根、模块、包,由显式标记决定,不按目录名猜)、四种断点状态。
- 生成脚本把文件名里的所有下划线换成连字符,db_set_breakpoint.svg 生成
  DbSetBreakpoint;NOTICE 补上新增 SVG 的来源。
日志存储写入时开启目录校验,级别或字段和目录声明不一致的事件会被丢弃。下面这些失败原来都没有写进日志。

- 打开、保存失败改用目录声明的 error 级别和字段:打开失败带 reason(not_text、too_large、not_found、permission_denied、read_failed),保存失败带 document、revision 和 stage=write;去掉目录里没有的 extension。
- 文件树列目录失败改用内核已有的 app.workspace.directory_skipped,每个没有列出的文件夹记一条,根目录记为“.”。原来用的 workspace.tree.list_failed 不在目录里。
- 分支名预览失败时,app.notification.operation_failed 的级别从 warn 改为目录声明的 error,并和其他 Git 操作失败共用 gitOperationFailed。
- 编辑器视图新增 Revision,编辑、撤销和重做都会让它加一,保存失败的日志用它区分同一个文件的多次保存。
- 新增测试:用真实内核开启目录校验,编码这些事件。
恢复共享契约与本地化资源;接入 Core 搜索及增量索引、文件外部变更与受保护保存,并展示插件市场目录。插件安装和完整跨平台验收仍待后续完成。
保留迁移前的能力清单与历史证据,明确区分 Viva 已核对与待盘点的状态,并将桌面客户端纳入矩阵变更检查。恢复发行目录静态检查,继续要求安装包运行时哈希验收。
验证隔离程序包中的工作台打开、编辑、保存和搜索不会改变包内文件;修复大型迁移差异导致稳定性扫描缓冲溢出,并核对 Agent、构建、调试和语言服务能力。
复用 Core 分页、排序和项目搜索的文件监听,支持结果打开、取消和输入法组合;类、符号、操作及完整原版交互仍待接入。
1lck and others added 4 commits November 9, 2026 00:48
前面几次提交把语言服务器从启动到诊断画完,但 Declared 仍只声明 languages,
所以任何声明语言服务器的插件都在安装阶段被 Core 拒绝——代码在跑,功能进不去。

现在声明基础版本,理由是它们真的实现了:
- serverProcess 1 的两种启动都支持:包内程序,以及宿主在 PATH 上解析的
  裸命令(上一提交补的)。
- languageServers 1 支持:宿主解析 Core 的启动计划、起进程、把打开文档按
  版本同步给服务器、接收并呈现它发布的诊断。

仍不声明更高版本,注释里写明三处缺口:
- languageServers 3 的 requestMethods / commands / serializedCommands /
  forwardNotifications / state / remote 还没有转发实现,插件后端无法驱动
  自己的服务器。
- 只有诊断有呈现面;补全、悬浮、跳转、重命名、格式化还没有编辑器入口。
- 工具发现只查 PATH,依赖项目工具链或用户覆盖的计划会被拒绝而不是凑合。

验证:gofmt、go vet、pluginhost / pluginservice / plugininstall /
pluginbackend / workbench 测试通过、i18n 与资源只读门禁。

Refs #1794

Co-Authored-By: AtomCode (deepseek-v4.1-flash) <noreply@atomgit.com>
诊断原来只有编辑器里的下划线一个出口。状态栏的 LSP 项现在显示问题总数,
没有诊断时保持原来的「语言服务未启动」文本。

- 新增 desktop.status.problems(带 count 参数)与 desktop.status.oneProblem:
  一个问题是自己的说法,否则会显示成「1 problems」。
- 两个语言包都接上访问器:只接英文会让中文界面在调用时 panic。
- pluginProblemCount 汇总各严重度计数;计数为 0 时不显示,避免把「没有问题」
  写成一条看起来像故障的状态。

验证:gofmt、go vet、i18n / workbench 测试通过、i18n 生成一致性与资源只读门禁。

Refs #1794

Co-Authored-By: AtomCode (deepseek-v4.1-flash) <noreply@atomgit.com>
lsp.request 已经在 core 里包好,但没有任何调用方,语言服务器起来后宿主
无法问它任何问题。补上两层入口:

- pluginbackend.Kernel 增加 RequestPluginSemantics;Runner.Request 填上
  Core 的会话标识并校验操作名。空操作名在本地拒绝,因为 Core 会回一个
  看起来像服务端故障的协议错误。
- pluginservice.RequestLanguageSession 让调用方只需要一个语言会话。
- 请求是异步的:返回的 operationId 就是之后 requestCompleted 事件携带的
  那个,调用方要留着它把答案对回来。

验证:gofmt、go vet、pluginbackend / pluginservice 测试通过。

Refs #1794

Co-Authored-By: AtomCode (deepseek-v4.1-flash) <noreply@atomgit.com>
Windows 与 Linux 的最小化、最大化/还原、关闭按钮由 Viva 画在自定义标题栏里。
没有样式时,Viva 按窗口清屏色推字形颜色;Lithe 没设清屏色,Viva 的默认值是深色,
于是字形是白色,浅色标题栏上看不见。

新增 windowcontrols.Style:字形用标题栏文字色,悬停和按下底色用同一颜色的
10% 和 18% 透明度,关闭按钮保留 Viva 的红色;按钮名称取共享 i18n 的
window.minimize/maximize/restore/close(该四条词条加入 desktop 平台)。
工作台、设置窗口和日志窗口都接上这个样式,随外观和语言切换。

功能矩阵 window-controls 的 Windows 状态改为部分实现:命令面板的窗口操作
尚未接入,原生窗口中的按钮行为待在 Windows 与 Linux 验证。
1lck and others added 14 commits November 9, 2026 04:26
- editorperf:确定性生成 Go、Java、JSON、Markdown、日志、混合六类文档,
  小文件 100 KiB,大文件默认 10 MiB,可用 LITHE_EDITOR_PERF_LARGE_BYTES 配置
- rope、syntax、editorview 三层的 BenchmarkEditor* 基准:绘制、输入、撤销、
  光标、翻页、滚动、粘贴、打开、重新加载、重新折行、内存,以及染色的冷热视口、
  行内输入、追赶和整篇分词
- desktop/benchmarks/editor-budgets.json:每项预算附理由,只报告不阻塞
- scripts/desktop/perf/editor-bench.sh:run、record(空闲检查后写入 perfhistory 历史)、
  compare、report;editor-perf-report.mjs 生成预算报告
- CI 只检查脚本语法、ShellCheck 和报告生成器测试,不跑基准
- 修改标记改用 rope.Equal 比较当前文本与已保存文本:共享节点直接跳过,
  不再每次按键把整篇文档复制成字节再比较
- 最宽行只重新扫描受编辑影响的行(widestLine),撤销、重做取快照里记下的宽度,
  不再整篇扫描
- 新增 editorview.Prepare/Content:建 rope、量缩进和最宽行都在 worker 上完成,
  窗口线程只拿结果;View 不再通过 cfg 持有打开时的原始字节,
  Reload、ResolveWithDisk、Snapshot 也改收 Content

同一台机器(Apple M4)改动前后对比,count 6:

| 基准 | 改动前 | 改动后 |
| --- | ---: | ---: |
| ViewType/go/10MiB | 678 µs,10 MiB/op | 62.5 µs,8.3 KiB/op |
| ViewEnter/go/10MiB | 706 µs,10 MiB/op | 43 µs,6.5 KiB/op |
| ViewUndoRedo/go/10MiB | 225 ms | 2.82 ms |
| ViewPaste/go/10MiB | 250 ms | 6.7 ms |
| ViewOpen/go/10MiB(窗口线程) | 224 ms | 1.19 ms |
| ViewPrepareReload/go/10MiB | 225 ms | 9.32 ms |
| ViewMemory/go/10MiB(打开并保留) | 228 ms,1.053/B | 12.4 ms,1.053/B |

Memory 基准自己持有输入字节,所以测不出 cfg 持有原始字节的问题;
这一项是读代码发现的,在真实打开路径上多保留约一份文件大小的内存。

PrepareReload/json 变慢约 30%(仍在预算内):最宽行扫描从窗口线程移到了这里。
UndoRedo 仍超 1 ms、Open/json/10MiB 和 Memory/json/10MiB 仍超预算,留待后续提交处理。
- 每个文字标签改为行文本的子串,不再逐帧把可见列复制进 display 缓冲再转成字符串;
  制表符、控制字符变成标签之间的空白格,非 UTF-8 字节单独画成 U+FFFD
- rope 叶子改为不可变 string,Chunks 回调改收 string;
  rope.LineCopied:行落在一个叶子里时直接返回叶子的子串,不复制;
  跨叶子的行复制一次后按 revision 缓存(最多 256 行、单行 64 KiB、合计 1 MiB)
- 行号改为从一段预先格式化好的数字窗口里取子串(lineNumbers),
  窗口只在可见行号离开它时才重建;行号位数用 decimalDigits 计算,不再 strconv.Itoa
- 折行、小地图、选区改用同一个 v.line 读取行,不再各自复制

同一台机器(Apple M4)改动前后对比,count 6:

| 基准 | 改动前 | 改动后 |
| --- | ---: | ---: |
| ViewPaint/go/10MiB | 57 µs,114 次分配,2.72 KiB | 55.3 µs,0 次分配 |
| ViewPaint/minimap/10MiB | 74.7 µs,114 次分配 | 73.1 µs,0 次分配 |
| ViewPaint/cjk/10MiB | 30.9 µs,115 次分配 | 28.4 µs,0 次分配 |
| ViewPaint/mixed/10MiB | 34.7 µs,152 次分配 | 31.8 µs,0 次分配 |
| ViewPaint/longline/10MiB | 14.2 ms,59.8 MiB,34 次分配 | 15.2 ms,10 MiB,1 次分配 |
| ViewScroll/go/10MiB | 56.9 µs,114 次分配 | 55.1 µs,0 次分配 |
| ViewCaretMove/go/10MiB | 45.9 µs,125 次分配 | 43.7 µs,0 次分配 |
| ViewRewrap/go/10MiB | 6.81 ms,8425 次分配,1.07 MiB | 6.45 ms,9 次分配,152 B |

报告里 Paint 仍有 5–127 B/op、0 次分配:这是基准为循环新建的 Canvas 第一帧扩容缓冲区,
总量约 50 KB(小地图约 400 KB),不随迭代次数增长;TestRepaintAllocatesNothing 守住稳态为 0。

已知的显示变化:语法下划线、删除线不再画过制表符占的格子。

Paint/longline/10MiB 仍要复制整行(10 MiB),留待下一个提交处理超长行。
超过 64 KiB 的行不再整行复制:

- longline.go:每 4 KiB 记一个停靠点(字节列和到该列的格数),按 revision 保存最多 64 行;
  编辑后 keepStops 保留被编辑行在编辑点之前的停靠点、前面各行的停靠点,
  以及行数没变时后面各行的停靠点,不重算
- 光标格数、点击定位(columnFrom)、选区、Tab 对齐、上下移动的目标列都从最近的停靠点往后数,
  不再从行首数起
- 绘制和小地图只取可见列附近的一段子串(part),按 (行, 折行起点, 列范围) 在本 revision 内缓存,最多 256 段
- 折行改为按 rope 的叶子逐段喂给 wrapper(wrapRange),不再先把整行拼出来;
  countRows 和小地图的折行同样不复制
- rope.Substring:范围落在一个叶子里时直接返回叶子的子串;LineCopied 改用它
- 语法高亮:tokenize 先看行长再取行,超长行不复制;indentLocked 用 Chunks 只读缩进

同一台机器(Apple M4)改动前后对比,count 6,基准为上一个提交:

| 基准 | 改动前 | 改动后 |
| --- | ---: | ---: |
| ViewPaint/longline/100KiB | 143 µs,104 KiB,1 次分配 | 1.22 µs,0 B |
| ViewPaint/longline/10MiB | 15.2 ms,10 MiB,1 次分配 | 1.25 µs,0 B |
| ViewOpen/json/10MiB | 2.65 ms,60 MiB | 75.8 µs,120 KiB |
| ViewMemory/json/10MiB 常驻 | 20.5 MiB(超预算) | 442 KiB |
| ViewMemory/json/100KiB 常驻 | 293 KiB | 61.6 KiB |

其他绘制类基准(Paint、Scroll、CaretMove、Type)同时快了 13–28%,
这次运行时机器负载与上次不同,这部分差异不全归功于本提交。
FirstUse/java +30% 是噪声:本提交没有改语法编译,改动前的基线同样是 4.0 ms 左右。

超长行停靠点和逐段绘制由 longline_test.go 对照整行结果验证(升序、降序、跳跃取列,
折行、小地图、编辑后停靠点保留与失效),TestRepaintingALongLineAllocatesNothing 守住 1 MiB 行重绘 0 分配。
TextMate 语法里大量模式以 lookahead、lookbehind 或 (^|\G) 开头,
原来的起点分析看不穿这些形状,只好在一行的每个位置都执行一遍。这次补上四项分析:

- prefix.go:模式整体没有首字符集时,再算一遍,把正向 lookahead 的内容当作首字符来源,
  例如 (?=;|\)|\]|\.|,) 只在这几个字符前尝试;否定前瞻和 lookbehind 仍然跳过
- prefix.go:交替的每个分支都以同一个锚点开头时,整个交替也算这个锚点;
  语法编译器把不能匹配的 \G 写成 (?!),这种分支不参与判断
- runner.go:以 ^ 开头的模式在行中间开始搜索时,直接跳到下一行行首,不再逐个位置试
- required.go:模式外面没有必需字面量时,改用 lookbehind 里的字面量,
  例如 (?<=\bconst\b)\s*(\w+) 必须有 "const";lookbehind 读的是搜索起点之前的文本,
  所以这类字面量在整段文本里找(anywhere)
- runner.go:Debug 开关在扫描和执行循环外读一次

Go、Java、Markdown 样例的解释执行指令数分别减少 29%、19%、41%,
进入执行的次数减少 42%、34%、64%,匹配结果数量与改前完全相同。

同一台机器(Apple M4)改动前后两个测试二进制交替运行,各 6 次,取中位数:

| 基准 | 改动前 | 改动后 | 变化 |
| --- | ---: | ---: | ---: |
| SyntaxFullPass/go/100KiB | 64.8 ms | 54.1 ms | -16.6% |
| SyntaxFullPass/java/100KiB | 45.0 ms | 40.9 ms | -9.1% |
| SyntaxFullPass/markdown/100KiB | 19.5 ms | 14.9 ms | -23.5% |
| SyntaxViewportCold/go/10MiB | 1.27 ms | 1.06 ms | -16.3% |
| SyntaxViewportCold/java/10MiB | 842 µs | 769 µs | -8.7% |
| SyntaxViewportCold/markdown/10MiB | 624 µs | 470 µs | -24.7% |
| SyntaxTypeInLine/go/10MiB | 59.6 µs | 51.2 µs | -14.2% |
| SyntaxTypeInLine/java/10MiB | 13.6 µs | 12.2 µs | -10.0% |
| SyntaxTypeInLine/markdown/10MiB | 5.48 µs | 4.86 µs | -11.3% |

FullPass/go 六次样本区间不重叠(64.0–67.1 ms 对 53.5–55.9 ms)。
TypeInLine 的 100KiB 文档变化在 ±8% 内,go/100KiB +1.2% 落在噪声范围。
FirstUse 耗时不变,首次编译分配多 4%(go 9.0→9.3 MiB,java 19.9→20.7 MiB):
没有首字符集的模式要多分析一遍,是一次性的临时分配。

整篇分词仍是约 530 ns/字节,离 50 ns/字节的预算还差一个数量级;
剩下的时间主要花在回溯执行本身。

lithe_test.go:TestStartShortcutsDoNotChangeMatches 把首字符集、Boyer-Moore 前缀、
锚点和必需字面量全部去掉作为参照,从每个起点比较匹配结果;TestStartAnalyses 确认这些形状
确实拿到了首字符集和锚点。对四项分析分别做了变异(放开否定前瞻、不比对分支锚点、
行首跳过错一位、anywhere 不从头找),测试都能失败。
原来撤销一步要把整篇旧文本放回去,高亮器丢掉所有状态从头分词,
折行布局也整个丢掉,等下一帧重新计算,一次撤销加一帧约 2.3 ms、分配 90 KiB,
超出 1 ms / 64 KiB 的预算。

- rope.go:新增 rope.Diff,找出两个版本之间改动的字节区间。同一文本的两个版本共享
  没被编辑过的节点,Diff 从头、从尾各走一遍,跳过共享节点不读,所以只读改动附近的几个叶子;
  结果不会切开 UTF-8 字符。Equal 改用同一个 common 实现
- rope.go:walker 的路径改成结构体里的定长数组加深度下标。原来用切片指向局部数组,
  append 让数组逃逸到堆上,Equal 每次比较都分配;现在 Diff 和 Equal 都不分配
- view.go:restore 用 Diff 得到的区间,像普通编辑一样调用 keepStops、hl.Edit 和 spliceRows,
  保留改动之外各行的颜色、长行列位置和折行布局

同一台机器(Apple M4)改动前后两个测试二进制交替运行,取中位数:

| 基准 | 改动前 | 改动后 | 变化 |
| --- | ---: | ---: | ---: |
| ViewUndoRedo/go/100KiB | 2.28 ms,90.5 KiB | 48.7 µs,58 B | -97.9% |
| ViewUndoRedo/go/10MiB | 2.25 ms,86.7 KiB | 45.4 µs,1.4 KiB | -98.0% |
| ViewPaste/go/100KiB | 5.84 ms | 3.46 ms | -40.8% |
| ViewPaste/go/10MiB | 5.80 ms | 3.51 ms | -39.4% |
| ViewType/go/10MiB | 46.1 µs,4.8 KiB | 45.7 µs,4.5 KiB | -0.7% |
| ViewEnter/go/10MiB | 33.8 µs,3.4 KiB | 33.7 µs,3.1 KiB | -0.1% |

UndoRedo 各 6 次、其余各 4 次。UndoRedo 现在在预算内,也不再随文档大小变化。
Paste 场景包含一次撤销,所以一起变快;Type 和 Enter 少的约 300 B 是 Equal 不再分配。

rope_test.go:随机编辑出一串版本,对每一对版本比对 Diff 和逐字节比较的结果;
补上小文本的边界情况;确认相邻版本之间 Diff 不分配。
undo_test.go:撤销和重做每一步之后,每行颜色都要等于整篇重新分词的结果,
折行行号等于重新布局的结果,长行的列位置等于整行重新计算的结果,
并且没受影响的长行保留原来的列位置缓存。等待高亮器都有 5 秒上限。
两处基准测错了对象,报告里留下 15 项并非来自编辑器的超预算结果。

绘制和滚动:benchView 把准备视图的几帧画在自己的一块画布上,计时循环
却从一块新画布开始,首帧为画布扩容的几十 KB 被平摊进每一帧,在
0 B/op 的预算上显示为每帧 1 到 40 字节。窗口每一帧复用同一块画布,
所以 benchView 改为接收调用方的画布,预热帧和计时循环画在同一块上。
之前先在循环画布上画过一帧的 selection、markers 和 longline 一直是
0 B/op,正好说明了原因。

验证:改前每帧字节数随迭代次数下降(minimap 2000 次 198 B、2 万次
19 B、8 万次 4 B,总量约 350 KB 不变),是一次性成本;改后 22 项绘制
和滚动结果在 2000 次与 2 万次下都是 0 B/op、0 allocs/op,耗时不变。

输入染色:TypeInLine 在每种规模下编辑各自文档的中间行,100 KiB 那行
是注释,10 MiB 那行是代码,规模比因此在 0.42x 到 5.93x 之间跳动,
比较的是两行内容而不是文档大小。改为每种规模都编辑小文档中间那一行
(大文档以小文档开头,这一行两边相同)。100 KiB 的结果不变(go
8.6 µs、java 27.8 µs、markdown 6.8 µs),10 MiB 的规模比变为 1.02x
到 1.03x。大文档把将近 10 MiB 文本放在编辑点之后,对“改动之后的
工作随文档增长”的检查反而更严。

预算、场景、文档规模和生成方式都没有改。
把 Viva 的性能要求写进开发规则,并记录编辑器性能基线的设计和第一次测量。

- AGENTS.md:新增“编辑器性能”入口提醒。改动编辑器模块前先读参考页;
  改动前后在同一台机器上各跑一次基线,对比写进 PR;超预算只报告不阻塞,
  但新引入的要逐项解释;不得上调预算、删除场景或缩小文档;只在空闲机器
  上 record。
- develop-lithe SKILL.md 第四节:定义热路径;新增空闲时不得重绘、热路径
  不分配也不做与总量成正比的工作、一帧开销只取决于可见范围、没有数据
  不得写性能结论四条规则,并指向编辑器参考页;后续条目顺延编号,第五节
  对虚拟化规则的引用同步改为第 14 条。
- references/editor-performance.md(新增):编辑器基线的规则真源,包括
  适用目录、何时必须运行、九条硬性规则、规模配置、运行与对比命令、读
  报告、历史记录和新增基准的写法。新增基准一节写明预热帧必须画在计时
  循环用的同一块画布上,以及有规模比预算的基准在两种规模下必须处理
  同样的文本。
- references/scripts.md:登记 scripts/desktop/perf/editor-bench.sh 和报告
  生成器。
- Agent Note 2026-11-09-viva-editor-performance-baseline:为什么用合成文档、
  三层基准、最高标准的预算、只报告不门禁、本地运行;第一次测量发现的
  六处缺陷和复核时发现的两处基准错误;正确做法与反例;被否方案;修复后
  的数字和分词器的已知状态。
- worktree-resources.json、reuse-worktree-resources 测试和 docs/ci-builds.md:
  把 .artifacts/editor-perf/ 登记为不可跨工作树复用的输出,复制入口拒绝它。
- desktop/benchmarks/history/m4-mbp.jsonl:修复前在 Apple M4 参考机上的
  第一次 record(a7e52e32e)。
上游的语言服务器同步(79bce218c)让视图通过 OnEdit 报告每次编辑。撤销、
重做和重新加载报告的是整篇替换,即使窗口没有设置 OnEdit,也每次把整篇
文档复制成字符串;每次按键还要从行首数到编辑点的 UTF-16 列,在只有一行
的大文件上与文档大小成正比。

- restore 只报告 rope.Diff 找到的那一段,文本用 Substring 共享叶子内存;
  文本没有变化的撤销步骤不报告。
- positionAt 从行首和 CaretUTF16 缓存的光标列中较近的一个开始数,超长行上
  按键只数光标和编辑点之间的字节。
- 没有 OnEdit 时,edit、restore 和 replaceFromDisk 都不计算报告;
  wholeDocumentEdit 只复制一次(原来是 Bytes 再转 string,两次)。
- edit 改为接收字符串,粘贴不再为报告多复制一份。
- 视图基准按窗口的方式配置视图:设置 OnEdit,并像状态栏那样每帧读取
  CaretUTF16。基准原来没有设置 OnEdit,所以测不到上面这些开销。
- TestEditCallbackReportsLSPRange 改为断言撤销和重做报告的范围;新增
  TestPositionAtCountsFromTheNearerKnownColumn,在含 1 到 4 字节字符的行上
  逐个光标位置核对两种数法给出的列相同。

同一台机器上两份二进制交替各运行 6 轮,取中位数(两边的基准都设置了
OnEdit):

| 基准 | 改前 | 改后 | 改前内存 | 改后内存 |
| --- | --- | --- | --- | --- |
| UndoRedo/go/100KiB | 56.0 µs | 48.9 µs | 208.1 KiB | 55 B |
| UndoRedo/go/10MiB | 784 µs | 46.4 µs | 20.02 MiB | 1.4 KiB |
| Paste/go/100KiB | 3.49 ms | 3.44 ms | 3.39 MiB | 1.17 MiB |
| Paste/go/10MiB | 4.41 ms | 3.49 ms | 23.21 MiB | 1.18 MiB |
| Type/go/10MiB | 46.1 µs | 46.1 µs | 4.5 KiB | 4.5 KiB |
| Enter/go/10MiB | 34.6 µs | 34.7 µs | 3.1 KiB | 3.1 KiB |

CaretMove 和 PageMove 在第一轮对比中变化不超过 1%,分配不变。
- references/editor-performance.md 的“新增基准”一节:视图基准必须设置
  窗口给视图的回调(例如语言服务器同步用的 OnEdit),并在每一帧读取窗口
  每帧都读取的状态(例如状态栏的 CaretUTF16);少设一个回调,视图为它
  做的工作就不在测量之内。
- Agent Note:记录变基到上游语言服务器同步(79bce218c)后,撤销把整篇
  文档报告给 OnEdit、基准却没有报出来的经过,以及修复后的数字;“正确
  做法”里的撤销示例补上只报告改动的一段。
插件契约和 Core 只接受 arm64 和 x86_64 两个架构名(契约 README 的
${arch} 与 platform.arch;Core declarative.rs 的 KNOWN_ARCHITECTURES)。
宿主却把 Go 的 amd64 换成插件市场用的 x64,Core 因此拒绝在 x64 的
Windows 和 Linux 上为任何插件规划后端:语言服务器和构建提供者都启动
不了。arm64 Mac 上名字恰好相同,所以本地没有发现;从 79bce21 起,
Linux CI 的 TestProviderModelTaskAndCancellation 一直以
“The architecture x64 is not supported.” 失败。

- core.PluginArchitecture 成为唯一的换算:amd64 换成 x86_64,其余
  原样。原来宿主和三个测试各写了一份,其中两份写成 x64。
- workbench 的语言服务器和构建提供者启动、buildprovider 和 core 的
  测试都改为调用它;删除 workbench 里断言 x64 的旧测试。
- 新增 TestPluginArchitectureUsesContractNames,在任何机器上都核对
  amd64 和 arm64 的换算,不依赖运行测试的架构。
- 插件市场的平台名(windows-x64、darwin-x64)是另一套约定,Core 的
  市场索引就用这些名字,设置窗口里的那两处不动。
xiaoyumuxi and others added 2 commits November 9, 2026 22:07
- 新增 desktop/internal/treelist:项目树和 Git Log 改动文件共用的折叠树列表,
  负责行几何、缩进、展开箭头、悬停、键盘焦点与失焦两种选择色、方向键/Home/End/
  翻页导航、双击展开和 Enter 激活,只声明可见行。项目树改用它绘制。
- Git Log 提交详情的改动文件按文件夹分组并可折叠:文件夹先于文件,文件保持 Git
  返回的顺序;单击箭头、双击文件夹或方向键展开收起,切换提交后文件夹重新展开。
  分组暂在 Go 中完成,下沉 Core 跟踪 #1814;分支面板迁移到 treelist 跟踪 #1815。
- Project 标题栏新增定位当前文件、刷新、全部展开、全部折叠四个按钮:定位逐级展开
  祖先目录后选中、居中并在下一帧聚焦目标行;刷新重读已展开目录,根目录失败时重新
  监听;全部展开最多打开 512 个目录,超出时提示;全部折叠保留根目录并把被隐藏的
  选择移到所在的顶层目录。
- 抽出 toolIconButton,禁用状态同时写入命中与无障碍树;搜索面板复用它和
  projectRelativePath。
- i18n:files.collapseAll、outline.expandAll、native.revealActiveFileInProjectTree
  加入 desktop 平台,新增 desktop.project.expandAllIncomplete 中英词条并重新生成。
- 功能矩阵:更新 file-explorer、project-sidebar-header-icons、git-history、
  macos-shared-tree-list-control 的证据、说明和验证步骤。
1lck and others added 2 commits November 9, 2026 06:40
- 编辑器组标签栏放不下时横向滚动:顶部细滚动条显示位置,滚轮和触控板(含纵向滚轮)
  在标签栏上左右滚动,所选标签自动滚入视野;右端 ⌄ 按钮列出部分或完全看不见的标签,
  每项带图标、名称、未保存标记和关闭按钮,选择后切换并滚入视野,关闭后列表保持打开,
  Esc 或点击外部关闭。新增 workbench/tabstrip.go。
- 设置 > 编辑器新增“标签页”,参照 IDEA:一行(滚动标签页面板 / 挤压标签页)或多行。
  挤压时标签最窄 72 并截断名称;多行按宽度换行,软换行等按钮在第一行右端。设置写入
  editorTabRows / editorTabOverflow,未知值回到默认,恢复默认同样复位。
- 终端标签拖到编辑器组时,插入位置和插入竖线按行计算,多行标签栏可跨行放置。
- 新增 desktop/internal/hscroll:横向滚动状态(范围、滚入视野、滚动条滑块、滚轮),
  项目树、Git Log 改动文件和编辑器标签栏共用;workbench/sideways.go 统一绘制细滚动条。
- Git Log 提交详情的改动文件列表可横向滚动,长路径不再被截断。
- 删除的文件改用红色(新增 Deleted 色),不再用暗色主题下发灰的次要文字色;
  Commit 工具窗口同样适用。
- i18n:新增 desktop.editor.hiddenTabs 和 desktop.settings.editorTabs 等七条中英词条并重新生成。
- 测试:tabstrip_test 覆盖滚动、隐藏标签列表、挤压、多行与恢复默认;editorgroups_test 覆盖
  跨行插入位置;settingswindow/editor_view_test 覆盖设置页;hscroll、gitlog_files_test 覆盖横向滚动。
- 功能矩阵:更新 editor-multiline-tabs、git-history、file-explorer 的状态、证据和验证步骤。
xiaoyumuxi and others added 3 commits November 9, 2026 23:58
- 终端工具窗口的标签栏与编辑器组共用 drawGroupStrip:放不下时单行横向滚动,顶部细滚动条,
  滚轮和触控板左右滚动,⋯ 列出看不见的标签;加号和下拉箭头始终可见,不跟随编辑器的挤压/多行设置。
- 加号旁新增 ⌄ 菜单(workbench/terminal_shells.go):在后台查找已安装的 Shell,同名多份时显示路径
  (bash (/bin/bash)、bash (/opt/homebrew/bin/bash)),选一项即以该 Shell 新建终端;分隔线下的
  “设置”打开 设置 > 工具 > 终端。Esc、点击外部或打开隐藏标签列表时关闭。
- localshell:非 Windows 新增 InstalledShells,列出 /etc/shells、常用前缀和 PATH 中每个不同的
  可执行文件(同一文件经符号链接或硬链接到达只保留第一个位置,csh/tcsh 仍合并);Windows 维持每个
  家族一项。新增 DefaultShell 和 Labels(同名才加路径)。
- 设置 > 工具 > 终端(settingswindow/terminal_view.go):选择新终端的默认 Shell,自动时显示实际生效的
  登录 Shell,已保存但不存在的路径显示为“未找到”;写入 terminalShell。Controller.OpenTerminal 打开该页
  并展开“工具”分组,已打开的窗口改用 requestPage 切页。新建终端时未指定 Shell 就用该设置。
- 横向细滚动条可拖动:hscroll 新增 Grab/DragTo,workbench/sideways.go 统一登记滑块并处理指针,
  编辑器标签栏、终端标签栏、项目树和 Git Log 改动文件都可拖动。
- 底部区域默认高度改为主列高度的 42%(未拖动前随窗口变化),停靠拖拽预览同用 bottomFor;
  固定 440 会在较矮窗口里挤掉左右工具窗口的内容。
- Git Log 提交详情的分支/标签引用改为换行显示。
- i18n:新增终端 Shell 菜单与终端设置页词条,desktop 平台复用 native.newTerminalWithShell 等已有词条,
  重新生成。
- 测试:hscroll 拖动、TestEditorTabStripThumbDrags、gitlog_details_test、localshell 多安装与合并、
  Labels、settingswindow 终端页与切页。
- 功能矩阵:terminal-sessions 补充 Viva 实现证据与验证步骤。
- 提交详情标题下的作者行原来是一行,作者名、邮箱和日期放不下时以省略号截断(如 “Yao…”、
  “<23722032@bjt…”)。现在拆成哈希、作者、邮箱、日期四段,逐段按绘制字体量宽,用 wrapTabs
  分行,每段完整显示;某一段单独就比一行宽(如很长的邮箱)时独占一行,用 ui Wrap 在段内折行。
- 新增 drawLogMeta / metaRows,宽度向上取整到整点,保证按数字放得下的行布局后也放得下;
  数组留在 logView 中逐帧复用。
- 测试:TestLogCommitDetailsShowWholeAuthorLine 在最窄的详情面板中检查四段都完整画在面板内、
  没有省略号,超长邮箱独占一行且各行拼起来是完整邮箱;把 Wrap 换回 Ellipsis 时该测试失败。
- 功能矩阵:git-history 补充证据与验证步骤。

This branch has not been deployed

No deployments
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.

2 participants