Skip to content

Prototype#1

Draft
jbdyn wants to merge 43 commits intomainfrom
prototype
Draft

Prototype#1
jbdyn wants to merge 43 commits intomainfrom
prototype

Conversation

@jbdyn
Copy link
Copy Markdown
Member

@jbdyn jbdyn commented Feb 25, 2026

First version of an ELVA plugin for Emacs by @stefanv.

Todo (now or in future PRs):

  • use elva.provider.WebsocketProvider instead of custom message encoding/decoding and reconnection handling
  • check lisp styling (e.g. by comparing to https://lisp-lang.org/style-guide/)
  • make elva-bridge.py a tool/package ready to be installed via uv tool install or pipx install or similar - or make it even installable as an ELVA app?

Initial skeleton for Emacs integration:
- elva-bridge.py: Python subprocess using pycrdt for Yjs protocol
- elva.el: Emacs package for buffer synchronization
- plan.org: Development plan and architecture notes
- Add proper Y-protocol message encoding/decoding
- Implement SYNC_STEP1/SYNC_STEP2 handshake
- Handle incremental updates (SYNC_UPDATE)
- Fix echo prevention for bidirectional sync
- Use "ytext" key to match Elva editor client
- Separate stderr to dedicated buffer so JSON parsing works
- Fix text observer to send server changes to Emacs
- Add fatal error logging with traceback
- Update plan: Phase 3 complete, add future improvements
pycrdt uses UTF-8 byte positions while Emacs uses character positions.
The bridge now converts between them:
- _byte_pos_to_char_pos: for server -> Emacs messages
- _char_pos_to_byte_pos: for Emacs -> server messages
- _byte_count_to_char_count: for delete operations

This fixes editing with multi-byte characters (CJK, emoji, etc).
Cursor preservation:
- Point now adjusts correctly when remote edits occur before cursor
- Inserts before cursor shift point forward
- Deletes before cursor shift point backward

Change batching:
- Rapid local changes are batched with configurable delay (50ms default)
- Reduces network traffic during fast typing
- Can be disabled by setting elva-batch-delay to 0

Reconnection handling:
- Automatic reconnection on connection loss
- Configurable retry count and delay
- Connection status tracking via elva-status
- Report HTTP errors with server's reason phrase to Emacs
- Add "error" op handling in elva.el
- Update plan with future improvements
- Update plan.org to mark error handling tasks as done
- Update TEST.md with room listing documentation
- Emacs client has elva-default-port customization
- Update documentation with new default port
- Add TODOs for Emacs room listing and safe room connection
- Accept room-id only (uses localhost:7654)
- Accept host/room-id (uses default port)
- Accept host:port/room-id
- Accept full ws:// URLs
- Add elva-default-host customization
- Update documentation
Room IDs must be 10-250 chars, alphanumeric plus hyphen/underscore
Room IDs must be 10-250 characters. Updated all examples
to use "my-room-0001" which clearly shows the pattern.
Bridge now sends {op: "sync_complete", length, content} to Emacs
after receiving the first SYNC_STEP2 from the server. This allows
Emacs to know the initial room content before deciding whether to
push local buffer content.
- Display room name in modeline: Elva[room-id]
- elva-connect creates new buffer *elva:room-id* for room content
- Add elva-room-from-buffer to push buffer to empty rooms only
- Fail with message if room already has content when pushing
- On reconnect, buffer content is restored to room if room is empty
- Modeline shows connection status with colored faces:
  - Green (success): connected
  - Yellow (warning): offline or reconnecting
- Push modes: 'if-empty (room-from-buffer) vs 'always (reconnect)
bridge identifies as "emacs"
- Clear buffer before connecting/reconnecting
- Save content before clearing for reconnect case
- Restore saved content only if room is empty after sync
Fetches available rooms from server and offers them as completion
candidates, allowing use of icomplete or other completion frameworks.
- Import and initialize pycrdt Awareness
- Handle incoming AWARENESS messages from server
- Track and send local cursor position
- Send awareness state updates to Emacs
- Queue and send awareness updates to server
- Show remote users' cursors as colored highlights
- Each user gets a unique color from palette
- Send local cursor position to bridge on movement
- Handle end-of-buffer cursor positions
- Clean up overlays on disconnect
- Clear stale awareness entries on connect
- Send awareness disconnect message on shutdown
- Kill existing bridge process before starting new one in Emacs
- Fix reconnection to properly load room content
- Add default case in sync_complete to load room content on normal connect
- Allow elva-connect to disconnect and reconnect if already connected
- Only suppress cursor broadcast for remote edits, not local edits
- Flush pending changes before applying remote edits
- Redraw cursor overlays after text operations
- Store cursor data for redrawing
Prevent cursor stretching and walking backward issues
- Mark awareness protocol and user identification as complete
- Add detailed cursor awareness implementation checklist
- Add technical notes on awareness protocol
- Only adjust remote cursor positions for local edits (not remote edits)
- Remote clients send their own updated cursor positions after their edits
- Fixes cursor "walking backwards" issue
- Add bounds checking for cursor byte positions
- Use refresh() instead of refresh_lines() for reliable cursor display
The bridge was computing character counts for DELETE operations using
the document state AFTER the deletion, causing the deleted bytes to be
missing from the calculation. This resulted in delete count of 0 for
end-of-file deletes, causing Emacs buffers to desync.

Fix: store pre-change text before applying server updates and use it
for position/count conversion in the text change observer.

Also add defensive bounds checking in elva.el to clamp insert positions
to valid buffer range.
- Remove dead _send_update method from bridge
- Use public awareness.client_states API instead of private _states
- Remove unused variable binding in elva--sentinel
- Fix process buffer leak: track and kill stdout/stderr buffers on
  disconnect and reconnect
- Rewrite cursor overlay logic with clearer bounds handling
- Bridge UTF-8 byte<->char position conversion (ASCII, multibyte, clamping)
- Bridge byte count to char count conversion
- Pre-change text mechanism for server delete operations
- Bridge delta processing (_on_text_change)
- Awareness filtering and byte->char cursor conversion

Also revert bridge _states->client_states change from previous cleanup:
pycrdt.Awareness only exposes _states; the public client_states property
is on elva.awareness.Awareness which the bridge doesn't use.
- Add `elva-list-rooms`: tabulated-list buffer showing active rooms
  from the server's /rooms endpoint (RET to connect, g to refresh)
- Move `elva--fetch-rooms` to return full room alists (identifier,
  clients, persistent) and reuse for both room listing and
  elva-connect completion
- Fix buffer leak: kill url-retrieve-synchronously temp buffer
- Fix disconnect: kill process before its buffers so Emacs doesn't
  prompt about running processes on the stderr buffer
@stefanv
Copy link
Copy Markdown
Collaborator

stefanv commented Mar 12, 2026

The emacs bridge probably needs to be matched in version to the elisp, so wondering how we advise users to do that: emacs can try to import it, and if missing provide the uv and pip instructions for the specific version required? We can match minor versions, allowing patches to vary.

@jbdyn
Copy link
Copy Markdown
Member Author

jbdyn commented Mar 13, 2026

Given the elva-bridge.py is reusable for other plugins (looking at you, vim), then we would need version matching, yes.
Your approach seems resonable to me. I would just add that the elva.el script has the minimal required version hardcoded somewhere, checks for that version and sets up the venv accordingly.

For now though, I would keep elva-bridge.py in here, ensuring compatibility with elva.el at all times.

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.

2 participants