diff --git a/.gitattributes b/.gitattributes index 2c256bb96e..26dbff3d69 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1333,6 +1333,7 @@ components/synedit/synedit.inc svneol=native#text/pascal components/synedit/synedit.pp svneol=native#text/pascal components/synedit/syneditautocomplete.pp svneol=native#text/pascal components/synedit/syneditexport.pas svneol=native#text/pascal +components/synedit/syneditfoldedview.pp svneol=native#text/plain components/synedit/synedithighlighter.pp svneol=native#text/pascal components/synedit/syneditkeycmds.pp svneol=native#text/pascal components/synedit/syneditlazdsgn.lrs svneol=native#text/pascal diff --git a/components/synedit/synedit.pp b/components/synedit/synedit.pp index 09074e0f9d..eb4755a048 100644 --- a/components/synedit/synedit.pp +++ b/components/synedit/synedit.pp @@ -76,6 +76,7 @@ uses {$ifdef SYN_LAZARUS} SynEditMarkup, SynEditMarkupHighAll, SynEditMarkupBracket, SynEditMarkupCtrlMouseLink, SynEditMarkupSpecialLine, SynEditMarkupSelection, + SynEditFoldedView, {$ENDIF} SynEditMiscClasses, SynEditTextBuffer, SynEditHighlighter, SynTextDrawer; @@ -387,7 +388,6 @@ type fCaretX: Integer; // physical position (screen) {$IFDEF SYN_LAZARUS} fCtrlMouseActive: boolean; - FCFDividerDrawLevel: Integer; fMarkupManager : TSynEditMarkupManager; fMarkupHighAll : TSynEditMarkupHighlightAll; fMarkupBracket : TSynEditMarkupBracket; @@ -416,6 +416,7 @@ type fHighlighterNeedsUpdateEndLine: integer; // 1 based, 0 means invalid fBeautifier: TSynCustomBeautifier; fExtraCharSpacing: integer; + fTextView : TSynEditFoldedView; {$ENDIF} fLines: TStrings; fLinesInWindow: Integer;// MG: fully visible lines in window @@ -528,6 +529,7 @@ type function AdjustPhysPosToCharacterStart(Line: integer; PhysPos: integer): integer; function GetLogicalCaretXY: TPoint; procedure SetCFDividerDrawLevel(const AValue: Integer); + function GetCFDividerDrawLevel : Integer; procedure SetLogicalCaretXY(const NewLogCaretXY: TPoint); procedure SetBeautifier(NewBeautifier: TSynCustomBeautifier); {$ENDIF} @@ -666,6 +668,8 @@ type procedure ListPutted(Index: integer); {$IFDEF SYN_LAZARUS} procedure FoldChanged(Index: integer); + function GetTopView : Integer; + procedure SetTopView(const AValue : Integer); {$ENDIF} procedure ListScanRanges(Sender: TObject); procedure Loaded; override; @@ -736,6 +740,8 @@ type function GetSelStart: integer; procedure SetSelEnd(const Value: integer); procedure SetSelStart(const Value: integer); + property TextView : TSynEditFoldedView read fTextView; + property TopView: Integer read GetTopView write SetTopView; // TopLine converted into Visible(View) lines {$ENDIF} public procedure FindMatchingBracket; virtual; @@ -961,7 +967,7 @@ type {$IFDEF SYN_LAZARUS} property TabChar: char read FTabChar write SetTabChar; property CFDividerDrawLevel: Integer - read FCFDividerDrawLevel write SetCFDividerDrawLevel; + read GetCFDividerDrawLevel write SetCFDividerDrawLevel; {$ENDIF} property TabWidth: integer read fTabWidth write SetTabWidth default 8; property WantTabs: boolean read fWantTabs write SetWantTabs default FALSE; @@ -1266,15 +1272,11 @@ begin end; function TCustomSynEdit.ScreenRowToRow(ScreenRow: integer): integer; +// ScreenRow is 0-base +// result is 1-based begin - Result:=TopLine; - if ScreenRow>LinesInWindow+1 then ScreenRow:=LinesInWindow+1; - while ScreenRow>0 do begin - inc(Result); - if (Result>Lines.Count) - or (not TSynEditStringList(fLines).Folded[Result-1]) then - dec(ScreenRow); - end; + Result := fTextView.ScreenLineToTextIndex(ScreenRow)+1; +// DebugLn(['=== SrceenRow TO Row In:',ScreenRow,' out:',Result, ' topline=',TopLine, ' view topline=',fTextView.TopLine]); end; function TCustomSynEdit.RowToScreenRow(PhysicalRow: integer): integer; @@ -1283,18 +1285,11 @@ function TCustomSynEdit.RowToScreenRow(PhysicalRow: integer): integer; // Max(0,LinesInWindow-1) for the last fully visible line // and returns LinesInWindow for lines below visible screen including the // partially visible line at the bottom -var - i: LongInt; begin - if PhysicalRowLines.Count) - or (not TSynEditStringList(fLines).Folded[i-1]) then - inc(Result); - inc(i); - end; + Result := fTextView.TextIndexToScreenLine(PhysicalRow-1); + if Result < -1 then Result := -1; + if Result > LinesInWindow then Result := LinesInWindow; +// DebugLn(['=== Row TO ScreenRow In:',PhysicalRow,' out:',Result]); end; {$ENDIF} @@ -1305,7 +1300,11 @@ function TCustomSynEdit.RowColumnToPixels( begin Result:=RowCol; Result.X := (Result.X - 1) * fCharWidth + fTextOffset; + {$IFDEF SYN_LAZARUS} + Result.Y := RowToScreenRow(fCaretY) * fTextHeight + 1; + {$ELSE} Result.Y := (Result.Y - fTopLine) * fTextHeight + 1; + {$ENDIF} end; procedure TCustomSynEdit.ComputeCaret(X, Y: Integer); @@ -1447,6 +1446,10 @@ begin {begin} //mh 2000-10-10 // fLines := TSynEditList.Create; fLines := TSynEditStringList.Create; + {$IFDEF SYN_LAZARUS} + fTextView := TSynEditFoldedView.Create(TSynEditStringList(fLines)); + fTextView.OnFoldChanged := {$IFDEF FPC}@{$ENDIF}FoldChanged; + {$ENDIF} // with TSynEditList(fLines) do begin with TSynEditStringList(fLines) do begin OnAdded := {$IFDEF FPC}@{$ENDIF}ListAdded; @@ -1455,9 +1458,6 @@ begin OnCleared := {$IFDEF FPC}@{$ENDIF}ListCleared; OnDeleted := {$IFDEF FPC}@{$ENDIF}ListDeleted; OnInserted := {$IFDEF FPC}@{$ENDIF}ListInserted; - {$IFDEF SYN_LAZARUS} - OnFoldChanged := {$IFDEF FPC}@{$ENDIF}FoldChanged; - {$ENDIF} OnPutted := {$IFDEF FPC}@{$ENDIF}ListPutted; // OnScanRanges := {$IFDEF FPC}@{$ENDIF}ListScanRanges; end; @@ -1560,6 +1560,9 @@ begin fTabWidth := 8; fLeftChar := 1; fTopLine := 1; + {$IFDEF SYN_LAZARUS} + fTextView.TopLine := 1; + {$ENDIF} fCaretX := 1; fLastCaretX := 1; //mh 2000-10-19 fCaretY := 1; @@ -1655,6 +1658,7 @@ begin {$IFDEF SYN_LAZARUS} if HandleAllocated then LCLIntf.DestroyCaret(Handle); Beautifier:=nil; + FreeAndNil(fTextView); {$ENDIF} Highlighter := nil; // free listeners while other fields are still valid @@ -1733,11 +1737,7 @@ end; function TCustomSynEdit.CaretYPix: Integer; begin - {$IFDEF SYN_LAZARUS} - Result := RowToScreenRow(fCaretY) * fTextHeight + 1; - {$ELSE} Result := RowColumnToPixels(Point(1, fCaretY)).Y; - {$ENDIF} end; procedure TCustomSynEdit.FontChanged(Sender: TObject); @@ -1854,9 +1854,12 @@ end; procedure TCustomSynEdit.SetCFDividerDrawLevel(const AValue: Integer); begin - if FCFDividerDrawLevel = AValue then - Exit; //==> - FCFDividerDrawLevel := AValue; + fTextView.CFDividerDrawLevel := AValue; +end; + +function TCustomSynEdit.GetCFDividerDrawLevel : Integer; +begin + Result := fTextView.CFDividerDrawLevel; end; procedure TCustomSynEdit.SetLogicalCaretXY(const NewLogCaretXY: TPoint); @@ -2664,6 +2667,15 @@ begin {$ELSE}CaretXY{$ENDIF}); end; if fScrollDeltaY <> 0 then begin + {$IFDEF SYN_LAZARUS} + if GetKeyState(VK_SHIFT) < 0 then + TopView := TopView + fScrollDeltaY * LinesInWindow + else + TopView := TopView + fScrollDeltaY; + if fScrollDeltaY > 0 + then Y := fTextView.TextIndex[LinesInWindow-1]+1 // scrolling down + else Y := TopLine; // scrolling up + {$ELSE} if GetKeyState(VK_SHIFT) < 0 then TopLine := TopLine + fScrollDeltaY * LinesInWindow else @@ -2671,6 +2683,7 @@ begin Y := TopLine; if fScrollDeltaY > 0 then // scrolling down? Inc(Y, LinesInWindow - 1); + {$ENDIF} CaretXY := Point(C.X, Y); {$IFDEF SYN_LAZARUS} if (not(sfIsDragging in fStateFlags)) @@ -2844,17 +2857,17 @@ begin (rcClip.Right - fGutterWidth - 2 + CharWidth - 1) div CharWidth; // lines nL1 := Max({$IFDEF SYN_LAZARUS} - ScreenRowToRow(rcClip.Top div fTextHeight) + rcClip.Top div fTextHeight, 0 {$ELSE} - TopLine + rcClip.Top div fTextHeight - {$ENDIF}, - TopLine); - nL2 := Min({$IFDEF SYN_LAZARUS} - ScreenRowToRow((rcClip.Bottom-1) div fTextHeight), - {$ELSE} - TopLine + (rcClip.Bottom + fTextHeight - 1) div fTextHeight, + TopLine + rcClip.Top div fTextHeight, TopLine {$ENDIF} - Lines.Count); + ); + nL2 := Min({$IFDEF SYN_LAZARUS} + (rcClip.Bottom-1) div fTextHeight, fTextView.Count - fTextView.TopLine + {$ELSE} + TopLine + (rcClip.Bottom + fTextHeight - 1) div fTextHeight, Lines.Count + {$ENDIF} + ); //DebugLn('TCustomSynEdit.Paint LinesInWindow=',dbgs(LinesInWindow),' nL1=',dbgs(nL1),' nL2=',dbgs(nL2)); // Now paint everything while the caret is hidden. HideCaret; @@ -2890,13 +2903,13 @@ end; {$IFDEF SYN_LAZARUS} procedure TCustomSynEdit.CodeFoldAction(iLine: integer); // iLine is 1 based as parameter -// and 0 based in TSynEsitStringList begin if (iLine<=0) or (iLine>Lines.Count) then exit; dec(iLine); - case TSynEditStringList(fLines).FoldType[iLine] of - cfExpanded : TSynEditStringList(fLines).FoldLines(iLine); - cfCollapsed : TSynEditStringList(fLines).UnFoldLines(iLine); +//DebugLn(['****** FoldAction at ',iLine,' scrline=',fTextView.TextIndexToScreenLine(iLine), ' type ', SynEditCodeFoldTypeNames[fTextView.FoldType[fTextView.TextIndexToScreenLine(iLine)]], ' view topline=',fTextView.TopLine ]); + case fTextView.FoldType[fTextView.TextIndexToScreenLine(iLine)] of + cfCollapsed : fTextView.UnFoldAtTextIndex(iLine); + cfExpanded : fTextView.FoldAtTextIndex(iLine); end; end; @@ -2906,13 +2919,13 @@ function TCustomSynEdit.FindNextUnfoldedLine(iLine: integer; Down: boolean begin Result:=iLine; while (Result>0) and (Result<=Lines.Count) - and (TSynEditStringList(fLines).Folded[Result-1]) do + and (fTextView.FoldedAtTextIndex[Result-1]) do if Down then inc(Result) else dec(Result); end; procedure TCustomSynEdit.UnfoldAll; begin - TSynEditStringList(Lines).UnfoldAll; + fTextView.UnfoldAll; Invalidate; end; @@ -2940,12 +2953,9 @@ var begin iTop := 0; CurMark:=Marks[iMark]; - if (CurMark.Line>LastLine) or (CurMark.LineLines.Count) then - exit; - if TSynEditStringList(fLines).Folded[CurMark.Line-1] then - exit; - iLine := RowToScreenRow(CurMark.Line); + if (CurMark.Line<1) or (CurMark.Line>Lines.Count) then exit; + if fTextView.FoldedAtTextIndex[CurMark.Line-1] then exit; + iLine := fTextView.TextIndexToScreenLine(CurMark.Line-1); if Assigned(fBookMarkOpt.BookmarkImages) and not CurMark.InternalImage then begin @@ -3079,8 +3089,10 @@ var end; begin + {$IFNDEF SYN_LAZARUS} if (FirstLine = 1) and (LastLine = 0) then LastLine := 1; + {$ENDIF} // Changed to use fTextDrawer.BeginDrawing and fTextDrawer.EndDrawing only // when absolutely necessary. Note: Never change brush / pen / font of the // canvas inside of this block (only through methods of fTextDrawer)! @@ -3109,31 +3121,36 @@ begin rcLine := AClip; rcLine.Right := fGutterWidth - 2; //rcLine.Right := Max(rcLine.Right, fGutterWidth - 2); - rcLine.Bottom := RowToScreenRow(FirstLine) * fTextHeight; + {$IFDEF SYN_LAZARUS} + rcLine.Bottom := FirstLine * fTextHeight; + for i := FirstLine to LastLine do begin + iLine := fTextView.DisplayNumber[i]; + // next line rect + rcLine.Top := rcLine.Bottom; + // Must show a dot instead of line number if + // line number is not the first, the last, the current line + // or a multiple of Gutter.ShowOnlyLineNumbersMultiplesOf + ShowDot := ((iLine mod fGutter.ShowOnlyLineNumbersMultiplesOf) <> 0) + and (iLine <> CaretY) and (iLine <> 1) and (iLine <> Lines.Count); + // Get the formatted line number or dot + s := fGutter.FormatLineNumber(iLine, ShowDot); + Inc(rcLine.Bottom, fTextHeight); + // erase the background and draw the line number string in one go + fTextDrawer.ExtTextOut(CodeFoldOffset+fGutter.LeftOffset, + rcLine.Top, ETO_OPAQUE,rcLine,PChar(Pointer(S)),Length(S)); + end; + {$ELSE} + rcLine.Bottom := (FirstLine - TopLine) * fTextHeight; for iLine := FirstLine to LastLine do begin // next line rect rcLine.Top := rcLine.Bottom; - // erase the background and draw the line number string in one go - {$IFDEF SYN_LAZARUS} - if not TSynEditStringList(fLines).Folded[iLine-1] then begin - // Must show a dot instead of line number if - // line number is not the first, the last, the current line - // or a multiple of Gutter.ShowOnlyLineNumbersMultiplesOf - ShowDot := ((iLine mod fGutter.ShowOnlyLineNumbersMultiplesOf) <> 0) - and (iLine <> CaretY) and (iLine <> 1) and (iLine <> Lines.Count); - // Get the formatted line number or dot - s := fGutter.FormatLineNumber(iLine, ShowDot); - Inc(rcLine.Bottom, fTextHeight); - fTextDrawer.ExtTextOut(CodeFoldOffset+fGutter.LeftOffset, - rcLine.Top, ETO_OPAQUE,rcLine,PChar(Pointer(S)),Length(S)); - end; - {$ELSE} s := fGutter.FormatLineNumber(iLine, false); Inc(rcLine.Bottom, fTextHeight); + // erase the background and draw the line number string in one go Windows.ExtTextOut(DC, fGutter.LeftOffset, rcLine.Top, ETO_OPAQUE, @rcLine, PChar(s), Length(s), nil); - {$ENDIF} end; + {$ENDIF} // now erase the remaining area if any if AClip.Bottom > rcLine.Bottom then begin rcLine.Top := rcLine.Bottom; @@ -3156,30 +3173,26 @@ begin Pen.Color := clDkGray; Pen.Width := 1; - rcLine.Bottom := RowToScreenRow(FirstLine) * fTextHeight; + rcLine.Bottom := FirstLine * fTextHeight; for iLine := FirstLine to LastLine do begin - //only draw visible items - if not TSynEditStringList(fLines).Folded[iLine-1] then - begin - // next line rect - rcLine.Top := rcLine.Bottom; + // next line rect + rcLine.Top := rcLine.Bottom; + Inc(rcLine.Bottom, fTextHeight); - Inc(rcLine.Bottom, fTextHeight); + rcCodeFold.Left := 0; + rcCodeFold.Right := 14; + rcCodeFold.Top := rcLine.Top; + rcCodeFold.Bottom := rcLine.Bottom; - rcCodeFold.Left := 0; - rcCodeFold.Right := 14; - rcCodeFold.Top := rcLine.Top; - rcCodeFold.Bottom := rcLine.Bottom; +//DebugLn(['** GUTTER at ',iLine,' scrline=',fTextView.TextIndexToScreenLine(iLine-1),' type ', SynEditCodeFoldTypeNames[fTextView.FoldType[fTextView.TextIndexToScreenLine(iLine-1)]]]); + tmp := fTextView.FoldType[iLine]; - tmp := TSynEditStringList(fLines).FoldType[iLine-1]; - - case tmp of - cfCollapsed: DrawNodeBox(rcCodeFold, True); - cfExpanded: DrawNodeBox(rcCodeFold, False); - cfContinue: DrawParagraphContinue(rcCodeFold); - cfEnd: DrawParagraphEnd(rcCodeFold); - end; + case tmp of + cfCollapsed: DrawNodeBox(rcCodeFold, True); + cfExpanded: DrawNodeBox(rcCodeFold, False); + cfContinue: DrawParagraphContinue(rcCodeFold); + cfEnd: DrawParagraphEnd(rcCodeFold); end; end; end; @@ -3202,13 +3215,17 @@ begin if BookMarkOptions.GlyphsVisible and (Marks.Count > 0) and (LastLine >= FirstLine) then begin - aGutterOffs := AllocMem((LastLine - TopLine + 1) * SizeOf(integer)); + aGutterOffs := AllocMem((LastLine+1{$IFNDEF SYN_LAZARUS}-TopLine{$ENDIF}) * SizeOf(integer)); try // Instead of making a two pass loop we look while drawing the bookmarks // whether there is any other mark to be drawn bHasOtherMarks := FALSE; for i := 0 to Marks.Count - 1 do with Marks[i] do + {$IFDEF SYN_LAZARUS} + if Visible and (Line >= fTextView.TextIndex[FirstLine]+1) and (Line <= fTextView.TextIndex[LastLine]+1) then + {$ELSE} if Visible and (Line >= FirstLine) and (Line <= LastLine) then + {$ENDIF} begin if IsBookmark <> BookMarkOptions.DrawBookmarksFirst then //mh 2000-10-12 bHasOtherMarks := TRUE @@ -3219,7 +3236,11 @@ begin for i := 0 to Marks.Count - 1 do with Marks[i] do begin if Visible and (IsBookmark <> BookMarkOptions.DrawBookmarksFirst) //mh 2000-10-12 + {$IFDEF SYN_LAZARUS} + and (Line >= fTextView.TextIndex[FirstLine]+1) and (Line <= fTextView.TextIndex[LastLine]+1) + {$ELSE} and (Line >= FirstLine) and (Line <= LastLine) + {$ENDIF} then DrawMark(i); end; @@ -3478,8 +3499,8 @@ var eolx := rcToken.Left; // remeber end of actual line, so we can decide to draw the right edge NextPos := Min(LastCol, TokenAccu.PhysicalEndPos+1); Repeat - MarkupInfo := fMarkupManager.GetMarkupAttributeAtRowCol(CurLine, NextPos); - NextPos := fMarkupManager.GetNextMarkupColAfterRowCol(CurLine, NextPos); + MarkupInfo := fMarkupManager.GetMarkupAttributeAtRowCol(fTextView.TextIndex[CurLine]+1, NextPos); + NextPos := fMarkupManager.GetNextMarkupColAfterRowCol(fTextView.TextIndex[CurLine]+1, NextPos); with fTextDrawer do if MarkupInfo = nil @@ -3573,7 +3594,7 @@ var PaintHighlightToken(FALSE); end; // Don't use AppendStr because it's more expensive. - //if (CurLine=fTopLine) then debugln(' -t-Accu len ',dbgs(TokenAccu.Len),' pstart ',dbgs(TokenAccu.PhysicalStartPos),' p-end ',dbgs(TokenAccu.PhysicalEndPos)); + //if (CurLine=TopLine) then debugln(' -t-Accu len ',dbgs(TokenAccu.Len),' pstart ',dbgs(TokenAccu.PhysicalStartPos),' p-end ',dbgs(TokenAccu.PhysicalEndPos)); if bCanAppend then begin if (TokenAccu.Len + TokenLen > TokenAccu.MaxLen) then begin TokenAccu.MaxLen := TokenAccu.Len + TokenLen + 32; @@ -3670,7 +3691,7 @@ var {TODO: cache NextPhysPos, and MarkupInfo between 2 calls } while (nTokenByteLen > 0) do begin // Calculate Token Sublen for current Markup - NextPhysPos := fMarkupManager.GetNextMarkupColAfterRowCol(CurLine, PhysicalStartPos); + NextPhysPos := fMarkupManager.GetNextMarkupColAfterRowCol(fTextView.TextIndex[CurLine]+1, PhysicalStartPos); if NextPhysPos < 1 then SubCharLen := TokenCharLen else SubCharLen := NextPhysPos - PhysicalStartPos; @@ -3692,7 +3713,7 @@ var BG := DefaultBGCol; FG := DefaultFGCol; Style := DefaultStyle; - MarkupInfo := fMarkupManager.GetMarkupAttributeAtRowCol(CurLine, PhysicalStartPos); + MarkupInfo := fMarkupManager.GetMarkupAttributeAtRowCol(fTextView.TextIndex[CurLine]+1, PhysicalStartPos); if assigned(MarkupInfo) then MarkupInfo.ModifyColors(FG, BG, Style); // Deal with equal colors @@ -3724,7 +3745,7 @@ var // Initialize rcLine for drawing. Note that Top and Bottom are updated // inside the loop. Get only the starting point for this. rcLine := AClip; - rcLine.Bottom := RowToScreenRow(FirstLine) * fTextHeight; + rcLine.Bottom := FirstLine * fTextHeight; // Make sure the token accumulator string doesn't get reassigned to often. if Assigned(fHighlighter) then begin TokenAccu.MaxLen := Max(128, fCharsInWindow * 4); @@ -3736,15 +3757,9 @@ var while CurLine skip - //debugln('line folded ',dbgs(CurLine)); - continue; - end; - - fMarkupManager.PrepareMarkupForRow(CurLine); + fMarkupManager.PrepareMarkupForRow(fTextView.TextIndex[CurLine]+1); // Get the line. - sLine := Lines[CurLine - 1]; + sLine := fTextView[CurLine]; // Update the rcLine rect to this line. rcLine.Top := rcLine.Bottom; Inc(rcLine.Bottom, fTextHeight); @@ -3760,8 +3775,8 @@ var // Initialize highlighter with line text and range info. It is // necessary because we probably did not scan to the end of the last // line - the internal highlighter range might be wrong. - fHighlighter.SetRange(TSynEditStringList(Lines).Ranges[CurLine - 1]); //mh 2000-10-10 - fHighlighter.SetLine(sLine, CurLine - 1); + fHighlighter.SetRange(fTextView.Ranges[CurLine]); //mh 2000-10-10 + fHighlighter.SetLine(sLine, fTextView.TextIndex[CurLine]); // Try to concatenate as many tokens as possible to minimize the count // of ExtTextOut calls necessary. This depends on the selection state // or the line having special colors. For spaces the foreground color @@ -3782,12 +3797,11 @@ var // of the invalid area with the correct colors. PaintHighlightToken(TRUE); - fMarkupManager.FinishMarkupForRow(CurLine); + fMarkupManager.FinishMarkupForRow(fTextView.TextIndex[CurLine]+1); // codefold draw splitter line - if Gutter.ShowCodeFolding and (CurLine>=0) - and (TSynEditStringList(Lines).FoldType[CurLine-1] in [cfEnd]) - and (TSynEditStringList(Lines).FoldEndLevel[CurLine-1] < CFDividerDrawLevel) then + if Gutter.ShowCodeFolding + and (fTextView.DrawDivider[curLine]) then begin ypos := rcToken.Bottom - 1; LCLIntf.MoveToEx(dc, nRightEdge, ypos, nil); @@ -3877,7 +3891,7 @@ begin // If there is anything visible below the last line, then fill this as well. rcToken := AClip; - rcToken.Top := (RowToScreenRow(LastLine)+1) * fTextHeight; + rcToken.Top := (LastLine+1) * fTextHeight; if (rcToken.Top < rcToken.Bottom) then begin SetBkColor(dc, ColorToRGB(colEditorBG)); InternalFillRect(dc, rcToken); @@ -3888,9 +3902,8 @@ begin end; // codefold draw splitter line - if Gutter.ShowCodeFolding and (LastLine TopLine then begin + {$IFDEF SYN_LAZARUS} + if fTextView.FoldedAtTextIndex[Value-1] then + Value := FindNextUnfoldedLine(Value, False); + {$ENDIF} + if Value <> fTopLine then begin {$ifdef SYN_LAZARUS} OldTopLine:=TopLine; fTopLine := Value; + fTextView.TopTextIndex := Value-1; UpdateScrollBars; Delta := OldTopLine - TopLine; if (Abs(Delta) < fLinesInWindow) and not (sfPainting in fStateFlags) @@ -5593,7 +5615,7 @@ begin {$ENDIF} end; if fScrollBars in [ssBoth, ssVertical] then begin - nMaxScroll := Lines.Count{$IFDEF SYN_LAZARUS}+1{$ENDIF}; + nMaxScroll := {$IFDEF SYN_LAZARUS}fTextView.Count+1{$ELSE}Lines.Count{$ENDIF}; if (eoScrollPastEof in Options) then Inc(nMaxScroll, LinesInWindow - 1); {$IFNDEF SYN_LAZARUS} @@ -5601,7 +5623,11 @@ begin {$ENDIF} ScrollInfo.nMax := Max(1, nMaxScroll); ScrollInfo.nPage := LinesInWindow; + {$IFDEF SYN_LAZARUS} + ScrollInfo.nPos := fTextView.TextIndexToViewPos(TopLine-1); + {$ELSE} ScrollInfo.nPos := TopLine; + {$ENDIF} {$IFNDEF SYN_LAZARUS} end else begin ScrollInfo.nMin := 0; @@ -5778,6 +5804,15 @@ begin SB_TOP: TopLine := 1; SB_BOTTOM: TopLine := Lines.Count; // Scrolls one line up / down +{$IFNDEF SYN_LAZARUS} + SB_LINEDOWN: TopView := TopView + 1; + SB_LINEUP: TopView := TopView - 1; + // Scrolls one page of lines up / down + SB_PAGEDOWN: TopView := TopView + + (fLinesInWindow - Ord(eoScrollByOneLess in fOptions)); + SB_PAGEUP: TopView := TopView + - (fLinesInWindow - Ord(eoScrollByOneLess in fOptions)); +{$ELSE} SB_LINEDOWN: TopLine := TopLine + 1; SB_LINEUP: TopLine := TopLine - 1; // Scrolls one page of lines up / down @@ -5785,6 +5820,7 @@ begin + (fLinesInWindow - Ord(eoScrollByOneLess in fOptions)); SB_PAGEUP: TopLine := TopLine - (fLinesInWindow - Ord(eoScrollByOneLess in fOptions)); +{$ENDIF} // Scrolls to the current scroll bar position SB_THUMBPOSITION, SB_THUMBTRACK: @@ -5795,7 +5831,11 @@ begin MAX_SCROLL) else {$ENDIF} + {$IFDEF SYN_LAZARUS} + TopView := Msg.Pos; + {$ELSE} TopLine := Msg.Pos; + {$ENDIF} if eoShowScrollHint in fOptions then begin {$IFNDEF SYN_LAZARUS} @@ -5839,58 +5879,14 @@ end; function TCustomSynEdit.ScanFrom(Index: integer {$IFDEF SYN_LAZARUS}; AtLeastTilIndex: integer{$ENDIF}): integer; -{$IFDEF SYN_LAZARUS} // Index and AtLeastTilIndex are 0 based +{$IFDEF SYN_LAZARUS} var FixFStart: Integer; - procedure SetCodeFoldAttributes; - var - CodeFoldMinLevel: LongInt; - CodeFoldEndLevel: LongInt; - CodeFoldType: TSynEditCodeFoldType; - LastCodeFoldEndLevel: LongInt; - i : integer; - UnFoldLevel: LongInt; begin - CodeFoldMinLevel:=fHighlighter.MinimumCodeFoldBlockLevel; - CodeFoldEndLevel:=fHighlighter.CurrentCodeFoldBlockLevel; - CodeFoldType:=cfNone; - if CodeFoldEndLevel > CodeFoldMinLevel then begin - // block started (and not closed in the same line) - if TSynEditStringList(Lines).FoldType[Result-1] = cfCollapsed - then CodeFoldType := cfCollapsed - else CodeFoldType := cfExpanded; - //debugln(['TCustomSynEdit.ScanFrom Block started Y=',Result,' MinLevel=',CodeFoldMinLevel,' EndLevel=',CodeFoldEndLevel,' CodeFoldType=',ord(CodeFoldType),' Line="',Lines[Result-1],'"']); - end else if (Result>1) then begin - LastCodeFoldEndLevel:=TSynEditStringList(Lines).FoldEndLevel[Result-2]; - if LastCodeFoldEndLevel>CodeFoldMinLevel then begin - // block closed - CodeFoldType:=cfEnd; - end else if CodeFoldEndLevel>0 then begin - // block continuing - CodeFoldType:=cfContinue; - end; - end; - // check if an cfEnd got removed - if (TSynEditStringList(Lines).FoldType[Result-1] = cfEnd) - and not(CodeFoldType = cfEnd) and (Result >= 2) then begin - // unfolde it; do not trust Folded[], as Fixfolded has not yet run - i := Result - 2; - UnFoldLevel := TSynEditStringList(Lines).FoldEndLevel[i]; - while (i >= 0) - and (TSynEditStringList(Lines).FoldMinLevel[i] >= UnFoldLevel) do - dec(i); - if TSynEditStringList(Lines).FoldType[i] = cfCollapsed then begin - TSynEditStringList(Lines).FoldType[i] := cfExpanded; - if i < FixFStart then FixFStart := i; - end; - end; - - //DebugLn(['TCustomSynEdit.ScanFrom CodeFoldType=',SynEditCodeFoldTypeNames[CodeFoldType],' FoldMinLevel=',CodeFoldMinLevel,' FoldEndLevel=',CodeFoldEndLevel,' Folded=',false]); - TSynEditStringList(Lines).FoldMinLevel[Result-1] := CodeFoldMinLevel; - TSynEditStringList(Lines).FoldEndLevel[Result-1] := CodeFoldEndLevel; - TSynEditStringList(Lines).FoldType[Result-1] := CodeFoldType; + TSynEditStringList(Lines).FoldMinLevel[Result-1] := fHighlighter.MinimumCodeFoldBlockLevel; + TSynEditStringList(Lines).FoldEndLevel[Result-1] := fHighlighter.CurrentCodeFoldBlockLevel; end; {$ENDIF} @@ -5930,11 +5926,10 @@ begin // => update code fold attributes of last scanned line if (Result>Index+1) and (Result<=Lines.Count) then SetCodeFoldAttributes; - TSynEditStringList(Lines).FixFolding(FixFStart, Result); - if TSynEditStringList(fLines).Folded[fCaretY - 1] then - TSynEditStringList(fLines).UnFoldLines(fCaretY - 1); - if TSynEditStringList(Lines).Folded[TopLine] then - TopLine := FindNextUnfoldedLine(TopLine, False); + fTextView.FixFoldingAtTextIndex(FixFStart, Result); + if fTextView.FoldedAtTextIndex[fCaretY - 1] then + fTextView.UnFoldAtTextIndex(fCaretY - 1); + Topline := TopLine; if FixFStart < index then Invalidate; {$ENDIF} Dec(Result); @@ -6086,10 +6081,20 @@ procedure TCustomSynEdit.FoldChanged(Index : integer); begin if Index + 1 > ScreenRowToRow(LinesInWindow + 1) then exit; if Index + 1 < TopLine then Index := TopLine; - if TSynEditStringList(Lines).Folded[TopLine] then - TopLine := FindNextUnfoldedLine(TopLine, False); + TopLine := TopLine; InvalidateLines(Index + 1, ScreenRowToRow(LinesInWindow + 1)); InvalidateGutterLines(Index + 1, ScreenRowToRow(LinesInWindow + 1)); + UpdateScrollBars; +end; + +procedure TCustomSynEdit.SetTopView(const AValue : Integer); +begin + TopLine := fTextView.ViewPosToTextIndex(AValue)+1; +end; + +function TCustomSynEdit.GetTopView : Integer; +begin + Result := fTextView.TextIndexToViewPos(TopLine-1); end; {$ENDIF} @@ -7381,7 +7386,6 @@ var {$IFDEF SYN_LAZARUS} MinX: Integer; MaxX: Integer; - i, Y : integer; PhysBlockBeginXY: TPoint; PhysBlockEndXY: TPoint; {$ENDIF} @@ -7438,14 +7442,8 @@ begin if CaretY < TopLine then TopLine := CaretY {$IFDEF SYN_LAZARUS} - else if CaretY > ScreenRowToRow(Max(1, LinesInWindow) - 1) then begin //mh 2000-10-19 - Y := CaretY; - for i:=1 to (Max(1, LinesInWindow) - 1) do begin - dec(Y); - Y:=FindNextUnfoldedLine(Y,false); - end; - TopLine:=Max(Y, 1); - end + else if CaretY > ScreenRowToRow(Max(1, LinesInWindow) - 1) then //mh 2000-10-19 + TopLine := fTextView.TextPosAddLines(CaretY, -Max(0, LinesInWindow-1)) {$ELSE} else if CaretY > TopLine + Max(1, LinesInWindow) - 1 then //mh 2000-10-19 TopLine := CaretY - (LinesInWindow - 1) @@ -7686,7 +7684,11 @@ begin Dec(counter); if (Command in [ecPageUp, ecSelPageUp]) then counter := -counter; + {$IFDEF SYN_LAZARUS} + TopView := TopView + counter; + {$ELSE} TopLine := TopLine + counter; + {$ENDIF} MoveCaretVert(counter, Command in [ecSelPageUp, ecSelPageDown]); Update; end; @@ -7753,7 +7755,7 @@ begin Caret := CaretXY; CaretNew := PrevWordPos; {$IFDEF SYN_LAZARUS} - if TSynEditStringList(fLines).Folded[CaretNew.Y - 1] then begin + if fTextView.FoldedAtTextIndex[CaretNew.Y - 1] then begin CY := FindNextUnfoldedLine(CaretNew.Y, False); CaretNew := LogicalToPhysicalPos(Point(1 + Length(fLines[CY-1]), CY)); end; @@ -7772,7 +7774,7 @@ begin Caret := CaretXY; CaretNew := NextWordPos; {$IFDEF SYN_LAZARUS} - if TSynEditStringList(fLines).Folded[CaretNew.Y - 1] then + if fTextView.FoldedAtTextIndex[CaretNew.Y - 1] then CaretNew := Point(1, FindNextUnfoldedLine(CaretNew.Y, True)); MoveCaretAndSelectionPhysical {$ELSE} @@ -8411,14 +8413,22 @@ begin end; ecScrollUp: begin + {$IFDEF SYN_LAZARUS} + TopView := TopView - 1; + {$ELSE} TopLine := TopLine - 1; + {$ENDIF} if CaretY > {$IFDEF SYN_LAZARUS}ScreenRowToRow(LinesInWindow-1){$ELSE}TopLine + LinesInWindow - 1{$ENDIF} then CaretY := {$IFDEF SYN_LAZARUS}ScreenRowToRow(LinesInWindow-1){$ELSE}TopLine + LinesInWindow - 1{$ENDIF}; Update; end; ecScrollDown: begin + {$IFDEF SYN_LAZARUS} + TopView := TopView + 1; + {$ELSE} TopLine := TopLine + 1; + {$ENDIF} if CaretY < TopLine then CaretY := TopLine; Update; @@ -8657,7 +8667,7 @@ begin {$ENDIF} LineLen := Length(Line); - if CX >= LineLen then begin + if CX >{$IFNDEF SYN_LAZARUS}={$ENDIF} LineLen then begin // find first IdentChar in the next line if CY < Lines.Count then begin Line := Lines[CY]; @@ -9086,7 +9096,11 @@ begin fMouseWheelAccumulator := fMouseWheelAccumulator mod WHEEL_DELTA; if (nDelta = integer(WHEEL_PAGESCROLL)) or (nDelta > LinesInWindow) then nDelta := LinesInWindow; + {$IFDEF SYN_LAZARUS} + TopView := TopView - (nDelta * nWheelClicks); + {$ELSE} TopLine := TopLine - (nDelta * nWheelClicks); + {$ENDIF} Update; end; @@ -9501,6 +9515,8 @@ begin fCharsInWindow := Max(1,(ClientWidth - fGutterWidth - 2 - ScrollBarWidth) div fCharWidth); fLinesInWindow := Max(0,ClientHeight - ScrollBarWidth) div Max(1,fTextHeight); + fTextView.LinesInWindow := fLinesInWindow; + fMarkupManager.LinesInWindow:= fLinesInWindow; //DebugLn('TCustomSynEdit.SizeOrFontChanged fLinesInWindow=',dbgs(fLinesInWindow),' ClientHeight=',dbgs(ClientHeight),' ',dbgs(fTextHeight)); //debugln('TCustomSynEdit.SizeOrFontChanged A ClientWidth=',dbgs(ClientWidth),' fGutterWidth=',dbgs(fGutterWidth),' ScrollBarWidth=',dbgs(ScrollBarWidth),' fCharWidth=',dbgs(fCharWidth),' fCharsInWindow=',dbgs(fCharsInWindow),' Width=',dbgs(Width)); @@ -9542,7 +9558,7 @@ begin NewCaret.X:=1 else begin // move to end of prev line - NewCaret.Y:=FindNextUnfoldedLine(NewCaret.Y-1, false); + NewCaret.Y:= fTextView.TextPosAddLines(NewCaret.Y, -1); s:=Lines[NewCaret.Y-1]; PhysicalLineLen:=LogicalToPhysicalPos(Point(length(s)+1,NewCaret.Y)).X-1; NewCaret.X:=PhysicalLineLen+1; @@ -9553,7 +9569,7 @@ begin if NewCaret.X>PhysicalLineLen+1 then begin // move to start of next line NewCaret.X:=1; - NewCaret.Y:=FindNextUnfoldedLine(NewCaret.Y+1, true); + NewCaret.Y:=fTextView.TextPosAddLines(NewCaret.Y, +1); end; end; @@ -9627,29 +9643,10 @@ var LogCaret: TPoint; OldCaret: TPoint; SaveLastCaretX: LongInt; - i: Integer; begin OldCaret:=CaretXY; NewCaret:=OldCaret; - with NewCaret do begin - if DY>=0 then begin - for i:=1 to DY do begin - Inc(Y); - Y:=FindNextUnfoldedLine(Y,true); - end; - end else begin - for i:=1 to -DY do begin - dec(Y); - Y:=FindNextUnfoldedLine(Y,false); - end; - end; - if DY >= 0 then begin - if (Y > Lines.Count) or (CaretY > Y) then - Y := Lines.Count; - end else - if (Y < 1) or (CaretY < Y) then - Y := 1; - end; + NewCaret.Y:=fTextView.TextPosAddLines(NewCaret.Y, DY); if (OldCaret.Y<>NewCaret.Y) and (fLastCaretX>0) and (eoKeepCaretX in Options) then NewCaret.X:=fLastCaretX; diff --git a/components/synedit/syneditfoldedview.pp b/components/synedit/syneditfoldedview.pp new file mode 100644 index 0000000000..f364c5e9f1 --- /dev/null +++ b/components/synedit/syneditfoldedview.pp @@ -0,0 +1,1681 @@ +{------------------------------------------------------------------------------- +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. + +-------------------------------------------------------------------------------} +(* some parts (AdjustBalance...) of this unit are based on the AVLTree unit *) +(* TODO: Implement node.eof / node.bof *) +unit SynEditFoldedView; + +{$mode objfpc}{$H+} +{//$INLINE OFF} + +interface + +uses +LCLProc, + Classes, SysUtils, SynEditTextBuffer; + +type + + TReplacedChildSite = (rplcLeft, rplcRight); + + { TSynTextFoldAVLNodeData } + + TSynTextFoldAVLNodeData = Class + public + Parent, Left, Right : TSynTextFoldAVLNodeData; (* AVL Links *) + Balance : shortint; (* AVL Balance *) + Nested : TSynTextFoldAVLNodeData; (* Nested folds (folds within this fold) do not need to be part of the searchable tree + They will be restored, if the outer fold (this fold) is unfolded + Nested points to a standalone tree, the root node in the nested tree, does *not* point back to this node *) + LineOffset : Integer; (* Line-Number Offset to parent node + All line numbers are stored as offsets, for faster updates if lines are inserted/deleted *) + LeftCount : Integer; (* Lines folded in left tree. Used to calculate how many lines are folded up to a specified line *) + LineCount : Integer; (* Amount of lines covered by this fold only *) + + function TreeDepth: integer; (* longest WAY down. Only one node => 1! *) + function RecursiveFoldCount : Integer; (* Amount of lines covered by this and all child nodes *) + + procedure SetLeftChild(ANode : TSynTextFoldAVLNodeData); overload; inline; + procedure SetLeftChild(ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset : Integer); overload; inline; + procedure SetLeftChild(ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset, aLeftCount : Integer); overload; inline; + + procedure SetRightChild(ANode : TSynTextFoldAVLNodeData); overload; inline; + procedure SetRightChild(ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset : Integer); overload; inline; + + function ReplaceChild(OldNode, ANode : TSynTextFoldAVLNodeData) : TReplacedChildSite; overload; inline; + function ReplaceChild(OldNode, ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset : Integer) : TReplacedChildSite; overload; inline; + + procedure AdjustLeftCount(AValue : Integer); + procedure AdjustParentLeftCount(AValue : Integer); + + function Precessor : TSynTextFoldAVLNodeData; + function Successor : TSynTextFoldAVLNodeData; + function Precessor(var aStartLine, aLinesBefore : Integer) : TSynTextFoldAVLNodeData; + function Successor(var aStartLine, aLinesBefore : Integer) : TSynTextFoldAVLNodeData; + end; + + { TSynTextFoldAVLNode } + + TSynTextFoldAVLNode = object + private + function GetLineCount : Integer; + protected + fData : TSynTextFoldAVLNodeData; // nil if unfolded + fStartLine : Integer; // start of folded + fFoldedBefore : Integer; + public + function IsInFold : Boolean; + function Next : TSynTextFoldAVLNode; + function Prev : TSynTextFoldAVLNode; + property LineCount : Integer read GetLineCount; // Zero, if Not in a fold + property StartLine : Integer read fStartLine; // 1st Line of Current Fold + property FoldedBefore : Integer read fFoldedBefore; // Count of Lines folded before Startline + end; + + { TSynTextFoldAVLTree + Nodes in the tree cover the folded lines only + the cfCollapsed line at the start of a fold, is *not* part of a node. + RemoveFoldForline has special precaution for this. + } + + TSynTextFoldAVLTree = class + protected + fRoot: TSynTextFoldAVLNodeData; + fNestParent: TSynTextFoldAVLNodeData; + fNestedNodesTree: TSynTextFoldAVLTree; // FlyWeight Tree used for any nested subtree. + fRootOffset : Integer; + + function NewNode : TSynTextFoldAVLNodeData; inline; + procedure DisposeNode(var ANode : TSynTextFoldAVLNodeData); inline; + + procedure SetRoot(ANode : TSynTextFoldAVLNodeData); overload; inline; + procedure SetRoot(ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset : Integer); overload; inline; + + Function InsertNode(ANode : TSynTextFoldAVLNodeData) : Integer; // returns FoldedBefore // ANode may not have children + procedure RemoveNode(ANode: TSynTextFoldAVLNodeData); + procedure BalanceAfterInsert(ANode: TSynTextFoldAVLNodeData); + procedure BalanceAfterDelete(ANode: TSynTextFoldAVLNodeData); + function TreeForNestedNode(ANode: TSynTextFoldAVLNodeData; aOffset : Integer) : TSynTextFoldAVLTree; + public + constructor Create; + destructor Destroy; override; + + procedure Clear; + (* Find Fold by Line in Real Text *) + Function FindFoldForLine(ALine : Integer; FindNextNode : Boolean = False) : TSynTextFoldAVLNode; + (* Find Fold by Line in Folded Text // always returns unfolded, unless next=true *) + Function FindFoldForFoldedLine(ALine : Integer; FindNextNode : Boolean = False) : TSynTextFoldAVLNode; + Function InsertNewFold(ALine, ACount : Integer) : TSynTextFoldAVLNode; + (* This will unfold the block which either contains tALine, or has Aline as its cgColapsed line + If IgnoreFirst, the cfCollapsed will *not* unfold => Hint: IgnoreFirst = Make folded visible + Returns the pos(1-based) of the cfCollapsed Line that was expanded; or ALine, if nothing was done + *) + Function RemoveFoldForLine(ALine : Integer; IgnoreFirst : Boolean = False) : Integer; + Procedure AdjustForLinesInserted(AStartLine, ALineCount : Integer); + Procedure AdjustForLinesDeleted(AStartLine, ALineCount : Integer); + Function FindLastFold : TSynTextFoldAVLNode; + end; + + TSynEditCodeFoldType = ( + cfNone, // line is not in a block + cfCollapsed, // line is start of collapsed block + cfExpanded, // line is start of expanded block + cfContinue, // line is middle part of block(s) + cfEnd // line is end of block(s) + ); + TFoldChangedEvent = procedure(aLine: Integer) of object; +const + SynEditCodeFoldTypeNames: array[TSynEditCodeFoldType] of string = ( + 'cfNone', + 'cfCollapsed', + 'cfExpanded', + 'cfContinue', + 'cfEnd' + ); + +type + { TSynTextFoldedView + *Line = Line (0-based) on Screen (except TopLine which should be TopViewPos) + *ViewPos = Line (1-based) in the array of viewable/visible lines + *TextIndex = Line (0-based) in the complete text(folded and unfolded) + } + + { TSynEditFoldedView } + + TSynEditFoldedView = class {TODO: Make a base class, that just maps everything one to one} + private + fLines : TSynEditStringList; + fFoldTree : TSynTextFoldAVLTree; // Folds are stored 1-based (the 1st line is 1) + fTopLine : Integer; + fLinesInWindow : Integer; // there may be an additional part visible line + fTextIndexList : Array of integer; (* Map each Screen line into a line in textbuffer *) + fFoldTypeList : Array of TSynEditCodeFoldType; + fOnFoldChanged : TFoldChangedEvent; + fCFDividerDrawLevel: Integer; + + procedure debug; + function GetCount : integer; + function GetDrawDivider(Index : integer) : Boolean; + function GetLines(index : Integer) : String; + function GetDisplayNumber(index : Integer) : Integer; + function GetRange(Index : integer) : TSynEditRange; + function GetTextIndex(index : Integer) : Integer; + function GetFoldType(index : Integer) : TSynEditCodeFoldType; + function IsFolded(index : integer) : Boolean; // TextIndex + procedure PutRange(Index : integer; const AValue : TSynEditRange); + procedure SetTopLine(const ALine : integer); + function GetTopTextIndex : integer; + procedure SetTopTextIndex(const AIndex : integer); + procedure SetLinesInWindow(const AValue : integer); + protected + Procedure CalculateMaps; + function LengthForFoldAtTextIndex(ALine : Integer) : Integer; + function FixFolding(AStart : Integer; AMinEnd : Integer; aFoldTree : TSynTextFoldAVLTree) : Boolean; + + Procedure LineCountChanged(AIndex, ACount : Integer); + Procedure LinesInsertedAtTextIndex(AStartIndex, ALineCount : Integer; + SkipFixFolding : Boolean = False); + Procedure LinesInsertedAtViewPos(AStartPos, ALineCount : Integer; + SkipFixFolding : Boolean = False); + Procedure LinesDeletedAtTextIndex(AStartIndex, ALineCount : Integer; + SkipFixFolding : Boolean = False); + Procedure LinesDeletedAtViewPos(AStartPos, ALineCount : Integer; + SkipFixFolding : Boolean = False); + public + constructor Create(aTextBuffer : TSynEditStringList); + destructor Destroy; override; + + function TextIndexToViewPos(aTextIndex : Integer) : Integer; (* Convert TextIndex (0-based) to ViewPos (1-based) *) + function TextIndexToScreenLine(aTextIndex : Integer) : Integer; (* Convert TextIndex (0-based) to Screen (0-based) *) + function ViewPosToTextIndex(aViewPos : Integer) : Integer; (* Convert ViewPos (1-based) to TextIndex (0-based) *) + function ScreenLineToTextIndex(aLine : Integer) : Integer; (* Convert Screen (0-based) to TextIndex (0-based) *) + + function TextIndexAddLines(aTextIndex, LineOffset : Integer) : Integer; (* Add/Sub to/from TextIndex (0-based) skipping folded *) + function TextPosAddLines(aTextpos, LineOffset : Integer) : Integer; (* Add/Sub to/from TextPos (1-based) skipping folded *) + + property Lines[index : Integer] : String (* Lines on screen / 0 = TopLine *) + read GetLines; default; + property Ranges[Index: integer]: TSynEditRange + read GetRange write PutRange; + property DisplayNumber[index : Integer] : Integer (* LineNumber for display in Gutter / result is 1-based *) + read GetDisplayNumber; + property FoldType[index : Integer] : TSynEditCodeFoldType (* FoldIcon / State *) + read GetFoldType; + property DrawDivider[Index: integer]: Boolean + read GetDrawDivider; + property TextIndex[index : Integer] : Integer (* Position in SynTextBuffer / result is 0-based *) + read GetTextIndex; // maybe writable + + property TopLine : integer (* refers to visible (unfolded) lines / 1-based *) + read fTopLine write SetTopLine; + property TopTextIndex : integer (* refers to TextIndex (folded + unfolded lines) / 1-based *) + read GetTopTextIndex write SetTopTextIndex; + property LinesInWindow : integer (* Fully Visible lines in Window; There may be one half visible line *) + read fLinesInWindow write SetLinesInWindow; + + property Count : integer read GetCount; (* refers to visible (unfolded) lines *) + + property CFDividerDrawLevel: Integer read fCFDividerDrawLevel write fCFDividerDrawLevel; + + public + procedure FoldAtLine(AStartLine: Integer); (* Folds at ScreenLine / 0-based *) + procedure FoldAtViewPos(AStartPos: Integer); (* Folds at nth visible/unfolded Line / 1-based *) + procedure FoldAtTextIndex(AStartIndex: Integer); (* Folds at nth TextIndex (all lines in buffer) / 1-based *) + procedure UnFoldAtLine(AStartLine: Integer; IgnoreFirst : Boolean = False); (* UnFolds at ScreenLine / 0-based *) + procedure UnFoldAtViewPos(AStartPos: Integer; IgnoreFirst : Boolean = False); (* UnFolds at nth visible/unfolded Line / 1-based *) + procedure UnFoldAtTextIndex(AStartIndex: Integer; IgnoreFirst : Boolean = False); (* UnFolds at nth TextIndex (all lines in buffer) / 1-based *) + + procedure UnfoldAll; + procedure FixFoldingAtTextIndex(AStartIndex: Integer; AMinEndLine: Integer = 0); // Real/All lines + + property OnFoldChanged: TFoldChangedEvent (* reports 1-based line *) {TODO: synedit expects 0 based } + read fOnFoldChanged write fOnFoldChanged; + property FoldedAtTextIndex [index : integer] : Boolean read IsFolded; + end; + + +implementation + +{ TSynTextFoldAVLNodeData } + +function TSynTextFoldAVLNodeData.TreeDepth : integer; +var t: integer; +begin + Result := 1; + if Left<>nil then Result := Left.TreeDepth+1; + if Right<>nil then t := Right.TreeDepth+1 else t := 0; + if t> Result then Result := t; +end; + +function TSynTextFoldAVLNodeData.RecursiveFoldCount : Integer; +var ANode : TSynTextFoldAVLNodeData; +begin + Result := 0; + ANode := self; + while ANode <> nil do begin + Result := Result + ANode.LineCount + ANode.LeftCount; + ANode := ANode.Right; + end; +end; + +procedure TSynTextFoldAVLNodeData.SetLeftChild(ANode : TSynTextFoldAVLNodeData); inline; +begin + Left := ANode; + if ANode <> nil then ANode.Parent := self; +end; + +procedure TSynTextFoldAVLNodeData.SetLeftChild(ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset : Integer); inline; +begin + Left := ANode; + if ANode <> nil then begin + ANode.Parent := self; + ANode.LineOffset := ANode.LineOffset + anAdjustChildLineOffset; + end; +end; + +procedure TSynTextFoldAVLNodeData.SetLeftChild(ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset, aLeftCount : Integer); inline; +begin + Left := ANode; + LeftCount := aLeftCount; + if ANode <> nil then begin + ANode.Parent := self; + ANode.LineOffset := ANode.LineOffset + anAdjustChildLineOffset; + end +end; + +procedure TSynTextFoldAVLNodeData.SetRightChild(ANode : TSynTextFoldAVLNodeData); inline; +begin + Right := ANode; + if ANode <> nil then ANode.Parent := self; +end; + +procedure TSynTextFoldAVLNodeData.SetRightChild(ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset : Integer); inline; +begin + Right := ANode; + if ANode <> nil then begin + ANode.Parent := self; + ANode.LineOffset := ANode.LineOffset + anAdjustChildLineOffset; + end; +end; + +function TSynTextFoldAVLNodeData.ReplaceChild(OldNode, ANode : TSynTextFoldAVLNodeData) : TReplacedChildSite; inline; +begin + if Left = OldNode then begin + SetLeftChild(ANode); + exit(rplcLeft); + end; + SetRightChild(ANode); + result := rplcRight; +end; + +function TSynTextFoldAVLNodeData.ReplaceChild(OldNode, ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset : Integer) : TReplacedChildSite; + inline; +begin + if Left = OldNode then begin + SetLeftChild(ANode, anAdjustChildLineOffset); + exit(rplcLeft); + end; + SetRightChild(ANode, anAdjustChildLineOffset); + result := rplcRight; +end; + +procedure TSynTextFoldAVLNodeData.AdjustLeftCount(AValue : Integer); +begin + LeftCount := LeftCount + AValue; + AdjustParentLeftCount(AValue); +end; + +procedure TSynTextFoldAVLNodeData.AdjustParentLeftCount(AValue : Integer); +var + node, pnode : TSynTextFoldAVLNodeData; +begin + node := self; + pnode := node.Parent; + while pnode <> nil do begin + if node = pnode.Left + then pnode.LeftCount := pnode.LeftCount + AValue; + node := pnode; + pnode := node.Parent; + end; +end; + +function TSynTextFoldAVLNodeData.Precessor : TSynTextFoldAVLNodeData; +begin + Result := Left; + if Result<>nil then begin + while (Result.Right<>nil) do Result := Result.Right; + end else begin + Result := self; + while (Result.Parent<>nil) and (Result.Parent.Left=Result) do + Result := Result.Parent; + Result := Result.Parent; + end; +end; + +function TSynTextFoldAVLNodeData.Successor : TSynTextFoldAVLNodeData; +begin + Result := Right; + if Result<>nil then begin + while (Result.Left<>nil) do Result := Result.Left; + end else begin + Result := self; + while (Result.Parent<>nil) and (Result.Parent.Right=Result) do + Result := Result.Parent; + Result := Result.Parent; + end; +end; + +function TSynTextFoldAVLNodeData.Precessor(var aStartLine, aLinesBefore : Integer) : TSynTextFoldAVLNodeData; +begin + aLinesBefore := aLinesBefore - LineCount; + Result := Left; + if Result<>nil then begin + aStartLine := aStartLine + Result.LineOffset; + while (Result.Right<>nil) do begin + Result := Result.Right; + aStartLine := aStartLine + Result.LineOffset; + end; + end else begin + Result := self; + while (Result.Parent<>nil) and (Result.Parent.Left=Result) do begin + aStartLine := aStartLine - Result.LineOffset; + Result := Result.Parent; + end; + // result is now a right son + aStartLine := aStartLine - Result.LineOffset; + Result := Result.Parent; + end; +end; + +function TSynTextFoldAVLNodeData.Successor(var aStartLine, aLinesBefore : Integer) : TSynTextFoldAVLNodeData; +begin + aLinesBefore := aLinesBefore + LineCount; + Result := Right; + if Result<>nil then begin + aStartLine := aStartLine + Result.LineOffset; + while (Result.Left<>nil) do begin + Result := Result.Left; + aStartLine := aStartLine + Result.LineOffset; + end; + end else begin + Result := self; + while (Result.Parent<>nil) and (Result.Parent.Right=Result) do begin + aStartLine := aStartLine - Result.LineOffset; + Result := Result.Parent; + end; + // Result is now a left son; result has a negative LineOffset + aStartLine := aStartLine - Result.LineOffset; + Result := Result.Parent; + end; +end; + +{ TSynTextFoldAVLNode } + +function TSynTextFoldAVLNode.GetLineCount : Integer; +begin + if fData = nil + then Result := 0 + else Result := fData.LineCount; +end; + +function TSynTextFoldAVLNode.IsInFold : Boolean; +begin + Result := fData <> nil; +end; + +function TSynTextFoldAVLNode.Next : TSynTextFoldAVLNode; +var aStart, aBefore : Integer; +begin + if fData <> nil then begin + aStart := StartLine; + aBefore := FoldedBefore; + Result.fData := fData.Successor(aStart, aBefore); + Result.fStartLine := aStart; + Result.fFoldedBefore := aBefore; + end + else Result.fData := nil; +end; + +function TSynTextFoldAVLNode.Prev : TSynTextFoldAVLNode; +var aStart, aBefore : Integer; +begin + if fData <> nil then begin + aStart := StartLine; + aBefore := FoldedBefore; + Result.fData := fData.Precessor(aStart, aBefore); + Result.fStartLine := aStart; + Result.fFoldedBefore := aBefore; + end + else Result.fData := nil; +end; + +{ TSynTextFoldAVLTree } + +function TSynTextFoldAVLTree.NewNode : TSynTextFoldAVLNodeData; +begin + Result := TSynTextFoldAVLNodeData.Create; +end; + +procedure TSynTextFoldAVLTree.DisposeNode(var ANode : TSynTextFoldAVLNodeData); +begin + FreeAndNil(ANode); +end; + +destructor TSynTextFoldAVLTree.Destroy; +begin + Clear; + if fNestedNodesTree <> nil then begin + fNestedNodesTree.fRoot := nil; //was freed in self.Clear + fNestedNodesTree.Free; + end; + inherited Destroy; +end; + +procedure TSynTextFoldAVLTree.Clear; + procedure DeleteNode(var ANode: TSynTextFoldAVLNodeData); + begin + if ANode.Left <>nil then DeleteNode(ANode.Left); + if ANode.Right <>nil then DeleteNode(ANode.Right); + if ANode.Nested <>nil then DeleteNode(ANode.Nested); + DisposeNode(ANode); + end; +begin + if fRoot <> nil then DeleteNode(fRoot); + SetRoot(nil); +end; + +procedure TSynTextFoldAVLTree.SetRoot(ANode : TSynTextFoldAVLNodeData); inline; +begin + fRoot := ANode; + if fNestParent <> nil then fNestParent.Nested := ANode; + if ANode <> nil then ANode.Parent := nil; +end; + +procedure TSynTextFoldAVLTree.SetRoot(ANode : TSynTextFoldAVLNodeData; anAdjustChildLineOffset : Integer); + inline; +begin + fRoot := ANode; + if fNestParent <> nil then fNestParent.Nested := ANode; + if ANode <> nil then begin + ANode.Parent := nil; + ANode.LineOffset := ANode.LineOffset + anAdjustChildLineOffset; + end; +end; + +function TSynTextFoldAVLTree.FindFoldForLine(ALine : Integer; FindNextNode : Boolean = False) : TSynTextFoldAVLNode; +var + r : TSynTextFoldAVLNodeData; + rStartLine : Integer; + rFoldedBefore : Integer; +begin + r := fRoot; + rStartLine := fRootOffset; + rFoldedBefore := 0; + while (r <> nil) do begin + rStartLine := rStartLine + r.LineOffset; + + if ALine < rStartLine then begin + if FindNextNode and (r.Left = nil) then break; + r := r.Left; // rStartLine points to r, so if r.Left is nil then it is pointing to the next fold; + continue; + end; + + rFoldedBefore := rFoldedBefore + r.LeftCount; + if ALine < rStartLine + r.LineCount + then break; + + if FindNextNode and (r.Right = nil) then begin + r := r.Successor(rStartLine, rFoldedBefore); + break; + end; + + rFoldedBefore := rFoldedBefore + r.LineCount; + r := r.Right; // rStartLine points to r, which now is the start of the previous fold; + end; + + Result.fData := r; + Result.fStartLine := rStartLine; // only IF r <> nil + Result.fFoldedBefore := rFoldedBefore; // always ok +end; + +function TSynTextFoldAVLTree.FindFoldForFoldedLine(ALine : Integer; FindNextNode : Boolean) : TSynTextFoldAVLNode; +var + r : TSynTextFoldAVLNodeData; + rStartLine : Integer; + rFoldedBefore : Integer; +begin + r := fRoot; + rStartLine := fRootOffset; + rFoldedBefore := 0; + while (r <> nil) do begin + rStartLine := rStartLine + r.LineOffset; + + if ALine + r.LeftCount < rStartLine then begin + if FindNextNode and (r.Left = nil) then break; + r := r.Left; // rStartLine points to r, so if r.Left is nil then it is pointing to the next fold; + continue; + end; + + ALine := ALine + r.LeftCount + r.LineCount; + rFoldedBefore := rFoldedBefore + r.LeftCount; + + if FindNextNode and (r.Right = nil) then begin + r := r.Successor(rStartLine, rFoldedBefore); + break; + end; + + rFoldedBefore := rFoldedBefore + r.LineCount; + r := r.Right; // rStartLine points to r, which now is the start of the previous fold; + end; + + Result.fData := r; + Result.fStartLine := rStartLine; // only IF r <> nil + Result.fFoldedBefore := rFoldedBefore; // always ok +end; + +procedure TSynTextFoldAVLTree.AdjustForLinesInserted(AStartLine, ALineCount : Integer); +var + Current : TSynTextFoldAVLNodeData; + CurrentLine : Integer; +begin + Current := fRoot; + CurrentLine := fRootOffset; + + while (Current <> nil) do begin + CurrentLine := CurrentLine + Current.LineOffset; + + if AStartLine < CurrentLine then begin + // move current node + Current.LineOffset := Current.LineOffset + ALineCount; + CurrentLine := CurrentLine + ALineCount; + if Current.Left <> nil then + Current.Left.LineOffset := Current.Left.LineOffset - ALineCount; + Current := Current.Left; + end + else if AStartLine > CurrentLine + Current.LineCount - 1 then begin + // The new lines are entirly behind the current node + Current := Current.Right; + end + else begin + // grow current node + Current.LineCount := Current.LineCount + ALineCount; + Current.AdjustParentLeftCount(ALineCount); + TreeForNestedNode(Current, CurrentLine).AdjustForLinesInserted(AStartLine, ALineCount); + if Current.Right <> nil then // and move entire right + Current.Right.LineOffset := Current.Right.LineOffset + ALineCount; + break; + end; + end; +end; + +procedure TSynTextFoldAVLTree.AdjustForLinesDeleted(AStartLine, ALineCount : Integer); + Procedure UnfoldForRange(Current : TSynTextFoldAVLNodeData; CurrentLine, StartLine, LineCount : Integer); + begin + // unfold any node, that has either start or end line in the range + end; + + Procedure AdjustNodeForLinesDeleted(Current : TSynTextFoldAVLNodeData; CurrentLine, StartLine, LineCount : Integer); + var + EndLine, LinesBefore, LinesInside, LinesAfter : Integer; + begin + EndLine := StartLine + LineCount - 1; // only valid for delete; LineCount < 0 + + while (Current <> nil) do begin + CurrentLine := CurrentLine + Current.LineOffset; + + if StartLine < CurrentLine then begin + // move current node + if EndLine >= CurrentLine then begin + // overlap => shrink + LinesBefore := CurrentLine - StartLine; + LinesInside := LineCount - LinesBefore; + LinesAfter := LinesInside - Current.LineCount; + // shrink + Current.LineCount := Current.LineCount - LinesInside; + Current.AdjustParentLeftCount(-LinesInside); + TreeForNestedNode(Current, CurrentLine).AdjustForLinesDeleted(CurrentLine, LinesInside); + if Current.Right <> nil then begin // and move entire right + Current.Right.LineOffset := Current.Right.LineOffset + LinesInside; + // move right + if LinesAfter > 0 then + AdjustNodeForLinesDeleted(Current.Right, CurrentLine, + CurrentLine + Current.LineCount, LinesAfter); + end; + end + else LinesBefore := LineCount; + + // move current node (includes right subtree / left subtree needs eval) + Current.LineOffset := Current.LineOffset - LinesBefore; + CurrentLine := CurrentLine - LinesBefore; + if Current.Left <> nil then + Current.Left.LineOffset := Current.Left.LineOffset + LinesBefore; + Current := Current.Left; + end + else if StartLine > CurrentLine + Current.LineCount - 1 then begin + // The new lines are entirly behind the current node + Current := Current.Right; + end + else begin + LinesAfter := EndLine - (CurrentLine + Current.LineCount - 1); + if LinesAfter < 0 then LinesAfter := 0;; + LinesInside := LineCount - LinesAfter; + // shrink current node + Current.LineCount := Current.LineCount - LinesInside; + Current.AdjustParentLeftCount(-LinesInside); + TreeForNestedNode(Current, CurrentLine).AdjustForLinesDeleted(StartLine, LinesInside); + + if Current.Right <> nil then // and move entire right + Current.Right.LineOffset := Current.Right.LineOffset - LinesInside; + + if LinesAfter > 0 then begin + StartLine := CurrentLine + Current.LineCount; + LineCount := LinesAfter; + Current := Current.Right; + end + else break; + end; + + end; + end; + +begin + AdjustNodeForLinesDeleted(fRoot, fRootOffset, AStartLine, ALineCount); +end; + +function TSynTextFoldAVLTree.FindLastFold : TSynTextFoldAVLNode; +var + r : TSynTextFoldAVLNodeData; + rStartLine : Integer; + rFoldedBefore : Integer; +begin + r := fRoot; + rStartLine := fRootOffset; + rFoldedBefore := 0; + while (r <> nil) do begin + rStartLine := rStartLine + r.LineOffset; + rFoldedBefore := rFoldedBefore + r.LeftCount + r.LineCount; + if r.Right = nil then break; + r := r.Right; // rStartLine points to r, which now is the start of the previous fold; + end; + + Result.fData := r; + Result.fStartLine := rStartLine; // only IF r <> nil + Result.fFoldedBefore := rFoldedBefore; // always ok +end; + + +function TSynTextFoldAVLTree.InsertNewFold(ALine, ACount : Integer) : TSynTextFoldAVLNode; +var + r : TSynTextFoldAVLNodeData; +begin + r := NewNode; + r.LineOffset := ALine; + r.LineCount := ACount; + r.LeftCount := 0; + Result.fData := r; + Result.fStartLine := ALine; + + Result.fFoldedBefore := InsertNode(r); +end; + +function TSynTextFoldAVLTree.RemoveFoldForLine(ALine : Integer; IgnoreFirst : Boolean = False) : Integer; +var + NestedNode, MergeNode : TSynTextFoldAVLNodeData; + NestedLine : LongInt; + OldFold : TSynTextFoldAVLNode; +begin + // The cfCollapsed line is one line before the fold + Result := ALine; + OldFold := FindFoldForLine(ALine, true); + if (not OldFold.IsInFold) // behind last node + or (IgnoreFirst and (OldFold.StartLine > ALine)) + or ((not IgnoreFirst) and (OldFold.StartLine > ALine+1)) + then exit; + Result := OldFold.StartLine-1; // Return the cfcollapsed that was unfolded + RemoveNode(OldFold.fData); + + If OldFold.fData.Nested <> nil then + begin + (*Todo: should we mark the tree as NO balancing needed ???*) + TreeForNestedNode(OldFold.fData, OldFold.StartLine).RemoveFoldForLine(ALine, IgnoreFirst); + + // merge the remaining nested into current + NestedNode := OldFold.fData.Nested; + if NestedNode <> nil + then NestedLine := OldFold.fStartLine + NestedNode.LineOffset; + + while NestedNode <> nil do begin + while NestedNode.Left <> nil do begin + NestedNode := NestedNode.Left; + NestedLine := NestedLine + NestedNode.LineOffset; + end; + + if NestedNode.Right <> nil then begin + NestedNode := NestedNode.Right; + NestedLine := NestedLine + NestedNode.LineOffset; + continue; + end; + + // leaf node + // Anything that is still nested (MergeNode.Nested), will stay nested + MergeNode := NestedNode; + + NestedLine := NestedLine - NestedNode.LineOffset; + NestedNode := NestedNode.Parent; + + MergeNode.LineOffset := MergeNode.LineOffset + NestedLine; + if NestedNode <> nil then begin + NestedNode.ReplaceChild(MergeNode, nil); + MergeNode.Parent := nil; + end; + MergeNode.LeftCount := 0; + MergeNode.Balance := 0; + InsertNode(MergeNode); + end; + + end; + + DisposeNode(OldFold.fData); +end; + +function TSynTextFoldAVLTree.InsertNode(ANode : TSynTextFoldAVLNodeData) : Integer; +var + rStartLine, NestStartLine : Integer; + rFoldedBefore, NestFoldedBefore : Integer; + + current, Nest : TSynTextFoldAVLNodeData; + ALine, AEnd, ACount : Integer; + + procedure NestCurrentIntoNewBlock; inline; + var + diff, start2, before2 : Integer; + p : TSynTextFoldAVLNodeData; + begin + // TODO => Check if LineCount needs to be extended (part overlap/nesting) + // include extension in below AdjustParentLeftCount + current.AdjustParentLeftCount(ACount-current.LineCount); // -RecursiveFoldCount(current)); + rStartLine := rStartLine - current.LineOffset; // rStarteLine is now current.Parent + p := current.Parent; + if p <> nil + then p.ReplaceChild(current, ANode, -rStartLine) + else SetRoot(ANode, -rStartLine); + + diff := current.LineOffset - ANode.LineOffset; + ANode.Nested := current; + ANode.Balance := current.Balance; + current.LineOffset := diff; // offset to ANode (via Nested) + current.Parent := nil; + current.Balance := 0; + + ANode.SetLeftChild(current.Left, diff, current.LeftCount); + current.Left := nil; + current.LeftCount := 0; + ANode.SetRightChild(current.Right, diff); + current.Right := nil; + + start2 := ALine; before2 := rFoldedBefore; + p := ANode.Successor(start2, before2); + while (p <> nil) and (start2 <= AEnd) do begin + RemoveNode(p); + p.LineOffset := start2- ALine; + TreeForNestedNode(Anode, 0).InsertNode(p); + + start2 := ALine; before2 := rFoldedBefore; + p := ANode.Successor(start2, before2); + end; + end; + + procedure NestNewBlockIntoCurrent; inline; + begin + // Check if current.LineCount needs extension + ANode.LineOffset := ALine - rStartLine; + if current.Nested <> nil + then TreeForNestedNode(current, 0).InsertNode(ANode) + else current.Nested := ANode; + // TODO => Find More Nodes (only if acount extended), that need to be nested => May include LineCount/LeftCount Adjustment + // TODO => BalanceAfterInsert (only if more nodes) + end; + +begin + if fRoot = nil then begin + SetRoot(ANode); + Result := 0; + exit; + end; + ALine := ANode.LineOffset; + ACount := ANode.LineCount; + AEnd := ALine + ACount - 1; + current := fRoot; + rStartLine := fRootOffset; + rFoldedBefore := 0; + Nest := nil; + NestFoldedBefore := 0; + NestStartLine := 0; + + while (current <> nil) do begin + rStartLine := rStartLine + current.LineOffset; + + if ALine < rStartLine then begin + (* *** New block goes to the left *** *) + // remember possible nesting, continue scan for nesting with precessor + if (AEnd >= rStartLine) then begin + Nest := current; + NestFoldedBefore := rFoldedBefore; + NestStartLine := rStartLine; + end; + + if current.Left <> nil Then begin + current := current.Left; + continue; + end + else if Nest = nil then begin // insert as Left - no nesting + current.AdjustParentLeftCount(ACount); + current.SetLeftChild(ANode, -rStartLine, ANode.LineCount); + BalanceAfterInsert(ANode); + end + else begin // nest + current := Nest; + rStartLine := NestStartLine; + rFoldedBefore := NestFoldedBefore; + NestCurrentIntoNewBlock; + end; + break; + end; + + rFoldedBefore := rFoldedBefore + current.LeftCount; + if ALine = rStartLine then begin + if ACount < current.LineCount + (* *** New Block will be nested in current *** *) + then NestNewBlockIntoCurrent + (* *** current will be nested in New Block *** *) + else NestCurrentIntoNewBlock; + end + else begin + If ALine <= rStartLine + current.LineCount - 1 + (* *** New Block will be nested in current *** *) + then NestNewBlockIntoCurrent + (* *** New block goes to the right *** *) + else begin + rFoldedBefore := rFoldedBefore + current.LineCount; + if current.Right <> nil then begin + current := current.Right; + continue; + end + else if Nest=nil then Begin // insert to the right - no nesting + current.AdjustParentLeftCount(ACount); + current.SetRightChild(ANode, -rStartLine); + BalanceAfterInsert(ANode); + end + else begin // nest + current := Nest; + rStartLine := NestStartLine; + rFoldedBefore := NestFoldedBefore; + NestCurrentIntoNewBlock; + end; + + end; + end; + + break; + end; // while + + Result := rFoldedBefore; +end; + +procedure TSynTextFoldAVLTree.RemoveNode(ANode : TSynTextFoldAVLNodeData); +var OldParent, Successor, OldSuccParent, OldSuccRight, + OldSubTree : TSynTextFoldAVLNodeData; + OldBalance, SuccOffset, SuccLeftCount : integer; +begin + if ((ANode.Left<>nil) and (ANode.Right<>nil)) then begin + // DelNode has both: Left and Right + Successor := ANode.Right; + SuccOffset := ANode.LineOffset; + while (Successor.Left<>nil) do begin + SuccOffset := SuccOffset + Successor.LineOffset; + Successor := Successor.Left; + end; + + // Successor.Left = nil + OldSuccRight := Successor.Right; + OldSuccParent := Successor.Parent; + + OldBalance := ANode.Balance; + ANode.Balance := Successor.Balance; + Successor.Balance := OldBalance; + + if (ANode.Parent<>nil) + then ANode.Parent.ReplaceChild(ANode, Successor, +SuccOffset) + else SetRoot(Successor, SuccOffset); + + SuccLeftCount := Successor.LeftCount; + Successor.SetLeftChild(ANode.Left, +ANode.LineOffset -Successor.LineOffset, ANode.LeftCount); + + if (OldSuccParent<>ANode) then begin + // at least one node between ANode and Successor ==> Successor = OldSuccParent.Left + Successor.SetRightChild(ANode.Right, +ANode.LineOffset-Successor.LineOffset); + // ANode.Left will be empty / ANode.Right will be Succesor.Right + // Successor.LeftCount := ANode.LineCount + CountTree(Successor.Right) + OldSuccParent.SetLeftChild(ANode, -ANode.LineOffset, ANode.LineCount +OldSuccParent.LeftCount -SuccLeftCount -Successor.LineCount); + ANode.SetRightChild(OldSuccRight, Successor.LineOffset - SuccOffset); // the original succ.lineoffset {TODO: keep in var } + end else begin + // Successor is right son of ANode ==> OldSuccParent = ANode; + Successor.SetRightChild(ANode, -ANode.LineOffset); + ANode.SetRightChild(OldSuccRight, 0); + end; + + ANode.Left := nil; + ANode.LeftCount := 0; + end; + + if (ANode.Right<>nil) then begin + OldSubTree := ANode.Right; + ANode.Right := nil; + end + else if (ANode.Left<>nil) then begin + OldSubTree := ANode.Left; + ANode.Left := nil; + end + else OldSubTree := nil; + + OldParent := ANode.Parent; + ANode.Parent := nil; + ANode.Left := nil; + ANode.Right := nil; + ANode.Balance := 0; + ANode.LeftCount := 0; + // nested??? + + if (OldParent<>nil) then begin // Node has parent + if OldParent.ReplaceChild(ANode, OldSubTree, ANode.LineOffset) = rplcLeft + then begin + Inc(OldParent.Balance); + OldParent.AdjustLeftCount(-ANode.LineCount); + end + else begin + Dec(OldParent.Balance); + OldParent.AdjustParentLeftCount(-ANode.LineCount); + end; + BalanceAfterDelete(OldParent); + end + else SetRoot(OldSubTree, ANode.LineOffset); +end; + + +procedure TSynTextFoldAVLTree.BalanceAfterInsert(ANode : TSynTextFoldAVLNodeData); +var OldParent, OldParentParent, OldRight, OldRightLeft, OldRightRight, OldLeft, + OldLeftLeft, OldLeftRight: TSynTextFoldAVLNodeData; + tmp : integer; +begin + OldParent := ANode.Parent; + if (OldParent=nil) then exit; + + if (OldParent.Left=ANode) then begin + (* *** Node is left son *** *) + dec(OldParent.Balance); + if (OldParent.Balance=0) then exit; + if (OldParent.Balance=-1) then begin + BalanceAfterInsert(OldParent); + exit; + end; + + // OldParent.Balance=-2 + if (ANode.Balance=-1) then begin + (* ** single rotate ** *) + (* [] + \ + [] ORight [] ORight [] + \ / \ \ / + ANode(-1) [] => [] OldParent(0) + \ / \ / + OldParent(-2) ANode(0) + *) + OldRight := ANode.Right; + OldParentParent := OldParent.Parent; + (* ANode moves into position of OldParent *) + if (OldParentParent<>nil) + then OldParentParent.ReplaceChild(OldParent, ANode, OldParent.LineOffset) + else SetRoot(ANode, OldParent.LineOffset); + + (* OldParent moves under ANode, replacing Anode.Right, which moves under OldParent *) + ANode.SetRightChild(OldParent, -ANode.LineOffset ); + OldParent.SetLeftChild(OldRight, -OldParent.LineOffset, OldParent.LeftCount - ANode.LineCount - ANode.LeftCount); + + ANode.Balance := 0; + OldParent.Balance := 0; + (* ** END single rotate ** *) + end + else begin // ANode.Balance = +1 + (* ** double rotate ** *) + OldParentParent := OldParent.Parent; + OldRight := ANode.Right; + OldRightLeft := OldRight.Left; + OldRightRight := OldRight.Right; + + (* OldRight moves into position of OldParent *) + if (OldParentParent<>nil) + then OldParentParent.ReplaceChild(OldParent, OldRight, OldParent.LineOffset + ANode.LineOffset) + else SetRoot(OldRight, OldParent.LineOffset + ANode.LineOffset); // OldParent was root node. new root node + + OldRight.SetRightChild(OldParent, -OldRight.LineOffset); + OldRight.SetLeftChild(ANode, OldParent.LineOffset, OldRight.LeftCount + ANode.LeftCount + ANode.LineCount); + ANode.SetRightChild(OldRightLeft, -ANode.LineOffset); + OldParent.SetLeftChild(OldRightRight, -OldParent.LineOffset, OldParent.LeftCount - OldRight.LeftCount - OldRight.LineCount); + + // balance + if (OldRight.Balance<=0) + then ANode.Balance := 0 + else ANode.Balance := -1; + if (OldRight.Balance=-1) + then OldParent.Balance := 1 + else OldParent.Balance := 0; + OldRight.Balance := 0; + (* ** END double rotate ** *) + end; + (* *** END Node is left son *** *) + end + else begin + (* *** Node is right son *** *) + Inc(OldParent.Balance); + if (OldParent.Balance=0) then exit; + if (OldParent.Balance=+1) then begin + BalanceAfterInsert(OldParent); + exit; + end; + + // OldParent.Balance = +2 + if(ANode.Balance=+1) then begin + (* ** single rotate ** *) + OldLeft := ANode.Left; + OldParentParent := OldParent.Parent; + + if (OldParentParent<>nil) + then OldParentParent.ReplaceChild(OldParent, ANode, OldParent.LineOffset) + else SetRoot(ANode, OldParent.LineOffset); + + (* OldParent moves under ANode, replacing Anode.Left, which moves under OldParent *) + ANode.SetLeftChild(OldParent, -ANode.LineOffset, ANode.LeftCount + OldParent.LineCount + OldParent.LeftCount); + OldParent.SetRightChild(OldLeft, -OldParent.LineOffset); + + ANode.Balance := 0; + OldParent.Balance := 0; + (* ** END single rotate ** *) + end + else begin // Node.Balance = -1 + (* ** double rotate ** *) + OldLeft := ANode.Left; + OldParentParent := OldParent.Parent; + OldLeftLeft := OldLeft.Left; + OldLeftRight := OldLeft.Right; + + (* OldLeft moves into position of OldParent *) + if (OldParentParent<>nil) + then OldParentParent.ReplaceChild(OldParent, OldLeft, OldParent.LineOffset + ANode.LineOffset) + else SetRoot(OldLeft, OldParent.LineOffset + ANode.LineOffset); + + tmp := OldLeft.LeftCount; + OldLeft.SetLeftChild (OldParent, -OldLeft.LineOffset, tmp + OldParent.LeftCount + OldParent.LineCount); + OldLeft.SetRightChild(ANode, OldParent.LineOffset); + + OldParent.SetRightChild(OldLeftLeft, -OldParent.LineOffset); + ANode.SetLeftChild(OldLeftRight, -ANode.LineOffset, ANode.LeftCount - tmp - OldLeft.LineCount); + + // Balance + if (OldLeft.Balance>=0) + then ANode.Balance := 0 + else ANode.Balance := +1; + if (OldLeft.Balance=+1) + then OldParent.Balance := -1 + else OldParent.Balance := 0; + OldLeft.Balance := 0; + (* ** END double rotate ** *) + end; + end; +end; + +procedure TSynTextFoldAVLTree.BalanceAfterDelete(ANode : TSynTextFoldAVLNodeData); +var OldParent, OldRight, OldRightLeft, OldLeft, OldLeftRight, + OldRightLeftLeft, OldRightLeftRight, OldLeftRightLeft, OldLeftRightRight + : TSynTextFoldAVLNodeData; + tmp : integer; +begin + if (ANode=nil) then exit; + if ((ANode.Balance=+1) or (ANode.Balance=-1)) then exit; + OldParent := ANode.Parent; + if (ANode.Balance=0) then begin + // Treeheight has decreased by one + if (OldParent<>nil) then begin + if(OldParent.Left=ANode) then + Inc(OldParent.Balance) + else + Dec(OldParent.Balance); + BalanceAfterDelete(OldParent); + end; + exit; + end; + + if (ANode.Balance=-2) then begin + // Node.Balance=-2 + // Node is overweighted to the left + (* + OLftRight + / + OLeft(<=0) + \ + ANode(-2) + *) + OldLeft := ANode.Left; + if (OldLeft.Balance<=0) then begin + // single rotate left + OldLeftRight := OldLeft.Right; + + if (OldParent<>nil) + then OldParent.ReplaceChild(ANode, OldLeft, ANode.LineOffset) + else SetRoot(OldLeft, ANode.LineOffset); + + OldLeft.SetRightChild(ANode, -OldLeft.LineOffset); + ANode.SetLeftChild(OldLeftRight, -ANode.LineOffset, ANode.LeftCount - OldLeft.LineCount - OldLeft.LeftCount); + + ANode.Balance := (-1-OldLeft.Balance); + Inc(OldLeft.Balance); + + BalanceAfterDelete(OldLeft); + end else begin + // OldLeft.Balance = 1 + // double rotate left left + OldLeftRight := OldLeft.Right; + OldLeftRightLeft := OldLeftRight.Left; + OldLeftRightRight := OldLeftRight.Right; + + if (OldParent<>nil) + then OldParent.ReplaceChild(ANode, OldLeftRight, ANode.LineOffset + OldLeft.LineOffset) + else SetRoot(OldLeftRight, ANode.LineOffset + OldLeft.LineOffset); + + OldLeftRight.SetRightChild(ANode, -OldLeftRight.LineOffset); + OldLeftRight.SetLeftChild(OldLeft, ANode.LineOffset, OldLeftRight.LeftCount + OldLeft.LeftCount + OldLeft.LineCount); + OldLeft.SetRightChild(OldLeftRightLeft, -OldLeft.LineOffset); + ANode.SetLeftChild(OldLeftRightRight, -ANode.LineOffset, ANode.LeftCount - OldLeftRight.LeftCount - OldLeftRight.LineCount); + + if (OldLeftRight.Balance<=0) + then OldLeft.Balance := 0 + else OldLeft.Balance := -1; + if (OldLeftRight.Balance>=0) + then ANode.Balance := 0 + else ANode.Balance := +1; + OldLeftRight.Balance := 0; + + BalanceAfterDelete(OldLeftRight); + end; + end else begin + // Node is overweighted to the right + OldRight := ANode.Right; + if (OldRight.Balance>=0) then begin + // OldRight.Balance=={0 or -1} + // single rotate right + OldRightLeft := OldRight.Left; + + if (OldParent<>nil) + then OldParent.ReplaceChild(ANode, OldRight, ANode.LineOffset) + else SetRoot(OldRight, ANode.LineOffset); + + OldRight.SetLeftChild(ANode, -OldRight.LineOffset, OldRight.LeftCount + ANode.LineCount + ANode.LeftCount); + ANode.SetRightChild(OldRightLeft, -ANode.LineOffset); + + ANode.Balance := (1-OldRight.Balance); + Dec(OldRight.Balance); + + BalanceAfterDelete(OldRight); + end else begin + // OldRight.Balance=-1 + // double rotate right left + OldRightLeft := OldRight.Left; + OldRightLeftLeft := OldRightLeft.Left; + OldRightLeftRight := OldRightLeft.Right; + if (OldParent<>nil) + then OldParent.ReplaceChild(ANode, OldRightLeft, ANode.LineOffset + OldRight.LineOffset) + else SetRoot(OldRightLeft, ANode.LineOffset + OldRight.LineOffset); + + tmp := OldRightLeft.LeftCount; + OldRightLeft.SetLeftChild(ANode, -OldRightLeft.LineOffset, tmp + ANode.LeftCount + ANode.LineCount); + OldRightLeft.SetRightChild(OldRight, ANode.LineOffset); + + ANode.SetRightChild(OldRightLeftLeft, -ANode.LineOffset); + OldRight.SetLeftChild(OldRightLeftRight, -OldRight.LineOffset, OldRight.LeftCount - tmp - OldRightLeft.LineCount); + + if (OldRightLeft.Balance<=0) + then ANode.Balance := 0 + else ANode.Balance := -1; + if (OldRightLeft.Balance>=0) + then OldRight.Balance := 0 + else OldRight.Balance := +1; + OldRightLeft.Balance := 0; + BalanceAfterDelete(OldRightLeft); + end; + end; + +end; + +function TSynTextFoldAVLTree.TreeForNestedNode(ANode: TSynTextFoldAVLNodeData; aOffset : Integer) : TSynTextFoldAVLTree; +begin + if fNestedNodesTree = nil then fNestedNodesTree := TSynTextFoldAVLTree.Create; + Result := fNestedNodesTree; + Result.fRoot := ANode.Nested; + Result.fNestParent := ANode; + Result.fRootOffset := aOffset; +end; + +constructor TSynTextFoldAVLTree.Create; +begin + fRoot := nil; + fRootOffset := 0; + fNestParent := nil; + fNestedNodesTree := nil; +end; + +{ TSynEditFoldedView } + +constructor TSynEditFoldedView.Create(aTextBuffer : TSynEditStringList); +begin + fLines := aTextBuffer; + fFoldTree := TSynTextFoldAVLTree.Create; + fTopLine := 0; + fLinesInWindow := -1; + fLines.OnLineCountChanged := {$IFDEF FPC}@{$ENDIF}LineCountChanged; +end; + +destructor TSynEditFoldedView.Destroy; +begin + fFoldTree.Free; + fTextIndexList := nil; + fFoldTypeList := nil; + inherited Destroy; +end; + +procedure TSynEditFoldedView.LinesInsertedAtTextIndex(AStartIndex, ALineCount : Integer; SkipFixFolding : Boolean); +var top : Integer; +begin + top := TopTextIndex; + fFoldTree.AdjustForLinesInserted(AStartIndex+1, ALineCount); + if AStartIndex < top then + TopTextIndex := top + ALineCount; + if not(SkipFixFolding) then FixFoldingAtTextIndex(AStartIndex, AStartIndex+ALineCount+1) + else + if AStartIndex < top + ALineCount then CalculateMaps; +end; + +procedure TSynEditFoldedView.LinesInsertedAtViewPos(AStartPos, ALineCount : Integer; SkipFixFolding : Boolean); +begin + LinesInsertedAtTextIndex(ViewPosToTextIndex(AStartPos), ALineCount, SkipFixFolding); +end; + +procedure TSynEditFoldedView.LinesDeletedAtTextIndex(AStartIndex, ALineCount : Integer; SkipFixFolding : Boolean); +var top : Integer; +begin + top := TopTextIndex; + fFoldTree.AdjustForLinesDeleted(AStartIndex+1, ALineCount); + if AStartIndex < top then + TopTextIndex := top - ALineCount; {TODO: this may be to much, if topline is in the middle of the block} + if not(SkipFixFolding) then FixFoldingAtTextIndex(AStartIndex, AStartIndex+ALineCount+1) + else + if AStartIndex < top - ALineCount then CalculateMaps; +end; + +procedure TSynEditFoldedView.LinesDeletedAtViewPos(AStartPos, ALineCount : Integer; SkipFixFolding : Boolean); +begin + LinesDeletedAtTextIndex(ViewPosToTextIndex(AStartPos), ALineCount, SkipFixFolding); +end; + +function TSynEditFoldedView.TextIndexToViewPos(aTextIndex : Integer) : Integer; +begin + result := aTextIndex + 1 - fFoldTree.FindFoldForLine(aTextIndex+1).FoldedBefore; +end; + +function TSynEditFoldedView.TextIndexToScreenLine(aTextIndex : Integer) : Integer; +begin + Result := TextIndexToViewPos(aTextIndex) - TopLine; +end; + +function TSynEditFoldedView.ViewPosToTextIndex(aViewPos : Integer) : Integer; +begin + result := aViewPos - 1 + fFoldTree.FindFoldForFoldedLine(aViewPos).FoldedBefore; +end; + +function TSynEditFoldedView.ScreenLineToTextIndex(aLine : Integer) : Integer; +begin + Result := ViewPosToTextIndex(aLine + TopLine); +end; + +function TSynEditFoldedView.TextIndexAddLines(aTextIndex, LineOffset : Integer) : Integer; +var + node : TSynTextFoldAVLNode; + cnt : integer; +begin + node := fFoldTree.FindFoldForLine(aTextIndex+1, True); + result := aTextIndex; + if LineOffset < 0 then begin + if node.IsInFold + then node := node.Prev + else node := fFoldTree.FindLastFold; + while LineOffset < 0 do begin + if Result <= 0 then exit(0); + dec(Result); + if node.IsInFold and (Result+1 < node.StartLine + node.LineCount) then begin + Result := Result - node.LineCount; + node := node.Prev; + end; + inc(LineOffset); + end; + end else begin + cnt := fLines.Count; + while LineOffset > 0 do begin + if Result >= cnt then exit(cnt); + inc(Result); + if node.IsInFold and (Result+1 >= node.StartLine) then begin + Result := Result + node.LineCount; + node := node.Next; + end; + dec(LineOffset); + end; + end; +end; + +function TSynEditFoldedView.TextPosAddLines(aTextpos, LineOffset : Integer) : Integer; +begin + Result := TextIndexAddLines(aTextpos-1, LineOffset)+1; +end; + +(* Count *) +function TSynEditFoldedView.GetCount : integer; +begin + Result := fLines.Count - fFoldTree.FindLastFold.FoldedBefore; +end; + +function TSynEditFoldedView.GetDrawDivider(Index : integer) : Boolean; +begin + result := (FoldType[Index] in [cfEnd]) + and (fLines.FoldEndLevel[TextIndex[index]] < CFDividerDrawLevel); +end; + +(* Topline *) +procedure TSynEditFoldedView.SetTopLine(const ALine : integer); +begin + if fTopLine = ALine then exit; + fTopLine := ALine; + CalculateMaps; +end; + +function TSynEditFoldedView.GetTopTextIndex : integer; +begin + Result := fTopLine + fFoldTree.FindFoldForFoldedLine(fTopLine).FoldedBefore - 1; +end; + +procedure TSynEditFoldedView.SetTopTextIndex(const AIndex : integer); +begin + TopLine := AIndex + 1 - fFoldTree.FindFoldForLine(AIndex+1).FoldedBefore; +end; + +(* LinesInWindow*) +procedure TSynEditFoldedView.SetLinesInWindow(const AValue : integer); +begin + if fLinesInWindow = AValue then exit; + fLinesInWindow := AValue; + SetLength(fTextIndexList, AValue + 1); + SetLength(fFoldTypeList, AValue + 1); + CalculateMaps; +end; + +procedure TSynEditFoldedView.CalculateMaps; +var + i, tpos, cnt : Integer; + node : TSynTextFoldAVLNode; +begin + node := fFoldTree.FindFoldForFoldedLine(fTopLine, true); + // ftopline is not a folded line + // so node.FoldedBefore(next node after ftopl) does apply + tpos := fTopLine + node.FoldedBefore; + cnt := fLines.Count; + for i := 0 to fLinesInWindow do begin + if tpos > cnt then begin + fTextIndexList[i] := -1; + fFoldTypeList[i] := cfNone; + end else begin + fTextIndexList[i] := tpos - 1; // TextIndex is 0-based + + if (node.IsInFold) and (tpos+1 = node.StartLine) + then fFoldTypeList[i] := cfCollapsed + else + if fLines.FoldEndLevel[tpos-1] > fLines.FoldMinLevel[tpos-1] + then fFoldTypeList[i] := cfExpanded + else + if (tpos > 1) and (fLines.FoldEndLevel[tpos-2] > fLines.FoldMinLevel[tpos-1]) + then fFoldTypeList[i] := cfEnd + else + if fLines.FoldEndLevel[tpos-1] > 0 + then fFoldTypeList[i] := cfContinue + else fFoldTypeList[i] := cfNone; + + inc(tpos); + if (node.IsInFold) and (tpos >= node.StartLine) then begin + tpos := tpos + node.LineCount; + node := node.Next; + end; + end; + end; +end; + +(* Lines *) +function TSynEditFoldedView.GetLines(index : Integer) : String; +begin + if (index < 0) or (index > fLinesInWindow) then exit(''); + Result := fLines[fTextIndexList[index]]; +end; + +function TSynEditFoldedView.GetDisplayNumber(index : Integer) : Integer; +begin + if (index < 0) or (index > fLinesInWindow) + or (fTextIndexList[index] < 0) then exit(-1); + Result := fTextIndexList[index]+1; +end; + +function TSynEditFoldedView.GetRange(Index : integer) : TSynEditRange; +begin + if (index < 0) or (index > fLinesInWindow) then exit(nil); + Result := fLines.Ranges[fTextIndexList[index]]; +end; + +function TSynEditFoldedView.GetTextIndex(index : Integer) : Integer; +begin + if (index < 0) or (index > fLinesInWindow) then exit(-1); + Result := fTextIndexList[index]; +end; + +function TSynEditFoldedView.GetFoldType(index : Integer) : TSynEditCodeFoldType; +begin + if (index < 0) or (index > fLinesInWindow) then exit(cfNone); + Result := fFoldTypeList[index]; +end; + +function TSynEditFoldedView.IsFolded(index : integer) : Boolean; +begin + Result := fFoldTree.FindFoldForLine(index+1).IsInFold; +end; + +procedure TSynEditFoldedView.PutRange(Index : integer; const AValue : TSynEditRange); +begin + if (index < 0) or (index > fLinesInWindow) then exit; + fLines.Ranges[fTextIndexList[index]] := AValue; +end; + +(* Folding *) +procedure TSynEditFoldedView.FoldAtLine(AStartLine : Integer); +begin + FoldAtViewPos(AStartLine + fTopLine); +end; + +procedure TSynEditFoldedView.FoldAtViewPos(AStartPos : Integer); +begin + FoldAtTextIndex(AStartPos - 1 + fFoldTree.FindFoldForFoldedLine(AStartPos).FoldedBefore); +end; + +function TSynEditFoldedView.LengthForFoldAtTextIndex(ALine : Integer) : Integer; +var + i, lvl, cnt : Integer; +begin + cnt := fLines.Count; + // AStartLine is 1-based // FoldEndLevel is 0-based + lvl := fLines.FoldEndLevel[ALine]; + i := ALine+1; + while (i < cnt) and (fLines.FoldMinLevel[i] >= lvl) do inc(i); + // check if fold last line of block (not mixed "end begin") + if (i < cnt) and (fLines.FoldEndLevel[i] <= fLines.FoldMinLevel[i]) then inc(i); + Result := i-ALine-1; +end; + +procedure TSynEditFoldedView.FoldAtTextIndex(AStartIndex : Integer); +var + top : Integer; +begin + top := TopTextIndex; + // AStartIndex is 0-based + // FoldTree is 1-based AND first line remains visble + fFoldTree.InsertNewFold(AStartIndex+2, LengthForFoldAtTextIndex(AStartIndex)); + fTopLine := -1; // make sure seting TopLineTextIndex, will do CalculateMaps; + TopTextIndex := top; + if Assigned(fOnFoldChanged) then + fOnFoldChanged(AStartIndex); +end; + +procedure TSynEditFoldedView.UnFoldAtLine(AStartLine : Integer; IgnoreFirst : Boolean = False); +begin + UnFoldAtViewPos(AStartLine + fTopLine, IgnoreFirst); +end; + +procedure TSynEditFoldedView.UnFoldAtViewPos(AStartPos : Integer; IgnoreFirst : Boolean = False); +begin + UnFoldAtTextIndex(AStartPos - 1 + fFoldTree.FindFoldForFoldedLine(AStartPos).FoldedBefore, IgnoreFirst); +end; + +procedure TSynEditFoldedView.UnFoldAtTextIndex(AStartIndex : Integer; IgnoreFirst : Boolean = False); +var + top : Integer; +begin + top := TopTextIndex; + // Foldtree needs 1-based + AStartIndex := fFoldTree.RemoveFoldForLine(AStartIndex+1, IgnoreFirst) - 1; + fTopLine := -1; // make sure seting TopLineTextIndex, will do CalculateMaps; + TopTextIndex := top; + if Assigned(fOnFoldChanged) then + fOnFoldChanged(AStartIndex); +end; + +procedure TSynEditFoldedView.UnfoldAll; +var + top : Integer; +begin + top := TopTextIndex; + fFoldTree.Clear; + fTopLine := -1; // make sure seting TopLineTextIndex, will do CalculateMaps; + TopTextIndex := top; + if Assigned(fOnFoldChanged) then + fOnFoldChanged(0); +end; + +function TSynEditFoldedView.FixFolding(AStart : Integer; AMinEnd : Integer; aFoldTree : TSynTextFoldAVLTree) : Boolean; +var + line, a : Integer; + node, tmpnode : TSynTextFoldAVLNode; +begin + Result := false; + node := aFoldTree.FindFoldForLine(AStart, true); + if not node.IsInFold then node:= aFoldTree.FindLastFold; + if not node.IsInFold then Begin + CalculateMaps; + exit; + end; + If AMinEnd < node.StartLine then AMinEnd := node.StartLine; + tmpnode := node.Prev; + if tmpnode.IsInFold then node := tmpnode; + + while node.IsInFold do begin + line := node.StartLine - 1; // the 1-based (unfolded) Line, that is Start of a fold + if not(fLines.FoldEndLevel[line -1] > fLines.FoldMinLevel[line - 1]) then begin + // the Fold-Begin of this node has gone + tmpnode := node.Prev; + aFoldTree.RemoveFoldForLine(line); // because the line itself is not folded + if tmpnode.IsInFold then node := tmpnode.Next + else node := aFoldTree.FindFoldForLine(0, true); // firstnode + continue; // catch nested nodes (now unfolded) + end; + + a:= LengthForFoldAtTextIndex(line-1); + if not(node.LineCount = a) then begin + // the Fold-End of this node has gone or moved + tmpnode := node.Prev; + aFoldTree.RemoveFoldForLine(line); // because the line itself is not folded + if tmpnode.IsInFold then node := tmpnode.Next + else node := aFoldTree.FindFoldForLine(0, true); // firstnode + continue; // catch nested nodes (now unfolded) + end; + + if (node.fData.Nested <> nil) + and (FixFolding(line, line+1+node.LineCount, aFoldTree.TreeForNestedNode(node.fData.Nested, line+1))) + then continue; + + // the node was ok + if node.StartLine >= AMinEnd then break; + + node := node.Next; + end; + CalculateMaps; +end; + +procedure TSynEditFoldedView.LineCountChanged(AIndex, ACount : Integer); +begin + // no need for fix folding => synedit will be called, and scanlines will call fixfolding + {TODO: a "need fix folding" flag => to ensure it will be called if synedit doesnt} + if ACount<0 + then LinesDeletedAtTextIndex(AIndex, -ACount, true) + else LinesInsertedAtTextIndex(AIndex, ACount, true); +end; + +procedure TSynEditFoldedView.FixFoldingAtTextIndex(AStartIndex: Integer; AMinEndLine : Integer); +begin + FixFolding(AStartIndex + 1, AMinEndLine, fFoldTree); +end; + +procedure TSynEditFoldedView.debug; + procedure debug2(ind, typ : String; ANode, AParent : TSynTextFoldAVLNodeData; offset : integer); + begin + if ANode = nil then exit; + with ANode do + DebugLn([ind,typ,' LineOffset: ',LineOffset,', LineCount: ', LineCount,', LeftCount: ',LeftCount,', Balance: ',Balance,' ##Line=', offset+LineOffset]); + if ANode.Parent <> AParent then DebugLn([ind,'* Bad parent']); + debug2(ind+' ', 'L', ANode.Left, ANode, offset+ANode.LineOffset); + debug2(ind+' ', 'R', ANode.Right, ANode, offset+ANode.LineOffset); + debug2(ind+' #', 'N', ANode.Nested, nil, offset+ANode.LineOffset); + end; +begin + debug2('', '-', fFoldTree.fRoot, nil, 0); +end; + + +end. + diff --git a/components/synedit/synedittextbuffer.pp b/components/synedit/synedittextbuffer.pp index 8921722cb5..f013b76fbb 100644 --- a/components/synedit/synedittextbuffer.pp +++ b/components/synedit/synedittextbuffer.pp @@ -57,15 +57,6 @@ type {begin} //mh 2000-10-19 TSynEditStringFlag = (sfHasTabs, sfHasNoTabs, sfExpandedLengthUnknown); TSynEditStringFlags = set of TSynEditStringFlag; - {$IFDEF SYN_LAZARUS} - TSynEditCodeFoldType = ( - cfNone, // line is not in a block - cfCollapsed, // line is start of collapsed block - cfExpanded, // line is start of expanded block - cfContinue, // line is middle part of block(s) - cfEnd // line is end of block(s) - ); - {$ENDIF} {end} //mh 2000-10-19 PSynEditStringRec = ^TSynEditStringRec; @@ -77,23 +68,13 @@ type fExpandedLength: integer; fFlags: TSynEditStringFlags; {$IFDEF SYN_LAZARUS} - fFolded: boolean; fFoldMinLevel: LongInt; // minimum block depth in this line fFoldEndLevel: LongInt; // block depth at end of this line - fFoldType: TSynEditCodeFoldType; {$ENDIF} {end} //mh 2000-10-19 end; const - SynEditCodeFoldTypeNames: array[TSynEditCodeFoldType] of string = ( - 'cfNone', - 'cfCollapsed', - 'cfExpanded', - 'cfContinue', - 'cfEnd' - ); - SynEditStringRecSize = SizeOf(TSynEditStringRec); MaxSynEditStrings = MaxInt div SynEditStringRecSize; @@ -104,6 +85,9 @@ type TSynEditStringRecList = array[0..MaxSynEditStrings - 1] of TSynEditStringRec; TStringListIndexEvent = procedure(Index: Integer) of object; + {$IFDEF SYN_LAZARUS} + TStringListLineCountEvent = procedure(Index, Count: Integer) of object; + {$ENDIF} { TSynEditStringList } @@ -129,12 +113,8 @@ type function ExpandedStringLength(Index: integer): integer; function GetFoldEndLevel(Index: integer): integer; function GetFoldMinLevel(Index: integer): integer; - function GetFoldType(Index: integer): TSynEditCodeFoldType; - function GetFolded(Index: integer): boolean; procedure SetFoldEndLevel(Index: integer; const AValue: integer); procedure SetFoldMinLevel(Index: integer; const AValue: integer); - procedure SetFoldType(Index: integer; const AValue: TSynEditCodeFoldType); - procedure SetFolded(Index: integer; const AValue: boolean); {$ENDIF} function GetExpandedString(Index: integer): string; function GetLengthOfLongestLine: integer; @@ -150,7 +130,7 @@ type fOnInserted: TStringListIndexEvent; fOnPutted: TStringListIndexEvent; {$IFDEF SYN_LAZARUS} - fOnFoldChanged: TStringListIndexEvent; + fOnLineCountChanged : TStringListLineCountEvent; {$ENDIF} function Get(Index: integer): string; override; function GetCapacity: integer; @@ -165,7 +145,6 @@ type procedure SetUpdateState(Updating: Boolean); override; {$IFDEF SYN_LAZARUS} procedure SetTextStr(const Value: string); override; - procedure MaybeUnfoldAboveNewLine(ANewLineIndex: Integer); {$ENDIF} public constructor Create; @@ -183,10 +162,6 @@ type procedure SaveToFile(const FileName: string); override; {$IFDEF SYN_LAZARUS} procedure ClearRanges(ARange: TSynEditRange); - procedure FoldLines(AStartIndex: Integer); - procedure UnFoldLines(AStartIndex: Integer); - procedure UnfoldAll; - procedure FixFolding(AStartIndex: Integer; AMinEndIndex: Integer = 0); {$ENDIF} public property DosFileFormat: boolean read fDosFileFormat write fDosFileFormat; @@ -205,15 +180,12 @@ type write fOnInserted; property OnPutted: TStringListIndexEvent read fOnPutted write fOnPutted; {$IFDEF SYN_LAZARUS} - property OnFoldChanged: TStringListIndexEvent read fOnFoldChanged - write fOnFoldChanged; - property Folded[Index: integer]: boolean read GetFolded write SetFolded; + property OnLineCountChanged: TStringListLineCountEvent + read fOnLineCountChanged write fOnLineCountChanged; property FoldMinLevel[Index: integer]: integer read GetFoldMinLevel write SetFoldMinLevel; property FoldEndLevel[Index: integer]: integer read GetFoldEndLevel write SetFoldEndLevel; - property FoldType[Index: integer]: TSynEditCodeFoldType read GetFoldType - write SetFoldType; {$ENDIF} end; @@ -543,7 +515,8 @@ begin Result := fCount; InsertItem(Result, S); {$IFDEF SYN_LAZARUS} - MaybeUnfoldAboveNewLine(Result); + if Assigned(fOnLineCountChanged) then + fOnLineCountChanged(Result, fCount - Result); {$ENDIF} if Assigned(fOnAdded) then fOnAdded(Result); @@ -572,16 +545,15 @@ begin fExpandedLength := -1; fFlags := [sfExpandedLengthUnknown]; {$IFDEF SYN_LAZARUS} - fFolded:=false; fFoldMinLevel:=0; fFoldEndLevel:=0; - fFoldType:=cfNone; {$ENDIF} end; Inc(fCount); end; {$IFDEF SYN_LAZARUS} - MaybeUnfoldAboveNewLine(FirstAdded); + if Assigned(fOnLineCountChanged) then + fOnLineCountChanged(FirstAdded, fCount - FirstAdded); {$ENDIF} if Assigned(fOnAdded) then fOnAdded(FirstAdded); @@ -625,6 +597,10 @@ begin (fCount - Index) * SynEditStringRecSize); end; fIndexOfLongestLine := -1; //mh 2000-10-19 + {$IFDEF SYN_LAZARUS} + if Assigned(fOnLineCountChanged) then + fOnLineCountChanged(Index, -1); + {$ENDIF} if Assigned(fOnDeleted) then fOnDeleted(Index); EndUpdate; @@ -664,7 +640,11 @@ begin end; end; Dec(fCount, NumLines); - if Assigned(fOnDeleted) then + {$IFDEF SYN_LAZARUS} + if Assigned(fOnLineCountChanged) then + fOnLineCountChanged(Index, -NumLines); + {$ENDIF} + if Assigned(fOnDeleted) then fOnDeleted(Index); end; end; @@ -758,23 +738,6 @@ begin Result := 0; end; -function TSynEditStringList.GetFoldType(Index: integer): TSynEditCodeFoldType; -begin - if (Index >= 0) and (Index < fCount) then - Result := fList^[Index].fFoldType - else - Result := cfNone; -end; - -function TSynEditStringList.GetFolded(Index: integer): boolean; -begin - if (Index >= 0) and (Index < fCount) then - Result := fList^[Index].fFolded - else - Result := false; - //if Result then debugln('TSynEditStringList.GetFolded Index=',dbgs(Index)); -end; - procedure TSynEditStringList.SetFoldEndLevel(Index: integer; const AValue: integer); begin @@ -788,20 +751,6 @@ begin if (Index >= 0) and (Index < fCount) then fList^[Index].fFoldMinLevel := AValue; end; - -procedure TSynEditStringList.SetFoldType(Index: integer; - const AValue: TSynEditCodeFoldType); -begin - if (Index >= 0) and (Index < fCount) then - fList^[Index].fFoldType := AValue; -end; - -procedure TSynEditStringList.SetFolded(Index: integer; const AValue: boolean); -begin - //if AValue then RaiseGDBException(''); - if (Index >= 0) and (Index < fCount) then - fList^[Index].fFolded := AValue; -end; {$ENDIF} function TSynEditStringList.Get(Index: integer): string; @@ -893,13 +842,21 @@ begin end; procedure TSynEditStringList.Insert(Index: integer; const S: string); +{$IFDEF SYN_LAZARUS} +var + OldCnt : integer; +{$ENDIF} begin if (Index < 0) or (Index > fCount) then ListIndexOutOfBounds(Index); BeginUpdate; + {$IFDEF SYN_LAZARUS} + OldCnt:=fCount; + {$ENDIF} InsertItem(Index, S); {$IFDEF SYN_LAZARUS} - MaybeUnfoldAboveNewLine(Index); + if Assigned(fOnLineCountChanged) then + fOnLineCountChanged(Index, fCount-OldCnt); {$ENDIF} if Assigned(fOnInserted) then fOnInserted(Index); @@ -925,10 +882,8 @@ begin fExpandedLength := -1; fFlags := [sfExpandedLengthUnknown]; {$IFDEF SYN_LAZARUS} - fFolded:=false; fFoldMinLevel:=0; fFoldEndLevel:=0; - fFoldType:=cfNone; {$ENDIF} {end} //mh 2000-10-19 end; @@ -953,7 +908,8 @@ begin FillChar(fList^[Index], NumLines * SynEditStringRecSize, 0); Inc(fCount, NumLines); {$IFDEF SYN_LAZARUS} - MaybeUnfoldAboveNewLine(Index); + if Assigned(fOnLineCountChanged) then + fOnLineCountChanged(Index, NumLines); {$ENDIF} if Assigned(fOnAdded) then fOnAdded(Index); @@ -1076,123 +1032,6 @@ begin fList^[Index].fRange := ARange; end; -procedure TSynEditStringList.FoldLines(AStartIndex : Integer); -begin - //debugln(['TSynEditStringList.FoldLines AstartIndex=', AStartIndex, ' FoldType=', ord(FoldType[AStartIndex]), ' Folded=',dbgs(Folded[AStartIndex]) ]); - if (AStartIndex < 0) or (AStartIndex >= Count) - or not (FoldType[AStartIndex] = cfExpanded) then exit; - - FoldType[AStartIndex] := cfCollapsed; - FixFolding(AStartIndex); - - if Assigned(fOnFoldChanged) then - fOnFoldChanged(AStartIndex); -end; - -procedure TSynEditStringList.UnFoldLines(AStartIndex : Integer); -var - i1, i2: LongInt; -begin - //debugln(['TSynEditStringList.UnFoldLines AstartIndex=', AStartIndex, ' FoldType(0=nil;1=col;2=exp)=', ord(FoldType[AStartIndex]), ' Folded=',dbgs(Folded[AStartIndex]) ]); - if (AStartIndex < 0) or (AStartIndex >= Count) - and ((FoldType[AStartIndex] = cfCollapsed) or Folded[AStartIndex]) - then exit; - - if Folded[AStartIndex] then begin - i2 := AStartIndex; - while Folded[AStartIndex] do begin - i1 := AStartIndex-1; - while (i1 >= 0) and Folded[i1] do dec(i1); - FoldType[i1] := cfExpanded; - FixFolding(i1); - if i1 < i2 then i2 := i1; - end; - - if Assigned(fOnFoldChanged) then - fOnFoldChanged(i2); - exit; - end; - - FoldType[AStartIndex] := cfExpanded; - FixFolding(AStartIndex); - if Assigned(fOnFoldChanged) then - fOnFoldChanged(AStartIndex); -end; - -procedure TSynEditStringList.UnfoldAll; -var - i: Integer; -begin - for i:=0 to Count-1 do begin - Folded[i]:=false; - if FoldType[i] = cfCollapsed then FoldType[i] := cfExpanded; - end; -end; - -procedure TSynEditStringList.FixFolding(AStartIndex: integer; AMinEndIndex : Integer = 0); -var - Level, CoLevel: LongInt; - cnt: Integer; -begin - cnt := Count; - if (AStartIndex < 0) or (AStartIndex >= cnt) then exit; - //debugln(['TSynEditStringList.FixFolding AStartIndex=', AStartIndex, ' AMinEndindex=', AMinEndIndex, ' FoldType(0=nil;1=col;2=exp)=', ord(FoldType[AStartIndex]), ' Folded=',dbgs(Folded[AStartIndex]) ]); - - // Always do the current line - if AMinEndIndex < AStartIndex then AMinEndIndex := AStartIndex; - // Always start from unfolded line - while (AStartIndex > 0) and Folded[AStartIndex] do - dec(AStartIndex); - // Remember current Level, fix must run at least to the end of current level - Level := FoldEndLevel[AStartIndex]; - - while (AStartIndex < cnt) - and ((FoldMinLevel[AStartIndex] >= Level) or (AStartIndex <= AMinEndIndex)) - do begin - if (FoldType[AStartIndex] = cfCollapsed) then begin - // begin of a new fold; keep first line visible - Folded[AStartIndex] := False; - CoLevel := FoldEndLevel[AStartIndex]; - inc(AStartIndex); - while (AStartIndex < cnt) - and (FoldMinLevel[AStartIndex] >= CoLevel) do begin - Folded[AStartIndex] := true; - inc(AStartIndex); - end; - // fold last line of block - if (AStartIndex < cnt) - and (FoldType[AStartIndex] = cfEnd) then begin - Folded[AStartIndex] := true; - inc(AStartIndex); - end; - end - else begin - //unfolded - Folded[AStartIndex] := false; - inc(AStartIndex); - end; - end; - - // last line of block, if cfEnd (otherwise the next block begins here) - if (AStartIndex < cnt) - and (FoldType[AStartIndex] = cfEnd) then - Folded[AStartIndex] := false; -end; - -procedure TSynEditStringList.MaybeUnfoldAboveNewLine(ANewLineIndex : Integer); -begin -exit; - if ANewLineIndex = 0 then exit; - if (FoldType[ANewLineIndex - 1] = cfCollapsed) - and not Folded[ANewLineIndex - 1] then begin - FoldType[ANewLineIndex - 1] := cfExpanded; - // ScanFrom will override this, unless we moved a "begin" from the previous line - FoldType[ANewLineIndex] := cfCollapsed; - if Assigned(fOnFoldChanged) then - fOnFoldChanged(ANewLineIndex - 1); - end; -end; - {$ENDIF} procedure TSynEditStringList.SetCapacity(NewCapacity: integer);