Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mcpp CLI 操作;它不实现新的 C++ 语言服务器,也不替代 mcpp 的
| --- | --- | --- | --- |
| 识别 mcpp 工程 | 支持 | 支持 | 支持 |
| `mcpp.toml` TOML 语法高亮 | 支持 | 支持 | 支持 |
| `mcpp.toml` 结构补全(段头 + 写法模板) | 支持 | 支持 | 支持 |
| `build.mcpp`、`.cppm`、`.ixx`、`.mpp`、`.ccm` 文件关联 | 支持 | 支持 | 支持 |
| `module`、`export module`、`import` 语法高亮 | 支持 | 支持 | 支持 |
| 构建、运行、测试、清理命令 | 支持 | 支持 | 支持 |
Expand Down Expand Up @@ -97,6 +98,19 @@ TextMate 语法规则提供。
`import mcpp;` 不会再被误报为缺少 C++ 模块。真正的 mcpp API 补全需要 mcpp 核心
未来生成宿主 helper 的 CDB 和 PCM 映射。

### mcpp.toml 结构补全

- 段头补全:26 个段(`[package]`、`[targets.<name>]`、`[dependencies]`、`[build-dependencies]`、
`[features]`、`[indices]`、`[pack]` 等),参数化段带可跳转占位符。
- 写法模板:依赖段的依赖写法(版本 / 路径 / git / features / tools)、`[features]` 表形式、
capabilities / xlings / tools.overrides / generated_files 等开放段的条目形态。
- 每条建议携带显式替换范围,部分输入(`[dep`、`na`)不会残留无效文本;所有语义规则有真实
mcpp 契约测试(44 例,无 mcpp 环境自动跳过)。
- 范围边界:不提供静态字段键/枚举(等上游版本化 manifest schema),不提供依赖包名/版本候选
(等上游批量 catalog 接口);未知自定义段与 `[[...]]` 数组表不提供建议。
- 由 `mcpp.tomlCompletion` 设置控制(默认开启);未受信任工作区仅做纯文本分析,不执行任何
外部程序。

### LLVM 与 clangd 集成

扩展读取 mcpp 生成的 `compile_commands.json`,然后:
Expand Down Expand Up @@ -172,7 +186,8 @@ xlings 补齐匹配版本的 llvm-tools(含 clangd),最后重新读取 CDB
"mcpp.path": "/path/to/mcpp",
"mcpp.clangd.path": "/path/to/matching/clangd",
"mcpp.modulesSupport": "auto",
"mcpp.configureCppTools": true
"mcpp.configureCppTools": true,
"mcpp.tomlCompletion": true
}
```

Expand All @@ -182,6 +197,7 @@ xlings 补齐匹配版本的 llvm-tools(含 clangd),最后重新读取 CDB
| `mcpp.clangd.path` | 空 | 与 CDB 中 LLVM 编译器匹配的 clangd;空值表示自动发现 |
| `mcpp.modulesSupport` | `auto` | `auto`、`on` 或 `off`,控制 clangd 实验模块参数 |
| `mcpp.configureCppTools` | `true` | 手动配置 clangd 时,是否询问关闭当前工作区的 cpptools IntelliSense |
| `mcpp.tomlCompletion` | `true` | 为 `mcpp.toml` 提供结构补全:段头与写法模板(snippet);所有建议带显式替换范围,并经真实 mcpp 契约测试验证 |

`mcpp.path` 只影响插件执行 mcpp CLI 命令。工程实际编译器来自
`compile_commands.json`,`mcpp.clangd.path` 只指定语言服务器,三者相互独立。
Expand Down
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"workspaceContains:mcpp.toml",
"onLanguage:cpp",
"onLanguage:mcpp-build",
"onLanguage:mcpp-toml",
"onCommand:mcpp.configureClangd",
"onCommand:mcpp.refreshCompilationDatabase",
"onCommand:mcpp.checkModuleSupport",
Expand All @@ -45,7 +46,7 @@
"capabilities": {
"untrustedWorkspaces": {
"supported": "limited",
"description": "未受信任工作区仅启用模块语法高亮,不执行 CDB、mcpp 或 clangd 指定的任何程序,也不接管 clangd 配置。"
"description": "未受信任工作区仅启用模块语法高亮与 mcpp.toml 结构补全(纯文本分析),不执行 CDB、mcpp 或 clangd 指定的任何程序,也不接管 clangd 配置。"
}
},
"main": "./dist/src/extension.js",
Expand Down Expand Up @@ -151,6 +152,12 @@
"default": true,
"scope": "resource",
"description": "配置 clangd 时,是否询问关闭 Microsoft C/C++ IntelliSense。"
},
"mcpp.tomlCompletion": {
"type": "boolean",
"default": true,
"scope": "resource",
"description": "为 mcpp.toml 提供结构补全:段头与写法模板(snippet)。所有建议带显式替换范围,并经真实 mcpp 契约测试验证。"
}
}
},
Expand Down
47 changes: 47 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
} from "./workflow";
import { classifyTaskExit, type TaskCompletion } from "./tasks";
import { MCPP_MANIFEST_GLOB, registerInProjectContext } from "./inProject";
import { computeMcppTomlCompletions } from "./mcppTomlCompletion";
import {
buildModuleSetupPlan,
executeModuleSetup,
Expand Down Expand Up @@ -825,6 +826,47 @@ async function autoConfigureModulesWizard(
}
}

// mcpp.toml 结构补全:建议由纯函数 computeMcppTomlCompletions 计算,这里只做
// vscode 类型映射。范围:段头 snippet + 开放词汇段的写法模板;不含字段键/枚举
// 与依赖数据(分别等上游版本化 schema 与批量 catalog 接口)。
const mcppTomlCompletionKinds = {
section: vscode.CompletionItemKind.Folder,
template: vscode.CompletionItemKind.Snippet,
} as const;

const mcppTomlCompletionProvider: vscode.CompletionItemProvider = {
provideCompletionItems(document, position) {
// mcpp.toml 结构补全由 mcpp.tomlCompletion 控制,按文档作用域读取。
if (!vscode.workspace.getConfiguration("mcpp", document.uri).get<boolean>("tomlCompletion", true)) {
return undefined;
}
const lines: string[] = [];
for (let line = 0; line <= position.line; line += 1) {
lines.push(document.lineAt(line).text);
}
return computeMcppTomlCompletions(lines, position.line, position.character).map((suggestion) => {
const item = new vscode.CompletionItem(
suggestion.label,
mcppTomlCompletionKinds[suggestion.kind],
);
item.detail = suggestion.detail;
if (suggestion.documentation !== undefined) {
item.documentation = new vscode.MarkdownString(suggestion.documentation);
}
if (suggestion.insertSnippet !== undefined) {
item.insertText = new vscode.SnippetString(suggestion.insertSnippet);
}
item.range = new vscode.Range(
position.line,
suggestion.range.startCharacter,
position.line,
suggestion.range.endCharacter,
);
return item;
});
},
};

export async function activate(extensionContext: vscode.ExtensionContext): Promise<void> {
moduleStatusByProject.clear();
moduleCheckOperations.clear();
Expand Down Expand Up @@ -1001,6 +1043,11 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi
output,
status,
...cliController.register(),
vscode.languages.registerCompletionItemProvider(
{ language: "mcpp-toml" },
mcppTomlCompletionProvider,
"[",
),
vscode.commands.registerCommand(COMMAND_CONFIGURE, runGuarded(async () => {
const project = findCurrentProject();
if (project === undefined) {
Expand Down
229 changes: 229 additions & 0 deletions src/mcppTomlCompletion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// mcpp.toml 的代码补全查询层(结构补全版)。
//
// 范围:段头结构建议 + 开放词汇段的写法模板。每条建议携带显式替换范围。
// 依赖包名/版本等动态数据补全与静态字段键/枚举补全均不在本版——前者等上游
// 批量 catalog 接口,后者等版本化 manifest schema(见设计 issue #8 与
// mcpp RFC #379)。
//
// 本模块不依赖 vscode API;上下文来自 mcppTomlParser 的 contextAt(容错解析)。

import {
contextAt,
type ReplaceRange,
type SectionResolution,
} from "./mcppTomlParser";

export type McppTomlSuggestionKind = "section" | "template";

export interface McppTomlSuggestion {
label: string;
kind: McppTomlSuggestionKind;
detail: string;
documentation?: string;
/** 插入文本;含 $1 等 snippet 占位符。缺省时插入 label。 */
insertSnippet?: string;
/** 替换范围(光标所在行的起止列)。 */
range: ReplaceRange;
}

export interface SectionHeaderSpec {
group: string;
label: string;
/** snippet 形式的段头(含 ${1:...} 占位)。 */
header: string;
detail: string;
}

// 段头结构清单:TOML 结构语法,非字段语义。出处:mcpp 文档 02/03/05/06
// 与 src/manifest/toml.cppm 的段清单(契约测试用真实 mcpp 逐段验证)。
export const SECTION_HEADERS: readonly SectionHeaderSpec[] = [
{ group: "package", label: "[package]", header: "[package]", detail: "包元数据" },
{ group: "lib", label: "[lib]", header: "[lib]", detail: "库根模块约定" },
{ group: "build", label: "[build]", header: "[build]", detail: "构建配置" },
{ group: "generated_files", label: "[generated_files]", header: "[generated_files]", detail: "生成文件(路径 → 内容)" },
{ group: "dependencies", label: "[dependencies]", header: "[dependencies]", detail: "运行时依赖" },
{ group: "dev-dependencies", label: "[dev-dependencies]", header: "[dev-dependencies]", detail: "开发/测试依赖" },
{ group: "build-dependencies", label: "[build-dependencies]", header: "[build-dependencies]", detail: "构建期依赖(仅构建期拉取,运行时不可见)" },
{ group: "workspace", label: "[workspace]", header: "[workspace]", detail: "工作空间成员声明" },
{ group: "workspace.dependencies", label: "[workspace.dependencies]", header: "[workspace.dependencies]", detail: "集中声明依赖版本,成员用 workspace = true 继承" },
{ group: "features", label: "[features]", header: "[features]", detail: "feature 定义" },
{ group: "feature-deps", label: "[feature-deps.<name>]", header: "[feature-deps.${1:name}]", detail: "由 feature 拉取的可选依赖" },
{ group: "capabilities", label: "[capabilities]", header: "[capabilities]", detail: "capability 绑定(provider 选择)" },
{ group: "targets", label: "[targets.<name>]", header: "[targets.${1:name}]", detail: "构建目标" },
{ group: "profile", label: "[profile.<name>]", header: "[profile.${1:name}]", detail: "构建档案" },
{ group: "runtime", label: "[runtime]", header: "[runtime]", detail: "主机运行时能力" },
{ group: "resources", label: "[resources]", header: "[resources]", detail: "编译进产物的元数据与资产(仅 PE 目标)" },
{ group: "toolchain", label: "[toolchain]", header: "[toolchain]", detail: "编译器工具链简写" },
{ group: "xlings", label: "[xlings]", header: "[xlings]", detail: "构建环境(xlings 供给)" },
{ group: "xlings.workspace", label: "[xlings.workspace]", header: "[xlings.workspace]", detail: "固定工具版本" },
{ group: "xlings.envs", label: "[xlings.envs]", header: "[xlings.envs]", detail: "工具环境的环境变量" },
{ group: "target", label: "[target.<triple>]", header: "[target.${1:x86_64-linux-gnu}]", detail: "按目标三元组的配置" },
{ group: "pack", label: "[pack]", header: "[pack]", detail: "mcpp pack 打包配置" },
{ group: "pack.bundle-project", label: "[pack.bundle-project]", header: "[pack.bundle-project]", detail: "vendored 过滤策略微调" },
{ group: "indices", label: "[indices]", header: "[indices]", detail: "项目级索引重定向" },
{ group: "tools.overrides", label: "[tools.overrides]", header: "[tools.overrides]", detail: "host 工具二进制覆盖" },
{ group: "language", label: "[language]", header: "[language]", detail: "旧版兼容字段;新项目请用 [package].standard" },
];

/** 依赖类段(键位置给依赖写法模板)。 */
const DEPENDENCY_GROUPS: ReadonlySet<string> = new Set([
"dependencies",
"dev-dependencies",
"build-dependencies",
"workspace.dependencies",
"feature-deps",
]);

interface TemplateSpec {
label: string;
detail: string;
documentation?: string;
insertSnippet: string;
}

const DEPENDENCY_TEMPLATES: readonly TemplateSpec[] = [
{
label: 'name = "version"',
detail: "SemVer 版本依赖",
documentation: "默认 caret 约束(^);也支持 ~、= 与 \">=1.0, <2.0\" 范围组合。",
insertSnippet: '${1:name} = "${2:1.0.0}"',
},
{
label: "name = { path = ... }",
detail: "路径依赖(本地开发)",
insertSnippet: '${1:name} = { path = "${2:../mylib}" }',
},
{
label: "name = { git = ..., tag = ... }",
detail: "Git 依赖(tag / branch / rev 三选一)",
insertSnippet: '${1:name} = { git = "${2:https://github.com/user/repo.git}", tag = "${3:v1.0.0}" }',
},
{
label: "name = { version = ..., features = [...] }",
detail: "长式 dep spec:请求该依赖的 feature",
insertSnippet: '${1:name} = { version = "${2:1.0}", features = ["${3:feature}"] }',
},
{
label: "name = { version = ..., tools = [...] }",
detail: "依赖产出的 host 工具(须为该包的 bin target)",
insertSnippet: '${1:name} = { version = "${2:1.0}", tools = ["${3:protoc}"] }',
},
];

const FEATURE_TEMPLATES: readonly TemplateSpec[] = [
{ label: "name = [...]", detail: "数组简写:仅隐含 feature", insertSnippet: "${1:name} = [${2}]" },
{ label: "name = { defines = [...] }", detail: "表形式:激活时贡献包自有宏", insertSnippet: '${1:name} = { defines = ["${2:MACRO}"] }' },
{ label: "name = { requires = [...] }", detail: "表形式:需要 capability", insertSnippet: '${1:name} = { requires = ["${2:blas}"] }' },
{ label: "name = { sources = [...] }", detail: "表形式:feature 门控的源 glob", insertSnippet: '${1:name} = { sources = ["${2:src/simd/**}"] }' },
];

const GENERATED_FILE_TEMPLATES: readonly TemplateSpec[] = [
{
label: '"path" = "content"',
detail: "生成文件(相对路径 → 内容,进指纹)",
insertSnippet: '"${1:src/gen/wrap.cppm}" = """\n${2:}\n"""',
},
];

const CAPABILITY_TEMPLATES: readonly TemplateSpec[] = [
{
label: 'capability = "provider"',
detail: "capability 绑定(等价于 --cap)",
insertSnippet: '${1:blas} = "${2:compat.openblas}"',
},
];

const XLINGS_WORKSPACE_TEMPLATES: readonly TemplateSpec[] = [
{ label: 'tool = "version"', detail: "固定工具版本", insertSnippet: '${1:clang} = "${2:20.1.7}"' },
];

const XLINGS_ENVS_TEMPLATES: readonly TemplateSpec[] = [
{ label: 'NAME = "value"', detail: "应用到工具环境的环境变量", insertSnippet: '${1:NAME} = "${2:value}"' },
];

const TOOLS_OVERRIDES_TEMPLATES: readonly TemplateSpec[] = [
{
label: '"pkg:tool" = "path"',
detail: "用已有二进制覆盖 host 工具(跳过构建)",
insertSnippet: '"${1:compat.protobuf:protoc}" = "${2:/usr/bin/protoc}"',
},
];

const TEMPLATES_BY_GROUP: Record<string, readonly TemplateSpec[]> = {
"features": FEATURE_TEMPLATES,
"generated_files": GENERATED_FILE_TEMPLATES,
"capabilities": CAPABILITY_TEMPLATES,
"xlings.workspace": XLINGS_WORKSPACE_TEMPLATES,
"xlings.envs": XLINGS_ENVS_TEMPLATES,
"tools.overrides": TOOLS_OVERRIDES_TEMPLATES,
};

function sectionHeaderSuggestions(range: ReplaceRange): McppTomlSuggestion[] {
return SECTION_HEADERS.map((section) => ({
label: section.label,
kind: "section",
detail: section.detail,
insertSnippet: section.header,
range,
}));
}

function templateSuggestions(templates: readonly TemplateSpec[], range: ReplaceRange): McppTomlSuggestion[] {
return templates.map((template) => ({
label: template.label,
kind: "template",
detail: template.detail,
documentation: template.documentation,
insertSnippet: template.insertSnippet,
range,
}));
}

/**
* 计算 mcpp.toml 在指定位置的补全建议(结构补全:段头 + 写法模板)。
*/
export function computeMcppTomlCompletions(
lines: readonly string[],
line: number,
character: number,
): McppTomlSuggestion[] {
const context = contextAt(lines, line, character);

if (context.kind === "section-header") {
// mcpp manifest 不使用 TOML 数组表([[...]]);[[ 内不提供建议,
// 避免把用户意图的数组表悄悄替换成普通段 [x](未知段会被 mcpp 静默忽略)。
if (context.isArray) {
return [];
}
// parser 的替换范围从段名 token 开始;段头建议插入的是完整 "[xxx]",
// 需要把范围扩展到本行的 "[",避免留下 "[["。仅当 "[" 是行内首个
// 非空白字符时才扩展(section-header 上下文正常都满足,防御奇怪输入)。
const lineText = (lines[line] ?? "").replace(/\r$/, "");
const bracket = lineText.indexOf("[");
const firstNonWs = lineText.search(/\S/);
const range = bracket >= 0 && bracket === firstNonWs
? { startCharacter: bracket, endCharacter: context.replaceRange.endCharacter }
: context.replaceRange;
return sectionHeaderSuggestions(range);
}

if (context.kind === "key") {
const { section, containerPath, replaceRange } = context;
// 文档顶部(尚无段头):提示段头。未知段:不提供建议
// (附录 A:不支持包自定义 toml 键)。
if (section.kind === "top") {
return sectionHeaderSuggestions(replaceRange);
}
if (section.kind !== "known" || containerPath.length > 0) {
return [];
}
if (DEPENDENCY_GROUPS.has(section.group)) {
return templateSuggestions(DEPENDENCY_TEMPLATES, replaceRange);
}
const templates = TEMPLATES_BY_GROUP[section.group];
return templates === undefined ? [] : templateSuggestions(templates, replaceRange);
}

// 值位置:自由格式值不瞎猜(版本候选等动态数据层落地后再说)。
return [];
}
Loading
Loading