Repository navigation
Conversation
`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.
- 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.
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>
There was a problem hiding this comment.
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
CompletionItemschema plus newui.codemirrorconstructor 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. |
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(...)`.
|
@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 |
|
Then @ me for reviews if needed then. I'm not using that for anything else. |
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.
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.
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>
Motivation
Embedding
ui.codemirroras 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/autocompletepackage supports this richly, but it isn't bundled in the current dist and there's no Python API for it.Implementation
CompletionItemTypedDictwith requiredlabeland 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.typeis constrained to CM6's 12 built-in icon names viaCOMPLETION_ICON_TYPES.snippet=Truetreatsapplyas a snippet template —${1:foo}tab-stops are honored, with Tab/Shift-Tab cycling between fields.completions=...,replace_language_completions=False(default merges with the active language pack;Truesuppresses language completions),complete_words_in_document=False(opt-in to CM6'scompleteAnyWord),completion_info_html=False(opt-in to sanitized-HTML rendering of side-panelinfocontent),tooltip_class=None(CSS class on the popup container).editor.completions = ...updates the list at runtime;editor.trigger_completion()opens the popup programmatically (equivalent toCtrl-Space) for "Suggest"-style buttons.The
@codemirror/autocompletepackage was already a transitive dep of thecodemirrormeta-package, sopackage.jsondoesn't change — just re-exports fromsrc/index.mjsand a bundle rebuild.The side-panel
infocontent renders as plain text by default; passcompletion_info_html=Trueto the constructor to render it as sanitized HTML via NiceGUI'ssetHTMLpolyfill.display_labelanddetail(dropdown row text) always render as plain text since CM6's row renderer doesn't expose a non-invasive HTML hook.Progress