Add schema-driven frontmatter autocomplete to CM6 editor#16753
Add schema-driven frontmatter autocomplete to CM6 editor#16753
Conversation
Wire the existing autocomplete system (parseFrontmatterContext + getSuggestions) into a CM6 CompletionSource so the playground editor suggests frontmatter keys and enum values as you type between the --- delimiters. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds schema-driven YAML frontmatter autocomplete to the CodeMirror 6 editor by loading a generated schema JSON at runtime and mapping existing context-parsing + suggestion logic into a CM6 CompletionSource.
Changes:
- Integrates CM6 autocompletion with a frontmatter-only completion source.
- Adds frontmatter context parsing + schema-driven suggestion generation utilities.
- Introduces
autocomplete-data.jsoncontaining the frontmatter schema (keys/types/descriptions/enums) used for suggestions.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| docs/public/editor/editor.js | Loads schema JSON and wires a CM6 CompletionSource to provide frontmatter-only suggestions. |
| docs/public/editor/autocomplete.js | Implements frontmatter context parsing and schema-driven suggestion generation (plus additional UI code). |
| docs/public/editor/autocomplete-data.json | Adds the schema dataset used to drive key/value completions. |
Comments suppressed due to low confidence (2)
docs/public/editor/autocomplete.js:111
- Sibling-key collection uses
trimmed.match(/^([a-zA-Z0-9_-]+)\s*:/), which misses keys on list-item lines like- key: .... In arrays of objects this preventssiblingsfrom including keys already present on the- key:line, so duplicate-key suggestions won’t be filtered.
Consider updating this regex (and the similar one in the backward path check) to accept an optional leading - list marker.
// Check this is at the same nesting level by verifying same parent path
// Simple heuristic: same indent = same level (works for well-formatted YAML)
const keyMatch = trimmed.match(/^([a-zA-Z0-9_-]+)\s*:/);
if (keyMatch) {
// Verify same parent: walk backward from line i to build its path
docs/public/editor/autocomplete.js:5
- The module header comment says this autocomplete is for use “inside a plain <textarea>”, but in this PR the exported parser/suggestion functions are used by the CodeMirror 6 adapter. Updating the comment to reflect the current usage (or splitting textarea-specific UI into a separate module) would avoid confusion for future maintainers.
* Frontmatter autocomplete for the gh-aw playground editor.
*
* Provides schema-driven suggestions for YAML frontmatter keys and values
* inside a plain <textarea>.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // This line is a parent — extract its key | ||
| const keyMatch = trimmed.match(/^([a-zA-Z0-9_-]+)\s*:/); |
There was a problem hiding this comment.
The parent-key extraction regex (trimmed.match(/^([a-zA-Z0-9_-]+)\s*:/)) won’t match YAML keys that occur on list-item lines like - key: .... That can cause path construction to skip parents when the cursor is inside an array-of-objects item.
Consider allowing an optional leading list marker (e.g. ^(?:-\s+)?([a-zA-Z0-9_-]+)\s*:).
This issue also appears on line 107 of the same file.
| // This line is a parent — extract its key | |
| const keyMatch = trimmed.match(/^([a-zA-Z0-9_-]+)\s*:/); | |
| // This line is a parent — extract its key (optionally after a list marker) | |
| const keyMatch = trimmed.match(/^(?:-\s+)?([a-zA-Z0-9_-]+)\s*:/); |
| function getValueSuggestions(node, context, rootNode) { | ||
| if (!context.currentKey) return []; | ||
|
|
||
| const entry = node[context.currentKey]; | ||
| if (!entry || !entry.enum) return []; |
There was a problem hiding this comment.
getValueSuggestions takes a rootNode parameter but never uses it. Removing the unused parameter (and the corresponding argument passed from getSuggestions) would simplify the API and avoid confusion about intended behavior.
| // --------------------------------------------------------------------------- | ||
|
|
||
| export class AutocompleteDropdown { | ||
| constructor(textarea) { | ||
| this.textarea = textarea; |
There was a problem hiding this comment.
AutocompleteDropdown (and its DOM/event handling code) is exported here but isn’t referenced anywhere in the CM6 integration (only parseFrontmatterContext/getSuggestions are imported). Shipping unused UI code increases bundle size and maintenance surface.
Consider removing it or moving textarea-specific UI into a separate module that’s only loaded when needed.
This issue also appears on line 2 of the same file.
| const doc = context.state.doc.toString(); | ||
| const pos = context.pos; | ||
| const ctx = parseFrontmatterContext(doc, pos); |
There was a problem hiding this comment.
frontmatterCompletionSource converts the entire CM document to a string (context.state.doc.toString()) and then parseFrontmatterContext re-splits/scans the full text on every completion query. Since CM can query completion sources frequently (often per-keystroke), this is O(doc length) work that can cause noticeable lag on larger files.
Consider rewriting the context extraction to operate on state.doc/lineAt without materializing the full document each time, and/or caching frontmatter start/end positions and only scanning within the frontmatter range.
Summary
autocomplete.js(context parser + suggestion generator) into CodeMirror 6 as aCompletionSourceautocomplete-data.json(57KB schema data) with all frontmatter keys, types, descriptions, and enum values---delimiters) — no suggestions in the markdown bodyensuggestsengine) and value completion (e.g., afterengine:suggestscopilot,claude,codex,custom)How it works
autocomplete-data.jsonis fetched on page load (async, degrades gracefully if unavailable)parseFrontmatterContext()determines the cursor's nesting path, mode (key vs value), typed prefix, and sibling keys already presentgetSuggestions()walks the schema tree and returns matching suggestions, filtering out already-used siblingsCompletionobjects with labels, type annotations, descriptions, and snippetsTesting
en→ suggestions forengine,envappearengine:then space → enum values (copilot, claude, codex, custom) appear---🤖 Generated with Claude Code