Blog O Sparky archive

Creating Live Templates - Basics (Delphi2006)

In this blog we’ll step through creating a simple template to make a more type safe descendant of TBucketList. This will go over the basics of code template creation which are:

  • Naming the template and specifying the target language.
  • Adding jump points for navigation and renaming synchronization.
  • Adding jump point default text and hints.
To start, we need to create the new template file. This can be done in two different ways, both of which have the same result:
  1. File | New | Other | Other Files | Code Template
  2. View | Templates
    • Click the "New" button in the template dialog.
Both will leave you in the same spot with a new code template file, in code template mode. The first item that should be filled out is the name of the template:

<template name="" invoke="manual">

Let’s use the name "NewBucketList" as the name of the template. The template line should now look as follows:

<template name="NewBucketList" invoke="manual">

Next we’ll fill out the description and author of this code template as follows:

<description>
   a more type safe BucketList descendant
</description>
<author>
   Adam Markowitz
</author>

Feel free to use a different author name. I like my name so I’m gonna use it for this example :)

Next we’ll put in the code we want and tell the template which language we’re targetting.  This code was mostly lifted from Contnrs.pas.  Note that it goes in the CDATA section of the code template.

<code language="Delphi"><![CDATA[type

|*|TControlBucketList = class(TBucketList)
|*|protected
|*||*|function GetData(AItem: TControl): TControl;
|*||*|procedure SetData(AItem: TControl; const AData: TControl);
|*|public
|*||*|function Add(AItem, AData: TControl): TControl;
|*||*|function Remove(AItem: TControl): TControl;
|*||*|property Data[AItem: TControl]: TControl read GetData write SetData; default;
|*|end;

{ TControlBucketList }

function TControlBucketList.Add(AItem, AData: TControl): TControl;
begin
|*|Result := TControl(inherited Add(Pointer(AItem), Pointer(AData)));
end;

function TControlBucketList.GetData(AItem: TControl): TControl;
begin
|*|Result := TControl(inherited Data[Pointer(AItem)]);
end;

function TControlBucketList.Remove(AItem: TControl): TControl;
begin
|*|Result := TControl(inherited Remove(Pointer(AItem)));
end;

procedure TControlBucketList.SetData(AItem: TControl; const AData: TControl);
begin
|*|inherited Data[Pointer(AItem)] := Pointer(AData);
end;]]>
</code>


At this point our file should look like the following:

<?xml version="1.0" encoding="utf-8" ?>
<codetemplate xmlns="http://schemas.borland.com/Delphi/2005/codetemplates" version="1.0.0">
   <template name="NewBucketList" invoke="manual">
      <description>
         Create a more type safe BucketList descendant
      </description>
      <author>
         Adam Markowitz
      </author>
      <code language="Delphi"><![CDATA[type

|*|TControlBucketList = class(TBucketList)
|*|protected
|*||*|function GetData(AItem: TControl): TControl;
|*||*|procedure SetData(AItem: TControl; const AData: TControl);
|*|public
|*||*|function Add(AItem, AData: TControl): TControl;
|*||*|function Remove(AItem: TControl): TControl;
|*||*|property Data[AItem: TControl]: TControl read GetData write SetData; default;
|*|end;

{ TControlBucketList }

function TControlBucketList.Add(AItem, AData: TControl): TControl;
begin
|*|Result := TControl(inherited Add(Pointer(AItem), Pointer(AData)));
end;

function TControlBucketList.GetData(AItem: TControl): TControl;
begin
|*|Result := TControl(inherited Data[Pointer(AItem)]);
end;

function TControlBucketList.Remove(AItem: TControl): TControl;
begin
|*|Result := TControl(inherited Remove(Pointer(AItem)));
end;

procedure TControlBucketList.SetData(AItem: TControl; const AData: TControl);
begin
|*|inherited Data[Pointer(AItem)] := Pointer(AData);
end;]]>
      </code>
   </template>
</codetemplate>


This is all fine and good, but what if we want to make a TBucketList descendant for TWinControl? Do we create a completely new template? The answer is ‘no’. We add what I call ‘jump points’ to this template to allow users to easily change the names of multiple identifiers at the same time.

Jump points need to be declared in the template so the template engine knows which points get what default text, etc.
For this example, we’ll want two points, one for the class name and one for the name of the type that we’re making the descendant for.

The "text" node contained within the point declaration defines what the default text is for that point.
"hint" is the pop up hint that is displayed when a user navigates to that point while the template is being invoked.

The point declarations below creat the two points we want, give them names, default text, and hint values.
<point name="classname">
   <text>
      TControlBucketList
   </text>
   <hint>
      name of new BucketList class type
   </hint>
</point>
<point name="typename">
   <text>
      TControl
   </text>
   <hint>
      BucketList data type
   </hint>
</point>

These declarations should be contained in the tag.
Now, all we need to do is replace TControlBucketList with |classname| and TControl with |typename| in the code to change the template so that it uses jump points instead of hard coded names. Following is the complete template.

<?xml version="1.0" encoding="utf-8" ?>
<codetemplate xmlns="http://schemas.borland.com/Delphi/2005/codetemplates" version="1.0.0">
   <template name="NewBucketList" invoke="manual">
      <description>
         Create a more type safe BucketList descendant
      </description>
      <author>
         Adam Markowitz
      </author>
      <point name="classname">
         <text>
            TControlBucketList
         </text>
         <hint>
            name of new BucketList class type
         </hint>
      </point>
      <point name="typename">
         <text>
            TControl
         </text>
         <hint>
            BucketList data type
         </hint>
      </point>
      <code language="Delphi" delimiter="|"><![CDATA[type

|*||classname| = class(TBucketList)
|*|protected
|*||*|function GetData(AItem: |typename|): |typename|;
|*||*|procedure SetData(AItem: |typename|; const AData: |typename|);
|*|public
|*||*|function Add(AItem, AData: |typename|): |typename|;
|*||*|function Remove(AItem: |typename|): |typename|;
|*||*|property Data[AItem: |typename|]: |typename| read GetData write SetData; default;
|*|end;

{ |classname| }

function |classname|.Add(AItem, AData: |typename|): |typename|;
begin
|*|Result := |typename|(inherited Add(Pointer(AItem), Pointer(AData)));
end;

function |classname|.GetData(AItem: |typename|): |typename|;
begin
|*|Result := |typename|(inherited Data[Pointer(AItem)]);
end;

function |classname|.Remove(AItem: |typename|): |typename|;
begin
|*|Result := |typename|(inherited Remove(Pointer(AItem)));
end;

procedure |classname|.SetData(AItem: |typename|; const AData: |typename|);
begin
|*|inherited Data[Pointer(AItem)] := Pointer(AData);
end;]]>
      </code>
   </template>
</codetemplate>



Note that default delimiter for jump points is the $ character and that I’ve changed the delimiter for the Delphi template to be the | character in the following line:

<code language="Delphi" delimiter="|">

The reason for this is that $ is a valid character for Delphi code:
Foo := $3F;

and we don’t want the engine to get confused by this if we were to use it in a template.

Other things of note:
the attribute invoke is used to tell the template engine how this template is invoked.
The following values can be used:

  • auto - invoked by pressing SPACE or TAB
  • manual - invoked by pressing TAB
  • none - invoked by using CTRL+J, CTRL+SPACE, or using the template viewer

Notes:

  1. TBucketList is declared in Contnrs.pas so be sure that Contnrs is in your uses clause.
  2. There are a number of languages that are supported by code templates and can be specified as the value of the language attribute in the <code> tag. Currently they are:
    • CSharp
    • VB
    • Jsharp
    • HTML
    • XML
    • Delphi
    • C
    • SQL
    • IDL
    • JavaScript
    • CSS
    • INI
    • PHP



Hopefully this covers some of the basics and answer some questions.

 

 

Posted by Adam Markowitz archive on December 5th, 2005 under Uncategorized |



23 Responses to “Creating Live Templates - Basics (Delphi2006)”

  1. Adam Markowitz Says:

    The code template can be downloaded from Code central ID: 23869

  2. Chee Wee Chua Says:

    Cool, Sparky! Definitely a brlght *spark*! ;o)

  3. Leonel Togniolli Says:

    Regarding note #1, that sounds like a good issue to be solved by the scripting engine. :)

  4. Adam Markowitz Says:

    Leonel,

    Yes. I was hoping to have had enough time to also hook up the Find Uses/Import Namespace refactoring as I did the DeclareVariable() refactoring, but time wasn’t on my side. I’ll see what I can do about this in the near future though :)

  5. John Moshakis Says:

    Thanks this is exactly what I wanted. Looking forward to the next one

  6. Sebastian Says:

    Is there a way to include the text currently in the clipboard into a live template?

  7. Sebastian Says:

    In addition to my comment above: it would be perfect if you could specify that the default value of a "point" in the live template should be the content of the clipboard.

    Essentially I want a template "f" which results in "FreeAndNil(ClipBoardContent);

  8. Adam Markowitz Says:

    Sebastian,

    Yes, this functionality is possible, but not out of the box. This will be implemented as soon as possible, most likely as a native point like $selected$ and $end$. Something like $clipboard$. I will post when this is completed.

  9. Sebastian Says:

    Adam,

    thanks for considering $clipboard$.

    Meanwhile I have found http://delphi-notes.blogspot.com/2005/12/creating-script-template-engine-for.html and tried to solve this with a script-engine, but it doesn’t work.

    It seems scripts inside a point are not executed. Have a look at arrayc for example: the point "var" should invoke CodeCompletion, but it doesn’t. Similar to that my own Script Engine wasn’t called!

  10. Adam Markowitz Says:

    Sebastian,

    This should be possible via a script engine, but it will most likely not be trivial and will require some work. You could also do it much easier if you call the CodeTemplateServices.InvokeCode() yourself by providing your own TemplateArgs implementation that has an entry for clipboard. I’d suggest waiting until it’s implemented intrinsically though.

    The CodeCompletion script (I thought I had removed that from the templates before ship, but possibly not) is commented out internally because of focusing issues. Point scripts do work, although there is an issue with point leave events firing when they are modified (ick) instead of when they are left. Your script engine should definitely be called if it is registered and the language of the script is changed to your own script language. Check the example you site above as that one works.

    Good luck,

    -Adam

  11. Sebastian Modersohn Says:

    Adam,

    thanks very much for taking the time - especiall on a sunday!

    I can’t get point scripts to work with my script engine. It all works perfectly if I just call the script for the complete template, but it doesn’t work if I put it inside a point tag.

    My point section looks like this (I hope this get’s through via Comments):

    <point name="variable">

    <script language="LWScript">Paste</script>

    <text>variable</text>

    <hint>variable to be freed</hint>

    </point>

    The same script tag works if I put it inside the template-tag. I’ve also tried with onenter=true or onleave=true; no luck.

    Thanks for your help

    -Sebastian

  12. Adam Markowitz Says:

    Sebastian:

    I’m pretty sure that the point events are firing, but I’ll test it when I get back from vacation (towards the end of December). Thanks for the info and feel free to post problems to QC :)

    Thanks.

  13. James Thompson Says:

    Thanks for the starting point. I am including this code as an example to really dumb it down. I was looking for a simple example to duplicate the function that is in codesite so that when a person does "sm", it would auto create a showmessage(”);

    <?xml version="1.0" encoding="utf-8" ?>

    <codetemplate xmlns="http://schemas.borland.com/Delphi/2005/codetemplates&quot;

    version="1.0.0">

    <template name="sm" invoke="auto">

    <point name="message">

    <text>

    s

    </text>

    <hint>

    Message String

    </hint>

    </point>

    <description>

    Duplicate function of Coderush "SM"

    </description>

    <author>

    James Thompson

    </author>

    <code language="Delphi" delimiter="|">

    <![CDATA[showmessage('|message|');]]>

    </code>

    </template>

    </codetemplate>

    Hope this will help someone else

    James

  14. James Thompson Says:

    2 things since my last post (previous message), the first is that there should be nothing between <text> and </text> as there should be no default. The second was this was to duplicate coderush not codesite.

    Now the question, the default directory for the templates

    C:\Documents and Settings\jthompson\Local Settings\Application Data\Borland\BDS\4.0\code_templates. Is there anyway of redirecting this to another directory so that it might get backup.

    ie: c:\doc…\jthompson\mydocs\code_templates or better yet \\server\projects\code_templates

    Thanks

    James

  15. Adam Markowitz Says:

    James,

    Currently there are only 2 directories that are read by default. One being the objrepos\code_templates and the other being the one you point out. You should be able to write an Open Tools API expert though and just call <b>(BorlandIDEServices as IOTACodeTemplateServices).LoadTemplates(’mydirectory’);</b> This should allow you to add any directory to the templating system. Note that this is what is done internally so it *should* work, but I haven’t stress tested this much.

    HTH,

    -Adam

  16. Carsten Says:

    Hi Sebastian,

    I’m also looking to replace my favorite coderush templates in D2006:)

    While trying I figured the following:

    Point scripts do only working, if the point isn’t the first point in CDATA of the code element. F.e. the following doesn’t work for me:

    <code language="Delphi" context="any" delimiter="|">

    <![CDATA[

    SendDebugMsg('|classname|');

    SendDebugMsg('|clipboard|');]]>

    </code>

    ———

    But both scripts are fired with

    <code language="Delphi" context="any" delimiter="|">

    <![CDATA[

    |dummypoint|

    SendDebugMsg('|classname|');

    SendDebugMsg('|clipboard|');]]>

    </code>

    (Where dummypoint is a normal point without a script.)

    That’s pretty frustating. Did get the clipboard pasting working but only with the dummy item:(

    Adam, any tips or tricks?

    Thanks

    Carsten

  17. Sebastian Modersohn Says:

    Carsten,

    thanks for posting your observations. I only tried with one point so no wonder I didn’t get it working.

    If you find the time, could you post this with a dummy test-case to QC? That would really be great.

    Cheers,

    Sebastian

  18. ismael Says:

    d

  19. Igor Skomorokh Says:

    Hello.

    I have a question. Why it works not correctly? E.g.

    begin

    close;

    end;

    When I am selecting "close" text and then surrounding it with e.g. "Begin .. end;" I receive

    begin

    begin

    close;

    end;

    end;

    instead of

    begin

    begin

    close;

    end;

    end;

    What can be done to remove this issue?

    PS: if it is possible, please send me an answer on igor_thief[~@~]pisem.net

    Thanks beforehand!

  20. Adam Markowitz Says:

    Igor,

    Those two look the same to me. Are you talking about indentation? If so, when you post indentation in HTML you’ll need to use the &nbsp; or something of that sort to show the indentation.

    When you do the selection for the Surround templates try make sure the selection always starts in the first column and see if that helps. Code Formatting is scheduled for a future release of the product.

    HTH,

    -Adam

  21. Shawn Oster Says:

    I’m trying to prevent a surround template from indenting my code. If I have a template that is this:

    {$IFDEF Dev}

    |selected|

    {$ENDIF}

    Then whatever is |selected| gets indented once more. I have |selected| in the first column, right up against the gutter but it still indents. Any thoughts or is this what you meant by "code formatting" in future releases?

  22. Adam Markowitz Says:

    Shawn,

    Yes, this is what I meant by ‘code formatter’ in a future release.

  23. Luke Says:

    Adam,

    Thanks for all the info. I am wondering if there is something about the DeclareVariable that is out of our control. Delphi seems to have some extra checking in this script. I have the following template to create an object that is declared as an interface:

    <?xml version="1.0" encoding="utf-8" ?>

    <codetemplate xmlns="http://schemas.borland.com/Delphi/2005/codetemplates&quot;

    version="1.0.0">

    <template name="intfdec" invoke="manual">

    <point name="ident">

    <hint>

    variable name to declare

    </hint>

    <text>

    MyVar

    </text>

    </point>

    <point name="type">

    <hint>

    variable type

    </hint>

    <text>

    IMyObject

    </text>

    </point>

    <point name="instance">

    <hint>

    instantiated type

    </hint>

    <text>

    TMyObject

    </text>

    </point>

    <description>

    create object with Interface variable declaration

    </description>

    <script language="Delphi" onenter="false" onleave="true">

    DeclareVariable(|ident|, |type|);

    </script>

    <code language="Delphi" context="methodbody" delimiter="|"><![CDATA[|ident|:= |instance|.Create;]]>

    </code>

    </template>

    </codetemplate>

    However, it doesn’t work. The code is generated but the variable is not declared. If I change the script code to DeclareVariable(|ident|, |instance|); it works. It won’t work whenever I try to declare an object of a differenct type to that which is being created.

    Any ideas??



Server Response from: blog1.codegear.com