mirror of https://gitlab.com/basile.b/dexed.git
501 lines
14 KiB
Plaintext
501 lines
14 KiB
Plaintext
unit SynEditMarkupFoldColors;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils,Graphics, SynEditMarkup, SynEditMiscClasses, Controls,
|
|
LCLProc, SynEditHighlighter, SynEditHighlighterFoldBase;
|
|
|
|
type
|
|
|
|
PMarkupFoldColorInfo = ^TMarkupFoldColorInfo;
|
|
TMarkupFoldColorInfo = record
|
|
Y, X, X2: Integer;
|
|
ColorIdx: Integer;
|
|
Border : Boolean;
|
|
end;
|
|
|
|
TMarkupFoldColorInfos = array of TMarkupFoldColorInfo;
|
|
TSynFoldNodeInfos = array of TSynFoldNodeInfo; //for quick compare detection
|
|
|
|
{ TSynEditMarkupFoldColors }
|
|
|
|
TSynEditMarkupFoldColors = class(TSynEditMarkup)
|
|
private
|
|
FDefaultGroup: integer;
|
|
// Physical Position
|
|
FHighlights : TMarkupFoldColorInfos; //array of TMarkupFoldColorInfo;
|
|
Colors : array of TColor;
|
|
CurrentY : integer; //??
|
|
FCaretY : integer; // flag identify for refresh begin______
|
|
FPrevCaretText : string; // flag identify for refresh begin______
|
|
|
|
procedure DoMarkupFoldAtRow(aRow: Integer);
|
|
procedure DoMarkupParentFoldAtRow(aRow: Integer);
|
|
function GetFoldHighLighter: TSynCustomFoldHighlighter;
|
|
protected
|
|
// Notifications about Changes to the text
|
|
procedure DoTextChanged({%H-}StartLine, EndLine, {%H-}ACountDiff: Integer); override; // 1 based
|
|
procedure DoCaretChanged(Sender: TObject); override;
|
|
public
|
|
constructor Create(ASynEdit : TSynEditBase);
|
|
function GetMarkupAttributeAtRowCol(const aRow: Integer;
|
|
const aStartCol: TLazSynDisplayTokenBound;
|
|
const {%H-}AnRtlInfo: TLazSynDisplayRtlInfo): TSynSelectedColor; override;
|
|
procedure GetNextMarkupColAfterRowCol(const aRow: Integer;
|
|
const aStartCol: TLazSynDisplayTokenBound;
|
|
const {%H-}AnRtlInfo: TLazSynDisplayRtlInfo;
|
|
out ANextPhys, ANextLog: Integer); override;
|
|
|
|
procedure PrepareMarkupForRow(aRow : Integer); override;
|
|
property DefaultGroup : integer read FDefaultGroup write FDefaultGroup;
|
|
end;
|
|
|
|
implementation
|
|
uses
|
|
Forms {debug},
|
|
SynEdit,SynEditTypes, SynEditFoldedView, SynEditMiscProcs;
|
|
|
|
function CompareFI(Item1, Item2: Pointer): Integer;
|
|
begin
|
|
result := PMarkupFoldColorInfo(Item1)^.X - PMarkupFoldColorInfo(Item2)^.X;
|
|
end;
|
|
|
|
function SortLeftMostFI(a: TMarkupFoldColorInfos): TMarkupFoldColorInfos;
|
|
var
|
|
l : TFpList;
|
|
i : integer;
|
|
begin
|
|
l := TFpList.Create;
|
|
for i := 0 to Pred(Length(a)) do
|
|
l.Add( PMarkupFoldColorInfo(@a[i]) );
|
|
l.Sort(@CompareFI);
|
|
|
|
SetLength(result, Length(a));
|
|
for i := 0 to Pred(l.Count) do
|
|
result[i] := PMarkupFoldColorInfo(l[i])^;
|
|
l.Free;
|
|
end;
|
|
|
|
{ TSynEditMarkupFoldColors }
|
|
|
|
constructor TSynEditMarkupFoldColors.Create(ASynEdit: TSynEditBase);
|
|
begin
|
|
inherited Create(ASynEdit);
|
|
MarkupInfo.Foreground := clGreen;
|
|
MarkupInfo.Background := clNone; //clFuchsia;
|
|
MarkupInfo.Style := [];
|
|
MarkupInfo.StyleMask := [];
|
|
MarkupInfo.FrameEdges:= sfeLeft;//sfeBottom;//sfeAround;//
|
|
|
|
SetLength(Colors, 6);
|
|
Colors[0] := clRed;
|
|
Colors[1] := $000098F7; //orange
|
|
Colors[2] := $0022CC40; //green
|
|
Colors[3] := $00D1D54A; // $0098CC42; //teal
|
|
Colors[4] := $00FF682A; //blue
|
|
Colors[5] := $00CF00C4; //purple
|
|
end;
|
|
|
|
function TSynEditMarkupFoldColors.GetMarkupAttributeAtRowCol(
|
|
const aRow: Integer; const aStartCol: TLazSynDisplayTokenBound;
|
|
const AnRtlInfo: TLazSynDisplayRtlInfo): TSynSelectedColor;
|
|
var
|
|
i : integer;
|
|
begin
|
|
Result := nil;
|
|
if (CurrentY = aRow) then
|
|
for i := 0 to length(FHighlights)-1 do
|
|
with FHighlights[i] do
|
|
if (aStartCol.Logical >= x) and (aStartCol.Logical < X2) then
|
|
begin
|
|
if ColorIdx >= 0 then
|
|
begin
|
|
MarkupInfo.FrameColor:= clNone;
|
|
MarkupInfo.Foreground:= clNone;
|
|
|
|
Result := MarkupInfo;
|
|
MarkupInfo.SetFrameBoundsLog(x, x2);
|
|
if Border then
|
|
MarkupInfo.FrameColor:= Colors[ColorIdx]
|
|
else
|
|
MarkupInfo.Foreground := Colors[ColorIdx]
|
|
end;
|
|
|
|
break;
|
|
end
|
|
end;
|
|
|
|
procedure TSynEditMarkupFoldColors.GetNextMarkupColAfterRowCol(
|
|
const aRow: Integer; const aStartCol: TLazSynDisplayTokenBound;
|
|
const AnRtlInfo: TLazSynDisplayRtlInfo; out ANextPhys, ANextLog: Integer);
|
|
var i : integer;
|
|
begin
|
|
ANextLog := -1;
|
|
ANextPhys := -1;
|
|
if (CurrentY = aRow) then
|
|
for i := 0 to length(FHighlights)-1 do begin
|
|
if FHighlights[i].X <= aStartCol.Logical then
|
|
continue;
|
|
if FHighlights[i].X2 < aStartCol.Logical then
|
|
continue;
|
|
ANextLog := FHighlights[i].X;
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditMarkupFoldColors.DoMarkupFoldAtRow(aRow: Integer);
|
|
|
|
procedure AddHighlight( ANode: TSynFoldNodeInfo );
|
|
var x,lvl : integer;
|
|
begin
|
|
//exit; //debug
|
|
x := Length(FHighlights);
|
|
SetLength(FHighlights, x+1);
|
|
with FHighlights[x] do begin
|
|
Border := False;
|
|
Y := ANode.LineIndex + 1;
|
|
X := ANode.LogXStart + 1;
|
|
X2 := ANode.LogXEnd + 1;
|
|
if sfaOpen in ANode.FoldAction then begin
|
|
lvl := ANode.FoldLvlStart;
|
|
//lvl := ANode.NestLvlStart; //http://forum.lazarus.freepascal.org/index.php/topic,30122.msg194841.html#msg194841
|
|
ColorIdx := lvl mod (length(Colors));
|
|
end
|
|
else
|
|
if sfaClose in ANode.FoldAction then begin
|
|
lvl := ANode.FoldLvlEnd;
|
|
ColorIdx := lvl mod (length(Colors));
|
|
end
|
|
else
|
|
ColorIdx := -1;
|
|
|
|
|
|
{if sfaOpen in ANode.FoldAction then
|
|
lvl := ANode.NestLvlStart
|
|
else
|
|
lvl := ANode.NestLvlEnd;
|
|
ColorIdx := lvl mod (length(Colors));
|
|
}
|
|
|
|
end;
|
|
end;
|
|
|
|
var
|
|
y,i : integer;
|
|
HL: TSynCustomFoldHighlighter;
|
|
NodeList: TLazSynFoldNodeInfoList;
|
|
TmpNode: TSynFoldNodeInfo;
|
|
|
|
begin
|
|
y := aRow -1;
|
|
|
|
HL := TCustomSynEdit(self.SynEdit).Highlighter as TSynCustomFoldHighlighter;
|
|
HL.CurrentLines := Lines;
|
|
HL.FoldNodeInfo[y].ClearFilter; // only needed once, in case the line was already used
|
|
|
|
NodeList := HL.FoldNodeInfo[y];
|
|
NodeList.AddReference;
|
|
try
|
|
NodeList.ActionFilter := [
|
|
{sfaMarkup,}
|
|
sfaFold
|
|
//sfaFoldFold
|
|
//sfaFoldHide
|
|
//sfaSingleLine
|
|
//sfaMultiLine
|
|
//sfaOpen
|
|
];
|
|
//NodeList.FoldFlags:= [sfbIncludeDisabled];
|
|
i := 0;
|
|
repeat
|
|
TmpNode := NodeList[i];
|
|
|
|
//find till valid
|
|
while (sfaInvalid in TmpNode.FoldAction) and (i < NodeList.Count) do
|
|
begin
|
|
inc(i);
|
|
TmpNode := NodeList[i];
|
|
end;
|
|
if not (sfaInvalid in TmpNode.FoldAction) then
|
|
AddHighlight(TmpNode);
|
|
|
|
inc(i);
|
|
until i >= NodeList.Count;
|
|
|
|
finally
|
|
NodeList.ReleaseReference;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditMarkupFoldColors.DoMarkupParentFoldAtRow(aRow: Integer);
|
|
|
|
procedure AddVerticalLine( ANode: TSynFoldNodeInfo );
|
|
var x,i,lvl : integer;
|
|
begin
|
|
//don't replace; don't add when already found
|
|
x := ANode.LogXStart + 1;
|
|
for i := 0 to Pred(length(FHighlights)) do
|
|
if FHighlights[i].X = x then
|
|
exit;
|
|
|
|
x := Length(FHighlights);
|
|
SetLength(FHighlights, x+1);
|
|
with FHighlights[x] do begin
|
|
Border := ANode.LineIndex + 1 <> aRow;
|
|
Y := aRow;//ANode.LineIndex + 1;
|
|
X := ANode.LogXStart + 1;
|
|
X2 := X+1; //ANode.LogXEnd + 1;
|
|
if sfaOpen in ANode.FoldAction then begin
|
|
lvl := ANode.FoldLvlStart;
|
|
ColorIdx := lvl mod (length(Colors));
|
|
end
|
|
else
|
|
if sfaClose in ANode.FoldAction then begin
|
|
lvl := ANode.FoldLvlEnd;
|
|
ColorIdx := lvl mod (length(Colors));
|
|
end
|
|
else
|
|
begin
|
|
ColorIdx := -1;
|
|
lvl := ANode.NestLvlStart;
|
|
ColorIdx := lvl mod (length(Colors));
|
|
end;
|
|
|
|
{
|
|
if sfaOpen in ANode.FoldAction then
|
|
lvl := ANode.NestLvlStart
|
|
else
|
|
lvl := ANode.NestLvlEnd;
|
|
|
|
//ColorIdx := ANode.NodeIndex mod (length(Colors));
|
|
|
|
lvl := ANode.NestLvlEnd;
|
|
//lvl := Longint(ANode.FoldTypeCompatible);
|
|
ColorIdx := lvl mod (length(Colors));
|
|
}
|
|
|
|
|
|
end;
|
|
end;
|
|
var
|
|
i,y: Integer;
|
|
Nest : TLazSynEditNestedFoldsList;
|
|
TmpNode: TSynFoldNodeInfo;
|
|
|
|
begin
|
|
y := aRow-1;
|
|
Nest := TLazSynEditNestedFoldsList.Create(@GetFoldHighLighter);
|
|
Nest.ResetFilter;
|
|
Nest.Clear;
|
|
Nest.Line := y;
|
|
Nest.FoldGroup := FDefaultGroup;//1;//FOLDGROUP_PASCAL;
|
|
Nest.FoldFlags := [];//[sfbIncludeDisabled]; //
|
|
Nest.IncludeOpeningOnLine := True; //False; //
|
|
|
|
//i := 0; while i < Nest.Count do
|
|
i := Nest.Count -1; while i >= 0 do //from right to left
|
|
begin
|
|
TmpNode := Nest.HLNode[i];
|
|
|
|
//find till valid
|
|
while (sfaInvalid in TmpNode.FoldAction) and (i < Nest.Count) do
|
|
begin
|
|
inc(i);
|
|
TmpNode := Nest.HLNode[i];
|
|
end;
|
|
if not (sfaInvalid in TmpNode.FoldAction) then
|
|
AddVerticalLine(TmpNode);
|
|
|
|
//inc(i);
|
|
dec(i);
|
|
end;
|
|
end;
|
|
|
|
procedure TSynEditMarkupFoldColors.PrepareMarkupForRow(aRow: Integer);
|
|
begin
|
|
CurrentY := aRow;
|
|
SetLength(FHighlights,0); //reset needed to prevent using of invalid area
|
|
|
|
if not (TCustomSynEdit(self.SynEdit).Highlighter is TSynCustomFoldHighlighter) then
|
|
exit;
|
|
|
|
DoMarkupFoldAtRow(aRow);
|
|
DoMarkupParentFoldAtRow(aRow);
|
|
|
|
FHighlights := SortLeftMostFI(FHighlights);
|
|
end;
|
|
|
|
function TSynEditMarkupFoldColors.GetFoldHighLighter: TSynCustomFoldHighlighter;
|
|
begin
|
|
result := TCustomSynEdit(self.SynEdit).Highlighter as TSynCustomFoldHighlighter;
|
|
end;
|
|
|
|
{.$define debug_FC_line_changed}
|
|
procedure TSynEditMarkupFoldColors.DoTextChanged(StartLine, EndLine,
|
|
ACountDiff: Integer);
|
|
{$ifdef debug_FC_line_changed}
|
|
var F : TCustomForm;
|
|
begin
|
|
F := GetParentForm(self.SynEdit);
|
|
if F <> nil then
|
|
//F.Caption := Format('Start:%d Endline:%d Diff:%d',[StartLine, EndLIne, ACountDiff]);
|
|
F.Caption := F.Caption + Caret.LineText
|
|
{$else}
|
|
|
|
|
|
|
|
function GetPairCloseFold(aRow, X : integer ): Integer;
|
|
var
|
|
y,i,LCnt : integer;
|
|
HL: TSynCustomFoldHighlighter;
|
|
NodeList: TLazSynFoldNodeInfoList;
|
|
TmpNode, CloseNode: TSynFoldNodeInfo;
|
|
|
|
function FindEndNode(StartNode: TSynFoldNodeInfo;
|
|
{var} YIndex, NIndex: Integer): TSynFoldNodeInfo;
|
|
function SearchLine(ALineIdx: Integer; var ANodeIdx: Integer): TSynFoldNodeInfo;
|
|
begin
|
|
NodeList.Line := ALineIdx;
|
|
repeat
|
|
inc(ANodeIdx);
|
|
Result := NodeList[ANodeIdx];
|
|
until (sfaInvalid in Result.FoldAction)
|
|
or (Result.NestLvlEnd <= StartNode.NestLvlStart);
|
|
end;
|
|
|
|
begin
|
|
Result := SearchLine(YIndex, NIndex);
|
|
if not (sfaInvalid in Result.FoldAction) then
|
|
exit;
|
|
|
|
inc(YIndex);
|
|
while (YIndex < LCnt) and
|
|
(HL.FoldBlockMinLevel(YIndex, StartNode.FoldGroup, [sfbIncludeDisabled])
|
|
> StartNode.NestLvlStart)
|
|
do
|
|
inc(YIndex);
|
|
if YIndex = LCnt then
|
|
exit;
|
|
|
|
NIndex := -1;
|
|
Result := SearchLine(YIndex, NIndex);
|
|
|
|
if (Result.LogXEnd = 0) or (sfaLastLineClose in Result.FoldAction) then
|
|
Result.FoldAction := [sfaInvalid]; // LastLine closed Node(maybe force-closed?)
|
|
end;
|
|
|
|
begin
|
|
Result := -1;
|
|
y := aRow -1;
|
|
|
|
HL := TCustomSynEdit(self.SynEdit).Highlighter as TSynCustomFoldHighlighter;
|
|
HL.CurrentLines := Lines;
|
|
LCnt := Lines.Count;
|
|
HL.FoldNodeInfo[y].ClearFilter; // only needed once, in case the line was already used
|
|
|
|
NodeList := HL.FoldNodeInfo[y];
|
|
NodeList.AddReference;
|
|
try
|
|
NodeList.ActionFilter := [sfaOpen];
|
|
i := 0;
|
|
repeat
|
|
TmpNode := NodeList[i];
|
|
|
|
if TmpNode.LogXStart < X-1 then
|
|
begin
|
|
inc(i);
|
|
continue;
|
|
end;
|
|
|
|
//find till valid
|
|
while (sfaInvalid in TmpNode.FoldAction) and (i < NodeList.Count) do
|
|
begin
|
|
inc(i);
|
|
TmpNode := NodeList[i];
|
|
end;
|
|
if not (sfaInvalid in TmpNode.FoldAction) then
|
|
begin
|
|
CloseNode := FindEndNode(TmpNode, y, i);
|
|
//AddHighlight(TmpNode);
|
|
Result := CloseNode.LineIndex;
|
|
exit;
|
|
end;
|
|
|
|
inc(i);
|
|
until i >= NodeList.Count;
|
|
|
|
finally
|
|
NodeList.ReleaseReference;
|
|
end;
|
|
end;
|
|
|
|
|
|
function IsFoldMoved( aRow: Integer ): integer;
|
|
var S : string;
|
|
i,n : integer;
|
|
begin
|
|
Result := -1;
|
|
n := -1;
|
|
|
|
S := Caret.LineText;
|
|
for i := 1 to Min(Length(S), Length(FPrevCaretText)) do
|
|
begin
|
|
if S[i] <> FPrevCaretText[i] then
|
|
begin
|
|
n := i;
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
if n < 0 then exit;
|
|
|
|
Result := GetPairCloseFold(aRow, n);
|
|
//limit to screen bottom
|
|
if Result > 0 then
|
|
begin
|
|
inc(Result);//because sometime 'end' has trailing vertical line
|
|
with TCustomSynEdit(SynEdit) do
|
|
Result := min(Result, TopLine +LinesInWindow);// . .RowToScreenRow(i);
|
|
end;
|
|
|
|
end;
|
|
var
|
|
EndFoldLine,y : integer;
|
|
begin
|
|
if EndLine < 0 then exit; //already refreshed by syn
|
|
|
|
y := Caret.LineBytePos.y;
|
|
EndFoldLine := IsFoldMoved(y);
|
|
if EndFoldLine > 0 then
|
|
begin
|
|
InvalidateSynLines(y+1, EndFoldLine);
|
|
end;
|
|
|
|
FPrevCaretText := Caret.LineText;
|
|
// I found that almost anything has been repaint by the SynEdit,
|
|
// except the trailing space editing: we should repaint them here.
|
|
{$endif}
|
|
end;
|
|
|
|
procedure TSynEditMarkupFoldColors.DoCaretChanged(Sender: TObject);
|
|
var Y : integer;
|
|
begin
|
|
Y := Caret.LineBytePos.y;
|
|
if Y = FCaretY then exit;
|
|
|
|
FCaretY := Y;
|
|
FPrevCaretText := Caret.LineText;
|
|
{$ifdef debug_FC_line_changed}
|
|
with GetParentForm(self.SynEdit) do
|
|
Caption:= Caret.LineText;
|
|
{$endif}
|
|
end;
|
|
|
|
|
|
|
|
end.
|
|
|