Skip to content

Add autocomplete API to CodeMirror - #5986

Draft
Jepson2k wants to merge 18 commits into
zauberzeug:mainfrom
Jepson2k:cm-custom-completions
Draft

Jepson2k wants to merge 18 commits into
zauberzeug:mainfrom
Jepson2k:cm-custom-completions

Conversation

@Jepson2k

@Jepson2k Jepson2k commented Apr 23, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Embedding ui.codemirror as an in-app editor often needs to surface host-application-specific identifiers (functions, variables, snippets, etc.) in the autocomplete dropdown — e.g. an in-app scripting environment exposing its own API to the user. CodeMirror 6's @codemirror/autocomplete package supports this richly, but it isn't bundled in the current dist and there's no Python API for it.

pr-5986

Implementation

  • CompletionItem TypedDict with required label and snake_case optional fields (apply, snippet, display_label, detail, info, type, boost, commit_characters, section, class_name). The JS layer maps these to CM6's camelCase.
  • type is constrained to CM6's 12 built-in icon names via COMPLETION_ICON_TYPES.
  • snippet=True treats apply as a snippet template — ${1:foo} tab-stops are honored, with Tab/Shift-Tab cycling between fields.
  • New constructor kwargs: completions=..., replace_language_completions=False (default merges with the active language pack; True suppresses language completions), complete_words_in_document=False (opt-in to CM6's completeAnyWord), completion_info_html=False (opt-in to sanitized-HTML rendering of side-panel info content), tooltip_class=None (CSS class on the popup container).
  • editor.completions = ... updates the list at runtime; editor.trigger_completion() opens the popup programmatically (equivalent to Ctrl-Space) for "Suggest"-style buttons.

The @codemirror/autocomplete package was already a transitive dep of the codemirror meta-package, so package.json doesn't change — just re-exports from src/index.mjs and a bundle rebuild.

The side-panel info content renders as plain text by default; pass completion_info_html=True to the constructor to render it as sanitized HTML via NiceGUI's setHTML polyfill. display_label and detail (dropdown row text) always render as plain text since CM6's row renderer doesn't expose a non-invasive HTML hook.

Progress

  • The PR title is a short phrase starting with a verb like "Add ...", "Fix ...", "Update ...", "Remove ...", etc.
  • The implementation is complete.
  • This PR does not address a security issue.
  • Pytests have been added.
  • Documentation has been added.
  • No breaking changes to the public API.

Jepson2k and others added 3 commits April 23, 2026 18:32
`custom_completions` constructor kwarg + property + `set_custom_completions(...)`
setter add a programmatic completion source to the editor's autocomplete
dropdown. Each entry is a `CompletionItem` TypedDict with required `label`
and optional `detail`, `info`, `apply`, and `type` (`'function'`,
`'variable'`, `'class'`, `'keyword'`, etc., controlling the icon shown
next to the entry).

The kwarg uses the `DEFAULT_PROP | None` pattern so per-instance defaults
compose correctly with the rest of the constructor.

JS side installs `CM.autocompletion({override: ...})` lazily through a
Compartment so the source can be reconfigured after mount when
`set_custom_completions` (or the `customCompletions` prop) changes.

Adds `@codemirror/autocomplete` to the dist bundle (already a transitive
dep of `codemirror`, just re-exported from `src/index.mjs`). Bundle
rebuilt.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
- Make `label` required on CompletionItem (was incorrectly optional under
  total=False); switch the rest to NotRequired.
- Constrain `type` to a Literal of CM6's 12 built-in icon names, matching
  SUPPORTED_LANGUAGES/SUPPORTED_THEMES precedent.
- Add `displayLabel`, `boost`, `commitCharacters`, and `section` (string
  form) to cover the full public CM6 Completion surface; field names are
  kept in CM6's camelCase so the dict round-trips verbatim.
- Stop forcing `type: function` and stop padding empty detail/info strings
  in the JS source — only forward keys the user supplied.
- Drop the customCompletions add_rename — add_rename is a deprecation
  shim, not a wire mapper, and isn't needed for a brand-new prop.
- Add version markers, drop a dead nonlocal in test_custom_completions,
  and replace the brittle screen.wait(0.3) sleeps in
  test_set_custom_completions_replaces_initial.
- Extend the autocomplete test to assert boost ordering and displayLabel
  rendering. Refresh the docs demo to exercise the new fields.
…rge-with-language default, manual trigger

Reshape the autocomplete surface introduced earlier in this branch to be a
complete, idiomatic NiceGUI feature rather than a thin pass-through to CM6.

Public API:
- Rename `custom_completions` -> `completions` on the constructor, property,
  and setter; the corresponding JS prop becomes `completions`.
- `CompletionItem` keys move from CM6's camelCase (`displayLabel`,
  `commitCharacters`) to Python-idiomatic snake_case (`display_label`,
  `commit_characters`); the JS layer maps them to CM6's wire format.
- Add `snippet: bool` per-item flag, wrapping `apply` with
  `snippetCompletion()` so templates with `${1:foo}` tab-stops work.
- Add `class_name: str` per-item field, surfaced through CM6's `optionClass`
  hook so individual entries can be styled.
- Add `tooltip_class: str | None` constructor kwarg for styling the popup
  container via `tooltipClass`.
- Add `replace_language_completions: bool = False` constructor kwarg. The
  default (`False`) registers entries via `EditorState.languageData` so they
  merge with the active language pack's completions; `True` switches to
  CM6's `override` to suppress them.
- Add `complete_words_in_document: bool = False` to enable CM6's
  `completeAnyWord` source.
- Add `trigger_completion()` method to open the popup programmatically
  (equivalent to Ctrl-Space).

Internal:
- Rename `COMPLETION_ICON_TYPE` -> `COMPLETION_ICON_TYPES` to match the
  plural naming of `SUPPORTED_LANGUAGES` / `SUPPORTED_THEMES` in the same
  file.
- Drop the manual `startsWith` filter in the JS source; CM6's built-in
  matcher scores results from `validFor`-cached options, which gives users
  fuzzy/substring matching for free.
- Drop the redundant `activateOnTyping: true` (CM6 default).
- All version annotations use `*Added in version X.Y.Z*` placeholder; the
  release version is filled in at release time.

Tests:
- Replace the original two completion tests with seven covering: basic
  rendering and ordering with `display_label` + `class_name`,
  set-replaces-initial (now strengthened to assert exact rendered labels so
  it actually fails if the new list is appended rather than replacing),
  parametric merge-vs-replace against Python's keyword pack, word-from-
  document, snippet acceptance, tooltip class, and manual trigger.

Docs:
- Rewrite the `codemirror_documentation.py` demo to showcase merge mode,
  snippet expansion, per-entry CSS classes, popup styling, and manual
  trigger via a button.
@Jepson2k Jepson2k changed the title Add custom completions to CodeMirror Add autocomplete API to CodeMirror Apr 27, 2026
- Drop duplicate `autocompletion()` extension in replace mode by carrying
  both sources and styling on a single call; gate the `Prec.highest`
  styling layer on merge-mode-with-styling-needed only.
- Use `??` (not `||`) for the `apply` fallback so an empty `apply`
  string is preserved instead of being overridden by `label`.
- `completions` getter returns a copy of the underlying list so
  in-place mutations cannot silently desync from the editor state.
- Make `test_replace_language_completions` deterministic by replacing
  the tautological wait + 0.3 s sleep with positive predicates: wait
  for `print` to appear (merge mode) or expect `wait_for` to time out
  (replace mode, mirroring `screen.should_not_contain`'s pattern).
Wrap the side-panel info content in a function returning a div whose
innerHTML is set via NiceGUI's global setHTML polyfill (DOMPurify-
backed). Plain text keeps working as-is; sanitized HTML (`<code>`,
`<b>`, links, etc.) renders inline alongside completion entries.
Jepson2k and others added 2 commits April 30, 2026 09:56
The test had a Selenium-vs-websocket race: cm.click() and cm.send_keys('ba')
fired immediately after editor.set_completions(...), so when the websocket
flush lagged the popup opened against the stale [banana] source.
CodeMirror's already-open autocomplete popup doesn't refresh on a later
compartment reconfigure, so wait_for(count == 2) timed out indefinitely
even though set_completions had eventually landed.

Wait for the new completions prop to land on the client before clicking
into the editor — getElement(editor.id).completions.length === 2 is the
synchronization point that guarantees Vue's watcher has fired and
rebuildCompletions() has reconfigured the source.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Jepson2k
Jepson2k marked this pull request as ready for review May 3, 2026 00:36
@evnchn
evnchn requested a review from Copilot May 3, 2026 07:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a first-class CodeMirror 6 autocompletion API to ui.codemirror, allowing applications to provide custom completion entries (including snippets, tooltips, grouping, and styling) and to control whether language-pack completions are merged or replaced.

Changes:

  • Add a Python-side CompletionItem schema plus new ui.codemirror constructor kwargs and runtime methods (set_completions, trigger_completion).
  • Implement the corresponding client-side completion source wiring (merge/replace modes, optional complete-from-document, tooltip/option CSS classes, snippet support, sanitized HTML info panel).
  • Add documentation and Selenium-based tests covering the new behaviors; rebuild the CodeMirror bundle/export surface to include @codemirror/autocomplete.

Reviewed changes

Copilot reviewed 2 out of 50 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
website/documentation/content/codemirror_documentation.py Adds an “Autocomplete” documentation demo showing custom completions, snippets, and styling.
tests/test_codemirror.py Adds integration tests for custom completions, replace/merge behavior, snippet insertion, tooltip class, trigger API, and sanitized HTML info.
nicegui/elements/codemirror/src/index.mjs Re-exports @codemirror/autocomplete from the NiceGUI CodeMirror ESM entrypoint.
nicegui/elements/codemirror/codemirror.py Introduces CompletionItem, new constructor kwargs, and new public methods/properties for completions.
nicegui/elements/codemirror/codemirror.js Implements client-side completion sources, rebuild logic, tooltip/option classes, and programmatic triggering.
nicegui/elements/codemirror/dist/index.js Rebuilt dist export surface including autocomplete symbols.
nicegui/elements/codemirror/dist/index-lFfiJp4G.js Rebuilt dist chunk (bundle artifact).
nicegui/elements/codemirror/dist/index-lFfiJp4G.js.map Updated source map for rebuilt dist chunk.
nicegui/elements/codemirror/dist/index-DK3B1lUH.js Rebuilt dist chunk (bundle artifact).
nicegui/elements/codemirror/dist/index-DK3B1lUH.js.map Updated source map for rebuilt dist chunk.
nicegui/elements/codemirror/dist/index-CjBeoanQ.js Rebuilt dist chunk (bundle artifact).
nicegui/elements/codemirror/dist/index-CjBeoanQ.js.map Updated source map for rebuilt dist chunk.
nicegui/elements/codemirror/dist/index-CbcSEhou.js Rebuilt dist chunk (bundle artifact).
nicegui/elements/codemirror/dist/index-CNNPw3hS.js Rebuilt dist chunk (bundle artifact).
nicegui/elements/codemirror/dist/index-BqMnV85q.js Rebuilt dist chunk (bundle artifact).
nicegui/elements/codemirror/dist/index-BcXIIfoH.js Rebuilt dist chunk (bundle artifact).
nicegui/elements/codemirror/dist/index-BcXIIfoH.js.map Updated source map for rebuilt dist chunk.

Comment thread nicegui/elements/codemirror/codemirror.py Outdated
Comment thread nicegui/elements/codemirror/codemirror.py
Comment thread nicegui/elements/codemirror/codemirror.py
Comment thread tests/test_codemirror.py Outdated
@falkoschindler falkoschindler added this to the Next milestone May 5, 2026
@falkoschindler falkoschindler added feature Type/scope: New or intentionally changed behavior review Status: PR is open and needs review labels May 5, 2026
Drop Sphinx-style `:class:` / `:meth:` / `:func:` cross-ref roles from
codemirror docstrings — NiceGUI renders docstrings via plain
`docutils.publish_parts` (html4 writer), not Sphinx, so unknown
interpreted-text roles render as the literal string in the docs page
instead of as cross-references. Use plain double-backticks like the rest
of the package. Also fix the `CompletionItem` first line so it points to
both the constructor's `completions` kwarg and `set_completions`, since
the type is used in both places.

Make `test_replace_language_completions` deterministic for `replace=True`:
the previous form relied on `screen.wait_for(...)` timing out at the 4 s
implicit wait to prove no popup appeared, which burned 4 s per
parametrized run and was timing-brittle under load. Replace with a 0.5 s
settle wait + direct `assert 'print' not in _rendered_labels(...)`.
@evnchn

evnchn commented May 6, 2026

Copy link
Copy Markdown
Collaborator

@Jepson2k I think Copilot review is free for the repo since it's public, so feel free to serve yourself for all your other PRs.

@Jepson2k

Jepson2k commented May 6, 2026

Copy link
Copy Markdown
Contributor Author

@Jepson2k I think Copilot review is free for the repo since it's public, so feel free to serve yourself for all your other PRs.

It appears I'd need to sign up to the pro plan to use it: https://docs.github.com/en/copilot/concepts/agents/code-review#about-automatic-pull-request-reviews

@evnchn

evnchn commented May 6, 2026

Copy link
Copy Markdown
Collaborator

Then @ me for reviews if needed then. I'm not using that for anything else.

Jepson2k added 3 commits May 6, 2026 16:01
The @completions.setter at line 441 already does the same thing
(self._props['completions'] = completions or [] + self.update()).
Two ways to do one thing is one too many — keep the property surface
only, as the cm-line-tooltips review established.
@falkoschindler
falkoschindler self-requested a review May 12, 2026 05:47
Jepson2k added 4 commits May 12, 2026 19:36
Default the side-panel info renderer to plain text so a caller passing
'a < b' sees 'a < b' rather than '<b>'-driven stripping. Hosts opt in to
sanitized HTML by passing completion_info_html=True to the constructor —
matching the line_tooltip_html / decoration_text_html precedent.
…ing ENTER

CM6's autocomplete plugin sets the first-option auto-selection on a microtask
after the popup mounts. On slow CI runners the wait_for(rendered labels) check
can pass before the selection is committed, causing the subsequent ENTER to fall
through as a newline insert instead of accepting the snippet completion.
@Jepson2k Jepson2k mentioned this pull request Jun 4, 2026
6 tasks done
Jepson2k and others added 2 commits June 29, 2026 12:28
Resolve conflicts from zauberzeug#6000 (CodeMirror custom keybindings), which refactored
the same files. Autocomplete and keybindings are orthogonal, so each conflict
keeps both:

- codemirror.py: keep `Literal`/`TypedDict` imports (still used by
  COMPLETION_ICON_TYPES and CompletionItem); import SUPPORTED_LANGUAGES/THEMES
  from .constants and KeyBindingElement from .keybindings (dropping the inline
  Literals); add CodeMirrorKeyBindingEventArguments to the events import; class
  now extends KeyBindingElement; keep COMPLETION_ICON_TYPES, CompletionItem, and
  both constructor params/docstrings.
- codemirror.js: keep both the autocomplete props/watchers/methods and
  compartment and the keymap props/watchers/methods and compartment.
- codemirror_documentation.py: keep both the Autocomplete and Custom
  Keybindings demos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Split the multi-sentence lines in the CompletionItem and completions docstrings
per CONTRIBUTING.md. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@falkoschindler

Copy link
Copy Markdown
Contributor

Same as #5984: conflicts in the CodeMirror element from the line-anchor work in 3.16. Please bring it up to date with main whenever it is this one's turn, then mark it "ready for review". Assigning to you and marking as draft until then. ※

@falkoschindler
falkoschindler marked this pull request as draft August 19, 2026 12:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Type/scope: New or intentionally changed behavior review Status: PR is open and needs review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants