diff --git a/.gitattributes b/.gitattributes index f2cb2eb8fe..18245124e2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2824,6 +2824,9 @@ ide/unitdependencies.pas svneol=native#text/pascal ide/unitinfodlg.lfm svneol=native#text/plain ide/unitinfodlg.lrs svneol=native#text/plain ide/unitinfodlg.pp svneol=native#text/pascal +ide/unusedunitsdlg.lfm svneol=native#text/plain +ide/unusedunitsdlg.lrs svneol=native#text/plain +ide/unusedunitsdlg.pas svneol=native#text/plain ide/version.inc svneol=native#text/plain ide/versioninfoadditionalinfo.lfm svneol=native#text/plain ide/versioninfoadditionalinfo.lrs svneol=native#text/plain diff --git a/components/codetools/codetoolmanager.pas b/components/codetools/codetoolmanager.pas index eda7323971..a08b418768 100644 --- a/components/codetools/codetoolmanager.pas +++ b/components/codetools/codetoolmanager.pas @@ -488,6 +488,7 @@ type out AllRemoved: boolean; const Attr: TProcHeadAttributes; out RemovedProcHeads: TStrings): boolean; + function FindUnusedUnits(Code: TCodeBuffer; Units: TStrings): boolean; // custom class completion function InitClassCompletion(Code: TCodeBuffer; @@ -3480,6 +3481,21 @@ begin end; end; +function TCodeToolManager.FindUnusedUnits(Code: TCodeBuffer; Units: TStrings + ): boolean; +begin + {$IFDEF CTDEBUG} + DebugLn('TCodeToolManager.FindEmptyMethods A ',Code.Filename); + {$ENDIF} + Result:=false; + if not InitCurCodeTool(Code) then exit; + try + Result:=FCurCodeTool.FindUnusedUnits(Units); + except + on e: Exception do Result:=HandleException(e); + end; +end; + function TCodeToolManager.InitClassCompletion(Code: TCodeBuffer; const UpperClassName: string; out CodeTool: TCodeTool): boolean; begin diff --git a/components/codetools/finddeclarationtool.pas b/components/codetools/finddeclarationtool.pas index 1b0c94f61b..a7bf5e7105 100644 --- a/components/codetools/finddeclarationtool.pas +++ b/components/codetools/finddeclarationtool.pas @@ -1752,7 +1752,7 @@ begin UnitNamePos.EndPos-UnitNamePos.StartPos); if UnitInFilePos.StartPos>=1 then begin UnitInFilename:=copy(Src,UnitInFilePos.StartPos+1, - UnitInFilePos.EndPos-UnitInFilePos.StartPos-2) + UnitInFilePos.EndPos-UnitInFilePos.StartPos-2); end else UnitInFilename:=''; NewPos.Code:=FindUnitSource(UnitName,UnitInFilename,true); diff --git a/components/codetools/pascalreadertool.pas b/components/codetools/pascalreadertool.pas index e0761758b6..2ebf9af3b4 100644 --- a/components/codetools/pascalreadertool.pas +++ b/components/codetools/pascalreadertool.pas @@ -1787,7 +1787,7 @@ begin then RaiseException('[TPascalParserTool.MoveCursorToUsesStart] ' +'internal error: invalid UsesNode'); - // search backwards through the uses section + // search through the uses section MoveCursorToCleanPos(UsesNode.StartPos); ReadNextAtom; if (not UpAtomIs('USES')) and (not UpAtomIs('CONTAINS')) then diff --git a/components/codetools/stdcodetools.pas b/components/codetools/stdcodetools.pas index 4ff790f730..ecaa849e00 100644 --- a/components/codetools/stdcodetools.pas +++ b/components/codetools/stdcodetools.pas @@ -118,6 +118,7 @@ type SourceChangeCache: TSourceChangeCache): boolean; function CommentUnitsInUsesSections(MissingUnits: TStrings; SourceChangeCache: TSourceChangeCache): boolean; + function FindUnusedUnits(Units: TStrings): boolean; // lazarus resources function FindNextIncludeInInitialization( @@ -1327,6 +1328,198 @@ begin Result:=true; end; +function TStandardCodeTool.FindUnusedUnits(Units: TStrings): boolean; +// returns a list of unitname=flags +// flags are a comma separated list of words: +// 'implementation': unit is in implementation uses section +// 'used': an identifier of the interface is used +// 'code': unit has non empty initialization/finalization section +var + Identifiers: TAVLTree;// all identifiers used in this unit + + procedure RaiseUsesExpected; + begin + RaiseExceptionFmt(ctsStrExpectedButAtomFound,['"uses"',GetAtom]); + end; + + procedure RaiseStrConstExpected; + begin + RaiseExceptionFmt(ctsStrExpectedButAtomFound,[ctsStringConstant,GetAtom]); + end; + + function IsUnitAlreadyChecked(const AnUnitName: string): boolean; + var + i: Integer; + begin + for i:=0 to Units.Count-1 do + if SysUtils.CompareText(Units.Names[i],AnUnitName)=0 then exit(true); + Result:=false; + end; + + procedure GatherIdentifiers; + var + Node: TCodeTreeNode; + Identifier: PChar; + begin + if Identifiers<>nil then exit; + Identifiers:=TAVLTree.Create(@CompareIdentifierPtrs); + Node:=Tree.Root; + while Node<>nil do begin + if (Node.Desc in [ctnBeginBlock,ctnAsmBlock]) + or ((Node.FirstChild=nil) + and (Node.Desc in [ctnIdentifier,ctnRangedArrayType,ctnOpenArrayType, + ctnOfConstType,ctnRecordVariant,ctnProcedureType,ctnRangeType, + ctnTypeType,ctnFileType,ctnPointerType,ctnClassOfType, + ctnSpecializeParams,ctnGenericParameter,ctnConstant])) + then begin + MoveCursorToNodeStart(Node); + repeat + ReadNextAtom; + if CurPos.StartPos>=Node.EndPos then break; + if IsIdentStartChar[Src[CurPos.StartPos]] then begin + Identifier:=@Src[CurPos.StartPos]; + if Identifiers.Find(Identifier)=nil then begin + DebugLn(['GatherIdentifiers ',GetIdentifier(Identifier)]); + Identifiers.Add(Identifier); + end; + end; + until false; + Node:=Node.NextSkipChilds; + end else + Node:=Node.Next; + end; + end; + + function InterfaceIsUsed(Tool: TFindDeclarationTool; + IntfNode: TCodeTreeNode): boolean; + + function IsIdentifierUsed(StartPos: integer): boolean; + begin + Result:=Identifiers.Find(@Tool.Src[StartPos])<>nil; + end; + + var + Node: TCodeTreeNode; + begin + Result:=true; + Node:=IntfNode.FirstChild; + while Node<>nil do begin + case Node.Desc of + ctnEnumIdentifier: + if IsIdentifierUsed(Node.StartPos) then exit; + end; + Node:=Node.Next; + end; + Result:=false; + end; + + procedure CheckUnit(Tool: TFindDeclarationTool; + out HasCode, UseInterface: boolean); + var + Node: TCodeTreeNode; + Identifier: String; + begin + GatherIdentifiers; + HasCode:=false; + UseInterface:=false; + // parse used unit + Tool.BuildTree(false); + Node:=Tool.Tree.Root; + while (Node<>nil) do begin + case Node.Desc of + ctnUnit,ctnPackage,ctnLibrary: + begin + Identifier:=Tool.ExtractSourceName; + if Identifiers.Find(PChar(Identifier))<>nil then + UseInterface:=true; + end; + ctnInterface: + if not UseInterface then + UseInterface:=InterfaceIsUsed(Tool,Node); + ctnInitialization,ctnFinalization,ctnBeginBlock: + begin + HasCode:=true; + break; + end; + end; + Node:=Node.NextBrother; + end; + end; + + procedure CheckUsesSection(UsesNode: TCodeTreeNode; InImplementation: boolean); + var + UnitNamePos: TAtomPosition; + UnitInFilePos: TAtomPosition; + UnitName: String; + UnitInFilename: String; + Tool: TFindDeclarationTool; + HasCode: boolean; + UseInterface: boolean; + Flags: String; + begin + HasCode:=false; + UseInterface:=false; + if UsesNode=nil then exit; + MoveCursorToNodeStart(UsesNode); + ReadNextAtom; + if not UpAtomIs('USES') then + RaiseUsesExpected; + repeat + ReadNextAtom; // read name + if AtomIsChar(';') then break; + AtomIsIdentifier(true); + UnitNamePos:=CurPos; + ReadNextAtom; + if UpAtomIs('IN') then begin + ReadNextAtom; + if not AtomIsStringConstant then RaiseStrConstExpected; + UnitInFilePos:=CurPos; + ReadNextAtom; + end else + UnitInFilePos.StartPos:=-1; + UnitName:=copy(Src,UnitNamePos.StartPos, + UnitNamePos.EndPos-UnitNamePos.StartPos); + if not IsUnitAlreadyChecked(UnitName) then begin + if UnitInFilePos.StartPos>=1 then begin + UnitInFilename:=copy(Src,UnitInFilePos.StartPos+1, + UnitInFilePos.EndPos-UnitInFilePos.StartPos-2); + end else + UnitInFilename:=''; + // try to load the used unit + DebugLn(['CheckUsesSection ',UnitName,UnitInFilename]); + Tool:=FindCodeToolForUsedUnit(UnitName,UnitInFilename,true); + // parse the used unit + CheckUnit(Tool,HasCode,UseInterface); + Flags:=''; + if InImplementation then + Flags:=Flags+',implementation'; + if HasCode then + Flags:=Flags+',code'; + if UseInterface then + Flags:=Flags+',used'; + DebugLn(['CheckUsesSection ',UnitName,'=',Flags]); + Units.Add(UnitName+'='+Flags); + end; + if AtomIsChar(';') then break; + if not AtomIsChar(',') then + RaiseExceptionFmt(ctsStrExpectedButAtomFound,[';',GetAtom]) + until (CurPos.StartPos>SrcLen); + end; + +begin + Result:=false; + DebugLn(['TStandardCodeTool.FindUnusedUnits ']); + BuildTree(false); + Identifiers:=nil; + try + CheckUsesSection(FindMainUsesSection,false); + CheckUsesSection(FindImplementationUsesSection,true); + finally + Identifiers.Free; + end; + Result:=true; +end; + function TStandardCodeTool.FindNextIncludeInInitialization( var LinkIndex: integer): TCodeBuffer; // LinkIndex < 0 -> search first diff --git a/ide/emptymethodsdlg.pas b/ide/emptymethodsdlg.pas index 74a4c5a2e2..4c33f56375 100644 --- a/ide/emptymethodsdlg.pas +++ b/ide/emptymethodsdlg.pas @@ -94,6 +94,7 @@ var ListOfPCodeXYPosition: TFPList; AllEmpty: boolean; begin + Result:=mrCancel; ListOfPCodeXYPosition:=TFPList.Create; try // init codetools diff --git a/ide/keymapping.pp b/ide/keymapping.pp index 1ad246da15..f524379627 100644 --- a/ide/keymapping.pp +++ b/ide/keymapping.pp @@ -403,6 +403,7 @@ begin ecGotoIncludeDirective: SetResult(VK_UNKNOWN,[],VK_UNKNOWN,[]); ecShowAbstractMethods: SetResult(VK_UNKNOWN,[],VK_UNKNOWN,[]); ecRemoveEmptyMethods: SetResult(VK_UNKNOWN,[],VK_UNKNOWN,[]); + ecRemoveUnusedUnits: SetResult(VK_UNKNOWN,[],VK_UNKNOWN,[]); // source notebook ecNextEditor: SetResult(VK_TAB, [ssCtrl], VK_UNKNOWN, []); @@ -1760,6 +1761,7 @@ begin ecFindBlockStart : Result:= srkmecFindBlockStart; ecShowAbstractMethods : Result:= srkmecShowAbstractMethods; ecRemoveEmptyMethods : Result:= srkmecRemoveEmptyMethods; + ecRemoveUnusedUnits : Result:= srkmecRemoveEmptyMethods; // project (menu string resource) ecNewProject : Result:= lisMenuNewProject; @@ -2220,6 +2222,8 @@ begin ecShowAbstractMethods); AddDefault(C, 'Remove empty methods', srkmecRemoveEmptyMethods, ecRemoveEmptyMethods); + AddDefault(C, 'Remove unused units', srkmecRemoveUnusedUnits, + ecRemoveUnusedUnits); // source notebook - without menu items in the IDE bar C:=Categories[AddCategory('SourceNotebook',srkmCatSrcNoteBook, diff --git a/ide/lazarusidestrconsts.pas b/ide/lazarusidestrconsts.pas index 31a9320ad3..804dc5486f 100644 --- a/ide/lazarusidestrconsts.pas +++ b/ide/lazarusidestrconsts.pas @@ -1785,6 +1785,7 @@ resourcestring lisUEDoNotSho = 'Do not show this message again.'; uemInsertTodo = 'Insert Todo'; lisCodeHelpShowEmptyMethods = 'Show empty methods'; + lisCodeHelpShowUnusedUnits = 'Show unused units'; uemHighlighter = 'Highlighter'; uemEncoding = 'Encoding'; @@ -2076,6 +2077,7 @@ resourcestring srkmecFindBlockStart = 'Find block start'; srkmecShowAbstractMethods = 'Show abstract methods'; srkmecRemoveEmptyMethods = 'Remove empty methods'; + srkmecRemoveUnusedUnits = 'Remove unused units'; // run menu srkmecBuild = 'build program/project'; diff --git a/ide/main.pp b/ide/main.pp index 91225373b8..7960eb9c06 100644 --- a/ide/main.pp +++ b/ide/main.pp @@ -133,8 +133,9 @@ uses ProcessList, InitialSetupDlgs, NewDialog, MakeResStrDlg, ToDoList, DialogProcs, FindReplaceDialog, FindInFilesDlg, CodeExplorer, BuildFileDlg, ProcedureList, ExtractProcDlg, FindRenameIdentifier, AbstractsMethodsDlg, - EmptyMethodsDlg, CleanDirDlg, CodeContextForm, AboutFrm, BuildManager, + EmptyMethodsDlg, UnusedUnitsDlg, CleanDirDlg, CodeContextForm, AboutFrm, CompatibilityRestrictions, RestrictionBrowser, ProjectWizardDlg, IDECmdLine, + BuildManager, // main ide MainBar, MainIntf, MainBase; @@ -864,6 +865,7 @@ type NewFilename, NewUnitName: string): TModalResult; function DoShowAbstractMethods: TModalResult; function DoRemoveEmptyMethods: TModalResult; + function DoRemoveUnusedUnits: TModalResult; function DoInitIdentCompletion(JumpToError: boolean): boolean; function DoShowCodeContext(JumpToError: boolean): boolean; procedure DoCompleteCodeAtCursor; @@ -2771,6 +2773,9 @@ begin ecRemoveEmptyMethods: DoRemoveEmptyMethods; + ecRemoveUnusedUnits: + DoRemoveUnusedUnits; + ecFindBlockOtherEnd: DoGoToPascalBlockOtherEnd; @@ -12713,6 +12718,11 @@ begin Result:=ShowEmptyMethodsDialog; end; +function TMainIDE.DoRemoveUnusedUnits: TModalResult; +begin + Result:=ShowUnusedUnitsDialog; +end; + {------------------------------------------------------------------------------- function TMainIDE.DoInitIdentCompletion(JumpToError: boolean): boolean; -------------------------------------------------------------------------------} diff --git a/ide/sourceeditor.pp b/ide/sourceeditor.pp index a34683bb44..b1fd17d300 100644 --- a/ide/sourceeditor.pp +++ b/ide/sourceeditor.pp @@ -433,6 +433,7 @@ type procedure RenameIdentifierMenuItemClick(Sender: TObject); procedure ShowAbstractMethodsMenuItemClick(Sender: TObject); procedure ShowEmptyMethodsMenuItemClick(Sender: TObject); + procedure ShowUnusedUnitsMenuItemClick(Sender: TObject); procedure RunToClicked(Sender: TObject); procedure ViewCallStackClick(Sender: TObject); procedure AddWatchAtCursor(Sender: TObject); @@ -857,6 +858,7 @@ var SrcEditMenuInvertAssignment: TIDEMenuCommand; SrcEditMenuShowAbstractMethods: TIDEMenuCommand; SrcEditMenuShowEmptyMethods: TIDEMenuCommand; + SrcEditMenuShowUnusedUnits: TIDEMenuCommand; SrcEditMenuInsertTodo: TIDEMenuCommand; SrcEditMenuMoveEditorLeft: TIDEMenuCommand; SrcEditMenuMoveEditorRight: TIDEMenuCommand; @@ -1015,9 +1017,14 @@ begin 'ShowAbstractMethods',srkmecShowAbstractMethods); SrcEditMenuShowEmptyMethods:=RegisterIDEMenuCommand(AParent, 'ShowEmptyMethods', lisCodeHelpShowEmptyMethods); + SrcEditMenuShowUnusedUnits:=RegisterIDEMenuCommand(AParent, + 'ShowUnusedUnits', lisCodeHelpShowUnusedUnits); + {$IFNDEF EnableUnusedUnits} + SrcEditMenuShowUnusedUnits.Visible:=false; + {$ENDIF} SrcEditMenuInsertTodo:=RegisterIDEMenuCommand(SourceEditorMenuRoot, - 'InsertTodo',uemInsertTodo, nil, nil, nil, 'item_todo'); + 'InsertTodo',uemInsertTodo, nil, nil, nil, 'item_todo'); // register the Flags section SrcEditSubMenuFlags:=RegisterIDESubMenu(SourceEditorMenuRoot, @@ -4383,6 +4390,7 @@ begin SrcEditMenuRenameIdentifier.OnClick:=@RenameIdentifierMenuItemClick; SrcEditMenuShowAbstractMethods.OnClick:=@ShowAbstractMethodsMenuItemClick; SrcEditMenuShowEmptyMethods.OnClick:=@ShowEmptyMethodsMenuItemClick; + SrcEditMenuShowUnusedUnits.OnClick:=@ShowUnusedUnitsMenuItemClick; SrcEditMenuReadOnly.OnClick:=@ReadOnlyClicked; SrcEditMenuShowLineNumbers.OnClick:=@ToggleLineNumbersClicked; @@ -5627,6 +5635,11 @@ begin MainIDEInterface.DoCommand(ecRemoveEmptyMethods); end; +procedure TSourceNotebook.ShowUnusedUnitsMenuItemClick(Sender: TObject); +begin + MainIDEInterface.DoCommand(ecRemoveUnusedUnits); +end; + procedure TSourceNotebook.RunToClicked(Sender: TObject); var ASrcEdit: TSourceEditor; diff --git a/ide/unusedunitsdlg.lfm b/ide/unusedunitsdlg.lfm new file mode 100644 index 0000000000..1b4804b49f --- /dev/null +++ b/ide/unusedunitsdlg.lfm @@ -0,0 +1,28 @@ +object UnusedUnitsDialog: TUnusedUnitsDialog + Left = 375 + Height = 365 + Top = 236 + Width = 340 + Caption = 'UnusedUnitsDialog' + ClientHeight = 365 + ClientWidth = 340 + OnCreate = FormCreate + LCLVersion = '0.9.27' + object ButtonPanel1: TButtonPanel + Left = 6 + Height = 44 + Top = 315 + Width = 328 + TabOrder = 0 + ShowButtons = [pbOK, pbCancel] + end + object UnitsTreeView: TTreeView + Left = 0 + Height = 309 + Top = 0 + Width = 340 + Align = alClient + DefaultItemHeight = 19 + TabOrder = 1 + end +end diff --git a/ide/unusedunitsdlg.lrs b/ide/unusedunitsdlg.lrs new file mode 100644 index 0000000000..457591d375 --- /dev/null +++ b/ide/unusedunitsdlg.lrs @@ -0,0 +1,12 @@ +{ This is an automatically generated lazarus resource file } + +LazarusResources.Add('TUnusedUnitsDialog','FORMDATA',[ + 'TPF0'#18'TUnusedUnitsDialog'#17'UnusedUnitsDialog'#4'Left'#3'w'#1#6'Height'#3 + +'m'#1#3'Top'#3#236#0#5'Width'#3'T'#1#7'Caption'#6#17'UnusedUnitsDialog'#12'C' + +'lientHeight'#3'm'#1#11'ClientWidth'#3'T'#1#8'OnCreate'#7#10'FormCreate'#10 + +'LCLVersion'#6#6'0.9.27'#0#12'TButtonPanel'#12'ButtonPanel1'#4'Left'#2#6#6'H' + +'eight'#2','#3'Top'#3';'#1#5'Width'#3'H'#1#8'TabOrder'#2#0#11'ShowButtons'#11 + +#4'pbOK'#8'pbCancel'#0#0#0#9'TTreeView'#13'UnitsTreeView'#4'Left'#2#0#6'Heig' + +'ht'#3'5'#1#3'Top'#2#0#5'Width'#3'T'#1#5'Align'#7#8'alClient'#17'DefaultItem' + +'Height'#2#19#8'TabOrder'#2#1#0#0#0 +]); diff --git a/ide/unusedunitsdlg.pas b/ide/unusedunitsdlg.pas new file mode 100644 index 0000000000..7d59e65d1a --- /dev/null +++ b/ide/unusedunitsdlg.pas @@ -0,0 +1,112 @@ +{ + *************************************************************************** + * * + * This source is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This code is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * General Public License for more details. * + * * + * A copy of the GNU General Public License is available on the World * + * Wide Web at . You can also * + * obtain it by writing to the Free Software Foundation, * + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + * * + *************************************************************************** + + Author: Mattias Gaertner + + Abstract: + A dialog showing the unused units of the current unit + (at cursor in source editor). + With the ability to remove them automatically. +} +unit UnusedUnitsDlg; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, LCLProc,FileUtil, LResources, Forms, Controls, Graphics, + Dialogs, ButtonPanel, ComCtrls, + SrcEditorIntf, LazIDEIntf, + CodeCache, CodeToolManager, + LazarusIDEStrConsts; + +type + + { TUnusedUnitsDialog } + + TUnusedUnitsDialog = class(TForm) + ButtonPanel1: TButtonPanel; + UnitsTreeView: TTreeView; + procedure FormCreate(Sender: TObject); + procedure OkClick(Sender: TObject); + private + { private declarations } + public + { public declarations } + end; + +var + UnusedUnitsDialog: TUnusedUnitsDialog; + +function ShowUnusedUnitsDialog: TModalResult; + +implementation + +function ShowUnusedUnitsDialog: TModalResult; +var + SrcEdit: TSourceEditorInterface; + Code: TCodeBuffer; + Units: TStringList; +begin + Result:=mrOk; + if not LazarusIDE.BeginCodeTools then exit; + + // get cursor position + SrcEdit:=SourceEditorWindow.ActiveEditor; + if SrcEdit=nil then exit; + Code:=TCodeBuffer(SrcEdit.CodeToolsBuffer); + if Code=nil then exit; + + Units:=TStringList.Create; + try + if not CodeToolBoss.FindUnusedUnits(Code,Units) then begin + DebugLn(['ShowUnusedUnitsDialog CodeToolBoss.FindUnusedUnits failed']); + LazarusIDE.DoJumpToCodeToolBossError; + exit(mrCancel); + end; + + + finally + Units.Free; + end; +end; + +{ TUnusedUnitsDialog } + +procedure TUnusedUnitsDialog.FormCreate(Sender: TObject); +begin + Caption:='Unused units'; + + ButtonPanel1.OKButton.Caption:='Remove selected units'; + ButtonPanel1.OKButton.OnClick:=@OkClick; + ButtonPanel1.CancelButton.Caption:='Cancel'; +end; + +procedure TUnusedUnitsDialog.OkClick(Sender: TObject); +begin + +end; + +initialization + {$I unusedunitsdlg.lrs} + +end. + diff --git a/ideintf/idecommands.pas b/ideintf/idecommands.pas index 10dcbf69ac..4e4b528c41 100644 --- a/ideintf/idecommands.pas +++ b/ideintf/idecommands.pas @@ -139,6 +139,7 @@ const ecShowCodeContext = ecFirstLazarus + 118; ecShowAbstractMethods = ecFirstLazarus + 119; ecRemoveEmptyMethods = ecFirstLazarus + 120; + ecRemoveUnusedUnits = ecFirstLazarus + 121; // file menu ecNew = ecFirstLazarus + 201;