{
 ***************************************************************************
 *                                                                         *
 *   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 <http://www.gnu.org/copyleft/gpl.html>. 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 for adding and editing code templates

}
unit CodeTemplatesDlg;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, LCLProc, LResources, Forms, Controls, Graphics, Dialogs,
  ClipBrd, StdCtrls, Buttons, ExtCtrls, Menus, FileUtil,
  SynEdit, SynHighlighterPas, SynEditAutoComplete, CodeToolManager, CodeCache,
  PascalParserTool,
  IDECommands, TextTools, SrcEditorIntf, MenuIntf, IDEWindowIntf, LazIDEIntf,
  IDEProcs, InputHistory, LazarusIDEStrConsts, EditorOptions, CodeMacroSelect;

type

  { TCodeTemplateDialog }

  TCodeTemplateDialog = class(TForm)
    AddButton: TButton;
    OkButton: TBitBtn;
    CancelButton: TBitBtn;
    InsertMacroButton: TButton;
    UseMacrosCheckBox: TCheckBox;
    EditButton: TButton;
    DeleteButton: TButton;
    TemplateListBox: TListBox;
    TemplateSynEdit: TSynEdit;
    ASynPasSyn: TSynPasSyn;
    TemplateGroupBox: TGroupBox;
    FilenameButton: TButton;
    FilenameEdit: TEdit;
    FilenameGroupBox: TGroupBox;
    MainPopupMenu: TPopupMenu;
    procedure AddButtonClick(Sender: TObject);
    procedure DeleteButtonClick(Sender: TObject);
    procedure EditButtonClick(Sender: TObject);
    procedure FilenameButtonClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure InsertMacroButtonClick(Sender: TObject);
    procedure OkButtonClick(Sender: TObject);
    procedure OnCopyMenuItem(Sender: TObject);
    procedure OnCutMenuItem(Sender: TObject);
    procedure OnInsertMacroMenuItem(Sender: TObject);
    procedure OnPasteMenuItem(Sender: TObject);
    procedure TemplateListBoxSelectionChange(Sender: TObject; User: boolean);
  private
    SynAutoComplete: TSynEditAutoComplete;
    LastTemplate: integer;
    procedure BuildPopupMenu;
    procedure DoInsertMacro;
  public
    procedure FillCodeTemplateListBox;
    procedure ShowCurCodeTemplate;
    procedure SaveCurCodeTemplate;
  end;

  { TCodeTemplateEditForm }

  TCodeTemplateEditForm = class(TForm)
    TokenLabel: TLabel;
    TokenEdit: TEdit;
    CommentLabel: TLabel;
    CommentEdit: TEdit;
    OkButton: TButton;
    CancelButton: TButton;
    procedure CodeTemplateEditFormResize(Sender: TObject);
    procedure OkButtonClick(Sender: TObject);
  public
    constructor Create(TheOwner: TComponent); override;
    SynAutoComplete: TSynEditAutoComplete;
    TemplateIndex: integer;
  end;
  
  { TLazCodeMacros }

  TLazCodeMacros = class(TIDECodeMacros)
  private
    FItems: TFPList; // list of TIDECodeMacro
  protected
    function GetItems(Index: integer): TIDECodeMacro; override;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Clear;
    property Items[Index: integer]: TIDECodeMacro read GetItems; default;
    function Count: integer; override;
    function Add(Macro: TIDECodeMacro): integer; override;
    function FindByName(const AName: string): TIDECodeMacro; override;
    function CreateUniqueName(const AName: string): string; override;
  end;

function ShowCodeTemplateDialog: TModalResult;

function AddCodeTemplate(ASynAutoComplete: TSynEditAutoComplete;
  var Token, Comment: string): TModalResult;
function EditCodeTemplate(ASynAutoComplete: TSynEditAutoComplete;
  Index: integer): TModalResult;
  
procedure CreateStandardCodeMacros;

// standard code macros
function CodeMacroUpper(const Parameter: string; InteractiveValue: TPersistent;
                        SrcEdit: TSourceEditorInterface;
                        var Value, ErrorMsg: string): boolean;
function CodeMacroLower(const Parameter: string; InteractiveValue: TPersistent;
                        SrcEdit: TSourceEditorInterface;
                        var Value, ErrorMsg: string): boolean;
function CodeMacroPaste(const Parameter: string; InteractiveValue: TPersistent;
                        SrcEdit: TSourceEditorInterface;
                        var Value, ErrorMsg: string): boolean;
function CodeMacroProcedureHead(const Parameter: string;
                        InteractiveValue: TPersistent;
                        SrcEdit: TSourceEditorInterface;
                        var Value, ErrorMsg: string): boolean;
function CodeMacroProcedureName(const Parameter: string;
                        InteractiveValue: TPersistent;
                        SrcEdit: TSourceEditorInterface;
                        var Value, ErrorMsg: string): boolean;
function CodeMacroDate(const Parameter: string; InteractiveValue: TPersistent;
                        SrcEdit: TSourceEditorInterface;
                        var Value, ErrorMsg: string): boolean;
function CodeMacroTime(const Parameter: string; InteractiveValue: TPersistent;
                        SrcEdit: TSourceEditorInterface;
                        var Value, ErrorMsg: string): boolean;
function CodeMacroDateTime(const Parameter: string;
                        InteractiveValue: TPersistent;
                        SrcEdit: TSourceEditorInterface;
                        var Value, ErrorMsg: string): boolean;

const
  CodeTemplatesMenuRootName = 'CodeTemplates';

var
  CodeTemplateCopyIDEMenuCommand: TIDEMenuCommand;
  CodeTemplateCutIDEMenuCommand: TIDEMenuCommand;
  CodeTemplatePasteIDEMenuCommand: TIDEMenuCommand;
  CodeTemplateInsertMacroIDEMenuCommand: TIDEMenuCommand;

procedure RegisterStandardCodeTemplatesMenuItems;

implementation

function ShowCodeTemplateDialog: TModalResult;
var
  CodeTemplateDialog: TCodeTemplateDialog;
begin
  CodeTemplateDialog:=TCodeTemplateDialog.Create(nil);
  Result:=CodeTemplateDialog.ShowModal;
  CodeTemplateDialog.Free;
end;

function AddCodeTemplate(ASynAutoComplete:TSynEditAutoComplete;
  var Token,Comment:ansistring):TModalResult;
var
  CodeTemplateEditForm:TCodeTemplateEditForm;
begin
  Result:=mrCancel;
  CodeTemplateEditForm:=TCodeTemplateEditForm.Create(nil);
  try
    CodeTemplateEditForm.SynAutoComplete:=ASynAutoComplete;
    CodeTemplateEditForm.TemplateIndex:=ASynAutoComplete.Completions.Count;
    CodeTemplateEditForm.Caption:=lisCodeTemplAddCodeTemplate;
    CodeTemplateEditForm.OkButton.Caption:=lisCodeTemplAdd;
    CodeTemplateEditForm.TokenEdit.Text:=Token;
    CodeTemplateEditForm.CommentEdit.Text:=Comment;
    Result:=CodeTemplateEditForm.ShowModal;
    if Result=mrOk then begin
      Token:=CodeTemplateEditForm.TokenEdit.Text;
      Comment:=CodeTemplateEditForm.CommentEdit.Text;
    end;
  finally
    CodeTemplateEditForm.Free;
  end;
end;

function EditCodeTemplate(ASynAutoComplete:TSynEditAutoComplete;
  Index:integer):TModalResult;
var
  CodeTemplateEditForm:TCodeTemplateEditForm;
begin
  Result:=mrCancel;
  if (Index<0) or (Index>=ASynAutoComplete.Completions.Count) then exit;
  CodeTemplateEditForm:=TCodeTemplateEditForm.Create(nil);
  try
    CodeTemplateEditForm.SynAutoComplete:=ASynAutoComplete;
    CodeTemplateEditForm.TemplateIndex:=Index;
    CodeTemplateEditForm.Caption:=lisCodeTemplEditCodeTemplate;
    CodeTemplateEditForm.OkButton.Caption:=lisCodeTemplChange;
    CodeTemplateEditForm.TokenEdit.Text:=ASynAutoComplete.Completions[Index];
    CodeTemplateEditForm.CommentEdit.Text:=
      ASynAutoComplete.CompletionComments[Index];
    Result:=CodeTemplateEditForm.ShowModal;
    if Result=mrOk then begin
      ASynAutoComplete.Completions[Index]:=
        CodeTemplateEditForm.TokenEdit.Text;
      ASynAutoComplete.CompletionComments[Index]:=
        CodeTemplateEditForm.CommentEdit.Text;
    end;
  finally
    CodeTemplateEditForm.Free;
  end;
end;

function CodeMacroUpper(const Parameter: string; InteractiveValue: TPersistent;
                        SrcEdit: TSourceEditorInterface;
                        var Value, ErrorMsg: string): boolean;
begin
  Value:=UpperCase(Parameter);
  Result:=true;
end;
                        
function CodeMacroLower(const Parameter: string; InteractiveValue: TPersistent;
                        SrcEdit: TSourceEditorInterface;
                        var Value, ErrorMsg: string): boolean;
begin
  Value:=LowerCase(Parameter);
  Result:=true;
end;

function CodeMacroPaste(const Parameter: string; InteractiveValue: TPersistent;
                        SrcEdit: TSourceEditorInterface;
                        var Value, ErrorMsg: string): boolean;
begin
  Value:=Clipboard.AsText;
  Result:=true;
end;

function CodeMacroProcedureHead(const Parameter: string;
  InteractiveValue: TPersistent; SrcEdit: TSourceEditorInterface; var Value,
  ErrorMsg: string): boolean;
var
  Params: TStrings;
  Param: string;
  i: Integer;
  Attributes: TProcHeadAttributes;
begin
  //debugln('CodeMacroProcedureHead A ',Parameter);

  // parse attributes
  Params:=SplitString(Parameter,',');
  if Params<>nil then begin
    try
      Attributes:=[];
      for i:=0 to Params.Count-1 do begin
        Param:=Params[i];
        if CompareText(Param,'WithStart')=0 then
          Include(Attributes,phpWithStart)
        else if CompareText(Param,'WithStart')=0 then
          Include(Attributes,phpWithStart)
        else if CompareText(Param,'WithoutClassKeyword')=0 then
          Include(Attributes,phpWithoutClassKeyword)
        else if CompareText(Param,'AddClassName')=0 then
          Include(Attributes,phpAddClassName)
        else if CompareText(Param,'WithoutClassName')=0 then
          Include(Attributes,phpWithoutClassName)
        else if CompareText(Param,'WithoutName')=0 then
          Include(Attributes,phpWithoutName)
        else if CompareText(Param,'WithoutParamList')=0 then
          Include(Attributes,phpWithoutParamList)
        else if CompareText(Param,'WithVarModifiers')=0 then
          Include(Attributes,phpWithVarModifiers)
        else if CompareText(Param,'WithParameterNames')=0 then
          Include(Attributes,phpWithParameterNames)
        else if CompareText(Param,'WithoutParamTypes')=0 then
          Include(Attributes,phpWithoutParamTypes)
        else if CompareText(Param,'WithDefaultValues')=0 then
          Include(Attributes,phpWithDefaultValues)
        else if CompareText(Param,'WithResultType')=0 then
          Include(Attributes,phpWithResultType)
        else if CompareText(Param,'WithOfObject')=0 then
          Include(Attributes,phpWithOfObject)
        else if CompareText(Param,'WithCallingSpecs')=0 then
          Include(Attributes,phpWithCallingSpecs)
        else if CompareText(Param,'WithProcModifiers')=0 then
          Include(Attributes,phpWithProcModifiers)
        else if CompareText(Param,'WithComments')=0 then
          Include(Attributes,phpWithComments)
        else if CompareText(Param,'InUpperCase')=0 then
          Include(Attributes,phpInUpperCase)
        else if CompareText(Param,'CommentsToSpace')=0 then
          Include(Attributes,phpCommentsToSpace)
        else if CompareText(Param,'WithoutBrackets')=0 then
          Include(Attributes,phpWithoutBrackets)
        else if CompareText(Param,'WithoutSemicolon')=0 then
          Include(Attributes,phpWithoutSemicolon)
        else begin
          Result:=false;
          ErrorMsg:='Unknown Option: "'+Param+'"';
          exit;
        end;
      end;

    finally
      Params.Free;
    end;
  end;

  //debugln('CodeMacroProcedureHead B ');
  if not CodeToolBoss.ExtractProcedureHeader(
    SrcEdit.CodeToolsBuffer as TCodeBuffer,
    SrcEdit.CursorTextXY.X,SrcEdit.CursorTextXY.Y,Attributes,Value) then
  begin
    Result:=false;
    ErrorMsg:=CodeToolBoss.ErrorMessage;
    LazarusIDE.DoJumpToCodeToolBossError;
    exit;
  end;
  //debugln('CodeMacroProcedureHead C Value="',Value,'"');

  Result:=true;
end;

function CodeMacroProcedureName(const Parameter: string;
  InteractiveValue: TPersistent; SrcEdit: TSourceEditorInterface; var Value,
  ErrorMsg: string): boolean;
begin
  Result:=CodeMacroProcedureHead(
                          'WithoutParamList,WithoutBrackets,WithoutSemicolon',
                          InteractiveValue,SrcEdit,Value,ErrorMsg);
  debugln('CodeMacroProcedureName ',Value);
end;

function CodeMacroDate(const Parameter: string; InteractiveValue: TPersistent;
  SrcEdit: TSourceEditorInterface; var Value, ErrorMsg: string): boolean;
begin
  if Parameter<>'' then
    Value:=FormatDateTime(Parameter,Now)
  else
    Value:=DateToStr(Now);
  Result:=true;
end;

function CodeMacroTime(const Parameter: string; InteractiveValue: TPersistent;
  SrcEdit: TSourceEditorInterface; var Value, ErrorMsg: string): boolean;
begin
  if Parameter<>'' then
    Value:=FormatDateTime(Parameter,Now)
  else
    Value:=TimeToStr(Now);
  Result:=true;
end;

function CodeMacroDateTime(const Parameter: string;
  InteractiveValue: TPersistent; SrcEdit: TSourceEditorInterface; var Value,
  ErrorMsg: string): boolean;
begin
  if Parameter<>'' then
    Value:=FormatDateTime(Parameter,Now)
  else
    Value:=DateTimeToStr(Now);
  Result:=true;
end;

procedure RegisterStandardCodeTemplatesMenuItems;
var
  Path: string;
begin
  CodeTemplatesMenuRoot := RegisterIDEMenuRoot(CodeTemplatesMenuRootName);
  Path := CodeTemplatesMenuRoot.Name;
  CodeTemplateCutIDEMenuCommand := RegisterIDEMenuCommand(Path,'Cut','Cut');
  CodeTemplateCopyIDEMenuCommand := RegisterIDEMenuCommand(Path,'Copy','Copy');
  CodeTemplatePasteIDEMenuCommand := RegisterIDEMenuCommand(Path,'Paste','Paste');
  CodeTemplateInsertMacroIDEMenuCommand := RegisterIDEMenuCommand(Path,
                                                  'InsertMacro','Insert Macro');
end;

procedure CreateStandardCodeMacros;
begin
  IDECodeMacros:=TLazCodeMacros.Create;
  RegisterCodeMacro('Upper','uppercase string',
                    'Uppercase string given as parameter',
                    @CodeMacroUpper,nil);
  RegisterCodeMacro('Lower','lowercase string',
                    'Lowercase string given as parameter',
                    @CodeMacroLower,nil);
  RegisterCodeMacro('Paste','paste clipboard',
                    'Paste text from clipboard',
                    @CodeMacroPaste,nil);
  RegisterCodeMacro('ProcedureHead','insert procedure head',
     'Insert header of current procedure'#13
    +#13
    +'Optional Parameters (comma separated):'#13
    +'WithStart,          // proc keyword e.g. ''function'', ''class procedure'''#13
    +'WithoutClassKeyword,// without ''class'' proc keyword'#13
    +'AddClassName,       // extract/add ClassName.'#13
    +'WithoutClassName,   // skip classname'#13
    +'WithoutName,        // skip function name'#13
    +'WithoutParamList,   // skip param list'#13
    +'WithVarModifiers,   // extract ''var'', ''out'', ''const'''#13
    +'WithParameterNames, // extract parameter names'#13
    +'WithoutParamTypes,  // skip colon, param types and default values'#13
    +'WithDefaultValues,  // extract default values'#13
    +'WithResultType,     // extract colon + result type'#13
    +'WithOfObject,       // extract ''of object'''#13
    +'WithCallingSpecs,   // extract cdecl; inline;'#13
    +'WithProcModifiers,  // extract forward; alias; external;'#13
    +'WithComments,       // extract comments and spaces'#13
    +'InUpperCase,        // turn to uppercase'#13
    +'CommentsToSpace,    // replace comments with a single space'#13
    +'                      //  (default is to skip unnecessary space,'#13
    +'                      //    e.g ''Do   ;'' normally becomes ''Do;'''#13
    +'                      //    with this option you get ''Do ;'')'#13
    +'WithoutBrackets,    // skip start- and end-bracket of parameter list'#13
    +'WithoutSemicolon,   // skip semicolon at end'#13,
    @CodeMacroProcedureHead,nil);
  RegisterCodeMacro('ProcedureName','insert procedure name',
                    'Insert name of current procedure',
                    @CodeMacroProcedureName,nil);
  RegisterCodeMacro('Date','insert date',
                    'Insert date. Optional: format string',
                    @CodeMacroDate,nil);
  RegisterCodeMacro('Time','insert time',
                    'Insert time. Optional: format string',
                    @CodeMacroTime,nil);
  RegisterCodeMacro('DateTime','insert date and time',
                    'Insert date and time. Optional: format string',
                    @CodeMacroDateTime,nil);
end;

{ TCodeTemplateEditForm }

constructor TCodeTemplateEditForm.Create(TheOwner:TComponent);
begin
  inherited Create(TheOwner);
  if LazarusResources.Find(ClassName)=nil then begin
    Width:=300;
    Height:=150;
    Position:=poScreenCenter;
    OnResize:=@CodeTemplateEditFormResize;

    TokenLabel:=TLabel.Create(Self);
    with TokenLabel do begin
      Name:='TokenLabel';
      Parent:=Self;
      Caption:=lisCodeTemplToken;
      Left:=12;
      Top:=6;
      Width:=Self.ClientWidth-Left-Left;
      Show;
    end;

    TokenEdit:=TEdit.Create(Self);
    with TokenEdit do begin
      Name:='TokenEdit';
      Parent:=Self;
      Left:=10;
      Top:=TokenLabel.Top+TokenLabel.Height+2;
      Width:=Self.ClientWidth-Left-Left-4;
      Text:='';
      Show;
    end;

    CommentLabel:=TLabel.Create(Self);
    with CommentLabel do begin
      Name:='CommentLabel';
      Parent:=Self;
      Caption:=lisCodeTemplComment;
      Left:=12;
      Top:=TokenEdit.Top+TokenEdit.Height+10;
      Width:=Self.ClientWidth-Left-Left;
      Show;
    end;

    CommentEdit:=TEdit.Create(Self);
    with CommentEdit do begin
      Name:='CommentEdit';
      Parent:=Self;
      Left:=10;
      Top:=CommentLabel.Top+CommentLabel.Height+2;
      Width:=Self.ClientWidth-Left-Left-4;
      Text:='';
      Show;
    end;

    OkButton:=TButton.Create(Self);
    with OkButton do begin
      Name:='OkButton';
      Parent:=Self;
      Caption:=lisLazBuildOk;
      OnClick:=@OkButtonClick;
      Left:=50;
      Top:=Self.ClientHeight-Height-12;
      Width:=80;
      Show;
    end;

    CancelButton:=TButton.Create(Self);
    with CancelButton do begin
      Name:='CancelButton';
      Parent:=Self;
      Caption:=dlgCancel;
      ModalResult:=mrCancel;
      Width:=80;
      Left:=Self.ClientWidth-50-Width;
      Top:=Self.ClientHeight-Height-12;
      Show;
    end;
  end;
  CodeTemplateEditFormResize(nil);
end;

procedure TCodeTemplateEditForm.CodeTemplateEditFormResize(Sender: TObject);
begin
  with TokenLabel do begin
    Left:=12;
    Top:=6;
    Width:=Self.ClientWidth-Left-Left;
  end;

  with TokenEdit do begin
    Left:=10;
    Top:=TokenLabel.Top+TokenLabel.Height+2;
    Width:=Self.ClientWidth-Left-Left-4;
  end;

  with CommentLabel do begin
    Left:=12;
    Top:=TokenEdit.Top+TokenEdit.Height+10;
    Width:=Self.ClientWidth-Left-Left;
  end;

  with CommentEdit do begin
    Left:=10;
    Top:=CommentLabel.Top+CommentLabel.Height+2;
    Width:=Self.ClientWidth-Left-Left-4;
  end;

  with OkButton do begin
    Left:=50;
    Top:=Self.ClientHeight-Height-12;
    Width:=80;
  end;

  with CancelButton do begin
    Width:=80;
    Left:=Self.ClientWidth-50-Width;
    Top:=Self.ClientHeight-Height-12;
  end;
end;

procedure TCodeTemplateEditForm.OkButtonClick(Sender:TObject);
var a:integer;
  AText,ACaption:AnsiString;
begin
  a:=SynAutoComplete.Completions.IndexOf(TokenEdit.Text);
  if (a<0) or (a=TemplateIndex) then
    ModalResult:=mrOk
  else begin
    AText:=Format(lisCodeTemplATokenAlreadyExists, ['"', TokenEdit.Text, '"']);
    ACaption:=lisCodeTemplError;

//    Application.MessageBox(PChar(AText),PChar(ACaption),0);
    MessageDlg(ACaption,AText,mterror,[mbok],0);

  end;
end;

{ TCodeTemplateDialog }

procedure TCodeTemplateDialog.FormCreate(Sender: TObject);
var
  s: String;
  ColorScheme: String;
begin
  IDEDialogLayoutList.ApplyLayout(Self,600,450);

  SynAutoComplete:=TSynEditAutoComplete.Create(Self);
  LastTemplate:=-1;

  // init captions
  Caption:=lisMenuEditCodeTemplates;
  AddButton.Caption:=lisCodeTemplAdd;
  EditButton.Caption:=lisCodeToolsDefsEdit;
  DeleteButton.Caption:=dlgEdDelete;
  CancelButton.Caption:=dlgCancel;
  TemplateGroupBox.Caption:=lisCTDTemplates;
  OkButton.Caption:=lisLazBuildOk;
  FilenameGroupBox.Caption:=lisToDoLFile;
  UseMacrosCheckBox.Caption:=lisEnableMacros;
  InsertMacroButton.Caption:=lisCTInsertMacro;

  FilenameEdit.Text:=EditorOpts.CodeTemplateFileName;

  // init synedit
  ColorScheme:=EditorOpts.ReadColorScheme(ASynPasSyn.GetLanguageName);
  EditorOpts.AddSpecialHilightAttribsToHighlighter(ASynPasSyn);
  EditorOpts.ReadHighlighterSettings(ASynPasSyn,ColorScheme);
  if EditorOpts.UseSyntaxHighlight then
    TemplateSynEdit.Highlighter:=ASynPasSyn
  else
    TemplateSynEdit.Highlighter:=nil;
  EditorOpts.GetSynEditSettings(TemplateSynEdit);
  EditorOpts.KeyMap.AssignTo(TemplateSynEdit.KeyStrokes,
                             TSourceEditorWindowInterface);
  TemplateSynEdit.Gutter.Visible:=false;

  // init SynAutoComplete
  with SynAutoComplete do begin
    s:=EditorOpts.CodeTemplateFileName;
    if FileExists(s) then
      try
        AutoCompleteList.LoadFromFile(s);
      except
        DebugLn('NOTE: unable to read code template file ''',s,'''');
      end;
  end;
  
  // init listbox
  FillCodeTemplateListBox;
  with TemplateListBox do
    if Items.Count>0 then begin
      ItemIndex:=0;
      ShowCurCodeTemplate;
    end;
    
  BuildPopupMenu;
end;

procedure TCodeTemplateDialog.InsertMacroButtonClick(Sender: TObject);
begin
  DoInsertMacro;
end;

procedure TCodeTemplateDialog.OkButtonClick(Sender: TObject);
var
  Res: TModalResult;
begin
  SaveCurCodeTemplate;

  EditorOpts.CodeTemplateFileName:=FilenameEdit.Text;
  //EditorOpts.CodeTemplateIndentToTokenStart:=
  //  (CodeTemplateIndentTypeRadioGroup.ItemIndex=0);

  EditorOpts.Save;

  if BuildBorlandDCIFile(SynAutoComplete) then begin
    Res:=mrOk;
    repeat
      try
        SynAutoComplete.AutoCompleteList.SaveToFile(
          EditorOpts.CodeTemplateFileName);
      except
        res:=MessageDlg(' Unable to write code templates to file '''
          +EditorOpts.CodeTemplateFileName+'''! ',mtError
          ,[mbAbort, mbIgnore, mbRetry],0);
        if res=mrAbort then exit;
      end;
    until Res<>mrRetry;
  end;

  ModalResult:=mrOk;
end;

procedure TCodeTemplateDialog.OnCopyMenuItem(Sender: TObject);
begin
  TemplateSynEdit.CopyToClipboard;
end;

procedure TCodeTemplateDialog.OnCutMenuItem(Sender: TObject);
begin
  TemplateSynEdit.CutToClipboard;
end;

procedure TCodeTemplateDialog.OnInsertMacroMenuItem(Sender: TObject);
begin
  DoInsertMacro;
end;

procedure TCodeTemplateDialog.OnPasteMenuItem(Sender: TObject);
begin
  TemplateSynEdit.PasteFromClipboard;
end;

procedure TCodeTemplateDialog.AddButtonClick(Sender: TObject);
var
  Token: String;
  Comment: String;
  Index: PtrInt;
begin
  SaveCurCodeTemplate;
  Token:='new';
  Comment:='(custom)';
  if AddCodeTemplate(SynAutoComplete,Token,Comment)=mrOk then begin
    SynAutoComplete.AddCompletion(Token, '', Comment);
    FillCodeTemplateListBox;
    Index := SynAutoComplete.Completions.IndexOf(Token);
    if Index >= 0
    then Index := TemplateListBox.Items.IndexOfObject(TObject(Pointer(Index)));
    if Index >= 0
    then TemplateListBox.ItemIndex:=Index;
    
    ShowCurCodeTemplate;
  end;
end;

procedure TCodeTemplateDialog.DeleteButtonClick(Sender: TObject);
var
  a, idx: LongInt;
begin
  idx := TemplateListBox.ItemIndex;
  if idx < 0 then exit;
  a := PtrInt(TemplateListBox.Items.Objects[idx]);
  if a < 0 then exit;

  if MessageDlg(dlgDelTemplate
      +'"'+SynAutoComplete.Completions[a]+' - '
      +SynAutoComplete.CompletionComments[a]+'"'
      +'?',mtConfirmation,[mbOk,mbCancel],0)=mrOK
  then begin
    SynAutoComplete.DeleteCompletion(a);
    LastTemplate := -1; // to prevent the saving of the deleted template
    FillCodeTemplateListBox;
    if idx < TemplateListBox.Items.Count then begin
      TemplateListBox.ItemIndex := idx;
    end;
    ShowCurCodeTemplate;
  end;
end;

procedure TCodeTemplateDialog.EditButtonClick(Sender: TObject);
var
  a, idx: LongInt;
begin
  idx := TemplateListBox.ItemIndex;
  if idx < 0 then exit;
  a := PtrInt(TemplateListBox.Items.Objects[idx]);
  if a < 0 then exit;

  if EditCodeTemplate(SynAutoComplete, a)=mrOk then begin
    TemplateListBox.Items[idx]:=
       SynAutoComplete.Completions[a]
       +' - "'+SynAutoComplete.CompletionComments[a]+'"';
    ShowCurCodeTemplate;
  end;
end;

procedure TCodeTemplateDialog.FilenameButtonClick(Sender: TObject);
var OpenDialog:TOpenDialog;
begin
  OpenDialog:=TOpenDialog.Create(nil);
  try
    InputHistories.ApplyFileDialogSettings(OpenDialog);
    with OpenDialog do begin
      Title:=dlgChsCodeTempl;
      Filter:='DCI file (*.dci)|*.dci|' + dlgAllFiles  + '|' + GetAllFilesMask;
      if Execute then
        FilenameEdit.Text:=FileName;
    end;
    InputHistories.StoreFileDialogSettings(OpenDialog);
  finally
    OpenDialog.Free;
  end;
end;

procedure TCodeTemplateDialog.FormClose(Sender: TObject;
  var CloseAction: TCloseAction);
begin
  IDEDialogLayoutList.SaveLayout(Self);
end;

procedure TCodeTemplateDialog.TemplateListBoxSelectionChange(Sender: TObject;
  User: boolean);
begin
  SaveCurCodeTemplate;
  ShowCurCodeTemplate;
end;

procedure TCodeTemplateDialog.BuildPopupMenu;
begin
  CodeTemplateCopyIDEMenuCommand.OnClick:=@OnCopyMenuItem;
  CodeTemplateCutIDEMenuCommand.OnClick:=@OnCutMenuItem;
  CodeTemplatePasteIDEMenuCommand.OnClick:=@OnPasteMenuItem;
  CodeTemplateInsertMacroIDEMenuCommand.OnClick:=@OnInsertMacroMenuItem;

  // assign the root TMenuItem to the registered menu root.
  MainPopupMenu:=TPopupMenu.Create(Self);
  // This will automatically create all registered items
  CodeTemplatesMenuRoot.MenuItem := MainPopupMenu.Items;
  //MainPopupMenu.Items.WriteDebugReport('TMessagesView.Create ');
  
  PopupMenu:=MainPopupMenu;
end;

procedure TCodeTemplateDialog.DoInsertMacro;
var
  Macro: TIDECodeMacro;
  Parameter: string;
begin
  Macro:=ShowCodeMacroSelectDialog(Parameter);
  if Macro<>nil then begin
    TemplateSynEdit.SelText:='$'+Macro.Name+'('+Parameter+')';
  end;
end;

procedure TCodeTemplateDialog.FillCodeTemplateListBox;
var
  a: PtrInt;
  sl: TStringList;
begin
  sl:=TStringList.Create;
  try
    for a:=0 to SynAutoComplete.Completions.Count-1 do begin
      // Add the index in SynAutoComplete as Object, since both indexes won't
      // be in sync after sorting
      sl.AddObject(SynAutoComplete.Completions[a]
          +' - "'+SynAutoComplete.CompletionComments[a]+'"', TObject(Pointer(a)));
    end;
    sl.Sort;
    TemplateListBox.Items.Assign(sl);
  finally
    sl.Free;
  end;
end;

procedure TCodeTemplateDialog.ShowCurCodeTemplate;
var
  EnableMacros: boolean;
  LineCount: integer;

  procedure AddLine(const s: string);
  begin
    if (LineCount=0) and (s=CodeTemplateMacroMagic) then
      EnableMacros:=true
    else
      TemplateSynEdit.Lines.Add(s);
    inc(LineCount);
  end;

var
  idx, a, sp, ep: integer;
  s: string;
begin
  EnableMacros:=false;
  LineCount := 0;
  idx := TemplateListBox.ItemIndex;
  // search template
  if idx >= 0
  then a := PtrInt(TemplateListBox.Items.Objects[idx])
  else a := -1;

  TemplateSynEdit.Lines.BeginUpdate;
  TemplateSynEdit.Lines.Clear;

  //debugln('TCodeTemplateDialog.ShowCurCodeTemplate A i=',dbgs(i));
  if a >= 0
  then begin
    LastTemplate := -1;
    s:=SynAutoComplete.CompletionValues[a];
    //debugln('TCodeTemplateDialog.ShowCurCodeTemplate s="',s,'"');
    sp:=1;
    ep:=1;
    while ep<=length(s) do begin
      if s[ep] in [#10,#13] then begin
        AddLine(copy(s,sp,ep-sp));
        inc(ep);
        if (ep<=length(s)) and (s[ep] in [#10,#13]) and (s[ep-1]<>s[ep]) then
          inc(ep);
        sp:=ep;
      end else inc(ep);
    end;
    if (ep>sp) or ((s<>'') and (s[length(s)] in [#10,#13])) then
      AddLine(copy(s,sp,ep-sp));
  end;
  LastTemplate := a;
  TemplateSynEdit.Lines.EndUpdate;
  TemplateSynEdit.Invalidate;
  UseMacrosCheckBox.Checked:=EnableMacros;
end;

procedure TCodeTemplateDialog.SaveCurCodeTemplate;
var
  NewValue: string;
  l: integer;
  a: LongInt;
begin
  if LastTemplate<0 then exit;
  a := LastTemplate;
  //DebugLn('TCodeTemplateDialog.SaveCurCodeTemplate A i=',dbgs(i));
  NewValue:=TemplateSynEdit.Lines.Text;
  // remove last EOL
  if NewValue<>'' then begin
    l:=length(NewValue);
    if NewValue[l] in [#10,#13] then begin
      dec(l);
      if (l>0) and (NewValue[l] in [#10,#13])
      and (NewValue[l]<>NewValue[l+1]) then
        dec(l);
      SetLength(NewValue,l);
    end;
  end;
  if UseMacrosCheckBox.Checked then
    NewValue:=CodeTemplateMacroMagic+LineEnding+NewValue;
  SynAutoComplete.CompletionValues[a]:=NewValue;
  //DebugLn('TCodeTemplateDialog.SaveCurCodeTemplate NewValue="',NewValue,'" SynAutoComplete.CompletionValues[a]="',SynAutoComplete.CompletionValues[a],'"');
end;

{ TLazCodeMacros }

function TLazCodeMacros.GetItems(Index: integer): TIDECodeMacro;
begin
  Result:=TIDECodeMacro(FItems[Index]);
end;

constructor TLazCodeMacros.Create;
begin
  FItems:=TFPList.Create;
end;

destructor TLazCodeMacros.Destroy;
begin
  Clear;
  FreeAndNil(FItems);
  inherited Destroy;
end;

procedure TLazCodeMacros.Clear;
var
  i: Integer;
begin
  for i:=0 to FItems.Count-1 do TObject(FItems[i]).Free;
  FItems.Clear;
end;

function TLazCodeMacros.Count: integer;
begin
  Result:=FItems.Count;
end;

function TLazCodeMacros.Add(Macro: TIDECodeMacro): integer;
begin
  if FindByName(Macro.Name)<>nil then
    RaiseGDBException('TLazCodeMacros.Add Name already exists');
  Result:=FItems.Add(Macro);
end;

function TLazCodeMacros.FindByName(const AName: string): TIDECodeMacro;
var
  i: LongInt;
begin
  i:=Count-1;
  while (i>=0) do begin
    Result:=Items[i];
    if (CompareText(Result.Name,AName)=0) then exit;
    dec(i);
  end;
  Result:=nil;
end;

function TLazCodeMacros.CreateUniqueName(const AName: string): string;
begin
  Result:=AName;
  if FindByName(Result)=nil then exit;
  Result:=CreateFirstIdentifier(Result);
  while FindByName(Result)<>nil do
    Result:=CreateNextIdentifier(Result);
end;

initialization
  {$I codetemplatesdlg.lrs}

end.
