1{------------------------------------------------------------------------------- 2The contents of this file are subject to the Mozilla Public License 3Version 1.1 (the "License"); you may not use this file except in compliance 4with the License. You may obtain a copy of the License at 5http://www.mozilla.org/MPL/ 6 7Software distributed under the License is distributed on an "AS IS" basis, 8WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for 9the specific language governing rights and limitations under the License. 10 11Alternatively, the contents of this file may be used under the terms of the 12GNU General Public License Version 2 or later (the "GPL"), in which case 13the provisions of the GPL are applicable instead of those above. 14If you wish to allow use of your version of this file only under the terms 15of the GPL and not to allow others to use your version of this file 16under the MPL, indicate your decision by deleting the provisions above and 17replace them with the notice and other provisions required by the GPL. 18If you do not delete the provisions above, a recipient may use your version 19of this file under either the MPL or the GPL. 20 21-------------------------------------------------------------------------------} 22unit SynEditMarkupBracket; 23 24{$mode objfpc}{$H+} 25 26interface 27 28uses 29 Classes, SysUtils, Graphics, SynEditMarkup, SynEditMiscClasses, SynEditTypes, Controls, LCLProc; 30 31type 32 TSynEditBracketHighlightStyle = ( 33 sbhsLeftOfCursor, 34 sbhsRightOfCursor, 35 sbhsBoth 36 ); 37 { TSynEditMarkupBracket } 38 39 TSynEditMarkupBracket = class(TSynEditMarkup) 40 private 41 // Physical Position 42 FBracketHighlightPos: TPoint; 43 FBracketHighlightAntiPos: TPoint; 44 FHighlightStyle: TSynEditBracketHighlightStyle; 45 FNeedInvalidate: Boolean; 46 procedure SetHighlightStyle(const AValue: TSynEditBracketHighlightStyle); 47 protected 48 procedure FindMatchingBracketPair(LogCaret: TPoint; 49 var StartBracket, EndBracket: TPoint); 50 procedure DoCaretChanged(Sender: TObject); override; 51 procedure DoTopLineChanged(OldTopLine : Integer); override; 52 procedure DoLinesInWindoChanged(OldLinesInWindow : Integer); override; 53 procedure DoTextChanged(StartLine, EndLine, ACountDiff: Integer); override; 54 procedure DoMarkupChanged(AMarkup: TSynSelectedColor); override; 55 procedure DoEnabledChanged(Sender: TObject); override; 56 procedure DoVisibleChanged(AVisible: Boolean); override; 57 public 58 constructor Create(ASynEdit: TSynEditBase); 59 procedure DecPaintLock; override; 60 61 function GetMarkupAttributeAtRowCol(const aRow: Integer; 62 const aStartCol: TLazSynDisplayTokenBound; 63 const AnRtlInfo: TLazSynDisplayRtlInfo): TSynSelectedColor; override; 64 procedure GetNextMarkupColAfterRowCol(const aRow: Integer; 65 const aStartCol: TLazSynDisplayTokenBound; 66 const AnRtlInfo: TLazSynDisplayRtlInfo; 67 out ANextPhys, ANextLog: Integer); override; 68 69 procedure InvalidateBracketHighlight; 70 property HighlightStyle: TSynEditBracketHighlightStyle read FHighlightStyle write SetHighlightStyle; 71 end; 72 73implementation 74 75{ TSynEditMarkupBracket } 76 77constructor TSynEditMarkupBracket.Create(ASynEdit : TSynEditBase); 78begin 79 inherited Create(ASynEdit); 80 FBracketHighlightPos.Y := -1; 81 FBracketHighlightAntiPos.Y := -1; 82 FHighlightStyle := sbhsBoth; 83 MarkupInfo.Foreground := clNone; 84 MarkupInfo.Background := clNone; 85 MarkupInfo.Style := [fsBold]; 86 MarkupInfo.StyleMask := []; 87end; 88 89procedure TSynEditMarkupBracket.DecPaintLock; 90begin 91 inherited DecPaintLock; 92 if (FPaintLock = 0) and FNeedInvalidate then 93 InvalidateBracketHighlight; 94end; 95 96procedure TSynEditMarkupBracket.SetHighlightStyle( 97 const AValue: TSynEditBracketHighlightStyle); 98begin 99 if FHighlightStyle <> AValue then 100 begin 101 FHighlightStyle := AValue; 102 InvalidateBracketHighlight; 103 end; 104end; 105 106procedure TSynEditMarkupBracket.FindMatchingBracketPair(LogCaret: TPoint; var StartBracket, 107 EndBracket: TPoint); 108const 109 Brackets: set of Char = ['(',')','{','}','[',']', '''', '"' ]; 110var 111 StartLine: string; 112 x: Integer; 113begin 114 StartBracket.Y := -1; 115 EndBracket.Y := -1; 116 if (LogCaret.Y < 1) or (LogCaret.Y > Lines.Count) or (LogCaret.X < 1) then 117 Exit; 118 119 StartLine := Lines[LogCaret.Y - 1]; 120 121 // check for bracket, left of cursor 122 if (HighlightStyle in [sbhsLeftOfCursor, sbhsBoth]) and (LogCaret.x > 1) then 123 begin 124 x := Lines.LogicPosAddChars(StartLine, LogCaret.x, -1); 125 if (x <= length(StartLine)) and (StartLine[x] in Brackets) then 126 begin 127 StartBracket := LogCaret; 128 StartBracket.x := x; 129 EndBracket := SynEdit.FindMatchingBracketLogical(StartBracket, False, False, False, False); 130 if EndBracket.y < 0 then 131 StartBracket.y := -1; 132 Exit; 133 end; 134 end; 135 136 // check for bracket after caret 137 if (HighlightStyle in [sbhsRightOfCursor, sbhsBoth]) then 138 begin 139 x := LogCaret.x ; 140 if (x <= length(StartLine)) and (StartLine[x] in Brackets) then 141 begin 142 StartBracket := LogCaret; 143 EndBracket := SynEdit.FindMatchingBracketLogical(LogCaret, False, False, False, False); 144 if EndBracket.y < 0 then 145 StartBracket.y := -1; 146 end; 147 end; 148end; 149 150procedure TSynEditMarkupBracket.DoCaretChanged(Sender: TObject); 151begin 152 InvalidateBracketHighlight; 153end; 154 155procedure TSynEditMarkupBracket.DoTopLineChanged(OldTopLine: Integer); 156begin 157 InvalidateBracketHighlight; 158end; 159 160procedure TSynEditMarkupBracket.DoLinesInWindoChanged(OldLinesInWindow: Integer); 161begin 162 InvalidateBracketHighlight; 163end; 164 165procedure TSynEditMarkupBracket.DoTextChanged(StartLine, EndLine, 166 ACountDiff: Integer); 167begin 168 InvalidateBracketHighlight; 169end; 170 171procedure TSynEditMarkupBracket.DoMarkupChanged(AMarkup: TSynSelectedColor); 172begin 173 InvalidateBracketHighlight; 174end; 175 176procedure TSynEditMarkupBracket.DoEnabledChanged(Sender: TObject); 177begin 178 InvalidateBracketHighlight; 179end; 180 181procedure TSynEditMarkupBracket.DoVisibleChanged(AVisible: Boolean); 182begin 183 inherited DoVisibleChanged(AVisible); 184 if SynEdit.IsVisible then 185 InvalidateBracketHighlight; 186end; 187 188procedure TSynEditMarkupBracket.InvalidateBracketHighlight; 189var 190 NewPos, NewAntiPos, SwapPos : TPoint; 191begin 192 FNeedInvalidate := True; 193 if (Caret = nil) or (not SynEdit.HandleAllocated) or (FPaintLock > 0) or 194 (not SynEdit.IsVisible) 195 then 196 exit; 197 198 FNeedInvalidate := False; 199 NewPos.Y:=-1; 200 NewAntiPos.Y:=-1; 201 if eoBracketHighlight in SynEdit.Options 202 then FindMatchingBracketPair(Caret.LineBytePos, NewPos, NewAntiPos); 203 204 // Always keep ordered 205 if (NewAntiPos.Y > 0) 206 and ((NewAntiPos.Y < NewPos.Y) or ((NewAntiPos.Y = NewPos.Y) and (NewAntiPos.X < NewPos.X))) 207 then begin 208 SwapPos := NewAntiPos; 209 NewAntiPos := NewPos; 210 NewPos := SwapPos; 211 end; 212 213 // invalidate old bracket highlighting, if changed 214 if (FBracketHighlightPos.Y > 0) 215 and ((FBracketHighlightPos.Y <> NewPos.Y) or (FBracketHighlightPos.X <> NewPos.X)) 216 then begin 217 //DebugLn('TCustomSynEdit.InvalidateBracketHighlight A Y=',dbgs(FBracketHighlightPos)); 218 InvalidateSynLines(FBracketHighlightPos.Y,FBracketHighlightPos.Y); 219 end; 220 221 if (FBracketHighlightAntiPos.Y > 0) 222 and (FBracketHighlightPos.Y <> FBracketHighlightAntiPos.Y) 223 and ((FBracketHighlightAntiPos.Y <> NewAntiPos.Y) or (FBracketHighlightAntiPos.X <> NewAntiPos.X)) 224 then 225 InvalidateSynLines(FBracketHighlightAntiPos.Y,FBracketHighlightAntiPos.Y); 226 227 // invalidate new bracket highlighting, if changed 228 if NewPos.Y>0 then begin 229 //DebugLn('TCustomSynEdit.InvalidateBracketHighlight C Y=',dbgs(NewPos.Y),' X=',dbgs(NewPos.X),' Y=',dbgs(NewAntiPos.Y),' X=',dbgs(NewAntiPos.X)); 230 if ((FBracketHighlightPos.Y <> NewPos.Y) or (FBracketHighlightPos.X <> NewPos.X)) 231 then InvalidateSynLines(NewPos.Y, NewPos.Y); 232 233 if ((NewPos.Y <> NewAntiPos.Y) 234 or ((FBracketHighlightPos.Y = NewPos.Y) and (FBracketHighlightPos.X = NewPos.X)) 235 ) 236 and ((FBracketHighlightAntiPos.Y <> NewAntiPos.Y) or (FBracketHighlightAntiPos.X <> NewAntiPos.X)) 237 then InvalidateSynLines(NewAntiPos.Y, NewAntiPos.Y); 238 end; 239 FBracketHighlightPos := NewPos; 240 FBracketHighlightAntiPos := NewAntiPos; 241// DebugLn('TCustomSynEdit.InvalidateBracketHighlight C P=',dbgs(NewPos),' A=',dbgs(NewAntiPos), ' LP=',dbgs(fLogicalPos),' LA',dbgs(fLogicalAntiPos)); 242end; 243 244function TSynEditMarkupBracket.GetMarkupAttributeAtRowCol(const aRow: Integer; 245 const aStartCol: TLazSynDisplayTokenBound; const AnRtlInfo: TLazSynDisplayRtlInfo): TSynSelectedColor; 246begin 247 Result := nil; 248 if ((FBracketHighlightPos.y = aRow) and (FBracketHighlightPos.x = aStartCol.Logical)) 249 or ((FBracketHighlightAntiPos.y = aRow) and (FBracketHighlightAntiPos.x = aStartCol.Logical)) 250 then begin 251 Result := MarkupInfo; 252 MarkupInfo.SetFrameBoundsLog(aStartCol.Logical, aStartCol.Logical + 1); // bracket is alvays 1 byte 253 end; 254end; 255 256procedure TSynEditMarkupBracket.GetNextMarkupColAfterRowCol(const aRow: Integer; 257 const aStartCol: TLazSynDisplayTokenBound; const AnRtlInfo: TLazSynDisplayRtlInfo; out ANextPhys, 258 ANextLog: Integer); 259begin 260 ANextLog := -1; 261 ANextPhys := -1; 262 if (FBracketHighlightPos.y = aRow) then begin 263 if (FBracketHighlightPos.x > aStartCol.Logical ) 264 then ANextLog := FBracketHighlightPos.x 265 else if (FBracketHighlightPos.x + 1 > aStartCol.Logical ) 266 then ANextLog := FBracketHighlightPos.x + 1; // end of bracket 267 end; 268 if (FBracketHighlightAntiPos.y = aRow) then begin 269 if (FBracketHighlightAntiPos.x > aStartCol.Logical ) 270 and ((FBracketHighlightAntiPos.x < ANextLog) or (ANextLog < 0)) 271 then ANextLog := FBracketHighlightAntiPos.x 272 else if (FBracketHighlightAntiPos.x + 1 > aStartCol.Logical ) 273 and ((FBracketHighlightAntiPos.x + 1 < ANextLog) or (ANextLog < 0)) 274 then ANextLog := FBracketHighlightAntiPos.x + 1; 275 end 276end; 277 278end. 279 280