microsoft / microsoft/WinAppVSCE

Go to Definition should never block on a project load

Open
#220 3 comments 0 reactions 1 assignee View on GitHub

@chiaramooney is already working on this.

Since Sep 8, 2026.

Dominant language
TypeScript
Stars
13
Forks
3
Avg merge
6d 1h
Merged PRs (30d)
11

Description

Summary

Go to Definition should never block on a project load. Today it sometimes does, and the resulting behavior is inconsistent depending on when the user presses F12.

The desired policy is the one the server already applies to hover: use whatever project context is currently available, and if the needed context is not ready, return no result rather than making the user wait. F12 for a C# target should stay a no-op until the full context is ready, at which point it starts working.

Current behavior

When F12 is pressed Outcome Desired
Before the framework stage publishes (_latest empty) TryGetAcceptedContext fails, so GetContextAsync awaits GetOrStartContext, which completes only with the Full context. The request blocks — potentially for seconds — then navigates. No-op
During the framework-ready window (_latest = Framework, _ready empty) TryGetAcceptedContext succeeds and hands back the Framework context. Resolution returns null. No-op (already correct)
After the full load Navigates correctly. Unchanged

The blocking case is the problem. A user who presses F12, sees nothing happen, and moves on can have the editor jump unexpectedly seconds later when the build finishes. Cancellation does not reliably protect against this: the request is bounded by _requestCancellation.Value, but simply moving the cursor does not cancel an in-flight definition request.

Why no-op is the right policy

The server already established this rule for hover, with an explicit comment:

// server/src/WinUiXaml.LanguageServer/XamlLanguageServer.Resolution.cs:27-33
// didOpen eagerly starts the trusted project context, but a cold MSBuild design-time build
// takes seconds. Never queue project-independent quick info behind it.
if (!TryGetAcceptedContext(document, out _))
{
    WarmUp(document.Uri);
    // ... return immediate, project-independent results

F12 is the outlier that violates it. Making F12 consistently non-blocking aligns it with hover and with the staged-loading design generally: features appear as their inputs become available, and no interaction is ever queued behind MSBuild.

Why the framework stage cannot answer a C# definition

GoToDefinitionAsync filters symbol locations to source:

// server/src/WinUiXaml.LanguageServer/XamlLanguageServer.DocumentFeatures.cs:88-90
var (symbol, _) = await ResolveNamedSymbolAsync(p).ConfigureAwait(false);
var location = symbol?.Locations.FirstOrDefault(l => l.IsInSource);
return location != null ? ToLspLocation(location) : null;

The framework-stage compilation is built from metadata references with no syntax trees (MsBuildFrameworkProject.cs), so no symbol has a source location. This is structural, not a lookup failure — the framework stage can never satisfy a C# definition request.

Supporting detail from the cache: publishIntermediate sets only _latest (AsyncSingleFlightCache.cs:223), while successful completion sets both _ready and _latest (:177-178). TryGetReadyProjectContext then falls through to returning any latest context regardless of stage (CompletionAndResources.cs:252-256), which is how a Framework context ends up serving a definition request.

Proposed change

Make GoToDefinitionAsync and its resolvers non-blocking:

  1. Use only an already-available context — TryGetAcceptedContext / TryGetReadyTypeSystem.
  2. When no suitable context is available, call WarmUp(uri) to ensure the single-flight load is running and return null immediately.
  3. Do not await GetOrStartContext on the definition path. In practice this means ResolveNameReferenceAsync(p, waitForTypeSystem: true) should no longer wait, and the shared resource-reference resolver should not fall back to an awaiting GetContextAsync when invoked for definition.

Note that the shared resolvers are used by both hover and definition, so the change should preserve hover's existing behavior rather than altering it.

Follow-on consideration

With F12 consistently non-blocking, "no result" covers both "not ready yet" and "genuinely not found," and VS Code renders both as "No definition found for 'X'". Worth considering a way to distinguish them — for example, surfacing the loading state when a definition request arrives before the full context is ready — so the message does not imply the handler is missing. Tracked here rather than split out, since it only matters once the blocking path is removed.

Unaffected behavior

  • Resource-key and x:Name navigation work correctly at the framework stage; they resolve against the XAML document and the MSBuild-evaluated file list, not the compilation.
  • F12 on SDK/NuGet types such as Button returning nothing is intended — those are metadata-only and there is no decompilation. This is documented in the README.

Context

Found while documenting per-stage feature availability for #50. The README's "What works at each loading stage" table already tells users that F12 into C# requires the ready stage, so the documentation is accurate today; this issue tracks making the runtime behavior consistent with it.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.