Skip to content

Conversation

@lextm
Copy link
Contributor

@lextm lextm commented Dec 17, 2025

Typical changes needed by the cross platform port are,

  1. A trivial namespace rename (TextView was both a control name and a namespace).
  2. A few types were moved to their own source files, either easy to be replaced in the cross platform project folder, or add as link.
  3. Split a few key classes into multiple source files, so that their WPF specific parts can be easily replaced in the cross platform project.

Still a work-in-progress, but should be ready to review and merge as it doesn't change anything fundamental on the WPF side.

@christophwille
Copy link
Member

Why that moving around? namespace ICSharpCode.ILSpy.TextView => namespace ICSharpCode.ILSpy.TextViewControl

@lextm
Copy link
Contributor Author

lextm commented Dec 17, 2025

Project Rover uses two compiler tricks to enable source file reuse without heavy modifications,

However, such tricks fail easily when a type has the same name as an existing namespace. The term TextView happens to be hitting this problem.

I will see how much effort it takes to avoid this namespace change though. Will provide an update once I have some results.

@lextm
Copy link
Contributor Author

lextm commented Dec 17, 2025

Reverted the namespace changes, but one more extra file to maintain in the cross platform port.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request refactors the ILSpy codebase to support cross-platform porting by separating WPF-specific code from platform-independent logic. The refactoring maintains existing functionality while restructuring code to enable easier platform abstraction.

Key Changes

  • Split multiple classes into platform-specific (.wpf.cs) and shared (.cs) files to isolate WPF dependencies
  • Extracted nested classes and extension methods into separate files for better modularity and platform-specific replacement
  • Added IList interface implementation to SharpTreeNodeCollection for cross-platform binding compatibility

Reviewed changes

Copilot reviewed 35 out of 35 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
ILSpy/Views/OpenFromGacDialog.xaml.cs Removed nested GacEntry class (extracted to separate file) and unused using statement
ILSpy/Views/GacEntry.cs New file containing extracted GacEntry class nested within OpenFromGacDialog partial class
ILSpy/ViewModels/TabPageModelExtensions.wpf.cs New file with WPF-specific Focus extension method for TabPageModel
ILSpy/ViewModels/TabPageModelExtensions.cs New file with platform-independent TabPageModel extension methods
ILSpy/ViewModels/TabPageModel.cs Removed TabPageModelExtensions static class (extracted to separate files) and unused using statements
ILSpy/ViewModels/PaneModel.cs Removed Pane static class with WPF dependency properties (extracted to separate file) and unused using statement
ILSpy/ViewModels/Pane.cs New file containing extracted Pane static class with WPF dependency properties
ILSpy/ViewModels/LegacyToolPaneModel.cs Removed unused System.Windows using statement
ILSpy/ViewModels/DebugStepsPaneModel.cs Removed unused System.Windows using statement
ILSpy/TreeNodes/AssemblyListTreeNode.wpf.cs New file with WPF-specific drag-drop implementation for AssemblyListTreeNode
ILSpy/TreeNodes/AssemblyListTreeNode.cs Made class partial, removed drag-drop methods (moved to .wpf.cs file) and unused using statements
ILSpy/TextView/VisualLineReferenceText.wpf.cs New file with WPF-specific cursor handling for VisualLineReferenceText
ILSpy/TextView/VisualLineReferenceText.cs New file with platform-independent VisualLineReferenceText implementation
ILSpy/TextView/ViewState.cs New file containing extracted ViewState class from DecompilerTextView
ILSpy/TextView/ReferenceElementGenerator.cs Removed nested VisualLineReferenceText class (extracted to separate files)
ILSpy/TextView/IBracketSearcher.cs New file containing extracted IBracketSearcher interface
ILSpy/TextView/DefaultBracketSearcher.cs New file containing extracted DefaultBracketSearcher class
ILSpy/TextView/DecompilerTextView.cs Removed ViewState class (extracted to separate file)
ILSpy/TextView/BracketSearchResult.cs New file containing extracted BracketSearchResult class
ILSpy/TextView/BracketHighlightRenderer.cs Removed bracket-related classes (extracted to separate files) and added null-safety for resource loading
ILSpy/SmartTextOutputExtensions.cs New file containing extracted SmartTextOutputExtensions static class
ILSpy/Search/ShowSearchCommand.cs New file containing extracted ShowSearchCommand class with debug logging
ILSpy/Search/SearchPaneModel.cs Added [Export] attribute and made class partial
ILSpy/Search/SearchPane.xaml.cs Removed ShowSearchCommand class (extracted to separate file) and unused using statement
ILSpy/Options/DisplaySettings.cs Added CROSS_PLATFORM conditional compilation for font handling
ILSpy/Languages/CSharpLanguage.wpf.cs New file with WPF-specific AddWarningMessage implementation
ILSpy/Languages/CSharpLanguage.cs Made class partial and removed AddWarningMessage method (moved to .wpf.cs file)
ILSpy/ISmartTextOutput.cs Removed SmartTextOutputExtensions class (extracted to separate file) and unused using statements
ILSpy/Docking/DockWorkspace.wpf.cs New file with WPF-specific docking and layout management implementation
ILSpy/Docking/DockWorkspace.cs Made class partial, changed Export attribute to explicit type, removed WPF-specific methods and using statements
ILSpy/AssemblyTree/AssemblyTreeModel.wpf.cs New file with WPF-specific constructor and initialization logic
ILSpy/AssemblyTree/AssemblyTreeModel.cs Made class partial, removed constructor and LoadInitialAssemblies method, added TODO comments
ILSpy/AboutPage.cs Added CROSS_PLATFORM conditional compilation for binding and image loading
ICSharpCode.ILSpyX/TreeView/SharpTreeNodeCollection.cs Added IList interface implementation with collection manipulation methods
ICSharpCode.ILSpyX/TreeView/SharpTreeNode.cs Added ViewChildren property and LoadingTreeNode class for cross-platform tree view binding

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +255 to +258
public int Add(object value)
{
return ((IList)list).Add(value);
}
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IList.Add implementation directly delegates to the underlying list without validation. This bypasses the parent node validation logic that exists in the generic Add method. Consider validating the node's parent before adding, similar to the generic Add(SharpTreeNode) method.

Copilot uses AI. Check for mistakes.

public void Remove(object value)
{
((IList)list).Remove(value);
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IList.Remove implementation directly delegates to the underlying list without using the CollectionChanged event mechanism. This bypasses the notification logic that exists in the generic Remove method. Consider using the generic Remove method or ensuring proper event notification.

Suggested change
((IList)list).Remove(value);
// Route through the generic Remove to ensure proper notification and validation.
if (value == null || value is SharpTreeNode)
{
Remove((SharpTreeNode)value);
}

Copilot uses AI. Check for mistakes.

public object SyncRoot => ((ICollection)list).SyncRoot;

object IList.this[int index] { get => ((IList)list)[index]; set => ((IList)list)[index] = value; }
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IList indexer setter directly delegates to the underlying list without validation. This bypasses the parent node validation logic that exists in the typed indexer. Consider validating the node's parent before setting, similar to the generic indexer setter.

Suggested change
object IList.this[int index] { get => ((IList)list)[index]; set => ((IList)list)[index] = value; }
object IList.this[int index]
{
get => this[index];
set
{
if (value is not SharpTreeNode node)
throw new ArgumentException("Value must be a SharpTreeNode.", nameof(value));
this[index] = node;
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +48
Console.WriteLine($"ShowSearchCommand: Executing ShowToolPane for SearchPaneModel.PaneContentId using dockWorkspace type: {dockWorkspace?.GetType().FullName}");
var result = dockWorkspace.ShowToolPane(SearchPaneModel.PaneContentId);
Console.WriteLine($"ShowSearchCommand: ShowToolPane returned: {result}");
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug console output statements should be removed before merging to production. These Console.WriteLine calls appear to be temporary debugging code.

Suggested change
Console.WriteLine($"ShowSearchCommand: Executing ShowToolPane for SearchPaneModel.PaneContentId using dockWorkspace type: {dockWorkspace?.GetType().FullName}");
var result = dockWorkspace.ShowToolPane(SearchPaneModel.PaneContentId);
Console.WriteLine($"ShowSearchCommand: ShowToolPane returned: {result}");
dockWorkspace.ShowToolPane(SearchPaneModel.PaneContentId);

Copilot uses AI. Check for mistakes.
var oldSelection = selectedItems;
selectedItems = value;
OnPropertyChanged();
// TODO: OnPropertyChanged(nameof(SelectedItem));
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO comment should be addressed or removed. If this is intentional work-in-progress, it should be tracked in an issue rather than left as an inline comment.

Suggested change
// TODO: OnPropertyChanged(nameof(SelectedItem));

Copilot uses AI. Check for mistakes.
}
else
{
// TODO: ExpandAncestors(node);
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO comment should be addressed or removed. If this is intentional work-in-progress, it should be tracked in an issue rather than left as an inline comment.

Suggested change
// TODO: ExpandAncestors(node);
// Ancestors are intentionally not expanded automatically when selecting a node.

Copilot uses AI. Check for mistakes.
Comment on lines +955 to +962
// TODO: void ExpandAncestors(SharpTreeNode node)
// {
// foreach (var ancestor in node.Ancestors().Reverse())
// {
// ancestor.EnsureLazyChildren();
// ancestor.IsExpanded = true;
// }
// }
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO comment should be addressed or removed. If this represents incomplete functionality, it should be tracked in an issue or completed before merging.

Copilot uses AI. Check for mistakes.
public ImageSource Image { get; init; }
}

[Export]
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the [Export] attribute alongside [ExportToolPane] may create duplicate exports. Verify that this additional attribute is necessary and doesn't conflict with the existing export mechanism.

Suggested change
[Export]

Copilot uses AI. Check for mistakes.
namespace ICSharpCode.ILSpy.Docking
{
[Export]
[Export(typeof(DockWorkspace))]
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Export attribute has been changed from [Export] to [Export(typeof(DockWorkspace))]. Ensure that all import sites are compatible with this change and that the explicit type export is necessary for the cross-platform implementation.

Suggested change
[Export(typeof(DockWorkspace))]
[Export]

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +55
if (icon != null)
{
button.Content = new StackPanel {
Orientation = Orientation.Horizontal,
Children = {
new Image { Width = 16, Height = 16, Source = icon, Margin = new Thickness(0, 0, 4, 0) },
new TextBlock { Text = text }
}
};
}
else
{
button.Content = text;
}
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both branches of this 'if' statement write to the same variable - consider using '?' to express intent better.

Suggested change
if (icon != null)
{
button.Content = new StackPanel {
Orientation = Orientation.Horizontal,
Children = {
new Image { Width = 16, Height = 16, Source = icon, Margin = new Thickness(0, 0, 4, 0) },
new TextBlock { Text = text }
}
};
}
else
{
button.Content = text;
}
button.Content = icon != null
? (object)new StackPanel {
Orientation = Orientation.Horizontal,
Children = {
new Image { Width = 16, Height = 16, Source = icon, Margin = new Thickness(0, 0, 4, 0) },
new TextBlock { Text = text }
}
}
: text;

Copilot uses AI. Check for mistakes.
@christophwille
Copy link
Member

Fyi, we run Copilot reviews on almost all PRs these days to see how it changes over time (quality-wise), so expect nuggets and duds.

@lextm
Copy link
Contributor Author

lextm commented Dec 18, 2025

@christophwille It actually provides some useful insights. I am still working on the repo reconfiguration, so will take care of them later.

@christophwille
Copy link
Member

No need to rush anything. A well-thought-out & tested approach is appreciated.

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.

2 participants