From 70a70544beb3eb42e0ef2c92244675b3f634f123 Mon Sep 17 00:00:00 2001 From: Brian Zenger Date: Mon, 13 Feb 2017 13:39:55 -0800 Subject: [PATCH 01/20] modified quick fix to use a regular expresssion --- .../PassParameterByReferenceQuickFix.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs index c53ed13793..47ecb21171 100644 --- a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs +++ b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs @@ -19,22 +19,19 @@ public PassParameterByReferenceQuickFix(ParserRuleContext context, QualifiedSele public override void Fix() { - var parameter = Context.GetText(); + string pattern = "^\\s*" + Tokens.ByVal + "(\\s+)"; + string rgxReplacement = Tokens.ByRef + "$1"; + Regex rgx = new Regex(pattern); - var parts = parameter.Split(new char[]{' '},2); - if (1 != parts.GetUpperBound(0)) - { - return; - } - parts[0] = parts[0].Replace(Tokens.ByVal, Tokens.ByRef); - var newContent = parts[0] + " " + parts[1]; + var parameter = Context.GetText(); + var newParameter = rgx.Replace(parameter, rgxReplacement); var selection = Selection.Selection; var module = Selection.QualifiedName.Component.CodeModule; { var lines = module.GetLines(selection.StartLine, selection.LineCount); - var result = lines.Replace(parameter, newContent); + var result = lines.Replace(parameter, newParameter); module.ReplaceLine(selection.StartLine, result); } } From eb6f24f92668a55ebbd8d545e5770ce25823c329 Mon Sep 17 00:00:00 2001 From: Brian Zenger Date: Mon, 13 Feb 2017 19:02:02 -0800 Subject: [PATCH 02/20] removed use of regular expression in PassParameterByReferenceQuickFix --- .../PassParameterByReferenceQuickFix.cs | 28 +++++++++++-------- .../AssignedByValParameterInspectionTests.cs | 20 +++++++++---- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs index 47ecb21171..29ddbcaac4 100644 --- a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs +++ b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs @@ -4,6 +4,7 @@ using Rubberduck.Parsing.Grammar; using Rubberduck.VBEditor; using System.Text.RegularExpressions; +using static Rubberduck.Parsing.Grammar.VBAParser; namespace Rubberduck.Inspections.QuickFixes { @@ -19,21 +20,26 @@ public PassParameterByReferenceQuickFix(ParserRuleContext context, QualifiedSele public override void Fix() { - string pattern = "^\\s*" + Tokens.ByVal + "(\\s+)"; - string rgxReplacement = Tokens.ByRef + "$1"; - Regex rgx = new Regex(pattern); + var byValParameter = Context.GetText(); - var parameter = Context.GetText(); - var newParameter = rgx.Replace(parameter, rgxReplacement); + var byRefParameter = BuildByRefParameter(byValParameter); - var selection = Selection.Selection; + ReplaceByValParameterInModule(byValParameter, byRefParameter); + } + private string BuildByRefParameter(string originalParameter) + { + var everythingAfterTheByValToken = originalParameter.Substring(Tokens.ByVal.Length); + return Tokens.ByRef + everythingAfterTheByValToken; + } + private void ReplaceByValParameterInModule( string byValParameter, string byRefParameter) + { + var selection = Selection.Selection; var module = Selection.QualifiedName.Component.CodeModule; - { - var lines = module.GetLines(selection.StartLine, selection.LineCount); - var result = lines.Replace(parameter, newParameter); - module.ReplaceLine(selection.StartLine, result); - } + + var lines = module.GetLines(selection.StartLine, selection.LineCount); + var result = lines.Replace(byValParameter, byRefParameter); + module.ReplaceLine(selection.StartLine, result); } } } \ No newline at end of file diff --git a/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs b/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs index 75a2d680ad..09efd783c9 100644 --- a/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs +++ b/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs @@ -97,17 +97,30 @@ Dim var1 As Integer [TestCategory("Inspections")] public void AssignedByValParameter_QuickFixWorks() { - const string inputCode = + string inputCode = @"Public Sub Foo(ByVal barByVal As String) Let barByVal = ""test"" End Sub"; - const string expectedCode = + string expectedCode = @"Public Sub Foo(ByRef barByVal As String) Let barByVal = ""test"" End Sub"; var quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode); Assert.AreEqual(expectedCode, quickFixResult); + + //check when ByVal argument is one of several parameters + inputCode = +@"Public Sub Foo(ByRef firstArg As Long, ByVal barByVal As String, secondArg as Double) + Let barByVal = ""test"" +End Sub"; + expectedCode = +@"Public Sub Foo(ByRef firstArg As Long, ByRef barByVal As String, secondArg as Double) + Let barByVal = ""test"" +End Sub"; + + quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode); + Assert.AreEqual(expectedCode, quickFixResult); } [TestMethod] @@ -507,9 +520,6 @@ private Mock BuildMockVBEStandardModuleForVBAFragment(string inputCode) { var builder = new MockVbeBuilder(); IVBComponent component; - //TODO: removal of the two lines below have no effect on the outcome of any test...remove? - //var mockHost = new Mock(); - //mockHost.SetupAllProperties(); return builder.BuildFromSingleStandardModule(inputCode, out component); } From 47aeed707815dec5de946cd5e8c77a5fa07b4db7 Mon Sep 17 00:00:00 2001 From: Brian Zenger Date: Tue, 14 Feb 2017 11:10:17 -0800 Subject: [PATCH 03/20] Strenghtened the ByRef QuickFix to handle line continuations etc --- .../PassParameterByReferenceQuickFix.cs | 111 ++++++++++++++++-- .../AssignedByValParameterInspectionResult.cs | 2 +- .../AssignedByValParameterInspectionTests.cs | 90 +++++++++++++- 3 files changed, 186 insertions(+), 17 deletions(-) diff --git a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs index 29ddbcaac4..64b1545fe8 100644 --- a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs +++ b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs @@ -2,7 +2,10 @@ using Rubberduck.Inspections.Abstract; using Rubberduck.Inspections.Resources; using Rubberduck.Parsing.Grammar; +using Rubberduck.Parsing.Symbols; using Rubberduck.VBEditor; +using System; +using System.Collections.Generic; using System.Text.RegularExpressions; using static Rubberduck.Parsing.Grammar.VBAParser; @@ -13,33 +16,117 @@ namespace Rubberduck.Inspections.QuickFixes /// public class PassParameterByReferenceQuickFix : QuickFixBase { - public PassParameterByReferenceQuickFix(ParserRuleContext context, QualifiedSelection selection) - : base(context, selection, InspectionsUI.PassParameterByReferenceQuickFix) + private Declaration _target; + private int _byValTokenProcLine; + private int _byValIdentifierNameProcLine; + + public PassParameterByReferenceQuickFix(Declaration target, QualifiedSelection selection) + : base(target.Context, selection, InspectionsUI.PassParameterByReferenceQuickFix) { + _target = target; + _byValTokenProcLine = 0; + _byValIdentifierNameProcLine = 0; } public override void Fix() { - var byValParameter = Context.GetText(); + string byValTargetString; + string byValTokenReplacementString; + string replacementString; - var byRefParameter = BuildByRefParameter(byValParameter); + var procLines = RetrieveProcedureLines(); - ReplaceByValParameterInModule(byValParameter, byRefParameter); - } + SetMemberLineValues(procLines); + + string moduleLineWithByValToken = procLines[_byValTokenProcLine - 1]; + + if (_byValTokenProcLine == _byValIdentifierNameProcLine) + { + //The replacement is based on the (e.g. "ByVal identifierName") + byValTargetString = Tokens.ByVal + " " + _target.IdentifierName; + byValTokenReplacementString = BuildByRefParameter(byValTargetString); + replacementString = moduleLineWithByValToken.Replace(byValTargetString, byValTokenReplacementString); + } + else + { + //if the token and identifier are on different lines, then the target + //string consists of the ByVal token and the LineContinuation token. + //(e.g. the replacement is based on "ByVal _". Spaces between tokens can vary) + byValTargetString = GetUniqueTargetStringForByValAtEndOfLine(moduleLineWithByValToken); + byValTokenReplacementString = BuildByRefParameter(byValTargetString); + + //avoid updating possible cases of ByVal followed by underscore-prefixed identifiers + var index = moduleLineWithByValToken.LastIndexOf(byValTargetString); + var firstPart = moduleLineWithByValToken.Substring(0, index); + replacementString = firstPart + byValTokenReplacementString; + } + var module = Selection.QualifiedName.Component.CodeModule; + module.ReplaceLine(RetrieveTheProcedureStartLine() + _byValTokenProcLine-1, replacementString); + } + private string[] RetrieveProcedureLines() + { + var moduleContent = Context.Start.InputStream.ToString(); + string[] newLine = { "\r\n" }; + var moduleLines = moduleContent.Split(newLine, StringSplitOptions.None); + var procLines = new List(); + var startIndex = RetrieveTheProcedureStartLine(); + var endIndex = RetrieveTheProcedureEndLine(); + for ( int idx = startIndex - 1; idx < endIndex; idx++) + { + procLines.Add(moduleLines[idx]); + } + return procLines.ToArray(); + } + private int RetrieveTheProcedureStartLine() + { + var parserRuleCtxt = (ParserRuleContext)Context.Parent.Parent; + return parserRuleCtxt.Start.Line; + } + private int RetrieveTheProcedureEndLine() + { + var parserRuleCtxt = (ParserRuleContext)Context.Parent.Parent; + return parserRuleCtxt.Stop.Line; + } private string BuildByRefParameter(string originalParameter) { var everythingAfterTheByValToken = originalParameter.Substring(Tokens.ByVal.Length); return Tokens.ByRef + everythingAfterTheByValToken; } - private void ReplaceByValParameterInModule( string byValParameter, string byRefParameter) + private string GetUniqueTargetStringForByValAtEndOfLine(string procLineWithByValToken) { - var selection = Selection.Selection; - var module = Selection.QualifiedName.Component.CodeModule; + System.Diagnostics.Debug.Assert(procLineWithByValToken.Contains(Tokens.LineContinuation)); + + var positionOfLineContinuation = procLineWithByValToken.LastIndexOf(Tokens.LineContinuation); + var positionOfLastByValToken = procLineWithByValToken.LastIndexOf(Tokens.ByVal); + return procLineWithByValToken.Substring(positionOfLastByValToken, positionOfLineContinuation - positionOfLastByValToken + 2); + } + private void SetMemberLineValues(string[] procedureLines) + { + string line; + bool byValTokenFound = false; + bool byValIdentifierNameFound = false; + for (int zbIndexByValLine = 0; !byValIdentifierNameFound && zbIndexByValLine < procedureLines.Length; zbIndexByValLine++) + { + line = procedureLines[zbIndexByValLine]; + if (line.Contains(Tokens.ByVal)) + { + _byValTokenProcLine = zbIndexByValLine + 1; + byValTokenFound = true; + } + if (byValTokenFound) + { + if (line.Contains(_target.IdentifierName)) + { + _byValIdentifierNameProcLine = zbIndexByValLine + 1; + byValIdentifierNameFound = true; + } + } + } - var lines = module.GetLines(selection.StartLine, selection.LineCount); - var result = lines.Replace(byValParameter, byRefParameter); - module.ReplaceLine(selection.StartLine, result); + System.Diagnostics.Debug.Assert(_byValTokenProcLine > 0); + System.Diagnostics.Debug.Assert(_byValIdentifierNameProcLine > 0); + return; } } } \ No newline at end of file diff --git a/RetailCoder.VBE/Inspections/Results/AssignedByValParameterInspectionResult.cs b/RetailCoder.VBE/Inspections/Results/AssignedByValParameterInspectionResult.cs index d622bec60e..0010f208b6 100644 --- a/RetailCoder.VBE/Inspections/Results/AssignedByValParameterInspectionResult.cs +++ b/RetailCoder.VBE/Inspections/Results/AssignedByValParameterInspectionResult.cs @@ -28,7 +28,7 @@ public override IEnumerable QuickFixes return _quickFixes ?? (_quickFixes = new QuickFixBase[] { new AssignedByValParameterQuickFix(Target, QualifiedSelection), - new PassParameterByReferenceQuickFix(Target.Context, QualifiedSelection), + new PassParameterByReferenceQuickFix(Target, QualifiedSelection), new IgnoreOnceQuickFix(Context, QualifiedSelection, Inspection.AnnotationName) }); } diff --git a/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs b/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs index 09efd783c9..d681ad8a37 100644 --- a/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs +++ b/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs @@ -97,12 +97,13 @@ Dim var1 As Integer [TestCategory("Inspections")] public void AssignedByValParameter_QuickFixWorks() { + string inputCode = -@"Public Sub Foo(ByVal barByVal As String) +@"Public Sub Foo(Optional ByVal barByVal As String = ""XYZ"") Let barByVal = ""test"" End Sub"; string expectedCode = -@"Public Sub Foo(ByRef barByVal As String) +@"Public Sub Foo(Optional ByRef barByVal As String = ""XYZ"") Let barByVal = ""test"" End Sub"; @@ -111,18 +112,99 @@ public void AssignedByValParameter_QuickFixWorks() //check when ByVal argument is one of several parameters inputCode = -@"Public Sub Foo(ByRef firstArg As Long, ByVal barByVal As String, secondArg as Double) +@"Public Sub Foo(ByRef firstArg As Long, Optional ByVal barByVal As String = """", secondArg as Double) Let barByVal = ""test"" End Sub"; expectedCode = -@"Public Sub Foo(ByRef firstArg As Long, ByRef barByVal As String, secondArg as Double) +@"Public Sub Foo(ByRef firstArg As Long, Optional ByRef barByVal As String = """", secondArg as Double) Let barByVal = ""test"" End Sub"; quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode); Assert.AreEqual(expectedCode, quickFixResult); + + inputCode = +@" +Private Sub Foo(Optional ByVal _ + bar _ + As _ + Long = 4, _ + ByVal _ + barTwo _ + As _ + Long) +bar = 42 +End Sub +" +; + expectedCode = +@" +Private Sub Foo(Optional ByRef _ + bar _ + As _ + Long = 4, _ + ByVal _ + barTwo _ + As _ + Long) +bar = 42 +End Sub +" +; + quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode); + Assert.AreEqual(expectedCode, quickFixResult); + + inputCode = +@"Private Sub Foo(ByVal barByVal As Long, ByVal _xByValbar As Long, ByVal _ + barTwo _ + As _ + Long) +barTwo = 42 +End Sub +"; + expectedCode = +@"Private Sub Foo(ByVal barByVal As Long, ByVal _xByValbar As Long, ByRef _ + barTwo _ + As _ + Long) +barTwo = 42 +End Sub +"; + + quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode); + Assert.AreEqual(expectedCode, quickFixResult); + + inputCode = +@"Sub DoSomething(_ + ByVal foo As Long, _ + ByRef _ + bar, _ + ByRef barbecue _ + ) + foo = 4 + bar = barbecue * _ + bar + foo / barbecue +End Sub +"; + + expectedCode = +@"Sub DoSomething(_ + ByRef foo As Long, _ + ByRef _ + bar, _ + ByRef barbecue _ + ) + foo = 4 + bar = barbecue * _ + bar + foo / barbecue +End Sub +"; + quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode); + Assert.AreEqual(expectedCode, quickFixResult); + } + [TestMethod] [TestCategory("Inspections")] public void AssignedByValParameter_IgnoreQuickFixWorks() From a3eb57525dd79bad874592a53abb2268efa20866 Mon Sep 17 00:00:00 2001 From: Brian Zenger Date: Tue, 14 Feb 2017 21:45:36 -0800 Subject: [PATCH 04/20] updated with changes that came from last sync --- .../Inspections/Concrete/Inspector.cs | 2 +- ...ocedureCanBeWrittenAsFunctionInspection.cs | 10 +- .../DeclareAsExplicitVariantQuickFix.cs | 4 +- .../PassParameterByReferenceQuickFix.cs | 94 ++++++++++++++++++- ...eCanBeWrittenAsFunctionInspectionResult.cs | 8 +- .../CodeExplorerMemberViewModel.cs | 2 +- .../IntroduceParameterRefactoring.cs | 6 +- .../RemoveParametersRefactoring.cs | 10 +- .../Refactorings/Rename/RenameRefactoring.cs | 2 +- .../ReorderParametersModel.cs | 2 +- .../ReorderParametersRefactoring.cs | 10 +- Rubberduck.Parsing/Grammar/VBAParser.cs | 36 +++---- .../Grammar/VBAParserBaseListener.cs | 4 +- .../Grammar/VBAParserBaseVisitor.cs | 2 +- .../Grammar/VBAParserListener.cs | 4 +- .../Grammar/VBAParserVisitor.cs | 2 +- .../Symbols/DeclarationSymbolsListener.cs | 2 +- .../AssignedByValParameterInspectionTests.cs | 38 ++++++++ 18 files changed, 184 insertions(+), 54 deletions(-) diff --git a/RetailCoder.VBE/Inspections/Concrete/Inspector.cs b/RetailCoder.VBE/Inspections/Concrete/Inspector.cs index 12cee20902..f5f3fe3934 100644 --- a/RetailCoder.VBE/Inspections/Concrete/Inspector.cs +++ b/RetailCoder.VBE/Inspections/Concrete/Inspector.cs @@ -139,7 +139,7 @@ before moving them into the ParseTreeResults after qualifying them if (argListWithOneByRefParamListener != null) { - result.AddRange(argListWithOneByRefParamListener.Contexts.Select(context => new QualifiedContext(componentTreePair.Key, context))); + result.AddRange(argListWithOneByRefParamListener.Contexts.Select(context => new QualifiedContext(componentTreePair.Key, context))); } if (emptyStringLiteralListener != null) { diff --git a/RetailCoder.VBE/Inspections/ProcedureCanBeWrittenAsFunctionInspection.cs b/RetailCoder.VBE/Inspections/ProcedureCanBeWrittenAsFunctionInspection.cs index fdd6b77ee1..295683bd47 100644 --- a/RetailCoder.VBE/Inspections/ProcedureCanBeWrittenAsFunctionInspection.cs +++ b/RetailCoder.VBE/Inspections/ProcedureCanBeWrittenAsFunctionInspection.cs @@ -27,7 +27,7 @@ public ProcedureCanBeWrittenAsFunctionInspection(RubberduckParserState state) public override string Description { get { return InspectionsUI.ProcedureCanBeWrittenAsFunctionInspectionName; } } public override CodeInspectionType InspectionType { get { return CodeInspectionType.LanguageOpportunities; } } - public IEnumerable> ParseTreeResults { get { return _results.OfType>(); } } + public IEnumerable> ParseTreeResults { get { return _results.OfType>(); } } public void SetResults(IEnumerable results) { @@ -62,17 +62,17 @@ public override IEnumerable GetInspectionResults() .Select(result => new ProcedureCanBeWrittenAsFunctionInspectionResult( this, State, - new QualifiedContext(result.QualifiedName,result.Context.GetChild(0)), + new QualifiedContext(result.QualifiedName,result.Context.GetChild(0)), new QualifiedContext(result.QualifiedName, (VBAParser.SubStmtContext)result.Context)) ); } public class SingleByRefParamArgListListener : VBAParserBaseListener { - private readonly IList _contexts = new List(); - public IEnumerable Contexts { get { return _contexts; } } + private readonly IList _contexts = new List(); + public IEnumerable Contexts { get { return _contexts; } } - public override void ExitArgList(VBAParser.ArgListContext context) + public override void ExitArgList(VBAParser.SubstmtContext context) { var args = context.arg(); if (args != null && args.All(a => a.PARAMARRAY() == null && a.LPAREN() == null) && args.Count(a => a.BYREF() != null || (a.BYREF() == null && a.BYVAL() == null)) == 1) diff --git a/RetailCoder.VBE/Inspections/QuickFixes/DeclareAsExplicitVariantQuickFix.cs b/RetailCoder.VBE/Inspections/QuickFixes/DeclareAsExplicitVariantQuickFix.cs index eeb40f057c..4402e41d9e 100644 --- a/RetailCoder.VBE/Inspections/QuickFixes/DeclareAsExplicitVariantQuickFix.cs +++ b/RetailCoder.VBE/Inspections/QuickFixes/DeclareAsExplicitVariantQuickFix.cs @@ -68,9 +68,9 @@ private string DeclareExplicitVariant(VBAParser.ArgContext context, out string i var fix = string.Empty; foreach (var child in memberContext.children) { - if (child is VBAParser.ArgListContext) + if (child is VBAParser.SubstmtContext) { - foreach (var tree in ((VBAParser.ArgListContext) child).children) + foreach (var tree in ((VBAParser.SubstmtContext) child).children) { if (tree.Equals(context)) { diff --git a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs index 64b1545fe8..1dba573db6 100644 --- a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs +++ b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs @@ -99,10 +99,11 @@ private string GetUniqueTargetStringForByValAtEndOfLine(string procLineWithByVal var positionOfLineContinuation = procLineWithByValToken.LastIndexOf(Tokens.LineContinuation); var positionOfLastByValToken = procLineWithByValToken.LastIndexOf(Tokens.ByVal); - return procLineWithByValToken.Substring(positionOfLastByValToken, positionOfLineContinuation - positionOfLastByValToken + 2); + return procLineWithByValToken.Substring(positionOfLastByValToken, positionOfLineContinuation - positionOfLastByValToken + Tokens.LineContinuation.Length); } private void SetMemberLineValues(string[] procedureLines) { + string line; bool byValTokenFound = false; bool byValIdentifierNameFound = false; @@ -116,11 +117,19 @@ private void SetMemberLineValues(string[] procedureLines) } if (byValTokenFound) { + int lineNum = GetIdentifierLineNumber(_target.IdentifierName); + if(lineNum > 0) + { + _byValIdentifierNameProcLine = lineNum; + byValIdentifierNameFound = true; + } + /* if (line.Contains(_target.IdentifierName)) { _byValIdentifierNameProcLine = zbIndexByValLine + 1; byValIdentifierNameFound = true; } + */ } } @@ -128,5 +137,88 @@ private void SetMemberLineValues(string[] procedureLines) System.Diagnostics.Debug.Assert(_byValIdentifierNameProcLine > 0); return; } + private int GetIdentifierLineNumber(string identifier) + { + var names = new List(); + var test = (SubStmtContext)Context.Parent.Parent; + var next = test.children; + for (int idx = 0; idx < next.Count; idx++) + { + if (next[idx] is SubstmtContext) + { + var child = (SubstmtContext)next[idx]; + var arg = child.children; + for (int idx2 = 0; idx2 < arg.Count; idx2++) + { + if (arg[idx2] is ArgContext) + { + var asdf = (ArgContext)arg[idx2]; + var kids = asdf.children; + for (int idx3 = 0; idx3 < kids.Count; idx3++) + { + var _start = (ParserRuleContext)kids[0]; + var _stop = (ParserRuleContext)kids[kids.Count-1]; + int startCol = _start.Start.Column; + int stopCol = _start.Stop.Column; + + if (kids[idx3] is UnrestrictedIdentifierContext) + { + var idRef = (UnrestrictedIdentifierContext)kids[idx3]; + var name = idRef.Start.Text; + if (name.Equals(identifier)) + { + int lineNum = idRef.Start.Line; + return lineNum; + } + } + } + } + } + } + } + return -1; + } + private int GetIdentifierStartIndex(string identifier, out int stopIndex) + { + var names = new List(); + var test = (SubStmtContext)Context.Parent.Parent; + var next = test.children; + for (int idx = 0; idx < next.Count; idx++) + { + if (next[idx] is SubstmtContext) + { + var child = (SubstmtContext)next[idx]; + var arg = child.children; + for (int idx2 = 0; idx2 < arg.Count; idx2++) + { + if (arg[idx2] is ArgContext) + { + var asdf = (ArgContext)arg[idx2]; + var kids = asdf.children; + for (int idx3 = 0; idx3 < kids.Count; idx3++) + { + var _start = (ParserRuleContext)kids[0]; + var _stop = (ParserRuleContext)kids[kids.Count - 1]; + stopIndex = _start.Stop.Column; + return _start.Start.Column; + + if (kids[idx3] is UnrestrictedIdentifierContext) + { + var idRef = (UnrestrictedIdentifierContext)kids[idx3]; + var name = idRef.Start.Text; + if (name.Equals(identifier)) + { + int lineNum = idRef.Start.Line; + return lineNum; + } + } + } + } + } + } + } + stopIndex = -1; + return -1; + } } } \ No newline at end of file diff --git a/RetailCoder.VBE/Inspections/Results/ProcedureCanBeWrittenAsFunctionInspectionResult.cs b/RetailCoder.VBE/Inspections/Results/ProcedureCanBeWrittenAsFunctionInspectionResult.cs index db9e599391..23b2512f60 100644 --- a/RetailCoder.VBE/Inspections/Results/ProcedureCanBeWrittenAsFunctionInspectionResult.cs +++ b/RetailCoder.VBE/Inspections/Results/ProcedureCanBeWrittenAsFunctionInspectionResult.cs @@ -16,12 +16,12 @@ namespace Rubberduck.Inspections.Results public class ProcedureCanBeWrittenAsFunctionInspectionResult : InspectionResultBase { private IEnumerable _quickFixes; - private readonly QualifiedContext _argListQualifiedContext; + private readonly QualifiedContext _argListQualifiedContext; private readonly QualifiedContext _subStmtQualifiedContext; private readonly RubberduckParserState _state; public ProcedureCanBeWrittenAsFunctionInspectionResult(IInspection inspection, RubberduckParserState state, - QualifiedContext argListQualifiedContext, QualifiedContext subStmtQualifiedContext) + QualifiedContext argListQualifiedContext, QualifiedContext subStmtQualifiedContext) : base(inspection, subStmtQualifiedContext.ModuleName, subStmtQualifiedContext.Context.subroutineName()) { _target = state.AllUserDeclarations.Single(declaration => declaration.DeclarationType == DeclarationType.Procedure @@ -57,14 +57,14 @@ public class ChangeProcedureToFunction : QuickFixBase public override bool CanFixInProject { get { return false; } } private readonly RubberduckParserState _state; - private readonly QualifiedContext _argListQualifiedContext; + private readonly QualifiedContext _argListQualifiedContext; private readonly QualifiedContext _subStmtQualifiedContext; private readonly QualifiedContext _argQualifiedContext; private int _lineOffset; public ChangeProcedureToFunction(RubberduckParserState state, - QualifiedContext argListQualifiedContext, + QualifiedContext argListQualifiedContext, QualifiedContext subStmtQualifiedContext, QualifiedSelection selection) : base(subStmtQualifiedContext.Context, selection, InspectionsUI.ProcedureShouldBeFunctionInspectionQuickFix) diff --git a/RetailCoder.VBE/Navigation/CodeExplorer/CodeExplorerMemberViewModel.cs b/RetailCoder.VBE/Navigation/CodeExplorer/CodeExplorerMemberViewModel.cs index 938cfb602c..d6416a2b1d 100644 --- a/RetailCoder.VBE/Navigation/CodeExplorer/CodeExplorerMemberViewModel.cs +++ b/RetailCoder.VBE/Navigation/CodeExplorer/CodeExplorerMemberViewModel.cs @@ -117,7 +117,7 @@ public override string NameWithSignature } var context = - _declaration.Context.children.FirstOrDefault(d => d is VBAParser.ArgListContext) as VBAParser.ArgListContext; + _declaration.Context.children.FirstOrDefault(d => d is VBAParser.SubstmtContext) as VBAParser.SubstmtContext; if (context == null) { diff --git a/RetailCoder.VBE/Refactorings/IntroduceParameter/IntroduceParameterRefactoring.cs b/RetailCoder.VBE/Refactorings/IntroduceParameter/IntroduceParameterRefactoring.cs index 914c9eaa46..10db282c5b 100644 --- a/RetailCoder.VBE/Refactorings/IntroduceParameter/IntroduceParameterRefactoring.cs +++ b/RetailCoder.VBE/Refactorings/IntroduceParameter/IntroduceParameterRefactoring.cs @@ -142,7 +142,7 @@ private void UpdateSignature(Declaration targetVariable) var functionDeclaration = _declarations.FindTarget(targetVariable.QualifiedSelection, ValidDeclarationTypes); var proc = (dynamic)functionDeclaration.Context; - var paramList = (VBAParser.ArgListContext)proc.argList(); + var paramList = (VBAParser.SubstmtContext)proc.argList(); var module = functionDeclaration.QualifiedName.QualifiedModuleName.Component.CodeModule; { var interfaceImplementation = GetInterfaceImplementation(functionDeclaration); @@ -182,14 +182,14 @@ private void UpdateSignature(Declaration targetVariable) private void UpdateSignature(Declaration targetMethod, Declaration targetVariable) { var proc = (dynamic)targetMethod.Context; - var paramList = (VBAParser.ArgListContext)proc.argList(); + var paramList = (VBAParser.SubstmtContext)proc.argList(); var module = targetMethod.QualifiedName.QualifiedModuleName.Component.CodeModule; { AddParameter(targetMethod, targetVariable, paramList, module); } } - private void AddParameter(Declaration targetMethod, Declaration targetVariable, VBAParser.ArgListContext paramList, ICodeModule module) + private void AddParameter(Declaration targetMethod, Declaration targetVariable, VBAParser.SubstmtContext paramList, ICodeModule module) { var argList = paramList.arg(); var lastParam = argList.LastOrDefault(); diff --git a/RetailCoder.VBE/Refactorings/RemoveParameters/RemoveParametersRefactoring.cs b/RetailCoder.VBE/Refactorings/RemoveParameters/RemoveParametersRefactoring.cs index 6d9893ceea..9aa6eb2ed7 100644 --- a/RetailCoder.VBE/Refactorings/RemoveParameters/RemoveParametersRefactoring.cs +++ b/RetailCoder.VBE/Refactorings/RemoveParameters/RemoveParametersRefactoring.cs @@ -279,7 +279,7 @@ private string GetOldSignature(Declaration target) private void AdjustSignatures() { var proc = (dynamic)_model.TargetDeclaration.Context; - var paramList = (VBAParser.ArgListContext)proc.argList(); + var paramList = (VBAParser.SubstmtContext)proc.argList(); var module = _model.TargetDeclaration.QualifiedName.QualifiedModuleName.Component.CodeModule; { // if we are adjusting a property getter, check if we need to adjust the letter/setter too @@ -336,23 +336,23 @@ private void AdjustSignatures(Declaration declaration) var proc = (dynamic)declaration.Context.Parent; var module = declaration.QualifiedName.QualifiedModuleName.Component.CodeModule; { - VBAParser.ArgListContext paramList; + VBAParser.SubstmtContext paramList; if (declaration.DeclarationType == DeclarationType.PropertySet || declaration.DeclarationType == DeclarationType.PropertyLet) { - paramList = (VBAParser.ArgListContext)proc.children[0].argList(); + paramList = (VBAParser.SubstmtContext)proc.children[0].argList(); } else { - paramList = (VBAParser.ArgListContext)proc.subStmt().argList(); + paramList = (VBAParser.SubstmtContext)proc.subStmt().argList(); } RemoveSignatureParameters(declaration, paramList, module); } } - private void RemoveSignatureParameters(Declaration target, VBAParser.ArgListContext paramList, ICodeModule module) + private void RemoveSignatureParameters(Declaration target, VBAParser.SubstmtContext paramList, ICodeModule module) { // property set/let have one more parameter than is listed in the getter parameters var nonRemovedParamNames = paramList.arg().Where((a, s) => s >= _model.Parameters.Count || !_model.Parameters[s].IsRemoved).Select(s => s.GetText()); diff --git a/RetailCoder.VBE/Refactorings/Rename/RenameRefactoring.cs b/RetailCoder.VBE/Refactorings/Rename/RenameRefactoring.cs index a7c5d94d12..d591fb9650 100644 --- a/RetailCoder.VBE/Refactorings/Rename/RenameRefactoring.cs +++ b/RetailCoder.VBE/Refactorings/Rename/RenameRefactoring.cs @@ -335,7 +335,7 @@ private void RenameDeclaration(Declaration target, string newName) if (target.DeclarationType == DeclarationType.Parameter) { - var argList = (VBAParser.ArgListContext)target.Context.Parent; + var argList = (VBAParser.SubstmtContext)target.Context.Parent; var lineNum = argList.GetSelection().LineCount; // delete excess lines to prevent removing our own changes diff --git a/RetailCoder.VBE/Refactorings/ReorderParameters/ReorderParametersModel.cs b/RetailCoder.VBE/Refactorings/ReorderParameters/ReorderParametersModel.cs index 316761ead8..5b0b565dff 100644 --- a/RetailCoder.VBE/Refactorings/ReorderParameters/ReorderParametersModel.cs +++ b/RetailCoder.VBE/Refactorings/ReorderParameters/ReorderParametersModel.cs @@ -50,7 +50,7 @@ private void LoadParameters() Parameters.Clear(); var procedure = (dynamic)TargetDeclaration.Context; - var argList = (VBAParser.ArgListContext)procedure.argList(); + var argList = (VBAParser.SubstmtContext)procedure.argList(); var args = argList.arg(); var index = 0; diff --git a/RetailCoder.VBE/Refactorings/ReorderParameters/ReorderParametersRefactoring.cs b/RetailCoder.VBE/Refactorings/ReorderParameters/ReorderParametersRefactoring.cs index 4b5a678729..7d8b289e10 100644 --- a/RetailCoder.VBE/Refactorings/ReorderParameters/ReorderParametersRefactoring.cs +++ b/RetailCoder.VBE/Refactorings/ReorderParameters/ReorderParametersRefactoring.cs @@ -189,7 +189,7 @@ private void RewriteCall(VBAParser.ArgumentListContext paramList, ICodeModule mo private void AdjustSignatures() { var proc = (dynamic)_model.TargetDeclaration.Context; - var paramList = (VBAParser.ArgListContext)proc.argList(); + var paramList = (VBAParser.SubstmtContext)proc.argList(); var module = _model.TargetDeclaration.QualifiedName.QualifiedModuleName.Component.CodeModule; // if we are reordering a property getter, check if we need to reorder a letter/setter too @@ -242,22 +242,22 @@ private void AdjustSignatures(Declaration declaration) var proc = (dynamic)declaration.Context.Parent; var module = declaration.QualifiedName.QualifiedModuleName.Component.CodeModule; { - VBAParser.ArgListContext paramList; + VBAParser.SubstmtContext paramList; if (declaration.DeclarationType == DeclarationType.PropertySet || declaration.DeclarationType == DeclarationType.PropertyLet) { - paramList = (VBAParser.ArgListContext)proc.children[0].argList(); + paramList = (VBAParser.SubstmtContext)proc.children[0].argList(); } else { - paramList = (VBAParser.ArgListContext)proc.subStmt().argList(); + paramList = (VBAParser.SubstmtContext)proc.subStmt().argList(); } RewriteSignature(declaration, paramList, module); } } - private void RewriteSignature(Declaration target, VBAParser.ArgListContext paramList, ICodeModule module) + private void RewriteSignature(Declaration target, VBAParser.SubstmtContext paramList, ICodeModule module) { var parameters = paramList.arg().Select((s, i) => new {Index = i, Text = s.GetText()}).ToList(); diff --git a/Rubberduck.Parsing/Grammar/VBAParser.cs b/Rubberduck.Parsing/Grammar/VBAParser.cs index 878c761d51..a1d0eaa1cf 100644 --- a/Rubberduck.Parsing/Grammar/VBAParser.cs +++ b/Rubberduck.Parsing/Grammar/VBAParser.cs @@ -5512,8 +5512,8 @@ public IdentifierContext identifier() { public VisibilityContext visibility() { return GetRuleContext(0); } - public ArgListContext argList() { - return GetRuleContext(0); + public SubstmtContext argList() { + return GetRuleContext(0); } public ITerminalNode LIB() { return GetToken(VBAParser.LIB, 0); } public ITerminalNode FUNCTION() { return GetToken(VBAParser.FUNCTION, 0); } @@ -5631,7 +5631,7 @@ public DeclareStmtContext declareStmt() { return _localctx; } - public partial class ArgListContext : ParserRuleContext { + public partial class SubstmtContext : ParserRuleContext { public ArgContext arg(int i) { return GetRuleContext(i); } @@ -5650,7 +5650,7 @@ public IReadOnlyList arg() { public ITerminalNode COMMA(int i) { return GetToken(VBAParser.COMMA, i); } - public ArgListContext(ParserRuleContext parent, int invokingState) + public SubstmtContext(ParserRuleContext parent, int invokingState) : base(parent, invokingState) { } @@ -5671,8 +5671,8 @@ public override TResult Accept(IParseTreeVisitor visitor) { } [RuleVersion(0)] - public ArgListContext argList() { - ArgListContext _localctx = new ArgListContext(_ctx, State); + public SubstmtContext argList() { + SubstmtContext _localctx = new SubstmtContext(_ctx, State); EnterRule(_localctx, 140, RULE_argList); int _la; try { @@ -7041,8 +7041,8 @@ public ErrorStmtContext errorStmt() { } public partial class EventStmtContext : ParserRuleContext { - public ArgListContext argList() { - return GetRuleContext(0); + public SubstmtContext argList() { + return GetRuleContext(0); } public WhiteSpaceContext whiteSpace(int i) { return GetRuleContext(i); @@ -7350,8 +7350,8 @@ public ForNextStmtContext forNextStmt() { } public partial class FunctionStmtContext : ParserRuleContext { - public ArgListContext argList() { - return GetRuleContext(0); + public SubstmtContext argList() { + return GetRuleContext(0); } public FunctionNameContext functionName() { return GetRuleContext(0); @@ -8930,8 +8930,8 @@ public OnGoSubStmtContext onGoSubStmt() { } public partial class PropertyGetStmtContext : ParserRuleContext { - public ArgListContext argList() { - return GetRuleContext(0); + public SubstmtContext argList() { + return GetRuleContext(0); } public FunctionNameContext functionName() { return GetRuleContext(0); @@ -9048,8 +9048,8 @@ public PropertyGetStmtContext propertyGetStmt() { } public partial class PropertySetStmtContext : ParserRuleContext { - public ArgListContext argList() { - return GetRuleContext(0); + public SubstmtContext argList() { + return GetRuleContext(0); } public WhiteSpaceContext whiteSpace(int i) { return GetRuleContext(i); @@ -9155,8 +9155,8 @@ public PropertySetStmtContext propertySetStmt() { public partial class PropertyLetStmtContext : ParserRuleContext { public ITerminalNode PROPERTY_LET() { return GetToken(VBAParser.PROPERTY_LET, 0); } - public ArgListContext argList() { - return GetRuleContext(0); + public SubstmtContext argList() { + return GetRuleContext(0); } public WhiteSpaceContext whiteSpace(int i) { return GetRuleContext(i); @@ -10999,8 +10999,8 @@ public SetStmtContext setStmt() { } public partial class SubStmtContext : ParserRuleContext { - public ArgListContext argList() { - return GetRuleContext(0); + public SubstmtContext argList() { + return GetRuleContext(0); } public WhiteSpaceContext whiteSpace(int i) { return GetRuleContext(i); diff --git a/Rubberduck.Parsing/Grammar/VBAParserBaseListener.cs b/Rubberduck.Parsing/Grammar/VBAParserBaseListener.cs index 7c144d4a73..45a6c12a26 100644 --- a/Rubberduck.Parsing/Grammar/VBAParserBaseListener.cs +++ b/Rubberduck.Parsing/Grammar/VBAParserBaseListener.cs @@ -2534,13 +2534,13 @@ public virtual void ExitSelectStartValue([NotNull] VBAParser.SelectStartValueCon /// The default implementation does nothing. /// /// The parse tree. - public virtual void EnterArgList([NotNull] VBAParser.ArgListContext context) { } + public virtual void EnterArgList([NotNull] VBAParser.SubstmtContext context) { } /// /// Exit a parse tree produced by . /// The default implementation does nothing. /// /// The parse tree. - public virtual void ExitArgList([NotNull] VBAParser.ArgListContext context) { } + public virtual void ExitArgList([NotNull] VBAParser.SubstmtContext context) { } /// /// Enter a parse tree produced by . diff --git a/Rubberduck.Parsing/Grammar/VBAParserBaseVisitor.cs b/Rubberduck.Parsing/Grammar/VBAParserBaseVisitor.cs index edf86d5823..4a87ec6339 100644 --- a/Rubberduck.Parsing/Grammar/VBAParserBaseVisitor.cs +++ b/Rubberduck.Parsing/Grammar/VBAParserBaseVisitor.cs @@ -2153,7 +2153,7 @@ public partial class VBAParserBaseVisitor : AbstractParseTreeVisitor /// The parse tree. /// The visitor result. - public virtual Result VisitArgList([NotNull] VBAParser.ArgListContext context) { return VisitChildren(context); } + public virtual Result VisitArgList([NotNull] VBAParser.SubstmtContext context) { return VisitChildren(context); } /// /// Visit a parse tree produced by . diff --git a/Rubberduck.Parsing/Grammar/VBAParserListener.cs b/Rubberduck.Parsing/Grammar/VBAParserListener.cs index 9489714147..11dec62036 100644 --- a/Rubberduck.Parsing/Grammar/VBAParserListener.cs +++ b/Rubberduck.Parsing/Grammar/VBAParserListener.cs @@ -2207,12 +2207,12 @@ public interface IVBAParserListener : IParseTreeListener { /// Enter a parse tree produced by . /// /// The parse tree. - void EnterArgList([NotNull] VBAParser.ArgListContext context); + void EnterArgList([NotNull] VBAParser.SubstmtContext context); /// /// Exit a parse tree produced by . /// /// The parse tree. - void ExitArgList([NotNull] VBAParser.ArgListContext context); + void ExitArgList([NotNull] VBAParser.SubstmtContext context); /// /// Enter a parse tree produced by . diff --git a/Rubberduck.Parsing/Grammar/VBAParserVisitor.cs b/Rubberduck.Parsing/Grammar/VBAParserVisitor.cs index 0c2912e460..9a0af15145 100644 --- a/Rubberduck.Parsing/Grammar/VBAParserVisitor.cs +++ b/Rubberduck.Parsing/Grammar/VBAParserVisitor.cs @@ -1410,7 +1410,7 @@ public interface IVBAParserVisitor : IParseTreeVisitor { /// /// The parse tree. /// The visitor result. - Result VisitArgList([NotNull] VBAParser.ArgListContext context); + Result VisitArgList([NotNull] VBAParser.SubstmtContext context); /// /// Visit a parse tree produced by . diff --git a/Rubberduck.Parsing/Symbols/DeclarationSymbolsListener.cs b/Rubberduck.Parsing/Symbols/DeclarationSymbolsListener.cs index 0954055c2c..1980f82792 100644 --- a/Rubberduck.Parsing/Symbols/DeclarationSymbolsListener.cs +++ b/Rubberduck.Parsing/Symbols/DeclarationSymbolsListener.cs @@ -664,7 +664,7 @@ public override void ExitDeclareStmt(VBAParser.DeclareStmtContext context) SetCurrentScope(); } - public override void EnterArgList(VBAParser.ArgListContext context) + public override void EnterArgList(VBAParser.SubstmtContext context) { var args = context.arg(); foreach (var argContext in args) diff --git a/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs b/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs index d681ad8a37..b1019a32e6 100644 --- a/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs +++ b/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs @@ -174,6 +174,44 @@ End Sub quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode); Assert.AreEqual(expectedCode, quickFixResult); + inputCode = +@"Private Sub Foo(ByVal barByVal As Long, ByVal barTwoon As Long, ByVal _ + barTwo _ + As _ + Long) +barTwo = 42 +End Sub +"; + expectedCode = +@"Private Sub Foo(ByVal barByVal As Long, ByVal barTwoon As Long, ByRef _ + barTwo _ + As _ + Long) +barTwo = 42 +End Sub +"; + + quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode); + Assert.AreEqual(expectedCode, quickFixResult); + + inputCode = +@"Private Sub Foo(ByVal barByVal As Long, ByVal barTwoon As Long, ByVal barTwo _ + As _ + Long) +barTwo = 42 +End Sub +"; + expectedCode = +@"Private Sub Foo(ByVal barByVal As Long, ByVal barTwoon As Long, ByRef barTwo _ + As _ + Long) +barTwo = 42 +End Sub +"; + + quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode); + Assert.AreEqual(expectedCode, quickFixResult); + inputCode = @"Sub DoSomething(_ ByVal foo As Long, _ From d4a5235e67fa1b0c4127330d0346a8346d14c8e7 Mon Sep 17 00:00:00 2001 From: Brian Zenger Date: Wed, 15 Feb 2017 13:53:54 -0800 Subject: [PATCH 05/20] Reworked QuickFix to use ArgContext --- .../PassParameterByReferenceQuickFix.cs | 231 +++++------------- 1 file changed, 62 insertions(+), 169 deletions(-) diff --git a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs index 1dba573db6..ada7749208 100644 --- a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs +++ b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs @@ -1,12 +1,10 @@ using Antlr4.Runtime; +using Antlr4.Runtime.Tree; using Rubberduck.Inspections.Abstract; using Rubberduck.Inspections.Resources; using Rubberduck.Parsing.Grammar; using Rubberduck.Parsing.Symbols; using Rubberduck.VBEditor; -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; using static Rubberduck.Parsing.Grammar.VBAParser; namespace Rubberduck.Inspections.QuickFixes @@ -17,208 +15,103 @@ namespace Rubberduck.Inspections.QuickFixes public class PassParameterByReferenceQuickFix : QuickFixBase { private Declaration _target; - private int _byValTokenProcLine; - private int _byValIdentifierNameProcLine; public PassParameterByReferenceQuickFix(Declaration target, QualifiedSelection selection) : base(target.Context, selection, InspectionsUI.PassParameterByReferenceQuickFix) { _target = target; - _byValTokenProcLine = 0; - _byValIdentifierNameProcLine = 0; } public override void Fix() { - string byValTargetString; - string byValTokenReplacementString; - string replacementString; + var argCtxt = GetArgContextForIdentifier(Context, _target.IdentifierName); - var procLines = RetrieveProcedureLines(); + var terminalNodeImpl = GetByValNodeForArgCtx(argCtxt); - SetMemberLineValues(procLines); + var replacementLine = GenerateByRefReplacementLine(terminalNodeImpl); - string moduleLineWithByValToken = procLines[_byValTokenProcLine - 1]; + ReplaceModuleLine(terminalNodeImpl.Symbol.Line, replacementLine); - if (_byValTokenProcLine == _byValIdentifierNameProcLine) - { - //The replacement is based on the (e.g. "ByVal identifierName") - byValTargetString = Tokens.ByVal + " " + _target.IdentifierName; - byValTokenReplacementString = BuildByRefParameter(byValTargetString); - replacementString = moduleLineWithByValToken.Replace(byValTargetString, byValTokenReplacementString); - } - else - { - //if the token and identifier are on different lines, then the target - //string consists of the ByVal token and the LineContinuation token. - //(e.g. the replacement is based on "ByVal _". Spaces between tokens can vary) - byValTargetString = GetUniqueTargetStringForByValAtEndOfLine(moduleLineWithByValToken); - byValTokenReplacementString = BuildByRefParameter(byValTargetString); - - //avoid updating possible cases of ByVal followed by underscore-prefixed identifiers - var index = moduleLineWithByValToken.LastIndexOf(byValTargetString); - var firstPart = moduleLineWithByValToken.Substring(0, index); - replacementString = firstPart + byValTokenReplacementString; - } - - var module = Selection.QualifiedName.Component.CodeModule; - module.ReplaceLine(RetrieveTheProcedureStartLine() + _byValTokenProcLine-1, replacementString); } - private string[] RetrieveProcedureLines() + private ArgContext GetArgContextForIdentifier(ParserRuleContext context, string identifier) { - var moduleContent = Context.Start.InputStream.ToString(); - string[] newLine = { "\r\n" }; - var moduleLines = moduleContent.Split(newLine, StringSplitOptions.None); - var procLines = new List(); - var startIndex = RetrieveTheProcedureStartLine(); - var endIndex = RetrieveTheProcedureEndLine(); - for ( int idx = startIndex - 1; idx < endIndex; idx++) + var procStmtCtx = (ParserRuleContext)context.Parent.Parent; + var procStmtCtxChildren = procStmtCtx.children; + for (int idx = 0; idx < procStmtCtxChildren.Count; idx++) { - procLines.Add(moduleLines[idx]); + if (procStmtCtxChildren[idx] is SubstmtContext) + { + var procStmtCtxChild = (SubstmtContext)procStmtCtxChildren[idx]; + var arg = procStmtCtxChild.children; + for (int idx2 = 0; idx2 < arg.Count; idx2++) + { + if (arg[idx2] is ArgContext) + { + var name = GetIdentifierNameFor((ArgContext)arg[idx2]); + if (name.Equals(identifier)) + { + return (ArgContext)arg[idx2]; + } + } + } + } } - return procLines.ToArray(); - } - private int RetrieveTheProcedureStartLine() - { - var parserRuleCtxt = (ParserRuleContext)Context.Parent.Parent; - return parserRuleCtxt.Start.Line; - } - private int RetrieveTheProcedureEndLine() - { - var parserRuleCtxt = (ParserRuleContext)Context.Parent.Parent; - return parserRuleCtxt.Stop.Line; - } - private string BuildByRefParameter(string originalParameter) - { - var everythingAfterTheByValToken = originalParameter.Substring(Tokens.ByVal.Length); - return Tokens.ByRef + everythingAfterTheByValToken; + return null; } - private string GetUniqueTargetStringForByValAtEndOfLine(string procLineWithByValToken) + private string GetIdentifierNameFor(ArgContext argCtxt) { - System.Diagnostics.Debug.Assert(procLineWithByValToken.Contains(Tokens.LineContinuation)); - - var positionOfLineContinuation = procLineWithByValToken.LastIndexOf(Tokens.LineContinuation); - var positionOfLastByValToken = procLineWithByValToken.LastIndexOf(Tokens.ByVal); - return procLineWithByValToken.Substring(positionOfLastByValToken, positionOfLineContinuation - positionOfLastByValToken + Tokens.LineContinuation.Length); + var argCtxtChild = argCtxt.children; + var idRef = GetUnRestrictedIdentifierCtx(argCtxt); + return idRef.GetText(); } - private void SetMemberLineValues(string[] procedureLines) + private UnrestrictedIdentifierContext GetUnRestrictedIdentifierCtx(ArgContext argCtxt) { - - string line; - bool byValTokenFound = false; - bool byValIdentifierNameFound = false; - for (int zbIndexByValLine = 0; !byValIdentifierNameFound && zbIndexByValLine < procedureLines.Length; zbIndexByValLine++) + var argCtxtChild = argCtxt.children; + for (int idx = 0; idx < argCtxtChild.Count; idx++) { - line = procedureLines[zbIndexByValLine]; - if (line.Contains(Tokens.ByVal)) + if (argCtxtChild[idx] is UnrestrictedIdentifierContext) { - _byValTokenProcLine = zbIndexByValLine + 1; - byValTokenFound = true; - } - if (byValTokenFound) - { - int lineNum = GetIdentifierLineNumber(_target.IdentifierName); - if(lineNum > 0) - { - _byValIdentifierNameProcLine = lineNum; - byValIdentifierNameFound = true; - } - /* - if (line.Contains(_target.IdentifierName)) - { - _byValIdentifierNameProcLine = zbIndexByValLine + 1; - byValIdentifierNameFound = true; - } - */ + return (UnrestrictedIdentifierContext)argCtxtChild[idx]; } } - - System.Diagnostics.Debug.Assert(_byValTokenProcLine > 0); - System.Diagnostics.Debug.Assert(_byValIdentifierNameProcLine > 0); - return; + return null; } - private int GetIdentifierLineNumber(string identifier) + private TerminalNodeImpl GetByValNodeForArgCtx(ArgContext argCtxt) { - var names = new List(); - var test = (SubStmtContext)Context.Parent.Parent; - var next = test.children; - for (int idx = 0; idx < next.Count; idx++) + var argCtxtChild = argCtxt.children; + for (int idx = 0; idx < argCtxtChild.Count; idx++) { - if (next[idx] is SubstmtContext) + if (argCtxtChild[idx] is TerminalNodeImpl) { - var child = (SubstmtContext)next[idx]; - var arg = child.children; - for (int idx2 = 0; idx2 < arg.Count; idx2++) + var candidate = (TerminalNodeImpl)argCtxtChild[idx]; + if (candidate.Symbol.Text.Equals(Tokens.ByVal)) { - if (arg[idx2] is ArgContext) - { - var asdf = (ArgContext)arg[idx2]; - var kids = asdf.children; - for (int idx3 = 0; idx3 < kids.Count; idx3++) - { - var _start = (ParserRuleContext)kids[0]; - var _stop = (ParserRuleContext)kids[kids.Count-1]; - int startCol = _start.Start.Column; - int stopCol = _start.Stop.Column; - - if (kids[idx3] is UnrestrictedIdentifierContext) - { - var idRef = (UnrestrictedIdentifierContext)kids[idx3]; - var name = idRef.Start.Text; - if (name.Equals(identifier)) - { - int lineNum = idRef.Start.Line; - return lineNum; - } - } - } - } + return candidate; } } } - return -1; + return null; } - private int GetIdentifierStartIndex(string identifier, out int stopIndex) + private string GenerateByRefReplacementLine(TerminalNodeImpl terminalNodeImpl) { - var names = new List(); - var test = (SubStmtContext)Context.Parent.Parent; - var next = test.children; - for (int idx = 0; idx < next.Count; idx++) - { - if (next[idx] is SubstmtContext) - { - var child = (SubstmtContext)next[idx]; - var arg = child.children; - for (int idx2 = 0; idx2 < arg.Count; idx2++) - { - if (arg[idx2] is ArgContext) - { - var asdf = (ArgContext)arg[idx2]; - var kids = asdf.children; - for (int idx3 = 0; idx3 < kids.Count; idx3++) - { - var _start = (ParserRuleContext)kids[0]; - var _stop = (ParserRuleContext)kids[kids.Count - 1]; - stopIndex = _start.Stop.Column; - return _start.Start.Column; + var module = Selection.QualifiedName.Component.CodeModule; + var byValTokenLine = module.GetLines(terminalNodeImpl.Symbol.Line, 1); - if (kids[idx3] is UnrestrictedIdentifierContext) - { - var idRef = (UnrestrictedIdentifierContext)kids[idx3]; - var name = idRef.Start.Text; - if (name.Equals(identifier)) - { - int lineNum = idRef.Start.Line; - return lineNum; - } - } - } - } - } - } - } - stopIndex = -1; - return -1; + return ReplaceAtIndex(byValTokenLine, Tokens.ByVal, Tokens.ByRef, terminalNodeImpl.Symbol.Column); + } + private string ReplaceAtIndex(string input, string toReplace, string replacement, int index) + { + int stopIndex = index + toReplace.Length; + var prefix = input.Substring(0, index); + var suffix = input.Substring(stopIndex + 1); + var target = input.Substring(index, stopIndex - index + 1); + return prefix + target.Replace(toReplace, replacement) + suffix; + } + private void ReplaceModuleLine(int lineNumber, string replacementLine) + { + var module = Selection.QualifiedName.Component.CodeModule; + module.DeleteLines(lineNumber); + module.InsertLines(lineNumber, replacementLine); } } } \ No newline at end of file From aade1ca11be947df0549c57b28a7c865b0e671df Mon Sep 17 00:00:00 2001 From: comintern Date: Wed, 15 Feb 2017 22:20:42 -0600 Subject: [PATCH 06/20] Change GC suppression on DockableWindowHost. Should be safer. --- RetailCoder.VBE/UI/DockableWindowHost.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/RetailCoder.VBE/UI/DockableWindowHost.cs b/RetailCoder.VBE/UI/DockableWindowHost.cs index b2b5442331..d7239a710f 100644 --- a/RetailCoder.VBE/UI/DockableWindowHost.cs +++ b/RetailCoder.VBE/UI/DockableWindowHost.cs @@ -5,6 +5,8 @@ using System.Windows.Forms; using Rubberduck.Common.WinAPI; using Rubberduck.VBEditor; +using Rubberduck.VBEditor.WindowsApi; +using User32 = Rubberduck.Common.WinAPI.User32; namespace Rubberduck.UI { @@ -52,6 +54,7 @@ private struct LParam private IntPtr _parentHandle; private ParentWindow _subClassingWindow; + private GCHandle _thisHandle; internal void AddUserControl(UserControl control, IntPtr vbeHwnd) { @@ -63,7 +66,7 @@ internal void AddUserControl(UserControl control, IntPtr vbeHwnd) //since we have to inherit from UserControl we don't have to keep handling window messages until the VBE gets //around to destroying the control's host or it results in an access violation when the base class is disposed. //We need to manually call base.Dispose() ONLY in response to a WM_DESTROY message. - GC.KeepAlive(this); + _thisHandle = GCHandle.Alloc(this, GCHandleType.Normal); if (control != null) { @@ -143,7 +146,7 @@ protected override void DefWndProc(ref Message m) //See the comment in the ctor for why we have to listen for this. if (m.Msg == (int) WM.DESTROY) { - base.Dispose(true); + _thisHandle.Free(); return; } base.DefWndProc(ref m); From 5216f76c1f39d23158a76ac4908104b0a3fcc405 Mon Sep 17 00:00:00 2001 From: comintern Date: Wed, 15 Feb 2017 22:28:05 -0600 Subject: [PATCH 07/20] Get status bar working (partially - CodePane only). First step in burniating RawInput. --- RetailCoder.VBE/App.cs | 55 +++++-- RetailCoder.VBE/Common/WinAPI/RawInput.cs | 2 +- RetailCoder.VBE/Common/WinAPI/User32.cs | 31 ---- RetailCoder.VBE/Extension.cs | 8 +- RetailCoder.VBE/Rubberduck.csproj | 2 - .../Events/SelectionChangedEventArgs.cs | 19 +++ Rubberduck.VBEEditor/Events/VBEEvents.cs | 155 ++++++++++++++++++ .../Events/WindowChangedEventArgs.cs | 18 ++ .../Rubberduck.VBEditor.csproj | 11 +- .../SafeComWrappers/VB6/CodePane.cs | 3 +- .../SafeComWrappers/VBA/CodePane.cs | 3 +- .../WindowsApi/ChildWindowFinder.cs | 44 +++++ .../{ => WindowsApi}/NativeMethods.cs | 56 ++----- .../WindowsApi}/SubclassingWindow.cs | 2 +- Rubberduck.VBEEditor/WindowsApi/User32.cs | 70 ++++++++ .../WindowsApi}/WM.cs | 0 Rubberduck.VBEEditor/WindowsApi/WinEvent.cs | 87 ++++++++++ .../WindowsApi/WinEventFlags.cs | 17 ++ 18 files changed, 486 insertions(+), 97 deletions(-) create mode 100644 Rubberduck.VBEEditor/Events/SelectionChangedEventArgs.cs create mode 100644 Rubberduck.VBEEditor/Events/VBEEvents.cs create mode 100644 Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs create mode 100644 Rubberduck.VBEEditor/WindowsApi/ChildWindowFinder.cs rename Rubberduck.VBEEditor/{ => WindowsApi}/NativeMethods.cs (79%) rename {RetailCoder.VBE/UI => Rubberduck.VBEEditor/WindowsApi}/SubclassingWindow.cs (98%) create mode 100644 Rubberduck.VBEEditor/WindowsApi/User32.cs rename {RetailCoder.VBE/Common/WinAPI => Rubberduck.VBEEditor/WindowsApi}/WM.cs (100%) create mode 100644 Rubberduck.VBEEditor/WindowsApi/WinEvent.cs create mode 100644 Rubberduck.VBEEditor/WindowsApi/WinEventFlags.cs diff --git a/RetailCoder.VBE/App.cs b/RetailCoder.VBE/App.cs index 374f7b87b4..4169d9926c 100644 --- a/RetailCoder.VBE/App.cs +++ b/RetailCoder.VBE/App.cs @@ -15,11 +15,13 @@ using System.Windows.Forms; using Rubberduck.UI.Command; using Rubberduck.UI.Command.MenuItems.CommandBars; +using Rubberduck.VBEditor.Events; using Rubberduck.VBEditor.SafeComWrappers; using Rubberduck.VBEditor.SafeComWrappers.Abstract; using Rubberduck.VBEditor.SafeComWrappers.MSForms; using Rubberduck.VBEditor.SafeComWrappers.Office.Core.Abstract; using Rubberduck.VersionCheck; +using Application = System.Windows.Forms.Application; namespace Rubberduck { @@ -61,7 +63,9 @@ public App(IVBE vbe, _version = version; _checkVersionCommand = checkVersionCommand; - _hooks.MessageReceived += _hooks_MessageReceived; + VBEEvents.SelectionChanged += _vbe_SelectionChanged; + VBEEvents.ForgroundWindowChanged += _vbe_ForegroundWindowChanged; + _configService.SettingsChanged += _configService_SettingsChanged; _parser.State.StateChanged += Parser_StateChanged; _parser.State.StatusMessageUpdate += State_StatusMessageUpdate; @@ -81,17 +85,46 @@ private void State_StatusMessageUpdate(object sender, RubberduckStatusMessageEve _stateBar.SetStatusLabelCaption(message, _parser.State.ModuleExceptions.Count); } - private void _hooks_MessageReceived(object sender, HookEventArgs e) + private void _vbe_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + RefreshSelection(e.CodePane); + } + + private void _vbe_ForegroundWindowChanged(object sender, WindowChangedEventArgs e) { - RefreshSelection(); + RefreshSelection(e.Window); } private ParserState _lastStatus; private Declaration _lastSelectedDeclaration; - - private void RefreshSelection() + private void RefreshSelection(ICodePane pane) { + Declaration selectedDeclaration = null; + if (!pane.IsWrappingNullReference) + { + selectedDeclaration = _parser.State.FindSelectedDeclaration(pane); + var refCount = selectedDeclaration == null ? 0 : selectedDeclaration.References.Count(); + var caption = _stateBar.GetContextSelectionCaption(_vbe.ActiveCodePane, selectedDeclaration); + _stateBar.SetContextSelectionCaption(caption, refCount); + } + + var currentStatus = _parser.State.Status; + if (ShouldEvaluateCanExecute(selectedDeclaration, currentStatus)) + { + _appMenus.EvaluateCanExecute(_parser.State); + _stateBar.EvaluateCanExecute(_parser.State); + } + + _lastStatus = currentStatus; + _lastSelectedDeclaration = selectedDeclaration; + } + private void RefreshSelection(IWindow window) + { + if (window.IsWrappingNullReference || window.Type != WindowKind.Designer) + { + return; + } var caption = String.Empty; var refCount = 0; @@ -103,7 +136,7 @@ private void RefreshSelection() //TODO - I doubt this is the best way to check if the SelectedVBComponent and the ActiveCodePane are the same component. if (windowKind == WindowKind.CodeWindow || (!_vbe.SelectedVBComponent.IsWrappingNullReference - && component.ParentProject.ProjectId == pane.CodeModule.Parent.ParentProject.ProjectId + && component.ParentProject.ProjectId == pane.CodeModule.Parent.ParentProject.ProjectId && component.Name == pane.CodeModule.Parent.Name)) { selectedDeclaration = _parser.State.FindSelectedDeclaration(pane); @@ -120,13 +153,13 @@ private void RefreshSelection() { //The user might have selected the project node in Project Explorer //If they've chosen a folder, we'll return the project anyway - caption = !_vbe.ActiveVBProject.IsWrappingNullReference + caption = !_vbe.ActiveVBProject.IsWrappingNullReference ? _vbe.ActiveVBProject.Name : null; } else { - caption = component.Type == ComponentType.UserForm && component.HasOpenDesigner + caption = component.Type == ComponentType.UserForm && component.HasOpenDesigner ? GetComponentControlsCaption(component) : String.Format("{0}.{1} ({2})", component.ParentProject.Name, component.Name, component.Type); } @@ -322,10 +355,8 @@ public void Dispose() _parser.State.StatusMessageUpdate -= State_StatusMessageUpdate; } - if (_hooks != null) - { - _hooks.MessageReceived -= _hooks_MessageReceived; - } + VBEEvents.SelectionChanged += _vbe_SelectionChanged; + VBEEvents.ForgroundWindowChanged += _vbe_ForegroundWindowChanged; if (_configService != null) { diff --git a/RetailCoder.VBE/Common/WinAPI/RawInput.cs b/RetailCoder.VBE/Common/WinAPI/RawInput.cs index 014aab4585..003189daaa 100644 --- a/RetailCoder.VBE/Common/WinAPI/RawInput.cs +++ b/RetailCoder.VBE/Common/WinAPI/RawInput.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Runtime.InteropServices; -using Rubberduck.UI; +using Rubberduck.VBEditor.WindowsApi; namespace Rubberduck.Common.WinAPI { diff --git a/RetailCoder.VBE/Common/WinAPI/User32.cs b/RetailCoder.VBE/Common/WinAPI/User32.cs index f4887ba4e3..c3d6866d06 100644 --- a/RetailCoder.VBE/Common/WinAPI/User32.cs +++ b/RetailCoder.VBE/Common/WinAPI/User32.cs @@ -190,36 +190,5 @@ public static class User32 public delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam); [DllImport("user32.dll")] public static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam); - - /// - /// A helper function that returns true when the specified handle is that of the foreground window. - /// - /// The handle for the VBE's MainWindow. - /// - public static bool IsVbeWindowActive(IntPtr mainWindowHandle) - { - uint vbeThread; - GetWindowThreadProcessId(mainWindowHandle, out vbeThread); - - uint hThread; - GetWindowThreadProcessId(GetForegroundWindow(), out hThread); - - return (IntPtr)hThread == (IntPtr)vbeThread; - } - - public enum WindowType - { - Indeterminate, - VbaWindow, - DesignerWindow - } - - public static WindowType ToWindowType(this IntPtr hwnd) - { - var name = new StringBuilder(128); - GetClassName(hwnd, name, name.Capacity); - WindowType id; - return Enum.TryParse(name.ToString(), out id) ? id : WindowType.Indeterminate; - } } } diff --git a/RetailCoder.VBE/Extension.cs b/RetailCoder.VBE/Extension.cs index 9b5b2e3c26..b53012ff34 100644 --- a/RetailCoder.VBE/Extension.cs +++ b/RetailCoder.VBE/Extension.cs @@ -18,6 +18,7 @@ using NLog; using Rubberduck.Settings; using Rubberduck.SettingsProvider; +using Rubberduck.VBEditor.Events; using Rubberduck.VBEditor.SafeComWrappers.Abstract; namespace Rubberduck @@ -53,8 +54,9 @@ public void OnConnection(object Application, ext_ConnectMode ConnectMode, object { if (Application is Microsoft.Vbe.Interop.VBE) { - var vbe = (Microsoft.Vbe.Interop.VBE) Application; + var vbe = (Microsoft.Vbe.Interop.VBE) Application; _ide = new VBEditor.SafeComWrappers.VBA.VBE(vbe); + VBEEvents.HookEvents(_ide); var addin = (Microsoft.Vbe.Interop.AddIn)AddInInst; _addin = new VBEditor.SafeComWrappers.VBA.AddIn(addin) { Object = this }; @@ -87,7 +89,7 @@ public void OnConnection(object Application, ext_ConnectMode ConnectMode, object Assembly LoadFromSameFolder(object sender, ResolveEventArgs args) { - var folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty; var assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll"); if (!File.Exists(assemblyPath)) { @@ -219,6 +221,8 @@ private void Startup() private void ShutdownAddIn() { + VBEEvents.UnhookEvents(); + var currentDomain = AppDomain.CurrentDomain; currentDomain.AssemblyResolve -= LoadFromSameFolder; diff --git a/RetailCoder.VBE/Rubberduck.csproj b/RetailCoder.VBE/Rubberduck.csproj index 21c0b9aa32..cacdcfb629 100644 --- a/RetailCoder.VBE/Rubberduck.csproj +++ b/RetailCoder.VBE/Rubberduck.csproj @@ -370,7 +370,6 @@ - @@ -500,7 +499,6 @@ - diff --git a/Rubberduck.VBEEditor/Events/SelectionChangedEventArgs.cs b/Rubberduck.VBEEditor/Events/SelectionChangedEventArgs.cs new file mode 100644 index 0000000000..7cff78714e --- /dev/null +++ b/Rubberduck.VBEEditor/Events/SelectionChangedEventArgs.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Rubberduck.VBEditor.SafeComWrappers.Abstract; + +namespace Rubberduck.VBEditor.Events +{ + public class SelectionChangedEventArgs : EventArgs + { + public ICodePane CodePane { get; private set; } + + public SelectionChangedEventArgs(ICodePane pane) + { + CodePane = pane; + } + } +} diff --git a/Rubberduck.VBEEditor/Events/VBEEvents.cs b/Rubberduck.VBEEditor/Events/VBEEvents.cs new file mode 100644 index 0000000000..d772b09931 --- /dev/null +++ b/Rubberduck.VBEEditor/Events/VBEEvents.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using Rubberduck.VBEditor.WindowsApi; + +namespace Rubberduck.VBEditor.Events +{ + public static class VBEEvents + { + private static User32.WinEventProc _eventProc; + private static IntPtr _eventHandle; + private static IVBE _vbe; + + public struct WindowInfo + { + private readonly IntPtr _handle; + private readonly IWindow _window; + + public IntPtr Hwnd { get { return _handle; } } + public IWindow Window { get { return _window; } } + + public WindowInfo(IntPtr handle, IWindow window) + { + _handle = handle; + _window = window; + } + } + + //This *could* be a ConcurrentDictionary, but there other operations that need the lock around it anyway. + private static readonly Dictionary TrackedWindows = new Dictionary(); + private static readonly object ThreadLock = new object(); + + private static uint _threadId; + + public static void HookEvents(IVBE vbe) + { + _vbe = vbe; + if (_eventHandle == IntPtr.Zero) + { + _eventProc = VbeEventCallback; + _threadId = User32.GetWindowThreadProcessId(new IntPtr(_vbe.MainWindow.HWnd), IntPtr.Zero); + _eventHandle = User32.SetWinEventHook((uint)WinEvent.Min, (uint)WinEvent.Max, IntPtr.Zero, _eventProc, 0, _threadId, WinEventFlags.OutOfContext); + } + } + + public static void UnhookEvents() + { + User32.UnhookWinEvent(_eventHandle); + } + + public static void VbeEventCallback(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, + uint dwEventThread, uint dwmsEventTime) + { + if (hwnd != IntPtr.Zero && idObject == (int)ObjId.Caret && eventType == (uint)WinEvent.ObjectLocationChange && hwnd.ToWindowType() == WindowType.VbaWindow) + { + OnSelectionChanged(hwnd); + } + else if (idObject == (int)ObjId.Window && + (eventType == (uint)WinEvent.ObjectCreate || eventType == (uint)WinEvent.ObjectDestroy) && + hwnd.ToWindowType() != WindowType.Indeterminate) + { + if (eventType == (uint) WinEvent.ObjectCreate) + { + AttachWindow(hwnd); + } + else if (eventType == (uint)WinEvent.ObjectDestroy) + { + DetachWindow(hwnd); + } + } + } + + private static void AttachWindow(IntPtr hwnd) + { + lock (ThreadLock) + { + Debug.Assert(!TrackedWindows.ContainsKey(hwnd)); + var window = GetWindowFromHwnd(hwnd); + if (window == null) return; + var info = new WindowInfo(hwnd, window); + TrackedWindows.Add(hwnd, info); + } + } + + private static void DetachWindow(IntPtr hwnd) + { + lock (ThreadLock) + { + Debug.Assert(TrackedWindows.ContainsKey(hwnd)); + TrackedWindows.Remove(hwnd); + } + } + + public static event EventHandler SelectionChanged; + private static void OnSelectionChanged(IntPtr hwnd) + { + if (SelectionChanged != null) + { + var pane = GetCodePaneFromHwnd(hwnd); + SelectionChanged.Invoke(_vbe, new SelectionChangedEventArgs(pane)); + } + } + + //Pending location of a suitable event - might need a subclass here instead. + public static event EventHandler ForgroundWindowChanged; + private static void OnForgroundWindowChanged(WindowInfo info) + { + if (ForgroundWindowChanged != null) + { + ForgroundWindowChanged.Invoke(_vbe, new WindowChangedEventArgs(info.Hwnd, info.Window)); + } + } + + private static ICodePane GetCodePaneFromHwnd(IntPtr hwnd) + { + var caption = hwnd.GetWindowText(); + return _vbe.CodePanes.FirstOrDefault(x => x.Window.Caption.Equals(caption)); + } + + private static IWindow GetWindowFromHwnd(IntPtr hwnd) + { + var caption = hwnd.GetWindowText(); + return _vbe.Windows.FirstOrDefault(x => x.Caption.Equals(caption)); + } + + /// + /// A helper function that returns true when the specified handle is that of the foreground window. + /// + /// True if the active thread is on the VBE's thread. + public static bool IsVbeWindowActive() + { + uint hThread; + User32.GetWindowThreadProcessId(User32.GetForegroundWindow(), out hThread); + return (IntPtr)hThread == (IntPtr)_threadId; + } + + public enum WindowType + { + Indeterminate, + VbaWindow, + DesignerWindow + } + + public static WindowType ToWindowType(this IntPtr hwnd) + { + var name = new StringBuilder(128); + User32.GetClassName(hwnd, name, name.Capacity); + WindowType id; + return Enum.TryParse(name.ToString(), out id) ? id : WindowType.Indeterminate; + } + } +} diff --git a/Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs b/Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs new file mode 100644 index 0000000000..d7040e8301 --- /dev/null +++ b/Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs @@ -0,0 +1,18 @@ +using System; +using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using Rubberduck.VBEditor.SafeComWrappers.MSForms; + +namespace Rubberduck.VBEditor.Events +{ + public class WindowChangedEventArgs : EventArgs + { + public IntPtr Hwnd { get; private set; } + public IWindow Window { get; private set; } + + public WindowChangedEventArgs(IntPtr hwnd, IWindow window) + { + Hwnd = hwnd; + Window = window; + } + } +} diff --git a/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj b/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj index 0f086f1ba7..9afc4b01fc 100644 --- a/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj +++ b/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj @@ -124,6 +124,9 @@ + + + @@ -223,7 +226,8 @@ - + + @@ -253,6 +257,11 @@ + + + + + diff --git a/Rubberduck.VBEEditor/SafeComWrappers/VB6/CodePane.cs b/Rubberduck.VBEEditor/SafeComWrappers/VB6/CodePane.cs index 4851c72bb5..9d962a9648 100644 --- a/Rubberduck.VBEEditor/SafeComWrappers/VB6/CodePane.cs +++ b/Rubberduck.VBEEditor/SafeComWrappers/VB6/CodePane.cs @@ -1,5 +1,6 @@ using System; using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using Rubberduck.VBEditor.WindowsApi; using VB = Microsoft.VB6.Interop.VBIDE; namespace Rubberduck.VBEditor.SafeComWrappers.VB6 @@ -101,7 +102,7 @@ private void ForceFocus() var window = VBE.MainWindow; var mainWindowHandle = window.Handle(); var caption = Window.Caption; - var childWindowFinder = new NativeMethods.ChildWindowFinder(caption); + var childWindowFinder = new ChildWindowFinder(caption); NativeMethods.EnumChildWindows(mainWindowHandle, childWindowFinder.EnumWindowsProcToChildWindowByCaption); var handle = childWindowFinder.ResultHandle; diff --git a/Rubberduck.VBEEditor/SafeComWrappers/VBA/CodePane.cs b/Rubberduck.VBEEditor/SafeComWrappers/VBA/CodePane.cs index ec9ab7c663..4fd8b87dd5 100644 --- a/Rubberduck.VBEEditor/SafeComWrappers/VBA/CodePane.cs +++ b/Rubberduck.VBEEditor/SafeComWrappers/VBA/CodePane.cs @@ -1,5 +1,6 @@ using System; using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using Rubberduck.VBEditor.WindowsApi; using VB = Microsoft.Vbe.Interop; namespace Rubberduck.VBEditor.SafeComWrappers.VBA @@ -105,7 +106,7 @@ private void ForceFocus() var window = VBE.MainWindow; var mainWindowHandle = window.Handle(); var caption = Window.Caption; - var childWindowFinder = new NativeMethods.ChildWindowFinder(caption); + var childWindowFinder = new ChildWindowFinder(caption); NativeMethods.EnumChildWindows(mainWindowHandle, childWindowFinder.EnumWindowsProcToChildWindowByCaption); var handle = childWindowFinder.ResultHandle; diff --git a/Rubberduck.VBEEditor/WindowsApi/ChildWindowFinder.cs b/Rubberduck.VBEEditor/WindowsApi/ChildWindowFinder.cs new file mode 100644 index 0000000000..3d55a01d13 --- /dev/null +++ b/Rubberduck.VBEEditor/WindowsApi/ChildWindowFinder.cs @@ -0,0 +1,44 @@ +using System; +using System.Text; + +namespace Rubberduck.VBEditor.WindowsApi +{ + internal class ChildWindowFinder + { + private IntPtr _resultHandle = IntPtr.Zero; + private readonly string _caption; + + internal ChildWindowFinder(string caption) + { + _caption = caption; + } + + public int EnumWindowsProcToChildWindowByCaption(IntPtr windowHandle, IntPtr param) + { + // By default it will continue enumeration after this call + var result = 1; + var caption = windowHandle.GetWindowText(); + + if (_caption == caption) + { + // Found + _resultHandle = windowHandle; + + // Stop enumeration after this call + result = 0; + } + return result; + } + + public IntPtr ResultHandle + { + get + { + return _resultHandle; + } + } + + + + } +} diff --git a/Rubberduck.VBEEditor/NativeMethods.cs b/Rubberduck.VBEEditor/WindowsApi/NativeMethods.cs similarity index 79% rename from Rubberduck.VBEEditor/NativeMethods.cs rename to Rubberduck.VBEEditor/WindowsApi/NativeMethods.cs index 1bf10c7635..43f7dd7846 100644 --- a/Rubberduck.VBEEditor/NativeMethods.cs +++ b/Rubberduck.VBEEditor/WindowsApi/NativeMethods.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; using System.Text; -namespace Rubberduck.VBEditor +namespace Rubberduck.VBEditor.WindowsApi { /// /// Collection of WinAPI methods and extensions to handle native windows. @@ -48,18 +48,12 @@ public static class NativeMethods [DllImport("user32", EntryPoint = "GetWindowTextW", ExactSpelling = true, CharSet = CharSet.Unicode)] internal static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); - /// Gets the parent window of this item. - /// - /// The window handle. - /// The parent window IntPtr handle. - [DllImport("User32.dll")] - internal static extern IntPtr GetParent(IntPtr hWnd); /// Gets window caption text by handle. /// /// Handle of the window to be activated. /// The window caption text. - internal static string GetWindowTextByHwnd(IntPtr windowHandle) + public static string GetWindowText(this IntPtr windowHandle) { const int MAX_BUFFER = 300; @@ -75,6 +69,15 @@ internal static string GetWindowTextByHwnd(IntPtr windowHandle) return result; } + /// Gets the parent window of this item. + /// + /// The window handle. + /// The parent window IntPtr handle. + [DllImport("User32.dll")] + internal static extern IntPtr GetParent(IntPtr hWnd); + + + /// Activates the window by simulating a click. /// /// Handle of the window to be activated. @@ -96,42 +99,5 @@ internal static void EnumChildWindows(IntPtr parentWindowHandle, EnumChildWindow Debug.WriteLine("EnumChildWindows failed"); } } - - internal class ChildWindowFinder - { - private IntPtr _resultHandle = IntPtr.Zero; - private readonly string _caption; - - internal ChildWindowFinder(string caption) - { - _caption = caption; - } - - public int EnumWindowsProcToChildWindowByCaption(IntPtr windowHandle, IntPtr param) - { - // By default it will continue enumeration after this call - var result = 1; - var caption = GetWindowTextByHwnd(windowHandle); - - - if (_caption == caption) - { - // Found - _resultHandle = windowHandle; - - // Stop enumeration after this call - result = 0; - } - return result; - } - - public IntPtr ResultHandle - { - get - { - return _resultHandle; - } - } - } } } diff --git a/RetailCoder.VBE/UI/SubclassingWindow.cs b/Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs similarity index 98% rename from RetailCoder.VBE/UI/SubclassingWindow.cs rename to Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs index c9ab5dc12c..9e531e8286 100644 --- a/RetailCoder.VBE/UI/SubclassingWindow.cs +++ b/Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; using Rubberduck.Common.WinAPI; -namespace Rubberduck.UI +namespace Rubberduck.VBEditor.WindowsApi { public abstract class SubclassingWindow : IDisposable { diff --git a/Rubberduck.VBEEditor/WindowsApi/User32.cs b/Rubberduck.VBEEditor/WindowsApi/User32.cs new file mode 100644 index 0000000000..dc6a2f1170 --- /dev/null +++ b/Rubberduck.VBEEditor/WindowsApi/User32.cs @@ -0,0 +1,70 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Rubberduck.VBEditor.WindowsApi +{ + public static class User32 + { + #region WinEvents + + //https://msdn.microsoft.com/en-us/library/windows/desktop/dd373885(v=vs.85).aspx + public delegate void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); + + //https://msdn.microsoft.com/en-us/library/windows/desktop/dd373640(v=vs.85).aspx + [DllImport("user32.dll")] + public static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventProc lpfnWinEventProc, uint idProcess, uint idThread, WinEventFlags dwFlags); + + /// + /// Removes event hooks set with SetWinEventHook. + /// https://msdn.microsoft.com/en-us/library/windows/desktop/dd373671(v=vs.85).aspx + /// + /// The hook handle to unregister. + /// + [DllImport("user32.dll")] + public static extern bool UnhookWinEvent(IntPtr hWinEventHook); + + #endregion + + /// + /// Returns the thread ID for thread that created the passed hWnd. + /// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633522(v=vs.85).aspx + /// + /// The window handle to get the thread ID for. + /// This is actually an out parameter in the API, but we don't care about it. Should always be IntPtr.Zero. + /// Unmanaged thread ID + [DllImport("user32.dll")] + public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId); + + /// + /// Retrieves the identifier of the thread that created the specified window and, optionally, + /// the identifier of the process that created the window. + /// + /// A handle to the window. + /// A pointer to a variable that receives the process identifier. + /// If this parameter is not NULL, GetWindowThreadProcessId copies the identifier of the process to the variable; otherwise, it does not. + /// The return value is the identifier of the thread that created the window. + [DllImport("user32.dll", SetLastError = true)] + public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + /// + /// Retrieves a handle to the foreground window (the window with which the user is currently working). + /// The system assigns a slightly higher priority to the thread that creates the foreground window than it does to other threads. + /// + /// The return value is a handle to the foreground window. + /// The foreground window can be NULL in certain circumstances, such as when a window is losing activation. + [DllImport("user32.dll")] + public static extern IntPtr GetForegroundWindow(); + + /// + /// Gets the underlying class name for a window handle. + /// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633582(v=vs.85).aspx + /// + /// The handle to retrieve the name for. + /// Buffer for returning the class name. + /// Buffer size in characters, including the null terminator. + /// The length of the returned class name (without the null terminator), zero on error. + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); + } +} diff --git a/RetailCoder.VBE/Common/WinAPI/WM.cs b/Rubberduck.VBEEditor/WindowsApi/WM.cs similarity index 100% rename from RetailCoder.VBE/Common/WinAPI/WM.cs rename to Rubberduck.VBEEditor/WindowsApi/WM.cs diff --git a/Rubberduck.VBEEditor/WindowsApi/WinEvent.cs b/Rubberduck.VBEEditor/WindowsApi/WinEvent.cs new file mode 100644 index 0000000000..d67c584de8 --- /dev/null +++ b/Rubberduck.VBEEditor/WindowsApi/WinEvent.cs @@ -0,0 +1,87 @@ +namespace Rubberduck.VBEditor.WindowsApi +{ + public enum WinEvent + { + Min = 0x0001, + SystemSound = 0x0001, + SystemAlert = 0x0002, + SystemForeground = 0x0003, + SystemMenuStart = 0x0004, + SystemMenuEnd = 0x0005, + SystemMenuPopupStart = 0x0006, + SystemMenuPopupEnd = 0x0007, + SystemCaptureStart = 0x0008, + SystemCaptureEnd = 0x0009, + SystemMoveSizeStart = 0x000A, + SystemMoveSizeEnd = 0x000B, + SystemContextHelpStart = 0x000C, + SystemContextHelpEnd = 0x000D, + SystemDragDropStart = 0x000E, + SystemDragDropEnd = 0x000F, + SystemDialogStart = 0x0010, + SystemDialogEnd = 0x0011, + SystemScrollingStart = 0x0012, + SystemScrollingEnd = 0x0013, + SystemSwitchStart = 0x0014, + SystemSwitchEnd = 0x0015, + SystemMinimizeStart = 0x0016, + SystemMinimizeEnd = 0x0017, + SystemDesktopSwitch = 0x0020, + SystemEnd = 0x00FF, + ObjectCreate = 0x8000, + ObjectDestroy = 0x8001, + ObjectShow = 0x8002, + ObjectHide = 0x8003, + ObjectReorder = 0x8004, + ObjectFocus = 0x8005, + ObjectSelection = 0x8006, + ObjectSelectionAdd = 0x8007, + ObjectSelectionRemove = 0x8008, + ObjectSelectionWithin = 0x8009, + ObjectStateChange = 0x800A, + ObjectLocationChange = 0x800B, + ObjectNameChange = 0x800C, + ObjectDescriptionChange = 0x800D, + ObjectValueChange = 0x800E, + ObjectParentChange = 0x800F, + ObjectHelpChange = 0x8010, + ObjectDefactionChange = 0x8011, + ObjectInvoked = 0x8013, + ObjectTextSelectionChanged = 0x8014, + SystemArrangmentPreview = 0x8016, + ObjectLiveRegionChanged = 0x8019, + ObjectHostedObjectsInvalidated = 0x8020, + ObjectDragStart = 0x8021, + ObjectDragCancel = 0x8022, + ObjectDragComplete = 0x8023, + ObjectDragEnter = 0x8024, + ObjectDragLeave = 0x8025, + ObjectDragDropped = 0x8026, + ObjectImeShow = 0x8027, + ObjectImeHide = 0x8028, + ObjectImeChange = 0x8029, + ObjectTexteditConversionTargetChanged = 0x8030, + ObjectEnd = 0x80FF, + AiaStart = 0xA000, + AiaEnd = 0xAFFF, + Max = 0x7FFFFFFF + } + + public enum ObjId + { + Window = 0, + SysMenu = -1, + TitleBar = -2, + Menu = -3, + Client = -4, + VScroll = -5, + HScroll = -6, + SizeGrip = -7, + Caret = -8, + Cursor = -9, + Alert = -10, + Sount = -11, + QueryClasNameIdx = -12, + NativeOM = -16 + } +} diff --git a/Rubberduck.VBEEditor/WindowsApi/WinEventFlags.cs b/Rubberduck.VBEEditor/WindowsApi/WinEventFlags.cs new file mode 100644 index 0000000000..7f5dffc2f7 --- /dev/null +++ b/Rubberduck.VBEEditor/WindowsApi/WinEventFlags.cs @@ -0,0 +1,17 @@ +using System; + +namespace Rubberduck.VBEditor.WindowsApi +{ + [Flags] + public enum WinEventFlags + { + //Asynchronous events. + OutOfContext = 0x0000, + //No events raised from caller thread. Must be combined with OutOfContext or InContext. + SkipOwnThread = 0x0001, + //No events raised from caller process. Must be combined with OutOfContext or InContext. + SkipOwnProcess = 0x0002, + //Synchronous events - injects into *all* processes. + InContext = 0x0004 + } +} From b48356f94aa2fcfc6bce16c9ba8159d548d44387 Mon Sep 17 00:00:00 2001 From: comintern Date: Wed, 15 Feb 2017 23:51:03 -0600 Subject: [PATCH 08/20] Fix stupid error that left Hwnd property unassigned. --- Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs b/Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs index 9e531e8286..16ae10c917 100644 --- a/Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs +++ b/Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs @@ -31,7 +31,7 @@ public abstract class SubclassingWindow : IDisposable [DllImport("ComCtl32.dll", CharSet = CharSet.Auto)] private static extern int DefSubclassProc(IntPtr hWnd, IntPtr msg, IntPtr wParam, IntPtr lParam); - public IntPtr Hwnd { get; set; } + public IntPtr Hwnd { get { return _hwnd; } } protected SubclassingWindow(IntPtr subclassId, IntPtr hWnd) { @@ -91,8 +91,9 @@ public virtual int SubClassProc(IntPtr hWnd, IntPtr msg, IntPtr wParam, IntPtr l } Debug.Assert(IsWindow(_hwnd)); + //TODO: This should change to WM.DESTROY once subclassing\hooking consolidation is complete. if ((uint)msg == (uint)WM.RUBBERDUCK_SINKING) - { + { ReleaseHandle(); } return DefSubclassProc(hWnd, msg, wParam, lParam); From f9a115d7e972d4737fb9100ab70fe9b5cec13fc4 Mon Sep 17 00:00:00 2001 From: comintern Date: Thu, 16 Feb 2017 00:26:45 -0600 Subject: [PATCH 09/20] Can't release the controls from DockableToolwindowPresenter. --- RetailCoder.VBE/UI/DockableToolwindowPresenter.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/RetailCoder.VBE/UI/DockableToolwindowPresenter.cs b/RetailCoder.VBE/UI/DockableToolwindowPresenter.cs index 4264db2308..047e66ca4f 100644 --- a/RetailCoder.VBE/UI/DockableToolwindowPresenter.cs +++ b/RetailCoder.VBE/UI/DockableToolwindowPresenter.cs @@ -130,16 +130,6 @@ protected virtual void Dispose(bool disposing) } if (disposing && _window != null) { - if (_userControlObject != null) - { - ((_DockableWindowHost)_userControlObject).Dispose(); - } - _userControlObject = null; - - if (_userControl != null) - { - _userControl.Dispose(); - } // cleanup unmanaged resource wrappers _window.Close(); _window.Release(true); From f8272c6b31e70661f0c8e7e0d8339c23f0670883 Mon Sep 17 00:00:00 2001 From: comintern Date: Thu, 16 Feb 2017 00:27:44 -0600 Subject: [PATCH 10/20] Add initial focus change events - may be a bit unstable ATM. --- RetailCoder.VBE/App.cs | 11 ++-- Rubberduck.VBEEditor/Events/VBEEvents.cs | 53 +++++++++++++++---- .../Events/WindowChangedEventArgs.cs | 11 +++- .../Rubberduck.VBEditor.csproj | 4 ++ .../WindowsApi/CodePaneSubclass.cs | 10 ++++ .../WindowsApi/DesignerWindowSubclass.cs | 10 ++++ .../WindowsApi/FocusSource.cs | 39 ++++++++++++++ .../WindowsApi/IWindowEventProvider.cs | 14 +++++ 8 files changed, 136 insertions(+), 16 deletions(-) create mode 100644 Rubberduck.VBEEditor/WindowsApi/CodePaneSubclass.cs create mode 100644 Rubberduck.VBEEditor/WindowsApi/DesignerWindowSubclass.cs create mode 100644 Rubberduck.VBEEditor/WindowsApi/FocusSource.cs create mode 100644 Rubberduck.VBEEditor/WindowsApi/IWindowEventProvider.cs diff --git a/RetailCoder.VBE/App.cs b/RetailCoder.VBE/App.cs index 4169d9926c..3cfca41f8c 100644 --- a/RetailCoder.VBE/App.cs +++ b/RetailCoder.VBE/App.cs @@ -64,7 +64,7 @@ public App(IVBE vbe, _checkVersionCommand = checkVersionCommand; VBEEvents.SelectionChanged += _vbe_SelectionChanged; - VBEEvents.ForgroundWindowChanged += _vbe_ForegroundWindowChanged; + VBEEvents.WindowFocusChange += _vbe_FocusChanged; _configService.SettingsChanged += _configService_SettingsChanged; _parser.State.StateChanged += Parser_StateChanged; @@ -90,9 +90,12 @@ private void _vbe_SelectionChanged(object sender, SelectionChangedEventArgs e) RefreshSelection(e.CodePane); } - private void _vbe_ForegroundWindowChanged(object sender, WindowChangedEventArgs e) + private void _vbe_FocusChanged(object sender, WindowChangedEventArgs e) { - RefreshSelection(e.Window); + if (e.EventType == WindowChangedEventArgs.FocusType.GotFocus) + { + RefreshSelection(e.Window); + } } private ParserState _lastStatus; @@ -356,7 +359,7 @@ public void Dispose() } VBEEvents.SelectionChanged += _vbe_SelectionChanged; - VBEEvents.ForgroundWindowChanged += _vbe_ForegroundWindowChanged; + VBEEvents.WindowFocusChange += _vbe_FocusChanged; if (_configService != null) { diff --git a/Rubberduck.VBEEditor/Events/VBEEvents.cs b/Rubberduck.VBEEditor/Events/VBEEvents.cs index d772b09931..e26c2cd3e5 100644 --- a/Rubberduck.VBEEditor/Events/VBEEvents.cs +++ b/Rubberduck.VBEEditor/Events/VBEEvents.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using Rubberduck.VBEditor.SafeComWrappers.MSForms; using Rubberduck.VBEditor.WindowsApi; namespace Rubberduck.VBEditor.Events @@ -13,19 +14,22 @@ public static class VBEEvents private static User32.WinEventProc _eventProc; private static IntPtr _eventHandle; private static IVBE _vbe; - + public struct WindowInfo { private readonly IntPtr _handle; private readonly IWindow _window; + private readonly IWindowEventProvider _subclass; public IntPtr Hwnd { get { return _handle; } } public IWindow Window { get { return _window; } } + internal IWindowEventProvider Subclass { get { return _subclass; } } - public WindowInfo(IntPtr handle, IWindow window) + internal WindowInfo(IntPtr handle, IWindow window, IWindowEventProvider source) { _handle = handle; - _window = window; + _window = window; + _subclass = source; } } @@ -48,7 +52,15 @@ public static void HookEvents(IVBE vbe) public static void UnhookEvents() { - User32.UnhookWinEvent(_eventHandle); + lock (ThreadLock) + { + User32.UnhookWinEvent(_eventHandle); + foreach (var info in TrackedWindows.Values) + { + info.Subclass.FocusChange -= FocusDispatcher; + info.Subclass.Dispose(); + } + } } public static void VbeEventCallback(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, @@ -80,7 +92,9 @@ private static void AttachWindow(IntPtr hwnd) Debug.Assert(!TrackedWindows.ContainsKey(hwnd)); var window = GetWindowFromHwnd(hwnd); if (window == null) return; - var info = new WindowInfo(hwnd, window); + var source = window.Type == WindowKind.CodeWindow ? new CodePaneSubclass(hwnd) as IWindowEventProvider: new DesignerWindowSubclass(hwnd); + var info = new WindowInfo(hwnd, window, source); + source.FocusChange += FocusDispatcher; TrackedWindows.Add(hwnd, info); } } @@ -90,10 +104,30 @@ private static void DetachWindow(IntPtr hwnd) lock (ThreadLock) { Debug.Assert(TrackedWindows.ContainsKey(hwnd)); + var info = TrackedWindows[hwnd]; + info.Subclass.FocusChange -= FocusDispatcher; + info.Subclass.Dispose(); TrackedWindows.Remove(hwnd); } } + private static void FocusDispatcher(object sender, WindowChangedEventArgs eventArgs) + { + OnWindowFocusChange(sender, eventArgs); + } + + public static WindowInfo? GetWindowInfoFromHwnd(IntPtr hwnd) + { + lock (ThreadLock) + { + if (!TrackedWindows.ContainsKey(hwnd)) + { + return null; + } + return TrackedWindows[hwnd]; + } + } + public static event EventHandler SelectionChanged; private static void OnSelectionChanged(IntPtr hwnd) { @@ -104,13 +138,12 @@ private static void OnSelectionChanged(IntPtr hwnd) } } - //Pending location of a suitable event - might need a subclass here instead. - public static event EventHandler ForgroundWindowChanged; - private static void OnForgroundWindowChanged(WindowInfo info) + public static event EventHandler WindowFocusChange; + private static void OnWindowFocusChange(object sender, WindowChangedEventArgs eventArgs) { - if (ForgroundWindowChanged != null) + if (WindowFocusChange != null) { - ForgroundWindowChanged.Invoke(_vbe, new WindowChangedEventArgs(info.Hwnd, info.Window)); + WindowFocusChange.Invoke(sender, eventArgs); } } diff --git a/Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs b/Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs index d7040e8301..cc12270111 100644 --- a/Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs +++ b/Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs @@ -1,18 +1,25 @@ using System; using Rubberduck.VBEditor.SafeComWrappers.Abstract; -using Rubberduck.VBEditor.SafeComWrappers.MSForms; namespace Rubberduck.VBEditor.Events { public class WindowChangedEventArgs : EventArgs { + public enum FocusType + { + GotFocus, + LostFocus + } + public IntPtr Hwnd { get; private set; } public IWindow Window { get; private set; } + public FocusType EventType { get; private set; } - public WindowChangedEventArgs(IntPtr hwnd, IWindow window) + public WindowChangedEventArgs(IntPtr hwnd, IWindow window, FocusType type) { Hwnd = hwnd; Window = window; + EventType = type; } } } diff --git a/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj b/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj index 9afc4b01fc..bb6b162fe0 100644 --- a/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj +++ b/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj @@ -227,6 +227,10 @@ + + + + diff --git a/Rubberduck.VBEEditor/WindowsApi/CodePaneSubclass.cs b/Rubberduck.VBEEditor/WindowsApi/CodePaneSubclass.cs new file mode 100644 index 0000000000..30998d8d46 --- /dev/null +++ b/Rubberduck.VBEEditor/WindowsApi/CodePaneSubclass.cs @@ -0,0 +1,10 @@ +using System; + +namespace Rubberduck.VBEditor.WindowsApi +{ + internal class CodePaneSubclass : FocusSource + { + //Stub for code pane replacement. :-) + internal CodePaneSubclass(IntPtr hwnd) : base(hwnd) { } + } +} diff --git a/Rubberduck.VBEEditor/WindowsApi/DesignerWindowSubclass.cs b/Rubberduck.VBEEditor/WindowsApi/DesignerWindowSubclass.cs new file mode 100644 index 0000000000..9edf56ec2b --- /dev/null +++ b/Rubberduck.VBEEditor/WindowsApi/DesignerWindowSubclass.cs @@ -0,0 +1,10 @@ +using System; + +namespace Rubberduck.VBEditor.WindowsApi +{ + internal class DesignerWindowSubclass : FocusSource + { + //Stub for designer window replacement. :-) + internal DesignerWindowSubclass(IntPtr hwnd) : base(hwnd) { } + } +} diff --git a/Rubberduck.VBEEditor/WindowsApi/FocusSource.cs b/Rubberduck.VBEEditor/WindowsApi/FocusSource.cs new file mode 100644 index 0000000000..979f85f9cb --- /dev/null +++ b/Rubberduck.VBEEditor/WindowsApi/FocusSource.cs @@ -0,0 +1,39 @@ +using System; +using Rubberduck.Common.WinAPI; +using Rubberduck.VBEditor.Events; + +namespace Rubberduck.VBEditor.WindowsApi +{ + internal abstract class FocusSource : SubclassingWindow, IWindowEventProvider + { + protected FocusSource(IntPtr hwnd) : base(hwnd, hwnd) { } + + public event EventHandler FocusChange; + private void OnFocusChange(WindowChangedEventArgs.FocusType type) + { + if (FocusChange != null) + { + var window = VBEEvents.GetWindowInfoFromHwnd(Hwnd); + if (window == null) + { + return; + } + FocusChange.Invoke(this, new WindowChangedEventArgs(window.Value.Hwnd, window.Value.Window, type)); + } + } + + public override int SubClassProc(IntPtr hWnd, IntPtr msg, IntPtr wParam, IntPtr lParam, IntPtr uIdSubclass, IntPtr dwRefData) + { + switch ((uint)msg) + { + case (uint)WM.SETFOCUS: + OnFocusChange(WindowChangedEventArgs.FocusType.GotFocus); + break; + case (uint)WM.KILLFOCUS: + OnFocusChange(WindowChangedEventArgs.FocusType.LostFocus); + break; + } + return base.SubClassProc(hWnd, msg, wParam, lParam, uIdSubclass, dwRefData); + } + } +} diff --git a/Rubberduck.VBEEditor/WindowsApi/IWindowEventProvider.cs b/Rubberduck.VBEEditor/WindowsApi/IWindowEventProvider.cs new file mode 100644 index 0000000000..4f12cb7764 --- /dev/null +++ b/Rubberduck.VBEEditor/WindowsApi/IWindowEventProvider.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Rubberduck.VBEditor.Events; + +namespace Rubberduck.VBEditor.WindowsApi +{ + public interface IWindowEventProvider : IDisposable + { + event EventHandler FocusChange; + } +} From e567400f9dbabc29887de5cf9814194e40baf8ee Mon Sep 17 00:00:00 2001 From: Brian Zenger Date: Wed, 15 Feb 2017 23:50:20 -0800 Subject: [PATCH 11/20] Recover lost updates for SubstmtContext to ArgListContext --- .../Inspections/Concrete/Inspector.cs | 2 +- ...ocedureCanBeWrittenAsFunctionInspection.cs | 10 +++--- .../DeclareAsExplicitVariantQuickFix.cs | 4 +-- .../PassParameterByReferenceQuickFix.cs | 4 +-- ...eCanBeWrittenAsFunctionInspectionResult.cs | 8 ++--- .../CodeExplorerMemberViewModel.cs | 2 +- .../IntroduceParameterRefactoring.cs | 6 ++-- .../RemoveParametersRefactoring.cs | 10 +++--- .../Refactorings/Rename/RenameRefactoring.cs | 2 +- .../ReorderParametersModel.cs | 2 +- .../ReorderParametersRefactoring.cs | 10 +++--- Rubberduck.Parsing/Grammar/VBAParser.cs | 36 +++++++++---------- .../Grammar/VBAParserBaseListener.cs | 4 +-- .../Grammar/VBAParserBaseVisitor.cs | 2 +- .../Grammar/VBAParserListener.cs | 4 +-- .../Grammar/VBAParserVisitor.cs | 2 +- .../Symbols/DeclarationSymbolsListener.cs | 2 +- 17 files changed, 55 insertions(+), 55 deletions(-) diff --git a/RetailCoder.VBE/Inspections/Concrete/Inspector.cs b/RetailCoder.VBE/Inspections/Concrete/Inspector.cs index f5f3fe3934..12cee20902 100644 --- a/RetailCoder.VBE/Inspections/Concrete/Inspector.cs +++ b/RetailCoder.VBE/Inspections/Concrete/Inspector.cs @@ -139,7 +139,7 @@ before moving them into the ParseTreeResults after qualifying them if (argListWithOneByRefParamListener != null) { - result.AddRange(argListWithOneByRefParamListener.Contexts.Select(context => new QualifiedContext(componentTreePair.Key, context))); + result.AddRange(argListWithOneByRefParamListener.Contexts.Select(context => new QualifiedContext(componentTreePair.Key, context))); } if (emptyStringLiteralListener != null) { diff --git a/RetailCoder.VBE/Inspections/ProcedureCanBeWrittenAsFunctionInspection.cs b/RetailCoder.VBE/Inspections/ProcedureCanBeWrittenAsFunctionInspection.cs index 295683bd47..fdd6b77ee1 100644 --- a/RetailCoder.VBE/Inspections/ProcedureCanBeWrittenAsFunctionInspection.cs +++ b/RetailCoder.VBE/Inspections/ProcedureCanBeWrittenAsFunctionInspection.cs @@ -27,7 +27,7 @@ public ProcedureCanBeWrittenAsFunctionInspection(RubberduckParserState state) public override string Description { get { return InspectionsUI.ProcedureCanBeWrittenAsFunctionInspectionName; } } public override CodeInspectionType InspectionType { get { return CodeInspectionType.LanguageOpportunities; } } - public IEnumerable> ParseTreeResults { get { return _results.OfType>(); } } + public IEnumerable> ParseTreeResults { get { return _results.OfType>(); } } public void SetResults(IEnumerable results) { @@ -62,17 +62,17 @@ public override IEnumerable GetInspectionResults() .Select(result => new ProcedureCanBeWrittenAsFunctionInspectionResult( this, State, - new QualifiedContext(result.QualifiedName,result.Context.GetChild(0)), + new QualifiedContext(result.QualifiedName,result.Context.GetChild(0)), new QualifiedContext(result.QualifiedName, (VBAParser.SubStmtContext)result.Context)) ); } public class SingleByRefParamArgListListener : VBAParserBaseListener { - private readonly IList _contexts = new List(); - public IEnumerable Contexts { get { return _contexts; } } + private readonly IList _contexts = new List(); + public IEnumerable Contexts { get { return _contexts; } } - public override void ExitArgList(VBAParser.SubstmtContext context) + public override void ExitArgList(VBAParser.ArgListContext context) { var args = context.arg(); if (args != null && args.All(a => a.PARAMARRAY() == null && a.LPAREN() == null) && args.Count(a => a.BYREF() != null || (a.BYREF() == null && a.BYVAL() == null)) == 1) diff --git a/RetailCoder.VBE/Inspections/QuickFixes/DeclareAsExplicitVariantQuickFix.cs b/RetailCoder.VBE/Inspections/QuickFixes/DeclareAsExplicitVariantQuickFix.cs index 4402e41d9e..eeb40f057c 100644 --- a/RetailCoder.VBE/Inspections/QuickFixes/DeclareAsExplicitVariantQuickFix.cs +++ b/RetailCoder.VBE/Inspections/QuickFixes/DeclareAsExplicitVariantQuickFix.cs @@ -68,9 +68,9 @@ private string DeclareExplicitVariant(VBAParser.ArgContext context, out string i var fix = string.Empty; foreach (var child in memberContext.children) { - if (child is VBAParser.SubstmtContext) + if (child is VBAParser.ArgListContext) { - foreach (var tree in ((VBAParser.SubstmtContext) child).children) + foreach (var tree in ((VBAParser.ArgListContext) child).children) { if (tree.Equals(context)) { diff --git a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs index ada7749208..28ec59127a 100644 --- a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs +++ b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs @@ -39,9 +39,9 @@ private ArgContext GetArgContextForIdentifier(ParserRuleContext context, string var procStmtCtxChildren = procStmtCtx.children; for (int idx = 0; idx < procStmtCtxChildren.Count; idx++) { - if (procStmtCtxChildren[idx] is SubstmtContext) + if (procStmtCtxChildren[idx] is ArgListContext) { - var procStmtCtxChild = (SubstmtContext)procStmtCtxChildren[idx]; + var procStmtCtxChild = (ArgListContext)procStmtCtxChildren[idx]; var arg = procStmtCtxChild.children; for (int idx2 = 0; idx2 < arg.Count; idx2++) { diff --git a/RetailCoder.VBE/Inspections/Results/ProcedureCanBeWrittenAsFunctionInspectionResult.cs b/RetailCoder.VBE/Inspections/Results/ProcedureCanBeWrittenAsFunctionInspectionResult.cs index 23b2512f60..db9e599391 100644 --- a/RetailCoder.VBE/Inspections/Results/ProcedureCanBeWrittenAsFunctionInspectionResult.cs +++ b/RetailCoder.VBE/Inspections/Results/ProcedureCanBeWrittenAsFunctionInspectionResult.cs @@ -16,12 +16,12 @@ namespace Rubberduck.Inspections.Results public class ProcedureCanBeWrittenAsFunctionInspectionResult : InspectionResultBase { private IEnumerable _quickFixes; - private readonly QualifiedContext _argListQualifiedContext; + private readonly QualifiedContext _argListQualifiedContext; private readonly QualifiedContext _subStmtQualifiedContext; private readonly RubberduckParserState _state; public ProcedureCanBeWrittenAsFunctionInspectionResult(IInspection inspection, RubberduckParserState state, - QualifiedContext argListQualifiedContext, QualifiedContext subStmtQualifiedContext) + QualifiedContext argListQualifiedContext, QualifiedContext subStmtQualifiedContext) : base(inspection, subStmtQualifiedContext.ModuleName, subStmtQualifiedContext.Context.subroutineName()) { _target = state.AllUserDeclarations.Single(declaration => declaration.DeclarationType == DeclarationType.Procedure @@ -57,14 +57,14 @@ public class ChangeProcedureToFunction : QuickFixBase public override bool CanFixInProject { get { return false; } } private readonly RubberduckParserState _state; - private readonly QualifiedContext _argListQualifiedContext; + private readonly QualifiedContext _argListQualifiedContext; private readonly QualifiedContext _subStmtQualifiedContext; private readonly QualifiedContext _argQualifiedContext; private int _lineOffset; public ChangeProcedureToFunction(RubberduckParserState state, - QualifiedContext argListQualifiedContext, + QualifiedContext argListQualifiedContext, QualifiedContext subStmtQualifiedContext, QualifiedSelection selection) : base(subStmtQualifiedContext.Context, selection, InspectionsUI.ProcedureShouldBeFunctionInspectionQuickFix) diff --git a/RetailCoder.VBE/Navigation/CodeExplorer/CodeExplorerMemberViewModel.cs b/RetailCoder.VBE/Navigation/CodeExplorer/CodeExplorerMemberViewModel.cs index d6416a2b1d..938cfb602c 100644 --- a/RetailCoder.VBE/Navigation/CodeExplorer/CodeExplorerMemberViewModel.cs +++ b/RetailCoder.VBE/Navigation/CodeExplorer/CodeExplorerMemberViewModel.cs @@ -117,7 +117,7 @@ public override string NameWithSignature } var context = - _declaration.Context.children.FirstOrDefault(d => d is VBAParser.SubstmtContext) as VBAParser.SubstmtContext; + _declaration.Context.children.FirstOrDefault(d => d is VBAParser.ArgListContext) as VBAParser.ArgListContext; if (context == null) { diff --git a/RetailCoder.VBE/Refactorings/IntroduceParameter/IntroduceParameterRefactoring.cs b/RetailCoder.VBE/Refactorings/IntroduceParameter/IntroduceParameterRefactoring.cs index 10db282c5b..914c9eaa46 100644 --- a/RetailCoder.VBE/Refactorings/IntroduceParameter/IntroduceParameterRefactoring.cs +++ b/RetailCoder.VBE/Refactorings/IntroduceParameter/IntroduceParameterRefactoring.cs @@ -142,7 +142,7 @@ private void UpdateSignature(Declaration targetVariable) var functionDeclaration = _declarations.FindTarget(targetVariable.QualifiedSelection, ValidDeclarationTypes); var proc = (dynamic)functionDeclaration.Context; - var paramList = (VBAParser.SubstmtContext)proc.argList(); + var paramList = (VBAParser.ArgListContext)proc.argList(); var module = functionDeclaration.QualifiedName.QualifiedModuleName.Component.CodeModule; { var interfaceImplementation = GetInterfaceImplementation(functionDeclaration); @@ -182,14 +182,14 @@ private void UpdateSignature(Declaration targetVariable) private void UpdateSignature(Declaration targetMethod, Declaration targetVariable) { var proc = (dynamic)targetMethod.Context; - var paramList = (VBAParser.SubstmtContext)proc.argList(); + var paramList = (VBAParser.ArgListContext)proc.argList(); var module = targetMethod.QualifiedName.QualifiedModuleName.Component.CodeModule; { AddParameter(targetMethod, targetVariable, paramList, module); } } - private void AddParameter(Declaration targetMethod, Declaration targetVariable, VBAParser.SubstmtContext paramList, ICodeModule module) + private void AddParameter(Declaration targetMethod, Declaration targetVariable, VBAParser.ArgListContext paramList, ICodeModule module) { var argList = paramList.arg(); var lastParam = argList.LastOrDefault(); diff --git a/RetailCoder.VBE/Refactorings/RemoveParameters/RemoveParametersRefactoring.cs b/RetailCoder.VBE/Refactorings/RemoveParameters/RemoveParametersRefactoring.cs index 9aa6eb2ed7..6d9893ceea 100644 --- a/RetailCoder.VBE/Refactorings/RemoveParameters/RemoveParametersRefactoring.cs +++ b/RetailCoder.VBE/Refactorings/RemoveParameters/RemoveParametersRefactoring.cs @@ -279,7 +279,7 @@ private string GetOldSignature(Declaration target) private void AdjustSignatures() { var proc = (dynamic)_model.TargetDeclaration.Context; - var paramList = (VBAParser.SubstmtContext)proc.argList(); + var paramList = (VBAParser.ArgListContext)proc.argList(); var module = _model.TargetDeclaration.QualifiedName.QualifiedModuleName.Component.CodeModule; { // if we are adjusting a property getter, check if we need to adjust the letter/setter too @@ -336,23 +336,23 @@ private void AdjustSignatures(Declaration declaration) var proc = (dynamic)declaration.Context.Parent; var module = declaration.QualifiedName.QualifiedModuleName.Component.CodeModule; { - VBAParser.SubstmtContext paramList; + VBAParser.ArgListContext paramList; if (declaration.DeclarationType == DeclarationType.PropertySet || declaration.DeclarationType == DeclarationType.PropertyLet) { - paramList = (VBAParser.SubstmtContext)proc.children[0].argList(); + paramList = (VBAParser.ArgListContext)proc.children[0].argList(); } else { - paramList = (VBAParser.SubstmtContext)proc.subStmt().argList(); + paramList = (VBAParser.ArgListContext)proc.subStmt().argList(); } RemoveSignatureParameters(declaration, paramList, module); } } - private void RemoveSignatureParameters(Declaration target, VBAParser.SubstmtContext paramList, ICodeModule module) + private void RemoveSignatureParameters(Declaration target, VBAParser.ArgListContext paramList, ICodeModule module) { // property set/let have one more parameter than is listed in the getter parameters var nonRemovedParamNames = paramList.arg().Where((a, s) => s >= _model.Parameters.Count || !_model.Parameters[s].IsRemoved).Select(s => s.GetText()); diff --git a/RetailCoder.VBE/Refactorings/Rename/RenameRefactoring.cs b/RetailCoder.VBE/Refactorings/Rename/RenameRefactoring.cs index d591fb9650..a7c5d94d12 100644 --- a/RetailCoder.VBE/Refactorings/Rename/RenameRefactoring.cs +++ b/RetailCoder.VBE/Refactorings/Rename/RenameRefactoring.cs @@ -335,7 +335,7 @@ private void RenameDeclaration(Declaration target, string newName) if (target.DeclarationType == DeclarationType.Parameter) { - var argList = (VBAParser.SubstmtContext)target.Context.Parent; + var argList = (VBAParser.ArgListContext)target.Context.Parent; var lineNum = argList.GetSelection().LineCount; // delete excess lines to prevent removing our own changes diff --git a/RetailCoder.VBE/Refactorings/ReorderParameters/ReorderParametersModel.cs b/RetailCoder.VBE/Refactorings/ReorderParameters/ReorderParametersModel.cs index 5b0b565dff..316761ead8 100644 --- a/RetailCoder.VBE/Refactorings/ReorderParameters/ReorderParametersModel.cs +++ b/RetailCoder.VBE/Refactorings/ReorderParameters/ReorderParametersModel.cs @@ -50,7 +50,7 @@ private void LoadParameters() Parameters.Clear(); var procedure = (dynamic)TargetDeclaration.Context; - var argList = (VBAParser.SubstmtContext)procedure.argList(); + var argList = (VBAParser.ArgListContext)procedure.argList(); var args = argList.arg(); var index = 0; diff --git a/RetailCoder.VBE/Refactorings/ReorderParameters/ReorderParametersRefactoring.cs b/RetailCoder.VBE/Refactorings/ReorderParameters/ReorderParametersRefactoring.cs index 7d8b289e10..4b5a678729 100644 --- a/RetailCoder.VBE/Refactorings/ReorderParameters/ReorderParametersRefactoring.cs +++ b/RetailCoder.VBE/Refactorings/ReorderParameters/ReorderParametersRefactoring.cs @@ -189,7 +189,7 @@ private void RewriteCall(VBAParser.ArgumentListContext paramList, ICodeModule mo private void AdjustSignatures() { var proc = (dynamic)_model.TargetDeclaration.Context; - var paramList = (VBAParser.SubstmtContext)proc.argList(); + var paramList = (VBAParser.ArgListContext)proc.argList(); var module = _model.TargetDeclaration.QualifiedName.QualifiedModuleName.Component.CodeModule; // if we are reordering a property getter, check if we need to reorder a letter/setter too @@ -242,22 +242,22 @@ private void AdjustSignatures(Declaration declaration) var proc = (dynamic)declaration.Context.Parent; var module = declaration.QualifiedName.QualifiedModuleName.Component.CodeModule; { - VBAParser.SubstmtContext paramList; + VBAParser.ArgListContext paramList; if (declaration.DeclarationType == DeclarationType.PropertySet || declaration.DeclarationType == DeclarationType.PropertyLet) { - paramList = (VBAParser.SubstmtContext)proc.children[0].argList(); + paramList = (VBAParser.ArgListContext)proc.children[0].argList(); } else { - paramList = (VBAParser.SubstmtContext)proc.subStmt().argList(); + paramList = (VBAParser.ArgListContext)proc.subStmt().argList(); } RewriteSignature(declaration, paramList, module); } } - private void RewriteSignature(Declaration target, VBAParser.SubstmtContext paramList, ICodeModule module) + private void RewriteSignature(Declaration target, VBAParser.ArgListContext paramList, ICodeModule module) { var parameters = paramList.arg().Select((s, i) => new {Index = i, Text = s.GetText()}).ToList(); diff --git a/Rubberduck.Parsing/Grammar/VBAParser.cs b/Rubberduck.Parsing/Grammar/VBAParser.cs index a1d0eaa1cf..878c761d51 100644 --- a/Rubberduck.Parsing/Grammar/VBAParser.cs +++ b/Rubberduck.Parsing/Grammar/VBAParser.cs @@ -5512,8 +5512,8 @@ public IdentifierContext identifier() { public VisibilityContext visibility() { return GetRuleContext(0); } - public SubstmtContext argList() { - return GetRuleContext(0); + public ArgListContext argList() { + return GetRuleContext(0); } public ITerminalNode LIB() { return GetToken(VBAParser.LIB, 0); } public ITerminalNode FUNCTION() { return GetToken(VBAParser.FUNCTION, 0); } @@ -5631,7 +5631,7 @@ public DeclareStmtContext declareStmt() { return _localctx; } - public partial class SubstmtContext : ParserRuleContext { + public partial class ArgListContext : ParserRuleContext { public ArgContext arg(int i) { return GetRuleContext(i); } @@ -5650,7 +5650,7 @@ public IReadOnlyList arg() { public ITerminalNode COMMA(int i) { return GetToken(VBAParser.COMMA, i); } - public SubstmtContext(ParserRuleContext parent, int invokingState) + public ArgListContext(ParserRuleContext parent, int invokingState) : base(parent, invokingState) { } @@ -5671,8 +5671,8 @@ public override TResult Accept(IParseTreeVisitor visitor) { } [RuleVersion(0)] - public SubstmtContext argList() { - SubstmtContext _localctx = new SubstmtContext(_ctx, State); + public ArgListContext argList() { + ArgListContext _localctx = new ArgListContext(_ctx, State); EnterRule(_localctx, 140, RULE_argList); int _la; try { @@ -7041,8 +7041,8 @@ public ErrorStmtContext errorStmt() { } public partial class EventStmtContext : ParserRuleContext { - public SubstmtContext argList() { - return GetRuleContext(0); + public ArgListContext argList() { + return GetRuleContext(0); } public WhiteSpaceContext whiteSpace(int i) { return GetRuleContext(i); @@ -7350,8 +7350,8 @@ public ForNextStmtContext forNextStmt() { } public partial class FunctionStmtContext : ParserRuleContext { - public SubstmtContext argList() { - return GetRuleContext(0); + public ArgListContext argList() { + return GetRuleContext(0); } public FunctionNameContext functionName() { return GetRuleContext(0); @@ -8930,8 +8930,8 @@ public OnGoSubStmtContext onGoSubStmt() { } public partial class PropertyGetStmtContext : ParserRuleContext { - public SubstmtContext argList() { - return GetRuleContext(0); + public ArgListContext argList() { + return GetRuleContext(0); } public FunctionNameContext functionName() { return GetRuleContext(0); @@ -9048,8 +9048,8 @@ public PropertyGetStmtContext propertyGetStmt() { } public partial class PropertySetStmtContext : ParserRuleContext { - public SubstmtContext argList() { - return GetRuleContext(0); + public ArgListContext argList() { + return GetRuleContext(0); } public WhiteSpaceContext whiteSpace(int i) { return GetRuleContext(i); @@ -9155,8 +9155,8 @@ public PropertySetStmtContext propertySetStmt() { public partial class PropertyLetStmtContext : ParserRuleContext { public ITerminalNode PROPERTY_LET() { return GetToken(VBAParser.PROPERTY_LET, 0); } - public SubstmtContext argList() { - return GetRuleContext(0); + public ArgListContext argList() { + return GetRuleContext(0); } public WhiteSpaceContext whiteSpace(int i) { return GetRuleContext(i); @@ -10999,8 +10999,8 @@ public SetStmtContext setStmt() { } public partial class SubStmtContext : ParserRuleContext { - public SubstmtContext argList() { - return GetRuleContext(0); + public ArgListContext argList() { + return GetRuleContext(0); } public WhiteSpaceContext whiteSpace(int i) { return GetRuleContext(i); diff --git a/Rubberduck.Parsing/Grammar/VBAParserBaseListener.cs b/Rubberduck.Parsing/Grammar/VBAParserBaseListener.cs index 45a6c12a26..7c144d4a73 100644 --- a/Rubberduck.Parsing/Grammar/VBAParserBaseListener.cs +++ b/Rubberduck.Parsing/Grammar/VBAParserBaseListener.cs @@ -2534,13 +2534,13 @@ public virtual void ExitSelectStartValue([NotNull] VBAParser.SelectStartValueCon /// The default implementation does nothing. /// /// The parse tree. - public virtual void EnterArgList([NotNull] VBAParser.SubstmtContext context) { } + public virtual void EnterArgList([NotNull] VBAParser.ArgListContext context) { } /// /// Exit a parse tree produced by . /// The default implementation does nothing. /// /// The parse tree. - public virtual void ExitArgList([NotNull] VBAParser.SubstmtContext context) { } + public virtual void ExitArgList([NotNull] VBAParser.ArgListContext context) { } /// /// Enter a parse tree produced by . diff --git a/Rubberduck.Parsing/Grammar/VBAParserBaseVisitor.cs b/Rubberduck.Parsing/Grammar/VBAParserBaseVisitor.cs index 4a87ec6339..edf86d5823 100644 --- a/Rubberduck.Parsing/Grammar/VBAParserBaseVisitor.cs +++ b/Rubberduck.Parsing/Grammar/VBAParserBaseVisitor.cs @@ -2153,7 +2153,7 @@ public partial class VBAParserBaseVisitor : AbstractParseTreeVisitor /// The parse tree. /// The visitor result. - public virtual Result VisitArgList([NotNull] VBAParser.SubstmtContext context) { return VisitChildren(context); } + public virtual Result VisitArgList([NotNull] VBAParser.ArgListContext context) { return VisitChildren(context); } /// /// Visit a parse tree produced by . diff --git a/Rubberduck.Parsing/Grammar/VBAParserListener.cs b/Rubberduck.Parsing/Grammar/VBAParserListener.cs index 11dec62036..9489714147 100644 --- a/Rubberduck.Parsing/Grammar/VBAParserListener.cs +++ b/Rubberduck.Parsing/Grammar/VBAParserListener.cs @@ -2207,12 +2207,12 @@ public interface IVBAParserListener : IParseTreeListener { /// Enter a parse tree produced by . /// /// The parse tree. - void EnterArgList([NotNull] VBAParser.SubstmtContext context); + void EnterArgList([NotNull] VBAParser.ArgListContext context); /// /// Exit a parse tree produced by . /// /// The parse tree. - void ExitArgList([NotNull] VBAParser.SubstmtContext context); + void ExitArgList([NotNull] VBAParser.ArgListContext context); /// /// Enter a parse tree produced by . diff --git a/Rubberduck.Parsing/Grammar/VBAParserVisitor.cs b/Rubberduck.Parsing/Grammar/VBAParserVisitor.cs index 9a0af15145..0c2912e460 100644 --- a/Rubberduck.Parsing/Grammar/VBAParserVisitor.cs +++ b/Rubberduck.Parsing/Grammar/VBAParserVisitor.cs @@ -1410,7 +1410,7 @@ public interface IVBAParserVisitor : IParseTreeVisitor { /// /// The parse tree. /// The visitor result. - Result VisitArgList([NotNull] VBAParser.SubstmtContext context); + Result VisitArgList([NotNull] VBAParser.ArgListContext context); /// /// Visit a parse tree produced by . diff --git a/Rubberduck.Parsing/Symbols/DeclarationSymbolsListener.cs b/Rubberduck.Parsing/Symbols/DeclarationSymbolsListener.cs index 1980f82792..0954055c2c 100644 --- a/Rubberduck.Parsing/Symbols/DeclarationSymbolsListener.cs +++ b/Rubberduck.Parsing/Symbols/DeclarationSymbolsListener.cs @@ -664,7 +664,7 @@ public override void ExitDeclareStmt(VBAParser.DeclareStmtContext context) SetCurrentScope(); } - public override void EnterArgList(VBAParser.SubstmtContext context) + public override void EnterArgList(VBAParser.ArgListContext context) { var args = context.arg(); foreach (var argContext in args) From b17da1eea965591ed3844a4ac115f65b1adf612d Mon Sep 17 00:00:00 2001 From: Brian Zenger Date: Thu, 16 Feb 2017 00:19:37 -0800 Subject: [PATCH 12/20] Clean-up, renaming --- .../QuickFixes/PassParameterByReferenceQuickFix.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs index 28ec59127a..b4151e0c14 100644 --- a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs +++ b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs @@ -41,8 +41,8 @@ private ArgContext GetArgContextForIdentifier(ParserRuleContext context, string { if (procStmtCtxChildren[idx] is ArgListContext) { - var procStmtCtxChild = (ArgListContext)procStmtCtxChildren[idx]; - var arg = procStmtCtxChild.children; + var argListContext = (ArgListContext)procStmtCtxChildren[idx]; + var arg = argListContext.children; for (int idx2 = 0; idx2 < arg.Count; idx2++) { if (arg[idx2] is ArgContext) @@ -99,13 +99,13 @@ private string GenerateByRefReplacementLine(TerminalNodeImpl terminalNodeImpl) return ReplaceAtIndex(byValTokenLine, Tokens.ByVal, Tokens.ByRef, terminalNodeImpl.Symbol.Column); } - private string ReplaceAtIndex(string input, string toReplace, string replacement, int index) + private string ReplaceAtIndex(string input, string toReplace, string replacement, int startIndex) { - int stopIndex = index + toReplace.Length; - var prefix = input.Substring(0, index); + int stopIndex = startIndex + toReplace.Length; + var prefix = input.Substring(0, startIndex); var suffix = input.Substring(stopIndex + 1); - var target = input.Substring(index, stopIndex - index + 1); - return prefix + target.Replace(toReplace, replacement) + suffix; + var tokenToBeReplaced = input.Substring(startIndex, stopIndex - startIndex + 1); + return prefix + tokenToBeReplaced.Replace(toReplace, replacement) + suffix; } private void ReplaceModuleLine(int lineNumber, string replacementLine) { From 7c5f915820b717710d5ed0eb9aa5f56ff52bad3f Mon Sep 17 00:00:00 2001 From: Brian Zenger Date: Thu, 16 Feb 2017 00:24:19 -0800 Subject: [PATCH 13/20] more renaming --- .../QuickFixes/PassParameterByReferenceQuickFix.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs index b4151e0c14..6062a184a4 100644 --- a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs +++ b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs @@ -43,14 +43,14 @@ private ArgContext GetArgContextForIdentifier(ParserRuleContext context, string { var argListContext = (ArgListContext)procStmtCtxChildren[idx]; var arg = argListContext.children; - for (int idx2 = 0; idx2 < arg.Count; idx2++) + for (int idxArgListCtx = 0; idxArgListCtx < arg.Count; idxArgListCtx++) { - if (arg[idx2] is ArgContext) + if (arg[idxArgListCtx] is ArgContext) { - var name = GetIdentifierNameFor((ArgContext)arg[idx2]); + var name = GetIdentifierNameFor((ArgContext)arg[idxArgListCtx]); if (name.Equals(identifier)) { - return (ArgContext)arg[idx2]; + return (ArgContext)arg[idxArgListCtx]; } } } From c82459ea793226340337cf5e1ed3d998b7e2c88d Mon Sep 17 00:00:00 2001 From: Brian Zenger Date: Thu, 16 Feb 2017 13:39:52 -0800 Subject: [PATCH 14/20] Updated per intial review of ByRef QuickFix comments --- .../PassParameterByReferenceQuickFix.cs | 108 +++++++----------- Rubberduck.Parsing/Symbols/Identifier.cs | 5 + .../AssignedByValParameterInspectionTests.cs | 13 +++ 3 files changed, 58 insertions(+), 68 deletions(-) diff --git a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs index 6062a184a4..3d8382a093 100644 --- a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs +++ b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs @@ -5,7 +5,7 @@ using Rubberduck.Parsing.Grammar; using Rubberduck.Parsing.Symbols; using Rubberduck.VBEditor; -using static Rubberduck.Parsing.Grammar.VBAParser; +using System.Linq; namespace Rubberduck.Inspections.QuickFixes { @@ -24,80 +24,34 @@ public PassParameterByReferenceQuickFix(Declaration target, QualifiedSelection s public override void Fix() { - var argCtxt = GetArgContextForIdentifier(Context, _target.IdentifierName); + var argCtxt = GetArgContextForIdentifier(Context.Parent.Parent, _target.IdentifierName); - var terminalNodeImpl = GetByValNodeForArgCtx(argCtxt); + var terminalNode = argCtxt.BYVAL(); - var replacementLine = GenerateByRefReplacementLine(terminalNodeImpl); + var replacementLine = GenerateByRefReplacementLine(terminalNode); - ReplaceModuleLine(terminalNodeImpl.Symbol.Line, replacementLine); + ReplaceModuleLine(terminalNode.Symbol.Line, replacementLine); } - private ArgContext GetArgContextForIdentifier(ParserRuleContext context, string identifier) + private VBAParser.ArgContext GetArgContextForIdentifier(RuleContext context, string identifier) { - var procStmtCtx = (ParserRuleContext)context.Parent.Parent; - var procStmtCtxChildren = procStmtCtx.children; - for (int idx = 0; idx < procStmtCtxChildren.Count; idx++) - { - if (procStmtCtxChildren[idx] is ArgListContext) - { - var argListContext = (ArgListContext)procStmtCtxChildren[idx]; - var arg = argListContext.children; - for (int idxArgListCtx = 0; idxArgListCtx < arg.Count; idxArgListCtx++) - { - if (arg[idxArgListCtx] is ArgContext) - { - var name = GetIdentifierNameFor((ArgContext)arg[idxArgListCtx]); - if (name.Equals(identifier)) - { - return (ArgContext)arg[idxArgListCtx]; - } - } - } - } - } - return null; - } - private string GetIdentifierNameFor(ArgContext argCtxt) - { - var argCtxtChild = argCtxt.children; - var idRef = GetUnRestrictedIdentifierCtx(argCtxt); - return idRef.GetText(); + var argList = GetArgListForContext(context); + return argList.arg().SingleOrDefault(parameter => + Identifier.GetName(parameter).Equals(identifier) + || Identifier.GetName(parameter).Equals("[" + identifier + "]")); } - private UnrestrictedIdentifierContext GetUnRestrictedIdentifierCtx(ArgContext argCtxt) + private string GenerateByRefReplacementLine(ITerminalNode terminalNode) { - var argCtxtChild = argCtxt.children; - for (int idx = 0; idx < argCtxtChild.Count; idx++) - { - if (argCtxtChild[idx] is UnrestrictedIdentifierContext) - { - return (UnrestrictedIdentifierContext)argCtxtChild[idx]; - } - } - return null; - } - private TerminalNodeImpl GetByValNodeForArgCtx(ArgContext argCtxt) - { - var argCtxtChild = argCtxt.children; - for (int idx = 0; idx < argCtxtChild.Count; idx++) - { - if (argCtxtChild[idx] is TerminalNodeImpl) - { - var candidate = (TerminalNodeImpl)argCtxtChild[idx]; - if (candidate.Symbol.Text.Equals(Tokens.ByVal)) - { - return candidate; - } - } - } - return null; + var module = Selection.QualifiedName.Component.CodeModule; + var byValTokenLine = module.GetLines(terminalNode.Symbol.Line, 1); + + return ReplaceAtIndex(byValTokenLine, Tokens.ByVal, Tokens.ByRef, terminalNode.Symbol.Column); } - private string GenerateByRefReplacementLine(TerminalNodeImpl terminalNodeImpl) + private void ReplaceModuleLine(int lineNumber, string replacementLine) { var module = Selection.QualifiedName.Component.CodeModule; - var byValTokenLine = module.GetLines(terminalNodeImpl.Symbol.Line, 1); - - return ReplaceAtIndex(byValTokenLine, Tokens.ByVal, Tokens.ByRef, terminalNodeImpl.Symbol.Column); + module.DeleteLines(lineNumber); + module.InsertLines(lineNumber, replacementLine); } private string ReplaceAtIndex(string input, string toReplace, string replacement, int startIndex) { @@ -107,11 +61,29 @@ private string ReplaceAtIndex(string input, string toReplace, string replacement var tokenToBeReplaced = input.Substring(startIndex, stopIndex - startIndex + 1); return prefix + tokenToBeReplaced.Replace(toReplace, replacement) + suffix; } - private void ReplaceModuleLine(int lineNumber, string replacementLine) + private VBAParser.ArgListContext GetArgListForContext(RuleContext context) { - var module = Selection.QualifiedName.Component.CodeModule; - module.DeleteLines(lineNumber); - module.InsertLines(lineNumber, replacementLine); + if (context is VBAParser.SubStmtContext) + { + return ((VBAParser.SubStmtContext)context).argList(); + } + else if (context is VBAParser.FunctionStmtContext) + { + return ((VBAParser.FunctionStmtContext)context).argList(); + } + else if (context is VBAParser.PropertyLetStmtContext) + { + return ((VBAParser.PropertyLetStmtContext)context).argList(); + } + else if (context is VBAParser.PropertyGetStmtContext) + { + return ((VBAParser.PropertyGetStmtContext)context).argList(); + } + else if (context is VBAParser.PropertySetStmtContext) + { + return ((VBAParser.PropertySetStmtContext)context).argList(); + } + return null; } } } \ No newline at end of file diff --git a/Rubberduck.Parsing/Symbols/Identifier.cs b/Rubberduck.Parsing/Symbols/Identifier.cs index 1386e73442..ba3021e9d4 100644 --- a/Rubberduck.Parsing/Symbols/Identifier.cs +++ b/Rubberduck.Parsing/Symbols/Identifier.cs @@ -7,6 +7,11 @@ namespace Rubberduck.Parsing.Symbols { public static class Identifier { + public static string GetName(VBAParser.ArgContext context) + { + return GetName(context.unrestrictedIdentifier()); + } + public static string GetName(VBAParser.FunctionNameContext context) { return GetName(context.identifier()); diff --git a/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs b/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs index b1019a32e6..196dd77bed 100644 --- a/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs +++ b/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs @@ -240,6 +240,19 @@ End Sub quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode); Assert.AreEqual(expectedCode, quickFixResult); + inputCode = +@"Private Sub Foo(ByVal barByVal As Long, ByVal barTwoon As Long, ByVal [barTwo] As Long) +barTwo = 42 +End Sub +"; + expectedCode = +@"Private Sub Foo(ByVal barByVal As Long, ByVal barTwoon As Long, ByRef [barTwo] As Long) +barTwo = 42 +End Sub +"; + + quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode); + Assert.AreEqual(expectedCode, quickFixResult); } From 14e24e83b1aad3b3200348cdaa3ade40eee180fa Mon Sep 17 00:00:00 2001 From: Brian Zenger Date: Thu, 16 Feb 2017 13:49:42 -0800 Subject: [PATCH 15/20] removed the bracketed parameter code and test --- .../QuickFixes/PassParameterByReferenceQuickFix.cs | 3 +-- .../AssignedByValParameterInspectionTests.cs | 13 ------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs index 3d8382a093..60c606608b 100644 --- a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs +++ b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs @@ -37,8 +37,7 @@ private VBAParser.ArgContext GetArgContextForIdentifier(RuleContext context, str { var argList = GetArgListForContext(context); return argList.arg().SingleOrDefault(parameter => - Identifier.GetName(parameter).Equals(identifier) - || Identifier.GetName(parameter).Equals("[" + identifier + "]")); + Identifier.GetName(parameter).Equals(identifier)); } private string GenerateByRefReplacementLine(ITerminalNode terminalNode) { diff --git a/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs b/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs index 196dd77bed..b1019a32e6 100644 --- a/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs +++ b/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs @@ -240,19 +240,6 @@ End Sub quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode); Assert.AreEqual(expectedCode, quickFixResult); - inputCode = -@"Private Sub Foo(ByVal barByVal As Long, ByVal barTwoon As Long, ByVal [barTwo] As Long) -barTwo = 42 -End Sub -"; - expectedCode = -@"Private Sub Foo(ByVal barByVal As Long, ByVal barTwoon As Long, ByRef [barTwo] As Long) -barTwo = 42 -End Sub -"; - - quickFixResult = ApplyPassParameterByReferenceQuickFixToVBAFragment(inputCode); - Assert.AreEqual(expectedCode, quickFixResult); } From 6b7f8fc195738a77ae7c0c1fd8a0bdb43ac596b4 Mon Sep 17 00:00:00 2001 From: comintern Date: Thu, 16 Feb 2017 19:25:54 -0600 Subject: [PATCH 16/20] Fix bracket escaping in end of line comments. Closes #2696 --- Rubberduck.SmartIndenter/AbsoluteCodeLine.cs | 33 +++++++------------ .../StringLiteralAndBracketEscaper.cs | 25 ++++++++++++-- .../SmartIndenter/MiscAndCornerCaseTests.cs | 25 ++++++++++++++ 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/Rubberduck.SmartIndenter/AbsoluteCodeLine.cs b/Rubberduck.SmartIndenter/AbsoluteCodeLine.cs index 19d746e85b..bb2ccca39b 100644 --- a/Rubberduck.SmartIndenter/AbsoluteCodeLine.cs +++ b/Rubberduck.SmartIndenter/AbsoluteCodeLine.cs @@ -10,8 +10,6 @@ namespace Rubberduck.SmartIndenter internal class AbsoluteCodeLine { private const string StupidLineEnding = ": _"; - private static readonly Regex StringReplaceRegex = new Regex(StringLiteralAndBracketEscaper.StringPlaceholder.ToString(CultureInfo.InvariantCulture)); - private static readonly Regex BracketReplaceRegex = new Regex(StringLiteralAndBracketEscaper.BracketPlaceholder.ToString(CultureInfo.InvariantCulture)); private static readonly Regex LineNumberRegex = new Regex(@"^(?(-?\d+)|(&H[0-9A-F]{1,8}))\s+(?.*)", RegexOptions.ExplicitCapture); private static readonly Regex EndOfLineCommentRegex = new Regex(@"^(?!(Rem\s)|('))(?[^']*)(\s(?'.*))$", RegexOptions.ExplicitCapture); private static readonly Regex ProcedureStartRegex = new Regex(@"^(Public\s|Private\s|Friend\s)?(Static\s)?(Sub|Function|Property\s(Let|Get|Set))\s"); @@ -61,8 +59,8 @@ public AbsoluteCodeLine(string code, IIndenterSettings settings, AbsoluteCodeLin ExtractLineNumber(); ExtractEndOfLineComment(); - _code = Regex.Replace(_code, StringLiteralAndBracketEscaper.StringPlaceholder + "+", StringLiteralAndBracketEscaper.StringPlaceholder.ToString(CultureInfo.InvariantCulture)); - _code = Regex.Replace(_code, StringLiteralAndBracketEscaper.BracketPlaceholder + "+", StringLiteralAndBracketEscaper.BracketPlaceholder.ToString(CultureInfo.InvariantCulture)).Trim(); + //_code = Regex.Replace(_code, StringLiteralAndBracketEscaper.StringPlaceholder + "+", StringLiteralAndBracketEscaper.StringPlaceholder.ToString(CultureInfo.InvariantCulture)); + //_code = Regex.Replace(_code, StringLiteralAndBracketEscaper.BracketPlaceholder + "+", StringLiteralAndBracketEscaper.BracketPlaceholder.ToString(CultureInfo.InvariantCulture)).Trim(); _segments = _code.Split(new[] { ": " }, StringSplitOptions.None); } @@ -267,38 +265,29 @@ public string Indent(int indents, bool atProcStart, bool absolute = false) } var code = string.Join(": ", _segments); - if (_escaper.EscapedStrings.Any()) - { - code = _escaper.EscapedStrings.Aggregate(code, (current, literal) => StringReplaceRegex.Replace(current, literal, 1)); - } - if (_escaper.EscapedBrackets.Any()) - { - code = _escaper.EscapedBrackets.Aggregate(code, (current, expr) => BracketReplaceRegex.Replace(current, expr, 1)); - } - code = string.Join(string.Empty, number, new string(' ', gap), code); if (string.IsNullOrEmpty(EndOfLineComment)) { - return code + (_stupidLineEnding ? StupidLineEnding : string.Empty); + return _escaper.UnescapeIndented(code + (_stupidLineEnding ? StupidLineEnding : string.Empty)); } var position = Original.LastIndexOf(EndOfLineComment, StringComparison.Ordinal); switch (_settings.EndOfLineCommentStyle) { case EndOfLineCommentStyle.Absolute: - return string.Format("{0}{1}{2}{3}", code, new string(' ', Math.Max(position - code.Length, 1)), - EndOfLineComment, _stupidLineEnding ? StupidLineEnding : string.Empty); + return _escaper.UnescapeIndented(string.Format("{0}{1}{2}{3}", code, new string(' ', Math.Max(position - code.Length, 1)), + EndOfLineComment, _stupidLineEnding ? StupidLineEnding : string.Empty)); case EndOfLineCommentStyle.SameGap: var uncommented = Original.Substring(0, position - 1); - return string.Format("{0}{1}{2}{3}", code, new string(' ', uncommented.Length - uncommented.TrimEnd().Length + 1), - EndOfLineComment, _stupidLineEnding ? StupidLineEnding : string.Empty); + return _escaper.UnescapeIndented(string.Format("{0}{1}{2}{3}", code, new string(' ', uncommented.Length - uncommented.TrimEnd().Length + 1), + EndOfLineComment, _stupidLineEnding ? StupidLineEnding : string.Empty)); case EndOfLineCommentStyle.StandardGap: - return string.Format("{0}{1}{2}{3}", code, new string(' ', _settings.IndentSpaces * 2), EndOfLineComment, - _stupidLineEnding ? StupidLineEnding : string.Empty); + return _escaper.UnescapeIndented(string.Format("{0}{1}{2}{3}", code, new string(' ', _settings.IndentSpaces * 2), EndOfLineComment, + _stupidLineEnding ? StupidLineEnding : string.Empty)); case EndOfLineCommentStyle.AlignInColumn: var align = _settings.EndOfLineCommentColumnSpaceAlignment - code.Length; - return string.Format("{0}{1}{2}{3}", code, new string(' ', Math.Max(align - 1, 1)), EndOfLineComment, - _stupidLineEnding ? StupidLineEnding : string.Empty); + return _escaper.UnescapeIndented(string.Format("{0}{1}{2}{3}", code, new string(' ', Math.Max(align - 1, 1)), EndOfLineComment, + _stupidLineEnding ? StupidLineEnding : string.Empty)); default: throw new InvalidEnumArgumentException(); } diff --git a/Rubberduck.SmartIndenter/StringLiteralAndBracketEscaper.cs b/Rubberduck.SmartIndenter/StringLiteralAndBracketEscaper.cs index 7b98f8a26e..89d4430d3b 100644 --- a/Rubberduck.SmartIndenter/StringLiteralAndBracketEscaper.cs +++ b/Rubberduck.SmartIndenter/StringLiteralAndBracketEscaper.cs @@ -1,12 +1,17 @@ using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Text.RegularExpressions; namespace Rubberduck.SmartIndenter { internal class StringLiteralAndBracketEscaper { public const char StringPlaceholder = '\a'; - public const char BracketPlaceholder = '\x2'; + public const char BracketPlaceholder = '\x02'; + + private static readonly Regex StringReplaceRegex = new Regex("\a+"); + private static readonly Regex BracketReplaceRegex = new Regex("\x02+"); private readonly List _strings = new List(); private readonly List _brackets = new List(); @@ -18,6 +23,20 @@ internal class StringLiteralAndBracketEscaper public IEnumerable EscapedStrings { get { return _strings; } } public IEnumerable EscapedBrackets { get { return _brackets; } } + public string UnescapeIndented(string indented) + { + var code = indented; + if (_strings.Any()) + { + code = _strings.Aggregate(code, (current, literal) => StringReplaceRegex.Replace(current, literal, 1)); + } + if (_brackets.Any()) + { + code = _brackets.Aggregate(code, (current, expr) => BracketReplaceRegex.Replace(current, expr, 1)); + } + return code; + } + public StringLiteralAndBracketEscaper(string code) { _unescaped = code; @@ -63,8 +82,8 @@ public StringLiteralAndBracketEscaper(string code) continue; } bracketed = false; - _brackets.Add(new string(chars.Skip(brkpos).Take(c - brkpos).ToArray())); - for (var e = brkpos; e < c; e++) + _brackets.Add(new string(chars.Skip(brkpos).Take(c - brkpos + 1).ToArray())); + for (var e = brkpos; e <= c; e++) { chars[e] = BracketPlaceholder; } diff --git a/RubberduckTests/SmartIndenter/MiscAndCornerCaseTests.cs b/RubberduckTests/SmartIndenter/MiscAndCornerCaseTests.cs index 347e023475..fb54cc6459 100644 --- a/RubberduckTests/SmartIndenter/MiscAndCornerCaseTests.cs +++ b/RubberduckTests/SmartIndenter/MiscAndCornerCaseTests.cs @@ -746,6 +746,31 @@ public void BracketedIdentifiersWork() Assert.IsTrue(expected.SequenceEqual(actual)); } + //https://github.com/rubberduck-vba/Rubberduck/issues/2696 + [TestMethod] + // Broken in VB6 SmartIndenter. + [TestCategory("Indenter")] + public void BracketsInEndOfLineCommentsWork() + { + var code = new[] + { + "Public Sub Test()", + "Debug.Print \"foo\" \'update [foo].[bar] in the frob.", + "End Sub" + }; + + var expected = new[] + { + "Public Sub Test()", + " Debug.Print \"foo\" 'update [foo].[bar] in the frob.", + "End Sub" + }; + + var indenter = new Indenter(null, () => IndenterSettingsTests.GetMockIndenterSettings()); + var actual = indenter.Indent(code); + Assert.IsTrue(expected.SequenceEqual(actual)); + } + //https://github.com/rubberduck-vba/Rubberduck/issues/2604 [TestMethod] [TestCategory("Indenter")] From dc8d33f2201f5362c17be3c3de298aa272cf7f40 Mon Sep 17 00:00:00 2001 From: comintern Date: Thu, 16 Feb 2017 19:32:33 -0600 Subject: [PATCH 17/20] Indenter cleanup - commented code, dead file. --- Rubberduck.SmartIndenter/AbsoluteCodeLine.cs | 4 ---- Rubberduck.SmartIndenter/Rubberduck.SmartIndenter.csproj | 1 - Rubberduck.SmartIndenter/Selection.cs | 3 --- Rubberduck.SmartIndenter/StringLiteralAndBracketEscaper.cs | 1 - 4 files changed, 9 deletions(-) delete mode 100644 Rubberduck.SmartIndenter/Selection.cs diff --git a/Rubberduck.SmartIndenter/AbsoluteCodeLine.cs b/Rubberduck.SmartIndenter/AbsoluteCodeLine.cs index bb2ccca39b..e0cd943e37 100644 --- a/Rubberduck.SmartIndenter/AbsoluteCodeLine.cs +++ b/Rubberduck.SmartIndenter/AbsoluteCodeLine.cs @@ -31,8 +31,6 @@ internal class AbsoluteCodeLine private readonly bool _stupidLineEnding; private readonly string[] _segments; private readonly StringLiteralAndBracketEscaper _escaper; - //private List _strings; - //private List _brackets; public AbsoluteCodeLine(string code, IIndenterSettings settings) : this(code, settings, null) { } @@ -59,8 +57,6 @@ public AbsoluteCodeLine(string code, IIndenterSettings settings, AbsoluteCodeLin ExtractLineNumber(); ExtractEndOfLineComment(); - //_code = Regex.Replace(_code, StringLiteralAndBracketEscaper.StringPlaceholder + "+", StringLiteralAndBracketEscaper.StringPlaceholder.ToString(CultureInfo.InvariantCulture)); - //_code = Regex.Replace(_code, StringLiteralAndBracketEscaper.BracketPlaceholder + "+", StringLiteralAndBracketEscaper.BracketPlaceholder.ToString(CultureInfo.InvariantCulture)).Trim(); _segments = _code.Split(new[] { ": " }, StringSplitOptions.None); } diff --git a/Rubberduck.SmartIndenter/Rubberduck.SmartIndenter.csproj b/Rubberduck.SmartIndenter/Rubberduck.SmartIndenter.csproj index 901240fb30..30c01cd295 100644 --- a/Rubberduck.SmartIndenter/Rubberduck.SmartIndenter.csproj +++ b/Rubberduck.SmartIndenter/Rubberduck.SmartIndenter.csproj @@ -56,7 +56,6 @@ - diff --git a/Rubberduck.SmartIndenter/Selection.cs b/Rubberduck.SmartIndenter/Selection.cs deleted file mode 100644 index 2370096e29..0000000000 --- a/Rubberduck.SmartIndenter/Selection.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Rubberduck.SmartIndenter -{ -} diff --git a/Rubberduck.SmartIndenter/StringLiteralAndBracketEscaper.cs b/Rubberduck.SmartIndenter/StringLiteralAndBracketEscaper.cs index 89d4430d3b..2ee10f2e8e 100644 --- a/Rubberduck.SmartIndenter/StringLiteralAndBracketEscaper.cs +++ b/Rubberduck.SmartIndenter/StringLiteralAndBracketEscaper.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Text.RegularExpressions; From 78e414020a2907b8fe9fc86789edfafeb6dd8b5b Mon Sep 17 00:00:00 2001 From: comintern Date: Thu, 16 Feb 2017 19:42:04 -0600 Subject: [PATCH 18/20] Clean up superfluous event code in parser state. --- .../VBA/RubberduckParserState.cs | 40 +++++-------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/Rubberduck.Parsing/VBA/RubberduckParserState.cs b/Rubberduck.Parsing/VBA/RubberduckParserState.cs index e8637d9965..c4c8cd2af6 100644 --- a/Rubberduck.Parsing/VBA/RubberduckParserState.cs +++ b/Rubberduck.Parsing/VBA/RubberduckParserState.cs @@ -72,7 +72,7 @@ internal void RefreshFinder(IHostApplication host) DeclarationFinder = new DeclarationFinder(AllDeclarations, AllAnnotations, host); } - private IVBE _vbe; + private readonly IVBE _vbe; public RubberduckParserState(IVBE vbe) { var values = Enum.GetValues(typeof(ParserState)); @@ -82,32 +82,27 @@ public RubberduckParserState(IVBE vbe) } _vbe = vbe; - - if (_vbe != null && _vbe.VBProjects != null) - { - VBProjects.ProjectAdded += Sinks_ProjectAdded; - VBProjects.ProjectRemoved += Sinks_ProjectRemoved; - VBProjects.ProjectRenamed += Sinks_ProjectRenamed; - foreach (var project in _vbe.VBProjects.Where(proj => proj.VBComponents != null)) - { - AddComponentEventHandlers(project); - } - } - + AddEventHandlers(); IsEnabled = true; } #region Event Handling - private void AddComponentEventHandlers(IVBProject project) + private void AddEventHandlers() { + VBProjects.ProjectAdded += Sinks_ProjectAdded; + VBProjects.ProjectRemoved += Sinks_ProjectRemoved; + VBProjects.ProjectRenamed += Sinks_ProjectRenamed; VBComponents.ComponentAdded += Sinks_ComponentAdded; VBComponents.ComponentRemoved += Sinks_ComponentRemoved; VBComponents.ComponentRenamed += Sinks_ComponentRenamed; } - private void RemoveComponentEventHandlers(IVBProject project) + private void RemoveEventHandlers() { + VBProjects.ProjectAdded += Sinks_ProjectAdded; + VBProjects.ProjectRemoved += Sinks_ProjectRemoved; + VBProjects.ProjectRenamed += Sinks_ProjectRenamed; VBComponents.ComponentAdded -= Sinks_ComponentAdded; VBComponents.ComponentRemoved -= Sinks_ComponentRemoved; VBComponents.ComponentRenamed -= Sinks_ComponentRenamed; @@ -118,8 +113,6 @@ private void Sinks_ProjectAdded(object sender, ProjectEventArgs e) if (!e.Project.VBE.IsInDesignMode) { return; } Logger.Debug("Project '{0}' was added.", e.ProjectId); - AddComponentEventHandlers(e.Project); - RefreshProjects(_vbe); // note side-effect: assigns ProjectId/HelpFile OnParseRequested(sender); } @@ -129,8 +122,6 @@ private void Sinks_ProjectRemoved(object sender, ProjectEventArgs e) if (!e.Project.VBE.IsInDesignMode) { return; } Debug.Assert(e.ProjectId != null); - RemoveComponentEventHandlers(e.Project); - RemoveProject(e.ProjectId, true); OnParseRequested(sender); } @@ -1110,16 +1101,7 @@ public void Dispose() CoClasses.Clear(); } - if (_vbe != null && _vbe.VBProjects != null) - { - VBProjects.ProjectAdded -= Sinks_ProjectAdded; - VBProjects.ProjectRemoved -= Sinks_ProjectRemoved; - VBProjects.ProjectRenamed -= Sinks_ProjectRenamed; - foreach (var project in _vbe.VBProjects.Where(proj => proj.VBComponents != null)) - { - RemoveComponentEventHandlers(project); - } - } + RemoveEventHandlers(); _moduleStates.Clear(); _declarationSelections.Clear(); From 651ee8855056bae1506fdce3c1bbda4e1bd29844 Mon Sep 17 00:00:00 2001 From: comintern Date: Thu, 16 Feb 2017 20:07:46 -0600 Subject: [PATCH 19/20] Fixed thread locking in SubclassingWindow - no need to throw in dtor. --- .../WindowsApi/SubclassingWindow.cs | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs b/Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs index 16ae10c917..e1072b2e29 100644 --- a/Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs +++ b/Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs @@ -13,8 +13,7 @@ public abstract class SubclassingWindow : IDisposable private readonly SubClassCallback _wndProc; private bool _listening; - private static readonly ConcurrentBag RubberduckProcs = new ConcurrentBag(); - private static readonly object SubclassLock = new object(); + private readonly object _subclassLock = new object(); public delegate int SubClassCallback(IntPtr hWnd, IntPtr msg, IntPtr wParam, IntPtr lParam, IntPtr uIdSubclass, IntPtr dwRefData); @@ -40,10 +39,6 @@ protected SubclassingWindow(IntPtr subclassId, IntPtr hWnd) _wndProc = SubClassProc; AssignHandle(); } - ~SubclassingWindow() - { - Debug.Assert(false, "Dispose() not called."); - } public void Dispose() { @@ -53,9 +48,8 @@ public void Dispose() private void AssignHandle() { - lock (SubclassLock) + lock (_subclassLock) { - RubberduckProcs.Add(_wndProc); var result = SetWindowSubclass(_hwnd, _wndProc, _subclassId, IntPtr.Zero); if (result != 1) { @@ -67,13 +61,13 @@ private void AssignHandle() private void ReleaseHandle() { - if (!_listening) + lock (_subclassLock) { - return; - } + if (!_listening) + { + return; + } - lock (SubclassLock) - { var result = RemoveWindowSubclass(_hwnd, _wndProc, _subclassId); if (result != 1) { From 58b650b94247ce2f6c71683e1e2f9e87919a95f7 Mon Sep 17 00:00:00 2001 From: comintern Date: Thu, 16 Feb 2017 20:15:10 -0600 Subject: [PATCH 20/20] Fixes for focus events to catch designer windows. Still needs work in App.cs --- RetailCoder.VBE/App.cs | 10 ++++++- Rubberduck.VBEEditor/Events/VBEEvents.cs | 4 ++- .../Events/WindowChangedEventArgs.cs | 4 ++- .../WindowsApi/CodePaneSubclass.cs | 23 ++++++++++++++-- .../WindowsApi/FocusSource.cs | 26 ++++++++++++------- 5 files changed, 52 insertions(+), 15 deletions(-) diff --git a/RetailCoder.VBE/App.cs b/RetailCoder.VBE/App.cs index 3cfca41f8c..78f6f1f1d6 100644 --- a/RetailCoder.VBE/App.cs +++ b/RetailCoder.VBE/App.cs @@ -94,7 +94,15 @@ private void _vbe_FocusChanged(object sender, WindowChangedEventArgs e) { if (e.EventType == WindowChangedEventArgs.FocusType.GotFocus) { - RefreshSelection(e.Window); + switch (e.Window.Type) + { + case WindowKind.Designer: + RefreshSelection(e.Window); + break; + case WindowKind.CodeWindow: + RefreshSelection(e.CodePane); + break; + } } } diff --git a/Rubberduck.VBEEditor/Events/VBEEvents.cs b/Rubberduck.VBEEditor/Events/VBEEvents.cs index e26c2cd3e5..fcebb59ce6 100644 --- a/Rubberduck.VBEEditor/Events/VBEEvents.cs +++ b/Rubberduck.VBEEditor/Events/VBEEvents.cs @@ -92,7 +92,9 @@ private static void AttachWindow(IntPtr hwnd) Debug.Assert(!TrackedWindows.ContainsKey(hwnd)); var window = GetWindowFromHwnd(hwnd); if (window == null) return; - var source = window.Type == WindowKind.CodeWindow ? new CodePaneSubclass(hwnd) as IWindowEventProvider: new DesignerWindowSubclass(hwnd); + var source = window.Type == WindowKind.CodeWindow + ? new CodePaneSubclass(hwnd, GetCodePaneFromHwnd(hwnd)) as IWindowEventProvider + : new DesignerWindowSubclass(hwnd); var info = new WindowInfo(hwnd, window, source); source.FocusChange += FocusDispatcher; TrackedWindows.Add(hwnd, info); diff --git a/Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs b/Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs index cc12270111..0d0bf99611 100644 --- a/Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs +++ b/Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs @@ -13,12 +13,14 @@ public enum FocusType public IntPtr Hwnd { get; private set; } public IWindow Window { get; private set; } + public ICodePane CodePane { get; private set; } public FocusType EventType { get; private set; } - public WindowChangedEventArgs(IntPtr hwnd, IWindow window, FocusType type) + public WindowChangedEventArgs(IntPtr hwnd, IWindow window, ICodePane pane, FocusType type) { Hwnd = hwnd; Window = window; + CodePane = pane; EventType = type; } } diff --git a/Rubberduck.VBEEditor/WindowsApi/CodePaneSubclass.cs b/Rubberduck.VBEEditor/WindowsApi/CodePaneSubclass.cs index 30998d8d46..5eee5941bf 100644 --- a/Rubberduck.VBEEditor/WindowsApi/CodePaneSubclass.cs +++ b/Rubberduck.VBEEditor/WindowsApi/CodePaneSubclass.cs @@ -1,10 +1,29 @@ using System; +using Rubberduck.VBEditor.Events; +using Rubberduck.VBEditor.SafeComWrappers.Abstract; namespace Rubberduck.VBEditor.WindowsApi { + //Stub for code pane replacement. :-) internal class CodePaneSubclass : FocusSource { - //Stub for code pane replacement. :-) - internal CodePaneSubclass(IntPtr hwnd) : base(hwnd) { } + private readonly ICodePane _pane; + + public ICodePane CodePane { get { return _pane; } } + + internal CodePaneSubclass(IntPtr hwnd, ICodePane pane) : base(hwnd) + { + _pane = pane; + } + + protected override void DispatchFocusEvent(WindowChangedEventArgs.FocusType type) + { + var window = VBEEvents.GetWindowInfoFromHwnd(Hwnd); + if (window == null) + { + return; + } + OnFocusChange(new WindowChangedEventArgs(window.Value.Hwnd, window.Value.Window, _pane, type)); + } } } diff --git a/Rubberduck.VBEEditor/WindowsApi/FocusSource.cs b/Rubberduck.VBEEditor/WindowsApi/FocusSource.cs index 979f85f9cb..9c6539a543 100644 --- a/Rubberduck.VBEEditor/WindowsApi/FocusSource.cs +++ b/Rubberduck.VBEEditor/WindowsApi/FocusSource.cs @@ -9,28 +9,34 @@ internal abstract class FocusSource : SubclassingWindow, IWindowEventProvider protected FocusSource(IntPtr hwnd) : base(hwnd, hwnd) { } public event EventHandler FocusChange; - private void OnFocusChange(WindowChangedEventArgs.FocusType type) + protected void OnFocusChange(WindowChangedEventArgs eventArgs) { if (FocusChange != null) { - var window = VBEEvents.GetWindowInfoFromHwnd(Hwnd); - if (window == null) - { - return; - } - FocusChange.Invoke(this, new WindowChangedEventArgs(window.Value.Hwnd, window.Value.Window, type)); + FocusChange.Invoke(this, eventArgs); } - } + } + + protected virtual void DispatchFocusEvent(WindowChangedEventArgs.FocusType type) + { + var window = VBEEvents.GetWindowInfoFromHwnd(Hwnd); + if (window == null) + { + return; + } + OnFocusChange(new WindowChangedEventArgs(Hwnd, window.Value.Window, null, type)); + } public override int SubClassProc(IntPtr hWnd, IntPtr msg, IntPtr wParam, IntPtr lParam, IntPtr uIdSubclass, IntPtr dwRefData) { switch ((uint)msg) { case (uint)WM.SETFOCUS: - OnFocusChange(WindowChangedEventArgs.FocusType.GotFocus); + + DispatchFocusEvent(WindowChangedEventArgs.FocusType.GotFocus); break; case (uint)WM.KILLFOCUS: - OnFocusChange(WindowChangedEventArgs.FocusType.LostFocus); + DispatchFocusEvent(WindowChangedEventArgs.FocusType.LostFocus); break; } return base.SubClassProc(hWnd, msg, wParam, lParam, uIdSubclass, dwRefData);