Skip to content

design: mcpp.toml 补全重做方案 —— 语义模块 + index 动态数据(#4 重做) #8

Description

@Ximiaw

mcpp.toml 自动补全 — 重做方案(#4)

状态:方案待 review,确认后动工。背景:#4 首轮实现(手写字段表)被 review 打回,本方案为重做稿。

1. 范围

做 不做
段头结构建议([package]、[targets.<name>] 等 snippet) 静态字段键/枚举值(standard、kind 等——等上游版本化 schema,接口已预留)
依赖包名 + 版本候选(动态数据) 诊断 / hover / 跳转
依赖/feature 写法模板(snippet) 市场中心 UI(二期,复用同一缓存层)

2. 架构

四层,单向依赖,跨层只传纯数据:

extension.ts        唯一 vscode 依赖:注册 provider、设置门、1:1 映射
  ↓
completion 查询层   compute(context) → 建议(每条带显式替换范围)
  ↓                ↓
parser+语义层       index 数据层
容错 TOML 解析      包名/版本候选

2.1 parser 层

  • 自写容错 TOML 解析:未闭合输入容错([dep、simd = { flags = [ { )、带 token 行列位置、contextAt(line, ch) 返回光标上下文(段路径 / 键路径 / 键位或值位 / 替换范围)。
  • 库选型已实测(2026-08,沙盒验证):
    • toml-eslint-parser:对未闭合段头/字符串/内联表、残缺键全部直接抛异常——它的错误恢复面向「完整但不合法」的 lint 场景,不面向补全时的半成品输入。排除。
    • @taplo/lib:lint 能容错并返回带 range 的错误,但 JS 绑定只暴露 lint/format/encode/decode,不暴露带位置的 AST,回答不了「光标在哪个段哪个键」。排除。
    • 结论:补全需要的是「光标上下文 + 容错位置」而非完整 TOML 一致性,自写范围收敛(段/键值/数组/内联表/字符串/注释),工程量可控且全程测试覆盖。
  • 结构性修复 review 问题 2(key/value 无替换范围)与问题 3(内嵌 flags 分支不可达)。

2.2 语义层

  • 段归属规范化、条件段规则(如 [target.<sel>.build] 只接受 build inputs——修复 review 问题 1)。
  • 规则从 mcpp 源码(src/manifest/toml.cppm)提取,注释带出处文件 + 行号 + commit hash,升级时 git diff 对照同步。

2.3 index 数据层

  • 扩展激活时后台对每个描述符执行 mcpp xpkg parse <file> --json(官方解析器,零漂移),结果缓存到扩展存储;每次补全只读缓存。
  • 只读性已实测(mcpp 2026.7.27.1):对 index 缓存内和外部目录的描述符分别执行 xpkg parse --json,前后对比 index 仓库 git 状态与 .xlings-index-cache.json mtime 均无变化——该命令直接解析单文件,不经过索引加载/缓存重建路径。考虑其重要性(见 §7),仍请上游把只读性确认为契约。
  • 缓存键 = index 仓库的 git 状态(.git/FETCH_HEAD mtime 或 HEAD commit hash),不用目录 mtime——目录 mtime 只在直接子项增删时变化,git pull 更新深层已有描述符时祖先目录 mtime 不动,会静默陈旧。项目级 path 索引通常没有 .git,fallback 为描述符集合的内容 hash(或 max mtime)。
  • 已在真实索引(mcpplibs/mcpp-index,81 包 / 16 命名空间 / 122 版本)验证输出结构:identity + versions(按 OS)+ targets + unknown_keys。
  • 降级行为:mcpp 二进制缺失、xpkg parse 失败或输出缺字段时,该数据源静默缺席,段头/模板等结构建议照常——任何数据层故障都不影响结构层。
  • 未受信任工作区不 spawn 外部进程,静默降级为纯结构建议。

2.4 跨平台

  • mcpp home 定位按 src/home.cppm 的顺序:$MCPP_HOME > 二进制自包含布局 > ~/.mcpp(Windows 为 %USERPROFILE%\.mcpp)。自包含布局的判定:二进制位于 <dir>/bin/mcpp,且祖先路径不含 target/(mcpp 源码开发构建产物)或 data/xpkgs/(xlings 包安装)——该检查只看二进制的上级目录,用户项目产生的 target/ 在项目内、不影响判定。扩展侧补充一条检查:PATH 上的 mcpp 可能是 xlings shim(符号链接追到调度器而非真实二进制),因此自包含分支额外要求 <dir>/registry 实际存在,否则落到默认 home。
  • index 路径 <home>/registry/data/mcpplibs/(src/xlings.cppm 硬编码)。
  • spawn mcpp 复用扩展现有 src/process.ts(Windows mcpp.exe)。
  • 版本候选取 linux/macosx/windows 三平台并集(manifest 可能交叉构建),semver 倒序。
  • parser 兼容 CRLF;fixture 路径全部 path.join。

3. 数据源路径(均有源码依据)

~/.mcpp/registry/data/mcpplibs/pkgs/**/*.lua   ← 库索引(唯一正确来源)

明确排除:

  • xim-pkgindex——xlings 工具链索引;
  • xim-index-repos/*——xlings 教学仓,扫错会建议出 mcpp 解析不了的包。

v1 只读默认 mcpplibs + 项目级 [indices] 的 path 索引;全局 config.toml 的 [indices] 语义实现时对照 src/pm/index_management.cppm。

4. 索引陈旧处理

  • 扩展只读不刷新(网络操作 + 工作区信任边界 + MCPP_OFFLINE 语义)。
  • 候选 detail 显示索引年龄(.git/FETCH_HEAD mtime)。
  • 超阈值追加提示「可运行 mcpp index update」,不弹窗、不打断。
  • MCPP_OFFLINE=1 或未受信任工作区:不提示。
  • 设计立场:旧索引是 mcpp 的合法状态(caret 约束本就按本地已知版本求解),候选 = mcpp 实际会解析的结果,「与 mcpp 所见一致」优先于「新」。

5. 设置

设置 类型 默认 说明
mcpp.tomlCompletion boolean true(建议改为默认开启) 补全总开关。范围已收窄为「结构 + 与 mcpp 所见一致的动态数据」,不存在生成无效配置的风险,「默认关闭」不再必要(待 wellwei 确认)
mcpp.tomlCompletionIndexStaleDays number 30 索引超过 N 天未更新时提示;0 = 关闭提示

6. 测试

  • 纯函数单测(parser / 语义 / completion / index fixture):node --test,零 vscode 依赖。
  • 替换范围、部分输入(default-p、"c++2)不破坏文本有显式断言(review 问题 5)。
  • 契约测试:段头 / 条件段规则生成最小 manifest 喂真实 mcpp,断言无 unsupported 诊断;无 mcpp 的环境 skip。硬承诺:语义层每条规则必须有对应契约测试——语义层仍是人工同步(漂移变慢但不是零),契约测试保证漂移发生时是测试红,而不是用户补全出错。
  • index 层 fixture 可用真实索引描述符(~/ln/code/test_mcpp/mcpp-index)做样本。

7. 待确认

@wellwei

  1. 段头 snippet + 写法模板是否保留?(我的立场:它们描述语法结构而非字段语义,属于允许手写的语义层)
  2. mcpp.tomlCompletion 建议默认开启(范围收窄后「默认关闭」已无必要),indexStaleDays 新设置是否接受?
  3. 重做方式:feat: add opt-in code completion for mcpp.toml #4 上 force-push 还是开新 PR?契约测试策略:本地强 CI 弱,还是 CI 钉版安装 mcpp?

@Sunrisepeak

  1. 依赖补全数据走 xpkg parse --json。建议给 --json 输出加一个 format/schema 版本字段:扩展对未知版本降级(只用结构建议)而非解析出错——把「输出结构变动请提前告知」这种单向通知变成机器可判定的契约,对上游只是一个字段的成本。
  2. 请确认 xpkg parse --json 无写副作用(不写 index cache / 不动磁盘状态)并可作为契约依赖。本机实测(2026.7.27.1)当前无写副作用,希望上游将其固定为保障。说明调用节奏:扩展不做高频调用——补全只读缓存,xpkg parse 仅在索引 git HEAD 变化后后台批量执行一次(典型触发:mcpp index update,或 mcpp add 添加本地索引中不存在的包时——cmd_add 源码确认仅本地未命中且归共享 registry 时才刷新)。

8. 演进预留

  • 上游版本化 manifest schema 落地后:数据层加 fetchManifestSchema(),查询层加 staticFieldProvider,其余不动。
  • 市场中心二期:复用 index 缓存层。
  • 远期可迁移为独立 LSP:parser + completion 原样搬进程,只重写协议胶水。

Activity

  1. Sunrisepeak commented on Aug 8, 2026

    @Sunrisepeak
    Member

    @Ximiaw mcpp xpkg parse --json 接口可以做支持, 但实现侧感觉可以不高频调用而是做数据缓存

  2. Ximiaw commented on Aug 8, 2026

    @Ximiaw
    MemberAuthor

    @Ximiaw 接口可以做支持, 但实现侧感觉可以不高频调用而是做数据缓存mcpp xpkg parse --json
    @Sunrisepeak 感谢支持!调用节奏正是缓存式的,之前「高频调用」的表述不准确,已修正:

    • 补全过程只读本地缓存,零 spawn;
    • xpkg parse 仅在索引 git HEAD 变化后后台批量执行一次(缓存键 = HEAD/FETCH_HEAD);
    • 典型触发是 mcpp index update,或 mcpp add 本地未命中的新包(读了 cmd_add 源码,
      仅本地索引查不到且归共享 registry 时才刷新)——日常可能几天都不到一次。

    只读性本机实测(2026.7.27.1)无写副作用,希望作为接口契约固定下来即可。

  3. wellwei commented on Aug 8, 2026

    @wellwei
    Member

    结合 mcpp-community/mcpp#379 的最新拆分决定和当前源码重新复核,结论是:方案适合拆成“结构补全先落地、动态索引继续等待”,不适合按现稿一次性实现。

    可以先实施的部分

    以下部分不依赖上游新协议,范围也可验证:

    • 段头结构建议;
    • 依赖/feature 写法 snippets;
    • 光标上下文与显式 replacement range;
    • provider 映射、引号闭合、部分输入和真实 mcpp parser 契约测试;
    • 数据源失败时静默降级为纯结构建议;
    • 未受信任工作区不启动外部进程。

    不过不建议为了这部分实现一个接近完整 TOML 的自制 parser。当前目标只需要回答“光标所在段、键位/值位、局部字符串或内联表位置和替换范围”,用带字符串/注释/括号状态的容错 context scanner 更容易审计,也更容易用残缺输入 fixture 覆盖。完整 parser 会扩大实现和维护面,却仍不能成为 mcpp manifest 的权威语义来源。

    动态包名/版本补全仍有阻塞

    1. Form A 仍不输出 versions

    当前 cmd_xpkg_parse 已经在进入 Form A 分支前计算了 linux/macosx/windows versions,但 --json 的 Form A 输出仍只有:

    {"namespace":"...","name":"...","form":"A"}

    最新 mcpp-index upstream/main 静态统计有 81 个 pkgs/**/*.lua 描述符,其中 63 个内联 mcpp = {},其余 18 个进入非 TableBody/Form A 路径。它们不能提供版本候选,因此“官方解析器零漂移”目前并不成立于完整索引。

    2. 缓存不能消除首次 N 进程成本

    补全过程读缓存是正确的,但第一次构建缓存、索引 revision 变化或缓存失效时,现稿仍需对约 81 个描述符分别启动 mcpp。缓存降低调用频率,不解决一次刷新时的进程数量、部分失败聚合和扩展激活后台负载。

    在批量 catalog API 出现前,不建议把动态数据层默认开启。更合适的上游接口仍是一次返回 canonical identity、三平台 versions、index revision 和逐项 diagnostics 的批量 catalog。

    3. Git HEAD/FETCH_HEAD 不是默认索引的权威身份

    mcpp/xlings 对安装索引使用:

    • .xlings-index-version:内容 revision,opaque,只比较相等性;
    • .mcpp-index-updated:最近刷新时间。

    默认索引通常是 artifact materialization,不保证存在 .git。因此不能把 HEAD/FETCH_HEAD 作为主缓存键或索引年龄;它们只适合作为用户显式 path/git index 的可选 fallback。没有 marker 的 path index 可以使用描述符内容摘要,但应避免每次补全现场全量计算。

    4. home/env 路径仍未形成只读契约

    mcpp-community/mcpp#379 已确认 self env 当前调用 load_or_init(),会创建或迁移配置并可能 bootstrap,不能直接标记为只读。扩展也不应复制完整 home/shim 推导逻辑。

    本机当前 PATH 上的 mcpp 实际是指向 xlings 的 shim;本次执行 mcpp --version 就因 shim 选择了本地索引不存在的版本而在进入 mcpp 前失败。这进一步说明动态数据层应允许完全缺席,不能把“可定位 mcpp home”当作激活阶段前提。

    建议实施顺序

    1. 新开 PR,只做 context scanner、段头/snippets、显式替换范围和 provider/parser 契约测试。
    2. 结构补全达到不生成无效文本的标准后,可以默认开启。
    3. 动态补全保持关闭或不实现,等待 Form A versions、只读 env resolver 和批量 catalog 契约。
    4. 上游提供版本化 manifest vocabulary 后,再接入静态字段键和枚举值。
    5. 不建议 force-push 重写现有 feat: add opt-in code completion for mcpp.toml #4;新 PR 建立后,将 feat: add opt-in code completion for mcpp.toml #4 标记为 superseded 并关闭,保留历史 review 上下文。

    因此 #4 当前继续保留但不合入;Issue #8 的结构层可进入实现准备,动态 index 层尚未达到可实施条件。

  4. Ximiaw commented on Aug 8, 2026

    @Ximiaw
    MemberAuthor

    结合 mcpp-community/mcpp#379 的最新拆分决定和当前源码重新复核,结论是:方案适合拆成“结构补全先落地、动态索引继续等待”,不适合按现稿一次性实现。

    可以先实施的部分

    以下部分不依赖上游新协议,范围也可验证:

    • 段头结构建议;
    • 依赖/feature 写法 snippets;
    • 光标上下文与显式替换范围;
    • provider 映射、引号闭合、部分输入和真实 mcpp parser 契约测试;
    • 数据源失败时静默降级为纯结构建议;
    • 未受信任工作区不启动外部进程。

    不过不建议为了这部分实现一个接近完整 TOML 的自制 parser。当前目标只需要回答“光标所在段、键位/值位、局部字符串或内联表位置和替换范围”,用带字符串/注释/括号状态的容错 context scanner 更容易审计,也更容易用残缺输入 fixture 覆盖。完整 Parser 会扩大实现和维护面,却仍不能成为 mcpp manifest 的权威语义来源。

    动态包名/版本补全仍有阻塞

    1. 表格A仍不输出

    当前 已经在进入 Form A 分支前计算了 linux/macosx/windows versions,但 的 Form A 输出仍只有:cmd_xpkg_parse``--json

    {"namespace":"...","name":"...","form":"A"}
    最新 mcpp-index 静态统计有 81 个 描述符,其中 63 个内联 ,其余 18 个进入非 TableBody/Form A 路径。它们不能提供版本候选,因此“官方解析器零漂移”目前并不成立于完整索引。upstream/main``pkgs/**/*.lua``mcpp = {}

    2. 缓存不能消除首次 N 进程成本

    补全过程读缓存是正确的,但第一次构建缓存、索引 revision 变化或缓存失效时,现稿仍需对约 81 个描述符分别启动 mcpp。缓存降低调用频率,不解决一次刷新时的进程数量、部分失败聚合和扩展激活后台负载。

    在批量 catalog API 出现前,不建议把动态数据层默认开启。更合适的上游接口仍是一次返回 canonical identity、三平台 versions、index revision 和逐项 diagnostics 的批量 catalog。

    3. Git HEAD/FETCH_HEAD 不是默认索引的权威身份

    MCPP/xlings 对安装索引使用:

    • .xlings-index-version:内容 revision,opaque,只比较相等性;
    • .mcpp-index-updated:最近刷新时间。

    默认索引通常是 artifact materialization,不保证存在 。因此不能把 HEAD/FETCH_HEAD 作为主缓存键或索引年龄;它们只适合作为用户显式 path/git index 的可选 fallback。没有 marker 的 path index 可以使用描述符内容摘要,但应避免每次补全现场全量计算。.git

    4. home/env 路径仍未形成只读契约

    mcpp-community/mcpp#379 已确认 当前调用 ,会创建或迁移配置并可能 bootstrap,不能直接标记为只读。扩展也不应复制完整 home/shim 推导逻辑。self env``load_or_init()

    本机当前 PATH 上的 实际是指向 xlings 的 shim;本次执行 就因 shim 选择了本地索引不存在的版本而在进入 mcpp 前失败。这进一步说明动态数据层应允许完全缺席,不能把“可定位 mcpp home”当作激活阶段前提。mcpp``mcpp --version

    建议实施顺序

    1. 新开 PR,只做 context scanner、段头/snippets、显式替换范围和 provider/parser 契约测试。
    2. 结构补全达到不生成无效文本的标准后,可以默认开启。
    3. 动态补全保持关闭或不实现,等待 Form A versions、只读 env resolver 和批量 catalog 契约。
    4. 上游提供版本化 manifest vocabulary 后,再接入静态字段键和枚举值。
    5. 不建议 force-push 重写现有功能:为mcpp.toml #4添加自愿加入的代码完成功能;新 PR 建立后,将功能:为mcpp.toml #4添加自愿加入的代码完成功能标记为 superseded 并关闭,保留历史 review 上下文。

    因此 #4 当前继续保留但不合入;Issue #8 的结构层可进入实现准备,动态 index 层尚未达到可实施条件。

    结构层已按拆分结论实现并提 PR:#9

    落地内容:容错 context scanner(显式替换范围)、段头 snippet(25 段)、
    开放段写法模板、provider 1:1 映射、契约测试(段头/模板键/条件段规则,
    真实 mcpp 验证,44 例)。mcpp.tomlCompletion 默认开启。

    留待上游契约的部分(本 PR 不含):

    #4 已标记 superseded 并关闭。

  5. wellwei commented on Aug 8, 2026

    @wellwei
    Member

    结构层已合并:#9(merge commit e8586b5),含 review 打磨项:

    • 补 [build-dependencies] 段头(26 段),新增真实 mcpp 契约用例;
    • [[...]] 数组表不提供建议(mcpp manifest 无数组表,避免语义被悄悄替换成普通段);
    • README 与 PR 描述同步更新(新增 103 例 / 全量 237 例通过,含真实 mcpp 契约测试)。

    动态层后续仍待上游契约(接口已预留,本 issue 继续跟踪):

    • Form A xpkg parse 输出三平台 versions 并集;
    • 批量 catalog 接口:一次返回 canonical identity + 三平台 versions + index revision + 逐项 diagnostics,避免首次 N 进程成本;
    • 接入机器输出协议(docs/zh/11-machine-output.md):--format json 信封 + --protocol-version 静态 effects 契约,替代 --json 裸输出与自研 home/shim 推导;self env --format json 只读已满足「只读 env resolver」前提;
    • 缓存键改用 .xlings-index-version / .mcpp-index-updated 官方 marker(默认索引无 .git);
    • 静态字段键/枚举:等上游版本化 manifest vocabulary(RFC: 统一机器可读输出协议 —— envelope + destructive + --format 归一 + 老命令永久兼容 mcpp#379)。

    「结构层先落地、动态层等契约」的拆分结论不变。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

documentationImprovements or additions to documentationenhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions