File Templates - custom variables

Issue #1474 resolved
Eric Kintzer created an issue

See also resolved issue: Alternative file templates

So, if I create a File Template (here called Apex Domain Class bis) for a .cls extension us...ng custom variables like ${SOBJ} such as shown below …

#parse("Apex File Header.cls")

public virtual class ${NAME} extends ApplicationSobjectDomain implements I${NAME} {

    public ${NAME}(${SOBJ}[] sObjectList) {
        super (sObjectList);
    }

    public static I${NAME} newInstance(List<${SOBJ}> sObjectList){
        return (I${NAME}) Application.Domain.newInstance(sObjectList);
    }

    public class Constructor implements fflib_SObjectDomain.IConstructable {
        public fflib_SObjectDomain construct(List<SObject> sObjectList) {
            return new ${NAME}(sObjectList);
        }
    }
}

Then if I invoke this on the File | New | Apex Domain Class bis I get prompted for the file name and SOBJ. All well and good. No need for the Live Template workaround and the ugly #[[$var$]]# syntax.

But if I invoke via File | New | Apex Class | Apex Domain Class bis, I do not get prompted for the $SOBJ variable. You only get an option to supply the file name and only the predefined ${NAME} field is applied to the template. The Live Template workaround also did not function.

Comments (31)

  1. Scott Wells repo owner

    Eric, it seems that something has changed in the live template-enabled file template stuff since I posted last to #949. As shown in the animated gif there, if you use File>New>Apex Class and then choose the desired template from the template drop-down, it would take you through the live template. It doesn't seem to do that anymore. Instead now you have to use File>New>Custom Template Name for it to activate the live template. That does work for me, though. I'll raise that seeming regression with JetBrains.

    Also note that I'm using the Velocity template placeholder syntax as shown in #949, e.g., #[[$SObject$]]#, and I don't think your template does use that. Take a look at the file template for HTML File for a built-in example of a template with post-creation params.

  2. Eric Kintzer reporter

    Scott --

    1 - I could also get Live Template syntax to work but only from File > New > Custom Template but of course, using the custom variable approach is a bit more convenient (and you mention it in the Description field for the various Apex templates which is how I got started in this whole “issue”).

    2 - My question was more about why File > New > Apex Class > choose custom template didn’t work for custom variables - that said, perhaps your remark above is the root cause for each file creation UI “path”. I could never get it to prompt for the custom variables.

    Eric

  3. Scott Wells repo owner

    Gotcha. The only way I've ever gotten it to work at all in a satisfactory manner is using Velocity live template placeholders. My big question is why that doesn't kick in when using FIle>New>Apex Class>Choose Custom Template. I'll follow up with JetBrains on that either this week or after I get back from Dreamforce.

  4. Eric Kintzer reporter

    my real goal was to get multi-files to be created but that seems to work only in ReSharper which I am not using (I wanted to build, for a given sobjectType, the fflib (Enterprise Architecture Pattern) Service, ServiceImpl, Selector, Domain, and various Interface classes in one click)

  5. Scott Wells repo owner

    Yeah, the standard file template-based file creation mechanism isn't going to help you there. I've implemented that in a custom manner such that meta.xml files are created when the corresponding source files are created, the correct set of bundle files are created when Aura and LWC components are created, etc., but I don't think there's a general-purpose way to do that in the JetBrains IDEs that IC supports.

  6. Xander Victory

    Just tested this myself (I’ve had very similar frustrations, funnily enough with trigger handler templates 😂)

    • using $FOO$ and new->ApexClass->Template subtype = blank spaces where the variables should be = code reformat breaks everything
    • using #[[$SObject$]]# and new->ApexClass->Template subtype = SObject in the variable places, no live template triggered (but hey, at least file is syntactically valid)
    • Using direct new->Template subtype = correct live template usage, but no meta file created (this seems to be the IDE’s expected way, native filetypes don’t display user templates in the ‘enter name’ list)

  7. Scott Wells repo owner

    This came back up this week and I dug in yet again. I found the root cause of the regression and finally figured out a viable workaround which I’ve implemented for the next build.

  8. Scott Wells repo owner

    Even more good news as I’ve extended the live template-based file template creation feature (for Apex classes, at least) to support live template macros. This means that you can have template variables default/prompt using much more contextually-aware information, e.g., apexSubtypes("SObject") or apexSuggestVariableName("Id", OTHER_VARIABLE_VALUE). I’ll elaborate more in the release notes and online documentation for the feature, but in short, you do have to include this variable metadata in the file template itself, e.g.:

    // SOBJECT.expression=apexSubtypes("SObject")
    // SOBJECT.defaultValue=SObject
    // NEWLIST.expression=apexSuggestVariableName(SOBJECT, "List")
    // NEWLIST.defaultValue=objects
    // OLDMAP.expression=apexSuggestVariableName("Id", SOBJECT)
    // OLDMAP.defaultValue=oldObjectsById
    

    though it’s of course stripped out of the generated file. Here’s a new trigger handler file template example that uses the configuration above to ensure that you first select some SObject type, then are prompted for variable names for a list of that type and a map of that type keyed by Id:

    This will also be included in this week’s build.

  9. Eric Kintzer reporter

    Ooh - can’t wait to try this with some templates to build fflib domain, selector, service classes from templates. Thanks Scott

  10. Eric Kintzer reporter

    Scott --

    I installed 2.2.5.5 and using the File Template as shown below (similar to the one in the OP), I still don’t get prompted for custom variables when I choose the file template in a similar manner to how you select TriggerHandler in your mini-video above. I only get prompted for for the custom variable if I choose from the Intellij New dialog - but then no .meta-xml file is created.

    My File Template fflib_Selector

    public class ${NAME} extends ApplicationSelector implements I${NAME} {
      private static final Boolean INCLUDE_FIELDSETS    = false;    // false is default, if true, class must include getSobjectFieldList
      private static final Boolean ENFORCE_CRUD     = false;    // true is default
      private static final Boolean ENFORCE_FLS      = false;    // false is default
      private static final Boolean SORT_SELECT_FIELDS   = true;     // true is defaultpublic override String getOrderBy() {return 'CreatedDate';}       // default field to order results by if not otherwise specified
    
      public List<Schema.SObjectField> getSObjectFieldList() {  // keep in alpha order, whatever is listed here is unioned with fieldsets
        return new List<Schema.SObjectField> {
            ${SOBJ}.Id,
            ${SOBJ}.Name    
        };
      }
    
    
    
      public ${NAME}() {    
        super(INCLUDE_FIELDSETS,ENFORCE_CRUD,ENFORCE_FLS,SORT_SELECT_FIELDS);       
      }
    
      /**
      * Factory to provide caller with a new Selector. Enables selectors to be mocked
      * @return selector's interface
      **/ 
      public static I${NAME} newInstance()    {
        return (I${NAME}) Application.Selector.newInstance(${SOBJ}.SObjectType);
      }
    
      /**
      * getSObjectType : Used to construct queries. Required.
      * @return ${SOBJ}.SObjectType
      **/
      public Schema.SObjectType getSObjectType() {return ${SOBJ}.SObjectType;}
    }
    

    Note the ${SOBJ} custom variable

    When selected in this dialog:

    I’m never prompted for ${SOBJ} and the rendered file just replaces ${SOBJ} with nothing. What am I doing wrong here?

  11. Scott Wells repo owner

    Eric, live template variables must take the form #[[$SOBJ$]]# (as opposed to ${SOBJ}) so that they're escaped from the Velocity templating engine that executes before it reaches the live template stage. Please take a look at the docs here for more info on the two types of variables.

  12. Eric Kintzer reporter

    Ah -- that worked -- I had tried this but forgot to check the box for Live Templates enabled. This is great stuff and is a huge productivity boon for me. You might want to point out in your doc on Fle Templates that custom variables - which the File Template Intellij doc says you can use https://groove.helix.com/url/wv1vm6sbklo6ffzvxrqa8/aHR0cHM6Ly93d3cuamV0YnJhaW5zLmNvbS9oZWxwL2lkZWEvZmlsZS10ZW1wbGF0ZS12YXJpYWJsZXMuaHRtbCNjdXN0b21fdGVtcGxhdGVfdmFyaWFibGVzI2dyb292ZXN1bTotODY4OTA2MjEw will not work for Apex class File Templates

    e.g. in this part of your doc https://groove.helix.com/url/wv1vm6sbklo6ffzvxrqa8/aHR0cHM6Ly9iaXRidWNrZXQub3JnL1Jvc2VTaWx2ZXJTb2Z0d2FyZS9pbGx1bWluYXRlZGNsb3VkL3dpa2kvVXNlcl9HdWlkZS9Db25maWd1cmluZ19JREVfRmVhdHVyZXMjbWFya2Rvd24taGVhZGVyLWZpbGUtYW5kLWNvZGUtdGVtcGxhdGVzI2dyb292ZXN1bTotOTU5MDg4MDgx :

    Enable Live Templates - If checked, additional variables of the form #[[$VARIABLE_NAME$]]# may be specified and used in the template so that the user can customize the final file. The template can also include variable configuration comments to specify each variable's expression, default value, whether the caret should always stop at the variable even if only one candidate value is available, and evaluation order, e.g.:

    Add: "Custom variables of the form ${xxxx} are not supported for Apex class File templates. Standard variables are supported". The OOTB Intellij help dialog states that custom variables are supported (which is true for non Apex class-type files like .html)

  13. Scott Wells repo owner

    Glad that worked for you! Regarding the suggested change, technically custom variables do work. That how, for example, ${NAME} gets populated. But I get your meaning that the term “custom” is misleading because it sounds like the user can specify ones of that form for replacement when in reality only the ones that the system (in this case, IC) makes available are going to be processed. I’ll play with the wording in the documentation so that it’s (more?) clear what role each type of variable plays.

  14. Eric Kintzer reporter

    SCott

    I spoke too soon

    while I can get the live templates to work as you suggested. No FoosSelector.cls-meta.xml file is generated for the className FoosSelector.cls and the class can't be saved --

  15. Scott Wells repo owner

    Are you creating it using File>New>Apex Class and then selecting YourTemplate, or are you creating it using File>New>YourTemplate? You must do it via the former as that’s the only way that IC2 gets to add post-processing login for meta.xml file creation, subscription update, deploy-on-create, etc.

    If you are doing it via the former and it’s not working properly, I’d need to see your idea.log as it sounds like something is failing.

  16. Eric Kintzer reporter

    I'm doing File > New > Apex Class and then selecting YourTemplate. idea.log is attached

    Thank you for looking into this (you'll see entries for me deleting FooXXX class files as I did multiple attempts - you can ignore these)

    Eric

  17. Scott Wells repo owner

    Still nothing. Do you just want to send it to me directly via email? Or is that what you’re already trying to do?

  18. Eric Kintzer reporter
      <div class="preview-container wiki-content"><!-- loaded via ajax --></div>
      <div class="mask"></div>
    </div>
    

    </div> </form>

  19. Scott Wells repo owner

    Okay, I know you said to ignore the deletes, but I’m confused by what I’m seeing:

    2023-02-03 09:53:43,925 [63884202]   INFO - #c.i.i.f.CreateApexClassAction - Disabling live templating temporarily during file creation for file template 'fflib_Selector'.
    2023-02-03 09:53:43,976 [63884253]   FINE - #c.i.i.b.ForceComBuilder -   Module = EKDEV.
    2023-02-03 09:53:43,976 [63884253]   FINE - #c.i.i.b.IlluminatedCloudDeployOnSaveAllActionListener - Adding modified file /Users/eric.kintzer/IdeaProjects/EKDEV/src/classes/FoosSelector.cls.
    2023-02-03 09:53:44,045 [63884322]   INFO - #c.i.i.f.CreateApexClassAction - Re-enabling live templating for file template 'fflib_Selector'.
    2023-02-03 09:53:44,048 [63884325]   INFO - #c.i.i.f.CreateApexClassAction - Executing live template for file 'FoosSelector.cls' created from file template 'fflib_Selector'.
    2023-02-03 09:53:44,049 [63884326]   INFO - #c.i.i.f.CreateApexClassAction - Variables discovered via segments: { SOBJ }
    2023-02-03 09:53:44,049 [63884326]   INFO - #c.i.i.f.CreateApexClassAction - Variables ordered for execution: { SOBJ }
    2023-02-03 09:53:44,049 [63884326]   INFO - #c.i.i.f.CreateApexClassAction - Added template variable 'SOBJ' with expression='null', defaultValue='SOBJ', alwaysStopAt=true.
    2023-02-03 09:53:54,401 [63894678]   FINE - #c.i.i.r.c.ApexCopyHandler - Checking to see whether a copy was requested for an Apex source file with a top-level type or trigger declaration.
    2023-02-03 09:53:54,402 [63894679]   FINE - #c.i.i.r.c.ApexCopyHandler -   It is an Apex source file with a top-level type or trigger declaration: FoosSelector.cls
    2023-02-03 09:54:00,763 [63901040]   INFO - #c.i.i.f.CreateApexClassAction - Live template finished. Performing file creation post-processing.
    2023-02-03 09:54:00,794 [63901071]   FINE - #c.i.i.b.ForceComBuilder -   Module = EKDEV.
    2023-02-03 09:54:00,795 [63901072]   FINE - #c.i.i.b.IlluminatedCloudDeployOnSaveAllActionListener - Adding modified file /Users/eric.kintzer/IdeaProjects/EKDEV/src/classes/FoosSelector.cls-meta.xml.
    2023-02-03 09:54:00,831 [63901108]   FINE - #c.i.i.b.ForceComBuilder - Handling file deleted event for filename FoosSelector.cls, file /Users/eric.kintzer/IdeaProjects/EKDEV/src/classes/FoosSelector.cls, parent /Users/eric.kintzer/IdeaProjects/EKDEV/src/classes.
    2023-02-03 09:54:00,831 [63901108]   FINE - #c.i.i.b.ForceComBuilder -   shouldProcess = true, deleteForMove = false
    2023-02-03 09:54:03,901 [63904178]   FINE - #c.i.i.b.ForceComBuilder - Handling file deleted event for filename FoosSelector.cls-meta.xml, file /Users/eric.kintzer/IdeaProjects/EKDEV/src/classes/FoosSelector.cls-meta.xml, parent /Users/eric.kintzer/IdeaProjects/EKDEV/src/classes.
    2023-02-03 09:54:03,901 [63904178]   FINE - #c.i.i.b.ForceComBuilder -   shouldProcess = true, deleteForMove = false
    2023-02-03 09:54:04,816 [63905093]   FINE - #c.i.u.VariableLengthPollingInterval - ForceComMetadataDeleter.waitForDeploymentToComplete: Using polling interval 1000 ms for polling iteration 1.
    2023-02-03 09:54:06,527 [63906804]   INFO - #c.i.i.b.ForceComMetadataDeleter - Deployment status = SUCCEEDED
    2023-02-03 09:54:06,530 [63906807]   FINE - #c.i.i.b.ForceComBuilderUtil - Converting deploy message:
    {
      "changed": false,
      "componentType": "ApexClass",
      "created": false,
      "createdDate": 1675446845000,
      "deleted": false,
      "fileName": "destructiveChanges.xml",
      "fullName": "destructiveChanges.xml",
      "problem": "No ApexClass named: FoosSelector found",
      "problemType": "WARNING",
      "success": true
    }
    2023-02-03 09:54:06,530 [63906807]   WARN - #c.i.i.b.ForceComMetadataDeleter -   Found WARNING for ApexClass /destructiveChanges.xml.cls: No ApexClass named: FoosSelector found
    

    That’s basically saying that FoosSelector.cls and FoosSelector.cls-meta.xml were successfully created, but a delete of those same files is interleaved toward the end. Can you please repeat this operation with some other name to be distinct and without any delete activity so I can see what’s going on with just the create action?

  20. Scott Wells repo owner

    Okay, that helped. How are you filling out the template? I ask because I see IC2 start the template in that log:

    2023-02-03 10:22:57,998 [65638275]   INFO - #c.i.i.f.CreateApexClassAction - Executing live template for file 'BarsSelector.cls' created from file template 'fflib_Selector'.
    

    but I don’t ever see it complete. There should be a log entry saying Live template [finished|cancelled]. Performing file creation post-processing. Those happen when the template completes as either finished (last template variable populated) or cancelled (Esc typed) respectively. There are no other listener methods to override in terms of completion state, but evidently you’re doing something else. Are you doing an explicit save via Ctrl/Cmd+S, perhaps? I’m not sure if that might cause the template to end early without firing a completion callback…

  21. Eric Kintzer reporter

    OK

    1. File > New > Apex Class > fflib_Selector and className BarsSelector, then press Enter
    2. Class file appears in editor with SOBJ appearing in Red as shown in screen shot (screenshot uses className FooBarSelector but that is an artifact at getting you more details). Cursor is positioned on first SOBJ
    3. I start typing and replace with Bar__c
    4. All SOBJ replaced with Bar__c as expected
    5. Command-Save (Mac)
    6. Error message - Must specify the metadata file.

    [image: Screenshot 2023-02-03 at 10.37.08.png]

  22. Scott Wells repo owner

    You shouldn’t need to do a Cmd+S at the end, though it also shouldn’t cause the template to fail to complete successfully. Can you confirm whether or not things work as expected/desired if you don’t do a Cmd+S at the end? I’ll also investigate why an explicit save is causing the template to fail to complete.

  23. Eric Kintzer reporter

    If I don't do CMD+S there is no metadata file - the Intellij editor window doesn’t show a source and metadata tab at the bottom - no tabs at all

    I’m running 2022.2.2 Community Edition of Intellij

  24. Scott Wells repo owner

    Okay. Sounds like the template isn’t completing properly for you. Can you update to 2022.3.2 which is the latest release build from JetBrains and see if the problem still exists?

  25. Scott Wells repo owner

    FWIW, I just confirmed the correct template completion behavior in IntelliJ IDEA Community Edition 2022.3.2:

    2023-02-03 13:10:02,829 [ 120739]   INFO - #com.illuminatedcloud.intellij.filetype.CreateApexClassAction - Disabling live templating temporarily during file creation for file template 'Comparator'.
    2023-02-03 13:10:03,036 [ 120946]   INFO - #com.illuminatedcloud.intellij.filetype.CreateApexClassAction - Re-enabling live templating for file template 'Comparator'.
    2023-02-03 13:10:03,057 [ 120967]   INFO - #com.illuminatedcloud.intellij.filetype.CreateApexClassAction - Executing live template for file 'StringComparator.cls' created from file template 'Comparator'.
    2023-02-03 13:10:03,059 [ 120969]   INFO - #com.illuminatedcloud.intellij.filetype.CreateApexClassAction - Expression for 'type' = 'apexSubtypes("Object")'.
    2023-02-03 13:10:03,059 [ 120969]   INFO - #com.illuminatedcloud.intellij.filetype.CreateApexClassAction - Order of evaluation for 'type' = 0.
    2023-02-03 13:10:03,059 [ 120969]   INFO - #com.illuminatedcloud.intellij.filetype.CreateApexClassAction - Always-stop-at setting for 'type' = 'true'.
    2023-02-03 13:10:03,059 [ 120969]   INFO - #com.illuminatedcloud.intellij.filetype.CreateApexClassAction - Expression for 'variable' = 'apexSuggestVariableName(type)'.
    2023-02-03 13:10:03,059 [ 120969]   INFO - #com.illuminatedcloud.intellij.filetype.CreateApexClassAction - Order of evaluation for 'variable' = 1.
    2023-02-03 13:10:03,060 [ 120970]   INFO - #com.illuminatedcloud.intellij.filetype.CreateApexClassAction - Always-stop-at setting for 'variable' = 'false'.
    2023-02-03 13:10:03,060 [ 120970]   INFO - #com.illuminatedcloud.intellij.filetype.CreateApexClassAction - Variables discovered via segments: { type, variable }
    2023-02-03 13:10:03,079 [ 120989]   INFO - #com.illuminatedcloud.intellij.filetype.CreateApexClassAction - Variables ordered for execution: { type, variable }
    2023-02-03 13:10:03,079 [ 120989]   INFO - #com.illuminatedcloud.intellij.filetype.CreateApexClassAction - Added template variable 'type' with expression='apexSubtypes("Object")', defaultValue='type', alwaysStopAt=true.
    2023-02-03 13:10:03,079 [ 120989]   INFO - #com.illuminatedcloud.intellij.filetype.CreateApexClassAction - Added template variable 'variable' with expression='apexSuggestVariableName(type)', defaultValue='variable', alwaysStopAt=false.
    2023-02-03 13:10:18,712 [ 136622]   INFO - #com.illuminatedcloud.intellij.filetype.CreateApexClassAction - Live template finished. Performing file creation post-processing.
    

    That last line is what’s missing in your logs, and it’s the one that results in the creation of the meta.xml file and (if configured to do so) deployment of the created class to the organization.

    Let me know if you don’t see that resolved by updating to the latest released build of IntelliJ IDEA.

  26. Eric Kintzer reporter

    That worked – metadata files created upon File > New > Apex Class > template+className > ENTER

    Something you might also want to add to the doc on File Templates - must be on Intellij version x.x

    I know yo repeatedly remind us that not all IC2 features will work as expected on older versions of Intellij but I’m not always upgrading to the latest IntelliJ because IC2 has had some compatibility issues with latest IntelliJ in the past – so, I try and strike a happy medium. This time, I struck out.

    Thank you for your patience here. FWIW, I tried the new postfix feature and it works great and is real handy, especially the .cast

  27. Scott Wells repo owner

    Okay, that’s great. I think I’ll just see if anyone else runs into this issues before adding a version qualification. In general when a support request comes in, if it’s something I can’t reproduce locally, my go-to is pretty much “make sure you’re 100% up-to-date and able to reproduce it before we go further”. This just reinforces that stance. I am surprised, though, that the completion callbacks weren’t invoked in something as recent as 2022.2.2, though. I guess I waited until the right time to revisit this issue! Better to be lucky than good…

    Glad to hear you’re liking the postfix completion stuff as well. That’s been rattling around in my head for WAY too long. Once I knock out the Spring ‘23 stuff next week, I may circle back to it and see if I can get customization going since I already anticipate folks wanting to be able to create their own.

  28. Log in to comment