-
Notifications
You must be signed in to change notification settings - Fork 107
/
GlobalCommandHook.cs
159 lines (130 loc) · 5.72 KB
/
GlobalCommandHook.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
namespace GitScc
{
using System;
using System.Collections.Generic;
using CommandID = System.ComponentModel.Design.CommandID;
using ErrorHandler = Microsoft.VisualStudio.ErrorHandler;
using IOleCommandTarget = Microsoft.VisualStudio.OLE.Interop.IOleCommandTarget;
using IVsRegisterPriorityCommandTarget = Microsoft.VisualStudio.Shell.Interop.IVsRegisterPriorityCommandTarget;
using OLECMD = Microsoft.VisualStudio.OLE.Interop.OLECMD;
using OleConstants = Microsoft.VisualStudio.OLE.Interop.Constants;
using SVsRegisterPriorityCommandTarget = Microsoft.VisualStudio.Shell.Interop.SVsRegisterPriorityCommandTarget;
internal sealed class GlobalCommandHook : IOleCommandTarget
{
private static GlobalCommandHook _instance;
private readonly BasicSccProvider _provider;
private readonly Dictionary<Guid, Dictionary<int, EventHandler>> _commandMap = new Dictionary<Guid, Dictionary<int, EventHandler>>();
private bool _hooked;
private uint _cookie;
private GlobalCommandHook(BasicSccProvider provider)
{
_provider = provider;
}
public static GlobalCommandHook GetInstance(BasicSccProvider provider)
{
if (provider == null)
throw new ArgumentNullException("provider");
if (_instance == null)
_instance = new GlobalCommandHook(provider);
return _instance;
}
public void HookCommand(CommandID command, EventHandler handler)
{
if (command == null)
throw new ArgumentNullException("command");
else if (handler == null)
throw new ArgumentNullException("handler");
Dictionary<int, EventHandler> map;
if (!_commandMap.TryGetValue(command.Guid, out map))
{
map = new Dictionary<int, EventHandler>();
_commandMap[command.Guid] = map;
}
EventHandler handlers;
if (!map.TryGetValue(command.ID, out handlers))
handlers = null;
map[command.ID] = (handlers + handler);
if (!_hooked)
{
IVsRegisterPriorityCommandTarget svc = (IVsRegisterPriorityCommandTarget)_provider.GetService(typeof(SVsRegisterPriorityCommandTarget));
if (svc != null && ErrorHandler.Succeeded(svc.RegisterPriorityCommandTarget(0, this, out _cookie)))
_hooked = true;
}
}
public void UnhookCommand(CommandID command, EventHandler handler)
{
if (command == null)
throw new ArgumentNullException("command");
else if (handler == null)
throw new ArgumentNullException("handler");
Dictionary<int, EventHandler> map;
if (!_commandMap.TryGetValue(command.Guid, out map))
return;
EventHandler handlers;
if (!map.TryGetValue(command.ID, out handlers))
return;
handlers -= handler;
if (handlers == null)
{
map.Remove(command.ID);
if (map.Count == 0)
{
_commandMap.Remove(command.Guid);
if (_commandMap.Count == 0)
{
Unhook();
}
}
return;
}
map[command.ID] = (handlers + handler);
}
private void Unhook()
{
if (_hooked)
{
_hooked = false;
((IVsRegisterPriorityCommandTarget)_provider.GetService(typeof(SVsRegisterPriorityCommandTarget))).UnregisterPriorityCommandTarget(_cookie);
}
}
private static bool GuidRefIsNull(ref Guid pguidCmdGroup)
{
// According to MSDN the Guid for the command group can be null and in this case the default
// command group should be used. Given the interop definition of IOleCommandTarget, the only way
// to detect a null guid is to try to access it and catch the NullReferenceExeption.
Guid commandGroup;
try
{
commandGroup = pguidCmdGroup;
}
catch (NullReferenceException)
{
// Here we assume that the only reason for the exception is a null guidGroup.
// We do not handle the default command group as definied in the spec for IOleCommandTarget,
// so we have to return OLECMDERR_E_NOTSUPPORTED.
return true;
}
return false;
}
int IOleCommandTarget.Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
{
// Exit as quickly as possible without creating exceptions. This function is performance critical!
if (GuidRefIsNull(ref pguidCmdGroup))
return (int)OleConstants.OLECMDERR_E_NOTSUPPORTED;
Dictionary<int, EventHandler> cmdMap;
if (!_commandMap.TryGetValue(pguidCmdGroup, out cmdMap))
return (int)OleConstants.OLECMDERR_E_UNKNOWNGROUP;
EventHandler handler;
if (cmdMap.TryGetValue(unchecked((int)nCmdID), out handler))
{
handler(this, EventArgs.Empty);
}
return (int)OleConstants.OLECMDERR_E_NOTSUPPORTED;
}
int IOleCommandTarget.QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
{
// Don't do anything here. This function is 100% performance critical!
return (int)OleConstants.OLECMDERR_E_NOTSUPPORTED;
}
}
}