Skip to content

feat: smart completion, hover and go-to-definition for koishi.yml - #7

Merged
MaikoTan merged 3 commits into
masterfrom
maikotan-koishi-yml-smart-completion
Oct 5, 2026
Merged

MaikoTan merged 3 commits into
masterfrom
maikotan-koishi-yml-smart-completion

Conversation

@MaikoTan

@MaikoTan MaikoTan commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Summary

Extends the extension from structural YAML validation of koishi.yml into real
language support: field / enum / plugin-name completion, hover
documentation
, and go to definition.

before after
koishi.yml structural validation only completion, hover, jump to definition

Features

Completion

  • Field names at the cursor's level, in schema declaration order so related
    fields stay grouped, with description, type and numeric constraints as
    documentation.
  • Enum values on the value side of a field (prefixMode, i18n.output),
    inserted pre-quoted.
  • Plugin names inside the plugins block — a bundled snapshot of the
    official plugins merged with whatever is installed in the current workspace,
    so third-party plugins appear too. Each item shows the npm package name and
    the services the plugin provides or requires.

Hover

  • Schema description, type, allowed values and numeric constraints for a field.
  • The npm package name and declared services for a plugin entry, so adapter-qq
    can be traced back to @koishijs/plugin-adapter-qq.

Go to definition

  • From a plugin name to that package's package.json in node_modules.

Design notes

The three providers share one cursor → config-path resolution
(KoishiYamlDocument.cursorAt), returning path, parentPath and onKey.

Two YAML shapes cannot be expressed by the AST and drove most of the design:

  • A key still being typed. While a user types plugins: / ada, ada
    parses as a scalar value of plugins, and at the document root
    (delay) as a stray scalar with no parent. Neither form says "a key is being
    entered", so indentation is used as the signal.
  • The empty value slot. A cursor in prefixMode: | must read as a value
    position, otherwise enum completion never fires — which is the whole point of
    being there.

Getting this wrong in either direction is silent: field names leak into the
value side, or completion returns nothing at all while typing the first field of
a file. Both directions are covered by tests.

Merged with #8

master picked up #8, which independently fixes the Yarn 4 issues this branch
had also hit. This branch was rebased onto it and both conflicts resolved in
#8's favour, since #8 is the more complete fix:

yarn.lock needed no resolution: both sides converged on identical content.

Because of #8, two items previously noted here as pre-existing are now fixed:
yarn install --immutable passes against the committed lockfile, and
yarn package works. The vsix is 255 KB rather than the 497 KB this branch
produced before, since #8 excludes .yarn/** from the package.

One follow-up fix

#8 made CI run the tests for the first time, which immediately surfaced a real
problem: schemata/*.json is gitignored and generated by yarn run convert,
which yarn test never invoked, so on a clean checkout every test file threw
Cannot find module schemata/koishi-yml.json before Mocha reported anything.
test now runs convert first, and SchemaIndex.fromFile names the command to
run when a schema is absent rather than surfacing a bare module error.

Validation

Run the same way CI does:

  • yarn install --immutable — passes
  • yarn test (compile + convert + lint + tests) — 63 passing in a real VS Code
    instance, confirmed in the CI log
  • yarn package — succeeds; yaml verified present in the packaged output

Verified from a clean state by deleting the generated JSON and re-running, which
is the case CI actually hits.

Tests are layered: schema.test.ts (pure logic), plugins.test.ts (logic plus
temp dirs), providers.test.ts (each provider invoked inside VS Code).

Note for review

The bundled plugin list is a static snapshot rather than a live registry query,
so completion stays instant and works offline; workspace-installed plugins
override it, which keeps newer releases covered.

Extend the extension from structural YAML validation into real language
support for koishi.yml.

Completion
  - Field names at the cursor's level, in schema declaration order, with the
    description, type and numeric constraints as documentation.
  - Enum values on the value side of a field (`prefixMode`, `i18n.output`),
    inserted pre-quoted.
  - Plugin names inside the `plugins` block, from a bundled snapshot of the
    official plugins merged with what is installed in the current workspace.

Hover
  - Schema description, type, allowed values and constraints for a field.
  - The npm package and declared services for a plugin entry.

Go to definition
  - From a plugin name to the installed package's package.json.

Implementation notes
  - The YAML AST cannot express a key that is still being typed, nor the empty
    value slot after `key:`. cursorAt therefore distinguishes the two by AST
    walk plus an indentation fallback, which is what decides whether field or
    enum suggestions are offered.
  - Plugin discovery follows symlinks so plugins developed with `yarn link`
    are recognised.

Also fixes the test infrastructure, which could not run: tsconfig excluded
src/test, so `yarn test` never found compiled tests, and the Mocha runner used
the removed callable `glob` export. Adds .vscode-test to .gitignore.
master picked up #8, which independently fixes the same Yarn 4 problems this
branch had worked around, and does so more completely: it pins yarn via
packageManager, regenerates the lockfile for Yarn 4, and makes CI actually run
the tests and the package step.

Resolved by taking #8's version of both conflicts:

- package.json: #8 folds compile and lint into `test` and passes --no-yarn to
  vsce. Yarn 4 no longer runs the implicit pretest hook, so the previous
  pretest script would have silently stopped running. Re-applied only this
  branch's feature metadata (categories, keywords, activationEvents, the yaml
  dependency).
- src/test/suite/index.ts: #8's globSync version is simpler and equivalent.
  Kept the .sort() this branch had, so test order does not depend on the order
  the filesystem reports.

yarn.lock needed no resolution and came out byte-identical to this branch's:
both sides converged on the same content.

Verified the same way CI will: yarn install --immutable, yarn test (63 passing),
and yarn package, which now succeeds and produces a 254 KB vsix.
CI failed after the merge with "Cannot find module schemata/koishi-yml.json".

The schemata are generated from schemata/*.yaml by `yarn run convert` and are
gitignored, so a clean CI checkout has no JSON. `yarn test` compiled and ran the
suite without ever invoking convert, and both schema.test.ts and
providers.test.ts load the schema at module scope, so every test file threw
before Mocha could report anything useful.

- test: run `yarn run convert` before the suite, since the schema is a build
  artifact of the .yaml sources
- SchemaIndex.fromFile: report a missing schema with the command that
  generates it, instead of letting require() fail with a bare module error
@MaikoTan
MaikoTan merged commit f4bc509 into master Oct 5, 2026
2 checks passed
@MaikoTan
MaikoTan deleted the maikotan-koishi-yml-smart-completion branch October 5, 2026 11:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant