Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Introducing TaHoGen, Part II: Plugging a CodeSmith-style Template Engine Directly into the VS.NET IDE

0.00/5 (No votes)
23 Sep 2005 1  
Have you ever wanted to output your CodeSmith templates 'directly' into the Code Window of the VS.NET IDE? If you have, then this article is for you!

Introduction

In this article of this series, I�m going to show you a simple VS.NET Add-In client I wrote for the TaHoGen template engine. (If you�re not familiar with TaHoGen, you can think of it as an Open Source implementation of a CodeSmith-compatible code generation engine.) Furthermore, we�re going to take it a step further by allowing the user to output his/her templates directly into the VS.NET Code Editor windows.

Background

One of the things that I�ve always wanted to do is to be able to run a CodeSmith template and send the output of that template directly to the code window. Unfortunately, the current version of CodeSmith (version 3.0 at the time of this writing) doesn�t support that out of the box. Secondly, its source code is proprietary, leaving Open Source tool developers like me with little or no options to work with. It�s either you buy a license to use it in your products, or you can�t use it at all (except from within CodeSmith itself).

For me, both options were unacceptable, and that was why the TaHoGen engine was born. The difference between TaHoGen and CodeSmith in this regard is simple: as long as you use TaHoGen in another project licensed under the terms of the GNU Public License, I will never, ever, charge you a cent for it. If you decide to include it in a commercial product, however, then, that�s a different story�

So, now that we as a developer community have our own Open Source CodeSmith-style compatible code generation engine (yeah, I know, that�s quite a mouthful), it�s time to make something useful out of it�let�s get started!

What you�re going to need before we get started

In addition to the TaHoGen Core Libraries from the previous article, you�re going to need:

  • Phil Wright�s MagicLibrary. You can get it from here.
  • MutantDesign�s Managed AddIns Library & AddIn Manager. Normally, you can find these libraries included as part of the TestDriven.NET library. (For your convenience, I�ve included a copy of the MSI installer that I used as a part of this add-in, in the section above.)

Using the client

Once you�ve installed the two libraries that I mentioned earlier (namely the ManagedAddIns library) and installed the add-in itself, you should get an option in your Tools menu in VS.NET that looks like this:

After you click on that option in the menu, you should see a dialog similar to the following appear:

Once you choose the File->Open option on the menu, a standard OpenFileDialog will ask you where you installed the add-in. Once you�ve selected where the file (TaHoGen.VSAddIn.exe) is located, the only thing you have to do is right click on the TaHoGen.VSAddIn list item and click on Connect. You should then see something like this:

This should be pretty familiar to those of you used to using the CodeSmith Explorer interface. (For now, don�t worry if you don�t see anything listed on the tree view just yet�we�ll get to that in a bit.) Double-clicking on a single template in the tree view will compile that template, and the add-in will automatically show you the properties for that template:

In this case, I double-clicked on the CSHashtable.cst template, and these were the properties that automatically came up. If you click on the Generate button, what will happen is that the add-in will run the template and send its output to the active code editor window in the VS.NET IDE. So, for example, if I wanted to generate a new strongly-typed collection (or hashtable, in this case), all I have to do is choose File->New->File�->New Text Document from the VS.NET IDE menu and click on the Generate button to dump the output of the template into the new file, and save and add that file as part of my project. It can�t get any simpler than that.

The rest of the options on this tab are fairly self-explanatory, so let�s move on to the next section.

Viewing the Output

There might be times where you need to see the diagnostic output of the code generation engine. For example, if a template fails to compile, we need to know exactly why it failed so we can go about fixing the compilation error(s). With that in mind, the add-in sends all of its messages to the output window to make templates easier to debug:

The add-in adds two new output window panes to the output window:

  • TaHoGen Build Output. This window will report any compile errors if they occur, in addition to any other error messages TaHoGen will display if something goes wrong.
  • TaHoGen Generated Template Source. This window shows you what the code generator you created would have looked like if you were to write it by hand. (This is useful for debugging code generators with additional code written in some script blocks in the template markup.)

Editing Template Files from within Visual Studio .NET

The add-in makes it easy to edit your template files without having to leave the confines of the VS.NET IDE. All you have to do is right click on any template file, choose the Edit option from the context menu, and you can edit the template file from within the IDE, as seen from this image:

In addition, if you make any changes to your template files within the IDE and need to rebuild, all you have to do is right click on the selected template, and choose the Build option.

Adding and Removing Template Directories

The Options tab lets you choose the directories the add-in should use to recursively search for (both TaHoGen and CodeSmith) template files. For example, here�s how it would look if you decided to add a few sample templates from the CodeSmith installation folder:

Once you select a directory, the add-in will update the treeview in the Templates tab with the new list of templates, and you�ll be able to use them just as you would be able to use any other template.

Using the code

Despite its functionality, the add-in itself is no more than a thousand lines of (mostly) user interface code. I won�t bore you with all the details since most of it is boilerplate code; instead, we�ll just pick up where we left off in the previous article, and I�ll show you a few notable portions of the code so you can see what�s going on behind the scenes.

Compiling the Template

As I mentioned in the previous article, you can compile a single template using the following lines of code:

string sourceText = ���;
CompilerCallback callback = new CompilerCallback();
�
Assembly templateAssembly = TemplateCompiler.Compile(sourceText, 
                                           "", false, callback); 
�

In this particular case, the add-in is reading the contents of a template file and sending its contents to the compiler. The TemplateCompiler class has the following signature:

// Methods for compiling a single template

public static Assembly Compile(string text)
public static Assembly Compile(string text, bool addDebugSymbols)
public static Assembly Compile(string text, string outputFileName, 
                               bool addDebugSymbols)
public static Assembly Compile(string text, string outputFileName, 
                               bool addDebugSymbols, 
                               ICompilerCallback compilerCallback)

// Methods for compiling multiple templates

// into one assembly (not shown)

The first three parameters are self-explanatory. The fourth parameter, however, needs a little more explanation. The ICompilerCallback interface is useful if you need to show the results of a compilation to the user. It is defined as follows:

public interface ICompilerCallback
{
  void BeginCompile(CompilerArgs args);
  void EndCompile(CompilerArgs args);
}

The CompilerArgs class, in turn, is defined as:

public class CompilerArgs
{
  private string _source;
  private CompilerErrorCollection _errors = new CompilerErrorCollection();
  public CompilerArgs(string source, CompilerErrorCollection errors) 
  {
      _source = source;
      if (errors != null)
          _errors.AddRange(errors);
  }
  public string CompiledCode
  {
    get { return _source; }
  }
  public CompilerErrorCollection Errors
  {
       get { return _errors; }
  }
}

Getting the results from the compiler

In order to get the results from the compiler, I had to define my own class implementation of ICompilerCallback:

using System;
using System.CodeDom.Compiler;
using System.Reflection;
using TaHoGen;

namespace TaHoGen.VSAddIn
{
    public class CompilerCallback : ICompilerCallback
    {
        private string _templateCode;
        #region ICompilerCallback Members
        public void BeginCompile(CompilerArgs args)
        {
            Console.WriteLine("----------------- Compile Started ----------------");
            Console.WriteLine();
        }

        public void EndCompile(CompilerArgs args)
        {
            // Save the compiled code so that

            // we can show it to the user later

            _templateCode = args.CompiledCode;

            foreach(CompilerError error in args.Errors)
            {
                string msg = string.Format("{0}({1}, {2}): error {3}: {4}", 
                                           error.FileName, 
                                           error.Line, 
                                           error.Column, 
                                           error.ErrorNumber, 
                                           error.ErrorText);
                Console.WriteLine(msg);
            }
            Console.WriteLine("Build Complete - {0} Errors, 0 Warnings", 
                                                     args.Errors.Count);
            Console.WriteLine();
            Console.WriteLine("---------------------- Done ----------------------");
            Console.WriteLine();

            string text = args.Errors.Count > 0 ? "Build Failed" : 
                                                   "Build Succeeded";
            Console.WriteLine(text);
        }

        #endregion

        public string GeneratedTemplateCode
        {
        get { return _templateCode; }
        }
    }
}

As you can see, the implementation of the class is very straightforward. The CompilerCallback class marks the beginning and end of the compilation process, reports any compilation errors if they occur, and saves the generated template code so that we can display it to the end user once it�s done compiling. The only thing that seems to be out of the ordinary here is the calls to Console.WriteLine() � how do you get its output to show up on the output window in VS.NET?

Redirecting the Console Output

It turns out that the Console class has a single static method named SetOut() that allows us to redirect its output to any object derived from System.IO.TextWriter. In this case, we�re redirecting the console output to a StringWriter class so that we can dump its contents to an output window once the template finishes compiling:

StreamReader reader = new StreamReader(fileName);
string sourceText = reader.ReadToEnd();

// The StringWriter will store the output of the compiler

StringWriter buildOutput = new StringWriter();

Console.SetOut(buildOutput);

// Compile the template

CompilerCallback callback = new CompilerCallback();
Assembly templateAssembly = TemplateCompiler.Compile(sourceText, 
                                           "", false, callback);

�

// Send the results to the output window

_buildOutputPane.OutputString(buildOutput.ToString());

Showing the Compiled Template Code

Next, we need to show the generated template code:

// Show the compiled code

_generatedSourcePane.OutputString(callback.GeneratedTemplateCode);
�

�and that�s all there is to sending text to an output window in VS.NET.

Adding Extra Output Panes

If you�re wondering how I added those extra output window panes, here�s the code I used to do it:

/// <summary>

/// Creates a special pane in the output window of the VS.NET IDE.

/// </summary>

/// <param name="paneName">The name of the pane to create.</param>

/// <returns>An OutputWindowPane.</returns>

private static OutputWindowPane CreatePane(string paneName)
{
    // We�re interested only in output windows

    Window window = 
      ManagedAddIns.DTE.Windows.Item(EnvDTE.Constants.vsWindowKindOutput);
    OutputWindow outputWindow = window.Object as OutputWindow;
    OutputWindowPane outputPane = null;


    outputPane = null;
    OutputWindowPanes panes = outputWindow.OutputWindowPanes;

    // Reuse the existing pane (if it exists)

    for(int i = 1; i <= panes.Count; i++)
    {
        outputPane = panes.Item(i);
        if (outputPane.Name == paneName)
            return outputPane;
    }
    // Otherwise, we�re going to create a new pane

    OutputWindowPane newPane = outputWindow.OutputWindowPanes.Add(paneName);

    return newPane;
}

Attaching the Template to the PropertyGrid

Wiring a template object to a PropertyGrid is equally just as simple. Once the template has been instantiated, the only thing left to do is to attach it to the property grid so that the user can set the template properties:

// Instantiate the template type

object template = Activator.CreateInstance(templateType);

_template = template as ITextGenerator;

// Once we're done compiling, we

// need to give the user a chance to

// set the properties for the template.


propertyGrid1.SelectedObject = _template;
�

Sending the Template Output to the Active Code Editor Window

Lastly, the add-in sends the output of the template to the current code window using the following code:

// Output the generated code at the current selection

TextSelection selection = 
  ManagedAddIns.DTE.ActiveDocument.Selection as TextSelection;
Debug.Assert(selection != null);

// Save the output of the template

// when it runs

StringTarget target = new StringTarget();
target.Attach(_template);


// Run the template itself

target.Write();


// Send the template output to 

// the current selection

selection.MoveTo(selection.CurrentLine, 1, false);
selection.Text = target.ToString();

Conclusion

Hopefully this article shows that you don�t need to write a complex client for TaHoGen in order for it to be useful. If you�ve read both of my articles on TaHoGen up to this point (including this one) and studied the sample code, then that�s all you�ll really need to know in order for you to get started. Good Luck!

Points of Interest

What surprised me about writing a simple COM-based add-in like this one was the amount of trouble it gave me when I had to deploy it on other people�s machines; it was a nightmare. Fortunately, I came across MutantDesign�s Managed AddIns which really made it easy to write a decent add-in in just a couple hundred lines of code. My hat is off to MutantDesign!

Special Thanks

  • Phil Wright. Thanks for your effort in writing MagicLibrary!
  • MutantDesign. I truly have to thank you guys for making this add-in easier to write. You have no idea how much trouble you saved me.

History

  • 9/12/2005 - First release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here