diff --git a/RetailCoder.VBE/App.cs b/RetailCoder.VBE/App.cs index 374f7b87b4..78f6f1f1d6 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.WindowFocusChange += _vbe_FocusChanged; + _configService.SettingsChanged += _configService_SettingsChanged; _parser.State.StateChanged += Parser_StateChanged; _parser.State.StatusMessageUpdate += State_StatusMessageUpdate; @@ -81,17 +85,57 @@ 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(); + RefreshSelection(e.CodePane); + } + + private void _vbe_FocusChanged(object sender, WindowChangedEventArgs e) + { + if (e.EventType == WindowChangedEventArgs.FocusType.GotFocus) + { + switch (e.Window.Type) + { + case WindowKind.Designer: + RefreshSelection(e.Window); + break; + case WindowKind.CodeWindow: + RefreshSelection(e.CodePane); + break; + } + } } 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 +147,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 +164,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 +366,8 @@ public void Dispose() _parser.State.StatusMessageUpdate -= State_StatusMessageUpdate; } - if (_hooks != null) - { - _hooks.MessageReceived -= _hooks_MessageReceived; - } + VBEEvents.SelectionChanged += _vbe_SelectionChanged; + VBEEvents.WindowFocusChange += _vbe_FocusChanged; 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/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs index c53ed13793..60c606608b 100644 --- a/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs +++ b/RetailCoder.VBE/Inspections/QuickFixes/PassParameterByReferenceQuickFix.cs @@ -1,9 +1,11 @@ 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.Text.RegularExpressions; +using System.Linq; namespace Rubberduck.Inspections.QuickFixes { @@ -12,31 +14,75 @@ namespace Rubberduck.Inspections.QuickFixes /// public class PassParameterByReferenceQuickFix : QuickFixBase { - public PassParameterByReferenceQuickFix(ParserRuleContext context, QualifiedSelection selection) - : base(context, selection, InspectionsUI.PassParameterByReferenceQuickFix) + private Declaration _target; + + public PassParameterByReferenceQuickFix(Declaration target, QualifiedSelection selection) + : base(target.Context, selection, InspectionsUI.PassParameterByReferenceQuickFix) { + _target = target; } public override void Fix() { - var parameter = Context.GetText(); + var argCtxt = GetArgContextForIdentifier(Context.Parent.Parent, _target.IdentifierName); - 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 terminalNode = argCtxt.BYVAL(); - var selection = Selection.Selection; + var replacementLine = GenerateByRefReplacementLine(terminalNode); + + ReplaceModuleLine(terminalNode.Symbol.Line, replacementLine); + + } + private VBAParser.ArgContext GetArgContextForIdentifier(RuleContext context, string identifier) + { + var argList = GetArgListForContext(context); + return argList.arg().SingleOrDefault(parameter => + Identifier.GetName(parameter).Equals(identifier)); + } + private string GenerateByRefReplacementLine(ITerminalNode terminalNode) + { + 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 void ReplaceModuleLine(int lineNumber, string replacementLine) + { var module = Selection.QualifiedName.Component.CodeModule; + module.DeleteLines(lineNumber); + module.InsertLines(lineNumber, replacementLine); + } + private string ReplaceAtIndex(string input, string toReplace, string replacement, int startIndex) + { + int stopIndex = startIndex + toReplace.Length; + var prefix = input.Substring(0, startIndex); + var suffix = input.Substring(stopIndex + 1); + var tokenToBeReplaced = input.Substring(startIndex, stopIndex - startIndex + 1); + return prefix + tokenToBeReplaced.Replace(toReplace, replacement) + suffix; + } + private VBAParser.ArgListContext GetArgListForContext(RuleContext context) + { + 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) { - var lines = module.GetLines(selection.StartLine, selection.LineCount); - var result = lines.Replace(parameter, newContent); - module.ReplaceLine(selection.StartLine, result); + return ((VBAParser.PropertySetStmtContext)context).argList(); } + return null; } } } \ 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/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/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); 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); 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/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(); diff --git a/Rubberduck.SmartIndenter/AbsoluteCodeLine.cs b/Rubberduck.SmartIndenter/AbsoluteCodeLine.cs index 19d746e85b..e0cd943e37 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"); @@ -33,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) { } @@ -61,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); } @@ -267,38 +261,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/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 7b98f8a26e..2ee10f2e8e 100644 --- a/Rubberduck.SmartIndenter/StringLiteralAndBracketEscaper.cs +++ b/Rubberduck.SmartIndenter/StringLiteralAndBracketEscaper.cs @@ -1,12 +1,16 @@ using System.Collections.Generic; 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 +22,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 +81,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/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..fcebb59ce6 --- /dev/null +++ b/Rubberduck.VBEEditor/Events/VBEEvents.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using Rubberduck.VBEditor.SafeComWrappers.MSForms; +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; + private readonly IWindowEventProvider _subclass; + + public IntPtr Hwnd { get { return _handle; } } + public IWindow Window { get { return _window; } } + internal IWindowEventProvider Subclass { get { return _subclass; } } + + internal WindowInfo(IntPtr handle, IWindow window, IWindowEventProvider source) + { + _handle = handle; + _window = window; + _subclass = source; + } + } + + //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() + { + 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, + 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 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); + } + } + + 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) + { + if (SelectionChanged != null) + { + var pane = GetCodePaneFromHwnd(hwnd); + SelectionChanged.Invoke(_vbe, new SelectionChangedEventArgs(pane)); + } + } + + public static event EventHandler WindowFocusChange; + private static void OnWindowFocusChange(object sender, WindowChangedEventArgs eventArgs) + { + if (WindowFocusChange != null) + { + WindowFocusChange.Invoke(sender, eventArgs); + } + } + + 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..0d0bf99611 --- /dev/null +++ b/Rubberduck.VBEEditor/Events/WindowChangedEventArgs.cs @@ -0,0 +1,27 @@ +using System; +using Rubberduck.VBEditor.SafeComWrappers.Abstract; + +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 ICodePane CodePane { get; private set; } + public FocusType EventType { get; private set; } + + public WindowChangedEventArgs(IntPtr hwnd, IWindow window, ICodePane pane, FocusType type) + { + Hwnd = hwnd; + Window = window; + CodePane = pane; + EventType = type; + } + } +} diff --git a/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj b/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj index 0f086f1ba7..bb6b162fe0 100644 --- a/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj +++ b/Rubberduck.VBEEditor/Rubberduck.VBEditor.csproj @@ -124,6 +124,9 @@ + + + @@ -223,7 +226,12 @@ - + + + + + + @@ -253,6 +261,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/WindowsApi/CodePaneSubclass.cs b/Rubberduck.VBEEditor/WindowsApi/CodePaneSubclass.cs new file mode 100644 index 0000000000..5eee5941bf --- /dev/null +++ b/Rubberduck.VBEEditor/WindowsApi/CodePaneSubclass.cs @@ -0,0 +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 + { + 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/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..9c6539a543 --- /dev/null +++ b/Rubberduck.VBEEditor/WindowsApi/FocusSource.cs @@ -0,0 +1,45 @@ +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; + protected void OnFocusChange(WindowChangedEventArgs eventArgs) + { + if (FocusChange != null) + { + 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: + + DispatchFocusEvent(WindowChangedEventArgs.FocusType.GotFocus); + break; + case (uint)WM.KILLFOCUS: + DispatchFocusEvent(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; + } +} 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 82% rename from RetailCoder.VBE/UI/SubclassingWindow.cs rename to Rubberduck.VBEEditor/WindowsApi/SubclassingWindow.cs index c9ab5dc12c..e1072b2e29 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 { @@ -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); @@ -31,7 +30,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) { @@ -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) { @@ -91,8 +85,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); 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 + } +} diff --git a/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs b/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs index 75a2d680ad..b1019a32e6 100644 --- a/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs +++ b/RubberduckTests/Inspections/AssignedByValParameterInspectionTests.cs @@ -97,19 +97,152 @@ Dim var1 As Integer [TestCategory("Inspections")] public void AssignedByValParameter_QuickFixWorks() { - const string inputCode = -@"Public Sub Foo(ByVal barByVal As String) + + string inputCode = +@"Public Sub Foo(Optional ByVal barByVal As String = ""XYZ"") Let barByVal = ""test"" End Sub"; - const string expectedCode = -@"Public Sub Foo(ByRef barByVal As String) + string expectedCode = +@"Public Sub Foo(Optional ByRef barByVal As String = ""XYZ"") 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, Optional ByVal barByVal As String = """", secondArg as Double) + Let barByVal = ""test"" +End Sub"; + expectedCode = +@"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 = +@"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, _ + 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() @@ -507,9 +640,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); } 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")]