diff --git a/src/Community.VisualStudio.Toolkit.Shared/Events/SelectionEvents.cs b/src/Community.VisualStudio.Toolkit.Shared/Events/SelectionEvents.cs
index 130a396..faa1880 100644
--- a/src/Community.VisualStudio.Toolkit.Shared/Events/SelectionEvents.cs
+++ b/src/Community.VisualStudio.Toolkit.Shared/Events/SelectionEvents.cs
@@ -37,8 +37,8 @@ int IVsSelectionEvents.OnSelectionChanged(IVsHierarchy pHierOld, uint itemidOld,
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
- IVsHierarchyItem? from = await pHierOld.ToHierarcyItemAsync(itemidOld);
- IVsHierarchyItem? to = await pHierNew.ToHierarcyItemAsync(itemidNew);
+ ItemNode? from = await ItemNode.CreateAsync(pHierOld, itemidOld);
+ ItemNode? to = await ItemNode.CreateAsync(pHierNew, itemidNew);
SelectionChanged?.Invoke(this, new SelectionChangedEventArgs(from, to));
}).FireAndForget();
@@ -66,7 +66,7 @@ public class SelectionChangedEventArgs : EventArgs
///
/// Creates a new instance of the EventArgs.
///
- public SelectionChangedEventArgs(IVsHierarchyItem? from, IVsHierarchyItem? to)
+ public SelectionChangedEventArgs(ItemNode? from, ItemNode? to)
{
From = from;
To = to;
@@ -75,11 +75,11 @@ public SelectionChangedEventArgs(IVsHierarchyItem? from, IVsHierarchyItem? to)
///
/// What the selection was before the change.
///
- public IVsHierarchyItem? From { get; }
+ public ItemNode? From { get; }
///
/// What the selection is currently after the change.
///
- public IVsHierarchyItem? To { get; }
+ public ItemNode? To { get; }
}
}
diff --git a/src/Community.VisualStudio.Toolkit.Shared/ExtensionMethods/IVsHierarchyExtensions.cs b/src/Community.VisualStudio.Toolkit.Shared/ExtensionMethods/IVsHierarchyExtensions.cs
index a36e794..8c09827 100644
--- a/src/Community.VisualStudio.Toolkit.Shared/ExtensionMethods/IVsHierarchyExtensions.cs
+++ b/src/Community.VisualStudio.Toolkit.Shared/ExtensionMethods/IVsHierarchyExtensions.cs
@@ -20,17 +20,7 @@ public static class IVsHierarchyExtensions
///
public static bool TryGetItemProperty(this IVsHierarchy hierarchy, uint itemId, int propertyId, out T? value)
{
- ThreadHelper.ThrowIfNotOnUIThread();
-
- if (ErrorHandler.Failed(hierarchy.GetProperty(itemId, propertyId, out var property)) || !(property is T))
- {
- value = default;
- return false;
- }
-
- value = (T)property;
-
- return true;
+ return HierarchyUtilities.TryGetHierarchyProperty(hierarchy, itemId, propertyId, out value);
}
///
diff --git a/src/Community.VisualStudio.Toolkit.Shared/Helpers/ProjectTypes.cs b/src/Community.VisualStudio.Toolkit.Shared/Helpers/ProjectTypes.cs
index 6610271..0798de2 100644
--- a/src/Community.VisualStudio.Toolkit.Shared/Helpers/ProjectTypes.cs
+++ b/src/Community.VisualStudio.Toolkit.Shared/Helpers/ProjectTypes.cs
@@ -1,4 +1,4 @@
-namespace EnvDTE
+namespace Community.VisualStudio.Toolkit
{
///
/// A list of known project types guids.
diff --git a/src/Community.VisualStudio.Toolkit.Shared/ItemNode.cs b/src/Community.VisualStudio.Toolkit.Shared/ItemNode.cs
new file mode 100644
index 0000000..24e926b
--- /dev/null
+++ b/src/Community.VisualStudio.Toolkit.Shared/ItemNode.cs
@@ -0,0 +1,217 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Internal.VisualStudio.PlatformUI;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Shell.Interop;
+
+namespace Community.VisualStudio.Toolkit
+{
+ ///
+ /// A node reprensenting a file, folder, project or other item in Solution Explorer.
+ ///
+ [DebuggerDisplay("{Name} ({Type})")]
+ public class ItemNode
+ {
+ private ItemNode? _parent;
+ private IEnumerable? _children;
+ private IVsHierarchy _hierarchy;
+ private uint _itemId;
+
+ private ItemNode(IVsHierarchyItem item)
+ {
+ ThreadHelper.ThrowIfNotOnUIThread();
+
+ _hierarchy = item.HierarchyIdentity.IsNestedItem ? item.HierarchyIdentity.NestedHierarchy : item.HierarchyIdentity.Hierarchy;
+ _itemId = item.HierarchyIdentity.IsNestedItem ? item.HierarchyIdentity.NestedItemID : item.HierarchyIdentity.ItemID;
+
+ _hierarchy.GetCanonicalName(_itemId, out var fileName);
+
+ FileName = fileName;
+ Item = item;
+ Name = item.Text;
+ Type = GetNodeType(item.HierarchyIdentity);
+ IsExpandable = HierarchyUtilities.IsExpandable(item.HierarchyIdentity);
+ IsFaulted = HierarchyUtilities.IsFaultedProject(item.HierarchyIdentity);
+ IsHidden = HierarchyUtilities.IsHiddenItem(_hierarchy, _itemId);
+ IsRoot = item.HierarchyIdentity.IsRoot;
+ IsNested = item.HierarchyIdentity.IsNestedItem;
+ }
+
+ ///
+ /// The underlying hierarchy item.
+ ///
+ public IVsHierarchyItem Item { get; }
+
+ ///
+ /// The display name of the node
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The absolute file path on disk.
+ ///
+ public string FileName { get; set; }
+
+ ///
+ /// The type of node.
+ ///
+ public NodeType Type { get; }
+
+ ///
+ /// A value indicating if the node can be expanded.
+ ///
+ public bool IsExpandable { get; }
+
+ ///
+ /// A value indicating if the node is in a faulted state.
+ ///
+ public bool IsFaulted { get; }
+
+ ///
+ /// A value indicating if the node is hidden.
+ ///
+ public bool IsHidden { get; }
+
+ ///
+ /// A value indicating if the node is a root node.
+ ///
+ public bool IsRoot { get; }
+
+ ///
+ /// A value indicating if the node is nested
+ ///
+ public bool IsNested { get; }
+
+ ///
+ /// The parent node. Is when there is no parent.
+ ///
+ public ItemNode? Parent => _parent ??= Create(Item.Parent);
+
+ ///
+ /// A list of child nodes.
+ ///
+ public IEnumerable Children => _children ??= Item.Children.Select(t => Create(t));
+
+ ///
+ /// Checks what kind the project is.
+ ///
+ /// Use the collection for known guids.
+ public bool IsKind(string typeGuid)
+ {
+ ThreadHelper.ThrowIfNotOnUIThread();
+
+ if (Type == NodeType.Project || Type == NodeType.VirtualProject)
+ {
+ return _hierarchy.IsProjectOfType(typeGuid);
+ }
+
+ return false;
+ }
+
+ ///
+ /// Converts the node a .
+ ///
+ public EnvDTE.Project? ToProject()
+ {
+ return HierarchyUtilities.GetProject(Item);
+ }
+
+ ///
+ /// Converts the node a .
+ ///
+ public EnvDTE.ProjectItem? ToProjectItem()
+ {
+ ThreadHelper.ThrowIfNotOnUIThread();
+
+ if (_hierarchy.TryGetItemProperty(_itemId, (int)__VSHPROPID.VSHPROPID_ExtObject, out object? obj))
+ {
+ return obj as EnvDTE.ProjectItem;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Creates a new instance based on a hierarchy.
+ ///
+ public static async Task CreateAsync(IVsHierarchy hierarchy, uint itemId)
+ {
+ await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
+ IVsHierarchyItem? item = await hierarchy.ToHierarcyItemAsync(itemId);
+
+ return Create(item);
+ }
+
+ ///
+ /// Creates a new instance based on a hierarchy item.
+ ///
+ public static ItemNode? Create(IVsHierarchyItem? item)
+ {
+ if (item == null)
+ {
+ return null;
+ }
+
+ return new ItemNode(item);
+ }
+
+ private NodeType GetNodeType(IVsHierarchyItemIdentity identity)
+ {
+ if (HierarchyUtilities.IsSolutionNode(identity))
+ {
+ return NodeType.Solution;
+ }
+ else if (HierarchyUtilities.IsSolutionFolder(identity))
+ {
+ return NodeType.SolutionFolder;
+ }
+ else if (HierarchyUtilities.IsMiscellaneousProject(identity))
+ {
+ return NodeType.MiscProject;
+ }
+ else if (HierarchyUtilities.IsVirtualProject(identity))
+ {
+ return NodeType.VirtualProject;
+ }
+ else if (HierarchyUtilities.IsProject(identity))
+ {
+ return NodeType.Project;
+ }
+ else if (HierarchyUtilities.IsPhysicalFile(identity))
+ {
+ return NodeType.PhysicalFile;
+ }
+ else if (HierarchyUtilities.IsPhysicalFolder(identity))
+ {
+ return NodeType.PhysicalFolder;
+ }
+
+ return NodeType.Unknown;
+ }
+ }
+
+ ///
+ /// Types of nodes.
+ ///
+ public enum NodeType
+ {
+ /// Physical file on disk
+ PhysicalFile,
+ /// Physical folder on disk
+ PhysicalFolder,
+ /// A project
+ Project,
+ /// A miscellaneous project
+ MiscProject,
+ /// A virtual project
+ VirtualProject,
+ /// The solution
+ Solution,
+ /// A solution folder
+ SolutionFolder,
+ /// Unknown node type
+ Unknown,
+ }
+}
diff --git a/src/Community.VisualStudio.Toolkit.Shared/Services/Solution.cs b/src/Community.VisualStudio.Toolkit.Shared/Services/Solution.cs
index 503ad73..5302582 100644
--- a/src/Community.VisualStudio.Toolkit.Shared/Services/Solution.cs
+++ b/src/Community.VisualStudio.Toolkit.Shared/Services/Solution.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using EnvDTE;
@@ -83,6 +84,134 @@ public async Task> GetSelectedItemsAsync()
return selectedObject as ProjectItem;
}
+ ///
+ /// Gets the currently selected hierarchy items.
+ ///
+ public async Task> GetSelectedHierarchiesAsync()
+ {
+ await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
+
+ IVsMonitorSelection? svc = await VS.GetRequiredServiceAsync();
+ IntPtr hierPtr = IntPtr.Zero;
+ IntPtr containerPtr = IntPtr.Zero;
+
+ List results = new();
+
+ try
+ {
+ svc.GetCurrentSelection(out hierPtr, out var itemId, out IVsMultiItemSelect multiSelect, out containerPtr);
+
+ if (itemId == VSConstants.VSITEMID_SELECTION)
+ {
+ multiSelect.GetSelectionInfo(out var itemCount, out var fSingleHierarchy);
+
+ var items = new VSITEMSELECTION[itemCount];
+ multiSelect.GetSelectedItems(0, itemCount, items);
+
+ foreach (VSITEMSELECTION item in items)
+ {
+ IVsHierarchyItem? hierItem = await item.pHier.ToHierarcyItemAsync(item.itemid);
+ if (hierItem != null && !results.Contains(hierItem))
+ {
+ results.Add(hierItem);
+ }
+ }
+ }
+ else
+ {
+ if (hierPtr != IntPtr.Zero)
+ {
+ var hierarchy = (IVsHierarchy)Marshal.GetUniqueObjectForIUnknown(hierPtr);
+ IVsHierarchyItem? hierItem = await hierarchy.ToHierarcyItemAsync(itemId);
+
+ if (hierItem != null)
+ {
+ results.Add(hierItem);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ await ex.LogAsync();
+ }
+ finally
+ {
+ if (hierPtr != IntPtr.Zero)
+ {
+ Marshal.Release(hierPtr);
+ }
+
+ if (containerPtr != IntPtr.Zero)
+ {
+ Marshal.Release(containerPtr);
+ }
+ }
+
+ return results;
+ }
+
+ ///
+ /// Gets the currently selected nodes.
+ ///
+ public async Task> GetSelectedNodesAsync()
+ {
+ IEnumerable? hierarchies = await GetSelectedHierarchiesAsync();
+ List nodes = new();
+
+ foreach (IVsHierarchyItem hierarchy in hierarchies)
+ {
+ ItemNode? node = await ItemNode.CreateAsync(hierarchy.HierarchyIdentity.Hierarchy, hierarchy.HierarchyIdentity.ItemID);
+
+ if (node != null)
+ {
+ nodes.Add(node);
+ }
+ }
+
+ return nodes;
+ }
+
+ ///
+ /// Gets the currently selected node.
+ ///
+ public async Task GetActiveProjectNodeAsync()
+ {
+ IEnumerable? hierarchies = await GetSelectedHierarchiesAsync();
+ IVsHierarchyItem? hierarchy = hierarchies.FirstOrDefault();
+
+ if (hierarchy != null)
+ {
+ return await ItemNode.CreateAsync(hierarchy.HierarchyIdentity.NestedHierarchy, VSConstants.VSITEMID_ROOT);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Gets all projects in the solution
+ ///
+ public async Task> GetAllProjectNodesAsync()
+ {
+ await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
+ IVsSolution solution = await GetSolutionAsync();
+ IEnumerable? hierarchies = solution.GetAllProjectHierarchys();
+
+ List list = new();
+
+ foreach (IVsHierarchy? hierarchy in hierarchies)
+ {
+ ItemNode? proj = await ItemNode.CreateAsync(hierarchy, VSConstants.VSITEMID_ROOT);
+
+ if (proj?.Type == NodeType.Project)
+ {
+ list.Add(proj);
+ }
+ }
+
+ return list;
+ }
+
///
/// Gets the active project.
///
diff --git a/src/Community.VisualStudio.Toolkit.Shared/VSSDK.Helpers.Shared.projitems b/src/Community.VisualStudio.Toolkit.Shared/VSSDK.Helpers.Shared.projitems
index 2b874de..dd7ca05 100644
--- a/src/Community.VisualStudio.Toolkit.Shared/VSSDK.Helpers.Shared.projitems
+++ b/src/Community.VisualStudio.Toolkit.Shared/VSSDK.Helpers.Shared.projitems
@@ -39,6 +39,7 @@
+
diff --git a/test/VSSDK.TestExtension/TestExtensionPackage.cs b/test/VSSDK.TestExtension/TestExtensionPackage.cs
index ddb8a8c..0615e06 100644
--- a/test/VSSDK.TestExtension/TestExtensionPackage.cs
+++ b/test/VSSDK.TestExtension/TestExtensionPackage.cs
@@ -37,7 +37,6 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
await MultiInstanceWindowCommand.InitializeAsync(this);
await BuildActiveProjectAsyncCommand.InitializeAsync(this);
await BuildSolutionAsyncCommand.InitializeAsync(this);
-
}
}
}
diff --git a/test/VSSDK.TestExtension/VSSDK.TestExtension.csproj b/test/VSSDK.TestExtension/VSSDK.TestExtension.csproj
index c63efcf..bc91a2d 100644
--- a/test/VSSDK.TestExtension/VSSDK.TestExtension.csproj
+++ b/test/VSSDK.TestExtension/VSSDK.TestExtension.csproj
@@ -103,15 +103,12 @@
16.10.1
-
- 16.4.29519.181
-
- 16.10.56
+ 17.0.17-alpha
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
@@ -146,9 +143,9 @@
-
- {38dc82ea-91d6-4360-b76c-995708ee43a3}
- Community.VisualStudio.Toolkit.16.0
+
+ {d000c76d-4d0f-48f9-b5ab-5696c92cc7bd}
+ Community.VisualStudio.Toolkit.17.0