Skip to content

Fix VS Code prepareRename raw error popup at non-renameable positions - #52

Merged
clrudolphi merged 2 commits into
masterfrom
fix/issue-47-preparerename-quiet-noop
Jul 6, 2026
Merged

Fix VS Code prepareRename raw error popup at non-renameable positions#52
clrudolphi merged 2 commits into
masterfrom
fix/issue-47-preparerename-quiet-noop

Conversation

@clrudolphi

Copy link
Copy Markdown
Collaborator

Summary

  • textDocument/prepareRename's handler in LanguageServerOptionsExtensions.InitializeCustomProtocolRouting threw an InvalidOperationException whenever StepRenameHandler.HandlePrepareRenameAsync returned null (i.e. "rename not available at this cursor position"). The comment above the code assumed vscode-languageclient would treat a thrown JSON-RPC error from prepareRename as a quiet no-op — it doesn't. It surfaces the raw exception text as a visible error popup, which is confusing for what should be silent, native-like rename behavior.
  • The LSP spec allows prepareRename to return null to mean "rename not supported here," and clients are expected to handle that quietly. HandlePrepareRenameAsync already returns Task<LspRange?>, so the fix is to register the OnRequest handler with a nullable LspRange? result and let null pass straight through, instead of translating it into a thrown exception.
  • This mirrors the already-working null-handling pattern on the same handler class: textDocument/rename (HandleRenameAsync) falls back to an empty WorkspaceEdit, and reqnroll/renameTargets (HandleRenameTargetsAsync) falls back to an empty RenameTargetsResponse — neither of those throws.
  • Updated the stale comment above the handler registrations to describe the actual (now correct) behavior instead of the old, incorrect assumption about VS Code's error handling.
  • This does not touch the ambiguous-step case, which was already fixed separately via the disambiguation-picker redesign in PR VS Code: QuickPick disambiguation for ambiguous F16 rename targets #27.

Test plan

  • Added PrepareRename_from_feature_returns_null_when_no_binding_matches_the_step to StepRenameHandlerTests, asserting HandlePrepareRenameAsync returns null (not a thrown exception) when no binding matches the cursor position.
  • dotnet build src/LSP/Reqnroll.IdeSupport.LSP.Server/Reqnroll.IdeSupport.LSP.Server.csproj -nologo — Build succeeded, 0 Warning(s), 0 Error(s).
  • dotnet test tests/LSP/Reqnroll.IdeSupport.LSP.Server.Tests -nologoPassed! - Failed: 0, Passed: 570, Skipped: 0, Total: 570, Duration: 8 s

Fixes #47

🤖 Generated with Claude Code

clrudolphi and others added 2 commits July 5, 2026 13:43
textDocument/prepareRename threw InvalidOperationException whenever
StepRenameHandler.HandlePrepareRenameAsync returned null (cursor not on
a renameable binding). vscode-languageclient surfaces that as a visible
error popup with the raw exception text, instead of quietly doing
nothing as the LSP spec intends for a null prepareRename result.

Register the handler with a nullable LspRange? result and let the null
flow straight through, matching the pattern already used for
textDocument/rename and reqnroll/renameTargets on the same handler.

Fixes #47

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The prior fix (b614aa1) registered the handler with a nullable
LspRange? result and let null flow straight through, but OmniSharp's
DelegatingRequestHandler<T, TResponse>.Handle always calls
JToken.FromObject((object)response, ...) with no null-check, so a
null TResponse throws ArgumentNullException regardless of declared
nullability -- reproducing the same raw-error-popup symptom the
original PR was fixing, just from a different exception.

textDocument/rename and reqnroll/renameTargets avoid this by
coalescing null into an empty WorkspaceEdit/RenameTargetsResponse
before it reaches OmniSharp -- but prepareRename's Range has no
"empty" value that means the same as null (any Range is a valid,
concrete rename target). This repo hit the identical
JToken.FromObject(null) crash once before for SemanticTokensHandler
and worked around it the same way there.

Register the route with TResponse = JToken instead, and return
JValue.CreateNull() -- a non-null JToken instance whose value *is*
JSON null -- so OmniSharp never sees an actual null reference while
the wire response still round-trips as null. This mirrors the
identical workaround in OmniSharp's own reference consumer,
omnisharp-roslyn's LanguageServerHost.cs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@clrudolphi
clrudolphi merged commit c0cdb29 into master Jul 6, 2026
8 checks passed
@clrudolphi
clrudolphi deleted the fix/issue-47-preparerename-quiet-noop branch July 6, 2026 14:17
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.

VS Code: prepareRename null-position shows a raw error popup instead of a quiet no-op

1 participant