The Code Explorer is a dockable toolwindow that displays a TreeView that shows all opened VBA projects and their respective modules, but unlike the native Project Explorer, Rubberduck's explorer also drills down to individual module members, and allows quickly navigating them:While it looks pretty, the code behind it is among the most painful code in the project - clearly, it needs to be refactored and optimized. I'm particularly interested in ways to improve the async void RefreshExplorerTreeView() method and the whole node-generation code.namespace Rubberduck.UI.CodeExplorer{    public class CodeExplorerDockablePresenter : DockablePresenterBase    {        private readonly IRubberduckParser _parser;        private CodeExplorerWindow Control { get { return UserControl as CodeExplorerWindow; } }        public CodeExplorerDockablePresenter(IRubberduckParser parser, VBE vbe, AddIn addIn, CodeExplorerWindow view)            : base(vbe, addIn, view)        {            _parser = parser;            RegisterControlEvents();        }        public override void Show()        {            base.Show();            Task.Run(() => RefreshExplorerTreeView());         }        private void RegisterControlEvents()        {            if (Control == null)            {                return;            }            Control.RefreshTreeView += RefreshExplorerTreeView;            Control.NavigateTreeNode += NavigateExplorerTreeNode;            Control.AddComponent += AddComponent;            Control.AddTestModule += AddTestModule;            Control.ToggleFolders += ToggleFolders;            Control.ShowDesigner += ShowDesigner;            Control.DisplayStyleChanged += DisplayStyleChanged;            Control.RunAllTests += ContextMenuRunAllTests;            Control.RunInspections += ContextMenuRunInspections;            Control.SelectionChanged += SelectionChanged;            Control.Rename += RenameSelection;            Control.FindAllReferences += FindAllReferencesForSelection;        }        public event EventHandler<NavigateCodeEventArgs> FindAllReferences;        private void FindAllReferencesForSelection(object sender, NavigateCodeEventArgs e)        {            var handler = FindAllReferences;            if (handler != null)            {                handler(sender, e);            }        }        public event EventHandler<TreeNodeNavigateCodeEventArgs> Rename;        private void RenameSelection(object sender, TreeNodeNavigateCodeEventArgs e)        {            if (e.Node == null || e.Selection.Equals(default(Selection)) && e.QualifiedName == default(QualifiedModuleName))            {                return;            }            var handler = Rename;            if (handler != null)            {                handler(this, e);                RefreshExplorerTreeView();                e.Node.EnsureVisible();            }        }        private void SelectionChanged(object sender, TreeNodeNavigateCodeEventArgs e)        {            if (e.Node == null || e.Node.Tag == null)            {                return;            }            try            {                VBE.ActiveVBProject = e.QualifiedName.Project;            }            catch (COMException)            {                // swallow catastrophic failure            }        }        public event EventHandler RunInspections;        private void ContextMenuRunInspections(object sender, EventArgs e)        {            var handler = RunInspections;            if (handler != null)            {                handler(this, EventArgs.Empty);            }        }        public event EventHandler RunAllTests;        private void ContextMenuRunAllTests(object sender, EventArgs e)        {            var handler = RunAllTests;            if (handler != null)            {                handler(this, EventArgs.Empty);            }        }        private void DisplayStyleChanged(object sender, EventArgs e)        {            RefreshExplorerTreeView();        }        private void AddTestModule(object sender, EventArgs e)        {            NewUnitTestModuleCommand.NewUnitTestModule(VBE);            RefreshExplorerTreeView();        }        private void ShowDesigner(object sender, EventArgs e)        {            var node = Control.SolutionTree.SelectedNode;            if (node != null && node.Tag != null)            {                var selection = (Declaration)node.Tag;                var module = selection.QualifiedName.QualifiedModuleName.Component.CodeModule;                if (module == null)                {                    return;                }                try                {                    module.Parent.DesignerWindow().Visible = true;                }                catch                {                    Control.ShowDesignerButton.Enabled = false;                }            }        }        private bool _showFolders = true;        private void ToggleFolders(object sender, EventArgs e)        {            _showFolders = !_showFolders;            RefreshExplorerTreeView();        }        private void AddComponent(object sender, AddComponentEventArgs e)        {            var project = VBE.ActiveVBProject;            if (project == null)            {                return;            }            project.VBComponents.Add(e.ComponentType);            RefreshExplorerTreeView();        }        private void NavigateExplorerTreeNode(object sender, TreeNodeNavigateCodeEventArgs e)        {            var declaration = e.Declaration;            if (declaration != null)            {                VBE.SetSelection(new QualifiedSelection(declaration.QualifiedName.QualifiedModuleName, declaration.Selection));            }        }        private void RefreshExplorerTreeView(object sender, EventArgs e)        {            Task.Run(() => RefreshExplorerTreeView());        }        private async void RefreshExplorerTreeView()        {            Control.Invoke((MethodInvoker) delegate            {                Control.SolutionTree.Nodes.Clear();                Control.ShowDesignerButton.Enabled = false;            });            var projects = VBE.VBProjects.Cast<VBProject>();            foreach (var vbProject in projects)            {                var project = vbProject;                await Task.Run(() =>                {                    var node = new TreeNode(project.Name +  (parsing...));                    node.ImageKey = Hourglass;                    node.SelectedImageKey = node.ImageKey;                    Control.Invoke((MethodInvoker)delegate                    {                        Control.SolutionTree.Nodes.Add(node);                        Control.SolutionTree.Refresh();                        AddProjectNodes(project, node);                    });                });            }        }        private void AddProjectNodes(VBProject project, TreeNode root)        {            Control.Invoke((MethodInvoker)async delegate            {                if (project.Protection == vbext_ProjectProtection.vbext_pp_locked)                {                    root.ImageKey = Locked;                }                else                {                    var nodes = (await CreateModuleNodesAsync(project)).ToArray();                    AddProjectFolders(project, root, nodes);                    root.ImageKey = ClosedFolder;                    root.Expand();                }                root.Text = project.Name;            });        }        private static readonly IDictionary<vbext_ComponentType, string> ComponentTypeIcons =            new Dictionary<vbext_ComponentType, string>            {                { vbext_ComponentType.vbext_ct_StdModule, StandardModule},                { vbext_ComponentType.vbext_ct_ClassModule, ClassModule},                { vbext_ComponentType.vbext_ct_Document, OfficeDocument},                { vbext_ComponentType.vbext_ct_ActiveXDesigner, ClassModule},                { vbext_ComponentType.vbext_ct_MSForm, Form}            };        private void AddProjectFolders(VBProject project, TreeNode root, TreeNode[] components)        {            var documentNodes = components.Where(node => node.ImageKey == OfficeDocument)                                          .OrderBy(node => node.Text)                                          .ToArray();            if (project.VBComponents.Cast<VBComponent>()                       .Any(component => component.Type == vbext_ComponentType.vbext_ct_Document))            {                AddFolderNode(root, Documents, ClosedFolder, documentNodes);            }            var formsNodes = components.Where(node => node.ImageKey == Form)                                       .OrderBy(node => node.Text)                                       .ToArray();            if (project.VBComponents.Cast<VBComponent>()                       .Any(component => component.Type == vbext_ComponentType.vbext_ct_MSForm))            {                AddFolderNode(root, Forms, ClosedFolder, formsNodes);            }            var stdModulesNodes = components.Where(node => node.ImageKey == StandardModule)                                            .OrderBy(node => node.Text)                                            .ToArray();            if (project.VBComponents.Cast<VBComponent>()                       .Any(component => component.Type == vbext_ComponentType.vbext_ct_StdModule))            {                AddFolderNode(root, Standard Modules, ClosedFolder, stdModulesNodes);            }            var classModulesNodes = components.Where(node => node.ImageKey == ClassModule)                                              .OrderBy(node => node.Text)                                              .ToArray();            if (project.VBComponents.Cast<VBComponent>()                       .Any(component => component.Type == vbext_ComponentType.vbext_ct_ClassModule                                      || component.Type == vbext_ComponentType.vbext_ct_ActiveXDesigner))            {                AddFolderNode(root, Class Modules, ClosedFolder, classModulesNodes);            }        }        private void AddFolderNode(TreeNode root, string text, string imageKey, TreeNode[] nodes)        {            if (_showFolders)            {                var node = root.Nodes.Add(text);                node.ImageKey = imageKey;                node.SelectedImageKey = imageKey;                node.Nodes.AddRange(nodes);                node.Expand();            }            else            {                root.Nodes.AddRange(nodes);            }        }        private async Task<IEnumerable<TreeNode>> CreateModuleNodesAsync(VBProject project)        {            var result = new List<TreeNode>();            var parseResult = _parser.Parse(project);            foreach (var componentParseResult in parseResult.ComponentParseResults)            {                var component = componentParseResult.Component;                var members = parseResult.Declarations.Items                    .Where(declaration => declaration.ParentScope == component.Collection.Parent.Name + . + component.Name                        && declaration.DeclarationType != DeclarationType.Control                        && declaration.DeclarationType != DeclarationType.ModuleOption);                var node = new TreeNode(component.Name);                node.ImageKey = ComponentTypeIcons[component.Type];                node.SelectedImageKey = node.ImageKey;                node.Tag = parseResult.Declarations.Items.SingleOrDefault(item =>                     item.IdentifierName == component.Name                     && item.Project == component.Collection.Parent                    && (item.DeclarationType == DeclarationType.Class || item.DeclarationType == DeclarationType.Module));                foreach (var declaration in members)                {                    if (declaration.DeclarationType == DeclarationType.UserDefinedTypeMember                        || declaration.DeclarationType == DeclarationType.EnumerationMember)                    {                        // these ones are handled by their respective parent                        continue;                    }                    var text = GetNodeText(declaration);                    var child = new TreeNode(text);                    child.ImageKey = GetImageKeyForDeclaration(declaration);                    child.SelectedImageKey = child.ImageKey;                    child.Tag = declaration;                    if (declaration.DeclarationType == DeclarationType.UserDefinedType                        || declaration.DeclarationType == DeclarationType.Enumeration)                    {                        var subDeclaration = declaration;                        var subMembers = parseResult.Declarations.Items.Where(item =>                             (item.DeclarationType == DeclarationType.EnumerationMember || item.DeclarationType == DeclarationType.UserDefinedTypeMember)                            && item.Context != null && subDeclaration.Context.Equals(item.Context.Parent));                        foreach (var subMember in subMembers)                        {                            var subChild = new TreeNode(subMember.IdentifierName);                            subChild.ImageKey = GetImageKeyForDeclaration(subMember);                            subChild.SelectedImageKey = subChild.ImageKey;                            subChild.Tag = subMember;                            child.Nodes.Add(subChild);                        }                    }                    node.Nodes.Add(child);                }                result.Add(node);            }            return result;        }        private string GetNodeText(Declaration declaration)        {            if (Control.DisplayStyle == TreeViewDisplayStyle.MemberNames)            {                var result = declaration.IdentifierName;                if (declaration.DeclarationType == DeclarationType.PropertyGet)                {                    result +=  ( + Tokens.Get + );                }                else if (declaration.DeclarationType == DeclarationType.PropertyLet)                {                    result +=  ( + Tokens.Let + );                }                else if (declaration.DeclarationType == DeclarationType.PropertySet)                {                    result +=  ( + Tokens.Set + );                }                return result;            }            if (declaration.DeclarationType == DeclarationType.Procedure)            {                return ((VBAParser.SubStmtContext) declaration.Context).Signature();            }            if (declaration.DeclarationType == DeclarationType.Function)            {                return ((VBAParser.FunctionStmtContext)declaration.Context).Signature();            }            if (declaration.DeclarationType == DeclarationType.PropertyGet)            {                return ((VBAParser.PropertyGetStmtContext)declaration.Context).Signature();            }            if (declaration.DeclarationType == DeclarationType.PropertyLet)            {                return ((VBAParser.PropertyLetStmtContext)declaration.Context).Signature();            }            if (declaration.DeclarationType == DeclarationType.PropertySet)            {                return ((VBAParser.PropertySetStmtContext)declaration.Context).Signature();            }            return declaration.IdentifierName;        }        private string GetImageKeyForDeclaration(Declaration declaration)        {            var result = string.Empty;            switch (declaration.DeclarationType)            {                case DeclarationType.Module:                    break;                case DeclarationType.Class:                    break;                case DeclarationType.Procedure:                case DeclarationType.Function:                    if (declaration.Accessibility == Accessibility.Private)                    {                        result = PrivateMethod;                        break;                    }                    if (declaration.Accessibility == Accessibility.Friend)                    {                        result = FriendMethod;                        break;                    }                    result = PublicMethod;                    break;                case DeclarationType.PropertyGet:                case DeclarationType.PropertyLet:                case DeclarationType.PropertySet:                    if (declaration.Accessibility == Accessibility.Private)                    {                        result = PrivateProperty;                        break;                    }                    if (declaration.Accessibility == Accessibility.Friend)                    {                        result = FriendProperty;                        break;                    }                    result = PublicProperty;                    break;                case DeclarationType.Parameter:                    break;                case DeclarationType.Variable:                    if (declaration.Accessibility == Accessibility.Private)                    {                        result = PrivateField;                        break;                    }                    if (declaration.Accessibility == Accessibility.Friend)                    {                        result = FriendField;                        break;                    }                    result = PublicField;                    break;                case DeclarationType.Constant:                    if (declaration.Accessibility == Accessibility.Private)                    {                        result = PrivateConst;                        break;                    }                    if (declaration.Accessibility == Accessibility.Friend)                    {                        result = FriendConst;                        break;                    }                    result = PublicConst;                    break;                case DeclarationType.Enumeration:                    if (declaration.Accessibility == Accessibility.Private)                    {                        result = PrivateEnum;                        break;                    }                    if (declaration.Accessibility == Accessibility.Friend)                    {                        result = FriendEnum;                        break;                    }                    result = PublicEnum;                    break;                case DeclarationType.EnumerationMember:                    result = EnumItem;                    break;                case DeclarationType.Event:                    if (declaration.Accessibility == Accessibility.Private)                    {                        result = PrivateEvent;                        break;                    }                    if (declaration.Accessibility == Accessibility.Friend)                    {                        result = FriendEvent;                        break;                    }                    result = PublicEvent;                    break;                case DeclarationType.UserDefinedType:                    if (declaration.Accessibility == Accessibility.Private)                    {                        result = PrivateType;                        break;                    }                    if (declaration.Accessibility == Accessibility.Friend)                    {                        result = FriendType;                        break;                    }                    result = PublicType;                    break;                case DeclarationType.UserDefinedTypeMember:                    result = PublicField;                    break;                case DeclarationType.LibraryProcedure:                case DeclarationType.LibraryFunction:                    result = Identifier;                    break;                default:                    throw new ArgumentOutOfRangeException();            }            return result;        }    }}One thing that bothers me above all, is the fact that this presenter is calling into the parser - which means the parse results can only be used in this class... but there are many other features that would love to know the VBA code has just been re-parsed. Part of me is starting to accept the idea that maybe perhaps a Singleton might be in order here. Is there another way?
Exploring the code of the Code Explorer
c#;winforms;mvp;rubberduck
(Not a full review, just focusing on one method.)I think private string GetImageKeyForDeclaration(Declaration declaration) contains a lot of repeated code that can be significantly reduced.First of all I'm not a fan of the use a switch to set a value that you then return-approach: just do the return directly, especially if there are a lot of cases.I'd group all of these:case DeclarationType.Module:    break;case DeclarationType.Class:    break;case DeclarationType.Parameter:    break;So that would become:case DeclarationType.Module:case DeclarationType.Class:case DeclarationType.Parameter:    return string.Empty;Each of these I'd change to a method call:if (declaration.Accessibility == Accessibility.Private){    result = PrivateMethod;    break;}if (declaration.Accessibility == Accessibility.Friend){    result = FriendMethod;    break;}result = PublicMethod;So that would become something along these lines:case DeclarationType.Function:    return GetImageKeyForDeclaration(declaration, Method);case DeclarationType.PropertyGet:case DeclarationType.PropertyLet:case DeclarationType.PropertySet:    return GetImageKeyForDeclaration(declaration, Property);private string GetImageKeyForDeclaration(Declaration declaration, string suffix){    if (declaration.Accessibility == Accessibility.Private)    {        return Private + suffix;    }    if (declaration.Accessibility == Accessibility.Friend)    {        return Friend + suffix;    }    return Public + suffix;}Which means this method is now half its original length:private string GetImageKeyForDeclaration(Declaration declaration){    switch (declaration.DeclarationType)    {        case DeclarationType.Module:        case DeclarationType.Class:        case DeclarationType.Parameter:            return string.Empty;        case DeclarationType.Procedure:        case DeclarationType.Function:            return GetImageKeyForDeclaration(declaration, Method);        case DeclarationType.PropertyGet:        case DeclarationType.PropertyLet:        case DeclarationType.PropertySet:            return GetImageKeyForDeclaration(declaration, Property);        case DeclarationType.Variable:            return GetImageKeyForDeclaration(declaration, Field);        case DeclarationType.Constant:            return GetImageKeyForDeclaration(declaration, Const);        case DeclarationType.Enumeration:            return GetImageKeyForDeclaration(declaration, Enum);        case DeclarationType.Event:            return GetImageKeyForDeclaration(declaration, Event);        case DeclarationType.UserDefinedType:            return GetImageKeyForDeclaration(declaration, Type);        case DeclarationType.EnumerationMember:            return EnumItem;        case DeclarationType.UserDefinedTypeMember:            return PublicField;        case DeclarationType.LibraryProcedure:        case DeclarationType.LibraryFunction:            return Identifier;        default:            throw new ArgumentOutOfRangeException();    }}I'm still inclined to move this to a helper class though, considering that CodeExplorerDockablePresenter is 500+ lines long.