Click here to Skip to main content
15,895,656 members
Articles / Programming Languages / C#

Automatic highlighting of matching braces for Visual Studio C++

Rate me:
Please Sign up or sign in to vote.
4.47/5 (12 votes)
5 Nov 2008GPL33 min read 115.5K   1K   27  
This add-in enables highlighting of matching braces next to the cursor for Visual Studio C++.
��using System;

using Extensibility;

using EnvDTE;

using EnvDTE80;

using System.Diagnostics;

using System.Runtime.InteropServices;

using System.Text;

using System.Windows.Forms;



namespace matchingbraces

{

  public struct Point

  {

    public int X;

    public int Y;

  }

  public struct MOUSEHOOKSTRUCT

  {

    public Point pt;

    public IntPtr WindowHandle;

    public uint wHitTestCode;

    public UIntPtr dwExtraInfo;

  }



  public struct MOUSEHOOKSTRUCTEX

  {

    public MOUSEHOOKSTRUCT mstruct;

    public uint mouseData;

  }



  public class Connect : IDTExtensibility2

  {

    private DTE2 _applicationObject;

    private AddIn _addInInstance;

    private EnvDTE80.TextDocumentKeyPressEvents textDocKeyEvents;



    #region constans

    private const int WH_MOUSE = 7;

    private const int WH_KEYBOARD = 2;

    private const int WM_PAINT = 0x000F;

    private const int GWL_WNDPROC = (-4);

    private const int WM_LBUTTONUP = 0x0202;

    private const int WM_LBUTTONDOWN = 0x0201;

    private const int WM_MOUSEMOVE = 0x0200;

    private const int HC_ACTION = 0;

    private const int VK_BACK = 0x08;

    private const int VK_LEFT = 0x25;

    private const int VK_RIGHT = 0x27;

    private const int VK_UP = 0x26;

    private const int VK_DOWN = 0x28;

    private const int VK_END = 0x23;

    private const int VK_HOME = 0x24;

    private const int VK_PRIOR = 0x21;

    private const int VK_NEXT = 0x22;

    private const int VK_CONTROL = 0x11;

    private const int KF_ALTDOWN = 1 << 29;

    private const int VK_DELETE = 0x7F;

    private const int WM_SETREDRAW = 0x000B;

    #endregion



    delegate int HookProc(int code, IntPtr wParam, ref MOUSEHOOKSTRUCTEX lParam);

    private HookProc MouseProcDelegate = null;

    private IntPtr mhhook;

    delegate int HookProcKeyboard(int code, IntPtr wParam, IntPtr lParam);

    private HookProcKeyboard KeyboardProcDelegate = null;

    private IntPtr khook;



    bool _highlight = false;

    bool _after = false;

    bool _ignore = false;

    private IntPtr _editorWindow = IntPtr.Zero;

    

    //check if the window with the focus/mouse inside is the editor window

    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;

    }



    private static bool isScrollWindow(IntPtr hWnd)

    {

      IntPtr res;

      StringBuilder ClassName = new StringBuilder(256);

      res = GetClassName(hWnd, ClassName, ClassName.Capacity);

      if (res != IntPtr.Zero)

      {

        if (ClassName.ToString() == "ScrollBar")

        {

          return true;

        }

      }

      return false;

    }



    private bool isCPlusPlusText()

    {

      if (_applicationObject.ActiveDocument == null ||

          _applicationObject.ActiveDocument.Language != "C/C++")

      {

        return false;

      }

      else

      {

        return true;

      }

    }

    

    private void EndUndoContext(TextSelection Selection, IntPtr EditorWindowHwnd)

    {

      if (_highlight == false)

      {

        return;

      }



      if (Selection == null)

      {

        Selection = (TextSelection)_applicationObject.ActiveDocument.Selection;

      }



      TextWindow tw = (TextWindow)_applicationObject.ActiveWindow.Object;

      TextPane tpn = tw.ActivePane;

      TextPoint tpt = tpn.StartPoint;



      EditPoint ep_before = Selection.ActivePoint.CreateEditPoint();

      bool selection_visible = false;

      if (tpn.IsVisible(ep_before, 1))

      {

        selection_visible = true;

      }

      else

      {

        SendMessage(EditorWindowHwnd, WM_SETREDRAW, (IntPtr)0, (IntPtr)0);

      }



      HideCaret(EditorWindowHwnd);



      _applicationObject.UndoContext.SetAborted();

      Selection.MoveToPoint(ep_before, false);



      if (!selection_visible)

      {

        tpn.TryToShow(tpt, vsPaneShowHow.vsPaneShowTop, 0);

        SendMessage(EditorWindowHwnd, WM_SETREDRAW, (IntPtr)1, (IntPtr)0);

      }



      ShowCaret(EditorWindowHwnd);



      _highlight = false;

    }



    private void Highlight(IntPtr EditorWindowHwnd)

    {

      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))

        {

          _applicationObject.UndoContext.Open("matchingbrace", false);

          ts.CharLeft(true, 1);

          _ignore = true;

          ts.Text = s;

          _editorWindow = EditorWindowHwnd;

          _highlight = true;

        }

      }

      catch

      {

      }

    }



    private int MouseProc(int code, IntPtr wParam, ref MOUSEHOOKSTRUCTEX lParam)

    {

      try

      {

        if (code == HC_ACTION && _highlight && (wParam.ToInt32() == WM_MOUSEMOVE))

        {

          if (!isEditorWindow(lParam.mstruct.WindowHandle) &&

              !isScrollWindow(lParam.mstruct.WindowHandle))

          {

            if (_editorWindow != IntPtr.Zero)

            {

              EndUndoContext(null, _editorWindow);

            }

            return CallNextHookEx(mhhook, code, wParam, ref lParam);

          }

        }



        if (code == HC_ACTION && (wParam.ToInt32() == WM_LBUTTONDOWN) &&

           !isScrollWindow(lParam.mstruct.WindowHandle))

        {

          EndUndoContext(null, lParam.mstruct.WindowHandle);

          return CallNextHookEx(mhhook, code, wParam, ref lParam);

        }



        //only left mouse button

        if (code != HC_ACTION || wParam.ToInt32() != WM_LBUTTONUP)

        {

          return CallNextHookEx(mhhook, code, wParam, ref lParam);

        }



        if (!isCPlusPlusText())

        {

          return CallNextHookEx(mhhook, code, wParam, ref lParam);

        }



        //only if clicked inside the editor window

        if (!isEditorWindow(lParam.mstruct.WindowHandle))

        {

          return CallNextHookEx(mhhook, code, wParam, ref lParam);

        }



        Highlight(lParam.mstruct.WindowHandle);

      }

      catch

      {

      }



      return CallNextHookEx(mhhook, code, wParam, ref lParam);

    }



    private int KeyboardProc(int code, IntPtr wParam, IntPtr lParam)

    {

      try

      {

        if (code != HC_ACTION)

        {

          return CallNextHookEx(khook, code, wParam, lParam);

        }



        if (!isCPlusPlusText())

        {

          return CallNextHookEx(khook, code, wParam, lParam);

        }



        IntPtr h = GetFocus();

        if (!isEditorWindow(h))

        {

          return CallNextHookEx(khook, code, wParam, lParam);

        }



        //key down event

        if ((0x40000000 & (int)lParam) == 0)

        {

          if ((int)wParam == VK_CONTROL || (KF_ALTDOWN & (int)lParam) != 0)

          {

            bool nix = false;

            BeforeKeyPress("CONTROL_KEY", 

                          (TextSelection)_applicationObject.ActiveDocument.Selection,

                           false, ref nix);

            return CallNextHookEx(khook, code, wParam, lParam);

          }

          return CallNextHookEx(khook, code, wParam, lParam);

        }



        //key up event

        if ((int)wParam == VK_LEFT || (int)wParam == VK_RIGHT || (int)wParam == VK_END ||

            (int)wParam == VK_UP || (int)wParam == VK_DOWN || (int)wParam == VK_PRIOR || 

            (int)wParam == VK_NEXT)

        {

          bool nix = false;

          BeforeKeyPress("MOVE_KEY", (TextSelection)_applicationObject.ActiveDocument.Selection,

                         false, ref nix);

          return CallNextHookEx(khook, code, wParam, lParam);

        }



        if ((int)wParam == VK_HOME)

        {

          bool nix = false;

          BeforeKeyPress("HOME_KEY", (TextSelection)_applicationObject.ActiveDocument.Selection,

                         false, ref nix);

          return CallNextHookEx(khook, code, wParam, lParam);

        }



      }

      catch

      {

      }



      return CallNextHookEx(khook, code, wParam, lParam);

    }



    private void AfterKeyPress(string Keypress, TextSelection Selection,

                               bool InStatementCompletion)

    {

      if (_ignore == true)

      {

        _ignore = false;

        return;

      }



      if (_after)

      {

        Highlight((IntPtr)Selection.TextPane.Window.HWnd);

        _after = false;

      }

    }



    private void BeforeKeyPress(string Keypress, EnvDTE.TextSelection Selection,

                                bool InStatementCompletion, ref bool CancelKeypress)

    {

      if (_ignore == true)

      {

        return;

      }



      EndUndoContext(Selection, (IntPtr)Selection.TextPane.Window.HWnd);



      //backspace

      if (Keypress == "\b")

      {

        _after = true;

      }



      if (Keypress[0] == (char)VK_DELETE)

      {

        _after = true;

      }



      if (Keypress == "MOVE_KEY")

      {

        _after = true;

        AfterKeyPress(Keypress, Selection, false);

      }

      return;

    }



    private void InitHook()

    {

      uint id = GetCurrentThreadId();



      //init the mouse and keyboard hook with the thread id of the Visual Studio IDE

      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);

    }



    //called on plugin loading

    public void OnConnection(object application, ext_ConnectMode connectMode, 

                             object addInInst, ref Array custom)

    {

      Debug.WriteLine("matchingbraces 0.3");

      _applicationObject = (DTE2)application;

      _addInInstance = (AddIn)addInInst;

      InitHook();



      _ignore = false;

      _highlight = false;

      _after = false;



      EnvDTE80.Events2 events = (EnvDTE80.Events2)_applicationObject.Events;

      textDocKeyEvents = 

        (EnvDTE80.TextDocumentKeyPressEvents)events.get_TextDocumentKeyPressEvents(null);

      textDocKeyEvents.BeforeKeyPress +=

         new _dispTextDocumentKeyPressEvents_BeforeKeyPressEventHandler(BeforeKeyPress);

      textDocKeyEvents.AfterKeyPress += 

         new _dispTextDocumentKeyPressEvents_AfterKeyPressEventHandler(AfterKeyPress);

    }



    public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)

    {

      UnhookWindowsHookEx(mhhook);

      UnhookWindowsHookEx(khook);

      textDocKeyEvents.BeforeKeyPress -=

        new _dispTextDocumentKeyPressEvents_BeforeKeyPressEventHandler(BeforeKeyPress);

      textDocKeyEvents.AfterKeyPress -= 

        new _dispTextDocumentKeyPressEvents_AfterKeyPressEventHandler(AfterKeyPress);

    }



    #region (Not used)

    public Connect()

    {

    }



    public void OnAddInsUpdate(ref Array custom)

    {

    }



    public void OnStartupComplete(ref Array custom)

    {

      EnvDTE.Properties txtEdCS2 =

       _applicationObject.get_Properties("TextEditor", "C/C++");

      bool word_wrap_c = (bool)txtEdCS2.Item("WordWrap").Value;



      if (word_wrap_c != true)

      {

        MessageBox.Show("You have 'WordWrap' disabled in the Editor settings. \nYou need" +

         " to enable it for the MatchingBraces plugin to work correctly.", "MatchingBraces - AddIn",

             MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

      }

    }



    public void OnBeginShutdown(ref Array custom)

    {

    }



    #endregion)



    #region (invokestuff)

    [DllImport("kernel32.dll")]

    static extern uint GetCurrentThreadId();

    [DllImport("user32.dll")]

    static extern IntPtr SetWindowsHookEx(int code, HookProc func, IntPtr hInstance, uint threadID);

    [DllImport("user32.dll")]

    static extern IntPtr SetWindowsHookEx(int code, HookProcKeyboard func, IntPtr hInstance, uint threadID);

    [DllImport("user32.dll")]

    static extern bool UnhookWindowsHookEx(IntPtr hhk);

    [DllImport("user32.dll")]

    static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, ref MOUSEHOOKSTRUCTEX lParam);

    [DllImport("user32.dll")]

    static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll")]

    static extern IntPtr GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

    [DllImport("user32.dll")]

    static extern IntPtr GetFocus();

    [DllImport("user32.dll", EntryPoint = "GetWindowLong")]

    static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);

    [DllImport("user32")]

    private static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr newProc);

    [DllImport("user32")]

    private static extern int CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, int Msg, int wParam, int lParam);

    [DllImport("user32.dll")]

    static extern bool HideCaret(IntPtr hWnd);

    [DllImport("user32.dll")]

    static extern bool ShowCaret(IntPtr hWnd);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

    #endregion



  }

}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions