Browse Source

Add single-stepping support to some ILAst transforms.

pull/728/merge
Daniel Grunwald 9 years ago
parent
commit
0fade5cb0f
  1. 21
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  2. 13
      ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs
  3. 4
      ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
  4. 11
      ICSharpCode.Decompiler/IL/Instructions/IfInstruction.cs
  5. 2
      ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs
  6. 17
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  7. 54
      ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs
  8. 7
      ICSharpCode.Decompiler/IL/Transforms/LoopingTransform.cs
  9. 25
      ICSharpCode.Decompiler/Tests/Helpers/Tester.cs
  10. 11
      ICSharpCode.Decompiler/Tests/PrettyTestRunner.cs
  11. 51
      ILSpy/Languages/ILAstLanguage.cs
  12. 28
      ILSpy/MainWindow.xaml.cs
  13. 5
      ILSpy/TextView/DecompilerTextView.cs

21
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1465,8 +1465,27 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1465,8 +1465,27 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitIfInstruction(IfInstruction inst, TranslationContext context)
{
var condition = TranslateCondition(inst.Condition);
var trueBranch = Translate(inst.TrueInst);
var falseBranch = Translate(inst.FalseInst);
if (falseBranch.Type.IsKnownType(KnownTypeCode.Boolean)) {
if (inst.TrueInst.MatchLdcI4(1)) {
// "a ? true : b" ==> "a || b"
return new BinaryOperatorExpression(
condition,
BinaryOperatorType.ConditionalOr,
falseBranch)
.WithILInstruction(inst)
.WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.Boolean)));
} else if (inst.TrueInst.MatchLdcI4(0)) {
// "a ? false : b" ==> "!a && b"
return new BinaryOperatorExpression(
LogicNot(condition),
BinaryOperatorType.ConditionalAnd,
falseBranch)
.WithILInstruction(inst)
.WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.Boolean)));
}
}
var trueBranch = Translate(inst.TrueInst);
IType targetType;
if (!trueBranch.Type.Equals(SpecialType.NullType) && !falseBranch.Type.Equals(SpecialType.NullType) && !trueBranch.Type.Equals(falseBranch.Type)) {
targetType = compilation.FindType(inst.ResultType.ToKnownTypeCode());

13
ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs

@ -32,7 +32,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -32,7 +32,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// Blocks should be basic blocks prior to this transform.
/// After this transform, they will be extended basic blocks.
/// </remarks>
public class ConditionDetection : IILTransform
public class ConditionDetection : IILTransform, ISingleStep
{
public void Run(ILFunction function, ILTransformContext context)
{
@ -41,11 +41,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -41,11 +41,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
}
public int MaxStepCount { get; set; } = int.MaxValue;
Stepper stepper;
BlockContainer currentContainer;
ControlFlowNode[] controlFlowGraph;
void Run(BlockContainer container, ILTransformContext context)
{
stepper = new Stepper(MaxStepCount);
currentContainer = container;
controlFlowGraph = LoopDetection.BuildCFG(container);
Dominance.ComputeDominance(controlFlowGraph[0], context.CancellationToken);
@ -54,7 +58,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -54,7 +58,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
currentContainer = null;
container.Blocks.RemoveAll(b => b.Parent != container || b.Instructions.Count == 0);
}
/// <summary>
/// Builds structured control flow for the block associated with the control flow node.
/// </summary>
@ -93,6 +97,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -93,6 +97,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
block.Instructions.RemoveAt(block.Instructions.Count - 1);
block.Instructions.AddRange(targetBlock.Instructions);
targetBlock.Instructions.Clear();
stepper.Stepped();
}
}
@ -106,6 +111,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -106,6 +111,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
ifInst.TrueInst = exitInst;
exitInst = block.Instructions.Last();
ifInst.Condition = new LogicNot(ifInst.Condition);
stepper.Stepped();
}
ILInstruction trueExitInst;
@ -122,6 +128,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -122,6 +128,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
targetBlock.Instructions.RemoveAt(targetBlock.Instructions.Count - 1);
trueExitInst = null;
}
stepper.Stepped();
} else {
trueExitInst = ifInst.TrueInst;
}
@ -142,6 +149,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -142,6 +149,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
Debug.Assert(trueExitInst == ifInst.TrueInst);
ifInst.TrueInst = new Nop { ILRange = ifInst.TrueInst.ILRange };
}
stepper.Stepped();
}
}
if (ifInst.FalseInst.OpCode != OpCode.Nop && ifInst.FalseInst.ILRange.Start < ifInst.TrueInst.ILRange.Start
@ -151,6 +159,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -151,6 +159,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
ifInst.TrueInst = ifInst.FalseInst;
ifInst.FalseInst = oldTrue;
ifInst.Condition = new LogicNot(ifInst.Condition);
stepper.Stepped();
}
}

4
ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs

@ -77,12 +77,10 @@ namespace ICSharpCode.Decompiler.IL @@ -77,12 +77,10 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>
/// Apply a list of transforms to this function.
/// </summary>
public void RunTransforms(IEnumerable<IILTransform> transforms, ILTransformContext context, Func<IILTransform, bool> stopTransform = null)
public void RunTransforms(IEnumerable<IILTransform> transforms, ILTransformContext context)
{
foreach (var transform in transforms) {
context.CancellationToken.ThrowIfCancellationRequested();
if (stopTransform != null && stopTransform(transform))
break;
transform.Run(this, context);
this.CheckInvariant(ILPhase.Normal);
}

11
ICSharpCode.Decompiler/IL/Instructions/IfInstruction.cs

@ -25,11 +25,14 @@ namespace ICSharpCode.Decompiler.IL @@ -25,11 +25,14 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>If statement / conditional expression. <c>if (condition) trueExpr else falseExpr</c></summary>
/// <remarks>
/// The condition must return StackType.I4, use comparison instructions like Ceq to check if other types are non-zero.
/// Phase-1 execution of an IfInstruction consists of phase-1 execution of the condition.
/// Phase-2 execution of an IfInstruction will phase-2-execute the condition.
/// If the condition evaluates to a non-zero, the TrueInst is executed (both phase-1 and phase-2).
/// If the condition evaluates to zero, the FalseInst is executed (both phase-1 and phase-2).
///
/// If the condition evaluates to non-zero, the TrueInst is executed.
/// If the condition evaluates to zero, the FalseInst is executed.
/// The return value of the IfInstruction is the return value of the TrueInst or FalseInst.
///
/// IfInstruction is also used to represent logical operators:
/// "a || b" ==> if (a) (ldc.i4 1) else (b)
/// "a && b" ==> if (logic.not(a)) (ldc.i4 0) else (b)
/// </remarks>
partial class IfInstruction : ILInstruction
{

2
ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs

@ -155,7 +155,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -155,7 +155,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
v.Name = contextPrefix + v.Name;
}
function.RunTransforms(CSharpDecompiler.GetILTransforms(), context, t => t is DelegateConstruction);
function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is DelegateConstruction)), context);
function.AcceptVisitor(new ReplaceDelegateTargetVisitor(target, function.Variables.SingleOrDefault(v => v.Index == -1 && v.Kind == VariableKind.Parameter)));
// handle nested lambdas
((IILTransform)new DelegateConstruction()).Run(function, new ILTransformContext { Settings = context.Settings, CancellationToken = context.CancellationToken, TypeSystem = localTypeSystem });

17
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -30,10 +30,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -30,10 +30,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <remarks>
/// Should run after inlining so that the expression patterns can be detected.
/// </remarks>
public class ExpressionTransforms : ILVisitor, IILTransform
public class ExpressionTransforms : ILVisitor, IILTransform, ISingleStep
{
public int MaxStepCount { get; set; } = int.MaxValue;
Stepper stepper;
void IILTransform.Run(ILFunction function, ILTransformContext context)
{
stepper = new Stepper(MaxStepCount);
function.AcceptVisitor(this);
}
@ -82,6 +86,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -82,6 +86,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
inst.Kind = ComparisonKind.Inequality;
else if (inst.Kind == ComparisonKind.LessThanOrEqual)
inst.Kind = ComparisonKind.Equality;
stepper.Stepped();
}
}
@ -93,6 +98,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -93,6 +98,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// conv.i4(ldlen array) => ldlen.i4(array)
inst.AddILRange(inst.Argument.ILRange);
inst.ReplaceWith(new LdLen(inst.TargetType.GetStackType(), array) { ILRange = inst.ILRange });
stepper.Stepped();
}
}
@ -107,6 +113,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -107,6 +113,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
arg.AddILRange(inst.ILRange);
arg.AddILRange(inst.Argument.ILRange);
inst.ReplaceWith(arg);
stepper.Stepped();
} else if (inst.Argument is Comp) {
Comp comp = (Comp)inst.Argument;
if (comp.InputType != StackType.F || comp.Kind.IsEqualityOrInequality()) {
@ -114,6 +121,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -114,6 +121,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
comp.Kind = comp.Kind.Negate();
comp.AddILRange(inst.ILRange);
inst.ReplaceWith(comp);
stepper.Stepped();
}
}
}
@ -130,6 +138,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -130,6 +138,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
newObj.Arguments.AddRange(inst.Arguments.Skip(1));
var expr = new StObj(inst.Arguments[0], newObj, inst.Method.DeclaringType);
inst.ReplaceWith(expr);
stepper.Stepped();
// Both the StObj and the NewObj may trigger further rules, so continue visiting the replacement:
VisitStObj(expr);
} else {
@ -142,6 +151,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -142,6 +151,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
LdcDecimal decimalConstant;
if (TransformDecimalCtorToConstant(inst, out decimalConstant)) {
inst.ReplaceWith(decimalConstant);
stepper.Stepped();
return;
}
base.VisitNewObj(inst);
@ -186,6 +196,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -186,6 +196,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// stobj(ldloca(v), ...)
// => stloc(v, ...)
inst.ReplaceWith(new StLoc(v, inst.Value));
stepper.Stepped();
}
ILInstruction target;
@ -195,6 +206,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -195,6 +206,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// stobj(target, binary.op(ldobj(target), ...))
// => compound.op(target, ...)
inst.ReplaceWith(new CompoundAssignmentInstruction(binary.Operator, binary.Left, binary.Right, t, binary.CheckForOverflow, binary.Sign, CompoundAssignmentType.EvaluatesToNewValue));
stepper.Stepped();
}
}
@ -211,7 +223,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -211,7 +223,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
ILVariable v1, v2;
ILInstruction value1, value2;
if (trueInst.Instructions[0].MatchStLoc(out v1, out value1) && falseInst.Instructions[0].MatchStLoc(out v2, out value2) && v1 == v2) {
inst.ReplaceWith(new StLoc(v1, new IfInstruction(inst.Condition, value1, value2)));
inst.ReplaceWith(new StLoc(v1, new IfInstruction(new LogicNot(inst.Condition), value2, value1)));
stepper.Stepped();
}
}
}

54
ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs

@ -21,15 +21,67 @@ using System.Threading; @@ -21,15 +21,67 @@ using System.Threading;
namespace ICSharpCode.Decompiler.IL.Transforms
{
/// <summary>
/// Parameter class holding various arguments for <see cref="IILTransform.Run(ILFunction, ILTransformContext)"/>.
/// </summary>
public class ILTransformContext
{
public IDecompilerTypeSystem TypeSystem { get; set; }
public DecompilerSettings Settings { get; set; }
public CancellationToken CancellationToken { get; set; }
}
public interface IILTransform
{
void Run(ILFunction function, ILTransformContext context);
}
/// <summary>
/// Interface for transforms that can be "single-stepped" for debug purposes.
///
/// After the transform has performed MaxStepCount steps, it throws a
/// <see cref="StepLimitReachedException"/>.
/// </summary>
public interface ISingleStep
{
/// <summary>
/// Limits the number of steps performed by the transform.
///
/// The default is int.MaxValue = unlimited.
/// </summary>
int MaxStepCount { get; set; }
}
/// <summary>
/// Exception thrown when an IL transform runs into the <see cref="ISingleStep.MaxStepCount"/> limit.
/// </summary>
public class StepLimitReachedException : Exception
{
}
/// <summary>
/// Helper struct for use in transforms that implement ISingleStep.
/// </summary>
internal struct Stepper
{
public readonly int MaxStepCount;
public int StepCount;
public Stepper(int maxStepCount)
{
this.StepCount = 0;
this.MaxStepCount = maxStepCount;
if (maxStepCount == 0)
throw new StepLimitReachedException();
}
/// <summary>
/// Called immediately after a transform step was taken.
/// </summary>
public void Stepped()
{
if (++StepCount == MaxStepCount)
throw new StepLimitReachedException();
}
}
}

7
ICSharpCode.Decompiler/IL/Transforms/LoopingTransform.cs

@ -25,10 +25,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -25,10 +25,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <summary>
/// Repeats the child transforms until the ILAst no longer changes.
/// </summary>
public class LoopingTransform : IILTransform
public class LoopingTransform : IILTransform, ISingleStep
{
public int MaxStepCount { get; set; } = int.MaxValue;
readonly IReadOnlyCollection<IILTransform> children;
public LoopingTransform(params IILTransform[] children)
{
this.children = children;
@ -36,9 +37,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -36,9 +37,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public void Run(ILFunction function, ILTransformContext context)
{
var stepper = new Stepper(MaxStepCount);
do {
function.ResetDirty();
function.RunTransforms(children, context);
stepper.Stepped();
} while (function.IsDirty);
}

25
ICSharpCode.Decompiler/Tests/Helpers/Tester.cs

@ -38,17 +38,18 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -38,17 +38,18 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
public enum CompilerOptions
{
None,
Optimize,
UseDebug,
Force32Bit
Optimize = 0x1,
UseDebug = 0x2,
Force32Bit = 0x4
}
[Flags]
public enum AssemblerOptions
{
None,
UseDebug,
Force32Bit
UseDebug = 0x1,
Force32Bit = 0x2,
Library = 0x4,
}
public static class Tester
@ -56,9 +57,16 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -56,9 +57,16 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
public static string AssembleIL(string sourceFileName, AssemblerOptions options = AssemblerOptions.UseDebug)
{
string ilasmPath = Path.Combine(Environment.GetEnvironmentVariable("windir"), @"Microsoft.NET\Framework\v4.0.30319\ilasm.exe");
string outputFile = Path.GetFileNameWithoutExtension(sourceFileName) + ".exe";
string outputFile = Path.GetFileNameWithoutExtension(sourceFileName);
string otherOptions = " ";
if (options.HasFlag(AssemblerOptions.Library)) {
outputFile += ".dll";
otherOptions += "/dll ";
} else {
outputFile += ".exe";
otherOptions += "/exe ";
}
if (options.HasFlag(AssemblerOptions.UseDebug)) {
otherOptions += "/debug ";
@ -68,7 +76,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -68,7 +76,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
}
ProcessStartInfo info = new ProcessStartInfo(ilasmPath);
info.Arguments = $"/nologo /exe{otherOptions}/output=\"{outputFile}\" \"{sourceFileName}\"";
info.Arguments = $"/nologo {otherOptions}/output=\"{outputFile}\" \"{sourceFileName}\"";
info.RedirectStandardError = true;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
@ -83,6 +91,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -83,6 +91,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
Console.WriteLine("output: " + outputTask.Result);
Console.WriteLine("errors: " + errorTask.Result);
Assert.AreEqual(0, process.ExitCode, "ilasm failed");
return outputFile;
}

11
ICSharpCode.Decompiler/Tests/PrettyTestRunner.cs

@ -68,14 +68,21 @@ namespace ICSharpCode.Decompiler.Tests @@ -68,14 +68,21 @@ namespace ICSharpCode.Decompiler.Tests
Run();
Run(asmOptions: AssemblerOptions.UseDebug);
}
[Test]
public void ShortCircuit()
{
Run();
Run(asmOptions: AssemblerOptions.UseDebug);
}
void Run([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None)
{
var ilFile = Path.Combine(TestCasePath, testName + ".il");
var csFile = Path.Combine(TestCasePath, testName + ".cs");
EnsureSourceFilesExist(Path.Combine(TestCasePath, testName));
var executable = Tester.AssembleIL(ilFile, asmOptions);
var executable = Tester.AssembleIL(ilFile, asmOptions | AssemblerOptions.Library);
var decompiled = Tester.DecompileCSharp(executable);
CodeAssert.FilesAreEqual(csFile, decompiled);

51
ILSpy/Languages/ILAstLanguage.cs

@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.Decompiler;
@ -92,16 +93,25 @@ namespace ICSharpCode.ILSpy @@ -92,16 +93,25 @@ namespace ICSharpCode.ILSpy
yield return new TypedIL();
CSharpDecompiler decompiler = new CSharpDecompiler(ModuleDefinition.CreateModule("Dummy", ModuleKind.Dll), new DecompilerSettings());
for (int i = 0; i <= decompiler.ILTransforms.Count; i++) {
yield return new BlockIL(decompiler.ILTransforms.Take(i).ToList());
yield return MakeDebugLanguage(decompiler.ILTransforms.Take(i));
var loop = decompiler.ILTransforms.ElementAtOrDefault(i) as LoopingTransform;
if (loop != null) {
for (int j = 1; j <= loop.Transforms.Count; j++) {
yield return new BlockIL(decompiler.ILTransforms.Take(i).Concat(loop.Transforms.Take(j)).ToList());
yield return MakeDebugLanguage(decompiler.ILTransforms.Take(i).Concat(loop.Transforms.Take(j)));
}
}
}
}
static ILAstLanguage MakeDebugLanguage(IEnumerable<IILTransform> transforms)
{
var list = transforms.ToList();
if (list.LastOrDefault() is ISingleStep)
return new SingleSteppableIL(list);
else
return new BlockIL(list);
}
public override string FileExtension {
get {
return ".il";
@ -159,6 +169,43 @@ namespace ICSharpCode.ILSpy @@ -159,6 +169,43 @@ namespace ICSharpCode.ILSpy
il.WriteTo(output);
}
}
class SingleSteppableIL : ILAstLanguage, ISingleStep
{
readonly IReadOnlyList<IILTransform> transforms;
readonly ISingleStep steppingTransform;
public SingleSteppableIL(IReadOnlyList<IILTransform> transforms) : base("ILAst (" + transforms.Last().GetType().Name + ")")
{
this.transforms = transforms;
this.steppingTransform = (ISingleStep)transforms.Last();
}
public int MaxStepCount { get; set; } = int.MaxValue;
public override void DecompileMethod(MethodDefinition method, ITextOutput output, DecompilationOptions options)
{
base.DecompileMethod(method, output, options);
output.WriteLine("// This transform support single-stepping. Press '+' or '-' to change the step limit.");
output.WriteLine("// Current step limit: " + MaxStepCount);
if (!method.HasBody)
return;
var typeSystem = new DecompilerTypeSystem(method.Module);
ILReader reader = new ILReader(typeSystem);
ILFunction il = reader.ReadIL(method.Body, options.CancellationToken);
steppingTransform.MaxStepCount = this.MaxStepCount;
try {
il.RunTransforms(transforms, new ILTransformContext { Settings = options.DecompilerSettings, TypeSystem = typeSystem });
output.WriteLine("// The transform ran to completion.");
} catch (StepLimitReachedException) {
output.WriteLine("// The transform was aborted after reaching the step limit.");
} finally {
steppingTransform.MaxStepCount = int.MaxValue; // unlimit again so that other languages can use the transform
}
output.WriteLine();
il.WriteTo(output);
}
}
}
#endif
}

28
ILSpy/MainWindow.xaml.cs

@ -778,8 +778,34 @@ namespace ICSharpCode.ILSpy @@ -778,8 +778,34 @@ namespace ICSharpCode.ILSpy
return treeView.GetTopLevelSelection().OfType<ILSpyTreeNode>();
}
}
#endregion
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
if (CurrentLanguage is Decompiler.IL.Transforms.ISingleStep) {
var step = (Decompiler.IL.Transforms.ISingleStep)CurrentLanguage;
if (e.Key == Key.OemPlus || e.Key == Key.Add) {
if (step.MaxStepCount == int.MaxValue) {
step.MaxStepCount = 0;
} else {
step.MaxStepCount++;
}
DecompileSelectedNodes(recordHistory: false);
e.Handled = true;
} else if (e.Key == Key.OemMinus || e.Key == Key.Subtract) {
if (step.MaxStepCount == 0) {
step.MaxStepCount = int.MaxValue;
} else {
step.MaxStepCount--;
}
DecompileSelectedNodes(recordHistory: false);
e.Handled = true;
}
} else {
base.OnPreviewKeyDown(e);
}
}
#endregion
#region Back/Forward navigation
void BackCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
{

5
ILSpy/TextView/DecompilerTextView.cs

@ -107,9 +107,8 @@ namespace ICSharpCode.ILSpy.TextView @@ -107,9 +107,8 @@ namespace ICSharpCode.ILSpy.TextView
DisplaySettingsPanel.CurrentDisplaySettings.PropertyChanged += CurrentDisplaySettings_PropertyChanged;
// SearchPanel
SearchPanel.Install(textEditor.TextArea);
// TODO: re-enable the RegisterCommands call after updating to AvalonEdit 5.0.3
// .RegisterCommands(Application.Current.MainWindow.CommandBindings);
SearchPanel.Install(textEditor.TextArea)
.RegisterCommands(Application.Current.MainWindow.CommandBindings);
ShowLineMargin();

Loading…
Cancel
Save