|
| 1 | +// mcpp.toml 的代码补全查询层(结构补全版)。 |
| 2 | +// |
| 3 | +// 范围:段头结构建议 + 开放词汇段的写法模板。每条建议携带显式替换范围。 |
| 4 | +// 依赖包名/版本等动态数据补全与静态字段键/枚举补全均不在本版——前者等上游 |
| 5 | +// 批量 catalog 接口,后者等版本化 manifest schema(见设计 issue #8 与 |
| 6 | +// mcpp RFC #379)。 |
| 7 | +// |
| 8 | +// 本模块不依赖 vscode API;上下文来自 mcppTomlParser 的 contextAt(容错解析)。 |
| 9 | + |
| 10 | +import { |
| 11 | + contextAt, |
| 12 | + type ReplaceRange, |
| 13 | + type SectionResolution, |
| 14 | +} from "./mcppTomlParser"; |
| 15 | + |
| 16 | +export type McppTomlSuggestionKind = "section" | "template"; |
| 17 | + |
| 18 | +export interface McppTomlSuggestion { |
| 19 | + label: string; |
| 20 | + kind: McppTomlSuggestionKind; |
| 21 | + detail: string; |
| 22 | + documentation?: string; |
| 23 | + /** 插入文本;含 $1 等 snippet 占位符。缺省时插入 label。 */ |
| 24 | + insertSnippet?: string; |
| 25 | + /** 替换范围(光标所在行的起止列)。 */ |
| 26 | + range: ReplaceRange; |
| 27 | +} |
| 28 | + |
| 29 | +export interface SectionHeaderSpec { |
| 30 | + group: string; |
| 31 | + label: string; |
| 32 | + /** snippet 形式的段头(含 ${1:...} 占位)。 */ |
| 33 | + header: string; |
| 34 | + detail: string; |
| 35 | +} |
| 36 | + |
| 37 | +// 段头结构清单:TOML 结构语法,非字段语义。出处:mcpp 文档 02/03/05/06 |
| 38 | +// 与 src/manifest/toml.cppm 的段清单(契约测试用真实 mcpp 逐段验证)。 |
| 39 | +export const SECTION_HEADERS: readonly SectionHeaderSpec[] = [ |
| 40 | + { group: "package", label: "[package]", header: "[package]", detail: "包元数据" }, |
| 41 | + { group: "lib", label: "[lib]", header: "[lib]", detail: "库根模块约定" }, |
| 42 | + { group: "build", label: "[build]", header: "[build]", detail: "构建配置" }, |
| 43 | + { group: "generated_files", label: "[generated_files]", header: "[generated_files]", detail: "生成文件(路径 → 内容)" }, |
| 44 | + { group: "dependencies", label: "[dependencies]", header: "[dependencies]", detail: "运行时依赖" }, |
| 45 | + { group: "dev-dependencies", label: "[dev-dependencies]", header: "[dev-dependencies]", detail: "开发/测试依赖" }, |
| 46 | + { group: "build-dependencies", label: "[build-dependencies]", header: "[build-dependencies]", detail: "构建期依赖(仅构建期拉取,运行时不可见)" }, |
| 47 | + { group: "workspace", label: "[workspace]", header: "[workspace]", detail: "工作空间成员声明" }, |
| 48 | + { group: "workspace.dependencies", label: "[workspace.dependencies]", header: "[workspace.dependencies]", detail: "集中声明依赖版本,成员用 workspace = true 继承" }, |
| 49 | + { group: "features", label: "[features]", header: "[features]", detail: "feature 定义" }, |
| 50 | + { group: "feature-deps", label: "[feature-deps.<name>]", header: "[feature-deps.${1:name}]", detail: "由 feature 拉取的可选依赖" }, |
| 51 | + { group: "capabilities", label: "[capabilities]", header: "[capabilities]", detail: "capability 绑定(provider 选择)" }, |
| 52 | + { group: "targets", label: "[targets.<name>]", header: "[targets.${1:name}]", detail: "构建目标" }, |
| 53 | + { group: "profile", label: "[profile.<name>]", header: "[profile.${1:name}]", detail: "构建档案" }, |
| 54 | + { group: "runtime", label: "[runtime]", header: "[runtime]", detail: "主机运行时能力" }, |
| 55 | + { group: "resources", label: "[resources]", header: "[resources]", detail: "编译进产物的元数据与资产(仅 PE 目标)" }, |
| 56 | + { group: "toolchain", label: "[toolchain]", header: "[toolchain]", detail: "编译器工具链简写" }, |
| 57 | + { group: "xlings", label: "[xlings]", header: "[xlings]", detail: "构建环境(xlings 供给)" }, |
| 58 | + { group: "xlings.workspace", label: "[xlings.workspace]", header: "[xlings.workspace]", detail: "固定工具版本" }, |
| 59 | + { group: "xlings.envs", label: "[xlings.envs]", header: "[xlings.envs]", detail: "工具环境的环境变量" }, |
| 60 | + { group: "target", label: "[target.<triple>]", header: "[target.${1:x86_64-linux-gnu}]", detail: "按目标三元组的配置" }, |
| 61 | + { group: "pack", label: "[pack]", header: "[pack]", detail: "mcpp pack 打包配置" }, |
| 62 | + { group: "pack.bundle-project", label: "[pack.bundle-project]", header: "[pack.bundle-project]", detail: "vendored 过滤策略微调" }, |
| 63 | + { group: "indices", label: "[indices]", header: "[indices]", detail: "项目级索引重定向" }, |
| 64 | + { group: "tools.overrides", label: "[tools.overrides]", header: "[tools.overrides]", detail: "host 工具二进制覆盖" }, |
| 65 | + { group: "language", label: "[language]", header: "[language]", detail: "旧版兼容字段;新项目请用 [package].standard" }, |
| 66 | +]; |
| 67 | + |
| 68 | +/** 依赖类段(键位置给依赖写法模板)。 */ |
| 69 | +const DEPENDENCY_GROUPS: ReadonlySet<string> = new Set([ |
| 70 | + "dependencies", |
| 71 | + "dev-dependencies", |
| 72 | + "build-dependencies", |
| 73 | + "workspace.dependencies", |
| 74 | + "feature-deps", |
| 75 | +]); |
| 76 | + |
| 77 | +interface TemplateSpec { |
| 78 | + label: string; |
| 79 | + detail: string; |
| 80 | + documentation?: string; |
| 81 | + insertSnippet: string; |
| 82 | +} |
| 83 | + |
| 84 | +const DEPENDENCY_TEMPLATES: readonly TemplateSpec[] = [ |
| 85 | + { |
| 86 | + label: 'name = "version"', |
| 87 | + detail: "SemVer 版本依赖", |
| 88 | + documentation: "默认 caret 约束(^);也支持 ~、= 与 \">=1.0, <2.0\" 范围组合。", |
| 89 | + insertSnippet: '${1:name} = "${2:1.0.0}"', |
| 90 | + }, |
| 91 | + { |
| 92 | + label: "name = { path = ... }", |
| 93 | + detail: "路径依赖(本地开发)", |
| 94 | + insertSnippet: '${1:name} = { path = "${2:../mylib}" }', |
| 95 | + }, |
| 96 | + { |
| 97 | + label: "name = { git = ..., tag = ... }", |
| 98 | + detail: "Git 依赖(tag / branch / rev 三选一)", |
| 99 | + insertSnippet: '${1:name} = { git = "${2:https://github.com/user/repo.git}", tag = "${3:v1.0.0}" }', |
| 100 | + }, |
| 101 | + { |
| 102 | + label: "name = { version = ..., features = [...] }", |
| 103 | + detail: "长式 dep spec:请求该依赖的 feature", |
| 104 | + insertSnippet: '${1:name} = { version = "${2:1.0}", features = ["${3:feature}"] }', |
| 105 | + }, |
| 106 | + { |
| 107 | + label: "name = { version = ..., tools = [...] }", |
| 108 | + detail: "依赖产出的 host 工具(须为该包的 bin target)", |
| 109 | + insertSnippet: '${1:name} = { version = "${2:1.0}", tools = ["${3:protoc}"] }', |
| 110 | + }, |
| 111 | +]; |
| 112 | + |
| 113 | +const FEATURE_TEMPLATES: readonly TemplateSpec[] = [ |
| 114 | + { label: "name = [...]", detail: "数组简写:仅隐含 feature", insertSnippet: "${1:name} = [${2}]" }, |
| 115 | + { label: "name = { defines = [...] }", detail: "表形式:激活时贡献包自有宏", insertSnippet: '${1:name} = { defines = ["${2:MACRO}"] }' }, |
| 116 | + { label: "name = { requires = [...] }", detail: "表形式:需要 capability", insertSnippet: '${1:name} = { requires = ["${2:blas}"] }' }, |
| 117 | + { label: "name = { sources = [...] }", detail: "表形式:feature 门控的源 glob", insertSnippet: '${1:name} = { sources = ["${2:src/simd/**}"] }' }, |
| 118 | +]; |
| 119 | + |
| 120 | +const GENERATED_FILE_TEMPLATES: readonly TemplateSpec[] = [ |
| 121 | + { |
| 122 | + label: '"path" = "content"', |
| 123 | + detail: "生成文件(相对路径 → 内容,进指纹)", |
| 124 | + insertSnippet: '"${1:src/gen/wrap.cppm}" = """\n${2:}\n"""', |
| 125 | + }, |
| 126 | +]; |
| 127 | + |
| 128 | +const CAPABILITY_TEMPLATES: readonly TemplateSpec[] = [ |
| 129 | + { |
| 130 | + label: 'capability = "provider"', |
| 131 | + detail: "capability 绑定(等价于 --cap)", |
| 132 | + insertSnippet: '${1:blas} = "${2:compat.openblas}"', |
| 133 | + }, |
| 134 | +]; |
| 135 | + |
| 136 | +const XLINGS_WORKSPACE_TEMPLATES: readonly TemplateSpec[] = [ |
| 137 | + { label: 'tool = "version"', detail: "固定工具版本", insertSnippet: '${1:clang} = "${2:20.1.7}"' }, |
| 138 | +]; |
| 139 | + |
| 140 | +const XLINGS_ENVS_TEMPLATES: readonly TemplateSpec[] = [ |
| 141 | + { label: 'NAME = "value"', detail: "应用到工具环境的环境变量", insertSnippet: '${1:NAME} = "${2:value}"' }, |
| 142 | +]; |
| 143 | + |
| 144 | +const TOOLS_OVERRIDES_TEMPLATES: readonly TemplateSpec[] = [ |
| 145 | + { |
| 146 | + label: '"pkg:tool" = "path"', |
| 147 | + detail: "用已有二进制覆盖 host 工具(跳过构建)", |
| 148 | + insertSnippet: '"${1:compat.protobuf:protoc}" = "${2:/usr/bin/protoc}"', |
| 149 | + }, |
| 150 | +]; |
| 151 | + |
| 152 | +const TEMPLATES_BY_GROUP: Record<string, readonly TemplateSpec[]> = { |
| 153 | + "features": FEATURE_TEMPLATES, |
| 154 | + "generated_files": GENERATED_FILE_TEMPLATES, |
| 155 | + "capabilities": CAPABILITY_TEMPLATES, |
| 156 | + "xlings.workspace": XLINGS_WORKSPACE_TEMPLATES, |
| 157 | + "xlings.envs": XLINGS_ENVS_TEMPLATES, |
| 158 | + "tools.overrides": TOOLS_OVERRIDES_TEMPLATES, |
| 159 | +}; |
| 160 | + |
| 161 | +function sectionHeaderSuggestions(range: ReplaceRange): McppTomlSuggestion[] { |
| 162 | + return SECTION_HEADERS.map((section) => ({ |
| 163 | + label: section.label, |
| 164 | + kind: "section", |
| 165 | + detail: section.detail, |
| 166 | + insertSnippet: section.header, |
| 167 | + range, |
| 168 | + })); |
| 169 | +} |
| 170 | + |
| 171 | +function templateSuggestions(templates: readonly TemplateSpec[], range: ReplaceRange): McppTomlSuggestion[] { |
| 172 | + return templates.map((template) => ({ |
| 173 | + label: template.label, |
| 174 | + kind: "template", |
| 175 | + detail: template.detail, |
| 176 | + documentation: template.documentation, |
| 177 | + insertSnippet: template.insertSnippet, |
| 178 | + range, |
| 179 | + })); |
| 180 | +} |
| 181 | + |
| 182 | +/** |
| 183 | + * 计算 mcpp.toml 在指定位置的补全建议(结构补全:段头 + 写法模板)。 |
| 184 | + */ |
| 185 | +export function computeMcppTomlCompletions( |
| 186 | + lines: readonly string[], |
| 187 | + line: number, |
| 188 | + character: number, |
| 189 | +): McppTomlSuggestion[] { |
| 190 | + const context = contextAt(lines, line, character); |
| 191 | + |
| 192 | + if (context.kind === "section-header") { |
| 193 | + // mcpp manifest 不使用 TOML 数组表([[...]]);[[ 内不提供建议, |
| 194 | + // 避免把用户意图的数组表悄悄替换成普通段 [x](未知段会被 mcpp 静默忽略)。 |
| 195 | + if (context.isArray) { |
| 196 | + return []; |
| 197 | + } |
| 198 | + // parser 的替换范围从段名 token 开始;段头建议插入的是完整 "[xxx]", |
| 199 | + // 需要把范围扩展到本行的 "[",避免留下 "[["。仅当 "[" 是行内首个 |
| 200 | + // 非空白字符时才扩展(section-header 上下文正常都满足,防御奇怪输入)。 |
| 201 | + const lineText = (lines[line] ?? "").replace(/\r$/, ""); |
| 202 | + const bracket = lineText.indexOf("["); |
| 203 | + const firstNonWs = lineText.search(/\S/); |
| 204 | + const range = bracket >= 0 && bracket === firstNonWs |
| 205 | + ? { startCharacter: bracket, endCharacter: context.replaceRange.endCharacter } |
| 206 | + : context.replaceRange; |
| 207 | + return sectionHeaderSuggestions(range); |
| 208 | + } |
| 209 | + |
| 210 | + if (context.kind === "key") { |
| 211 | + const { section, containerPath, replaceRange } = context; |
| 212 | + // 文档顶部(尚无段头):提示段头。未知段:不提供建议 |
| 213 | + // (附录 A:不支持包自定义 toml 键)。 |
| 214 | + if (section.kind === "top") { |
| 215 | + return sectionHeaderSuggestions(replaceRange); |
| 216 | + } |
| 217 | + if (section.kind !== "known" || containerPath.length > 0) { |
| 218 | + return []; |
| 219 | + } |
| 220 | + if (DEPENDENCY_GROUPS.has(section.group)) { |
| 221 | + return templateSuggestions(DEPENDENCY_TEMPLATES, replaceRange); |
| 222 | + } |
| 223 | + const templates = TEMPLATES_BY_GROUP[section.group]; |
| 224 | + return templates === undefined ? [] : templateSuggestions(templates, replaceRange); |
| 225 | + } |
| 226 | + |
| 227 | + // 值位置:自由格式值不瞎猜(版本候选等动态数据层落地后再说)。 |
| 228 | + return []; |
| 229 | +} |
0 commit comments