Toggle folding picked the innermost fold containing the offset, but a
member's logical region is fragmented: the body fold starts at the
opening brace, the XML documentation has an independent fold, and the
header line belongs to neither, so toggling there collapsed the whole
enclosing type. Visual Studio's source editor keeps documentation
regions independent, but its metadata-as-source view treats the
member's leading trivia as one hideable unit; for a read-only
decompiler view the grouped behavior is the intuitive one.
The writer now records where an entity declaration begins, and the
definition's fold carries that logical start. Toggling targets the fold
whose logical region innermost-contains the offset, so the header line
targets the member rather than the type, and leading documentation
folds follow the member fold's new state. Inside the documentation the
doc fold itself is the innermost region and still toggles alone.
Toggle all folding now follows Visual Studio's Toggle All Outlining
parity: a mixed state expands everything, a uniform state flips.
Assisted-by: Claude:claude-fable-5:Claude Code
The main tree, tooltips, and search results once showed the parameter
list of a parameterized property; the ambience lost that when property
rendering went through the converted AST node, whose C# property syntax
cannot carry parameters. Take the parameter list from the symbol
instead and render it in parentheses (matching VB.NET usage syntax and
distinguishing these properties from indexers).
Assisted-by: Claude:claude-fable-5:Claude Code
A dynamic index access (a[b]) gave its IndexerExpression a
DynamicInvocationResolveResult with no symbol, so the brackets carried no
tooltip. Synthesize an indexer (FakeProperty, IsIndexer) on the target
type with the index parameters typed from the callsite delegate, and
attach it. Route it hover-only by detecting a DynamicInvocationResolveResult
directly on the node - which also covers an invoke-member's own
parentheses, so those stop producing a dead navigation link too.
Assisted-by: Claude:claude-fable-5:Claude Code
Lock the hover content the dynamic-tooltip work produces. Ambience-level
cases (CSharpAmbienceTests) pin that SpecialType.Dynamic renders as
"dynamic" and that a synthetic dynamic method renders its return and
per-argument types - including the full hover form, confirming the
unnamed synthetic parameters collapse to their types with no dangling
name. An end-to-end case (HoverOnlyReferenceTests) decompiles a dynamic
call and renders the symbol GetSymbol hands back, exercising the actual
synthesis (argument typing from the callsite delegate), not a hand-built
stand-in.
Assisted-by: Claude:claude-fable-5:Claude Code
References had two behaviors encoded in one bool: navigable links, and
"local" references that are non-navigable but highlight all occurrences
on click. Synthesized dynamic members were routed through the latter, so
they picked up the occurrence highlight even though each use is a
distinct synthetic member with nothing to group.
Model the three modes explicitly with a ReferenceMode enum on
ReferenceSegment (Link / LocalHighlight / HoverOnly). Dynamic members,
the dynamic constructor and the dynamic keyword now use HoverOnly: they
still show a hover tooltip (BuildHoverContent resolves any IEntity
reference regardless of mode) but get the arrow cursor, do not navigate,
and do not highlight. Local variables keep LocalHighlight. Plumbed via a
new isHoverOnly flag on ITextOutput.WriteLocalReference.
Assisted-by: Claude:claude-fable-5:Claude Code
LocalFunctionMethod.MemberDefinition returned the instance itself, so the
declaration of a generic local function (whose base method is an identity
specialization) and its use sites (whose base methods carry the use-site
substitutions) never compared equal, and click-highlighting could not group
them. Returning a wrapper around the unspecialized base method lets the
token writer record the same definition object for the declaration and all
use sites. Method-group references to local functions additionally need
their own lookup, because GetSymbol() does not surface a
MethodGroupResolveResult.
The token-writer tests now keep the PEFile alive for the duration of each
test: recorded references are type-system entities that lazily read from
the PE image, and formatting one in an assertion message after disposal
crashed with an AccessViolationException.
Assisted-by: Claude:claude-fable-5:Claude Code
Doc-comment folds were created with defaultCollapsed hardcoded to true,
so /// blocks always started collapsed. The new Display option is
bridged into DecompilerSettings like the other expand flags and drives
the fold's default state.
Detecting the last line of a doc-comment block requires sibling
navigation between trivia nodes, so trivia now carry parent, sibling
list, and index state. That state lives on the Trivia subclass to keep
AstNode's size unchanged, and the tree API treats trivia as a separate
navigation space: Next/PrevSibling and Remove work within the owning
list, Slot is null, ReplaceWith throws instead of aliasing the trivia
index into the parent's child slots, GetNextNode/GetPrevNode stop at
the list boundary, Clone and CopyAnnotationsFrom deep-copy and reparent
trivia, and CheckInvariant validates the trivia lists.
Assisted-by: Claude:claude-fable-5:Claude Code
The C# debug-steps view highlights and centers the exact AST node a
transform changed; the ILAst view already had the step tree and
replay-at-step but produced no highlight. Bring it to parity.
IL rendering has no token-writer seam like the C# output visitor, so
per-instruction text spans are recorded by bracketing
ILInstruction.WriteTo via a new INodeTrackingOutput. The dominant
inst.ReplaceWith(newInst) transform pattern detaches the instruction
passed to Step, so ILTransformContext gains EndStep to record the
produced instruction; Stepper additionally records the position's
ancestor chain as fallback candidates before the step-limit throw, so
the "show state before" view -- which halts at the selected step --
still resolves to a surviving ancestor (ultimately the ILFunction).
The highlight-range resolver is shared with the C# language.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Two crash paths surfaced by the null-object -> nullable-reference migration of the
C# AST. A void 'return;' forced into lambda syntax NRE'd on
ReturnStatement.Expression.Detach() now that Expression is nullable; the body now
falls through to the block form, matching the existing guards in CallBuilder and
InferReturnType. Separately, the location-setting token decorator routed a parentless
print-only node (e.g. the detached name-token clone of a renamed constructor) into the
child setter as SlotKind.None, which throws since the Role hierarchy was removed; such
nodes have no slot and are now skipped. A regression test covers the decorator path.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Source locations were virtual, computed by recursing to the first and last
child, whose leftmost and rightmost leaves are token nodes; sequence-point
coordinates likewise came from reconstructed token nodes. Store locations as
fields assigned while printing, and derive sequence-point coordinates from the
surrounding real nodes plus the decompiler's fixed formatting, so neither
depends on token children. The using/foreach await modifier becomes a plain
bool field. Characterization gates lock the emitted locations and PDB
coordinates, which are unchanged.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The InsertMissingTokensDecorator path (TokenWriter.CreateWriterThatSetsLocationsInAST)
reconstructs token nodes and assigns source locations onto the AST, feeding PDB
sequence points and GUI navigation. The Pretty suite never drives it, so it had no
coverage at all. Before reworking the token model, lock its observable consequences:
the located path emits the same text as the plain path, real nodes receive ordered
locations, location-based navigation resolves into the method body, and sequence
points are produced for a method body.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The decompiled view only offered navigation on member names; finding
what an override actually overrides required opening the analyzer.
Attaching a reference to the modifier token gives the same
go-to-definition affordance Visual Studio has on 'override'. The
reference resolves via InheritanceHelper.GetBaseMember, so it targets
the nearest overridden member and degrades to plain text when the base
member cannot be resolved.
Assisted-by: Claude:claude-fable-5:Claude Code