Skip to content

Add CodeMirror editor signals (selection, focus, viewport, geometry) and reveal_line#5984

Draft
Jepson2k wants to merge 2 commits intozauberzeug:mainfrom
Jepson2k:cm-cursor-save-reveal
Draft

Add CodeMirror editor signals (selection, focus, viewport, geometry) and reveal_line#5984
Jepson2k wants to merge 2 commits intozauberzeug:mainfrom
Jepson2k:cm-cursor-save-reveal

Conversation

@Jepson2k
Copy link
Copy Markdown

@Jepson2k Jepson2k commented Apr 23, 2026

Motivation

Hosts that embed ui.codemirror as an in-app editor often need feedback about the editor's state — cursor line / column, focus, the visible line range, and editor geometry. None of this is exposed through the Python API today, and there's no programmatic way to scroll a specific line into view.

Implementation

This PR adds:

  • reveal_line(line_number) — scrolls a 1-indexed line into view via CM.EditorView.scrollIntoView.
  • Four typed signal handlers: on_selection_change(line, column), on_focus_change(focused), on_viewport_change(from_line, to_line), on_geometry_change(width, height, content_height).
  • ui.codemirror.handler(callback, debounce_ms=...) factory for per-registration debounce overrides. Both constructor kwargs and methods accept either a bare callable or a wrapped spec, so the two surfaces stay shape-identical.

A single JS ViewPlugin reads the four CM6 ViewUpdate flags and emits per-signal events. Each signal has its own enable prop (auto-flipped when a handler is registered) and debounce-ms prop (defaults 30 / 0 / 100 / 100); when nothing is subscribed the dispatcher bails before computing a payload, so unsubscribed signals cost nothing on the wire. Per-signal dedupe drops redundant emits; debounce timers live on the plugin instance and destroy() clears them.

Tests cover all four signals, the reveal_line → viewport feedback loop, subscription gating (no traffic when nothing's listening), and the debounce-override path end-to-end. Selection / focus events are driven via editor.dispatch({...}) in JS rather than Selenium keystrokes to avoid focus-timing flakiness.

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 2 commits April 23, 2026 18:19
Three small editor-event primitives requested by host applications that
embed CodeMirror as an in-app code editor:

- `on_cursor_line` (typed `CodeMirrorCursorLineEventArguments`, 1-indexed
  line number, 30 ms debounce on the JS side) reports cursor-line
  changes. The debounce is short enough to feel immediate when arrow-
  keying through a file but long enough to coalesce bursts during
  multi-line selection drags.
- `on_save` (typed `CodeMirrorSaveEventArguments`) fires on Ctrl/Cmd+S
  inside the editor and suppresses the browser's default save dialog.
  The Mod-s keymap binding is only installed when the host opts in via
  the `save-shortcut-enabled` prop, so editors without an `on_save`
  handler keep the browser's default behavior.
- `reveal_line(line_number)` scrolls a 1-indexed line into view via
  CodeMirror's `EditorView.scrollIntoView` effect.

Tests dispatch CM6 selection transactions directly rather than relying
on Selenium keystroke focus timing.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the narrow `on_cursor_line` and `on_save` primitives added in
the prior commit with two generic mechanisms (one of which lands here,
the other deferred to a follow-up PR). Both removals are unreleased.

ViewUpdate signal dispatcher (this PR):

- Single JS `ViewPlugin` (`updateDispatcher`) reads four CM6 ViewUpdate
  flags and emits one event per signal: `selection-change` (line +
  column), `focus-change` (focused), `viewport-change` (visible line
  range), `geometry-change` (width / height / content height). Each
  signal has a dedicated `<signal>-tracking-enabled` prop so the
  dispatcher bails early when the host has not subscribed; per-signal
  `<signal>-debounce-ms` props (defaults 30 / 0 / 100 / 100) are read
  fresh on each emit so runtime overrides apply on the next event
  without a Vue watch. Per-signal dedupe of the last emitted payload
  drops redundant traffic. Timers live on the plugin instance and
  `destroy()` clears them, removing the leak-prone `_cursorTimer` on
  the Vue component.
- Public Python API: four typed `on_*_change` handlers + corresponding
  event-args dataclasses. Both the constructor kwargs and the methods
  accept `Handler | CodeMirrorHandlerSpec`; per-registration overrides
  go through a single `ui.codemirror.handler(callback, debounce_ms=...)`
  factory (a static method exposing the spec dataclass) so constructor
  and method surfaces stay shape-identical.

Generic keybindings API (separate follow-up PR): replaces `on_save` with
`keybindings={...}` and `editor.on_keybinding(key, handler)`. No
JS-plugin overlap with the dispatcher landed here (different CM6
primitives — `ViewPlugin` vs `keymap`); the only shared piece is
`CodeMirrorHandlerSpec`, which the keybindings PR extends with
`prevent_default`. Brief no-save-binding gap is invisible to users
since `on_save` is also unreleased.

`reveal_line` is unchanged. The new `viewport-change` signal closes
its previously missing feedback channel — a host can now confirm a
revealed line landed in the visible range.

Tests cover all four signals (with mid-line cursor positions to
exercise the column field, viewport range after `reveal_line`, focus
toggles via JS, geometry under container resize), plus
`test_no_handler_no_traffic` (verifies subscription gating prevents
emit when nothing is listening) and `test_debounce_override` (verifies
the spec factory's debounce_ms is honored end-to-end).

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
@Jepson2k Jepson2k changed the title Add cursor-line, save, and reveal-line events to CodeMirror Add reveal_line and a generic ViewUpdate signal dispatcher to CodeMirror Apr 25, 2026
@Jepson2k Jepson2k changed the title Add reveal_line and a generic ViewUpdate signal dispatcher to CodeMirror Add CodeMirror editor signals (selection, focus, viewport, geometry) and reveal_line Apr 25, 2026
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