Skip to content

Add lint diagnostics API to CodeMirror - #5985

Draft
Jepson2k wants to merge 11 commits into
zauberzeug:mainfrom
Jepson2k:cm-diagnostics
Draft

Jepson2k wants to merge 11 commits into
zauberzeug:mainfrom
Jepson2k:cm-diagnostics

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 programmatically computed diagnostics — type-check results, lint output, runtime errors mapped back to source — onto specific lines or column ranges, with hover tooltips so the user can read the message without leaving the editor. CodeMirror 6's @codemirror/lint package provides this, but it isn't bundled in the current dist and there's no Python API for it.

pr-5985

Implementation

  • Diagnostic TypedDict with required line (1-indexed) and message, plus optional severity ('error' | 'warning' | 'info' | 'hint', default 'error'), source (label shown next to the message), and column / end_column (1-indexed, exclusive end) for sub-line ranges.
  • diagnostics is a mutable list of Diagnostic dicts; mutating it (or reassigning) syncs to the client.
  • open_lint_panel() / close_lint_panel() / toggle_lint_panel() show or hide CodeMirror's built-in panel listing the current diagnostics.
  • get_diagnostic_count() (async) returns a per-severity dict for status-bar UIs.

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

Diagnostic messages render as plain text by default; pass diagnostic_message_html=True to the constructor to render messages as sanitized HTML via NiceGUI's setHTML polyfill. Out-of-range diagnostic lines are routed to the Python logger via logAndEmit('warning', ...).

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 5 commits April 23, 2026 18:27
`set_diagnostics(list[Diagnostic])` and `clear_diagnostics()` render
inline error/warning marks with hover tooltips on user-supplied lines,
backed by CodeMirror's built-in lint system. Each `Diagnostic` is a
small TypedDict with required `line` (1-indexed) and `message`, plus
optional `severity` (`'error'` | `'warning'` | `'info'` | `'hint'`,
default `'error'`) and `source` (label shown next to the message).

The editor installs `CM.linter(() => [])` so that `setDiagnostics()`
works and inline error marks render. **Important:** we deliberately
do NOT use `CM.lintGutter()` — it pulls in `lintGutterTooltip`, a
StateField that registers itself via `showTooltip.from(field)` and
returns null on most transactions. That null provider sits in the
`showTooltip` facet and silently suppresses the autocomplete popup
outside of paren contexts. `CM.linter()`'s only tooltip is a
`hoverTooltip` that fires on mouseover, not on every keystroke.

Adds `@codemirror/lint` to the dist bundle (already a transitive
dependency of `codemirror`, just needs to be re-exported from
`src/index.mjs` so it ends up in `dist/`). Bundle rebuilt.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
- Diagnostic TypedDict: make `line`/`message` required and use
  `NotRequired` for `severity`/`source`, matching the docstring.
- `set_diagnostics` docstring: drop the inaccurate gutter-underline
  claim; we deliberately don't install `lintGutter()`.
- JS: drop the dead empty-string fallback for `message` (now required),
  and `console.warn` + skip non-integer or out-of-range line numbers
  instead of silently clamping them onto the wrong line.
- Test: drop the slack `screen.wait(0.3)`; assert that severity-suffixed
  classes (`.cm-lintRange-error`, `.cm-lintRange-info`) appear; add a
  case exercising the default-`error` severity branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ount read-back

- Add optional column/end_column to Diagnostic for sub-line marks
- Add open_lint_panel/close_lint_panel/toggle_lint_panel
- Add async get_diagnostic_count returning a per-severity dict
- Use ?? instead of || when forwarding source so empty strings pass through
- Drop :class: cross-ref and add *Added in version X.Y.0* placeholders
Adds DiagnosticCount TypedDict and exposes both Diagnostic and DiagnosticCount
from nicegui.elements.codemirror so users can import them without reaching
into the inner module.
Inject a renderMessage callback per diagnostic in setDiagnostics that
runs the message through NiceGUI's global setHTML polyfill (DOMPurify-
backed). Plain text keeps working as-is; sanitized HTML (`<code>`,
`<b>`, etc.) renders inline in the lint panel and hover tooltips.
@Jepson2k
Jepson2k marked this pull request as ready for review May 2, 2026 23:32
@falkoschindler
falkoschindler self-requested a review May 5, 2026 12:58
@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
Jepson2k added 3 commits May 12, 2026 19:31
…gs to logAndEmit

Replace set_diagnostics()/clear_diagnostics() with a mutable diagnostics
property keyed on the codemirror element. Constructor gains diagnostics and
diagnostic_message_html parameters; message content renders as plain text
by default. JS routes out-of-range line warnings through logAndEmit so the
Python logger picks them up.
@Jepson2k Jepson2k mentioned this pull request Jun 4, 2026
6 tasks done
Jepson2k and others added 2 commits June 29, 2026 12:21
Resolve conflicts from zauberzeug#6000 (CodeMirror custom keybindings), which refactored
the same files. Diagnostics and keybindings are orthogonal, so each conflict
keeps both:

- codemirror.py: keep `Literal`/`TypedDict` imports (still used by the
  Diagnostic severity literal and the TypedDicts); 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 the Diagnostic/DiagnosticCount TypedDicts and both
  constructor params/docstrings.
- codemirror.js: keep both the diagnostics props/methods (applyDiagnostics,
  lint-panel helpers, getDiagnosticCount) and the keymap props/methods.
- codemirror_documentation.py: keep both the Linting Diagnostics and Custom
  Keybindings demos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the defensive list() copy when storing diagnostics in the constructor and
setter: the declared list[Diagnostic] type already guarantees the shape, and
this matches how the sibling line_tooltips stores its value verbatim.

Also reflow the Diagnostic and diagnostics docstrings to one sentence per line
per CONTRIBUTING.md.

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.

2 participants