diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index a5e2d752b..9eac3d0bd 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -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()); diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs index 0b1caa4e2..7c59cfdf7 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs @@ -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. /// - 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 } } + 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 currentContainer = null; container.Blocks.RemoveAll(b => b.Parent != container || b.Instructions.Count == 0); } - + /// /// Builds structured control flow for the block associated with the control flow node. /// @@ -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 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 targetBlock.Instructions.RemoveAt(targetBlock.Instructions.Count - 1); trueExitInst = null; } + stepper.Stepped(); } else { trueExitInst = ifInst.TrueInst; } @@ -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 ifInst.TrueInst = ifInst.FalseInst; ifInst.FalseInst = oldTrue; ifInst.Condition = new LogicNot(ifInst.Condition); + stepper.Stepped(); } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs index 39472bce2..8f5af702e 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs @@ -77,12 +77,10 @@ namespace ICSharpCode.Decompiler.IL /// /// Apply a list of transforms to this function. /// - public void RunTransforms(IEnumerable transforms, ILTransformContext context, Func stopTransform = null) + public void RunTransforms(IEnumerable 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); } diff --git a/ICSharpCode.Decompiler/IL/Instructions/IfInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/IfInstruction.cs index 43030cad2..b364f8a25 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/IfInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/IfInstruction.cs @@ -25,11 +25,14 @@ namespace ICSharpCode.Decompiler.IL /// If statement / conditional expression. if (condition) trueExpr else falseExpr /// /// 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) /// partial class IfInstruction : ILInstruction { diff --git a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs index 0a7621470..20041a9cb 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs @@ -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 }); diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 585b6287a..8228ec30f 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -30,10 +30,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// Should run after inlining so that the expression patterns can be detected. /// - 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 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 // 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 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 comp.Kind = comp.Kind.Negate(); comp.AddILRange(inst.ILRange); inst.ReplaceWith(comp); + stepper.Stepped(); } } } @@ -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 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 // 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 // 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 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(); } } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs index 32ae5cf2d..f1384e303 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs @@ -21,15 +21,67 @@ using System.Threading; namespace ICSharpCode.Decompiler.IL.Transforms { + /// + /// Parameter class holding various arguments for . + /// 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); } + + /// + /// Interface for transforms that can be "single-stepped" for debug purposes. + /// + /// After the transform has performed MaxStepCount steps, it throws a + /// . + /// + public interface ISingleStep + { + /// + /// Limits the number of steps performed by the transform. + /// + /// The default is int.MaxValue = unlimited. + /// + int MaxStepCount { get; set; } + } + + /// + /// Exception thrown when an IL transform runs into the limit. + /// + public class StepLimitReachedException : Exception + { + } + + /// + /// Helper struct for use in transforms that implement ISingleStep. + /// + 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(); + } + + /// + /// Called immediately after a transform step was taken. + /// + public void Stepped() + { + if (++StepCount == MaxStepCount) + throw new StepLimitReachedException(); + } + } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/LoopingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/LoopingTransform.cs index 02edd0b2f..2806db3f7 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LoopingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LoopingTransform.cs @@ -25,10 +25,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// Repeats the child transforms until the ILAst no longer changes. /// - public class LoopingTransform : IILTransform + public class LoopingTransform : IILTransform, ISingleStep { + public int MaxStepCount { get; set; } = int.MaxValue; readonly IReadOnlyCollection children; - + public LoopingTransform(params IILTransform[] children) { this.children = children; @@ -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); } diff --git a/ICSharpCode.Decompiler/Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler/Tests/Helpers/Tester.cs index 2d09d385a..9d35f2cfa 100644 --- a/ICSharpCode.Decompiler/Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler/Tests/Helpers/Tester.cs @@ -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 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 } 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 Console.WriteLine("output: " + outputTask.Result); Console.WriteLine("errors: " + errorTask.Result); + Assert.AreEqual(0, process.ExitCode, "ilasm failed"); return outputFile; } diff --git a/ICSharpCode.Decompiler/Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler/Tests/PrettyTestRunner.cs index f10a3d434..1f3a69bd5 100644 --- a/ICSharpCode.Decompiler/Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler/Tests/PrettyTestRunner.cs @@ -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); diff --git a/ILSpy/Languages/ILAstLanguage.cs b/ILSpy/Languages/ILAstLanguage.cs index 4f48c71e1..65828015c 100644 --- a/ILSpy/Languages/ILAstLanguage.cs +++ b/ILSpy/Languages/ILAstLanguage.cs @@ -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 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 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 il.WriteTo(output); } } + + class SingleSteppableIL : ILAstLanguage, ISingleStep + { + readonly IReadOnlyList transforms; + readonly ISingleStep steppingTransform; + + public SingleSteppableIL(IReadOnlyList 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 } diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index b09081b1f..941f61cd4 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -778,8 +778,34 @@ namespace ICSharpCode.ILSpy return treeView.GetTopLevelSelection().OfType(); } } - #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) { diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index e2fd98db1..84da42692 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -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();