How this repository is put together, and which file is the single source of truth for what.
The extension is a thin VS Code assembly layer (src/extension.ts) over a set of pure
modules: project discovery, TOML/build-script text analysis, cache aggregation, the mcppls
capability table and command-id tables. Nearly everything can be exercised by node --test
without an Extension Host, and the parts that cannot are small.
| Directory | What lives there | Touches vscode |
|---|---|---|
src/extension.ts |
The only assembly point: activate / deactivate |
yes |
src/commands/ |
Command ids (ids.ts) and the quick-menu table (menu.ts) |
no |
src/config/ |
Settings registry access, validation, the settings panel, presets | access.ts, panel.ts, migrate.ts |
src/cli/ |
mcpp process runner, protocol probe, tasks, toolchain parsing, cache/cleanup plans, artifacts walk, self-check text | controller.ts |
src/projects/ |
mcpp.toml discovery, the mcpp.inProject context key, the manifest summary |
no |
src/toml/ |
Tolerant TOML parser, snapshot reader, structural completion, diagnostics | providers.ts |
src/buildscript/ |
build.mcpp API snapshot, known modules, static analysis |
providers.ts |
src/mcppls/ |
The mcppls contract, capability probe, state normaliser, bridge | stateSource.ts |
src/views/ |
Tree models as data, the shared tree provider, the three views | treeProvider.ts, projectView.ts, cacheView.ts, languageServerView.ts |
src/i18n/ |
Runtime string resolution (t.ts) and the pure resolver (translate.ts) |
t.ts |
src/util/ |
Byte/count formatting, text truncation | no |
src/workflows/ |
The build-then-refresh state machine | no |
test/ mirrors src/ and the directory names match, so a failing test names its module.
A module that does not import vscode runs under plain node --test, cannot touch the editor
by accident, and is the place to put a decision. The vscode-touching modules are exactly:
src/extension.ts, src/cli/controller.ts, src/config/{access,panel,migrate}.ts,
src/i18n/t.ts, src/mcppls/stateSource.ts, src/toml/providers.ts,
src/buildscript/providers.ts, src/views/{treeProvider,projectView,cacheView,languageServerView}.ts.
Two rules follow from that:
- a policy (which argv, how much confirmation, which severity) belongs in a pure module —
src/cli/clean.tsis the model: it owns the whole cleanup danger table so a UI change cannot quietly add a sixth level of danger; - a
vscode-touching module should only translate a decision into API calls, not make it.
CI also asserts the boundary that matters most: the packaged extension contains no
vscode-languageclient and no second language client (test ! -e dist/src/languageClient.js
plus a rg check in .github/workflows/ci.yml).
| Fact | Source | Read by |
|---|---|---|
| Every setting | data/config-registry.json |
src/config/registry.ts, tools/check-config.mjs, tools/generate-settings-docs.mjs |
| Setting labels | package.nls.json / package.nls.zh-cn.json |
VS Code, at startup |
| Runtime strings (Chinese) | data/i18n/zh-cn.json |
src/i18n/t.ts, tools/generate-l10n.mjs |
| Runtime strings VS Code reads | l10n/bundle.l10n*.json (generated, do not edit) |
vscode.l10n.t under mcpp.ui.language: auto |
build.mcpp API |
data/buildscript-api.json (generated) |
src/buildscript/api.ts |
mcpp.toml shape |
data/toml-schema.json (generated) |
src/toml/schema.ts |
| Command ids | src/commands/ids.ts |
the manifest is held to it by test/artifacts.test.ts (command list, view ids, colour ids) and test/commands/ids.test.ts |
The two generated snapshots come from an mcpp checkout and are committed, because the extension
must build without that checkout. Regenerate them with npm run gen:buildscript and
npm run gen:toml (MCPP_REPO, default ../mcpp).
package.json's contributes.configuration is hand-written, not generated: rewriting a
several-hundred-line manifest on every change would produce an unreviewable diff. It is held to
the registry semantically instead.
Two independent paths decide which language you see, and they cannot be made to agree:
- VS Code's own strings — command titles, setting names and descriptions, deprecation
messages — come from
package.nls.json/package.nls.zh-cn.json, which VS Code resolves once at startup from its display language. Nothing this extension does at runtime can change them. - Our runtime strings and our panels — resolved by
src/i18n/t.ts. Withmcpp.ui.language: "auto"(the default) it callsvscode.l10n.t(english), which readsl10n/bundle.l10n.<locale>.json(generated fromdata/i18n/zh-cn.json) and falls back to the English text passed in. With"en"or"zh-cn"it reads our own bundle directly, so the escape hatch works even when the editor is in a third language.
The convention is that the English text is the key: a missing translation degrades to
readable English, never to an identifier. tools/l10n-check.mjs fails the build when a new
t("…") literal has no entry in data/i18n/zh-cn.json, and when the two package.nls.* files
have different key sets.
The deliberate consequence: a user on a Chinese VS Code who sets mcpp.ui.language to en
sees Chinese setting names and English notifications. That mixture is the documented behaviour
of the escape hatch, not a bug. Note also that the migration is partial in this build: many
strings in src/cli/controller.ts and src/extension.ts (task completion messages, blocked
states, toolchain prompts) are still hardcoded Chinese and never go through t() at all. They
do not follow mcpp.ui.language, and on an English VS Code they are shown as-is.
A note on the two directory names, because they look like synonyms and are not:
| Layer | Lives in | What it is |
|---|---|---|
| Our runtime strings | src/i18n/ (t, translate) + data/i18n/zh-cn.json |
The t() lookup table, switchable at runtime by mcpp.ui.language — i18n in the sense that the engineering keeps every string adaptable |
| VS Code's manifest strings | l10n/bundle.l10n.*.json (generated from package.nls*) |
What VS Code itself localizes — l10n proper, resolved once at startup; the l10n/ directory name is a platform convention and cannot be renamed |
| Gate | Command | Checks |
|---|---|---|
| Config | npm run check:config (tools/check-config.mjs) |
registry shape; that package.json has exactly the registry's keys with the same type, default, enum, scope, bounds and %key% references; that package.nls.json carries <key>.title, <key>.description and any deprecationMessage, matching the registry text |
| Localisation | npm run check:l10n (tools/l10n-check.mjs) |
every t("…") literal in src/** has a data/i18n/zh-cn.json entry; package.nls.json and package.nls.zh-cn.json have identical key sets; every %key% in package.json resolves |
| Snapshots | npm run check:generated (tools/check-generators.mjs) |
regenerates data/buildscript-api.json and data/toml-schema.json and fails on any diff; skips with a notice when there is no mcpp checkout |
| Docs | npm run gen:docs (tools/generate-settings-docs.mjs) |
regenerates docs/settings.md; the stated drift check is npm run gen:docs then git diff --exit-code |
| Tests | npm test |
runs npm run check, compiles, then node --test "dist/test/**/*.test.js" |
| Extension Host | npm run test:e2e |
activates the extension against the test/e2e fixtures |
npm test runs the three checks as one step (npm run check), so a manifest/registry
mismatch, an untranslated runtime string or a stale snapshot fails before any test runs.
- A new setting: add it to
data/config-registry.json, add the property tocontributes.configuration, add the threepackage.nls*entries, thennpm run gen:docs.check-configwill tell you what is missing. - A new command: add the id to
src/commands/ids.ts, contribute it inpackage.jsonwith a%command.<id>.title%placeholder, register it, and add bothpackage.nls*strings. Never use anmcppls.id — see compatibility.md. - A new runtime string: use
t("English text", …), then add the Chinese entry todata/i18n/zh-cn.json(orl10n-checkfails). - A new user-facing page:
docs/, written so that every relative link resolves in the repository.
docs/ is excluded from the VSIX by .vscodeignore, so these links work on GitHub and in the
repository but not inside an installed extension.