mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
Replaces the NotImplementedDialog stub in OpenFromGacCommand with a real dialog. CanExecute gates on OperatingSystem.IsWindows() since the Windows Global Assembly Cache doesn't exist on Linux/macOS (matches WPF's AppEnvironment.IsWindows gate). The actual GAC enumeration uses the shared UniversalAssemblyResolver.EnumerateGac() + GetAssemblyInGac() pair from ICSharpCode.Decompiler — no platform-specific code in the Avalonia tree. Assisted-by: Claude:claude-opus-4-7:Claude Codepull/3755/head
3 changed files with 207 additions and 1 deletions
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
<Window xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
x:Class="ILSpy.Views.OpenFromGacDialog" |
||||
Width="600" Height="450" MinWidth="500" MinHeight="300" |
||||
WindowStartupLocation="CenterOwner" |
||||
Title="Open from GAC"> |
||||
<Grid Margin="12,8" RowDefinitions="Auto,*,Auto,Auto" RowSpacing="6"> |
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,*"> |
||||
<TextBlock Grid.Column="0" Text="Filter:" VerticalAlignment="Center" Margin="0,0,8,0" /> |
||||
<TextBox Grid.Column="1" Name="FilterBox" /> |
||||
</Grid> |
||||
<ListBox Grid.Row="1" Name="EntriesList" SelectionMode="Multiple"> |
||||
<ListBox.ItemTemplate> |
||||
<DataTemplate> |
||||
<TextBlock Text="{Binding}" /> |
||||
</DataTemplate> |
||||
</ListBox.ItemTemplate> |
||||
</ListBox> |
||||
<ProgressBar Grid.Row="2" Name="LoadingBar" Height="6" IsIndeterminate="True" /> |
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="6"> |
||||
<Button Name="OkButton" Content="Open" IsDefault="True" IsEnabled="False" MinWidth="72" /> |
||||
<Button Name="CancelButton" Content="Cancel" IsCancel="True" MinWidth="72" /> |
||||
</StackPanel> |
||||
</Grid> |
||||
</Window> |
||||
@ -0,0 +1,149 @@
@@ -0,0 +1,149 @@
|
||||
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.Linq; |
||||
using System.Threading.Tasks; |
||||
|
||||
using Avalonia.Controls; |
||||
using Avalonia.Markup.Xaml; |
||||
using Avalonia.Threading; |
||||
|
||||
using ICSharpCode.Decompiler.Metadata; |
||||
|
||||
namespace ILSpy.Views |
||||
{ |
||||
/// <summary>
|
||||
/// "Open from GAC" dialog: enumerates the Global Assembly Cache via the shared
|
||||
/// <see cref="UniversalAssemblyResolver.EnumerateGac"/> + <see cref="UniversalAssemblyResolver.GetAssemblyInGac"/>
|
||||
/// pair, surfaces the assemblies in a filterable list, returns the selected
|
||||
/// absolute file paths. Windows-only — the GAC doesn't exist on Linux/macOS, and
|
||||
/// the command's CanExecute gates on <c>OperatingSystem.IsWindows()</c>.
|
||||
/// </summary>
|
||||
public partial class OpenFromGacDialog : Window |
||||
{ |
||||
readonly ObservableCollection<GacEntry> allEntries = new(); |
||||
readonly ObservableCollection<GacEntry> filteredEntries = new(); |
||||
readonly System.Threading.CancellationTokenSource fetchCts = new(); |
||||
|
||||
ListBox entriesList = null!; |
||||
TextBox filterBox = null!; |
||||
ProgressBar loadingBar = null!; |
||||
Button okButton = null!; |
||||
|
||||
public OpenFromGacDialog() |
||||
{ |
||||
InitializeComponent(); |
||||
entriesList = this.FindControl<ListBox>("EntriesList")!; |
||||
filterBox = this.FindControl<TextBox>("FilterBox")!; |
||||
loadingBar = this.FindControl<ProgressBar>("LoadingBar")!; |
||||
okButton = this.FindControl<Button>("OkButton")!; |
||||
entriesList.ItemsSource = filteredEntries; |
||||
entriesList.SelectionChanged += (_, _) => okButton.IsEnabled = entriesList.SelectedItems!.Count > 0; |
||||
filterBox.TextChanged += (_, _) => Refilter(); |
||||
okButton.Click += (_, _) => Close(SelectedFileNames); |
||||
this.FindControl<Button>("CancelButton")!.Click += (_, _) => Close(System.Array.Empty<string>()); |
||||
Closed += (_, _) => fetchCts.Cancel(); |
||||
_ = FetchAsync(); |
||||
} |
||||
|
||||
void InitializeComponent() => AvaloniaXamlLoader.Load(this); |
||||
|
||||
public string[] SelectedFileNames |
||||
=> entriesList.SelectedItems!.OfType<GacEntry>().Select(e => e.FileName).ToArray(); |
||||
|
||||
async Task FetchAsync() |
||||
{ |
||||
var token = fetchCts.Token; |
||||
var seen = new HashSet<string>(); |
||||
try |
||||
{ |
||||
await Task.Run(() => { |
||||
foreach (var reference in UniversalAssemblyResolver.EnumerateGac()) |
||||
{ |
||||
if (token.IsCancellationRequested) |
||||
return; |
||||
if (!seen.Add(reference.FullName)) |
||||
continue; |
||||
var path = UniversalAssemblyResolver.GetAssemblyInGac(reference); |
||||
if (path == null) |
||||
continue; |
||||
var entry = new GacEntry(reference, path); |
||||
Dispatcher.UIThread.Post(() => AddEntry(entry)); |
||||
} |
||||
}, token); |
||||
} |
||||
catch (OperationCanceledException) |
||||
{ |
||||
// Window closed before the enumeration finished — nothing to do.
|
||||
} |
||||
finally |
||||
{ |
||||
loadingBar.IsVisible = false; |
||||
} |
||||
} |
||||
|
||||
void AddEntry(GacEntry entry) |
||||
{ |
||||
allEntries.Add(entry); |
||||
if (PassesFilter(entry)) |
||||
filteredEntries.Add(entry); |
||||
} |
||||
|
||||
void Refilter() |
||||
{ |
||||
filteredEntries.Clear(); |
||||
foreach (var entry in allEntries.Where(PassesFilter)) |
||||
filteredEntries.Add(entry); |
||||
} |
||||
|
||||
bool PassesFilter(GacEntry entry) |
||||
{ |
||||
var text = filterBox.Text?.Trim(); |
||||
if (string.IsNullOrEmpty(text)) |
||||
return true; |
||||
var label = entry.ToString(); |
||||
foreach (var token in text.Split(' ', System.StringSplitOptions.RemoveEmptyEntries)) |
||||
{ |
||||
if (label.IndexOf(token, System.StringComparison.OrdinalIgnoreCase) < 0) |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
sealed class GacEntry(AssemblyNameReference reference, string fileName) |
||||
{ |
||||
public string FileName { get; } = fileName; |
||||
public override string ToString() |
||||
=> $"{reference.Name}, Version={reference.Version}, Culture={(reference.Culture is { Length: > 0 } c ? c : "neutral")}, PublicKeyToken={FormatPublicKey(reference)}"; |
||||
|
||||
static string FormatPublicKey(AssemblyNameReference reference) |
||||
{ |
||||
var token = reference.PublicKeyToken; |
||||
if (token == null || token.Length == 0) |
||||
return "null"; |
||||
var sb = new System.Text.StringBuilder(token.Length * 2); |
||||
foreach (var b in token) |
||||
sb.AppendFormat("{0:x2}", b); |
||||
return sb.ToString(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue