Brings the WPF decompiler-pipeline-stepper feature across. Available only
in Debug builds — the entire feature set is gated behind `#if DEBUG`, so
Release users see neither the pane nor the ILAst languages in the picker.
The user reported that expanding an enum (System.DayOfWeek) with a search
term active showed only Base/Derived nodes — every FieldTreeNode child
hidden. This is NOT a recent regression. It traces back to commit
45461ddde, which made LanguageSettings.SearchTermMatches honour the
SearchTerm and wired SearchPaneModel to push its term into
LanguageSettings on every keystroke.
Refactor of the MessageBus port: CurrentAssemblyListChangedEventArgs,
TabPagesCollectionChangedEventArgs, and SettingsChangedEventArgs now
share a generic base — abstract WrappedEventArgs<T>(T inner) : EventArgs
exposing an explicit `Inner` property. The three subclasses collapse to
one-liners with no extra body, and consumers read .Inner uniformly
across all three. No implicit conversion operator — the unwrap is
always visible at the read site.
Brings WPF's MessageBus pattern to the Avalonia port so future features
(updater banner, navigate-to-reference plumbing, cross-pane settings
notifications) can fan out without each subscriber importing every
publisher's class. Infrastructure only — no existing Avalonia code wires
the bus yet; this commit makes it available.
The pane's watermark advertises a prefix DSL that the parser didn't
implement — only inassembly: and innamespace: were honoured. Typing
"t:String" treated the whole thing as a literal keyword and matched
nothing useful.
The PE TypeDef table includes a <Module> pseudo-type plus any user-declared
top-level types in the empty namespace. Until now AssemblyTreeNode skipped
the empty namespace when building NamespaceTreeNodes and instead hung the
global types directly off the assembly node, breaking the long-standing
tree shape (every type lives under a namespace folder).
MemberSearchStrategy.Search uses language.GetEntityName(module, handle, ...)
as a cheap pre-filter: read the entity name straight from metadata, run the
caller's match (Contains / Equals / Regex), and ONLY when it matches do the
expensive typeSystem.MainModule.GetDefinition(handle) resolve. The contract
is "this must be cheap because it's called for every type / method / field /
property / event handle in the module."
Search felt much slower than the WPF baseline despite Parallel.ForEachAsync
across producers. The slowness wasn't in the producer — it was in the drain:
a 50ms Task.Delay polling loop that posted batches to the UI thread via
Dispatcher.UIThread.Post, with no per-tick time budget and append-order
insertion. That meant up-to-50ms of latency before each visible batch, an
extra thread-hop per batch, and the most relevant hit sitting buried behind
arrival-order results until the run finished.
User report: search stalls after 3 results on a real codebase. Root
cause: strategy.Search(module, ct) can throw on any single assembly
(malformed metadata, missing dependency, an internal Debug.Assert
that fires outside the test fixture). Nothing caught it; the
exception faulted runTask, the drain saw IsCompleted+empty queue and
exited, the user saw three results then a stuck spinner.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Default DataGridRow height from the Simple theme is ~28px — visibly
looser than the SearchPane ListBox rows (which sit at ~22px with the
icon+text template). Pin TreeGrid RowHeight to 22 so the two panes
match: 16px icon + 3px top + 3px bottom.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Ctrl+E / Ctrl+Shift+F previously activated the pane but left
keyboard focus wherever it was — the user still had to click the
TextBox before typing. Add a FocusRequested event on SearchPaneModel
that the view subscribes to and pushes Focus() on the SearchInput
TextBox through Dispatcher.UIThread.Post (a tick lets the freshly-
active pane surface in the layout so .Focus() actually takes).
Assisted-by: Claude:claude-opus-4-7:Claude Code
SearchPaneModel gains an Activate(SearchResult) method that resolves
result.Reference through AssemblyTreeModel.FindTreeNode and moves the
assembly-tree selection there — exactly the WPF NavigateToReferenceEventArgs
flow, minus the message-bus indirection. SearchPane.axaml.cs wires
DoubleTapped + Enter on the results ListBox to call Activate.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Flip LanguageSettings.SearchTerm from the always-empty placeholder
to a real ObservableProperty backing field, and replace the
always-true SearchTermMatches stub with a case-insensitive substring
contains. Empty term still matches everything (no filter active).
Assisted-by: Claude:claude-opus-4-7:Claude Code
Wire the search pane to the shared ICSharpCode.ILSpyX.Search engine.
Setting SearchTerm or SelectedSearchMode now cancels any in-flight
run and starts a fresh one; cleared term tears the run down.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replace the placeholder TextBlock with the real layout: a TextBox at
the top bound two-way to SearchPaneModel.SearchTerm (UpdateSourceTrigger
PropertyChanged so the orchestrator reacts on every keystroke once it
lands), a ComboBox bound to SearchModes / SelectedSearchMode, and a
ListBox that renders each SearchResult as three trimmed columns
(Name / Location / Assembly).
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replace the SearchPaneModel stub with the real shape that the pane's
view, the background streaming orchestrator, and the LanguageSettings
filter cascade will all bind against.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Port all five analyzer test files from ILSpy.Tests/Analyzers (the
WPF-side test project that exercises the shared ICSharpCode.ILSpyX
library) into ILSpy.Avalonia.Tests/Analyzers/Library. The analyzer
logic itself is platform-agnostic, so the tests run unchanged once
the test assembly can reach the library's internals.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Errors thrown by an analyser used to collapse to a single-line
ex.Message on the row; that lost the stack trace and made the
InvalidCastException in GetCodeMappingInfo nearly impossible to
diagnose from the UI alone.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Language.GetCodeMappingInfo was unconditionally casting the incoming
EntityHandle to TypeDefinitionHandle. MethodUsesAnalyzer calls it
with the method's own handle and expects the language to walk to
the declaring type — the WPF impl does, the Avalonia port didn't.
Right-clicking a constructor → Analyze → expanding "Uses" therefore
threw at runtime; the error surfaced only as a single-line message
in the analyzer pane because AnalyzerErrorNode dropped the stack.
Assisted-by: Claude:claude-opus-4-7:Claude Code
AnalyzeContextMenuEntry surfaces "Analyze" in the assembly-tree
right-click menu when every selected node implements IMemberTreeNode
(types, methods, fields, properties, events). Execute hands each
member to AnalyzerTreeViewModel.Analyze, which dedupes by metadata
token + parent module so re-running on the same entity refocuses
instead of duplicating. Const fields are excluded — they're literals
at every use-site, nothing to scan for.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Lay the foundation for the analyzer pane. None of these classes are
wired into the live view yet — that lands when the pane view-model and
the ProDataGrid view are filled in two commits down. Background-fetch
behaviour on AnalyzerSearchTreeNode is also deferred.
Assisted-by: Claude:claude-opus-4-7:Claude Code
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
Bring the per-step screenshot helper (TestCaptureExtensions.CaptureForReview)
into tree without yet adding the call sites — the existing instrumented
test files stay clean for now and can be re-instrumented selectively
when needed.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Browsing portable PDB metadata required drilling three levels deep:
Assembly → Metadata → Debug Directory → Debug Metadata (Embedded).
The PDB's tables are now exposed as a top-level MetadataTreeNode
alongside the host module's Metadata folder, so PDB browsing is one
click away. The PdbProvider already cached for decompilation is
reused — no second parse.
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
HandleFileDrop now resolves each opened LoadedAssembly to its
AssemblyTreeNode (via AssemblyListTreeNode.FindAssemblyNode) after
any Move and writes the set into model.SelectedItems. Lookup happens
post-Move because Move re-creates the node wrappers — a pre-Move
reference would dangle. Selecting drives the decompiler view to
render the new assembly, which is what makes the LoadAsync task
observable to the user.
Assisted-by: Claude:claude-opus-4-7:Claude Code
ProDataGrid's row-drag pipeline only sees in-grid drags, so external
file drops bypass it entirely. Wires Avalonia's standard DragDrop
pipeline alongside it: DragDrop.AllowDrop on the tree DataGrid plus
DragOver / Drop handlers that read DataFormat.File from the
IDataTransfer, resolve each IStorageItem to a local path, and route
through AssemblyListPane.HandleFileDrop.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Wires ProDataGrid's native row-drag API on the assembly tree:
CanUserReorderRows=True, RowDragHandle=Row, RowDragStarting cancels
drags whose source isn't a top-level AssemblyTreeNode (or is a
package-nested entry). AssemblyRowDropHandler validates that the
target is a sibling (not Inside, not deeper than top-level) and
routes the reorder through AssemblyList.Move so the same persistence
path the rest of the app uses (Unload / OpenAssembly) captures the
new ordering.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Text-filter column headers no longer swap the label out for a TextBox when
the cursor brushes across the column name. The funnel icon (click to focus
the textbox, click again to clear an active filter) is now the sole entry
point. The TextBox stays in the layout pass at rest (Opacity=0 instead of
IsVisible=false) so the header row's height matches its eventual filled
state — no more bouncing on show/hide.
Assisted-by: Claude:claude-opus-4-7:Claude Code
DecompilerTextView subscribes to SettingsService.DisplaySettings.PropertyChanged
and mirrors the new value into the AvaloniaEdit TextEditor immediately — no
re-decompile, no Apply. Properties wired:
Assisted-by: Claude:claude-opus-4-7:Claude Code
Replaces the WPF modal Options dialog with a Dock document tab — opens via
View → Options at the same MenuOrder=999 mount point, hosts the same three
panels (Decompiler / Display / Misc), and uses the same SettingsSnapshot
commit pattern so closing the tab without Apply discards in-flight edits.
Re-invoking View → Options while one's open just focuses the existing tab.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Audit had ExitCommand listed as Missing because it grepped for a separate
ExitCommand.cs file (the WPF layout). Avalonia bundles File-menu commands
into FileCommands.cs so the audit missed it. Command was already implemented
correctly — uses IClassicDesktopStyleApplicationLifetime.Shutdown() rather
than mainWindow.Close() (cleaner since Avalonia apps can have multiple
windows).
Assisted-by: Claude:claude-opus-4-7:Claude Code
Builds on the LanguageVersions virtual + HasLanguageVersions helper that
landed in the prior tree-completeness commit:
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
GrayscaleAwareImage : Image bakes a luma-transformed Bitmap on the disabled
edge (BT.601 weights) and swaps Source. Replaces the blanket Opacity=0.35
setter that was the previous cue. Applied to the static toolbar buttons in
MainToolBar.axaml plus the MEF-built buttons constructed in BuildButton.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Flag columns no longer carry a separate "▾" trigger button. The funnel icon
already owns the popup-open gesture (clicking it opens the popup, click-while-
active clears the filter), so the second affordance was redundant — and forced
the same hover-swap dance text columns need to keep the column-name visible.
With the chevron gone, the label stays visible at all times for flag columns;
the popup is parked invisibly in the visual tree with PlacementTarget pointing
at the funnel icon. Text columns keep the existing label/TextBox swap because
they need somewhere to type.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Default click on a value chip in a flags-filter mutex group now collapses the
selection to {value} — the file-manager / Excel intuition. Shift+click keeps
the existing additive multi-toggle behaviour. Plain click on the <Any> chip
clears the filter for the group.
Assisted-by: Claude:claude-opus-4-7:Claude Code
Each metadata tree node was projecting its rows into List<object> via Cast<object>
before assigning to MetadataTablePageModel.Items. That collapsed the source's
element type at the runtime interface chain — DataGridCollectionView.GetItemType
walks IEnumerable<T> and only saw IEnumerable<object>, which made
DataGridSortDescription.Initialize resolve a null propertyType, which made
ReflectionHelper.GetNestedPropertyValue's "propertyInfo.PropertyType != propertyType"
check return null for every row. Result: header click flipped the sort indicator
but the row order never changed.
Assisted-by: Claude:claude-opus-4-7:Claude Code