Skip to content

feat: rebuild RPC client and API generator - #42

Open
alsi-lawr wants to merge 3 commits into
neovim:masterfrom
alsi-lawr:feat/use-roslyn-generators
Open

feat: rebuild RPC client and API generator#42
alsi-lawr wants to merge 3 commits into
neovim:masterfrom
alsi-lawr:feat/use-roslyn-generators

Conversation

@alsi-lawr

@alsi-lawr alsi-lawr commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

This replaces the old split RPC client and generated API with one
Nvim.Client package. It also:

  • adds explicit shutdown, connection completion, and resource ownership;
  • keeps raw requests, notifications, and handlers as the core API;
  • adds typed MessagePack values and generated convenience methods;
  • supports inbound requests, notifications, and ordered UI redraw events;
  • rebuilds the generator around a neutral API model and Roslyn syntax trees;
  • imports Doxygen structure and maps Neovim references to emitted C# names;
  • emits the contract, client, handles, UI event model, and UI factory as five
    separate generated files.

This is intentionally one breaking change. The old generator emitted code
against the old runtime, so splitting this into several migrations would
require temporary compatibility code solely to connect the two designs.

Size and trade-offs

The branch remains larger than upstream/master, but the final implementation
is substantially smaller than the earlier version of this PR:

Area upstream/master This branch Change
Generator and CLI 786 1,676 +890 (+113%)
Handwritten client/runtime 956 1,258 +302 (+32%)
Maintained production code 1,742 2,934 +1,192 (+68%)
Tests 457 274 -183 (-40%)
All hand-maintained C# 2,199 3,208 +1,009 (+46%)

These are SCC code lines. Generated output, build output, and the old tracked
NvimAPI.generated.cs file are excluded.

Some runtime growth is necessary to fix #40 properly. A client which starts
Neovim or opens a connection has to know whether it owns the underlying
resource, stop it correctly, complete or fail pending requests, and expose when
the connection has ended. The old client mostly avoided this lifecycle state.

INvimClient is a separate trade-off. It lets application code accept, wrap,
or substitute an existing client instead of depending directly on the sealed
NvimClient and its static connection factories. It retains the raw RPC API
and generated methods, and is the documentation source inherited by
NvimClient. There is no second implementation in this repository, so this
also adds another public compatibility surface.

The test suite remains five integration tests. The metadata/emitter test now
checks the five generated output files, and the standard-I/O test waits for the
module's existing readiness signal before sending a request. No additional test
case was added.

Maintained production code by domain

Domain SCC What it gives us Trade-off
Generator: API metadata 260 A neutral API model and source-generated MessagePack metadata decoding rather than sharing the runtime model. Required for the generator/runtime split, but adds a second wire-to-model mapping.
Generator: documentation 500 Reads Doxygen structure, resolves Neovim references, maps parameter and function names to C#, and emits valid XML docs. Not required merely to compile, but required for useful public generated documentation.
Generator: Roslyn emission 797 Uses SyntaxGenerator for ordinary AST construction, with direct C# syntax where it is clearer. It emits separate contract, client, handle, UI event, and UI factory files. Adds the generator-only Workspaces dependency and its development-time transitive packages. It does not enter the runtime package.
Generator: CLI 117 Keeps Neovim and Doxygen processes, filesystem output, diagnostics, and exit codes out of the generator library. A small separate orchestration project.
Generator: assembly metadata 3 Internal visibility for the test assembly and generator. Negligible.
Runtime: transport and lifecycle 613 Owned and borrowed transports, StopAsync, Dispose, completion, connection failure, process shutdown, and TCP, Unix socket, named pipe, standard-I/O, and embedded Neovim support. Necessary correctness code. The old client mostly avoided this state handling. (see #40 )
Runtime: handlers and UI dispatch 322 Async inbound request and notification handlers, cancellation, failure isolation, and ordered UI event batches. More task and registration tracking, but the client can now be used for modules rather than only outbound calls.
Runtime: values and codec 243 A closed typed NvimValue model, full MessagePack integer support, arbitrary map keys, immutable inputs, and low-level decoding using UntrustedData. More public value types than an object-shaped API, in exchange for predictable wire behaviour.
Runtime: public client, interface and errors 80 A small NvimClient facade, specific RPC errors, and the documented INvimClient abstraction. The interface is not required for lifecycle correctness, and it has no second implementation here.

SCC does not count XML comments, so the documentation figure represents the
Doxygen parser and XML-documentation emitter rather than the generated
documentation itself.

The old generator combined metadata acquisition, documentation processing, and
C# generation in NvimAPIGenerator.cs, so there is no useful upstream delta
for each generator subdomain. The branch can be split precisely because those
responsibilities are now separate.

The majority of the remaining increase is therefore deliberate:

  • lifecycle and ownership state that the old client did not implement;
  • inbound handler and UI-event support;
  • a typed wire-value model;
  • useful generated documentation;
  • a generator/runtime boundary that does not share internal wire models.

Dropping netstandard2.0 removes the compatibility socket, buffer, process,
and IsExternalInit code. This reduces the cost, but does not make the branch
smaller than master.

Breaking changes

  • Package, assembly, projects, and namespaces move to Nvim.Client.
  • The package now targets net10.0 only.
  • RPC values use the typed NvimValue model.
  • There are no compatibility packages or forwarding namespaces.

Verification

  • Release build passes with 0 warnings and 0 errors.

    dotnet build Nvim.Client.slnx -c Release --no-restore

  • All 5 integration tests pass.

    dotnet test Nvim.Client.slnx -c Release --no-build --no-restore

  • CSharpier passes.

    csharpier check src test Nvim.Client.slnx Directory.Build.props Directory.Packages.props

  • Regenerating from Neovim 0.12.4 produces no tracked changes.

    dotnet run --project src/Nvim.Client.Generator.Cli /path/to/neovim

    git diff --exit-code

  • The only skipped methods are the two unsupported LuaRef APIs:
    nvim_buf_call and nvim_win_call.

  • The release package builds successfully with one net10.0 DLL/XML pair and
    no Workspaces dependency.

    dotnet pack src/Nvim.Client/Nvim.Client.csproj -c Release --no-build --no-restore

Follow-up

#36 will have the major CI changes (with the version change to match neovim versioning + the hard requirement for a nuget api key in project secrets).

Closes #31
Closes #35
Closes #40

@alsi-lawr
alsi-lawr force-pushed the feat/use-roslyn-generators branch 2 times, most recently from df46a49 to d3c200c Compare July 22, 2026 18:36
Comment thread src/Nvim.Client.Generator/Api/ApiDocumentationReader.cs Outdated
Comment thread src/Nvim.Client.Generator.Cli/Program.cs Outdated
@alsi-lawr
alsi-lawr marked this pull request as draft July 23, 2026 11:19
@alsi-lawr
alsi-lawr force-pushed the feat/use-roslyn-generators branch 8 times, most recently from 27fb366 to 795082d Compare July 24, 2026 03:53
@alsi-lawr
alsi-lawr marked this pull request as ready for review July 24, 2026 04:01
@alsi-lawr
alsi-lawr requested a review from justinmk July 24, 2026 09:07
@alsi-lawr
alsi-lawr force-pushed the feat/use-roslyn-generators branch from 795082d to 2bc6f1a Compare July 24, 2026 10:05
@alsi-lawr

Copy link
Copy Markdown
Contributor Author

@justinmk I appreciate this is a big change. I've gone through the whole solution multiple times to get to this final minimal implementation that captures the requirements for a real durable RPC client. The diff looks a lot bigger than the actual code because a lot of it comes from the added documentation.

test: await standard-I/O module readiness
@alsi-lawr
alsi-lawr force-pushed the feat/use-roslyn-generators branch from 2bc6f1a to 4a4b4b2 Compare July 24, 2026 10:30
@alsi-lawr alsi-lawr changed the title feat: rebuild RPC client and API generation feat: rebuild RPC client and API generator Jul 24, 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

2 participants