{$ifdef nn}begin end;{$endif}

function TATSynEdit.IsLineFolded(ALine: integer; ADetectPartialFold: boolean): boolean;
var
  Flag: integer;
begin
  if not Strings.IsIndexValid(ALine) then
  begin
    Result:= false;
    Exit;
  end;

  Flag:= Strings.LinesHidden[ALine, FEditorIndex];
  Result:= (Flag=-1) or (ADetectPartialFold and (Flag>0));
end;

function TATSynEdit.IsLineFoldedFull(ALine: integer): boolean;
begin
  Result:= IsLineFolded(ALine, false);
end;

function TATSynEdit.GetFirstUnfoldedLineNumber: integer;
begin
  Result:= GetNextUnfoldedLineNumber(0, true);
end;

function TATSynEdit.GetLastUnfoldedLineNumber: integer;
begin
  Result:= GetNextUnfoldedLineNumber(Strings.Count-1, false);
end;

function TATSynEdit.GetNextUnfoldedLineNumber(ALine: integer; ADown: boolean): integer;
var
  N: integer;
begin
  Result:= ALine;
  N:= Result;
  while IsLineFolded(N) and Strings.IsIndexValid(N) do
    N:= N+BoolToPlusMinusOne(ADown);
  if Strings.IsIndexValid(N) then Result:= N;
end;

function TATSynEdit.IsPosFolded(AX, AY: integer): boolean;
begin
  Result:= Strings.IsPosFolded(AX, AY, FEditorIndex);
end;

(*
example of CPP file which is hard to unfold (if nested ranges folded).
 {
 d1
   {
   d2a
   }
   {
   d2b
     {
     d3a
     }
     {
     d3b
       {
       d4a
       }
       {
       d4b
       }
     }
   }
 }

what we do. for each line in range:
a) if line not in any subrange, show it
b) for all subranges at top level:
  b1) if subrange marked folded, unfold 1st line "[...]"
  b2) if subrange marked unfolded, recursion
*)
procedure TATSynEdit.DoRangeUnfold(ARange: TATSynRange);
var
  List: TATIntArray;
  R: TATSynRange;
  i, j: integer;
  InSubrange: boolean;
begin
  ARange.Folded:= false;
  FWrapUpdateNeeded:= true;

  List:= FFold.FindRangesContainingLines(-1, -1, ARange,
    false{OnlyFolded}, true{TopLevel}, cRngIgnore);

  //show all lines not in subranges
  for i:= ARange.Y to ARange.Y2 do
  begin
    InSubrange:= false;
    for j:= Low(List) to High(List) do
      if FFold[List[j]].IsLineInside(i) then
      begin
        InSubrange:= true;
        Break
      end;

    if not InSubrange then
      Strings.LinesHidden[i, FEditorIndex]:= 0;
  end;

  //unfold subranges, resursion
  for i:= Low(List) to High(List) do
  begin
    R:= FFold[List[i]];
    if R.Folded then
      Strings.LinesHidden[R.Y, FEditorIndex]:= R.X
    else
      DoRangeUnfold(R);
  end;
end;

procedure TATSynEdit.DoRangeFold(ARange: TATSynRange);
var
  i: integer;
begin
  ARange.Folded:= true;
  FWrapUpdateNeeded:= true;

  //partially fold 1st line
  if ARange.Hint<>'' then
  begin
    Strings.LinesHidden[ARange.Y, FEditorIndex]:= ARange.X;
  end
  else
  case FFoldStyle of
    cFoldHereWithDots:
    begin
      Strings.LinesHidden[ARange.Y, FEditorIndex]:= ARange.X;
    end;
    cFoldHereWithTruncatedText:
    begin
      Strings.LinesHidden[ARange.Y, FEditorIndex]:= ARange.X;
      ARange.Hint:= Copy(Strings.Lines[ARange.Y], ARange.X, cFoldedLenOfEmptyHint)+'...';
    end;
    cFoldFromEndOfLine:
    begin
      Strings.LinesHidden[ARange.Y, FEditorIndex]:= Length(Strings.Lines[ARange.Y])+1;
    end;
    cFoldFromNextLine:
    begin
      //don't fold line
    end;
  end;

  //fully fold next lines
  for i:= ARange.Y+1 to ARange.Y2 do
    Strings.LinesHidden[i, FEditorIndex]:= -1;
end;


procedure TATSynEdit.DoUnfoldLine(ALine: integer);
var
  List: TATIntArray;
  i: integer;
begin
  List:= FFold.FindRangesContainingLines(ALine, ALine, nil,
    true{OnlyFolded}, false{TopLevelOnly}, cRngHasAllLines);
  for i:= Low(List) to High(List) do
    DoRangeUnfold(FFold[List[i]]);
end;

procedure TATSynEdit.DoFoldbarClick(ALine: integer);
var
  R: TATSynRange;
begin
  R:= FFold.FindRangeWithPlusAtLine(ALine);
  if Assigned(R) then
  begin
    if R.Folded then
      DoRangeUnfold(R)
    else
      DoRangeFold(R);
    Update;
  end;
end;


function TATSynEdit.GetFoldedMarkText(ALine: integer): string;
var
  R: TATSynRange;
begin
  Result:= '';
  R:= FFold.FindRangeWithPlusAtLine(ALine);
  if Assigned(R) then
    Result:= R.Hint;
  if Result='' then
    Result:= '...';
end;

procedure TATSynEdit.UpdateFoldedFromLinesHidden;
var
  i, j: integer;
  N: integer;
  R: TATSynRange;
begin
  for i:= 0 to Strings.Count-1 do
  begin
    N:= Strings.LinesHidden[i, FEditorIndex];
    if N<=0 then Continue;

    for j:= 0 to Fold.Count-1 do
    begin
      R:= Fold.Items[j];
      if (R.Y>i) then Break;
      if (R.Y=i) and (R.X=N) then
      begin
        DoRangeFold(R); //do not just R.Folded:= true;
        Break
      end;
    end;
  end;
end;

function TATSynEdit.IsFoldLineNeededBeforeWrapitem(N: integer): boolean;
var
  NLineCur, NLinePrev: integer;
begin
  if FWrapInfo.IsIndexValid(N) and (N>0) then
  begin
    NLineCur:= FWrapInfo.Items[N].NLineIndex;
    NLinePrev:= FWrapInfo.Items[N-1].NLineIndex;
    //before this line some is skipped
    Result:= NLineCur-NLinePrev > 1;
    //and prev visible line is fully visible
    if Result then
      Result:= Strings.LinesHidden[NLinePrev, FEditorIndex]=0;
  end
  else
    Result:= false;
end;

procedure TATSynEdit.DoMenuGutterFold;
var
  Menu: TPopupMenu;
  mi, miSub: TMenuItem;
  i: integer;
begin
  InitResourcesFoldbar;
  if FMenuGutterFoldStd=nil then
    FMenuGutterFoldStd:= TPopupMenu.Create(Self);

  Menu:= FMenuGutterFoldStd;
  Menu.Images:= FFoldImageList;
  Menu.Items.Clear;

  //items "fold all", "unfold all"
  mi:= TMenuItem.Create(Self);
  mi.Caption:= cStrMenuItemFoldAll;
  mi.OnClick:= @MenuFoldFoldAllClick;
  mi.Enabled:= Fold.Count>0;
  Menu.Items.Add(mi);

  mi:= TMenuItem.Create(Self);
  mi.Caption:= cStrMenuItemUnfoldAll;
  mi.OnClick:= @MenuFoldUnfoldAllClick;
  mi.Enabled:= Fold.Count>0;
  Menu.Items.Add(mi);

  //submenu "fold level"
  miSub:= TMenuItem.Create(Self);
  miSub.Caption:= cStrMenuItemFoldLevel;
  miSub.Enabled:= Fold.Count>0;
  Menu.Items.Add(miSub);

  for i:= 2 to 9 do
  begin
    mi:= TMenuItem.Create(Self);
    mi.Caption:= Inttostr(i);
    mi.Tag:= i-1;
    mi.OnClick:=@MenuFoldLevelClick;
    miSub.Add(mi);
  end;

  //dynamic items [+], [-]
  DoMenuGutterFold_AddDynamicItems(Menu);

  Menu.Popup;
end;


procedure TATSynEdit.DoMenuGutterFold_AddDynamicItems(Menu: TPopupMenu);
var
  Pnt: TPoint;
  AtEnd: boolean;
  NLine: integer;
  IntList: TATIntArray;
  Rng: TATSynRange;
  mi: TMenuItem;
  i: integer;
begin
  //calc ranges for curr line
  Pnt:= ScreenToClient(Mouse.CursorPos);
  Pnt:= ClientPosToCaretPos(Pnt, AtEnd);
  NLine:= Pnt.Y;
  if NLine<0 then Exit;

  IntList:= Fold.FindRangesContainingLines(NLine, NLine, nil,
    false{OnlyFolded}, false{TopLevel}, cRngHasAllLines);
  if Length(IntList)=0 then Exit;

  //separator
  mi:= TMenuItem.Create(Self);
  mi.Caption:= '-';
  Menu.Items.Add(mi);

  //items for ranges for current line
  for i:= 0 to High(IntList) do
  begin
    Rng:= Fold[IntList[i]];
    mi:= TMenuItem.Create(Self);
    mi.Tag:= ptrint(Rng);
    mi.OnClick:= @MenuFoldPlusMinusClick;

    mi.Caption:=
      cHintScrollPrefix+' '+Inttostr(Rng.Y+1)+': '+
      UTF8Encode(Copy(Strings.Lines[Rng.Y], 1, cFoldedLenOfEmptyHint));

    if Rng.Folded then
      mi.ImageIndex:= 0
    else
      mi.ImageIndex:= 1;

    Menu.Items.Add(mi);
  end;
end;

procedure TATSynEdit.InitResourcesFoldbar;
begin
  if FFoldImageList=nil then
  begin
    FFoldImageList:= TImageList.Create(Self);
    FFoldImageList.Width:= 12;
    FFoldImageList.Height:= 12;
    FFoldImageList.AddResourceName(HInstance, 'FOLDBAR_P');
    FFoldImageList.AddResourceName(HInstance, 'FOLDBAR_M');
  end;
end;


procedure TATSynEdit.MenuFoldPlusMinusClick(Sender: TObject);
var
  Rng: TATSynRange;
begin
  Rng:= TATSynRange((Sender as TComponent).Tag);
  if Rng.Folded then
    DoRangeUnfold(Rng)
  else
    DoRangeFold(Rng);
  Update;
end;

procedure TATSynEdit.MenuFoldFoldAllClick(Sender: TObject);
begin
  DoCommand(cCommand_FoldAll);
end;

procedure TATSynEdit.MenuFoldLevelClick(Sender: TObject);
begin
  DoFoldForLevel((Sender as TComponent).Tag);
end;

procedure TATSynEdit.MenuFoldUnfoldAllClick(Sender: TObject);
begin
  DoCommand(cCommand_UnfoldAll);
end;


procedure TATSynEdit.DoFoldForLevelAndLines(ALineFrom, ALineTo: integer;
  ALevel: integer; AForThisRange: TATSynRange);
var
  List: TATIntArray;
  R: TATSynRange;
  i: integer;
begin
  //this func recursive. it calls itself with ALevel-1.
  //folds ranges if ALevel=0, else goes to subranges until found ALevel=0.
  if ALevel<0 then exit;

  List:= Fold.FindRangesContainingLines(ALineFrom, ALineTo, AForThisRange,
    false{OnlyFolded}, true{TopLevel}, cRngExceptThisRange);

  for i:= Low(List) to High(List) do
  begin
    R:= Fold.Items[List[i]];
    if R.IsSimple then Continue;
    if R.Folded then Continue;
    if ALevel=0 then
      DoRangeFold(R)
    else
      DoFoldForLevelAndLines(R.Y, R.Y2, ALevel-1, R);
  end;
end;

procedure TATSynEdit.DoFoldForLevel(ALevel: integer);
begin
  DoCommand(cCommand_UnfoldAll);
  DoFoldForLevelAndLines(0, Strings.Count-1, ALevel, nil);
  Update;
end;

