Creating a Live Templates Scripting Engine

By: Nick Hodges

Abstract: In this article, I’m going to show you how easy it can be to create a Live Template Scripting engine.

    Introduction

One of the cool features of Delphi 2007 is Live Templates. Live Templates let you write a more code with a lot fewer key strokes. Live Templates use SyncEdit technology to make filling in portions of a Live Template really easy, further accelerating your coding. Finally, Live Templates are fully scriptable; you can write your own scripting engine to make Live Templates really do what you want them to do.

In this article, I’m going to show you how easy it can be to create a Live Template Scripting engine. I’ll show you a simple base class you can use to create your own scripting engine that not only does almost anything you want inside of a template but that even knows how to allow you to enter script entries in your Live Templates to further customize their behavior.

    Code for this Article

Before getting into the thick of things, you might want to get a look at the resulting code. The code for this article, which includes a number of other helpful Live Templates and scripting engines, can be found in CodeCentral. It includes two scripting engines, one to support Blackfish SQL development, and the other to fill in date/time information in your code. It’s the latter scripting engine that I’ll be covering below.

    Getting Started

First, a scripting engine is implemented within a design-time package. Design-time packages are packages purely for use by the IDE and not for other applications. You can set a package to be a design-time only package as part of the package’s options. Design-time packages have characteristics that enable them to be “plugged into” and used by the IDE.

To get started, follow these steps:

  1. Select File|New|Other… from the main menu
  2. In the resulting dialog box, select “Delphi Projects” in the treeview on the right.
  3. Select “Package” in the listview on the right.
  4. Press ‘Ok
  5. At this point, you should have a new package. You can save it wherever you like, and give it any name you like. I saved mine with the name “BaseScriptingEngine.bpl
  6. Right click on the package name in the Project Manager and select “Options…”
  7. In the resulting dialog box, on the Description page, set the Usage Options to “Designtime only”. Your dialog ought to look a lot like the one below.
  8. Right-click again on the package name and select Add New|Unit..
  9. Save the unit where you like with any name you like. I named mine uBaseScriptEngine.pas

Hide image
DesignTimePackageOptions

Figure 1 -- The Package Options dialog with the "Designtime only" option set, meaning that the package can only be used by the IDE.

Once you’ve done this, you’ve created an empty design-time package which will hold the base class we’ll use to create the actual scripting engines. The package can then be referenced by any other packages that want to descend from our base class and actually do some work creating and helping out with Live Templates.

    The Base Class for Creating Scripting Engines

To create a Live Templates scripting engine, you need to create a class that descends from TNotifierObject and that implements the IOTACodeTemplateScriptEngine interface. TNotifierObject is a class that resides in the ToolsAPI unit which, in turn, resides in the designide.bpl package. The IOTACodeTemplateScriptEngine is in the CodeTemplateAPI unit, which is also in the DesignIDE package.

So, our new package will ‘require’ that package. There is no other way to get at the ToolsAPI. So, therefore, you need to do the following to make that happen:

  1. In the Project Manager, go to the package, and right click on the “Requires” item in the treeview.
  2. From there, select “Add Reference…”
  3. When the resulting dialog pops up, press the Browse button and browse to where the designide.dcp file resides. On my machine, it is in: C:\Program Files\CodeGear\RAD Studio\5.0\lib\designide.dcp
  4. Select that file and press ‘Ok’

At this point, your package now can “see” all the code in the DesignIDE package, including the code in the ToolsAPI unit. That’s good, because you need that unit to create a scripting engine.

So, let’s do that. Go to your empty unit, and in the interface section add the following:

uses
        CodeTemplateAPI
      , ToolsAPI
      , Classes
      ;

Once you have done that, all the code you need will be available to your unit.

    The TBaseScriptEngine Class

Next, let’s create our base class. Right below your uses clause, add the following declaration.

type
  TBaseScriptEngine = class abstract(TNotifierObject, IOTACodeTemplateScriptEngine)
  private
    FScriptInfo: TStrings;
    procedure FetchScriptInfo(const AScript: IOTACodeTemplateScript);
    function GetScriptInfo: TStrings;
  protected
    function GetIDString: WideString; virtual; abstract;
    function GetLanguage: WideString; virtual; abstract;
  public
    constructor Create;
    procedure Execute(const ATemplate: IOTACodeTemplate; 
      const APoint: IOTACodeTemplatePoint; 
      const ASyncPoints: IOTASyncEditPoints; 
      const AScript: IOTACodeTemplateScript; 
      var Cancel: Boolean); virtual;
    property Language: WideString read GetLanguage;
    property ScriptInfo: TStrings read GetScriptInfo;
  end;

First, you should notice that this is an abstract class, with the GetIDStrings and GetLanguage functions being labeled as abstract. Obviously, those two methods will have to be provided by any descending class. In addition, the Execute method is declared as virtual. Any scripting engine that wants to do anything of significance will have to override this method, call the inherited method, and then provide its own additional functionality.

The class also includes two properties: Language and ScriptInfo. Language is a string that identifies the name of the scripting language you want to give to your scripting engine. It will be referenced by the XML of your Live Template and ensure that the proper script engine is called and initialized. Basically, it creates a namespace for your script. The ScriptInfo is TStrings that holds any scripting information that your Live Template wants to pass. We’ll see how this works in a few minutes.

To complete the class, we merely need to implement four methods.

The constructor for the class is very simple, and merely calls the inherited constructor:

constructor TBaseScriptEngine.Create;
begin
  inherited Create;
end;

The Execute method has a rather impressive collection of parameters, but in fact, for now, we are only going to call a helper function in it:

procedure TBaseScriptEngine.Execute(const ATemplate: IOTACodeTemplate;
  const APoint: IOTACodeTemplatePoint; const ASyncPoints: IOTASyncEditPoints;
  const AScript: IOTACodeTemplateScript; var Cancel: Boolean);
begin
  FetchScriptInfo(aScript);
end;

That helper function is the FetchScriptInfo method, which looks like this:

procedure TBaseScriptEngine.FetchScriptInfo(const AScript: IOTACodeTemplateScript);
var
  Counter: Integer;
begin
  ScriptInfo.Text := UTF8Encode(AScript.Script);
  // Trim each item in the stringlist
  for Counter := 0 to ScriptInfo.Count - 1 do
  begin
    ScriptInfo[Counter] := Trim(ScriptInfo[Counter]);
  end;
end;

This code is a little more interesting. What this code does is fetch any script entries that are part of the Live Template. For example, the live template that we create below will include the following XML:

<script language="DateTimeScript" onvalidate="true">
  date=InsertDate
  dateformatstr=mmmm dd, yyyy
</script>

That code contains two lines of script, the date and dateformatstr elements. It is this information that the FetchScriptInfo method fetches when the script is actually executed.

The first thing that FetchScriptInfo does is to set the Text property of TBaseScriptEngine’s ScriptInfo property. The IOTACodeTemplateScript passed into the procedure contains, as raw text, the stuff found inside the live templates <script> tag. Since TStringList knows how to handle name=value pairs, it’s a natural class for handling this type of simple scripting.

Note that it encodes the string using UTF-8. This is done to ensure that the string encoding matches that of the XML file. In addition, the procedure iterates over the items in the list to trim the spaces off of the ends of the string.

The final method, GetScriptInfo, is simply a helper method that creates the TStringList instance behind the ScriptInfo property when it is needed or returns the existing instance if it has already been created. (It’s a simple implementation of the Singleton pattern).


One final note – to use the Trim function, you’ll want to add SysUtils to the uses clause of the implementation section.

And that’s it – a pretty simple class. All it really does is ensure that your script engine has a namespace and that it knows how to grab any script information found inside the Live Template. At this point you can build the package and then right click and select Install in order to install the package into the IDE. It won’t do anything, but it will be there, ready to be referenced by the next package that we are about to create.

    The Actual Script Engine – Adding Date/Time information to Your Code

I don’t know about you, but I often need to put date and/or time information into my code, usually as part of a comment. Wouldn’t it be nice if there were a Live Template that did that for you quickly and easily? Wouldn’t it be even cooler if you could easily format the date/time information to your liking? I think it would. And that is exactly what we are going to do here. (Note: For simplicity’s sake, this article will cover only putting the date into your code. The full code sample linked above has code to put in the date, time, and a date/time string.)

First, we need another design-time package. Create another one just as you did above. I named mine DateTimeScriptEngine. (Don’t forget to set its “Designtime only” option). Once you do that, add another empty unit to it as before. I named mine uDateTimeScriptEngine.pas. Finally, this new package will require the package we just got done with, so right click on its Requires tree item, and add the new package to it. (You’ll likely find the required DCP file in the C:\Documents and Settings\All Users \Documents\RAD Studio\5.0\dcp or some similarly named location). And finally, once you have required the package with the base class in it, you can use the uBaseScriptEngine unit in your new unit by adding it to the uses clause.

Thus, in your new unit, the implementation uses clause should look like this:

uses
        CodeTemplateAPI
      , ToolsAPI
      , Classes
      ;

and the implementation uses clause should look like this:

uses
        DateUtils
      , SysUtils
      ;

Now that that is all hooked up correctly, you can create a class that will actually do some Live Template scripting. What we’ll do is we’ll create a simple scripting engine that will take a specific point in a live template and replace it with the text for the current date. In addition, we’ll make it powerful enough to accept a date formatting string that will enable the user to output the date in whatever format he chooses.

The first order of business is to declare the class:

type
  TDateTimeScriptEngine = class(TBaseScriptEngine)
  private
    const
      cDateFormatStr = 'dateformatstr';
    procedure RemoveFormatStrings;
    procedure DeleteIfExists(i: Integer);
  private
    FDateFormatString: string;
    procedure GetFormatStrings;
    function GetDateString: string;
  protected
    function GetIDString: WideString; override;
    function GetLanguage: WideString; override;
  public
    procedure Execute(const ATemplate: IOTACodeTemplate; 
      const APoint: IOTACodeTemplatePoint; 
      const ASyncPoints: IOTASyncEditPoints; 
      const AScript: IOTACodeTemplateScript; 
      var Cancel: Boolean); override;
  end;

This class overrides the two abstract methods, GetIDString and GetLanguage. Secondly, it overrides the Execute procedure, and finally, includes a few helper functions. We’ll look at each of those three things in order.

First, here are the declarations of GetIDString and GetLanguage:

function TDateTimeScriptEngine.GetIDString: WideString;
begin
  // This function is required by the IOTACodeTemplateScriptEngine
  // interface, and needs a unique identifier.  A GUID is unique as
  // they come, no?
  Result := '{5A866B09-828F-425C-99B6-4FDA2C446D3A}';
end;

function TDateTimeScriptEngine.GetLanguage: WideString;
begin
  // Should return a name for the scripting engine.  This will be used
  // in the Live Template to identify the scriptengine to use.
  Result := 'DateTimeScript';
end;

These two functions uniquely identify the scripting engine so that the IDE can easily find the right code to execute. GetIDString merely requires a unique string, and thus returns a GUID. GetLanguage allows you to give your script engine a name so that it can be referenced by name in your Live Templates. This is mostly admin work here – you can pretty much put any string values you want as return values for these functions.

The real work gets done in the Execute Method, which is declared as follows:

procedure TDateTimeScriptEngine.Execute(
const ATemplate: IOTACodeTemplate;
const APoint: IOTACodeTemplatePoint; 
const ASyncPoints: IOTASyncEditPoints;
  const AScript: IOTACodeTemplateScript; var Cancel: Boolean);
var
  TempPoint: IOTACodeTemplatePoint;
  i: integer;
  TempPointName: string;
  TempFunctionName: string;
  TempDate: string;
const
  cDate = 'InsertDate';
begin
  inherited;
  GetFormatStrings;
  for i := 0 to ScriptInfo.Count - 1 do
  begin
    TempPointName := ScriptInfo.Names[i];
    TempFunctionName := ScriptInfo.Values[TempPointName];
    if (aTemplate <> nil) then
    begin
      TempPoint := aTemplate.FindPoint(TempPointName);
      if TempPoint <> nil then
      begin
        TempPoint.Editable := False;
        if TempFunctionName = cDate then
        begin
          TempPoint.Value := GetDateString;
        end;
      end;      
    end;
  end;
  Cancel := False;
end;

This is a fairly complex procedure, so I’ll go through it one step at a time. It is made complicated first by a rather interesting set of parameters that is passed to it – four interfaces and a Boolean var parameter. A good idea here might be to take a look at the interfaces that this procedure uses in the CodeTemplatesAPI unit. (You can bring it up by putting the cursor on the unit name in your uses class an hitting CTRL+Enter).

First, the procedure calls the inherited procedure which, if you recall, grabs any scripting information that might be part of the Live Template. Once the script information is grabbed and put into a TStringList, the GetFormatStrings procedure is called. This procedure is declared as follows:

procedure TDateTimeScriptEngine.GetFormatStrings;
begin
  // Pull the formatting strings out of the script string
  FDateFormatString := ScriptInfo.Values[cDateFormatStr];
  // Once you have them, get rid of entries in string list
  RemoveFormatStrings;
end;

This procedure merely pokes into the ScriptInfo StringList and grabs out the formatting information, if there is any. RemoveFormatString simply takes that string out of the ScriptInfo property since we’ve already used it and don’t want to deal with it again.

Next, the procedure iterates over every scripting entry in the ScriptInfo property. For each item found, it grabs the name and value entries for the given name=value pair in the script text.

Once that information is found, it then goes to the interface that represents the template itself, the aTemplate parameter, and calls FindPoint to get a reference to the Point in the Live Template that is being processed. If it has a value reference to the point, it asks the point for its Name, and if the name matches the string date, then the template is actually processed. First, since we are doing a simple replacement, we don’t want the point to be editable. Setting the Editable property to False will keep the point from ever entering into SyncEdit mode, and will instead allow the scripting engine to do the replacement of text.

Once we know that we have the correct point name, and that we want to actually replace the text with the current date, the actual work of the scripting engine gets done. We set the Point’s Value property to the current date. The call to GetDateString looks like the following. Notice that it uses the FDateFormatString property, if there is a value for it, to format the string as desired by the user. Otherwise, the default formatting is used.

function TDateTimeScriptEngine.GetDateString: string;

  function StringIsEmpty(Str: string): Boolean;
  begin
    Result := Str = '';
    if not Result then
    begin
      Result := Trim(Str) = EmptyStr;
    end;
end;

begin
  // First look for format string.....
  if not StringIsEmpty(FDateFormatString) then
  begin
    Result := FormatDateTime(FDateFormatString, Now);
  end else
  begin
    Result := DateToStr(Now);
  end;
end;

Finally, note that if the script engine can’t find any matching points to deal with, it does nothing.

By now you are probably wondering where a bunch of that scripting information came from. The actual Live Template itself that executes this simple script looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<codetemplate   xmlns="http://schemas.borland.com/Delphi/2005/codetemplates"
                   version="1.0.0">
    <template name="date" invoke="manual">
         <description>
              Inserts the Current Date at the point where the 
                  template is invoked
         </description>
         <author>
              CodeGear
         </author>
         <point name="date">
            <text>
                date
            </text>
            <hint>
                Current Date
            </hint>
         </point>
         <script language="DateTimeScript" onvalidate="true">
            date=InsertDate
            dateformatstr=mmmm dd, yyyy
         </script>
         <code language="Delphi" delimiter="|"><![CDATA[
|date|
         ]]>
         </code>
    </template>
</codetemplate>

The key portion, of course is the <script> tag that contains two parts: the attributes declarations and the script itself. There are two attributes: the language attribute, which should match what is returned in the GetLanguage function, and the onvalidate=”true” attribute which basically tells the script to run when the point is accessed for the first time. The script itself contains two name=value pairs that contain the name of the script to be called. In this case, the script InsertDate should be called whenever the date point is encountered. In addition, the user can pass in a format string for the date in question.

The last thing that needs to be done is to register the scripting engine with the IDE. As you probably know, whenever a package is installed into the IDE as a design-time package, the IDE searches for any and all procedures in the package called Register and calls them. Thus, we include a Register procedure in our unit, and declare it as follows:

procedure Register;
begin
(BorlandIDEServices as IOTACodeTemplateServices). 
          RegisterScriptEngine(TDateTimeScriptEngine.Create);
end;

This code merely registers an instance of our scripting engine with the IDE.


And that is pretty much it. Compile the package, install it, and put the Live Template XML file in the code_templates directory, and you should be good to go. All you need to do at this point is type date and then hit CTRL+J and the current date will be inserted into your code at the same spot where the word date was. (Note again that the sample code includes Live Templates for date, time and datetime. All are format-able via a simple script entry).

From here, you should be able to use the class in the BaseScriptingEngine package to create more descendent classes. If you really wanted to get fancy, you could enhance the TBaseScriptEngine class to provide a generic way of processing the name=value pairs in the scripts.

    Conclusion

So, in this article, you saw how easy it is to create a customized scripting engine for handling text in the editor via Live Templates. You created a base class which can be extended to handle almost any action for a given point in a Live Template. At this point, you are limited only by your imagination in what you can do with a Live Template scripting engine.

    Find More Information

There is a lot of information about Live Templates out on the web:

Server Response from: ETNASC04