View Issue Details

IDProjectCategoryView StatusLast Update
0038370LazarusPackagespublic2021-01-28 20:35
ReporterDomingo Galmés Assigned ToMartin Friebe  
Status closedResolutionfixed 
Fixed in Version2.2 
Summary0038370: [Patch] TSynEdit Change request in TSynGutterLineNumber component. Add event OnGetText.
DescriptionThis patch adds an event to customize the line number text in the TSynedit gutter line number.

The motivation for the change is to be used in a utility to compare files where the line numbers of the editor do not match the line numbers of the original file (there may be lines added or deleted).

This change allows the user of the TSynEdit component full control over the text of the editor line number part of the gutter.
Besides adding the OnGetText event I have changed the FormatLineNumber function from private to public to be able to use it in the event routine.

Example of using the proposed changes.

procedure TForm1.SynGutterLineNumber1GetText (Sender: TSynGutterLineNumber;
   ALine: integer; var AText: string; AIsDot: boolean);
   if aLine <= 3 then
     AText: = '-'
     AText: = Sender.FormatLineNumber (ALine - 3, AIsDot);

I hope you can apply these changes.
Steps To ReproduceThe contains a sample project to test the changes.

TagsNo tags attached.
Fixed in Revision64426
Attached Files


Domingo Galmés

2021-01-18 21:30


AddOnGetTextGutterLineText.patch (2,721 bytes)   
From ab49c6df394351fd014af0a0257f96ced2b64acc Mon Sep 17 00:00:00 2001
From: DomingoGP <>
Date: Mon, 18 Jan 2021 20:55:43 +0100
Subject: [PATCH] Add event OnGetText to TSynGutterLineNumber.

 components/synedit/syngutterlinenumber.pp | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/components/synedit/syngutterlinenumber.pp b/components/synedit/syngutterlinenumber.pp
index a9270c5378..6f53eae7da 100644
--- a/components/synedit/syngutterlinenumber.pp
+++ b/components/synedit/syngutterlinenumber.pp
@@ -11,6 +11,9 @@ uses
+  TSynGutterLineNumber = class;
+  TSynEditGetGutterLineTextEvent = procedure(Sender: TSynGutterLineNumber; ALine: integer; var AText: string; AIsDot: boolean) of object;
   TSynEditMouseActionsLineNum = class(TSynEditMouseInternalActions)
   public  // empty by default
@@ -26,12 +29,12 @@ type
     FShowOnlyLineNumbersMultiplesOf: integer;
     FLeadingZeros: boolean;
     FZeroStart: boolean;
+    FOnGetText: TSynEditGetGutterLineTextEvent;
     procedure SetDigitCount(AValue : integer);
     procedure SetLeadingZeros(const AValue : boolean);
     procedure SetShowOnlyLineNumbersMultiplesOf(const AValue : integer);
     procedure SetZeroStart(const AValue : boolean);
-    function FormatLineNumber(Line: integer; IsDot: boolean): string;
     procedure Init; override;
     function  PreferedWidthAtCurrentPPI: Integer; override;
@@ -45,7 +48,7 @@ type
     constructor Create(AOwner: TComponent); override;
     destructor Destroy; override;
     procedure Assign(Source: TPersistent); override;
+    function FormatLineNumber(Line: integer; IsDot: boolean): string;
     procedure Paint(Canvas: TCanvas; AClip: TRect; FirstLine, LastLine: integer);
@@ -55,6 +58,7 @@ type
              read FShowOnlyLineNumbersMultiplesOf write SetShowOnlyLineNumbersMultiplesOf;
     property ZeroStart: boolean read FZeroStart write SetZeroStart;
     property LeadingZeros: boolean read FLeadingZeros write SetLeadingZeros;
+    property OnGetText: TSynEditGetGutterLineTextEvent read FOnGetText write FOnGetText;
@@ -275,7 +279,13 @@ begin
           and (iLine <> TCustomSynEdit(SynEdit).CaretY) and (iLine <> 1)
           and (iLine <> SynEdit.Lines.Count);
       // Get the formatted line number or dot
-      s := FormatLineNumber(iLine, ShowDot);
+      if Assigned(FOnGetText) then
+      begin
+        s:='';
+        FOnGetText(Self,iLine,s,ShowDot);
+      end
+      else
+        s := FormatLineNumber(iLine, ShowDot);
       Inc(rcLine.Bottom, LineHeight);
       if i <> IRange.Top then
         s := '';
-- (109,597 bytes)
project1_image.png (20,852 bytes)   
project1_image.png (20,852 bytes)   

Martin Friebe

2021-01-19 01:48

manager   ~0128403

Generally ok.
Couple of notes / thoughts (I can change name/var-out when I apply the patch):

You may comment on those, if you wish. Otherwise I will adjust and apply. Hopefully in a few days / a week or so.

- OnGetText should be called OnFormatLineNumber
- AText should be an out param, not var) / that also remove "s:='';"

Reason for the name:
It should be clear that this is still a line number (and the same line number). And not arbitrary text.
Though of course nothing prevents to display arbitrary text, but doing so would count as abuse. For example, "line number hints" (eoShowScrollHint) when scrolling are not affected by this. And they will not ever be.

For general info / not about the patch:
Anything that goes beyond *formatting* for the text for the gutter would need to be outside the gutter. SynEdit has a concept of "views" which can (or will in future be able) to map and transform the content of the editor. However they are not user friendly for quick modification like this. So in this case adding an Event is ok.

Under those terms: Applying the patch would allow you to do what you need to do. It would however not be within the intended use of the event. (Since you actually map numbers, rather than formatting them).
Technically what you do, would require a custom written TSynEditStrings with a TLazSynDisplayView => this can map line numbers.
Though, this would be total overkill for what you want to do.

Further more:
The result of formatting the line number does not affect the Gutters width. And I do not think it should. (That will be documented via comment in the code).
You could at any time set the width yourself. You do need to disable autosize.

If wanted, you could add an OnGutterWidthNeeded. Since the gutter believes width is related to the number of digits, this will be called when lines are added or removed (probably always, not just when the magnitude (digit count) changes).
This doesn't provide much functionality, since it is easy to set the value from other code. But if you want it, you can add it to the patch (that I will not add myself).
// TSynGutterLineNumber.LineCountChanged

Domingo Galmés

2021-01-19 16:11

reporter   ~0128423

The proposed changes seem correct to me.

I don't will need the OnGutterWidthNeeded since i know the number of digits so i can disable autosize and set the width manually when filling the editor lines so I will not add them to the patch.

Martin Friebe

2021-01-27 14:45

manager   ~0128622

Applied, with a few more changes.

I made the last param to TSynEditGetGutterLineTextEvent a record.

- In addition to "ShowDot", I wanted to pass the LineRange info and ViewedLine too (more below)
- passing a record is forward compatible.
   That is, if one day more params need to be passed, they can be added to the record, without breaking any existing code.
   Adding them to the function directly would break its signature, and fail events that are set from lfm files (the compiler would not give any warning)

About LineRange / ViewedLine.
I recently added the most basic line wrapping to SynEdit (still needs a lot of polishing).
Then a single line in the text can occupy several lines on the screen.

Before going into details, this introduces new coordinate system in SynEdit.
There always was Logical/Physical:

Now there also is "ViewedPos" (I have to check if the X is logical or physical [[physical is now a misnomer, it refers to the screen (screen=physical), but this is only for X, not for Y]])
The Y is mapped according to FOLDING and WRAPPING.
- I.e if you are in the 3rd line of text (text as it is in memory, in SynEdit.Lines), but the first line is folded and not visible, then on the screen this is the 2nd line.
- If you are in the 3rd line of text, but line are wrapped then you may be on the 4th or 5th line on the screen.

The params to the callback
- ALine => the phys/log line (line as in SynEdit.Lines)
- LineInfo.ViewLine => The viewed line
- LineInfo.LineRange => The first and last viewed line belonging to "ALine"

LineInfo.ViewLine = LineInfo.LineRange.Top
=> This is the first line (-part) of ALine.
   Otherwise ALine is wrapped and this is a continuation line.

You only need this, if you add the wrap plugin.
SynEdit will never wrap on its own, you have to add the plugin


Domingo Galmés

2021-01-28 20:35

reporter   ~0128645

Thank you for applying the patch and for the extensive information provided.
Tested and works as expected.

Issue History

Date Modified Username Field Change
2021-01-18 21:30 Domingo Galmés New Issue
2021-01-18 21:30 Domingo Galmés File Added: AddOnGetTextGutterLineText.patch
2021-01-18 21:30 Domingo Galmés File Added:
2021-01-18 21:30 Domingo Galmés File Added: project1_image.png
2021-01-19 01:02 Martin Friebe Assigned To => Martin Friebe
2021-01-19 01:02 Martin Friebe Status new => assigned
2021-01-19 01:48 Martin Friebe Note Added: 0128403
2021-01-19 16:11 Domingo Galmés Note Added: 0128423
2021-01-27 14:45 Martin Friebe Status assigned => resolved
2021-01-27 14:45 Martin Friebe Resolution open => fixed
2021-01-27 14:45 Martin Friebe Fixed in Version => 2.2
2021-01-27 14:45 Martin Friebe Fixed in Revision => 64426
2021-01-27 14:45 Martin Friebe LazTarget => 2.2
2021-01-27 14:45 Martin Friebe Note Added: 0128622
2021-01-28 20:35 Domingo Galmés Status resolved => closed
2021-01-28 20:35 Domingo Galmés Note Added: 0128645