{-------------------------------------------------------------------------------
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
the specific language governing rights and limitations under the License.

Alternatively, the contents of this file may be used under the terms of the
GNU General Public License Version 2 or later (the "GPL"), in which case
the provisions of the GPL are applicable instead of those above.
If you wish to allow use of your version of this file only under the terms
of the GPL and not to allow others to use your version of this file
under the MPL, indicate your decision by deleting the provisions above and
replace them with the notice and other provisions required by the GPL.
If you do not delete the provisions above, a recipient may use your version
of this file under either the MPL or the GPL.

Abstract:
  This is a basic synedit highlighter that does not parse text to create the
  attributes, but stores a list of ranges.

-------------------------------------------------------------------------------}
unit SynHighlighterPosition;

{$I synedit.inc}

interface

uses
  Classes, SysUtils, Graphics, SynEditStrConst, SynEditTypes,
  SynEditHighlighter;

const
  tkNone   = 0;
  tkText   = 1;
  tkCustom = 2;
  
  MaxColumns = 100000;

type
  TtkTokenKind = integer;

  TPositionToken = record
    Kind: TtkTokenKind;
    Column: integer;
  end;
  PPositionToken = ^TPositionToken;

  TPositionTokens = record
    Count: integer;
    Tokens: array[0..MaxColumns] of TPositionToken;
  end;
  PPositionTokens = ^TPositionTokens;

  { TSynPositionHighlighter }

  TSynPositionHighlighter = class(TSynCustomHighlighter)
  private
    fCopiedAttributes: TList;
    fLine: string;
    fLineLen: integer;
    fLineNumber: Integer;
    fTokenEnd: LongInt; // end of current token
    fTextAttri: TSynHighlighterAttributes;
    fTokenPos: Integer;
    fTokenArrayPos: integer;
    FTokenKind: TtkTokenKind;
    fTokens: TList; // list of PPositionTokens
    function GetTokens(TheLineNumber: integer): PPositionTokens;
  protected
    function GetIdentChars: TSynIdentChars; override;
    function IsFilterStored: boolean; override;                                 //mh 2000-10-08
    function GetPositionTokensSize(ItemCount: integer): integer;
    function GetDefaultAttribute(Index: integer): TSynHighlighterAttributes;
      override;
  public
    class function GetLanguageName: string; override;
  public
    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;
    function GetEol: Boolean; override;
    function GetRange: Pointer; override;
    function GetToken: string; override;
    procedure GetTokenEx(out TokenStart: PChar; out TokenLength: integer); override;
    function GetTokenAttribute: TSynHighlighterAttributes; override;
    function GetTokenKind: integer; override;
    function GetTokenPos: Integer; override;
    procedure Next; override;
    procedure ResetRange; override;
    procedure SetLine(const NewValue: string;
      LineNumber:Integer); override;
    procedure SetRange(Value: Pointer); override;
    function UseUserSettings(settingIndex: integer): boolean; override;
    procedure EnumUserSettings(settings: TStrings); override;
    property IdentChars;
  public
    procedure AddToken(Line, // 0 based
                       Col: integer; // 1 based
                       TokenKind: TtkTokenKind);
    procedure ClearTokens(Line: integer); // 0 based
    procedure ClearAllCopiedAttributes;
    procedure ClearAllTokens;
    procedure InsertTokens(Lines: TStringList;
      Highlighter: TSynCustomHighlighter;
      Line, //0 based
      Col: integer // 1 based
      );
    function CreateTokenID(const aName: string; Foreground, BackGround: TColor;
                           Style: TFontStyles): TtkTokenKind;
    function GetCopiedTokenID(Attr: TSynHighlighterAttributes): TtkTokenKind;
    function GetCopiedAttribute(TokenID: TtkTokenKind): TSynHighlighterAttributes;
    property Tokens[TheLineNumber: integer]: PPositionTokens read GetTokens;
  published
    property TextAttri: TSynHighlighterAttributes read fTextAttri write fTextAttri;
  end;

implementation

{ TSynPositionHighlighter }

function TSynPositionHighlighter.GetTokens(TheLineNumber: integer
  ): PPositionTokens;
begin
  if (TheLineNumber>=0) and (TheLineNumber<fTokens.Count) then
    Result:=PPositionTokens(fTokens[TheLineNumber])
  else
    Result:=nil;
end;

function TSynPositionHighlighter.GetIdentChars: TSynIdentChars;
begin
  Result := ['_', '0'..'9', 'a'..'z', 'A'..'Z'];
end;

function TSynPositionHighlighter.IsFilterStored: boolean;
begin
  Result:=true;
end;

function TSynPositionHighlighter.GetPositionTokensSize(ItemCount: integer
  ): integer;
begin
  Result:=SizeOf(integer)+SizeOf(TPositionToken)*ItemCount;
end;

function TSynPositionHighlighter.GetDefaultAttribute(Index: integer
  ): TSynHighlighterAttributes;
begin
  Result:=nil;
end;

class function TSynPositionHighlighter.GetLanguageName: string;
begin
  Result:='Position based highlighter';
end;

constructor TSynPositionHighlighter.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);
  fTokens:=TList.Create;
  fCopiedAttributes:=TList.Create;
  fTextAttri := TSynHighlighterAttributes.Create(@SYNS_AttrText, SYNS_XML_AttrText);
  AddAttribute(fTextAttri);
  SetAttributesOnChange(@DefHighlightChange);

  fDefaultFilter := '';
end;

destructor TSynPositionHighlighter.Destroy;
begin
  ClearAllTokens;
  fTokens.Free;
  ClearAllCopiedAttributes;
  fCopiedAttributes.Free;
  inherited Destroy;
end;

function TSynPositionHighlighter.GetEol: Boolean;
begin
  Result := fTokenKind = tkNone;
end;

function TSynPositionHighlighter.GetRange: Pointer;
begin
  Result := Pointer(PtrInt(fLineNumber));
end;

function TSynPositionHighlighter.GetToken: string;
var
  Len: LongInt;
begin
  Len := fTokenEnd - fTokenPos;
  SetLength(Result,Len);
  System.Move(fLine[fTokenPos],Result[1],Len);
end;

procedure TSynPositionHighlighter.GetTokenEx(out TokenStart: PChar;
  out TokenLength: integer);
begin
  TokenLength:=fTokenEnd-fTokenPos;
  if TokenLength>0 then begin
    TokenStart:=@fLine[fTokenPos];
  end else begin
    TokenStart:=nil;
  end;
end;

function TSynPositionHighlighter.GetTokenAttribute: TSynHighlighterAttributes;
var
  t: TtkTokenKind;
begin
  t:=GetTokenKind;
  if t=tkText then
    Result:=fTextAttri
  else if (t<0) then
    // this is a copied attribute
    Result:=GetCopiedAttribute(t)
  else
    Result:=nil;
end;

function TSynPositionHighlighter.GetTokenKind: integer;
begin
  Result:=fTokenKind;
end;

function TSynPositionHighlighter.GetTokenPos: Integer;
begin
  Result:=fTokenPos-1;
end;

procedure TSynPositionHighlighter.Next;
var
  p: PPositionTokens;
begin
  fTokenPos := fTokenEnd;
  if fTokenEnd>fLineLen then begin
    fTokenKind := tkNone;
    exit;
  end;
  inc(fTokenArrayPos);
  p:=Tokens[fLineNumber];
  if (p<>nil) and (p^.Count>fTokenArrayPos) then begin
    fTokenKind := p^.Tokens[fTokenArrayPos].Kind;
    fTokenEnd := p^.Tokens[fTokenArrayPos].Column+1;
    if fTokenEnd>fLineLen+1 then
      fTokenEnd := fLineLen+1;
    if fTokenEnd=fTokenPos then
      Next;
  end else begin
    fTokenEnd := fLineLen+1;
    fTokenKind := tkText;
  end;
end;

procedure TSynPositionHighlighter.ResetRange;
begin
  inherited ResetRange;
end;

procedure TSynPositionHighlighter.SetLine(const NewValue: string;
  LineNumber: Integer);
var
  p: PPositionTokens;
begin
  inherited;
  fLine := NewValue;
  fLineLen := length(fLine);
  fLineNumber := LineNumber;
  fTokenArrayPos := 0;
  fTokenPos := 1;
  p:=Tokens[fLineNumber];
  if p<>nil then begin
    fTokenEnd := p^.Tokens[0].Column+1;
    if fTokenEnd>fLineLen+1 then
      fTokenEnd:=fLineLen+1;
    FTokenKind := p^.Tokens[0].Kind;
    if fTokenEnd=fTokenPos then Next;
  end else begin
    fTokenEnd := fLineLen+1;
    FTokenKind := tkText;
  end;
end;

procedure TSynPositionHighlighter.SetRange(Value: Pointer);
begin
  inherited SetRange(Value);
end;

function TSynPositionHighlighter.UseUserSettings(settingIndex: integer
  ): boolean;
begin
  Result:=inherited UseUserSettings(settingIndex);
end;

procedure TSynPositionHighlighter.EnumUserSettings(settings: TStrings);
begin
  inherited EnumUserSettings(settings);
end;

procedure TSynPositionHighlighter.AddToken(Line, Col: integer;
  TokenKind: TtkTokenKind);
var
  p: PPositionTokens;
  TokenIndex, TokenCount: integer;
begin
  if Col<1 then exit;
  // fill up lines
  while (Line>=fTokens.Count) do fTokens.Add(nil);
  // resize Token array
  p:=Tokens[Line];
  TokenIndex:=0;
  if p<>nil then
    TokenCount:=p^.Count+1
  else
    TokenCount:=1;
  ReAllocMem(p,GetPositionTokensSize(TokenCount));
  fTokens[Line]:=p;
  p^.Count:=TokenCount;
  // insert Token
  TokenIndex:=TokenCount-1;
  while (TokenIndex>0)
  and (p^.Tokens[TokenIndex-1].Column>Col) do
    dec(TokenIndex);
  if TokenIndex<TokenCount-1 then begin
    System.Move(p^.Tokens[TokenIndex],p^.Tokens[TokenIndex+1],
      SizeOf(TPositionToken)*(TokenCount-TokenIndex-1));
  end;
  with p^.Tokens[TokenIndex] do begin
    Kind:=TokenKind;
    Column:=Col;
  end;
  DefHighlightChange(Self);
end;

procedure TSynPositionHighlighter.ClearTokens(Line: integer);
var
  p: pointer;
begin
  if (Line>=0) and (Line<fTokens.Count) then begin
    p:=fTokens[Line];
    if p<>nil then begin
      FreeMem(p);
      fTokens[Line]:=nil;
    end;
  end;
end;

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

procedure TSynPositionHighlighter.ClearAllTokens;
var
  i: Integer;
begin
  for i:=0 to fTokens.Count-1 do
    ClearTokens(i);
  fTokens.Clear;
end;

procedure TSynPositionHighlighter.InsertTokens(Lines: TStringList;
  Highlighter: TSynCustomHighlighter; Line, Col: integer);
var
  RelLine: integer;
  nTokenLen: integer;
  sToken: PChar;
  Attr: TSynHighlighterAttributes;
  TokenID: integer;
begin
  if (Lines=nil) or (Lines.Count=0) or (Highlighter=nil) then exit;
  RelLine:=0;
  while RelLine<Lines.Count do begin
    HighLighter.SetLine(Lines[RelLine], RelLine);
    while not Highlighter.GetEol do begin
      Attr:=Highlighter.GetTokenAttribute;
      TokenID:=GetCopiedTokenID(Attr);
      Highlighter.GetTokenEx(sToken,nTokenLen);
      inc(Col,nTokenLen);
      AddToken(Line,Col-1,TokenID);
      // next Token
      Highlighter.Next;
    end;
    // next line
    inc(Line);
    inc(RelLine);
    Col:=1;
  end;
end;

function TSynPositionHighlighter.CreateTokenID(const aName: string;
  Foreground, BackGround: TColor;
  Style: TFontStyles): TtkTokenKind;
var
  Attr: TSynHighlighterAttributes;
begin
  Attr:=TSynHighlighterAttributes.Create(aName);
  Attr.Foreground:=Foreground;
  Attr.Background:=BackGround;
  Attr.Style:=Style;
  Result:=GetCopiedTokenID(Attr);
  Attr.Free;
end;

function TSynPositionHighlighter.GetCopiedTokenID(
  Attr: TSynHighlighterAttributes): TtkTokenKind;
var
  i: Integer;
  CurAttr: TSynHighlighterAttributes;
begin
  i:=fCopiedAttributes.Count-1;
  while i>=0 do begin
    CurAttr:=TSynHighlighterAttributes(fCopiedAttributes[i]);
    if (Attr.ForeGround=CurAttr.ForeGround)
    and (Attr.BackGround=CurAttr.BackGround)
    and (Attr.Style=CurAttr.Style) then begin
      // attribute already exists
      Result:=(-i-1);
      exit;
    end;
    dec(i);
  end;
  // create new attribute
  CurAttr:=TSynHighlighterAttributes.Create(nil);
  CurAttr.Assign(Attr);
  fCopiedAttributes.Add(CurAttr);
  Result:= -fCopiedAttributes.Count;
end;

function TSynPositionHighlighter.GetCopiedAttribute(TokenID: TtkTokenKind
  ): TSynHighlighterAttributes;
begin
  if (TokenID<0) and (fCopiedAttributes.Count>=-TokenID) then
    Result:=TSynHighlighterAttributes(fCopiedAttributes[-TokenID-1])
  else
    Result:=nil;
end;

end.

