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