Skip to content

feat: rope-backed Documents store with self.documents() (#12)#15

Merged
meymchen merged 1 commit into
mainfrom
issue/12-documents-rope
Jun 16, 2026
Merged

feat: rope-backed Documents store with self.documents() (#12)#15
meymchen merged 1 commit into
mainfrom
issue/12-documents-rope

Conversation

@meymchen

Copy link
Copy Markdown
Owner

Summary

Closes #12.

The root of the document-sync work (CONTEXT.md, ADR 0003 / 0005): a rope-backed document store, concurrency-safe and reachable from handlers without writing lock code.

Change

  • Document — backed by ropey::Rope, keyed by Uri, carrying language id, version, and contents. ropey never leaks into the public API.
  • DocumentsArc<RwLock<..>> handle, cheap to clone. Exposed on the trait via LanguageServer::documents() and on the handler receiver via Context::documents(); all clones share one store.
  • Position encodingPositionEncoding field defaulting to Utf16 (the LSP-mandatory default; Position encoding negotiation (UTF-8 preferred, UTF-16 fallback) #10 layers the UTF-8 handshake on top). Both position <-> offset paths read it: UTF-8 native byte index, UTF-16 via char/utf16 helpers (ADR 0016). Position.character is never treated as a ropey char index.
  • Mutation primitivesopen / apply_incremental_change / close / save as plain methods.

Robustness (from review)

  • apply_incremental_change advances the document version, and rejects a reversed range (end before start) with InvalidRequest instead of panicking Rope::remove under the write lock (which would poison the store for every later access).
  • save() returns None for a URI that was never opened.
  • UTF-8 position_to_offset rejects mid-codepoint and past-end-of-line offsets.

Out of scope

Test

tests/documents.rs — 16 tests covering read-back, encoding default/set, UTF-8 & UTF-16 conversions (incl. emoji / combining-char round-trips), incremental + full-document replacement with version assertions, reversed-range rejection (store stays usable), save on a missing doc, UTF-8 boundary rejection, cheap clone, and self.documents()/ctx.documents() sharing the same store. cargo clippy --all-targets clean.

🤖 Generated with Claude Code

Build the root of document-sync (CONTEXT.md, ADR 0003/0005):

- Rope-backed `Document` (ropey::Rope) keyed by URI, carrying language id,
  version, and contents; `ropey` never leaks into the public API.
- Concurrency-safe `Documents` handle (`Arc<RwLock<..>>`), cheap to clone;
  exposed on the trait via `documents()` and on the handler receiver via
  `Context::documents()`.
- Position encoding field defaulting to UTF-16 (the LSP-mandatory default;
  #10 layers the UTF-8 handshake on top). Both position<->offset paths read
  it: UTF-8 native byte index, UTF-16 via char/utf16 helpers (ADR 0016).
- Mutation primitives (open / apply_incremental_change / close / save) as
  plain methods. apply_incremental_change advances the document version and
  rejects a reversed range (which would otherwise panic Rope::remove under
  the write lock and poison the store). save() returns None for an unopened
  URI. UTF-8 position_to_offset rejects mid-codepoint / past-EOL offsets.

Wiring the mutations into the dispatcher is out of scope (#9); the UTF-8
negotiation handshake is out of scope (#10).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@meymchen meymchen force-pushed the issue/12-documents-rope branch from 6055a72 to 6e3ebda Compare June 16, 2026 17:24
@meymchen meymchen merged commit 6304df8 into main Jun 16, 2026
3 checks passed
@meymchen meymchen deleted the issue/12-documents-rope branch June 16, 2026 17:25
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.

Build the Documents store (rope-backed Document, concurrency-safe self.documents())

1 participant