Skip to content

Commit

Permalink
Merge pull request #3205 from icsharpcode/feature/atfile
Browse files Browse the repository at this point in the history
`@file` support with breaking changes to command line options
  • Loading branch information
siegfriedpammer authored May 26, 2024
2 parents e5d1120 + e4e2dd3 commit d777148
Show file tree
Hide file tree
Showing 15 changed files with 292 additions and 167 deletions.
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<PackageVersion Include="Iced" Version="1.21.0" />
<PackageVersion Include="JunitXml.TestLogger" Version="3.1.12" />
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
<PackageVersion Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<PackageVersion Include="McMaster.Extensions.Hosting.CommandLine" Version="4.1.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.9.2" />
Expand Down
2 changes: 1 addition & 1 deletion ILSpy.AddIn.Shared/Commands/OpenCodeItemCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ protected override async void OnExecute(object sender, EventArgs e)
return;
}

OpenAssembliesInILSpy(new ILSpyParameters(validRefs.Select(r => r.AssemblyFile), "/navigateTo:" +
OpenAssembliesInILSpy(new ILSpyParameters(validRefs.Select(r => r.AssemblyFile), "--navigateto:" +
(symbol.OriginalDefinition ?? symbol).GetDocumentationCommentId()));
}

Expand Down
18 changes: 18 additions & 0 deletions ILSpy.AddIn.Shared/ILSpyAddInPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,24 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
OpenReferenceCommand.Register(this);
OpenCodeItemCommand.Register(this);
}

protected override int QueryClose(out bool canClose)
{
var tempFiles = ILSpyInstance.TempFiles;
while (tempFiles.TryPop(out var filename))
{
try
{
System.IO.File.Delete(filename);
}
catch (Exception)
{
}
}

return base.QueryClose(out canClose);
}

#endregion

public void ShowMessage(string format, params object[] items)
Expand Down
94 changes: 19 additions & 75 deletions ILSpy.AddIn.Shared/ILSpyInstance.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ICSharpCode.ILSpy.AddIn
{
Expand All @@ -23,8 +21,9 @@ public ILSpyParameters(IEnumerable<string> assemblyFileNames, params string[] ar

class ILSpyInstance
{
readonly ILSpyParameters parameters;
internal static readonly ConcurrentStack<string> TempFiles = new ConcurrentStack<string>();

readonly ILSpyParameters parameters;
public ILSpyInstance(ILSpyParameters parameters = null)
{
this.parameters = parameters;
Expand All @@ -47,85 +46,30 @@ public void Start()
{
var commandLineArguments = parameters?.AssemblyFileNames?.Concat(parameters.Arguments);
string ilSpyExe = GetILSpyPath();
var process = new Process() {
StartInfo = new ProcessStartInfo() {
FileName = ilSpyExe,
UseShellExecute = false,
Arguments = "/navigateTo:none"
}
};
process.Start();

const string defaultOptions = "--navigateto:none";
string argumentsToPass = defaultOptions;

if ((commandLineArguments != null) && commandLineArguments.Any())
{
// Only need a message to started process if there are any parameters to pass
SendMessage(ilSpyExe, "ILSpy:\r\n" + string.Join(Environment.NewLine, commandLineArguments), true);
}
}
string assemblyArguments = string.Join("\r\n", commandLineArguments);

[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD110:Observe result of async calls", Justification = "<Pending>")]
void SendMessage(string ilSpyExe, string message, bool activate)
{
string expectedProcessName = Path.GetFileNameWithoutExtension(ilSpyExe);
// We wait asynchronously until target window can be found and try to find it multiple times
Task.Run(async () => {
bool success = false;
int remainingAttempts = 20;
do
{
NativeMethods.EnumWindows(
(hWnd, lParam) => {
string windowTitle = NativeMethods.GetWindowText(hWnd, 100);
if (windowTitle.StartsWith("ILSpy", StringComparison.Ordinal))
{
string processName = NativeMethods.GetProcessNameFromWindow(hWnd);
Debug.WriteLine("Found {0:x4}: '{1}' in '{2}'", hWnd, windowTitle, processName);
if (string.Equals(processName, expectedProcessName, StringComparison.OrdinalIgnoreCase))
{
IntPtr result = Send(hWnd, message);
Debug.WriteLine("WM_COPYDATA result: {0:x8}", result);
if (result == (IntPtr)1)
{
if (activate)
NativeMethods.SetForegroundWindow(hWnd);
success = true;
return false; // stop enumeration
}
}
}
return true; // continue enumeration
}, IntPtr.Zero);
string filepath = Path.GetTempFileName();
File.WriteAllText(filepath, assemblyArguments);

// Wait some time before next attempt
await Task.Delay(500);
remainingAttempts--;
} while (!success && (remainingAttempts > 0));
});
}
TempFiles.Push(filepath);

unsafe static IntPtr Send(IntPtr hWnd, string message)
{
const uint SMTO_NORMAL = 0;
argumentsToPass = $"@\"{filepath}\"";
}

CopyDataStruct lParam;
lParam.Padding = IntPtr.Zero;
lParam.Size = message.Length * 2;
fixed (char* buffer = message)
{
lParam.Buffer = (IntPtr)buffer;
IntPtr result;
// SendMessage with 3s timeout (e.g. when the target process is stopped in the debugger)
if (NativeMethods.SendMessageTimeout(
hWnd, NativeMethods.WM_COPYDATA, IntPtr.Zero, ref lParam,
SMTO_NORMAL, 3000, out result) != IntPtr.Zero)
{
return result;
}
else
{
return IntPtr.Zero;
var process = new Process() {
StartInfo = new ProcessStartInfo() {
FileName = ilSpyExe,
UseShellExecute = false,
Arguments = argumentsToPass
}
}
};
process.Start();
}
}
}
1 change: 0 additions & 1 deletion ILSpy.AddIn.VS2022/ILSpy.AddIn.VS2022.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@
<Compile Include="..\ICSharpCode.Decompiler\Metadata\LightJson\Serialization\TextScanner.cs" Link="Decompiler\LightJson\Serialization\TextScanner.cs" />
<Compile Include="..\ICSharpCode.Decompiler\Metadata\UniversalAssemblyResolver.cs" Link="UniversalAssemblyResolver.cs" />
<Compile Include="..\ICSharpCode.Decompiler\Util\EmptyList.cs" Link="Decompiler\EmptyList.cs" />
<Compile Include="..\ILSpy\NativeMethods.cs" Link="NativeMethods.cs" />
<Compile Include="Decompiler\Dummy.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
Expand Down
1 change: 0 additions & 1 deletion ILSpy.AddIn/ILSpy.AddIn.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@
<Compile Include="..\ICSharpCode.Decompiler\Metadata\LightJson\Serialization\TextScanner.cs" Link="Decompiler\LightJson\Serialization\TextScanner.cs" />
<Compile Include="..\ICSharpCode.Decompiler\Metadata\UniversalAssemblyResolver.cs" Link="UniversalAssemblyResolver.cs" />
<Compile Include="..\ICSharpCode.Decompiler\Util\EmptyList.cs" Link="Decompiler\EmptyList.cs" />
<Compile Include="..\ILSpy\NativeMethods.cs" Link="NativeMethods.cs" />
<Compile Include="Decompiler\Dummy.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
Expand Down
123 changes: 123 additions & 0 deletions ILSpy.Tests/CommandLineArgumentsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System;

using FluentAssertions;

using NUnit.Framework;

namespace ICSharpCode.ILSpy.Tests
{
[TestFixture]
public class CommandLineArgumentsTests
{
[Test]
public void VerifyEmptyArgumentsArray()
{
var cmdLineArgs = new CommandLineArguments(new string[] { });

cmdLineArgs.AssembliesToLoad.Should().BeEmpty();
cmdLineArgs.SingleInstance.Should().BeNull();
cmdLineArgs.NavigateTo.Should().BeNull();
cmdLineArgs.Search.Should().BeNull();
cmdLineArgs.Language.Should().BeNull();
cmdLineArgs.NoActivate.Should().BeFalse();
cmdLineArgs.ConfigFile.Should().BeNull();
}

[Test]
public void VerifyHelpOption()
{
var cmdLineArgs = new CommandLineArguments(new string[] { "--help" });
cmdLineArgs.ArgumentsParser.IsShowingInformation.Should().BeTrue();
}

[Test]
public void VerifyForceNewInstanceOption()
{
var cmdLineArgs = new CommandLineArguments(new string[] { "--newinstance" });
cmdLineArgs.SingleInstance.Should().BeFalse();
}

[Test]
public void VerifyNavigateToOption()
{
const string navigateTo = "MyNamespace.MyClass";
var cmdLineArgs = new CommandLineArguments(new string[] { "--navigateto", navigateTo });
cmdLineArgs.NavigateTo.Should().BeEquivalentTo(navigateTo);
}

[Test]
public void VerifyNavigateToOption_NoneTest_Matching_VSAddin()
{
var cmdLineArgs = new CommandLineArguments(new string[] { "--navigateto:none" });
cmdLineArgs.NavigateTo.Should().BeEquivalentTo("none");
}

[Test]
public void VerifyCaseSensitivityOfOptionsDoesntThrow()
{
var cmdLineArgs = new CommandLineArguments(new string[] { "--navigateTo:none" });

cmdLineArgs.ArgumentsParser.RemainingArguments.Should().HaveCount(1);
}

[Test]
public void VerifySearchOption()
{
const string searchWord = "TestContainers";
var cmdLineArgs = new CommandLineArguments(new string[] { "--search", searchWord });
cmdLineArgs.Search.Should().BeEquivalentTo(searchWord);
}

[Test]
public void VerifyLanguageOption()
{
const string language = "csharp";
var cmdLineArgs = new CommandLineArguments(new string[] { "--language", language });
cmdLineArgs.Language.Should().BeEquivalentTo(language);
}

[Test]
public void VerifyConfigOption()
{
const string configFile = "myilspyoptions.xml";
var cmdLineArgs = new CommandLineArguments(new string[] { "--config", configFile });
cmdLineArgs.ConfigFile.Should().BeEquivalentTo(configFile);
}

[Test]
public void VerifyNoActivateOption()
{
var cmdLineArgs = new CommandLineArguments(new string[] { "--noactivate" });
cmdLineArgs.NoActivate.Should().BeTrue();
}

[Test]
public void MultipleAssembliesAsArguments()
{
var cmdLineArgs = new CommandLineArguments(new string[] { "assembly1", "assembly2", "assembly3" });
cmdLineArgs.AssembliesToLoad.Should().HaveCount(3);
}

[Test]
public void PassAtFileArguments()
{
string filepath = System.IO.Path.GetTempFileName();

System.IO.File.WriteAllText(filepath, "assembly1\r\nassembly2\r\nassembly3\r\n--newinstance\r\n--noactivate");

var cmdLineArgs = new CommandLineArguments(new string[] { $"@{filepath}" });

try
{
System.IO.File.Delete(filepath);
}
catch (Exception)
{
}

cmdLineArgs.SingleInstance.Should().BeFalse();
cmdLineArgs.NoActivate.Should().BeTrue();
cmdLineArgs.AssembliesToLoad.Should().HaveCount(3);
}
}
}
1 change: 1 addition & 0 deletions ILSpy.Tests/ILSpy.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<Compile Include="Analyzers\MethodUsesAnalyzerTests.cs" />
<Compile Include="Analyzers\TestCases\MainAssembly.cs" />
<Compile Include="Analyzers\TypeUsedByAnalyzerTests.cs" />
<Compile Include="CommandLineArgumentsTests.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
11 changes: 11 additions & 0 deletions ILSpy/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,17 @@ public App()
Hyperlink.RequestNavigateEvent,
new RequestNavigateEventHandler(Window_RequestNavigate));
ILSpyTraceListener.Install();

if (App.CommandLineArguments.ArgumentsParser.IsShowingInformation)
{
MessageBox.Show(App.CommandLineArguments.ArgumentsParser.GetHelpText(), "ILSpy Command Line Arguments");
}

if (App.CommandLineArguments.ArgumentsParser.RemainingArguments.Any())
{
string unknownArguments = string.Join(", ", App.CommandLineArguments.ArgumentsParser.RemainingArguments);
MessageBox.Show(unknownArguments, "ILSpy Unknown Command Line Arguments Passed");
}
}

static Assembly ResolvePluginDependencies(AssemblyLoadContext context, AssemblyName assemblyName)
Expand Down
Loading

0 comments on commit d777148

Please sign in to comment.