FYI I'm working with claude on some LSP features, hear some sneak peak:
Stop capping LSP line tables at 4096 lines (https://github.com/roc-lang/roc/pull/10944) (independent, mergeable on its own)
document_highlight, folding_range and selection_range each built their own byte-offset-to-line table into a fixed [4096]u32. Past line 4096 every position collapsed onto 4095 — for folding this
means start and end become the same line, so ranges are dropped and files over 4096 lines get no folding at all. Fixed by deleting the three copies and using the exactly-sized table position.zig
already had, plus a binary-search lookup. Regression test uses a 5000-line document.
Add LSP rename and find-references (https://github.com/roc-lang/roc/pull/10945)
Adds textDocument/rename, prepareRename and references. Occurrences come from the CIR, so shadowing is respected. Rename refuses rather than half-rewriting: on an unbuildable document, on
non-assign patterns, on names that would change meaning (foo → foo!, case swaps — checked through the real tokenizer and Ident.Attributes), and on captures (k → v inside |v| { inner = |k| k + v
}).
Includes one compiler change: Annotation.name_region in the CIR. A matching annotation is merged into its def and emits no s_type_anno, so the name token on the annotation line had no CIR
representation at all — a rename would have left double : I64 -> I64 behind. Bumps CACHE_VERSION.
Add LSP inlay hints for inferred types (https://github.com/roc-lang/roc/pull/10946) (stacked on #10945)
Renders inferred types next to unannotated bindings, including per-element hints on tuple destructuring. Skips annotated bindings, _-prefixed ones, and any pattern whose source text isn't
exactly the name it binds (compiler-synthesized bindings from ?? desugaring; import "x" as name : Str). Labels are cut at 56 bytes — the generic functions in examples/main.roc render past 300
characters with their where clauses.
Known limitation noted in #10945 and #10946: positions are byte columns, not UTF-16, as position.zig has documented since before these branches. Pre-existing across the whole LSP, but rename
raises the stakes — it edits rather than highlights. Should be fixed at the protocol boundary in its own PR.
Not implemented: cross-module rename or references. Only the requested document is touched, so renaming an exported name breaks importers.
2/3 PRs are green in CI/CD Pipeline
![]()
#10944 (https://github.com/roc-lang/roc/pull/10944) │ Stop capping line tables at 4096 lines, failures with "The hosted runner lost communication with the server" after 47 minutes; "
@Eli Dowling or @Luke Boswell or @Anton , could one of you help or do the final review?
Yes, I will get started on #10946
cool, FYI its stacked on #10945
Yeah, I just noticed :)
I will start with #10945
Here an example without and with LSP inlay hints for inferred types:
![]()
![]()
Toggled via settings.json "inlay_hints"
![]()
I have reviewed 14 out of 20 files and will continue tomorrow
@Anton I'll tackle the utf16/bytes from Ticket "# LSP: positions are byte offsets, not UTF-16 code units #10948" in #10945 too at the moment, and ping you when all pipelines are green again.
@Anton thanks for the review, what are the next steps to get this merged, so we can continue with final one #10946 (#10944 is already merged)? I merged main again, all checks were green before.
I just came back from vacation @Alf Richter, I should be able to review PR#10946 in the coming days
@Alf Richter I also wanted to tackle a few LSP related things but didn't get any feedback as to whether it would be useful (and then other projects took precedence) – perhaps some of the things reported in #contributing > lsp: suggested improvements/refactors might come in handy?
I recall that the utf-16 issue was definitely in there as well.
In any case, cool to see that someone wants to drive LSP features forward!
@Anton thanks for the review and merge, I have a new PR with two new code actions:
new LSP actions New LSP actions — sneak peek:
1) annotate function or binding
![]()
![]()
![]()
![]()
2) generate test:
![]()
![]()
FYI:
# No test is offered for these three, each for its own reason:
# identity a type variable has no one literal that fits it
# apply an argument that is itself a function would have to be
# written out, and this cannot invent one
# answer not a function, so there is no call to generate
# The action stays away rather than writing something that looks like a
# test and is not one.
identity : a -> a
identity = |x| x
apply : (I64 -> I64), I64 -> I64
apply = |f, n| f(n)
answer : I64
answer = 42
Last updated: Sep 03 2026 at 15:16 UTC