Recent

Author Topic: [SOLVED] Extending Lazarus IDE — custom file types in "New…" menu  (Read 995 times)

furious programming

  • Hero Member
  • *****
  • Posts: 858
I'm working on a project that currently has a hundred source files and will have several hundred more. Each of these files (units and include files) has a specific structure (header, default modules, etc.), which I also intend to use for each new file of a given type. Currently, I have created code templates for these two types of files, which I have to manually insert into each new fileand this is a pain.

Lazarus is extensible, so it's better to just add two more items to the dialog, available in the File/New... menu. And here comes the problem, because I can't understand how it's supposed to look like. I used this article — Extending the IDE: Add a new file type — but it seems to be outdated because some things just don't exist anymore and the template it provides doesn't work very well.



I created a new package and added the following test code to it, to register two new file types (custom unit and custom include file):

Code: Pascal  [Select][+][-]
  1. unit CustomFileTypes;
  2.  
  3. {$MODE OBJFPC}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   ProjectIntf;
  9.  
  10. type
  11.   TFileDescriptorCustomUnit = class(TProjectFileDescriptor)
  12.   public
  13.     constructor Create(); override;
  14.     function GetLocalizedName(): String; override;
  15.     function GetLocalizedDescription(): String; override;
  16.   end;
  17.  
  18. type
  19.   TFileDescriptorCustomInclude = class(TProjectFileDescriptor)
  20.   public
  21.     constructor Create(); override;
  22.     function GetLocalizedName(): String; override;
  23.     function GetLocalizedDescription(): String; override;
  24.   end;
  25.  
  26.   procedure Register();
  27.  
  28. implementation
  29.  
  30. procedure Register();
  31. begin
  32.   RegisterProjectFileDescriptor(TFileDescriptorCustomUnit.Create(),    FileDescGroupName);
  33.   RegisterProjectFileDescriptor(TFileDescriptorCustomInclude.Create(), FileDescGroupName);
  34. end;
  35.  
  36. constructor TFileDescriptorCustomUnit.Create();
  37. begin
  38.   inherited Create();
  39.  
  40.   Name := 'CustomUnit';
  41.   DefaultFilename := 'CustomUnit.pp';
  42. end;
  43.  
  44. function TFileDescriptorCustomUnit.GetLocalizedName(): String;
  45. begin
  46.   Result := 'Custom unit';
  47. end;
  48.  
  49. function TFileDescriptorCustomUnit.GetLocalizedDescription(): String;
  50. begin
  51.   Result := 'Create a new custom unit.';
  52. end;
  53.  
  54. constructor TFileDescriptorCustomInclude.Create();
  55. begin
  56.   inherited Create();
  57.  
  58.   Name := 'CustomInclude';
  59.   DefaultFilename := 'CustomInclude.inc';
  60. end;
  61.  
  62. function TFileDescriptorCustomInclude.GetLocalizedName(): String;
  63. begin
  64.   Result := 'Custom include file';
  65. end;
  66.  
  67. function TFileDescriptorCustomInclude.GetLocalizedDescription(): String;
  68. begin
  69.   Result := 'Create a new custom include file.';
  70. end;
  71.  
  72. end.

After installing the package and rebuilding Lazarus, these two new items are visible in the dialog box, in the branch Module (see screenshot attached). So far I've apparently done everything right.

When I select CustomInclude from this dialog, Lazarus creates a new file, attaches it to the project and opens it in the code editor — the name of the tab is CustomInclude.inc, which is what it should be to be. The second screenshot in the attachments shows what the code editor and the Project Inspector window look like (properly).

Unfortunately, strange things happen with CustomUnit. First, the name of the module in the Project Inspector window is .pp instead of CustomUnit.pp, second, the name of the tab in the code editor is unit1 instead of CustomUnit, and thirdly, the code editor does not treat this module as a Pascal unit, but as a plain text file (hence the editor's white background instead of black).



I know I didn't do everything necessary, but when I started adding my descriptor's text property settings to the package code, Lazarus after rebuild either didn't start at all, or crashed, or freezed for a long time. I'd rather not waste any more time guessing what to do — it's better to ask and do whatever it takes to make it work properly.

Surely something needs to be done to get CustomUnit to create properly, because for now something is wrong or missing and crazy things are happening. After fixing the problem with custom units, I would like new files (both units and include files) to be created with specific content, determined by me (with the same one I currently have in the code templates). And in addition, if possible, I would like my two new file types to be in a separate branch, preferably at the top of the tree, instead of in the Module branch.

Could someone help me with this? I will be very grateful for tips.
« Last Edit: January 15, 2023, 04:38:20 pm by furious programming »
Lazarus 3.2 with FPC 3.2.2, Windows 10 — all 64-bit

Working solo on an acrade, action/adventure game in retro style (pixelart), programming the engine and shell from scratch, using Free Pascal and SDL. Release planned in 2026.

dbannon

  • Hero Member
  • *****
  • Posts: 2794
    • tomboy-ng, a rewrite of the classic Tomboy
Re: Extending Lazarus IDE — custom file types in "New…" menu
« Reply #1 on: January 15, 2023, 02:26:29 am »
I wonder, given it might be a "one off" to use the External Tools model. You can run a shell script from there using various defines Lazarus makes available.

Tools -> Configure External Tools .....

Davo
Lazarus 3, Linux (and reluctantly Win10/11, OSX Monterey)
My Project - https://github.com/tomboy-notes/tomboy-ng and my github - https://github.com/davidbannon

furious programming

  • Hero Member
  • *****
  • Posts: 858
Re: Extending Lazarus IDE — custom file types in "New…" menu
« Reply #2 on: January 15, 2023, 02:35:41 am »
Hey, that's why Lazarus gives the ability to extend its functionality to take advantage of it. The most convenient solution for me is just adding my own file types to the discussed dialog box, which is why I want to do it this way. But since I've never done it before, I don't really know how to program such things — that's why I'm asking for help.

It would be good to specify how to extend this dialog with new items, because I'm not the only one interested in this, and the wiki contains virtually no useful information (relating to the current version of Lazarus).
Lazarus 3.2 with FPC 3.2.2, Windows 10 — all 64-bit

Working solo on an acrade, action/adventure game in retro style (pixelart), programming the engine and shell from scratch, using Free Pascal and SDL. Release planned in 2026.

paweld

  • Hero Member
  • *****
  • Posts: 996
Re: Extending Lazarus IDE — custom file types in "New…" menu
« Reply #3 on: January 15, 2023, 07:10:24 am »
Code: Pascal  [Select][+][-]
  1. unit CustomFileTypes;
  2.  
  3. {$MODE OBJFPC}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, ProjectIntf;
  9.  
  10. type
  11.  
  12.   { TFileDescriptorCustomUnit }
  13.  
  14.   TFileDescriptorCustomUnit = class(TFileDescPascalUnit)
  15.   public
  16.     constructor Create(); override;
  17.     function GetLocalizedName(): String; override;
  18.     function GetLocalizedDescription(): String; override;
  19.     function GetInterfaceUsesSection : String; override;
  20.     function GetInterfaceSource(const aFilename, aSourceName, aResourceName: String): String; override;
  21.     function GetImplementationSource(const Filename, SourceName, ResourceName: String): String; override;
  22.   end;
  23.  
  24. type
  25.   TFileDescriptorCustomInclude = class(TProjectFileDescriptor)
  26.   public
  27.     constructor Create(); override;
  28.     function GetLocalizedName(): String; override;
  29.     function GetLocalizedDescription(): String; override;
  30.   end;
  31.  
  32.   procedure Register();
  33.  
  34. implementation
  35.  
  36. procedure Register();
  37. begin
  38.   RegisterProjectFileDescriptor(TFileDescriptorCustomUnit.Create(),    FileDescGroupName);
  39.   RegisterProjectFileDescriptor(TFileDescriptorCustomInclude.Create(), FileDescGroupName);
  40. end;
  41.  
  42. constructor TFileDescriptorCustomUnit.Create();
  43. begin
  44.   inherited Create();
  45.  
  46.   Name := 'CustomUnit';
  47.   DefaultSourceName := 'CustomUnit1';
  48.   DefaultFilename := 'CustomUnit.pp';
  49. end;
  50.  
  51. function TFileDescriptorCustomUnit.GetLocalizedName(): String;
  52. begin
  53.   Result := 'Custom unit';
  54. end;
  55.  
  56. function TFileDescriptorCustomUnit.GetLocalizedDescription(): String;
  57. begin
  58.   Result := 'Create a new custom unit.';
  59. end;
  60.  
  61. function TFileDescriptorCustomUnit.GetInterfaceUsesSection: String;
  62. begin
  63.   Result := inherited GetInterfaceUsesSection + ', fgl, MyUnit';
  64. end;
  65.  
  66. function TFileDescriptorCustomUnit.GetInterfaceSource(const aFilename, aSourceName, aResourceName: String): String;
  67. var
  68.   src: TStringList;
  69. begin
  70.   Result := inherited GetInterfaceSource(aFilename, aSourceName, aResourceName);
  71.   src := TStringList.Create;
  72.   src.Add('type');
  73.   src.Add('  TMyList = specialize TFPGList<Integer>;');
  74.   src.Add('');
  75.   src.Add('function MyFunc(i: Integer): Boolean;');  
  76.   src.Add(Result);
  77.   Result := src.Text;
  78.   src.Free;
  79. end;
  80.  
  81. function TFileDescriptorCustomUnit.GetImplementationSource(const Filename,
  82.   SourceName, ResourceName: String): String;
  83. var
  84.   src: TStringList;
  85. begin
  86.   Result := inherited GetImplementationSource(Filename, SourceName, ResourceName);
  87.   src := TStringList.Create;
  88.   src.Add('function MyFunc(i: Integer): Boolean;');
  89.   src.Add('begin');
  90.   src.Add('  Result := i = 9;');
  91.   src.Add('end;');      
  92.   src.Add(Result);
  93.   Result := src.Text;
  94.   src.Free;
  95. end;
  96.  
  97. constructor TFileDescriptorCustomInclude.Create();
  98. begin
  99.   inherited Create();
  100.  
  101.   Name := 'CustomInclude';
  102.   DefaultFilename := 'CustomInclude.inc';
  103. end;
  104.  
  105. function TFileDescriptorCustomInclude.GetLocalizedName(): String;
  106. begin
  107.   Result := 'Custom include file';
  108. end;
  109.  
  110. function TFileDescriptorCustomInclude.GetLocalizedDescription(): String;
  111. begin
  112.   Result := 'Create a new custom include file.';
  113. end;
  114.  
  115. end.
Best regards / Pozdrawiam
paweld

furious programming

  • Hero Member
  • *****
  • Posts: 858
Re: Extending Lazarus IDE — custom file types in "New…" menu
« Reply #4 on: January 15, 2023, 02:49:54 pm »
Nah, I used the wrong class for inheritance... Thank you @paweld for the example, as always you can be counted on.

Well, I did what I wanted, the above example is correct and allows me to do everything I needed. The only thing I had to do differently was the include files — I also created the descriptor class based on TFileDescPascalUnit, otherwise I couldn't specify the default content. However, in both cases (units and include files) I specified the entire content of the file in the overridden CreateSource method, and I overwrote the other ones and they return empty strings. This is more convenient to me.



However, I discovered one problem. I installed the package in the IDE and rebuilt the IDE. In a new project, I created a new unit using my GameUnit position. A new unit was created, added to the Project Inspector, the new file appeared in the code editor with the content I specified. Everything is fine except for the file name in the Project Inspector window and the file name in the code editor tab.

In both cases, the IDE uses the name lowercase — game_unit — despite the fact that in the descriptor code I have set a PascalCase-style name — Game_Unit. This is not the case with an include file — Game_Include is used everywhere (Project Inspector, editor code, save dialog etc.), not lowercase. Both my descriptor classes inherit from TFileDescPascalUnit and their constructors look identical:

Code: Pascal  [Select][+][-]
  1. constructor TFileDescriptorGameUnit.Create();
  2. begin
  3.   inherited Create();
  4.  
  5.   Name              := 'GameUnit';
  6.   DefaultSourceName := 'Game_Unit';
  7.   DefaultFilename   := 'Game_Unit.pp';
  8. end;
  9.  
  10. constructor TFileDescriptorGameInclude.Create();
  11. begin
  12.   inherited Create();
  13.  
  14.   Name              := 'GameInclude';
  15.   DefaultSourceName := 'Game_Include';
  16.   DefaultFilename   := 'Game_Include.inc';
  17. end;

It seems that in the case of units, the IDE intentionally modifies the filename and changes it to lowercase. What is the reason for this? Can I fix it somehow? The attachments show screenshots of the code editor and the Project Inspector window, illustrating the issue with filenames.



An additional question still remains — can I somehow create a new branch in the item tree in the New... dialog window and register my own file templates to it, or do they have to be registered in the Module branch? I'd rather have a separate branch at the top of this tree.
« Last Edit: January 15, 2023, 02:54:10 pm by furious programming »
Lazarus 3.2 with FPC 3.2.2, Windows 10 — all 64-bit

Working solo on an acrade, action/adventure game in retro style (pixelart), programming the engine and shell from scratch, using Free Pascal and SDL. Release planned in 2026.

paweld

  • Hero Member
  • *****
  • Posts: 996
Re: Extending Lazarus IDE — custom file types in "New…" menu
« Reply #5 on: January 15, 2023, 03:54:57 pm »
Unfortunately, the names of the new files are lowercase, only when saving the name from the definition is proposed.
 
As for the new category it can be created, but it is added under the standards lazarus categories
Code: Pascal  [Select][+][-]
  1. unit CustomFileTypes;
  2.  
  3. {$MODE OBJFPC}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, ProjectIntf, NewItemIntf;
  9.  
  10. type
  11.  
  12.   { TFileDescriptorCustomUnit }
  13.  
  14.   TFileDescriptorCustomUnit = class(TFileDescPascalUnit)
  15.   public
  16.     constructor Create(); override;
  17.     function GetLocalizedName(): String; override;
  18.     function GetLocalizedDescription(): String; override;
  19.     function GetInterfaceUsesSection : String; override;
  20.     function GetInterfaceSource(const aFilename, aSourceName, aResourceName: String): String; override;
  21.     function GetImplementationSource(const Filename, SourceName, ResourceName: String): String; override;
  22.   end;
  23.  
  24. type
  25.   TFileDescriptorCustomInclude = class(TProjectFileDescriptor)
  26.   public
  27.     constructor Create(); override;
  28.     function GetLocalizedName(): String; override;
  29.     function GetLocalizedDescription(): String; override;
  30.   end;
  31.  
  32.   procedure Register();
  33.  
  34. implementation
  35.  
  36. ResourceString
  37.   MyGroupName = 'Mój moduł';
  38.  
  39. procedure Register();
  40. begin
  41.   RegisterNewItemCategory(TNewIDEItemCategory.Create(MyGroupName));
  42.   RegisterProjectFileDescriptor(TFileDescriptorCustomUnit.Create(),    MyGroupName);
  43.   RegisterProjectFileDescriptor(TFileDescriptorCustomInclude.Create(), MyGroupName);
  44. end;  
  45. //  .........
  46.  
Best regards / Pozdrawiam
paweld

furious programming

  • Hero Member
  • *****
  • Posts: 858
Re: Extending Lazarus IDE — custom file types in "New…" menu
« Reply #6 on: January 15, 2023, 04:37:41 pm »
Ok, done — works like a charm. 8)

Unfortunately, the names of the new files are lowercase, only when saving the name from the definition is proposed.

Indeed, but why? Is it a bug or a feature? And if it is a feature, what is the purpose of this? Can somebody explain this?
 
Quote
As for the new category it can be created, but it is added under the standards lazarus categories

Meh, that's a pity... It's because the RegisterNewItemCategory function can only add new items, instead of taking additional parameter with the target index of the new category. It would be nice, however, if we could interfere with the order of branches in this tree.
« Last Edit: January 15, 2023, 04:45:39 pm by furious programming »
Lazarus 3.2 with FPC 3.2.2, Windows 10 — all 64-bit

Working solo on an acrade, action/adventure game in retro style (pixelart), programming the engine and shell from scratch, using Free Pascal and SDL. Release planned in 2026.

tetrastes

  • Sr. Member
  • ****
  • Posts: 481
Re: Extending Lazarus IDE — custom file types in "New…" menu
« Reply #7 on: January 16, 2023, 01:06:23 pm »
It seems that in the case of units, the IDE intentionally modifies the filename and changes it to lowercase. What is the reason for this? Can I fix it somehow?

May be so? For me it works:

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: [SOLVED] Extending Lazarus IDE — custom file types in "New…" menu
« Reply #8 on: January 16, 2023, 01:23:44 pm »
The reason for the default auto-renaming-to-lowercase is to minimise cross-platform incompatibilities.
Windows treats unit1.pas, Unit1.pas, unit1.PAS, etc. as identical files. Linuxes and Mac do not, and those OSs regard unit1.pas as an entirely different file from Unit1.pas.

If you only ever work under Windows, and your apps are only ever run on that particular OS, you can get away with the casing of filenames being quite sloppy.

Lazarus, however, expects that apps you produce may be cross-platform, when the slightest alteration in the casing of any file will scupper your app. So using all lowercase file naming is the simplest "lowest common denominator" for file names, and a convention that is very widely respected.

furious programming

  • Hero Member
  • *****
  • Posts: 858
Re: [SOLVED] Extending Lazarus IDE — custom file types in "New…" menu
« Reply #9 on: January 16, 2023, 02:17:58 pm »
May be so? For me it works:

My settings also look like this, but they don't apply to the names visible in the tabs of the code editor and in the files tree of the Project Inspector window (for files that are newly created but not yet saved to disk). So, this unfortunately can't solve the problem.



The reason for the default auto-renaming-to-lowercase is to minimise cross-platform incompatibilities.

[...]

Lazarus, however, expects that apps you produce may be cross-platform, when the slightest alteration in the casing of any file will scupper your app. So using all lowercase file naming is the simplest "lowest common denominator" for file names, and a convention that is very widely respected.

Case sensitivity in filenames across platforms is no reason for the IDE to interfere with module naming. Firstly, very few people use lowercase style filenames anyway (change them to PascalCase, which is proper for Pascal), and secondly, the IDE only changes the case of the modules, and not the include files, although these also contain source code.

It doesn't matter what the files are named, as long as they are valid for the given platform (i.e. do not contain illegal characters for filenames). The IDE uses the names you specify anyway, so it doesn't matter if they are lowercase or uppercase, with dot/underscore characters or not, because filenames don't change by themselves, so the IDE knows what names to use — always.

Right now, it looks like the Lazarus developers themselves don't trust how it works, and instead of using user-specified names, they tweak those names just in case the IDE itself can't work properly. It's ridiculous, and it's irritating. Especially that the simplest and safest solution is to use what the user specifies, and forcibly correcting names only complicates the software development process and introduces ambiguities.

Quote
Lazarus, however, expects that apps you produce may be cross-platform, when the slightest alteration in the casing of any file will scupper your app.

First, Lazarus shouldn't expect that the application you're producing will be cross-platform, because there's no obligation to code to be portable. You may want to write code for only one platform (or a range of selected ones) and the IDE should not make it difficult for you.

Secondly, if Lazarus doesn't mess with the module naming style, then no filename errors will be allowed to occur. Also, the supposed reason you gave is not implemented correctly (and not necessary at all), because the name of the created unit is changed to lowercase only in the code editor and in the Project Inspector window, but the original name is put in the unit body and in the dialog for saving the file to disk. What Lazarus is doing now is absurd and only confuses the user, this is asking for troubles. This automatic renaming mechanism doesn't protect IDE from anything, it just makes your life harder.
« Last Edit: January 16, 2023, 02:35:22 pm by furious programming »
Lazarus 3.2 with FPC 3.2.2, Windows 10 — all 64-bit

Working solo on an acrade, action/adventure game in retro style (pixelart), programming the engine and shell from scratch, using Free Pascal and SDL. Release planned in 2026.

 

TinyPortal © 2005-2018