Get mouse and keyboard events:
To get the add-in working, we first need to get notified if the user has moved the cursor somewhere inside the text editor using the mouse or the keyboard. The Visual Studio Add-in API does not provide a functionality for mouse events. It is necessary to use a Window Hook using the SetWindowsHookEx() function. The built-in TextDocumentKeyPressEvents does not work with the arrow keys. For these keys, a keyboard hook is needed:
private void InitHook()
{
uint id = GetCurrentThreadId();
this.MouseProcDelegate = new HookProc(this.MouseProc);
mhhook = SetWindowsHookEx(WH_MOUSE,
this.MouseProcDelegate, IntPtr.Zero, id);
this.KeyboardProcDelegate =
new HookProcKeyboard(this.KeyboardProc);
khook = SetWindowsHookEx(WH_KEYBOARD,
this.KeyboardProcDelegate, IntPtr.Zero, id);
}
To make use of SetWindowsHookEx(), it is necessary to use the Platform Invoke stuff. If the user released a key or released a mouse button, the MouseProc (or KeyboardProc) 'function' will be called before the event reaches the Visual Studio window.
Do something with the event (here keyboard):
If KeyboardProc is called, we only know that a key is released somewhere. First, we need to make sure that a document is open and that the document is a C++ source code:
private int KeyboardProc(int code, IntPtr wParam, IntPtr lParam)
{
try
{
if (code != HC_ACTION)
{
return CallNextHookEx(khook, code, wParam, lParam);
}
if (_applicationObject.ActiveDocument == null ||
_applicationObject.ActiveDocument.Language != "C/C++")
{
return CallNextHookEx(mhhook, code, wParam, lParam);
}
Next, we need to check if the event occurred inside the editor window and not somewhere else:
IntPtr h = GetFocus();
if (!isEditorWindow(h))
{
return CallNextHookEx(mhhook, code, wParam, lParam);
}
[...]
private static bool isEditorWindow(IntPtr hWnd)
{
IntPtr res;
StringBuilder ClassName = new StringBuilder(256);
res = GetClassName(hWnd, ClassName, ClassName.Capacity);
if (res != IntPtr.Zero)
{
if (ClassName.ToString() == "VsTextEditPane")
{
return true;
}
}
return false;
}
This is the case if the keyboard focus belongs to a window with the class "VsTextEditPane".
Next, we are (almost) ready to highlight the matching braces:
private void Highlight()
{
try
{
TextSelection ts =
(TextSelection)_applicationObject.ActiveDocument.Selection;
if (!ts.IsEmpty)
{
return;
}
EditPoint ep = ts.ActivePoint.CreateEditPoint();
String s = ep.GetText(-1);
String pattern = "({|}|\\[|\\]|\\(|\\))";
if (System.Text.RegularExpressions.Regex.IsMatch(s, pattern))
{
ts.CharLeft(true, 1);
ts.Text = s;
}
}
catch
{
}
}
Of course, it is first necessary to check if the cursor (returned by ts.ActivePoint.CreateEditPoint()) is on the right side of a brace, using GetText(-1) and a Regex which will match for all braces. If 'Yes', the brace will be selected using ts.CharLeft(true, 1) and replaced with a new one. This will trigger the built-in brace matching functionality of Visual Studio.
Avoid visible page scrolling:
After calling UndoContext.SetAborted(), the cursor will jump to the last position before UndoContext.Open() was called. It is necessary to move the page back to the current position. To hide this movement from the user, it is necessary to forbid the updating of the editor window by using: SendMessage(EditorWindowHwnd, WM_SETREDRAW, (IntPtr)0, (IntPtr)0). If everything is finished, window updating could be re-enabled using SendMessage(EditorWindowHwnd, WM_SETREDRAW, (IntPtr)1, (IntPtr)0).