The headless UI tests synchronized with the application by pumping a fixed
number of frames (39 loops of RunJobs/Delay across 19 files) and by pressing
at a point computed once from a control's bounds. Both encode how fast the
machine that wrote the test was: on the loaded Windows Debug CI agent the
frame count comes up short and the point goes stale, which is the recurring
timeout in the tree context-menu tests and the reason each such failure was
repaired one test at a time.
Waiters.WaitForIdleAsync replaces the frame loops. It observes the actual
precondition - no dispatcher job queued at Background priority or above, no
assembly still loading in the background sweep, a frame rendered - and
requires it on two consecutive polls so a thread-pool continuation about to
post back is caught as well.
Window.ClickAsync replaces element-targeted MouseDown/MouseUp pairs. It
re-resolves the target on every poll and presses only once the window's hit
test at the click point answers with that target, reporting the point and
what was hit instead on timeout. That diagnostic exposed one vacuous test:
User_Click_On_Visible_Row_Does_Not_Recentre_Viewport clicked the centre of a
row wider than the tree viewport, which lies under the decompiler text view,
so its assertion held without the row ever being clicked. It now clamps the
point to the viewport like the other tree-row clicks.
Clicks at text positions and press-only gutter clicks stay raw; they do not
target an element.
Assisted-by: Claude:claude-fable-5:Claude Code
The pane used to split the pipeline across two languages: the ILAst language
stepped the IL transforms, the C# language stepped the AST transforms, and
nothing showed the seam between them, so a step index meant a different thing
depending on which language happened to be selected. Recording both halves into
one Stepper makes an index replayable across the whole pipeline; a limit that
lands in the IL phase has no C# to print, so the halted function is rendered as
ILAst instead.
Which function that is takes some care, because a member group's EndStep is the
next member's first step: a halt standing on a member's opening step belongs to
the member that just finished, a transform that throws where the limit was aimed
has to hand over the ILAst it half-transformed (what the ILAst language showed
as "ILAst after the crash"), and a step recorded on a helper function the
pipeline has not attached yet belongs to that function's own tree.
Retention stays opt-in twice over: the decompiler records IL steps only when
asked to, and the pane asks only while its view is on screen. Every kept step
pins the ILAst it captured, which for one type runs to tens of thousands of
nodes, so a closed pane would be paying for a tree nobody displays.
What is left of the ILAst language is its typed-IL dump, which runs no
transforms at all. That stays, as TypedILLanguage. IDebugStepProvider was down
to a single implementation and is removed.
Assisted-by: Claude:claude-opus-5[1m]:Claude Code
The rich hover popup hardcoded a near-white background and border while
its signature text is coloured by the active highlighting theme, so in
dark mode light-on-dark syntax colours landed on a light box and were
unreadable. The chrome and the doc-link colour now route through
theme-variant brushes; light mode keeps the established near-white look.
Assisted-by: Claude:claude-fable-5:Claude Code
These explain the code by pointing at the front-end that used to implement it.
That front-end is no longer in the tree, so the referent a reader would go
looking for does not exist: "Mirrors WPF's RefreshDecompiledView() call" names
a method nobody can open. In almost every case the sentence beside it already
carried the reason, and the reference was an appendix.
Comments citing a live platform difference are left alone, because there the
comparison is the reason rather than a memory: Avalonia genuinely has no
global RequerySuggested signal, which is why SimpleCommand exists at all.
One had gone stale rather than merely redundant. DerivedTypesEntryNode
described consulting the active search term as a missing feature to reinstate,
but SearchTermMatches is deliberately a no-op so the assembly tree stays
independent of the search pane; the comment now says so.
Assisted-by: Claude:claude-opus-5[1m]:Claude Code
A comment that justifies behaviour by pointing at the WPF front-end means
nothing to someone reading the file cold: the reason is either already stated
beside it or is not stated anywhere. Each of these now names the constraint
itself - why navigation waits for pointer-release, why a signature block wraps,
why the tree filter ignores the search term.
Comments citing an external product's documented behaviour as the source of a
rule are left alone; there the reference is the reason, not a memory of how the
code arrived.
Assisted-by: Claude:claude-opus-5[1m]:Claude Code
Zoom was stored as the font size itself, so the zoom overlay had no way to
tell a Ctrl+Wheel zoom from a font size picked in the options dialog: any
size other than the hard-coded default made the overlay appear, and the
percentage was measured against that default rather than the user's font.
A separate multiplier restores the split the setting always implied - the
options dialog moves the base size, zoom scales it - so 100% means "the
font you configured", whatever that is.
Assisted-by: Claude:claude-opus-5[1m]:Claude Code
These fixtures were written while porting to Avalonia, as an author's own
verification step rather than as coverage: reflection asserting that a type
derives from its base and that a property has the type it is declared with;
literals (MinHeight 29, Padding 3, MaxWidth 900) copied out of the .axaml
beside them; a property override asserted only so pane descendants stay
reachable from tests. None of them can fail except when someone deliberately
edits the line they mirror, and then they fail as a chore.
StartupPerfTests keeps its two [Explicit] benchmarks, which print per-phase
timings worth reading. The third was a wall-clock assertion (8 CoreLib copies
must settle in under 15s) that ran in CI, where a shared runner decides the
verdict; as [Explicit] it would be strictly dominated by the 200-assembly
benchmark it was derived from, so it goes.
Two fixtures are trimmed rather than deleted, because their kernel is real:
XmlDocLoader's ref-pack fallback has no other test in the repo, and the
MenuIcon metadata rasterisation was dropped once during the port already.
Both now assert that without booting MainWindow to reach it.
This is worth about two seconds - it buys reviewers less to read, not CI
less to do.
Assisted-by: Claude:claude-opus-5:Claude Code
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
Clicking a local variable highlights its occurrences, but clicking a
member or type always navigated away, so there was no way to see all
uses of a member within the current view. With the new display setting
enabled (off by default, as discussed in the issue), a single click on
a member or type reference paints every occurrence in the view using
the local-reference highlight infrastructure, and Ctrl+Click performs
the navigation. Opcode references keep navigating on plain click.
Matching occurrences are compared by definition token and module
rather than by Equals, because a use site carries a specialized member
instance while the declaration carries the definition; this also fixes
the analyzer-driven highlight for specialized members. Unresolved
entity references from the IL and metadata views are compared
structurally to avoid building a type system per click.
The hand cursor promises navigation, so it is shown only when a click
would actually navigate: cursor queries factor in the setting and the
Ctrl state, and Ctrl presses/releases while hovering a reference
repaint the cursor via top-level key handlers, since keyboard focus is
usually elsewhere while hovering.
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
AvaloniaEdit 12's TextView.InvalidateLayer only invalidates the TextView's
measure and never re-renders the per-layer child visuals, so the caret layer
repaints only on caret blinks (focused editor) or scroll changes. During
debug-step navigation focus stays in the Debug Steps panel, so the one scroll
repaint painted an early animation frame and nothing ever erased it: the frame
stayed composited as a caret-sized black line fixed in the viewport. The
adorner now invalidates the layer visuals itself on start, every animation
frame, and dismissal, and keeps its rectangles in document coordinates so the
deferred centering scroll cannot strand the highlight at a stale viewport
position.
Assisted-by: Claude:claude-fable-5:Claude Code
ILSpy resolves an assembly's references against the target framework it
detects from the TargetFrameworkAttribute. When that attribute is missing,
wrong, or the user wants to force a different framework, there was no way to
hint the correct one, so references could resolve against the wrong runtime
pack or framework directory.
A LoadedAssembly can now carry a TargetFrameworkIdOverride that short-circuits
detection (it is the single value every LoadedAssembly-based resolution path
reads), is persisted in the assembly-list XML, and is carried across a reload
so a runtime change re-resolves against the new framework. The "Set Target
Framework" context-menu entry edits it through a dialog with a free-form text
box and an always-visible list of common monikers to pick from (the app forces
overlay popups, so a dropdown would be clamped inside the small dialog); input
is validated and converted from the short TFM users know (net48) to the long
FrameworkName form the resolver consumes (.NETFramework,Version=v4.8) via
NuGet. The direct DetectTargetFrameworkId callers in the decompiler core
(project export, language version) intentionally keep reading the real
attribute; only reference resolution is overridden.
Resurrects a 2020 prototype (branch tfmoverride) re-implemented against the
current ILSpyX/Avalonia code, whose surrounding structures no longer matched.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The text area paints the I-beam across its whole surface, so the bookmark
icon gutter, line numbers, and folding margin inherited it by walking up the
visual tree. Those margins are click targets, not text, so the I-beam read
wrong over them. Force the normal arrow there, re-applying as the line-number
and folding margins come and go with their display settings.
Assisted-by: Claude:claude-opus-4-8:Claude Code
There was no way to mark and return to interesting spots in decompiled
code. This adds a flat (no folders, no labels) bookmark list: toggle on a
line from the context menu, the gutter, or Ctrl+B; see an icon in a new
left-margin gutter that honours a disabled state; and manage the list in a
dockable pane that auto-registers in the Window menu.
Bookmarks anchor by metadata token, never by a raw line number, so they
survive re-decompilation and decompiler-setting changes that reflow the C#
text: a definition line anchors to its token, while a line inside a method
body anchors to the method token plus an IL offset. Recovering an IL offset
needs the decompiler's sequence points, which the normal C# output did not
carry, so they are captured once at the WriteCode chokepoint and stored as a
per-document line/offset map. The map is also what places gutter icons and
scrolls navigation to the exact line.
The list persists to an ILSpy.Bookmarks.json sidecar next to ILSpy.xml; the
path logic is extracted into AppEnv/ConfigurationFiles so the dock layout
sidecar shares it. Navigating to a bookmark loads its assembly from disk if
it dropped out of the list (and only then offers to remove a bookmark whose
file is gone), then centres the line and plays a brief line flash plus a
gutter-icon pulse. Disabled bookmarks stay visible but are skipped by the
next/previous actions.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The "Toggle folding" context-menu entry folded the block at the caret, so
right-clicking a fold on a different line than the caret toggled the wrong
block. Record the document offset under the pointer when the text-view menu
opens and expose it on TextViewContext as TextLocation; the folding entry acts
on that offset (falling back to the caret only when no click position was
recorded). The pointer handler now records the offset before the reference
lookup, so a document with foldings but no clickable references still gets a
position.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The Analyze panel showed each signature as a flat string, so the type names
that carry the key information were hard to spot in long Used-By / Uses lists.
Render analyzer entity rows as syntax-highlighted rich text with the type-name
spans emboldened (issue #2164).
Replace Language.GetRichTextTooltip(entity) with a general
GetRichText(entity, conversionFlags, boldTypeNames): the hover tooltip is one
caller, the analyzer pane another. C# colours the signature via
CSharpHighlightingTokenWriter and bolds the type-name colour spans; IL renders
its disassembled header (its signature form), already lexically highlighted;
the base produces plain text. The IL method/type/field header is semantically
the same artifact as the C# signature, so it shares the one method.
Rendering is opt-in: nodes that want rich labels implement IRichTextNode, and
the single shared SharpTreeView cell renders its runs via the new RichNodeText
attached behaviour, falling back to the plain Text otherwise. SharpTreeNode.Text
stays the authoritative plain string, so search, copy and keyboard navigation
are unchanged; analyzer entity nodes derive Text from the same RichText so the
two never diverge.
Fixes#2164
Assisted-by: Claude:claude-opus-4-8:Claude Code
Clicking a reference left AvaloniaEdit's selection-drag mode stuck, so a
subsequent mouse move (with no button held) extended a selection.
AvaloniaEdit's SelectionMouseHandler captures the pointer and enters
selection mode on press, and extends the selection on every move while that
mode is active -- it keys off the mode, not whether a button is down. Only
its bubble-phase PointerReleased handler resets the mode and releases the
capture, and that handler early-returns when the event is already handled.
The reference-click handler ran in the tunnel phase and marked the release
handled before AvaloniaEdit saw it, so AvaloniaEdit's cleanup never ran.
Move the release handler to the bubble phase so it runs after AvaloniaEdit
has reset its state. AvaloniaEdit subscribes from the TextArea constructor,
before this view attaches its handlers, so the ordering is deterministic and
navigation can stay synchronous.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The global:: prefixes existed because the test project's namespace
ICSharpCode.ILSpy.Tests used to shadow the app's old top-level ILSpy
namespace. With the UI code back under ICSharpCode.ILSpy there is
nothing left to shadow, so plain fully qualified names resolve fine.
Assisted-by: Claude:claude-fable-5:Claude Code
The Avalonia port had placed the UI app in an ILSpy.* namespace tree,
while the csproj RootNamespace and every prior release (through 10.1)
use ICSharpCode.ILSpy.*. Restoring the historical namespace reduces the
public API diff against release/10.1 for plugin authors and removes the
shadowing that forced global:: qualifiers in the test project. The
Images class and AccessOverlayIcon enum move back into the root
namespace (as in 10.1), since an ICSharpCode.ILSpy.Images namespace
would shadow the Images class for all code inside ICSharpCode.ILSpy.
Assisted-by: Claude:claude-fable-5:Claude Code
Reference links navigated on pointer-press, which meant a press-drag
over a link could never select its text -- the press fired navigation
and handled the event before a selection could start. Move navigation
to pointer-release and only follow the link when the pointer did not
drag past the click threshold, matching the WPF view's mouse-up
handling. A press-drag now selects link text like any other span.
Assisted-by: Claude:claude-fable-5:Claude Code
Selecting text inside the popup closed it: the selection drag captures
the pointer, so sweeping past the popup edge fired the content's
PointerExited with IsPointerOver already false. The close veto now
covers pointer-over, keyboard-focus-within (so a finished selection
survives for Ctrl+C, as the WPF tooltip did), and pointer capture held
inside the popup; only a document change force-closes.
Assisted-by: Claude:claude-fable-5:Claude Code
Doc-comment crefs in hover tooltips rendered as link-styled but inert
text, and cref resolution was never wired up. Crefs now resolve against
the current tab's assembly list and navigate through the same
NavigateToReferenceEventArgs channel the analyzer uses; href links open
via the TopLevel launcher. Avalonia inlines are not input elements, so
links are hit-testable TextBlocks embedded via InlineUIContainer.
Interactive content also requires the popup to survive the pointer
travelling onto it: the editor's exit handler now tolerates the
overlay-popup enter/exit ordering via the distance corridor, and the
popup closes when the pointer leaves its own content instead.
Assisted-by: Claude:claude-fable-5:Claude Code
The WPF app showed the disassembled IL header when hovering a member
reference in IL view, and the opcode's XML documentation when hovering
an instruction. The Avalonia port lost both: ILLanguage fell back to
the base ambience one-liner and the opcode tooltip carried only the
name and encoding. Opcode docs additionally need a modern-.NET source:
the WPF code read the .NET Framework reference-assembly docs, which do
not exist on modern .NET, so MscorlibDocumentation now falls back to
the ref pack parallel to the hosting runtime.
Assisted-by: Claude:claude-fable-5:Claude Code
CaretHighlightAdornerTests waited on the wall clock (3s budget) for the
adorner's 1s real-time DispatcherTimer to remove it. Avalonia.Headless has no
simulated clock, so under a loaded CI runner the dispatcher-thread timer plus
the poll loop slipped past the tight budget and the test timed out
intermittently.
Extract the teardown the lifetime timer runs into Dismiss(); the test grabs the
live adorner and calls it directly, verifying the unregister half without any
timing dependency. Registration is still exercised through the real
OnReferenceClicked path, and production behaviour is unchanged.
Assisted-by: Claude:claude-opus-4-8:Claude Code
Keep the headless UI tests from timing out on the slow Windows runner:
decompile a small CoreLib type (System.Object) in the folding and
token-history tests instead of a large one, and raise the shared
decompile-wait timeout so a cold first decompile (JIT plus building a
CoreLib-scale type system) finishes in time.
Assisted-by: Claude:claude-opus-4-8:Claude Code
AvaloniaEdit's copy puts only plain text on the clipboard, so pasting into
an HTML-aware target (a document, mail, chat) lost the colours the previous
version preserved. Build a coloured HTML fragment from the selection that
merges BOTH the xshd syntax highlighter and ILSpy's semantic RichTextModel
(the decompiler's reference / theme colours the xshd alone doesn't carry),
and place it alongside the text under the native HTML format -- text/html on
Linux/macOS, CF_HTML on Windows. Wired to Ctrl+C (tunnel, so it pre-empts
the plain-text copy) and the right-click Copy entry.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The decompiler text-view context menu was missing the navigation and folding entries the previous version had. Add Decompile (go to definition) on a right-clicked symbol -- it navigates the tree to the entity's definition via the existing NavigateToReference bus message -- and Toggle folding / Toggle all folding, which reuse the editor's existing Ctrl+M logic (extracted into public DecompilerTextView methods). The folding entries only show when the document has foldings.
Assisted-by: Claude:claude-opus-4-8:Claude Code
The Display-options "Expand using declarations / member definitions after decompilation" checkboxes had no effect: the live decompile cloned DecompilerSettings and baked in the language version but never copied these two flags from DisplaySettings. TextTokenWriter reads settings.ExpandUsingDeclarations / ExpandMemberDefinitions to decide each fold's DefaultClosed, so without the bridge both defaulted false and every using / member fold came back collapsed regardless of the setting.
This fixture was missed by both the helper-extraction and visual-breakpoint
sweeps, so it still carried 16 copies of the boot prologue and no captures.
Route it through TestHarness.BootAsync / TreeNavigation and add a Step after
each decompile, matching every other headless fixture.
Assisted-by: Claude:claude-opus-4-8:Claude Code
WPF's CSharpLanguage doesn't override DecompileNamespace -- clicking
a namespace falls through to the base Language.DecompileNamespace,
which writes just the namespace name as a `// Foo.Bar` comment.
The avalonia port's override decompiled every top-level type in the
namespace, which on a large assembly turned a single click into a
multi-second blocking decompile of hundreds of types.
Remove the override so clicking a namespace lands the same cheap
comment-only output the WPF app shows. Users who do want the full
namespace decompiled can still get it via right-click > Save Code,
which routes through a separate path that constructs the project
explicitly.
ILLanguage's override is intentionally preserved -- IL mode in WPF
does disassemble the namespace; we match that.
Assisted-by: Claude:claude-opus-4-7:Claude Code
User-reported: editor hover tooltips were not wide enough to show full
method signatures. Root cause was a three-way layout interaction —
DocumentationRenderer.AddSignatureBlock created the signature
SelectableTextBlock with TextWrapping.NoWrap, the outer Border capped
MaxWidth at 600px, and the wrapping ScrollViewer disabled horizontal
scrolling. So anything past 600px was simply clipped, with no way to
reveal the rest.
Assisted-by: Claude:claude-opus-4-7:Claude Code
The three editor-command tests (Copy_Entry_Reflects, Copy_Execute, SelectAll_Execute)
selected `System.Linq.Enumerable` (the whole type, 300+ LINQ methods) and waited
on `view.Editor.Document.TextLength > 0` — a 15s `WaitForAsync` poll. In suite
context this was sloppy: it could latch on stale editor content from a previous
test, and in isolation it occasionally raced past 15s on a slow system,
producing the intermittent `failed: 1` we saw earlier.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Ctrl+M toggles the innermost fold containing the caret. Ctrl+Shift+M collapses
all when any fold is open and expands all otherwise (parity-based toggle).
Assisted-by: Claude:claude-opus-4-7:Claude Code
Mirrors WPF's ZoomScrollViewer behaviour without templating AvaloniaEdit's
TextEditor. The Avalonia path scales the editor's FontSize (which feeds
DisplaySettings.SelectedFontSize) on Ctrl+Wheel; AvaloniaEdit lays out at the
new size on the next render. WPF's full-content-tree LayoutTransform approach
doesn't fit AvaloniaEdit's architecture, but the font-scale result is what
users actually expect from "editor zoom".
Assisted-by: Claude:claude-opus-4-7:Claude Code
Foldings persistence rounds out the view-state work that landed in 185e0551d
(caret + scroll). Mirrors WPF's DecompilerTextViewState.SaveFoldingsState /
RestoreFoldings semantics verbatim — including the layout-checksum gate that
refuses restoration when the new document's foldings don't match the captured
layout, so a stale snapshot can't accidentally expand random regions of a
shifted document.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Promotes the modern .NET XML-doc lookup from the Avalonia port into the
shared ICSharpCode.Decompiler library so every host (WPF, Avalonia, any
third-party consumer of XmlDocLoader) gets hover/tooltip documentation
for system entities without per-host fallback wiring.
The decompiler-view hover tooltip's XML-documentation lookup goes through
the shared ICSharpCode.Decompiler.Documentation.XmlDocLoader. That helper
only knows two layouts: .xml beside the .dll, and the .NET Framework
reference-assemblies paths (Program Files (x86)\Reference Assemblies\
Microsoft\Framework\v1.0 … v4.8.1). Modern .NET runtime DLLs
(System.Private.CoreLib.dll, System.Linq.dll, …) ship without any .xml
beside them — the XML files live in the parallel reference pack at
The Reload/Remove/SearchMSDN/OpenContainingFolder/DecompileInNewView
tests used to call `entry.Execute(synthetic TextViewContext)` against
a hand-built context. They now select via the assembly-tree model,
build the context menu through `BuildContextMenuForCurrentState`
(the same call the live `Opening` event makes), and fire the menu
item's `Click` routed event — the same handler the user's click
invokes. This widens the coverage to the menu-build → click-handler
attachment pipeline.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Mirrors WPF's TextView/EditorCommands.cs: CopyContextMenuEntry and
SelectAllContextMenuEntry are [ExportContextMenuEntry] MEF exports
gated by TextViewContext.TextView, with Category=Editor and low Order
so they appear at the top of the editor right-click menu. The
hand-rolled MenuItem fallback in DecompilerTextView.OnContextMenuOpening
is removed — the MEF entries replace it.
Assisted-by: Claude:claude-opus-4-7:Claude Code
CaretHighlightAdorner is an IBackgroundRenderer on KnownLayer.Caret with a
hand-rolled animation curve (Stopwatch + DispatcherTimer at ~60fps invalidating
the layer) instead of WPF's RectAnimation/BeginAnimation, since AvaloniaEdit
has no XAML-style animation primitive for ad-hoc visuals. Same timing as WPF
(300ms grow, 300ms shrink, opacity fade 450-650ms, 1s lifetime).
Assisted-by: Claude:claude-opus-4-7:Claude Code
Tree-node selection now updates the inner Content of one persistent
ContentTabPage instead of swapping the dockable in the dock. The
wrapper view (ContentTabPageView) keeps both inner views — the
decompiler text editor and the metadata grid — pre-realised in the
visual tree from construction time and toggles which is visible based
on Content's runtime type.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replaces the brittle root.GetVisualDescendants().OfType<T>().Single() / .First()
pattern with a new WaitForComponent<T>() extension that polls until the requested
control is in the visual tree, then returns it. Avalonia.Headless tests routinely
queried the visual tree before lazily-templated panes (DataGrid, dock content)
had materialised, surfacing as intermittent 'Sequence contains no elements'
failures across the suite.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Each tree-expansion, selection change, command execution, async wait and
key/mouse injection inside an [AvaloniaTest] now carries a short imperative
comment on the line above (e.g. "// expand typeNode", "// select methodA",
"// wait for assemblies to load", "// execute aboutCmd"). The comments are
the same scaffolding the manual debug-with-screenshot workflow uses to
follow what's happening at each breakpoint, surfaced into the committed
source so the tests are readable without the breakpoint markers attached.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Adds a leading paragraph describing what each test verifies and Arrange /
Act / Assert markers (split into numbered phases for multi-step tests) so
the intent of each fixture is readable from comments alone.
Assisted-by: Claude:claude-opus-4-7:Claude Code
XML / XSD / XSLT / XAML resources don't go through the C# decompiler so the
DecompilerTabPageModel has no pre-collected foldings — only HighlightingService
was kicking in. Run AvaloniaEdit's bundled XmlFoldingStrategy when the active
syntax extension is .xml so multi-line elements collapse from the gutter.
Assisted-by: Claude:claude-opus-4-7:Claude Code