Skip to content

Commit 88de2f0

Browse files
Added selection events and FindCommand helper method
1 parent d435c15 commit 88de2f0

File tree

6 files changed

+160
-19
lines changed

6 files changed

+160
-19
lines changed

src/Community.VisualStudio.Toolkit.Shared/Commands/Commands.cs

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,33 +21,56 @@ internal Commands()
2121
/// <summary>Used to register and unregister a command target as a high priority command handler.</summary>
2222
public Task<IVsRegisterPriorityCommandTarget> GetPriorityCommandTargetAsync() => VS.GetRequiredServiceAsync<SVsRegisterPriorityCommandTarget, IVsRegisterPriorityCommandTarget>();
2323

24+
/// <summary>
25+
/// Finds a command by cannonical name.
26+
/// </summary>
27+
public async Task<CommandID?> FindCommandAsync(string name)
28+
{
29+
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
30+
IVsCommandWindow cw = await VS.Windows.GetCommandWindowAsync();
31+
32+
var hr = cw.PrepareCommand(name, out Guid commandGroup, out var commandId, out _, new PREPARECOMMANDRESULT[0]);
33+
34+
if (hr == VSConstants.S_OK)
35+
{
36+
return new CommandID(commandGroup, (int)commandId);
37+
}
38+
39+
return null;
40+
}
41+
2442
/// <summary>
2543
/// Executes a command by name
2644
/// </summary>
2745
/// <returns>Returns <see langword="true"/> if the command was succesfully executed; otherwise <see langword="false"/>.</returns>
2846
public async Task<bool> ExecuteAsync(string name, string argument = "")
2947
{
30-
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
31-
IVsCommandWindow cw = await VS.Windows.GetCommandWindowAsync();
48+
CommandID? cmd = await FindCommandAsync(name);
49+
50+
if (cmd != null)
51+
{
52+
return await ExecuteAsync(cmd.Guid, cmd.ID, argument);
53+
}
3254

33-
return cw.ExecuteCommand($"{name} {argument}") == VSConstants.S_OK;
55+
return false;
3456
}
3557

3658
/// <summary>
3759
/// Executes a command by guid and ID
3860
/// </summary>
3961
/// <returns>Returns <see langword="true"/> if the command was succesfully executed; otherwise <see langword="false"/>.</returns>
40-
public async Task<bool> ExecuteAsync(Guid menuGroup, uint commandId, string argument = "")
62+
public Task<bool> ExecuteAsync(Guid menuGroup, int commandId, string argument = "")
4163
{
42-
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
43-
IOleCommandTarget cs = await VS.GetRequiredServiceAsync<SUIHostCommandDispatcher, IOleCommandTarget>();
44-
45-
IntPtr inArgPtr = Marshal.AllocCoTaskMem(200);
46-
Marshal.GetNativeVariantForObject(argument, inArgPtr);
47-
48-
var result = cs.Exec(menuGroup, commandId, (uint)OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT, inArgPtr, IntPtr.Zero);
64+
return ExecuteAsync(new CommandID(menuGroup, commandId), argument);
65+
}
4966

50-
return result == VSConstants.S_OK;
67+
/// <summary>
68+
/// Executes a command by guid and ID
69+
/// </summary>
70+
/// <returns>Returns <see langword="true"/> if the command was succesfully executed; otherwise <see langword="false"/>.</returns>
71+
public Task<bool> ExecuteAsync(CommandID cmd, string argument = "")
72+
{
73+
return cmd.ExecuteAsync(argument);
5174
}
5275

5376
/// <summary>
@@ -56,7 +79,7 @@ public async Task<bool> ExecuteAsync(Guid menuGroup, uint commandId, string argu
5679
/// <returns>Returns <see langword="true"/> if the command was succesfully executed; otherwise <see langword="false"/>.</returns>
5780
public Task<bool> ExecuteAsync(VSConstants.VSStd97CmdID command, string argument = "")
5881
{
59-
return ExecuteAsync(typeof(VSConstants.VSStd97CmdID).GUID, (uint)command, argument);
82+
return ExecuteAsync(typeof(VSConstants.VSStd97CmdID).GUID, (int)command, argument);
6083
}
6184

6285
/// <summary>
@@ -65,7 +88,7 @@ public Task<bool> ExecuteAsync(VSConstants.VSStd97CmdID command, string argument
6588
/// <returns>Returns <see langword="true"/> if the command was succesfully executed; otherwise <see langword="false"/>.</returns>
6689
public Task<bool> ExecuteAsync(VSConstants.VSStd2KCmdID command, string argument = "")
6790
{
68-
return ExecuteAsync(typeof(VSConstants.VSStd2KCmdID).GUID, (uint)command, argument);
91+
return ExecuteAsync(typeof(VSConstants.VSStd2KCmdID).GUID, (int)command, argument);
6992
}
7093
}
7194
}

src/Community.VisualStudio.Toolkit.Shared/Events/Events.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ internal Events()
1515
ThreadHelper.ThrowIfNotOnUIThread();
1616
var dte = (DTE2)ServiceProvider.GlobalProvider.GetService(typeof(DTE));
1717
Assumes.Present(dte);
18-
_events = (Events2)dte.Events;
18+
_events = (Events2)dte.Events;
1919
}
2020

2121
/// <summary>
@@ -76,9 +76,10 @@ internal Events()
7676
private PublishEvents? _publishEvents;
7777
public PublishEvents? PublishEvents => _publishEvents ??= _events?.PublishEvents;
7878

79-
80-
private SelectionEvents? _selectionEvents;
81-
public SelectionEvents? SelectionEvents => _selectionEvents ??= _events?.SelectionEvents;
79+
/// <summary>
80+
/// Events related to the selection in Visusal Studio
81+
/// </summary>
82+
public SelectionEvents? SelectionEvents => new();
8283

8384
/// <summary>
8485
/// Events related to the editor documents.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System;
2+
using Microsoft;
3+
using Microsoft.VisualStudio;
4+
using Microsoft.VisualStudio.Shell;
5+
using Microsoft.VisualStudio.Shell.Interop;
6+
7+
namespace Community.VisualStudio.Toolkit
8+
{
9+
/// <summary>
10+
/// Events related to the selection in Visusal Studio.
11+
/// </summary>
12+
public class SelectionEvents : IVsSelectionEvents
13+
{
14+
internal SelectionEvents()
15+
{
16+
ThreadHelper.ThrowIfNotOnUIThread();
17+
var monitor = (IVsMonitorSelection)ServiceProvider.GlobalProvider.GetService(typeof(SVsShellMonitorSelection));
18+
Assumes.Present(monitor);
19+
monitor.AdviseSelectionEvents(this, out _);
20+
}
21+
22+
/// <summary>
23+
/// Fires when the selection changes
24+
/// </summary>
25+
public event EventHandler<SelectionChangedEventArgs>? SelectionChanged;
26+
27+
/// <summary>
28+
/// Fires when the UI Context changes.
29+
/// </summary>
30+
public event EventHandler<UIContextChangedEventArgs>? UIContextChanged;
31+
32+
int IVsSelectionEvents.OnSelectionChanged(IVsHierarchy pHierOld, uint itemidOld, IVsMultiItemSelect pMISOld, ISelectionContainer pSCOld, IVsHierarchy pHierNew, uint itemidNew, IVsMultiItemSelect pMISNew, ISelectionContainer pSCNew)
33+
{
34+
SelectionChanged?.Invoke(this, new SelectionChangedEventArgs(pHierOld, pHierNew));
35+
return VSConstants.S_OK;
36+
}
37+
38+
int IVsSelectionEvents.OnElementValueChanged(uint elementid, object varValueOld, object varValueNew)
39+
{
40+
return VSConstants.S_OK;
41+
}
42+
43+
int IVsSelectionEvents.OnCmdUIContextChanged(uint dwCmdUICookie, int fActive)
44+
{
45+
UIContextChanged?.Invoke(this, new UIContextChangedEventArgs(fActive == 1));
46+
return VSConstants.S_OK;
47+
}
48+
}
49+
50+
/// <summary>
51+
/// EventArgs for when the selection changes in Visual Studio
52+
/// </summary>
53+
public class SelectionChangedEventArgs : EventArgs
54+
{
55+
/// <summary>
56+
/// Creates a new instance of the EventArgs.
57+
/// </summary>
58+
public SelectionChangedEventArgs(IVsHierarchy from, IVsHierarchy to)
59+
{
60+
From = from;
61+
To = to;
62+
}
63+
64+
/// <summary>
65+
/// What the selection was before the change.
66+
/// </summary>
67+
public IVsHierarchy From { get; }
68+
69+
/// <summary>
70+
/// What the selection is currently after the change.
71+
/// </summary>
72+
public IVsHierarchy To { get; }
73+
}
74+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System.Runtime.InteropServices;
2+
using System.Threading.Tasks;
3+
using Community.VisualStudio.Toolkit;
4+
using Microsoft.VisualStudio;
5+
using Microsoft.VisualStudio.OLE.Interop;
6+
using Microsoft.VisualStudio.Shell;
7+
using Microsoft.VisualStudio.Shell.Interop;
8+
using Microsoft.VisualStudio.Threading;
9+
10+
namespace System.ComponentModel.Design
11+
{
12+
/// <summary>
13+
/// Extension methods for <see cref="System.Threading.Tasks.Task" /> and <see cref="JoinableTask" />.
14+
/// </summary>
15+
public static class CommandExtensions
16+
{
17+
/// <summary>
18+
/// Executes the command.
19+
/// </summary>
20+
/// <returns>Returns <see langword="true"/> if the command was succesfully executed; otherwise <see langword="false"/>.</returns>
21+
public static async Task<bool> ExecuteAsync(this CommandID cmd, string argument = "")
22+
{
23+
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
24+
IOleCommandTarget cs = await VS.GetRequiredServiceAsync<SUIHostCommandDispatcher, IOleCommandTarget>();
25+
26+
IntPtr inArgPtr = Marshal.AllocCoTaskMem(200);
27+
28+
try
29+
{
30+
Marshal.GetNativeVariantForObject(argument, inArgPtr);
31+
var result = cs.Exec(cmd.Guid, (uint)cmd.ID, (uint)OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT, inArgPtr, IntPtr.Zero);
32+
33+
return result == VSConstants.S_OK;
34+
}
35+
finally
36+
{
37+
Marshal.Release(inArgPtr);
38+
}
39+
}
40+
}
41+
}

src/Community.VisualStudio.Toolkit.Shared/VSSDK.Helpers.Shared.projitems

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<Compile Include="$(MSBuildThisFileDirectory)Commands\CommandAttribute.cs" />
1313
<Compile Include="$(MSBuildThisFileDirectory)Editor\WpfTextViewCreationListener.cs" />
1414
<Compile Include="$(MSBuildThisFileDirectory)Events\BuildEvents.cs" />
15+
<Compile Include="$(MSBuildThisFileDirectory)Events\SelectionEvents.cs" />
1516
<Compile Include="$(MSBuildThisFileDirectory)Events\SolutionEvents.cs" />
1617
<Compile Include="$(MSBuildThisFileDirectory)Events\DocumentEvents.cs" />
1718
<Compile Include="$(MSBuildThisFileDirectory)ExtensionMethods\ExceptionExtensions.cs" />
@@ -24,6 +25,7 @@
2425
<Compile Include="$(MSBuildThisFileDirectory)ExtensionMethods\DteExtensions.cs" />
2526
<Compile Include="$(MSBuildThisFileDirectory)ExtensionMethods\SolutionExtensions.cs" />
2627
<Compile Include="$(MSBuildThisFileDirectory)ExtensionMethods\ITextViewExtensions.cs" />
28+
<Compile Include="$(MSBuildThisFileDirectory)ExtensionMethods\CommandExtensions.cs" />
2729
<Compile Include="$(MSBuildThisFileDirectory)ExtensionMethods\TaskExtensions.cs" />
2830
<Compile Include="$(MSBuildThisFileDirectory)ExtensionMethods\ITextBufferExtensions.cs" />
2931
<Compile Include="$(MSBuildThisFileDirectory)ExtensionMethods\WindowExtensions.cs" />

test/VSSDK.TestExtension/TestExtensionPackage.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Threading;
44
using Community.VisualStudio.Toolkit;
55
using Microsoft.VisualStudio;
6-
using Microsoft.VisualStudio.Imaging;
76
using Microsoft.VisualStudio.Shell;
87
using TestExtension;
98
using TestExtension.Commands;
@@ -38,6 +37,7 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
3837
await MultiInstanceWindowCommand.InitializeAsync(this);
3938
await BuildActiveProjectAsyncCommand.InitializeAsync(this);
4039
await BuildSolutionAsyncCommand.InitializeAsync(this);
40+
4141
}
4242
}
4343
}

0 commit comments

Comments
 (0)