Forum > General
[SOLVED] Extending Lazarus IDE — custom file types in "New…" menu
furious programming:
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 [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---unit CustomFileTypes; {$MODE OBJFPC}{$H+} interface uses ProjectIntf; type TFileDescriptorCustomUnit = class(TProjectFileDescriptor) public constructor Create(); override; function GetLocalizedName(): String; override; function GetLocalizedDescription(): String; override; end; type TFileDescriptorCustomInclude = class(TProjectFileDescriptor) public constructor Create(); override; function GetLocalizedName(): String; override; function GetLocalizedDescription(): String; override; end; procedure Register(); implementation procedure Register();begin RegisterProjectFileDescriptor(TFileDescriptorCustomUnit.Create(), FileDescGroupName); RegisterProjectFileDescriptor(TFileDescriptorCustomInclude.Create(), FileDescGroupName);end; constructor TFileDescriptorCustomUnit.Create();begin inherited Create(); Name := 'CustomUnit'; DefaultFilename := 'CustomUnit.pp';end; function TFileDescriptorCustomUnit.GetLocalizedName(): String;begin Result := 'Custom unit';end; function TFileDescriptorCustomUnit.GetLocalizedDescription(): String;begin Result := 'Create a new custom unit.';end; constructor TFileDescriptorCustomInclude.Create();begin inherited Create(); Name := 'CustomInclude'; DefaultFilename := 'CustomInclude.inc';end; function TFileDescriptorCustomInclude.GetLocalizedName(): String;begin Result := 'Custom include file';end; function TFileDescriptorCustomInclude.GetLocalizedDescription(): String;begin Result := 'Create a new custom include file.';end; 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.
dbannon:
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
furious programming:
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).
paweld:
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---unit CustomFileTypes; {$MODE OBJFPC}{$H+} interface uses Classes, SysUtils, ProjectIntf; type { TFileDescriptorCustomUnit } TFileDescriptorCustomUnit = class(TFileDescPascalUnit) public constructor Create(); override; function GetLocalizedName(): String; override; function GetLocalizedDescription(): String; override; function GetInterfaceUsesSection : String; override; function GetInterfaceSource(const aFilename, aSourceName, aResourceName: String): String; override; function GetImplementationSource(const Filename, SourceName, ResourceName: String): String; override; end; type TFileDescriptorCustomInclude = class(TProjectFileDescriptor) public constructor Create(); override; function GetLocalizedName(): String; override; function GetLocalizedDescription(): String; override; end; procedure Register(); implementation procedure Register();begin RegisterProjectFileDescriptor(TFileDescriptorCustomUnit.Create(), FileDescGroupName); RegisterProjectFileDescriptor(TFileDescriptorCustomInclude.Create(), FileDescGroupName);end; constructor TFileDescriptorCustomUnit.Create();begin inherited Create(); Name := 'CustomUnit'; DefaultSourceName := 'CustomUnit1'; DefaultFilename := 'CustomUnit.pp';end; function TFileDescriptorCustomUnit.GetLocalizedName(): String;begin Result := 'Custom unit';end; function TFileDescriptorCustomUnit.GetLocalizedDescription(): String;begin Result := 'Create a new custom unit.';end; function TFileDescriptorCustomUnit.GetInterfaceUsesSection: String;begin Result := inherited GetInterfaceUsesSection + ', fgl, MyUnit';end; function TFileDescriptorCustomUnit.GetInterfaceSource(const aFilename, aSourceName, aResourceName: String): String;var src: TStringList;begin Result := inherited GetInterfaceSource(aFilename, aSourceName, aResourceName); src := TStringList.Create; src.Add('type'); src.Add(' TMyList = specialize TFPGList<Integer>;'); src.Add(''); src.Add('function MyFunc(i: Integer): Boolean;'); src.Add(Result); Result := src.Text; src.Free;end; function TFileDescriptorCustomUnit.GetImplementationSource(const Filename, SourceName, ResourceName: String): String;var src: TStringList;begin Result := inherited GetImplementationSource(Filename, SourceName, ResourceName); src := TStringList.Create; src.Add('function MyFunc(i: Integer): Boolean;'); src.Add('begin'); src.Add(' Result := i = 9;'); src.Add('end;'); src.Add(Result); Result := src.Text; src.Free;end; constructor TFileDescriptorCustomInclude.Create();begin inherited Create(); Name := 'CustomInclude'; DefaultFilename := 'CustomInclude.inc';end; function TFileDescriptorCustomInclude.GetLocalizedName(): String;begin Result := 'Custom include file';end; function TFileDescriptorCustomInclude.GetLocalizedDescription(): String;begin Result := 'Create a new custom include file.';end; end.
furious programming:
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 [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---constructor TFileDescriptorGameUnit.Create();begin inherited Create(); Name := 'GameUnit'; DefaultSourceName := 'Game_Unit'; DefaultFilename := 'Game_Unit.pp';end; constructor TFileDescriptorGameInclude.Create();begin inherited Create(); Name := 'GameInclude'; DefaultSourceName := 'Game_Include'; DefaultFilename := 'Game_Include.inc';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.
Navigation
[0] Message Index
[#] Next page