unit FoldHl;
(*
  This is an example how to implement your own highlighter.

  This example extends the Simple and Context HL:
  - The token -(- and -)- (must be surrounded by space or line-begin/end to be
    a token of their own) will add foldable sections

    Multply -(- and -)- can be nested.

  See comments below and http://wiki.lazarus.freepascal.org/SynEdit_Highlighter

*)

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Graphics, SynEditTypes, SynEditHighlighter, SynEditHighlighterFoldBase, ContextHL;

type

  (*   This is an EXACT COPY of SynEditHighlighter

       ONLY the base class is changed to add support for folding

       The new code follows below
  *)

  TSynDemoHlFoldBase = class(TSynCustomFoldHighlighter)
  private
    FNotAttri: TSynHighlighterAttributes;
    fSpecialAttri: TSynHighlighterAttributes;
    fIdentifierAttri: TSynHighlighterAttributes;
    fSpaceAttri: TSynHighlighterAttributes;
    procedure SetIdentifierAttri(AValue: TSynHighlighterAttributes);
    procedure SetNotAttri(AValue: TSynHighlighterAttributes);
    procedure SetSpaceAttri(AValue: TSynHighlighterAttributes);
    procedure SetSpecialAttri(AValue: TSynHighlighterAttributes);
  protected
    // accesible for the other examples
    FTokenPos, FTokenEnd: Integer;
    FLineText: String;
  public
    procedure SetLine(const NewValue: String; LineNumber: Integer); override;
    procedure Next; override;
    function  GetEol: Boolean; override;
    procedure GetTokenEx(out TokenStart: PChar; out TokenLength: integer); override;
    function  GetTokenAttribute: TSynHighlighterAttributes; override;
  public
    function GetToken: String; override;
    function GetTokenPos: Integer; override;
    function GetTokenKind: integer; override;
    function GetDefaultAttribute(Index: integer): TSynHighlighterAttributes; override;
    constructor Create(AOwner: TComponent); override;
  published
    (* Define 4 Attributes, for the different highlights. *)
    property SpecialAttri: TSynHighlighterAttributes read fSpecialAttri
      write SetSpecialAttri;
    property NotAttri: TSynHighlighterAttributes read FNotAttri
      write SetNotAttri;
    property IdentifierAttri: TSynHighlighterAttributes read fIdentifierAttri
      write SetIdentifierAttri;
    property SpaceAttri: TSynHighlighterAttributes read fSpaceAttri
      write SetSpaceAttri;
  end;

  (*   This is a COPY of SynEditHighlighter

       ONLY the base class is changed to add support for folding

       The new code follows below
  *)

  TSynDemoHlContextFoldBase = class(TSynDemoHlFoldBase)
  protected
    FCurRange: Integer;
  public
    procedure Next; override;
    function GetTokenAttribute: TSynHighlighterAttributes; override;
  public
  (* The below needed to be changed and are in TSynDemoHlFold
     TSynDemoHlContextFoldBase uses Ranges itself.
     The Range needed here is therefore stored in a diff location
  *)
    //procedure SetRange(Value: Pointer); override;
    //procedure ResetRange; override;
    //function GetRange: Pointer; override;
  end;

  { TSynDemoHlContext }

  (* You can base this on either
     TSynDemoHlFoldBase or TSynDemoHlContextFoldBase

     Using ranges is NOT a condition for fold.
     (If changing, remove Range related code)

     Note that ranges to change.
  *)

  //TSynDemoHlFold = class(TSynDemoHlFoldBase)
  TSynDemoHlFold = class(TSynDemoHlContextFoldBase)
  public
    procedure Next; override;
  public
    procedure SetRange(Value: Pointer); override;
    procedure ResetRange; override;
    function GetRange: Pointer; override;
  end;

implementation

{ TSynDemoHlFold }

procedure TSynDemoHlFold.Next;
begin
  inherited Next;
  if (copy(FLineText, FTokenPos, FTokenEnd - FTokenPos) = '-(-') then
    StartCodeFoldBlock(nil);
  if (copy(FLineText, FTokenPos, FTokenEnd - FTokenPos) = '-)-') then
    EndCodeFoldBlock;
end;

procedure TSynDemoHlFold.SetRange(Value: Pointer);
begin
  // must call the SetRange in TSynCustomFoldHighlighter
  inherited SetRange(Value);
  FCurRange := PtrInt(CodeFoldRange.RangeType);
 end;

procedure TSynDemoHlFold.ResetRange;
begin
  inherited ResetRange;
  FCurRange := 0;
end;

function TSynDemoHlFold.GetRange: Pointer;
begin
  // Store the range first
  CodeFoldRange.RangeType := Pointer(PtrInt(FCurRange));
  Result := inherited GetRange;
end;


(*   This is an EXACT COPY of SynEditHighlighter

     ONLY the base class is changed to add support for folding
*)

constructor TSynDemoHlFoldBase.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  (* Create and initialize the attributes *)
  fSpecialAttri := TSynHighlighterAttributes.Create('special', 'special');
  AddAttribute(fSpecialAttri);
  fSpecialAttri.Style := [fsBold];

  FNotAttri := TSynHighlighterAttributes.Create('not', 'not');
  AddAttribute(FNotAttri);
  FNotAttri.Background := clRed;

  fIdentifierAttri := TSynHighlighterAttributes.Create('ident', 'ident');
  AddAttribute(fIdentifierAttri);

  fSpaceAttri := TSynHighlighterAttributes.Create('space', 'space');
  AddAttribute(fSpaceAttri);
  fSpaceAttri.FrameColor := clSilver;
  fSpaceAttri.FrameEdges := sfeAround;

  // Ensure the HL reacts to changes in the attributes. Do this once, if all attributes are created
  SetAttributesOnChange(@DefHighlightChange);
end;

(* Setters for attributes / This allows using in Object inspector*)
procedure TSynDemoHlFoldBase.SetIdentifierAttri(AValue: TSynHighlighterAttributes);
begin
  fIdentifierAttri.Assign(AValue);
end;

procedure TSynDemoHlFoldBase.SetNotAttri(AValue: TSynHighlighterAttributes);
begin
  FNotAttri.Assign(AValue);
end;

procedure TSynDemoHlFoldBase.SetSpaceAttri(AValue: TSynHighlighterAttributes);
begin
  fSpaceAttri.Assign(AValue);
end;

procedure TSynDemoHlFoldBase.SetSpecialAttri(AValue: TSynHighlighterAttributes);
begin
  fSpecialAttri.Assign(AValue);
end;

procedure TSynDemoHlFoldBase.SetLine(const NewValue: String; LineNumber: Integer);
begin
  inherited;
  FLineText := NewValue;
  // Next will start at "FTokenEnd", so set this to 1
  FTokenEnd := 1;
  Next;
end;

procedure TSynDemoHlFoldBase.Next;
var
  l: Integer;
begin
  // FTokenEnd should be at the start of the next Token (which is the Token we want)
  FTokenPos := FTokenEnd;
  // assume empty, will only happen for EOL
  FTokenEnd := FTokenPos;

  // Scan forward
  // FTokenEnd will be set 1 after the last char. That is:
  // - The first char of the next token
  // - or past the end of line (which allows GetEOL to work)

  l := length(FLineText);
  If FTokenPos > l then
    // At line end
    exit
  else
  if FLineText[FTokenEnd] in [#9, ' '] then
    // At Space? Find end of spaces
    while (FTokenEnd <= l) and (FLineText[FTokenEnd] in [#0..#32]) do inc (FTokenEnd)
  else
    // At None-Space? Find end of None-spaces
    while (FTokenEnd <= l) and not(FLineText[FTokenEnd] in [#9, ' ']) do inc (FTokenEnd)
end;

function TSynDemoHlFoldBase.GetEol: Boolean;
begin
  Result := FTokenPos > length(FLineText);
end;

procedure TSynDemoHlFoldBase.GetTokenEx(out TokenStart: PChar; out TokenLength: integer);
begin
  TokenStart := @FLineText[FTokenPos];
  TokenLength := FTokenEnd - FTokenPos;
end;

function TSynDemoHlFoldBase.GetTokenAttribute: TSynHighlighterAttributes;
begin
  // Match the text, specified by FTokenPos and FTokenEnd

  if FLineText[FTokenPos] in [#9, ' '] then
    Result := SpaceAttri
  else
  if LowerCase(FLineText[FTokenPos]) in ['a', 'e', 'i', 'o', 'u'] then
    Result := SpecialAttri
  else
  if LowerCase(copy(FLineText, FTokenPos, FTokenEnd - FTokenPos)) = 'not' then
    Result := NotAttri
  else
    Result := IdentifierAttri;
end;

function TSynDemoHlFoldBase.GetToken: String;
begin
  Result := copy(FLineText, FTokenPos, FTokenEnd - FTokenPos);
end;

function TSynDemoHlFoldBase.GetTokenPos: Integer;
begin
  Result := FTokenPos - 1;
end;

function TSynDemoHlFoldBase.GetDefaultAttribute(Index: integer): TSynHighlighterAttributes;
begin
  // Some default attributes
  case Index of
    SYN_ATTR_COMMENT: Result := fSpecialAttri;
    SYN_ATTR_IDENTIFIER: Result := fIdentifierAttri;
    SYN_ATTR_WHITESPACE: Result := fSpaceAttri;
    else Result := nil;
  end;
end;

function TSynDemoHlFoldBase.GetTokenKind: integer;
var
  a: TSynHighlighterAttributes;
begin
  // Map Attribute into a unique number
  a := GetTokenAttribute;
  Result := 0;
  if a = fSpaceAttri then Result := 1;
  if a = fSpecialAttri then Result := 2;
  if a = fIdentifierAttri then Result := 3;
  if a = FNotAttri then Result := 4;
end;


(*   This is an EXACT COPY of SynEditHighlighter

     ONLY the base class is changed to add support for folding
*)

procedure TSynDemoHlContextFoldBase.Next;
begin
  inherited Next;
  if (copy(FLineText, FTokenPos, FTokenEnd - FTokenPos) = '--') then
    inc(FCurRange);
  if (copy(FLineText, FTokenPos, FTokenEnd - FTokenPos) = '++') and (FCurRange > 0) then
    dec(FCurRange);
end;

function TSynDemoHlContextFoldBase.GetTokenAttribute: TSynHighlighterAttributes;
begin
  Result := inherited GetTokenAttribute;
  if (Result = SpecialAttri) and (FCurRange > 0) then
    Result := IdentifierAttribute;
end;


end.

