Click here to Skip to main content
15,888,521 members
Articles / Programming Languages / C# 4.0

Writing a P2P Snippet sharing Extension for Visual Studio 2010 (VSX 2010)

Rate me:
Please Sign up or sign in to vote.
4.50/5 (6 votes)
31 Mar 2010GPL311 min read 36.4K   332   21  
CodeXchange is a simple Visual Studio extension which allows you to create, edit and share snippets with your peers without leaving the Visual Studio 2010 IDE.
using System;
using System.Windows.Forms;
using System.Drawing.Design;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.ComponentModel.Design;

using Microsoft.Win32;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.TextManager.Interop;

using EnvDTE;
using EnvDTE90;
using EnvDTE80;

using Sand.Services.CodeXchange.API;
using Sand.Services.CodeXchange.Client;
using Sand.Services.CodeXchange.Client.CodeXchangeService;

namespace CodeXchange.CodeXchangeVS2010Addin
{
    /// <summary>
    /// This is the class that implements the package exposed by this assembly.
    ///
    /// The minimum requirement for a class to be considered a valid package for Visual Studio
    /// is to implement the IVsPackage interface and register itself with the shell.
    /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
    /// to do it: it derives from the Package class that provides the implementation of the 
    /// IVsPackage interface and uses the registration attributes defined in the framework to 
    /// register itself and its components with the shell.
    /// </summary>
    // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is
    // a package.
    [PackageRegistration(UseManagedResourcesOnly = true)]
    // This attribute is used to register the informations needed to show the this package
    // in the Help/About dialog of Visual Studio.
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    // This attribute is needed to let the shell know that this package exposes some menus.
    [ProvideMenuResource("Menus.ctmenu", 1)]
    // This attribute registers a tool window exposed by this package.
    [ProvideToolWindow(typeof(CodeXchangeToolWindow))]
    [Guid(GuidList.guidCodeXchangeVS2010AddinPkgString)]
    public sealed class CodeXchangeVS2010AddinPackage : Package
    {
        /* Extension menus */
        OleMenuCommand menuShowToolWin = null;
        OleMenuCommand menuInsertFromCodeXchange = null;
        OleMenuCommand menuContributeToCodeXchange = null;
        OleMenuCommand menuConnectToCodeXchange = null;

        /* Extension tool windows */
        CodeXchangeToolWindow toolwndCodeXchange = null;

        /// <summary>
        /// Default constructor of the package.
        /// Inside this method you can place any initialization code that does not require 
        /// any Visual Studio service because at this point the package object is created but 
        /// not sited yet inside Visual Studio environment. The place to do all the other 
        /// initialization is the Initialize method.
        /// </summary>
        public CodeXchangeVS2010AddinPackage()
        {
            Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
        }

        /// <summary>
        /// This function is called when the user clicks the menu item that shows the 
        /// tool window. See the Initialize method to see how the menu item is associated to 
        /// this function using the OleMenuCommandService service and the MenuCommand class.
        /// </summary>
        private void CreateToolWindow()
        {
            // Get the instance number 0 of this tool window. This window is single instance so this instance
            // is actually the only one.
            // The last flag is set to true so that if the tool window does not exists it will be created.
            toolwndCodeXchange = FindToolWindow(typeof(CodeXchangeToolWindow), 0, true) as CodeXchangeToolWindow;

            if ((null == toolwndCodeXchange) || (null == toolwndCodeXchange.Frame))
            {
                throw new NotSupportedException(Resources.CanNotCreateWindow);
            }
        }

        private void ShowToolWindow()
        {
            IVsWindowFrame windowFrame = (IVsWindowFrame)toolwndCodeXchange.Frame;
            Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show());
        }

        private void ExecuteMenuCommandCallback(object sender, EventArgs e)
        {
            OleMenuCommand command = sender as OleMenuCommand;

            if (command.CommandID.ID == (int)PkgCmdIDList.cmdidCodeXchange)
            {
                // Show the CodeXchange VS tool window
                ShowToolWindow();
            }

            if (command.CommandID.ID == (int)PkgCmdIDList.cmdidConnectToCodeXchange)
            {
                if (toolwndCodeXchange.CodeXchange.IsConnected == false)
                    toolwndCodeXchange.CodeXchange.Connect();
            }

            if (command.CommandID.ID == (int)PkgCmdIDList.cmdidContributeToCodeXchange)
            {
                // Get active document currently selected text
                string selectedText = GetSelectedText();

                toolwndCodeXchange.CodeXchange.AddCodeSnippet(selectedText);
            }

            if (command.CommandID.ID == (int)PkgCmdIDList.cmdidInsertFromCodeXchange)
            {
                // Ensure the tool window is currently visible
                ShowToolWindow();

                // Show search user interface
                toolwndCodeXchange.CodeXchange.SwithToSearchMode();
            }
        }

        private void ChangeCallback(object sender, EventArgs e)
        {
            OleMenuCommand command = sender as OleMenuCommand;
        }

        private void BeforeStatusQueryCallback(object sender, EventArgs e)
        {
            OleMenuCommand command = sender as OleMenuCommand;

            if (command.CommandID.ID == (int)PkgCmdIDList.cmdidCodeXchange)
            {
                // This command is always available
                command.Enabled = true;
            }

            if (command.CommandID.ID == (int)PkgCmdIDList.cmdidInsertFromCodeXchange)
            {
                // This command is only available if we are logged to the online repository
                command.Visible = command.Enabled = toolwndCodeXchange.CodeXchange.IsConnected;
            }

            if (command.CommandID.ID == (int)PkgCmdIDList.cmdidConnectToCodeXchange)
            {
                // This command is only available if we are logged to the online repository
                command.Visible = command.Enabled = !toolwndCodeXchange.CodeXchange.IsConnected;
            }

            if (command.CommandID.ID == (int)PkgCmdIDList.cmdidContributeToCodeXchange)
            {
                // This command is only available if we are logged to the online repository
                // and some text is selected on the active editor
                if (toolwndCodeXchange.CodeXchange.IsConnected)
                {
                    //check if any text is highlighted or disable the menu
                    if (GetSelectedText().Length > 0)
                        command.Enabled = true;
                    else
                        command.Enabled = false;
                }
                else
                {
                    command.Enabled = false;
                }
            }
        }

        public string GetSelectedText()
        {
            IVsTextManager txtMgr = (IVsTextManager)GetService(typeof(SVsTextManager));
            IVsTextView txtView = null;
            string selectedText = string.Empty;
            
            int mustHaveFocus = 1;
            txtMgr.GetActiveView(mustHaveFocus, null, out txtView);
            txtView.GetSelectedText(out selectedText);
            
            return selectedText;
        }

        private void SetupMenus()
        {
            // Add our command handlers for menu (commands must exist in the .vsct file)
            OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if (null != mcs)
            {
                // Create the command for the tool window
                CommandID commandID = null;

                commandID = new CommandID(GuidList.guidCodeXchangeVS2010AddinCmdSet, (int)PkgCmdIDList.cmdidCodeXchange);
                menuShowToolWin = new OleMenuCommand(
                    ExecuteMenuCommandCallback,
                    ChangeCallback,
                    BeforeStatusQueryCallback,
                    commandID);

                /* Add the show/hide tool window command */
                mcs.AddCommand(menuShowToolWin);

                commandID = new CommandID(GuidList.guidCodeXchangeVS2010AddinCmdSet, (int)PkgCmdIDList.cmdidInsertFromCodeXchange);
                menuInsertFromCodeXchange = new OleMenuCommand(
                    ExecuteMenuCommandCallback,
                    ChangeCallback,
                    BeforeStatusQueryCallback,
                    commandID);

                /* Add the 'Insert from CodeXchange' command */
                mcs.AddCommand(menuInsertFromCodeXchange);

                commandID = new CommandID(GuidList.guidCodeXchangeVS2010AddinCmdSet, (int)PkgCmdIDList.cmdidContributeToCodeXchange);
                menuContributeToCodeXchange = new OleMenuCommand(
                    ExecuteMenuCommandCallback,
                    ChangeCallback,
                    BeforeStatusQueryCallback,
                    commandID);

                /* Add the 'Contribute to CodeXchange' command */
                mcs.AddCommand(menuContributeToCodeXchange);

                commandID = new CommandID(GuidList.guidCodeXchangeVS2010AddinCmdSet, (int)PkgCmdIDList.cmdidConnectToCodeXchange);
                menuConnectToCodeXchange = new OleMenuCommand(
                    ExecuteMenuCommandCallback,
                    ChangeCallback,
                    BeforeStatusQueryCallback,
                    commandID);

                /* Add the 'Connect to CodeXchange' command */
                mcs.AddCommand(menuConnectToCodeXchange);
            }
        }

        private void ShowNoDocumentOnIDEError()
        {
            MessageBox.Show(
                "No document openend in the IDE.\n\n" +
                "Please select a document prior downloading the snippet.",
                "CodeXchange Addin",
                MessageBoxButtons.OK,
                MessageBoxIcon.Error);
        }

        private void ConnectEvents()
        {
            toolwndCodeXchange.CodeXchange.SnippetDownload += new Sand.Services.CodeXchange.Client.CodeXchangeDownloadSnippetEventHandler(CodeXchange_SnippetDownload);
            toolwndCodeXchange.CodeXchange.OnConnected += new EventHandler(CodeXchange_OnConnected);
        }

        /// <summary>
        /// This method is called when the user has succesfully connected to the online repository
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void CodeXchange_OnConnected(object sender, EventArgs e)
        {
            // Get a refence to the IVsToolbox interface.
            IVsToolbox  tbs = GetService(typeof(IVsToolbox)) as IVsToolbox;

            // For each snippet add a toolbox item
            foreach (Snippet snippet in toolwndCodeXchange.CodeXchange.UserContributedSnippets)
            {
                TBXITEMINFO[] itemInfo = new TBXITEMINFO[1];
                OleDataObject tbItem = new OleDataObject();
             
                itemInfo[0].bstrText = snippet.Summary;
                itemInfo[0].dwFlags = (uint)__TBXITEMINFOFLAGS.TBXIF_DONTPERSIST;

                tbItem.SetText(snippet.Code, TextDataFormat.Text);

                tbs.AddItem(tbItem, itemInfo, "My CodeXchange Snippets");
            }
        }

        /// <summary>
        /// This method is called when the user wants to insert a snippet from the online repository
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void CodeXchange_SnippetDownload(object sender, Sand.Services.CodeXchange.Client.CodeXchangeSnippetEventArgs e)
        {
            // Get a reference to the DTE object service
            DTE2 dte2 = GetService(typeof(SDTE)) as EnvDTE80.DTE2;

            // Get a reference to the shell service interface
            IVsUIShell uiShell = GetService(typeof(SVsUIShell)) as IVsUIShell;

            // Check if we have a valid document...
            if (dte2.ActiveDocument != null)
            {
                if (!dte2.ActiveDocument.ReadOnly)
                {
                    //Get the current text selection from the active document...
                    TextSelection tsSelection = dte2.ActiveDocument.Selection as TextSelection;

                    // We can edit the document ....
                    if (tsSelection != null)
                    {
                        try
                        {
                            bool undoContext = false;

                            //Check to see if UndoContext object is already open. 
                            if (dte2.UndoContext.IsOpen == true)
                                dte2.UndoContext.Close();

                            // Open a new undo context 
                            dte2.UndoContext.Open("Snippet inserted from CodeXchange", true);

                            // remeber we opened an undo context
                            undoContext = true;

                            EditPoint start = tsSelection.TopPoint.CreateEditPoint();
                            EditPoint endpt = tsSelection.BottomPoint.CreateEditPoint();

                            //Insert source code to document....
                            endpt.Insert(
                                System.Environment.NewLine +
                                e.Snippet.Code +
                                System.Environment.NewLine);
                            endpt.StartOfDocument();
                            start.EndOfDocument();
                            endpt.SmartFormat(start);

                            //If UndoContext was already open, don't close it. 
                            if (undoContext == true)
                            {
                                //Close the UndoContext object to commit the changes. 
                                dte2.UndoContext.Close();
                            }
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show(
                                "Could not insert code snippet .The error message was : " + ex.Message.ToString(),
                                "CodeXchange Addin",
                                MessageBoxButtons.OK,
                                MessageBoxIcon.Error);
                        }
                    }
                    else
                    {
                        Guid clsid = Guid.Empty;
                        int result;
                        uiShell.ShowMessageBox(
                                   0,
                                   ref clsid,
                                   "CodeXchange",
                                   "Could not insert code snippet. The active document is read-only",
                                   string.Empty,
                                   0,
                                   OLEMSGBUTTON.OLEMSGBUTTON_OK,
                                   OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
                                   OLEMSGICON.OLEMSGICON_CRITICAL,
                                   0,        // false
                                   out result);
                    }
                }
                else
                    ShowNoDocumentOnIDEError();
            }
            else
                ShowNoDocumentOnIDEError();
        }

        /////////////////////////////////////////////////////////////////////////////
        // Overriden Package Implementation
        #region Package Members

        /// <summary>
        /// Initialization of the package; this method is called right after the package is sited, so this is the place
        /// where you can put all the initilaization code that rely on services provided by VisualStudio.
        /// </summary>
        protected override void Initialize()
        {
            Trace.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
            base.Initialize();

            CreateToolWindow();
            SetupMenus();
            ConnectEvents();

        }
        #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
Software Developer
Spain Spain
Hi! I'm 22 years old. I live in a sunny mediterranian city called Barcelona.

I am a big fan of .NET and have been working with c# for a few years now.

Comments and Discussions