Did you know that you can navigate the posts by swiping left and right?

Visual Studio 2010 Extension - Unicode Converter 1.0 (with Tutorial)

19 Aug 2010 . Unknown . Comments

Unicode Converter V1.0For quite some time I’m using a tool a co-worker has written to convert some text into “websafe” text. That’s implying the convertion of special characters into html entities wherever possible, otherwise in their corresponding unicode notation (&#0000;), replacing more then one space in a row with non-breaking spaces (&nbsp;), replacing single line-breaks with the corresponding html-tag (<br />) and also enclosing paragraphs with their html-tag (<p>…</p>). Texts converted in this manner are having less or even no encoding problems in any webbrowsers and you don’t have to fiddle around with manual adding those paragraph tags.

I’m using this tool a lot when working with html files directly in Notepad++ or a similar texteditor. But since that time I’m now mainly working within Visual Studio. You may argue that you can add the text in the design-view and Visual Studio is converting it for you. But not all special chars are converted and when the text is some kind of rich edit (e.g. from Word) you get a lot of unwanted formatting, too! So you could paste it into a plain-text editor, copy it back to the clipboardf and then paste it into Vis…. For God’s sake! Let’s abadon this thought immediately before someone will really do it this way! Saying it with a borrowed term: There’s an app for that! Winking smile

First you have to make a tough decission: Go straight to the Visual Studio Gallery, download, install and just use my “Unicode Converter”-Extension. Or you read on and I will explain a few things about how I solved some problems. Oh, and just in case: there is a link to the sourcecode of the extension at the end of this article! [more]

I started with a new “Shared Add-In”-project, entered all the standard stuff and took a look at the created files. Great, there are a lot of comments and even if you never had created a Visual Studio extension before (like me!) you quickly grasp the base-correlations. So, first step was to implement the main logic: The convertion of a given string. I was allowed to use the prepared hashtables with the html-entities and other special strings, after all a 1.5MB ressource-file. To avoid using the ResourceReader-class I converted that .rersource-file into a .resx-file with the Resource File Generator (Resgen.exe). Filling my hastables with the data from the resource-file starts in the Initialize() method of the extension, right after adding my command to the Visual Studio menu:

protected override void Initialize() {
    base.Initialize();
    OleMenuCommandService mcs =
        GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
    if (null != mcs) {
        CommandID menuCommandID =
            new CommandID(
                GuidList.guidUnicodeConverterCmdSet,
                (int)PkgCmdIDList.cmdidUnicode);
        MenuCommand menuItem =
            new MenuCommand(MenuItemCallback, menuCommandID);
        mcs.AddCommand(menuItem);
    }
    fillHash();
}

The actual convertion is happening in three foreach-loops, nothing special here. So let’s take a look at the MenuItemCallback() method that is called whenever my command is started. Let’s enumerate what we want to do here:

  1. Get the currently selected text from the editor
  2. Convert this text
  3. Write the converted text back to the editor, replacing the original one

Sounds pretty easy, doesn’t it? Well, step 1 and 2 are not quite difficult, but step 3 needs some considerations because our Span with the selected text is a readonly-property. So we lookup the index of our selected text, delete the old Span and insert a newly created span with our converted text at the very same position. Phew, that wasn’t that hard, was it? Here we go:

private void MenuItemCallback(object sender, EventArgs e) {
    IVsUIShell uiShell =
        (IVsUIShell)GetService(typeof(SVsUIShell));
    Guid clsid = Guid.Empty;
    IWpfTextView view = GetActiveTextView();
    String selectedText =
        view.Selection.SelectedSpans[0].GetText();
    var allText = view.TextSnapshot.GetText();
    int pos = allText.IndexOf(selectedText);
    OptionPageGrid page =
        (OptionPageGrid)GetDialogPage(typeof(OptionPageGrid));

    convertUnicode(ref selectedText);
    if (page.replaceSpaces)
        replaceDoubleSpace(ref selectedText);
    if (page.replaceNewline)
        replaceNewLine(ref selectedText);

    Span deleteSpan = view.Selection.SelectedSpans[0];
    view.TextBuffer.Delete(deleteSpan);
    view.TextBuffer.Insert(pos, selectedText);
}

Before I did a first test-run I wanted to add my new command to the context menu of the Visual Studio editor instead of creating a new menu entry. So, next stop: The Visual Studio Command Table, a XML-file with the extension .vsct. It took a few minutes until I understood what happend in there. But after some Bing’ing (gosh, that sounds awfull! Sad smile) I found the lines I need to change to place my command into the context menu:

<Group guid="guidUnicodeConverterCmdSet" id="UnicodeMenuGroup" priority="0x0600">
    <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CODEWIN"/>
</Group>

Compiling, installing the Add-In, restarting Visual Studio and… where is my context menu entry?! I opened a simple html file and tried to convert some text in it, but my extension didn’t show up. Well, actually it DID show up but I searched for it at the wrong place: When in a codefile (.cs) I could happily see my context menu entry and it worked like a charm. But why did it only appears in the code editor and not in the html code-view?

I was Bing’ing again like “Big Ben” (knee-slapper…) but all I found were some cryptical information about .h-files with a C++ syntax. There were some GUIDs similiar to those I saw in my command table but when I tried using them as a parent I got some compile-errors. Some time-consuming investigations later (I did not found a hint even in the msdn) I found a small tool, the VSCT PowerToy in the MSDN Code Gallery. It’s “a read-only viewer for Visual Studio 2010 that you can use to explore the commands associated with a VSPackage, and with Visual Studio itself. You can quickly search for any existing commands in the Visual Studio IDE. By browsing through the command groups, GUIDs and IDs, priorities, and other properties of existing commands, you can more easily place and integrate the commands of your own VSPackage.” – sounds pretty good to me: I have no clue how to do it, so I look at others how they accomplished it! Winking smile

After playing around with the tool for a while I found what I was looking for, the GUIDs and IDs of the context menu used by the html-, aspx-, aspx.cs-, … editors. Yepp, all those editors have their own context menu. It’s even splitted to html in aspx-files, inline-c# in aspx-files and inline-vb in aspx-files. That’s pretty cool when you want to make an extension that’s only working with a subset of all editors or languages! To test my newly found GUIDs and IDs (CMDSETID_HtmEdGrp & IDMX_HTM_SOURCE_HTML) I added them as “parent” to my group and… my build failed again. This GUID and ID are unknown – corresponding to the compiler. Wait, there was a section within my .vsct-file to define my own command-names with their GUID. Perhaps I can add a few more..?

There you are! Even if those GUIDs and IDs are internally used by Visual Studio you have to define them for your extension first. The VSCT PowerToy can switch the view and instead of the name I get the corresponding GUIDs and IDs. After defining them as a GuidSymbol my project compiled without an error and it even showed my context menu entry in the html-editor. Strike!

<GuidSymbol name="CMDSETID_HtmEdGrp"
            value="{d7e8c5e1-bdb8-11d0-9c88-0000f8040a53}">
    <IDSymbol name="IDMX_HTM_SOURCE_BASIC" value="0x32" />
    <IDSymbol name="IDMX_HTM_SOURCE_HTML" value="0x33" />
    <IDSymbol name="IDMX_HTM_SOURCE_SCRIPT" value="0x34" />
    <IDSymbol name="IDMX_HTM_SOURCE_ASPX" value="0x35" />
    <IDSymbol name="IDMX_HTM_SOURCE_ASAX" value="0x3B" />
    <IDSymbol name="IDMX_HTM_SOURCE_ASPX_CODE" value="0x36" />
    <IDSymbol name="IDMX_HTM_SOURCE_ASAX_CODE" value="0x3C" />
    <IDSymbol name="IDMX_HTM_SOURCE_ASPX_CODE_VB" value="0x37" />
    <IDSymbol name="IDMX_HTM_SOURCE_ASAX_CODE_VB" value="0x3D" />
    <IDSymbol name="IDMX_HTM_SOURCE_ASMX_CODE" value="0x38" />
    <IDSymbol name="IDMX_HTM_SOURCE_ASMX_CODE_VB" value="0x39" />
</GuidSymbol>

The next challenge was a no-brainer compared to the last one: I want my extension show up in the context menu not only of the code editor or the html-editor but in several of them at once. A solution was quickly found: With CommandPlacements you can add a command to several places so I added my extension to a few of them in case they make sense to me (“Where could you have some text that has to be websafe?”).

<CommandPlacements>
    <CommandPlacement guid="guidUnicodeConverterCmdSet"
                      id="UnicodeMenuGroup" priority="0x0300">
        <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CODEWIN"/>
    </CommandPlacement>
    <CommandPlacement guid="guidUnicodeConverterCmdSet"
                      id="UnicodeMenuGroup" priority="0x0300">
        <Parent guid="CMDSETID_HtmEdGrp" id="IDMX_HTM_SOURCE_HTML"/>
    </CommandPlacement>
    <CommandPlacement guid="guidUnicodeConverterCmdSet"
                      id="UnicodeMenuGroup" priority="0x0300">
        <Parent guid="CMDSETID_HtmEdGrp" id="IDMX_HTM_SOURCE_SCRIPT"/>
    </CommandPlacement>
    <CommandPlacement guid="guidUnicodeConverterCmdSet"
                      id="UnicodeMenuGroup" priority="0x0300">
        <Parent guid="CMDSETID_HtmEdGrp" id="IDMX_HTM_SOURCE_ASPX"/>
    </CommandPlacement>
    <CommandPlacement guid="guidUnicodeConverterCmdSet"
                      id="UnicodeMenuGroup" priority="0x0300">
        <Parent guid="CMDSETID_HtmEdGrp" id="IDMX_HTM_SOURCE_ASAX"/>
    </CommandPlacement>
</CommandPlacements>

And thats it! I didn’t made a step-by-step tutorial how to make a new extension for Visual Studio. There are a lot of other articles and examples around for that. But I thought I show you what problems I was confronted with and how I finally circumnavigate them so that you can learn from my researches.

Last but not least you can take a look at my sourcecode. I’m publishing it under the MS-PL so you are free to use and change it for your purposes but of course it would be great if you could give me a hint in that case. All feedback is greatly appreciated, so leave me a comment or write a message via email, facebook, twitter, … whatever you like Winking smile

Questions/Suggestions
As always, for questions or feedback, contact me or leave a comment.

Octocat by GitHubEdit this page on GitHub